feat: Initial devtools support (#1125)
This commit is contained in:
parent
5ed73cd874
commit
568b6db12b
@ -57,7 +57,7 @@ export interface SFCTemplateCompileOptions {
|
||||
*/
|
||||
transformAssetUrls?: AssetURLOptions | AssetURLTagConfig | boolean
|
||||
}
|
||||
|
||||
|
||||
interface PreProcessor {
|
||||
render(
|
||||
source: string,
|
||||
|
@ -45,7 +45,7 @@ describe('component props', () => {
|
||||
render(h(Comp, { 'foo-bar': 3, bar: 3, baz: 4, barBaz: 5 }), root)
|
||||
expect(proxy.fooBar).toBe(3)
|
||||
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 })
|
||||
|
||||
render(h(Comp, { qux: 5 }), root)
|
||||
|
@ -19,7 +19,7 @@ describe('renderSlot', () => {
|
||||
})
|
||||
|
||||
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()
|
||||
})
|
||||
})
|
||||
|
@ -13,6 +13,7 @@ import { isFunction, NO, isObject } from '@vue/shared'
|
||||
import { warn } from './warning'
|
||||
import { createVNode, cloneVNode, VNode } from './vnode'
|
||||
import { RootHydrateFunction } from './hydration'
|
||||
import { initApp, appUnmounted } from './devtools'
|
||||
import { version } from '.'
|
||||
|
||||
export interface App<HostElement = any> {
|
||||
@ -31,7 +32,7 @@ export interface App<HostElement = any> {
|
||||
unmount(rootContainer: HostElement | string): void
|
||||
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
|
||||
_props: Data | null
|
||||
_container: HostElement | null
|
||||
@ -73,6 +74,9 @@ export interface AppContext {
|
||||
directives: Record<string, Directive>
|
||||
provides: Record<string | symbol, any>
|
||||
reload?: () => void // HMR only
|
||||
|
||||
// internal for devtools
|
||||
__app?: App
|
||||
}
|
||||
|
||||
type PluginInstallFunction = (app: App, ...options: any[]) => any
|
||||
@ -226,6 +230,9 @@ export function createAppAPI<HostElement>(
|
||||
}
|
||||
isMounted = true
|
||||
app._container = rootContainer
|
||||
|
||||
__DEV__ && initApp(app, version)
|
||||
|
||||
return vnode.component!.proxy
|
||||
} else if (__DEV__) {
|
||||
warn(
|
||||
@ -240,6 +247,8 @@ export function createAppAPI<HostElement>(
|
||||
unmount() {
|
||||
if (isMounted) {
|
||||
render(null, app._container)
|
||||
|
||||
__DEV__ && appUnmounted(app)
|
||||
} else if (__DEV__) {
|
||||
warn(`Cannot unmount an app that is not mounted.`)
|
||||
}
|
||||
@ -260,6 +269,8 @@ export function createAppAPI<HostElement>(
|
||||
}
|
||||
}
|
||||
|
||||
context.__app = app
|
||||
|
||||
return app
|
||||
}
|
||||
}
|
||||
|
@ -49,6 +49,7 @@ import {
|
||||
markAttrsAccessed
|
||||
} from './componentRenderUtils'
|
||||
import { startMeasure, endMeasure } from './profiling'
|
||||
import { componentAdded } from './devtools'
|
||||
|
||||
export type Data = { [key: string]: unknown }
|
||||
|
||||
@ -408,6 +409,9 @@ export function createComponentInstance(
|
||||
}
|
||||
instance.root = parent ? parent.root : instance
|
||||
instance.emit = emit.bind(null, instance)
|
||||
|
||||
__DEV__ && componentAdded(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
|
||||
} from './components/BaseTransition'
|
||||
|
||||
// Types -----------------------------------------------------------------------
|
||||
// For devtools
|
||||
export { devtools, setDevtoolsHook } from './devtools'
|
||||
|
||||
// Types -------------------------------------------------------------------------
|
||||
|
||||
import { VNode } from './vnode'
|
||||
import { ComponentInternalInstance } from './component'
|
||||
|
@ -65,6 +65,7 @@ import { createHydrationFunctions, RootHydrateFunction } from './hydration'
|
||||
import { invokeDirectiveHook } from './directives'
|
||||
import { startMeasure, endMeasure } from './profiling'
|
||||
import { ComponentPublicInstance } from './componentProxy'
|
||||
import { componentRemoved, componentUpdated } from './devtools'
|
||||
|
||||
export interface Renderer<HostElement = RendererElement> {
|
||||
render: RootRenderFunction<HostElement>
|
||||
@ -1417,6 +1418,7 @@ function baseCreateRenderer(
|
||||
}
|
||||
if (__DEV__) {
|
||||
popWarningContext()
|
||||
componentUpdated(instance)
|
||||
}
|
||||
}
|
||||
}, __DEV__ ? createDevEffectOptions(instance) : prodEffectOptions)
|
||||
@ -2068,6 +2070,8 @@ function baseCreateRenderer(
|
||||
parentSuspense.resolve()
|
||||
}
|
||||
}
|
||||
|
||||
__DEV__ && componentRemoved(instance)
|
||||
}
|
||||
|
||||
const unmountChildren: UnmountChildrenFn = (
|
||||
|
@ -84,7 +84,7 @@ describe('ssr: renderToStream', () => {
|
||||
expect(
|
||||
await renderToStream(
|
||||
createApp(
|
||||
defineComponent((props: {}) => {
|
||||
defineComponent(() => {
|
||||
const msg = ref('hello')
|
||||
return () => h('div', msg.value)
|
||||
})
|
||||
@ -266,7 +266,7 @@ describe('ssr: renderToStream', () => {
|
||||
{ msg: 'hello' },
|
||||
{
|
||||
// optimized slot using string push
|
||||
default: ({ msg }: any, push: any, p: any) => {
|
||||
default: ({ msg }: any, push: any) => {
|
||||
push(`<span>${msg}</span>`)
|
||||
},
|
||||
// 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
|
||||
// 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 { registerRuntimeCompiler, RenderFunction, warn } from '@vue/runtime-dom'
|
||||
import * as runtimeDom from '@vue/runtime-dom'
|
||||
import { isString, NOOP, generateCodeFrame, extend } from '@vue/shared'
|
||||
|
||||
__DEV__ && initDev()
|
||||
|
||||
const compileCache: Record<string, RenderFunction> = Object.create(null)
|
||||
|
||||
function compileToFunction(
|
||||
|
@ -1,8 +1,10 @@
|
||||
// This entry exports the runtime only, and is built as
|
||||
// `dist/vue.esm-bundler.js` which is used by default for bundlers.
|
||||
import './devCheck'
|
||||
import { initDev } from './dev'
|
||||
import { warn } from '@vue/runtime-dom'
|
||||
|
||||
__DEV__ && initDev()
|
||||
|
||||
export * from '@vue/runtime-dom'
|
||||
|
||||
export const compile = () => {
|
||||
|
@ -622,7 +622,7 @@ describe('emits', () => {
|
||||
defineComponent({
|
||||
emits: {
|
||||
click: (n: number) => typeof n === 'number',
|
||||
input: (b: string) => null
|
||||
input: (b: string) => b.length > 1
|
||||
},
|
||||
setup(props, { emit }) {
|
||||
emit('click', 1)
|
||||
|
@ -76,11 +76,13 @@ function bailType(arg: HTMLElement | Ref<HTMLElement>) {
|
||||
expectType<HTMLElement>(unref(arg))
|
||||
|
||||
// ref inner type should be unwrapped
|
||||
// eslint-disable-next-line no-restricted-globals
|
||||
const nestedRef = ref({ foo: ref(document.createElement('DIV')) })
|
||||
|
||||
expectType<Ref<{ foo: HTMLElement }>>(nestedRef)
|
||||
expectType<{ foo: HTMLElement }>(nestedRef.value)
|
||||
}
|
||||
// eslint-disable-next-line no-restricted-globals
|
||||
const el = document.createElement('DIV')
|
||||
bailType(el)
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user