feat(compiler-vapor): node transform
This commit is contained in:
parent
0b765bcea3
commit
cfd6d40d72
|
@ -16,6 +16,9 @@ PR are welcome! However, please create an issue before you start to work on it,
|
|||
- [x] simple bindings
|
||||
- [x] simple events
|
||||
- [ ] TODO-MVC App
|
||||
- [ ] transform
|
||||
- [x] NodeTransform
|
||||
- [ ] DirectiveTransform
|
||||
- [ ] directives
|
||||
- [x] `v-once`
|
||||
- [x] `v-html`
|
||||
|
|
|
@ -85,6 +85,13 @@ export interface Position {
|
|||
column: number
|
||||
}
|
||||
|
||||
export type AllNode =
|
||||
| ParentNode
|
||||
| ExpressionNode
|
||||
| TemplateChildNode
|
||||
| AttributeNode
|
||||
| DirectiveNode
|
||||
|
||||
export type ParentNode = RootNode | ElementNode | IfBranchNode | ForNode
|
||||
|
||||
export type ExpressionNode = SimpleExpressionNode | CompoundExpressionNode
|
||||
|
|
|
@ -68,5 +68,6 @@ export { generateCodeFrame } from '@vue/shared'
|
|||
export {
|
||||
checkCompatEnabled,
|
||||
warnDeprecation,
|
||||
CompilerDeprecationTypes
|
||||
CompilerDeprecationTypes,
|
||||
type CompilerCompatOptions
|
||||
} from './compat/compatConfig'
|
||||
|
|
|
@ -224,6 +224,7 @@ export function assert(condition: boolean, msg?: string) {
|
|||
}
|
||||
}
|
||||
|
||||
/** find directive */
|
||||
export function findDir(
|
||||
node: ElementNode,
|
||||
name: string | RegExp,
|
||||
|
|
|
@ -104,16 +104,14 @@ export function render(_ctx) {
|
|||
`;
|
||||
|
||||
exports[`compile > directives > v-once > as root node 1`] = `
|
||||
"import { template, children, effect, setAttr } from 'vue/vapor';
|
||||
"import { template, children, setAttr } from 'vue/vapor';
|
||||
const t0 = template('<div></div>');
|
||||
export function render(_ctx) {
|
||||
const n0 = t0();
|
||||
const {
|
||||
0: [n1],
|
||||
} = children(n0);
|
||||
effect(() => {
|
||||
setAttr(n1, 'id', undefined, foo);
|
||||
});
|
||||
setAttr(n1, 'id', undefined, foo);
|
||||
return n0;
|
||||
}
|
||||
"
|
||||
|
|
|
@ -1,8 +1,12 @@
|
|||
import { BindingTypes, CompilerOptions, RootNode } from '@vue/compiler-dom'
|
||||
import { type RootNode, BindingTypes } from '@vue/compiler-dom'
|
||||
import {
|
||||
type CompilerOptions,
|
||||
VaporErrorCodes,
|
||||
compile as _compile,
|
||||
} from '../src'
|
||||
|
||||
// TODO remove it
|
||||
import { format } from 'prettier'
|
||||
import { compile as _compile } from '../src'
|
||||
import { ErrorCodes } from '../src/errors'
|
||||
|
||||
async function compile(
|
||||
template: string | RootNode,
|
||||
|
@ -78,7 +82,7 @@ describe('compile', () => {
|
|||
await compile(`<div v-bind:arg />`, { onError })
|
||||
|
||||
expect(onError.mock.calls[0][0]).toMatchObject({
|
||||
code: ErrorCodes.VAPOR_BIND_NO_EXPRESSION,
|
||||
code: VaporErrorCodes.X_VAPOR_BIND_NO_EXPRESSION,
|
||||
loc: {
|
||||
start: {
|
||||
line: 1,
|
||||
|
@ -107,7 +111,7 @@ describe('compile', () => {
|
|||
const onError = vi.fn()
|
||||
await compile(`<div v-on:click />`, { onError })
|
||||
expect(onError.mock.calls[0][0]).toMatchObject({
|
||||
code: ErrorCodes.VAPOR_ON_NO_EXPRESSION,
|
||||
code: VaporErrorCodes.X_VAPOR_ON_NO_EXPRESSION,
|
||||
loc: {
|
||||
start: {
|
||||
line: 1,
|
||||
|
@ -170,9 +174,9 @@ describe('compile', () => {
|
|||
test('basic', async () => {
|
||||
const code = await compile(
|
||||
`<div v-once>
|
||||
{{ msg }}
|
||||
<span :class="clz" />
|
||||
</div>`,
|
||||
{{ msg }}
|
||||
<span :class="clz" />
|
||||
</div>`,
|
||||
{
|
||||
bindingMetadata: {
|
||||
msg: BindingTypes.SETUP_REF,
|
||||
|
@ -183,7 +187,7 @@ describe('compile', () => {
|
|||
expect(code).matchSnapshot()
|
||||
})
|
||||
|
||||
test.fails('as root node', async () => {
|
||||
test('as root node', async () => {
|
||||
const code = await compile(`<div :id="foo" v-once />`)
|
||||
expect(code).toMatchSnapshot()
|
||||
expect(code).not.contains('effect')
|
||||
|
|
|
@ -3,11 +3,11 @@ import { parse, compileScript } from '@vue/compiler-sfc'
|
|||
import source from './fixtures/counter.vue?raw'
|
||||
|
||||
test('fixtures', async () => {
|
||||
const { descriptor } = parse(source, { compiler: CompilerVapor })
|
||||
const { descriptor } = parse(source, { compiler: CompilerVapor as any })
|
||||
const script = compileScript(descriptor, {
|
||||
id: 'counter.vue',
|
||||
inlineTemplate: true,
|
||||
templateOptions: { compiler: CompilerVapor },
|
||||
templateOptions: { compiler: CompilerVapor as any },
|
||||
})
|
||||
expect(script.content).matchSnapshot()
|
||||
})
|
||||
|
|
|
@ -1,19 +1,86 @@
|
|||
import {
|
||||
type CodegenResult,
|
||||
type CompilerOptions,
|
||||
type CompilerOptions as BaseCompilerOptions,
|
||||
type RootNode,
|
||||
type DirectiveTransform,
|
||||
parse,
|
||||
} from '@vue/compiler-dom'
|
||||
import { isString } from '@vue/shared'
|
||||
import { transform } from './transform'
|
||||
import { extend, isString } from '@vue/shared'
|
||||
import { NodeTransform, transform } from './transform'
|
||||
import { generate } from './generate'
|
||||
import { defaultOnError, createCompilerError, VaporErrorCodes } from './errors'
|
||||
import { transformOnce } from './transforms/vOnce'
|
||||
import { HackOptions } from './hack'
|
||||
|
||||
export type CompilerOptions = HackOptions<BaseCompilerOptions>
|
||||
|
||||
// TODO: copied from @vue/compiler-core
|
||||
// code/AST -> IR -> JS codegen
|
||||
export function compile(
|
||||
template: string | RootNode,
|
||||
source: string | RootNode,
|
||||
options: CompilerOptions = {},
|
||||
): CodegenResult {
|
||||
const ast = isString(template) ? parse(template, options) : template
|
||||
const ir = transform(ast, options)
|
||||
return generate(ir, options)
|
||||
const onError = options.onError || defaultOnError
|
||||
const isModuleMode = options.mode === 'module'
|
||||
/* istanbul ignore if */
|
||||
if (__BROWSER__) {
|
||||
if (options.prefixIdentifiers === true) {
|
||||
onError(createCompilerError(VaporErrorCodes.X_PREFIX_ID_NOT_SUPPORTED))
|
||||
} else if (isModuleMode) {
|
||||
onError(createCompilerError(VaporErrorCodes.X_MODULE_MODE_NOT_SUPPORTED))
|
||||
}
|
||||
}
|
||||
|
||||
const prefixIdentifiers =
|
||||
!__BROWSER__ && (options.prefixIdentifiers === true || isModuleMode)
|
||||
|
||||
// TODO scope id
|
||||
// if (options.scopeId && !isModuleMode) {
|
||||
// onError(createCompilerError(ErrorCodes.X_SCOPE_ID_NOT_SUPPORTED))
|
||||
// }
|
||||
|
||||
const ast = isString(source) ? parse(source, options) : source
|
||||
const [nodeTransforms, directiveTransforms] =
|
||||
getBaseTransformPreset(prefixIdentifiers)
|
||||
|
||||
if (!__BROWSER__ && options.isTS) {
|
||||
const { expressionPlugins } = options
|
||||
if (!expressionPlugins || !expressionPlugins.includes('typescript')) {
|
||||
options.expressionPlugins = [...(expressionPlugins || []), 'typescript']
|
||||
}
|
||||
}
|
||||
|
||||
const ir = transform(
|
||||
ast,
|
||||
extend({}, options, {
|
||||
prefixIdentifiers,
|
||||
nodeTransforms: [
|
||||
...nodeTransforms,
|
||||
...(options.nodeTransforms || []), // user transforms
|
||||
],
|
||||
directiveTransforms: extend(
|
||||
{},
|
||||
directiveTransforms,
|
||||
options.directiveTransforms || {}, // user transforms
|
||||
),
|
||||
}),
|
||||
)
|
||||
|
||||
return generate(
|
||||
ir,
|
||||
extend({}, options, {
|
||||
prefixIdentifiers,
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
export type TransformPreset = [
|
||||
NodeTransform[],
|
||||
Record<string, DirectiveTransform>,
|
||||
]
|
||||
|
||||
export function getBaseTransformPreset(
|
||||
prefixIdentifiers?: boolean,
|
||||
): TransformPreset {
|
||||
return [[transformOnce], {}]
|
||||
}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import { CompilerError } from '@vue/compiler-dom'
|
||||
import type { CompilerError } from '@vue/compiler-dom'
|
||||
|
||||
export { createCompilerError } from '@vue/compiler-dom'
|
||||
|
||||
export function defaultOnError(error: CompilerError) {
|
||||
throw error
|
||||
}
|
||||
|
@ -10,14 +9,21 @@ export function defaultOnWarn(msg: CompilerError) {
|
|||
__DEV__ && console.warn(`[Vue warn] ${msg.message}`)
|
||||
}
|
||||
|
||||
export enum ErrorCodes {
|
||||
export enum VaporErrorCodes {
|
||||
// transform errors
|
||||
VAPOR_BIND_NO_EXPRESSION,
|
||||
VAPOR_ON_NO_EXPRESSION,
|
||||
X_VAPOR_BIND_NO_EXPRESSION,
|
||||
X_VAPOR_ON_NO_EXPRESSION,
|
||||
|
||||
// generic errors
|
||||
X_PREFIX_ID_NOT_SUPPORTED,
|
||||
X_MODULE_MODE_NOT_SUPPORTED,
|
||||
}
|
||||
|
||||
export const errorMessages: Record<ErrorCodes, string> = {
|
||||
export const errorMessages: Record<VaporErrorCodes, string> = {
|
||||
// transform errors
|
||||
[ErrorCodes.VAPOR_BIND_NO_EXPRESSION]: `v-bind is missing expression.`,
|
||||
[ErrorCodes.VAPOR_ON_NO_EXPRESSION]: `v-on is missing expression.`,
|
||||
[VaporErrorCodes.X_VAPOR_BIND_NO_EXPRESSION]: `v-bind is missing expression.`,
|
||||
[VaporErrorCodes.X_VAPOR_ON_NO_EXPRESSION]: `v-on is missing expression.`,
|
||||
|
||||
[VaporErrorCodes.X_PREFIX_ID_NOT_SUPPORTED]: `"prefixIdentifiers" option is not supported in this build of compiler.`,
|
||||
[VaporErrorCodes.X_MODULE_MODE_NOT_SUPPORTED]: `ES module mode is not supported in this build of compiler.`,
|
||||
}
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
import type { Prettify } from '@vue/shared'
|
||||
import type { NodeTransform } from './transform'
|
||||
|
||||
type Overwrite<T, U> = Pick<T, Exclude<keyof T, keyof U>> &
|
||||
Pick<U, Extract<keyof U, keyof T>>
|
||||
|
||||
export type HackOptions<T> = Prettify<
|
||||
Overwrite<T, { nodeTransforms?: NodeTransform[] }>
|
||||
>
|
|
@ -1,5 +1,6 @@
|
|||
export { parse } from '@vue/compiler-dom'
|
||||
export { transform } from './transform'
|
||||
export { generate } from './generate'
|
||||
export { compile } from './compile'
|
||||
export { compile, type CompilerOptions } from './compile'
|
||||
export * from './ir'
|
||||
export * from './errors'
|
||||
|
|
|
@ -1,15 +1,17 @@
|
|||
import {
|
||||
type RootNode,
|
||||
type Node,
|
||||
type TemplateChildNode,
|
||||
type ElementNode,
|
||||
type AttributeNode,
|
||||
type InterpolationNode,
|
||||
type TransformOptions,
|
||||
type TransformOptions as BaseTransformOptions,
|
||||
type DirectiveNode,
|
||||
type ExpressionNode,
|
||||
type ParentNode,
|
||||
type AllNode,
|
||||
NodeTypes,
|
||||
BindingTypes,
|
||||
CompilerCompatOptions,
|
||||
} from '@vue/compiler-dom'
|
||||
import {
|
||||
type OperationNode,
|
||||
|
@ -17,25 +19,35 @@ import {
|
|||
IRNodeTypes,
|
||||
DynamicInfo,
|
||||
} from './ir'
|
||||
import { isVoidTag } from '@vue/shared'
|
||||
import { EMPTY_OBJ, NOOP, isArray, isVoidTag } from '@vue/shared'
|
||||
import {
|
||||
ErrorCodes,
|
||||
VaporErrorCodes,
|
||||
createCompilerError,
|
||||
defaultOnError,
|
||||
defaultOnWarn,
|
||||
} from './errors'
|
||||
import { HackOptions } from './hack'
|
||||
|
||||
export interface TransformContext<T extends Node = Node> {
|
||||
export type NodeTransform = (
|
||||
node: RootNode | TemplateChildNode,
|
||||
context: TransformContext,
|
||||
) => void | (() => void) | (() => void)[]
|
||||
|
||||
export type TransformOptions = HackOptions<BaseTransformOptions>
|
||||
|
||||
export interface TransformContext<T extends AllNode = AllNode> {
|
||||
node: T
|
||||
parent: TransformContext | null
|
||||
parent: TransformContext<ParentNode> | null
|
||||
root: TransformContext<RootNode>
|
||||
index: number
|
||||
options: TransformOptions
|
||||
options: Required<
|
||||
Omit<TransformOptions, 'filename' | keyof CompilerCompatOptions>
|
||||
>
|
||||
|
||||
template: string
|
||||
dynamic: DynamicInfo
|
||||
|
||||
once: boolean
|
||||
inVOnce: boolean
|
||||
|
||||
reference(): number
|
||||
increaseId(): number
|
||||
|
@ -45,10 +57,11 @@ export interface TransformContext<T extends Node = Node> {
|
|||
helper(name: string): string
|
||||
}
|
||||
|
||||
// TODO use class for better perf
|
||||
function createRootContext(
|
||||
ir: RootIRNode,
|
||||
node: RootNode,
|
||||
options: TransformOptions,
|
||||
options: TransformOptions = {},
|
||||
): TransformContext<RootNode> {
|
||||
let globalId = 0
|
||||
const { effect, operation: operation, helpers, vaporHelpers } = ir
|
||||
|
@ -58,9 +71,32 @@ function createRootContext(
|
|||
parent: null,
|
||||
index: 0,
|
||||
root: null!, // set later
|
||||
options,
|
||||
options: {
|
||||
filename: '',
|
||||
prefixIdentifiers: false,
|
||||
hoistStatic: false,
|
||||
hmr: false,
|
||||
cacheHandlers: false,
|
||||
nodeTransforms: [],
|
||||
directiveTransforms: {},
|
||||
transformHoist: null,
|
||||
isBuiltInComponent: NOOP,
|
||||
isCustomElement: NOOP,
|
||||
expressionPlugins: [],
|
||||
scopeId: null,
|
||||
slotted: true,
|
||||
ssr: false,
|
||||
inSSR: false,
|
||||
ssrCssVars: ``,
|
||||
bindingMetadata: EMPTY_OBJ,
|
||||
inline: false,
|
||||
isTS: false,
|
||||
onError: defaultOnError,
|
||||
onWarn: defaultOnWarn,
|
||||
...options,
|
||||
},
|
||||
dynamic: ir.dynamic,
|
||||
once: false,
|
||||
inVOnce: false,
|
||||
|
||||
increaseId: () => globalId++,
|
||||
reference() {
|
||||
|
@ -69,7 +105,7 @@ function createRootContext(
|
|||
return (this.dynamic.id = this.increaseId())
|
||||
},
|
||||
registerEffect(expr, operation) {
|
||||
if (this.once) {
|
||||
if (this.inVOnce) {
|
||||
return this.registerOperation(operation)
|
||||
}
|
||||
if (!effect[expr]) effect[expr] = []
|
||||
|
@ -110,7 +146,7 @@ function createRootContext(
|
|||
|
||||
function createContext<T extends TemplateChildNode>(
|
||||
node: T,
|
||||
parent: TransformContext,
|
||||
parent: TransformContext<ParentNode>,
|
||||
index: number,
|
||||
): TransformContext<T> {
|
||||
const ctx: TransformContext<T> = {
|
||||
|
@ -159,7 +195,7 @@ export function transform(
|
|||
const ctx = createRootContext(ir, root, options)
|
||||
|
||||
// TODO: transform presets, see packages/compiler-core/src/transforms
|
||||
transformChildren(ctx, true)
|
||||
transformNode(ctx)
|
||||
if (ir.template.length === 0) {
|
||||
ir.template.push({
|
||||
type: IRNodeTypes.FRAGMENT_FACTORY,
|
||||
|
@ -170,20 +206,108 @@ export function transform(
|
|||
return ir
|
||||
}
|
||||
|
||||
function transformChildren(
|
||||
ctx: TransformContext<RootNode | ElementNode>,
|
||||
root?: boolean,
|
||||
function transformNode(
|
||||
context: TransformContext<RootNode | TemplateChildNode>,
|
||||
) {
|
||||
let { node, index } = context
|
||||
|
||||
// apply transform plugins
|
||||
const { nodeTransforms } = context.options
|
||||
const exitFns = []
|
||||
for (const nodeTransform of nodeTransforms) {
|
||||
// TODO nodeTransform type
|
||||
const onExit = nodeTransform(node, context as any)
|
||||
if (onExit) {
|
||||
if (isArray(onExit)) {
|
||||
exitFns.push(...onExit)
|
||||
} else {
|
||||
exitFns.push(onExit)
|
||||
}
|
||||
}
|
||||
if (!context.node) {
|
||||
// node was removed
|
||||
return
|
||||
} else {
|
||||
// node may have been replaced
|
||||
node = context.node
|
||||
}
|
||||
}
|
||||
|
||||
if (node.type === NodeTypes.ROOT) {
|
||||
transformChildren(context as TransformContext<RootNode>)
|
||||
return
|
||||
}
|
||||
|
||||
const parentChildren = context.parent!.node.children
|
||||
const isFirst = index === 0
|
||||
const isLast = index === parentChildren.length - 1
|
||||
|
||||
switch (node.type) {
|
||||
case NodeTypes.ELEMENT: {
|
||||
transformElement(context as TransformContext<ElementNode>)
|
||||
break
|
||||
}
|
||||
case NodeTypes.TEXT: {
|
||||
context.template += node.content
|
||||
break
|
||||
}
|
||||
case NodeTypes.COMMENT: {
|
||||
context.template += `<!--${node.content}-->`
|
||||
break
|
||||
}
|
||||
case NodeTypes.INTERPOLATION: {
|
||||
transformInterpolation(
|
||||
context as TransformContext<InterpolationNode>,
|
||||
isFirst,
|
||||
isLast,
|
||||
)
|
||||
break
|
||||
}
|
||||
case NodeTypes.TEXT_CALL:
|
||||
// never
|
||||
break
|
||||
default: {
|
||||
// TODO handle other types
|
||||
// CompoundExpressionNode
|
||||
// IfNode
|
||||
// IfBranchNode
|
||||
// ForNode
|
||||
context.template += `[type: ${node.type}]`
|
||||
}
|
||||
}
|
||||
|
||||
// exit transforms
|
||||
context.node = node
|
||||
let i = exitFns.length
|
||||
while (i--) {
|
||||
exitFns[i]()
|
||||
}
|
||||
}
|
||||
|
||||
function transformChildren(ctx: TransformContext<RootNode | ElementNode>) {
|
||||
const {
|
||||
node: { children },
|
||||
} = ctx
|
||||
const childrenTemplate: string[] = []
|
||||
children.forEach((child, i) => walkNode(child, i))
|
||||
children.forEach((child, index) => {
|
||||
const childContext = createContext(child, ctx, index)
|
||||
transformNode(childContext)
|
||||
|
||||
childrenTemplate.push(childContext.template)
|
||||
if (
|
||||
childContext.dynamic.ghost ||
|
||||
childContext.dynamic.referenced ||
|
||||
childContext.dynamic.placeholder ||
|
||||
Object.keys(childContext.dynamic.children).length
|
||||
) {
|
||||
ctx.dynamic.children[index] = childContext.dynamic
|
||||
}
|
||||
})
|
||||
|
||||
processDynamicChildren()
|
||||
ctx.template += childrenTemplate.join('')
|
||||
|
||||
if (root) ctx.registerTemplate()
|
||||
if (ctx.node.type === NodeTypes.ROOT) ctx.registerTemplate()
|
||||
|
||||
function processDynamicChildren() {
|
||||
let prevChildren: DynamicInfo[] = []
|
||||
|
@ -229,57 +353,6 @@ function transformChildren(
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
function walkNode(node: TemplateChildNode, index: number) {
|
||||
const child = createContext(node, ctx, index)
|
||||
const isFirst = index === 0
|
||||
const isLast = index === children.length - 1
|
||||
|
||||
switch (node.type) {
|
||||
case NodeTypes.ELEMENT: {
|
||||
transformElement(child as TransformContext<ElementNode>)
|
||||
break
|
||||
}
|
||||
case NodeTypes.TEXT: {
|
||||
child.template += node.content
|
||||
break
|
||||
}
|
||||
case NodeTypes.COMMENT: {
|
||||
child.template += `<!--${node.content}-->`
|
||||
break
|
||||
}
|
||||
case NodeTypes.INTERPOLATION: {
|
||||
transformInterpolation(
|
||||
child as TransformContext<InterpolationNode>,
|
||||
isFirst,
|
||||
isLast,
|
||||
)
|
||||
break
|
||||
}
|
||||
case NodeTypes.TEXT_CALL:
|
||||
// never?
|
||||
break
|
||||
default: {
|
||||
// TODO handle other types
|
||||
// CompoundExpressionNode
|
||||
// IfNode
|
||||
// IfBranchNode
|
||||
// ForNode
|
||||
child.template += `[type: ${node.type}]`
|
||||
}
|
||||
}
|
||||
|
||||
childrenTemplate.push(child.template)
|
||||
|
||||
if (
|
||||
child.dynamic.ghost ||
|
||||
child.dynamic.referenced ||
|
||||
child.dynamic.placeholder ||
|
||||
Object.keys(child.dynamic.children).length
|
||||
) {
|
||||
ctx.dynamic.children[index] = child.dynamic
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function transformElement(ctx: TransformContext<ElementNode>) {
|
||||
|
@ -365,7 +438,7 @@ function transformProp(
|
|||
(exp.type === NodeTypes.SIMPLE_EXPRESSION && !exp.content.trim())
|
||||
) {
|
||||
ctx.options.onError!(
|
||||
createCompilerError(ErrorCodes.VAPOR_BIND_NO_EXPRESSION, loc),
|
||||
createCompilerError(VaporErrorCodes.X_VAPOR_BIND_NO_EXPRESSION, loc),
|
||||
)
|
||||
return
|
||||
}
|
||||
|
@ -394,7 +467,7 @@ function transformProp(
|
|||
case 'on': {
|
||||
if (!exp && !modifiers.length) {
|
||||
ctx.options.onError!(
|
||||
createCompilerError(ErrorCodes.VAPOR_ON_NO_EXPRESSION, loc),
|
||||
createCompilerError(VaporErrorCodes.X_VAPOR_ON_NO_EXPRESSION, loc),
|
||||
)
|
||||
return
|
||||
}
|
||||
|
@ -441,10 +514,6 @@ function transformProp(
|
|||
})
|
||||
break
|
||||
}
|
||||
case 'once': {
|
||||
ctx.once = true
|
||||
break
|
||||
}
|
||||
case 'cloak': {
|
||||
// do nothing
|
||||
break
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
import { NodeTypes, findDir } from '@vue/compiler-dom'
|
||||
import { NodeTransform } from '../transform'
|
||||
|
||||
const seen = new WeakSet()
|
||||
|
||||
export const transformOnce: NodeTransform = (node, context) => {
|
||||
if (node.type === NodeTypes.ELEMENT && findDir(node, 'once', true)) {
|
||||
if (seen.has(node) || context.inVOnce /* || context.inSSR */) {
|
||||
return
|
||||
}
|
||||
seen.add(node)
|
||||
context.inVOnce = true
|
||||
}
|
||||
}
|
|
@ -13,7 +13,7 @@ export default defineConfig({
|
|||
plugins: [
|
||||
Vue({
|
||||
template: {
|
||||
compiler: CompilerVapor
|
||||
compiler: CompilerVapor as any
|
||||
},
|
||||
compiler: CompilerSFC
|
||||
}),
|
||||
|
|
Loading…
Reference in New Issue