diff --git a/.gitignore b/.gitignore index 9a8d54baf..1f1130e7f 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ build tests_output selenium-debug.log TODOs.md +.vscode diff --git a/package.json b/package.json index 60a2fb93a..529c9c43d 100644 --- a/package.json +++ b/package.json @@ -53,6 +53,7 @@ "lodash.debounce": "^4.0.6", "lodash.groupby": "^4.6.0", "vue": "^2.0.0", + "vue-router": "^2.3.0", "vuex": "^2.0.0" } } diff --git a/shells/chrome/webpack.config.js b/shells/chrome/webpack.config.js index 8b1f24c25..c1863f2b1 100644 --- a/shells/chrome/webpack.config.js +++ b/shells/chrome/webpack.config.js @@ -18,8 +18,8 @@ module.exports = { detector: './src/detector.js' }, output: { - path: __dirname + '/build', - filename: '[name].js', + path: path.join(__dirname, '/build'), + filename: '[name].js' }, resolve: { alias @@ -28,7 +28,7 @@ module.exports = { rules: [ { test: /\.js$/, - loader: 'buble-loader', + loader: 'buble-loader', exclude: /node_modules|vue\/dist|vuex\/dist/, options: bubleOptions }, diff --git a/shells/dev/target/index.js b/shells/dev/target/index.js index 7d6197cec..bf9ba0343 100644 --- a/shells/dev/target/index.js +++ b/shells/dev/target/index.js @@ -6,22 +6,72 @@ import Counter from './Counter.vue' import Events from './Events.vue' import MyClass from './MyClass.js' -let items = [] +import IndexRoute from './router/IndexRoute.vue' +import RouteOne from './router/RouteOne.vue' +import RouteTwo from './router/RouteTwo.vue' +import RouteWithParams from './router/RouteWithParams.vue' +import NamedRoute from './router/NamedRoute.vue' +import RouteWithQuery from './router/RouteWithQuery.vue' +import RouteWithBeforeEnter from './router/RouteWithBeforeEnter.vue' +import RouteWithAlias from './router/RouteWithAlias.vue' +import RouteWithProps from './router/RouteWithProps.vue' +import ParentRoute from './router/ParentRoute.vue' +import ChildRoute from './router/ChildRoute.vue' + +import VueRouter from 'vue-router' + +Vue.use(VueRouter) + +const DynamicComponent = { + template: '
Hello from dynamic component
' +} + +const routes = [ + { path: '/route-one', component: RouteOne }, + { path: '/route-two', component: RouteTwo }, + { path: '/route-with-params/:username/:id', component: RouteWithParams }, + { path: '/route-named', component: NamedRoute, name: 'NamedRoute' }, + { path: '/route-with-query', component: RouteWithQuery }, + { path: '/route-with-before-enter', component: RouteWithBeforeEnter, beforeEnter: (to, from, next) => { + next() + }}, + { path: '/route-with-redirect', redirect: '/route-one' }, + { path: '/route-with-alias', component: RouteWithAlias, alias: '/this-is-the-alias' }, + { path: '/route-with-dynamic-component', component: DynamicComponent, props: true }, + { path: '/route-with-props', component: RouteWithProps, props: { + username: 'My Username', + id: 99 + }}, + { path: '/route-with-props-default', component: RouteWithProps }, + { path: '/route-parent', component: ParentRoute, + children: [ + { path: '/route-child', component: ChildRoute } + ] + } +] + +const router = new VueRouter({ + routes +}) + +const items = [] for (var i = 0; i < 100; i++) { items.push({ id: i }) } -let circular = {} +const circular = {} circular.self = circular new Vue({ store, + router, render (h) { return h('div', null, [ h(Counter), - h(Target, {props:{msg: 'hi', ins: new MyClass()}}), + h(Target, {props: {msg: 'hi', ins: new MyClass()}}), h(Other), - h(Events) + h(Events), + h(IndexRoute) ]) }, data: { diff --git a/shells/dev/target/router/ChildRoute.vue b/shells/dev/target/router/ChildRoute.vue new file mode 100644 index 000000000..3337f2ee4 --- /dev/null +++ b/shells/dev/target/router/ChildRoute.vue @@ -0,0 +1,9 @@ + + + diff --git a/shells/dev/target/router/IndexRoute.vue b/shells/dev/target/router/IndexRoute.vue new file mode 100644 index 000000000..926e63a59 --- /dev/null +++ b/shells/dev/target/router/IndexRoute.vue @@ -0,0 +1,33 @@ + + + diff --git a/shells/dev/target/router/NamedRoute.vue b/shells/dev/target/router/NamedRoute.vue new file mode 100644 index 000000000..e68fd580b --- /dev/null +++ b/shells/dev/target/router/NamedRoute.vue @@ -0,0 +1,10 @@ + + + \ No newline at end of file diff --git a/shells/dev/target/router/ParentRoute.vue b/shells/dev/target/router/ParentRoute.vue new file mode 100644 index 000000000..27138e6d6 --- /dev/null +++ b/shells/dev/target/router/ParentRoute.vue @@ -0,0 +1,12 @@ + + + diff --git a/shells/dev/target/router/RouteOne.vue b/shells/dev/target/router/RouteOne.vue new file mode 100644 index 000000000..b131fd159 --- /dev/null +++ b/shells/dev/target/router/RouteOne.vue @@ -0,0 +1,11 @@ + + + diff --git a/shells/dev/target/router/RouteTwo.vue b/shells/dev/target/router/RouteTwo.vue new file mode 100644 index 000000000..ffa59b0ad --- /dev/null +++ b/shells/dev/target/router/RouteTwo.vue @@ -0,0 +1,11 @@ + + + \ No newline at end of file diff --git a/shells/dev/target/router/RouteWithAlias.vue b/shells/dev/target/router/RouteWithAlias.vue new file mode 100644 index 000000000..0a67a2ec8 --- /dev/null +++ b/shells/dev/target/router/RouteWithAlias.vue @@ -0,0 +1,10 @@ + + + \ No newline at end of file diff --git a/shells/dev/target/router/RouteWithBeforeEnter.vue b/shells/dev/target/router/RouteWithBeforeEnter.vue new file mode 100644 index 000000000..479291b0a --- /dev/null +++ b/shells/dev/target/router/RouteWithBeforeEnter.vue @@ -0,0 +1,10 @@ + + + \ No newline at end of file diff --git a/shells/dev/target/router/RouteWithParams.vue b/shells/dev/target/router/RouteWithParams.vue new file mode 100644 index 000000000..0b8db1849 --- /dev/null +++ b/shells/dev/target/router/RouteWithParams.vue @@ -0,0 +1,11 @@ + + + \ No newline at end of file diff --git a/shells/dev/target/router/RouteWithProps.vue b/shells/dev/target/router/RouteWithProps.vue new file mode 100644 index 000000000..34e7af10a --- /dev/null +++ b/shells/dev/target/router/RouteWithProps.vue @@ -0,0 +1,20 @@ + + + \ No newline at end of file diff --git a/shells/dev/target/router/RouteWithQuery.vue b/shells/dev/target/router/RouteWithQuery.vue new file mode 100644 index 000000000..759da6f26 --- /dev/null +++ b/shells/dev/target/router/RouteWithQuery.vue @@ -0,0 +1,10 @@ + + + \ No newline at end of file diff --git a/src/backend/index.js b/src/backend/index.js index 61244c225..4a15dde4d 100644 --- a/src/backend/index.js +++ b/src/backend/index.js @@ -4,6 +4,7 @@ import { highlight, unHighlight, getInstanceRect } from './highlighter' import { initVuexBackend } from './vuex' import { initEventsBackend } from './events' +import { initRouterBackend } from './router' import { stringify, classify, camelize } from '../util' import path from 'path' @@ -95,6 +96,9 @@ function connect () { bridge.send('ready', hook.Vue.version) console.log('[vue-devtools] Ready. Detected Vue v' + hook.Vue.version) scan() + + // router + initRouterBackend(hook.Vue, bridge, rootInstances) } /** diff --git a/src/backend/router.js b/src/backend/router.js new file mode 100644 index 000000000..ff4c66130 --- /dev/null +++ b/src/backend/router.js @@ -0,0 +1,51 @@ +import { stringify } from '../util' + +export function initRouterBackend (Vue, bridge, rootInstances) { + let recording = true + + const getSnapshot = () => { + const routeChanges = [] + rootInstances.forEach(instance => { + const router = instance._router + if (router && router.options && router.options.routes) { + routeChanges.push(...router.options.routes) + } + }) + return stringify({ + routeChanges + }) + } + + bridge.send('routes:init', getSnapshot()) + + bridge.on('router:toggle-recording', enabled => { + recording = enabled + }) + + rootInstances.forEach(instance => { + const router = instance._router + if (router) { + router.afterEach((to, from) => { + if (!recording) return + bridge.send('router:changed', stringify({ + to, + from, + timestamp: Date.now() + })) + }) + bridge.send('router:init', stringify({ + mode: router.mode + })) + + if (router.matcher && router.matcher.addRoutes) { + const addRoutes = router.matcher.addRoutes + router.matcher.addRoutes = function (routes) { + routes.forEach((item) => { + bridge.send('routes:changed', stringify(item)) + }) + addRoutes.call(this, routes) + } + } + } + }) +} diff --git a/src/devtools/App.vue b/src/devtools/App.vue index 285bc401a..618ea7954 100644 --- a/src/devtools/App.vue +++ b/src/devtools/App.vue @@ -31,6 +31,20 @@ Events {{ newEventCount }} + + directions + Router + + + book + Routes + @@ -47,6 +61,8 @@ import ComponentsTab from './views/components/ComponentsTab.vue' import EventsTab from './views/events/EventsTab.vue' import VuexTab from './views/vuex/VuexTab.vue' +import RouterTab from './views/router/RouterTab.vue' +import RoutesTab from './views/routes/RoutesTab.vue' import { mapState } from 'vuex' @@ -62,7 +78,9 @@ export default { components: { components: ComponentsTab, vuex: VuexTab, - events: EventsTab + events: EventsTab, + router: RouterTab, + routes: RoutesTab }, computed: mapState({ message: state => state.message, diff --git a/src/devtools/common.styl b/src/devtools/common.styl index d9b54f50c..9f2530569 100644 --- a/src/devtools/common.styl +++ b/src/devtools/common.styl @@ -22,3 +22,47 @@ $dark-active-color = $active-color $dark-border-color = lighten($slate, 10%) $dark-background-color = $slate $dark-component-color = $active-color + +// Entries + +.no-entries + color: #ccc + text-align: center + margin-top: 50px + line-height: 30px + +.entry + position: relative; + font-family Menlo, Consolas, monospace + color #881391 + cursor pointer + padding 10px 20px + font-size 12px + background-color $background-color + box-shadow 0 1px 5px rgba(0,0,0,.12) + .entry-name + font-weight 600 + .entry-source + color #999 + .component-name + color $component-color + .entry-type + color #999 + margin-left 8px + &.active + color #fff + background-color $active-color + .time, .entry-type, .component-name + color lighten($active-color, 75%) + .entry-name + color: #fff + .entry-source + color #ddd + .app.dark & + background-color $dark-background-color + +.time + font-size 11px + color #999 + float right + margin-top 3px diff --git a/src/devtools/components/SplitPane.vue b/src/devtools/components/SplitPane.vue index 5786fc1b5..d41cd3277 100644 --- a/src/devtools/components/SplitPane.vue +++ b/src/devtools/components/SplitPane.vue @@ -45,27 +45,5 @@ export default { diff --git a/src/devtools/components/TriplePane.vue b/src/devtools/components/TriplePane.vue new file mode 100644 index 000000000..12b52fb80 --- /dev/null +++ b/src/devtools/components/TriplePane.vue @@ -0,0 +1,78 @@ + + + + + diff --git a/src/devtools/components/splitPanes.styl b/src/devtools/components/splitPanes.styl new file mode 100644 index 000000000..2a5ba77c5 --- /dev/null +++ b/src/devtools/components/splitPanes.styl @@ -0,0 +1,22 @@ +.split-pane + display flex + height 100% + &.dragging + cursor ew-resize + +.left, .right, .middle + position relative + +.left, .middle + border-right 1px solid $border-color + .app.dark & + border-right 1px solid $dark-border-color + +.dragger + position absolute + z-index 99 + top 0 + bottom 0 + right -5px + width 10px + cursor ew-resize \ No newline at end of file diff --git a/src/devtools/index.js b/src/devtools/index.js index 0576bd2a7..4996f643f 100644 --- a/src/devtools/index.js +++ b/src/devtools/index.js @@ -94,6 +94,27 @@ function initApp (shell) { } }) + bridge.on('router:init', payload => { + store.commit('router/INIT', parse(payload)) + }) + + bridge.on('router:changed', payload => { + store.commit('router/CHANGED', parse(payload)) + }) + + bridge.on('routes:init', payload => { + store.commit('routes/INIT', parse(payload)) + }) + + bridge.on('routes:changed', payload => { + store.commit('routes/CHANGED', parse(payload)) + }) + + // register filters + Vue.filter('formatTime', function (timestamp) { + return (new Date(timestamp)).toString().match(/\d\d:\d\d:\d\d/)[0] + }) + app = new Vue({ store, render (h) { diff --git a/src/devtools/store/index.js b/src/devtools/store/index.js index c14ebb1bd..f1c150da8 100644 --- a/src/devtools/store/index.js +++ b/src/devtools/store/index.js @@ -3,6 +3,8 @@ import Vuex from 'vuex' import components from 'views/components/module' import vuex from 'views/vuex/module' import events from 'views/events/module' +import router from 'views/router/module' +import routes from 'views/routes/module' Vue.use(Vuex) @@ -25,7 +27,9 @@ const store = new Vuex.Store({ modules: { components, vuex, - events + events, + router, + routes } }) @@ -35,14 +39,18 @@ if (module.hot) { module.hot.accept([ 'views/components/module', 'views/vuex/module', - 'views/events/module' + 'views/events/module', + 'views/router/module', + 'views/routes/module' ], () => { try { store.hotUpdate({ modules: { components: require('views/components/module').default, vuex: require('views/vuex/module').default, - events: require('views/events/module').default + events: require('views/events/module').default, + router: require('views/router/module').default, + routes: require('views/routes/module').default } }) } catch (e) { diff --git a/src/devtools/views/events/EventsHistory.vue b/src/devtools/views/events/EventsHistory.vue index 2792ca486..fee6c077d 100644 --- a/src/devtools/views/events/EventsHistory.vue +++ b/src/devtools/views/events/EventsHistory.vue @@ -15,7 +15,7 @@
-
+
No events found
(Recording is paused)
- {{ event.eventName }} - {{ event.type }} - + {{ event.eventName }} + {{ event.type }} + by < {{ event.instanceName }} @@ -70,18 +70,12 @@ export default { inspect: 'INSPECT', reset: 'RESET', toggleRecording: 'TOGGLE' - }), - filters: { - formatTime (timestamp) { - return (new Date(timestamp)).toString().match(/\d\d:\d\d:\d\d/)[0] - } - } + }) } diff --git a/src/devtools/views/router/RouterMeta.vue b/src/devtools/views/router/RouterMeta.vue new file mode 100644 index 000000000..97f670459 --- /dev/null +++ b/src/devtools/views/router/RouterMeta.vue @@ -0,0 +1,93 @@ + + + + + diff --git a/src/devtools/views/router/RouterTab.vue b/src/devtools/views/router/RouterTab.vue new file mode 100644 index 000000000..6e5f71ac7 --- /dev/null +++ b/src/devtools/views/router/RouterTab.vue @@ -0,0 +1,31 @@ + + + diff --git a/src/devtools/views/router/module.js b/src/devtools/views/router/module.js new file mode 100644 index 000000000..6343c8e26 --- /dev/null +++ b/src/devtools/views/router/module.js @@ -0,0 +1,61 @@ +import storage from '../../storage' + +const ENABLED_KEY = 'EVENTS_ENABLED' +const enabled = storage.get(ENABLED_KEY) + +const state = { + enabled: enabled == null ? true : enabled, + hasRouter: false, + instances: [], + routeChanges: [], + inspectedIndex: -1, + filter: '' +} + +const mutations = { + 'INIT' (state, payload) { + state.instances = [] + state.routeChanges = [] + state.inspectedIndex = -1 + state.hasRouter = true + state.instances.push(payload) + }, + 'RESET' (state) { + state.routeChanges = [] + state.inspectedIndex = -1 + }, + 'CHANGED' (state, payload) { + state.routeChanges.push(payload) + if (!state.filter) { + state.inspectedIndex = state.routeChanges.length - 1 + } + }, + 'INSPECT' (state, index) { + state.inspectedIndex = index + }, + 'UPDATE_FILTER' (state, filter) { + state.filter = filter + }, + 'TOGGLE' (state) { + storage.set(ENABLED_KEY, state.enabled = !state.enabled) + bridge.send('router:toggle-recording', state.enabled) + } +} + +const getters = { + activeRouteChange: state => { + return state.routeChanges[state.inspectedIndex] + }, + filteredRoutes: state => { + return state.routeChanges.filter(routeChange => { + return routeChange.from.fullPath.indexOf(state.filter) > -1 || routeChange.to.fullPath.indexOf(state.filter) > -1 + }) + } +} + +export default { + namespaced: true, + state, + mutations, + getters +} diff --git a/src/devtools/views/routes/RouterTab.vue b/src/devtools/views/routes/RouterTab.vue new file mode 100644 index 000000000..d712d4fea --- /dev/null +++ b/src/devtools/views/routes/RouterTab.vue @@ -0,0 +1,36 @@ + + + diff --git a/src/devtools/views/routes/RoutesMeta.vue b/src/devtools/views/routes/RoutesMeta.vue new file mode 100644 index 000000000..a58ce1ed5 --- /dev/null +++ b/src/devtools/views/routes/RoutesMeta.vue @@ -0,0 +1,89 @@ + + + + + diff --git a/src/devtools/views/routes/RoutesTab.vue b/src/devtools/views/routes/RoutesTab.vue new file mode 100644 index 000000000..b4beb9fe5 --- /dev/null +++ b/src/devtools/views/routes/RoutesTab.vue @@ -0,0 +1,31 @@ + + + diff --git a/src/devtools/views/routes/RoutesTree.vue b/src/devtools/views/routes/RoutesTree.vue new file mode 100644 index 000000000..d465c6338 --- /dev/null +++ b/src/devtools/views/routes/RoutesTree.vue @@ -0,0 +1,57 @@ + + + + + diff --git a/src/devtools/views/routes/RoutesTreeItem.vue b/src/devtools/views/routes/RoutesTreeItem.vue new file mode 100644 index 000000000..c888f5f47 --- /dev/null +++ b/src/devtools/views/routes/RoutesTreeItem.vue @@ -0,0 +1,146 @@ + + + + + diff --git a/src/devtools/views/routes/module.js b/src/devtools/views/routes/module.js new file mode 100644 index 000000000..2ad88c18d --- /dev/null +++ b/src/devtools/views/routes/module.js @@ -0,0 +1,55 @@ +import storage from '../../storage' + +const ENABLED_KEY = 'EVENTS_ENABLED' +const enabled = storage.get(ENABLED_KEY) + +const state = { + enabled: enabled == null ? true : enabled, + hasRouter: false, + routeChanges: [], + inspectedIndex: -1, + filter: '' +} + +const mutations = { + INIT (state, payload) { + state.inspectedIndex = -1 + state.hasRouter = true + state.routeChanges = payload.routeChanges + }, + CHANGED (state, payload) { + state.routeChanges.push(payload) + }, + INSPECT (state, index) { + state.inspectedIndex = index + }, + UPDATE_FILTER (state, filter) { + state.filter = filter + } +} + +const getters = { + activeRouteChange: state => { + if (typeof state.inspectedIndex === 'string') { + const path = state.inspectedIndex.split('_') + let obj = state.routeChanges[parseInt(path[0])] + for (var i = 1, len = path.length; i < len; ++i) { + obj = obj.children[parseInt(path[i])] + } + return obj + } + return state.routeChanges[state.inspectedIndex] + }, + filteredRoutes: state => { + return state.routeChanges.filter(routeChange => { + return routeChange.path.indexOf(state.filter) > -1 + }) + } +} + +export default { + namespaced: true, + state, + mutations, + getters +}