diff --git a/packages/compiler-sfc/__tests__/__snapshots__/templateTransformAssetUrl.spec.ts.snap b/packages/compiler-sfc/__tests__/__snapshots__/templateTransformAssetUrl.spec.ts.snap
index ddf319427..e997cd09c 100644
--- a/packages/compiler-sfc/__tests__/__snapshots__/templateTransformAssetUrl.spec.ts.snap
+++ b/packages/compiler-sfc/__tests__/__snapshots__/templateTransformAssetUrl.spec.ts.snap
@@ -36,3 +36,16 @@ export function render(_ctx, _cache) {
], 64 /* STABLE_FRAGMENT */))
}"
`;
+
+exports[`compiler sfc: transform asset url with explicit base 1`] = `
+"import { createVNode as _createVNode, Fragment as _Fragment, openBlock as _openBlock, createBlock as _createBlock } from \\"vue\\"
+
+export function render(_ctx, _cache) {
+ return (_openBlock(), _createBlock(_Fragment, null, [
+ _createVNode(\\"img\\", { src: \\"/foo/bar.png\\" }),
+ _createVNode(\\"img\\", { src: \\"/foo/bar.png\\" }),
+ _createVNode(\\"img\\", { src: \\"bar.png\\" }),
+ _createVNode(\\"img\\", { src: \\"@theme/bar.png\\" })
+ ], 64 /* STABLE_FRAGMENT */))
+}"
+`;
diff --git a/packages/compiler-sfc/__tests__/templateTransformAssetUrl.spec.ts b/packages/compiler-sfc/__tests__/templateTransformAssetUrl.spec.ts
index 40531d780..64a94c6a6 100644
--- a/packages/compiler-sfc/__tests__/templateTransformAssetUrl.spec.ts
+++ b/packages/compiler-sfc/__tests__/templateTransformAssetUrl.spec.ts
@@ -1,5 +1,8 @@
import { generate, baseParse, transform } from '@vue/compiler-core'
-import { transformAssetUrl } from '../src/templateTransformAssetUrl'
+import {
+ transformAssetUrl,
+ createAssetUrlTransformWithOptions
+} from '../src/templateTransformAssetUrl'
import { transformElement } from '../../compiler-core/src/transforms/transformElement'
import { transformBind } from '../../compiler-core/src/transforms/vBind'
@@ -46,4 +49,26 @@ describe('compiler sfc: transform asset url', () => {
expect(result.code).toMatchSnapshot()
})
+
+ test('with explicit base', () => {
+ const ast = baseParse(
+ `
` + // -> /foo/bar.png
+ `
` + // -> /foo/bar.png
+ `
` + // -> bar.png (untouched)
+ `
` // -> @theme/bar.png (untouched)
+ )
+ transform(ast, {
+ nodeTransforms: [
+ createAssetUrlTransformWithOptions({
+ base: '/foo'
+ }),
+ transformElement
+ ],
+ directiveTransforms: {
+ bind: transformBind
+ }
+ })
+ const { code } = generate(ast, { mode: 'module' })
+ expect(code).toMatchSnapshot()
+ })
})
diff --git a/packages/compiler-sfc/src/compileTemplate.ts b/packages/compiler-sfc/src/compileTemplate.ts
index 2c50c7f3e..df0051b00 100644
--- a/packages/compiler-sfc/src/compileTemplate.ts
+++ b/packages/compiler-sfc/src/compileTemplate.ts
@@ -40,8 +40,23 @@ export interface SFCTemplateCompileOptions {
compilerOptions?: CompilerOptions
preprocessLang?: string
preprocessOptions?: any
+ /**
+ * In some cases, compiler-sfc may not be inside the project root (e.g. when
+ * linked or globally installed). In such cases a custom `require` can be
+ * passed to correctly resolve the preprocessors.
+ */
preprocessCustomRequire?: (id: string) => any
+ /**
+ * Configure what tags/attributes to trasnform into relative asset url imports
+ * in the form of `{ [tag: string]: string[] }`, or disable the transform with
+ * `false`.
+ */
transformAssetUrls?: AssetURLOptions | boolean
+ /**
+ * If base is provided, instead of transforming relative asset urls into
+ * imports, they will be directly rewritten to absolute urls.
+ */
+ transformAssetUrlsBase?: string
}
function preprocess(
@@ -129,18 +144,24 @@ function doCompileTemplate({
ssr = false,
compiler = ssr ? (CompilerSSR as TemplateCompiler) : CompilerDOM,
compilerOptions = {},
- transformAssetUrls
+ transformAssetUrls,
+ transformAssetUrlsBase
}: SFCTemplateCompileOptions): SFCTemplateCompileResults {
const errors: CompilerError[] = []
let nodeTransforms: NodeTransform[] = []
- if (isObject(transformAssetUrls)) {
- nodeTransforms = [
- createAssetUrlTransformWithOptions(transformAssetUrls),
- transformSrcset
- ]
- } else if (transformAssetUrls !== false) {
- nodeTransforms = [transformAssetUrl, transformSrcset]
+ if (transformAssetUrls !== false) {
+ if (transformAssetUrlsBase || isObject(transformAssetUrls)) {
+ nodeTransforms = [
+ createAssetUrlTransformWithOptions({
+ base: transformAssetUrlsBase,
+ tags: isObject(transformAssetUrls) ? transformAssetUrls : undefined
+ }),
+ transformSrcset
+ ]
+ } else {
+ nodeTransforms = [transformAssetUrl, transformSrcset]
+ }
}
let { code, map } = compiler.compile(source, {
diff --git a/packages/compiler-sfc/src/templateTransformAssetUrl.ts b/packages/compiler-sfc/src/templateTransformAssetUrl.ts
index b981bf670..44ebe36af 100644
--- a/packages/compiler-sfc/src/templateTransformAssetUrl.ts
+++ b/packages/compiler-sfc/src/templateTransformAssetUrl.ts
@@ -1,3 +1,4 @@
+import path from 'path'
import {
createSimpleExpression,
ExpressionNode,
@@ -12,54 +13,97 @@ export interface AssetURLOptions {
[name: string]: string[]
}
-const defaultOptions: AssetURLOptions = {
- video: ['src', 'poster'],
- source: ['src'],
- img: ['src'],
- image: ['xlink:href', 'href'],
- use: ['xlink:href', 'href']
+export interface NormlaizedAssetURLOptions {
+ base?: string | null
+ tags?: AssetURLOptions
+}
+
+const defaultAssetUrlOptions: Required = {
+ base: null,
+ tags: {
+ video: ['src', 'poster'],
+ source: ['src'],
+ img: ['src'],
+ image: ['xlink:href', 'href'],
+ use: ['xlink:href', 'href']
+ }
}
export const createAssetUrlTransformWithOptions = (
- options: AssetURLOptions
+ options: NormlaizedAssetURLOptions
): NodeTransform => {
const mergedOptions = {
- ...defaultOptions,
+ ...defaultAssetUrlOptions,
...options
}
return (node, context) =>
(transformAssetUrl as Function)(node, context, mergedOptions)
}
+/**
+ * A `@vue/compiler-core` plugin that transforms relative asset urls into
+ * either imports or absolute urls.
+ *
+ * ``` js
+ * // Before
+ * createVNode('img', { src: './logo.png' })
+ *
+ * // After
+ * import _imports_0 from './logo.png'
+ * createVNode('img', { src: _imports_0 })
+ * ```
+ */
export const transformAssetUrl: NodeTransform = (
node,
context,
- options: AssetURLOptions = defaultOptions
+ options: NormlaizedAssetURLOptions = defaultAssetUrlOptions
) => {
if (node.type === NodeTypes.ELEMENT) {
- for (const tag in options) {
+ const tags = options.tags || defaultAssetUrlOptions.tags
+ for (const tag in tags) {
if ((tag === '*' || node.tag === tag) && node.props.length) {
- const attributes = options[tag]
- attributes.forEach(item => {
+ const attributes = tags[tag]
+ attributes.forEach(name => {
node.props.forEach((attr, index) => {
- if (attr.type !== NodeTypes.ATTRIBUTE) return
- if (attr.name !== item) return
- if (!attr.value) return
- if (!isRelativeUrl(attr.value.content)) return
+ if (
+ attr.type !== NodeTypes.ATTRIBUTE ||
+ attr.name !== name ||
+ !attr.value ||
+ !isRelativeUrl(attr.value.content)
+ ) {
+ return
+ }
const url = parseUrl(attr.value.content)
- const exp = getImportsExpressionExp(
- url.path,
- url.hash,
- attr.loc,
- context
- )
- node.props[index] = {
- type: NodeTypes.DIRECTIVE,
- name: 'bind',
- arg: createSimpleExpression(item, true, attr.loc),
- exp,
- modifiers: [],
- loc: attr.loc
+ if (options.base) {
+ // explicit base - directly rewrite the url into absolute url
+ // does not apply to url that starts with `@` since they are
+ // aliases
+ if (attr.value.content[0] !== '@') {
+ // when packaged in the browser, path will be using the posix-
+ // only version provided by rollup-plugin-node-builtins.
+ attr.value.content = (path.posix || path).join(
+ options.base,
+ url.path + (url.hash || '')
+ )
+ }
+ } else {
+ // 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
+ )
+ node.props[index] = {
+ type: NodeTypes.DIRECTIVE,
+ name: 'bind',
+ arg: createSimpleExpression(name, true, attr.loc),
+ exp,
+ modifiers: [],
+ loc: attr.loc
+ }
}
})
})
diff --git a/packages/compiler-sfc/src/templateUtils.ts b/packages/compiler-sfc/src/templateUtils.ts
index 1040571af..70a5b26fa 100644
--- a/packages/compiler-sfc/src/templateUtils.ts
+++ b/packages/compiler-sfc/src/templateUtils.ts
@@ -6,8 +6,9 @@ export function isRelativeUrl(url: string): boolean {
return firstChar === '.' || firstChar === '~' || firstChar === '@'
}
-// We need an extra transform context API for injecting arbitrary import
-// statements.
+/**
+ * Parses string url into URL object.
+ */
export function parseUrl(url: string): UrlWithStringQuery {
const firstChar = url.charAt(0)
if (firstChar === '~') {
diff --git a/rollup.config.js b/rollup.config.js
index e282f777e..ef8d7af16 100644
--- a/rollup.config.js
+++ b/rollup.config.js
@@ -129,7 +129,7 @@ function createConfig(format, output, plugins = []) {
[
...Object.keys(pkg.dependencies || {}),
...Object.keys(pkg.peerDependencies || {}),
- 'url' // for @vue/compiler-sfc
+ ...['path', 'url'] // for @vue/compiler-sfc
]
// the browser builds of @vue/compiler-sfc requires postcss to be available