Skip to content

Commit

Permalink
feat(hmr): re-design HMR API
Browse files Browse the repository at this point in the history
BREAKING CHANGE: HRM API has been re-designed.

  - All HMR APIs are now exposed on `import.meta.hot` and HMR API
    calls should be nested inside `if (import.meta.hot) {}` conditional
    checks.

  - `import.meta.hot.accept()` is now only used for self-accepting updates.

  - `import.meta.hot.acceptDeps()` is now used for accepting dependency
    updates without re-instantiating acceptor.

  - `import.meta.hot.data` is an object that is persisted across hot
    updates of a module.

  - `import.meta.hot.dispose()` callback now receives the persisted data
    object.

  - `import.meta.hot.decline()` can be used to decline updates and if
    the module is affected in an HMR update chain and force full page
    reload.

  - `import.meta.hot.invalidate()` can be used inside an acceptance
    callback to conditionally reject the update and force full page
    reload.

  See `hmr.d.ts` for full API definitions.
  • Loading branch information
yyx990803 committed May 26, 2020
1 parent ca95925 commit a68bfc3
Show file tree
Hide file tree
Showing 10 changed files with 366 additions and 312 deletions.
27 changes: 16 additions & 11 deletions hmr.d.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
export declare const hot: {
// single dep
accept(dep: string, cb?: (newModule: any) => void): void
// multiple deps
accept(deps: string[], cb?: (newModules: any[]) => void): void
// self-accepting
accept(cb: (newModule: any) => void): void
// dispose
dispose(cb: () => void): void
// custom events
on(event: string, cb: (data: any) => void): void
declare interface ImportMeta {
hot: {
data: any

accept(): void
accept(cb: (mod: any) => void): void

acceptDeps(dep: string, cb: (mod: any) => void): void
acceptDeps(deps: string[], cb: (mods: any[]) => void): void

dispose(cb: (data: any) => void): void
decline(): void
invalidate(): void

on(event: string, cb: (...args: any[]) => void): void
}
}
13 changes: 6 additions & 7 deletions playground/testHmrManual.js
Original file line number Diff line number Diff line change
@@ -1,26 +1,25 @@
import { hot } from 'vite/hmr'
import './testHmrManualDep'

export const foo = 1

if (__DEV__) {
hot.accept(({ foo }) => {
if (import.meta.hot) {
import.meta.hot.accept(({ foo }) => {
console.log('(self-accepting)1.foo is now:', foo)
})

hot.accept(({ foo }) => {
import.meta.hot.accept(({ foo }) => {
console.log('(self-accepting)2.foo is now:', foo)
})

hot.dispose(() => {
import.meta.hot.dispose(() => {
console.log(`foo was: ${foo}`)
})

hot.accept('./testHmrManualDep.js', ({ foo }) => {
import.meta.hot.acceptDeps('./testHmrManualDep.js', ({ foo }) => {
console.log('(single dep) foo is now:', foo)
})

hot.accept(['./testHmrManualDep.js'], (modules) => {
import.meta.hot.acceptDeps(['./testHmrManualDep.js'], (modules) => {
console.log('(multiple deps) foo is now:', modules[0].foo)
})
}
10 changes: 6 additions & 4 deletions playground/testHmrManualDep.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { hot } from 'vite/hmr'

export const foo = 1

if (__DEV__) {
hot.dispose(() => {
if (import.meta.hot) {
const data = import.meta.hot.data
console.log(`(dep) foo from dispose: ${data.fromDispose}`)

import.meta.hot.dispose((data) => {
console.log(`(dep) foo was: ${foo}`)
data.fromDispose = foo * 10
})
}
79 changes: 53 additions & 26 deletions src/client/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ async function updateModule(
changedPath: string,
timestamp: string
) {
const mod = jsHotModuleMap.get(id)
const mod = hotModulesMap.get(id)
if (!mod) {
console.error(
`[vite] got js update notification but no client callback was registered. Something is wrong.`
Expand Down Expand Up @@ -187,8 +187,8 @@ async function updateModule(

await Promise.all(
Array.from(modulesToUpdate).map(async (dep) => {
const disposer = jsDisposeMap.get(dep)
if (disposer) await disposer()
const disposer = disposeMap.get(dep)
if (disposer) await disposer(dataMap.get(dep))
try {
const newMod = await import(
dep + (dep.includes('?') ? '&' : '?') + `t=${timestamp}`
Expand Down Expand Up @@ -221,33 +221,60 @@ interface HotCallback {
fn: (modules: object | object[]) => void
}

const jsHotModuleMap = new Map<string, HotModule>()
const jsDisposeMap = new Map<string, () => void | Promise<void>>()
const hotModulesMap = new Map<string, HotModule>()
const disposeMap = new Map<string, (data: any) => void | Promise<void>>()
const dataMap = new Map<string, any>()
const customUpdateMap = new Map<string, ((customData: any) => void)[]>()

export const hot = {
accept(
id: string,
deps: HotCallback['deps'],
callback: HotCallback['fn'] = () => {}
) {
const mod: HotModule = jsHotModuleMap.get(id) || {
id,
callbacks: []
export const createHotContext = (id: string) => {
if (!dataMap.has(id)) {
dataMap.set(id, {})
}

const hot = {
get data() {
return dataMap.get(id)
},

accept(callback: HotCallback['fn'] = () => {}) {
hot.acceptDeps(id, callback)
},

acceptDeps(
deps: HotCallback['deps'],
callback: HotCallback['fn'] = () => {}
) {
const mod: HotModule = hotModulesMap.get(id) || {
id,
callbacks: []
}
mod.callbacks.push({
deps: deps as HotCallback['deps'],
fn: callback
})
hotModulesMap.set(id, mod)
},

dispose(cb: (data: any) => void) {
disposeMap.set(id, cb)
},

// noop, used for static analysis only
decline() {},

invalidate() {
location.reload()
},

// custom events
on(event: string, cb: () => void) {
const existing = customUpdateMap.get(event) || []
existing.push(cb)
customUpdateMap.set(event, existing)
}
mod.callbacks.push({ deps, fn: callback })
jsHotModuleMap.set(id, mod)
},

dispose(id: string, cb: () => void) {
jsDisposeMap.set(id, cb)
},

on(event: string, cb: () => void) {
const existing = customUpdateMap.get(event) || []
existing.push(cb)
customUpdateMap.set(event, existing)
}

return hot
}

function bustSwCache(path: string) {
Expand Down
9 changes: 0 additions & 9 deletions src/node/build/buildPluginResolve.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { Plugin } from 'rollup'
import fs from 'fs-extra'
import { hmrClientId } from '../server/serverPluginHmr'
import { resolveVue } from '../utils/resolveVue'
import { InternalResolver } from '../resolver'

Expand All @@ -15,9 +14,6 @@ export const createBuildResolvePlugin = (
async resolveId(id, importer) {
const original = id
id = resolver.alias(id) || id
if (id === hmrClientId) {
return hmrClientId
}
if (id === 'vue' || id.startsWith('@vue/')) {
const vuePaths = resolveVue(root)
if (id in vuePaths) {
Expand All @@ -36,11 +32,6 @@ export const createBuildResolvePlugin = (
const resolved = this.resolve(id, importer, { skipSelf: true })
return resolved || { id }
}
},
load(id: string) {
if (id === hmrClientId) {
return `export const hot = {accept(){},dispose(){},on(){}}`
}
}
}
}
3 changes: 2 additions & 1 deletion src/node/build/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,8 @@ export async function build(options: BuildConfig): Promise<BuildResult> {
(id) => /\.(j|t)sx?$/.test(id),
{
...envReplacements,
'process.env.': `({}).`
'process.env.': `({}).`,
'import.meta.hot': `false`
},
sourcemap
),
Expand Down
Loading

0 comments on commit a68bfc3

Please sign in to comment.