refactor: simplify sfc script transform usage

This commit is contained in:
Evan You
2020-07-09 18:18:46 -04:00
parent 9f706a9f5e
commit b4f7ab45ea
4 changed files with 122 additions and 99 deletions

View File

@@ -1,4 +1,4 @@
import MagicString, { SourceMap } from 'magic-string'
import MagicString from 'magic-string'
import { SFCDescriptor, SFCScriptBlock } from './parse'
import { parse, ParserPlugin } from '@babel/parser'
import { babelParserDefautPlugins, generateCodeFrame } from '@vue/shared'
@@ -17,15 +17,16 @@ import {
TSDeclareFunction
} from '@babel/types'
import { walk } from 'estree-walker'
import { RawSourceMap } from 'source-map'
export interface SFCScriptCompileOptions {
babelParserPlugins?: ParserPlugin[]
}
export interface BindingMetadata {
[key: string]: 'data' | 'props' | 'setup' | 'ctx'
}
export interface SFCScriptCompileOptions {
parserPlugins?: ParserPlugin[]
}
let hasWarned = false
/**
@@ -33,10 +34,10 @@ let hasWarned = false
* It requires the whole SFC descriptor because we need to handle and merge
* normal `<script>` + `<script setup>` if both are present.
*/
export function compileScriptSetup(
export function compileScript(
sfc: SFCDescriptor,
options: SFCScriptCompileOptions = {}
) {
): SFCScriptBlock {
if (__DEV__ && !__TEST__ && !hasWarned) {
hasWarned = true
console.log(
@@ -47,7 +48,13 @@ export function compileScriptSetup(
const { script, scriptSetup, source, filename } = sfc
if (!scriptSetup) {
throw new Error('SFC has no <script setup>.')
if (!script) {
throw new Error(`SFC contains no <script> tags.`)
}
return {
...script,
bindings: analyzeScriptBindings(script)
}
}
if (script && script.lang !== scriptSetup.lang) {
@@ -86,7 +93,7 @@ export function compileScriptSetup(
const isTS = scriptSetup.lang === 'ts'
const plugins: ParserPlugin[] = [
...(options.parserPlugins || []),
...(options.babelParserPlugins || []),
...babelParserDefautPlugins,
...(isTS ? (['typescript'] as const) : [])
]
@@ -154,7 +161,8 @@ export function compileScriptSetup(
}
// 2. check <script setup="xxx"> function signature
const hasExplicitSignature = typeof scriptSetup.setup === 'string'
const setupValue = scriptSetup.attrs.setup
const hasExplicitSignature = typeof setupValue === 'string'
let propsVar: string | undefined
let emitVar: string | undefined
@@ -179,8 +187,8 @@ export function compileScriptSetup(
// <script setup="xxx" lang="ts">
// parse the signature to extract the props/emit variables the user wants
// we need them to find corresponding type declarations.
const signatureAST = parse(`(${scriptSetup.setup})=>{}`, { plugins })
.program.body[0]
const signatureAST = parse(`(${setupValue})=>{}`, { plugins }).program
.body[0]
const params = ((signatureAST as ExpressionStatement)
.expression as ArrowFunctionExpression).params
if (params[0] && params[0].type === 'Identifier') {
@@ -464,7 +472,7 @@ export function compileScriptSetup(
}`
if (hasExplicitSignature) {
// inject types to user signature
args = scriptSetup.setup as string
args = setupValue as string
const ss = new MagicString(args)
if (propsASTNode) {
// compensate for () wraper offset
@@ -476,7 +484,7 @@ export function compileScriptSetup(
args = ss.toString()
}
} else {
args = hasExplicitSignature ? (scriptSetup.setup as string) : ``
args = hasExplicitSignature ? (setupValue as string) : ``
}
// 6. wrap setup code with function.
@@ -530,13 +538,14 @@ export function compileScriptSetup(
s.trim()
return {
...scriptSetup,
bindings,
code: s.toString(),
map: s.generateMap({
content: s.toString(),
map: (s.generateMap({
source: filename,
hires: true,
includeContent: true
}) as SourceMap
}) as unknown) as RawSourceMap
}
}

View File

@@ -2,7 +2,7 @@
export { parse } from './parse'
export { compileTemplate } from './compileTemplate'
export { compileStyle, compileStyleAsync } from './compileStyle'
export { compileScriptSetup, analyzeScriptBindings } from './compileScript'
export { compileScript, analyzeScriptBindings } from './compileScript'
// Types
export {
@@ -23,7 +23,7 @@ export {
SFCAsyncStyleCompileOptions,
SFCStyleCompileResults
} from './compileStyle'
export { SFCScriptCompileOptions } from './compileScript'
export { SFCScriptCompileOptions, BindingMetadata } from './compileScript'
export {
CompilerOptions,
CompilerError,

View File

@@ -5,10 +5,12 @@ import {
CompilerError,
TextModes
} from '@vue/compiler-core'
import * as CompilerDOM from '@vue/compiler-dom'
import { RawSourceMap, SourceMapGenerator } from 'source-map'
import { generateCodeFrame } from '@vue/shared'
import { TemplateCompiler } from './compileTemplate'
import * as CompilerDOM from '@vue/compiler-dom'
import { compileScript, BindingMetadata } from './compileScript'
import { ParserPlugin } from '@babel/parser'
export interface SFCParseOptions {
filename?: string
@@ -16,6 +18,7 @@ export interface SFCParseOptions {
sourceRoot?: string
pad?: boolean | 'line' | 'space'
compiler?: TemplateCompiler
babelParserPlugins?: ParserPlugin[]
}
export interface SFCBlock {
@@ -35,7 +38,7 @@ export interface SFCTemplateBlock extends SFCBlock {
export interface SFCScriptBlock extends SFCBlock {
type: 'script'
setup?: boolean | string
bindings?: BindingMetadata
}
export interface SFCStyleBlock extends SFCBlock {
@@ -50,6 +53,7 @@ export interface SFCDescriptor {
template: SFCTemplateBlock | null
script: SFCScriptBlock | null
scriptSetup: SFCScriptBlock | null
scriptTransformed: SFCScriptBlock | null
styles: SFCStyleBlock[]
customBlocks: SFCBlock[]
}
@@ -75,7 +79,8 @@ export function parse(
filename = 'component.vue',
sourceRoot = '',
pad = false,
compiler = CompilerDOM
compiler = CompilerDOM,
babelParserPlugins
}: SFCParseOptions = {}
): SFCParseResult {
const sourceKey =
@@ -91,6 +96,7 @@ export function parse(
template: null,
script: null,
scriptSetup: null,
scriptTransformed: null,
styles: [],
customBlocks: []
}
@@ -146,15 +152,16 @@ export function parse(
break
case 'script':
const block = createBlock(node, source, pad) as SFCScriptBlock
if (block.setup && !descriptor.scriptSetup) {
const isSetup = !!block.attrs.setup
if (isSetup && !descriptor.scriptSetup) {
descriptor.scriptSetup = block
break
}
if (!block.setup && !descriptor.script) {
if (!isSetup && !descriptor.script) {
descriptor.script = block
break
}
warnDuplicateBlock(source, filename, node, !!block.setup)
warnDuplicateBlock(source, filename, node, isSetup)
break
case 'style':
descriptor.styles.push(createBlock(node, source, pad) as SFCStyleBlock)
@@ -182,6 +189,16 @@ export function parse(
descriptor.styles.forEach(genMap)
}
if (descriptor.script || descriptor.scriptSetup) {
try {
descriptor.scriptTransformed = compileScript(descriptor, {
babelParserPlugins
})
} catch (e) {
errors.push(e)
}
}
const result = {
descriptor,
errors
@@ -252,8 +269,6 @@ function createBlock(
}
} else if (type === 'template' && p.name === 'functional') {
;(block as SFCTemplateBlock).functional = true
} else if (type === 'script' && p.name === 'setup') {
;(block as SFCScriptBlock).setup = attrs.setup || true
}
}
})