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
4 changes: 4 additions & 0 deletions packages/compiler-core/src/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,10 @@ export interface TransformOptions
* correctly, e.g. #6938, #7138
*/
hmr?: boolean
/**
* Indicates whether the template is a vapor component.
*/
vapor?: boolean
}

export interface CodegenOptions extends SharedTransformCodegenOptions {
Expand Down
2 changes: 2 additions & 0 deletions packages/compiler-core/src/transform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,7 @@ export function createTransformContext(
onError = defaultOnError,
onWarn = defaultOnWarn,
compatConfig,
vapor = false,
}: TransformOptions,
): TransformContext {
const context: TransformContext = {
Expand Down Expand Up @@ -180,6 +181,7 @@ export function createTransformContext(
onError,
onWarn,
compatConfig,
vapor,

// state
root,
Expand Down
17 changes: 8 additions & 9 deletions packages/compiler-sfc/src/compileTemplate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -185,14 +182,15 @@ 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

nodeTransforms = [
createAssetUrlTransformWithOptions(assetOptions),
createSrcsetTransformWithOptions(assetOptions),
]
} else if (transformAssetUrls !== false) {
nodeTransforms = [transformAssetUrl, transformSrcset]
}

if (ssr && !ssrCssVars) {
Expand Down Expand Up @@ -254,6 +252,7 @@ function doCompileTemplate({
sourceMap: true,
...compilerOptions,
hmr: !isProd,
vapor,
nodeTransforms: nodeTransforms.concat(
compilerOptions.nodeTransforms || [],
),
Expand Down
14 changes: 12 additions & 2 deletions packages/compiler-sfc/src/template/transformAssetUrl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
type TransformContext,
createSimpleExpression,
} from '@vue/compiler-core'
import { isVapor } from './utils'
import {
isDataUrl,
isExternalUrl,
Expand Down Expand Up @@ -134,7 +135,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,
isVapor(context),
)
node.props[index] = {
type: NodeTypes.DIRECTIVE,
name: 'bind',
Expand All @@ -152,6 +159,7 @@ function getImportsExpressionExp(
hash: string | null,
loc: SourceLocation,
context: TransformContext,
vapor = false,
): ExpressionNode {
if (path) {
let name: string
Expand Down Expand Up @@ -211,6 +219,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)
}
}
59 changes: 54 additions & 5 deletions packages/compiler-sfc/src/template/transformSrcset.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import path from 'path'
import {
type CompoundExpressionNode,
ConstantTypes,
type ExpressionNode,
type NodeTransform,
Expand All @@ -18,6 +19,7 @@ import {
type AssetURLOptions,
defaultAssetUrlOptions,
} from './transformAssetUrl'
import { isVapor } from './utils'

const srcsetTags = ['img', 'source']

Expand All @@ -36,18 +38,55 @@ 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,
options: Required<AssetURLOptions> = defaultAssetUrlOptions,
) => {
if (node.type === NodeTypes.ELEMENT) {
if (srcsetTags.includes(node.tag) && node.props.length) {
const vapor = isVapor(context)
node.props.forEach((attr, index) => {
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 (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
Expand Down Expand Up @@ -152,10 +191,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 (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] = {
Expand Down
10 changes: 10 additions & 0 deletions packages/compiler-sfc/src/template/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import type { TransformContext as CoreTransformContext } from '@vue/compiler-core'
import type { TransformContext as VaporTransformContext } from '@vue/compiler-vapor'

export function isVapor(
context: CoreTransformContext | VaporTransformContext,
): boolean {
return 'vapor' in context
? (context as CoreTransformContext).vapor
: (context as VaporTransformContext).options.vapor
}
Original file line number Diff line number Diff line change
@@ -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("<img src=\\"http://localhost:3000/src/logo.png\\">", 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("<img src=\\"http://localhost:3000/logo.png\\">", 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("<img src=\\"http://localhost/logo.png\\">", 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("<img src=\\"//localhost/logo.png\\">", 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("<use href=\\"" + _imports_0 + '#fragment' + "\\"></use>")

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("<use href=\\"\\"></use>", 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("<img src=\\"" + _imports_0 + "\\">")
const t1 = _template("<img src=\\"" + _imports_1 + "\\">")
const t2 = _template("<img src=\\"http://example.com/fixtures/logo.png\\">")
const t3 = _template("<img src=\\"//example.com/fixtures/logo.png\\">")
const t4 = _template("<img src=\\"" + _imports_2 + "\\">")
const t5 = _template("<img src=\\"data:image/png;base64,i\\">")

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("<div><img src=\\"" + _imports_0 + "\\"><img src=\\"" + _imports_1 + "\\"><img src=\\"https://foo.bar/baz.png\\"><img src=\\"//foo.bar/baz.png\\"><img src=\\"" + _imports_0 + "\\"></div>", 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("<img src=\\"/foo/bar.png\\">")
const t1 = _template("<img src=\\"" + _imports_0 + "\\">")
const t2 = _template("<img src=\\"" + _imports_1 + "\\">")

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("<img src=\\"" + _imports_0 + "\\">")
const t1 = _template("<img src=\\"" + _imports_1 + "\\">")
const t2 = _template("<img src=\\"https://foo.bar/baz.png\\">")
const t3 = _template("<img src=\\"//foo.bar/baz.png\\">")

export function render(_ctx) {
const n0 = t0()
const n1 = t1()
const n2 = t2()
const n3 = t3()
return [n0, n1, n2, n3]
}"
`;
Loading