From cf2a0b281f869aeee64667c0fe914c3a5736ca72 Mon Sep 17 00:00:00 2001
From: likui <2218301630@qq.com>
Date: Tue, 3 Dec 2019 12:06:14 +0800
Subject: [PATCH] feat(compiler-sfc): transform srcset (#501)
---
.../templateTransformSrcset.spec.ts.snap | 49 ++++++++++
.../__tests__/templateTransformSrcset.spec.ts | 31 +++++++
.../src/templateTransformSrcset.ts | 89 ++++++++++++++++++-
3 files changed, 166 insertions(+), 3 deletions(-)
create mode 100644 packages/compiler-sfc/__tests__/__snapshots__/templateTransformSrcset.spec.ts.snap
create mode 100644 packages/compiler-sfc/__tests__/templateTransformSrcset.spec.ts
diff --git a/packages/compiler-sfc/__tests__/__snapshots__/templateTransformSrcset.spec.ts.snap b/packages/compiler-sfc/__tests__/__snapshots__/templateTransformSrcset.spec.ts.snap
new file mode 100644
index 00000000..444b0ea4
--- /dev/null
+++ b/packages/compiler-sfc/__tests__/__snapshots__/templateTransformSrcset.spec.ts.snap
@@ -0,0 +1,49 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`compiler sfc: transform srcset transform srcset 1`] = `
+"import { createVNode, createBlock, Fragment, openBlock } from \\"vue\\"
+import _imports_0 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'
+
+export default function render() {
+ const _ctx = this
+ 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
+ })
+ ]))
+}"
+`;
diff --git a/packages/compiler-sfc/__tests__/templateTransformSrcset.spec.ts b/packages/compiler-sfc/__tests__/templateTransformSrcset.spec.ts
new file mode 100644
index 00000000..5aedc90f
--- /dev/null
+++ b/packages/compiler-sfc/__tests__/templateTransformSrcset.spec.ts
@@ -0,0 +1,31 @@
+import { generate, parse, transform } from '@vue/compiler-core'
+import { transformSrcset } from '../src/templateTransformSrcset'
+import { transformElement } from '../../compiler-core/src/transforms/transformElement'
+import { transformBind } from '../../compiler-core/src/transforms/vBind'
+
+function compileWithSrcset(template: string) {
+ const ast = parse(template)
+ transform(ast, {
+ nodeTransforms: [transformSrcset, transformElement],
+ directiveTransforms: {
+ bind: transformBind
+ }
+ })
+ return generate(ast, { mode: 'module' })
+}
+
+describe('compiler sfc: transform srcset', () => {
+ test('transform srcset', () => {
+ const result = compileWithSrcset(`
+
+
+
+
+
+
+
+ `)
+
+ expect(result.code).toMatchSnapshot()
+ })
+})
diff --git a/packages/compiler-sfc/src/templateTransformSrcset.ts b/packages/compiler-sfc/src/templateTransformSrcset.ts
index 144749ce..cf557ec2 100644
--- a/packages/compiler-sfc/src/templateTransformSrcset.ts
+++ b/packages/compiler-sfc/src/templateTransformSrcset.ts
@@ -1,5 +1,88 @@
-import { NodeTransform } from '@vue/compiler-core'
+import {
+ createCompoundExpression,
+ createSimpleExpression,
+ NodeTransform,
+ NodeTypes,
+ SimpleExpressionNode
+} from '@vue/compiler-core'
+import { parseUrl } from './templateUtils'
-export const transformSrcset: NodeTransform = () => {
- // TODO
+const srcsetTags = ['img', 'source']
+
+interface ImageCandidate {
+ url: string
+ descriptor: string
+}
+
+// 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) => {
+ 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 => {
+ // 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 { url, descriptor }
+ })
+
+ const compoundExpression = createCompoundExpression([], attr.loc)
+ imageCandidates.forEach(({ url, descriptor }, index) => {
+ const { path } = parseUrl(url)
+ let exp: SimpleExpressionNode
+ if (path) {
+ const importsArray = Array.from(context.imports)
+ const existingImportsIndex = importsArray.findIndex(
+ i => i.path === path
+ )
+ if (existingImportsIndex > -1) {
+ exp = createSimpleExpression(
+ `_imports_${existingImportsIndex}`,
+ false,
+ attr.loc,
+ true
+ )
+ } else {
+ exp = createSimpleExpression(
+ `_imports_${importsArray.length}`,
+ false,
+ attr.loc,
+ true
+ )
+ context.imports.add({ exp, path })
+ }
+ compoundExpression.children.push(exp)
+ }
+ const isNotLast = imageCandidates.length - 1 > index
+ if (descriptor && isNotLast) {
+ compoundExpression.children.push(` + '${descriptor}, ' + `)
+ } else if (descriptor) {
+ compoundExpression.children.push(` + '${descriptor}'`)
+ } else if (isNotLast) {
+ compoundExpression.children.push(` + ', ' + `)
+ }
+ })
+
+ node.props[index] = {
+ type: NodeTypes.DIRECTIVE,
+ name: 'bind',
+ arg: createSimpleExpression('srcset', true, attr.loc),
+ exp: context.hoist(compoundExpression),
+ modifiers: [],
+ loc: attr.loc
+ }
+ }
+ })
+ }
+ }
}