describe('compiler sfc: rewriteDefault', () => {
test('without export default', () => {
- expect(rewriteDefault(`export a = {}`, 'script')).toMatchInlineSnapshot(`
- "export a = {}
+ expect(rewriteDefault(`export const a = {}`, 'script'))
+ .toMatchInlineSnapshot(`
+ "export const a = {}
const script = {}"
`)
})
).toMatchInlineSnapshot(`"const script = {}"`)
})
+ test('rewrite variable value default', () => {
+ expect(rewriteDefault(`export const foo = 'default'`, 'script'))
+ .toMatchInlineSnapshot(`
+ "export const foo = 'default'
+ const script = {}"
+ `)
+ })
+
test('rewrite export named default', () => {
expect(
rewriteDefault(
export { a as b, a as c}
const script = a"
`)
+
+ expect(
+ rewriteDefault(
+ `const a = 1 \n export { a as b } \n export { a as default, a as c }`,
+ 'script'
+ )
+ ).toMatchInlineSnapshot(`
+ "const a = 1
+ export { a as b }
+ export { a as c }
+ const script = a"
+ `)
})
test('w/ comments', async () => {
).toMatchInlineSnapshot(`
"let App = {}
export {
-
+
}
const _sfc_main = App"
`)
expect(
rewriteDefault(`export { default, foo } from './index.js'`, 'script')
).toMatchInlineSnapshot(`
- "import { default as __VUE_DEFAULT__ } from './index.js'
- export { foo } from './index.js'
- const script = __VUE_DEFAULT__"
+ "import { default as __VUE_DEFAULT__ } from './index.js'
+ export { foo } from './index.js'
+ const script = __VUE_DEFAULT__"
`)
expect(
rewriteDefault(`export { default , foo } from './index.js'`, 'script')
).toMatchInlineSnapshot(`
- "import { default as __VUE_DEFAULT__ } from './index.js'
- export { foo } from './index.js'
- const script = __VUE_DEFAULT__"
+ "import { default as __VUE_DEFAULT__ } from './index.js'
+ export { foo } from './index.js'
+ const script = __VUE_DEFAULT__"
`)
expect(
rewriteDefault(`export { foo, default } from './index.js'`, 'script')
).toMatchInlineSnapshot(`
- "import { default as __VUE_DEFAULT__ } from './index.js'
- export { foo, } from './index.js'
- const script = __VUE_DEFAULT__"
+ "import { default as __VUE_DEFAULT__ } from './index.js'
+ export { foo, } from './index.js'
+ const script = __VUE_DEFAULT__"
`)
expect(
'script'
)
).toMatchInlineSnapshot(`
- "import { foo } from './index.js'
- export { bar } from './index.js'
- const script = foo"
+ "import { foo as __VUE_DEFAULT__ } from './index.js'
+ export { bar } from './index.js'
+ const script = __VUE_DEFAULT__"
`)
expect(
'script'
)
).toMatchInlineSnapshot(`
- "import { foo } from './index.js'
- export { bar } from './index.js'
- const script = foo"
+ "import { foo as __VUE_DEFAULT__ } from './index.js'
+ export { bar } from './index.js'
+ const script = __VUE_DEFAULT__"
`)
expect(
'script'
)
).toMatchInlineSnapshot(`
- "import { foo } from './index.js'
- export { bar, } from './index.js'
- const script = foo"
+ "import { foo as __VUE_DEFAULT__ } from './index.js'
+ export { bar, } from './index.js'
+ const script = __VUE_DEFAULT__"
+ `)
+
+ expect(
+ rewriteDefault(
+ `export { foo as default } from './index.js' \n const foo = 1`,
+ 'script'
+ )
+ ).toMatchInlineSnapshot(`
+ "import { foo as __VUE_DEFAULT__ } from './index.js'
+ export { } from './index.js'
+ const foo = 1
+ const script = __VUE_DEFAULT__"
+ `)
+
+ expect(
+ rewriteDefault(
+ `const a = 1 \nexport { a as default } from 'xxx'`,
+ 'script'
+ )
+ ).toMatchInlineSnapshot(`
+ "import { a as __VUE_DEFAULT__ } from 'xxx'
+ const a = 1
+ export { } from 'xxx'
+ const script = __VUE_DEFAULT__"
`)
})
test('export default class', async () => {
expect(rewriteDefault(`export default class Foo {}`, 'script'))
.toMatchInlineSnapshot(`
- "class Foo {}
- const script = Foo"
- `)
+ " class Foo {}
+ const script = Foo"
+ `)
})
test('export default class w/ comments', async () => {
rewriteDefault(`// export default\nexport default class Foo {}`, 'script')
).toMatchInlineSnapshot(`
"// export default
- class Foo {}
+ class Foo {}
const script = Foo"
`)
})
).toMatchInlineSnapshot(`
"/*
export default class Foo {}*/
- class Bar {}
+ class Bar {}
const script = Bar"
`)
})
test('@Component\nexport default class', async () => {
- expect(rewriteDefault(`@Component\nexport default class Foo {}`, 'script'))
- .toMatchInlineSnapshot(`
- "@Component
- class Foo {}
+ expect(
+ rewriteDefault(`@Component\nexport default class Foo {}`, 'script', [
+ 'decorators-legacy'
+ ])
+ ).toMatchInlineSnapshot(`
+ "@Component class Foo {}
const script = Foo"
`)
})
expect(
rewriteDefault(
`// export default\n@Component\nexport default class Foo {}`,
- 'script'
+ 'script',
+ ['decorators-legacy']
)
).toMatchInlineSnapshot(`
"// export default
- @Component
- class Foo {}
+ @Component class Foo {}
const script = Foo"
`)
})
"/*
@Component
export default class Foo {}*/
- class Bar {}
+ class Bar {}
const script = Bar"
`)
})
} from './cssVars'
import { compileTemplate, SFCTemplateCompileOptions } from './compileTemplate'
import { warnOnce } from './warn'
-import { rewriteDefault } from './rewriteDefault'
+import { rewriteDefaultAST } from './rewriteDefault'
import { createCache } from './cache'
import { shouldTransform, transformAST } from '@vue/reactivity-transform'
}
}
if (cssVars.length) {
- content = rewriteDefault(content, DEFAULT_VAR, plugins)
+ const s = new MagicString(content)
+ rewriteDefaultAST(scriptAst.body, s, DEFAULT_VAR)
+ content = s.toString()
content += genNormalScriptCssVarsCode(
cssVars,
bindings,
return {
...scriptSetup,
+ s,
bindings: bindingMetadata,
imports: userImports,
content: s.toString(),
export { compileTemplate } from './compileTemplate'
export { compileStyle, compileStyleAsync } from './compileStyle'
export { compileScript } from './compileScript'
-export { rewriteDefault } from './rewriteDefault'
+export { rewriteDefault, rewriteDefaultAST } from './rewriteDefault'
export {
shouldTransform as shouldTransformRef,
transform as transformRef,
import { parseCssVars } from './cssVars'
import { createCache } from './cache'
import { hmrShouldReload, ImportBinding } from './compileScript'
+import MagicString from 'magic-string'
export const DEFAULT_FILENAME = 'anonymous.vue'
export interface SFCScriptBlock extends SFCBlock {
type: 'script'
+ s: MagicString
setup?: string | boolean
bindings?: BindingMetadata
imports?: Record<string, ImportBinding>
-import { parse, ParserPlugin } from '@babel/parser'
+import { parse } from '@babel/parser'
import MagicString from 'magic-string'
+import type { ParserPlugin } from '@babel/parser'
+import type { Identifier, Statement } from '@babel/types'
-const defaultExportRE = /((?:^|\n|;)\s*)export(\s*)default/
-const namedDefaultExportRE = /((?:^|\n|;)\s*)export(.+)(?:as)?(\s*)default/s
-const exportDefaultClassRE =
- /((?:^|\n|;)\s*)export\s+default\s+class\s+([\w$]+)/
-
-/**
- * Utility for rewriting `export default` in a script block into a variable
- * declaration so that we can inject things into it
- */
export function rewriteDefault(
input: string,
as: string,
parserPlugins?: ParserPlugin[]
): string {
- if (!hasDefaultExport(input)) {
- return input + `\nconst ${as} = {}`
- }
+ const ast = parse(input, {
+ sourceType: 'module',
+ plugins: parserPlugins
+ }).program.body
+ const s = new MagicString(input)
- let replaced: string | undefined
+ rewriteDefaultAST(ast, s, as)
- const classMatch = input.match(exportDefaultClassRE)
- if (classMatch) {
- replaced =
- input.replace(exportDefaultClassRE, '$1class $2') +
- `\nconst ${as} = ${classMatch[2]}`
- } else {
- replaced = input.replace(defaultExportRE, `$1const ${as} =`)
- }
- if (!hasDefaultExport(replaced)) {
- return replaced
+ return s.toString()
+}
+
+/**
+ * Utility for rewriting `export default` in a script block into a variable
+ * declaration so that we can inject things into it
+ */
+export function rewriteDefaultAST(
+ ast: Statement[],
+ s: MagicString,
+ as: string
+): void {
+ if (!hasDefaultExport(ast)) {
+ s.append(`\nconst ${as} = {}`)
+ return
}
// if the script somehow still contains `default export`, it probably has
// multi-line comments or template strings. fallback to a full parse.
- const s = new MagicString(input)
- const ast = parse(input, {
- sourceType: 'module',
- plugins: parserPlugins
- }).program.body
ast.forEach(node => {
if (node.type === 'ExportDefaultDeclaration') {
if (node.declaration.type === 'ClassDeclaration') {
- s.overwrite(node.start!, node.declaration.id.start!, `class `)
+ let start: number =
+ node.declaration.decorators && node.declaration.decorators.length > 0
+ ? node.declaration.decorators[
+ node.declaration.decorators.length - 1
+ ].end!
+ : node.start!
+ s.overwrite(start, node.declaration.id.start!, ` class `)
s.append(`\nconst ${as} = ${node.declaration.id.name}`)
} else {
s.overwrite(node.start!, node.declaration.start!, `const ${as} = `)
}
- }
- if (node.type === 'ExportNamedDeclaration') {
+ } else if (node.type === 'ExportNamedDeclaration') {
for (const specifier of node.specifiers) {
if (
specifier.type === 'ExportSpecifier' &&
) {
if (node.source) {
if (specifier.local.name === 'default') {
- const end = specifierEnd(input, specifier.local.end!, node.end!)
s.prepend(
`import { default as __VUE_DEFAULT__ } from '${node.source.value}'\n`
)
- s.overwrite(specifier.start!, end, ``)
+ const end = specifierEnd(s, specifier.local.end!, node.end!)
+ s.remove(specifier.start!, end)
s.append(`\nconst ${as} = __VUE_DEFAULT__`)
continue
} else {
- const end = specifierEnd(
- input,
- specifier.exported.end!,
- node.end!
- )
s.prepend(
- `import { ${input.slice(
+ `import { ${s.slice(
specifier.local.start!,
specifier.local.end!
- )} } from '${node.source.value}'\n`
+ )} as __VUE_DEFAULT__ } from '${node.source.value}'\n`
)
- s.overwrite(specifier.start!, end, ``)
- s.append(`\nconst ${as} = ${specifier.local.name}`)
+ const end = specifierEnd(s, specifier.exported.end!, node.end!)
+ s.remove(specifier.start!, end)
+ s.append(`\nconst ${as} = __VUE_DEFAULT__`)
continue
}
}
- const end = specifierEnd(input, specifier.end!, node.end!)
- s.overwrite(specifier.start!, end, ``)
+
+ const end = specifierEnd(s, specifier.end!, node.end!)
+ s.remove(specifier.start!, end)
s.append(`\nconst ${as} = ${specifier.local.name}`)
}
}
}
})
- return s.toString()
}
-export function hasDefaultExport(input: string): boolean {
- return defaultExportRE.test(input) || namedDefaultExportRE.test(input)
+export function hasDefaultExport(ast: Statement[]): boolean {
+ for (const stmt of ast) {
+ if (stmt.type === 'ExportDefaultDeclaration') {
+ return true
+ } else if (
+ stmt.type === 'ExportNamedDeclaration' &&
+ stmt.specifiers.some(
+ spec => (spec.exported as Identifier).name === 'default'
+ )
+ ) {
+ return true
+ }
+ }
+ return false
}
-function specifierEnd(input: string, end: number, nodeEnd: number | null) {
+function specifierEnd(s: MagicString, end: number, nodeEnd: number | null) {
// export { default , foo } ...
let hasCommas = false
let oldEnd = end
while (end < nodeEnd!) {
- if (/\s/.test(input.charAt(end))) {
+ if (/\s/.test(s.slice(end, end + 1))) {
end++
- } else if (input.charAt(end) === ',') {
+ } else if (s.slice(end, end + 1) === ',') {
end++
hasCommas = true
break
- } else if (input.charAt(end) === '}') {
+ } else if (s.slice(end, end + 1) === '}') {
break
}
}