wip: compileScriptSetup
This commit is contained in:
parent
1ad3f975ed
commit
1359cc3a28
@ -31,8 +31,8 @@
|
|||||||
"homepage": "https://github.com/vuejs/vue-next/tree/master/packages/compiler-core#readme",
|
"homepage": "https://github.com/vuejs/vue-next/tree/master/packages/compiler-core#readme",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@vue/shared": "3.0.0-beta.20",
|
"@vue/shared": "3.0.0-beta.20",
|
||||||
"@babel/parser": "^7.8.6",
|
"@babel/parser": "^7.10.4",
|
||||||
"@babel/types": "^7.8.6",
|
"@babel/types": "^7.10.4",
|
||||||
"estree-walker": "^0.8.1",
|
"estree-walker": "^0.8.1",
|
||||||
"source-map": "^0.6.1"
|
"source-map": "^0.6.1"
|
||||||
}
|
}
|
||||||
|
@ -22,10 +22,15 @@ import {
|
|||||||
parseJS,
|
parseJS,
|
||||||
walkJS
|
walkJS
|
||||||
} from '../utils'
|
} from '../utils'
|
||||||
import { isGloballyWhitelisted, makeMap } from '@vue/shared'
|
import {
|
||||||
|
isGloballyWhitelisted,
|
||||||
|
makeMap,
|
||||||
|
babelParserDefautPlugins
|
||||||
|
} from '@vue/shared'
|
||||||
import { createCompilerError, ErrorCodes } from '../errors'
|
import { createCompilerError, ErrorCodes } from '../errors'
|
||||||
import { Node, Function, Identifier, ObjectProperty } from '@babel/types'
|
import { Node, Function, Identifier, ObjectProperty } from '@babel/types'
|
||||||
import { validateBrowserExpression } from '../validateExpression'
|
import { validateBrowserExpression } from '../validateExpression'
|
||||||
|
import { ParserPlugin } from '@babel/parser'
|
||||||
|
|
||||||
const isLiteralWhitelisted = /*#__PURE__*/ makeMap('true,false,null,this')
|
const isLiteralWhitelisted = /*#__PURE__*/ makeMap('true,false,null,this')
|
||||||
|
|
||||||
@ -127,12 +132,7 @@ export function processExpression(
|
|||||||
ast = parseJS(source, {
|
ast = parseJS(source, {
|
||||||
plugins: [
|
plugins: [
|
||||||
...context.expressionPlugins,
|
...context.expressionPlugins,
|
||||||
// by default we enable proposals slated for ES2020.
|
...(babelParserDefautPlugins as ParserPlugin[])
|
||||||
// full list at https://babeljs.io/docs/en/next/babel-parser#plugins
|
|
||||||
// this will need to be updated as the spec moves forward.
|
|
||||||
'bigInt',
|
|
||||||
'optionalChaining',
|
|
||||||
'nullishCoalescingOperator'
|
|
||||||
]
|
]
|
||||||
}).program
|
}).program
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
@ -34,6 +34,7 @@
|
|||||||
"vue": "3.0.0-beta.20"
|
"vue": "3.0.0-beta.20"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@babel/parser": "^7.10.4",
|
||||||
"@vue/compiler-core": "3.0.0-beta.20",
|
"@vue/compiler-core": "3.0.0-beta.20",
|
||||||
"@vue/compiler-dom": "3.0.0-beta.20",
|
"@vue/compiler-dom": "3.0.0-beta.20",
|
||||||
"@vue/compiler-ssr": "3.0.0-beta.20",
|
"@vue/compiler-ssr": "3.0.0-beta.20",
|
||||||
@ -41,6 +42,7 @@
|
|||||||
"consolidate": "^0.15.1",
|
"consolidate": "^0.15.1",
|
||||||
"hash-sum": "^2.0.0",
|
"hash-sum": "^2.0.0",
|
||||||
"lru-cache": "^5.1.1",
|
"lru-cache": "^5.1.1",
|
||||||
|
"magic-string": "^0.25.7",
|
||||||
"merge-source-map": "^1.1.0",
|
"merge-source-map": "^1.1.0",
|
||||||
"postcss": "^7.0.27",
|
"postcss": "^7.0.27",
|
||||||
"postcss-modules": "^3.1.0",
|
"postcss-modules": "^3.1.0",
|
||||||
|
214
packages/compiler-sfc/src/compileScript.ts
Normal file
214
packages/compiler-sfc/src/compileScript.ts
Normal file
@ -0,0 +1,214 @@
|
|||||||
|
import MagicString, { SourceMap } from 'magic-string'
|
||||||
|
import { SFCDescriptor, SFCScriptBlock } from './parse'
|
||||||
|
import { parse, ParserPlugin } from '@babel/parser'
|
||||||
|
import { babelParserDefautPlugins } from '@vue/shared'
|
||||||
|
import { ObjectPattern, ArrayPattern } from '@babel/types'
|
||||||
|
|
||||||
|
export interface BindingMetadata {
|
||||||
|
[key: string]: 'data' | 'props' | 'setup' | 'ctx'
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SFCScriptCompileOptions {
|
||||||
|
parserPlugins?: ParserPlugin[]
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compile `<script setup>`
|
||||||
|
* It requires the whole SFC descriptor because we need to handle and merge
|
||||||
|
* normal `<script>` + `<script setup>` if both are present.
|
||||||
|
*/
|
||||||
|
export function compileScriptSetup(
|
||||||
|
sfc: SFCDescriptor,
|
||||||
|
options: SFCScriptCompileOptions = {}
|
||||||
|
) {
|
||||||
|
const { script, scriptSetup, source, filename } = sfc
|
||||||
|
if (!scriptSetup) {
|
||||||
|
throw new Error('SFC has no <script setup>.')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (script && script.lang !== scriptSetup.lang) {
|
||||||
|
throw new Error(
|
||||||
|
`<script> and <script setup> must have the same language type.`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const bindings: BindingMetadata = {}
|
||||||
|
const setupExports: string[] = []
|
||||||
|
let exportAllIndex = 0
|
||||||
|
|
||||||
|
const s = new MagicString(source)
|
||||||
|
const startOffset = scriptSetup.loc.start.offset
|
||||||
|
const endOffset = scriptSetup.loc.end.offset
|
||||||
|
|
||||||
|
// parse and transform <script setup>
|
||||||
|
const plugins: ParserPlugin[] = [
|
||||||
|
...(options.parserPlugins || []),
|
||||||
|
...(babelParserDefautPlugins as ParserPlugin[])
|
||||||
|
]
|
||||||
|
if (scriptSetup.lang === 'ts') {
|
||||||
|
plugins.push('typescript')
|
||||||
|
}
|
||||||
|
|
||||||
|
const ast = parse(scriptSetup.content, {
|
||||||
|
plugins,
|
||||||
|
sourceType: 'module'
|
||||||
|
}).program.body
|
||||||
|
|
||||||
|
for (const node of ast) {
|
||||||
|
const start = node.start! + startOffset
|
||||||
|
let end = node.end! + startOffset
|
||||||
|
// import or type declarations: move to top
|
||||||
|
// locate the end of whitespace between this statement and the next
|
||||||
|
while (end <= source.length) {
|
||||||
|
if (!/\s/.test(source.charAt(end))) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
end++
|
||||||
|
}
|
||||||
|
if (node.type === 'ImportDeclaration') {
|
||||||
|
s.move(start, end, 0)
|
||||||
|
}
|
||||||
|
if (node.type === 'ExportNamedDeclaration') {
|
||||||
|
// named exports
|
||||||
|
if (node.declaration) {
|
||||||
|
// variable/function/class declarations.
|
||||||
|
// remove leading `export ` keyword
|
||||||
|
s.remove(start, start + 7)
|
||||||
|
if (node.declaration.type === 'VariableDeclaration') {
|
||||||
|
// export const foo = ...
|
||||||
|
// export declarations can only have one declaration at a time
|
||||||
|
const id = node.declaration.declarations[0].id
|
||||||
|
if (id.type === 'Identifier') {
|
||||||
|
setupExports.push(id.name)
|
||||||
|
} else if (id.type === 'ObjectPattern') {
|
||||||
|
walkObjectPattern(id, setupExports)
|
||||||
|
} else if (id.type === 'ArrayPattern') {
|
||||||
|
walkArrayPattern(id, setupExports)
|
||||||
|
}
|
||||||
|
} else if (
|
||||||
|
node.declaration.type === 'FunctionDeclaration' ||
|
||||||
|
node.declaration.type === 'ClassDeclaration'
|
||||||
|
) {
|
||||||
|
// export function foo() {} / export class Foo {}
|
||||||
|
// export declarations must be named.
|
||||||
|
setupExports.push(node.declaration.id!.name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (node.specifiers.length) {
|
||||||
|
for (const { exported } of node.specifiers) {
|
||||||
|
if (exported.name === 'default') {
|
||||||
|
// TODO
|
||||||
|
// check duplicated default export
|
||||||
|
// walk export default to make sure it does not reference exported
|
||||||
|
// variables
|
||||||
|
throw new Error(
|
||||||
|
'export default in <script setup> not supported yet'
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
setupExports.push(exported.name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (node.source) {
|
||||||
|
// export { x } from './x'
|
||||||
|
// change it to import and move to top
|
||||||
|
s.overwrite(start, start + 6, 'import')
|
||||||
|
s.move(start, end, 0)
|
||||||
|
} else {
|
||||||
|
// export { x }
|
||||||
|
s.remove(start, end)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (node.type === 'ExportAllDeclaration') {
|
||||||
|
// export * from './x'
|
||||||
|
s.overwrite(
|
||||||
|
start,
|
||||||
|
node.source.start! + startOffset,
|
||||||
|
`import * as __import_all_${exportAllIndex++}__ from `
|
||||||
|
)
|
||||||
|
s.move(start, end, 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove non-script content
|
||||||
|
if (script) {
|
||||||
|
const s2 = script.loc.start.offset
|
||||||
|
const e2 = script.loc.end.offset
|
||||||
|
if (startOffset < s2) {
|
||||||
|
// <script setup> before <script>
|
||||||
|
s.remove(endOffset, s2)
|
||||||
|
s.remove(e2, source.length)
|
||||||
|
} else {
|
||||||
|
// <script> before <script setup>
|
||||||
|
s.remove(0, s2)
|
||||||
|
s.remove(e2, startOffset)
|
||||||
|
s.remove(endOffset, source.length)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// only <script setup>
|
||||||
|
s.remove(0, startOffset)
|
||||||
|
s.remove(endOffset, source.length)
|
||||||
|
}
|
||||||
|
|
||||||
|
// wrap setup code with function
|
||||||
|
// determine the argument signature.
|
||||||
|
const args =
|
||||||
|
typeof scriptSetup.setup === 'string'
|
||||||
|
? scriptSetup.setup
|
||||||
|
: // TODO should we force explicit args signature?
|
||||||
|
`$props, { attrs: $attrs, slots: $slots, emit: $emit }`
|
||||||
|
// export the content of <script setup> as a named export, `setup`.
|
||||||
|
// this allows `import { setup } from '*.vue'` for testing purposes.
|
||||||
|
s.appendLeft(startOffset, `\nexport function setup(${args}) {\n`)
|
||||||
|
|
||||||
|
// generate return statement
|
||||||
|
let returned = `{ ${setupExports.join(', ')} }`
|
||||||
|
|
||||||
|
// handle `export * from`. We need to call `toRefs` on the imported module
|
||||||
|
// object before merging.
|
||||||
|
if (exportAllIndex > 0) {
|
||||||
|
s.prepend(`import { toRefs as __toRefs__ } from 'vue'\n`)
|
||||||
|
for (let i = 0; i < exportAllIndex; i++) {
|
||||||
|
returned += `,\n __toRefs__(__export_all_${i}__)`
|
||||||
|
}
|
||||||
|
returned = `Object.assign(\n ${returned}\n)`
|
||||||
|
}
|
||||||
|
|
||||||
|
s.appendRight(
|
||||||
|
endOffset,
|
||||||
|
`\nreturn ${returned}\n}\n\nexport default { setup }\n`
|
||||||
|
)
|
||||||
|
|
||||||
|
s.trim()
|
||||||
|
|
||||||
|
setupExports.forEach(key => {
|
||||||
|
bindings[key] = 'setup'
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
bindings,
|
||||||
|
code: s.toString(),
|
||||||
|
map: s.generateMap({
|
||||||
|
source: filename,
|
||||||
|
includeContent: true
|
||||||
|
}) as SourceMap
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Analyze bindings in normal `<script>`
|
||||||
|
* Note that `compileScriptSetup` already analyzes bindings as part of its
|
||||||
|
* compilation process so this should only be used on single `<script>` SFCs.
|
||||||
|
*/
|
||||||
|
export function analyzeScriptBindings(
|
||||||
|
_script: SFCScriptBlock
|
||||||
|
): BindingMetadata {
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
|
||||||
|
function walkObjectPattern(_node: ObjectPattern, _setupExports: string[]) {
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
|
||||||
|
function walkArrayPattern(_node: ArrayPattern, _setupExports: string[]) {
|
||||||
|
// TODO
|
||||||
|
}
|
@ -2,6 +2,7 @@
|
|||||||
export { parse } from './parse'
|
export { parse } from './parse'
|
||||||
export { compileTemplate } from './compileTemplate'
|
export { compileTemplate } from './compileTemplate'
|
||||||
export { compileStyle, compileStyleAsync } from './compileStyle'
|
export { compileStyle, compileStyleAsync } from './compileStyle'
|
||||||
|
export { compileScriptSetup, analyzeScriptBindings } from './compileScript'
|
||||||
|
|
||||||
// Types
|
// Types
|
||||||
export {
|
export {
|
||||||
|
@ -35,7 +35,7 @@ export interface SFCTemplateBlock extends SFCBlock {
|
|||||||
|
|
||||||
export interface SFCScriptBlock extends SFCBlock {
|
export interface SFCScriptBlock extends SFCBlock {
|
||||||
type: 'script'
|
type: 'script'
|
||||||
setup?: boolean
|
setup?: boolean | string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SFCStyleBlock extends SFCBlock {
|
export interface SFCStyleBlock extends SFCBlock {
|
||||||
@ -46,6 +46,7 @@ export interface SFCStyleBlock extends SFCBlock {
|
|||||||
|
|
||||||
export interface SFCDescriptor {
|
export interface SFCDescriptor {
|
||||||
filename: string
|
filename: string
|
||||||
|
source: string
|
||||||
template: SFCTemplateBlock | null
|
template: SFCTemplateBlock | null
|
||||||
script: SFCScriptBlock | null
|
script: SFCScriptBlock | null
|
||||||
scriptSetup: SFCScriptBlock | null
|
scriptSetup: SFCScriptBlock | null
|
||||||
@ -86,6 +87,7 @@ export function parse(
|
|||||||
|
|
||||||
const descriptor: SFCDescriptor = {
|
const descriptor: SFCDescriptor = {
|
||||||
filename,
|
filename,
|
||||||
|
source,
|
||||||
template: null,
|
template: null,
|
||||||
script: null,
|
script: null,
|
||||||
scriptSetup: null,
|
scriptSetup: null,
|
||||||
@ -152,7 +154,7 @@ export function parse(
|
|||||||
descriptor.script = block
|
descriptor.script = block
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
warnDuplicateBlock(source, filename, node, block.setup)
|
warnDuplicateBlock(source, filename, node, !!block.setup)
|
||||||
break
|
break
|
||||||
case 'style':
|
case 'style':
|
||||||
descriptor.styles.push(createBlock(node, source, pad) as SFCStyleBlock)
|
descriptor.styles.push(createBlock(node, source, pad) as SFCStyleBlock)
|
||||||
@ -251,7 +253,7 @@ function createBlock(
|
|||||||
} else if (type === 'template' && p.name === 'functional') {
|
} else if (type === 'template' && p.name === 'functional') {
|
||||||
;(block as SFCTemplateBlock).functional = true
|
;(block as SFCTemplateBlock).functional = true
|
||||||
} else if (type === 'script' && p.name === 'setup') {
|
} else if (type === 'script' && p.name === 'setup') {
|
||||||
;(block as SFCScriptBlock).setup = true
|
;(block as SFCScriptBlock).setup = attrs.setup || true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -13,6 +13,18 @@ export * from './escapeHtml'
|
|||||||
export * from './looseEqual'
|
export * from './looseEqual'
|
||||||
export * from './toDisplayString'
|
export * from './toDisplayString'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List of @babel/parser plugins that are used for template expression
|
||||||
|
* transforms and SFC script transforms. By default we enable proposals slated
|
||||||
|
* for ES2020. This will need to be updated as the spec moves forward.
|
||||||
|
* Full list at https://babeljs.io/docs/en/next/babel-parser#plugins
|
||||||
|
*/
|
||||||
|
export const babelParserDefautPlugins = [
|
||||||
|
'bigInt',
|
||||||
|
'optionalChaining',
|
||||||
|
'nullishCoalescingOperator'
|
||||||
|
]
|
||||||
|
|
||||||
export const EMPTY_OBJ: { readonly [key: string]: any } = __DEV__
|
export const EMPTY_OBJ: { readonly [key: string]: any } = __DEV__
|
||||||
? Object.freeze({})
|
? Object.freeze({})
|
||||||
: {}
|
: {}
|
||||||
|
11
yarn.lock
11
yarn.lock
@ -212,7 +212,7 @@
|
|||||||
chalk "^2.0.0"
|
chalk "^2.0.0"
|
||||||
js-tokens "^4.0.0"
|
js-tokens "^4.0.0"
|
||||||
|
|
||||||
"@babel/parser@^7.1.0", "@babel/parser@^7.4.4", "@babel/parser@^7.5.5", "@babel/parser@^7.7.5", "@babel/parser@^7.8.6", "@babel/parser@^7.9.0":
|
"@babel/parser@^7.1.0", "@babel/parser@^7.10.4", "@babel/parser@^7.4.4", "@babel/parser@^7.5.5", "@babel/parser@^7.7.5", "@babel/parser@^7.8.6", "@babel/parser@^7.9.0":
|
||||||
version "7.10.4"
|
version "7.10.4"
|
||||||
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.10.4.tgz#9eedf27e1998d87739fb5028a5120557c06a1a64"
|
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.10.4.tgz#9eedf27e1998d87739fb5028a5120557c06a1a64"
|
||||||
integrity sha512-8jHII4hf+YVDsskTF6WuMB3X4Eh+PsUkC2ljq22so5rHvH+T8BzyL94VOdyFLNR8tBSVXOTbNHOKpR4TfRxVtA==
|
integrity sha512-8jHII4hf+YVDsskTF6WuMB3X4Eh+PsUkC2ljq22so5rHvH+T8BzyL94VOdyFLNR8tBSVXOTbNHOKpR4TfRxVtA==
|
||||||
@ -335,7 +335,7 @@
|
|||||||
globals "^11.1.0"
|
globals "^11.1.0"
|
||||||
lodash "^4.17.13"
|
lodash "^4.17.13"
|
||||||
|
|
||||||
"@babel/types@^7.0.0", "@babel/types@^7.3.0", "@babel/types@^7.3.3", "@babel/types@^7.4.4", "@babel/types@^7.5.5", "@babel/types@^7.8.3", "@babel/types@^7.8.6", "@babel/types@^7.9.0":
|
"@babel/types@^7.0.0", "@babel/types@^7.10.4", "@babel/types@^7.3.0", "@babel/types@^7.3.3", "@babel/types@^7.4.4", "@babel/types@^7.5.5", "@babel/types@^7.8.3", "@babel/types@^7.8.6", "@babel/types@^7.9.0":
|
||||||
version "7.10.4"
|
version "7.10.4"
|
||||||
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.10.4.tgz#369517188352e18219981efd156bfdb199fff1ee"
|
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.10.4.tgz#369517188352e18219981efd156bfdb199fff1ee"
|
||||||
integrity sha512-UTCFOxC3FsFHb7lkRMVvgLzaRVamXuAs2Tz4wajva4WxtVY82eZeaUBtC2Zt95FU9TiznuC0Zk35tsim8jeVpg==
|
integrity sha512-UTCFOxC3FsFHb7lkRMVvgLzaRVamXuAs2Tz4wajva4WxtVY82eZeaUBtC2Zt95FU9TiznuC0Zk35tsim8jeVpg==
|
||||||
@ -4648,6 +4648,13 @@ magic-string@^0.25.2, magic-string@^0.25.5:
|
|||||||
dependencies:
|
dependencies:
|
||||||
sourcemap-codec "^1.4.4"
|
sourcemap-codec "^1.4.4"
|
||||||
|
|
||||||
|
magic-string@^0.25.7:
|
||||||
|
version "0.25.7"
|
||||||
|
resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.25.7.tgz#3f497d6fd34c669c6798dcb821f2ef31f5445051"
|
||||||
|
integrity sha512-4CrMT5DOHTDk4HYDlzmwu4FVCcIYI8gauveasrdCu2IKIFOJ3f0v/8MDGJCDL9oD2ppz/Av1b0Nj345H9M+XIA==
|
||||||
|
dependencies:
|
||||||
|
sourcemap-codec "^1.4.4"
|
||||||
|
|
||||||
make-dir@^3.0.0:
|
make-dir@^3.0.0:
|
||||||
version "3.0.0"
|
version "3.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.0.0.tgz#1b5f39f6b9270ed33f9f054c5c0f84304989f801"
|
resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.0.0.tgz#1b5f39f6b9270ed33f9f054c5c0f84304989f801"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user