From 2e81e64929d9c2909ff5882b26933ea54a353aab Mon Sep 17 00:00:00 2001 From: Evan You Date: Tue, 26 May 2020 11:11:44 -0400 Subject: [PATCH] fix: use more robust export default replacement for SFC scripts fix #271 --- src/node/server/serverPluginHmr.ts | 17 ++-------------- src/node/server/serverPluginVue.ts | 31 +++++++++++++++++++++++++++--- src/node/utils/babelParse.ts | 18 +++++++++++++++++ 3 files changed, 48 insertions(+), 18 deletions(-) create mode 100644 src/node/utils/babelParse.ts diff --git a/src/node/server/serverPluginHmr.ts b/src/node/server/serverPluginHmr.ts index 39ca26d971862f..80d7d225923f86 100644 --- a/src/node/server/serverPluginHmr.ts +++ b/src/node/server/serverPluginHmr.ts @@ -26,7 +26,7 @@ import { vueCache, srcImportMap } from './serverPluginVue' import { resolveImport } from './serverPluginModuleRewrite' import { FSWatcher } from 'chokidar' import MagicString from 'magic-string' -import { parse } from '@babel/parser' +import { parse } from '../utils/babelParse' import { Node, StringLiteral, Statement, Expression } from '@babel/types' import { InternalResolver } from '../resolver' import LRUCache from 'lru-cache' @@ -406,20 +406,7 @@ export function rewriteFileWithHMR( } } - const ast = parse(source, { - sourceType: 'module', - plugins: [ - // required for import.meta.hot - 'importMeta', - // by default we enable proposals slated for ES2020. - // full list at https://babeljs.io/docs/en/next/babel-parser#plugins - // this should be kept in async with @vue/compiler-core's support range - 'bigInt', - 'optionalChaining', - 'nullishCoalescingOperator' - ] - }).program.body - + const ast = parse(source) ast.forEach((s) => checkStatements(s, true, false)) // inject import.meta.hot diff --git a/src/node/server/serverPluginVue.ts b/src/node/server/serverPluginVue.ts index 556a98c9fba3af..fffe1162e5a6b4 100644 --- a/src/node/server/serverPluginVue.ts +++ b/src/node/server/serverPluginVue.ts @@ -29,6 +29,8 @@ import { transform } from '../esbuildService' import { InternalResolver } from '../resolver' import { seenUrls } from './serverPluginServeStatic' import { codegenCss, compileCss, rewriteCssUrls } from '../utils/cssUtils' +import { parse } from '../utils/babelParse' +import MagicString from 'magic-string' const debug = require('debug')('vite:sfc') const getEtag = require('etag') @@ -340,6 +342,8 @@ export async function parseSFC( return descriptor } +const defaultExportRE = /((?:\n|;)\s*)export default/ + async function compileSFCMain( descriptor: SFCDescriptor, filePath: string, @@ -351,14 +355,22 @@ async function compileSFCMain( } const id = hash_sum(publicPath) - let code = `\nimport { updateStyle } from "${hmrClientPublicPath}"\n` + let code = `` if (descriptor.script) { let content = descriptor.script.content if (descriptor.script.lang === 'ts') { + // TODO merge lang=ts source map content = (await transform(content, publicPath, { loader: 'ts' })).code } - - code += content.replace(`export default`, 'const __script =') + // rewrite export default. + // fast path: simple regex replacement to avoid full-blown babel parse. + let replaced = content.replace(defaultExportRE, '$1const __script =') + // if the script somehow still contains `default export`, it probably has + // multi-line comments or template strings. fallback to a full parse. + if (defaultExportRE.test(replaced)) { + replaced = rewriteDefaultExport(content) + } + code += replaced } else { code += `const __script = {}` } @@ -366,6 +378,7 @@ async function compileSFCMain( let hasScoped = false let hasCSSModules = false if (descriptor.styles) { + code += `\nimport { updateStyle } from "${hmrClientPublicPath}"\n` descriptor.styles.forEach((s, i) => { const styleRequest = publicPath + `?type=style&index=${i}` if (s.scoped) hasScoped = true @@ -545,3 +558,15 @@ async function compileSFCStyle( debug(`${publicPath} style compiled in ${Date.now() - start}ms`) return result } + +function rewriteDefaultExport(code: string): string { + const s = new MagicString(code) + const ast = parse(code) + ast.forEach((node) => { + if (node.type === 'ExportDefaultDeclaration') { + s.overwrite(node.start!, node.declaration.start!, `const __script = `) + } + }) + const ret = s.toString() + return ret +} diff --git a/src/node/utils/babelParse.ts b/src/node/utils/babelParse.ts new file mode 100644 index 00000000000000..74cca13c4c2126 --- /dev/null +++ b/src/node/utils/babelParse.ts @@ -0,0 +1,18 @@ +import { parse as _parse } from '@babel/parser' +import { Statement } from '@babel/types' + +export function parse(source: string): Statement[] { + return _parse(source, { + sourceType: 'module', + plugins: [ + // required for import.meta.hot + 'importMeta', + // by default we enable proposals slated for ES2020. + // full list at https://babeljs.io/docs/en/next/babel-parser#plugins + // this should be kept in async with @vue/compiler-core's support range + 'bigInt', + 'optionalChaining', + 'nullishCoalescingOperator' + ] + }).program.body +}