Skip to content

Commit

Permalink
feat(runtime-core): hot module replacement
Browse files Browse the repository at this point in the history
  • Loading branch information
yyx990803 committed Dec 13, 2019
1 parent 3116b5d commit efe39db
Show file tree
Hide file tree
Showing 7 changed files with 108 additions and 2 deletions.
1 change: 1 addition & 0 deletions packages/global.d.ts
Expand Up @@ -2,6 +2,7 @@
declare var __DEV__: boolean
declare var __TEST__: boolean
declare var __BROWSER__: boolean
declare var __BUNDLER__: boolean
declare var __RUNTIME_COMPILE__: boolean
declare var __COMMIT__: string
declare var __VERSION__: string
Expand Down
4 changes: 4 additions & 0 deletions packages/runtime-core/src/apiOptions.ts
Expand Up @@ -66,6 +66,10 @@ export interface ComponentOptionsBase<
directives?: Record<string, Directive>
inheritAttrs?: boolean

// SFC & dev only
__scopeId?: string
__hmrId?: string

// type-only differentiator to separate OptionWithoutProps from a constructor
// type returned by createComponent() or FunctionalComponent
call?: never
Expand Down
1 change: 1 addition & 0 deletions packages/runtime-core/src/component.ts
Expand Up @@ -37,6 +37,7 @@ export interface FunctionalComponent<P = {}> {
props?: ComponentPropsOptions<P>
inheritAttrs?: boolean
displayName?: string
__hmrId?: string
}

export type Component = ComponentOptions | FunctionalComponent
Expand Down
85 changes: 85 additions & 0 deletions packages/runtime-core/src/hmr.ts
@@ -0,0 +1,85 @@
import {
ComponentInternalInstance,
ComponentOptions,
RenderFunction
} from './component'

// Expose the HMR runtime on the global object
// This makes it entirely tree-shakable without polluting the exports and makes
// it easier to be used in toolings like vue-loader
// Note: for a component to be eligible for HMR it also needs the __hmrId option
// to be set so that its instances can be registered / removed.
if (__BUNDLER__ && __DEV__) {
const globalObject: any =
typeof global !== 'undefined'
? global
: typeof self !== 'undefined'
? self
: typeof window !== 'undefined'
? window
: {}

globalObject.__VUE_HMR_RUNTIME__ = {
isRecorded: tryWrap(isRecorded),
createRecord: tryWrap(createRecord),
rerender: tryWrap(rerender),
reload: tryWrap(reload)
}
}

interface HMRRecord {
comp: ComponentOptions
instances: Set<ComponentInternalInstance>
}

const map: Map<string, HMRRecord> = new Map()

export function registerHMR(instance: ComponentInternalInstance) {
map.get(instance.type.__hmrId!)!.instances.add(instance)
}

export function unregisterHMR(instance: ComponentInternalInstance) {
map.get(instance.type.__hmrId!)!.instances.delete(instance)
}

function isRecorded(id: string): boolean {
return map.has(id)
}

function createRecord(id: string, comp: ComponentOptions) {
if (map.has(id)) {
return
}
map.set(id, {
comp,
instances: new Set()
})
}

function rerender(id: string, newRender: RenderFunction) {
map.get(id)!.instances.forEach(instance => {
instance.render = newRender
instance.renderCache = []
instance.update()
// TODO force scoped slots passed to children to have DYNAMIC_SLOTS flag
})
}

function reload(id: string, newComp: ComponentOptions) {
// TODO
console.log('reload', id)
}

function tryWrap(fn: (id: string, arg: any) => void): Function {
return (id: string, arg: any) => {
try {
fn(id, arg)
} catch (e) {
console.error(e)
console.warn(
`Something went wrong during Vue component hot-reload. ` +
`Full reload required.`
)
}
}
}
6 changes: 4 additions & 2 deletions packages/runtime-core/src/index.ts
Expand Up @@ -66,7 +66,9 @@ export {
TransitionHooks
} from './components/BaseTransition'

// Internal, for compiler generated code
// Internal API ----------------------------------------------------------------

// For compiler generated code
// should sync with '@vue/compiler-core/src/runtimeConstants.ts'
export { withDirectives } from './directives'
export {
Expand All @@ -87,7 +89,7 @@ import { capitalize as _capitalize, camelize as _camelize } from '@vue/shared'
export const capitalize = _capitalize as (s: string) => string
export const camelize = _camelize as (s: string) => string

// Internal, for integration with runtime compiler
// For integration with runtime compiler
export { registerRuntimeCompiler } from './component'

// Types -----------------------------------------------------------------------
Expand Down
11 changes: 11 additions & 0 deletions packages/runtime-core/src/renderer.ts
Expand Up @@ -53,6 +53,7 @@ import {
} from './components/Suspense'
import { ErrorCodes, callWithErrorHandling } from './errorHandling'
import { KeepAliveSink, isKeepAlive } from './components/KeepAlive'
import { registerHMR, unregisterHMR } from './hmr'

export interface RendererOptions<HostNode = any, HostElement = any> {
patchProp(
Expand Down Expand Up @@ -857,6 +858,11 @@ export function createRenderer<
parentComponent
))

// HMR
if (__BUNDLER__ && __DEV__ && instance.type.__hmrId != null) {
registerHMR(instance)
}

if (__DEV__) {
pushWarningContext(initialVNode)
}
Expand Down Expand Up @@ -1549,6 +1555,11 @@ export function createRenderer<
parentSuspense: HostSuspenseBoundary | null,
doRemove?: boolean
) {
// HMR
if (__BUNDLER__ && __DEV__ && instance.type.__hmrId != null) {
unregisterHMR(instance)
}

const { bum, effects, update, subTree, um, da, isDeactivated } = instance
// beforeUnmount hook
if (bum !== null) {
Expand Down
2 changes: 2 additions & 0 deletions rollup.config.js
Expand Up @@ -147,6 +147,8 @@ function createReplacePlugin(
__TEST__: isBundlerESMBuild ? `(process.env.NODE_ENV === 'test')` : false,
// If the build is expected to run directly in the browser (global / esm builds)
__BROWSER__: isBrowserBuild,
// is targeting bundlers?
__BUNDLER__: isBundlerESMBuild,
// support compile in browser?
__RUNTIME_COMPILE__: isRuntimeCompileBuild,
// support options?
Expand Down

0 comments on commit efe39db

Please sign in to comment.