update
This commit is contained in:
+423
@@ -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
|
||||
}
|
||||
+1905
File diff suppressed because it is too large
Load Diff
+147
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
}
|
||||
+84
@@ -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
|
||||
}
|
||||
+76
@@ -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
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
+81
@@ -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
@@ -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
@@ -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`
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user