files.forEach(file => { if (fs.existsSync(file)) { const code = fs.readFileSync(file).toString() const transformResult = wxTransformer({ code, sourcePath: file, outputPath: file, isNormal: true, isTyped: REG_TYPESCRIPT.test(file) }) const { styleFiles, scriptFiles, jsonFiles, mediaFiles } = parseAst(PARSE_AST_TYPE.NORMAL, transformResult.ast, [], file, file, true) const resFiles = styleFiles.concat(scriptFiles, jsonFiles, mediaFiles) if (resFiles.length) { resFiles.forEach(item => { copyFileToDist(item, sourceDir, outputDir) }) } if (scriptFiles.length) { analyzeFiles(scriptFiles, sourceDir, outputDir) } if (styleFiles.length) { analyzeStyleFilesImport(styleFiles, sourceDir, outputDir) } } })
export function isFileToBeTaroComponent ( code: string, sourcePath: string, outputPath: string ) { const { buildAdapter, sourceDir, constantsReplaceList, jsxAttributeNameReplace } = getBuildData() const transformResult: IWxTransformResult = wxTransformer({ code, sourcePath: sourcePath, sourceDir, outputPath: outputPath, isNormal: true, isTyped: REG_TYPESCRIPT.test(sourcePath), adapter: buildAdapter, env: constantsReplaceList, jsxAttributeNameReplace }) const { ast }: IWxTransformResult = transformResult let isTaroComponent = false traverse(ast, { ClassDeclaration (astPath) { astPath.traverse({ ClassMethod (astPath) { if (astPath.get('key').isIdentifier({ name: 'render' })) { astPath.traverse({ JSXElement () { isTaroComponent = true } }) } } }) }, ClassExpression (astPath) { astPath.traverse({ ClassMethod (astPath) { if (astPath.get('key').isIdentifier({ name: 'render' })) { astPath.traverse({ JSXElement () { isTaroComponent = true } }) } } }) } }) return { isTaroComponent, transformResult } }
async function buildH5Lib () { try { const { appPath, outputDirName, tempPath } = buildData const outputDir = path.join(appPath, outputDirName, h5OutputName) const tempEntryFilePath = resolveScriptPath(path.join(tempPath, 'index')) const outputEntryFilePath = path.join(outputDir, path.basename(tempEntryFilePath)) const code = fs.readFileSync(tempEntryFilePath).toString() const transformResult = wxTransformer({ code, sourcePath: tempEntryFilePath, outputPath: outputEntryFilePath, isNormal: true, isTyped: REG_TYPESCRIPT.test(tempEntryFilePath) }) const { styleFiles, components, code: generateCode } = parseEntryAst(transformResult.ast, tempEntryFilePath) const relativePath = path.relative(appPath, tempEntryFilePath) printLog(processTypeEnum.COPY, '发现文件', relativePath) fs.ensureDirSync(path.dirname(outputEntryFilePath)) fs.writeFileSync(outputEntryFilePath, generateCode) if (components.length) { components.forEach(item => { copyFileToDist(item.path as string, tempPath, outputDir) }) analyzeFiles(components.map(item => item.path as string), tempPath, outputDir) } if (styleFiles.length) { styleFiles.forEach(item => { copyFileToDist(item, tempPath, path.join(appPath, outputDirName)) }) analyzeStyleFilesImport(styleFiles, tempPath, path.join(appPath, outputDirName)) } } catch (err) { console.log(err) } }
files.forEach(file => { if (!fs.existsSync(file) || this.hadBeenCopyedFiles.has(file)) { return } const code = fs.readFileSync(file).toString() let outputFilePath = file.replace(this.root, this.convertDir) const extname = path.extname(outputFilePath) if (/\.wxs/.test(extname)) { outputFilePath += '.js' } const transformResult = wxTransformer({ code, sourcePath: file, outputPath: outputFilePath, isNormal: true, isTyped: REG_TYPESCRIPT.test(file) }) const { ast, scriptFiles } = this.parseAst({ ast: transformResult.ast, outputFilePath, sourceFilePath: file }) const jsCode = generateMinimalEscapeCode(ast) this.writeFileToTaro(outputFilePath, prettier.format(jsCode, prettierJSConfig)) printLog(processTypeEnum.COPY, 'JS 文件', this.generateShowPath(outputFilePath)) this.hadBeenCopyedFiles.add(file) this.generateScriptFiles(scriptFiles) })
function getJSAst (code, filePath) { return wxTransformer({ code, sourcePath: filePath, isNormal: true, isTyped: REG_TYPESCRIPT.test(filePath), adapter: 'rn' }).ast }
async function buildForWeapp () { const { appPath, entryFilePath, outputDirName, entryFileName, sourceDir } = buildData console.log() console.log(chalk.green('开始编译小程序端组件库!')) if (!fs.existsSync(entryFilePath)) { console.log(chalk.red('入口文件不存在,请检查!')) return } try { const outputDir = path.join(appPath, outputDirName, weappOutputName) const outputEntryFilePath = path.join(outputDir, entryFileName) const code = fs.readFileSync(entryFilePath).toString() const transformResult = wxTransformer({ code, sourcePath: entryFilePath, outputPath: outputEntryFilePath, isNormal: true, isTyped: REG_TYPESCRIPT.test(entryFilePath) }) const { styleFiles, components } = parseEntryAst(transformResult.ast, entryFilePath) if (styleFiles.length) { const outputStylePath = path.join(outputDir, 'css', 'index.css') await compileDepStyles(outputStylePath, styleFiles) } const relativePath = path.relative(appPath, entryFilePath) printLog(processTypeEnum.COPY, '发现文件', relativePath) fs.ensureDirSync(path.dirname(outputEntryFilePath)) fs.copyFileSync(entryFilePath, path.format({ dir: path.dirname(outputEntryFilePath), base: path.basename(outputEntryFilePath) })) if (components.length) { components.forEach(item => { copyFileToDist(item.path as string, sourceDir, outputDir) }) analyzeFiles(components.map(item => item.path as string), sourceDir, outputDir) } } catch (err) { console.log(err) } }
copyFilesFromSrcToOutput(cFiles, (sourceFilePath, outputFilePath) => { if (fs.existsSync(sourceFilePath)) { const fileContent = fs.readFileSync(sourceFilePath).toString() const match = SCRIPT_CONTENT_REG.exec(fileContent) if (match) { const scriptContent = match[1] const transformResult: IWxTransformResult = wxTransformer({ code: scriptContent, sourcePath: sourceFilePath, sourceDir: getBuildData().sourceDir, outputPath: outputFilePath, isNormal: true, isTyped: false, adapter: BUILD_TYPES.QUICKAPP }) const res = parseAst(PARSE_AST_TYPE.NORMAL, transformResult.ast, [], sourceFilePath, outputFilePath) const newFileContent = fileContent.replace(SCRIPT_CONTENT_REG, `<script>${res.code}</script>`) fs.ensureDirSync(path.dirname(outputFilePath)) fs.writeFileSync(outputFilePath, newFileContent) } } })
export async function compileScriptFile ( content: string, sourceFilePath: string, outputFilePath: string, adapter: BUILD_TYPES ): Promise<string> { const { appPath, sourceDir, constantsReplaceList, jsxAttributeNameReplace, projectConfig } = getBuildData() if (NODE_MODULES_REG.test(sourceFilePath) && fs.existsSync(outputFilePath)) { return fs.readFileSync(outputFilePath).toString() } const babelConfig = getBabelConfig(projectConfig!.plugins!.babel) const compileScriptRes = await callPlugin('babel', content, sourceFilePath, babelConfig, appPath) const code = compileScriptRes.code if (!shouldTransformAgain()) { return code } const transformResult: IWxTransformResult = wxTransformer({ code, sourcePath: sourceFilePath, sourceDir, outputPath: outputFilePath, isNormal: true, isTyped: false, adapter, env: constantsReplaceList, jsxAttributeNameReplace }) const res = parseAst(PARSE_AST_TYPE.NORMAL, transformResult.ast, [], sourceFilePath, outputFilePath) return res.code }
export async function buildSingleComponent ( componentObj: IComponentObj, buildConfig: IComponentBuildConfig = {} ): Promise<IBuildResult> { const componentsBuildResult = getComponentsBuildResult() if (isComponentHasBeenBuilt(componentObj.path as string) && componentsBuildResult.get(componentObj.path as string)) { return componentsBuildResult.get(componentObj.path as string) as IBuildResult } const { appPath, buildAdapter, constantsReplaceList, sourceDir, outputDir, sourceDirName, outputDirName, npmOutputDir, nodeModulesPath, outputFilesTypes, isProduction, jsxAttributeNameReplace, projectConfig } = getBuildData() const isQuickApp = buildAdapter === BUILD_TYPES.QUICKAPP if (componentObj.path) { componentsNamedMap.set(componentObj.path, { name: componentObj.name, type: componentObj.type }) } const component = componentObj.path if (!component) { printLog(processTypeEnum.ERROR, '组件错误', `组件${_.upperFirst(_.camelCase(componentObj.name))}路径错误,请检查!(可能原因是导出的组件名不正确)`) return { js: '', wxss: '', wxml: '' } } let componentShowPath = component.replace(appPath + path.sep, '') componentShowPath = componentShowPath.split(path.sep).join('/') let isComponentFromNodeModules = false let sourceDirPath = sourceDir let buildOutputDir = outputDir // 来自 node_modules 的组件 if (NODE_MODULES_REG.test(componentShowPath)) { isComponentFromNodeModules = true sourceDirPath = nodeModulesPath buildOutputDir = npmOutputDir } let outputComponentShowPath = componentShowPath.replace(isComponentFromNodeModules ? NODE_MODULES : sourceDirName, buildConfig.outputDirName || outputDirName) outputComponentShowPath = outputComponentShowPath.replace(path.extname(outputComponentShowPath), '') printLog(processTypeEnum.COMPILE, '组件文件', componentShowPath) const componentContent = fs.readFileSync(component).toString() const outputComponentJSPath = component.replace(sourceDirPath, buildConfig.outputDir || buildOutputDir).replace(path.extname(component), outputFilesTypes.SCRIPT) const outputComponentWXMLPath = outputComponentJSPath.replace(path.extname(outputComponentJSPath), outputFilesTypes.TEMPL) const outputComponentWXSSPath = outputComponentJSPath.replace(path.extname(outputComponentJSPath), outputFilesTypes.STYLE) const outputComponentJSONPath = outputComponentJSPath.replace(path.extname(outputComponentJSPath), outputFilesTypes.CONFIG) if (!isComponentHasBeenBuilt(component)) { setHasBeenBuiltComponents(component) } try { const isTaroComponentRes = isFileToBeTaroComponent(componentContent, component, outputComponentJSPath) const componentExportsMap = getComponentExportsMap() if (!isTaroComponentRes.isTaroComponent) { const transformResult = isTaroComponentRes.transformResult const componentRealPath = parseComponentExportAst(transformResult.ast, componentObj.name as string, component, componentObj.type as string) const realComponentObj: IComponentObj = { path: componentRealPath, name: componentObj.name, type: componentObj.type } let isInMap = false notTaroComponents.add(component) if (componentExportsMap.size) { componentExportsMap.forEach(componentExports => { componentExports.forEach(item => { if (item.path === component) { isInMap = true item.path = componentRealPath } }) }) } if (!isInMap) { const componentExportsMapItem = componentExportsMap.get(component) || [] componentExportsMapItem.push(realComponentObj) setComponentExportsMap(component, componentExportsMapItem) } return await buildSingleComponent(realComponentObj, buildConfig) } const transformResult: IWxTransformResult = wxTransformer({ code: componentContent, sourcePath: component, sourceDir, outputPath: outputComponentJSPath, isRoot: false, isTyped: REG_TYPESCRIPT.test(component), isNormal: false, adapter: buildAdapter, env: constantsReplaceList, jsxAttributeNameReplace }) const componentWXMLContent = isProduction ? transformResult.compressedTemplate : transformResult.template const componentDepComponents = transformResult.components const res = parseAst(PARSE_AST_TYPE.COMPONENT, transformResult.ast, componentDepComponents, component, outputComponentJSPath, buildConfig.npmSkip) let resCode = res.code fs.ensureDirSync(path.dirname(outputComponentJSPath)) // 解析原生组件 const { usingComponents = {} }: IConfig = res.configObj if (usingComponents && !isEmptyObject(usingComponents)) { const keys = Object.keys(usingComponents) keys.forEach(item => { componentDepComponents.forEach(component => { if (_.camelCase(item) === _.camelCase(component.name)) { delete usingComponents[item] } }) }) transfromNativeComponents(outputComponentJSONPath.replace(buildConfig.outputDir || buildOutputDir, sourceDirPath), res.configObj) } if (!isQuickApp) { resCode = await compileScriptFile(resCode, component, outputComponentJSPath, buildAdapter) if (isProduction) { uglifyJS(resCode, component, appPath, projectConfig!.plugins!.uglify as TogglableOptions) } } else { // 快应用编译,搜集创建组件 ux 文件 const importTaroSelfComponents = getImportTaroSelfComponents(outputComponentJSPath, res.taroSelfComponents) const importCustomComponents = new Set(componentDepComponents.map(item => { delete item.type return item })) const styleRelativePath = promoteRelativePath(path.relative(outputComponentJSPath, outputComponentWXSSPath)) const uxTxt = generateQuickAppUx({ script: resCode, style: styleRelativePath, imports: new Set([...importTaroSelfComponents, ...importCustomComponents]), template: componentWXMLContent }) fs.writeFileSync(outputComponentWXMLPath, uxTxt) printLog(processTypeEnum.GENERATE, '组件文件', `${outputDirName}/${componentObj.name}${outputFilesTypes.TEMPL}`) } const dependencyTree = getDependencyTree() const fileDep = dependencyTree.get(component) || { style: [], script: [], json: [], media: [] } // 编译依赖的组件文件 let realComponentsPathList: IComponentObj[] = [] if (componentDepComponents.length) { realComponentsPathList = getRealComponentsPathList(component, componentDepComponents) res.scriptFiles = res.scriptFiles.map(item => { for (let i = 0; i < realComponentsPathList.length; i++) { const componentObj = realComponentsPathList[i] const componentPath = componentObj.path if (item === componentPath) { return '' } } return item }).filter(item => item) realComponentsPathList = realComponentsPathList.filter(item => !isComponentHasBeenBuilt(item.path as string) || notTaroComponents.has(item.path as string)) await buildDepComponents(realComponentsPathList) } if (componentExportsMap.size && realComponentsPathList.length) { realComponentsPathList.forEach(componentObj => { if (componentExportsMap.has(componentObj.path as string)) { const componentMap = componentExportsMap.get(componentObj.path as string) componentMap && componentMap.forEach(componentObj => { componentDepComponents.forEach(depComponent => { if (depComponent.name === componentObj.name) { let componentPath = componentObj.path let realPath if (NODE_MODULES_REG.test(componentPath as string)) { componentPath = (componentPath as string).replace(nodeModulesPath, npmOutputDir) realPath = promoteRelativePath(path.relative(outputComponentJSPath, componentPath)) } else { realPath = promoteRelativePath(path.relative(component, (componentPath as string))) } depComponent.path = realPath.replace(path.extname(realPath), '') } }) }) } }) } if (!isQuickApp) { fs.writeFileSync(outputComponentJSONPath, JSON.stringify(_.merge({}, buildUsingComponents(component, componentDepComponents, true), res.configObj), null, 2)) printLog(processTypeEnum.GENERATE, '组件配置', `${outputDirName}/${outputComponentShowPath}${outputFilesTypes.CONFIG}`) fs.writeFileSync(outputComponentJSPath, resCode) printLog(processTypeEnum.GENERATE, '组件逻辑', `${outputDirName}/${outputComponentShowPath}${outputFilesTypes.SCRIPT}`) fs.writeFileSync(outputComponentWXMLPath, componentWXMLContent) processNativeWxml(outputComponentWXMLPath.replace(outputDir, sourceDir), componentWXMLContent, outputComponentWXMLPath) printLog(processTypeEnum.GENERATE, '组件模板', `${outputDirName}/${outputComponentShowPath}${outputFilesTypes.TEMPL}`) } // 编译依赖的脚本文件 if (isDifferentArray(fileDep['script'], res.scriptFiles)) { await compileDepScripts(res.scriptFiles, !isQuickApp) } const depComponents = getDepComponents() // 编译样式文件 if (isDifferentArray(fileDep['style'], res.styleFiles) || isDifferentArray(depComponents.get(component) || [], componentDepComponents)) { printLog(processTypeEnum.GENERATE, '组件样式', `${outputDirName}/${outputComponentShowPath}${outputFilesTypes.STYLE}`) await compileDepStyles(outputComponentWXSSPath, res.styleFiles) } // 拷贝依赖文件 if (isDifferentArray(fileDep['json'], res.jsonFiles)) { copyFilesFromSrcToOutput(res.jsonFiles) } if (isDifferentArray(fileDep['media'], res.mediaFiles)) { copyFilesFromSrcToOutput(res.mediaFiles) } fileDep['style'] = res.styleFiles fileDep['script'] = res.scriptFiles fileDep['json'] = res.jsonFiles fileDep['media'] = res.mediaFiles dependencyTree.set(component, fileDep) depComponents.set(component, componentDepComponents) const buildResult = { js: outputComponentJSPath, wxss: outputComponentWXSSPath, wxml: outputComponentWXMLPath } componentsBuildResult.set(component, buildResult) return buildResult } catch (err) { printLog(processTypeEnum.ERROR, '组件编译', `组件${componentShowPath}编译失败!`) console.log(err) return { js: '', wxss: '', wxml: '' } } }
return scriptFiles.map(async item => { if (path.isAbsolute(item)) { let outputItem if (NODE_MODULES_REG.test(item)) { outputItem = item.replace(nodeModulesPath, npmOutputDir).replace(path.extname(item), '.js') } else { outputItem = item.replace(path.join(sourceDir), path.join(outputDir)).replace(path.extname(item), '.js') } const weappConf = Object.assign({}, projectConfig.weapp) const useCompileConf = Object.assign({}, weappConf.compile) const compileExclude = useCompileConf.exclude || [] let isInCompileExclude = false compileExclude.forEach(excludeItem => { if (item.indexOf(path.join(appPath, excludeItem)) >= 0) { isInCompileExclude = true } }) if (isInCompileExclude) { copyFileSync(item, outputItem) return } if (!isBuildingScripts.get(outputItem)) { isBuildingScripts.set(outputItem, true) try { const code = fs.readFileSync(item).toString() const transformResult = wxTransformer({ code, sourcePath: item, sourceDir, outputPath: outputItem, isNormal: true, isTyped: REG_TYPESCRIPT.test(item), adapter: buildAdapter, env: constantsReplaceList, jsxAttributeNameReplace }) const ast = transformResult.ast const res = parseAst(PARSE_AST_TYPE.NORMAL, ast, [], item, outputItem) const fileDep = dependencyTree.get(item) || {} as IDependency let resCode = res.code if (needUseBabel) { resCode = await compileScriptFile(res.code, item, outputItem, buildAdapter) } fs.ensureDirSync(path.dirname(outputItem)) if (isProduction && needUseBabel) { uglifyJS(resCode, item, appPath, projectConfig!.plugins!.uglify as TogglableOptions) } if (NODE_MODULES_REG.test(item)) { resCode = npmCodeHack(outputItem, resCode, buildAdapter) } fs.writeFileSync(outputItem, resCode) let modifyOutput = outputItem.replace(appPath + path.sep, '') modifyOutput = modifyOutput.split(path.sep).join('/') printLog(processTypeEnum.GENERATE, '依赖文件', modifyOutput) // 编译依赖的脚本文件 if (isDifferentArray(fileDep['script'], res.scriptFiles)) { if (buildDepSync) { await Promise.all(compileDepScripts(res.scriptFiles, needUseBabel, buildDepSync)) } else { compileDepScripts(res.scriptFiles, needUseBabel, buildDepSync) } } // 拷贝依赖文件 if (isDifferentArray(fileDep['json'], res.jsonFiles)) { copyFilesFromSrcToOutput(res.jsonFiles) } if (isDifferentArray(fileDep['media'], res.mediaFiles)) { copyFilesFromSrcToOutput(res.mediaFiles) } fileDep['script'] = res.scriptFiles fileDep['json'] = res.jsonFiles fileDep['media'] = res.mediaFiles dependencyTree.set(item, fileDep) } catch (err) { printLog(processTypeEnum.ERROR, '编译失败', item.replace(appPath + path.sep, '')) console.log(err) } } } })