Skip to content

Commit 8ce585d

Browse files
committed
feat: further align compiler-sfc api + expose rewriteDefault
1 parent ac88827 commit 8ce585d

File tree

4 files changed

+116
-5
lines changed

4 files changed

+116
-5
lines changed

packages/compiler-sfc/src/index.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,10 @@ export { compileTemplate } from './compileTemplate'
44
export { compileStyle, compileStyleAsync } from './compileStyle'
55
export { compileScript } from './compileScript'
66
export { generateCodeFrame } from 'compiler/codeframe'
7+
export { rewriteDefault } from './rewriteDefault'
78

89
// types
9-
export { CompilerOptions } from 'types/compiler'
10+
export { CompilerOptions, WarningMessage } from 'types/compiler'
1011
export { TemplateCompiler } from './types'
1112
export {
1213
SFCBlock,

packages/compiler-sfc/src/parse.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ export interface ParseOptions {
2222
compiler?: TemplateCompiler
2323
compilerParseOptions?: VueTemplateCompilerParseOptions
2424
sourceRoot?: string
25-
needMap?: boolean
25+
sourceMap?: boolean
2626
}
2727

2828
export function parse(options: ParseOptions): SFCDescriptor {
@@ -32,7 +32,7 @@ export function parse(options: ParseOptions): SFCDescriptor {
3232
compiler,
3333
compilerParseOptions = { pad: false } as VueTemplateCompilerParseOptions,
3434
sourceRoot = '',
35-
needMap = true
35+
sourceMap = true
3636
} = options
3737
const cacheKey = hash(
3838
filename + source + JSON.stringify(compilerParseOptions)
@@ -55,7 +55,7 @@ export function parse(options: ParseOptions): SFCDescriptor {
5555
output.shouldForceReload = prevImports =>
5656
hmrShouldReload(prevImports, output!)
5757

58-
if (needMap) {
58+
if (sourceMap) {
5959
if (output.script && !output.script.src) {
6060
output.script.map = generateSourceMap(
6161
filename,

packages/compiler-sfc/src/parseComponent.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,12 @@ export interface SFCCustomBlock {
1717
attrs: { [key: string]: string | true }
1818
start: number
1919
end: number
20+
src?: string
2021
map?: RawSourceMap
2122
}
2223

2324
export interface SFCBlock extends SFCCustomBlock {
2425
lang?: string
25-
src?: string
2626
scoped?: boolean
2727
module?: string | boolean
2828
}
+110
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
import { parse, ParserPlugin } from '@babel/parser'
2+
import MagicString from 'magic-string'
3+
4+
const defaultExportRE = /((?:^|\n|;)\s*)export(\s*)default/
5+
const namedDefaultExportRE = /((?:^|\n|;)\s*)export(.+)(?:as)?(\s*)default/s
6+
const exportDefaultClassRE =
7+
/((?:^|\n|;)\s*)export\s+default\s+class\s+([\w$]+)/
8+
9+
/**
10+
* Utility for rewriting `export default` in a script block into a variable
11+
* declaration so that we can inject things into it
12+
*/
13+
export function rewriteDefault(
14+
input: string,
15+
as: string,
16+
parserPlugins?: ParserPlugin[]
17+
): string {
18+
if (!hasDefaultExport(input)) {
19+
return input + `\nconst ${as} = {}`
20+
}
21+
22+
let replaced: string | undefined
23+
24+
const classMatch = input.match(exportDefaultClassRE)
25+
if (classMatch) {
26+
replaced =
27+
input.replace(exportDefaultClassRE, '$1class $2') +
28+
`\nconst ${as} = ${classMatch[2]}`
29+
} else {
30+
replaced = input.replace(defaultExportRE, `$1const ${as} =`)
31+
}
32+
if (!hasDefaultExport(replaced)) {
33+
return replaced
34+
}
35+
36+
// if the script somehow still contains `default export`, it probably has
37+
// multi-line comments or template strings. fallback to a full parse.
38+
const s = new MagicString(input)
39+
const ast = parse(input, {
40+
sourceType: 'module',
41+
plugins: parserPlugins
42+
}).program.body
43+
ast.forEach(node => {
44+
if (node.type === 'ExportDefaultDeclaration') {
45+
s.overwrite(node.start!, node.declaration.start!, `const ${as} = `)
46+
}
47+
if (node.type === 'ExportNamedDeclaration') {
48+
for (const specifier of node.specifiers) {
49+
if (
50+
specifier.type === 'ExportSpecifier' &&
51+
specifier.exported.type === 'Identifier' &&
52+
specifier.exported.name === 'default'
53+
) {
54+
if (node.source) {
55+
if (specifier.local.name === 'default') {
56+
const end = specifierEnd(input, specifier.local.end!, node.end)
57+
s.prepend(
58+
`import { default as __VUE_DEFAULT__ } from '${node.source.value}'\n`
59+
)
60+
s.overwrite(specifier.start!, end, ``)
61+
s.append(`\nconst ${as} = __VUE_DEFAULT__`)
62+
continue
63+
} else {
64+
const end = specifierEnd(input, specifier.exported.end!, node.end)
65+
s.prepend(
66+
`import { ${input.slice(
67+
specifier.local.start!,
68+
specifier.local.end!
69+
)} } from '${node.source.value}'\n`
70+
)
71+
s.overwrite(specifier.start!, end, ``)
72+
s.append(`\nconst ${as} = ${specifier.local.name}`)
73+
continue
74+
}
75+
}
76+
const end = specifierEnd(input, specifier.end!, node.end)
77+
s.overwrite(specifier.start!, end, ``)
78+
s.append(`\nconst ${as} = ${specifier.local.name}`)
79+
}
80+
}
81+
}
82+
})
83+
return s.toString()
84+
}
85+
86+
export function hasDefaultExport(input: string): boolean {
87+
return defaultExportRE.test(input) || namedDefaultExportRE.test(input)
88+
}
89+
90+
function specifierEnd(
91+
input: string,
92+
end: number,
93+
nodeEnd: number | undefined | null
94+
) {
95+
// export { default , foo } ...
96+
let hasCommas = false
97+
let oldEnd = end
98+
while (end < nodeEnd!) {
99+
if (/\s/.test(input.charAt(end))) {
100+
end++
101+
} else if (input.charAt(end) === ',') {
102+
end++
103+
hasCommas = true
104+
break
105+
} else if (input.charAt(end) === '}') {
106+
break
107+
}
108+
}
109+
return hasCommas ? end : oldEnd
110+
}

0 commit comments

Comments
 (0)