From d0b7210f78ef2f149a8651ef0f2c08a3782850b9 Mon Sep 17 00:00:00 2001 From: 0xfe <2953427626@qq.com> Date: Tue, 15 Jul 2025 00:18:27 +0800 Subject: [PATCH 1/4] fix(vapor): Fix asset import from public directory. --- packages/compiler-vapor/src/generate.ts | 13 +++++++++++- .../src/generators/expression.ts | 21 ++++++++++++++++++- packages/compiler-vapor/src/transform.ts | 8 +++++++ 3 files changed, 40 insertions(+), 2 deletions(-) diff --git a/packages/compiler-vapor/src/generate.ts b/packages/compiler-vapor/src/generate.ts index 193a0f5da77..bacc2daf34a 100644 --- a/packages/compiler-vapor/src/generate.ts +++ b/packages/compiler-vapor/src/generate.ts @@ -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 @@ -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 +} diff --git a/packages/compiler-vapor/src/generators/expression.ts b/packages/compiler-vapor/src/generators/expression.ts index aa7edf658f5..44945620830 100644 --- a/packages/compiler-vapor/src/generators/expression.ts +++ b/packages/compiler-vapor/src/generators/expression.ts @@ -29,6 +29,9 @@ export function genExpression( assignment?: string, ): CodeFragment[] { const { content, ast, isStatic, loc } = node + const imports = context.ir.node.imports.map( + i => (i.exp as SimpleExpressionNode).content || i.exp, + ) if (isStatic) { return [[JSON.stringify(content), NewlineType.None, loc]] @@ -44,7 +47,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) } @@ -129,6 +132,13 @@ function genIdentifier( const { options, helper, identifiers } = context const { inline, bindingMetadata } = options let name: string | undefined = raw + const imports = context.ir.node.imports.map( + i => (i.exp as SimpleExpressionNode).content || i.exp, + ) + + if (imports.includes(raw)) { + return [[raw, NewlineType.None, loc]] + } const idMap = identifiers[raw] if (idMap && idMap.length) { @@ -249,6 +259,15 @@ export function processExpressions( expressions: SimpleExpressionNode[], shouldDeclare: boolean, ): DeclarationResult { + // filter out asset import expressions + const imports = context.ir.node.imports + const importVariables = imports.map( + i => (i.exp as SimpleExpressionNode).content, + ) + expressions = expressions.filter( + exp => !importVariables.includes(exp.content), + ) + // analyze variables const { seenVariable, diff --git a/packages/compiler-vapor/src/transform.ts b/packages/compiler-vapor/src/transform.ts index 946c89b734a..7a9c0b62560 100644 --- a/packages/compiler-vapor/src/transform.ts +++ b/packages/compiler-vapor/src/transform.ts @@ -5,6 +5,7 @@ import { type CompilerCompatOptions, type ElementNode, ElementTypes, + type ExpressionNode, NodeTypes, type RootNode, type SimpleExpressionNode, @@ -60,6 +61,10 @@ export type StructuralDirectiveTransform = ( ) => void | (() => void) export type TransformOptions = HackOptions +export interface ImportItem { + exp: string | ExpressionNode + path: string +} export class TransformContext { selfName: string | null = null @@ -75,6 +80,7 @@ export class TransformContext { template: string = '' childrenTemplate: (string | null)[] = [] dynamic: IRDynamicInfo = this.ir.block.dynamic + imports: ImportItem[] = [] inVOnce: boolean = false inVFor: number = 0 @@ -225,6 +231,8 @@ export function transform( transformNode(context) + ir.node.imports = context.imports + return ir } From d50620c0faf88af87d7baf580e7c400ac571a955 Mon Sep 17 00:00:00 2001 From: 0xfe <2953427626@qq.com> Date: Tue, 15 Jul 2025 01:08:47 +0800 Subject: [PATCH 2/4] test(compiler-vapor): add snapshot test for asset imports from public directory --- .../__snapshots__/compile.spec.ts.snap | 12 +++++++++ .../compiler-vapor/__tests__/compile.spec.ts | 25 +++++++++++++++++++ 2 files changed, 37 insertions(+) diff --git a/packages/compiler-vapor/__tests__/__snapshots__/compile.spec.ts.snap b/packages/compiler-vapor/__tests__/__snapshots__/compile.spec.ts.snap index b10a98d32cb..c248e6c7e06 100644 --- a/packages/compiler-vapor/__tests__/__snapshots__/compile.spec.ts.snap +++ b/packages/compiler-vapor/__tests__/__snapshots__/compile.spec.ts.snap @@ -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("\\"Vite", 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("
", true) diff --git a/packages/compiler-vapor/__tests__/compile.spec.ts b/packages/compiler-vapor/__tests__/compile.spec.ts index 178021d13dd..e82c4e1987e 100644 --- a/packages/compiler-vapor/__tests__/compile.spec.ts +++ b/packages/compiler-vapor/__tests__/compile.spec.ts @@ -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. @@ -262,4 +263,28 @@ describe('compile', () => { ) }) }) + + describe('asset imports', () => { + const compileWithAssets = (template: string) => { + const { code } = compileTemplate({ + vapor: true, + id: 'test', + filename: 'test.vue', + source: template, + transformAssetUrls: { + base: 'foo/', + includeAbsolute: true, + }, + }) + return code + } + + test('from public directory', () => { + const code = compileWithAssets( + ``, + ) + expect(code).matchSnapshot() + expect(code).contains(`import _imports_0 from '/vite.svg';`) + }) + }) }) From 2b9f5fdca3ce79756e2ad331d53cbd77c0b64ad4 Mon Sep 17 00:00:00 2001 From: 0xfe <2953427626@qq.com> Date: Tue, 15 Jul 2025 22:53:14 +0800 Subject: [PATCH 3/4] wip: avoid renderEffect and setProp. --- packages/compiler-core/src/index.ts | 1 + .../compiler-vapor/__tests__/compile.spec.ts | 24 ++++++++++++++++--- .../src/generators/expression.ts | 22 ++++------------- .../compiler-vapor/src/generators/template.ts | 17 ++++++++----- packages/compiler-vapor/src/transform.ts | 7 +----- .../src/transforms/transformElement.ts | 7 ++++-- packages/compiler-vapor/src/utils.ts | 13 +++++++++- 7 files changed, 55 insertions(+), 36 deletions(-) diff --git a/packages/compiler-core/src/index.ts b/packages/compiler-core/src/index.ts index e54b0c3a498..920634d0032 100644 --- a/packages/compiler-core/src/index.ts +++ b/packages/compiler-core/src/index.ts @@ -21,6 +21,7 @@ export { type NodeTransform, type StructuralDirectiveTransform, type DirectiveTransform, + type ImportItem, } from './transform' export { generate, diff --git a/packages/compiler-vapor/__tests__/compile.spec.ts b/packages/compiler-vapor/__tests__/compile.spec.ts index e82c4e1987e..e26c04dc656 100644 --- a/packages/compiler-vapor/__tests__/compile.spec.ts +++ b/packages/compiler-vapor/__tests__/compile.spec.ts @@ -272,7 +272,7 @@ describe('compile', () => { filename: 'test.vue', source: template, transformAssetUrls: { - base: 'foo/', + base: 'base/', includeAbsolute: true, }, }) @@ -280,11 +280,29 @@ describe('compile', () => { } test('from public directory', () => { + const code = compileWithAssets(``) + expect(code).matchSnapshot() + expect(code).contains(`import _imports_0 from '/foo.svg';`) + }) + + test(`multiple public assets`, () => { + const code = compileWithAssets( + ` + `, + ) + 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( - ``, + ` + `, ) expect(code).matchSnapshot() - expect(code).contains(`import _imports_0 from '/vite.svg';`) + expect(code).contains(`import _imports_0 from '/foo.svg';`) + expect(code).contains(`src=\\"base/bar.svg\\"`) }) }) }) diff --git a/packages/compiler-vapor/src/generators/expression.ts b/packages/compiler-vapor/src/generators/expression.ts index 44945620830..c15fec5d1db 100644 --- a/packages/compiler-vapor/src/generators/expression.ts +++ b/packages/compiler-vapor/src/generators/expression.ts @@ -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' @@ -29,9 +29,7 @@ export function genExpression( assignment?: string, ): CodeFragment[] { const { content, ast, isStatic, loc } = node - const imports = context.ir.node.imports.map( - i => (i.exp as SimpleExpressionNode).content || i.exp, - ) + const imports = getAssetImports(context.ir) if (isStatic) { return [[JSON.stringify(content), NewlineType.None, loc]] @@ -132,13 +130,6 @@ function genIdentifier( const { options, helper, identifiers } = context const { inline, bindingMetadata } = options let name: string | undefined = raw - const imports = context.ir.node.imports.map( - i => (i.exp as SimpleExpressionNode).content || i.exp, - ) - - if (imports.includes(raw)) { - return [[raw, NewlineType.None, loc]] - } const idMap = identifiers[raw] if (idMap && idMap.length) { @@ -260,13 +251,8 @@ export function processExpressions( shouldDeclare: boolean, ): DeclarationResult { // filter out asset import expressions - const imports = context.ir.node.imports - const importVariables = imports.map( - i => (i.exp as SimpleExpressionNode).content, - ) - expressions = expressions.filter( - exp => !importVariables.includes(exp.content), - ) + const imports = getAssetImports(context.ir) + expressions = expressions.filter(exp => !imports.includes(exp.content)) // analyze variables const { diff --git a/packages/compiler-vapor/src/generators/template.ts b/packages/compiler-vapor/src/generators/template.ts index 5a066b09e9a..9e7b59cce90 100644 --- a/packages/compiler-vapor/src/generators/template.ts +++ b/packages/compiler-vapor/src/generators/template.ts @@ -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('') } diff --git a/packages/compiler-vapor/src/transform.ts b/packages/compiler-vapor/src/transform.ts index 7a9c0b62560..7673b5c3b6d 100644 --- a/packages/compiler-vapor/src/transform.ts +++ b/packages/compiler-vapor/src/transform.ts @@ -5,7 +5,6 @@ import { type CompilerCompatOptions, type ElementNode, ElementTypes, - type ExpressionNode, NodeTypes, type RootNode, type SimpleExpressionNode, @@ -29,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, @@ -61,11 +61,6 @@ export type StructuralDirectiveTransform = ( ) => void | (() => void) export type TransformOptions = HackOptions -export interface ImportItem { - exp: string | ExpressionNode - path: string -} - export class TransformContext { selfName: string | null = null parent: TransformContext | null = null diff --git a/packages/compiler-vapor/src/transforms/transformElement.ts b/packages/compiler-vapor/src/transforms/transformElement.ts index 05153e729af..267f3b0079b 100644 --- a/packages/compiler-vapor/src/transforms/transformElement.ts +++ b/packages/compiler-vapor/src/transforms/transformElement.ts @@ -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 @@ -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 { diff --git a/packages/compiler-vapor/src/utils.ts b/packages/compiler-vapor/src/utils.ts index 728281914fd..61dcbc6e7cb 100644 --- a/packages/compiler-vapor/src/utils.ts +++ b/packages/compiler-vapor/src/utils.ts @@ -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, @@ -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, + ) +} From 620885263e96efd3438e2a4c3d7e83e4a23c2315 Mon Sep 17 00:00:00 2001 From: 0xfe <2953427626@qq.com> Date: Thu, 17 Jul 2025 22:46:26 +0800 Subject: [PATCH 4/4] wip: fix asset Url importing TODO: fix https://github.com/Gianthard-cyh/core/blob/minor/packages/compiler-vapor/__tests__/transforms/templateTransformAssetUrl.spec.ts#L95 --- .../src/template/transformAssetUrl.ts | 2 +- .../compiler-vapor/__tests__/compile.spec.ts | 43 ---- .../templateTransformAssetUrl.spec.ts.snap | 134 +++++++++++++ .../templateTransformAssetUrl.spec.ts | 183 ++++++++++++++++++ .../templateTransformSrcset.spec.ts | 85 ++++++++ .../compiler-vapor/src/generators/template.ts | 20 +- .../src/transforms/transformElement.ts | 7 +- 7 files changed, 416 insertions(+), 58 deletions(-) create mode 100644 packages/compiler-vapor/__tests__/transforms/__snapshots__/templateTransformAssetUrl.spec.ts.snap create mode 100644 packages/compiler-vapor/__tests__/transforms/templateTransformAssetUrl.spec.ts create mode 100644 packages/compiler-vapor/__tests__/transforms/templateTransformSrcset.spec.ts diff --git a/packages/compiler-sfc/src/template/transformAssetUrl.ts b/packages/compiler-sfc/src/template/transformAssetUrl.ts index 6291e21bbba..50a210ed3f1 100644 --- a/packages/compiler-sfc/src/template/transformAssetUrl.ts +++ b/packages/compiler-sfc/src/template/transformAssetUrl.ts @@ -211,6 +211,6 @@ function getImportsExpressionExp( } return context.hoist(finalExp) } else { - return createSimpleExpression(`''`, false, loc, ConstantTypes.CAN_STRINGIFY) + return createSimpleExpression(`''`, true, loc, ConstantTypes.CAN_STRINGIFY) } } diff --git a/packages/compiler-vapor/__tests__/compile.spec.ts b/packages/compiler-vapor/__tests__/compile.spec.ts index e26c04dc656..178021d13dd 100644 --- a/packages/compiler-vapor/__tests__/compile.spec.ts +++ b/packages/compiler-vapor/__tests__/compile.spec.ts @@ -1,6 +1,5 @@ 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. @@ -263,46 +262,4 @@ 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(``) - expect(code).matchSnapshot() - expect(code).contains(`import _imports_0 from '/foo.svg';`) - }) - - test(`multiple public assets`, () => { - const code = compileWithAssets( - ` - `, - ) - 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( - ` - `, - ) - expect(code).matchSnapshot() - expect(code).contains(`import _imports_0 from '/foo.svg';`) - expect(code).contains(`src=\\"base/bar.svg\\"`) - }) - }) }) diff --git a/packages/compiler-vapor/__tests__/transforms/__snapshots__/templateTransformAssetUrl.spec.ts.snap b/packages/compiler-vapor/__tests__/transforms/__snapshots__/templateTransformAssetUrl.spec.ts.snap new file mode 100644 index 00000000000..4edfd2ed174 --- /dev/null +++ b/packages/compiler-vapor/__tests__/transforms/__snapshots__/templateTransformAssetUrl.spec.ts.snap @@ -0,0 +1,134 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`compiler sfc: transform asset url > should allow for full base URLs, with paths 1`] = ` +"import { template as _template } from 'vue'; +const t0 = _template("", true) + +export function render(_ctx) { + const n0 = t0() + return n0 +}" +`; + +exports[`compiler sfc: transform asset url > should allow for full base URLs, without paths 1`] = ` +"import { template as _template } from 'vue'; +const t0 = _template("", true) + +export function render(_ctx) { + const n0 = t0() + return n0 +}" +`; + +exports[`compiler sfc: transform asset url > should allow for full base URLs, without port 1`] = ` +"import { template as _template } from 'vue'; +const t0 = _template("", true) + +export function render(_ctx) { + const n0 = t0() + return n0 +}" +`; + +exports[`compiler sfc: transform asset url > should allow for full base URLs, without protocol 1`] = ` +"import { template as _template } from 'vue'; +const t0 = _template("", true) + +export function render(_ctx) { + const n0 = t0() + return n0 +}" +`; + +exports[`compiler sfc: transform asset url > support uri fragment 1`] = ` +"import { template as _template } from 'vue'; +import _imports_0 from '@svg/file.svg'; +const t0 = _template("") + +export function render(_ctx) { + const n0 = t0() + const n1 = t0() + return [n0, n1] +}" +`; + +exports[`compiler sfc: transform asset url > support uri is empty 1`] = ` +"import { template as _template } from 'vue'; +const t0 = _template("", true) + +export function render(_ctx) { + const n0 = t0() + return n0 +}" +`; + +exports[`compiler sfc: transform asset url > transform assetUrls 1`] = ` +"import { template as _template } from 'vue'; +import _imports_0 from './logo.png'; +import _imports_1 from 'fixtures/logo.png'; +import _imports_2 from '/fixtures/logo.png'; +const t0 = _template("") +const t1 = _template("") +const t2 = _template("") +const t3 = _template("") +const t4 = _template("") +const t5 = _template("") + +export function render(_ctx) { + const n0 = t0() + const n1 = t1() + const n2 = t1() + const n3 = t2() + const n4 = t3() + const n5 = t4() + const n6 = t5() + return [n0, n1, n2, n3, n4, n5, n6] +}" +`; + +exports[`compiler sfc: transform asset url > transform with stringify 1`] = ` +"import { template as _template } from 'vue'; +import _imports_0 from './bar.png'; +import _imports_1 from '/bar.png'; +const t0 = _template("
", true) + +export function render(_ctx) { + const n0 = t0() + return n0 +}" +`; + +exports[`compiler sfc: transform asset url > with explicit base 1`] = ` +"import { template as _template } from 'vue'; +import _imports_0 from 'bar.png'; +import _imports_1 from '@theme/bar.png'; +const t0 = _template("") +const t1 = _template("") +const t2 = _template("") + +export function render(_ctx) { + const n0 = t0() + const n1 = t1() + const n2 = t1() + const n3 = t2() + return [n0, n1, n2, n3] +}" +`; + +exports[`compiler sfc: transform asset url > with includeAbsolute: true 1`] = ` +"import { template as _template } from 'vue'; +import _imports_0 from './bar.png'; +import _imports_1 from '/bar.png'; +const t0 = _template("") +const t1 = _template("") +const t2 = _template("") +const t3 = _template("") + +export function render(_ctx) { + const n0 = t0() + const n1 = t1() + const n2 = t2() + const n3 = t3() + return [n0, n1, n2, n3] +}" +`; diff --git a/packages/compiler-vapor/__tests__/transforms/templateTransformAssetUrl.spec.ts b/packages/compiler-vapor/__tests__/transforms/templateTransformAssetUrl.spec.ts new file mode 100644 index 00000000000..52e25a7d95f --- /dev/null +++ b/packages/compiler-vapor/__tests__/transforms/templateTransformAssetUrl.spec.ts @@ -0,0 +1,183 @@ +import type { TransformOptions } from '@vue/compiler-core' +import type { AssetURLOptions } from '../../../compiler-sfc/src/template/transformAssetUrl' +import { stringifyStatic } from '../../../compiler-dom/src/transforms/stringifyStatic' +import { compileTemplate } from '../../../compiler-sfc/src' + +function compileWithAssetUrls( + template: string, + options?: AssetURLOptions, + transformOptions?: TransformOptions, +) { + return compileTemplate({ + vapor: true, + id: 'test', + filename: 'test.vue', + source: template, + transformAssetUrls: { + includeAbsolute: true, + ...options, + }, + }) +} + +describe('compiler sfc: transform asset url', () => { + test('transform assetUrls', () => { + const result = compileWithAssetUrls(` + + + + + + + + `) + + expect(result.code).toMatchSnapshot() + }) + + /** + * vuejs/component-compiler-utils#22 Support uri fragment in transformed require + */ + test('support uri fragment', () => { + const result = compileWithAssetUrls( + '' + + '', + {}, + { + hoistStatic: true, + }, + ) + expect(result.code).toMatchSnapshot() + }) + + /** + * vuejs/component-compiler-utils#22 Support uri fragment in transformed require + */ + test('support uri is empty', () => { + const result = compileWithAssetUrls('') + + expect(result.code).toMatchSnapshot() + }) + + test('with explicit base', () => { + const { code } = compileWithAssetUrls( + `` + // -> /foo/bar.png + `` + // -> bar.png (untouched) + `` + // -> still converts to import + ``, // -> still converts to import + { + base: '/foo', + }, + ) + expect(code).toMatch(`import _imports_0 from 'bar.png'`) + expect(code).toMatch(`import _imports_1 from '@theme/bar.png'`) + expect(code).toMatchSnapshot() + }) + + test('with includeAbsolute: true', () => { + const { code } = compileWithAssetUrls( + `` + + `` + + `` + + ``, + { + includeAbsolute: true, + }, + ) + expect(code).toMatchSnapshot() + }) + + // vitejs/vite#298 + test('should not transform hash fragments', () => { + const { code } = compileWithAssetUrls( + ` + + + + + `, + ) + // should not remove it + expect(code).toMatch(`"xlink:href": "#myCircle"`) + }) + + test('should allow for full base URLs, with paths', () => { + const { code } = compileWithAssetUrls(``, { + base: 'http://localhost:3000/src/', + }) + + expect(code).toMatchSnapshot() + }) + + test('should allow for full base URLs, without paths', () => { + const { code } = compileWithAssetUrls(``, { + base: 'http://localhost:3000', + }) + + expect(code).toMatchSnapshot() + }) + + test('should allow for full base URLs, without port', () => { + const { code } = compileWithAssetUrls(``, { + base: 'http://localhost', + }) + + expect(code).toMatchSnapshot() + }) + + test('should allow for full base URLs, without protocol', () => { + const { code } = compileWithAssetUrls(``, { + base: '//localhost', + }) + + expect(code).toMatchSnapshot() + }) + + test('transform with stringify', () => { + const { code } = compileWithAssetUrls( + `
` + + `` + + `` + + `` + + `` + + `` + + `
`, + { + includeAbsolute: true, + }, + { + hoistStatic: true, + transformHoist: stringifyStatic, + }, + ) + expect(code).toMatchSnapshot() + }) + + test('transform with stringify with space in absolute filename', () => { + const { code } = compileWithAssetUrls( + `
`, + { + includeAbsolute: true, + }, + { + hoistStatic: true, + transformHoist: stringifyStatic, + }, + ) + expect(code).toContain(`import _imports_0 from '/foo bar.png'`) + }) + + test('transform with stringify with space in relative filename', () => { + const { code } = compileWithAssetUrls( + `
`, + { + includeAbsolute: true, + }, + { + hoistStatic: true, + transformHoist: stringifyStatic, + }, + ) + expect(code).toContain(`import _imports_0 from './foo bar.png'`) + }) +}) diff --git a/packages/compiler-vapor/__tests__/transforms/templateTransformSrcset.spec.ts b/packages/compiler-vapor/__tests__/transforms/templateTransformSrcset.spec.ts new file mode 100644 index 00000000000..5462caeab7d --- /dev/null +++ b/packages/compiler-vapor/__tests__/transforms/templateTransformSrcset.spec.ts @@ -0,0 +1,85 @@ +import type { TransformOptions } from '@vue/compiler-core' +import type { AssetURLOptions } from '../../../compiler-sfc/src/template/transformAssetUrl' +import { compileTemplate } from '../../../compiler-sfc/src/compileTemplate' +import { stringifyStatic } from '../../../compiler-dom/src/transforms/stringifyStatic' + +function compileWithSrcset( + template: string, + options?: AssetURLOptions, + transformOptions?: TransformOptions, +) { + return compileTemplate({ + vapor: true, + id: 'test', + filename: 'test.vue', + source: template, + transformAssetUrls: { + includeAbsolute: true, + ...options, + }, + }) +} + +const src = ` + + + + + + + + + + + + +` + +describe('compiler sfc: transform srcset', () => { + test('transform srcset', () => { + expect(compileWithSrcset(src).code).toMatchSnapshot() + }) + + test('transform srcset w/ base', () => { + expect( + compileWithSrcset(src, { + base: '/foo', + }).code, + ).toMatchSnapshot() + }) + + test('transform srcset w/ includeAbsolute: true', () => { + expect( + compileWithSrcset(src, { + includeAbsolute: true, + }).code, + ).toMatchSnapshot() + }) + + test('transform srcset w/ stringify', () => { + const code = compileWithSrcset( + `
${src}
`, + { + includeAbsolute: true, + }, + { + hoistStatic: true, + transformHoist: stringifyStatic, + }, + ).code + expect(code).toMatch(`_createStaticVNode`) + expect(code).toMatchSnapshot() + }) + + test('srcset w/ explicit base option', () => { + const code = compileWithSrcset( + ` + + + `, + { base: '/foo/' }, + { hoistStatic: true }, + ).code + expect(code).toMatchSnapshot() + }) +}) diff --git a/packages/compiler-vapor/src/generators/template.ts b/packages/compiler-vapor/src/generators/template.ts index 9e7b59cce90..f5b2b0af006 100644 --- a/packages/compiler-vapor/src/generators/template.ts +++ b/packages/compiler-vapor/src/generators/template.ts @@ -4,23 +4,21 @@ 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 escaped = escapeTemplate(template) - return `const t${i} = ${helper('template')}(\`${escaped}\`${i === rootIndex ? ', true' : ''})\n` - }) + .map( + (template, i) => + `const t${i} = ${helper('template')}(${ + JSON.stringify(template).replace( + /\${_imports_(\d+)}\$/g, + '"+_imports_$1+"', + ) // replace asset imports with string concatenation + }${i === rootIndex ? ', true' : ''})\n`, + ) .join('') } diff --git a/packages/compiler-vapor/src/transforms/transformElement.ts b/packages/compiler-vapor/src/transforms/transformElement.ts index 267f3b0079b..8e641207235 100644 --- a/packages/compiler-vapor/src/transforms/transformElement.ts +++ b/packages/compiler-vapor/src/transforms/transformElement.ts @@ -224,11 +224,12 @@ function transformNativeElement( for (const prop of propsResult[1]) { const { key, values } = prop const imports = getAssetImports(context) - if (imports.includes(values[0].content)) { - template += ` ${key.content}=""+${values[0].content}+""` + if (imports.some(imported => values[0].content.includes(imported))) { + 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}"` + const valueContent = values[0].content === "''" ? '' : values[0].content + template += `="${valueContent}"` } else { dynamicProps.push(key.content) context.registerEffect(