| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114 |
- import type { CodegenOptions } from './options'
- import {
- type ArrayExpression,
- type AssignmentExpression,
- type CacheExpression,
- type CallExpression,
- type CommentNode,
- type CompoundExpressionNode,
- type ConditionalExpression,
- type ExpressionNode,
- type FunctionExpression,
- type IfStatement,
- type InterpolationNode,
- type JSChildNode,
- NodeTypes,
- type ObjectExpression,
- type Position,
- type ReturnStatement,
- type RootNode,
- type SSRCodegenNode,
- type SequenceExpression,
- type SimpleExpressionNode,
- type TemplateChildNode,
- type TemplateLiteral,
- type TextNode,
- type VNodeCall,
- getVNodeBlockHelper,
- getVNodeHelper,
- locStub,
- } from './ast'
- import { SourceMapGenerator } from 'source-map-js'
- import {
- advancePositionWithMutation,
- assert,
- isSimpleIdentifier,
- toValidAssetId,
- } from './utils'
- import {
- PatchFlagNames,
- type PatchFlags,
- isArray,
- isString,
- isSymbol,
- } from '@vue/shared'
- import {
- CREATE_COMMENT,
- CREATE_ELEMENT_VNODE,
- CREATE_STATIC,
- CREATE_TEXT,
- CREATE_VNODE,
- OPEN_BLOCK,
- RESOLVE_COMPONENT,
- RESOLVE_DIRECTIVE,
- RESOLVE_FILTER,
- SET_BLOCK_TRACKING,
- TO_DISPLAY_STRING,
- WITH_CTX,
- WITH_DIRECTIVES,
- helperNameMap,
- } from './runtimeHelpers'
- import type { ImportItem } from './transform'
- /**
- * The `SourceMapGenerator` type from `source-map-js` is a bit incomplete as it
- * misses `toJSON()`. We also need to add types for internal properties which we
- * need to access for better performance.
- *
- * Since TS 5.3, dts generation starts to strangely include broken triple slash
- * references for source-map-js, so we are inlining all source map related types
- * here to to workaround that.
- */
- export interface CodegenSourceMapGenerator {
- setSourceContent(sourceFile: string, sourceContent: string): void
- // SourceMapGenerator has this method but the types do not include it
- toJSON(): RawSourceMap
- _sources: Set<string>
- _names: Set<string>
- _mappings: {
- add(mapping: MappingItem): void
- }
- }
- export interface RawSourceMap {
- file?: string
- sourceRoot?: string
- version: string
- sources: string[]
- names: string[]
- sourcesContent?: string[]
- mappings: string
- }
- interface MappingItem {
- source: string
- generatedLine: number
- generatedColumn: number
- originalLine: number
- originalColumn: number
- name: string | null
- }
- const PURE_ANNOTATION = `/*@__PURE__*/`
- const aliasHelper = (s: symbol) => `${helperNameMap[s]}: _${helperNameMap[s]}`
- type CodegenNode = TemplateChildNode | JSChildNode | SSRCodegenNode
- export interface CodegenResult {
- code: string
- preamble: string
- ast: RootNode
- map?: RawSourceMap
- }
- enum NewlineType {
- Start = 0,
- End = -1,
- None = -2,
- Unknown = -3,
- }
- export interface CodegenContext
- extends Omit<Required<CodegenOptions>, 'bindingMetadata' | 'inline'> {
- source: string
- code: string
- line: number
- column: number
- offset: number
- indentLevel: number
- pure: boolean
- map?: CodegenSourceMapGenerator
- helper(key: symbol): string
- push(code: string, newlineIndex?: number, node?: CodegenNode): void
- indent(): void
- deindent(withoutNewLine?: boolean): void
- newline(): void
- }
- function createCodegenContext(
- ast: RootNode,
- {
- mode = 'function',
- prefixIdentifiers = mode === 'module',
- sourceMap = false,
- filename = `template.vue.html`,
- scopeId = null,
- optimizeImports = false,
- runtimeGlobalName = `Vue`,
- runtimeModuleName = `vue`,
- ssrRuntimeModuleName = 'vue/server-renderer',
- ssr = false,
- isTS = false,
- inSSR = false,
- }: CodegenOptions,
- ): CodegenContext {
- const context: CodegenContext = {
- mode,
- prefixIdentifiers,
- sourceMap,
- filename,
- scopeId,
- optimizeImports,
- runtimeGlobalName,
- runtimeModuleName,
- ssrRuntimeModuleName,
- ssr,
- isTS,
- inSSR,
- source: ast.source,
- code: ``,
- column: 1,
- line: 1,
- offset: 0,
- indentLevel: 0,
- pure: false,
- map: undefined,
- helper(key) {
- return `_${helperNameMap[key]}`
- },
- push(code, newlineIndex = NewlineType.None, node) {
- context.code += code
- if (!__BROWSER__ && context.map) {
- if (node) {
- let name
- if (node.type === NodeTypes.SIMPLE_EXPRESSION && !node.isStatic) {
- const content = node.content.replace(/^_ctx\./, '')
- if (content !== node.content && isSimpleIdentifier(content)) {
- name = content
- }
- }
- addMapping(node.loc.start, name)
- }
- if (newlineIndex === NewlineType.Unknown) {
- // multiple newlines, full iteration
- advancePositionWithMutation(context, code)
- } else {
- // fast paths
- context.offset += code.length
- if (newlineIndex === NewlineType.None) {
- // no newlines; fast path to avoid newline detection
- if (__TEST__ && code.includes('\n')) {
- throw new Error(
- `CodegenContext.push() called newlineIndex: none, but contains` +
- `newlines: ${code.replace(/\n/g, '\\n')}`,
- )
- }
- context.column += code.length
- } else {
- // single newline at known index
- if (newlineIndex === NewlineType.End) {
- newlineIndex = code.length - 1
- }
- if (
- __TEST__ &&
- (code.charAt(newlineIndex) !== '\n' ||
- code.slice(0, newlineIndex).includes('\n') ||
- code.slice(newlineIndex + 1).includes('\n'))
- ) {
- throw new Error(
- `CodegenContext.push() called with newlineIndex: ${newlineIndex} ` +
- `but does not conform: ${code.replace(/\n/g, '\\n')}`,
- )
- }
- context.line++
- context.column = code.length - newlineIndex
- }
- }
- if (node && node.loc !== locStub) {
- addMapping(node.loc.end)
- }
- }
- },
- indent() {
- newline(++context.indentLevel)
- },
- deindent(withoutNewLine = false) {
- if (withoutNewLine) {
- --context.indentLevel
- } else {
- newline(--context.indentLevel)
- }
- },
- newline() {
- newline(context.indentLevel)
- },
- }
- function newline(n: number) {
- context.push('\n' + ` `.repeat(n), NewlineType.Start)
- }
- function addMapping(loc: Position, name: string | null = null) {
- // we use the private property to directly add the mapping
- // because the addMapping() implementation in source-map-js has a bunch of
- // unnecessary arg and validation checks that are pure overhead in our case.
- const { _names, _mappings } = context.map!
- if (name !== null && !_names.has(name)) _names.add(name)
- _mappings.add({
- originalLine: loc.line,
- originalColumn: loc.column - 1, // source-map column is 0 based
- generatedLine: context.line,
- generatedColumn: context.column - 1,
- source: filename,
- name,
- })
- }
- if (!__BROWSER__ && sourceMap) {
- // lazy require source-map implementation, only in non-browser builds
- context.map =
- new SourceMapGenerator() as unknown as CodegenSourceMapGenerator
- context.map.setSourceContent(filename, context.source)
- context.map._sources.add(filename)
- }
- return context
- }
- export function generate(
- ast: RootNode,
- options: CodegenOptions & {
- onContextCreated?: (context: CodegenContext) => void
- } = {},
- ): CodegenResult {
- const context = createCodegenContext(ast, options)
- if (options.onContextCreated) options.onContextCreated(context)
- const {
- mode,
- push,
- prefixIdentifiers,
- indent,
- deindent,
- newline,
- scopeId,
- ssr,
- } = context
- const helpers = Array.from(ast.helpers)
- const hasHelpers = helpers.length > 0
- const useWithBlock = !prefixIdentifiers && mode !== 'module'
- const genScopeId = !__BROWSER__ && scopeId != null && mode === 'module'
- const isSetupInlined = !__BROWSER__ && !!options.inline
- // preambles
- // in setup() inline mode, the preamble is generated in a sub context
- // and returned separately.
- const preambleContext = isSetupInlined
- ? createCodegenContext(ast, options)
- : context
- if (!__BROWSER__ && mode === 'module') {
- genModulePreamble(ast, preambleContext, genScopeId, isSetupInlined)
- } else {
- genFunctionPreamble(ast, preambleContext)
- }
- // enter render function
- const functionName = ssr ? `ssrRender` : `render`
- const args = ssr ? ['_ctx', '_push', '_parent', '_attrs'] : ['_ctx', '_cache']
- if (!__BROWSER__ && options.bindingMetadata && !options.inline) {
- // binding optimization args
- args.push('$props', '$setup', '$data', '$options')
- }
- const signature =
- !__BROWSER__ && options.isTS
- ? args.map(arg => `${arg}: any`).join(',')
- : args.join(', ')
- if (isSetupInlined) {
- push(`(${signature}) => {`)
- } else {
- push(`function ${functionName}(${signature}) {`)
- }
- indent()
- if (useWithBlock) {
- push(`with (_ctx) {`)
- indent()
- // function mode const declarations should be inside with block
- // also they should be renamed to avoid collision with user properties
- if (hasHelpers) {
- push(
- `const { ${helpers.map(aliasHelper).join(', ')} } = _Vue\n`,
- NewlineType.End,
- )
- newline()
- }
- }
- // generate asset resolution statements
- if (ast.components.length) {
- genAssets(ast.components, 'component', context)
- if (ast.directives.length || ast.temps > 0) {
- newline()
- }
- }
- if (ast.directives.length) {
- genAssets(ast.directives, 'directive', context)
- if (ast.temps > 0) {
- newline()
- }
- }
- if (__COMPAT__ && ast.filters && ast.filters.length) {
- newline()
- genAssets(ast.filters, 'filter', context)
- newline()
- }
- if (ast.temps > 0) {
- push(`let `)
- for (let i = 0; i < ast.temps; i++) {
- push(`${i > 0 ? `, ` : ``}_temp${i}`)
- }
- }
- if (ast.components.length || ast.directives.length || ast.temps) {
- push(`\n`, NewlineType.Start)
- newline()
- }
- // generate the VNode tree expression
- if (!ssr) {
- push(`return `)
- }
- if (ast.codegenNode) {
- genNode(ast.codegenNode, context)
- } else {
- push(`null`)
- }
- if (useWithBlock) {
- deindent()
- push(`}`)
- }
- deindent()
- push(`}`)
- return {
- ast,
- code: context.code,
- preamble: isSetupInlined ? preambleContext.code : ``,
- map: context.map ? context.map.toJSON() : undefined,
- }
- }
- function genFunctionPreamble(ast: RootNode, context: CodegenContext) {
- const {
- ssr,
- prefixIdentifiers,
- push,
- newline,
- runtimeModuleName,
- runtimeGlobalName,
- ssrRuntimeModuleName,
- } = context
- const VueBinding =
- !__BROWSER__ && ssr
- ? `require(${JSON.stringify(runtimeModuleName)})`
- : runtimeGlobalName
- // Generate const declaration for helpers
- // In prefix mode, we place the const declaration at top so it's done
- // only once; But if we not prefixing, we place the declaration inside the
- // with block so it doesn't incur the `in` check cost for every helper access.
- const helpers = Array.from(ast.helpers)
- if (helpers.length > 0) {
- if (!__BROWSER__ && prefixIdentifiers) {
- push(
- `const { ${helpers.map(aliasHelper).join(', ')} } = ${VueBinding}\n`,
- NewlineType.End,
- )
- } else {
- // "with" mode.
- // save Vue in a separate variable to avoid collision
- push(`const _Vue = ${VueBinding}\n`, NewlineType.End)
- // in "with" mode, helpers are declared inside the with block to avoid
- // has check cost, but hoists are lifted out of the function - we need
- // to provide the helper here.
- if (ast.hoists.length) {
- const staticHelpers = [
- CREATE_VNODE,
- CREATE_ELEMENT_VNODE,
- CREATE_COMMENT,
- CREATE_TEXT,
- CREATE_STATIC,
- ]
- .filter(helper => helpers.includes(helper))
- .map(aliasHelper)
- .join(', ')
- push(`const { ${staticHelpers} } = _Vue\n`, NewlineType.End)
- }
- }
- }
- // generate variables for ssr helpers
- if (!__BROWSER__ && ast.ssrHelpers && ast.ssrHelpers.length) {
- // ssr guarantees prefixIdentifier: true
- push(
- `const { ${ast.ssrHelpers
- .map(aliasHelper)
- .join(', ')} } = require("${ssrRuntimeModuleName}")\n`,
- NewlineType.End,
- )
- }
- genHoists(ast.hoists, context)
- newline()
- push(`return `)
- }
- function genModulePreamble(
- ast: RootNode,
- context: CodegenContext,
- genScopeId: boolean,
- inline?: boolean,
- ) {
- const {
- push,
- newline,
- optimizeImports,
- runtimeModuleName,
- ssrRuntimeModuleName,
- } = context
- // generate import statements for helpers
- if (ast.helpers.size) {
- const helpers = Array.from(ast.helpers)
- if (optimizeImports) {
- // when bundled with webpack with code-split, calling an import binding
- // as a function leads to it being wrapped with `Object(a.b)` or `(0,a.b)`,
- // incurring both payload size increase and potential perf overhead.
- // therefore we assign the imports to variables (which is a constant ~50b
- // cost per-component instead of scaling with template size)
- push(
- `import { ${helpers
- .map(s => helperNameMap[s])
- .join(', ')} } from ${JSON.stringify(runtimeModuleName)}\n`,
- NewlineType.End,
- )
- push(
- `\n// Binding optimization for webpack code-split\nconst ${helpers
- .map(s => `_${helperNameMap[s]} = ${helperNameMap[s]}`)
- .join(', ')}\n`,
- NewlineType.End,
- )
- } else {
- push(
- `import { ${helpers
- .map(s => `${helperNameMap[s]} as _${helperNameMap[s]}`)
- .join(', ')} } from ${JSON.stringify(runtimeModuleName)}\n`,
- NewlineType.End,
- )
- }
- }
- if (ast.ssrHelpers && ast.ssrHelpers.length) {
- push(
- `import { ${ast.ssrHelpers
- .map(s => `${helperNameMap[s]} as _${helperNameMap[s]}`)
- .join(', ')} } from "${ssrRuntimeModuleName}"\n`,
- NewlineType.End,
- )
- }
- if (ast.imports.length) {
- genImports(ast.imports, context)
- newline()
- }
- genHoists(ast.hoists, context)
- newline()
- if (!inline) {
- push(`export `)
- }
- }
- function genAssets(
- assets: string[],
- type: 'component' | 'directive' | 'filter',
- { helper, push, newline, isTS }: CodegenContext,
- ) {
- const resolver = helper(
- __COMPAT__ && type === 'filter'
- ? RESOLVE_FILTER
- : type === 'component'
- ? RESOLVE_COMPONENT
- : RESOLVE_DIRECTIVE,
- )
- for (let i = 0; i < assets.length; i++) {
- let id = assets[i]
- // potential component implicit self-reference inferred from SFC filename
- const maybeSelfReference = id.endsWith('__self')
- if (maybeSelfReference) {
- id = id.slice(0, -6)
- }
- push(
- `const ${toValidAssetId(id, type)} = ${resolver}(${JSON.stringify(id)}${
- maybeSelfReference ? `, true` : ``
- })${isTS ? `!` : ``}`,
- )
- if (i < assets.length - 1) {
- newline()
- }
- }
- }
- function genHoists(hoists: (JSChildNode | null)[], context: CodegenContext) {
- if (!hoists.length) {
- return
- }
- context.pure = true
- const { push, newline } = context
- newline()
- for (let i = 0; i < hoists.length; i++) {
- const exp = hoists[i]
- if (exp) {
- push(`const _hoisted_${i + 1} = `)
- genNode(exp, context)
- newline()
- }
- }
- context.pure = false
- }
- function genImports(importsOptions: ImportItem[], context: CodegenContext) {
- if (!importsOptions.length) {
- return
- }
- importsOptions.forEach(imports => {
- context.push(`import `)
- genNode(imports.exp, context)
- context.push(` from '${imports.path}'`)
- context.newline()
- })
- }
- function isText(n: string | CodegenNode) {
- return (
- isString(n) ||
- n.type === NodeTypes.SIMPLE_EXPRESSION ||
- n.type === NodeTypes.TEXT ||
- n.type === NodeTypes.INTERPOLATION ||
- n.type === NodeTypes.COMPOUND_EXPRESSION
- )
- }
- function genNodeListAsArray(
- nodes: (string | CodegenNode | TemplateChildNode[])[],
- context: CodegenContext,
- ) {
- const multilines =
- nodes.length > 3 ||
- ((!__BROWSER__ || __DEV__) && nodes.some(n => isArray(n) || !isText(n)))
- context.push(`[`)
- multilines && context.indent()
- genNodeList(nodes, context, multilines)
- multilines && context.deindent()
- context.push(`]`)
- }
- function genNodeList(
- nodes: (string | symbol | CodegenNode | TemplateChildNode[])[],
- context: CodegenContext,
- multilines: boolean = false,
- comma: boolean = true,
- ) {
- const { push, newline } = context
- for (let i = 0; i < nodes.length; i++) {
- const node = nodes[i]
- if (isString(node)) {
- push(node, NewlineType.Unknown)
- } else if (isArray(node)) {
- genNodeListAsArray(node, context)
- } else {
- genNode(node, context)
- }
- if (i < nodes.length - 1) {
- if (multilines) {
- comma && push(',')
- newline()
- } else {
- comma && push(', ')
- }
- }
- }
- }
- function genNode(node: CodegenNode | symbol | string, context: CodegenContext) {
- if (isString(node)) {
- context.push(node, NewlineType.Unknown)
- return
- }
- if (isSymbol(node)) {
- context.push(context.helper(node))
- return
- }
- switch (node.type) {
- case NodeTypes.ELEMENT:
- case NodeTypes.IF:
- case NodeTypes.FOR:
- __DEV__ &&
- assert(
- node.codegenNode != null,
- `Codegen node is missing for element/if/for node. ` +
- `Apply appropriate transforms first.`,
- )
- genNode(node.codegenNode!, context)
- break
- case NodeTypes.TEXT:
- genText(node, context)
- break
- case NodeTypes.SIMPLE_EXPRESSION:
- genExpression(node, context)
- break
- case NodeTypes.INTERPOLATION:
- genInterpolation(node, context)
- break
- case NodeTypes.TEXT_CALL:
- genNode(node.codegenNode, context)
- break
- case NodeTypes.COMPOUND_EXPRESSION:
- genCompoundExpression(node, context)
- break
- case NodeTypes.COMMENT:
- genComment(node, context)
- break
- case NodeTypes.VNODE_CALL:
- genVNodeCall(node, context)
- break
- case NodeTypes.JS_CALL_EXPRESSION:
- genCallExpression(node, context)
- break
- case NodeTypes.JS_OBJECT_EXPRESSION:
- genObjectExpression(node, context)
- break
- case NodeTypes.JS_ARRAY_EXPRESSION:
- genArrayExpression(node, context)
- break
- case NodeTypes.JS_FUNCTION_EXPRESSION:
- genFunctionExpression(node, context)
- break
- case NodeTypes.JS_CONDITIONAL_EXPRESSION:
- genConditionalExpression(node, context)
- break
- case NodeTypes.JS_CACHE_EXPRESSION:
- genCacheExpression(node, context)
- break
- case NodeTypes.JS_BLOCK_STATEMENT:
- genNodeList(node.body, context, true, false)
- break
- // SSR only types
- case NodeTypes.JS_TEMPLATE_LITERAL:
- !__BROWSER__ && genTemplateLiteral(node, context)
- break
- case NodeTypes.JS_IF_STATEMENT:
- !__BROWSER__ && genIfStatement(node, context)
- break
- case NodeTypes.JS_ASSIGNMENT_EXPRESSION:
- !__BROWSER__ && genAssignmentExpression(node, context)
- break
- case NodeTypes.JS_SEQUENCE_EXPRESSION:
- !__BROWSER__ && genSequenceExpression(node, context)
- break
- case NodeTypes.JS_RETURN_STATEMENT:
- !__BROWSER__ && genReturnStatement(node, context)
- break
- /* v8 ignore start */
- case NodeTypes.IF_BRANCH:
- // noop
- break
- default:
- if (__DEV__) {
- assert(false, `unhandled codegen node type: ${(node as any).type}`)
- // make sure we exhaust all possible types
- const exhaustiveCheck: never = node
- return exhaustiveCheck
- }
- /* v8 ignore stop */
- }
- }
- function genText(
- node: TextNode | SimpleExpressionNode,
- context: CodegenContext,
- ) {
- context.push(JSON.stringify(node.content), NewlineType.Unknown, node)
- }
- function genExpression(node: SimpleExpressionNode, context: CodegenContext) {
- const { content, isStatic } = node
- context.push(
- isStatic ? JSON.stringify(content) : content,
- NewlineType.Unknown,
- node,
- )
- }
- function genInterpolation(node: InterpolationNode, context: CodegenContext) {
- const { push, helper, pure } = context
- if (pure) push(PURE_ANNOTATION)
- push(`${helper(TO_DISPLAY_STRING)}(`)
- genNode(node.content, context)
- push(`)`)
- }
- function genCompoundExpression(
- node: CompoundExpressionNode,
- context: CodegenContext,
- ) {
- for (let i = 0; i < node.children!.length; i++) {
- const child = node.children![i]
- if (isString(child)) {
- context.push(child, NewlineType.Unknown)
- } else {
- genNode(child, context)
- }
- }
- }
- function genExpressionAsPropertyKey(
- node: ExpressionNode,
- context: CodegenContext,
- ) {
- const { push } = context
- if (node.type === NodeTypes.COMPOUND_EXPRESSION) {
- push(`[`)
- genCompoundExpression(node, context)
- push(`]`)
- } else if (node.isStatic) {
- // only quote keys if necessary
- const text = isSimpleIdentifier(node.content)
- ? node.content
- : JSON.stringify(node.content)
- push(text, NewlineType.None, node)
- } else {
- push(`[${node.content}]`, NewlineType.Unknown, node)
- }
- }
- function genComment(node: CommentNode, context: CodegenContext) {
- const { push, helper, pure } = context
- if (pure) {
- push(PURE_ANNOTATION)
- }
- push(
- `${helper(CREATE_COMMENT)}(${JSON.stringify(node.content)})`,
- NewlineType.Unknown,
- node,
- )
- }
- function genVNodeCall(node: VNodeCall, context: CodegenContext) {
- const { push, helper, pure } = context
- const {
- tag,
- props,
- children,
- patchFlag,
- dynamicProps,
- directives,
- isBlock,
- disableTracking,
- isComponent,
- } = node
- // add dev annotations to patch flags
- let patchFlagString
- if (patchFlag) {
- if (__DEV__) {
- if (patchFlag < 0) {
- // special flags (negative and mutually exclusive)
- patchFlagString = patchFlag + ` /* ${PatchFlagNames[patchFlag]} */`
- } else {
- // bitwise flags
- const flagNames = Object.keys(PatchFlagNames)
- .map(Number)
- .filter(n => n > 0 && patchFlag & n)
- .map(n => PatchFlagNames[n as PatchFlags])
- .join(`, `)
- patchFlagString = patchFlag + ` /* ${flagNames} */`
- }
- } else {
- patchFlagString = String(patchFlag)
- }
- }
- if (directives) {
- push(helper(WITH_DIRECTIVES) + `(`)
- }
- if (isBlock) {
- push(`(${helper(OPEN_BLOCK)}(${disableTracking ? `true` : ``}), `)
- }
- if (pure) {
- push(PURE_ANNOTATION)
- }
- const callHelper: symbol = isBlock
- ? getVNodeBlockHelper(context.inSSR, isComponent)
- : getVNodeHelper(context.inSSR, isComponent)
- push(helper(callHelper) + `(`, NewlineType.None, node)
- genNodeList(
- genNullableArgs([tag, props, children, patchFlagString, dynamicProps]),
- context,
- )
- push(`)`)
- if (isBlock) {
- push(`)`)
- }
- if (directives) {
- push(`, `)
- genNode(directives, context)
- push(`)`)
- }
- }
- function genNullableArgs(args: any[]): CallExpression['arguments'] {
- let i = args.length
- while (i--) {
- if (args[i] != null) break
- }
- return args.slice(0, i + 1).map(arg => arg || `null`)
- }
- // JavaScript
- function genCallExpression(node: CallExpression, context: CodegenContext) {
- const { push, helper, pure } = context
- const callee = isString(node.callee) ? node.callee : helper(node.callee)
- if (pure) {
- push(PURE_ANNOTATION)
- }
- push(callee + `(`, NewlineType.None, node)
- genNodeList(node.arguments, context)
- push(`)`)
- }
- function genObjectExpression(node: ObjectExpression, context: CodegenContext) {
- const { push, indent, deindent, newline } = context
- const { properties } = node
- if (!properties.length) {
- push(`{}`, NewlineType.None, node)
- return
- }
- const multilines =
- properties.length > 1 ||
- ((!__BROWSER__ || __DEV__) &&
- properties.some(p => p.value.type !== NodeTypes.SIMPLE_EXPRESSION))
- push(multilines ? `{` : `{ `)
- multilines && indent()
- for (let i = 0; i < properties.length; i++) {
- const { key, value } = properties[i]
- // key
- genExpressionAsPropertyKey(key, context)
- push(`: `)
- // value
- genNode(value, context)
- if (i < properties.length - 1) {
- // will only reach this if it's multilines
- push(`,`)
- newline()
- }
- }
- multilines && deindent()
- push(multilines ? `}` : ` }`)
- }
- function genArrayExpression(node: ArrayExpression, context: CodegenContext) {
- genNodeListAsArray(node.elements as CodegenNode[], context)
- }
- function genFunctionExpression(
- node: FunctionExpression,
- context: CodegenContext,
- ) {
- const { push, indent, deindent } = context
- const { params, returns, body, newline, isSlot } = node
- if (isSlot) {
- // wrap slot functions with owner context
- push(`_${helperNameMap[WITH_CTX]}(`)
- }
- push(`(`, NewlineType.None, node)
- if (isArray(params)) {
- genNodeList(params, context)
- } else if (params) {
- genNode(params, context)
- }
- push(`) => `)
- if (newline || body) {
- push(`{`)
- indent()
- }
- if (returns) {
- if (newline) {
- push(`return `)
- }
- if (isArray(returns)) {
- genNodeListAsArray(returns, context)
- } else {
- genNode(returns, context)
- }
- } else if (body) {
- genNode(body, context)
- }
- if (newline || body) {
- deindent()
- push(`}`)
- }
- if (isSlot) {
- if (__COMPAT__ && node.isNonScopedSlot) {
- push(`, undefined, true`)
- }
- push(`)`)
- }
- }
- function genConditionalExpression(
- node: ConditionalExpression,
- context: CodegenContext,
- ) {
- const { test, consequent, alternate, newline: needNewline } = node
- const { push, indent, deindent, newline } = context
- if (test.type === NodeTypes.SIMPLE_EXPRESSION) {
- const needsParens = !isSimpleIdentifier(test.content)
- needsParens && push(`(`)
- genExpression(test, context)
- needsParens && push(`)`)
- } else {
- push(`(`)
- genNode(test, context)
- push(`)`)
- }
- needNewline && indent()
- context.indentLevel++
- needNewline || push(` `)
- push(`? `)
- genNode(consequent, context)
- context.indentLevel--
- needNewline && newline()
- needNewline || push(` `)
- push(`: `)
- const isNested = alternate.type === NodeTypes.JS_CONDITIONAL_EXPRESSION
- if (!isNested) {
- context.indentLevel++
- }
- genNode(alternate, context)
- if (!isNested) {
- context.indentLevel--
- }
- needNewline && deindent(true /* without newline */)
- }
- function genCacheExpression(node: CacheExpression, context: CodegenContext) {
- const { push, helper, indent, deindent, newline } = context
- const { needPauseTracking, needArraySpread } = node
- if (needArraySpread) {
- push(`[...(`)
- }
- push(`_cache[${node.index}] || (`)
- if (needPauseTracking) {
- indent()
- push(`${helper(SET_BLOCK_TRACKING)}(-1`)
- if (node.inVOnce) push(`, true`)
- push(`),`)
- newline()
- push(`(`)
- }
- push(`_cache[${node.index}] = `)
- genNode(node.value, context)
- if (needPauseTracking) {
- push(`).cacheIndex = ${node.index},`)
- newline()
- push(`${helper(SET_BLOCK_TRACKING)}(1),`)
- newline()
- push(`_cache[${node.index}]`)
- deindent()
- }
- push(`)`)
- if (needArraySpread) {
- push(`)]`)
- }
- }
- function genTemplateLiteral(node: TemplateLiteral, context: CodegenContext) {
- const { push, indent, deindent } = context
- push('`')
- const l = node.elements.length
- const multilines = l > 3
- for (let i = 0; i < l; i++) {
- const e = node.elements[i]
- if (isString(e)) {
- push(e.replace(/(`|\$|\\)/g, '\\$1'), NewlineType.Unknown)
- } else {
- push('${')
- if (multilines) indent()
- genNode(e, context)
- if (multilines) deindent()
- push('}')
- }
- }
- push('`')
- }
- function genIfStatement(node: IfStatement, context: CodegenContext) {
- const { push, indent, deindent } = context
- const { test, consequent, alternate } = node
- push(`if (`)
- genNode(test, context)
- push(`) {`)
- indent()
- genNode(consequent, context)
- deindent()
- push(`}`)
- if (alternate) {
- push(` else `)
- if (alternate.type === NodeTypes.JS_IF_STATEMENT) {
- genIfStatement(alternate, context)
- } else {
- push(`{`)
- indent()
- genNode(alternate, context)
- deindent()
- push(`}`)
- }
- }
- }
- function genAssignmentExpression(
- node: AssignmentExpression,
- context: CodegenContext,
- ) {
- genNode(node.left, context)
- context.push(` = `)
- genNode(node.right, context)
- }
- function genSequenceExpression(
- node: SequenceExpression,
- context: CodegenContext,
- ) {
- context.push(`(`)
- genNodeList(node.expressions, context)
- context.push(`)`)
- }
- function genReturnStatement(
- { returns }: ReturnStatement,
- context: CodegenContext,
- ) {
- context.push(`return `)
- if (isArray(returns)) {
- genNodeListAsArray(returns, context)
- } else {
- genNode(returns, context)
- }
- }
|