This commit is contained in:
2022-07-21 03:28:35 +00:00
parent d7c883d6df
commit 51b34b0e1d
30103 changed files with 4152204 additions and 23 deletions
+423
View File
@@ -0,0 +1,423 @@
// https://github.com/vuejs/core/blob/main/packages/compiler-core/src/babelUtils.ts
// should only use types from @babel/types
// do not import runtime methods
import type {
Identifier,
Node,
Function,
ObjectProperty,
BlockStatement,
Program
} from '@babel/types'
import { walk } from 'estree-walker'
export function walkIdentifiers(
root: Node,
onIdentifier: (
node: Identifier,
parent: Node,
parentStack: Node[],
isReference: boolean,
isLocal: boolean
) => void,
onNode?: (node: Node) => void
) {
const includeAll = false
const parentStack: Node[] = []
const knownIds: Record<string, number> = Object.create(null)
const rootExp =
root.type === 'Program' &&
root.body[0].type === 'ExpressionStatement' &&
root.body[0].expression
;(walk as any)(root, {
enter(node: Node & { scopeIds?: Set<string> }, parent: Node | undefined) {
parent && parentStack.push(parent)
if (
parent &&
parent.type.startsWith('TS') &&
parent.type !== 'TSAsExpression' &&
parent.type !== 'TSNonNullExpression' &&
parent.type !== 'TSTypeAssertion'
) {
return this.skip()
}
if (onNode) onNode(node)
if (node.type === 'Identifier') {
const isLocal = !!knownIds[node.name]
const isRefed = isReferencedIdentifier(node, parent!, parentStack)
if (includeAll || (isRefed && !isLocal)) {
onIdentifier(node, parent!, parentStack, isRefed, isLocal)
}
} else if (
node.type === 'ObjectProperty' &&
parent!.type === 'ObjectPattern'
) {
// mark property in destructure pattern
;(node as any).inPattern = true
} else if (isFunctionType(node)) {
// walk function expressions and add its arguments to known identifiers
// so that we don't prefix them
walkFunctionParams(node, id => markScopeIdentifier(node, id, knownIds))
} else if (node.type === 'BlockStatement') {
// #3445 record block-level local variables
walkBlockDeclarations(node, id =>
markScopeIdentifier(node, id, knownIds)
)
}
},
leave(node: Node & { scopeIds?: Set<string> }, parent: Node | undefined) {
parent && parentStack.pop()
if (node !== rootExp && node.scopeIds) {
for (const id of node.scopeIds) {
knownIds[id]--
if (knownIds[id] === 0) {
delete knownIds[id]
}
}
}
}
})
}
export function isReferencedIdentifier(
id: Identifier,
parent: Node | null,
parentStack: Node[]
) {
if (!parent) {
return true
}
// is a special keyword but parsed as identifier
if (id.name === 'arguments') {
return false
}
if (isReferenced(id, parent)) {
return true
}
// babel's isReferenced check returns false for ids being assigned to, so we
// need to cover those cases here
switch (parent.type) {
case 'AssignmentExpression':
case 'AssignmentPattern':
return true
case 'ObjectPattern':
case 'ArrayPattern':
return isInDestructureAssignment(parent, parentStack)
}
return false
}
export function isInDestructureAssignment(
parent: Node,
parentStack: Node[]
): boolean {
if (
parent &&
(parent.type === 'ObjectProperty' || parent.type === 'ArrayPattern')
) {
let i = parentStack.length
while (i--) {
const p = parentStack[i]
if (p.type === 'AssignmentExpression') {
return true
} else if (p.type !== 'ObjectProperty' && !p.type.endsWith('Pattern')) {
break
}
}
}
return false
}
export function walkFunctionParams(
node: Function,
onIdent: (id: Identifier) => void
) {
for (const p of node.params) {
for (const id of extractIdentifiers(p)) {
onIdent(id)
}
}
}
export function walkBlockDeclarations(
block: BlockStatement | Program,
onIdent: (node: Identifier) => void
) {
for (const stmt of block.body) {
if (stmt.type === 'VariableDeclaration') {
if (stmt.declare) continue
for (const decl of stmt.declarations) {
for (const id of extractIdentifiers(decl.id)) {
onIdent(id)
}
}
} else if (
stmt.type === 'FunctionDeclaration' ||
stmt.type === 'ClassDeclaration'
) {
if (stmt.declare || !stmt.id) continue
onIdent(stmt.id)
}
}
}
export function extractIdentifiers(
param: Node,
nodes: Identifier[] = []
): Identifier[] {
switch (param.type) {
case 'Identifier':
nodes.push(param)
break
case 'MemberExpression':
let object: any = param
while (object.type === 'MemberExpression') {
object = object.object
}
nodes.push(object)
break
case 'ObjectPattern':
for (const prop of param.properties) {
if (prop.type === 'RestElement') {
extractIdentifiers(prop.argument, nodes)
} else {
extractIdentifiers(prop.value, nodes)
}
}
break
case 'ArrayPattern':
param.elements.forEach(element => {
if (element) extractIdentifiers(element, nodes)
})
break
case 'RestElement':
extractIdentifiers(param.argument, nodes)
break
case 'AssignmentPattern':
extractIdentifiers(param.left, nodes)
break
}
return nodes
}
function markScopeIdentifier(
node: Node & { scopeIds?: Set<string> },
child: Identifier,
knownIds: Record<string, number>
) {
const { name } = child
if (node.scopeIds && node.scopeIds.has(name)) {
return
}
if (name in knownIds) {
knownIds[name]++
} else {
knownIds[name] = 1
}
;(node.scopeIds || (node.scopeIds = new Set())).add(name)
}
export const isFunctionType = (node: Node): node is Function => {
return /Function(?:Expression|Declaration)$|Method$/.test(node.type)
}
export const isStaticProperty = (node: Node): node is ObjectProperty =>
node &&
(node.type === 'ObjectProperty' || node.type === 'ObjectMethod') &&
!node.computed
export const isStaticPropertyKey = (node: Node, parent: Node) =>
isStaticProperty(parent) && parent.key === node
/**
* Copied from https://github.com/babel/babel/blob/main/packages/babel-types/src/validators/isReferenced.ts
* To avoid runtime dependency on @babel/types (which includes process references)
* This file should not change very often in babel but we may need to keep it
* up-to-date from time to time.
*
* https://github.com/babel/babel/blob/main/LICENSE
*
*/
function isReferenced(node: Node, parent: Node, grandparent?: Node): boolean {
switch (parent.type) {
// yes: PARENT[NODE]
// yes: NODE.child
// no: parent.NODE
case 'MemberExpression':
case 'OptionalMemberExpression':
if (parent.property === node) {
return !!parent.computed
}
return parent.object === node
case 'JSXMemberExpression':
return parent.object === node
// no: let NODE = init;
// yes: let id = NODE;
case 'VariableDeclarator':
return parent.init === node
// yes: () => NODE
// no: (NODE) => {}
case 'ArrowFunctionExpression':
return parent.body === node
// no: class { #NODE; }
// no: class { get #NODE() {} }
// no: class { #NODE() {} }
// no: class { fn() { return this.#NODE; } }
case 'PrivateName':
return false
// no: class { NODE() {} }
// yes: class { [NODE]() {} }
// no: class { foo(NODE) {} }
case 'ClassMethod':
case 'ClassPrivateMethod':
case 'ObjectMethod':
if (parent.key === node) {
return !!parent.computed
}
return false
// yes: { [NODE]: "" }
// no: { NODE: "" }
// depends: { NODE }
// depends: { key: NODE }
case 'ObjectProperty':
if (parent.key === node) {
return !!parent.computed
}
// parent.value === node
return !grandparent || grandparent.type !== 'ObjectPattern'
// no: class { NODE = value; }
// yes: class { [NODE] = value; }
// yes: class { key = NODE; }
case 'ClassProperty':
if (parent.key === node) {
return !!parent.computed
}
return true
case 'ClassPrivateProperty':
return parent.key !== node
// no: class NODE {}
// yes: class Foo extends NODE {}
case 'ClassDeclaration':
case 'ClassExpression':
return parent.superClass === node
// yes: left = NODE;
// no: NODE = right;
case 'AssignmentExpression':
return parent.right === node
// no: [NODE = foo] = [];
// yes: [foo = NODE] = [];
case 'AssignmentPattern':
return parent.right === node
// no: NODE: for (;;) {}
case 'LabeledStatement':
return false
// no: try {} catch (NODE) {}
case 'CatchClause':
return false
// no: function foo(...NODE) {}
case 'RestElement':
return false
case 'BreakStatement':
case 'ContinueStatement':
return false
// no: function NODE() {}
// no: function foo(NODE) {}
case 'FunctionDeclaration':
case 'FunctionExpression':
return false
// no: export NODE from "foo";
// no: export * as NODE from "foo";
case 'ExportNamespaceSpecifier':
case 'ExportDefaultSpecifier':
return false
// no: export { foo as NODE };
// yes: export { NODE as foo };
// no: export { NODE as foo } from "foo";
case 'ExportSpecifier':
// @ts-expect-error
if (grandparent?.source) {
return false
}
return parent.local === node
// no: import NODE from "foo";
// no: import * as NODE from "foo";
// no: import { NODE as foo } from "foo";
// no: import { foo as NODE } from "foo";
// no: import NODE from "bar";
case 'ImportDefaultSpecifier':
case 'ImportNamespaceSpecifier':
case 'ImportSpecifier':
return false
// no: import "foo" assert { NODE: "json" }
case 'ImportAttribute':
return false
// no: <div NODE="foo" />
case 'JSXAttribute':
return false
// no: [NODE] = [];
// no: ({ NODE }) = [];
case 'ObjectPattern':
case 'ArrayPattern':
return false
// no: new.NODE
// no: NODE.target
case 'MetaProperty':
return false
// yes: type X = { someProperty: NODE }
// no: type X = { NODE: OtherType }
case 'ObjectTypeProperty':
return parent.key !== node
// yes: enum X { Foo = NODE }
// no: enum X { NODE }
case 'TSEnumMember':
return parent.id !== node
// yes: { [NODE]: value }
// no: { NODE: value }
case 'TSPropertySignature':
if (parent.key === node) {
return !!parent.computed
}
return true
}
return true
}
File diff suppressed because it is too large Load Diff
+147
View File
@@ -0,0 +1,147 @@
const postcss = require('postcss')
import { ProcessOptions, LazyResult } from 'postcss'
import trimPlugin from './stylePlugins/trim'
import scopedPlugin from './stylePlugins/scoped'
import {
processors,
StylePreprocessor,
StylePreprocessorResults
} from './stylePreprocessors'
import { cssVarsPlugin } from './cssVars'
export interface SFCStyleCompileOptions {
source: string
filename: string
id: string
map?: any
scoped?: boolean
trim?: boolean
preprocessLang?: string
preprocessOptions?: any
postcssOptions?: any
postcssPlugins?: any[]
isProd?: boolean
}
export interface SFCAsyncStyleCompileOptions extends SFCStyleCompileOptions {
isAsync?: boolean
}
export interface SFCStyleCompileResults {
code: string
map: any | void
rawResult: LazyResult | void
errors: string[]
}
export function compileStyle(
options: SFCStyleCompileOptions
): SFCStyleCompileResults {
return doCompileStyle({ ...options, isAsync: false })
}
export function compileStyleAsync(
options: SFCStyleCompileOptions
): Promise<SFCStyleCompileResults> {
return Promise.resolve(doCompileStyle({ ...options, isAsync: true }))
}
export function doCompileStyle(
options: SFCAsyncStyleCompileOptions
): SFCStyleCompileResults {
const {
filename,
id,
scoped = true,
trim = true,
isProd = false,
preprocessLang,
postcssOptions,
postcssPlugins
} = options
const preprocessor = preprocessLang && processors[preprocessLang]
const preProcessedSource = preprocessor && preprocess(options, preprocessor)
const map = preProcessedSource ? preProcessedSource.map : options.map
const source = preProcessedSource ? preProcessedSource.code : options.source
const plugins = (postcssPlugins || []).slice()
plugins.unshift(cssVarsPlugin({ id: id.replace(/^data-v-/, ''), isProd }))
if (trim) {
plugins.push(trimPlugin())
}
if (scoped) {
plugins.push(scopedPlugin(id))
}
const postCSSOptions: ProcessOptions = {
...postcssOptions,
to: filename,
from: filename
}
if (map) {
postCSSOptions.map = {
inline: false,
annotation: false,
prev: map
}
}
let result, code, outMap
const errors: any[] = []
if (preProcessedSource && preProcessedSource.errors.length) {
errors.push(...preProcessedSource.errors)
}
try {
result = postcss(plugins).process(source, postCSSOptions)
// In async mode, return a promise.
if (options.isAsync) {
return result
.then(
(result: LazyResult): SFCStyleCompileResults => ({
code: result.css || '',
map: result.map && result.map.toJSON(),
errors,
rawResult: result
})
)
.catch(
(error: Error): SFCStyleCompileResults => ({
code: '',
map: undefined,
errors: [...errors, error.message],
rawResult: undefined
})
)
}
// force synchronous transform (we know we only have sync plugins)
code = result.css
outMap = result.map
} catch (e) {
errors.push(e)
}
return {
code: code || ``,
map: outMap && outMap.toJSON(),
errors,
rawResult: result
}
}
function preprocess(
options: SFCStyleCompileOptions,
preprocessor: StylePreprocessor
): StylePreprocessorResults {
return preprocessor(
options.source,
options.map,
Object.assign(
{
filename: options.filename
},
options.preprocessOptions
)
)
}
+206
View File
@@ -0,0 +1,206 @@
import { BindingMetadata, TemplateCompiler } from './types'
import assetUrlsModule, {
AssetURLOptions,
TransformAssetUrlsOptions
} from './templateCompilerModules/assetUrl'
import srcsetModule from './templateCompilerModules/srcset'
import consolidate from '@vue/consolidate'
import * as _compiler from 'web/entry-compiler'
import { prefixIdentifiers } from './prefixIdentifiers'
import { CompilerOptions, WarningMessage } from 'types/compiler'
export interface SFCTemplateCompileOptions {
source: string
filename: string
compiler?: TemplateCompiler
compilerOptions?: CompilerOptions
transformAssetUrls?: AssetURLOptions | boolean
transformAssetUrlsOptions?: TransformAssetUrlsOptions
preprocessLang?: string
preprocessOptions?: any
transpileOptions?: any
isProduction?: boolean
isFunctional?: boolean
optimizeSSR?: boolean
prettify?: boolean
isTS?: boolean
bindings?: BindingMetadata
}
export interface SFCTemplateCompileResults {
ast: Object | undefined
code: string
source: string
tips: (string | WarningMessage)[]
errors: (string | WarningMessage)[]
}
export function compileTemplate(
options: SFCTemplateCompileOptions
): SFCTemplateCompileResults {
const { preprocessLang } = options
const preprocessor = preprocessLang && consolidate[preprocessLang]
if (preprocessor) {
return actuallyCompile(
Object.assign({}, options, {
source: preprocess(options, preprocessor)
})
)
} else if (preprocessLang) {
return {
ast: {},
code: `var render = function () {}\n` + `var staticRenderFns = []\n`,
source: options.source,
tips: [
`Component ${options.filename} uses lang ${preprocessLang} for template. Please install the language preprocessor.`
],
errors: [
`Component ${options.filename} uses lang ${preprocessLang} for template, however it is not installed.`
]
}
} else {
return actuallyCompile(options)
}
}
function preprocess(
options: SFCTemplateCompileOptions,
preprocessor: any
): string {
const { source, filename, preprocessOptions } = options
const finalPreprocessOptions = Object.assign(
{
filename
},
preprocessOptions
)
// Consolidate exposes a callback based API, but the callback is in fact
// called synchronously for most templating engines. In our case, we have to
// expose a synchronous API so that it is usable in Jest transforms (which
// have to be sync because they are applied via Node.js require hooks)
let res: any, err
preprocessor.render(
source,
finalPreprocessOptions,
(_err: Error | null, _res: string) => {
if (_err) err = _err
res = _res
}
)
if (err) throw err
return res
}
function actuallyCompile(
options: SFCTemplateCompileOptions
): SFCTemplateCompileResults {
const {
source,
compiler = _compiler,
compilerOptions = {},
transpileOptions = {},
transformAssetUrls,
transformAssetUrlsOptions,
isProduction = process.env.NODE_ENV === 'production',
isFunctional = false,
optimizeSSR = false,
prettify = true,
isTS = false,
bindings
} = options
const compile =
optimizeSSR && compiler.ssrCompile ? compiler.ssrCompile : compiler.compile
let finalCompilerOptions = compilerOptions
if (transformAssetUrls) {
const builtInModules = [
transformAssetUrls === true
? assetUrlsModule(undefined, transformAssetUrlsOptions)
: assetUrlsModule(transformAssetUrls, transformAssetUrlsOptions),
srcsetModule(transformAssetUrlsOptions)
]
finalCompilerOptions = Object.assign({}, compilerOptions, {
modules: [...builtInModules, ...(compilerOptions.modules || [])],
filename: options.filename
})
}
finalCompilerOptions.bindings = bindings
const { ast, render, staticRenderFns, tips, errors } = compile(
source,
finalCompilerOptions
)
if (errors && errors.length) {
return {
ast,
code: `var render = function () {}\n` + `var staticRenderFns = []\n`,
source,
tips,
errors
}
} else {
// transpile code with vue-template-es2015-compiler, which is a forked
// version of Buble that applies ES2015 transforms + stripping `with` usage
let code =
`var __render__ = ${prefixIdentifiers(
`function render(${isFunctional ? `_c,_vm` : ``}){${render}\n}`,
isFunctional,
isTS,
transpileOptions,
bindings
)}\n` +
`var __staticRenderFns__ = [${staticRenderFns.map(code =>
prefixIdentifiers(
`function (${isFunctional ? `_c,_vm` : ``}){${code}\n}`,
isFunctional,
isTS,
transpileOptions,
bindings
)
)}]` +
`\n`
// #23 we use __render__ to avoid `render` not being prefixed by the
// transpiler when stripping with, but revert it back to `render` to
// maintain backwards compat
code = code.replace(/\s__(render|staticRenderFns)__\s/g, ' $1 ')
if (!isProduction) {
// mark with stripped (this enables Vue to use correct runtime proxy
// detection)
code += `render._withStripped = true`
if (prettify) {
try {
code = require('prettier').format(code, {
semi: false,
parser: 'babel'
})
} catch (e: any) {
if (e.code === 'MODULE_NOT_FOUND') {
tips.push(
'The `prettify` option is on, but the dependency `prettier` is not found.\n' +
'Please either turn off `prettify` or manually install `prettier`.'
)
}
tips.push(
`Failed to prettify component ${options.filename} template source after compilation.`
)
}
}
}
return {
ast,
code,
source,
tips,
errors
}
}
}
+179
View File
@@ -0,0 +1,179 @@
import { BindingMetadata } from './types'
import { SFCDescriptor } from './parseComponent'
import { PluginCreator } from 'postcss'
import hash from 'hash-sum'
import { prefixIdentifiers } from './prefixIdentifiers'
export const CSS_VARS_HELPER = `useCssVars`
export function genCssVarsFromList(
vars: string[],
id: string,
isProd: boolean,
isSSR = false
): string {
return `{\n ${vars
.map(
key => `"${isSSR ? `--` : ``}${genVarName(id, key, isProd)}": (${key})`
)
.join(',\n ')}\n}`
}
function genVarName(id: string, raw: string, isProd: boolean): string {
if (isProd) {
return hash(id + raw)
} else {
return `${id}-${raw.replace(/([^\w-])/g, '_')}`
}
}
function normalizeExpression(exp: string) {
exp = exp.trim()
if (
(exp[0] === `'` && exp[exp.length - 1] === `'`) ||
(exp[0] === `"` && exp[exp.length - 1] === `"`)
) {
return exp.slice(1, -1)
}
return exp
}
const vBindRE = /v-bind\s*\(/g
export function parseCssVars(sfc: SFCDescriptor): string[] {
const vars: string[] = []
sfc.styles.forEach(style => {
let match
// ignore v-bind() in comments /* ... */
const content = style.content.replace(/\/\*([\s\S]*?)\*\//g, '')
while ((match = vBindRE.exec(content))) {
const start = match.index + match[0].length
const end = lexBinding(content, start)
if (end !== null) {
const variable = normalizeExpression(content.slice(start, end))
if (!vars.includes(variable)) {
vars.push(variable)
}
}
}
})
return vars
}
const enum LexerState {
inParens,
inSingleQuoteString,
inDoubleQuoteString
}
function lexBinding(content: string, start: number): number | null {
let state: LexerState = LexerState.inParens
let parenDepth = 0
for (let i = start; i < content.length; i++) {
const char = content.charAt(i)
switch (state) {
case LexerState.inParens:
if (char === `'`) {
state = LexerState.inSingleQuoteString
} else if (char === `"`) {
state = LexerState.inDoubleQuoteString
} else if (char === `(`) {
parenDepth++
} else if (char === `)`) {
if (parenDepth > 0) {
parenDepth--
} else {
return i
}
}
break
case LexerState.inSingleQuoteString:
if (char === `'`) {
state = LexerState.inParens
}
break
case LexerState.inDoubleQuoteString:
if (char === `"`) {
state = LexerState.inParens
}
break
}
}
return null
}
// for compileStyle
export interface CssVarsPluginOptions {
id: string
isProd: boolean
}
export const cssVarsPlugin: PluginCreator<CssVarsPluginOptions> = opts => {
const { id, isProd } = opts!
return {
postcssPlugin: 'vue-sfc-vars',
Declaration(decl) {
// rewrite CSS variables
const value = decl.value
if (vBindRE.test(value)) {
vBindRE.lastIndex = 0
let transformed = ''
let lastIndex = 0
let match
while ((match = vBindRE.exec(value))) {
const start = match.index + match[0].length
const end = lexBinding(value, start)
if (end !== null) {
const variable = normalizeExpression(value.slice(start, end))
transformed +=
value.slice(lastIndex, match.index) +
`var(--${genVarName(id, variable, isProd)})`
lastIndex = end + 1
}
}
decl.value = transformed + value.slice(lastIndex)
}
}
}
}
cssVarsPlugin.postcss = true
export function genCssVarsCode(
vars: string[],
bindings: BindingMetadata,
id: string,
isProd: boolean
) {
const varsExp = genCssVarsFromList(vars, id, isProd)
return `_${CSS_VARS_HELPER}((_vm, _setup) => ${prefixIdentifiers(
`(${varsExp})`,
false,
false,
undefined,
bindings
)})`
}
// <script setup> already gets the calls injected as part of the transform
// this is only for single normal <script>
export function genNormalScriptCssVarsCode(
cssVars: string[],
bindings: BindingMetadata,
id: string,
isProd: boolean
): string {
return (
`\nimport { ${CSS_VARS_HELPER} as _${CSS_VARS_HELPER} } from 'vue'\n` +
`const __injectCSSVars__ = () => {\n${genCssVarsCode(
cssVars,
bindings,
id,
isProd
)}}\n` +
`const __setup__ = __default__.setup\n` +
`__default__.setup = __setup__\n` +
` ? (props, ctx) => { __injectCSSVars__();return __setup__(props, ctx) }\n` +
` : __injectCSSVars__\n`
)
}
+24
View File
@@ -0,0 +1,24 @@
// API
export { parse } from './parse'
export { compileTemplate } from './compileTemplate'
export { compileStyle, compileStyleAsync } from './compileStyle'
export { compileScript } from './compileScript'
export { generateCodeFrame } from 'compiler/codeframe'
export { rewriteDefault } from './rewriteDefault'
// types
export { SFCParseOptions } from './parse'
export { CompilerOptions, WarningMessage } from 'types/compiler'
export { TemplateCompiler } from './types'
export {
SFCBlock,
SFCCustomBlock,
SFCScriptBlock,
SFCDescriptor
} from './parseComponent'
export {
SFCTemplateCompileOptions,
SFCTemplateCompileResults
} from './compileTemplate'
export { SFCStyleCompileOptions, SFCStyleCompileResults } from './compileStyle'
export { SFCScriptCompileOptions } from './compileScript'
+129
View File
@@ -0,0 +1,129 @@
import { SourceMapGenerator } from 'source-map'
import { RawSourceMap, TemplateCompiler } from './types'
import {
parseComponent,
VueTemplateCompilerParseOptions,
SFCDescriptor,
DEFAULT_FILENAME
} from './parseComponent'
import hash from 'hash-sum'
import LRU from 'lru-cache'
import { hmrShouldReload } from './compileScript'
import { parseCssVars } from './cssVars'
const cache = new LRU<string, SFCDescriptor>(100)
const splitRE = /\r?\n/g
const emptyRE = /^(?:\/\/)?\s*$/
export interface SFCParseOptions {
source: string
filename?: string
compiler?: TemplateCompiler
compilerParseOptions?: VueTemplateCompilerParseOptions
sourceRoot?: string
sourceMap?: boolean
/**
* @deprecated use `sourceMap` instead.
*/
needMap?: boolean
}
export function parse(options: SFCParseOptions): SFCDescriptor {
const {
source,
filename = DEFAULT_FILENAME,
compiler,
compilerParseOptions = { pad: false } as VueTemplateCompilerParseOptions,
sourceRoot = '',
needMap = true,
sourceMap = needMap
} = options
const cacheKey = hash(
filename + source + JSON.stringify(compilerParseOptions)
)
let output = cache.get(cacheKey)
if (output) {
return output
}
if (compiler) {
// user-provided compiler
output = compiler.parseComponent(source, compilerParseOptions)
} else {
// use built-in compiler
output = parseComponent(source, compilerParseOptions)
}
output.filename = filename
// parse CSS vars
output.cssVars = parseCssVars(output)
output.shouldForceReload = prevImports =>
hmrShouldReload(prevImports, output!)
if (sourceMap) {
if (output.script && !output.script.src) {
output.script.map = generateSourceMap(
filename,
source,
output.script.content,
sourceRoot,
compilerParseOptions.pad
)
}
if (output.styles) {
output.styles.forEach(style => {
if (!style.src) {
style.map = generateSourceMap(
filename,
source,
style.content,
sourceRoot,
compilerParseOptions.pad
)
}
})
}
}
cache.set(cacheKey, output)
return output
}
function generateSourceMap(
filename: string,
source: string,
generated: string,
sourceRoot: string,
pad?: 'line' | 'space' | boolean
): RawSourceMap {
const map = new SourceMapGenerator({
file: filename.replace(/\\/g, '/'),
sourceRoot: sourceRoot.replace(/\\/g, '/')
})
let offset = 0
if (!pad) {
offset = source.split(generated).shift()!.split(splitRE).length - 1
}
map.setSourceContent(filename, source)
generated.split(splitRE).forEach((line, index) => {
if (!emptyRE.test(line)) {
map.addMapping({
source: filename,
original: {
line: index + 1 + offset,
column: 0
},
generated: {
line: index + 1,
column: 0
}
})
}
})
return JSON.parse(map.toString())
}
+220
View File
@@ -0,0 +1,220 @@
import deindent from 'de-indent'
import { parseHTML } from 'compiler/parser/html-parser'
import { makeMap } from 'shared/util'
import { ASTAttr, WarningMessage } from 'types/compiler'
import { BindingMetadata, RawSourceMap } from './types'
import type { ImportBinding } from './compileScript'
export const DEFAULT_FILENAME = 'anonymous.vue'
const splitRE = /\r?\n/g
const replaceRE = /./g
const isSpecialTag = makeMap('script,style,template', true)
export interface SFCCustomBlock {
type: string
content: string
attrs: { [key: string]: string | true }
start: number
end: number
src?: string
map?: RawSourceMap
}
export interface SFCBlock extends SFCCustomBlock {
lang?: string
scoped?: boolean
module?: string | boolean
}
export interface SFCScriptBlock extends SFCBlock {
type: 'script'
setup?: string | boolean
bindings?: BindingMetadata
imports?: Record<string, ImportBinding>
/**
* import('\@babel/types').Statement
*/
scriptAst?: any[]
/**
* import('\@babel/types').Statement
*/
scriptSetupAst?: any[]
}
export interface SFCDescriptor {
source: string
filename: string
template: SFCBlock | null
script: SFCScriptBlock | null
scriptSetup: SFCScriptBlock | null
styles: SFCBlock[]
customBlocks: SFCCustomBlock[]
cssVars: string[]
errors: (string | WarningMessage)[]
/**
* compare with an existing descriptor to determine whether HMR should perform
* a reload vs. re-render.
*
* Note: this comparison assumes the prev/next script are already identical,
* and only checks the special case where `<script setup lang="ts">` unused
* import pruning result changes due to template changes.
*/
shouldForceReload: (prevImports: Record<string, ImportBinding>) => boolean
}
export interface VueTemplateCompilerParseOptions {
pad?: 'line' | 'space' | boolean
deindent?: boolean
outputSourceRange?: boolean
}
/**
* Parse a single-file component (*.vue) file into an SFC Descriptor Object.
*/
export function parseComponent(
source: string,
options: VueTemplateCompilerParseOptions = {}
): SFCDescriptor {
const sfc: SFCDescriptor = {
source,
filename: DEFAULT_FILENAME,
template: null,
script: null,
scriptSetup: null, // TODO
styles: [],
customBlocks: [],
cssVars: [],
errors: [],
shouldForceReload: null as any // attached in parse() by compiler-sfc
}
let depth = 0
let currentBlock: SFCBlock | null = null
let warn: any = msg => {
sfc.errors.push(msg)
}
if (__DEV__ && options.outputSourceRange) {
warn = (msg, range) => {
const data: WarningMessage = { msg }
if (range.start != null) {
data.start = range.start
}
if (range.end != null) {
data.end = range.end
}
sfc.errors.push(data)
}
}
function start(
tag: string,
attrs: ASTAttr[],
unary: boolean,
start: number,
end: number
) {
if (depth === 0) {
currentBlock = {
type: tag,
content: '',
start: end,
end: 0, // will be set on tag close
attrs: attrs.reduce((cumulated, { name, value }) => {
cumulated[name] = value || true
return cumulated
}, {})
}
if (typeof currentBlock.attrs.src === 'string') {
currentBlock.src = currentBlock.attrs.src
}
if (isSpecialTag(tag)) {
checkAttrs(currentBlock, attrs)
if (tag === 'script') {
const block = currentBlock as SFCScriptBlock
if (block.attrs.setup) {
block.setup = currentBlock.attrs.setup
sfc.scriptSetup = block
} else {
sfc.script = block
}
} else if (tag === 'style') {
sfc.styles.push(currentBlock)
} else {
sfc[tag] = currentBlock
}
} else {
// custom blocks
sfc.customBlocks.push(currentBlock)
}
}
if (!unary) {
depth++
}
}
function checkAttrs(block: SFCBlock, attrs: ASTAttr[]) {
for (let i = 0; i < attrs.length; i++) {
const attr = attrs[i]
if (attr.name === 'lang') {
block.lang = attr.value
}
if (attr.name === 'scoped') {
block.scoped = true
}
if (attr.name === 'module') {
block.module = attr.value || true
}
}
}
function end(tag: string, start: number) {
if (depth === 1 && currentBlock) {
currentBlock.end = start
let text = source.slice(currentBlock.start, currentBlock.end)
if (
options.deindent === true ||
// by default, deindent unless it's script with default lang or ts
(options.deindent !== false &&
!(
currentBlock.type === 'script' &&
(!currentBlock.lang || currentBlock.lang === 'ts')
))
) {
text = deindent(text)
}
// pad content so that linters and pre-processors can output correct
// line numbers in errors and warnings
if (currentBlock.type !== 'template' && options.pad) {
text = padContent(currentBlock, options.pad) + text
}
currentBlock.content = text
currentBlock = null
}
depth--
}
function padContent(block: SFCBlock, pad: true | 'line' | 'space') {
if (pad === 'space') {
return source.slice(0, block.start).replace(replaceRE, ' ')
} else {
const offset = source.slice(0, block.start).split(splitRE).length
const padChar = block.type === 'script' && !block.lang ? '//\n' : '\n'
return Array(offset).join(padChar)
}
}
parseHTML(source, {
warn,
start,
end,
outputSourceRange: options.outputSourceRange
})
return sfc
}
+82
View File
@@ -0,0 +1,82 @@
import MagicString from 'magic-string'
import { parseExpression, ParserOptions, ParserPlugin } from '@babel/parser'
import { makeMap } from 'shared/util'
import { isStaticProperty, walkIdentifiers } from './babelUtils'
import { BindingMetadata } from './types'
const doNotPrefix = makeMap(
'Infinity,undefined,NaN,isFinite,isNaN,' +
'parseFloat,parseInt,decodeURI,decodeURIComponent,encodeURI,encodeURIComponent,' +
'Math,Number,Date,Array,Object,Boolean,String,RegExp,Map,Set,JSON,Intl,' +
'require,' + // for webpack
'arguments,' + // parsed as identifier but is a special keyword...
'_c' // cached to save property access
)
/**
* The input is expected to be a valid expression.
*/
export function prefixIdentifiers(
source: string,
isFunctional = false,
isTS = false,
babelOptions: ParserOptions = {},
bindings?: BindingMetadata
) {
const s = new MagicString(source)
const plugins: ParserPlugin[] = [
...(isTS ? (['typescript'] as const) : []),
...(babelOptions?.plugins || [])
]
const ast = parseExpression(source, {
...babelOptions,
plugins
})
const isScriptSetup = bindings && bindings.__isScriptSetup !== false
walkIdentifiers(
ast,
(ident, parent) => {
const { name } = ident
if (doNotPrefix(name)) {
return
}
let prefix = `_vm.`
if (isScriptSetup) {
const type = bindings[name]
if (type && type.startsWith('setup')) {
prefix = `_setup.`
}
}
if (isStaticProperty(parent) && parent.shorthand) {
// property shorthand like { foo }, we need to add the key since
// we rewrite the value
// { foo } -> { foo: _vm.foo }
s.appendLeft(ident.end!, `: ${prefix}${name}`)
} else {
s.prependRight(ident.start!, prefix)
}
},
node => {
if (node.type === 'WithStatement') {
s.remove(node.start!, node.body.start! + 1)
s.remove(node.end! - 1, node.end!)
if (!isFunctional) {
s.prependRight(
node.start!,
`var _vm=this,_c=_vm._self._c${
isScriptSetup ? `,_setup=_vm._self._setupProxy;` : `;`
}`
)
}
}
}
)
return s.toString()
}
+110
View File
@@ -0,0 +1,110 @@
import { parse, ParserPlugin } from '@babel/parser'
import MagicString from 'magic-string'
const defaultExportRE = /((?:^|\n|;)\s*)export(\s*)default/
const namedDefaultExportRE = /((?:^|\n|;)\s*)export(.+)(?:as)?(\s*)default/s
const exportDefaultClassRE =
/((?:^|\n|;)\s*)export\s+default\s+class\s+([\w$]+)/
/**
* Utility for rewriting `export default` in a script block into a variable
* declaration so that we can inject things into it
*/
export function rewriteDefault(
input: string,
as: string,
parserPlugins?: ParserPlugin[]
): string {
if (!hasDefaultExport(input)) {
return input + `\nconst ${as} = {}`
}
let replaced: string | undefined
const classMatch = input.match(exportDefaultClassRE)
if (classMatch) {
replaced =
input.replace(exportDefaultClassRE, '$1class $2') +
`\nconst ${as} = ${classMatch[2]}`
} else {
replaced = input.replace(defaultExportRE, `$1const ${as} =`)
}
if (!hasDefaultExport(replaced)) {
return replaced
}
// if the script somehow still contains `default export`, it probably has
// multi-line comments or template strings. fallback to a full parse.
const s = new MagicString(input)
const ast = parse(input, {
sourceType: 'module',
plugins: parserPlugins
}).program.body
ast.forEach(node => {
if (node.type === 'ExportDefaultDeclaration') {
s.overwrite(node.start!, node.declaration.start!, `const ${as} = `)
}
if (node.type === 'ExportNamedDeclaration') {
for (const specifier of node.specifiers) {
if (
specifier.type === 'ExportSpecifier' &&
specifier.exported.type === 'Identifier' &&
specifier.exported.name === 'default'
) {
if (node.source) {
if (specifier.local.name === 'default') {
const end = specifierEnd(input, specifier.local.end!, node.end)
s.prepend(
`import { default as __VUE_DEFAULT__ } from '${node.source.value}'\n`
)
s.overwrite(specifier.start!, end, ``)
s.append(`\nconst ${as} = __VUE_DEFAULT__`)
continue
} else {
const end = specifierEnd(input, specifier.exported.end!, node.end)
s.prepend(
`import { ${input.slice(
specifier.local.start!,
specifier.local.end!
)} } from '${node.source.value}'\n`
)
s.overwrite(specifier.start!, end, ``)
s.append(`\nconst ${as} = ${specifier.local.name}`)
continue
}
}
const end = specifierEnd(input, specifier.end!, node.end)
s.overwrite(specifier.start!, end, ``)
s.append(`\nconst ${as} = ${specifier.local.name}`)
}
}
}
})
return s.toString()
}
export function hasDefaultExport(input: string): boolean {
return defaultExportRE.test(input) || namedDefaultExportRE.test(input)
}
function specifierEnd(
input: string,
end: number,
nodeEnd: number | undefined | null
) {
// export { default , foo } ...
let hasCommas = false
let oldEnd = end
while (end < nodeEnd!) {
if (/\s/.test(input.charAt(end))) {
end++
} else if (input.charAt(end) === ',') {
end++
hasCommas = true
break
} else if (input.charAt(end) === '}') {
break
}
}
return hasCommas ? end : oldEnd
}
+204
View File
@@ -0,0 +1,204 @@
import { PluginCreator, Rule, AtRule } from 'postcss'
import selectorParser from 'postcss-selector-parser'
import { warn } from '../warn'
const animationNameRE = /^(-\w+-)?animation-name$/
const animationRE = /^(-\w+-)?animation$/
const scopedPlugin: PluginCreator<string> = (id = '') => {
const keyframes = Object.create(null)
const shortId = id.replace(/^data-v-/, '')
return {
postcssPlugin: 'vue-sfc-scoped',
Rule(rule) {
processRule(id, rule)
},
AtRule(node) {
if (
/-?keyframes$/.test(node.name) &&
!node.params.endsWith(`-${shortId}`)
) {
// register keyframes
keyframes[node.params] = node.params = node.params + '-' + shortId
}
},
OnceExit(root) {
if (Object.keys(keyframes).length) {
// If keyframes are found in this <style>, find and rewrite animation names
// in declarations.
// Caveat: this only works for keyframes and animation rules in the same
// <style> element.
// individual animation-name declaration
root.walkDecls(decl => {
if (animationNameRE.test(decl.prop)) {
decl.value = decl.value
.split(',')
.map(v => keyframes[v.trim()] || v.trim())
.join(',')
}
// shorthand
if (animationRE.test(decl.prop)) {
decl.value = decl.value
.split(',')
.map(v => {
const vals = v.trim().split(/\s+/)
const i = vals.findIndex(val => keyframes[val])
if (i !== -1) {
vals.splice(i, 1, keyframes[vals[i]])
return vals.join(' ')
} else {
return v
}
})
.join(',')
}
})
}
}
}
}
const processedRules = new WeakSet<Rule>()
function processRule(id: string, rule: Rule) {
if (
processedRules.has(rule) ||
(rule.parent &&
rule.parent.type === 'atrule' &&
/-?keyframes$/.test((rule.parent as AtRule).name))
) {
return
}
processedRules.add(rule)
rule.selector = selectorParser(selectorRoot => {
selectorRoot.each(selector => {
rewriteSelector(id, selector, selectorRoot)
})
}).processSync(rule.selector)
}
function rewriteSelector(
id: string,
selector: selectorParser.Selector,
selectorRoot: selectorParser.Root
) {
let node: selectorParser.Node | null = null
let shouldInject = true
// find the last child node to insert attribute selector
selector.each(n => {
// DEPRECATED ">>>" and "/deep/" combinator
if (
n.type === 'combinator' &&
(n.value === '>>>' || n.value === '/deep/')
) {
n.value = ' '
n.spaces.before = n.spaces.after = ''
warn(
`the >>> and /deep/ combinators have been deprecated. ` +
`Use :deep() instead.`
)
return false
}
if (n.type === 'pseudo') {
const { value } = n
// deep: inject [id] attribute at the node before the ::v-deep
// combinator.
if (value === ':deep' || value === '::v-deep') {
if (n.nodes.length) {
// .foo ::v-deep(.bar) -> .foo[xxxxxxx] .bar
// replace the current node with ::v-deep's inner selector
let last: selectorParser.Selector['nodes'][0] = n
n.nodes[0].each(ss => {
selector.insertAfter(last, ss)
last = ss
})
// insert a space combinator before if it doesn't already have one
const prev = selector.at(selector.index(n) - 1)
if (!prev || !isSpaceCombinator(prev)) {
selector.insertAfter(
n,
selectorParser.combinator({
value: ' '
})
)
}
selector.removeChild(n)
} else {
// DEPRECATED usage
// .foo ::v-deep .bar -> .foo[xxxxxxx] .bar
warn(
`::v-deep usage as a combinator has ` +
`been deprecated. Use :deep(<inner-selector>) instead.`
)
const prev = selector.at(selector.index(n) - 1)
if (prev && isSpaceCombinator(prev)) {
selector.removeChild(prev)
}
selector.removeChild(n)
}
return false
}
// !!! Vue 2 does not have :slotted support
// ::v-slotted(.foo) -> .foo[xxxxxxx-s]
// if (value === ':slotted' || value === '::v-slotted') {
// rewriteSelector(id, n.nodes[0], selectorRoot, true /* slotted */)
// let last: selectorParser.Selector['nodes'][0] = n
// n.nodes[0].each(ss => {
// selector.insertAfter(last, ss)
// last = ss
// })
// // selector.insertAfter(n, n.nodes[0])
// selector.removeChild(n)
// // since slotted attribute already scopes the selector there's no
// // need for the non-slot attribute.
// shouldInject = false
// return false
// }
// global: replace with inner selector and do not inject [id].
// ::v-global(.foo) -> .foo
if (value === ':global' || value === '::v-global') {
selectorRoot.insertAfter(selector, n.nodes[0])
selectorRoot.removeChild(selector)
return false
}
}
if (n.type !== 'pseudo' && n.type !== 'combinator') {
node = n
}
})
if (node) {
;(node as selectorParser.Node).spaces.after = ''
} else {
// For deep selectors & standalone pseudo selectors,
// the attribute selectors are prepended rather than appended.
// So all leading spaces must be eliminated to avoid problems.
selector.first.spaces.before = ''
}
if (shouldInject) {
selector.insertAfter(
// If node is null it means we need to inject [id] at the start
// insertAfter can handle `null` here
node as any,
selectorParser.attribute({
attribute: id,
value: id,
raws: {},
quoteMark: `"`
})
)
}
}
function isSpaceCombinator(node: selectorParser.Node) {
return node.type === 'combinator' && /^\s+$/.test(node.value)
}
scopedPlugin.postcss = true
export default scopedPlugin
+18
View File
@@ -0,0 +1,18 @@
import { PluginCreator } from 'postcss'
const trimPlugin: PluginCreator<{}> = () => {
return {
postcssPlugin: 'vue-sfc-trim',
Once(root) {
root.walk(({ type, raws }) => {
if (type === 'rule' || type === 'atrule') {
if (raws.before) raws.before = '\n'
if ('after' in raws && raws.after) raws.after = '\n'
}
})
}
}
}
trimPlugin.postcss = true
export default trimPlugin
+135
View File
@@ -0,0 +1,135 @@
import merge from 'merge-source-map'
import { RawSourceMap } from 'source-map'
import { isFunction } from 'shared/util'
export type StylePreprocessor = (
source: string,
map: RawSourceMap | undefined,
options: {
[key: string]: any
additionalData?: string | ((source: string, filename: string) => string)
filename: string
}
) => StylePreprocessorResults
export interface StylePreprocessorResults {
code: string
map?: object
errors: Error[]
dependencies: string[]
}
// .scss/.sass processor
const scss: StylePreprocessor = (source, map, options) => {
const nodeSass = require('sass')
const finalOptions = {
...options,
data: getSource(source, options.filename, options.additionalData),
file: options.filename,
outFile: options.filename,
sourceMap: !!map
}
try {
const result = nodeSass.renderSync(finalOptions)
const dependencies = result.stats.includedFiles
if (map) {
return {
code: result.css.toString(),
map: merge(map, JSON.parse(result.map.toString())),
errors: [],
dependencies
}
}
return { code: result.css.toString(), errors: [], dependencies }
} catch (e: any) {
return { code: '', errors: [e], dependencies: [] }
}
}
const sass: StylePreprocessor = (source, map, options) =>
scss(source, map, {
...options,
indentedSyntax: true
})
// .less
const less: StylePreprocessor = (source, map, options) => {
const nodeLess = require('less')
let result: any
let error: Error | null = null
nodeLess.render(
getSource(source, options.filename, options.additionalData),
{ ...options, syncImport: true },
(err: Error | null, output: any) => {
error = err
result = output
}
)
if (error) return { code: '', errors: [error], dependencies: [] }
const dependencies = result.imports
if (map) {
return {
code: result.css.toString(),
map: merge(map, result.map),
errors: [],
dependencies: dependencies
}
}
return {
code: result.css.toString(),
errors: [],
dependencies: dependencies
}
}
// .styl
const styl: StylePreprocessor = (source, map, options) => {
const nodeStylus = require('stylus')
try {
const ref = nodeStylus(source)
Object.keys(options).forEach(key => ref.set(key, options[key]))
if (map) ref.set('sourcemap', { inline: false, comment: false })
const result = ref.render()
const dependencies = ref.deps()
if (map) {
return {
code: result,
map: merge(map, ref.sourcemap),
errors: [],
dependencies
}
}
return { code: result, errors: [], dependencies }
} catch (e: any) {
return { code: '', errors: [e], dependencies: [] }
}
}
function getSource(
source: string,
filename: string,
additionalData?: string | ((source: string, filename: string) => string)
) {
if (!additionalData) return source
if (isFunction(additionalData)) {
return additionalData(source, filename)
}
return additionalData + source
}
export type PreprocessLang = 'less' | 'sass' | 'scss' | 'styl' | 'stylus'
export const processors: Record<PreprocessLang, StylePreprocessor> = {
less,
sass,
scss,
styl,
stylus: styl
}
@@ -0,0 +1,84 @@
// vue compiler module for transforming `<tag>:<attribute>` to `require`
import { urlToRequire } from './utils'
import { ASTNode, ASTAttr } from 'types/compiler'
export interface AssetURLOptions {
[name: string]: string | string[]
}
export interface TransformAssetUrlsOptions {
/**
* If base is provided, instead of transforming relative asset urls into
* imports, they will be directly rewritten to absolute urls.
*/
base?: string
/**
* If true, also processes absolute urls.
*/
includeAbsolute?: boolean
}
const defaultOptions: AssetURLOptions = {
audio: 'src',
video: ['src', 'poster'],
source: 'src',
img: 'src',
image: ['xlink:href', 'href'],
use: ['xlink:href', 'href']
}
export default (
userOptions?: AssetURLOptions,
transformAssetUrlsOption?: TransformAssetUrlsOptions
) => {
const options = userOptions
? Object.assign({}, defaultOptions, userOptions)
: defaultOptions
return {
postTransformNode: (node: ASTNode) => {
transform(node, options, transformAssetUrlsOption)
}
}
}
function transform(
node: ASTNode,
options: AssetURLOptions,
transformAssetUrlsOption?: TransformAssetUrlsOptions
) {
if (node.type !== 1 || !node.attrs) return
for (const tag in options) {
if (tag === '*' || node.tag === tag) {
const attributes = options[tag]
if (typeof attributes === 'string') {
node.attrs!.some(attr =>
rewrite(attr, attributes, transformAssetUrlsOption)
)
} else if (Array.isArray(attributes)) {
attributes.forEach(item =>
node.attrs!.some(attr =>
rewrite(attr, item, transformAssetUrlsOption)
)
)
}
}
}
}
function rewrite(
attr: ASTAttr,
name: string,
transformAssetUrlsOption?: TransformAssetUrlsOptions
) {
if (attr.name === name) {
const value = attr.value
// only transform static URLs
if (value.charAt(0) === '"' && value.charAt(value.length - 1) === '"') {
attr.value = urlToRequire(value.slice(1, -1), transformAssetUrlsOption)
return true
}
}
return false
}
@@ -0,0 +1,76 @@
// vue compiler module for transforming `img:srcset` to a number of `require`s
import { urlToRequire } from './utils'
import { TransformAssetUrlsOptions } from './assetUrl'
import { ASTNode } from 'types/compiler'
interface ImageCandidate {
require: string
descriptor: string
}
export default (transformAssetUrlsOptions?: TransformAssetUrlsOptions) => ({
postTransformNode: (node: ASTNode) => {
transform(node, transformAssetUrlsOptions)
}
})
// http://w3c.github.io/html/semantics-embedded-content.html#ref-for-image-candidate-string-5
const escapedSpaceCharacters = /( |\\t|\\n|\\f|\\r)+/g
function transform(
node: ASTNode,
transformAssetUrlsOptions?: TransformAssetUrlsOptions
) {
if (node.type !== 1 || !node.attrs) {
return
}
if (node.tag === 'img' || node.tag === 'source') {
node.attrs.forEach(attr => {
if (attr.name === 'srcset') {
// same logic as in transform-require.js
const value = attr.value
const isStatic =
value.charAt(0) === '"' && value.charAt(value.length - 1) === '"'
if (!isStatic) {
return
}
const imageCandidates: ImageCandidate[] = value
.slice(1, -1)
.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, transformAssetUrlsOptions),
descriptor
}
})
// "require(url1)"
// "require(url1) 1x"
// "require(url1), require(url2)"
// "require(url1), require(url2) 2x"
// "require(url1) 1x, require(url2)"
// "require(url1) 1x, require(url2) 2x"
const code = imageCandidates
.map(
({ require, descriptor }) =>
`${require} + "${descriptor ? ' ' + descriptor : ''}, " + `
)
.join('')
.slice(0, -6)
.concat('"')
.replace(/ \+ ""$/, '')
attr.value = code
}
})
}
}
@@ -0,0 +1,81 @@
import { TransformAssetUrlsOptions } from './assetUrl'
import { UrlWithStringQuery, parse as uriParse } from 'url'
import path from 'path'
export function urlToRequire(
url: string,
transformAssetUrlsOption: TransformAssetUrlsOptions = {}
): string {
const returnValue = `"${url}"`
// same logic as in transform-require.js
const firstChar = url.charAt(0)
if (firstChar === '~') {
const secondChar = url.charAt(1)
url = url.slice(secondChar === '/' ? 2 : 1)
}
if (isExternalUrl(url) || isDataUrl(url) || firstChar === '#') {
return returnValue
}
const uriParts = parseUriParts(url)
if (transformAssetUrlsOption.base) {
// explicit base - directly rewrite the url into absolute url
// does not apply to absolute urls or urls that start with `@`
// since they are aliases
if (firstChar === '.' || firstChar === '~') {
// when packaged in the browser, path will be using the posix-
// only version provided by rollup-plugin-node-builtins.
return `"${(path.posix || path).join(
transformAssetUrlsOption.base,
uriParts.path + (uriParts.hash || '')
)}"`
}
}
if (
transformAssetUrlsOption.includeAbsolute ||
firstChar === '.' ||
firstChar === '~' ||
firstChar === '@'
) {
if (!uriParts.hash) {
return `require("${url}")`
} else {
// support uri fragment case by excluding it from
// the require and instead appending it as string;
// assuming that the path part is sufficient according to
// the above caseing(t.i. no protocol-auth-host parts expected)
return `require("${uriParts.path}") + "${uriParts.hash}"`
}
}
return returnValue
}
/**
* vuejs/component-compiler-utils#22 Support uri fragment in transformed require
* @param urlString an url as a string
*/
function parseUriParts(urlString: string): UrlWithStringQuery {
// initialize return value
const returnValue: UrlWithStringQuery = uriParse('')
if (urlString) {
// A TypeError is thrown if urlString is not a string
// @see https://nodejs.org/api/url.html#url_url_parse_urlstring_parsequerystring_slashesdenotehost
if ('string' === typeof urlString) {
// check is an uri
return uriParse(urlString) // take apart the uri
}
}
return returnValue
}
const externalRE = /^(https?:)?\/\//
function isExternalUrl(url: string): boolean {
return externalRE.test(url)
}
const dataUrlRE = /^\s*data:/i
function isDataUrl(url: string): boolean {
return dataUrlRE.test(url)
}
+69
View File
@@ -0,0 +1,69 @@
import { CompilerOptions, CompiledResult } from 'types/compiler'
import { SFCDescriptor } from './parseComponent'
export interface StartOfSourceMap {
file?: string
sourceRoot?: string
}
export interface RawSourceMap extends StartOfSourceMap {
version: string
sources: string[]
names: string[]
sourcesContent?: string[]
mappings: string
}
export interface TemplateCompiler {
parseComponent(source: string, options?: any): SFCDescriptor
compile(template: string, options: CompilerOptions): CompiledResult
ssrCompile(template: string, options: CompilerOptions): CompiledResult
}
export const enum BindingTypes {
/**
* returned from data()
*/
DATA = 'data',
/**
* declared as a prop
*/
PROPS = 'props',
/**
* a local alias of a `<script setup>` destructured prop.
* the original is stored in __propsAliases of the bindingMetadata object.
*/
PROPS_ALIASED = 'props-aliased',
/**
* a let binding (may or may not be a ref)
*/
SETUP_LET = 'setup-let',
/**
* a const binding that can never be a ref.
* these bindings don't need `unref()` calls when processed in inlined
* template expressions.
*/
SETUP_CONST = 'setup-const',
/**
* a const binding that does not need `unref()`, but may be mutated.
*/
SETUP_REACTIVE_CONST = 'setup-reactive-const',
/**
* a const binding that may be a ref.
*/
SETUP_MAYBE_REF = 'setup-maybe-ref',
/**
* bindings that are guaranteed to be refs
*/
SETUP_REF = 'setup-ref',
/**
* declared by other options, e.g. computed, inject
*/
OPTIONS = 'options'
}
export type BindingMetadata = {
[key: string]: BindingTypes | undefined
} & {
__isScriptSetup?: boolean
}
+16
View File
@@ -0,0 +1,16 @@
const hasWarned: Record<string, boolean> = {}
export function warnOnce(msg: string) {
const isNodeProd =
typeof process !== 'undefined' && process.env.NODE_ENV === 'production'
if (!isNodeProd && !hasWarned[msg]) {
hasWarned[msg] = true
warn(msg)
}
}
export function warn(msg: string) {
console.warn(
`\x1b[1m\x1b[33m[@vue/compiler-sfc]\x1b[0m\x1b[33m ${msg}\x1b[0m\n`
)
}