vue3-core/packages/compiler-core/src/codegen.ts

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

1119 lines
28 KiB
TypeScript
Raw Normal View History

import type { CodegenOptions } from './options'
import {
type ArrayExpression,
type AssignmentExpression,
2019-10-19 09:51:34 +08:00
type CacheExpression,
2019-09-23 04:50:57 +08:00
type CallExpression,
type CommentNode,
type CompoundExpressionNode,
type ConditionalExpression,
type ExpressionNode,
type FunctionExpression,
type IfStatement,
type InterpolationNode,
type JSChildNode,
2019-09-23 04:50:57 +08:00
NodeTypes,
type ObjectExpression,
type Position,
type ReturnStatement,
type RootNode,
type SSRCodegenNode,
type SequenceExpression,
2019-09-28 08:29:20 +08:00
type SimpleExpressionNode,
type TemplateChildNode,
type TemplateLiteral,
2020-02-07 14:06:51 +08:00
type TextNode,
type VNodeCall,
getVNodeBlockHelper,
getVNodeHelper,
locStub,
} from './ast'
2024-04-15 13:36:43 +08:00
import { SourceMapGenerator } from 'source-map-js'
2019-09-25 10:39:20 +08:00
import {
advancePositionWithMutation,
assert,
2019-10-05 05:43:20 +08:00
isSimpleIdentifier,
toValidAssetId,
2019-09-25 10:39:20 +08:00
} from './utils'
import { isArray, isString, isSymbol } from '@vue/shared'
import {
CREATE_COMMENT,
CREATE_ELEMENT_VNODE,
2020-01-27 06:35:21 +08:00
CREATE_STATIC,
CREATE_TEXT,
CREATE_VNODE,
OPEN_BLOCK,
POP_SCOPE_ID,
PUSH_SCOPE_ID,
RESOLVE_COMPONENT,
RESOLVE_DIRECTIVE,
2021-04-20 00:08:26 +08:00
RESOLVE_FILTER,
SET_BLOCK_TRACKING,
TO_DISPLAY_STRING,
2021-04-20 00:08:26 +08:00
WITH_CTX,
WITH_DIRECTIVES,
helperNameMap,
} from './runtimeHelpers'
import type { ImportItem } from './transform'
2019-09-23 04:50:57 +08:00
2024-04-15 13:36:43 +08:00
/**
* 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 BaseCodegenResult {
code: string
preamble: string
ast: unknown
map?: RawSourceMap
helpers?: Set<string> | Set<symbol>
}
export interface CodegenResult extends BaseCodegenResult {
ast: RootNode
helpers: Set<symbol>
}
2023-12-01 22:12:19 +08:00
export enum NewlineType {
2023-12-06 00:15:57 +08:00
/** Start with `\n` */
Start = 0,
2023-12-06 00:15:57 +08:00
/** Ends with `\n` */
End = -1,
2023-12-06 00:15:57 +08:00
/** No `\n` included */
None = -2,
2023-12-06 00:15:57 +08:00
/** Don't know, calc it */
Unknown = -3,
}
2020-07-11 10:12:25 +08:00
export interface CodegenContext
extends Omit<
Required<CodegenOptions>,
| 'bindingMetadata'
| 'inline'
| 'vaporRuntimeModuleName'
| 'expressionPlugins'
> {
source: string
code: string
line: number
column: number
offset: number
2019-09-23 04:50:57 +08:00
indentLevel: number
pure: boolean
2024-04-15 13:36:43 +08:00
map?: CodegenSourceMapGenerator
2019-10-11 06:02:51 +08:00
helper(key: symbol): string
push(code: string, newlineIndex?: number, node?: CodegenNode): void
2019-09-23 04:50:57 +08:00
indent(): void
deindent(withoutNewLine?: boolean): void
2019-09-23 04:50:57 +08:00
newline(): void
}
function createCodegenContext(
ast: RootNode,
2019-09-23 04:50:57 +08:00
{
mode = 'function',
2020-02-04 06:47:06 +08:00
prefixIdentifiers = mode === 'module',
2019-09-25 03:49:02 +08:00
sourceMap = false,
filename = `template.vue.html`,
scopeId = null,
optimizeImports = false,
runtimeGlobalName = `Vue`,
runtimeModuleName = `vue`,
ssrRuntimeModuleName = 'vue/server-renderer',
ssr = false,
isTS = false,
inSSR = false,
2019-09-23 04:50:57 +08:00
}: CodegenOptions,
): CodegenContext {
const context: CodegenContext = {
2019-09-23 04:50:57 +08:00
mode,
2019-09-24 01:29:41 +08:00
prefixIdentifiers,
2019-09-25 03:49:02 +08:00
sourceMap,
filename,
scopeId,
optimizeImports,
runtimeGlobalName,
runtimeModuleName,
ssrRuntimeModuleName,
ssr,
isTS,
inSSR,
source: ast.source,
code: ``,
column: 1,
line: 1,
offset: 0,
2019-09-23 04:50:57 +08:00
indentLevel: 0,
pure: false,
map: undefined,
helper(key) {
return `_${helperNameMap[key]}`
},
push(code, newlineIndex = NewlineType.None, node) {
2019-09-23 04:50:57 +08:00
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)
}
}
2019-09-23 04:50:57 +08:00
},
indent() {
newline(++context.indentLevel)
},
deindent(withoutNewLine = false) {
if (withoutNewLine) {
--context.indentLevel
} else {
newline(--context.indentLevel)
}
2019-09-23 04:50:57 +08:00
},
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
2024-04-15 13:36:43 +08:00
context.map =
new SourceMapGenerator() as unknown as CodegenSourceMapGenerator
context.map.setSourceContent(filename, context.source)
context.map._sources.add(filename)
}
return context
}
2019-09-23 04:50:57 +08:00
export function generate(
ast: RootNode,
options: CodegenOptions & {
onContextCreated?: (context: CodegenContext) => void
} = {},
2019-09-23 04:50:57 +08:00
): 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
2019-09-26 10:29:37 +08:00
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)
2019-09-23 04:50:57 +08:00
} else {
genFunctionPreamble(ast, preambleContext)
2019-09-23 04:50:57 +08:00
}
// 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(',')
2020-11-19 11:34:55 +08:00
: args.join(', ')
if (isSetupInlined) {
push(`(${signature}) => {`)
} else {
push(`function ${functionName}(${signature}) {`)
}
2019-09-23 14:52:54 +08:00
indent()
2019-09-26 10:29:37 +08:00
if (useWithBlock) {
2020-02-14 07:28:40 +08:00
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()
}
}
2021-04-20 00:08:26 +08:00
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)
2019-09-23 14:52:54 +08:00
newline()
}
// generate the VNode tree expression
if (!ssr) {
push(`return `)
}
if (ast.codegenNode) {
genNode(ast.codegenNode, context)
} else {
push(`null`)
}
2019-09-26 10:29:37 +08:00
if (useWithBlock) {
2019-09-23 04:50:57 +08:00
deindent()
push(`}`)
}
2019-09-26 10:29:37 +08:00
2019-09-23 04:50:57 +08:00
deindent()
push(`}`)
2019-09-23 04:50:57 +08:00
return {
ast,
2019-09-23 04:50:57 +08:00
code: context.code,
preamble: isSetupInlined ? preambleContext.code : ``,
map: context.map ? context.map.toJSON() : undefined,
helpers: ast.helpers,
2019-09-23 04:50:57 +08:00
}
}
2020-02-04 06:47:06 +08:00
function genFunctionPreamble(ast: RootNode, context: CodegenContext) {
const {
ssr,
prefixIdentifiers,
push,
newline,
runtimeModuleName,
runtimeGlobalName,
ssrRuntimeModuleName,
} = context
const VueBinding =
!__BROWSER__ && ssr
? `require(${JSON.stringify(runtimeModuleName)})`
: runtimeGlobalName
2020-02-04 06:47:06 +08:00
// 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,
)
2020-02-04 06:47:06 +08:00
} else {
// "with" mode.
// save Vue in a separate variable to avoid collision
push(`const _Vue = ${VueBinding}\n`, NewlineType.End)
2020-02-04 06:47:06 +08:00
// 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)
2020-02-04 06:47:06 +08:00
.join(', ')
push(`const { ${staticHelpers} } = _Vue\n`, NewlineType.End)
2020-02-04 06:47:06 +08:00
}
}
}
// generate variables for ssr helpers
if (!__BROWSER__ && ast.ssrHelpers && ast.ssrHelpers.length) {
// ssr guarantees prefixIdentifier: true
2020-02-04 06:47:06 +08:00
push(
`const { ${ast.ssrHelpers
.map(aliasHelper)
.join(', ')} } = require("${ssrRuntimeModuleName}")\n`,
NewlineType.End,
2020-02-04 06:47:06 +08:00
)
}
genHoists(ast.hoists, context)
newline()
push(`return `)
}
function genModulePreamble(
ast: RootNode,
context: CodegenContext,
genScopeId: boolean,
inline?: boolean,
2020-02-04 06:47:06 +08:00
) {
const {
push,
newline,
optimizeImports,
runtimeModuleName,
ssrRuntimeModuleName,
} = context
2020-02-07 05:51:26 +08:00
if (genScopeId && ast.hoists.length) {
ast.helpers.add(PUSH_SCOPE_ID)
ast.helpers.add(POP_SCOPE_ID)
2020-02-04 06:47:06 +08:00
}
2020-02-07 05:51:26 +08:00
// 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,
)
}
2020-02-04 06:47:06 +08:00
}
2020-02-07 05:51:26 +08:00
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()
}
2020-02-04 06:47:06 +08:00
genHoists(ast.hoists, context)
newline()
if (!inline) {
push(`export `)
}
2020-02-04 06:47:06 +08:00
}
function genAssets(
assets: string[],
2021-04-20 00:08:26 +08:00
type: 'component' | 'directive' | 'filter',
{ helper, push, newline, isTS }: CodegenContext,
) {
2020-02-06 12:07:23 +08:00
const resolver = helper(
2021-04-20 00:08:26 +08:00
__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)
}
2020-02-06 12:07:23 +08:00
push(
`const ${toValidAssetId(id, type)} = ${resolver}(${JSON.stringify(id)}${
maybeSelfReference ? `, true` : ``
})${isTS ? `!` : ``}`,
)
2020-02-06 12:07:23 +08:00
if (i < assets.length - 1) {
newline()
}
}
}
function genHoists(hoists: (JSChildNode | null)[], context: CodegenContext) {
if (!hoists.length) {
return
}
context.pure = true
const { push, newline, helper, scopeId, mode } = context
const genScopeId = !__BROWSER__ && scopeId != null && mode !== 'function'
newline()
// generate inlined withScopeId helper
if (genScopeId) {
push(
`const _withScopeId = n => (${helper(
PUSH_SCOPE_ID,
)}("${scopeId}"),n=n(),${helper(POP_SCOPE_ID)}(),n)`,
)
newline()
}
for (let i = 0; i < hoists.length; i++) {
const exp = hoists[i]
if (exp) {
const needScopeIdWrapper = genScopeId && exp.type === NodeTypes.VNODE_CALL
push(
`const _hoisted_${i + 1} = ${
needScopeIdWrapper ? `${PURE_ANNOTATION} _withScopeId(() => ` : ``
}`,
)
genNode(exp, context)
if (needScopeIdWrapper) {
push(`)`)
}
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
)
}
2019-09-23 04:50:57 +08:00
function genNodeListAsArray(
nodes: (string | CodegenNode | TemplateChildNode[])[],
2019-09-23 04:50:57 +08:00
context: CodegenContext,
) {
const multilines =
nodes.length > 3 ||
((!__BROWSER__ || __DEV__) && nodes.some(n => isArray(n) || !isText(n)))
2019-09-23 04:50:57 +08:00
context.push(`[`)
multilines && context.indent()
genNodeList(nodes, context, multilines)
multilines && context.deindent()
context.push(`]`)
}
2019-09-23 04:50:57 +08:00
function genNodeList(
2019-10-11 06:02:51 +08:00
nodes: (string | symbol | CodegenNode | TemplateChildNode[])[],
2019-09-23 04:50:57 +08:00
context: CodegenContext,
multilines: boolean = false,
comma: boolean = true,
2019-09-23 04:50:57 +08:00
) {
const { push, newline } = context
for (let i = 0; i < nodes.length; i++) {
const node = nodes[i]
if (isString(node)) {
push(node, NewlineType.Unknown)
2019-09-23 04:50:57 +08:00
} else if (isArray(node)) {
genNodeListAsArray(node, context)
2019-09-23 04:50:57 +08:00
} else {
genNode(node, context)
}
if (i < nodes.length - 1) {
2020-02-04 07:30:56 +08:00
if (multilines) {
comma && push(',')
newline()
} else {
comma && push(', ')
}
2019-09-23 04:50:57 +08:00
}
}
}
2019-10-11 06:02:51 +08:00
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
2019-09-23 04:50:57 +08:00
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)
2019-09-23 14:52:54 +08:00
break
case NodeTypes.JS_FUNCTION_EXPRESSION:
genFunctionExpression(node, context)
break
case NodeTypes.JS_CONDITIONAL_EXPRESSION:
genConditionalExpression(node, context)
break
2019-10-19 09:51:34 +08:00
case NodeTypes.JS_CACHE_EXPRESSION:
genCacheExpression(node, context)
break
case NodeTypes.JS_BLOCK_STATEMENT:
2021-07-10 09:41:44 +08:00
genNodeList(node.body, context, true, false)
break
2021-07-10 09:41:44 +08:00
// 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
2020-02-07 14:06:51 +08:00
case NodeTypes.JS_RETURN_STATEMENT:
!__BROWSER__ && genReturnStatement(node, context)
break
2019-09-29 02:15:10 +08:00
/* istanbul ignore next */
case NodeTypes.IF_BRANCH:
// noop
break
2019-09-23 14:52:54 +08:00
default:
if (__DEV__) {
2019-09-23 14:52:54 +08:00
assert(false, `unhandled codegen node type: ${(node as any).type}`)
// make sure we exhaust all possible types
const exhaustiveCheck: never = node
return exhaustiveCheck
}
}
}
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)
2020-01-27 06:35:21 +08:00
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)
}
2019-09-23 14:52:54 +08:00
}
2019-09-23 04:50:57 +08:00
}
function genExpressionAsPropertyKey(
node: ExpressionNode,
context: CodegenContext,
) {
2019-09-24 09:22:52 +08:00
const { push } = context
if (node.type === NodeTypes.COMPOUND_EXPRESSION) {
2019-09-24 09:22:52 +08:00
push(`[`)
genCompoundExpression(node, context)
push(`]`)
} else if (node.isStatic) {
2019-09-23 04:50:57 +08:00
// only quote keys if necessary
const text = isSimpleIdentifier(node.content)
? node.content
: JSON.stringify(node.content)
push(text, NewlineType.None, node)
2019-09-23 04:50:57 +08:00
} else {
push(`[${node.content}]`, NewlineType.Unknown, node)
2019-09-23 14:52:54 +08:00
}
}
function genComment(node: CommentNode, context: CodegenContext) {
const { push, helper, pure } = context
if (pure) {
push(PURE_ANNOTATION)
2019-09-25 03:49:02 +08:00
}
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
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, patchFlag, 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`)
}
2019-09-23 04:50:57 +08:00
// 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(`)`)
2019-09-23 04:50:57 +08:00
}
function genObjectExpression(node: ObjectExpression, context: CodegenContext) {
const { push, indent, deindent, newline } = context
2019-09-23 04:50:57 +08:00
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 ? `{` : `{ `)
2019-09-23 04:50:57 +08:00
multilines && indent()
for (let i = 0; i < properties.length; i++) {
const { key, value } = properties[i]
2019-09-23 04:50:57 +08:00
// key
genExpressionAsPropertyKey(key, context)
push(`: `)
// value
2019-09-26 00:39:46 +08:00
genNode(value, context)
2019-09-23 04:50:57 +08:00
if (i < properties.length - 1) {
2019-09-25 04:35:01 +08:00
// will only reach this if it's multilines
push(`,`)
newline()
2019-09-23 04:50:57 +08:00
}
}
multilines && deindent()
2019-11-16 05:47:55 +08:00
push(multilines ? `}` : ` }`)
2019-09-23 04:50:57 +08:00
}
function genArrayExpression(node: ArrayExpression, context: CodegenContext) {
genNodeListAsArray(node.elements as CodegenNode[], context)
2019-09-23 04:50:57 +08:00
}
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++
2020-02-05 10:03:16 +08:00
needNewline || push(` `)
push(`? `)
genNode(consequent, context)
context.indentLevel--
needNewline && newline()
2020-02-05 10:03:16 +08:00
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 */)
}
2019-10-19 09:51:34 +08:00
function genCacheExpression(node: CacheExpression, context: CodegenContext) {
const { push, helper, indent, deindent, newline } = context
push(`_cache[${node.index}] || (`)
if (node.isVNode) {
indent()
push(`${helper(SET_BLOCK_TRACKING)}(-1),`)
newline()
}
push(`_cache[${node.index}] = `)
2019-10-19 09:51:34 +08:00
genNode(node.value, context)
if (node.isVNode) {
push(`,`)
newline()
push(`${helper(SET_BLOCK_TRACKING)}(1),`)
newline()
push(`_cache[${node.index}]`)
deindent()
}
push(`)`)
2019-10-19 09:51:34 +08:00
}
function genTemplateLiteral(node: TemplateLiteral, context: CodegenContext) {
2020-02-05 04:58:54 +08:00
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)
}
2020-02-07 14:06:51 +08:00
function genSequenceExpression(
node: SequenceExpression,
context: CodegenContext,
) {
context.push(`(`)
genNodeList(node.expressions, context)
context.push(`)`)
}
2020-02-07 14:06:51 +08:00
function genReturnStatement(
{ returns }: ReturnStatement,
context: CodegenContext,
) {
context.push(`return `)
if (isArray(returns)) {
genNodeListAsArray(returns, context)
} else {
genNode(returns, context)
}
}