Skip to content

Commit

Permalink
feat: further align compiler-sfc api + expose rewriteDefault
Browse files Browse the repository at this point in the history
  • Loading branch information
yyx990803 committed Jun 16, 2022
1 parent ac88827 commit 8ce585d
Show file tree
Hide file tree
Showing 4 changed files with 116 additions and 5 deletions.
3 changes: 2 additions & 1 deletion packages/compiler-sfc/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@ export { compileTemplate } from './compileTemplate'
export { compileStyle, compileStyleAsync } from './compileStyle'
export { compileScript } from './compileScript'
export { generateCodeFrame } from 'compiler/codeframe'
export { rewriteDefault } from './rewriteDefault'

// types
export { CompilerOptions } from 'types/compiler'
export { CompilerOptions, WarningMessage } from 'types/compiler'
export { TemplateCompiler } from './types'
export {
SFCBlock,
Expand Down
6 changes: 3 additions & 3 deletions packages/compiler-sfc/src/parse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export interface ParseOptions {
compiler?: TemplateCompiler
compilerParseOptions?: VueTemplateCompilerParseOptions
sourceRoot?: string
needMap?: boolean
sourceMap?: boolean
}

export function parse(options: ParseOptions): SFCDescriptor {
Expand All @@ -32,7 +32,7 @@ export function parse(options: ParseOptions): SFCDescriptor {
compiler,
compilerParseOptions = { pad: false } as VueTemplateCompilerParseOptions,
sourceRoot = '',
needMap = true
sourceMap = true
} = options
const cacheKey = hash(
filename + source + JSON.stringify(compilerParseOptions)
Expand All @@ -55,7 +55,7 @@ export function parse(options: ParseOptions): SFCDescriptor {
output.shouldForceReload = prevImports =>
hmrShouldReload(prevImports, output!)

if (needMap) {
if (sourceMap) {
if (output.script && !output.script.src) {
output.script.map = generateSourceMap(
filename,
Expand Down
2 changes: 1 addition & 1 deletion packages/compiler-sfc/src/parseComponent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,12 @@ export interface SFCCustomBlock {
attrs: { [key: string]: string | true }
start: number
end: number
src?: string
map?: RawSourceMap
}

export interface SFCBlock extends SFCCustomBlock {
lang?: string
src?: string
scoped?: boolean
module?: string | boolean
}
Expand Down
110 changes: 110 additions & 0 deletions packages/compiler-sfc/src/rewriteDefault.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import { parse, ParserPlugin } from '@babel/parser'
import MagicString from 'magic-string'

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} = {}`
}

let replaced: string | undefined

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
}

// 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') {
s.overwrite(node.start!, node.declaration.start!, `const ${as} = `)
}
if (node.type === 'ExportNamedDeclaration') {
for (const specifier of node.specifiers) {
if (
specifier.type === 'ExportSpecifier' &&
specifier.exported.type === 'Identifier' &&
specifier.exported.name === 'default'
) {
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, ``)
s.append(`\nconst ${as} = __VUE_DEFAULT__`)
continue
} else {
const end = specifierEnd(input, specifier.exported.end!, node.end)
s.prepend(
`import { ${input.slice(
specifier.local.start!,
specifier.local.end!
)} } from '${node.source.value}'\n`
)
s.overwrite(specifier.start!, end, ``)
s.append(`\nconst ${as} = ${specifier.local.name}`)
continue
}
}
const end = specifierEnd(input, specifier.end!, node.end)
s.overwrite(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)
}

function specifierEnd(
input: string,
end: number,
nodeEnd: number | undefined | null
) {
// export { default , foo } ...
let hasCommas = false
let oldEnd = end
while (end < nodeEnd!) {
if (/\s/.test(input.charAt(end))) {
end++
} else if (input.charAt(end) === ',') {
end++
hasCommas = true
break
} else if (input.charAt(end) === '}') {
break
}
}
return hasCommas ? end : oldEnd
}

0 comments on commit 8ce585d

Please sign in to comment.