tiny-engine/packages/build/vite-plugin-generate-comments/src/utils.js

249 lines
7.7 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* Copyright (c) 2024 - present TinyEngine Authors.
* Copyright (c) 2024 - 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 template from '@babel/template'
import path from 'node:path'
import fs from 'node:fs'
export const CALLENTRY = 'callEntry'
export const BEFORE_CALLENTRY = 'beforeCallEntry'
export const AFTER_CALLENTRY = 'afterCallEntry'
export const USE_COMPILE = 'useCompile'
export const METADATANAME = 'metaData'
export const COMMON_PACKAGE_NAME = '@opentiny/tiny-engine-entry'
export const vueLifeHook = [
'onMounted',
'onUpdated',
'onUnmounted',
'onBeforeMount',
'onBeforeUpdate',
'onBeforeUnmount',
'onActivated',
'onDeactivated'
]
const callEntryExp = /\/\*\s*metaService/
const compileExp = /\/\*\s*metaComponent/
const statement = (code) => template.statement(code, { placeholderPattern: false })
export const isCallEntryFile = (code) => {
return callEntryExp.test(code)
}
export const isCompileFile = (code) => {
return compileExp.test(code)
}
export const getModuleId = (str) => {
const [, moduleId = ''] = str.match(/\/\*\s*metaService: \s*(.+?)\s*\*\//) || []
return moduleId
}
// 将注释中的参数提取出来,并组合成目前参数格式
export const getEntryParam = ({ functionName = '', syncVars, asyncVars, state }) => {
const { varName, moduleId, noUseVars } = state
const metaData = varName[METADATANAME]
const id = moduleId ? `'${moduleId}.${functionName}'` : `\`\${${metaData}.id}.${functionName}\``
const syncVarsKey = Object.keys(syncVars).filter((key) => !noUseVars.includes(key))
const asyncVarsKey = Object.keys(asyncVars).filter((key) => !noUseVars.includes(key) && !syncVarsKey.includes(key))
const ctx = ` () => {
let asyncVars = {}
const syncVars = {${syncVarsKey.join(',')}}
try {
asyncVars = { ${asyncVarsKey.join(',')} }
} catch {
return syncVars
}
return { ...syncVars, ...asyncVars }
}`
if (functionName) {
return `{ ${METADATANAME}: { id: ${id} }, ctx: ${ctx}}`
}
return `{ ${METADATANAME}: ${metaData} }`
}
const getParentVariableDeclaration = (path) => {
if (!path) {
return
}
if (path.type === 'VariableDeclaration' && path.parentPath.type !== 'ExportNamedDeclaration') {
return path
} else {
return getParentVariableDeclaration(path?.parentPath)
}
}
const generateBeforeAfterEntry = ({ path, beforeEntryAst, afterEntryAst }) => {
const parent = getParentVariableDeclaration(path)
if (parent) {
parent.insertBefore(beforeEntryAst)
parent.insertAfter(afterEntryAst)
}
}
export const getOuterBingdings = (path) => {
const outerBindings = {}
const allBindings = path.scope.getAllBindings()
const selfBindings = path.scope.bindings
Object.keys(allBindings).forEach((key) => {
if (allBindings[key] && !selfBindings[key]) {
outerBindings[key] = allBindings[key]
}
})
return outerBindings
}
// 获取当前上下文已经可以使用的scope变量
export const getValidBingdinngs = ({ path, state, functionName }) => {
const validBindings = {}
const { varDeclartion } = state
let varArr = []
let parentPath = path.parentPath
let block
while (parentPath) {
const newBlock = parentPath.scope.block
parentPath = parentPath.parentPath
if (newBlock === block) {
continue
}
block = newBlock
varArr = varArr.concat(varDeclartion.get(block))
}
const allBindings = path.scope.getAllBindings()
const selfBindings = path.scope.bindings
Object.keys(allBindings).forEach((key) => {
if (selfBindings[key]) {
return
}
const value = allBindings[key]
// 如果是变量定义,并且此时还没有初始化,则过滤掉
if ((['var', 'const', 'let'].includes(value.kind) && !varArr.includes(key)) || key === functionName) {
return
}
validBindings[key] = value
})
return validBindings
}
export const getModuleBindings = (path) => {
const moduleBindings = {}
const allBindings = path.scope.getAllBindings()
Object.keys(allBindings).forEach((key) => {
if (allBindings[key].kind === 'module') {
moduleBindings[key] = allBindings[key]
}
})
return moduleBindings
}
// 生成callEntry表达式并包裹当前函数如果有参与还需要处理参数
export const wrapEntryFuncNode = ({ path, functionName = '', varName, state }) => {
const syncVars = getValidBingdinngs({ path, state, functionName })
const asyncVars = getOuterBingdings(path)
const entryParam = getEntryParam({
functionName,
syncVars,
asyncVars,
varName,
state
})
const callEntry = varName[CALLENTRY]
const beforeCallEntry = varName[BEFORE_CALLENTRY]
const afterCallEntry = varName[AFTER_CALLENTRY]
const entryAst = statement(`${callEntry}(${entryParam})`)()
const beforeEntryAst = statement(`${beforeCallEntry}(${entryParam})`)()
const afterEntryAst = statement(`${afterCallEntry}(${entryParam})`)()
const resultNode = path.node
generateBeforeAfterEntry({ path, beforeEntryAst, afterEntryAst })
entryAst.expression.arguments.unshift(JSON.parse(JSON.stringify(resultNode)))
// 替换整个节点
path.replaceWith(entryAst)
}
// 获取两个文件路径的相对路径,入参为两个文件绝对路径
export const getRelFilePath = (path1, path2) => {
const dir1 = path.join(path1, '..')
const dir2 = path.join(path2, '..')
const relPath = path.relative(dir1, dir2) || '.'
return `${relPath}/${path.basename(path2)}`.replaceAll('\\', '/')
}
// 向上获取meta.js的相对路径
export const getMeataPath = (id) => {
let tempPath = path.join(id, '../meta.js')
const endCondition = () => {
// 找到了meta.js
const findMeta = fs.existsSync(tempPath)
// 发现了package.json说明到达子包根目录
const isSubRoot = fs.existsSync(path.join(tempPath, '../package.json'))
// 到达系统根节点,防止死循环
const isRoot = tempPath === path.join(tempPath, '../../meta.js')
return findMeta || isSubRoot || isRoot
}
while (!endCondition()) {
tempPath = path.join(tempPath, '../../meta.js')
}
if (fs.existsSync(tempPath)) {
return getRelFilePath(id, tempPath)
}
return null
}
export const wrapExportComp = ({ path, varName }) => {
const properties = path.node.declaration?.properties || []
const metaData = varName[METADATANAME]
const useCompile = varName[USE_COMPILE]
// 对键值为component属性包一层useCompile
properties.forEach((prop) => {
if (prop.key?.name === 'component') {
const val = prop.value
const compileAst = statement(`${useCompile}({ component: null, ${METADATANAME}: ${metaData} });`)()
compileAst.expression.arguments[0].properties[0].value = val
path.traverse({
enter(subPath) {
if (subPath.node === val) {
subPath.replaceWith(compileAst)
subPath.skip()
}
}
})
}
})
}
export const wrapHookCall = ({ path, varName, functionName, callName, state }) => {
// vue的生命周期hook只有一个参数
const argument = path.node.expression.arguments[0]
const callEntry = varName[CALLENTRY]
const syncVars = getValidBingdinngs({ path, state, functionName })
const asyncVars = path.scope.getAllBindings()
const entryParam = getEntryParam({
functionName,
syncVars,
asyncVars,
varName,
state
})
const wrapAst = statement(`${callName}(${callEntry}(${entryParam}))`)()
wrapAst.expression.arguments[0].arguments.unshift(argument)
path.replaceWith(wrapAst)
path.skip()
}