2020-05-03 02:49:28 +08:00
|
|
|
import path from 'path'
|
2019-12-02 01:02:53 +08:00
|
|
|
import {
|
|
|
|
createSimpleExpression,
|
|
|
|
ExpressionNode,
|
|
|
|
NodeTransform,
|
|
|
|
NodeTypes,
|
|
|
|
SourceLocation,
|
|
|
|
TransformContext
|
|
|
|
} from '@vue/compiler-core'
|
2020-05-06 21:40:55 +08:00
|
|
|
import { isRelativeUrl, parseUrl, isExternalUrl } from './templateUtils'
|
2020-05-05 04:45:19 +08:00
|
|
|
import { isArray } from '@vue/shared'
|
2019-11-07 10:58:15 +08:00
|
|
|
|
2020-05-05 04:45:19 +08:00
|
|
|
export interface AssetURLTagConfig {
|
2019-12-02 01:02:53 +08:00
|
|
|
[name: string]: string[]
|
|
|
|
}
|
|
|
|
|
2020-05-05 04:45:19 +08:00
|
|
|
export interface AssetURLOptions {
|
|
|
|
/**
|
|
|
|
* If base is provided, instead of transforming relative asset urls into
|
|
|
|
* imports, they will be directly rewritten to absolute urls.
|
|
|
|
*/
|
2020-05-03 02:49:28 +08:00
|
|
|
base?: string | null
|
2020-05-05 04:45:19 +08:00
|
|
|
/**
|
|
|
|
* If true, also processes absolute urls.
|
|
|
|
*/
|
|
|
|
includeAbsolute?: boolean
|
|
|
|
tags?: AssetURLTagConfig
|
2020-05-03 02:49:28 +08:00
|
|
|
}
|
|
|
|
|
2020-05-05 04:45:19 +08:00
|
|
|
export const defaultAssetUrlOptions: Required<AssetURLOptions> = {
|
2020-05-03 02:49:28 +08:00
|
|
|
base: null,
|
2020-05-05 04:45:19 +08:00
|
|
|
includeAbsolute: false,
|
2020-05-03 02:49:28 +08:00
|
|
|
tags: {
|
|
|
|
video: ['src', 'poster'],
|
|
|
|
source: ['src'],
|
|
|
|
img: ['src'],
|
|
|
|
image: ['xlink:href', 'href'],
|
|
|
|
use: ['xlink:href', 'href']
|
|
|
|
}
|
2019-12-02 01:02:53 +08:00
|
|
|
}
|
|
|
|
|
2020-05-05 04:45:19 +08:00
|
|
|
export const normalizeOptions = (
|
|
|
|
options: AssetURLOptions | AssetURLTagConfig
|
|
|
|
): Required<AssetURLOptions> => {
|
|
|
|
if (Object.keys(options).some(key => isArray((options as any)[key]))) {
|
|
|
|
// legacy option format which directly passes in tags config
|
|
|
|
return {
|
|
|
|
...defaultAssetUrlOptions,
|
|
|
|
tags: options as any
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return {
|
2020-05-03 02:49:28 +08:00
|
|
|
...defaultAssetUrlOptions,
|
2019-12-11 01:22:23 +08:00
|
|
|
...options
|
|
|
|
}
|
2020-05-05 04:45:19 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
export const createAssetUrlTransformWithOptions = (
|
|
|
|
options: Required<AssetURLOptions>
|
|
|
|
): NodeTransform => {
|
2019-12-11 01:22:23 +08:00
|
|
|
return (node, context) =>
|
2020-05-05 04:45:19 +08:00
|
|
|
(transformAssetUrl as Function)(node, context, options)
|
2019-12-11 01:22:23 +08:00
|
|
|
}
|
|
|
|
|
2020-05-03 02:49:28 +08:00
|
|
|
/**
|
|
|
|
* 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 })
|
|
|
|
* ```
|
|
|
|
*/
|
2019-12-11 01:22:23 +08:00
|
|
|
export const transformAssetUrl: NodeTransform = (
|
|
|
|
node,
|
|
|
|
context,
|
2020-05-05 04:45:19 +08:00
|
|
|
options: AssetURLOptions = defaultAssetUrlOptions
|
2019-12-11 01:22:23 +08:00
|
|
|
) => {
|
2019-12-02 01:02:53 +08:00
|
|
|
if (node.type === NodeTypes.ELEMENT) {
|
2020-05-06 04:07:15 +08:00
|
|
|
if (!node.props.length) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2020-05-03 02:49:28 +08:00
|
|
|
const tags = options.tags || defaultAssetUrlOptions.tags
|
2020-05-06 04:07:15 +08:00
|
|
|
const attrs = tags[node.tag]
|
|
|
|
const wildCardAttrs = tags['*']
|
|
|
|
if (!attrs && !wildCardAttrs) {
|
|
|
|
return
|
|
|
|
}
|
2020-05-05 04:45:19 +08:00
|
|
|
|
2020-05-06 04:07:15 +08:00
|
|
|
const assetAttrs = (attrs || []).concat(wildCardAttrs || [])
|
|
|
|
node.props.forEach((attr, index) => {
|
|
|
|
if (
|
|
|
|
attr.type !== NodeTypes.ATTRIBUTE ||
|
|
|
|
!assetAttrs.includes(attr.name) ||
|
|
|
|
!attr.value ||
|
2020-05-06 21:40:55 +08:00
|
|
|
isExternalUrl(attr.value.content) ||
|
2020-05-29 22:20:57 +08:00
|
|
|
attr.value.content[0] === '#' ||
|
2020-05-06 04:07:15 +08:00
|
|
|
(!options.includeAbsolute && !isRelativeUrl(attr.value.content))
|
|
|
|
) {
|
|
|
|
return
|
|
|
|
}
|
2020-05-05 04:45:19 +08:00
|
|
|
|
2020-05-06 04:07:15 +08:00
|
|
|
const url = parseUrl(attr.value.content)
|
|
|
|
if (options.base) {
|
|
|
|
// explicit base - directly rewrite the url into absolute url
|
|
|
|
// does not apply to absolute urls or urls that start with `@`
|
|
|
|
// since they are aliases
|
|
|
|
if (
|
|
|
|
attr.value.content[0] !== '@' &&
|
|
|
|
isRelativeUrl(attr.value.content)
|
|
|
|
) {
|
|
|
|
// 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 || '')
|
|
|
|
)
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
2020-05-05 04:45:19 +08:00
|
|
|
|
2020-05-06 04:07:15 +08:00
|
|
|
// 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(attr.name, true, attr.loc),
|
|
|
|
exp,
|
|
|
|
modifiers: [],
|
|
|
|
loc: attr.loc
|
2019-12-02 01:02:53 +08:00
|
|
|
}
|
2020-05-06 04:07:15 +08:00
|
|
|
})
|
2019-12-02 01:02:53 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function getImportsExpressionExp(
|
2020-04-16 21:33:30 +08:00
|
|
|
path: string | null,
|
|
|
|
hash: string | null,
|
2019-12-02 01:02:53 +08:00
|
|
|
loc: SourceLocation,
|
|
|
|
context: TransformContext
|
|
|
|
): ExpressionNode {
|
|
|
|
if (path) {
|
|
|
|
const importsArray = Array.from(context.imports)
|
|
|
|
const existing = importsArray.find(i => i.path === path)
|
|
|
|
if (existing) {
|
|
|
|
return existing.exp as ExpressionNode
|
|
|
|
}
|
|
|
|
const name = `_imports_${importsArray.length}`
|
|
|
|
const exp = createSimpleExpression(name, false, loc, true)
|
2020-05-05 03:15:26 +08:00
|
|
|
exp.isRuntimeConstant = true
|
2019-12-02 01:02:53 +08:00
|
|
|
context.imports.add({ exp, path })
|
|
|
|
if (hash && path) {
|
2020-05-05 03:15:26 +08:00
|
|
|
const ret = context.hoist(
|
2019-12-02 01:02:53 +08:00
|
|
|
createSimpleExpression(`${name} + '${hash}'`, false, loc, true)
|
|
|
|
)
|
2020-05-05 03:15:26 +08:00
|
|
|
ret.isRuntimeConstant = true
|
|
|
|
return ret
|
2019-12-02 01:02:53 +08:00
|
|
|
} else {
|
|
|
|
return exp
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
return createSimpleExpression(`''`, false, loc, true)
|
|
|
|
}
|
2019-11-07 10:58:15 +08:00
|
|
|
}
|