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 + } + } + }) + } + } }