Repository: vuejs/vue-loader
Pull request #953 (2017-08-23 19:28:27)

Support for srcset
This PR adds support for `<img srcset="...">`. The `srcset` URLs are converted to `require()`s, just as `<img src="...">` is, the descriptors stay untouched.

narrowtux: Would love to have this pulled, when can I expect this?
AndreKR: As a workaround I'm currently putting
```json
"vue-loader": "AndreKR/vue-loader#srcset",
```
in my package.json.

Note however, that I'm not actively monitoring `vuejs/vue-loader` to pull in updates.
therealpecus: Hi @AndreKr,
thank you for this. We tested it and discovered a couple of issues: your code does not take into account attributes spawning multiple lines, and `srcset` attributes on `source` tags (useful for `picture` elements).

I made a quick review of your code that I submitted as a PR, but I think credit fir this should be yours, so feel free to review them and update this PR.

For all of those that can't wait, see https://github.com/therealpecus/vue-loader `srcset` branch.
AndreKR: @therealpecus Thanks for the fixes, I cherry-picked your commit into this branch. I found another issue with whitespace within the candidates. I also added a test for [all three cases](https://github.com/vuejs/vue-loader/pull/953/commits/d31d0976ab78a534eb2816c83590779822e87c68#diff-969754c85777905d6fcc43c2dd062a7cR7). Please check again.
therealpecus: Hopefully this will speed up the approval process. Thanks for the further review!
assembledadam: Any word on merging this? Thanks.
narrowtux: bump

Patch:
diff --git a/lib/template-compiler/index.js b/lib/template-compiler/index.js
index 1dbfffe..7eed3a1 100644
--- a/lib/template-compiler/index.js
+++ b/lib/template-compiler/index.js
@@ -5,6 +5,7 @@ const compiler = require('vue-template-compiler')
 const transpile = require('vue-template-es2015-compiler')
 const hotReloadAPIPath = normalize.dep('vue-hot-reload-api')
 const transformRequire = require('./modules/transform-require')
+const transformSrcset = require('./modules/transform-srcset')
 
 module.exports = function (html) {
   this.cacheable()
@@ -13,8 +14,9 @@ module.exports = function (html) {
   const vueOptions = this.options.__vueOptions__ || {}
   const options = loaderUtils.getOptions(this) || {}
 
-  const defaultModules = [transformRequire(options.transformToRequire)]
+  const defaultModules = [transformRequire(options.transformToRequire), transformSrcset()]
   let userModules = vueOptions.compilerModules || options.compilerModules
+
   // for HappyPack cross-process use cases
   if (typeof userModules === 'string') {
     userModules = require(userModules)
diff --git a/lib/template-compiler/modules/transform-srcset.js b/lib/template-compiler/modules/transform-srcset.js
new file mode 100644
index 0000000..7e44a60
--- /dev/null
+++ b/lib/template-compiler/modules/transform-srcset.js
@@ -0,0 +1,54 @@
+// vue compiler module for transforming `img:srcset` to a number of `require`s
+
+module.exports = function () {
+  return {
+    postTransformNode: node => {
+      transform(node)
+    }
+  }
+}
+
+function transform (node) {
+  const tags = ['img', 'source']
+
+  if (tags.indexOf(node.tag) !== -1 && node.attrs) {
+    node.attrs.forEach(attr => {
+      if (attr.name === 'srcset') {
+        // same logic as in transform-require.js
+        var value = attr.value
+        var isStatic = value.charAt(0) === '"' && value.charAt(value.length - 1) === '"'
+        if (!isStatic) {
+          return
+        }
+
+        // http://w3c.github.io/html/semantics-embedded-content.html#ref-for-image-candidate-string-5
+        const escapedSpaceCharacters = /( |\\t|\\n|\\f|\\r)+/g
+
+        const imageCandidates = value.substr(1, value.length - 2).split(',').map(s => {
+          // The attribute value arrives here with all whitespace, except normal spaces, represented by escape sequences
+          const [url, descriptor] = s.replace(escapedSpaceCharacters, ' ').trim().split(' ', 2)
+          return { require: urlToRequire(url), descriptor: descriptor }
+        })
+
+        let code = ''
+        imageCandidates.forEach((o, i, a) => {
+          code += o.require + ' + " ' + o.descriptor + (i < a.length - 1 ? ', " + ' : '"')
+        })
+
+        attr.value = code
+      }
+    })
+  }
+}
+
+function urlToRequire (url) {
+  // same logic as in transform-require.js
+  var firstChar = url.charAt(0)
+  if (firstChar === '.' || firstChar === '~') {
+    if (firstChar === '~') {
+      var secondChar = url.charAt(1)
+      url = '"' + url.slice(secondChar === '/' ? 2 : 1)
+    }
+    return `require("${url}")`
+  }
+}


Test patch:
diff --git a/test/fixtures/transform.vue b/test/fixtures/transform.vue
index 33442c6..1e358f8 100644
--- a/test/fixtures/transform.vue
+++ b/test/fixtures/transform.vue
@@ -4,6 +4,14 @@
   <svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink= "http://www.w3.org/1999/xlink">
     <image xlink:href="./logo.png" />
   </svg>
+  <img src="./logo.png" srcset="./logo.png 2x">
+  <img src="./logo.png" srcset="./logo.png 2x, ./logo.png 3x">
+  <img
+    src="./logo.png"
+    srcset="
+      ./logo.png 2x,
+      ./logo.png 3x
+  ">
 </div>
 </template>
 
diff --git a/test/test.js b/test/test.js
index 0e461a4..9e921e8 100644
--- a/test/test.js
+++ b/test/test.js
@@ -434,6 +434,16 @@ describe('vue-loader', function () {
       // image tag (SVG)
       expect(includeDataURL(vnode.children[2].children[0].data.attrs['xlink:href'])).to.equal(true)
       var style = window.document.querySelector('style').textContent
+
+      let dataURL = vnode.children[0].data.attrs.src
+
+      // image tag with srcset
+      expect(vnode.children[4].data.attrs.srcset).to.equal(dataURL + " 2x")
+      // image tag with srcset with two candidates
+      expect(vnode.children[6].data.attrs.srcset).to.equal(dataURL + " 2x, " + dataURL + " 3x")
+      // image tag with multiline srcset
+      expect(vnode.children[8].data.attrs.srcset).to.equal(dataURL + " 2x, " + dataURL + " 3x")
+
       // style
       expect(includeDataURL(style)).to.equal(true)
       done()