Skip to content
1 change: 1 addition & 0 deletions packages/compiler-core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export {
type NodeTransform,
type StructuralDirectiveTransform,
type DirectiveTransform,
type ImportItem,
} from './transform'
export {
generate,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,17 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html

exports[`compile > asset imports > from public directory 1`] = `
"import { setProp as _setProp, renderEffect as _renderEffect, template as _template } from 'vue';
import _imports_0 from '/vite.svg';
const t0 = _template("<img class=\\"logo\\" alt=\\"Vite logo\\">", true)
export function render(_ctx) {
const n0 = t0()
_renderEffect(() => _setProp(n0, "src", _imports_0))
return n0
}"
`;
exports[`compile > bindings 1`] = `
"import { child as _child, toDisplayString as _toDisplayString, setText as _setText, renderEffect as _renderEffect, template as _template } from 'vue';
const t0 = _template("<div> </div>", true)
Expand Down
43 changes: 43 additions & 0 deletions packages/compiler-vapor/__tests__/compile.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { BindingTypes, type RootNode } from '@vue/compiler-dom'
import { type CompilerOptions, compile as _compile } from '../src'
import { compileTemplate } from '@vue/compiler-sfc'

// TODO This is a temporary test case for initial implementation.
// Remove it once we have more comprehensive tests.
Expand Down Expand Up @@ -262,4 +263,46 @@ describe('compile', () => {
)
})
})

describe('asset imports', () => {
const compileWithAssets = (template: string) => {
const { code } = compileTemplate({
vapor: true,
id: 'test',
filename: 'test.vue',
source: template,
transformAssetUrls: {
base: 'base/',
includeAbsolute: true,
},
})
return code
}

test('from public directory', () => {
const code = compileWithAssets(`<img src="/foo.svg" />`)
expect(code).matchSnapshot()
expect(code).contains(`import _imports_0 from '/foo.svg';`)
})

test(`multiple public assets`, () => {
const code = compileWithAssets(
`<img src="/foo.svg" />
<img src="/bar.svg" />`,
)
expect(code).matchSnapshot()
expect(code).contains(`import _imports_0 from '/foo.svg';`)
expect(code).contains(`import _imports_1 from '/bar.svg';`)
})

test(`hybrid assets`, () => {
const code = compileWithAssets(
`<img src="/foo.svg" />
<img src="./bar.svg" />`,
)
expect(code).matchSnapshot()
expect(code).contains(`import _imports_0 from '/foo.svg';`)
expect(code).contains(`src=\\"base/bar.svg\\"`)
})
})
})
13 changes: 12 additions & 1 deletion packages/compiler-vapor/src/generate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ export function generate(

const delegates = genDelegates(context)
const templates = genTemplates(ir.template, ir.rootTemplateIndex, context)
const imports = genHelperImports(context)
const imports = genHelperImports(context) + genAssetImports(context)
const preamble = imports + templates + delegates

const newlineCount = [...preamble].filter(c => c === '\n').length
Expand Down Expand Up @@ -178,3 +178,14 @@ function genHelperImports({ helpers, helper, options }: CodegenContext) {
}
return imports
}

function genAssetImports({ ir, helper, options }: CodegenContext) {
const assetImports = ir.node.imports
let imports = ''
for (const assetImport of assetImports) {
const exp = assetImport.exp as SimpleExpressionNode
const name = exp.content
imports += `import ${name} from '${assetImport.path}';\n`
}
return imports
}
9 changes: 7 additions & 2 deletions packages/compiler-vapor/src/generators/expression.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import {
} from '@vue/compiler-dom'
import type { Identifier, Node } from '@babel/types'
import type { CodegenContext } from '../generate'
import { isConstantExpression } from '../utils'
import { getAssetImports, isConstantExpression } from '../utils'
import { type CodeFragment, NEWLINE, buildCodeFragment } from './utils'
import { type ParserOptions, parseExpression } from '@babel/parser'

Expand All @@ -29,6 +29,7 @@ export function genExpression(
assignment?: string,
): CodeFragment[] {
const { content, ast, isStatic, loc } = node
const imports = getAssetImports(context.ir)

if (isStatic) {
return [[JSON.stringify(content), NewlineType.None, loc]]
Expand All @@ -44,7 +45,7 @@ export function genExpression(
}

// the expression is a simple identifier
if (ast === null) {
if (ast === null || imports.includes(content)) {
return genIdentifier(content, context, loc, assignment)
}

Expand Down Expand Up @@ -249,6 +250,10 @@ export function processExpressions(
expressions: SimpleExpressionNode[],
shouldDeclare: boolean,
): DeclarationResult {
// filter out asset import expressions
const imports = getAssetImports(context.ir)
expressions = expressions.filter(exp => !imports.includes(exp.content))

// analyze variables
const {
seenVariable,
Expand Down
17 changes: 11 additions & 6 deletions packages/compiler-vapor/src/generators/template.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,23 @@ import { genDirectivesForElement } from './directive'
import { genOperationWithInsertionState } from './operation'
import { type CodeFragment, NEWLINE, buildCodeFragment, genCall } from './utils'

function escapeTemplate(str: string): string {
return str
.replace(/\\/g, '\\\\') // 转义反斜杠
.replace(/`/g, '\\`') // 转义反引号
// 不转义 `${`,保留插值
}

export function genTemplates(
templates: string[],
rootIndex: number | undefined,
{ helper }: CodegenContext,
): string {
return templates
.map(
(template, i) =>
`const t${i} = ${helper('template')}(${JSON.stringify(
template,
)}${i === rootIndex ? ', true' : ''})\n`,
)
.map((template, i) => {
const escaped = escapeTemplate(template)
return `const t${i} = ${helper('template')}(\`${escaped}\`${i === rootIndex ? ', true' : ''})\n`
})
.join('')
}

Expand Down
5 changes: 4 additions & 1 deletion packages/compiler-vapor/src/transform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import {
} from './ir'
import { isConstantExpression, isStaticExpression } from './utils'
import { newBlock, newDynamic } from './transforms/utils'
import type { ImportItem } from '@vue/compiler-core'

export type NodeTransform = (
node: RootNode | TemplateChildNode,
Expand Down Expand Up @@ -60,7 +61,6 @@ export type StructuralDirectiveTransform = (
) => void | (() => void)

export type TransformOptions = HackOptions<BaseTransformOptions>

export class TransformContext<T extends AllNode = AllNode> {
selfName: string | null = null
parent: TransformContext<RootNode | ElementNode> | null = null
Expand All @@ -75,6 +75,7 @@ export class TransformContext<T extends AllNode = AllNode> {
template: string = ''
childrenTemplate: (string | null)[] = []
dynamic: IRDynamicInfo = this.ir.block.dynamic
imports: ImportItem[] = []

inVOnce: boolean = false
inVFor: number = 0
Expand Down Expand Up @@ -225,6 +226,8 @@ export function transform(

transformNode(context)

ir.node.imports = context.imports

return ir
}

Expand Down
7 changes: 5 additions & 2 deletions packages/compiler-vapor/src/transforms/transformElement.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ import {
type VaporDirectiveNode,
} from '../ir'
import { EMPTY_EXPRESSION } from './utils'
import { findProp } from '../utils'
import { findProp, getAssetImports } from '../utils'

export const isReservedProp: (key: string) => boolean = /*#__PURE__*/ makeMap(
// the leading comma is intentional so empty string "" is also included
Expand Down Expand Up @@ -223,7 +223,10 @@ function transformNativeElement(
} else {
for (const prop of propsResult[1]) {
const { key, values } = prop
if (key.isStatic && values.length === 1 && values[0].isStatic) {
const imports = getAssetImports(context)
if (imports.includes(values[0].content)) {
template += ` ${key.content}=""+${values[0].content}+""`
} else if (key.isStatic && values.length === 1 && values[0].isStatic) {
template += ` ${key.content}`
if (values[0].content) template += `="${values[0].content}"`
} else {
Expand Down
13 changes: 12 additions & 1 deletion packages/compiler-vapor/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,9 @@ import {
isConstantNode,
isLiteralWhitelisted,
} from '@vue/compiler-dom'
import type { VaporDirectiveNode } from './ir'
import type { RootIRNode, VaporDirectiveNode } from './ir'
import { EMPTY_EXPRESSION } from './transforms/utils'
import { TransformContext } from './transform'

export const findProp = _findProp as (
node: ElementNode,
Expand Down Expand Up @@ -88,3 +89,13 @@ export function getLiteralExpressionValue(
}
return exp.isStatic ? exp.content : null
}

export function getAssetImports(context: TransformContext): string[]
export function getAssetImports(ir: RootIRNode): string[]
export function getAssetImports(ctx: TransformContext | RootIRNode): string[] {
const imports =
ctx instanceof TransformContext ? ctx.imports : ctx.node.imports
return imports.map(i =>
typeof i === 'string' ? i : (i.exp as SimpleExpressionNode).content,
)
}