diff --git a/lib/compileTemplate.ts b/lib/compileTemplate.ts index e1108a5..28151e3 100644 --- a/lib/compileTemplate.ts +++ b/lib/compileTemplate.ts @@ -5,7 +5,8 @@ import { } from './types' import assetUrlsModule, { - AssetURLOptions + AssetURLOptions, + TransformAssetUrlsOptions } from './templateCompilerModules/assetUrl' import srcsetModule from './templateCompilerModules/srcset' @@ -18,6 +19,7 @@ export interface TemplateCompileOptions { compiler: VueTemplateCompiler compilerOptions?: VueTemplateCompilerOptions transformAssetUrls?: AssetURLOptions | boolean + transformAssetUrlsOptions?: TransformAssetUrlsOptions preprocessLang?: string preprocessOptions?: any transpileOptions?: any @@ -103,6 +105,7 @@ function actuallyCompile( compilerOptions = {}, transpileOptions = {}, transformAssetUrls, + transformAssetUrlsOptions, isProduction = process.env.NODE_ENV === 'production', isFunctional = false, optimizeSSR = false, @@ -116,9 +119,9 @@ function actuallyCompile( if (transformAssetUrls) { const builtInModules = [ transformAssetUrls === true - ? assetUrlsModule() - : assetUrlsModule(transformAssetUrls), - srcsetModule() + ? assetUrlsModule(undefined, transformAssetUrlsOptions) + : assetUrlsModule(transformAssetUrls, transformAssetUrlsOptions), + srcsetModule(transformAssetUrlsOptions) ] finalCompilerOptions = Object.assign({}, compilerOptions, { modules: [...builtInModules, ...(compilerOptions.modules || [])], diff --git a/lib/templateCompilerModules/assetUrl.ts b/lib/templateCompilerModules/assetUrl.ts index f375ebc..3bc490a 100644 --- a/lib/templateCompilerModules/assetUrl.ts +++ b/lib/templateCompilerModules/assetUrl.ts @@ -6,6 +6,14 @@ export interface AssetURLOptions { [name: string]: string | string[] } +export interface TransformAssetUrlsOptions { + /** + * If base is provided, instead of transforming relative asset urls into + * imports, they will be directly rewritten to absolute urls. + */ + base?: string +} + const defaultOptions: AssetURLOptions = { audio: 'src', video: ['src', 'poster'], @@ -15,37 +23,52 @@ const defaultOptions: AssetURLOptions = { use: ['xlink:href', 'href'] } -export default (userOptions?: AssetURLOptions) => { +export default ( + userOptions?: AssetURLOptions, + transformAssetUrlsOption?: TransformAssetUrlsOptions +) => { const options = userOptions ? Object.assign({}, defaultOptions, userOptions) : defaultOptions return { postTransformNode: (node: ASTNode) => { - transform(node, options) + transform(node, options, transformAssetUrlsOption) } } } -function transform(node: ASTNode, options: AssetURLOptions) { +function transform( + node: ASTNode, + options: AssetURLOptions, + transformAssetUrlsOption?: TransformAssetUrlsOptions +) { for (const tag in options) { if ((tag === '*' || node.tag === tag) && node.attrs) { const attributes = options[tag] if (typeof attributes === 'string') { - node.attrs.some(attr => rewrite(attr, attributes)) + node.attrs.some(attr => + rewrite(attr, attributes, transformAssetUrlsOption) + ) } else if (Array.isArray(attributes)) { - attributes.forEach(item => node.attrs.some(attr => rewrite(attr, item))) + attributes.forEach(item => + node.attrs.some(attr => rewrite(attr, item, transformAssetUrlsOption)) + ) } } } } -function rewrite(attr: Attr, name: string) { +function rewrite( + attr: Attr, + name: string, + transformAssetUrlsOption?: TransformAssetUrlsOptions +) { if (attr.name === name) { const value = attr.value // only transform static URLs if (value.charAt(0) === '"' && value.charAt(value.length - 1) === '"') { - attr.value = urlToRequire(value.slice(1, -1)) + attr.value = urlToRequire(value.slice(1, -1), transformAssetUrlsOption) return true } } diff --git a/lib/templateCompilerModules/srcset.ts b/lib/templateCompilerModules/srcset.ts index 7cb7544..7f66ab3 100644 --- a/lib/templateCompilerModules/srcset.ts +++ b/lib/templateCompilerModules/srcset.ts @@ -1,22 +1,26 @@ // vue compiler module for transforming `img:srcset` to a number of `require`s import { urlToRequire, ASTNode } from './utils' +import { TransformAssetUrlsOptions } from './assetUrl' interface ImageCandidate { require: string descriptor: string } -export default () => ({ +export default (transformAssetUrlsOptions?: TransformAssetUrlsOptions) => ({ postTransformNode: (node: ASTNode) => { - transform(node) + transform(node, transformAssetUrlsOptions) } }) // http://w3c.github.io/html/semantics-embedded-content.html#ref-for-image-candidate-string-5 const escapedSpaceCharacters = /( |\\t|\\n|\\f|\\r)+/g -function transform(node: ASTNode) { +function transform( + node: ASTNode, + transformAssetUrlsOptions?: TransformAssetUrlsOptions +) { const tags = ['img', 'source'] if (tags.indexOf(node.tag) !== -1 && node.attrs) { @@ -40,7 +44,10 @@ function transform(node: ASTNode) { .replace(escapedSpaceCharacters, ' ') .trim() .split(' ', 2) - return { require: urlToRequire(url), descriptor } + return { + require: urlToRequire(url, transformAssetUrlsOptions), + descriptor + } }) // "require(url1)" diff --git a/lib/templateCompilerModules/utils.ts b/lib/templateCompilerModules/utils.ts index 893d8a4..f81862c 100644 --- a/lib/templateCompilerModules/utils.ts +++ b/lib/templateCompilerModules/utils.ts @@ -1,3 +1,7 @@ +import { TransformAssetUrlsOptions } from './assetUrl' +import { UrlWithStringQuery, parse as uriParse } from 'url' +import path from 'path' + export interface Attr { name: string value: string @@ -8,20 +12,36 @@ export interface ASTNode { attrs: Attr[] } -import { UrlWithStringQuery, parse as uriParse } from 'url' - -export function urlToRequire(url: string): string { +export function urlToRequire( + url: string, + transformAssetUrlsOption: TransformAssetUrlsOptions = {} +): string { const returnValue = `"${url}"` // same logic as in transform-require.js const firstChar = url.charAt(0) - if (firstChar === '.' || firstChar === '~' || firstChar === '@') { - if (firstChar === '~') { - const secondChar = url.charAt(1) - url = url.slice(secondChar === '/' ? 2 : 1) - } + if (firstChar === '~') { + const secondChar = url.charAt(1) + url = url.slice(secondChar === '/' ? 2 : 1) + } - const uriParts = parseUriParts(url) + const uriParts = parseUriParts(url) + if (transformAssetUrlsOption.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 (firstChar === '.' || firstChar === '~') { + // when packaged in the browser, path will be using the posix- + // only version provided by rollup-plugin-node-builtins. + return `"${(path.posix || path).join( + transformAssetUrlsOption.base, + uriParts.path + (uriParts.hash || '') + )}"` + } + return returnValue + } + + if (firstChar === '.' || firstChar === '~' || firstChar === '@') { if (!uriParts.hash) { return `require("${url}")` } else { diff --git a/test/compileTemplate.spec.ts b/test/compileTemplate.spec.ts index 762a3ff..c923a49 100644 --- a/test/compileTemplate.spec.ts +++ b/test/compileTemplate.spec.ts @@ -204,3 +204,31 @@ test('transform srcset', () => { ) expect(vnode.children[18].data.attrs.srcset).toBe('test-url 2x, test-url 3x') }) + +test('transform assetUrls and srcset with base option', () => { + const source = ` +
+ + + + +
+` + const result = compileTemplate({ + compiler: compiler as VueTemplateCompiler, + filename: 'example.vue', + source, + transformAssetUrls: true, + transformAssetUrlsOptions: { base: '/base/' } + }) + + expect(result.errors.length).toBe(0) + + const vnode = mockRender(result.code) + expect(vnode.children[0].data.attrs.src).toBe('/base/logo.png') + expect(vnode.children[2].data.attrs.src).toBe('/base/fixtures/logo.png') + expect(vnode.children[4].data.attrs.src).toBe('/base/fixtures/logo.png') + expect(vnode.children[6].data.attrs.srcset).toBe( + '/base/logo.png 2x, /base/logo.png 3x' + ) +})