feat: Initial devtools support (#1125)
This commit is contained in:
parent
5ed73cd874
commit
568b6db12b
@ -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)
|
||||||
|
@ -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()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
packages/runtime-core/src/devtools.ts
Normal file
78
packages/runtime-core/src/devtools.ts
Normal file
@ -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
|
||||||
|
)
|
||||||
|
}
|
@ -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'
|
||||||
|
@ -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 = (
|
||||||
|
@ -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
packages/vue/src/dev.ts
Normal file
16
packages/vue/src/dev.ts
Normal file
@ -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.`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@ -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.`
|
|
||||||
)
|
|
||||||
}
|
|
@ -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(
|
||||||
|
@ -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 = () => {
|
||||||
|
@ -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)
|
||||||
|
@ -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)
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user