]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
feat(compiler-sfc): support transforming absolute asset urls
authorEvan You <yyx990803@gmail.com>
Mon, 4 May 2020 20:45:19 +0000 (16:45 -0400)
committerEvan You <yyx990803@gmail.com>
Mon, 4 May 2020 20:45:19 +0000 (16:45 -0400)
BREAKING CHANGE: `@vue/compiler-sfc`'s `transformAssetUrlsBase` option
has been removed. It is merged into `trasnformAssetUrls` which now also
accepts the format of

  ```ts
  {
    base?: string
    includeAbsolute?: string
    tags?: { [name: string]: string[] }
  }
  ```

packages/compiler-sfc/__tests__/__snapshots__/templateTransformAssetUrl.spec.ts.snap
packages/compiler-sfc/__tests__/__snapshots__/templateTransformSrcset.spec.ts.snap
packages/compiler-sfc/__tests__/compileTemplate.spec.ts
packages/compiler-sfc/__tests__/templateTransformAssetUrl.spec.ts
packages/compiler-sfc/__tests__/templateTransformSrcset.spec.ts
packages/compiler-sfc/src/compileTemplate.ts
packages/compiler-sfc/src/templateTransformAssetUrl.ts
packages/compiler-sfc/src/templateTransformSrcset.ts
packages/compiler-sfc/src/templateUtils.ts

index e997cd09c1b1e58ee42f829a996245dcdc7d4124..3373e885ff81f0a3aa74d02ec4a7038ff8cac9f6 100644 (file)
@@ -49,3 +49,17 @@ export function render(_ctx, _cache) {
   ], 64 /* STABLE_FRAGMENT */))
 }"
 `;
+
+exports[`compiler sfc: transform asset url with includeAbsolute: true 1`] = `
+"import { createVNode as _createVNode, Fragment as _Fragment, openBlock as _openBlock, createBlock as _createBlock } from \\"vue\\"
+import _imports_0 from './bar.png'
+import _imports_1 from '/bar.png'
+
+
+export function render(_ctx, _cache) {
+  return (_openBlock(), _createBlock(_Fragment, null, [
+    _createVNode(\\"img\\", { src: _imports_0 }),
+    _createVNode(\\"img\\", { src: _imports_1 })
+  ], 64 /* STABLE_FRAGMENT */))
+}"
+`;
index dc0c5ee412b9978262e7a3fd529b55bdd00a03f4..75c72ac1947dd0dcab7881b005c9e454bd78668b 100644 (file)
@@ -59,3 +59,115 @@ export function render(_ctx, _cache) {
   ], 64 /* STABLE_FRAGMENT */))
 }"
 `;
+
+exports[`compiler sfc: transform srcset transform srcset w/ 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: \\"./logo.png\\",
+      srcset: \\"/foo/logo.png\\"
+    }),
+    _createVNode(\\"img\\", {
+      src: \\"./logo.png\\",
+      srcset: \\"/foo/logo.png 2x\\"
+    }),
+    _createVNode(\\"img\\", {
+      src: \\"./logo.png\\",
+      srcset: \\"/foo/logo.png 2x\\"
+    }),
+    _createVNode(\\"img\\", {
+      src: \\"./logo.png\\",
+      srcset: \\"/foo/logo.png, /foo/logo.png 2x\\"
+    }),
+    _createVNode(\\"img\\", {
+      src: \\"./logo.png\\",
+      srcset: \\"/foo/logo.png 2x, /foo/logo.png\\"
+    }),
+    _createVNode(\\"img\\", {
+      src: \\"./logo.png\\",
+      srcset: \\"/foo/logo.png 2x, /foo/logo.png 3x\\"
+    }),
+    _createVNode(\\"img\\", {
+      src: \\"./logo.png\\",
+      srcset: \\"/foo/logo.png, /foo/logo.png 2x, /foo/logo.png 3x\\"
+    }),
+    _createVNode(\\"img\\", {
+      src: \\"/logo.png\\",
+      srcset: \\"/logo.png, /logo.png 2x\\"
+    }),
+    _createVNode(\\"img\\", {
+      src: \\"https://example.com/logo.png\\",
+      srcset: \\"https://example.com/logo.png, https://example.com/logo.png 2x\\"
+    }),
+    _createVNode(\\"img\\", {
+      src: \\"/logo.png\\",
+      srcset: \\"/logo.png, /foo/logo.png 2x\\"
+    })
+  ], 64 /* STABLE_FRAGMENT */))
+}"
+`;
+
+exports[`compiler sfc: transform srcset transform srcset w/ includeAbsolute: true 1`] = `
+"import { createVNode as _createVNode, Fragment as _Fragment, openBlock as _openBlock, createBlock as _createBlock } from \\"vue\\"
+import _imports_0 from './logo.png'
+import _imports_1 from '/logo.png'
+
+
+const _hoisted_1 = _imports_0
+const _hoisted_2 = _imports_0 + '2x'
+const _hoisted_3 = _imports_0 + '2x'
+const _hoisted_4 = _imports_0 + ', ' + _imports_0 + '2x'
+const _hoisted_5 = _imports_0 + '2x, ' + _imports_0
+const _hoisted_6 = _imports_0 + '2x, ' + _imports_0 + '3x'
+const _hoisted_7 = _imports_0 + ', ' + _imports_0 + '2x, ' + _imports_0 + '3x'
+const _hoisted_8 = _imports_1 + ', ' + _imports_1 + '2x'
+const _hoisted_9 = \\"https://example.com/logo.png\\" + ', ' + \\"https://example.com/logo.png\\" + '2x'
+const _hoisted_10 = _imports_1 + ', ' + _imports_0 + '2x'
+
+export function render(_ctx, _cache) {
+  return (_openBlock(), _createBlock(_Fragment, null, [
+    _createVNode(\\"img\\", {
+      src: \\"./logo.png\\",
+      srcset: _hoisted_1
+    }),
+    _createVNode(\\"img\\", {
+      src: \\"./logo.png\\",
+      srcset: _hoisted_2
+    }),
+    _createVNode(\\"img\\", {
+      src: \\"./logo.png\\",
+      srcset: _hoisted_3
+    }),
+    _createVNode(\\"img\\", {
+      src: \\"./logo.png\\",
+      srcset: _hoisted_4
+    }),
+    _createVNode(\\"img\\", {
+      src: \\"./logo.png\\",
+      srcset: _hoisted_5
+    }),
+    _createVNode(\\"img\\", {
+      src: \\"./logo.png\\",
+      srcset: _hoisted_6
+    }),
+    _createVNode(\\"img\\", {
+      src: \\"./logo.png\\",
+      srcset: _hoisted_7
+    }),
+    _createVNode(\\"img\\", {
+      src: \\"/logo.png\\",
+      srcset: _hoisted_8
+    }),
+    _createVNode(\\"img\\", {
+      src: \\"https://example.com/logo.png\\",
+      srcset: _hoisted_9
+    }),
+    _createVNode(\\"img\\", {
+      src: \\"/logo.png\\",
+      srcset: _hoisted_10
+    })
+  ], 64 /* STABLE_FRAGMENT */))
+}"
+`;
index e6188f3f6da8b62c12222551ebfdb4346ad7a7fb..2a456d8ef5b702b32c383990e0f90d7fe2473785 100644 (file)
@@ -54,15 +54,27 @@ test('transform asset url options', () => {
   // Object option
   const { code: code1 } = compileTemplate({
     ...input,
-    transformAssetUrls: { foo: ['bar'] }
+    transformAssetUrls: {
+      tags: { foo: ['bar'] }
+    }
   })
   expect(code1).toMatch(`import _imports_0 from 'baz'\n`)
-  // false option
+
+  // legacy object option (direct tags config)
   const { code: code2 } = compileTemplate({
+    ...input,
+    transformAssetUrls: {
+      foo: ['bar']
+    }
+  })
+  expect(code2).toMatch(`import _imports_0 from 'baz'\n`)
+
+  // false option
+  const { code: code3 } = compileTemplate({
     ...input,
     transformAssetUrls: false
   })
-  expect(code2).not.toMatch(`import _imports_0 from 'baz'\n`)
+  expect(code3).not.toMatch(`import _imports_0 from 'baz'\n`)
 })
 
 test('source map', () => {
index 64a94c6a6e47c093a20daebef0d7d5946aee5031..1102690fdcff0290c1aa2d569629e9675b960475 100644 (file)
@@ -1,15 +1,20 @@
 import { generate, baseParse, transform } from '@vue/compiler-core'
 import {
   transformAssetUrl,
-  createAssetUrlTransformWithOptions
+  createAssetUrlTransformWithOptions,
+  AssetURLOptions,
+  normalizeOptions
 } from '../src/templateTransformAssetUrl'
 import { transformElement } from '../../compiler-core/src/transforms/transformElement'
 import { transformBind } from '../../compiler-core/src/transforms/vBind'
 
-function compileWithAssetUrls(template: string) {
+function compileWithAssetUrls(template: string, options?: AssetURLOptions) {
   const ast = baseParse(template)
+  const t = options
+    ? createAssetUrlTransformWithOptions(normalizeOptions(options))
+    : transformAssetUrl
   transform(ast, {
-    nodeTransforms: [transformAssetUrl, transformElement],
+    nodeTransforms: [t, transformElement],
     directiveTransforms: {
       bind: transformBind
     }
@@ -51,24 +56,25 @@ describe('compiler sfc: transform asset url', () => {
   })
 
   test('with explicit base', () => {
-    const ast = baseParse(
+    const { code } = compileWithAssetUrls(
       `<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)
+        `<img src="@theme/bar.png"></img>`, // -> @theme/bar.png (untouched)
+      {
+        base: '/foo'
+      }
     )
-    transform(ast, {
-      nodeTransforms: [
-        createAssetUrlTransformWithOptions({
-          base: '/foo'
-        }),
-        transformElement
-      ],
-      directiveTransforms: {
-        bind: transformBind
+    expect(code).toMatchSnapshot()
+  })
+
+  test('with includeAbsolute: true', () => {
+    const { code } = compileWithAssetUrls(
+      `<img src="./bar.png"></img>` + `<img src="/bar.png"></img>`,
+      {
+        includeAbsolute: true
       }
-    })
-    const { code } = generate(ast, { mode: 'module' })
+    )
     expect(code).toMatchSnapshot()
   })
 })
index 7b2e365493d54d6e4361bc41a9063577562ac38f..1bba07672b1deef2fd9654e693b40efdb0d1ecdb 100644 (file)
@@ -1,12 +1,22 @@
 import { generate, baseParse, transform } from '@vue/compiler-core'
-import { transformSrcset } from '../src/templateTransformSrcset'
+import {
+  transformSrcset,
+  createSrcsetTransformWithOptions
+} from '../src/templateTransformSrcset'
 import { transformElement } from '../../compiler-core/src/transforms/transformElement'
 import { transformBind } from '../../compiler-core/src/transforms/vBind'
+import {
+  AssetURLOptions,
+  normalizeOptions
+} from '../src/templateTransformAssetUrl'
 
-function compileWithSrcset(template: string) {
+function compileWithSrcset(template: string, options?: AssetURLOptions) {
   const ast = baseParse(template)
+  const srcsetTrasnform = options
+    ? createSrcsetTransformWithOptions(normalizeOptions(options))
+    : transformSrcset
   transform(ast, {
-    nodeTransforms: [transformSrcset, transformElement],
+    nodeTransforms: [srcsetTrasnform, transformElement],
     directiveTransforms: {
       bind: transformBind
     }
@@ -14,21 +24,37 @@ function compileWithSrcset(template: string) {
   return generate(ast, { mode: 'module' })
 }
 
+const src = `
+<img src="./logo.png" srcset="./logo.png"/>
+<img src="./logo.png" srcset="./logo.png 2x"/>
+<img src="./logo.png" srcset="./logo.png 2x"/>
+<img src="./logo.png" srcset="./logo.png, ./logo.png 2x"/>
+<img src="./logo.png" srcset="./logo.png 2x, ./logo.png"/>
+<img src="./logo.png" srcset="./logo.png 2x, ./logo.png 3x"/>
+<img src="./logo.png" srcset="./logo.png, ./logo.png 2x, ./logo.png 3x"/>
+<img src="/logo.png" srcset="/logo.png, /logo.png 2x"/>
+<img src="https://example.com/logo.png" srcset="https://example.com/logo.png, https://example.com/logo.png 2x"/>
+<img src="/logo.png" srcset="/logo.png, ./logo.png 2x"/>
+`
+
 describe('compiler sfc: transform srcset', () => {
   test('transform srcset', () => {
-    const result = compileWithSrcset(`
-                       <img src="./logo.png" srcset="./logo.png"/>
-                       <img src="./logo.png" srcset="./logo.png 2x"/>
-                       <img src="./logo.png" srcset="./logo.png 2x"/>
-                       <img src="./logo.png" srcset="./logo.png, ./logo.png 2x"/>
-                       <img src="./logo.png" srcset="./logo.png 2x, ./logo.png"/>
-                       <img src="./logo.png" srcset="./logo.png 2x, ./logo.png 3x"/>
-                       <img src="./logo.png" srcset="./logo.png, ./logo.png 2x, ./logo.png 3x"/>
-                       <img src="/logo.png" srcset="/logo.png, /logo.png 2x"/>
-                       <img src="https://example.com/logo.png" srcset="https://example.com/logo.png, https://example.com/logo.png 2x"/>
-                       <img src="/logo.png" srcset="/logo.png, ./logo.png 2x"/>
-               `)
+    expect(compileWithSrcset(src).code).toMatchSnapshot()
+  })
+
+  test('transform srcset w/ base', () => {
+    expect(
+      compileWithSrcset(src, {
+        base: '/foo'
+      }).code
+    ).toMatchSnapshot()
+  })
 
-    expect(result.code).toMatchSnapshot()
+  test('transform srcset w/ includeAbsolute: true', () => {
+    expect(
+      compileWithSrcset(src, {
+        includeAbsolute: true
+      }).code
+    ).toMatchSnapshot()
   })
 })
index df0051b0032f5b329dcd94d0791867c221d1e292..f7f858a4bd298273a665cc4d514199abb8e4e5c9 100644 (file)
@@ -10,9 +10,14 @@ import { SourceMapConsumer, SourceMapGenerator, RawSourceMap } from 'source-map'
 import {
   transformAssetUrl,
   AssetURLOptions,
-  createAssetUrlTransformWithOptions
+  createAssetUrlTransformWithOptions,
+  AssetURLTagConfig,
+  normalizeOptions
 } from './templateTransformAssetUrl'
-import { transformSrcset } from './templateTransformSrcset'
+import {
+  transformSrcset,
+  createSrcsetTransformWithOptions
+} from './templateTransformSrcset'
 import { isObject } from '@vue/shared'
 import * as CompilerDOM from '@vue/compiler-dom'
 import * as CompilerSSR from '@vue/compiler-ssr'
@@ -47,16 +52,10 @@ export interface SFCTemplateCompileOptions {
    */
   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.
+   * Configure what tags/attributes to trasnform into asset url imports,
+   * or disable the transform altogether with `false`.
    */
-  transformAssetUrlsBase?: string
+  transformAssetUrls?: AssetURLOptions | AssetURLTagConfig | boolean
 }
 
 function preprocess(
@@ -144,24 +143,19 @@ function doCompileTemplate({
   ssr = false,
   compiler = ssr ? (CompilerSSR as TemplateCompiler) : CompilerDOM,
   compilerOptions = {},
-  transformAssetUrls,
-  transformAssetUrlsBase
+  transformAssetUrls
 }: SFCTemplateCompileOptions): SFCTemplateCompileResults {
   const errors: CompilerError[] = []
 
   let nodeTransforms: NodeTransform[] = []
-  if (transformAssetUrls !== false) {
-    if (transformAssetUrlsBase || isObject(transformAssetUrls)) {
-      nodeTransforms = [
-        createAssetUrlTransformWithOptions({
-          base: transformAssetUrlsBase,
-          tags: isObject(transformAssetUrls) ? transformAssetUrls : undefined
-        }),
-        transformSrcset
-      ]
-    } else {
-      nodeTransforms = [transformAssetUrl, transformSrcset]
-    }
+  if (isObject(transformAssetUrls)) {
+    const assetOptions = normalizeOptions(transformAssetUrls)
+    nodeTransforms = [
+      createAssetUrlTransformWithOptions(assetOptions),
+      createSrcsetTransformWithOptions(assetOptions)
+    ]
+  } else if (transformAssetUrls !== false) {
+    nodeTransforms = [transformAssetUrl, transformSrcset]
   }
 
   let { code, map } = compiler.compile(source, {
index 40189be86afbb5e25489c1d8948989ea34a9826a..b491de6c930f720e09d3ee2c976ce6e78697837a 100644 (file)
@@ -8,18 +8,28 @@ import {
   TransformContext
 } from '@vue/compiler-core'
 import { isRelativeUrl, parseUrl } from './templateUtils'
+import { isArray } from '@vue/shared'
 
-export interface AssetURLOptions {
+export interface AssetURLTagConfig {
   [name: string]: string[]
 }
 
-export interface NormlaizedAssetURLOptions {
+export interface AssetURLOptions {
+  /**
+   * If base is provided, instead of transforming relative asset urls into
+   * imports, they will be directly rewritten to absolute urls.
+   */
   base?: string | null
-  tags?: AssetURLOptions
+  /**
+   * If true, also processes absolute urls.
+   */
+  includeAbsolute?: boolean
+  tags?: AssetURLTagConfig
 }
 
-const defaultAssetUrlOptions: Required<NormlaizedAssetURLOptions> = {
+export const defaultAssetUrlOptions: Required<AssetURLOptions> = {
   base: null,
+  includeAbsolute: false,
   tags: {
     video: ['src', 'poster'],
     source: ['src'],
@@ -29,15 +39,27 @@ const defaultAssetUrlOptions: Required<NormlaizedAssetURLOptions> = {
   }
 }
 
-export const createAssetUrlTransformWithOptions = (
-  options: NormlaizedAssetURLOptions
-): NodeTransform => {
-  const mergedOptions = {
+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 {
     ...defaultAssetUrlOptions,
     ...options
   }
+}
+
+export const createAssetUrlTransformWithOptions = (
+  options: Required<AssetURLOptions>
+): NodeTransform => {
   return (node, context) =>
-    (transformAssetUrl as Function)(node, context, mergedOptions)
+    (transformAssetUrl as Function)(node, context, options)
 }
 
 /**
@@ -56,7 +78,7 @@ export const createAssetUrlTransformWithOptions = (
 export const transformAssetUrl: NodeTransform = (
   node,
   context,
-  options: NormlaizedAssetURLOptions = defaultAssetUrlOptions
+  options: AssetURLOptions = defaultAssetUrlOptions
 ) => {
   if (node.type === NodeTypes.ELEMENT) {
     const tags = options.tags || defaultAssetUrlOptions.tags
@@ -69,16 +91,21 @@ export const transformAssetUrl: NodeTransform = (
               attr.type !== NodeTypes.ATTRIBUTE ||
               attr.name !== name ||
               !attr.value ||
-              !isRelativeUrl(attr.value.content)
+              (!options.includeAbsolute && !isRelativeUrl(attr.value.content))
             ) {
               return
             }
+
             const url = parseUrl(attr.value.content)
+
             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] !== '@') {
+              // 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(
@@ -86,24 +113,25 @@ export const transformAssetUrl: NodeTransform = (
                   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
-              }
+              return
+            }
+
+            // 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
             }
           })
         })
index 06b16ffd67c31b1714360418d0c995804adb05ab..aa7a400870cbc29aacd4bd847e2e329c39ac71ca 100644 (file)
@@ -1,3 +1,4 @@
+import path from 'path'
 import {
   createCompoundExpression,
   createSimpleExpression,
@@ -5,7 +6,11 @@ import {
   NodeTypes,
   SimpleExpressionNode
 } from '@vue/compiler-core'
-import { isRelativeUrl, parseUrl } from './templateUtils'
+import { isRelativeUrl, parseUrl, isExternalUrl } from './templateUtils'
+import {
+  AssetURLOptions,
+  defaultAssetUrlOptions
+} from './templateTransformAssetUrl'
 
 const srcsetTags = ['img', 'source']
 
@@ -17,13 +22,23 @@ interface ImageCandidate {
 // http://w3c.github.io/html/semantics-embedded-content.html#ref-for-image-candidate-string-5
 const escapedSpaceCharacters = /( |\\t|\\n|\\f|\\r)+/g
 
-export const transformSrcset: NodeTransform = (node, context) => {
+export const createSrcsetTransformWithOptions = (
+  options: Required<AssetURLOptions>
+): NodeTransform => {
+  return (node, context) =>
+    (transformSrcset as Function)(node, context, options)
+}
+
+export const transformSrcset: NodeTransform = (
+  node,
+  context,
+  options: Required<AssetURLOptions> = defaultAssetUrlOptions
+) => {
   if (node.type === NodeTypes.ELEMENT) {
     if (srcsetTags.includes(node.tag) && node.props.length) {
       node.props.forEach((attr, index) => {
         if (attr.name === 'srcset' && attr.type === NodeTypes.ATTRIBUTE) {
           if (!attr.value) return
-          // same logic as in transform-require.js
           const value = attr.value.content
 
           const imageCandidates: ImageCandidate[] = value.split(',').map(s => {
@@ -37,11 +52,34 @@ export const transformSrcset: NodeTransform = (node, context) => {
           })
 
           // When srcset does not contain any relative URLs, skip transforming
-          if (!imageCandidates.some(({ url }) => isRelativeUrl(url))) return
+          if (
+            !options.includeAbsolute &&
+            !imageCandidates.some(({ url }) => isRelativeUrl(url))
+          ) {
+            return
+          }
+
+          if (options.base) {
+            const base = options.base
+            const set: string[] = []
+            imageCandidates.forEach(({ url, descriptor }, index) => {
+              descriptor = descriptor ? ` ${descriptor}` : ``
+              if (isRelativeUrl(url)) {
+                set.push((path.posix || path).join(base, url) + descriptor)
+              } else {
+                set.push(url + descriptor)
+              }
+            })
+            attr.value.content = set.join(', ')
+            return
+          }
 
           const compoundExpression = createCompoundExpression([], attr.loc)
           imageCandidates.forEach(({ url, descriptor }, index) => {
-            if (isRelativeUrl(url)) {
+            if (
+              !isExternalUrl(url) &&
+              (options.includeAbsolute || isRelativeUrl(url))
+            ) {
               const { path } = parseUrl(url)
               let exp: SimpleExpressionNode
               if (path) {
index 70a5b26fa608f951bbcae9cf77f01141b739bc4f..40b40199089b71b6b5010d4da42bd73f3daa9c52 100644 (file)
@@ -6,6 +6,11 @@ export function isRelativeUrl(url: string): boolean {
   return firstChar === '.' || firstChar === '~' || firstChar === '@'
 }
 
+const externalRE = /^https?:\/\//
+export function isExternalUrl(url: string): boolean {
+  return externalRE.test(url)
+}
+
 /**
  * Parses string url into URL object.
  */