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-sfc/src/compileTemplate.ts b/packages/compiler-sfc/src/compileTemplate.ts index 29d1853d2d6..f299b5436c2 100644 --- a/packages/compiler-sfc/src/compileTemplate.ts +++ b/packages/compiler-sfc/src/compileTemplate.ts @@ -15,13 +15,10 @@ import { type AssetURLOptions, type AssetURLTagConfig, createAssetUrlTransformWithOptions, + defaultAssetUrlOptions, normalizeOptions, - transformAssetUrl, } from './template/transformAssetUrl' -import { - createSrcsetTransformWithOptions, - transformSrcset, -} from './template/transformSrcset' +import { createSrcsetTransformWithOptions } from './template/transformSrcset' import { generateCodeFrame, isObject } from '@vue/shared' import * as CompilerDOM from '@vue/compiler-dom' import * as CompilerVapor from '@vue/compiler-vapor' @@ -185,14 +182,17 @@ function doCompileTemplate({ const warnings: CompilerError[] = [] let nodeTransforms: NodeTransform[] = [] - if (isObject(transformAssetUrls)) { - const assetOptions = normalizeOptions(transformAssetUrls) + if (transformAssetUrls !== false) { + const assetOptions = isObject(transformAssetUrls) + ? normalizeOptions(transformAssetUrls) + : defaultAssetUrlOptions + + assetOptions.vapor = vapor + nodeTransforms = [ createAssetUrlTransformWithOptions(assetOptions), createSrcsetTransformWithOptions(assetOptions), ] - } else if (transformAssetUrls !== false) { - nodeTransforms = [transformAssetUrl, transformSrcset] } if (ssr && !ssrCssVars) { diff --git a/packages/compiler-sfc/src/template/transformAssetUrl.ts b/packages/compiler-sfc/src/template/transformAssetUrl.ts index 6291e21bbba..917b28e0a40 100644 --- a/packages/compiler-sfc/src/template/transformAssetUrl.ts +++ b/packages/compiler-sfc/src/template/transformAssetUrl.ts @@ -32,6 +32,7 @@ export interface AssetURLOptions { */ includeAbsolute?: boolean tags?: AssetURLTagConfig + vapor?: boolean } export const defaultAssetUrlOptions: Required = { @@ -44,6 +45,7 @@ export const defaultAssetUrlOptions: Required = { image: ['xlink:href', 'href'], use: ['xlink:href', 'href'], }, + vapor: false, } export const normalizeOptions = ( @@ -134,7 +136,13 @@ export const transformAssetUrl: NodeTransform = ( // otherwise, transform the url into an import. // this assumes a bundler will resolve the import into the correct // absolute url (e.g. webpack file-loader) - const exp = getImportsExpressionExp(url.path, url.hash, attr.loc, context) + const exp = getImportsExpressionExp( + url.path, + url.hash, + attr.loc, + context, + options.vapor, + ) node.props[index] = { type: NodeTypes.DIRECTIVE, name: 'bind', @@ -152,6 +160,7 @@ function getImportsExpressionExp( hash: string | null, loc: SourceLocation, context: TransformContext, + vapor = false, ): ExpressionNode { if (path) { let name: string @@ -211,6 +220,8 @@ function getImportsExpressionExp( } return context.hoist(finalExp) } else { - return createSimpleExpression(`''`, false, loc, ConstantTypes.CAN_STRINGIFY) + return vapor + ? createSimpleExpression(`''`, true, loc, ConstantTypes.CAN_STRINGIFY) + : createSimpleExpression(`''`, false, loc, ConstantTypes.CAN_STRINGIFY) } } diff --git a/packages/compiler-sfc/src/template/transformSrcset.ts b/packages/compiler-sfc/src/template/transformSrcset.ts index 8f00f86e3c3..acc3c06a925 100644 --- a/packages/compiler-sfc/src/template/transformSrcset.ts +++ b/packages/compiler-sfc/src/template/transformSrcset.ts @@ -1,5 +1,6 @@ import path from 'path' import { + type CompoundExpressionNode, ConstantTypes, type ExpressionNode, type NodeTransform, @@ -36,6 +37,24 @@ export const createSrcsetTransformWithOptions = ( (transformSrcset as Function)(node, context, options) } +export function flattenCompoundExpression( + compoundExpression: CompoundExpressionNode, +): string { + return compoundExpression.children + .map(child => { + if (typeof child === 'string') { + return child + } else if (typeof child === 'symbol') { + return child.description + } else if (child.type === NodeTypes.COMPOUND_EXPRESSION) { + return flattenCompoundExpression(child) + } else { + return child.content + } + }) + .join('') +} + export const transformSrcset: NodeTransform = ( node, context, @@ -47,7 +66,25 @@ export const transformSrcset: NodeTransform = ( if (attr.name === 'srcset' && attr.type === NodeTypes.ATTRIBUTE) { if (!attr.value) return const value = attr.value.content - if (!value) return + if (!value) { + // Handle empty srcset + if (options.vapor) { + node.props[index] = { + type: NodeTypes.DIRECTIVE, + name: 'bind', + arg: createSimpleExpression('srcset', true, attr.loc), + exp: createSimpleExpression( + `''`, + true, + attr.loc, + ConstantTypes.CAN_STRINGIFY, + ), + modifiers: [], + loc: attr.loc, + } + } + return + } const imageCandidates: ImageCandidate[] = value.split(',').map(s => { // The attribute value arrives here with all whitespace, except // normal spaces, represented by escape sequences @@ -151,10 +188,20 @@ export const transformSrcset: NodeTransform = ( } }) - let exp: ExpressionNode = compoundExpression - if (context.hoistStatic) { - exp = context.hoist(compoundExpression) - exp.constType = ConstantTypes.CAN_STRINGIFY + let exp: ExpressionNode + if (options.vapor) { + exp = createSimpleExpression( + flattenCompoundExpression(compoundExpression), + false, + attr.loc, + ConstantTypes.CAN_STRINGIFY, + ) + } else { + exp = compoundExpression + if (context.hoistStatic) { + exp = context.hoist(compoundExpression) + exp.constType = ConstantTypes.CAN_STRINGIFY + } } node.props[index] = { 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..625fe726bde --- /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/__snapshots__/templateTransformSrcset.spec.ts.snap b/packages/compiler-vapor/__tests__/transforms/__snapshots__/templateTransformSrcset.spec.ts.snap new file mode 100644 index 00000000000..370a8f13809 --- /dev/null +++ b/packages/compiler-vapor/__tests__/transforms/__snapshots__/templateTransformSrcset.spec.ts.snap @@ -0,0 +1,126 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`compiler sfc: transform srcset > srcset w/ explicit base option 1`] = ` +"import { template as _template } from 'vue'; +import _imports_0 from '@/logo.png'; +import _imports_1 from '/foo/logo.png'; +const t0 = _template("") +const t1 = _template("") + +export function render(_ctx) { + const n0 = t0() + const n1 = t1() + return [n0, n1] +}" +`; + +exports[`compiler sfc: transform srcset > transform srcset 1`] = ` +"import { template as _template } from 'vue'; +import _imports_0 from './logo.png'; +import _imports_1 from '/logo.png'; +const t0 = _template("") +const t1 = _template("") +const t2 = _template("") +const t3 = _template("") +const t4 = _template("") +const t5 = _template("") +const t6 = _template("") +const t7 = _template("") +const t8 = _template("") +const t9 = _template("") +const t10 = _template("") + +export function render(_ctx) { + const n0 = t0() + const n1 = t1() + const n2 = t2() + const n3 = t2() + const n4 = t3() + const n5 = t4() + const n6 = t5() + const n7 = t6() + const n8 = t7() + const n9 = t8() + const n10 = t9() + const n11 = t10() + return [n0, n1, n2, n3, n4, n5, n6, n7, n8, n9, n10, n11] +}" +`; + +exports[`compiler sfc: transform srcset > transform srcset w/ base 1`] = ` +"import { template as _template } from 'vue'; +import _imports_0 from '/logo.png'; +import _imports_1 from '/foo/logo.png'; +const t0 = _template("") +const t1 = _template("") +const t2 = _template("") +const t3 = _template("") +const t4 = _template("") +const t5 = _template("") +const t6 = _template("") +const t7 = _template("") +const t8 = _template("") +const t9 = _template("") +const t10 = _template("") + +export function render(_ctx) { + const n0 = t0() + const n1 = t1() + const n2 = t2() + const n3 = t2() + const n4 = t3() + const n5 = t4() + const n6 = t5() + const n7 = t6() + const n8 = t7() + const n9 = t8() + const n10 = t9() + const n11 = t10() + return [n0, n1, n2, n3, n4, n5, n6, n7, n8, n9, n10, n11] +}" +`; + +exports[`compiler sfc: transform srcset > transform srcset w/ includeAbsolute: true 1`] = ` +"import { template as _template } from 'vue'; +import _imports_0 from './logo.png'; +import _imports_1 from '/logo.png'; +const t0 = _template("") +const t1 = _template("") +const t2 = _template("") +const t3 = _template("") +const t4 = _template("") +const t5 = _template("") +const t6 = _template("") +const t7 = _template("") +const t8 = _template("") +const t9 = _template("") +const t10 = _template("") + +export function render(_ctx) { + const n0 = t0() + const n1 = t1() + const n2 = t2() + const n3 = t2() + const n4 = t3() + const n5 = t4() + const n6 = t5() + const n7 = t6() + const n8 = t7() + const n9 = t8() + const n10 = t9() + const n11 = t10() + return [n0, n1, n2, n3, n4, n5, n6, n7, n8, n9, n10, n11] +}" +`; + +exports[`compiler sfc: transform srcset > transform srcset w/ stringify 1`] = ` +"import { template as _template } from 'vue'; +import _imports_0 from './logo.png'; +import _imports_1 from '/logo.png'; +const t0 = _template("
", true) + +export function render(_ctx) { + const n0 = t0() + return n0 +}" +`; 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..225d18aded2 --- /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\\"`) // compiled to template string, not object, so remove quotes + }) + + 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..7f249e5c7ae --- /dev/null +++ b/packages/compiler-vapor/__tests__/transforms/templateTransformSrcset.spec.ts @@ -0,0 +1,84 @@ +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).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/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 c70d4a56613..2a95ddc3d23 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,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]] @@ -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) } @@ -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, diff --git a/packages/compiler-vapor/src/generators/template.ts b/packages/compiler-vapor/src/generators/template.ts index 5a066b09e9a..811a77604f5 100644 --- a/packages/compiler-vapor/src/generators/template.ts +++ b/packages/compiler-vapor/src/generators/template.ts @@ -12,9 +12,12 @@ export function genTemplates( return templates .map( (template, i) => - `const t${i} = ${helper('template')}(${JSON.stringify( - template, - )}${i === rootIndex ? ', true' : ''})\n`, + `const t${i} = ${helper('template')}(${ + JSON.stringify(template).replace( + /\$\{(.*?)\}\$/g, + (_, expr) => `" + ${expr} + "`, + ) // replace asset imports with string concatenation + }${i === rootIndex ? ', true' : ''})\n`, ) .join('') } diff --git a/packages/compiler-vapor/src/transform.ts b/packages/compiler-vapor/src/transform.ts index 946c89b734a..7673b5c3b6d 100644 --- a/packages/compiler-vapor/src/transform.ts +++ b/packages/compiler-vapor/src/transform.ts @@ -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, @@ -60,7 +61,6 @@ export type StructuralDirectiveTransform = ( ) => void | (() => void) export type TransformOptions = HackOptions - export class TransformContext { selfName: string | null = null parent: TransformContext | null = null @@ -75,6 +75,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 +226,8 @@ export function transform( transformNode(context) + ir.node.imports = context.imports + return ir } diff --git a/packages/compiler-vapor/src/transforms/transformElement.ts b/packages/compiler-vapor/src/transforms/transformElement.ts index 05153e729af..d3a2dc179b9 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,9 +223,16 @@ 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.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}"` + if (values[0].content) { + const valueContent = + values[0].content === "''" ? '' : values[0].content + template += `="${valueContent}"` + } } else { dynamicProps.push(key.content) context.registerEffect( 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, + ) +}