Skip to content

Commit

Permalink
refactor: vue update by hmr api (#243)
Browse files Browse the repository at this point in the history
  • Loading branch information
underfin committed May 25, 2020
1 parent db8b6b2 commit cf5de5b
Show file tree
Hide file tree
Showing 6 changed files with 100 additions and 119 deletions.
53 changes: 14 additions & 39 deletions src/client/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,15 +63,9 @@ function warnFailedFetch(err: Error, path: string | string[]) {

// Listen for messages
socket.addEventListener('message', async ({ data }) => {
const {
type,
path,
changeSrcPath,
id,
index,
timestamp,
customData
} = JSON.parse(data)
const { type, path, changeSrcPath, id, timestamp, customData } = JSON.parse(
data
)

if (changeSrcPath) {
await bustSwCache(changeSrcPath)
Expand All @@ -84,35 +78,10 @@ socket.addEventListener('message', async ({ data }) => {
case 'connected':
console.log(`[vite] connected.`)
break
case 'vue-reload':
import(`${path}?t=${timestamp}`)
.then((m) => {
__VUE_HMR_RUNTIME__.reload(path, m.default)
console.log(`[vite] ${path} reloaded.`)
})
.catch((err) => warnFailedFetch(err, path))
break
case 'vue-rerender':
const templatePath = `${path}?type=template`
await bustSwCache(templatePath)
import(`${templatePath}&t=${timestamp}`).then((m) => {
__VUE_HMR_RUNTIME__.rerender(path, m.render)
console.log(`[vite] ${path} template updated.`)
})
break
case 'vue-style-update':
const stylePath = `${path}?type=style&index=${index}`
await bustSwCache(stylePath)
const content = await import(stylePath + `&t=${timestamp}`)
updateStyle(id, content.default)
console.log(
`[vite] ${path} style${index > 0 ? `#${index}` : ``} updated.`
)
break
case 'style-update':
await bustSwCache(`${path}?import`)
const style = await import(`${path}?t=${timestamp}`)
updateStyle(id, style.default)
const hasQuery = path.includes('?') ? '&' : '?'
await bustSwCache(`${path}${hasQuery}import`)
await import(`${path}${hasQuery}t=${timestamp}`)
console.log(`[vite] ${path} updated.`)
break
case 'style-remove':
Expand Down Expand Up @@ -220,8 +189,14 @@ async function updateModule(
Array.from(modulesToUpdate).map(async (dep) => {
const disposer = jsDisposeMap.get(dep)
if (disposer) await disposer()
const newMod = await import(dep + `?t=${timestamp}`)
moduleMap.set(dep, newMod)
try {
const newMod = await import(
dep + (dep.includes('?') ? '&' : '?') + `t=${timestamp}`
)
moduleMap.set(dep, newMod)
} catch (e) {
warnFailedFetch(e, dep)
}
})
)

Expand Down
30 changes: 7 additions & 23 deletions src/node/server/serverPluginCss.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { basename } from 'path'
import { ServerPlugin } from '.'
import { hmrClientId } from './serverPluginHmr'
import hash_sum from 'hash-sum'
import { Context } from 'koa'
import { cleanUrl, isImportRequest, readBody } from '../utils'
import { srcImportMap, vueCache } from './serverPluginVue'
import {
codegenCss,
compileCss,
cssPreprocessLangRE,
rewriteCssUrls
Expand All @@ -31,33 +31,21 @@ export const cssPlugin: ServerPlugin = ({ root, app, watcher, resolver }) => {
// note ctx.body could be null if upstream set status to 304
ctx.body
) {
const id = JSON.stringify(hash_sum(ctx.path))
if (isImportRequest(ctx)) {
await processCss(root, ctx)
// we rewrite css with `?import` to a js module that inserts a style
// tag linking to the actual raw url
ctx.type = 'js'
const id = JSON.stringify(hash_sum(ctx.path))
let code =
`import { updateStyle } from "${hmrClientId}"\n` +
`const css = ${JSON.stringify(processedCSS.get(ctx.path)!.css)}\n` +
`updateStyle(${id}, css)\n`
if (ctx.path.endsWith('.module.css')) {
code += `export default ${JSON.stringify(
processedCSS.get(ctx.path)!.modules
)}`
} else {
code += `export default css`
}
ctx.body = code.trim()
const { css, modules } = processedCSS.get(ctx.path)!
ctx.body = codegenCss(id, css, modules)
} else {
// raw request, return compiled css
if (!processedCSS.has(ctx.path)) {
await processCss(root, ctx)
}
ctx.type = 'js'
ctx.body = `export default ${JSON.stringify(
processedCSS.get(ctx.path)!.css
)}`
ctx.body = codegenCss(id, processedCSS.get(ctx.path)!.css)
}
}
})
Expand Down Expand Up @@ -87,10 +75,8 @@ export const cssPlugin: ServerPlugin = ({ root, app, watcher, resolver }) => {
chalk.green(`[vite:hmr] `) + `${publicPath} updated. (style)`
)
watcher.send({
type: 'vue-style-update',
path: publicPath,
index: Number(index),
id: `${hash_sum(publicPath)}-${index}`,
type: 'style-update',
path: `${publicPath}?type=style&index=${index}`,
timestamp: Date.now()
})
return
Expand All @@ -103,14 +89,12 @@ export const cssPlugin: ServerPlugin = ({ root, app, watcher, resolver }) => {
}

const publicPath = resolver.fileToRequest(file)
const id = hash_sum(publicPath)

// bust process cache
processedCSS.delete(publicPath)

watcher.send({
type: 'style-update',
id,
path: publicPath,
timestamp: Date.now()
})
Expand Down
76 changes: 38 additions & 38 deletions src/node/server/serverPluginHmr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,9 +73,6 @@ export const hmrClientPublicPath = `/${hmrClientId}`

interface HMRPayload {
type:
| 'vue-rerender'
| 'vue-reload'
| 'vue-style-update'
| 'js-update'
| 'style-update'
| 'style-remove'
Expand Down Expand Up @@ -178,12 +175,24 @@ export const hmrPlugin: ServerPlugin = ({
}

// check which part of the file changed
let needReload = false
let needCssModuleReload = false
let needRerender = false

const vueReload = () => {
send({
type: 'js-update',
path: publicPath,
changeSrcPath: publicPath,
timestamp
})
console.log(
chalk.green(`[vite:hmr] `) +
`${path.relative(root, file)} updated. (reload)`
)
}

if (!isEqual(descriptor.script, prevDescriptor.script)) {
needReload = true
vueReload()
return
}

if (!isEqual(descriptor.template, prevDescriptor.template)) {
Expand All @@ -194,12 +203,6 @@ export const hmrPlugin: ServerPlugin = ({
const styleId = hash_sum(publicPath)
const prevStyles = prevDescriptor.styles || []
const nextStyles = descriptor.styles || []
if (
!needReload &&
prevStyles.some((s) => s.scoped) !== nextStyles.some((s) => s.scoped)
) {
needReload = true
}

// css modules update causes a reload because the $style object is changed
// and it may be used in JS. It also needs to trigger a vue-style-update
Expand All @@ -208,25 +211,26 @@ export const hmrPlugin: ServerPlugin = ({
prevStyles.some((s) => s.module != null) ||
nextStyles.some((s) => s.module != null)
) {
needCssModuleReload = true
vueReload()
return
}

if (prevStyles.some((s) => s.scoped) !== nextStyles.some((s) => s.scoped)) {
needRerender = true
}

// only need to update styles if not reloading, since reload forces
// style updates as well.
if (!needReload) {
nextStyles.forEach((_, i) => {
if (!prevStyles[i] || !isEqual(prevStyles[i], nextStyles[i])) {
didUpdateStyle = true
send({
type: 'vue-style-update',
path: publicPath,
index: i,
id: `${styleId}-${i}`,
timestamp
})
}
})
}
nextStyles.forEach((_, i) => {
if (!prevStyles[i] || !isEqual(prevStyles[i], nextStyles[i])) {
didUpdateStyle = true
send({
type: 'style-update',
path: `${publicPath}?type=style&index=${i}`,
timestamp
})
}
})

// stale styles always need to be removed
prevStyles.slice(nextStyles.length).forEach((_, i) => {
Expand All @@ -239,22 +243,17 @@ export const hmrPlugin: ServerPlugin = ({
})
})

if (needReload || needCssModuleReload) {
send({
type: 'vue-reload',
path: publicPath,
timestamp
})
} else if (needRerender) {
if (needRerender) {
send({
type: 'vue-rerender',
type: 'js-update',
path: publicPath,
changeSrcPath: `${publicPath}?type=template`,
timestamp
})
}

if (needReload || needRerender || didUpdateStyle) {
let updateType = needReload ? `reload` : needRerender ? `template` : ``
if (needRerender || didUpdateStyle) {
let updateType = needRerender ? `template` : ``
if (didUpdateStyle) {
updateType += ` & style`
}
Expand Down Expand Up @@ -308,7 +307,7 @@ export const hmrPlugin: ServerPlugin = ({
`${vueBoundary} reloaded due to change in ${relativeFile}.`
)
send({
type: 'vue-reload',
type: 'js-update',
path: vueBoundary,
changeSrcPath: publicPath,
timestamp
Expand Down Expand Up @@ -385,6 +384,7 @@ function isHmrAccepted(importer: string, dep: string): boolean {
function isEqual(a: SFCBlock | null, b: SFCBlock | null) {
if (!a && !b) return true
if (!a || !b) return false
if (a.content.length !== b.content.length) return false
if (a.content !== b.content) return false
const keysA = Object.keys(a.attrs)
const keysB = Object.keys(b.attrs)
Expand Down
5 changes: 1 addition & 4 deletions src/node/server/serverPluginModuleRewrite.ts
Original file line number Diff line number Diff line change
Expand Up @@ -150,10 +150,7 @@ export function rewriteImports(
let resolved
if (id === hmrClientId) {
resolved = hmrClientPublicPath
if (
/\bhot\b/.test(source.substring(ss, se)) &&
!/.vue$|.vue\?type=/.test(importer)
) {
if (/\bhot\b/.test(source.substring(ss, se))) {
// the user explicit imports the HMR API in a js file
// making the module hot.
rewriteFileWithHMR(root, source, importer, resolver, s)
Expand Down
37 changes: 22 additions & 15 deletions src/node/server/serverPluginVue.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import { Context } from 'koa'
import { transform } from '../esbuildService'
import { InternalResolver } from '../resolver'
import { seenUrls } from './serverPluginServeStatic'
import { compileCss, rewriteCssUrls } from '../utils/cssUtils'
import { codegenCss, compileCss, rewriteCssUrls } from '../utils/cssUtils'

const debug = require('debug')('vite:sfc')
const getEtag = require('etag')
Expand Down Expand Up @@ -114,20 +114,16 @@ export const vuePlugin: ServerPlugin = ({
if (styleBlock.src) {
filename = await resolveSrcImport(styleBlock, ctx, resolver)
}
const id = hash_sum(publicPath)
const result = await compileSFCStyle(
root,
styleBlock,
index,
filename,
publicPath
)
if (query.module != null) {
ctx.type = 'js'
ctx.body = `export default ${JSON.stringify(result.modules)}`
} else {
ctx.type = 'js'
ctx.body = `export default ${JSON.stringify(result.code)}`
}
ctx.type = 'js'
ctx.body = codegenCss(`${id}-${index}`, result.code, result.modules)
return etagCacheCheck(ctx)
}

Expand Down Expand Up @@ -219,7 +215,8 @@ async function compileSFCMain(
return cached.script
}

let code = ''
const id = hash_sum(publicPath)
let code = `\nimport { updateStyle, hot } from "${hmrClientId}"\n`
if (descriptor.script) {
let content = descriptor.script.content
if (descriptor.script.lang === 'ts') {
Expand All @@ -231,11 +228,15 @@ async function compileSFCMain(
code += `const __script = {}`
}

const id = hash_sum(publicPath)
code += `\n if (__DEV__) {
hot.accept((m) => {
__VUE_HMR_RUNTIME__.reload("${id}", m.default)
})
}`

let hasScoped = false
let hasCSSModules = false
if (descriptor.styles) {
code += `\nimport { updateStyle } from "${hmrClientId}"\n`
descriptor.styles.forEach((s, i) => {
const styleRequest = publicPath + `?type=style&index=${i}`
if (s.scoped) hasScoped = true
Expand All @@ -250,22 +251,28 @@ async function compileSFCMain(
styleRequest + '&module'
)}`
code += `\n__cssModules[${JSON.stringify(moduleName)}] = ${styleVar}`
} else {
code += `\nimport ${JSON.stringify(styleRequest)}`
}
code += `\nimport css_${i} from ${JSON.stringify(styleRequest)}`
code += `\nupdateStyle("${id}-${i}", css_${i})`
})
if (hasScoped) {
code += `\n__script.__scopeId = "data-v-${id}"`
}
}

if (descriptor.template) {
const templateRequest = publicPath + `?type=template`
code += `\nimport { render as __render } from ${JSON.stringify(
publicPath + `?type=template`
templateRequest
)}`
code += `\n__script.render = __render`
code += `\n if (__DEV__) {
hot.accept(${JSON.stringify(templateRequest)}, (m) => {
__VUE_HMR_RUNTIME__.rerender("${id}", m.render)
})
}`
}
code += `\n__script.__hmrId = ${JSON.stringify(publicPath)}`
code += `\n__script.__hmrId = ${JSON.stringify(id)}`
code += `\n__script.__file = ${JSON.stringify(filePath)}`
code += `\nexport default __script`

Expand Down
Loading

0 comments on commit cf5de5b

Please sign in to comment.