* Copyright (c) 2023 - present TinyEngine Authors.
* Copyright (c) 2023 - present Huawei Cloud Computing Technologies Co., Ltd.
*
* Use of this source code is governed by an MIT-style license.
*
* THE OPEN SOURCE SOFTWARE IN THIS PRODUCT IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL,
* BUT WITHOUT ANY WARRANTY, WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR
* A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS.
*
*/
import { parse } from '@babel/parser'
import generateLib from '@babel/generator'
import traverseLib from '@babel/traverse'
import template from '@babel/template'
import {
wrapEntryFuncNode,
COMMON_PACKAGE_NAME,
CALLENTRY,
USE_COMPILE,
METADATANAME,
isCallEntryFile,
isCompileFile,
getMeataPath,
wrapExportComp,
vueLifeHook,
wrapHookCall,
getModuleId
} from './utils.js'
const traverse = traverseLib.default || traverseLib
const generate = generateLib.default || generateLib
function handleFunctionExpression(state) {
return function (path) {
const parentNode = path.parentPath || {}
const functionName = parentNode.node?.id?.name
if (functionName) {
state.ArrowOrFunctionExpression.push(functionName)
wrapEntryFuncNode({
path,
functionName,
varName: state.varName,
state
})
}
}
}
function handleImportDeclaration(state) {
return function (path) {
const depName = path.node?.source?.value
if (depName === 'vue') {
const specifiers = path.node.specifiers
specifiers?.forEach((importSpecifier) => {
const { imported, local } = importSpecifier
const hookName = vueLifeHook.find((name) => imported.name === name)
if (hookName) {
state.hooksName[local.name] = hookName
}
state.noUseVars.push(local.name)
})
} else if (depName === '@opentiny/vue') {
const specifiers = path.node.specifiers
specifiers?.forEach((importSpecifier) => {
const { local } = importSpecifier
state.noUseVars.push(local.name)
})
}
}
}
* 处理变量声明节点
* @param {Object} state - 状态对象,用于存储变量声明信息
* @returns {Function} 返回处理变量声明的函数
*/
function handleVariableDeclaration(state) {
return function (path) {
path.node.declarations?.forEach((val) => {
const name = val.id.name
const block = path.scope.block
if (!state.varDeclartion.has(block)) {
const arr = [name]
state.varDeclartion.set(block, arr)
} else {
const arr = state.varDeclartion.get(block)
arr.push(name)
}
})
}
}
function handleExpressionStatement(state) {
return function (path) {
const { hooksName, varName, hooksIndex } = state
const callName = path.node.expression?.callee?.name
const hookName = hooksName[callName]
if (!hookName) {
return
}
let hookIndex
if (hooksIndex[hookName]) {
hookIndex = hooksIndex[hookName]
hooksIndex[hookName] = hookIndex + 1
} else {
hooksIndex[hookName] = 1
hookIndex = 0
}
const functionName = `${hookName}[${hookIndex}]`
wrapHookCall({
path,
varName,
hooksName,
functionName,
callName,
state
})
}
}
function handleProgram(state, metaPath) {
return function (path) {
const code = path.toString()
state.moduleId = getModuleId(code)
const metaData = path.scope.generateUid(METADATANAME)
state.varName[METADATANAME] = metaData
path.node.body.unshift(template.statement(`import ${metaData} from '${metaPath}'`)())
const callEntry = path.scope.generateUid(CALLENTRY)
const useCompile = path.scope.generateUid(USE_COMPILE)
state.varName[CALLENTRY] = callEntry
state.varName[USE_COMPILE] = useCompile
path.node.body.unshift(
template.statement(
`import {
${CALLENTRY} as ${callEntry},
${USE_COMPILE} as ${useCompile}
} from '${COMMON_PACKAGE_NAME}'`
)()
)
}
}
function handleExportDefaultDeclaration(state) {
return function (path) {
const comment = path.node.leadingComments
if (!comment) {
return
}
const lastComment = comment[comment.length - 1].value
if (lastComment.includes('metaComponent')) {
wrapExportComp({ path, varName: state.varName, lastComment })
}
}
}
export const transform = (code, id) => {
const isCallEntry = isCallEntryFile(code)
const isCompile = isCompileFile(code)
if (!isCallEntry && !isCompile) {
return
}
const state = {
varName: {},
hooksName: {},
hooksIndex: {},
varDeclartion: new Map(),
moduleId: '',
noUseVars: [],
fileId: id,
ArrowOrFunctionExpression: []
}
const metaPath = getMeataPath(id)
if (!metaPath) {
console.log(`${id}: 找不到对应的meta.js`)
return
}
const resultAst = parse(code, {
sourceType: 'module',
plugins: ['typescript', 'jsx']
})
traverse(resultAst, {
'ArrowFunctionExpression|FunctionExpression': handleFunctionExpression(state),
ImportDeclaration: handleImportDeclaration(state),
VariableDeclaration: handleVariableDeclaration(state),
ExpressionStatement: handleExpressionStatement(state),
Program: handleProgram(state, metaPath),
ExportDefaultDeclaration: handleExportDefaultDeclaration(state)
})
return generate(resultAst).code || ''
}