feat(compiler-sfc): add transformAssetUrlsBase option
This commit is contained in:
parent
71a942b25a
commit
36972c20b5
@ -36,3 +36,16 @@ export function render(_ctx, _cache) {
|
|||||||
], 64 /* STABLE_FRAGMENT */))
|
], 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 */))
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
@ -1,5 +1,8 @@
|
|||||||
import { generate, baseParse, transform } from '@vue/compiler-core'
|
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 { transformElement } from '../../compiler-core/src/transforms/transformElement'
|
||||||
import { transformBind } from '../../compiler-core/src/transforms/vBind'
|
import { transformBind } from '../../compiler-core/src/transforms/vBind'
|
||||||
|
|
||||||
@ -46,4 +49,26 @@ describe('compiler sfc: transform asset url', () => {
|
|||||||
|
|
||||||
expect(result.code).toMatchSnapshot()
|
expect(result.code).toMatchSnapshot()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('with explicit base', () => {
|
||||||
|
const ast = baseParse(
|
||||||
|
`<img src="./bar.png"></img>` + // -> /foo/bar.png
|
||||||
|
`<img src="~bar.png"></img>` + // -> /foo/bar.png
|
||||||
|
`<img src="bar.png"></img>` + // -> bar.png (untouched)
|
||||||
|
`<img src="@theme/bar.png"></img>` // -> @theme/bar.png (untouched)
|
||||||
|
)
|
||||||
|
transform(ast, {
|
||||||
|
nodeTransforms: [
|
||||||
|
createAssetUrlTransformWithOptions({
|
||||||
|
base: '/foo'
|
||||||
|
}),
|
||||||
|
transformElement
|
||||||
|
],
|
||||||
|
directiveTransforms: {
|
||||||
|
bind: transformBind
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const { code } = generate(ast, { mode: 'module' })
|
||||||
|
expect(code).toMatchSnapshot()
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
@ -40,8 +40,23 @@ export interface SFCTemplateCompileOptions {
|
|||||||
compilerOptions?: CompilerOptions
|
compilerOptions?: CompilerOptions
|
||||||
preprocessLang?: string
|
preprocessLang?: string
|
||||||
preprocessOptions?: any
|
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
|
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
|
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(
|
function preprocess(
|
||||||
@ -129,18 +144,24 @@ function doCompileTemplate({
|
|||||||
ssr = false,
|
ssr = false,
|
||||||
compiler = ssr ? (CompilerSSR as TemplateCompiler) : CompilerDOM,
|
compiler = ssr ? (CompilerSSR as TemplateCompiler) : CompilerDOM,
|
||||||
compilerOptions = {},
|
compilerOptions = {},
|
||||||
transformAssetUrls
|
transformAssetUrls,
|
||||||
|
transformAssetUrlsBase
|
||||||
}: SFCTemplateCompileOptions): SFCTemplateCompileResults {
|
}: SFCTemplateCompileOptions): SFCTemplateCompileResults {
|
||||||
const errors: CompilerError[] = []
|
const errors: CompilerError[] = []
|
||||||
|
|
||||||
let nodeTransforms: NodeTransform[] = []
|
let nodeTransforms: NodeTransform[] = []
|
||||||
if (isObject(transformAssetUrls)) {
|
if (transformAssetUrls !== false) {
|
||||||
nodeTransforms = [
|
if (transformAssetUrlsBase || isObject(transformAssetUrls)) {
|
||||||
createAssetUrlTransformWithOptions(transformAssetUrls),
|
nodeTransforms = [
|
||||||
transformSrcset
|
createAssetUrlTransformWithOptions({
|
||||||
]
|
base: transformAssetUrlsBase,
|
||||||
} else if (transformAssetUrls !== false) {
|
tags: isObject(transformAssetUrls) ? transformAssetUrls : undefined
|
||||||
nodeTransforms = [transformAssetUrl, transformSrcset]
|
}),
|
||||||
|
transformSrcset
|
||||||
|
]
|
||||||
|
} else {
|
||||||
|
nodeTransforms = [transformAssetUrl, transformSrcset]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let { code, map } = compiler.compile(source, {
|
let { code, map } = compiler.compile(source, {
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import path from 'path'
|
||||||
import {
|
import {
|
||||||
createSimpleExpression,
|
createSimpleExpression,
|
||||||
ExpressionNode,
|
ExpressionNode,
|
||||||
@ -12,54 +13,97 @@ export interface AssetURLOptions {
|
|||||||
[name: string]: string[]
|
[name: string]: string[]
|
||||||
}
|
}
|
||||||
|
|
||||||
const defaultOptions: AssetURLOptions = {
|
export interface NormlaizedAssetURLOptions {
|
||||||
video: ['src', 'poster'],
|
base?: string | null
|
||||||
source: ['src'],
|
tags?: AssetURLOptions
|
||||||
img: ['src'],
|
}
|
||||||
image: ['xlink:href', 'href'],
|
|
||||||
use: ['xlink:href', 'href']
|
const defaultAssetUrlOptions: Required<NormlaizedAssetURLOptions> = {
|
||||||
|
base: null,
|
||||||
|
tags: {
|
||||||
|
video: ['src', 'poster'],
|
||||||
|
source: ['src'],
|
||||||
|
img: ['src'],
|
||||||
|
image: ['xlink:href', 'href'],
|
||||||
|
use: ['xlink:href', 'href']
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const createAssetUrlTransformWithOptions = (
|
export const createAssetUrlTransformWithOptions = (
|
||||||
options: AssetURLOptions
|
options: NormlaizedAssetURLOptions
|
||||||
): NodeTransform => {
|
): NodeTransform => {
|
||||||
const mergedOptions = {
|
const mergedOptions = {
|
||||||
...defaultOptions,
|
...defaultAssetUrlOptions,
|
||||||
...options
|
...options
|
||||||
}
|
}
|
||||||
return (node, context) =>
|
return (node, context) =>
|
||||||
(transformAssetUrl as Function)(node, context, mergedOptions)
|
(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 = (
|
export const transformAssetUrl: NodeTransform = (
|
||||||
node,
|
node,
|
||||||
context,
|
context,
|
||||||
options: AssetURLOptions = defaultOptions
|
options: NormlaizedAssetURLOptions = defaultAssetUrlOptions
|
||||||
) => {
|
) => {
|
||||||
if (node.type === NodeTypes.ELEMENT) {
|
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) {
|
if ((tag === '*' || node.tag === tag) && node.props.length) {
|
||||||
const attributes = options[tag]
|
const attributes = tags[tag]
|
||||||
attributes.forEach(item => {
|
attributes.forEach(name => {
|
||||||
node.props.forEach((attr, index) => {
|
node.props.forEach((attr, index) => {
|
||||||
if (attr.type !== NodeTypes.ATTRIBUTE) return
|
if (
|
||||||
if (attr.name !== item) return
|
attr.type !== NodeTypes.ATTRIBUTE ||
|
||||||
if (!attr.value) return
|
attr.name !== name ||
|
||||||
if (!isRelativeUrl(attr.value.content)) return
|
!attr.value ||
|
||||||
|
!isRelativeUrl(attr.value.content)
|
||||||
|
) {
|
||||||
|
return
|
||||||
|
}
|
||||||
const url = parseUrl(attr.value.content)
|
const url = parseUrl(attr.value.content)
|
||||||
const exp = getImportsExpressionExp(
|
if (options.base) {
|
||||||
url.path,
|
// explicit base - directly rewrite the url into absolute url
|
||||||
url.hash,
|
// does not apply to url that starts with `@` since they are
|
||||||
attr.loc,
|
// aliases
|
||||||
context
|
if (attr.value.content[0] !== '@') {
|
||||||
)
|
// when packaged in the browser, path will be using the posix-
|
||||||
node.props[index] = {
|
// only version provided by rollup-plugin-node-builtins.
|
||||||
type: NodeTypes.DIRECTIVE,
|
attr.value.content = (path.posix || path).join(
|
||||||
name: 'bind',
|
options.base,
|
||||||
arg: createSimpleExpression(item, true, attr.loc),
|
url.path + (url.hash || '')
|
||||||
exp,
|
)
|
||||||
modifiers: [],
|
}
|
||||||
loc: attr.loc
|
} 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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -6,8 +6,9 @@ export function isRelativeUrl(url: string): boolean {
|
|||||||
return firstChar === '.' || firstChar === '~' || firstChar === '@'
|
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 {
|
export function parseUrl(url: string): UrlWithStringQuery {
|
||||||
const firstChar = url.charAt(0)
|
const firstChar = url.charAt(0)
|
||||||
if (firstChar === '~') {
|
if (firstChar === '~') {
|
||||||
|
@ -129,7 +129,7 @@ function createConfig(format, output, plugins = []) {
|
|||||||
[
|
[
|
||||||
...Object.keys(pkg.dependencies || {}),
|
...Object.keys(pkg.dependencies || {}),
|
||||||
...Object.keys(pkg.peerDependencies || {}),
|
...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
|
// the browser builds of @vue/compiler-sfc requires postcss to be available
|
||||||
|
Loading…
Reference in New Issue
Block a user