type CodegenNode = TemplateChildNode | JSChildNode | SSRCodegenNode
-export interface CodegenResult {
+export interface BaseCodegenResult {
code: string
preamble: string
- ast: RootNode
+ ast: unknown
map?: RawSourceMap
+ helpers?: Set<string> | Set<symbol>
+}
+
+export interface CodegenResult extends BaseCodegenResult {
+ ast: RootNode
+ helpers: Set<symbol>
}
export enum NewlineType {
code: context.code,
preamble: isSetupInlined ? preambleContext.code : ``,
map: context.map ? context.map.toJSON() : undefined,
+ helpers: ast.helpers,
}
}
NewlineType,
type CodegenContext,
type CodegenResult,
+ type BaseCodegenResult,
} from './codegen'
export {
ErrorCodes,
}
// inline render function mode - we are going to compile the template and
// inline it right here
- const { code, ast, preamble, tips, errors } = compileTemplate({
+ const { code, preamble, tips, errors, helpers } = compileTemplate({
filename,
ast: sfc.template.ast,
source: sfc.template.content,
// avoid duplicated unref import
// as this may get injected by the render function preamble OR the
// css vars codegen
- if (ast && ast.helpers.has(UNREF)) {
+ if (helpers && helpers.has(UNREF)) {
ctx.helperImports.delete('unref')
}
returned = code
import {
- type CodegenResult,
+ type BaseCodegenResult,
type CompilerError,
type CompilerOptions,
type ElementNode,
import { genCssVarsFromList } from './style/cssVars'
export interface TemplateCompiler {
- compile(source: string | RootNode, options: CompilerOptions): CodegenResult
+ compile(
+ source: string | RootNode,
+ options: CompilerOptions,
+ ): BaseCodegenResult
parse(template: string, options: ParserOptions): RootNode
}
export interface SFCTemplateCompileResults {
code: string
- ast?: RootNode
+ ast?: unknown
preamble?: string
source: string
tips: string[]
errors: (string | CompilerError)[]
map?: RawSourceMap
+ helpers?: Set<string | symbol>
}
export interface SFCTemplateCompileOptions {
inAST = createRoot(template.children, inAST.source)
}
- let { code, ast, preamble, map } = compiler.compile(inAST || source, {
- mode: 'module',
- prefixIdentifiers: true,
- hoistStatic: true,
- cacheHandlers: true,
- ssrCssVars:
- ssr && ssrCssVars && ssrCssVars.length
- ? genCssVarsFromList(ssrCssVars, shortId, isProd, true)
- : '',
- scopeId: scoped ? longId : undefined,
- slotted,
- sourceMap: true,
- ...compilerOptions,
- hmr: !isProd,
- nodeTransforms: nodeTransforms.concat(compilerOptions.nodeTransforms || []),
- filename,
- onError: e => errors.push(e),
- onWarn: w => warnings.push(w),
- })
+ let { code, ast, preamble, map, helpers } = compiler.compile(
+ inAST || source,
+ {
+ mode: 'module',
+ prefixIdentifiers: true,
+ hoistStatic: true,
+ cacheHandlers: true,
+ ssrCssVars:
+ ssr && ssrCssVars && ssrCssVars.length
+ ? genCssVarsFromList(ssrCssVars, shortId, isProd, true)
+ : '',
+ scopeId: scoped ? longId : undefined,
+ slotted,
+ sourceMap: true,
+ ...compilerOptions,
+ hmr: !isProd,
+ nodeTransforms: nodeTransforms.concat(
+ compilerOptions.nodeTransforms || [],
+ ),
+ filename,
+ onError: e => errors.push(e),
+ onWarn: w => warnings.push(w),
+ },
+ )
// inMap should be the map produced by ./parse.ts which is a simple line-only
// mapping. If it is present, we need to adjust the final map and errors to
return msg
})
- return { code, ast, preamble, source, errors, tips, map }
+ return {
+ code,
+ ast,
+ preamble,
+ source,
+ errors,
+ tips,
+ map,
+ helpers,
+ }
}
function mapLines(oldMap: RawSourceMap, newMap: RawSourceMap): RawSourceMap {
import {
type CompilerOptions,
IRNodeTypes,
- type RootIRNode,
compile as _compile,
generate,
transform,
} from '../../src'
import { getBaseTransformPreset } from '../../src/compile'
-function compileWithVHtml(
- template: string,
- options: CompilerOptions = {},
-): {
- ir: RootIRNode
- code: string
-} {
+function compileWithVHtml(template: string, options: CompilerOptions = {}) {
const ast = parse(template, { prefixIdentifiers: true, ...options })
const [nodeTransforms, directiveTransforms] = getBaseTransformPreset(true)
const ir = transform(ast, {
prefixIdentifiers: true,
...options,
})
- const { code } = generate(ir, { prefixIdentifiers: true, ...options })
- return { ir, code }
+ const { code, helpers, vaporHelpers } = generate(ir, {
+ prefixIdentifiers: true,
+ ...options,
+ })
+ return { ir, code, helpers, vaporHelpers }
}
describe('v-html', () => {
test('should convert v-html to innerHTML', () => {
- const { code, ir } = compileWithVHtml(`<div v-html="code"></div>`, {
- bindingMetadata: {
- code: BindingTypes.SETUP_REF,
+ const { code, ir, helpers, vaporHelpers } = compileWithVHtml(
+ `<div v-html="code"></div>`,
+ {
+ bindingMetadata: {
+ code: BindingTypes.SETUP_REF,
+ },
},
- })
+ )
- expect(ir.vaporHelpers).contains('setHtml')
- expect(ir.helpers.size).toBe(0)
+ expect(vaporHelpers).contains('setHtml')
+ expect(helpers.size).toBe(0)
expect(ir.operation).toEqual([])
expect(ir.effect).toMatchObject([
test('should raise error and ignore children when v-html is present', () => {
const onError = vi.fn()
- const { code, ir } = compileWithVHtml(`<div v-html="test">hello</div>`, {
- onError,
- })
+ const { code, ir, helpers, vaporHelpers } = compileWithVHtml(
+ `<div v-html="test">hello</div>`,
+ {
+ onError,
+ },
+ )
- expect(ir.vaporHelpers).contains('setHtml')
- expect(ir.helpers.size).toBe(0)
+ expect(vaporHelpers).contains('setHtml')
+ expect(helpers.size).toBe(0)
// children should have been removed
expect(ir.template).toMatchObject([{ template: '<div></div>' }])
import {
type CompilerOptions,
IRNodeTypes,
- type RootIRNode,
compile as _compile,
generate,
transform,
import { transformVOn } from '../../src/transforms/vOn'
import { transformElement } from '../../src/transforms/transformElement'
-function compileWithVOn(
- template: string,
- options: CompilerOptions = {},
-): {
- ir: RootIRNode
- code: string
-} {
+function compileWithVOn(template: string, options: CompilerOptions = {}) {
const ast = parse(template, { prefixIdentifiers: true, ...options })
const ir = transform(ast, {
nodeTransforms: [transformElement],
prefixIdentifiers: true,
...options,
})
- const { code } = generate(ir, { prefixIdentifiers: true, ...options })
- return { ir, code }
+ const { code, helpers, vaporHelpers } = generate(ir, {
+ prefixIdentifiers: true,
+ ...options,
+ })
+ return { ir, code, helpers, vaporHelpers }
}
describe('v-on', () => {
test('simple expression', () => {
- const { code, ir } = compileWithVOn(`<div @click="handleClick"></div>`, {
- bindingMetadata: {
- handleClick: BindingTypes.SETUP_CONST,
+ const { code, ir, helpers, vaporHelpers } = compileWithVOn(
+ `<div @click="handleClick"></div>`,
+ {
+ bindingMetadata: {
+ handleClick: BindingTypes.SETUP_CONST,
+ },
},
- })
+ )
- expect(ir.vaporHelpers).contains('on')
- expect(ir.helpers.size).toBe(0)
+ expect(vaporHelpers).contains('on')
+ expect(helpers.size).toBe(0)
expect(ir.effect).toEqual([])
expect(ir.operation).toMatchObject([
})
test('dynamic arg', () => {
- const { code, ir } = compileWithVOn(`<div v-on:[event]="handler"/>`)
+ const { code, ir, helpers, vaporHelpers } = compileWithVOn(
+ `<div v-on:[event]="handler"/>`,
+ )
- expect(ir.vaporHelpers).contains('on')
- expect(ir.vaporHelpers).contains('renderEffect')
- expect(ir.helpers.size).toBe(0)
+ expect(vaporHelpers).contains('on')
+ expect(vaporHelpers).contains('renderEffect')
+ expect(helpers.size).toBe(0)
expect(ir.operation).toEqual([])
expect(ir.effect[0].operations[0]).toMatchObject({
})
test('dynamic arg with complex exp prefixing', () => {
- const { ir, code } = compileWithVOn(`<div v-on:[event(foo)]="handler"/>`, {
- prefixIdentifiers: true,
- })
+ const { ir, code, helpers, vaporHelpers } = compileWithVOn(
+ `<div v-on:[event(foo)]="handler"/>`,
+ {
+ prefixIdentifiers: true,
+ },
+ )
- expect(ir.vaporHelpers).contains('on')
- expect(ir.vaporHelpers).contains('renderEffect')
- expect(ir.helpers.size).toBe(0)
+ expect(vaporHelpers).contains('on')
+ expect(vaporHelpers).contains('renderEffect')
+ expect(helpers.size).toBe(0)
expect(ir.operation).toEqual([])
expect(ir.effect[0].operations[0]).toMatchObject({
})
test('should wrap as function if expression is inline statement', () => {
- const { code, ir } = compileWithVOn(`<div @click="i++"/>`)
+ const { code, ir, helpers, vaporHelpers } =
+ compileWithVOn(`<div @click="i++"/>`)
- expect(ir.vaporHelpers).contains('on')
- expect(ir.helpers.size).toBe(0)
+ expect(vaporHelpers).contains('on')
+ expect(helpers.size).toBe(0)
expect(ir.effect).toEqual([])
expect(ir.operation).toMatchObject([
})
test('case conversion for kebab-case events', () => {
- const { ir, code } = compileWithVOn(`<div v-on:foo-bar="onMount"/>`)
+ const { ir, code, helpers, vaporHelpers } = compileWithVOn(
+ `<div v-on:foo-bar="onMount"/>`,
+ )
- expect(ir.vaporHelpers).contains('on')
- expect(ir.helpers.size).toBe(0)
+ expect(vaporHelpers).contains('on')
+ expect(helpers.size).toBe(0)
expect(ir.effect).toEqual([])
expect(ir.operation).toMatchObject([
test.todo('vue: prefixed events')
test('should support multiple modifiers and event options w/ prefixIdentifiers: true', () => {
- const { code, ir } = compileWithVOn(
+ const { code, ir, vaporHelpers } = compileWithVOn(
`<div @click.stop.prevent.capture.once="test"/>`,
{
prefixIdentifiers: true,
},
)
- expect(ir.vaporHelpers).contains('on')
- expect(ir.vaporHelpers).contains('withModifiers')
+ expect(vaporHelpers).contains('on')
+ expect(vaporHelpers).contains('withModifiers')
expect(ir.operation).toMatchObject([
{
import {
type CompilerOptions,
IRNodeTypes,
- type RootIRNode,
compile as _compile,
generate as generate,
transform,
} from '../../src'
import { getBaseTransformPreset } from '../../src/compile'
-function compileWithOnce(
- template: string,
- options: CompilerOptions = {},
-): {
- ir: RootIRNode
- code: string
-} {
+function compileWithOnce(template: string, options: CompilerOptions = {}) {
const ast = parse(template, { prefixIdentifiers: true, ...options })
const [nodeTransforms, directiveTransforms] = getBaseTransformPreset(true)
const ir = transform(ast, {
prefixIdentifiers: true,
...options,
})
- const { code } = generate(ir, { prefixIdentifiers: true, ...options })
- return { ir, code }
+ const { code, helpers, vaporHelpers } = generate(ir, {
+ prefixIdentifiers: true,
+ ...options,
+ })
+ return { ir, code, helpers, vaporHelpers }
}
describe('compiler: v-once', () => {
test('basic', () => {
- const { ir, code } = compileWithOnce(
+ const { ir, code, helpers } = compileWithOnce(
`<div v-once>
{{ msg }}
<span :class="clz" />
},
},
)
- expect(ir.helpers.size).toBe(0)
+ expect(helpers.size).toBe(0)
expect(ir.effect).toEqual([])
expect(ir.operation).toMatchObject([
})
test('as root node', () => {
- const { ir, code } = compileWithOnce(`<div :id="foo" v-once />`)
+ const { ir, code, helpers } = compileWithOnce(`<div :id="foo" v-once />`)
- expect(ir.helpers.size).toBe(0)
+ expect(helpers.size).toBe(0)
expect(ir.effect).toEqual([])
expect(ir.operation).toMatchObject([
})
test('on nested plain element', () => {
- const { ir, code } = compileWithOnce(`<div><div :id="foo" v-once /></div>`)
- expect(ir.helpers.size).toBe(0)
+ const { ir, code, helpers } = compileWithOnce(
+ `<div><div :id="foo" v-once /></div>`,
+ )
+ expect(helpers.size).toBe(0)
expect(ir.effect).toEqual([])
expect(ir.operation).toMatchObject([
test.todo('on slot outlet')
test('inside v-once', () => {
- const { ir, code } = compileWithOnce(`<div v-once><div v-once/></div>`)
- expect(ir.helpers.size).toBe(0)
+ const { ir, code, helpers } = compileWithOnce(
+ `<div v-once><div v-once/></div>`,
+ )
+ expect(helpers.size).toBe(0)
expect(ir.effect).toMatchObject([])
expect(ir.operation).toMatchObject([])
import {
type CompilerOptions,
IRNodeTypes,
- type RootIRNode,
compile as _compile,
generate,
transform,
} from '../../src'
import { getBaseTransformPreset } from '../../src/compile'
-function compileWithVText(
- template: string,
- options: CompilerOptions = {},
-): {
- ir: RootIRNode
- code: string
-} {
+function compileWithVText(template: string, options: CompilerOptions = {}) {
const ast = parse(template, { prefixIdentifiers: true, ...options })
const [nodeTransforms, directiveTransforms] = getBaseTransformPreset(true)
const ir = transform(ast, {
prefixIdentifiers: true,
...options,
})
- const { code } = generate(ir, { prefixIdentifiers: true, ...options })
- return { ir, code }
+ const { code, helpers, vaporHelpers } = generate(ir, {
+ prefixIdentifiers: true,
+ ...options,
+ })
+ return { ir, code, helpers, vaporHelpers }
}
describe('v-text', () => {
test('should convert v-text to textContent', () => {
- const { code, ir } = compileWithVText(`<div v-text="str"></div>`, {
- bindingMetadata: {
- str: BindingTypes.SETUP_REF,
+ const { code, ir, helpers, vaporHelpers } = compileWithVText(
+ `<div v-text="str"></div>`,
+ {
+ bindingMetadata: {
+ str: BindingTypes.SETUP_REF,
+ },
},
- })
+ )
- expect(ir.vaporHelpers).contains('setText')
- expect(ir.helpers.size).toBe(0)
+ expect(vaporHelpers).contains('setText')
+ expect(helpers.size).toBe(0)
expect(ir.operation).toEqual([])
import {
type CompilerOptions as BaseCompilerOptions,
- type CodegenResult,
ErrorCodes,
type RootNode,
createCompilerError,
type NodeTransform,
transform,
} from './transform'
-import { generate } from './generate'
+import { type VaporCodegenResult, generate } from './generate'
import { transformOnce } from './transforms/vOnce'
import { transformElement } from './transforms/transformElement'
import { transformVHtml } from './transforms/vHtml'
export function compile(
source: string | RootNode,
options: CompilerOptions = {},
-): CodegenResult {
+): VaporCodegenResult {
const onError = options.onError || defaultOnError
const isModuleMode = options.mode === 'module'
/* istanbul ignore if */
import {
type CodegenOptions as BaseCodegenOptions,
- type CodegenResult,
+ type BaseCodegenResult,
NewlineType,
type Position,
type SourceLocation,
expressionPlugins = [],
}: CodegenOptions,
) {
- const { helpers, vaporHelpers } = ir
+ const helpers = new Set<string>([])
+ const vaporHelpers = new Set<string>([])
const context: CodegenContext = {
mode,
prefixIdentifiers,
return context
}
+export interface VaporCodegenResult extends BaseCodegenResult {
+ ast: RootIRNode
+ helpers: Set<string>
+ vaporHelpers: Set<string>
+}
+
// IR -> JS codegen
export function generate(
ir: RootIRNode,
options: CodegenOptions = {},
-): CodegenResult {
+): VaporCodegenResult {
const ctx = createCodegenContext(ir, options)
const {
push,
return {
code: ctx.code,
- ast: ir as any,
+ ast: ir,
preamble,
map: ctx.map ? ctx.map.toJSON() : undefined,
+ helpers,
+ vaporHelpers,
}
}
dynamic: IRDynamicInfo
effect: IREffect[]
operation: OperationNode[]
- helpers: Set<string>
- vaporHelpers: Set<VaporHelper>
}
export interface TemplateFactoryIRNode extends BaseIRNode {
operation: OperationNode[],
): void
registerOperation(...operations: OperationNode[]): void
- helper(name: string): string
+}
+
+const defaultOptions = {
+ filename: '',
+ prefixIdentifiers: false,
+ hoistStatic: false,
+ hmr: false,
+ cacheHandlers: false,
+ nodeTransforms: [],
+ directiveTransforms: {},
+ transformHoist: null,
+ isBuiltInComponent: NOOP,
+ isCustomElement: NOOP,
+ expressionPlugins: [],
+ scopeId: null,
+ slotted: true,
+ ssr: false,
+ inSSR: false,
+ ssrCssVars: ``,
+ bindingMetadata: EMPTY_OBJ,
+ inline: false,
+ isTS: false,
+ onError: defaultOnError,
+ onWarn: defaultOnWarn,
}
// TODO use class for better perf
options: TransformOptions = {},
): TransformContext<RootNode> {
let globalId = 0
- const { effect, operation: operation, helpers, vaporHelpers } = ir
+ const { effect, operation: operation } = ir
const ctx: TransformContext<RootNode> = {
node,
parent: null,
index: 0,
root: null!, // set later
- options: extend(
- {},
- {
- filename: '',
- prefixIdentifiers: false,
- hoistStatic: false,
- hmr: false,
- cacheHandlers: false,
- nodeTransforms: [],
- directiveTransforms: {},
- transformHoist: null,
- isBuiltInComponent: NOOP,
- isCustomElement: NOOP,
- expressionPlugins: [],
- scopeId: null,
- slotted: true,
- ssr: false,
- inSSR: false,
- ssrCssVars: ``,
- bindingMetadata: EMPTY_OBJ,
- inline: false,
- isTS: false,
- onError: defaultOnError,
- onWarn: defaultOnWarn,
- },
- options,
- ),
+ options: extend({}, defaultOptions, options),
dynamic: ir.dynamic,
inVOnce: false,
registerOperation(...node) {
operation.push(...node)
},
- // TODO not used yet
- helper(name, vapor = true) {
- ;(vapor ? vaporHelpers : helpers).add(name)
- return name
- },
}
ctx.root = ctx
ctx.reference()
},
effect: [],
operation: [],
- helpers: new Set([]),
- vaporHelpers: new Set([]),
}
const ctx = createRootContext(ir, root, options)