Skip to content

Commit

Permalink
feat(add): sfc->srcset transform
Browse files Browse the repository at this point in the history
  • Loading branch information
gcclll committed Dec 31, 2020
1 parent 6a986cd commit 61c3b7a
Show file tree
Hide file tree
Showing 3 changed files with 195 additions and 8 deletions.
56 changes: 51 additions & 5 deletions packages/compiler-sfc/src/compileTemplate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import {
createSrcsetTransformWithOptions,
transformSrcset
} from './templateTransformSrcset'
import { genCssVarsFromList } from './cssVars'

export interface TemplateCompiler {
compile(template: string, options: CompilerOptions): CodegenResult
Expand Down Expand Up @@ -145,7 +146,7 @@ export function compileTemplate(
tips: [
`Component ${
options.filename
} uses lang ${preprocessLang} fro template. Please install the language preprocessor.`
} uses lang ${preprocessLang} for template. Please install the language preprocessor.`
],
errors: [
`Component ${
Expand All @@ -169,7 +170,7 @@ function doCompileTemplate({
isProd = false,
compiler = ssr
? ({
/* TODO */
/* TODO (CompilerSSR as TemplateCompiler) */
} as TemplateCompiler)
: CompilerDOM,
compilerOptions = {},
Expand Down Expand Up @@ -211,7 +212,7 @@ function doCompileTemplate({
cacheHandlers: true,
ssrCssVars:
ssr && ssrCssVars && ssrCssVars.length
? '' /* TODO genCssVarsFromList(ssrCssVars, shortId, isProd) */
? genCssVarsFromList(ssrCssVars, shortId, isProd)
: '',
// css 局部使用,加上对应的唯一 id
scopeId: scoped ? longId : undefined,
Expand Down Expand Up @@ -242,8 +243,53 @@ function mapLines(oldMap: RawSourceMap, newMap: RawSourceMap): RawSourceMap {
if (!oldMap) return newMap
if (!newMap) return oldMap

// TODO
return oldMap
const oldMapConsumer = new SourceMapConsumer(oldMap)
const newMapConsumer = new SourceMapConsumer(newMap)
const mergedMapGenerator = new SourceMapGenerator()

newMapConsumer.eachMapping(m => {
if (m.originalLine == null) {
return
}

const origPosInOldMap = oldMapConsumer.originalPositionFor({
line: m.originalLine,
column: m.originalColumn
})

if (origPosInOldMap.source == null) {
return
}

mergedMapGenerator.addMapping({
generated: {
line: m.generatedLine,
column: m.generatedColumn
},
original: {
line: origPosInOldMap.line, // map line
// use current column, since the oldMap produced by @vue/compiler-sfc
// does not
column: m.originalColumn
},
source: origPosInOldMap.source,
name: origPosInOldMap.name
})
})

// source-map's type definition is incomplete
const generator = mergedMapGenerator as any
;(oldMapConsumer as any).sources.forEach((sourceFile: string) => {
generator._sources.add(sourceFile)
const sourceContent = oldMapConsumer.sourceContentFor(sourceFile)
if (sourceContent != null) {
mergedMapGenerator.setSourceContent(sourceFile, sourceContent)
}
})

generator._sourceRoot = oldMap.sourceRoot
generator._file = oldMap.file
return generator.toJSON()
}

function patchErrors(
Expand Down
3 changes: 2 additions & 1 deletion packages/compiler-sfc/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
// API
export { parse } from './parse'
export { compileTemplate } from './compileTemplate'
export { compileStyle } from './compileStyle'
export { compileStyle, compileStyleAsync } from './compileStyle'
export { compileScript } from './compileScript'
export { rewriteDefault } from './rewriteDefault'
export { generateCodeFrame } from '@vue/compiler-core'

// Types
Expand Down
144 changes: 142 additions & 2 deletions packages/compiler-sfc/src/templateTransformSrcset.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,33 @@
import { NodeTransform } from '@vue/compiler-dom'
import path from 'path'
import {
ConstantTypes,
createCompoundExpression,
createSimpleExpression,
NodeTransform,
NodeTypes,
SimpleExpressionNode
} from '@vue/compiler-core'
import {
isRelativeUrl,
parseUrl,
isExternalUrl,
isDataUrl
} from './templateUtils'
import {
AssetURLOptions,
defaultAssetUrlOptions
} from './templateTransformAssetUrl'

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 createSrcsetTransformWithOptions = (
options: Required<AssetURLOptions>
): NodeTransform => {
Expand All @@ -15,4 +39,120 @@ 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
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 }
})

// for data url need recheck url
for (let i = 0; i < imageCandidates.length; i++) {
if (imageCandidates[i].url.trim().startsWith('data:')) {
imageCandidates[i + 1].url =
imageCandidates[i].url + ',' + imageCandidates[i + 1].url
imageCandidates.splice(i, 1)
}
}

// When srcset does not contain any relative URLs, skip transforming
if (
!options.includeAbsolute &&
!imageCandidates.some(({ url }) => isRelativeUrl(url))
) {
return
}

if (options.base) {
const base = options.base
const set: string[] = []
imageCandidates.forEach(({ url, descriptor }) => {
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 (
!isExternalUrl(url) &&
!isDataUrl(url) &&
(options.includeAbsolute || isRelativeUrl(url))
) {
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,
ConstantTypes.CAN_HOIST
)
} else {
exp = createSimpleExpression(
`_imports_${importsArray.length}`,
false,
attr.loc,
ConstantTypes.CAN_HOIST
)
context.imports.add({ exp, path })
}
compoundExpression.children.push(exp)
}
} else {
const exp = createSimpleExpression(
`"${url}"`,
false,
attr.loc,
ConstantTypes.CAN_HOIST
)
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(` + ', ' + `)
}
})

const hoisted = context.hoist(compoundExpression)
hoisted.constType = ConstantTypes.CAN_HOIST

node.props[index] = {
type: NodeTypes.DIRECTIVE,
name: 'bind',
arg: createSimpleExpression('srcset', true, attr.loc),
exp: hoisted,
modifiers: [],
loc: attr.loc
}
}
})
}
}
}

0 comments on commit 61c3b7a

Please sign in to comment.