@mpxjs/webpack-plugin
Version:
mpx compile core
1,201 lines (1,114 loc) • 32.7 kB
JavaScript
const babylon = require('@babel/parser')
const MagicString = require('magic-string')
const { SourceMapConsumer, SourceMapGenerator } = require('source-map')
const traverse = require('@babel/traverse').default
const t = require('@babel/types')
const formatCodeFrame = require('@babel/code-frame')
const parseRequest = require('../utils/parse-request')
// Special compiler macros
const DEFINE_PROPS = 'defineProps'
const DEFINE_OPTIONS = 'defineOptions'
const DEFINE_EXPOSE = 'defineExpose'
const WITH_DEFAULTS = 'withDefaults'
const MPX_CORE = '@mpxjs/core'
const BindingTypes = {
/**
* 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'
}
function compileScriptSetup (
scriptSetup,
ctorType,
filePath
) {
const content = scriptSetup.content
const _s = new MagicString(content)
const scriptBindings = Object.create(null)
const setupBindings = Object.create(null)
const userImportAlias = Object.create(null)
const userImports = Object.create(null)
// const genSourceMap = false
let startOffset = 0
let endOffset = 0
let hasDefinePropsCall = false
let hasDefineOptionsCall = false
let hasDefineExposeCall = false
let propsRuntimeDecl
let propsRuntimeDefaults
let propsTypeDeclRaw
let propsTypeDecl
let propsIdentifier
let optionsRuntimeDecl
let exposeRuntimeDecl
const scriptSetupLang = scriptSetup && scriptSetup.lang
const isTS =
scriptSetupLang === 'ts' ||
scriptSetupLang === 'tsx'
const plugins = []
if (isTS) plugins.push('typescript', 'decorators-legacy')
// props/emits declared via types
const typeDeclaredProps = {}
const declaredTypes = {}
function error (
msg,
node,
end
) {
if (node) {
throw new Error(
`[@mpxjs/webpack-plugin script-setup-compiler] ${msg}\n\n${filePath}\n${formatCodeFrame(
content,
node.start + startOffset,
end
)}`
)
} else {
throw new Error(
`[@mpxjs/webpack-plugin script-setup-compiler] ${msg}\n\n${filePath}\n`
)
}
}
function registerUserImport (
source,
local,
imported = false,
isType,
isFromSetup = true,
needTemplateUsageCheck
) {
if (source === MPX_CORE && imported) {
userImportAlias[imported] = local
}
userImports[local] = {
isType,
imported: imported || 'default',
source,
isFromSetup
}
}
function processDefineExpose (node, declId) {
if (!isCallOf(node, DEFINE_EXPOSE)) {
return false
}
if (hasDefineExposeCall) {
error(`duplicate ${DEFINE_EXPOSE}() call`, node)
}
hasDefineExposeCall = true
exposeRuntimeDecl = node.arguments[0]
if (node.typeParameters) {
error(`${DEFINE_EXPOSE} is not support type parameters`, node)
}
if (declId) {
console.warn(`${DEFINE_OPTIONS} no return value, please check it`, declId)
}
return true
}
function processDefineOptions (node, declId) {
if (!isCallOf(node, DEFINE_OPTIONS)) {
return false
}
if (hasDefineOptionsCall) {
error(`duplicate ${DEFINE_OPTIONS}() call`, node)
}
hasDefineOptionsCall = true
const arg0 = node.arguments[0]
if (arg0.type === 'ObjectExpression') {
optionsRuntimeDecl = arg0.properties
} else {
error(`${DEFINE_OPTIONS} input parameter must be an object`)
}
if (node.typeParameters) {
error(`${DEFINE_OPTIONS} is not support type parameters`, node)
}
if (declId) {
console.warn(`${DEFINE_OPTIONS} no return value, please check it`, declId)
}
return true
}
function processDefineProps (node, declId) {
if (!isCallOf(node, DEFINE_PROPS)) {
return false
}
if (hasDefinePropsCall) {
error(`duplicate ${DEFINE_PROPS}() call`, node)
}
hasDefinePropsCall = true
propsRuntimeDecl = node.arguments[0]
/**
* defineProps<{
* foo: string
* bar?: number
* }>()
*/
if (node.typeParameters) {
if (propsRuntimeDecl) {
error(
`${DEFINE_PROPS}() cannot accept both type and non-type arguments ` +
'at the same time. Use one or the other.',
node
)
}
propsTypeDeclRaw = node.typeParameters.params[0]
propsTypeDecl = resolveQualifiedType(
propsTypeDeclRaw,
node => node.type === 'TSFunctionType' || node.type === 'TSTypeLiteral'
)
if (!propsTypeDecl) {
error(
`type argument passed to ${DEFINE_PROPS}() must be a literal type, ` +
'or a reference to an interface or literal type.',
propsTypeDeclRaw
)
}
}
// 处理 VariableDeclaration declarations id
if (declId) {
propsIdentifier = content.slice(declId.start, declId.end)
}
return true
}
function processWithDefaults (node, declId) {
if (!isCallOf(node, WITH_DEFAULTS)) {
return false
}
if (processDefineProps(node.arguments[0], declId)) {
if (propsRuntimeDecl) {
error(
`${WITH_DEFAULTS} can only be used with type-based ` +
`${DEFINE_PROPS} declaration.`,
node
)
}
propsRuntimeDefaults = node.arguments[1]
if (
!propsRuntimeDefaults ||
propsRuntimeDefaults.type !== 'ObjectExpression'
) {
error(
`The 2nd argument of ${WITH_DEFAULTS} must be an object literal.`,
propsRuntimeDefaults || node
)
}
} else {
error(
`${WITH_DEFAULTS}' first argument must be a ${DEFINE_PROPS} call.`,
node.arguments[0] || node
)
}
return true
}
function resolveQualifiedType (node, qualifier) {
if (qualifier(node)) {
return node
}
// TSTypeReference defineProps<Props>()
if (node.type === 'TSTypeReference' && node.typeName.type === 'Identifier') {
const refName = node.typeName.name
const isQualifiedType = (node) => {
// interface Props TSInterfaceDeclaration
if (node.type === 'TSInterfaceDeclaration' && node.id.name === refName) {
return node.body
}
// type Props TSTypeAliasDeclaration
if (node.type === 'TSTypeAliasDeclaration' && node.id.name === refName && qualifier(node.typeAnnotation)) {
return node.typeAnnotation
}
// export type Props
if (node.type === 'ExportNamedDeclaration' && node.declaration) {
return isQualifiedType(node.declaration)
}
}
const body = scriptSetupAst.program.body
for (const node of body) {
const qualified = isQualifiedType(node)
if (qualified) {
return qualified
}
}
}
}
function hasStaticWithDefaults () {
return (
propsRuntimeDefaults &&
propsRuntimeDefaults.type === 'ObjectExpression' &&
propsRuntimeDefaults.properties.every(
node =>
(node.type === 'ObjectProperty' && !node.computed) ||
node.type === 'ObjectMethod'
)
)
}
function genRuntimeProps (props) {
const keys = Object.keys(props)
if (!keys.length) {
return ''
}
const hasStaticDefaults = hasStaticWithDefaults()
const scriptSetupSource = content
const propsDecls = `{
${keys.map(key => {
let defaultString
if (hasStaticDefaults) {
const prop = propsRuntimeDefaults.properties.find(
node => node.key.name === key
)
if (prop) {
if (prop.type === 'ObjectProperty') {
defaultString = `value: ${scriptSetupSource.slice(
prop.value.start,
prop.value.end
)}`
}
}
}
const { type } = props[key]
const propRuntimeTypes = toRuntimeTypeString(type)
if (propRuntimeTypes.optionalTypes) {
return `${key}: { type: ${propRuntimeTypes.type}, optionalTypes: ${propRuntimeTypes.optionalTypes}${
defaultString ? `, ${defaultString}` : ''
} }`
} else {
return `${key}: { type: ${propRuntimeTypes.type}${
defaultString ? `, ${defaultString}` : ''
} }`
}
}).join(',\n ')}
}`
return `\n properties: ${propsDecls},`
}
function checkInvalidScopeReference (node, method) {
if (!node) return
walkIdentifiers(node, id => {
if (setupBindings[id.name]) {
error(
`\`${method}()\` in <script setup> cannot reference locally ` +
'declared variables because it will be hoisted outside of the ' +
'setup() function.',
id
)
}
})
}
// 1. parse <script setup> and walk over top level statements
const scriptSetupAst = babylon.parse(content, {
plugins: [
...plugins,
'topLevelAwait'
],
sourceType: 'module'
})
for (const node of scriptSetupAst.program.body) {
const start = node.start
let end = node.end
// 定位comment,例如 var a= 1; // a为属性
if (node.trailingComments && node.trailingComments.length > 0) {
const lastCommentNode =
node.trailingComments[node.trailingComments.length - 1]
end = lastCommentNode.end
}
// locate the end of whitespace between this statement and the next
while (end <= content.length) {
if (!/\s/.test(content.charAt(end))) {
break
}
end++
}
if (node.type === 'ImportDeclaration') {
// import declarations are moved to top
if (start !== 0) {
_s.move(start, end, 0)
}
// dedupe imports
let removed = 0
const removeSpecifier = (i) => {
const removeLeft = i > removed
removed++
const current = node.specifiers[i]
const next = node.specifiers[i + 1]
_s.remove(
removeLeft ? node.specifiers[i - 1].end + startOffset : current.start + startOffset,
next && !removeLeft ? next.start + startOffset : current.end + startOffset
)
}
// 判断defineProps等是否有被引入
// record imports for dedupe
for (let i = 0; i < node.specifiers.length; i++) {
const specifier = node.specifiers[i]
let imported = specifier.type === 'ImportSpecifier' && specifier.imported.type === 'Identifier' && specifier.imported.name
if (specifier.type === 'ImportNamespaceSpecifier') {
imported = '*'
}
const local = specifier.local.name
const source = node.source.value
const existing = userImports[local]
if (source === MPX_CORE && isCompilerMacro(imported)) {
console.warn(`${imported} is a compiler macro and no longer needs to be imported.`)
// 不需要经过
removeSpecifier(i)
} else if (existing) {
if (existing.source === source && existing.imported === imported) {
// already imported in <script setup>, dedupe
removeSpecifier(i)
} else {
error('different imports aliased to same local name.', specifier)
}
} else {
registerUserImport(
node.source.value,
specifier.local.name,
imported,
node.importKind === 'type' ||
(specifier.type === 'ImportSpecifier' &&
specifier.importKind === 'type'),
true
)
}
}
if (node.specifiers.length && removed === node.specifiers.length) {
_s.remove(node.start, node.end)
}
startOffset = node.loc.end.index
}
// process defineProps defineOptions 等编译宏
if (node.type === 'ExpressionStatement') {
if (
processDefineProps(node.expression) ||
processDefineOptions(node.expression) ||
processDefineExpose(node.expression) ||
processWithDefaults(node.expression)
) {
_s.remove(node.start, node.end)
}
}
if (node.type === 'VariableDeclaration' && !node.declare) {
const total = node.declarations.length
let left = total
for (let i = 0; i < total; i++) {
const decl = node.declarations[i]
if (decl.init) {
const isDefineProps = processDefineProps(decl.init, decl.id) || processWithDefaults(decl.init, decl.id)
const isDefineOptions = processDefineOptions(decl.init, decl.id)
if (isDefineProps || isDefineOptions) {
if (left === 1) {
_s.remove(node.start, node.end)
} else {
// let a,b = defineProps({})
let start = decl.start
let end = decl.end
if (i < total - 1) {
// not the last one, locate the start of the next
end = node.declarations[i + 1].start
} else {
// last one, locate the end of the prev
start = node.declarations[i - 1].end
}
_s.remove(start, end)
left--
}
}
}
}
}
// walk declarations to record declared bindings
if (
(node.type === 'VariableDeclaration' ||
node.type === 'FunctionDeclaration' ||
node.type === 'ClassDeclaration') &&
!node.declare
) {
walkDeclaration(node, setupBindings, userImportAlias)
}
// walk statements & named exports / variable declarations for top level
// await,when find await will throw error
// { await someFunc ()}
if (
(node.type === 'VariableDeclaration' && !node.declare) ||
node.type.endsWith('Statement')
) {
const scope = [scriptSetupAst.body]
traverse(node, {
enter (path) {
if (isFunctionType(path.node)) {
path.skip()
}
if (t.isBlockStatement(path.node)) {
scope.push(path.node.body)
}
if (t.isAwaitExpression(path.node)) {
// error
error('if the await expression is an expression statement and is in the root scope or is not the first statement in a nested block scope, this is not support in miniprogram')
}
},
exit (path) {
if (t.isBlockStatement(path.node)) scope.pop()
},
noScope: true
})
}
if (
(node.type === 'ExportNamedDeclaration' && node.exportKind !== 'type') ||
node.type === 'ExportAllDeclaration' ||
node.type === 'ExportDefaultDeclaration'
) {
error(
'<script setup> cannot contain ES module exports. ',
node
)
}
// working with TS code
if (isTS) {
if (
node.type.startsWith('TS') ||
(node.type === 'ExportNamedDeclaration' &&
node.exportKind === 'type') ||
(node.type === 'VariableDeclaration' && node.declare)
) {
recordType(node, declaredTypes)
if (node.start !== 0) {
_s.move(node.start, node.end + 1, 0)
}
}
}
endOffset = node.loc.end.index
}
// 2. extract runtime props code from setup context type
if (propsTypeDecl) {
// TS defineProps 提取Props
extractRuntimeProps(propsTypeDecl, typeDeclaredProps, declaredTypes)
}
// 3. check useOptions args to make sure it doesn't reference setup scope
checkInvalidScopeReference(propsRuntimeDecl, DEFINE_PROPS)
checkInvalidScopeReference(optionsRuntimeDecl, DEFINE_OPTIONS)
// 4. finalize setup() argument signature
let args = '__props'
if (propsTypeDecl) {
args += ': any'
}
// inject user assignment of props
if (propsIdentifier) {
_s.prependLeft(
startOffset,
`\nconst ${propsIdentifier} = __props${
propsTypeDecl ? ' as any' : ''
}\n`)
}
// args 添加 __context
args += `, __context${isTS ? ': any' : ''}`
// 5. generate return statement
let returned
if (hasDefineExposeCall && exposeRuntimeDecl) {
const declCode = content.slice(exposeRuntimeDecl.start, exposeRuntimeDecl.end).trim()
returned = `${declCode}`
} else {
const allBindings = {
...scriptBindings,
...setupBindings
}
for (const key in userImports) {
// import 进的的变量或方法,非mpxjs/core中的,暂时都进行return
if (!userImports[key].isType && !userImportAlias[key]) {
allBindings[key] = true
}
}
returned = `{ ${Object.keys(allBindings).join(', ')} }`
}
_s.appendRight(endOffset, `\nreturn ${returned}\n}\n\n`)
// 6. finalize default export
let runtimeOptions = ''
if (propsRuntimeDecl) {
const declCode = content.slice(propsRuntimeDecl.start, propsRuntimeDecl.end).trim()
runtimeOptions += `\n properties: ${declCode},`
} else if (propsTypeDecl) {
runtimeOptions += genRuntimeProps(typeDeclaredProps)
}
if (optionsRuntimeDecl) {
for (const node of optionsRuntimeDecl) {
if (node.key.name === 'properties' && hasDefinePropsCall) {
console.warn(`${DEFINE_PROPS} has been called, ${DEFINE_OPTIONS} set properties will be ignored`)
} else {
const declCode = content.slice(node.value.start, node.value.end).trim()
runtimeOptions += `\n ${node.key.name}: ${declCode},`
}
}
}
// 添加defineExpose强制配置校验
if (!hasDefineExposeCall) {
error('Mpx script setup must define the variables for return using defineExpose, see details: https://mpxjs.cn/guide/composition-api/composition-api.html#defineexpose')
}
// import {createComponent} from '@mpxjs/core' 添加是否已有import判断
const ctor = getCtor(ctorType)
_s.prependLeft(
startOffset,
`\nimport {${ctor}} from '${MPX_CORE}'\n${getCtor(ctorType)} ({${runtimeOptions}\n ` +
`setup(${args}) {\n const useContext = () => { return __context }\n`
)
_s.appendRight(endOffset, '})')
return {
content: _s.toString(),
map: _s.generateMap({
source: filePath,
hires: true,
includeContent: true
})
}
}
function isCallOf (
node,
test
) {
return !!(
node &&
node.type === 'CallExpression' &&
node.callee.type === 'Identifier' &&
(typeof test === 'string'
? node.callee.name === test
: test(node.callee.name))
)
}
function canNeverBeRef (
node,
userReactiveImport
) {
if (isCallOf(node, userReactiveImport)) {
return true
}
switch (node.type) {
case 'UnaryExpression':
case 'BinaryExpression':
case 'ArrayExpression':
case 'ObjectExpression':
case 'FunctionExpression':
case 'ArrowFunctionExpression':
case 'UpdateExpression':
case 'ClassExpression':
case 'TaggedTemplateExpression':
return true
case 'SequenceExpression':
return canNeverBeRef(
node.expressions[node.expressions.length - 1],
userReactiveImport
)
default:
if (node.type.endsWith('Literal')) {
return true
}
return false
}
}
function isCompilerMacro (c) {
return c === DEFINE_PROPS || c === DEFINE_OPTIONS || c === DEFINE_EXPOSE
}
function registerBinding (
bindings,
node,
type
) {
bindings[node.name] = type
}
function walkDeclaration (
node,
bindings,
userImportAlias
) {
if (node.type === 'VariableDeclaration') {
const isConst = node.kind === 'const'
for (const { id, init } of node.declarations) {
const isDefineCall = !!(
isConst &&
isCallOf(
init,
isCompilerMacro
)
)
if (id.type === 'Identifier') {
let bindingType
const userReactiveBinding = userImportAlias.reactive || 'reactive'
// 是否是 reactive init
if (isCallOf(init, userReactiveBinding)) {
bindingType = isConst
? BindingTypes.SETUP_REACTIVE_CONST
: BindingTypes.SETUP_LET
} else if (
isDefineCall ||
(isConst && canNeverBeRef(init, userReactiveBinding))
) {
bindingType = isCallOf(init, DEFINE_PROPS)
? BindingTypes.SETUP_REACTIVE_CONST
: BindingTypes.SETUP_CONST
} else if (isConst) {
if (isCallOf(init, userImportAlias.ref || 'ref')) {
bindingType = BindingTypes.SETUP_REF
} else {
bindingType = BindingTypes.SETUP_MAYBE_REF
}
} else {
bindingType = BindingTypes.SETUP_LET
}
registerBinding(bindings, id, bindingType)
} else {
if (isCallOf(init, DEFINE_PROPS)) {
// skip walking props destructure
return
}
if (id.type === 'ObjectPattern') {
walkObjectPattern(id, bindings, isConst, isDefineCall)
} else if (id.type === 'ArrayPattern') {
walkArrayPattern(id, bindings, isConst, isDefineCall)
}
}
}
} else if (
node.type === 'TSEnumDeclaration' ||
node.type === 'FunctionDeclaration' ||
node.type === 'ClassDeclaration'
) {
bindings[node.id.name] = BindingTypes.SETUP_CONST
}
}
function walkObjectPattern (
node,
bindings,
isConst,
isDefineCall = false
) {
for (const p of node.properties) {
if (p.type === 'ObjectProperty') {
if (p.key.type === 'Identifier' && p.key === p.value) {
// shorthand: const { x } = ...
const type = isDefineCall ? BindingTypes.SETUP_CONST : isConst ? BindingTypes.SETUP_MAYBE_REF : BindingTypes.SETUP_LET
registerBinding(bindings, p.key, type)
} else {
walkPattern(p.value, bindings, isConst, isDefineCall)
}
} else {
// ...rest
const type = isConst ? BindingTypes.SETUP_CONST : BindingTypes.SETUP_LET
registerBinding(bindings, p.argument, type)
}
}
}
function walkArrayPattern (
node,
bindings,
isConst,
isDefineCall = false
) {
for (const e of node.elements) {
e && walkPattern(e, bindings, isConst, isDefineCall)
}
}
function walkPattern (
node,
bindings,
isConst,
isDefineCall = false
) {
if (node.type === 'Identifier') {
const type = isDefineCall
? BindingTypes.SETUP_CONST
: isConst
? BindingTypes.SETUP_MAYBE_REF
: BindingTypes.SETUP_LET
registerBinding(bindings, node, type)
} else if (node.type === 'RestElement') {
const type = isConst ? BindingTypes.SETUP_CONST : BindingTypes.SETUP_LET
registerBinding(bindings, node.argument, type)
} else if (node.type === 'ObjectPattern') {
walkObjectPattern(node, bindings, isConst)
} else if (node.type === 'ArrayPattern') {
walkArrayPattern(node, bindings, isConst)
} else if (node.type === 'AssignmentPattern') {
// const {propsA:c=2} = defineProps
if (node.left.type === 'Identifier') {
const type = isDefineCall
? BindingTypes.SETUP_CONST
: isConst
? BindingTypes.SETUP_MAYBE_REF
: BindingTypes.SETUP_LET
registerBinding(bindings, node.left, type)
} else {
walkPattern(node.left, bindings, isConst)
}
}
}
function recordType (node, declaredTypes) {
if (node.type === 'TSInterfaceDeclaration') {
declaredTypes[node.id.name] = ['Object']
} else if (node.type === 'TSTypeAliasDeclaration') {
declaredTypes[node.id.name] = inferRuntimeType(
node.typeAnnotation,
declaredTypes
)
} else if (node.type === 'ExportNamedDeclaration' && node.declaration) {
recordType(node.declaration, declaredTypes)
}
}
function toRuntimeTypeString (types) {
return {
type: types[0],
optionalTypes: types.length > 1 ? `[${types.slice(1).join(', ')}]` : undefined
}
}
// extract runtime props from ts types
function extractRuntimeProps (
node,
props,
declaredTypes
) {
const members = node.type === 'TSTypeLiteral' ? node.members : node.body
for (const m of members) {
if (
(m.type === 'TSPropertySignature' || m.type === 'TSMethodSignature') &&
m.key.type === 'Identifier'
) {
let type
if (m.type === 'TSMethodSignature') {
type = ['Function']
} else if (m.typeAnnotation) {
type = inferRuntimeType(m.typeAnnotation.typeAnnotation, declaredTypes)
}
props[m.key.name] = {
key: m.key.name,
required: !m.optional,
type: type || ['null']
}
}
}
}
function inferRuntimeType (
node,
declaredTypes
) {
switch (node.type) {
case 'TSStringKeyword':
return ['String']
case 'TSNumberKeyword':
return ['Number']
case 'TSBooleanKeyword':
return ['Boolean']
case 'TSObjectKeyword':
return ['Object']
case 'TSFunctionType':
return ['Function']
case 'TSArrayType':
case 'TSTupleType':
return ['Array']
case 'TSLiteralType':
switch (node.literal.type) {
case 'StringLiteral':
return ['String']
case 'BooleanLiteral':
return ['Boolean']
case 'NumericLiteral':
case 'BigIntLiteral':
return ['Number']
default:
return ['null']
}
case 'TSTypeReference':
if (node.typeName.type === 'Identifier') {
if (declaredTypes[node.typeName.name]) {
return declaredTypes[node.typeName.name]
}
switch (node.typeName.name) {
case 'Array':
case 'Function':
case 'Object':
case 'Set':
case 'Map':
case 'WeakSet':
case 'WeakMap':
case 'Date':
case 'Promise':
return [node.typeName.name]
case 'Record':
case 'Partial':
case 'Readonly':
case 'Pick':
case 'Omit':
case 'Exclude':
case 'Extract':
case 'Required':
case 'InstanceType':
return ['Object']
}
}
return ['null']
case 'TSParenthesizedType':
return inferRuntimeType(node.typeAnnotation, declaredTypes)
case 'TSUnionType':
return [
...new Set(
[].concat(
...(node.types.map(t => inferRuntimeType(t, declaredTypes)))
)
)
]
case 'TSIntersectionType':
return ['Object']
default:
return ['null']
}
}
function walkIdentifiers (
root,
onIdentifier,
parentStack = [],
knownIds = Object.create(null)
) {
const rootExp =
root.type === 'Program' &&
root.body[0].type === 'ExpressionStatement' &&
root.body[0].expression
traverse(root, {
enter (path) {
const { node, parent } = path
if (
parent &&
parent.type.startsWith('TS') &&
parent.type !== 'TSAsExpression' &&
parent.type !== 'TSNonNullExpression' &&
parent.type !== 'TSTypeAssertion'
) {
return path.skip()
}
if (node.type === 'Identifier') {
const isLocal = knownIds[node.name]
const isRefed = isReferencedIdentifier(node, parent, parentStack)
if (isRefed && !isLocal) {
onIdentifier(node, parent, parentStack, isRefed, isLocal)
}
} else if (
node.type === 'ObjectProperty' &&
parent.type === 'ObjectPattern'
) {
node.inPattern = true
} else if (isFunctionType(node)) {
walkFunctionParams(node, id => markScopeIdentifier(node, id, knownIds))
} else if (node.type === 'BlockStatement') {
walkBlockDeclarations(node, id =>
markScopeIdentifier(node, id, knownIds)
)
}
},
exit (path) {
const { node, parent } = path
parent && parentStack.pop()
if (node !== rootExp && node.scopeIds) {
for (const id of node.scopeIds) {
knownIds[id]--
if (knownIds[id] === 0) {
delete knownIds[id]
}
}
}
},
noScope: true
})
}
function markScopeIdentifier (
node,
child,
knownIds
) {
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)
}
function walkFunctionParams (
node,
onIdent
) {
for (const p of node.params) {
for (const id of extractIdentifiers(p)) {
onIdent(id)
}
}
}
function walkBlockDeclarations (
block,
onIndent
) {
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)) {
onIndent(id)
}
}
} else if (
stmt.type === 'FunctionDeclaration' ||
stmt.type === 'ClassDeclaration'
) {
if (stmt.declare || !stmt.id) continue
onIndent(stmt.id)
}
}
}
function isReferencedIdentifier (
id,
parent,
parentStack
) {
if (!parent) {
return true
}
// is a special keyword but parsed as identifier
if (id.name === 'arguments') {
return false
}
if (t.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
}
function extractIdentifiers (
param,
nodes
) {
switch (param.type) {
case 'Identifier':
nodes.push(param)
break
case 'MemberExpression': {
let object = 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 isInDestructureAssignment (
parent,
parentStack
) {
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
}
function isFunctionType (node) {
return /Function(?:Expression|Declaration)$|Method$/.test(node.type)
}
function getCtor (ctorType) {
let ctor = 'createComponent'
switch (ctorType) {
case 'app':
ctor = 'createApp'
break
case 'page':
ctor = 'createPage'
break
}
return ctor
}
module.exports = async function (content, sourceMap) {
const { queryObj } = parseRequest(this.resource)
const { ctorType, lang } = queryObj
const filePath = this.resourcePath
const callback = this.async()
let finalSourceMap = null
const {
content: callbackContent,
map
} = compileScriptSetup({
content,
lang
}, ctorType, filePath)
finalSourceMap = map
if (sourceMap) {
const compiledMapConsumer = await new SourceMapConsumer(map)
const compiledMapGenerator = SourceMapGenerator.fromSourceMap(compiledMapConsumer)
const originalConsumer = await new SourceMapConsumer(sourceMap)
compiledMapGenerator.applySourceMap(
originalConsumer,
filePath // 需要确保与原始映射的source路径一致
)
finalSourceMap = compiledMapGenerator.toJSON()
}
callback(null, callbackContent, finalSourceMap)
}