wip(ssr): ssr helper codegen
This commit is contained in:
@@ -103,6 +103,7 @@ export interface RootNode extends Node {
|
||||
imports: ImportItem[]
|
||||
cached: number
|
||||
codegenNode?: TemplateChildNode | JSChildNode | BlockStatement | undefined
|
||||
ssrHelpers?: symbol[]
|
||||
}
|
||||
|
||||
export type ElementNode =
|
||||
|
||||
@@ -74,7 +74,7 @@ function createCodegenContext(
|
||||
ast: RootNode,
|
||||
{
|
||||
mode = 'function',
|
||||
prefixIdentifiers = mode === 'module' || mode === 'cjs',
|
||||
prefixIdentifiers = mode === 'module',
|
||||
sourceMap = false,
|
||||
filename = `template.vue.html`,
|
||||
scopeId = null,
|
||||
@@ -169,7 +169,6 @@ export function generate(
|
||||
const {
|
||||
mode,
|
||||
push,
|
||||
helper,
|
||||
prefixIdentifiers,
|
||||
indent,
|
||||
deindent,
|
||||
@@ -182,58 +181,10 @@ export function generate(
|
||||
const genScopeId = !__BROWSER__ && scopeId != null && mode === 'module'
|
||||
|
||||
// preambles
|
||||
if (mode === 'function' || mode === 'cjs') {
|
||||
const VueBinding = mode === 'function' ? `Vue` : `require("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 (hasHelpers) {
|
||||
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`)
|
||||
}
|
||||
}
|
||||
}
|
||||
genHoists(ast.hoists, context)
|
||||
newline()
|
||||
push(`return `)
|
||||
if (mode === 'module') {
|
||||
genModulePreamble(ast, context, genScopeId)
|
||||
} else {
|
||||
// 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 (hasHelpers) {
|
||||
push(`import { ${ast.helpers.map(helper).join(', ')} } from "vue"\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 `)
|
||||
genFunctionPreamble(ast, context)
|
||||
}
|
||||
|
||||
// enter render function
|
||||
@@ -315,6 +266,82 @@ export function generate(
|
||||
}
|
||||
}
|
||||
|
||||
function genFunctionPreamble(ast: RootNode, context: CodegenContext) {
|
||||
const { mode, helper, prefixIdentifiers, push, newline } = context
|
||||
const VueBinding = mode === 'function' ? `Vue` : `require("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',
|
||||
|
||||
@@ -33,6 +33,7 @@ export { transformOn } from './transforms/vOn'
|
||||
|
||||
// exported for compiler-ssr
|
||||
export { processIfBranches } from './transforms/vIf'
|
||||
export { processForNode } from './transforms/vFor'
|
||||
export {
|
||||
transformExpression,
|
||||
processExpression
|
||||
|
||||
@@ -23,7 +23,6 @@ export interface ParserOptions {
|
||||
// this number is based on the map above, but it should be pre-computed
|
||||
// to avoid the cost on every parse() call.
|
||||
maxCRNameLength?: number
|
||||
|
||||
onError?: (error: CompilerError) => void
|
||||
}
|
||||
|
||||
@@ -32,6 +31,8 @@ export interface TransformOptions {
|
||||
directiveTransforms?: { [name: string]: DirectiveTransform | undefined }
|
||||
isBuiltInComponent?: (tag: string) => symbol | void
|
||||
// Transform expressions like {{ foo }} to `_ctx.foo`.
|
||||
// If this option is false, the generated code will be wrapped in a
|
||||
// `with (this) { ... }` block.
|
||||
// - This is force-enabled in module mode, since modules are by default strict
|
||||
// and cannot use `with`
|
||||
// - Default: mode === 'module'
|
||||
@@ -48,6 +49,7 @@ export interface TransformOptions {
|
||||
// analysis to determine if a handler is safe to cache.
|
||||
// - Default: false
|
||||
cacheHandlers?: boolean
|
||||
ssr?: boolean
|
||||
onError?: (error: CompilerError) => void
|
||||
}
|
||||
|
||||
@@ -61,13 +63,6 @@ export interface CodegenOptions {
|
||||
// `require('vue')`.
|
||||
// - Default: 'function'
|
||||
mode?: 'module' | 'function' | 'cjs'
|
||||
// Prefix suitable identifiers with _ctx.
|
||||
// If this option is false, the generated code will be wrapped in a
|
||||
// `with (this) { ... }` block.
|
||||
// - This is force-enabled in module mode, since modules are by default strict
|
||||
// and cannot use `with`
|
||||
// - Default: mode === 'module'
|
||||
prefixIdentifiers?: boolean
|
||||
// Generate source map?
|
||||
// - Default: false
|
||||
sourceMap?: boolean
|
||||
@@ -76,7 +71,9 @@ export interface CodegenOptions {
|
||||
filename?: string
|
||||
// SFC scoped styles ID
|
||||
scopeId?: string | null
|
||||
// generate SSR specific code?
|
||||
// we need to know about this to generate proper preambles
|
||||
prefixIdentifiers?: boolean
|
||||
// generate ssr-specific code?
|
||||
ssr?: boolean
|
||||
}
|
||||
|
||||
|
||||
@@ -115,6 +115,7 @@ function createTransformContext(
|
||||
nodeTransforms = [],
|
||||
directiveTransforms = {},
|
||||
isBuiltInComponent = NOOP,
|
||||
ssr = false,
|
||||
onError = defaultOnError
|
||||
}: TransformOptions
|
||||
): TransformContext {
|
||||
@@ -126,6 +127,7 @@ function createTransformContext(
|
||||
nodeTransforms,
|
||||
directiveTransforms,
|
||||
isBuiltInComponent,
|
||||
ssr,
|
||||
onError,
|
||||
|
||||
// state
|
||||
@@ -256,10 +258,19 @@ export function transform(root: RootNode, options: TransformOptions) {
|
||||
if (options.hoistStatic) {
|
||||
hoistStatic(root, context)
|
||||
}
|
||||
finalizeRoot(root, context)
|
||||
if (!options.ssr) {
|
||||
createRootCodegen(root, context)
|
||||
}
|
||||
// finalize meta information
|
||||
root.helpers = [...context.helpers]
|
||||
root.components = [...context.components]
|
||||
root.directives = [...context.directives]
|
||||
root.imports = [...context.imports]
|
||||
root.hoists = context.hoists
|
||||
root.cached = context.cached
|
||||
}
|
||||
|
||||
function finalizeRoot(root: RootNode, context: TransformContext) {
|
||||
function createRootCodegen(root: RootNode, context: TransformContext) {
|
||||
const { helper } = context
|
||||
const { children } = root
|
||||
const child = children[0]
|
||||
@@ -304,13 +315,6 @@ function finalizeRoot(root: RootNode, context: TransformContext) {
|
||||
} else {
|
||||
// no children = noop. codegen will return null.
|
||||
}
|
||||
// finalize meta information
|
||||
root.helpers = [...context.helpers]
|
||||
root.components = [...context.components]
|
||||
root.directives = [...context.directives]
|
||||
root.imports = [...context.imports]
|
||||
root.hoists = context.hoists
|
||||
root.cached = context.cached
|
||||
}
|
||||
|
||||
export function traverseChildren(
|
||||
@@ -359,13 +363,17 @@ export function traverseNode(
|
||||
|
||||
switch (node.type) {
|
||||
case NodeTypes.COMMENT:
|
||||
// inject import for the Comment symbol, which is needed for creating
|
||||
// comment nodes with `createVNode`
|
||||
context.helper(CREATE_COMMENT)
|
||||
if (!context.ssr) {
|
||||
// inject import for the Comment symbol, which is needed for creating
|
||||
// comment nodes with `createVNode`
|
||||
context.helper(CREATE_COMMENT)
|
||||
}
|
||||
break
|
||||
case NodeTypes.INTERPOLATION:
|
||||
// no need to traverse, but we need to inject toString helper
|
||||
context.helper(TO_DISPLAY_STRING)
|
||||
if (!context.ssr) {
|
||||
context.helper(TO_DISPLAY_STRING)
|
||||
}
|
||||
break
|
||||
|
||||
// for container types, further traverse downwards
|
||||
|
||||
@@ -17,7 +17,10 @@ import {
|
||||
ForCodegenNode,
|
||||
ElementCodegenNode,
|
||||
SlotOutletCodegenNode,
|
||||
SlotOutletNode
|
||||
SlotOutletNode,
|
||||
ElementNode,
|
||||
DirectiveNode,
|
||||
ForNode
|
||||
} from '../ast'
|
||||
import { createCompilerError, ErrorCodes } from '../errors'
|
||||
import {
|
||||
@@ -41,141 +44,163 @@ import { PatchFlags, PatchFlagNames } from '@vue/shared'
|
||||
export const transformFor = createStructuralDirectiveTransform(
|
||||
'for',
|
||||
(node, dir, context) => {
|
||||
if (!dir.exp) {
|
||||
context.onError(
|
||||
createCompilerError(ErrorCodes.X_V_FOR_NO_EXPRESSION, dir.loc)
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
const parseResult = parseForExpression(
|
||||
// can only be simple expression because vFor transform is applied
|
||||
// before expression transform.
|
||||
dir.exp as SimpleExpressionNode,
|
||||
context
|
||||
)
|
||||
|
||||
if (!parseResult) {
|
||||
context.onError(
|
||||
createCompilerError(ErrorCodes.X_V_FOR_MALFORMED_EXPRESSION, dir.loc)
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
const { helper, addIdentifiers, removeIdentifiers, scopes } = context
|
||||
const { source, value, key, index } = parseResult
|
||||
|
||||
// create the loop render function expression now, and add the
|
||||
// iterator on exit after all children have been traversed
|
||||
const renderExp = createCallExpression(helper(RENDER_LIST), [source])
|
||||
const keyProp = findProp(node, `key`)
|
||||
const fragmentFlag = keyProp
|
||||
? PatchFlags.KEYED_FRAGMENT
|
||||
: PatchFlags.UNKEYED_FRAGMENT
|
||||
const codegenNode = createSequenceExpression([
|
||||
// fragment blocks disable tracking since they always diff their children
|
||||
createCallExpression(helper(OPEN_BLOCK), [`false`]),
|
||||
createCallExpression(helper(CREATE_BLOCK), [
|
||||
helper(FRAGMENT),
|
||||
`null`,
|
||||
renderExp,
|
||||
`${fragmentFlag} /* ${PatchFlagNames[fragmentFlag]} */`
|
||||
const { helper } = context
|
||||
return processForNode(node, dir, context, (forNode, parseResult) => {
|
||||
// create the loop render function expression now, and add the
|
||||
// iterator on exit after all children have been traversed
|
||||
const renderExp = createCallExpression(helper(RENDER_LIST), [
|
||||
forNode.source
|
||||
])
|
||||
]) as ForCodegenNode
|
||||
const keyProp = findProp(node, `key`)
|
||||
const fragmentFlag = keyProp
|
||||
? PatchFlags.KEYED_FRAGMENT
|
||||
: PatchFlags.UNKEYED_FRAGMENT
|
||||
forNode.codegenNode = createSequenceExpression([
|
||||
// fragment blocks disable tracking since they always diff their children
|
||||
createCallExpression(helper(OPEN_BLOCK), [`false`]),
|
||||
createCallExpression(helper(CREATE_BLOCK), [
|
||||
helper(FRAGMENT),
|
||||
`null`,
|
||||
renderExp,
|
||||
`${fragmentFlag} /* ${PatchFlagNames[fragmentFlag]} */`
|
||||
])
|
||||
]) as ForCodegenNode
|
||||
|
||||
context.replaceNode({
|
||||
type: NodeTypes.FOR,
|
||||
loc: dir.loc,
|
||||
source,
|
||||
valueAlias: value,
|
||||
keyAlias: key,
|
||||
objectIndexAlias: index,
|
||||
children: node.tagType === ElementTypes.TEMPLATE ? node.children : [node],
|
||||
codegenNode
|
||||
})
|
||||
|
||||
// bookkeeping
|
||||
scopes.vFor++
|
||||
if (!__BROWSER__ && context.prefixIdentifiers) {
|
||||
// scope management
|
||||
// inject identifiers to context
|
||||
value && addIdentifiers(value)
|
||||
key && addIdentifiers(key)
|
||||
index && addIdentifiers(index)
|
||||
}
|
||||
|
||||
return () => {
|
||||
scopes.vFor--
|
||||
if (!__BROWSER__ && context.prefixIdentifiers) {
|
||||
value && removeIdentifiers(value)
|
||||
key && removeIdentifiers(key)
|
||||
index && removeIdentifiers(index)
|
||||
}
|
||||
|
||||
// finish the codegen now that all children have been traversed
|
||||
let childBlock
|
||||
const isTemplate = isTemplateNode(node)
|
||||
const slotOutlet = isSlotOutlet(node)
|
||||
? node
|
||||
: isTemplate &&
|
||||
node.children.length === 1 &&
|
||||
isSlotOutlet(node.children[0])
|
||||
? (node.children[0] as SlotOutletNode) // api-extractor somehow fails to infer this
|
||||
return () => {
|
||||
// finish the codegen now that all children have been traversed
|
||||
let childBlock
|
||||
const isTemplate = isTemplateNode(node)
|
||||
const slotOutlet = isSlotOutlet(node)
|
||||
? node
|
||||
: isTemplate &&
|
||||
node.children.length === 1 &&
|
||||
isSlotOutlet(node.children[0])
|
||||
? (node.children[0] as SlotOutletNode) // api-extractor somehow fails to infer this
|
||||
: null
|
||||
const keyProperty = keyProp
|
||||
? createObjectProperty(
|
||||
`key`,
|
||||
keyProp.type === NodeTypes.ATTRIBUTE
|
||||
? createSimpleExpression(keyProp.value!.content, true)
|
||||
: keyProp.exp!
|
||||
)
|
||||
: null
|
||||
const keyProperty = keyProp
|
||||
? createObjectProperty(
|
||||
`key`,
|
||||
keyProp.type === NodeTypes.ATTRIBUTE
|
||||
? createSimpleExpression(keyProp.value!.content, true)
|
||||
: keyProp.exp!
|
||||
if (slotOutlet) {
|
||||
// <slot v-for="..."> or <template v-for="..."><slot/></template>
|
||||
childBlock = slotOutlet.codegenNode as SlotOutletCodegenNode
|
||||
if (isTemplate && keyProperty) {
|
||||
// <template v-for="..." :key="..."><slot/></template>
|
||||
// we need to inject the key to the renderSlot() call.
|
||||
// the props for renderSlot is passed as the 3rd argument.
|
||||
injectProp(childBlock, keyProperty, context)
|
||||
}
|
||||
} else if (isTemplate) {
|
||||
// <template v-for="...">
|
||||
// should generate a fragment block for each loop
|
||||
childBlock = createBlockExpression(
|
||||
createCallExpression(helper(CREATE_BLOCK), [
|
||||
helper(FRAGMENT),
|
||||
keyProperty ? createObjectExpression([keyProperty]) : `null`,
|
||||
node.children,
|
||||
`${PatchFlags.STABLE_FRAGMENT} /* ${
|
||||
PatchFlagNames[PatchFlags.STABLE_FRAGMENT]
|
||||
} */`
|
||||
]),
|
||||
context
|
||||
)
|
||||
: null
|
||||
if (slotOutlet) {
|
||||
// <slot v-for="..."> or <template v-for="..."><slot/></template>
|
||||
childBlock = slotOutlet.codegenNode as SlotOutletCodegenNode
|
||||
if (isTemplate && keyProperty) {
|
||||
// <template v-for="..." :key="..."><slot/></template>
|
||||
// we need to inject the key to the renderSlot() call.
|
||||
// the props for renderSlot is passed as the 3rd argument.
|
||||
injectProp(childBlock, keyProperty, context)
|
||||
}
|
||||
} else if (isTemplate) {
|
||||
// <template v-for="...">
|
||||
// should generate a fragment block for each loop
|
||||
childBlock = createBlockExpression(
|
||||
createCallExpression(helper(CREATE_BLOCK), [
|
||||
helper(FRAGMENT),
|
||||
keyProperty ? createObjectExpression([keyProperty]) : `null`,
|
||||
node.children,
|
||||
`${PatchFlags.STABLE_FRAGMENT} /* ${
|
||||
PatchFlagNames[PatchFlags.STABLE_FRAGMENT]
|
||||
} */`
|
||||
]),
|
||||
context
|
||||
)
|
||||
} else {
|
||||
// Normal element v-for. Directly use the child's codegenNode
|
||||
// arguments, but replace createVNode() with createBlock()
|
||||
let codegenNode = node.codegenNode as ElementCodegenNode
|
||||
if (codegenNode.callee === WITH_DIRECTIVES) {
|
||||
codegenNode.arguments[0].callee = helper(CREATE_BLOCK)
|
||||
} else {
|
||||
codegenNode.callee = helper(CREATE_BLOCK)
|
||||
// Normal element v-for. Directly use the child's codegenNode
|
||||
// arguments, but replace createVNode() with createBlock()
|
||||
let codegenNode = node.codegenNode as ElementCodegenNode
|
||||
if (codegenNode.callee === WITH_DIRECTIVES) {
|
||||
codegenNode.arguments[0].callee = helper(CREATE_BLOCK)
|
||||
} else {
|
||||
codegenNode.callee = helper(CREATE_BLOCK)
|
||||
}
|
||||
childBlock = createBlockExpression(codegenNode, context)
|
||||
}
|
||||
childBlock = createBlockExpression(codegenNode, context)
|
||||
}
|
||||
|
||||
renderExp.arguments.push(
|
||||
createFunctionExpression(
|
||||
createForLoopParams(parseResult),
|
||||
childBlock,
|
||||
true /* force newline */
|
||||
renderExp.arguments.push(
|
||||
createFunctionExpression(
|
||||
createForLoopParams(parseResult),
|
||||
childBlock,
|
||||
true /* force newline */
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
// target-agnostic transform used for both Client and SSR
|
||||
export function processForNode(
|
||||
node: ElementNode,
|
||||
dir: DirectiveNode,
|
||||
context: TransformContext,
|
||||
processCodegen?: (
|
||||
forNode: ForNode,
|
||||
parseResult: ForParseResult
|
||||
) => (() => void) | undefined
|
||||
) {
|
||||
if (!dir.exp) {
|
||||
context.onError(
|
||||
createCompilerError(ErrorCodes.X_V_FOR_NO_EXPRESSION, dir.loc)
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
const parseResult = parseForExpression(
|
||||
// can only be simple expression because vFor transform is applied
|
||||
// before expression transform.
|
||||
dir.exp as SimpleExpressionNode,
|
||||
context
|
||||
)
|
||||
|
||||
if (!parseResult) {
|
||||
context.onError(
|
||||
createCompilerError(ErrorCodes.X_V_FOR_MALFORMED_EXPRESSION, dir.loc)
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
const { addIdentifiers, removeIdentifiers, scopes } = context
|
||||
const { source, value, key, index } = parseResult
|
||||
|
||||
const forNode: ForNode = {
|
||||
type: NodeTypes.FOR,
|
||||
loc: dir.loc,
|
||||
source,
|
||||
valueAlias: value,
|
||||
keyAlias: key,
|
||||
objectIndexAlias: index,
|
||||
children: node.tagType === ElementTypes.TEMPLATE ? node.children : [node]
|
||||
}
|
||||
|
||||
context.replaceNode(forNode)
|
||||
|
||||
// bookkeeping
|
||||
scopes.vFor++
|
||||
if (!__BROWSER__ && context.prefixIdentifiers) {
|
||||
// scope management
|
||||
// inject identifiers to context
|
||||
value && addIdentifiers(value)
|
||||
key && addIdentifiers(key)
|
||||
index && addIdentifiers(index)
|
||||
}
|
||||
|
||||
const onExit = processCodegen && processCodegen(forNode, parseResult)
|
||||
|
||||
return () => {
|
||||
scopes.vFor--
|
||||
if (!__BROWSER__ && context.prefixIdentifiers) {
|
||||
value && removeIdentifiers(value)
|
||||
key && removeIdentifiers(key)
|
||||
index && removeIdentifiers(index)
|
||||
}
|
||||
if (onExit) onExit()
|
||||
}
|
||||
}
|
||||
|
||||
const forAliasRE = /([\s\S]*?)\s+(?:in|of)\s+([\s\S]*)/
|
||||
// This regex doesn't cover the case if key or index aliases have destructuring,
|
||||
// but those do not make sense in the first place, so this works in practice.
|
||||
|
||||
@@ -71,7 +71,8 @@ export const transformIf = createStructuralDirectiveTransform(
|
||||
}
|
||||
)
|
||||
|
||||
export const processIfBranches = (
|
||||
// target-agnostic transform used for both Client and SSR
|
||||
export function processIfBranches(
|
||||
node: ElementNode,
|
||||
dir: DirectiveNode,
|
||||
context: TransformContext,
|
||||
@@ -79,8 +80,8 @@ export const processIfBranches = (
|
||||
node: IfNode,
|
||||
branch: IfBranchNode,
|
||||
isRoot: boolean
|
||||
) => (() => void) | void
|
||||
) => {
|
||||
) => (() => void) | undefined
|
||||
) {
|
||||
if (
|
||||
dir.name !== 'else' &&
|
||||
(!dir.exp || !(dir.exp as SimpleExpressionNode).content.trim())
|
||||
|
||||
Reference in New Issue
Block a user