Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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
})
]))
}"
`;
31 changes: 31 additions & 0 deletions packages/compiler-sfc/__tests__/templateTransformSrcset.spec.ts
Original file line number Diff line number Diff line change
@@ -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(`
<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"/>
`)

expect(result.code).toMatchSnapshot()
})
})
89 changes: 86 additions & 3 deletions packages/compiler-sfc/src/templateTransformSrcset.ts
Original file line number Diff line number Diff line change
@@ -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
}
}
})
}
}
}