Skip to content

Commit 36972c2

Browse files
committed
feat(compiler-sfc): add transformAssetUrlsBase option
1 parent 71a942b commit 36972c2

File tree

6 files changed

+145
-41
lines changed

6 files changed

+145
-41
lines changed

packages/compiler-sfc/__tests__/__snapshots__/templateTransformAssetUrl.spec.ts.snap

+13
Original file line numberDiff line numberDiff line change
@@ -36,3 +36,16 @@ export function render(_ctx, _cache) {
3636
], 64 /* STABLE_FRAGMENT */))
3737
}"
3838
`;
39+
40+
exports[`compiler sfc: transform asset url with explicit base 1`] = `
41+
"import { createVNode as _createVNode, Fragment as _Fragment, openBlock as _openBlock, createBlock as _createBlock } from \\"vue\\"
42+
43+
export function render(_ctx, _cache) {
44+
return (_openBlock(), _createBlock(_Fragment, null, [
45+
_createVNode(\\"img\\", { src: \\"/foo/bar.png\\" }),
46+
_createVNode(\\"img\\", { src: \\"/foo/bar.png\\" }),
47+
_createVNode(\\"img\\", { src: \\"bar.png\\" }),
48+
_createVNode(\\"img\\", { src: \\"@theme/bar.png\\" })
49+
], 64 /* STABLE_FRAGMENT */))
50+
}"
51+
`;

packages/compiler-sfc/__tests__/templateTransformAssetUrl.spec.ts

+26-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
import { generate, baseParse, transform } from '@vue/compiler-core'
2-
import { transformAssetUrl } from '../src/templateTransformAssetUrl'
2+
import {
3+
transformAssetUrl,
4+
createAssetUrlTransformWithOptions
5+
} from '../src/templateTransformAssetUrl'
36
import { transformElement } from '../../compiler-core/src/transforms/transformElement'
47
import { transformBind } from '../../compiler-core/src/transforms/vBind'
58

@@ -46,4 +49,26 @@ describe('compiler sfc: transform asset url', () => {
4649

4750
expect(result.code).toMatchSnapshot()
4851
})
52+
53+
test('with explicit base', () => {
54+
const ast = baseParse(
55+
`<img src="./bar.png"></img>` + // -> /foo/bar.png
56+
`<img src="~bar.png"></img>` + // -> /foo/bar.png
57+
`<img src="bar.png"></img>` + // -> bar.png (untouched)
58+
`<img src="@theme/bar.png"></img>` // -> @theme/bar.png (untouched)
59+
)
60+
transform(ast, {
61+
nodeTransforms: [
62+
createAssetUrlTransformWithOptions({
63+
base: '/foo'
64+
}),
65+
transformElement
66+
],
67+
directiveTransforms: {
68+
bind: transformBind
69+
}
70+
})
71+
const { code } = generate(ast, { mode: 'module' })
72+
expect(code).toMatchSnapshot()
73+
})
4974
})

packages/compiler-sfc/src/compileTemplate.ts

+29-8
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,23 @@ export interface SFCTemplateCompileOptions {
4040
compilerOptions?: CompilerOptions
4141
preprocessLang?: string
4242
preprocessOptions?: any
43+
/**
44+
* In some cases, compiler-sfc may not be inside the project root (e.g. when
45+
* linked or globally installed). In such cases a custom `require` can be
46+
* passed to correctly resolve the preprocessors.
47+
*/
4348
preprocessCustomRequire?: (id: string) => any
49+
/**
50+
* Configure what tags/attributes to trasnform into relative asset url imports
51+
* in the form of `{ [tag: string]: string[] }`, or disable the transform with
52+
* `false`.
53+
*/
4454
transformAssetUrls?: AssetURLOptions | boolean
55+
/**
56+
* If base is provided, instead of transforming relative asset urls into
57+
* imports, they will be directly rewritten to absolute urls.
58+
*/
59+
transformAssetUrlsBase?: string
4560
}
4661

4762
function preprocess(
@@ -129,18 +144,24 @@ function doCompileTemplate({
129144
ssr = false,
130145
compiler = ssr ? (CompilerSSR as TemplateCompiler) : CompilerDOM,
131146
compilerOptions = {},
132-
transformAssetUrls
147+
transformAssetUrls,
148+
transformAssetUrlsBase
133149
}: SFCTemplateCompileOptions): SFCTemplateCompileResults {
134150
const errors: CompilerError[] = []
135151

136152
let nodeTransforms: NodeTransform[] = []
137-
if (isObject(transformAssetUrls)) {
138-
nodeTransforms = [
139-
createAssetUrlTransformWithOptions(transformAssetUrls),
140-
transformSrcset
141-
]
142-
} else if (transformAssetUrls !== false) {
143-
nodeTransforms = [transformAssetUrl, transformSrcset]
153+
if (transformAssetUrls !== false) {
154+
if (transformAssetUrlsBase || isObject(transformAssetUrls)) {
155+
nodeTransforms = [
156+
createAssetUrlTransformWithOptions({
157+
base: transformAssetUrlsBase,
158+
tags: isObject(transformAssetUrls) ? transformAssetUrls : undefined
159+
}),
160+
transformSrcset
161+
]
162+
} else {
163+
nodeTransforms = [transformAssetUrl, transformSrcset]
164+
}
144165
}
145166

146167
let { code, map } = compiler.compile(source, {

packages/compiler-sfc/src/templateTransformAssetUrl.ts

+73-29
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import path from 'path'
12
import {
23
createSimpleExpression,
34
ExpressionNode,
@@ -12,54 +13,97 @@ export interface AssetURLOptions {
1213
[name: string]: string[]
1314
}
1415

15-
const defaultOptions: AssetURLOptions = {
16-
video: ['src', 'poster'],
17-
source: ['src'],
18-
img: ['src'],
19-
image: ['xlink:href', 'href'],
20-
use: ['xlink:href', 'href']
16+
export interface NormlaizedAssetURLOptions {
17+
base?: string | null
18+
tags?: AssetURLOptions
19+
}
20+
21+
const defaultAssetUrlOptions: Required<NormlaizedAssetURLOptions> = {
22+
base: null,
23+
tags: {
24+
video: ['src', 'poster'],
25+
source: ['src'],
26+
img: ['src'],
27+
image: ['xlink:href', 'href'],
28+
use: ['xlink:href', 'href']
29+
}
2130
}
2231

2332
export const createAssetUrlTransformWithOptions = (
24-
options: AssetURLOptions
33+
options: NormlaizedAssetURLOptions
2534
): NodeTransform => {
2635
const mergedOptions = {
27-
...defaultOptions,
36+
...defaultAssetUrlOptions,
2837
...options
2938
}
3039
return (node, context) =>
3140
(transformAssetUrl as Function)(node, context, mergedOptions)
3241
}
3342

43+
/**
44+
* A `@vue/compiler-core` plugin that transforms relative asset urls into
45+
* either imports or absolute urls.
46+
*
47+
* ``` js
48+
* // Before
49+
* createVNode('img', { src: './logo.png' })
50+
*
51+
* // After
52+
* import _imports_0 from './logo.png'
53+
* createVNode('img', { src: _imports_0 })
54+
* ```
55+
*/
3456
export const transformAssetUrl: NodeTransform = (
3557
node,
3658
context,
37-
options: AssetURLOptions = defaultOptions
59+
options: NormlaizedAssetURLOptions = defaultAssetUrlOptions
3860
) => {
3961
if (node.type === NodeTypes.ELEMENT) {
40-
for (const tag in options) {
62+
const tags = options.tags || defaultAssetUrlOptions.tags
63+
for (const tag in tags) {
4164
if ((tag === '*' || node.tag === tag) && node.props.length) {
42-
const attributes = options[tag]
43-
attributes.forEach(item => {
65+
const attributes = tags[tag]
66+
attributes.forEach(name => {
4467
node.props.forEach((attr, index) => {
45-
if (attr.type !== NodeTypes.ATTRIBUTE) return
46-
if (attr.name !== item) return
47-
if (!attr.value) return
48-
if (!isRelativeUrl(attr.value.content)) return
68+
if (
69+
attr.type !== NodeTypes.ATTRIBUTE ||
70+
attr.name !== name ||
71+
!attr.value ||
72+
!isRelativeUrl(attr.value.content)
73+
) {
74+
return
75+
}
4976
const url = parseUrl(attr.value.content)
50-
const exp = getImportsExpressionExp(
51-
url.path,
52-
url.hash,
53-
attr.loc,
54-
context
55-
)
56-
node.props[index] = {
57-
type: NodeTypes.DIRECTIVE,
58-
name: 'bind',
59-
arg: createSimpleExpression(item, true, attr.loc),
60-
exp,
61-
modifiers: [],
62-
loc: attr.loc
77+
if (options.base) {
78+
// explicit base - directly rewrite the url into absolute url
79+
// does not apply to url that starts with `@` since they are
80+
// aliases
81+
if (attr.value.content[0] !== '@') {
82+
// when packaged in the browser, path will be using the posix-
83+
// only version provided by rollup-plugin-node-builtins.
84+
attr.value.content = (path.posix || path).join(
85+
options.base,
86+
url.path + (url.hash || '')
87+
)
88+
}
89+
} else {
90+
// otherwise, transform the url into an import.
91+
// this assumes a bundler will resolve the import into the correct
92+
// absolute url (e.g. webpack file-loader)
93+
const exp = getImportsExpressionExp(
94+
url.path,
95+
url.hash,
96+
attr.loc,
97+
context
98+
)
99+
node.props[index] = {
100+
type: NodeTypes.DIRECTIVE,
101+
name: 'bind',
102+
arg: createSimpleExpression(name, true, attr.loc),
103+
exp,
104+
modifiers: [],
105+
loc: attr.loc
106+
}
63107
}
64108
})
65109
})

packages/compiler-sfc/src/templateUtils.ts

+3-2
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,9 @@ export function isRelativeUrl(url: string): boolean {
66
return firstChar === '.' || firstChar === '~' || firstChar === '@'
77
}
88

9-
// We need an extra transform context API for injecting arbitrary import
10-
// statements.
9+
/**
10+
* Parses string url into URL object.
11+
*/
1112
export function parseUrl(url: string): UrlWithStringQuery {
1213
const firstChar = url.charAt(0)
1314
if (firstChar === '~') {

rollup.config.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ function createConfig(format, output, plugins = []) {
129129
[
130130
...Object.keys(pkg.dependencies || {}),
131131
...Object.keys(pkg.peerDependencies || {}),
132-
'url' // for @vue/compiler-sfc
132+
...['path', 'url'] // for @vue/compiler-sfc
133133
]
134134

135135
// the browser builds of @vue/compiler-sfc requires postcss to be available

0 commit comments

Comments
 (0)