feat(compiler-sfc): transform asset url (#500)
This commit is contained in:
@@ -0,0 +1,39 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`compiler sfc: transform asset url support uri fragment 1`] = `
|
||||
"import { createVNode, createBlock, openBlock } from \\"vue\\"
|
||||
import _imports_0 from '@svg/file.svg'
|
||||
|
||||
|
||||
const _hoisted_1 = _imports_0 + '#fragment'
|
||||
|
||||
export default function render() {
|
||||
const _ctx = this
|
||||
return (openBlock(), createBlock(\\"use\\", { href: _hoisted_1 }))
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`compiler sfc: transform asset url support uri is empty 1`] = `
|
||||
"import { createVNode, createBlock, openBlock } from \\"vue\\"
|
||||
|
||||
export default function render() {
|
||||
const _ctx = this
|
||||
return (openBlock(), createBlock(\\"use\\", { href: '' }))
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`compiler sfc: transform asset url transform assetUrls 1`] = `
|
||||
"import { createVNode, createBlock, Fragment, openBlock } from \\"vue\\"
|
||||
import _imports_0 from './logo.png'
|
||||
import _imports_1 from 'fixtures/logo.png'
|
||||
|
||||
|
||||
export default function render() {
|
||||
const _ctx = this
|
||||
return (openBlock(), createBlock(Fragment, null, [
|
||||
createVNode(\\"img\\", { src: _imports_0 }),
|
||||
createVNode(\\"img\\", { src: _imports_1 }),
|
||||
createVNode(\\"img\\", { src: _imports_1 })
|
||||
]))
|
||||
}"
|
||||
`;
|
||||
@@ -0,0 +1,47 @@
|
||||
import { generate, parse, transform } from '@vue/compiler-core'
|
||||
import { transformAssetUrl } from '../src/templateTransformAssetUrl'
|
||||
import { transformElement } from '../../compiler-core/src/transforms/transformElement'
|
||||
import { transformBind } from '../../compiler-core/src/transforms/vBind'
|
||||
|
||||
function compileWithAssetUrls(template: string) {
|
||||
const ast = parse(template)
|
||||
transform(ast, {
|
||||
nodeTransforms: [transformAssetUrl, transformElement],
|
||||
directiveTransforms: {
|
||||
bind: transformBind
|
||||
}
|
||||
})
|
||||
return generate(ast, { mode: 'module' })
|
||||
}
|
||||
|
||||
describe('compiler sfc: transform asset url', () => {
|
||||
test('transform assetUrls', () => {
|
||||
const result = compileWithAssetUrls(`
|
||||
<img src="./logo.png"/>
|
||||
<img src="~fixtures/logo.png"/>
|
||||
<img src="~/fixtures/logo.png"/>
|
||||
`)
|
||||
|
||||
expect(result.code).toMatchSnapshot()
|
||||
})
|
||||
|
||||
/**
|
||||
* vuejs/component-compiler-utils#22 Support uri fragment in transformed require
|
||||
*/
|
||||
test('support uri fragment', () => {
|
||||
const result = compileWithAssetUrls(
|
||||
'<use href="~@svg/file.svg#fragment"></use>'
|
||||
)
|
||||
|
||||
expect(result.code).toMatchSnapshot()
|
||||
})
|
||||
|
||||
/**
|
||||
* vuejs/component-compiler-utils#22 Support uri fragment in transformed require
|
||||
*/
|
||||
test('support uri is empty', () => {
|
||||
const result = compileWithAssetUrls('<use href="~"></use>')
|
||||
|
||||
expect(result.code).toMatchSnapshot()
|
||||
})
|
||||
})
|
||||
@@ -1,5 +1,81 @@
|
||||
import { NodeTransform } from '@vue/compiler-core'
|
||||
import {
|
||||
AttributeNode,
|
||||
createSimpleExpression,
|
||||
ExpressionNode,
|
||||
NodeTransform,
|
||||
NodeTypes,
|
||||
SourceLocation,
|
||||
TransformContext
|
||||
} from '@vue/compiler-core'
|
||||
import { parseUrl } from './templateUtils'
|
||||
|
||||
export const transformAssetUrl: NodeTransform = () => {
|
||||
// TODO
|
||||
export interface AssetURLOptions {
|
||||
[name: string]: string[]
|
||||
}
|
||||
|
||||
const assetURLOptions: AssetURLOptions = {
|
||||
video: ['src', 'poster'],
|
||||
source: ['src'],
|
||||
img: ['src'],
|
||||
image: ['xlink:href', 'href'],
|
||||
use: ['xlink:href', 'href']
|
||||
}
|
||||
|
||||
export const transformAssetUrl: NodeTransform = (node, context) => {
|
||||
if (node.type === NodeTypes.ELEMENT) {
|
||||
for (const tag in assetURLOptions) {
|
||||
if ((tag === '*' || node.tag === tag) && node.props.length) {
|
||||
const attributes = assetURLOptions[tag]
|
||||
attributes.forEach(item => {
|
||||
node.props.forEach((attr: AttributeNode, index) => {
|
||||
if (attr.type !== NodeTypes.ATTRIBUTE) return
|
||||
if (attr.name !== item) return
|
||||
if (!attr.value) 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
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getImportsExpressionExp(
|
||||
path: string | undefined,
|
||||
hash: string | undefined,
|
||||
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)
|
||||
context.imports.add({ exp, path })
|
||||
if (hash && path) {
|
||||
return context.hoist(
|
||||
createSimpleExpression(`${name} + '${hash}'`, false, loc, true)
|
||||
)
|
||||
} else {
|
||||
return exp
|
||||
}
|
||||
} else {
|
||||
return createSimpleExpression(`''`, false, loc, true)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,30 +1,16 @@
|
||||
import { UrlWithStringQuery, parse as uriParse } from 'url'
|
||||
|
||||
// TODO use imports instead.
|
||||
// We need an extra transform context API for injecting arbitrary import
|
||||
// statements.
|
||||
export function urlToRequire(url: string): string {
|
||||
const returnValue = `"${url}"`
|
||||
export function parseUrl(url: string): UrlWithStringQuery {
|
||||
const firstChar = url.charAt(0)
|
||||
if (firstChar === '.' || firstChar === '~' || firstChar === '@') {
|
||||
if (firstChar === '~') {
|
||||
const secondChar = url.charAt(1)
|
||||
url = url.slice(secondChar === '/' ? 2 : 1)
|
||||
}
|
||||
|
||||
const uriParts = parseUriParts(url)
|
||||
|
||||
if (!uriParts.hash) {
|
||||
return `require("${url}")`
|
||||
} else {
|
||||
// support uri fragment case by excluding it from
|
||||
// the require and instead appending it as string;
|
||||
// assuming that the path part is sufficient according to
|
||||
// the above caseing(t.i. no protocol-auth-host parts expected)
|
||||
return `require("${uriParts.path}") + "${uriParts.hash}"`
|
||||
}
|
||||
}
|
||||
return returnValue
|
||||
return parseUriParts(url)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user