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 bindings
|
||||||
- [x] simple events
|
- [x] simple events
|
||||||
- [ ] TODO-MVC App
|
- [ ] TODO-MVC App
|
||||||
|
- [ ] transform
|
||||||
|
- [x] NodeTransform
|
||||||
|
- [ ] DirectiveTransform
|
||||||
- [ ] directives
|
- [ ] directives
|
||||||
- [x] `v-once`
|
- [x] `v-once`
|
||||||
- [x] `v-html`
|
- [x] `v-html`
|
||||||
|
|
|
@ -85,6 +85,13 @@ export interface Position {
|
||||||
column: number
|
column: number
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type AllNode =
|
||||||
|
| ParentNode
|
||||||
|
| ExpressionNode
|
||||||
|
| TemplateChildNode
|
||||||
|
| AttributeNode
|
||||||
|
| DirectiveNode
|
||||||
|
|
||||||
export type ParentNode = RootNode | ElementNode | IfBranchNode | ForNode
|
export type ParentNode = RootNode | ElementNode | IfBranchNode | ForNode
|
||||||
|
|
||||||
export type ExpressionNode = SimpleExpressionNode | CompoundExpressionNode
|
export type ExpressionNode = SimpleExpressionNode | CompoundExpressionNode
|
||||||
|
|
|
@ -68,5 +68,6 @@ export { generateCodeFrame } from '@vue/shared'
|
||||||
export {
|
export {
|
||||||
checkCompatEnabled,
|
checkCompatEnabled,
|
||||||
warnDeprecation,
|
warnDeprecation,
|
||||||
CompilerDeprecationTypes
|
CompilerDeprecationTypes,
|
||||||
|
type CompilerCompatOptions
|
||||||
} from './compat/compatConfig'
|
} from './compat/compatConfig'
|
||||||
|
|
|
@ -224,6 +224,7 @@ export function assert(condition: boolean, msg?: string) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** find directive */
|
||||||
export function findDir(
|
export function findDir(
|
||||||
node: ElementNode,
|
node: ElementNode,
|
||||||
name: string | RegExp,
|
name: string | RegExp,
|
||||||
|
|
|
@ -104,16 +104,14 @@ export function render(_ctx) {
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`compile > directives > v-once > as root node 1`] = `
|
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>');
|
const t0 = template('<div></div>');
|
||||||
export function render(_ctx) {
|
export function render(_ctx) {
|
||||||
const n0 = t0();
|
const n0 = t0();
|
||||||
const {
|
const {
|
||||||
0: [n1],
|
0: [n1],
|
||||||
} = children(n0);
|
} = children(n0);
|
||||||
effect(() => {
|
|
||||||
setAttr(n1, 'id', undefined, foo);
|
setAttr(n1, 'id', undefined, foo);
|
||||||
});
|
|
||||||
return n0;
|
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
|
// TODO remove it
|
||||||
import { format } from 'prettier'
|
import { format } from 'prettier'
|
||||||
import { compile as _compile } from '../src'
|
|
||||||
import { ErrorCodes } from '../src/errors'
|
|
||||||
|
|
||||||
async function compile(
|
async function compile(
|
||||||
template: string | RootNode,
|
template: string | RootNode,
|
||||||
|
@ -78,7 +82,7 @@ describe('compile', () => {
|
||||||
await compile(`<div v-bind:arg />`, { onError })
|
await compile(`<div v-bind:arg />`, { onError })
|
||||||
|
|
||||||
expect(onError.mock.calls[0][0]).toMatchObject({
|
expect(onError.mock.calls[0][0]).toMatchObject({
|
||||||
code: ErrorCodes.VAPOR_BIND_NO_EXPRESSION,
|
code: VaporErrorCodes.X_VAPOR_BIND_NO_EXPRESSION,
|
||||||
loc: {
|
loc: {
|
||||||
start: {
|
start: {
|
||||||
line: 1,
|
line: 1,
|
||||||
|
@ -107,7 +111,7 @@ describe('compile', () => {
|
||||||
const onError = vi.fn()
|
const onError = vi.fn()
|
||||||
await compile(`<div v-on:click />`, { onError })
|
await compile(`<div v-on:click />`, { onError })
|
||||||
expect(onError.mock.calls[0][0]).toMatchObject({
|
expect(onError.mock.calls[0][0]).toMatchObject({
|
||||||
code: ErrorCodes.VAPOR_ON_NO_EXPRESSION,
|
code: VaporErrorCodes.X_VAPOR_ON_NO_EXPRESSION,
|
||||||
loc: {
|
loc: {
|
||||||
start: {
|
start: {
|
||||||
line: 1,
|
line: 1,
|
||||||
|
@ -183,7 +187,7 @@ describe('compile', () => {
|
||||||
expect(code).matchSnapshot()
|
expect(code).matchSnapshot()
|
||||||
})
|
})
|
||||||
|
|
||||||
test.fails('as root node', async () => {
|
test('as root node', async () => {
|
||||||
const code = await compile(`<div :id="foo" v-once />`)
|
const code = await compile(`<div :id="foo" v-once />`)
|
||||||
expect(code).toMatchSnapshot()
|
expect(code).toMatchSnapshot()
|
||||||
expect(code).not.contains('effect')
|
expect(code).not.contains('effect')
|
||||||
|
|
|
@ -3,11 +3,11 @@ import { parse, compileScript } from '@vue/compiler-sfc'
|
||||||
import source from './fixtures/counter.vue?raw'
|
import source from './fixtures/counter.vue?raw'
|
||||||
|
|
||||||
test('fixtures', async () => {
|
test('fixtures', async () => {
|
||||||
const { descriptor } = parse(source, { compiler: CompilerVapor })
|
const { descriptor } = parse(source, { compiler: CompilerVapor as any })
|
||||||
const script = compileScript(descriptor, {
|
const script = compileScript(descriptor, {
|
||||||
id: 'counter.vue',
|
id: 'counter.vue',
|
||||||
inlineTemplate: true,
|
inlineTemplate: true,
|
||||||
templateOptions: { compiler: CompilerVapor },
|
templateOptions: { compiler: CompilerVapor as any },
|
||||||
})
|
})
|
||||||
expect(script.content).matchSnapshot()
|
expect(script.content).matchSnapshot()
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,19 +1,86 @@
|
||||||
import {
|
import {
|
||||||
type CodegenResult,
|
type CodegenResult,
|
||||||
type CompilerOptions,
|
type CompilerOptions as BaseCompilerOptions,
|
||||||
type RootNode,
|
type RootNode,
|
||||||
|
type DirectiveTransform,
|
||||||
parse,
|
parse,
|
||||||
} from '@vue/compiler-dom'
|
} from '@vue/compiler-dom'
|
||||||
import { isString } from '@vue/shared'
|
import { extend, isString } from '@vue/shared'
|
||||||
import { transform } from './transform'
|
import { NodeTransform, transform } from './transform'
|
||||||
import { generate } from './generate'
|
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
|
// code/AST -> IR -> JS codegen
|
||||||
export function compile(
|
export function compile(
|
||||||
template: string | RootNode,
|
source: string | RootNode,
|
||||||
options: CompilerOptions = {},
|
options: CompilerOptions = {},
|
||||||
): CodegenResult {
|
): CodegenResult {
|
||||||
const ast = isString(template) ? parse(template, options) : template
|
const onError = options.onError || defaultOnError
|
||||||
const ir = transform(ast, options)
|
const isModuleMode = options.mode === 'module'
|
||||||
return generate(ir, options)
|
/* 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 { createCompilerError } from '@vue/compiler-dom'
|
||||||
|
|
||||||
export function defaultOnError(error: CompilerError) {
|
export function defaultOnError(error: CompilerError) {
|
||||||
throw error
|
throw error
|
||||||
}
|
}
|
||||||
|
@ -10,14 +9,21 @@ export function defaultOnWarn(msg: CompilerError) {
|
||||||
__DEV__ && console.warn(`[Vue warn] ${msg.message}`)
|
__DEV__ && console.warn(`[Vue warn] ${msg.message}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum ErrorCodes {
|
export enum VaporErrorCodes {
|
||||||
// transform errors
|
// transform errors
|
||||||
VAPOR_BIND_NO_EXPRESSION,
|
X_VAPOR_BIND_NO_EXPRESSION,
|
||||||
VAPOR_ON_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
|
// transform errors
|
||||||
[ErrorCodes.VAPOR_BIND_NO_EXPRESSION]: `v-bind is missing expression.`,
|
[VaporErrorCodes.X_VAPOR_BIND_NO_EXPRESSION]: `v-bind is missing expression.`,
|
||||||
[ErrorCodes.VAPOR_ON_NO_EXPRESSION]: `v-on 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 { parse } from '@vue/compiler-dom'
|
||||||
export { transform } from './transform'
|
export { transform } from './transform'
|
||||||
export { generate } from './generate'
|
export { generate } from './generate'
|
||||||
export { compile } from './compile'
|
export { compile, type CompilerOptions } from './compile'
|
||||||
export * from './ir'
|
export * from './ir'
|
||||||
|
export * from './errors'
|
||||||
|
|
|
@ -1,15 +1,17 @@
|
||||||
import {
|
import {
|
||||||
type RootNode,
|
type RootNode,
|
||||||
type Node,
|
|
||||||
type TemplateChildNode,
|
type TemplateChildNode,
|
||||||
type ElementNode,
|
type ElementNode,
|
||||||
type AttributeNode,
|
type AttributeNode,
|
||||||
type InterpolationNode,
|
type InterpolationNode,
|
||||||
type TransformOptions,
|
type TransformOptions as BaseTransformOptions,
|
||||||
type DirectiveNode,
|
type DirectiveNode,
|
||||||
type ExpressionNode,
|
type ExpressionNode,
|
||||||
|
type ParentNode,
|
||||||
|
type AllNode,
|
||||||
NodeTypes,
|
NodeTypes,
|
||||||
BindingTypes,
|
BindingTypes,
|
||||||
|
CompilerCompatOptions,
|
||||||
} from '@vue/compiler-dom'
|
} from '@vue/compiler-dom'
|
||||||
import {
|
import {
|
||||||
type OperationNode,
|
type OperationNode,
|
||||||
|
@ -17,25 +19,35 @@ import {
|
||||||
IRNodeTypes,
|
IRNodeTypes,
|
||||||
DynamicInfo,
|
DynamicInfo,
|
||||||
} from './ir'
|
} from './ir'
|
||||||
import { isVoidTag } from '@vue/shared'
|
import { EMPTY_OBJ, NOOP, isArray, isVoidTag } from '@vue/shared'
|
||||||
import {
|
import {
|
||||||
ErrorCodes,
|
VaporErrorCodes,
|
||||||
createCompilerError,
|
createCompilerError,
|
||||||
defaultOnError,
|
defaultOnError,
|
||||||
defaultOnWarn,
|
defaultOnWarn,
|
||||||
} from './errors'
|
} 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
|
node: T
|
||||||
parent: TransformContext | null
|
parent: TransformContext<ParentNode> | null
|
||||||
root: TransformContext<RootNode>
|
root: TransformContext<RootNode>
|
||||||
index: number
|
index: number
|
||||||
options: TransformOptions
|
options: Required<
|
||||||
|
Omit<TransformOptions, 'filename' | keyof CompilerCompatOptions>
|
||||||
|
>
|
||||||
|
|
||||||
template: string
|
template: string
|
||||||
dynamic: DynamicInfo
|
dynamic: DynamicInfo
|
||||||
|
|
||||||
once: boolean
|
inVOnce: boolean
|
||||||
|
|
||||||
reference(): number
|
reference(): number
|
||||||
increaseId(): number
|
increaseId(): number
|
||||||
|
@ -45,10 +57,11 @@ export interface TransformContext<T extends Node = Node> {
|
||||||
helper(name: string): string
|
helper(name: string): string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO use class for better perf
|
||||||
function createRootContext(
|
function createRootContext(
|
||||||
ir: RootIRNode,
|
ir: RootIRNode,
|
||||||
node: RootNode,
|
node: RootNode,
|
||||||
options: TransformOptions,
|
options: TransformOptions = {},
|
||||||
): TransformContext<RootNode> {
|
): TransformContext<RootNode> {
|
||||||
let globalId = 0
|
let globalId = 0
|
||||||
const { effect, operation: operation, helpers, vaporHelpers } = ir
|
const { effect, operation: operation, helpers, vaporHelpers } = ir
|
||||||
|
@ -58,9 +71,32 @@ function createRootContext(
|
||||||
parent: null,
|
parent: null,
|
||||||
index: 0,
|
index: 0,
|
||||||
root: null!, // set later
|
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,
|
dynamic: ir.dynamic,
|
||||||
once: false,
|
inVOnce: false,
|
||||||
|
|
||||||
increaseId: () => globalId++,
|
increaseId: () => globalId++,
|
||||||
reference() {
|
reference() {
|
||||||
|
@ -69,7 +105,7 @@ function createRootContext(
|
||||||
return (this.dynamic.id = this.increaseId())
|
return (this.dynamic.id = this.increaseId())
|
||||||
},
|
},
|
||||||
registerEffect(expr, operation) {
|
registerEffect(expr, operation) {
|
||||||
if (this.once) {
|
if (this.inVOnce) {
|
||||||
return this.registerOperation(operation)
|
return this.registerOperation(operation)
|
||||||
}
|
}
|
||||||
if (!effect[expr]) effect[expr] = []
|
if (!effect[expr]) effect[expr] = []
|
||||||
|
@ -110,7 +146,7 @@ function createRootContext(
|
||||||
|
|
||||||
function createContext<T extends TemplateChildNode>(
|
function createContext<T extends TemplateChildNode>(
|
||||||
node: T,
|
node: T,
|
||||||
parent: TransformContext,
|
parent: TransformContext<ParentNode>,
|
||||||
index: number,
|
index: number,
|
||||||
): TransformContext<T> {
|
): TransformContext<T> {
|
||||||
const ctx: TransformContext<T> = {
|
const ctx: TransformContext<T> = {
|
||||||
|
@ -159,7 +195,7 @@ export function transform(
|
||||||
const ctx = createRootContext(ir, root, options)
|
const ctx = createRootContext(ir, root, options)
|
||||||
|
|
||||||
// TODO: transform presets, see packages/compiler-core/src/transforms
|
// TODO: transform presets, see packages/compiler-core/src/transforms
|
||||||
transformChildren(ctx, true)
|
transformNode(ctx)
|
||||||
if (ir.template.length === 0) {
|
if (ir.template.length === 0) {
|
||||||
ir.template.push({
|
ir.template.push({
|
||||||
type: IRNodeTypes.FRAGMENT_FACTORY,
|
type: IRNodeTypes.FRAGMENT_FACTORY,
|
||||||
|
@ -170,20 +206,108 @@ export function transform(
|
||||||
return ir
|
return ir
|
||||||
}
|
}
|
||||||
|
|
||||||
function transformChildren(
|
function transformNode(
|
||||||
ctx: TransformContext<RootNode | ElementNode>,
|
context: TransformContext<RootNode | TemplateChildNode>,
|
||||||
root?: boolean,
|
|
||||||
) {
|
) {
|
||||||
|
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 {
|
const {
|
||||||
node: { children },
|
node: { children },
|
||||||
} = ctx
|
} = ctx
|
||||||
const childrenTemplate: string[] = []
|
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()
|
processDynamicChildren()
|
||||||
ctx.template += childrenTemplate.join('')
|
ctx.template += childrenTemplate.join('')
|
||||||
|
|
||||||
if (root) ctx.registerTemplate()
|
if (ctx.node.type === NodeTypes.ROOT) ctx.registerTemplate()
|
||||||
|
|
||||||
function processDynamicChildren() {
|
function processDynamicChildren() {
|
||||||
let prevChildren: DynamicInfo[] = []
|
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>) {
|
function transformElement(ctx: TransformContext<ElementNode>) {
|
||||||
|
@ -365,7 +438,7 @@ function transformProp(
|
||||||
(exp.type === NodeTypes.SIMPLE_EXPRESSION && !exp.content.trim())
|
(exp.type === NodeTypes.SIMPLE_EXPRESSION && !exp.content.trim())
|
||||||
) {
|
) {
|
||||||
ctx.options.onError!(
|
ctx.options.onError!(
|
||||||
createCompilerError(ErrorCodes.VAPOR_BIND_NO_EXPRESSION, loc),
|
createCompilerError(VaporErrorCodes.X_VAPOR_BIND_NO_EXPRESSION, loc),
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -394,7 +467,7 @@ function transformProp(
|
||||||
case 'on': {
|
case 'on': {
|
||||||
if (!exp && !modifiers.length) {
|
if (!exp && !modifiers.length) {
|
||||||
ctx.options.onError!(
|
ctx.options.onError!(
|
||||||
createCompilerError(ErrorCodes.VAPOR_ON_NO_EXPRESSION, loc),
|
createCompilerError(VaporErrorCodes.X_VAPOR_ON_NO_EXPRESSION, loc),
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -441,10 +514,6 @@ function transformProp(
|
||||||
})
|
})
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
case 'once': {
|
|
||||||
ctx.once = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
case 'cloak': {
|
case 'cloak': {
|
||||||
// do nothing
|
// do nothing
|
||||||
break
|
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: [
|
plugins: [
|
||||||
Vue({
|
Vue({
|
||||||
template: {
|
template: {
|
||||||
compiler: CompilerVapor
|
compiler: CompilerVapor as any
|
||||||
},
|
},
|
||||||
compiler: CompilerSFC
|
compiler: CompilerSFC
|
||||||
}),
|
}),
|
||||||
|
|
Loading…
Reference in New Issue