| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790 |
- import { CodegenOptions } from './options'
- import {
- RootNode,
- TemplateChildNode,
- TextNode,
- CommentNode,
- ExpressionNode,
- NodeTypes,
- JSChildNode,
- CallExpression,
- ArrayExpression,
- ObjectExpression,
- Position,
- InterpolationNode,
- CompoundExpressionNode,
- SimpleExpressionNode,
- FunctionExpression,
- SequenceExpression,
- ConditionalExpression,
- CacheExpression,
- locStub,
- SSRCodegenNode,
- TemplateLiteral,
- IfStatement
- } from './ast'
- import { SourceMapGenerator, RawSourceMap } from 'source-map'
- import {
- advancePositionWithMutation,
- assert,
- isSimpleIdentifier,
- loadDep,
- toValidAssetId
- } from './utils'
- import { isString, isArray, isSymbol } from '@vue/shared'
- import {
- helperNameMap,
- TO_DISPLAY_STRING,
- CREATE_VNODE,
- RESOLVE_COMPONENT,
- RESOLVE_DIRECTIVE,
- SET_BLOCK_TRACKING,
- CREATE_COMMENT,
- CREATE_TEXT,
- PUSH_SCOPE_ID,
- POP_SCOPE_ID,
- WITH_SCOPE_ID
- } from './runtimeHelpers'
- import { ImportItem } from './transform'
- type CodegenNode = TemplateChildNode | JSChildNode | SSRCodegenNode
- export interface CodegenResult {
- code: string
- ast: RootNode
- map?: RawSourceMap
- }
- export interface CodegenContext extends Required<CodegenOptions> {
- source: string
- code: string
- line: number
- column: number
- offset: number
- indentLevel: number
- map?: SourceMapGenerator
- helper(key: symbol): string
- push(code: string, 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,
- ssr = false
- }: CodegenOptions
- ): CodegenContext {
- const context: CodegenContext = {
- mode,
- prefixIdentifiers,
- sourceMap,
- filename,
- scopeId,
- ssr,
- source: ast.loc.source,
- code: ``,
- column: 1,
- line: 1,
- offset: 0,
- indentLevel: 0,
- map: undefined,
- helper(key) {
- const name = helperNameMap[key]
- return prefixIdentifiers ? name : `_${name}`
- },
- push(code, 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)
- }
- advancePositionWithMutation(context, code)
- 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))
- }
- function addMapping(loc: Position, name?: string) {
- context.map!.addMapping({
- name,
- source: context.filename,
- original: {
- line: loc.line,
- column: loc.column - 1 // source-map column is 0 based
- },
- generated: {
- line: context.line,
- column: context.column - 1
- }
- })
- }
- if (!__BROWSER__ && sourceMap) {
- // lazy require source-map implementation, only in non-browser builds
- context.map = new (loadDep('source-map')).SourceMapGenerator()
- context.map!.setSourceContent(filename, context.source)
- }
- return context
- }
- export function generate(
- ast: RootNode,
- options: CodegenOptions = {}
- ): CodegenResult {
- const context = createCodegenContext(ast, options)
- const {
- mode,
- push,
- prefixIdentifiers,
- indent,
- deindent,
- newline,
- scopeId,
- ssr
- } = context
- const hasHelpers = ast.helpers.length > 0
- const useWithBlock = !prefixIdentifiers && mode !== 'module'
- const genScopeId = !__BROWSER__ && scopeId != null && mode === 'module'
- // preambles
- if (mode === 'module') {
- genModulePreamble(ast, context, genScopeId)
- } else {
- genFunctionPreamble(ast, context)
- }
- // enter render function
- if (genScopeId && !ssr) {
- push(`const render = withId(`)
- }
- if (!ssr) {
- push(`function render() {`)
- } else {
- push(`function ssrRender(_ctx, _push, _parent) {`)
- }
- indent()
- if (useWithBlock) {
- push(`with (this) {`)
- 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 { ${ast.helpers
- .map(s => `${helperNameMap[s]}: _${helperNameMap[s]}`)
- .join(', ')} } = _Vue`
- )
- newline()
- if (ast.cached > 0) {
- push(`const _cache = $cache`)
- newline()
- }
- newline()
- }
- } else if (!ssr) {
- push(`const _ctx = this`)
- if (ast.cached > 0) {
- newline()
- push(`const _cache = _ctx.$cache`)
- }
- newline()
- }
- // generate asset resolution statements
- if (ast.components.length) {
- genAssets(ast.components, 'component', context)
- }
- if (ast.directives.length) {
- genAssets(ast.directives, 'directive', context)
- }
- if (ast.components.length || ast.directives.length) {
- 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(`}`)
- if (genScopeId && !ssr) {
- push(`)`)
- }
- return {
- ast,
- code: context.code,
- // SourceMapGenerator does have toJSON() method but it's not in the types
- map: context.map ? (context.map as any).toJSON() : undefined
- }
- }
- function genFunctionPreamble(ast: RootNode, context: CodegenContext) {
- const { ssr, helper, prefixIdentifiers, push, newline } = context
- const VueBinding = ssr ? `require("vue")` : `Vue`
- // 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.
- if (ast.helpers.length > 0) {
- if (prefixIdentifiers) {
- push(`const { ${ast.helpers.map(helper).join(', ')} } = ${VueBinding}\n`)
- } else {
- // "with" mode.
- // save Vue in a separate variable to avoid collision
- push(`const _Vue = ${VueBinding}\n`)
- // 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_COMMENT, CREATE_TEXT]
- .filter(helper => ast.helpers.includes(helper))
- .map(s => `${helperNameMap[s]}: _${helperNameMap[s]}`)
- .join(', ')
- push(`const { ${staticHelpers} } = _Vue\n`)
- }
- }
- }
- // generate variables for ssr helpers
- if (!__BROWSER__ && ast.ssrHelpers && ast.ssrHelpers.length) {
- // ssr guaruntees prefixIdentifier: true
- push(
- `const { ${ast.ssrHelpers
- .map(helper)
- .join(', ')} } = require("@vue/server-renderer")\n`
- )
- }
- genHoists(ast.hoists, context)
- newline()
- push(`return `)
- }
- function genModulePreamble(
- ast: RootNode,
- context: CodegenContext,
- genScopeId: boolean
- ) {
- const { push, helper, newline, scopeId } = context
- // generate import statements for helpers
- if (genScopeId) {
- ast.helpers.push(WITH_SCOPE_ID)
- if (ast.hoists.length) {
- ast.helpers.push(PUSH_SCOPE_ID, POP_SCOPE_ID)
- }
- }
- if (ast.helpers.length) {
- push(`import { ${ast.helpers.map(helper).join(', ')} } from "vue"\n`)
- }
- if (!__BROWSER__ && ast.ssrHelpers && ast.ssrHelpers.length) {
- push(
- `import { ${ast.ssrHelpers
- .map(helper)
- .join(', ')} } from "@vue/server-renderer"\n`
- )
- }
- if (ast.imports.length) {
- genImports(ast.imports, context)
- newline()
- }
- if (genScopeId) {
- push(`const withId = ${helper(WITH_SCOPE_ID)}("${scopeId}")`)
- newline()
- }
- genHoists(ast.hoists, context)
- newline()
- push(`export `)
- }
- function genAssets(
- assets: string[],
- type: 'component' | 'directive',
- context: CodegenContext
- ) {
- const resolver = context.helper(
- type === 'component' ? RESOLVE_COMPONENT : RESOLVE_DIRECTIVE
- )
- for (let i = 0; i < assets.length; i++) {
- const id = assets[i]
- context.push(
- `const ${toValidAssetId(id, type)} = ${resolver}(${JSON.stringify(id)})`
- )
- context.newline()
- }
- }
- function genHoists(hoists: JSChildNode[], context: CodegenContext) {
- if (!hoists.length) {
- return
- }
- const { push, newline, helper, scopeId, mode } = context
- const genScopeId = !__BROWSER__ && scopeId != null && mode !== 'function'
- newline()
- // push scope Id before initilaizing hoisted vnodes so that these vnodes
- // get the proper scopeId as well.
- if (genScopeId) {
- push(`${helper(PUSH_SCOPE_ID)}("${scopeId}")`)
- newline()
- }
- hoists.forEach((exp, i) => {
- push(`const _hoisted_${i + 1} = `)
- genNode(exp, context)
- newline()
- })
- if (genScopeId) {
- push(`${helper(POP_SCOPE_ID)}()`)
- newline()
- }
- }
- 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)
- } 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)
- 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.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_SEQUENCE_EXPRESSION:
- genSequenceExpression(node, context)
- break
- case NodeTypes.JS_CONDITIONAL_EXPRESSION:
- genConditionalExpression(node, context)
- break
- case NodeTypes.JS_CACHE_EXPRESSION:
- genCacheExpression(node, context)
- break
- // SSR only types
- case NodeTypes.JS_BLOCK_STATEMENT:
- !__BROWSER__ && genNodeList(node.body, context, true, false)
- break
- case NodeTypes.JS_TEMPLATE_LITERAL:
- !__BROWSER__ && genTemplateLiteral(node, context)
- break
- case NodeTypes.JS_IF_STATEMENT:
- !__BROWSER__ && genIfStatement(node, context)
- break
- /* istanbul ignore next */
- 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
- }
- }
- }
- function genText(
- node: TextNode | SimpleExpressionNode,
- context: CodegenContext
- ) {
- context.push(JSON.stringify(node.content), node)
- }
- function genExpression(node: SimpleExpressionNode, context: CodegenContext) {
- const { content, isStatic } = node
- context.push(isStatic ? JSON.stringify(content) : content, node)
- }
- function genInterpolation(node: InterpolationNode, context: CodegenContext) {
- const { push, helper } = context
- 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)
- } 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, node)
- } else {
- push(`[${node.content}]`, node)
- }
- }
- function genComment(node: CommentNode, context: CodegenContext) {
- if (__DEV__) {
- const { push, helper } = context
- push(`${helper(CREATE_COMMENT)}(${JSON.stringify(node.content)})`, node)
- }
- }
- // JavaScript
- function genCallExpression(node: CallExpression, context: CodegenContext) {
- const callee = isString(node.callee)
- ? node.callee
- : context.helper(node.callee)
- context.push(callee + `(`, node)
- genNodeList(node.arguments, context)
- context.push(`)`)
- }
- function genObjectExpression(node: ObjectExpression, context: CodegenContext) {
- const { push, indent, deindent, newline } = context
- const { properties } = node
- if (!properties.length) {
- push(`{}`, 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, context)
- }
- function genFunctionExpression(
- node: FunctionExpression,
- context: CodegenContext
- ) {
- const { push, indent, deindent, scopeId, mode } = context
- const { params, returns, body, newline, isSlot } = node
- // slot functions also need to push scopeId before rendering its content
- const genScopeId =
- !__BROWSER__ && isSlot && scopeId != null && mode !== 'function'
- if (genScopeId) {
- push(`withId(`)
- }
- push(`(`, 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 (genScopeId) {
- 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++
- push(`? `)
- genNode(consequent, context)
- context.indentLevel--
- needNewline && newline()
- 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 genSequenceExpression(
- node: SequenceExpression,
- context: CodegenContext
- ) {
- context.push(`(`)
- genNodeList(node.expressions, context)
- context.push(`)`)
- }
- 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}] = `)
- genNode(node.value, context)
- if (node.isVNode) {
- push(`,`)
- newline()
- push(`${helper(SET_BLOCK_TRACKING)}(1),`)
- newline()
- push(`_cache[${node.index}]`)
- deindent()
- }
- 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, '\\`'))
- } 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(`}`)
- }
- }
- }
|