diff --git a/docs/introduction/getting-started.md b/docs/introduction/getting-started.md index b2cb1be..563f77e 100644 --- a/docs/introduction/getting-started.md +++ b/docs/introduction/getting-started.md @@ -25,7 +25,7 @@ We assume you are already familiar with the basic usages of Vue before you conti pnpm add vue-jsx-vapor # runtime -pnpm add https://pkg.pr.new/vue@51677cd +pnpm add https://pkg.pr.new/vue@715b798 ``` The Vue Vapor runtime is not release, so we use [pkg.pr.new](https://github.com/stackblitz-labs/pkg.pr.new) to install. diff --git a/packages/babel/package.json b/packages/babel/package.json index b8cb77c..cd42597 100644 --- a/packages/babel/package.json +++ b/packages/babel/package.json @@ -46,7 +46,7 @@ "@babel/traverse": "catalog:", "@babel/types": "catalog:", "@vue-jsx-vapor/compiler": "workspace:*", - "source-map-js": "catalog:" + "source-map-js": "^1.2.1" }, "devDependencies": { "@types/babel__core": "catalog:", diff --git a/packages/babel/src/index.ts b/packages/babel/src/index.ts index dc2f92f..a398a84 100644 --- a/packages/babel/src/index.ts +++ b/packages/babel/src/index.ts @@ -14,7 +14,8 @@ export type Options = { filename: string importSet: Set delegateEventSet: Set - templates: string[] + preambleMap: Map + preambleIndex: number file: BabelFile roots: { node: JSXElement | JSXFragment @@ -42,7 +43,8 @@ export default (): { enter: (path, state) => { state.importSet = new Set() state.delegateEventSet = new Set() - state.templates = [] + state.preambleMap = new Map() + state.preambleIndex = 0 state.roots = [] const collectRoot: VisitNodeFunction< Node, @@ -76,24 +78,20 @@ export default (): { }) }, exit: (path, state) => { - const { delegateEventSet, importSet, templates } = state + const { delegateEventSet, importSet, preambleMap } = state const statements: string[] = [] if (delegateEventSet.size) { statements.unshift( - `_delegateEvents("${Array.from(delegateEventSet).join('", "')}");`, + `_delegateEvents(${Array.from(delegateEventSet).join(', ')});`, ) } - if (templates.length) { - let preambleResult = 'const ' - const definedTemplates: Record = {} - templates.forEach((template, index) => { - preambleResult += `t${index} = ${ - definedTemplates[template] || template - }${templates.length - 1 === index ? ';' : ','}\n` - definedTemplates[template] = `t${index}` - }) + if (preambleMap.size) { + let preambleResult = '' + for (const [value, key] of preambleMap) { + preambleResult += `const ${key} = ${value}\n` + } statements.unshift(preambleResult) } diff --git a/packages/babel/src/transform.ts b/packages/babel/src/transform.ts index 2428e31..3b691c9 100644 --- a/packages/babel/src/transform.ts +++ b/packages/babel/src/transform.ts @@ -19,18 +19,37 @@ export const transformJSX: VisitNodeFunction< if (!root || !root.inVaporComponent) return const isTS = state.filename?.endsWith('tsx') - const { code, map, helpers, templates, delegates } = compile(root.node, { + let { code, helpers, preamble, map } = compile(root.node, { isTS, filename: state.filename, sourceMap: !!state.file.opts.sourceMaps, source: ' '.repeat(root.node.start || 0) + root.source, - templates: state.templates.slice(), ...state.opts.compile, }) helpers.forEach((helper) => state.importSet.add(helper)) - delegates.forEach((delegate) => state.delegateEventSet.add(delegate)) - state.templates.push(...templates.slice(state.templates.length)) + + preamble = preamble.replaceAll( + /(?<=const )t(?=(\d))/g, + `_t${state.preambleIndex}`, + ) + code = code.replaceAll(/(?<== )t(?=\d)/g, `_t${state.preambleIndex}`) + state.preambleIndex++ + + for (const [, key, value] of preamble.matchAll( + /const (_t\d+) = (_template\(.*\))/g, + )) { + const result = state.preambleMap.get(value) + if (result) { + code = code.replaceAll(key, result) + } else { + state.preambleMap.set(value, key) + } + } + + for (const [, events] of preamble.matchAll(/_delegateEvents\((.*)\)/g)) { + events.split(', ').forEach((event) => state.delegateEventSet.add(event)) + } const ast = parse(`(() => {${code}})()`, { sourceFilename: state.filename, diff --git a/packages/babel/test/__snapshots__/interop.spec.ts.snap b/packages/babel/test/__snapshots__/interop.spec.ts.snap deleted file mode 100644 index ec9a927..0000000 --- a/packages/babel/test/__snapshots__/interop.spec.ts.snap +++ /dev/null @@ -1,26 +0,0 @@ -// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html - -exports[`transform > transform multiple components 1`] = ` -"import { template as _template } from 'vue'; -const t0 = _template("
", true), - t1 = t0, - t2 = t1; -const A = defineComponent(() => { - defineVaporComponent(() => (() => { - const n0 = t0(); - return n0; - })()); - return () =>
; -}); -const B = defineVaporComponent(() => { - const C = defineComponent(() =>
); - const D = (() => { - const n0 = t1(); - return n0; - })(); - return (() => { - const n0 = t2(); - return n0; - })(); -});" -`; diff --git a/packages/babel/test/__snapshots__/transform.spec.ts.snap b/packages/babel/test/__snapshots__/transform.spec.ts.snap deleted file mode 100644 index 9f592e0..0000000 --- a/packages/babel/test/__snapshots__/transform.spec.ts.snap +++ /dev/null @@ -1,29 +0,0 @@ -// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html - -exports[`transform > transform multiple components 1`] = ` -"import { child as _child, delegateEvents as _delegateEvents, template as _template, createIf as _createIf } from 'vue'; -import { setNodes as _setNodes } from 'vue-jsx-vapor/runtime'; -const t0 = _template("
", true), - t1 = _template("
Hello
"), - t2 = _template("
World
"); -_delegateEvents("click", "dblclick"); -const a = (() => { - const n0 = t0(); - const x0 = _child(n0); - _setNodes(x0, () => Hello); - n0.$evtclick = e => onClick(e); - return n0; -})(); -const b = (() => { - const n0 = _createIf(() => foo, () => { - const n2 = t1(); - n2.$evtclick = e => onClick(e); - return n2; - }, () => { - const n4 = t2(); - n4.$evtdblclick = e => onDblclick(e); - return n4; - }); - return n0; -})();" -`; diff --git a/packages/babel/test/interop.spec.ts b/packages/babel/test/interop.spec.ts index 2f285d2..24a8631 100644 --- a/packages/babel/test/interop.spec.ts +++ b/packages/babel/test/interop.spec.ts @@ -19,6 +19,27 @@ describe('transform', () => { plugins: [[jsx, { interop: true }]], }, )! - expect(code).matchSnapshot() + expect(code).toMatchInlineSnapshot(` + "import { template as _template } from 'vue'; + const _t00 = _template("
", true); + const A = defineComponent(() => { + defineVaporComponent(() => (() => { + const n0 = _t00(); + return n0; + })()); + return () =>
; + }); + const B = defineVaporComponent(() => { + const C = defineComponent(() =>
); + const D = (() => { + const n0 = _t00(); + return n0; + })(); + return (() => { + const n0 = _t00(); + return n0; + })(); + });" + `) }) }) diff --git a/packages/babel/test/transform.spec.ts b/packages/babel/test/transform.spec.ts index 4bee9b5..fdf536e 100644 --- a/packages/babel/test/transform.spec.ts +++ b/packages/babel/test/transform.spec.ts @@ -12,6 +12,32 @@ describe('transform', () => { plugins: [[jsx]], }, )! - expect(code).matchSnapshot() + expect(code).toMatchInlineSnapshot(` + "import { child as _child, delegateEvents as _delegateEvents, template as _template, createIf as _createIf } from 'vue'; + import { setNodes as _setNodes } from 'vue-jsx-vapor/runtime'; + const _t00 = _template("
", true); + const _t10 = _template("
Hello
"); + const _t11 = _template("
World
"); + _delegateEvents("click", "dblclick"); + const a = (() => { + const n0 = _t00(); + const x0 = _child(n0); + _setNodes(x0, () => Hello); + n0.$evtclick = e => onClick(e); + return n0; + })(); + const b = (() => { + const n0 = _createIf(() => foo, () => { + const n2 = _t10(); + n2.$evtclick = e => onClick(e); + return n2; + }, () => { + const n4 = _t11(); + n4.$evtdblclick = e => onDblclick(e); + return n4; + }); + return n0; + })();" + `) }) }) diff --git a/packages/compiler/package.json b/packages/compiler/package.json index 9f6e3f6..dde3481 100644 --- a/packages/compiler/package.json +++ b/packages/compiler/package.json @@ -43,8 +43,7 @@ "@babel/parser": "catalog:", "@babel/types": "catalog:", "@vue/compiler-dom": "catalog:", - "@vue/shared": "catalog:", - "ast-kit": "^2.1.1", - "source-map-js": "catalog:" + "@vue/compiler-vapor": "catalog:", + "@vue/shared": "catalog:" } } diff --git a/packages/compiler/src/compile.ts b/packages/compiler/src/compile.ts index 6e4aba8..b4f8e9b 100644 --- a/packages/compiler/src/compile.ts +++ b/packages/compiler/src/compile.ts @@ -1,7 +1,17 @@ import { parse } from '@babel/parser' +import { + generate, + type VaporCodegenResult as BaseVaporCodegenResult, +} from '@vue/compiler-vapor' import { extend, isString } from '@vue/shared' -import { generate, type VaporCodegenResult } from './generate' -import { IRNodeTypes, type HackOptions, type RootNode } from './ir' +import { customGenOperation } from './generate' + +import { + IRNodeTypes, + type HackOptions, + type RootIRNode, + type RootNode, +} from './ir' import { transform, type DirectiveTransform, @@ -25,12 +35,22 @@ import { transformVText } from './transforms/vText' import type { ExpressionStatement, JSXElement, JSXFragment } from '@babel/types' import type { CompilerOptions as BaseCompilerOptions } from '@vue/compiler-dom' +export { generate } + +export interface VaporCodegenResult + extends Omit { + ast: RootIRNode + customHelpers: Set +} + // code/AST -> IR (transform) -> JS (generate) export function compile( source: JSXElement | JSXFragment | string, options: CompilerOptions = {}, ): VaporCodegenResult { const resolvedOptions = extend({}, options, { + inline: true, + prefixIdentifiers: false, expressionPlugins: options.expressionPlugins || ['jsx'], }) if (!resolvedOptions.source && isString(source)) { @@ -81,12 +101,14 @@ export function compile( }), ) - return generate(ir, resolvedOptions) + return generate(ir as any, { + ...resolvedOptions, + customGenOperation, + }) as unknown as VaporCodegenResult } export type CompilerOptions = HackOptions & { source?: string - templates?: string[] } export type TransformPreset = [ NodeTransform[], diff --git a/packages/compiler/src/generate.ts b/packages/compiler/src/generate.ts index 23f500d..e2251f5 100644 --- a/packages/compiler/src/generate.ts +++ b/packages/compiler/src/generate.ts @@ -1,138 +1,67 @@ -import { extend, remove } from '@vue/shared' -import { genBlockContent } from './generators/block' -import { genTemplates } from './generators/template' -import { setTemplateRefIdent } from './generators/templateRef' import { - buildCodeFragment, - codeFragmentToString, - INDENT_END, - INDENT_START, + genCall, + genExpression, NEWLINE, -} from './generators/utils' -import type { BlockIRNode, RootIRNode } from './ir' -import type { - CodegenOptions as BaseCodegenOptions, - BaseCodegenResult, - SimpleExpressionNode, -} from '@vue/compiler-dom' - -export type CodegenOptions = Omit< - BaseCodegenOptions, - 'optimizeImports' | 'inline' | 'bindingMetadata' | 'prefixIdentifiers' -> & { - templates?: string[] -} - -export class CodegenContext { - options: Required - - helpers: Set = new Set([]) - - helper = (name: string) => { - this.helpers.add(name) - return `_${name}` - } - - delegates: Set = new Set() - - identifiers: Record = - Object.create(null) - - seenInlineHandlerNames: Record = Object.create(null) - - block: BlockIRNode - withId( - fn: () => T, - map: Record, - ): T { - const { identifiers } = this - const ids = Object.keys(map) - - for (const id of ids) { - identifiers[id] ||= [] - identifiers[id].unshift(map[id] || id) - } - - const ret = fn() - ids.forEach((id) => remove(identifiers[id], map[id] || id)) - - return ret - } - - enterBlock(block: BlockIRNode) { - const parent = this.block - this.block = block - return (): BlockIRNode => (this.block = parent) - } - - scopeLevel: number = 0 - enterScope(): [level: number, exit: () => number] { - return [this.scopeLevel++, () => this.scopeLevel--] as const - } - - constructor( - public ir: RootIRNode, - options: CodegenOptions, - ) { - const defaultOptions: Required = { - mode: 'module', - sourceMap: false, - filename: `template.vue.html`, - scopeId: null, - runtimeGlobalName: `Vue`, - runtimeModuleName: `vue`, - ssrRuntimeModuleName: 'vue/server-renderer', - ssr: false, - isTS: false, - inSSR: false, - templates: [], - expressionPlugins: [], - } - this.options = extend(defaultOptions, options) - this.block = ir.block + type CodeFragment, + type CodegenContext, +} from '@vue/compiler-vapor' +import { + IRNodeTypes, + type CreateNodesIRNode, + type OperationNode, + type SetNodesIRNode, +} from './ir' +import type { SimpleExpressionNode } from '@vue/compiler-dom' + +export const customGenOperation = ( + oper: OperationNode, + context: CodegenContext, +) => { + if (oper.type === IRNodeTypes.CREATE_NODES) { + return genCreateNodes(oper, context) + } else if (oper.type === IRNodeTypes.SET_NODES) { + return genSetNodes(oper, context) } } -export interface VaporCodegenResult - extends Omit { - ast: RootIRNode - helpers: Set - templates: string[] - delegates: Set +export function genSetNodes( + oper: SetNodesIRNode, + context: CodegenContext, +): CodeFragment[] { + const { helper } = context + const { element, values, generated } = oper + return [ + NEWLINE, + ...genCall( + helper('setNodes'), + `${generated ? 'x' : 'n'}${element}`, + combineValues(values, context), + ), + ] } -// IR -> JS codegen -export function generate( - ir: RootIRNode, - options: CodegenOptions = {}, -): VaporCodegenResult { - const [frag, push] = buildCodeFragment() - const context = new CodegenContext(ir, options) - const { helpers } = context - - push(INDENT_START) - if (ir.hasTemplateRef) { - push( - NEWLINE, - `const ${setTemplateRefIdent} = ${context.helper('createTemplateRefSetter')}()`, - ) - } - push(...genBlockContent(ir.block, context, true)) - push(INDENT_END, NEWLINE) - - if (context.delegates.size) { - context.helper('delegateEvents') - } - const templates = genTemplates(ir.templates, ir.rootTemplateIndex, context) - - const [code, map] = codeFragmentToString(frag, context) +export function genCreateNodes( + oper: CreateNodesIRNode, + context: CodegenContext, +): CodeFragment[] { + const { helper } = context + const { id, values } = oper + return [ + NEWLINE, + `const n${id} = `, + ...genCall(helper('createNodes'), values && combineValues(values, context)), + ] +} - return { - code, - ast: ir, - map: map && map.toJSON(), - helpers, - templates, - delegates: context.delegates, - } +function combineValues( + values: SimpleExpressionNode[], + context: CodegenContext, +): CodeFragment[] { + return values.flatMap((value, i) => { + const exp = genExpression(value, context) + if (i > 0) { + exp.unshift(', ') + } + return exp + }) } diff --git a/packages/compiler/src/generators/block.ts b/packages/compiler/src/generators/block.ts deleted file mode 100644 index ab86abd..0000000 --- a/packages/compiler/src/generators/block.ts +++ /dev/null @@ -1,96 +0,0 @@ -import { toValidAssetId } from '@vue/compiler-dom' -import type { CodegenContext } from '../generate' -import type { BlockIRNode } from '../ir' -import { genEffects, genOperations } from './operation' -import { genChildren, genSelf } from './template' -import { - buildCodeFragment, - DELIMITERS_ARRAY, - genCall, - genMulti, - INDENT_END, - INDENT_START, - NEWLINE, - type CodeFragment, -} from './utils' - -export function genBlock( - oper: BlockIRNode, - context: CodegenContext, - args: CodeFragment[] = [], - root?: boolean, - customReturns?: (returns: CodeFragment[]) => CodeFragment[], -): CodeFragment[] { - return [ - '(', - ...args, - ') => {', - INDENT_START, - ...genBlockContent(oper, context, root, customReturns), - INDENT_END, - NEWLINE, - '}', - ] -} - -export function genBlockContent( - block: BlockIRNode, - context: CodegenContext, - root?: boolean, - customReturns?: (returns: CodeFragment[]) => CodeFragment[], -): CodeFragment[] { - const [frag, push] = buildCodeFragment() - const { dynamic, effect, operation, returns } = block - const resetBlock = context.enterBlock(block) - - if (root) { - for (let name of context.ir.component) { - const id = toValidAssetId(name, 'component') - const maybeSelfReference = name.endsWith('__self') - if (maybeSelfReference) name = name.slice(0, -6) - push( - NEWLINE, - `const ${id} = `, - ...genCall( - context.helper('resolveComponent'), - JSON.stringify(name), - // pass additional `maybeSelfReference` flag - maybeSelfReference ? 'true' : undefined, - ), - ) - } - genResolveAssets('directive', 'resolveDirective') - } - - for (const child of dynamic.children) { - push(...genSelf(child, context)) - } - for (const child of dynamic.children) { - push(...genChildren(child, context, push, `n${child.id!}`)) - } - - push(...genOperations(operation, context)) - push(...genEffects(effect, context)) - - push(NEWLINE, `return `) - - const returnNodes = returns.map((n) => `n${n}`) - const returnsCode: CodeFragment[] = - returnNodes.length > 1 - ? genMulti(DELIMITERS_ARRAY, ...returnNodes) - : [returnNodes[0] || 'null'] - push(...(customReturns ? customReturns(returnsCode) : returnsCode)) - - resetBlock() - return frag - - function genResolveAssets(kind: 'component' | 'directive', helper: string) { - for (const name of context.ir[kind]) { - push( - NEWLINE, - `const ${toValidAssetId(name, kind)} = `, - ...genCall(context.helper(helper), JSON.stringify(name)), - ) - } - } -} diff --git a/packages/compiler/src/generators/component.ts b/packages/compiler/src/generators/component.ts deleted file mode 100644 index 2144b74..0000000 --- a/packages/compiler/src/generators/component.ts +++ /dev/null @@ -1,429 +0,0 @@ -import { - createSimpleExpression, - isMemberExpression, - toValidAssetId, - type SimpleExpressionNode, -} from '@vue/compiler-dom' -import { camelize, extend, isArray } from '@vue/shared' -import { walkIdentifiers } from 'ast-kit' -import { - IRDynamicPropsKind, - IRSlotType, - type CreateComponentIRNode, - type IRProp, - type IRProps, - type IRPropsStatic, - type IRSlotDynamic, - type IRSlotDynamicBasic, - type IRSlotDynamicConditional, - type IRSlotDynamicLoop, - type IRSlots, - type IRSlotsStatic, - type SlotBlockIRNode, -} from '../ir' -import type { CodegenContext } from '../generate' -import { genBlock } from './block' -import { genDirectiveModifiers, genDirectivesForElement } from './directive' -import { genEventHandler } from './event' -import { genExpression } from './expression' -import { genPropKey, genPropValue } from './prop' -import { - DELIMITERS_ARRAY_NEWLINE, - DELIMITERS_OBJECT, - DELIMITERS_OBJECT_NEWLINE, - genCall, - genMulti, - INDENT_END, - INDENT_START, - NEWLINE, - type CodeFragment, -} from './utils' -import { genModelHandler } from './vModel' - -export function genCreateComponent( - operation: CreateComponentIRNode, - context: CodegenContext, -): CodeFragment[] { - const { helper } = context - - const tag = genTag() - const { root, props, slots, once } = operation - const rawSlots = genRawSlots(slots, context) - const [ids, handlers] = processInlineHandlers(props, context) - const rawProps = context.withId(() => genRawProps(props, context), ids) - - const inlineHandlers: CodeFragment[] = handlers.reduce( - (acc, { name, value }) => { - const handler = genEventHandler(context, value, undefined, false) - return [...acc, `const ${name} = `, ...handler, NEWLINE] - }, - [], - ) - - return [ - NEWLINE, - ...inlineHandlers, - `const n${operation.id} = `, - ...genCall( - operation.dynamic && !operation.dynamic.isStatic - ? helper('createDynamicComponent') - : operation.asset - ? helper('createComponentWithFallback') - : helper('createComponent'), - tag, - rawProps, - rawSlots, - root ? 'true' : false, - once && 'true', - ), - ...genDirectivesForElement(operation.id, context), - ] - - function genTag() { - if (operation.dynamic) { - if (operation.dynamic.isStatic) { - return genCall( - helper('resolveDynamicComponent'), - genExpression(operation.dynamic, context), - ) - } else { - return ['() => (', ...genExpression(operation.dynamic, context), ')'] - } - } else if (operation.asset) { - return toValidAssetId(operation.tag, 'component') - } else { - return genExpression( - extend(createSimpleExpression(operation.tag, false), { ast: null }), - context, - ) - } - } -} - -function getUniqueHandlerName(context: CodegenContext, name: string): string { - const { seenInlineHandlerNames } = context - const count = seenInlineHandlerNames[name] || 0 - seenInlineHandlerNames[name] = count + 1 - return count === 0 ? name : `${name}${count}` -} - -type InlineHandler = { - name: string - value: SimpleExpressionNode -} - -function processInlineHandlers( - props: IRProps[], - context: CodegenContext, -): [Record, InlineHandler[]] { - const ids: Record = Object.create(null) - const handlers: InlineHandler[] = [] - const staticProps = props[0] - if (isArray(staticProps)) { - for (const prop of staticProps) { - if (!prop.handler) continue - prop.values.forEach((value, i) => { - const isMemberExp = isMemberExpression(value, context.options) - // cache inline handlers (fn expression or inline statement) - if (!isMemberExp) { - const name = getUniqueHandlerName(context, `_on_${prop.key.content}`) - handlers.push({ name, value }) - ids[name] = null - // replace the original prop value with the handler name - prop.values[i] = extend({ ast: null }, createSimpleExpression(name)) - } - }) - } - } - return [ids, handlers] -} - -export function genRawProps( - props: IRProps[], - context: CodegenContext, -): CodeFragment[] | undefined { - const staticProps = props[0] - if (isArray(staticProps)) { - if (!staticProps.length && props.length === 1) { - return - } - return genStaticProps( - staticProps, - context, - genDynamicProps(props.slice(1), context), - ) - } else if (props.length) { - // all dynamic - return genStaticProps([], context, genDynamicProps(props, context)) - } -} - -function genStaticProps( - props: IRPropsStatic, - context: CodegenContext, - dynamicProps?: CodeFragment[], -): CodeFragment[] { - const args = props.map((prop) => genProp(prop, context, true)) - if (dynamicProps) { - args.push([`$: `, ...dynamicProps]) - } - return genMulti( - args.length > 1 ? DELIMITERS_OBJECT_NEWLINE : DELIMITERS_OBJECT, - ...args, - ) -} - -function genDynamicProps( - props: IRProps[], - context: CodegenContext, -): CodeFragment[] | undefined { - const { helper } = context - const frags: CodeFragment[][] = [] - for (const p of props) { - let expr: CodeFragment[] - if (isArray(p)) { - if (p.length) { - frags.push(genStaticProps(p, context)) - } - continue - } else if (p.kind === IRDynamicPropsKind.ATTRIBUTE) - expr = genMulti(DELIMITERS_OBJECT, genProp(p, context)) - else { - expr = genExpression(p.value, context) - if (p.handler) expr = genCall(helper('toHandlers'), expr) - } - frags.push(['() => (', ...expr, ')']) - } - if (frags.length) { - return genMulti(DELIMITERS_ARRAY_NEWLINE, ...frags) - } -} - -function genProp(prop: IRProp, context: CodegenContext, isStatic?: boolean) { - const values = genPropValue(prop.values, context) - return [ - ...genPropKey(prop, context), - ': ', - ...(prop.handler - ? genEventHandler( - context, - prop.values[0], - prop.handlerModifiers, - true /* wrap handlers passed to components */, - ) - : isStatic - ? ['() => (', ...values, ')'] - : values), - ...(prop.model - ? [...genModelEvent(prop, context), ...genModelModifiers(prop, context)] - : []), - ] -} - -function genModelEvent(prop: IRProp, context: CodegenContext): CodeFragment[] { - const name = prop.key.isStatic - ? [JSON.stringify(`onUpdate:${camelize(prop.key.content)}`)] - : ['["onUpdate:" + ', ...genExpression(prop.key, context), ']'] - const handler = genModelHandler(prop.values[0], context) - - return [',', NEWLINE, ...name, ': () => ', ...handler] -} - -function genModelModifiers( - prop: IRProp, - context: CodegenContext, -): CodeFragment[] { - const { key, modelModifiers } = prop - if (!modelModifiers || !modelModifiers.length) return [] - - const modifiersKey = key.isStatic - ? [`${key.content}Modifiers`] - : ['[', ...genExpression(key, context), ' + "Modifiers"]'] - - const modifiersVal = genDirectiveModifiers(modelModifiers) - return [',', NEWLINE, ...modifiersKey, `: () => ({ ${modifiersVal} })`] -} - -function genRawSlots(slots: IRSlots[], context: CodegenContext) { - if (!slots.length) return - const staticSlots = slots[0] - if (staticSlots.slotType === IRSlotType.STATIC) { - // single static slot - return genStaticSlots( - staticSlots, - context, - slots.length > 1 ? slots.slice(1) : undefined, - ) - } else { - return genStaticSlots( - { slotType: IRSlotType.STATIC, slots: {} }, - context, - slots, - ) - } -} - -function genStaticSlots( - { slots }: IRSlotsStatic, - context: CodegenContext, - dynamicSlots?: IRSlots[], -) { - const args = Object.keys(slots).map((name) => [ - `${JSON.stringify(name)}: `, - ...genSlotBlockWithProps(slots[name], context), - ]) - if (dynamicSlots) { - args.push([`$: `, ...genDynamicSlots(dynamicSlots, context)]) - } - return genMulti(DELIMITERS_OBJECT_NEWLINE, ...args) -} - -function genDynamicSlots( - slots: IRSlots[], - context: CodegenContext, -): CodeFragment[] { - return genMulti( - DELIMITERS_ARRAY_NEWLINE, - ...slots.map((slot) => - slot.slotType === IRSlotType.STATIC - ? genStaticSlots(slot, context) - : slot.slotType === IRSlotType.EXPRESSION - ? slot.slots.content - : genDynamicSlot(slot, context, true), - ), - ) -} - -function genDynamicSlot( - slot: IRSlotDynamic, - context: CodegenContext, - withFunction = false, -): CodeFragment[] { - let frag: CodeFragment[] - switch (slot.slotType) { - case IRSlotType.DYNAMIC: - frag = genBasicDynamicSlot(slot, context) - break - case IRSlotType.LOOP: - frag = genLoopSlot(slot, context) - break - case IRSlotType.CONDITIONAL: - frag = genConditionalSlot(slot, context) - break - } - return withFunction ? ['() => (', ...frag, ')'] : frag -} - -function genBasicDynamicSlot( - slot: IRSlotDynamicBasic, - context: CodegenContext, -): CodeFragment[] { - const { name, fn } = slot - return genMulti( - DELIMITERS_OBJECT_NEWLINE, - ['name: ', ...genExpression(name, context)], - ['fn: ', ...genSlotBlockWithProps(fn, context)], - ) -} - -function genLoopSlot( - slot: IRSlotDynamicLoop, - context: CodegenContext, -): CodeFragment[] { - const { name, fn, loop } = slot - const { value, key, index, source } = loop - const rawValue = value && value.content - const rawKey = key && key.content - const rawIndex = index && index.content - - const idMap: Record = {} - if (rawValue) idMap[rawValue] = rawValue - if (rawKey) idMap[rawKey] = rawKey - if (rawIndex) idMap[rawIndex] = rawIndex - const slotExpr = genMulti( - DELIMITERS_OBJECT_NEWLINE, - ['name: ', ...context.withId(() => genExpression(name, context), idMap)], - [ - 'fn: ', - ...context.withId(() => genSlotBlockWithProps(fn, context), idMap), - ], - ) - return [ - ...genCall( - context.helper('createForSlots'), - genExpression(source, context), - [ - ...genMulti( - ['(', ')', ', '], - rawValue ? rawValue : rawKey || rawIndex ? '_' : undefined, - rawKey ? rawKey : rawIndex ? '__' : undefined, - rawIndex, - ), - ' => (', - ...slotExpr, - ')', - ], - ), - ] -} - -function genConditionalSlot( - slot: IRSlotDynamicConditional, - context: CodegenContext, -): CodeFragment[] { - const { condition, positive, negative } = slot - return [ - ...genExpression(condition, context), - INDENT_START, - NEWLINE, - '? ', - ...genDynamicSlot(positive, context), - NEWLINE, - ': ', - ...(negative ? [...genDynamicSlot(negative, context)] : ['void 0']), - INDENT_END, - ] -} - -function genSlotBlockWithProps(oper: SlotBlockIRNode, context: CodegenContext) { - let isDestructureAssignment = false - let rawProps: string | undefined - let propsName: string | undefined - let exitScope: (() => void) | undefined - let depth: number | undefined - const { props } = oper - const idsOfProps = new Set() - - if (props) { - rawProps = props.content - if ((isDestructureAssignment = !!props.ast)) { - ;[depth, exitScope] = context.enterScope() - propsName = `_slotProps${depth}` - walkIdentifiers( - props.ast, - (id, _, __, ___, isLocal) => { - if (isLocal) idsOfProps.add(id.name) - }, - true, - ) - } else { - idsOfProps.add((propsName = rawProps)) - } - } - - const idMap: Record = {} - - idsOfProps.forEach( - (id) => - (idMap[id] = isDestructureAssignment - ? `${propsName}[${JSON.stringify(id)}]` - : null), - ) - const blockFn = context.withId( - () => genBlock(oper, context, [propsName]), - idMap, - ) - exitScope && exitScope() - - return blockFn -} diff --git a/packages/compiler/src/generators/directive.ts b/packages/compiler/src/generators/directive.ts deleted file mode 100644 index bd752da..0000000 --- a/packages/compiler/src/generators/directive.ts +++ /dev/null @@ -1,111 +0,0 @@ -import { - createSimpleExpression, - isSimpleIdentifier, - toValidAssetId, -} from '@vue/compiler-dom' -import { extend } from '@vue/shared' -import { IRNodeTypes, type DirectiveIRNode, type OperationNode } from '../ir' -import type { CodegenContext } from '../generate' -import { genExpression } from './expression' -import { - DELIMITERS_ARRAY, - genCall, - genMulti, - NEWLINE, - type CodeFragment, - type CodeFragmentDelimiters, -} from './utils' -import { genVModel } from './vModel' -import { genVShow } from './vShow' - -export function genBuiltinDirective( - oper: DirectiveIRNode, - context: CodegenContext, -): CodeFragment[] { - switch (oper.name) { - case 'show': - return genVShow(oper, context) - case 'model': - return genVModel(oper, context) - default: - return [] - } -} - -/** - * user directives via `withVaporDirectives` - * TODO the compiler side is implemented but no runtime support yet - * it was removed due to perf issues - */ -export function genDirectivesForElement( - id: number, - context: CodegenContext, -): CodeFragment[] { - const dirs = filterCustomDirectives(id, context.block.operation) - return dirs.length ? genCustomDirectives(dirs, context) : [] -} - -function genCustomDirectives( - opers: DirectiveIRNode[], - context: CodegenContext, -): CodeFragment[] { - const { helper } = context - - const element = `n${opers[0].element}` - const directiveItems = opers.map(genDirectiveItem) - const directives = genMulti(DELIMITERS_ARRAY, ...directiveItems) - - return [ - NEWLINE, - ...genCall(helper('withVaporDirectives'), element, directives), - ] - - function genDirectiveItem({ - dir, - name, - asset, - }: DirectiveIRNode): CodeFragment[] { - const directiveVar = asset - ? toValidAssetId(name, 'directive') - : genExpression( - extend(createSimpleExpression(name, false), { ast: null }), - context, - ) - const value = dir.exp && ['() => ', ...genExpression(dir.exp, context)] - const argument = dir.arg && genExpression(dir.arg, context) - const modifiers = !!dir.modifiers.length && [ - '{ ', - genDirectiveModifiers(dir.modifiers.map((m) => m.content)), - ' }', - ] - - return genMulti( - DELIMITERS_ARRAY.concat('void 0') as CodeFragmentDelimiters, - directiveVar, - value, - argument, - modifiers, - ) - } -} - -export function genDirectiveModifiers(modifiers: string[]): string { - return modifiers - .map( - (value) => - `${isSimpleIdentifier(value) ? value : JSON.stringify(value)}: true`, - ) - .join(', ') -} - -function filterCustomDirectives( - id: number, - operations: OperationNode[], -): DirectiveIRNode[] { - return operations.filter( - (oper): oper is DirectiveIRNode => - oper.type === IRNodeTypes.DIRECTIVE && - oper.element === id && - !oper.builtin, - ) -} diff --git a/packages/compiler/src/generators/dom.ts b/packages/compiler/src/generators/dom.ts deleted file mode 100644 index 571dba5..0000000 --- a/packages/compiler/src/generators/dom.ts +++ /dev/null @@ -1,34 +0,0 @@ -import type { CodegenContext } from '../generate' -import type { InsertNodeIRNode, PrependNodeIRNode } from '../ir' -import { genCall, NEWLINE, type CodeFragment } from './utils' - -export function genInsertNode( - { parent, elements, anchor }: InsertNodeIRNode, - { helper }: CodegenContext, -): CodeFragment[] { - let element = elements.map((el) => `n${el}`).join(', ') - if (elements.length > 1) element = `[${element}]` - return [ - NEWLINE, - ...genCall( - helper('insert'), - element, - `n${parent}`, - anchor === undefined ? undefined : `n${anchor}`, - ), - ] -} - -export function genPrependNode( - oper: PrependNodeIRNode, - { helper }: CodegenContext, -): CodeFragment[] { - return [ - NEWLINE, - ...genCall( - helper('prepend'), - `n${oper.parent}`, - ...oper.elements.map((el) => `n${el}`), - ), - ] -} diff --git a/packages/compiler/src/generators/event.ts b/packages/compiler/src/generators/event.ts deleted file mode 100644 index 8d9a40f..0000000 --- a/packages/compiler/src/generators/event.ts +++ /dev/null @@ -1,180 +0,0 @@ -import { - isFnExpression, - isMemberExpression, - type SimpleExpressionNode, -} from '@vue/compiler-dom' -import { - IRNodeTypes, - type OperationNode, - type SetDynamicEventsIRNode, - type SetEventIRNode, -} from '../ir' -import type { CodegenContext } from '../generate' -import { genExpression } from './expression' -import { - DELIMITERS_OBJECT_NEWLINE, - genCall, - genMulti, - NEWLINE, - type CodeFragment, -} from './utils' - -export function genSetEvent( - oper: SetEventIRNode, - context: CodegenContext, -): CodeFragment[] { - const { helper } = context - const { element, key, keyOverride, value, modifiers, delegate, effect } = oper - - const name = genName() - const handler = genEventHandler(context, value, modifiers) - const eventOptions = genEventOptions() - - if (delegate) { - // key is static - context.delegates.add(key.content) - // if this is the only delegated event of this name on this element, - // we can generate optimized handler attachment code - // e.g. n1.$evtclick = () => {} - if (!context.block.operation.some(isSameDelegateEvent)) { - return [NEWLINE, `n${element}.$evt${key.content} = `, ...handler] - } - } - - return [ - NEWLINE, - ...genCall( - helper(delegate ? 'delegate' : 'on'), - `n${element}`, - name, - handler, - eventOptions, - ), - ] - - function genName(): CodeFragment[] { - const expr = genExpression(key, context) - if (keyOverride) { - // TODO unit test - const find = JSON.stringify(keyOverride[0]) - const replacement = JSON.stringify(keyOverride[1]) - const wrapped: CodeFragment[] = ['(', ...expr, ')'] - return [...wrapped, ` === ${find} ? ${replacement} : `, ...wrapped] - } else { - return genExpression(key, context) - } - } - - function genEventOptions(): CodeFragment[] | undefined { - const { options } = modifiers - if (!options.length && !effect) return - - return genMulti( - DELIMITERS_OBJECT_NEWLINE, - effect && ['effect: true'], - ...options.map((option): CodeFragment[] => [`${option}: true`]), - ) - } - - function isSameDelegateEvent(op: OperationNode) { - if ( - op.type === IRNodeTypes.SET_EVENT && - op !== oper && - op.delegate && - op.element === oper.element && - op.key.content === key.content - ) { - return true - } - } -} - -export function genSetDynamicEvents( - oper: SetDynamicEventsIRNode, - context: CodegenContext, -): CodeFragment[] { - const { helper } = context - return [ - NEWLINE, - ...genCall( - helper('setDynamicEvents'), - `n${oper.element}`, - genExpression(oper.event, context), - ), - ] -} - -export function genEventHandler( - context: CodegenContext, - value: SimpleExpressionNode | undefined, - modifiers: { - nonKeys: string[] - keys: string[] - } = { nonKeys: [], keys: [] }, - // passed as component prop - need additional wrap - extraWrap: boolean = false, -): CodeFragment[] { - let handlerExp: CodeFragment[] = [`() => {}`] - if (value && value.content.trim()) { - // Determine how the handler should be wrapped so it always reference the - // latest value when invoked. - if (isMemberExpression(value, context.options)) { - // e.g. @click="foo.bar" - handlerExp = genExpression(value, context) - if (!extraWrap) { - // non constant, wrap with invocation as `e => foo.bar(e)` - // when passing as component handler, access is always dynamic so we - // can skip this - handlerExp = [`e => `, ...handlerExp, `(e)`] - } - } else if (isFnExpression(value, context.options)) { - // Fn expression: @click="e => foo(e)" - // no need to wrap in this case - handlerExp = genExpression(value, context) - } else { - // inline statement - // @click="foo($event)" ---> $event => foo($event) - const referencesEvent = value.content.includes('$event') - const hasMultipleStatements = value.content.includes(`;`) - const expr = referencesEvent - ? context.withId(() => genExpression(value, context), { - $event: null, - }) - : genExpression(value, context) - handlerExp = [ - referencesEvent ? '$event => ' : '() => ', - hasMultipleStatements ? '{' : '(', - ...expr, - hasMultipleStatements ? '}' : ')', - ] - } - } - - const { keys, nonKeys } = modifiers - if (nonKeys.length) - handlerExp = genWithModifiers(context, handlerExp, nonKeys) - if (keys.length) handlerExp = genWithKeys(context, handlerExp, keys) - - if (extraWrap) handlerExp.unshift(`() => `) - return handlerExp -} - -function genWithModifiers( - context: CodegenContext, - handler: CodeFragment[], - nonKeys: string[], -): CodeFragment[] { - return genCall( - context.helper('withModifiers'), - handler, - JSON.stringify(nonKeys), - ) -} - -function genWithKeys( - context: CodegenContext, - handler: CodeFragment[], - keys: string[], -): CodeFragment[] { - return genCall(context.helper('withKeys'), handler, JSON.stringify(keys)) -} diff --git a/packages/compiler/src/generators/expression.ts b/packages/compiler/src/generators/expression.ts deleted file mode 100644 index 50fc67e..0000000 --- a/packages/compiler/src/generators/expression.ts +++ /dev/null @@ -1,151 +0,0 @@ -import { - advancePositionWithClone, - isStaticProperty, - NewlineType, - TS_NODE_TYPES, - type SimpleExpressionNode, - type SourceLocation, -} from '@vue/compiler-dom' -import { isString } from '@vue/shared' -import { walkIdentifiers } from 'ast-kit' -import { isConstantExpression } from '../utils' -import type { CodegenContext } from '../generate' -import { buildCodeFragment, type CodeFragment } from './utils' -import type { Identifier, Node } from '@babel/types' - -export function genExpression( - node: SimpleExpressionNode, - context: CodegenContext, - assignment?: string, -): CodeFragment[] { - const { content, ast, isStatic, loc } = node - - if (isStatic) { - return [[JSON.stringify(content), NewlineType.None, loc]] - } - - if ( - !node.content.trim() || - // there was a parsing error - ast === false || - isConstantExpression(node) - ) { - return [[content, NewlineType.None, loc], assignment && ` = ${assignment}`] - } - - // the expression is a simple identifier - if (ast === null) { - return genIdentifier(content, context, loc, assignment) - } - - const ids: Identifier[] = [] - const parentStackMap = new Map() - const parentStack: Node[] = [] - walkIdentifiers( - ast!, - (id) => { - ids.push(id) - parentStackMap.set(id, parentStack.slice()) - }, - false, - parentStack, - ) - - let hasMemberExpression = false - if (ids.length) { - const [frag, push] = buildCodeFragment() - const isTSNode = ast && TS_NODE_TYPES.includes(ast.type) - const offset = - (ast?.start ? ast.start - 1 : 0) - ((ast as any)._offset || 0) - ids - .sort((a, b) => a.start! - b.start!) - .forEach((id, i) => { - // range is offset by -1 due to the wrapping parens when parsed - const start = id.start! - 1 - offset - const end = id.end! - 1 - offset - const last = ids[i - 1] - - if (!isTSNode || i !== 0) { - const leadingText = content.slice( - last ? last.end! - 1 - offset : 0, - start, - ) - if (leadingText.length) push([leadingText, NewlineType.Unknown]) - } - - const source = content.slice(start, end) - const parentStack = parentStackMap.get(id)! - const parent = parentStack.at(-1) - - hasMemberExpression ||= - !!parent && - (parent.type === 'MemberExpression' || - parent.type === 'OptionalMemberExpression') - - push( - ...genIdentifier( - source, - context, - { - start: advancePositionWithClone(node.loc.start, source, start), - end: advancePositionWithClone(node.loc.start, source, end), - source, - }, - hasMemberExpression ? undefined : assignment, - parent, - ), - ) - - if (i === ids.length - 1 && end < content.length && !isTSNode) { - push([content.slice(end), NewlineType.Unknown]) - } - }) - - if (assignment && hasMemberExpression) { - push(` = ${assignment}`) - } - return frag - } else { - return [[content, NewlineType.Unknown, loc]] - } -} - -function genIdentifier( - raw: string, - context: CodegenContext, - loc?: SourceLocation, - assignment?: string, - parent?: Node, -): CodeFragment[] { - const { identifiers } = context - const name: string | undefined = raw - - const idMap = identifiers[raw] - if (idMap && idMap.length) { - const replacement = idMap[0] - if (isString(replacement)) { - if (parent && parent.type === 'ObjectProperty' && parent.shorthand) { - return [[`${name}: ${replacement}`, NewlineType.None, loc]] - } else { - return [[replacement, NewlineType.None, loc]] - } - } else { - // replacement is an expression - process it again - return genExpression(replacement, context, assignment) - } - } - - let prefix: string | undefined - if (isStaticProperty(parent) && parent.shorthand) { - // property shorthand like { foo }, we need to add the key since - // we rewrite the value - prefix = `${raw}: ` - } - - raw = withAssignment(raw) - return [prefix, [raw, NewlineType.None, loc, name]] - - function withAssignment(s: string) { - return assignment ? `${s} = ${assignment}` : s - } -} diff --git a/packages/compiler/src/generators/for.ts b/packages/compiler/src/generators/for.ts deleted file mode 100644 index d5bb945..0000000 --- a/packages/compiler/src/generators/for.ts +++ /dev/null @@ -1,253 +0,0 @@ -import { parseExpression } from '@babel/parser' -import { - createSimpleExpression, - walkIdentifiers, - type SimpleExpressionNode, -} from '@vue/compiler-dom' -import type { CodegenContext } from '../generate' -import type { ForIRNode } from '../ir' -import { genBlock } from './block' -import { genExpression } from './expression' -import { genCall, genMulti, NEWLINE, type CodeFragment } from './utils' -import type { Identifier } from '@babel/types' - -/** - * Flags to optimize vapor `createFor` runtime behavior, shared between the - * compiler and the runtime - */ -export enum VaporVForFlags { - /** - * v-for is the only child of a parent container, so it can take the fast - * path with textContent = '' when the whole list is emptied - */ - FAST_REMOVE = 1, - /** - * v-for used on component - we can skip creating child scopes for each block - * because the component itself already has a scope. - */ - IS_COMPONENT = 1 << 1, - /** - * v-for inside v-ince - */ - ONCE = 1 << 2, -} - -export function genFor( - oper: ForIRNode, - context: CodegenContext, -): CodeFragment[] { - const { helper } = context - const { - source, - value, - key, - index, - render, - keyProp, - once, - id, - component, - onlyChild, - } = oper - - let rawValue: string | null = null - const rawKey = key && key.content - const rawIndex = index && index.content - - const sourceExpr = ['() => (', ...genExpression(source, context), ')'] - const idToPathMap = parseValueDestructure() - - const [depth, exitScope] = context.enterScope() - const idMap: Record = {} - - const itemVar = `_for_item${depth}` - idMap[itemVar] = null - - idToPathMap.forEach((pathInfo, id) => { - let path = `${itemVar}.value${pathInfo ? pathInfo.path : ''}` - if (pathInfo) { - if (pathInfo.helper) { - idMap[pathInfo.helper] = null - path = `${pathInfo.helper}(${path}, ${pathInfo.helperArgs})` - } - if (pathInfo.dynamic) { - const node = (idMap[id] = createSimpleExpression(path)) - const plugins = context.options.expressionPlugins - node.ast = parseExpression(`(${path})`, { - plugins: plugins ? [...plugins, 'typescript'] : ['typescript'], - }) - } else { - idMap[id] = path - } - } else { - idMap[id] = path - } - }) - - const args = [itemVar] - if (rawKey) { - const keyVar = `_for_key${depth}` - args.push(`, ${keyVar}`) - idMap[rawKey] = `${keyVar}.value` - idMap[keyVar] = null - } - if (rawIndex) { - const indexVar = `_for_index${depth}` - args.push(`, ${indexVar}`) - idMap[rawIndex] = `${indexVar}.value` - idMap[indexVar] = null - } - - const blockFn = context.withId(() => genBlock(render, context, args), idMap) - exitScope() - - let flags = 0 - if (onlyChild) { - flags |= VaporVForFlags.FAST_REMOVE - } - if (component) { - flags |= VaporVForFlags.IS_COMPONENT - } - if (once) { - flags |= VaporVForFlags.ONCE - } - - return [ - NEWLINE, - `const n${id} = `, - ...genCall( - helper('createFor'), - sourceExpr, - blockFn, - genCallback(keyProp), - flags ? String(flags) : undefined, - // todo: hydrationNode - ), - ] - - // construct a id -> accessor path map. - // e.g. `{ x: { y: [z] }}` -> `Map{ 'z' => '.x.y[0]' }` - function parseValueDestructure() { - const map = new Map< - string, - { - path: string - dynamic: boolean - helper?: string - helperArgs?: string - } | null - >() - if (value) { - rawValue = value && value.content - if (value.ast) { - walkIdentifiers( - value.ast, - (id, _, parentStack, ___, isLocal) => { - if (isLocal) { - let path = '' - let isDynamic = false - let helper - let helperArgs - for (let i = 0; i < parentStack.length; i++) { - const parent = parentStack[i] - const child = parentStack[i + 1] || id - - if ( - parent.type === 'ObjectProperty' && - parent.value === child - ) { - if (parent.key.type === 'StringLiteral') { - path += `[${JSON.stringify(parent.key.value)}]` - } else if (parent.computed) { - isDynamic = true - path += `[${value.content.slice( - parent.key.start! - 1, - parent.key.end! - 1, - )}]` - } else { - // non-computed, can only be identifier - path += `.${(parent.key as Identifier).name}` - } - } else if (parent.type === 'ArrayPattern') { - const index = parent.elements.indexOf(child as any) - if (child.type === 'RestElement') { - path += `.slice(${index})` - } else { - path += `[${index}]` - } - } else if ( - parent.type === 'ObjectPattern' && - child.type === 'RestElement' - ) { - helper = context.helper('getRestElement') - helperArgs = `[${parent.properties - .filter((p) => p.type === 'ObjectProperty') - .map((p) => { - if (p.key.type === 'StringLiteral') { - return JSON.stringify(p.key.value) - } else if (p.computed) { - isDynamic = true - return value.content.slice( - p.key.start! - 1, - p.key.end! - 1, - ) - } else { - return JSON.stringify((p.key as Identifier).name) - } - }) - .join(', ')}]` - } - - // default value - if ( - child.type === 'AssignmentPattern' && - (parent.type === 'ObjectProperty' || - parent.type === 'ArrayPattern') - ) { - isDynamic = true - helper = context.helper('getDefaultValue') - helperArgs = value.content.slice( - child.right.start! - 1, - child.right.end! - 1, - ) - } - } - map.set(id.name, { path, dynamic: isDynamic, helper, helperArgs }) - } - }, - true, - ) - } else { - map.set(rawValue, null) - } - } - return map - } - - function genCallback(expr: SimpleExpressionNode | undefined) { - if (!expr) return false - const res = context.withId( - () => genExpression(expr, context), - genSimpleIdMap(), - ) - return [ - ...genMulti( - ['(', ')', ', '], - rawValue ? rawValue : rawKey || rawIndex ? '_' : undefined, - rawKey ? rawKey : rawIndex ? '__' : undefined, - rawIndex, - ), - ' => (', - ...res, - ')', - ] - } - - function genSimpleIdMap() { - const idMap: Record = {} - if (rawKey) idMap[rawKey] = null - if (rawIndex) idMap[rawIndex] = null - idToPathMap.forEach((_, id) => (idMap[id] = null)) - return idMap - } -} diff --git a/packages/compiler/src/generators/html.ts b/packages/compiler/src/generators/html.ts deleted file mode 100644 index 1befd42..0000000 --- a/packages/compiler/src/generators/html.ts +++ /dev/null @@ -1,16 +0,0 @@ -import type { CodegenContext } from '../generate' -import type { SetHtmlIRNode } from '../ir' -import { genExpression } from './expression' -import { genCall, NEWLINE, type CodeFragment } from './utils' - -export function genSetHtml( - oper: SetHtmlIRNode, - context: CodegenContext, -): CodeFragment[] { - const { helper } = context - const { value, element } = oper - return [ - NEWLINE, - ...genCall(helper('setHtml'), `n${element}`, genExpression(value, context)), - ] -} diff --git a/packages/compiler/src/generators/if.ts b/packages/compiler/src/generators/if.ts deleted file mode 100644 index c59a752..0000000 --- a/packages/compiler/src/generators/if.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { IRNodeTypes, type IfIRNode } from '../ir' -import type { CodegenContext } from '../generate' -import { genBlock } from './block' -import { genExpression } from './expression' -import { buildCodeFragment, genCall, NEWLINE, type CodeFragment } from './utils' - -export function genIf( - oper: IfIRNode, - context: CodegenContext, - isNested = false, -): CodeFragment[] { - const { helper } = context - const { condition, positive, negative, once } = oper - const [frag, push] = buildCodeFragment() - - const conditionExpr: CodeFragment[] = [ - '() => (', - ...genExpression(condition, context), - ')', - ] - - const positiveArg = genBlock(positive, context) - let negativeArg: false | CodeFragment[] = false - - if (negative) { - if (negative.type === IRNodeTypes.BLOCK) { - negativeArg = genBlock(negative, context) - } else { - negativeArg = ['() => ', ...genIf(negative!, context, true)] - } - } - - if (!isNested) push(NEWLINE, `const n${oper.id} = `) - push( - ...genCall( - helper('createIf'), - conditionExpr, - positiveArg, - negativeArg, - once && 'true', - ), - ) - - return frag -} diff --git a/packages/compiler/src/generators/operation.ts b/packages/compiler/src/generators/operation.ts deleted file mode 100644 index 8125050..0000000 --- a/packages/compiler/src/generators/operation.ts +++ /dev/null @@ -1,170 +0,0 @@ -import { - IRNodeTypes, - isBlockOperation, - type InsertionStateTypes, - type IREffect, - type OperationNode, -} from '../ir' -import type { CodegenContext } from '../generate' -import { genCreateComponent } from './component' -import { genBuiltinDirective } from './directive' -import { genInsertNode, genPrependNode } from './dom' -import { genSetDynamicEvents, genSetEvent } from './event' -import { genFor } from './for' -import { genSetHtml } from './html' -import { genIf } from './if' -import { genDynamicProps, genSetProp } from './prop' -import { genDeclareOldRef, genSetTemplateRef } from './templateRef' -import { - genCreateNodes, - genGetTextChild, - genSetNodes, - genSetText, -} from './text' -import { - buildCodeFragment, - genCall, - INDENT_END, - INDENT_START, - NEWLINE, - type CodeFragment, -} from './utils' - -export function genOperations( - opers: OperationNode[], - context: CodegenContext, -): CodeFragment[] { - const [frag, push] = buildCodeFragment() - for (const operation of opers) { - push(...genOperationWithInsertionState(operation, context)) - } - return frag -} - -export function genOperationWithInsertionState( - oper: OperationNode, - context: CodegenContext, -): CodeFragment[] { - const [frag, push] = buildCodeFragment() - if (isBlockOperation(oper) && oper.parent) { - push(...genInsertionState(oper, context)) - } - push(...genOperation(oper, context)) - return frag -} - -export function genOperation( - oper: OperationNode, - context: CodegenContext, -): CodeFragment[] { - switch (oper.type) { - case IRNodeTypes.SET_PROP: - return genSetProp(oper, context) - case IRNodeTypes.SET_DYNAMIC_PROPS: - return genDynamicProps(oper, context) - case IRNodeTypes.SET_TEXT: - return genSetText(oper, context) - case IRNodeTypes.SET_EVENT: - return genSetEvent(oper, context) - case IRNodeTypes.SET_DYNAMIC_EVENTS: - return genSetDynamicEvents(oper, context) - case IRNodeTypes.SET_HTML: - return genSetHtml(oper, context) - case IRNodeTypes.SET_TEMPLATE_REF: - return genSetTemplateRef(oper, context) - case IRNodeTypes.INSERT_NODE: - return genInsertNode(oper, context) - case IRNodeTypes.PREPEND_NODE: - return genPrependNode(oper, context) - case IRNodeTypes.IF: - return genIf(oper, context) - case IRNodeTypes.FOR: - return genFor(oper, context) - case IRNodeTypes.CREATE_COMPONENT_NODE: - return genCreateComponent(oper, context) - case IRNodeTypes.DECLARE_OLD_REF: - return genDeclareOldRef(oper) - case IRNodeTypes.SLOT_OUTLET_NODE: - return [] - case IRNodeTypes.DIRECTIVE: - return genBuiltinDirective(oper, context) - case IRNodeTypes.GET_TEXT_CHILD: - return genGetTextChild(oper, context) - case IRNodeTypes.SET_NODES: - return genSetNodes(oper, context) - case IRNodeTypes.CREATE_NODES: - return genCreateNodes(oper, context) - default: { - const exhaustiveCheck = oper - throw new Error( - `Unhandled operation type in genOperation: ${exhaustiveCheck}`, - ) - } - } -} - -export function genEffects( - effects: IREffect[], - context: CodegenContext, -): CodeFragment[] { - const { helper } = context - const [frag, push, unshift] = buildCodeFragment() - let operationsCount = 0 - for (const [i, effect] of effects.entries()) { - operationsCount += effect.operations.length - const frags = genEffect(effect, context) - i > 0 && push(NEWLINE) - if (frag.at(-1) === ')' && frags[0] === '(') { - push(';') - } - push(...frags) - } - - const newLineCount = frag.filter((frag) => frag === NEWLINE).length - if (newLineCount > 1 || operationsCount > 1) { - unshift(`{`, INDENT_START, NEWLINE) - push(INDENT_END, NEWLINE, '}') - } - - if (effects.length) { - unshift(NEWLINE, `${helper('renderEffect')}(() => `) - push(`)`) - } - - return frag -} - -export function genEffect( - { operations }: IREffect, - context: CodegenContext, -): CodeFragment[] { - const [frag, push] = buildCodeFragment() - const operationsExps = genOperations(operations, context) - const newlineCount = operationsExps.filter((frag) => frag === NEWLINE).length - - if (newlineCount > 1) { - push(...operationsExps) - } else { - push(...operationsExps.filter((frag) => frag !== NEWLINE)) - } - - return frag -} - -function genInsertionState( - operation: InsertionStateTypes, - context: CodegenContext, -): CodeFragment[] { - return [ - NEWLINE, - ...genCall( - context.helper('setInsertionState'), - `n${operation.parent}`, - operation.anchor == null - ? undefined - : operation.anchor === -1 // -1 indicates prepend - ? `0` // runtime anchor value for prepend - : `n${operation.anchor}`, - ), - ] -} diff --git a/packages/compiler/src/generators/prop.ts b/packages/compiler/src/generators/prop.ts deleted file mode 100644 index 1faf01e..0000000 --- a/packages/compiler/src/generators/prop.ts +++ /dev/null @@ -1,225 +0,0 @@ -import { - isSimpleIdentifier, - NewlineType, - type SimpleExpressionNode, -} from '@vue/compiler-dom' -import { - canSetValueDirectly, - capitalize, - isSVGTag, - shouldSetAsAttr, - toHandlerKey, -} from '@vue/shared' -import { - IRDynamicPropsKind, - type IRProp, - type SetDynamicPropsIRNode, - type SetPropIRNode, -} from '../ir' -import type { CodegenContext } from '../generate' -import { genExpression } from './expression' -import { - DELIMITERS_ARRAY, - DELIMITERS_OBJECT, - genCall, - genMulti, - NEWLINE, - type CodeFragment, -} from './utils' - -export type HelperConfig = { - name: string - needKey?: boolean - acceptRoot?: boolean -} - -// this should be kept in sync with runtime-vapor/src/dom/prop.ts -const helpers = { - setText: { name: 'setText' }, - setHtml: { name: 'setHtml' }, - setClass: { name: 'setClass' }, - setStyle: { name: 'setStyle' }, - setValue: { name: 'setValue' }, - setAttr: { name: 'setAttr', needKey: true }, - setProp: { name: 'setProp', needKey: true }, - setDOMProp: { name: 'setDOMProp', needKey: true }, - setDynamicProps: { name: 'setDynamicProps' }, -} as const satisfies Partial> - -// only the static key prop will reach here -export function genSetProp( - oper: SetPropIRNode, - context: CodegenContext, -): CodeFragment[] { - const { helper } = context - const { - prop: { key, values, modifier }, - tag, - } = oper - const resolvedHelper = getRuntimeHelper(tag, key.content, modifier) - const propValue = genPropValue(values, context) - return [ - NEWLINE, - ...genCall( - [helper(resolvedHelper.name), null], - `n${oper.element}`, - resolvedHelper.needKey ? genExpression(key, context) : false, - propValue, - ), - ] -} - -// dynamic key props and v-bind="{}" will reach here -export function genDynamicProps( - oper: SetDynamicPropsIRNode, - context: CodegenContext, -): CodeFragment[] { - const { helper } = context - const values = oper.props.map((props) => - Array.isArray(props) - ? genLiteralObjectProps(props, context) // static and dynamic arg props - : props.kind === IRDynamicPropsKind.ATTRIBUTE - ? genLiteralObjectProps([props], context) // dynamic arg props - : genExpression(props.value, context), - ) // v-bind="" - return [ - NEWLINE, - ...genCall( - helper('setDynamicProps'), - `n${oper.element}`, - genMulti(DELIMITERS_ARRAY, ...values), - oper.root && 'true', - ), - ] -} - -function genLiteralObjectProps( - props: IRProp[], - context: CodegenContext, -): CodeFragment[] { - return genMulti( - DELIMITERS_OBJECT, - ...props.map((prop) => [ - ...genPropKey(prop, context), - `: `, - ...genPropValue(prop.values, context), - ]), - ) -} - -export function genPropKey( - { key: node, modifier, runtimeCamelize, handler, handlerModifiers }: IRProp, - context: CodegenContext, -): CodeFragment[] { - const { helper } = context - - const handlerModifierPostfix = - handlerModifiers && handlerModifiers.options - ? handlerModifiers.options.map(capitalize).join('') - : '' - // static arg was transformed by v-bind transformer - if (node.isStatic) { - // only quote keys if necessary - const keyName = - (handler ? toHandlerKey(node.content) : node.content) + - handlerModifierPostfix - return [ - [ - isSimpleIdentifier(keyName) ? keyName : JSON.stringify(keyName), - NewlineType.None, - node.loc, - ], - ] - } - - let key = genExpression(node, context) - if (runtimeCamelize) { - key = genCall(helper('camelize'), key) - } - if (handler) { - key = genCall(helper('toHandlerKey'), key) - } - return [ - '[', - modifier && `${JSON.stringify(modifier)} + `, - ...key, - handlerModifierPostfix - ? ` + ${JSON.stringify(handlerModifierPostfix)}` - : undefined, - ']', - ] -} - -export function genPropValue( - values: SimpleExpressionNode[], - context: CodegenContext, -): CodeFragment[] { - if (values.length === 1) { - return genExpression(values[0], context) - } - return genMulti( - DELIMITERS_ARRAY, - ...values.map((expr) => genExpression(expr, context)), - ) -} - -function getRuntimeHelper( - tag: string, - key: string, - modifier: '.' | '^' | undefined, -): HelperConfig { - const tagName = tag.toUpperCase() - if (modifier) { - if (modifier === '.') { - return getSpecialHelper(key, tagName) || helpers.setDOMProp - } else { - return helpers.setAttr - } - } - - // 1. special handling for value / style / class / textContent / innerHTML - const helper = getSpecialHelper(key, tagName) - if (helper) { - return helper - } - - // 2. Aria DOM properties shared between all Elements in - // https://developer.mozilla.org/en-US/docs/Web/API/Element - if (/aria[A-Z]/.test(key)) { - return helpers.setDOMProp - } - - // 3. SVG: always attribute - if (isSVGTag(tag)) { - // TODO pass svg flag - return helpers.setAttr - } - - // 4. respect shouldSetAsAttr used in vdom and setDynamicProp for consistency - // also fast path for presence of hyphen (covers data-* and aria-*) - if (shouldSetAsAttr(tagName, key) || key.includes('-')) { - return helpers.setAttr - } - - // 5. Fallback to setDOMProp, which has a runtime `key in el` check to - // ensure behavior consistency with vdom - return helpers.setProp -} - -function getSpecialHelper( - keyName: string, - tagName: string, -): HelperConfig | undefined { - // special case for 'value' property - if (keyName === 'value' && canSetValueDirectly(tagName)) { - return helpers.setValue - } else if (keyName === 'class') { - return helpers.setClass - } else if (keyName === 'style') { - return helpers.setStyle - } else if (keyName === 'innerHTML') { - return helpers.setHtml - } else if (keyName === 'textContent') { - return helpers.setText - } -} diff --git a/packages/compiler/src/generators/template.ts b/packages/compiler/src/generators/template.ts deleted file mode 100644 index 5c36e7b..0000000 --- a/packages/compiler/src/generators/template.ts +++ /dev/null @@ -1,115 +0,0 @@ -import { DynamicFlag, type IRDynamicInfo } from '../ir' -import type { CodegenContext } from '../generate' -import { genDirectivesForElement } from './directive' -import { genOperationWithInsertionState } from './operation' -import { buildCodeFragment, genCall, NEWLINE, type CodeFragment } from './utils' - -export function genTemplates( - templates: string[], - rootIndex: number | undefined, - { helper }: CodegenContext, -): string[] { - return templates.map((template, i) => - template.startsWith('_template') - ? template - : `${helper('template')}(${JSON.stringify( - template, - )}${i === rootIndex ? ', true' : ''})`, - ) -} - -export function genSelf( - dynamic: IRDynamicInfo, - context: CodegenContext, -): CodeFragment[] { - const [frag, push] = buildCodeFragment() - const { id, template, operation } = dynamic - - if (id !== undefined && template !== undefined) { - push(NEWLINE, `const n${id} = t${template}()`) - push(...genDirectivesForElement(id, context)) - } - - if (operation) { - push(...genOperationWithInsertionState(operation, context)) - } - - return frag -} - -export function genChildren( - dynamic: IRDynamicInfo, - context: CodegenContext, - pushBlock: (...items: CodeFragment[]) => number, - from: string = `n${dynamic.id}`, -): CodeFragment[] { - const { helper } = context - const [frag, push] = buildCodeFragment() - const { children } = dynamic - - let offset = 0 - let prev: [variable: string, elementIndex: number] | undefined - const childrenToGen: [IRDynamicInfo, string][] = [] - - for (const [index, child] of children.entries()) { - if (child.flags & DynamicFlag.NON_TEMPLATE) { - offset-- - } - - const id = - child.flags & DynamicFlag.REFERENCED - ? child.flags & DynamicFlag.INSERT - ? child.anchor - : child.id - : undefined - - if (id === undefined && !child.hasDynamicChild) { - push(...genSelf(child, context)) - continue - } - - const elementIndex = Number(index) + offset - // p for "placeholder" variables that are meant for possible reuse by - // other access paths - const variable = id === undefined ? `p${context.block.tempId++}` : `n${id}` - pushBlock(NEWLINE, `const ${variable} = `) - - if (prev) { - if (elementIndex - prev[1] === 1) { - pushBlock(...genCall(helper('next'), prev[0])) - } else { - pushBlock(...genCall(helper('nthChild'), from, String(elementIndex))) - } - } else if (elementIndex === 0) { - pushBlock(...genCall(helper('child'), from)) - } else { - // check if there's a node that we can reuse from - let init = genCall(helper('child'), from) - if (elementIndex === 1) { - init = genCall(helper('next'), init) - } else if (elementIndex > 1) { - init = genCall(helper('nthChild'), from, String(elementIndex)) - } - pushBlock(...init) - } - - if (id === child.anchor) { - push(...genSelf(child, context)) - } - - if (id !== undefined) { - push(...genDirectivesForElement(id, context)) - } - - prev = [variable, elementIndex] - childrenToGen.push([child, variable]) - } - - if (childrenToGen.length) { - for (const [child, from] of childrenToGen) { - push(...genChildren(child, context, pushBlock, from)) - } - } - - return frag -} diff --git a/packages/compiler/src/generators/templateRef.ts b/packages/compiler/src/generators/templateRef.ts deleted file mode 100644 index f3f511c..0000000 --- a/packages/compiler/src/generators/templateRef.ts +++ /dev/null @@ -1,26 +0,0 @@ -import type { CodegenContext } from '../generate' -import type { DeclareOldRefIRNode, SetTemplateRefIRNode } from '../ir' -import { genCall, genExpression, NEWLINE, type CodeFragment } from './utils' - -export const setTemplateRefIdent = `_setTemplateRef` - -export function genSetTemplateRef( - oper: SetTemplateRefIRNode, - context: CodegenContext, -): CodeFragment[] { - return [ - NEWLINE, - oper.effect && `r${oper.element} = `, - ...genCall( - setTemplateRefIdent, // will be generated in root scope - `n${oper.element}`, - genExpression(oper.value, context), - oper.effect ? `r${oper.element}` : oper.refFor ? 'void 0' : undefined, - oper.refFor && 'true', - ), - ] -} - -export function genDeclareOldRef(oper: DeclareOldRefIRNode): CodeFragment[] { - return [NEWLINE, `let r${oper.id}`] -} diff --git a/packages/compiler/src/generators/text.ts b/packages/compiler/src/generators/text.ts deleted file mode 100644 index c7bdf80..0000000 --- a/packages/compiler/src/generators/text.ts +++ /dev/null @@ -1,81 +0,0 @@ -import { getLiteralExpressionValue } from '../utils' -import type { CodegenContext } from '../generate' -import type { - CreateNodesIRNode, - GetTextChildIRNode, - SetNodesIRNode, - SetTextIRNode, -} from '../ir' -import { genExpression } from './expression' -import { genCall, NEWLINE, type CodeFragment } from './utils' -import type { SimpleExpressionNode } from '@vue/compiler-dom' - -export function genSetText( - oper: SetTextIRNode, - context: CodegenContext, -): CodeFragment[] { - const { helper } = context - const { element, values, generated } = oper - const texts = combineValues(values, context, true) - return [ - NEWLINE, - ...genCall(helper('setText'), `${generated ? 'x' : 'n'}${element}`, texts), - ] -} - -export function genGetTextChild( - oper: GetTextChildIRNode, - context: CodegenContext, -): CodeFragment[] { - return [ - NEWLINE, - `const x${oper.parent} = ${context.helper('child')}(n${oper.parent})`, - ] -} - -export function genSetNodes( - oper: SetNodesIRNode, - context: CodegenContext, -): CodeFragment[] { - const { helper } = context - const { element, values, generated } = oper - return [ - NEWLINE, - ...genCall( - helper('setNodes'), - `${generated ? 'x' : 'n'}${element}`, - combineValues(values, context), - ), - ] -} - -export function genCreateNodes( - oper: CreateNodesIRNode, - context: CodegenContext, -): CodeFragment[] { - const { helper } = context - const { id, values } = oper - return [ - NEWLINE, - `const n${id} = `, - ...genCall(helper('createNodes'), values && combineValues(values, context)), - ] -} - -function combineValues( - values: SimpleExpressionNode[], - context: CodegenContext, - setText?: boolean, -): CodeFragment[] { - return values.flatMap((value, i) => { - let exp = genExpression(value, context) - if (setText && getLiteralExpressionValue(value) == null) { - // dynamic, wrap with toDisplayString - exp = genCall(context.helper('toDisplayString'), exp) - } - if (i > 0) { - exp.unshift(setText ? ' + ' : ', ') - } - return exp - }) -} diff --git a/packages/compiler/src/generators/utils.ts b/packages/compiler/src/generators/utils.ts deleted file mode 100644 index 030b9a8..0000000 --- a/packages/compiler/src/generators/utils.ts +++ /dev/null @@ -1,180 +0,0 @@ -import { - advancePositionWithMutation, - locStub, - NewlineType, - type CodegenSourceMapGenerator, - type Position, - type SourceLocation, -} from '@vue/compiler-dom' -import { isArray, isString } from '@vue/shared' -import { SourceMapGenerator } from 'source-map-js' -import type { CodegenContext } from '../generate' - -export { genExpression } from './expression' - -export const NEWLINE: unique symbol = Symbol(`newline`) -export const INDENT_START: unique symbol = Symbol(`indent start`) -export const INDENT_END: unique symbol = Symbol(`indent end`) - -type FalsyValue = false | null | undefined -export type CodeFragment = - | typeof NEWLINE - | typeof INDENT_START - | typeof INDENT_END - | string - | [code: string, newlineIndex?: number, loc?: SourceLocation, name?: string] - | FalsyValue -export type CodeFragments = Exclude | CodeFragment[] - -export function buildCodeFragment( - ...frag: CodeFragment[] -): [ - CodeFragment[], - (...items: CodeFragment[]) => number, - (...items: CodeFragment[]) => number, -] { - const push = frag.push.bind(frag) - const unshift = frag.unshift.bind(frag) - return [frag, push, unshift] -} - -export type CodeFragmentDelimiters = [ - left: CodeFragments, - right: CodeFragments, - delimiter: CodeFragments, - placeholder?: CodeFragments, -] - -export function genMulti( - [left, right, seg, placeholder]: CodeFragmentDelimiters, - ...frags: CodeFragments[] -): CodeFragment[] { - if (placeholder) { - while (frags.length > 0 && !frags.at(-1)) { - frags.pop() - } - frags = frags.map((frag) => frag || placeholder) - } else { - frags = frags.filter(Boolean) - } - - const frag: CodeFragment[] = [] - push(left) - for (const [i, fn] of ( - frags as Array> - ).entries()) { - push(fn) - if (i < frags.length - 1) push(seg) - } - push(right) - return frag - - function push(fn: CodeFragments) { - if (!isArray(fn)) fn = [fn] - frag.push(...fn) - } -} -export const DELIMITERS_ARRAY: CodeFragmentDelimiters = ['[', ']', ', '] -export const DELIMITERS_ARRAY_NEWLINE: CodeFragmentDelimiters = [ - ['[', INDENT_START, NEWLINE], - [INDENT_END, NEWLINE, ']'], - [', ', NEWLINE], -] -export const DELIMITERS_OBJECT: CodeFragmentDelimiters = ['{ ', ' }', ', '] -export const DELIMITERS_OBJECT_NEWLINE: CodeFragmentDelimiters = [ - ['{', INDENT_START, NEWLINE], - [INDENT_END, NEWLINE, '}'], - [', ', NEWLINE], -] - -export function genCall( - name: string | [name: string, placeholder?: CodeFragments], - ...frags: CodeFragments[] -): CodeFragment[] { - const hasPlaceholder = isArray(name) - const fnName = hasPlaceholder ? name[0] : name - const placeholder = hasPlaceholder ? name[1] : 'null' - return [fnName, ...genMulti(['(', ')', ', ', placeholder], ...frags)] -} - -export function codeFragmentToString( - code: CodeFragment[], - context: CodegenContext, -): [code: string, map: CodegenSourceMapGenerator | undefined] { - const { - options: { filename, sourceMap }, - } = context - - let map: CodegenSourceMapGenerator | undefined - if (sourceMap) { - // lazy require source-map implementation, only in non-browser builds - map = new SourceMapGenerator() as unknown as CodegenSourceMapGenerator - map.setSourceContent(filename, context.ir.source) - map._sources.add(filename) - } - - let codegen = '' - const pos = { line: 1, column: 1, offset: 0 } - let indentLevel = 0 - - for (let frag of code) { - if (!frag) continue - - if (frag === NEWLINE) { - frag = [`\n${` `.repeat(indentLevel)}`, NewlineType.Start] - } else if (frag === INDENT_START) { - indentLevel++ - continue - } else if (frag === INDENT_END) { - indentLevel-- - continue - } - - if (isString(frag)) frag = [frag] - - let [code, newlineIndex = NewlineType.None, loc, name] = frag - codegen += code - - if (map) { - if (loc) addMapping(loc.start, name) - if (newlineIndex === NewlineType.Unknown) { - // multiple newlines, full iteration - advancePositionWithMutation(pos, code) - } else { - // fast paths - pos.offset += code.length - if (newlineIndex === NewlineType.None) { - pos.column += code.length - } else { - // single newline at known index - if (newlineIndex === NewlineType.End) { - newlineIndex = code.length - 1 - } - pos.line++ - pos.column = code.length - newlineIndex - } - } - if (loc && loc !== locStub) { - addMapping(loc.end) - } - } - } - - return [codegen, map] - - 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 } = 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: pos.line, - generatedColumn: pos.column - 1, - source: filename, - name, - }) - } -} diff --git a/packages/compiler/src/generators/vModel.ts b/packages/compiler/src/generators/vModel.ts deleted file mode 100644 index 5bee594..0000000 --- a/packages/compiler/src/generators/vModel.ts +++ /dev/null @@ -1,52 +0,0 @@ -import type { CodegenContext } from '../generate' -import type { DirectiveIRNode } from '../ir' -import { genExpression } from './expression' -import { genCall, NEWLINE, type CodeFragment } from './utils' -import type { SimpleExpressionNode } from '@vue/compiler-dom' - -const helperMap = { - text: 'applyTextModel', - radio: 'applyRadioModel', - checkbox: 'applyCheckboxModel', - select: 'applySelectModel', - dynamic: 'applyDynamicModel', -} as const - -// This is only for built-in v-model on native elements. -export function genVModel( - oper: DirectiveIRNode, - context: CodegenContext, -): CodeFragment[] { - const { - modelType, - element, - dir: { exp, modifiers }, - } = oper - - return [ - NEWLINE, - ...genCall( - context.helper(helperMap[modelType!]), - `n${element}`, - // getter - [`() => (`, ...genExpression(exp!, context), `)`], - // setter - genModelHandler(exp!, context), - // modifiers - modifiers.length - ? `{ ${modifiers.map((e) => `${e.content}: true`).join(',')} }` - : undefined, - ), - ] -} - -export function genModelHandler( - exp: SimpleExpressionNode, - context: CodegenContext, -): CodeFragment[] { - return [ - `${context.options.isTS ? `(_value: any)` : `_value`} => (`, - ...genExpression(exp, context, '_value'), - ')', - ] -} diff --git a/packages/compiler/src/generators/vShow.ts b/packages/compiler/src/generators/vShow.ts deleted file mode 100644 index db77f82..0000000 --- a/packages/compiler/src/generators/vShow.ts +++ /dev/null @@ -1,18 +0,0 @@ -import type { CodegenContext } from '../generate' -import type { DirectiveIRNode } from '../ir' -import { genExpression } from './expression' -import { genCall, NEWLINE, type CodeFragment } from './utils' - -export function genVShow( - oper: DirectiveIRNode, - context: CodegenContext, -): CodeFragment[] { - return [ - NEWLINE, - ...genCall(context.helper('applyVShow'), `n${oper.element}`, [ - `() => (`, - ...genExpression(oper.dir.exp!, context), - `)`, - ]), - ] -} diff --git a/packages/compiler/src/index.ts b/packages/compiler/src/index.ts index c872008..79e0c24 100644 --- a/packages/compiler/src/index.ts +++ b/packages/compiler/src/index.ts @@ -1,11 +1,10 @@ -export { compile, type CompilerOptions, type TransformPreset } from './compile' -export * from './transform' export { - CodegenContext, + compile, generate, - type CodegenOptions, - type VaporCodegenResult, -} from './generate' + type CompilerOptions, + type TransformPreset, +} from './compile' +export * from './transform' export * from './ir' diff --git a/packages/compiler/src/ir/component.ts b/packages/compiler/src/ir/component.ts index d968108..70046ee 100644 --- a/packages/compiler/src/ir/component.ts +++ b/packages/compiler/src/ir/component.ts @@ -1,6 +1,7 @@ import type { DirectiveTransformResult } from '../transform' -import type { BlockIRNode, IRFor } from './index' +import type { BlockIRNode } from './index' import type { SimpleExpressionNode } from '@vue/compiler-dom' +import type { IRFor } from '@vue/compiler-vapor' // props export interface IRProp extends Omit { diff --git a/packages/compiler/src/ir/index.ts b/packages/compiler/src/ir/index.ts index 4fee654..b46f8d1 100644 --- a/packages/compiler/src/ir/index.ts +++ b/packages/compiler/src/ir/index.ts @@ -64,7 +64,7 @@ export interface RootIRNode { type: IRNodeTypes.ROOT node: RootNode source: string - templates: string[] + template: string[] rootTemplateIndex?: number component: Set directive: Set diff --git a/packages/compiler/src/transform.ts b/packages/compiler/src/transform.ts index 2909893..e794d8b 100644 --- a/packages/compiler/src/transform.ts +++ b/packages/compiler/src/transform.ts @@ -7,7 +7,7 @@ import { type CompilerCompatOptions, type SimpleExpressionNode, } from '@vue/compiler-dom' -import { extend, isArray, isString, NOOP } from '@vue/shared' +import { EMPTY_OBJ, extend, isArray, isString, NOOP } from '@vue/shared' import { DynamicFlag, IRNodeTypes, @@ -54,17 +54,15 @@ export type StructuralDirectiveTransform = ( context: TransformContext, ) => void | (() => void) -export type TransformOptions = HackOptions & { - templates?: string[] -} +export type TransformOptions = HackOptions const defaultOptions = { filename: '', + prefixIdentifiers: false, hoistStatic: false, hmr: false, cacheHandlers: false, nodeTransforms: [], directiveTransforms: {}, - templates: [], transformHoist: null, isBuiltInComponent: NOOP, isCustomElement: NOOP, @@ -74,6 +72,8 @@ const defaultOptions = { ssr: false, inSSR: false, ssrCssVars: ``, + bindingMetadata: EMPTY_OBJ, + inline: false, isTS: false, onError: defaultOnError, onWarn: defaultOnWarn, @@ -88,14 +88,7 @@ export class TransformContext< block: BlockIRNode options: Required< - Omit< - TransformOptions, - | 'filename' - | 'inline' - | 'bindingMetadata' - | 'prefixIdentifiers' - | keyof CompilerCompatOptions - > + Omit > template: string = '' @@ -154,10 +147,10 @@ export class TransformContext< } pushTemplate(content: string) { - const existing = this.ir.templates.indexOf(content) + const existing = this.ir.template.indexOf(content) if (existing !== -1) return existing - this.ir.templates.push(content) - return this.ir.templates.length - 1 + this.ir.template.push(content) + return this.ir.template.length - 1 } registerTemplate() { @@ -217,7 +210,7 @@ export function transform( type: IRNodeTypes.ROOT, node, source: node.source, - templates: options.templates || [], + template: [], component: new Set(), directive: new Set(), block: newBlock(node), diff --git a/packages/compiler/src/transforms/transformElement.ts b/packages/compiler/src/transforms/transformElement.ts index 227feda..3ea9e9b 100644 --- a/packages/compiler/src/transforms/transformElement.ts +++ b/packages/compiler/src/transforms/transformElement.ts @@ -181,7 +181,7 @@ function transformNativeElement( } if (singleRoot) { - context.ir.rootTemplateIndex = context.ir.templates.length + context.ir.rootTemplateIndex = context.ir.template.length } if ( diff --git a/packages/compiler/src/transforms/vIf.ts b/packages/compiler/src/transforms/vIf.ts index 1b3bd51..5934b21 100644 --- a/packages/compiler/src/transforms/vIf.ts +++ b/packages/compiler/src/transforms/vIf.ts @@ -49,7 +49,9 @@ export function processIf( id, condition: dir.exp!, positive: branch, - once: context.inVOnce || isConstantNode(attribute.value!, {}), + once: + context.inVOnce || + isConstantNode(attribute.value!, context.options.bindingMetadata), } } } else { diff --git a/packages/compiler/src/utils.ts b/packages/compiler/src/utils.ts index 2b976a3..d2978ae 100644 --- a/packages/compiler/src/utils.ts +++ b/packages/compiler/src/utils.ts @@ -127,6 +127,7 @@ export function resolveSimpleExpressionNode( return exp } +const resolvedExpressions = new WeakSet() export function resolveExpression( node: Node | undefined | null, context: TransformContext, @@ -153,17 +154,24 @@ export function resolveExpression( ? node.name : context.ir.source.slice(node.start!, node.end!) const location = node.loc + const isResolved = resolvedExpressions.has(node) if (source && !isStatic && effect && !isConstant(node)) { source = `() => (${source})` - // add offset flag to avoid re-parsing - ;(node as any)._offset = 7 + if (location && node && !isResolved) { + location.start.column -= 7 + node.start! -= 7 + } } return resolveSimpleExpression( source, isStatic, location, - isStatic ? undefined : node, + isStatic + ? undefined + : parseExpression(`(${source})`, { + plugins: context.options.expressionPlugins, + }), ) } diff --git a/packages/compiler/test/compile.spec.ts b/packages/compiler/test/compile.spec.ts index 2e0fd01..ba7b9a8 100644 --- a/packages/compiler/test/compile.spec.ts +++ b/packages/compiler/test/compile.spec.ts @@ -25,7 +25,9 @@ describe('compile', () => { describe('expression parsing', () => { test('interpolation', () => { - const { code } = compile(`<>{ a + b }`) + const { code } = compile(`<>{ a + b }`, { + inline: true, + }) expect(code).toMatchSnapshot() expect(code).contains('a + b') }) diff --git a/packages/compiler/test/transforms/__snapshots__/expression.spec.ts.snap b/packages/compiler/test/transforms/__snapshots__/expression.spec.ts.snap index a1778b8..79389c6 100644 --- a/packages/compiler/test/transforms/__snapshots__/expression.spec.ts.snap +++ b/packages/compiler/test/transforms/__snapshots__/expression.spec.ts.snap @@ -1,14 +1,18 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html exports[`compiler: expression > conditional expression 1`] = ` -" - const n0 = _createIf(() => (ok), () => { +"import { child as _child, setNodes as _setNodes, createNodes as _createNodes, createIf as _createIf, template as _template } from 'vue'; +const t0 = _template(" ") +const t1 = _template("
fail
") + +export function render(_ctx) { + const n0 = _createIf(() => (_ctx.ok), () => { const n2 = t0() const x2 = _child(n2) - _setNodes(x2, () => (msg)) + _setNodes(x2, () => (_ctx.msg)) return n2 }, () => { - const n4 = _createIf(() => (fail), () => { + const n4 = _createIf(() => (_ctx.fail), () => { const n6 = t1() return n6 }, () => { @@ -18,18 +22,22 @@ exports[`compiler: expression > conditional expression 1`] = ` return n4 }) return n0 -" +}" `; exports[`compiler: expression > conditional expression 2`] = ` -" - const n0 = _createIf(() => (ok), () => { +"import { child as _child, setNodes as _setNodes, createNodes as _createNodes, createIf as _createIf, template as _template } from 'vue'; +const t0 = _template(" ") +const t1 = _template("
fail
") + +export function render(_ctx) { + const n0 = _createIf(() => (_ctx.ok), () => { const n2 = t0() const x2 = _child(n2) - _setNodes(x2, () => (msg)) + _setNodes(x2, () => (_ctx.msg)) return n2 }, () => { - const n4 = _createIf(() => (fail), () => { + const n4 = _createIf(() => (_ctx.fail), () => { const n6 = t1() return n6 }, () => { @@ -39,7 +47,7 @@ exports[`compiler: expression > conditional expression 2`] = ` return n4 }) return n0 -" +}" `; exports[`compiler: expression > conditional expression with v-once 1`] = ` diff --git a/packages/compiler/test/transforms/__snapshots__/vOn.spec.ts.snap b/packages/compiler/test/transforms/__snapshots__/vOn.spec.ts.snap index 19c1c45..dc76af4 100644 --- a/packages/compiler/test/transforms/__snapshots__/vOn.spec.ts.snap +++ b/packages/compiler/test/transforms/__snapshots__/vOn.spec.ts.snap @@ -57,11 +57,15 @@ exports[`v-on > event modifier 1`] = ` `; exports[`v-on > should delegate event 1`] = ` -" +"import { delegateEvents as _delegateEvents, template as _template } from 'vue'; +const t0 = _template("
", true) +_delegateEvents("click") + +export function render(_ctx) { const n0 = t0() - n0.$evtclick = e => test(e) + n0.$evtclick = e => _ctx.test(e) return n0 -" +}" `; exports[`v-on > should not wrap keys guard if no key modifier is present 1`] = ` @@ -72,7 +76,7 @@ exports[`v-on > should not wrap keys guard if no key modifier is present 1`] = ` " `; -exports[`v-on > should support multiple modifiers and event options 1`] = ` +exports[`v-on > should support multiple modifiers and event options w/ prefixIdentifiers: true 1`] = ` " const n0 = t0() _on(n0, "click", _withModifiers(e => test(e), ["stop","prevent"]), { @@ -84,28 +88,40 @@ exports[`v-on > should support multiple modifiers and event options 1`] = ` `; exports[`v-on > should transform click.middle 1`] = ` -" +"import { withModifiers as _withModifiers, delegateEvents as _delegateEvents, template as _template } from 'vue'; +const t0 = _template("
", true) +_delegateEvents("mouseup") + +export function render(_ctx) { const n0 = t0() - n0.$evtmouseup = _withModifiers(e => test(e), ["middle"]) + n0.$evtmouseup = _withModifiers(e => _ctx.test(e), ["middle"]) return n0 -" +}" `; exports[`v-on > should transform click.right 1`] = ` -" +"import { withModifiers as _withModifiers, delegateEvents as _delegateEvents, template as _template } from 'vue'; +const t0 = _template("
", true) +_delegateEvents("contextmenu") + +export function render(_ctx) { const n0 = t0() - n0.$evtcontextmenu = _withModifiers(e => test(e), ["right"]) + n0.$evtcontextmenu = _withModifiers(e => _ctx.test(e), ["right"]) return n0 -" +}" `; exports[`v-on > should use delegate helper when have multiple events of same name 1`] = ` -" +"import { delegate as _delegate, withModifiers as _withModifiers, delegateEvents as _delegateEvents, template as _template } from 'vue'; +const t0 = _template("
", true) +_delegateEvents("click") + +export function render(_ctx) { const n0 = t0() - _delegate(n0, "click", e => test(e)) - _delegate(n0, "click", _withModifiers(e => test(e), ["stop"])) + _delegate(n0, "click", e => _ctx.test(e)) + _delegate(n0, "click", _withModifiers(e => _ctx.test(e), ["stop"])) return n0 -" +}" `; exports[`v-on > should wrap keys guard for keyboard events or dynamic events 1`] = ` diff --git a/packages/compiler/test/transforms/__snapshots__/vShow.spec.ts.snap b/packages/compiler/test/transforms/__snapshots__/vShow.spec.ts.snap index 603d358..0fa9297 100644 --- a/packages/compiler/test/transforms/__snapshots__/vShow.spec.ts.snap +++ b/packages/compiler/test/transforms/__snapshots__/vShow.spec.ts.snap @@ -3,7 +3,7 @@ exports[`compiler: v-show transform > simple expression 1`] = ` " const n0 = t0() - _applyVShow(n0, () => (foo)) + _applyVShow(n0, () => ("foo")) return n0 " `; diff --git a/packages/compiler/test/transforms/_utils.ts b/packages/compiler/test/transforms/_utils.ts index 10a564a..9441c91 100644 --- a/packages/compiler/test/transforms/_utils.ts +++ b/packages/compiler/test/transforms/_utils.ts @@ -1,5 +1,6 @@ import { parse } from '@babel/parser' import { generate, transform, type CompilerOptions } from '../../src' +import { customGenOperation } from '../../src/generate' import { IRNodeTypes, type RootNode } from '../../src/ir' import type { JSXElement, JSXFragment } from '@babel/types' @@ -29,15 +30,18 @@ export function makeCompile(options: CompilerOptions = {}) { } const ir = transform(ast, { expressionPlugins: ['typescript', 'jsx'], + inline: true, + prefixIdentifiers: false, ...options, ...overrideOptions, }) as any - return { - ir, - ...generate(ir, { - ...options, - ...overrideOptions, - }), - } + const { code, helpers, preamble } = generate(ir, { + inline: true, + prefixIdentifiers: false, + ...options, + ...overrideOptions, + customGenOperation, + }) + return { ast, ir, code, helpers, preamble } } } diff --git a/packages/compiler/test/transforms/expression.spec.ts b/packages/compiler/test/transforms/expression.spec.ts index 3fb3365..7ab8c30 100644 --- a/packages/compiler/test/transforms/expression.spec.ts +++ b/packages/compiler/test/transforms/expression.spec.ts @@ -23,13 +23,14 @@ describe('compiler: expression', () => { test('conditional expression', () => { const { code, helpers, ir } = compileWithVIf( `<>{ok? {msg} : fail ?
fail
: null }`, + { inline: false }, ) expect(code).toMatchSnapshot() expect(helpers).contains('createIf') - expect(ir.templates).toEqual([' ', '
fail
']) + expect(ir.template).toEqual([' ', '
fail
']) const op = ir.block.dynamic.children[0].operation expect(op).toMatchObject({ type: IRNodeTypes.IF, @@ -63,7 +64,7 @@ describe('compiler: expression', () => { expect(helpers).contains('createIf') - expect(ir.templates).toEqual(['
']) + expect(ir.template).toEqual(['
']) const op = ir.block.dynamic.children[0].operation expect(op).toMatchObject({ type: IRNodeTypes.IF, @@ -96,7 +97,7 @@ describe('compiler: expression', () => { expect(code).toMatchSnapshot() expect(helpers).contains('createIf') - expect(ir.templates).toEqual([ + expect(ir.template).toEqual([ ' ', '
fail
', '
', diff --git a/packages/compiler/test/transforms/transformChildren.spec.ts b/packages/compiler/test/transforms/transformChildren.spec.ts index 737ba5a..d1f4475 100644 --- a/packages/compiler/test/transforms/transformChildren.spec.ts +++ b/packages/compiler/test/transforms/transformChildren.spec.ts @@ -1,9 +1,8 @@ import { NodeTypes } from '@vue/compiler-dom' +import { IRDynamicPropsKind, IRNodeTypes } from '@vue/compiler-vapor' import { describe, expect, test } from 'vitest' import { - IRDynamicPropsKind, - IRNodeTypes, transformChildren, transformElement, transformText, @@ -37,22 +36,32 @@ describe('compiler: children transform', () => { test('comments', () => { const { code } = compileWithElementTransform( '<>{/*foo*/}
{/*bar*/}
', + { + inline: false, + }, ) expect(code).toMatchInlineSnapshot(` - " + "import { template as _template } from 'vue'; + const t0 = _template("
") + + export function render(_ctx) { const n1 = t0() return n1 - " + }" `) }) test('fragment', () => { - const { code } = compileWithElementTransform('<>{foo}') + const { code } = compileWithElementTransform('<>{foo}', { + inline: false, + }) expect(code).toMatchInlineSnapshot(` - " - const n0 = _createNodes(() => (foo)) + "import { createNodes as _createNodes } from 'vue'; + + export function render(_ctx) { + const n0 = _createNodes(() => (_ctx.foo)) return n0 - " + }" `) }) diff --git a/packages/compiler/test/transforms/transformElement.spec.ts b/packages/compiler/test/transforms/transformElement.spec.ts index 2fe3812..50b69d1 100644 --- a/packages/compiler/test/transforms/transformElement.spec.ts +++ b/packages/compiler/test/transforms/transformElement.spec.ts @@ -37,7 +37,9 @@ describe('compiler: element transform', () => { }) test('resolve namespaced component from setup bindings (inline const)', () => { - const { code, helpers } = compileWithElementTransform(``) + const { code, helpers } = compileWithElementTransform(``, { + inline: true, + }) expect(code).toMatchInlineSnapshot(` " const n0 = _createComponent(Foo.Example, null, null, true) diff --git a/packages/compiler/test/transforms/transformTemplateRef.spec.ts b/packages/compiler/test/transforms/transformTemplateRef.spec.ts index f916a84..c3c4094 100644 --- a/packages/compiler/test/transforms/transformTemplateRef.spec.ts +++ b/packages/compiler/test/transforms/transformTemplateRef.spec.ts @@ -31,7 +31,7 @@ describe('compiler: template ref transform', () => { id: 0, flags: DynamicFlag.REFERENCED, }) - expect(ir.templates).toEqual(['
']) + expect(ir.template).toEqual(['
']) expect(ir.block.operation).lengthOf(1) expect(ir.block.operation[0]).toMatchObject({ type: IRNodeTypes.SET_TEMPLATE_REF, @@ -57,7 +57,7 @@ describe('compiler: template ref transform', () => { id: 0, flags: DynamicFlag.REFERENCED, }) - expect(ir.templates).toEqual(['
']) + expect(ir.template).toEqual(['
']) expect(ir.block.operation).toMatchObject([ { type: IRNodeTypes.DECLARE_OLD_REF, diff --git a/packages/compiler/test/transforms/vBind.spec.ts b/packages/compiler/test/transforms/vBind.spec.ts index 4db2820..bc519a9 100644 --- a/packages/compiler/test/transforms/vBind.spec.ts +++ b/packages/compiler/test/transforms/vBind.spec.ts @@ -31,7 +31,7 @@ describe('compiler v-bind', () => { id: 0, flags: DynamicFlag.REFERENCED, }) - expect(ir.templates).toEqual(['
']) + expect(ir.template).toEqual(['
']) expect(ir.block.effect).lengthOf(1) expect(ir.block.effect[0].expressions).lengthOf(1) expect(ir.block.effect[0].operations).lengthOf(1) @@ -248,7 +248,7 @@ describe('compiler v-bind', () => { end: { line: 1, column: 19 }, }, }) - expect(ir.templates).toEqual(['
']) + expect(ir.template).toEqual(['
']) expect(code).matchSnapshot() expect(code).contains(JSON.stringify('
')) diff --git a/packages/compiler/test/transforms/vFor.spec.ts b/packages/compiler/test/transforms/vFor.spec.ts index 6e1e833..0cafac3 100644 --- a/packages/compiler/test/transforms/vFor.spec.ts +++ b/packages/compiler/test/transforms/vFor.spec.ts @@ -34,7 +34,7 @@ describe('compiler: v-for', () => { expect(code).toMatchSnapshot() expect(helpers).contains('createFor') - expect(ir.templates).toEqual(['
']) + expect(ir.template).toEqual(['
']) const op = ir.block.dynamic.children[0].operation expect(op).toMatchObject({ type: IRNodeTypes.FOR, @@ -84,7 +84,7 @@ describe('compiler: v-for', () => { `_createFor(() => (_for_item0.value), (_for_item1) => {`, ) expect(code).contains(`_for_item1.value+_for_item0.value`) - expect(ir.templates).toEqual([' ', '
']) + expect(ir.template).toEqual([' ', '
']) const op = ir.block.dynamic.children[0].operation expect(op).toMatchObject({ type: IRNodeTypes.FOR, diff --git a/packages/compiler/test/transforms/vHtml.spec.ts b/packages/compiler/test/transforms/vHtml.spec.ts index 8a0b87a..17178b3 100644 --- a/packages/compiler/test/transforms/vHtml.spec.ts +++ b/packages/compiler/test/transforms/vHtml.spec.ts @@ -52,7 +52,7 @@ describe('v-html', () => { test('should raise error and ignore children when v-html is present', () => { const onError = vi.fn() - const { ir, helpers, templates } = compileWithVHtml( + const { ir, helpers, preamble } = compileWithVHtml( `
hello
`, { onError, @@ -62,7 +62,7 @@ describe('v-html', () => { expect(helpers).contains('setHtml') // children should have been removed - expect(ir.templates).toEqual(['
']) + expect(ir.template).toEqual(['
']) expect(ir.block.operation).toMatchObject([]) expect(ir.block.effect).toMatchObject([ @@ -93,7 +93,7 @@ describe('v-html', () => { ]) // children should have been removed - expect(templates).includes('_template("
", true)') + expect(preamble).contains('template("
", true)') }) test('should raise error if has no expression', () => { diff --git a/packages/compiler/test/transforms/vIf.spec.ts b/packages/compiler/test/transforms/vIf.spec.ts index c0bb605..3a2dce4 100644 --- a/packages/compiler/test/transforms/vIf.spec.ts +++ b/packages/compiler/test/transforms/vIf.spec.ts @@ -30,7 +30,7 @@ describe('compiler: v-if', () => { expect(helpers).contains('createIf') expect(code).toMatchSnapshot() - expect(ir.templates).toEqual(['
']) + expect(ir.template).toEqual(['
']) const op = ir.block.dynamic.children[0].operation expect(op).toMatchObject({ type: IRNodeTypes.IF, @@ -61,7 +61,7 @@ describe('compiler: v-if', () => { ) expect(code).matchSnapshot() - expect(ir.templates).toEqual(['
', 'hello', '

']) + expect(ir.template).toEqual(['
', 'hello', '

']) const op = ir.block.dynamic.children[0].operation expect((op as IfIRNode).positive.effect).toMatchObject([ { @@ -95,7 +95,7 @@ describe('compiler: v-if', () => { `<>
hello
hello
`, ) expect(code).matchSnapshot() - expect(ir.templates).toEqual(['
hello
']) + expect(ir.template).toEqual(['
hello
']) expect(ir.block.returns).toEqual([0, 3]) }) @@ -106,7 +106,7 @@ describe('compiler: v-if', () => { `<>

`, ) expect(code).matchSnapshot() - expect(ir.templates).toEqual(['

', '

']) + expect(ir.template).toEqual(['
', '

']) expect(helpers).contains('createIf') expect(ir.block.effect).lengthOf(0) @@ -140,7 +140,7 @@ describe('compiler: v-if', () => { `<>

`, ) expect(code).matchSnapshot() - expect(ir.templates).toEqual(['

', '

']) + expect(ir.template).toEqual(['
', '

']) const op = ir.block.dynamic.children[0].operation expect(op).toMatchObject({ @@ -180,7 +180,7 @@ describe('compiler: v-if', () => { `<>

`, ) expect(code).matchSnapshot() - expect(ir.templates).toEqual(['

', '

', 'fine']) + expect(ir.template).toEqual(['
', '

', 'fine']) expect(ir.block.returns).toEqual([0]) const op = ir.block.dynamic.children[0].operation @@ -236,7 +236,7 @@ describe('compiler: v-if', () => { `, ) expect(code).toMatchSnapshot() - expect(ir.templates).toEqual([ + expect(ir.template).toEqual([ '
', '

', 'fine', diff --git a/packages/compiler/test/transforms/vOn.spec.ts b/packages/compiler/test/transforms/vOn.spec.ts index c9b6547..3d4650e 100644 --- a/packages/compiler/test/transforms/vOn.spec.ts +++ b/packages/compiler/test/transforms/vOn.spec.ts @@ -108,7 +108,7 @@ describe('v-on', () => { expect(onError).not.toHaveBeenCalled() }) - test('should support multiple modifiers and event options', () => { + test('should support multiple modifiers and event options w/ prefixIdentifiers: true', () => { const { code, ir, helpers } = compileWithVOn( `
`, ) @@ -140,7 +140,7 @@ describe('v-on', () => { ) }) - test('should support multiple events and modifiers options', () => { + test('should support multiple events and modifiers options w/ prefixIdentifiers: true', () => { const { code, ir } = compileWithVOn( `
`, ) @@ -241,7 +241,9 @@ describe('v-on', () => { }) test('should wrap keys guard for static key event w/ left/right modifiers', () => { - const { code, ir } = compileWithVOn(`
`) + const { code, ir } = compileWithVOn(`
`, { + prefixIdentifiers: true, + }) expect(ir.block.operation).toMatchObject([ { @@ -258,9 +260,9 @@ describe('v-on', () => { }) test('should transform click.right', () => { - const { code, ir, delegates } = compileWithVOn( - `
`, - ) + const { code, ir } = compileWithVOn(`
`, { + inline: false, + }) expect(ir.block.operation).toMatchObject([ { type: IRNodeTypes.SET_EVENT, @@ -275,13 +277,13 @@ describe('v-on', () => { ]) expect(code).toMatchSnapshot() - expect(delegates).includes('contextmenu') + expect(code).contains('"contextmenu"') }) test('should transform click.middle', () => { - const { code, ir, delegates } = compileWithVOn( - `
`, - ) + const { code, ir } = compileWithVOn(`
`, { + inline: false, + }) expect(ir.block.operation).toMatchObject([ { type: IRNodeTypes.SET_EVENT, @@ -296,16 +298,16 @@ describe('v-on', () => { ]) expect(code).matchSnapshot() - expect(delegates).includes('mouseup') + expect(code).contains('"mouseup"') }) test('should delegate event', () => { - const { code, ir, helpers, delegates } = compileWithVOn( - `
`, - ) + const { code, ir, helpers } = compileWithVOn(`
`, { + inline: false, + }) expect(code).matchSnapshot() - expect(delegates).contains('click') + expect(code).contains('_delegateEvents("click")') expect(helpers).contains('delegateEvents') expect(ir.block.operation).toMatchObject([ { @@ -318,12 +320,13 @@ describe('v-on', () => { test('should use delegate helper when have multiple events of same name', () => { const { code, helpers } = compileWithVOn( `
`, + { inline: false }, ) expect(helpers).contains('delegate') expect(code).toMatchSnapshot() - expect(code).contains('_delegate(n0, "click", e => test(e))') + expect(code).contains('_delegate(n0, "click", e => _ctx.test(e))') expect(code).contains( - '_delegate(n0, "click", _withModifiers(e => test(e), ["stop"]))', + '_delegate(n0, "click", _withModifiers(e => _ctx.test(e), ["stop"]))', ) }) diff --git a/packages/compiler/test/transforms/vShow.spec.ts b/packages/compiler/test/transforms/vShow.spec.ts index 5be94aa..d33d777 100644 --- a/packages/compiler/test/transforms/vShow.spec.ts +++ b/packages/compiler/test/transforms/vShow.spec.ts @@ -12,7 +12,7 @@ const compileWithVShow = makeCompile({ describe('compiler: v-show transform', () => { test('simple expression', () => { - const { code } = compileWithVShow(`
`) + const { code } = compileWithVShow(`
`) expect(code).toMatchSnapshot() }) diff --git a/packages/compiler/test/transforms/vSlot.spec.ts b/packages/compiler/test/transforms/vSlot.spec.ts index 5ff2cd9..7fcba29 100644 --- a/packages/compiler/test/transforms/vSlot.spec.ts +++ b/packages/compiler/test/transforms/vSlot.spec.ts @@ -34,7 +34,7 @@ describe('compiler: transform slot', () => { const { ir, code } = compileWithSlots(`
`) expect(code).toMatchSnapshot() - expect(ir.templates).toEqual(['
']) + expect(ir.template).toEqual(['
']) const op = ir.block.dynamic.children[0].operation expect(op).toMatchObject({ type: IRNodeTypes.CREATE_COMPONENT_NODE, @@ -181,7 +181,7 @@ describe('compiler: transform slot', () => { return n6 " `) - expect(ir.templates).toEqual(['foo', 'bar', '']) + expect(ir.template).toEqual(['foo', 'bar', '']) const op = ir.block.dynamic.children[0].operation expect(op).toMatchObject({ type: IRNodeTypes.CREATE_COMPONENT_NODE, diff --git a/packages/compiler/test/transforms/vText.spec.ts b/packages/compiler/test/transforms/vText.spec.ts index 61a6241..c17aac0 100644 --- a/packages/compiler/test/transforms/vText.spec.ts +++ b/packages/compiler/test/transforms/vText.spec.ts @@ -59,7 +59,7 @@ describe('v-text', () => { test('should raise error and ignore children when v-text is present', () => { const onError = vi.fn() - const { code, ir, templates } = compileWithVText( + const { code, ir, preamble } = compileWithVText( `
hello
`, { onError, @@ -70,7 +70,7 @@ describe('v-text', () => { ]) // children should have been removed - expect(ir.templates).toEqual(['
']) + expect(ir.template).toEqual(['
']) expect(ir.block.effect).toMatchObject([ { @@ -99,7 +99,7 @@ describe('v-text', () => { expect(code).matchSnapshot() // children should have been removed - expect(templates).contains('_template("
", true)') + expect(preamble).contains('template("
", true)') }) test('should raise error if has no expression', () => { diff --git a/playground/package.json b/playground/package.json index b23278d..7c13a11 100644 --- a/playground/package.json +++ b/playground/package.json @@ -10,7 +10,6 @@ "devDependencies": { "vite": "catalog:", "vite-hyper-config": "^0.7.0", - "vite-node": "^3.2.4", "vite-plugin-inspect": "^11.3.0", "vue": "catalog:", "vue-jsx-vapor": "workspace:*" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f16649d..5911a2f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -57,15 +57,15 @@ catalogs: '@vue/compiler-sfc': specifier: https://pkg.pr.new/@vue/compiler-sfc@51677cd version: 3.5.17 + '@vue/compiler-vapor': + specifier: https://pkg.pr.new/@vue/compiler-vapor@51677cd + version: 3.5.13 '@vue/shared': specifier: https://pkg.pr.new/@vue/shared@51677cd version: 3.5.17 hash-sum: specifier: ^2.0.0 version: 2.0.0 - source-map-js: - specifier: ^1.2.1 - version: 1.2.1 unplugin: specifier: ^2.3.5 version: 2.3.5 @@ -170,7 +170,7 @@ importers: specifier: workspace:* version: link:../compiler source-map-js: - specifier: 'catalog:' + specifier: ^1.2.1 version: 1.2.1 devDependencies: '@types/babel__core': @@ -197,15 +197,12 @@ importers: '@vue/compiler-dom': specifier: 'catalog:' version: https://pkg.pr.new/@vue/compiler-dom@51677cd + '@vue/compiler-vapor': + specifier: 'catalog:' + version: https://pkg.pr.new/@vue/compiler-vapor@51677cd '@vue/shared': specifier: 'catalog:' version: https://pkg.pr.new/@vue/shared@51677cd - ast-kit: - specifier: ^2.1.1 - version: 2.1.1 - source-map-js: - specifier: 'catalog:' - version: 1.2.1 packages/eslint: dependencies: @@ -350,9 +347,6 @@ importers: vite-hyper-config: specifier: ^0.7.0 version: 0.7.0(@types/node@22.16.0)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.19.2)(vite@7.0.2(@types/node@22.16.0)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.19.2)(yaml@2.8.0))(yaml@2.8.0) - vite-node: - specifier: ^3.2.4 - version: 3.2.4(@types/node@22.16.0)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.19.2)(yaml@2.8.0) vite-plugin-inspect: specifier: ^11.3.0 version: 11.3.0(vite@7.0.2(@types/node@22.16.0)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.19.2)(yaml@2.8.0)) @@ -2209,6 +2203,10 @@ packages: resolution: {tarball: https://pkg.pr.new/vuejs/core/@vue/compiler-ssr@51677cd} version: 3.5.17 + '@vue/compiler-vapor@https://pkg.pr.new/@vue/compiler-vapor@51677cd': + resolution: {tarball: https://pkg.pr.new/@vue/compiler-vapor@51677cd} + version: 3.5.13 + '@vue/compiler-vapor@https://pkg.pr.new/vuejs/core/@vue/compiler-vapor@51677cd': resolution: {tarball: https://pkg.pr.new/vuejs/core/@vue/compiler-vapor@51677cd} version: 3.5.13 @@ -2483,10 +2481,6 @@ packages: resolution: {integrity: sha512-ROM2LlXbZBZVk97crfw8PGDOBzzsJvN2uJCmwswvPUNyfH14eg90mSN3xNqsri1JS1G9cz0VzeDUhxJkTrr4Ew==} engines: {node: '>=20.18.0'} - ast-kit@2.1.1: - resolution: {integrity: sha512-mfh6a7gKXE8pDlxTvqIc/syH/P3RkzbOF6LeHdcKztLEzYe6IMsRCL7N8vI7hqTGWNxpkCuuRTpT21xNWqhRtQ==} - engines: {node: '>=20.18.0'} - asynckit@0.4.0: resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} @@ -4580,6 +4574,11 @@ packages: peerDependencies: vite: ^4.0.0 || ^5.0.0 || ^6.0.0 + vite-node@3.1.4: + resolution: {integrity: sha512-6enNwYnpyDo4hEgytbmc6mYWHXDHYEn0D1/rw4Q+tnHUGtKTJsn8T1YkX6Q18wI5LCrS8CTYlBaiCqxOy2kvUA==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + hasBin: true + vite-node@3.2.4: resolution: {integrity: sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==} engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} @@ -4984,7 +4983,7 @@ snapshots: '@babel/helper-compilation-targets': 7.27.2 '@babel/helper-module-transforms': 7.27.3(@babel/core@7.27.7) '@babel/helpers': 7.27.6 - '@babel/parser': 7.28.0 + '@babel/parser': 7.27.7 '@babel/template': 7.27.2 '@babel/traverse': 7.27.7 '@babel/types': 7.28.0 @@ -5018,7 +5017,7 @@ snapshots: '@babel/generator@7.27.5': dependencies: - '@babel/parser': 7.28.0 + '@babel/parser': 7.27.7 '@babel/types': 7.27.7 '@jridgewell/gen-mapping': 0.3.8 '@jridgewell/trace-mapping': 0.3.25 @@ -6721,7 +6720,7 @@ snapshots: '@vue/compiler-core@3.5.17': dependencies: - '@babel/parser': 7.28.0 + '@babel/parser': 7.27.7 '@vue/shared': 3.5.17 entities: 4.5.0 estree-walker: 2.0.2 @@ -6798,6 +6797,12 @@ snapshots: '@vue/compiler-dom': https://pkg.pr.new/vuejs/core/@vue/compiler-dom@51677cd '@vue/shared': https://pkg.pr.new/vuejs/core/@vue/shared@51677cd + '@vue/compiler-vapor@https://pkg.pr.new/@vue/compiler-vapor@51677cd': + dependencies: + '@vue/compiler-dom': https://pkg.pr.new/vuejs/core/@vue/compiler-dom@51677cd + '@vue/shared': https://pkg.pr.new/vuejs/core/@vue/shared@51677cd + source-map-js: 1.2.1 + '@vue/compiler-vapor@https://pkg.pr.new/vuejs/core/@vue/compiler-vapor@51677cd': dependencies: '@vue/compiler-dom': https://pkg.pr.new/vuejs/core/@vue/compiler-dom@51677cd @@ -7095,11 +7100,6 @@ snapshots: '@babel/parser': 7.27.7 pathe: 2.0.3 - ast-kit@2.1.1: - dependencies: - '@babel/parser': 7.28.0 - pathe: 2.0.3 - asynckit@0.4.0: optional: true @@ -9619,7 +9619,28 @@ snapshots: cac: 6.7.14 picocolors: 1.1.1 vite: 7.0.2(@types/node@22.16.0)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.19.2)(yaml@2.8.0) - vite-node: 3.2.4(@types/node@22.16.0)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.19.2)(yaml@2.8.0) + vite-node: 3.1.4(@types/node@22.16.0)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.19.2)(yaml@2.8.0) + transitivePeerDependencies: + - '@types/node' + - jiti + - less + - lightningcss + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + - tsx + - yaml + + vite-node@3.1.4(@types/node@22.16.0)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.19.2)(yaml@2.8.0): + dependencies: + cac: 6.7.14 + debug: 4.4.1 + es-module-lexer: 1.7.0 + pathe: 2.0.3 + vite: 6.3.5(@types/node@22.16.0)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.39.0)(tsx@4.19.2)(yaml@2.8.0) transitivePeerDependencies: - '@types/node' - jiti diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index a4eb3de..b2874c3 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -22,6 +22,7 @@ catalog: '@vue/compiler-dom': https://pkg.pr.new/@vue/compiler-dom@51677cd '@vue/compiler-sfc': https://pkg.pr.new/@vue/compiler-sfc@51677cd + '@vue/compiler-vapor': https://pkg.pr.new/@vue/compiler-vapor@51677cd '@vue/shared': https://pkg.pr.new/@vue/shared@51677cd '@vue-macros/common': *vue-macros @@ -32,7 +33,6 @@ catalog: '@ts-macro/tsc': *ts-macro '@types/hash-sum': ^1.0.2 hash-sum: ^2.0.0 - source-map-js: ^1.2.1 ts-macro: *ts-macro unplugin: ^2.3.5 unplugin-utils: ^0.2.4