| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360 |
- import {
- BindingTypes,
- UNREF,
- isFunctionType,
- isStaticNode,
- unwrapTSNode,
- walkIdentifiers,
- } from '@vue/compiler-dom'
- import {
- DEFAULT_FILENAME,
- type SFCDescriptor,
- type SFCScriptBlock,
- } from './parse'
- import type { ParserPlugin } from '@babel/parser'
- import { generateCodeFrame } from '@vue/shared'
- import type {
- ArrayPattern,
- CallExpression,
- Declaration,
- ExportSpecifier,
- Identifier,
- LVal,
- Node,
- ObjectPattern,
- Statement,
- } from '@babel/types'
- import { walk } from 'estree-walker'
- import {
- type RawSourceMap,
- SourceMapConsumer,
- SourceMapGenerator,
- } from 'source-map-js'
- import {
- normalScriptDefaultVar,
- processNormalScript,
- } from './script/normalScript'
- import { CSS_VARS_HELPER, genCssVarsCode } from './style/cssVars'
- import {
- type SFCTemplateCompileOptions,
- compileTemplate,
- } from './compileTemplate'
- import { warnOnce } from './warn'
- import { transformDestructuredProps } from './script/definePropsDestructure'
- import { ScriptCompileContext } from './script/context'
- import {
- DEFINE_PROPS,
- WITH_DEFAULTS,
- genRuntimeProps,
- processDefineProps,
- } from './script/defineProps'
- import {
- DEFINE_EMITS,
- genRuntimeEmits,
- processDefineEmits,
- } from './script/defineEmits'
- import { DEFINE_EXPOSE, processDefineExpose } from './script/defineExpose'
- import { DEFINE_OPTIONS, processDefineOptions } from './script/defineOptions'
- import { DEFINE_SLOTS, processDefineSlots } from './script/defineSlots'
- import { DEFINE_MODEL, processDefineModel } from './script/defineModel'
- import { getImportedName, isCallOf, isLiteralNode } from './script/utils'
- import { analyzeScriptBindings } from './script/analyzeScriptBindings'
- import { isUsedInTemplate } from './script/importUsageCheck'
- import { processAwait } from './script/topLevelAwait'
- export interface SFCScriptCompileOptions {
- /**
- * Scope ID for prefixing injected CSS variables.
- * This must be consistent with the `id` passed to `compileStyle`.
- */
- id: string
- /**
- * Production mode. Used to determine whether to generate hashed CSS variables
- */
- isProd?: boolean
- /**
- * Enable/disable source map. Defaults to true.
- */
- sourceMap?: boolean
- /**
- * https://babeljs.io/docs/en/babel-parser#plugins
- */
- babelParserPlugins?: ParserPlugin[]
- /**
- * A list of files to parse for global types to be made available for type
- * resolving in SFC macros. The list must be fully resolved file system paths.
- */
- globalTypeFiles?: string[]
- /**
- * Compile the template and inline the resulting render function
- * directly inside setup().
- * - Only affects `<script setup>`
- * - This should only be used in production because it prevents the template
- * from being hot-reloaded separately from component state.
- */
- inlineTemplate?: boolean
- /**
- * Generate the final component as a variable instead of default export.
- * This is useful in e.g. @vitejs/plugin-vue where the script needs to be
- * placed inside the main module.
- */
- genDefaultAs?: string
- /**
- * Options for template compilation when inlining. Note these are options that
- * would normally be passed to `compiler-sfc`'s own `compileTemplate()`, not
- * options passed to `compiler-dom`.
- */
- templateOptions?: Partial<SFCTemplateCompileOptions>
- /**
- * Hoist <script setup> static constants.
- * - Only enables when one `<script setup>` exists.
- * @default true
- */
- hoistStatic?: boolean
- /**
- * Set to `false` to disable reactive destructure for `defineProps` (pre-3.5
- * behavior), or set to `'error'` to throw hard error on props destructures.
- * @default true
- */
- propsDestructure?: boolean | 'error'
- /**
- * File system access methods to be used when resolving types
- * imported in SFC macros. Defaults to ts.sys in Node.js, can be overwritten
- * to use a virtual file system for use in browsers (e.g. in REPLs)
- */
- fs?: {
- fileExists(file: string): boolean
- readFile(file: string): string | undefined
- realpath?(file: string): string
- }
- /**
- * Transform Vue SFCs into custom elements.
- */
- customElement?: boolean | ((filename: string) => boolean)
- /**
- * Force to use of Vapor mode.
- */
- vapor?: boolean
- }
- export interface ImportBinding {
- isType: boolean
- imported: string
- local: string
- source: string
- isFromSetup: boolean
- isUsedInTemplate: boolean
- }
- const MACROS = [
- DEFINE_PROPS,
- DEFINE_EMITS,
- DEFINE_EXPOSE,
- DEFINE_OPTIONS,
- DEFINE_SLOTS,
- DEFINE_MODEL,
- WITH_DEFAULTS,
- ]
- /**
- * 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 compileScript(
- sfc: SFCDescriptor,
- options: SFCScriptCompileOptions,
- ): SFCScriptBlock {
- if (!options.id) {
- warnOnce(
- `compileScript now requires passing the \`id\` option.\n` +
- `Upgrade your vite or vue-loader version for compatibility with ` +
- `the latest experimental proposals.`,
- )
- }
- const ctx = new ScriptCompileContext(sfc, options)
- const { script, scriptSetup, source, filename } = sfc
- const hoistStatic = options.hoistStatic !== false && !script
- const scopeId = options.id ? options.id.replace(/^data-v-/, '') : ''
- const scriptLang = script && script.lang
- const scriptSetupLang = scriptSetup && scriptSetup.lang
- const vapor = sfc.vapor || options.vapor
- const ssr = options.templateOptions?.ssr
- const setupPreambleLines = [] as string[]
- if (!scriptSetup) {
- if (!script) {
- throw new Error(`[@vue/compiler-sfc] SFC contains no <script> tags.`)
- }
- // normal <script> only
- return processNormalScript(ctx, scopeId)
- }
- if (script && scriptLang !== scriptSetupLang) {
- throw new Error(
- `[@vue/compiler-sfc] <script> and <script setup> must have the same ` +
- `language type.`,
- )
- }
- if (scriptSetupLang && !ctx.isJS && !ctx.isTS) {
- // do not process non js/ts script blocks
- return scriptSetup
- }
- // metadata that needs to be returned
- // const ctx.bindingMetadata: BindingMetadata = {}
- const scriptBindings: Record<string, BindingTypes> = Object.create(null)
- const setupBindings: Record<string, BindingTypes> = Object.create(null)
- let defaultExport: Node | undefined
- let hasAwait = false
- let hasInlinedSsrRenderFn = false
- // string offsets
- const startOffset = ctx.startOffset!
- const endOffset = ctx.endOffset!
- const scriptStartOffset = script && script.loc.start.offset
- const scriptEndOffset = script && script.loc.end.offset
- function hoistNode(node: Statement) {
- const start = node.start! + startOffset
- let end = node.end! + startOffset
- // locate comment
- if (node.trailingComments && node.trailingComments.length > 0) {
- const lastCommentNode =
- node.trailingComments[node.trailingComments.length - 1]
- end = lastCommentNode.end! + startOffset
- }
- // locate the end of whitespace between this statement and the next
- while (end <= source.length) {
- if (!/\s/.test(source.charAt(end))) {
- break
- }
- end++
- }
- ctx.s.move(start, end, 0)
- }
- function registerUserImport(
- source: string,
- local: string,
- imported: string,
- isType: boolean,
- isFromSetup: boolean,
- needTemplateUsageCheck: boolean,
- ) {
- // template usage check is only needed in non-inline mode, so we can skip
- // the work if inlineTemplate is true.
- let isImportUsed = needTemplateUsageCheck
- if (
- needTemplateUsageCheck &&
- ctx.isTS &&
- sfc.template &&
- !sfc.template.src &&
- !sfc.template.lang
- ) {
- isImportUsed = isUsedInTemplate(local, sfc)
- }
- ctx.userImports[local] = {
- isType,
- imported,
- local,
- source,
- isFromSetup,
- isUsedInTemplate: isImportUsed,
- }
- }
- function checkInvalidScopeReference(node: Node | undefined, method: string) {
- if (!node) return
- walkIdentifiers(node, id => {
- const binding = setupBindings[id.name]
- if (binding && binding !== BindingTypes.LITERAL_CONST) {
- ctx.error(
- `\`${method}()\` in <script setup> cannot reference locally ` +
- `declared variables because it will be hoisted outside of the ` +
- `setup() function. If your component options require initialization ` +
- `in the module scope, use a separate normal <script> to export ` +
- `the options instead.`,
- id,
- )
- }
- })
- }
- function buildDestructureElements() {
- if (!sfc.template || !sfc.template.ast) return
- const builtins = {
- $props: {
- bindingType: BindingTypes.SETUP_REACTIVE_CONST,
- setup: () => setupPreambleLines.push(`const $props = __props`),
- },
- $emit: {
- bindingType: BindingTypes.SETUP_CONST,
- setup: () =>
- ctx.emitDecl
- ? setupPreambleLines.push(`const $emit = __emit`)
- : destructureElements.push('emit: $emit'),
- },
- $attrs: {
- bindingType: BindingTypes.SETUP_REACTIVE_CONST,
- setup: () => destructureElements.push('attrs: $attrs'),
- },
- $slots: {
- bindingType: BindingTypes.SETUP_REACTIVE_CONST,
- setup: () => destructureElements.push('slots: $slots'),
- },
- }
- for (const [name, config] of Object.entries(builtins)) {
- if (isUsedInTemplate(name, sfc) && !ctx.bindingMetadata[name]) {
- config.setup()
- ctx.bindingMetadata[name] = config.bindingType
- }
- }
- }
- const scriptAst = ctx.scriptAst
- const scriptSetupAst = ctx.scriptSetupAst!
- const inlineMode = options.inlineTemplate
- // 1.1 walk import declarations of <script>
- if (scriptAst) {
- for (const node of scriptAst.body) {
- if (node.type === 'ImportDeclaration') {
- // record imports for dedupe
- for (const specifier of node.specifiers) {
- const imported = getImportedName(specifier)
- registerUserImport(
- node.source.value,
- specifier.local.name,
- imported,
- node.importKind === 'type' ||
- (specifier.type === 'ImportSpecifier' &&
- specifier.importKind === 'type'),
- false,
- !inlineMode,
- )
- }
- }
- }
- }
- // 1.2 walk import declarations of <script setup>
- for (const node of scriptSetupAst.body) {
- if (node.type === 'ImportDeclaration') {
- // import declarations are moved to top
- hoistNode(node)
- // dedupe imports
- let removed = 0
- const removeSpecifier = (i: number) => {
- const removeLeft = i > removed
- removed++
- const current = node.specifiers[i]
- const next = node.specifiers[i + 1]
- ctx.s.remove(
- removeLeft
- ? node.specifiers[i - 1].end! + startOffset
- : current.start! + startOffset,
- next && !removeLeft
- ? next.start! + startOffset
- : current.end! + startOffset,
- )
- }
- for (let i = 0; i < node.specifiers.length; i++) {
- const specifier = node.specifiers[i]
- const local = specifier.local.name
- const imported = getImportedName(specifier)
- const source = node.source.value
- const existing = ctx.userImports[local]
- if (source === 'vue' && MACROS.includes(imported)) {
- if (local === imported) {
- warnOnce(
- `\`${imported}\` is a compiler macro and no longer needs to be imported.`,
- )
- } else {
- ctx.error(
- `\`${imported}\` is a compiler macro and cannot be aliased to ` +
- `a different name.`,
- specifier,
- )
- }
- removeSpecifier(i)
- } else if (existing) {
- if (existing.source === source && existing.imported === imported) {
- // already imported in <script setup>, dedupe
- removeSpecifier(i)
- } else {
- ctx.error(
- `different imports aliased to same local name.`,
- specifier,
- )
- }
- } else {
- registerUserImport(
- source,
- local,
- imported,
- node.importKind === 'type' ||
- (specifier.type === 'ImportSpecifier' &&
- specifier.importKind === 'type'),
- true,
- !inlineMode,
- )
- }
- }
- if (node.specifiers.length && removed === node.specifiers.length) {
- ctx.s.remove(node.start! + startOffset, node.end! + startOffset)
- }
- }
- }
- // 1.3 resolve possible user import alias of `ref` and `reactive`
- const vueImportAliases: Record<string, string> = {}
- for (const key in ctx.userImports) {
- const { source, imported, local } = ctx.userImports[key]
- if (source === 'vue') vueImportAliases[imported] = local
- }
- // 2.1 process normal <script> body
- if (script && scriptAst) {
- for (const node of scriptAst.body) {
- if (node.type === 'ExportDefaultDeclaration') {
- // export default
- defaultExport = node
- // check if user has manually specified `name` or 'render` option in
- // export default
- // if has name, skip name inference
- // if has render and no template, generate return object instead of
- // empty render function (#4980)
- let optionProperties
- if (defaultExport.declaration.type === 'ObjectExpression') {
- optionProperties = defaultExport.declaration.properties
- } else if (
- defaultExport.declaration.type === 'CallExpression' &&
- defaultExport.declaration.arguments[0] &&
- defaultExport.declaration.arguments[0].type === 'ObjectExpression'
- ) {
- optionProperties = defaultExport.declaration.arguments[0].properties
- }
- if (optionProperties) {
- for (const p of optionProperties) {
- if (
- p.type === 'ObjectProperty' &&
- p.key.type === 'Identifier' &&
- p.key.name === 'name'
- ) {
- ctx.hasDefaultExportName = true
- }
- if (
- (p.type === 'ObjectMethod' || p.type === 'ObjectProperty') &&
- p.key.type === 'Identifier' &&
- p.key.name === 'render'
- ) {
- // TODO warn when we provide a better way to do it?
- ctx.hasDefaultExportRender = true
- }
- }
- }
- // export default { ... } --> const __default__ = { ... }
- const start = node.start! + scriptStartOffset!
- const end = node.declaration.start! + scriptStartOffset!
- ctx.s.overwrite(start, end, `const ${normalScriptDefaultVar} = `)
- } else if (node.type === 'ExportNamedDeclaration') {
- const defaultSpecifier = node.specifiers.find(
- s =>
- s.exported.type === 'Identifier' && s.exported.name === 'default',
- ) as ExportSpecifier
- if (defaultSpecifier) {
- defaultExport = node
- // 1. remove specifier
- if (node.specifiers.length > 1) {
- ctx.s.remove(
- defaultSpecifier.start! + scriptStartOffset!,
- defaultSpecifier.end! + scriptStartOffset!,
- )
- } else {
- ctx.s.remove(
- node.start! + scriptStartOffset!,
- node.end! + scriptStartOffset!,
- )
- }
- if (node.source) {
- // export { x as default } from './x'
- // rewrite to `import { x as __default__ } from './x'` and
- // add to top
- ctx.s.prepend(
- `import { ${defaultSpecifier.local.name} as ${normalScriptDefaultVar} } from '${node.source.value}'\n`,
- )
- } else {
- // export { x as default }
- // rewrite to `const __default__ = x` and move to end
- ctx.s.appendLeft(
- scriptEndOffset!,
- `\nconst ${normalScriptDefaultVar} = ${defaultSpecifier.local.name}\n`,
- )
- }
- }
- if (node.declaration) {
- walkDeclaration(
- 'script',
- node.declaration,
- scriptBindings,
- vueImportAliases,
- hoistStatic,
- )
- }
- } else if (
- (node.type === 'VariableDeclaration' ||
- node.type === 'FunctionDeclaration' ||
- node.type === 'ClassDeclaration' ||
- node.type === 'TSEnumDeclaration') &&
- !node.declare
- ) {
- walkDeclaration(
- 'script',
- node,
- scriptBindings,
- vueImportAliases,
- hoistStatic,
- )
- }
- }
- // <script> after <script setup>
- // we need to move the block up so that `const __default__` is
- // declared before being used in the actual component definition
- if (scriptStartOffset! > startOffset) {
- // if content doesn't end with newline, add one
- if (!/\n$/.test(script.content.trim())) {
- ctx.s.appendLeft(scriptEndOffset!, `\n`)
- }
- ctx.s.move(scriptStartOffset!, scriptEndOffset!, 0)
- }
- }
- // 2.2 process <script setup> body
- for (const node of scriptSetupAst.body) {
- if (node.type === 'ExpressionStatement') {
- const expr = unwrapTSNode(node.expression)
- // process `defineProps` and `defineEmit(s)` calls
- if (
- processDefineProps(ctx, expr) ||
- processDefineEmits(ctx, expr) ||
- processDefineOptions(ctx, expr) ||
- processDefineSlots(ctx, expr)
- ) {
- ctx.s.remove(node.start! + startOffset, node.end! + startOffset)
- } else if (processDefineExpose(ctx, expr)) {
- // defineExpose({}) -> expose({})
- const callee = (expr as CallExpression).callee
- ctx.s.overwrite(
- callee.start! + startOffset,
- callee.end! + startOffset,
- '__expose',
- )
- } else {
- processDefineModel(ctx, expr)
- }
- }
- if (node.type === 'VariableDeclaration' && !node.declare) {
- const total = node.declarations.length
- let left = total
- let lastNonRemoved: number | undefined
- for (let i = 0; i < total; i++) {
- const decl = node.declarations[i]
- const init = decl.init && unwrapTSNode(decl.init)
- if (init) {
- if (processDefineOptions(ctx, init)) {
- ctx.error(
- `${DEFINE_OPTIONS}() has no returning value, it cannot be assigned.`,
- node,
- )
- }
- // defineProps
- const isDefineProps = processDefineProps(ctx, init, decl.id as LVal)
- if (ctx.propsDestructureRestId) {
- setupBindings[ctx.propsDestructureRestId] =
- BindingTypes.SETUP_REACTIVE_CONST
- }
- // defineEmits
- const isDefineEmits =
- !isDefineProps && processDefineEmits(ctx, init, decl.id as LVal)
- !isDefineEmits &&
- (processDefineSlots(ctx, init, decl.id as LVal) ||
- processDefineModel(ctx, init, decl.id as LVal))
- if (
- isDefineProps &&
- !ctx.propsDestructureRestId &&
- ctx.propsDestructureDecl
- ) {
- if (left === 1) {
- ctx.s.remove(node.start! + startOffset, node.end! + startOffset)
- } else {
- let start = decl.start! + startOffset
- let end = decl.end! + startOffset
- if (i === total - 1) {
- // last one, locate the end of the last one that is not removed
- // if we arrive at this branch, there must have been a
- // non-removed decl before us, so lastNonRemoved is non-null.
- start = node.declarations[lastNonRemoved!].end! + startOffset
- } else {
- // not the last one, locate the start of the next
- end = node.declarations[i + 1].start! + startOffset
- }
- ctx.s.remove(start, end)
- left--
- }
- } else if (isDefineEmits) {
- ctx.s.overwrite(
- startOffset + init.start!,
- startOffset + init.end!,
- '__emit',
- )
- } else {
- lastNonRemoved = i
- }
- }
- }
- }
- let isAllLiteral = false
- // walk declarations to record declared bindings
- if (
- (node.type === 'VariableDeclaration' ||
- node.type === 'FunctionDeclaration' ||
- node.type === 'ClassDeclaration' ||
- node.type === 'TSEnumDeclaration') &&
- !node.declare
- ) {
- isAllLiteral = walkDeclaration(
- 'scriptSetup',
- node,
- setupBindings,
- vueImportAliases,
- hoistStatic,
- !!ctx.propsDestructureDecl,
- )
- }
- // hoist literal constants
- if (hoistStatic && isAllLiteral) {
- hoistNode(node)
- }
- // walk statements & named exports / variable declarations for top level
- // await
- if (
- (node.type === 'VariableDeclaration' && !node.declare) ||
- node.type.endsWith('Statement')
- ) {
- const scope: Statement[][] = [scriptSetupAst.body]
- walk(node, {
- enter(child: Node, parent: Node | null) {
- if (isFunctionType(child)) {
- this.skip()
- }
- if (child.type === 'BlockStatement') {
- scope.push(child.body)
- }
- if (child.type === 'AwaitExpression') {
- hasAwait = true
- // if the await expression is an expression statement and
- // - is in the root scope
- // - or is not the first statement in a nested block scope
- // then it needs a semicolon before the generated code.
- const currentScope = scope[scope.length - 1]
- const needsSemi = currentScope.some((n, i) => {
- return (
- (scope.length === 1 || i > 0) &&
- n.type === 'ExpressionStatement' &&
- n.start === child.start
- )
- })
- processAwait(
- ctx,
- child,
- needsSemi,
- parent!.type === 'ExpressionStatement',
- )
- }
- },
- exit(node: Node) {
- if (node.type === 'BlockStatement') scope.pop()
- },
- })
- }
- if (
- (node.type === 'ExportNamedDeclaration' && node.exportKind !== 'type') ||
- node.type === 'ExportAllDeclaration' ||
- node.type === 'ExportDefaultDeclaration'
- ) {
- ctx.error(
- `<script setup> cannot contain ES module exports. ` +
- `If you are using a previous version of <script setup>, please ` +
- `consult the updated RFC at https://github.com/vuejs/rfcs/pull/227.`,
- node,
- )
- }
- if (ctx.isTS) {
- // move all Type declarations to outer scope
- if (
- node.type.startsWith('TS') ||
- (node.type === 'ExportNamedDeclaration' &&
- node.exportKind === 'type') ||
- (node.type === 'VariableDeclaration' && node.declare)
- ) {
- if (node.type !== 'TSEnumDeclaration') {
- hoistNode(node)
- }
- }
- }
- }
- // 3 props destructure transform
- if (ctx.propsDestructureDecl) {
- transformDestructuredProps(ctx, vueImportAliases)
- }
- // 4. check macro args to make sure it doesn't reference setup scope
- // variables
- checkInvalidScopeReference(ctx.propsRuntimeDecl, DEFINE_PROPS)
- checkInvalidScopeReference(ctx.propsRuntimeDefaults, DEFINE_PROPS)
- checkInvalidScopeReference(ctx.propsDestructureDecl, DEFINE_PROPS)
- checkInvalidScopeReference(ctx.emitsRuntimeDecl, DEFINE_EMITS)
- checkInvalidScopeReference(ctx.optionsRuntimeDecl, DEFINE_OPTIONS)
- for (const { runtimeOptionNodes } of Object.values(ctx.modelDecls)) {
- for (const node of runtimeOptionNodes) {
- checkInvalidScopeReference(node, DEFINE_MODEL)
- }
- }
- // 5. remove non-script content
- if (script) {
- if (startOffset < scriptStartOffset!) {
- // <script setup> before <script>
- ctx.s.remove(0, startOffset)
- ctx.s.remove(endOffset, scriptStartOffset!)
- ctx.s.remove(scriptEndOffset!, source.length)
- } else {
- // <script> before <script setup>
- ctx.s.remove(0, scriptStartOffset!)
- ctx.s.remove(scriptEndOffset!, startOffset)
- ctx.s.remove(endOffset, source.length)
- }
- } else {
- // only <script setup>
- ctx.s.remove(0, startOffset)
- ctx.s.remove(endOffset, source.length)
- }
- // 6. analyze binding metadata
- // `defineProps` & `defineModel` also register props bindings
- if (scriptAst) {
- Object.assign(ctx.bindingMetadata, analyzeScriptBindings(scriptAst.body))
- }
- for (const [key, { isType, imported, source }] of Object.entries(
- ctx.userImports,
- )) {
- if (isType) continue
- ctx.bindingMetadata[key] =
- imported === '*' ||
- (imported === 'default' && source.endsWith('.vue')) ||
- source === 'vue'
- ? BindingTypes.SETUP_CONST
- : BindingTypes.SETUP_MAYBE_REF
- }
- for (const key in scriptBindings) {
- ctx.bindingMetadata[key] = scriptBindings[key]
- }
- for (const key in setupBindings) {
- ctx.bindingMetadata[key] = setupBindings[key]
- }
- // 7. inject `useCssVars` calls
- if (
- sfc.cssVars.length &&
- // no need to do this when targeting SSR
- !ssr
- ) {
- ctx.helperImports.add(CSS_VARS_HELPER)
- ctx.helperImports.add('unref')
- ctx.s.prependLeft(
- startOffset,
- `\n${genCssVarsCode(
- sfc.cssVars,
- ctx.bindingMetadata,
- scopeId,
- !!options.isProd,
- )}\n`,
- )
- }
- // 8. finalize setup() argument signature
- let args = `__props`
- if (ctx.propsTypeDecl) {
- // mark as any and only cast on assignment
- // since the user defined complex types may be incompatible with the
- // inferred type from generated runtime declarations
- args += `: any`
- }
- // inject user assignment of props
- // we use a default __props so that template expressions referencing props
- // can use it directly
- if (ctx.propsDecl) {
- if (ctx.propsDestructureRestId) {
- ctx.s.overwrite(
- startOffset + ctx.propsCall!.start!,
- startOffset + ctx.propsCall!.end!,
- `${ctx.helper(`createPropsRestProxy`)}(__props, ${JSON.stringify(
- Object.keys(ctx.propsDestructuredBindings),
- )})`,
- )
- ctx.s.overwrite(
- startOffset + ctx.propsDestructureDecl!.start!,
- startOffset + ctx.propsDestructureDecl!.end!,
- ctx.propsDestructureRestId,
- )
- } else if (!ctx.propsDestructureDecl) {
- ctx.s.overwrite(
- startOffset + ctx.propsCall!.start!,
- startOffset + ctx.propsCall!.end!,
- '__props',
- )
- }
- }
- // inject temp variables for async context preservation
- if (hasAwait) {
- const any = ctx.isTS ? `: any` : ``
- ctx.s.prependLeft(startOffset, `\nlet __temp${any}, __restore${any}\n`)
- }
- const destructureElements =
- ctx.hasDefineExposeCall || !inlineMode ? [`expose: __expose`] : []
- if (ctx.emitDecl) {
- destructureElements.push(`emit: __emit`)
- }
- // destructure built-in properties (e.g. $emit, $attrs, $slots)
- if (inlineMode) {
- buildDestructureElements()
- }
- if (destructureElements.length) {
- args += `, { ${destructureElements.join(', ')} }`
- }
- let templateMap
- // 9. generate return statement
- let returned
- if (!inlineMode || (!sfc.template && ctx.hasDefaultExportRender)) {
- // non-inline mode, or has manual render in normal <script>
- // return bindings from script and script setup
- const allBindings: Record<string, any> = {
- ...scriptBindings,
- ...setupBindings,
- }
- for (const key in ctx.userImports) {
- if (
- !ctx.userImports[key].isType &&
- ctx.userImports[key].isUsedInTemplate
- ) {
- allBindings[key] = true
- }
- }
- returned = `{ `
- for (const key in allBindings) {
- if (
- allBindings[key] === true &&
- ctx.userImports[key].source !== 'vue' &&
- !ctx.userImports[key].source.endsWith('.vue')
- ) {
- // generate getter for import bindings
- // skip vue imports since we know they will never change
- returned += `get ${key}() { return ${key} }, `
- } else if (ctx.bindingMetadata[key] === BindingTypes.SETUP_LET) {
- // local let binding, also add setter
- const setArg = key === 'v' ? `_v` : `v`
- returned +=
- `get ${key}() { return ${key} }, ` +
- `set ${key}(${setArg}) { ${key} = ${setArg} }, `
- } else {
- returned += `${key}, `
- }
- }
- returned = returned.replace(/, $/, '') + ` }`
- } else {
- // inline mode
- if (sfc.template && !sfc.template.src) {
- if (ssr) {
- hasInlinedSsrRenderFn = true
- }
- // inline render function mode - we are going to compile the template and
- // inline it right here
- const { code, preamble, tips, errors, helpers, map } = compileTemplate({
- filename,
- ast: sfc.template.ast,
- source: sfc.template.content,
- inMap: sfc.template.map,
- ...options.templateOptions,
- id: scopeId,
- scoped: sfc.styles.some(s => s.scoped),
- isProd: options.isProd,
- ssrCssVars: sfc.cssVars,
- vapor,
- compilerOptions: {
- ...(options.templateOptions &&
- options.templateOptions.compilerOptions),
- inline: true,
- isTS: ctx.isTS,
- bindingMetadata: ctx.bindingMetadata,
- },
- })
- templateMap = map
- if (tips.length) {
- tips.forEach(warnOnce)
- }
- const err = errors[0]
- if (typeof err === 'string') {
- throw new Error(err)
- } else if (err) {
- if (err.loc) {
- err.message +=
- `\n\n` +
- sfc.filename +
- '\n' +
- generateCodeFrame(
- source,
- err.loc.start.offset,
- err.loc.end.offset,
- ) +
- `\n`
- }
- throw err
- }
- if (preamble) {
- ctx.s.prepend(preamble)
- }
- // avoid duplicated unref import
- // as this may get injected by the render function preamble OR the
- // css vars codegen
- if (helpers && helpers.has(UNREF)) {
- ctx.helperImports.delete('unref')
- }
- returned = code
- } else {
- returned = `() => {}`
- }
- }
- if (!inlineMode && !__TEST__) {
- // in non-inline mode, the `__isScriptSetup: true` flag is used by
- // componentPublicInstance proxy to allow properties that start with $ or _
- ctx.s.appendRight(
- endOffset,
- `\nconst __returned__ = ${returned}\n` +
- `Object.defineProperty(__returned__, '__isScriptSetup', { enumerable: false, value: true })\n` +
- `return __returned__` +
- `\n}\n\n`,
- )
- } else {
- ctx.s.appendRight(
- endOffset,
- // vapor mode generates its own return when inlined
- `\n${vapor && !ssr ? `` : `return `}${returned}\n}\n\n`,
- )
- }
- // 10. finalize default export
- const genDefaultAs = options.genDefaultAs
- ? `const ${options.genDefaultAs} =`
- : `export default`
- let runtimeOptions = ``
- if (!ctx.hasDefaultExportName && filename && filename !== DEFAULT_FILENAME) {
- const match = filename.match(/([^/\\]+)\.\w+$/)
- if (match) {
- runtimeOptions += `\n __name: '${match[1]}',`
- }
- }
- if (hasInlinedSsrRenderFn) {
- runtimeOptions += `\n __ssrInlineRender: true,`
- }
- const propsDecl = genRuntimeProps(ctx)
- if (propsDecl) runtimeOptions += `\n props: ${propsDecl},`
- const emitsDecl = genRuntimeEmits(ctx)
- if (emitsDecl) runtimeOptions += `\n emits: ${emitsDecl},`
- let definedOptions = ''
- if (ctx.optionsRuntimeDecl) {
- definedOptions = scriptSetup.content
- .slice(ctx.optionsRuntimeDecl.start!, ctx.optionsRuntimeDecl.end!)
- .trim()
- }
- // <script setup> components are closed by default. If the user did not
- // explicitly call `defineExpose`, call expose() with no args.
- if (!ctx.hasDefineExposeCall && !inlineMode)
- setupPreambleLines.push(`__expose();`)
- const setupPreamble = setupPreambleLines.length
- ? ` ${setupPreambleLines.join('\n ')}\n`
- : ''
- // wrap setup code with function.
- if (ctx.isTS) {
- // for TS, make sure the exported type is still valid type with
- // correct props information
- // we have to use object spread for types to be merged properly
- // user's TS setting should compile it down to proper targets
- // export default defineComponent({ ...__default__, ... })
- const def =
- (defaultExport ? `\n ...${normalScriptDefaultVar},` : ``) +
- (definedOptions ? `\n ...${definedOptions},` : '')
- ctx.s.prependLeft(
- startOffset,
- `\n${genDefaultAs} /*@__PURE__*/${ctx.helper(
- vapor && !ssr ? `defineVaporComponent` : `defineComponent`,
- )}({${def}${runtimeOptions}\n ${
- hasAwait ? `async ` : ``
- }setup(${args}) {\n${setupPreamble}`,
- )
- ctx.s.appendRight(endOffset, `})`)
- } else {
- // in TS, defineVaporComponent adds the option already
- if (vapor) {
- runtimeOptions += `\n __vapor: true,`
- }
- if (defaultExport || definedOptions) {
- // without TS, can't rely on rest spread, so we use Object.assign
- // export default Object.assign(__default__, { ... })
- ctx.s.prependLeft(
- startOffset,
- `\n${genDefaultAs} /*@__PURE__*/Object.assign(${
- defaultExport ? `${normalScriptDefaultVar}, ` : ''
- }${definedOptions ? `${definedOptions}, ` : ''}{${runtimeOptions}\n ` +
- `${hasAwait ? `async ` : ``}setup(${args}) {\n${setupPreamble}`,
- )
- ctx.s.appendRight(endOffset, `})`)
- } else {
- ctx.s.prependLeft(
- startOffset,
- `\n${genDefaultAs} {${runtimeOptions}\n ` +
- `${hasAwait ? `async ` : ``}setup(${args}) {\n${setupPreamble}`,
- )
- ctx.s.appendRight(endOffset, `}`)
- }
- }
- // 11. finalize Vue helper imports
- if (ctx.helperImports.size > 0) {
- const runtimeModuleName =
- options.templateOptions?.compilerOptions?.runtimeModuleName
- const importSrc = runtimeModuleName
- ? JSON.stringify(runtimeModuleName)
- : `'vue'`
- ctx.s.prepend(
- `import { ${[...ctx.helperImports]
- .map(h => `${h} as _${h}`)
- .join(', ')} } from ${importSrc}\n`,
- )
- }
- const content = ctx.s.toString()
- let map =
- options.sourceMap !== false
- ? (ctx.s.generateMap({
- source: filename,
- hires: true,
- includeContent: true,
- }) as unknown as RawSourceMap)
- : undefined
- // merge source maps of the script setup and template in inline mode
- if (templateMap && map) {
- const offset = content.indexOf(returned)
- const templateLineOffset =
- content.slice(0, offset).split(/\r?\n/).length - 1
- map = mergeSourceMaps(map, templateMap, templateLineOffset)
- }
- return {
- ...scriptSetup,
- bindings: ctx.bindingMetadata,
- imports: ctx.userImports,
- content,
- map,
- scriptAst: scriptAst?.body,
- scriptSetupAst: scriptSetupAst?.body,
- deps: ctx.deps ? [...ctx.deps] : undefined,
- }
- }
- function registerBinding(
- bindings: Record<string, BindingTypes>,
- node: Identifier,
- type: BindingTypes,
- ) {
- bindings[node.name] = type
- }
- function walkDeclaration(
- from: 'script' | 'scriptSetup',
- node: Declaration,
- bindings: Record<string, BindingTypes>,
- userImportAliases: Record<string, string>,
- hoistStatic: boolean,
- isPropsDestructureEnabled = false,
- ): boolean {
- let isAllLiteral = false
- if (node.type === 'VariableDeclaration') {
- const isConst = node.kind === 'const'
- isAllLiteral =
- isConst &&
- node.declarations.every(
- decl => decl.id.type === 'Identifier' && isStaticNode(decl.init!),
- )
- // export const foo = ...
- for (const { id, init: _init } of node.declarations) {
- const init = _init && unwrapTSNode(_init)
- const isConstMacroCall =
- isConst &&
- isCallOf(
- init,
- c =>
- c === DEFINE_PROPS ||
- c === DEFINE_EMITS ||
- c === WITH_DEFAULTS ||
- c === DEFINE_SLOTS,
- )
- if (id.type === 'Identifier') {
- let bindingType
- const userReactiveBinding = userImportAliases['reactive']
- if (
- (hoistStatic || from === 'script') &&
- (isAllLiteral || (isConst && isStaticNode(init!)))
- ) {
- bindingType = BindingTypes.LITERAL_CONST
- } else if (isCallOf(init, userReactiveBinding)) {
- // treat reactive() calls as let since it's meant to be mutable
- bindingType = isConst
- ? BindingTypes.SETUP_REACTIVE_CONST
- : BindingTypes.SETUP_LET
- } else if (
- // if a declaration is a const literal, we can mark it so that
- // the generated render fn code doesn't need to unref() it
- isConstMacroCall ||
- (isConst && canNeverBeRef(init!, userReactiveBinding))
- ) {
- bindingType = isCallOf(init, DEFINE_PROPS)
- ? BindingTypes.SETUP_REACTIVE_CONST
- : BindingTypes.SETUP_CONST
- } else if (isConst) {
- if (
- isCallOf(
- init,
- m =>
- m === userImportAliases['ref'] ||
- m === userImportAliases['computed'] ||
- m === userImportAliases['shallowRef'] ||
- m === userImportAliases['customRef'] ||
- m === userImportAliases['toRef'] ||
- m === userImportAliases['useTemplateRef'] ||
- m === DEFINE_MODEL,
- )
- ) {
- bindingType = BindingTypes.SETUP_REF
- } else {
- bindingType = BindingTypes.SETUP_MAYBE_REF
- }
- } else {
- bindingType = BindingTypes.SETUP_LET
- }
- registerBinding(bindings, id, bindingType)
- } else {
- if (isCallOf(init, DEFINE_PROPS) && isPropsDestructureEnabled) {
- continue
- }
- if (id.type === 'ObjectPattern') {
- walkObjectPattern(id, bindings, isConst, isConstMacroCall)
- } else if (id.type === 'ArrayPattern') {
- walkArrayPattern(id, bindings, isConst, isConstMacroCall)
- }
- }
- }
- } else if (node.type === 'TSEnumDeclaration') {
- isAllLiteral = node.members.every(
- member => !member.initializer || isStaticNode(member.initializer),
- )
- bindings[node.id!.name] = isAllLiteral
- ? BindingTypes.LITERAL_CONST
- : BindingTypes.SETUP_CONST
- } else if (
- node.type === 'FunctionDeclaration' ||
- node.type === 'ClassDeclaration'
- ) {
- // export function foo() {} / export class Foo {}
- // export declarations must be named.
- bindings[node.id!.name] = BindingTypes.SETUP_CONST
- }
- return isAllLiteral
- }
- function walkObjectPattern(
- node: ObjectPattern,
- bindings: Record<string, BindingTypes>,
- isConst: boolean,
- isDefineCall = false,
- ) {
- for (const p of node.properties) {
- if (p.type === 'ObjectProperty') {
- if (p.key.type === 'Identifier' && p.key === p.value) {
- // shorthand: const { x } = ...
- const type = isDefineCall
- ? BindingTypes.SETUP_CONST
- : isConst
- ? BindingTypes.SETUP_MAYBE_REF
- : BindingTypes.SETUP_LET
- registerBinding(bindings, p.key, type)
- } else {
- walkPattern(p.value, bindings, isConst, isDefineCall)
- }
- } else {
- // ...rest
- // argument can only be identifier when destructuring
- const type = isConst ? BindingTypes.SETUP_CONST : BindingTypes.SETUP_LET
- registerBinding(bindings, p.argument as Identifier, type)
- }
- }
- }
- function walkArrayPattern(
- node: ArrayPattern,
- bindings: Record<string, BindingTypes>,
- isConst: boolean,
- isDefineCall = false,
- ) {
- for (const e of node.elements) {
- e && walkPattern(e, bindings, isConst, isDefineCall)
- }
- }
- function walkPattern(
- node: Node,
- bindings: Record<string, BindingTypes>,
- isConst: boolean,
- isDefineCall = false,
- ) {
- if (node.type === 'Identifier') {
- const type = isDefineCall
- ? BindingTypes.SETUP_CONST
- : isConst
- ? BindingTypes.SETUP_MAYBE_REF
- : BindingTypes.SETUP_LET
- registerBinding(bindings, node, type)
- } else if (node.type === 'RestElement') {
- // argument can only be identifier when destructuring
- const type = isConst ? BindingTypes.SETUP_CONST : BindingTypes.SETUP_LET
- registerBinding(bindings, node.argument as Identifier, type)
- } else if (node.type === 'ObjectPattern') {
- walkObjectPattern(node, bindings, isConst)
- } else if (node.type === 'ArrayPattern') {
- walkArrayPattern(node, bindings, isConst)
- } else if (node.type === 'AssignmentPattern') {
- if (node.left.type === 'Identifier') {
- const type = isDefineCall
- ? BindingTypes.SETUP_CONST
- : isConst
- ? BindingTypes.SETUP_MAYBE_REF
- : BindingTypes.SETUP_LET
- registerBinding(bindings, node.left, type)
- } else {
- walkPattern(node.left, bindings, isConst)
- }
- }
- }
- function canNeverBeRef(node: Node, userReactiveImport?: string): boolean {
- if (isCallOf(node, userReactiveImport)) {
- return true
- }
- switch (node.type) {
- case 'UnaryExpression':
- case 'BinaryExpression':
- case 'ArrayExpression':
- case 'ObjectExpression':
- case 'FunctionExpression':
- case 'ArrowFunctionExpression':
- case 'UpdateExpression':
- case 'ClassExpression':
- case 'TaggedTemplateExpression':
- return true
- case 'SequenceExpression':
- return canNeverBeRef(
- node.expressions[node.expressions.length - 1],
- userReactiveImport,
- )
- default:
- if (isLiteralNode(node)) {
- return true
- }
- return false
- }
- }
- export function mergeSourceMaps(
- scriptMap: RawSourceMap,
- templateMap: RawSourceMap,
- templateLineOffset: number,
- ): RawSourceMap {
- const generator = new SourceMapGenerator()
- const addMapping = (map: RawSourceMap, lineOffset = 0) => {
- const consumer = new SourceMapConsumer(map)
- ;(consumer as any).sources.forEach((sourceFile: string) => {
- ;(generator as any)._sources.add(sourceFile)
- const sourceContent = consumer.sourceContentFor(sourceFile)
- if (sourceContent != null) {
- generator.setSourceContent(sourceFile, sourceContent)
- }
- })
- consumer.eachMapping(m => {
- if (m.originalLine == null) return
- generator.addMapping({
- generated: {
- line: m.generatedLine + lineOffset,
- column: m.generatedColumn,
- },
- original: {
- line: m.originalLine,
- column: m.originalColumn!,
- },
- source: m.source,
- name: m.name,
- })
- })
- }
- addMapping(scriptMap)
- addMapping(templateMap, templateLineOffset)
- ;(generator as any)._sourceRoot = scriptMap.sourceRoot
- ;(generator as any)._file = scriptMap.file
- return (generator as any).toJSON()
- }
|