@tarojs/cli
Version:
cli tool for taro
160 lines (137 loc) • 6.51 kB
text/typescript
import { babelKit } from '@tarojs/helper'
import { ConfigModificationState, ModifyCallback } from '../create/page'
import type { ArrayExpression, ExportDefaultDeclaration, Node, ObjectExpression, ObjectProperty } from '@babel/types'
import type { NodePath } from 'babel__traverse'
const t = babelKit.types
// undefined means absent; null means the effective property cannot be safely resolved.
const getObjectProperty = (node: Node | null | undefined, name: string): ObjectProperty | null | undefined => {
if (node?.type !== 'ObjectExpression') return null
for (let index = node.properties.length - 1; index >= 0; index--) {
const property = node.properties[index]
if (property.type === 'SpreadElement' || property.computed) return null
if (t.isIdentifier(property.key, { name }) || t.isStringLiteral(property.key, { value: name })) {
return property.type === 'ObjectProperty' ? property : null
}
}
}
const generateNewSubPackageItem = (subPackage: string) => {
const pageObject = t.objectProperty(t.identifier('pages'), t.arrayExpression([]))
const subPkgRootObject = t.objectProperty(t.identifier('root'), t.stringLiteral(subPackage))
const subPkgItemObject = t.objectExpression([subPkgRootObject, pageObject])
return subPkgItemObject
}
const isValidSubPkgObject = (subPkgObject: ObjectExpression) => {
const rootProperty = getObjectProperty(subPkgObject, 'root')
const pagesProperty = getObjectProperty(subPkgObject, 'pages')
const rootPropertyValueType = rootProperty?.value?.type
const pagesPropertyValueType = pagesProperty?.value?.type
return rootPropertyValueType === 'StringLiteral' && pagesPropertyValueType === 'ArrayExpression'
}
const addNewSubPackage = (node: ObjectExpression, page: string, subPackage: string): ConfigModificationState => {
if (!t.isObjectExpression(node)) return ConfigModificationState.Fail
let subPackages = getObjectProperty(node, 'subPackages')
if (subPackages === null) return ConfigModificationState.Fail
if (!subPackages) {
// config 文件不存在 subPackages 字段的情况,给该字段赋予默认值
const subPkgObject = t.objectProperty(t.identifier('subPackages'), t.arrayExpression([]))
subPackages = subPkgObject
node?.properties.push(subPkgObject)
}
const value = subPackages?.value
// 文件格式不对的情况
if (!value || value?.type !== 'ArrayExpression') return ConfigModificationState.Fail
let targetSubPkgObject: ObjectExpression | undefined
let hasUnresolvedRoot = false
for (const element of value.elements) {
const rootProperty = getObjectProperty(element, 'root')
if (rootProperty === undefined) continue
if (rootProperty === null || rootProperty.value.type !== 'StringLiteral') {
hasUnresolvedRoot = true
continue
}
if (rootProperty.value.value === subPackage && element?.type === 'ObjectExpression') {
targetSubPkgObject = element
break
}
}
if (!targetSubPkgObject) {
if (hasUnresolvedRoot) return ConfigModificationState.Fail
// 不存在 当前分包配置对象的情况
const subPkgItemObject = generateNewSubPackageItem(subPackage)
targetSubPkgObject = subPkgItemObject
value.elements.push(subPkgItemObject)
}
if (!targetSubPkgObject || targetSubPkgObject.type !== 'ObjectExpression' || !isValidSubPkgObject(targetSubPkgObject)) return ConfigModificationState.Fail
const pagesProperty = getObjectProperty(targetSubPkgObject, 'pages')!
const currentPages = (pagesProperty.value as ArrayExpression).elements
const isPageExists = Boolean(currentPages.find(node => (node as any).value === page))
if (isPageExists) return ConfigModificationState.NeedLess
currentPages.push(t.stringLiteral(page))
return ConfigModificationState.Success
}
const addNewPage = (node: ObjectExpression, page: string): ConfigModificationState => {
const pages = getObjectProperty(node, 'pages')
if (!pages) return ConfigModificationState.Fail
const value = pages?.value
// 仅处理 pages 为数组字面量的情形
if (!value || value?.type !== 'ArrayExpression') return ConfigModificationState.Fail
const isPageExists = Boolean(value.elements.find(node => (node as any).value === page))
if (isPageExists) return ConfigModificationState.NeedLess
const newArrayElement = t.stringLiteral(page)
value.elements.push(newArrayElement)
return ConfigModificationState.Success
}
const modifyPages = (path: NodePath<ExportDefaultDeclaration>, newPageConfig, callback: ModifyCallback) => {
let state = ConfigModificationState.Fail
const node = path.node.declaration as any
// Case 1. `export default defineAppConfig({})` 这种情况
if (node.type === 'CallExpression' && node.callee.name === 'defineAppConfig') {
const configNode = node.arguments[0]
state = addNewPage(configNode, newPageConfig.page)
}
// Case 2. `export default {}` 这种情况
if (node.type === 'ObjectExpression') {
state = addNewPage(node, newPageConfig.page)
}
callback(state)
}
const modifySubPackages = (path: NodePath<ExportDefaultDeclaration>, newPageConfig, callback: ModifyCallback) => {
let state = ConfigModificationState.Fail
const node = path.node.declaration as any
// `export default defineAppConfig({})` 这种情况
if (node.type === 'CallExpression' && node.callee.name === 'defineAppConfig') {
const configNode = node.arguments[0]
state = addNewSubPackage(configNode, newPageConfig.page, newPageConfig.pkg)
}
// `export default {}` 这种情况
if (node.type === 'ObjectExpression') {
state = addNewSubPackage(node, newPageConfig.page, newPageConfig.pkg)
}
callback(state)
}
const generateNewPageConfig = (fullPagePath: string, subPkgRootPath = '') => {
const newPageConfig = {
pkg: '',
page: ''
}
if (subPkgRootPath) {
const processedSubPkg = `${subPkgRootPath}/`
newPageConfig.pkg = processedSubPkg
newPageConfig.page = fullPagePath.split(processedSubPkg)[1]
} else {
newPageConfig.page = fullPagePath
}
return newPageConfig
}
export const modifyPagesOrSubPackages = (params: {
path: NodePath<ExportDefaultDeclaration>
fullPagePath: string
subPkgRootPath?: string
callback: ModifyCallback
}) => {
const { fullPagePath, subPkgRootPath, callback, path } = params
const newPageConfig = generateNewPageConfig(fullPagePath, subPkgRootPath)
subPkgRootPath
? modifySubPackages(path, newPageConfig, callback)
: modifyPages(path, newPageConfig, callback)
}