Przeglądaj źródła

feat: Initial devtools support (#1125)

Guillaume Chau 5 lat temu
rodzic
commit
568b6db12b

+ 1 - 1
packages/compiler-sfc/src/compileTemplate.ts

@@ -57,7 +57,7 @@ export interface SFCTemplateCompileOptions {
    */
    */
   transformAssetUrls?: AssetURLOptions | AssetURLTagConfig | boolean
   transformAssetUrls?: AssetURLOptions | AssetURLTagConfig | boolean
 }
 }
-  
+
 interface PreProcessor {
 interface PreProcessor {
   render(
   render(
     source: string,
     source: string,

+ 1 - 1
packages/runtime-core/__tests__/componentProps.spec.ts

@@ -45,7 +45,7 @@ describe('component props', () => {
     render(h(Comp, { 'foo-bar': 3, bar: 3, baz: 4, barBaz: 5 }), root)
     render(h(Comp, { 'foo-bar': 3, bar: 3, baz: 4, barBaz: 5 }), root)
     expect(proxy.fooBar).toBe(3)
     expect(proxy.fooBar).toBe(3)
     expect(proxy.barBaz).toBe(5)
     expect(proxy.barBaz).toBe(5)
-    expect(props).toEqual({ fooBar: 3,barBaz: 5 })
+    expect(props).toEqual({ fooBar: 3, barBaz: 5 })
     expect(attrs).toEqual({ bar: 3, baz: 4 })
     expect(attrs).toEqual({ bar: 3, baz: 4 })
 
 
     render(h(Comp, { qux: 5 }), root)
     render(h(Comp, { qux: 5 }), root)

+ 1 - 1
packages/runtime-core/__tests__/helpers/renderSlot.spec.ts

@@ -19,7 +19,7 @@ describe('renderSlot', () => {
   })
   })
 
 
   it('should warn render ssr slot', () => {
   it('should warn render ssr slot', () => {
-    renderSlot({ default: (a, b, c) => [h('child')] }, 'default')
+    renderSlot({ default: (_a, _b, _c) => [h('child')] }, 'default')
     expect('SSR-optimized slot function detected').toHaveBeenWarned()
     expect('SSR-optimized slot function detected').toHaveBeenWarned()
   })
   })
 })
 })

+ 12 - 1
packages/runtime-core/src/apiCreateApp.ts

@@ -13,6 +13,7 @@ import { isFunction, NO, isObject } from '@vue/shared'
 import { warn } from './warning'
 import { warn } from './warning'
 import { createVNode, cloneVNode, VNode } from './vnode'
 import { createVNode, cloneVNode, VNode } from './vnode'
 import { RootHydrateFunction } from './hydration'
 import { RootHydrateFunction } from './hydration'
+import { initApp, appUnmounted } from './devtools'
 import { version } from '.'
 import { version } from '.'
 
 
 export interface App<HostElement = any> {
 export interface App<HostElement = any> {
@@ -31,7 +32,7 @@ export interface App<HostElement = any> {
   unmount(rootContainer: HostElement | string): void
   unmount(rootContainer: HostElement | string): void
   provide<T>(key: InjectionKey<T> | string, value: T): this
   provide<T>(key: InjectionKey<T> | string, value: T): this
 
 
-  // internal. We need to expose these for the server-renderer
+  // internal. We need to expose these for the server-renderer and devtools
   _component: Component
   _component: Component
   _props: Data | null
   _props: Data | null
   _container: HostElement | null
   _container: HostElement | null
@@ -73,6 +74,9 @@ export interface AppContext {
   directives: Record<string, Directive>
   directives: Record<string, Directive>
   provides: Record<string | symbol, any>
   provides: Record<string | symbol, any>
   reload?: () => void // HMR only
   reload?: () => void // HMR only
+
+  // internal for devtools
+  __app?: App
 }
 }
 
 
 type PluginInstallFunction = (app: App, ...options: any[]) => any
 type PluginInstallFunction = (app: App, ...options: any[]) => any
@@ -226,6 +230,9 @@ export function createAppAPI<HostElement>(
           }
           }
           isMounted = true
           isMounted = true
           app._container = rootContainer
           app._container = rootContainer
+
+          __DEV__ && initApp(app, version)
+
           return vnode.component!.proxy
           return vnode.component!.proxy
         } else if (__DEV__) {
         } else if (__DEV__) {
           warn(
           warn(
@@ -240,6 +247,8 @@ export function createAppAPI<HostElement>(
       unmount() {
       unmount() {
         if (isMounted) {
         if (isMounted) {
           render(null, app._container)
           render(null, app._container)
+
+          __DEV__ && appUnmounted(app)
         } else if (__DEV__) {
         } else if (__DEV__) {
           warn(`Cannot unmount an app that is not mounted.`)
           warn(`Cannot unmount an app that is not mounted.`)
         }
         }
@@ -260,6 +269,8 @@ export function createAppAPI<HostElement>(
       }
       }
     }
     }
 
 
+    context.__app = app
+
     return app
     return app
   }
   }
 }
 }

+ 4 - 0
packages/runtime-core/src/component.ts

@@ -49,6 +49,7 @@ import {
   markAttrsAccessed
   markAttrsAccessed
 } from './componentRenderUtils'
 } from './componentRenderUtils'
 import { startMeasure, endMeasure } from './profiling'
 import { startMeasure, endMeasure } from './profiling'
+import { componentAdded } from './devtools'
 
 
 export type Data = { [key: string]: unknown }
 export type Data = { [key: string]: unknown }
 
 
@@ -408,6 +409,9 @@ export function createComponentInstance(
   }
   }
   instance.root = parent ? parent.root : instance
   instance.root = parent ? parent.root : instance
   instance.emit = emit.bind(null, instance)
   instance.emit = emit.bind(null, instance)
+
+  __DEV__ && componentAdded(instance)
+
   return instance
   return instance
 }
 }
 
 

+ 78 - 0
packages/runtime-core/src/devtools.ts

@@ -0,0 +1,78 @@
+import { App } from './apiCreateApp'
+import { Fragment, Text, Comment, Static } from './vnode'
+import { ComponentInternalInstance } from './component'
+
+export interface AppRecord {
+  id: number
+  app: App
+  version: string
+  types: { [key: string]: string | Symbol }
+}
+
+enum DevtoolsHooks {
+  APP_INIT = 'app:init',
+  APP_UNMOUNT = 'app:unmount',
+  COMPONENT_UPDATED = 'component:updated',
+  COMPONENT_ADDED = 'component:added',
+  COMPONENT_REMOVED = 'component:removed'
+}
+
+export interface DevtoolsHook {
+  emit: (event: string, ...payload: any[]) => void
+  on: (event: string, handler: Function) => void
+  once: (event: string, handler: Function) => void
+  off: (event: string, handler: Function) => void
+  appRecords: AppRecord[]
+}
+
+export let devtools: DevtoolsHook
+
+export function setDevtoolsHook(hook: DevtoolsHook) {
+  devtools = hook
+}
+
+export function initApp(app: App, version: string) {
+  // TODO queue if devtools is undefined
+  if (!devtools) return
+  devtools.emit(DevtoolsHooks.APP_INIT, app, version, {
+    Fragment: Fragment,
+    Text: Text,
+    Comment: Comment,
+    Static: Static
+  })
+}
+
+export function appUnmounted(app: App) {
+  if (!devtools) return
+  devtools.emit(DevtoolsHooks.APP_UNMOUNT, app)
+}
+
+export function componentAdded(component: ComponentInternalInstance) {
+  if (!devtools || !component.appContext.__app) return
+  devtools.emit(
+    DevtoolsHooks.COMPONENT_ADDED,
+    component.appContext.__app,
+    component.uid,
+    component.parent ? component.parent.uid : undefined
+  )
+}
+
+export function componentUpdated(component: ComponentInternalInstance) {
+  if (!devtools || !component.appContext.__app) return
+  devtools.emit(
+    DevtoolsHooks.COMPONENT_UPDATED,
+    component.appContext.__app,
+    component.uid,
+    component.parent ? component.parent.uid : undefined
+  )
+}
+
+export function componentRemoved(component: ComponentInternalInstance) {
+  if (!devtools || !component.appContext.__app) return
+  devtools.emit(
+    DevtoolsHooks.COMPONENT_REMOVED,
+    component.appContext.__app,
+    component.uid,
+    component.parent ? component.parent.uid : undefined
+  )
+}

+ 4 - 1
packages/runtime-core/src/index.ts

@@ -93,7 +93,10 @@ export {
   getTransitionRawChildren
   getTransitionRawChildren
 } from './components/BaseTransition'
 } from './components/BaseTransition'
 
 
-// Types -----------------------------------------------------------------------
+// For devtools
+export { devtools, setDevtoolsHook } from './devtools'
+
+// Types -------------------------------------------------------------------------
 
 
 import { VNode } from './vnode'
 import { VNode } from './vnode'
 import { ComponentInternalInstance } from './component'
 import { ComponentInternalInstance } from './component'

+ 4 - 0
packages/runtime-core/src/renderer.ts

@@ -65,6 +65,7 @@ import { createHydrationFunctions, RootHydrateFunction } from './hydration'
 import { invokeDirectiveHook } from './directives'
 import { invokeDirectiveHook } from './directives'
 import { startMeasure, endMeasure } from './profiling'
 import { startMeasure, endMeasure } from './profiling'
 import { ComponentPublicInstance } from './componentProxy'
 import { ComponentPublicInstance } from './componentProxy'
+import { componentRemoved, componentUpdated } from './devtools'
 
 
 export interface Renderer<HostElement = RendererElement> {
 export interface Renderer<HostElement = RendererElement> {
   render: RootRenderFunction<HostElement>
   render: RootRenderFunction<HostElement>
@@ -1417,6 +1418,7 @@ function baseCreateRenderer(
         }
         }
         if (__DEV__) {
         if (__DEV__) {
           popWarningContext()
           popWarningContext()
+          componentUpdated(instance)
         }
         }
       }
       }
     }, __DEV__ ? createDevEffectOptions(instance) : prodEffectOptions)
     }, __DEV__ ? createDevEffectOptions(instance) : prodEffectOptions)
@@ -2068,6 +2070,8 @@ function baseCreateRenderer(
         parentSuspense.resolve()
         parentSuspense.resolve()
       }
       }
     }
     }
+
+    __DEV__ && componentRemoved(instance)
   }
   }
 
 
   const unmountChildren: UnmountChildrenFn = (
   const unmountChildren: UnmountChildrenFn = (

+ 2 - 2
packages/server-renderer/__tests__/renderToStream.spec.ts

@@ -84,7 +84,7 @@ describe('ssr: renderToStream', () => {
       expect(
       expect(
         await renderToStream(
         await renderToStream(
           createApp(
           createApp(
-            defineComponent((props: {}) => {
+            defineComponent(() => {
               const msg = ref('hello')
               const msg = ref('hello')
               return () => h('div', msg.value)
               return () => h('div', msg.value)
             })
             })
@@ -266,7 +266,7 @@ describe('ssr: renderToStream', () => {
                   { msg: 'hello' },
                   { msg: 'hello' },
                   {
                   {
                     // optimized slot using string push
                     // optimized slot using string push
-                    default: ({ msg }: any, push: any, p: any) => {
+                    default: ({ msg }: any, push: any) => {
                       push(`<span>${msg}</span>`)
                       push(`<span>${msg}</span>`)
                     },
                     },
                     // important to avoid slots being normalized
                     // important to avoid slots being normalized

+ 16 - 0
packages/vue/src/dev.ts

@@ -0,0 +1,16 @@
+import { version, setDevtoolsHook } from '@vue/runtime-dom'
+
+export function initDev() {
+  const target: any = __BROWSER__ ? window : global
+
+  target.__VUE__ = version
+  setDevtoolsHook(target.__VUE_DEVTOOLS_GLOBAL_HOOK__)
+
+  if (__BROWSER__) {
+    // @ts-ignore `console.info` cannot be null error
+    console[console.info ? 'info' : 'log'](
+      `You are running a development build of Vue.\n` +
+        `Make sure to use the production build (*.prod.js) when deploying for production.`
+    )
+  }
+}

+ 0 - 7
packages/vue/src/devCheck.ts

@@ -1,7 +0,0 @@
-if (__BROWSER__ && __DEV__) {
-  // @ts-ignore `console.info` cannot be null error
-  console[console.info ? 'info' : 'log'](
-    `You are running a development build of Vue.\n` +
-      `Make sure to use the production build (*.prod.js) when deploying for production.`
-  )
-}

+ 3 - 1
packages/vue/src/index.ts

@@ -1,11 +1,13 @@
 // This entry is the "full-build" that includes both the runtime
 // This entry is the "full-build" that includes both the runtime
 // and the compiler, and supports on-the-fly compilation of the template option.
 // and the compiler, and supports on-the-fly compilation of the template option.
-import './devCheck'
+import { initDev } from './dev'
 import { compile, CompilerOptions, CompilerError } from '@vue/compiler-dom'
 import { compile, CompilerOptions, CompilerError } from '@vue/compiler-dom'
 import { registerRuntimeCompiler, RenderFunction, warn } from '@vue/runtime-dom'
 import { registerRuntimeCompiler, RenderFunction, warn } from '@vue/runtime-dom'
 import * as runtimeDom from '@vue/runtime-dom'
 import * as runtimeDom from '@vue/runtime-dom'
 import { isString, NOOP, generateCodeFrame, extend } from '@vue/shared'
 import { isString, NOOP, generateCodeFrame, extend } from '@vue/shared'
 
 
+__DEV__ && initDev()
+
 const compileCache: Record<string, RenderFunction> = Object.create(null)
 const compileCache: Record<string, RenderFunction> = Object.create(null)
 
 
 function compileToFunction(
 function compileToFunction(

+ 3 - 1
packages/vue/src/runtime.ts

@@ -1,8 +1,10 @@
 // This entry exports the runtime only, and is built as
 // This entry exports the runtime only, and is built as
 // `dist/vue.esm-bundler.js` which is used by default for bundlers.
 // `dist/vue.esm-bundler.js` which is used by default for bundlers.
-import './devCheck'
+import { initDev } from './dev'
 import { warn } from '@vue/runtime-dom'
 import { warn } from '@vue/runtime-dom'
 
 
+__DEV__ && initDev()
+
 export * from '@vue/runtime-dom'
 export * from '@vue/runtime-dom'
 
 
 export const compile = () => {
 export const compile = () => {

+ 1 - 1
test-dts/defineComponent.test-d.tsx

@@ -622,7 +622,7 @@ describe('emits', () => {
   defineComponent({
   defineComponent({
     emits: {
     emits: {
       click: (n: number) => typeof n === 'number',
       click: (n: number) => typeof n === 'number',
-      input: (b: string) => null
+      input: (b: string) => b.length > 1
     },
     },
     setup(props, { emit }) {
     setup(props, { emit }) {
       emit('click', 1)
       emit('click', 1)

+ 2 - 0
test-dts/ref.test-d.ts

@@ -76,11 +76,13 @@ function bailType(arg: HTMLElement | Ref<HTMLElement>) {
   expectType<HTMLElement>(unref(arg))
   expectType<HTMLElement>(unref(arg))
 
 
   // ref inner type should be unwrapped
   // ref inner type should be unwrapped
+  // eslint-disable-next-line no-restricted-globals
   const nestedRef = ref({ foo: ref(document.createElement('DIV')) })
   const nestedRef = ref({ foo: ref(document.createElement('DIV')) })
 
 
   expectType<Ref<{ foo: HTMLElement }>>(nestedRef)
   expectType<Ref<{ foo: HTMLElement }>>(nestedRef)
   expectType<{ foo: HTMLElement }>(nestedRef.value)
   expectType<{ foo: HTMLElement }>(nestedRef.value)
 }
 }
+// eslint-disable-next-line no-restricted-globals
 const el = document.createElement('DIV')
 const el = document.createElement('DIV')
 bailType(el)
 bailType(el)