diff --git a/compiler/src/gen_abc_plugin.ts b/compiler/src/gen_abc_plugin.ts index 4a30556d91e3de4b12448eda438414c5d33f9587..2d524a4e45469f983e17c7f2629b3dfc6c3f0935 100644 --- a/compiler/src/gen_abc_plugin.ts +++ b/compiler/src/gen_abc_plugin.ts @@ -15,108 +15,512 @@ import * as process from 'child_process'; import * as fs from 'fs'; - import * as path from 'path'; -import Compiler from 'webpack/lib/Compiler'; +import webpack from 'webpack'; import { logger } from './compile_info'; +import { + EXTNAME_ETS, + NODE_MODULES +} from './pre_define'; +import { + BUILD, + genTemporaryPath, + mkdirsSync, + readFile, + TEMPORARYS, + toUnixPath +} from './utils'; +import { processSystemApi } from './validate_ui_syntax'; -const firstFileEXT: string = '_.js'; -let output: string; -let isWin: boolean = false; -let isMac: boolean = false; -let isDebug: boolean = false; -let arkDir: string; -let nodeJs: string; - +const declarationsDir: string = 'declarations'; +const pluginName: string = 'GenAbcPlugin'; const red: string = '\u001b[31m'; const reset: string = '\u001b[39m'; +const configJsonFile: string = 'src/main/config.json'; export class GenAbcPlugin { - constructor(output_, arkDir_, nodeJs_, isDebug_) { - output = output_; - arkDir = arkDir_; - nodeJs = nodeJs_; - isDebug = isDebug_; - } - apply(compiler: Compiler) { - if (fs.existsSync(path.resolve(arkDir, 'build-win'))) { - isWin = true; + protected entryList: string[]; + protected allDependencies: Map; + protected dependencies: Map>; + protected modulesInfo: Map>; + protected esmInfos: Map; + protected buildPath: string; + protected projectPath: string; + protected arkDir: string; + protected nodeJs: string; + protected isDebug: boolean; + protected temporaryFilePath: string; + protected __tapCount: number; + protected isWin: boolean; + protected isMac: boolean; + protected declarationFiles: string[]; + + constructor( + buildPath_: string, + projectPath_: string, + arkDir_: string, + nodeJs_: string, + isDebug_: boolean + ) { + this.buildPath = buildPath_; + this.projectPath = projectPath_; + this.arkDir = arkDir_; + this.nodeJs = nodeJs_; + this.isDebug = isDebug_; + + this.entryList = []; + this.allDependencies = new Map(); + this.dependencies = new Map>(); + this.modulesInfo = new Map>(); + this.esmInfos = new Map(); + this.temporaryFilePath = genTemporaryPath( + this.projectPath, + this.projectPath, + this.buildPath + ); + this.__tapCount = 0; + this.isWin = false; + this.isMac = false; + this.declarationFiles = []; + } + + getEntryList(config): string[] { + const entry = config.entry; + if (typeof entry === 'string') { + return [entry]; + } + if (Array.isArray(entry)) { + return [].concat(entry); + } + if (typeof entry === 'object') { + return Object.values(entry); + } + return []; + } + + conversionFilePath(value): void { + const entrys: string[] = value.import; + for (let index = 0; index < entrys.length; index++) { + let entry: string = entrys[index]; + if (entry.endsWith('?entry')) { + entry = entry.split('?entry')[0]; + } + this.entryList.push(toUnixPath(entry)); + } + } + + addDependencies( + dependencies: Map, + filePath: string, + rawRequest: string + ): Map { + const tmpDeps: Map = + dependencies || new Map(); + if (filePath) { + filePath = toUnixPath(filePath); + if (!tmpDeps.has(filePath)) { + tmpDeps.set(filePath, rawRequest); + } + if (!this.allDependencies.has(filePath)) { + this.allDependencies.set(filePath, rawRequest); + } + } + return tmpDeps; + } + + generateDependencies( + issuer: string, + requireFile: string, + rawRequest: string + ): void { + if (issuer) { + issuer = toUnixPath(issuer); + const dep: Map = this.addDependencies( + this.dependencies.get(issuer), + requireFile, + rawRequest + ); + this.dependencies.set(issuer, dep); + } + } + + afterResolve(result, callback): void { + const issuer: string = result.contextInfo.issuer; + const userRequest: string = result.createData.userRequest; + const rawRequest: string = result.createData.rawRequest; + if (issuer !== userRequest) { + this.generateDependencies(issuer, userRequest, rawRequest); + } + callback(); + } + + genModuleInfo(entry: string): Map { + const moduleInfo: Map = new Map(); + let tmpModuleInfo: Map = new Map(); + tmpModuleInfo = this.dependencies.get(entry); + + if (!tmpModuleInfo) { + this.modulesInfo.set(entry, moduleInfo); + return moduleInfo; + } + + while (tmpModuleInfo.size !== 0) { + const topModuleName: string = tmpModuleInfo.keys().next().value; + if (!moduleInfo.has(topModuleName)) { + moduleInfo.set(topModuleName, tmpModuleInfo.get(topModuleName)!); + } + + tmpModuleInfo.delete(topModuleName); + const topModuleValues: Map = + this.dependencies.get(topModuleName); + if (!topModuleValues) { + continue; + } + + for (const dep of topModuleValues.keys()) { + if (!tmpModuleInfo.has(dep) && !moduleInfo.has(dep)) { + tmpModuleInfo.set(dep, topModuleValues.get(dep)); + } + } + } + this.modulesInfo.set(entry, moduleInfo); + return moduleInfo; + } + + handleFinishModules(_, callback): void { + this.__tapCount -= 1; + if (this.__tapCount <= 0) { + console.error('----dependencies: ', this.dependencies); + this.processModuleRequests(); + // this.processAllTsFiles(); + this.processEntryListFiles(); + } + callback(); + } + + apply(compiler: webpack.Compiler) { + if (fs.existsSync(path.resolve(this.arkDir, 'build-win'))) { + this.isWin = true; } else { - if (fs.existsSync(path.resolve(arkDir, 'build-mac'))) { - isMac = true; + if (fs.existsSync(path.resolve(this.arkDir, 'build-mac'))) { + this.isMac = true; } else { - if (!fs.existsSync(path.resolve(arkDir, 'build'))) { + if (!fs.existsSync(path.resolve(this.arkDir, 'build'))) { logger.error(red, 'ETS:ERROR find build fail', reset); return; } } } - compiler.hooks.emit.tap('GenAbcPlugin', (compilation) => { - Object.keys(compilation.assets).forEach(key => { - // choice *.js - if (output && path.extname(key) === '.js') { - const newContent: string = compilation.assets[key].source(); - const keyPath: string = key.replace(/\.js$/, firstFileEXT); - writeFileSync(newContent, path.resolve(output, keyPath), key); - } - }); + const insDeclarationsDir: string = path.join('bin', 'ark', 'declarations'); + if (fs.existsSync(declarationsDir)) { + readFile(declarationsDir, this.declarationFiles); + } + if (fs.existsSync(insDeclarationsDir)) { + readFile(insDeclarationsDir, this.declarationFiles); + } + + const entryList: string[] = this.getEntryList(compiler.options); + entryList.map((entry) => this.conversionFilePath(entry)); + + compiler.hooks.normalModuleFactory.tap(pluginName, (nmf) => { + nmf.hooks.afterResolve.tapAsync(pluginName, this.afterResolve.bind(this)); + }); + + compiler.hooks.compilation.tap(pluginName, (compilation) => { + this.__tapCount += 1; + compilation.hooks.finishModules.tapAsync(pluginName, this.handleFinishModules.bind(this)); }); } -} -function writeFileSync(inputString: string, output: string, jsBundleFile: string): void { - const parent: string = path.join(output, '..'); - if (!(fs.existsSync(parent) && fs.statSync(parent).isDirectory())) { - mkDir(parent); + genModileDirFromConfig(moduleRequest: string): string { + const moduleNames: string[] = moduleRequest.split(`${BUILD}/${TEMPORARYS}/`); + const moduleDir = moduleNames[1]; + const preModuleDir: string = moduleNames[0]; + const jsonFile: string = path.join(preModuleDir, configJsonFile); + if (fs.existsSync(jsonFile)) { + const esmInfo: ESM = getEsmInfo(jsonFile, this.esmInfos); + const bundleName: string = esmInfo.getBundleName(); + const moduleName: string = esmInfo.getModuleName(); + const abilityName: string | undefined = esmInfo.getAbilityName(); + const component: string = esmInfo.getComponent(); + const index: number = moduleRequest.indexOf(component); + let modulePath: string = moduleNames[1]; + if (index !== -1) { + modulePath = moduleRequest.substring(index); + } + + if (abilityName) { + return path.join(bundleName, moduleName, abilityName, modulePath); + } else { + return path.join(bundleName, moduleName, modulePath); + } + } + return moduleDir; } - fs.writeFileSync(output, inputString); - if (fs.existsSync(output)) { - js2abcFirst(output); - } else { - logger.error(red, `ETS:ERROR Failed to convert file ${jsBundleFile} to bin. ${output} is lost`, reset); + + genModuleName(moduleRequest: string): string { + moduleRequest = this.genTemporaryFile( + moduleRequest, + this.projectPath, + this.buildPath + ); + let moduleName: string = moduleRequest.substring(0, moduleRequest.lastIndexOf('.')); + moduleName = toUnixPath(moduleName); + let moduleDir: string = ''; + if (moduleName.indexOf(NODE_MODULES) !== -1) { + moduleDir = moduleName.substring(moduleName.indexOf(NODE_MODULES)); + } else if (moduleName.indexOf(`${BUILD}/${TEMPORARYS}`) !== -1) { + moduleDir = this.genModileDirFromConfig(moduleName); + } else { + const projectRoot: string = this.buildPath.substring(0, this.buildPath.indexOf(BUILD) - 1); + moduleDir = moduleName.substring(projectRoot.length); + } + const moduleDirs: string[] = toUnixPath(moduleDir).split('/'); + moduleDir = moduleDirs.join('.'); + return moduleDir; + } + + genModuleDirMap(moduleInfo: Map, moduleDirMap: Map): void { + if (!moduleInfo) { + return; + } + + for (let moduleDep of moduleInfo.keys()) { + const moduleName: string = this.genModuleName(moduleDep); + moduleDep = this.genTemporaryFile( + moduleDep, + this.projectPath, + this.buildPath + ); + moduleDirMap.set(toUnixPath(moduleDep), moduleName); + } + } + + genAbcFileName(temporaryFile: string): string { + const fileTmps: string[] = temporaryFile.split(toUnixPath(this.temporaryFilePath)); + const tmpFile: string = path.join(this.buildPath, fileTmps[1]); + const entryBaseName: string = tmpFile.substring(0, tmpFile.lastIndexOf('.')); + const abcFile: string = `${entryBaseName}.abc`; + return abcFile; + } + + modifyModuleRequest(moduleRequestInfo: Map, content: string): string { + if (!moduleRequestInfo) { + return; + } + + for (const key of moduleRequestInfo.keys()) { + let temporaryFile: string = genTemporaryPath( + key, + this.projectPath, + this.buildPath + ); + temporaryFile = toUnixPath(temporaryFile); + const value: string = moduleRequestInfo.get(key); + content = content.replace(value, temporaryFile); + } + return content; + } + + processModuleRequests(): void { + for (const key of this.dependencies.keys()) { + if (key.indexOf(NODE_MODULES) !== -1) { + continue; + } + const temporaryFile: string = this.genTemporaryFile( + key, + this.projectPath, + this.buildPath + ); + let content: string = ''; + if (path.extname(key) === EXTNAME_ETS) { + content = fs.readFileSync(temporaryFile, 'utf8'); + } else { + content = fs.readFileSync(key, 'utf8'); + content = processSystemApi(content); + } + + const moduleRequestInfo: Map = this.dependencies.get(key); + content = this.modifyModuleRequest(moduleRequestInfo, content); + + const entryDirName: string = path.dirname(temporaryFile); + if (!fs.existsSync(entryDirName)) { + mkdirsSync(entryDirName); + } + fs.writeFileSync(temporaryFile, content); + } + } + + processAllTsFiles(): void { + const esmInfo: ESM = getEsmInfo(path.join(this.projectPath, '../../../../', configJsonFile), this.esmInfos); + const abcFile: string = `${path.join(this.buildPath, esmInfo.getModuleName())}.abc`; + const moduleDirMap: Map = new Map(); + const allTsFiles: string[] = this.entryList.concat(Array.from(this.allDependencies.keys())); + + console.error('---------------allTsFiles----------------: ', allTsFiles); + allTsFiles.map((tsFile: string) => { + let temporaryFile: string = this.genTemporaryFile( + tsFile, + this.projectPath, + this.buildPath + ); + temporaryFile = toUnixPath(temporaryFile); + const moduleDir: string = this.genModuleName(tsFile); + moduleDirMap.set(temporaryFile, moduleDir); + }); + + console.error('---------------moduleDirMap----------------: ', moduleDirMap); + this.processTsFile(abcFile, moduleDirMap); + } + + processEntryListFiles(): void { + this.entryList.map((entryFile: string) => { + let temporaryFile: string = this.genTemporaryFile( + entryFile, + this.projectPath, + this.buildPath + ); + temporaryFile = toUnixPath(temporaryFile); + const abcFile: string = this.genAbcFileName(temporaryFile); + const moduleInfo: Map = this.genModuleInfo(entryFile); + const moduleDirMap: Map = new Map(); + this.genModuleDirMap(moduleInfo, moduleDirMap); + const moduleDir: string = this.genModuleName(entryFile); + moduleDirMap.set(temporaryFile, moduleDir); + console.error('---------------moduleDirMap----------------: ', moduleDirMap); + this.processTsFile(abcFile, moduleDirMap); + }); + } + + genPathMapToStr(moduleDirMap: Map): string { + let data: string = ''; + for (const moduleName of moduleDirMap.keys()) { + const distName: string | undefined = moduleDirMap.get(moduleName); + if (distName) { + data += `${moduleName},${distName},`; + } + } + if (data.length) { + data = data.substring(0, data.length - 1); + } + + return data.trim(); } -} -function mkDir(path_: string): void { - const parent: string = path.join(path_, '..'); - if (!(fs.existsSync(parent) && !fs.statSync(parent).isFile())) { - mkDir(parent); + genTemporaryFile(data: string, projectPath: string, buildPath: string): string { + let newFile: string = genTemporaryPath(data, projectPath, buildPath); + if (fs.statSync(data).isFile() && path.extname(data) === EXTNAME_ETS) { + newFile = `${newFile}.ts`; + } + return newFile; + } + + processTsFile( + outPutFile: string, + moduleDirMap: Map + ): void { + const dirName: string = path.dirname(outPutFile); + if (!(fs.existsSync(dirName) && fs.statSync(dirName).isDirectory())) { + mkdirsSync(dirName); + } + + let js2abc: string = path.join(this.arkDir, 'build', 'src', 'index.js'); + if (this.isWin) { + js2abc = path.join(this.arkDir, 'build-win', 'src', 'index.js'); + } else if (this.isMac) { + js2abc = path.join(this.arkDir, 'build-mac', 'src', 'index.js'); + } + + const args: string[] = [ + '--expose-gc', + js2abc, + '-o', + outPutFile, + '-r', + '-m', + '--merge-abc-files', + '--dts-type-record' + ]; + if (this.isDebug) { + args.push('--debug'); + } + + if (moduleDirMap.size !== 0) { + args.push('--modules-dir-map', this.genPathMapToStr(moduleDirMap)); + const dependencies: string[] = Array.from(moduleDirMap.keys()); + console.error('-----------dependencies------------------: ', dependencies); + args.push(...dependencies); + } + + if (this.declarationFiles.length !== 0) { + args.push(...this.declarationFiles); + } + + try { + console.error(`${this.nodeJs} ` + args.join(' ') + '\n'); + process.execFileSync(`${this.nodeJs}`, args); + } catch (e) { + logger.error(red, `ETS:ERROR Failed to convert file to abc , message: ${e}`, reset); + return; + } } - fs.mkdirSync(path_); } -function js2abcFirst(inputPath: string): void { - let param: string = '-r'; - if (isDebug) { - param += ' --debug'; +class ESM { + protected bundle: string; + protected moduleName: string; + protected abilityName: string | undefined; + protected component: string = 'default'; + + constructor(bundle: string, moduleName: string, abilityName: string | undefined, component?: string) { + this.bundle = bundle; + this.moduleName = moduleName; + this.abilityName = abilityName; + if (component) { + this.component = component; + } } - let js2abc: string = path.join(arkDir, 'build', 'src', 'index.js'); - if (isWin) { - js2abc = path.join(arkDir, 'build-win', 'src', 'index.js'); - } else if (isMac) { - js2abc = path.join(arkDir, 'build-mac', 'src', 'index.js'); + getBundleName(): string { + return this.bundle; } - const cmd: string = `${nodeJs} --expose-gc "${js2abc}" "${inputPath}" ${param}`; + getModuleName(): string { + return this.moduleName; + } - try { - process.execSync(cmd); - } catch (e) { - logger.error(red, `ETS:ERROR Failed to convert file ${inputPath} to abc `, reset); - return; + getAbilityName(): string { + return this.abilityName; } - if (fs.existsSync(inputPath)) { - fs.unlinkSync(inputPath); + getComponent(): string { + return this.component; } +} - const abcFile: string = inputPath.replace(/\.js$/, '.abc'); - if (fs.existsSync(abcFile)) { - const abcFileNew: string = abcFile.replace(/_.abc$/, '.abc'); - fs.renameSync(abcFile, abcFileNew); +function getEsmInfo(jsonFile: string, esmInfos: Map): ESM { + if (esmInfos.has(jsonFile)) { + return esmInfos.get(jsonFile); + } + + const rawdata: string = fs.readFileSync(jsonFile, 'utf-8'); + const data = JSON.parse(rawdata); + const bundleName: string = data.app.bundleName; + const moduleName: string = data.module.distro.moduleName; + let component: string = 'default'; + let abilityName: string | undefined; + if (data.module.abilities) { + const label: string = data.module.abilities[0].label; + abilityName = label.substring(label.indexOf('_') + 1); + if (data.module.abilities[0].srcPath) { + component = data.module.abilities[0].srcPath; + } } else { - logger.error(red, `ETS:ERROR ${abcFile} is lost`, reset); + abilityName = undefined; } + + const esmInfo: ESM = new ESM(bundleName, moduleName, abilityName, component); + esmInfos.set(jsonFile, esmInfo); + return esmInfo; } diff --git a/compiler/src/process_ui_syntax.ts b/compiler/src/process_ui_syntax.ts index 61c64420ce9fbe5a74f8ce0d397c515be08445b5..50ae22afb5c906e176d9bbc704280a51133ec004 100644 --- a/compiler/src/process_ui_syntax.ts +++ b/compiler/src/process_ui_syntax.ts @@ -40,7 +40,8 @@ import { LogInfo, LogType, hasDecorator, - FileLog + FileLog, + writeFileSync } from './utils'; import { getName, @@ -73,6 +74,7 @@ export function processUISyntax(program: ts.Program, ut = false): Function { if (process.env.compiler === BUILD_ON) { if (!ut && (path.basename(node.fileName) === 'app.ets.ts' || !/\.ets\.ts$/.test(node.fileName))) { node = ts.visitEachChild(node, processResourceNode, context); + writeFileSync(node); return node; } collectComponents(node); @@ -90,6 +92,7 @@ export function processUISyntax(program: ts.Program, ut = false): Function { }); node = ts.factory.updateSourceFile(node, statements); INTERFACE_NODE_SET.clear(); + writeFileSync(node); return node; } else { return node; diff --git a/compiler/src/utils.ts b/compiler/src/utils.ts index c7a7700bbf4cc6e5436220aa60a69f42568229be..0961f42e74067a66ab5e63041116322df64c7647 100644 --- a/compiler/src/utils.ts +++ b/compiler/src/utils.ts @@ -13,9 +13,10 @@ * limitations under the License. */ -import ts from 'typescript'; -import path from 'path'; import fs from 'fs'; +import path from 'path'; +import ts from 'typescript'; +import { projectConfig } from '../main'; export enum LogType { ERROR = 'ERROR', @@ -205,4 +206,72 @@ function mkDir(path_: string): void { mkDir(parent); } fs.mkdirSync(path_); -} +} + +export const TEMPORARYS: string = 'temporarys'; +export const BUILD: string = 'build'; +export const SRC_MAIN: string = 'src/main'; +const TS_NOCHECK: string = '// @ts-nocheck'; + +export function genTemporaryPath(filePath: string, projectPath: string, buildPath: string): string { + filePath = toUnixPath(filePath); + projectPath = toUnixPath(projectPath); + if (projectPath.indexOf(SRC_MAIN) === -1) { + return path.join(buildPath, TEMPORARYS, filePath.substring(projectPath.length)); + } + + if (filePath.indexOf(SRC_MAIN) === -1 || filePath.indexOf(`${BUILD}/${TEMPORARYS}`) !== -1) { + return filePath; + } + + const dataTmps = filePath.split(SRC_MAIN); + const preStr = dataTmps[0]; + const sufStr = dataTmps[1]; + const output: string = path.join(preStr, BUILD, TEMPORARYS, sufStr); + return output; +} + +export function mkdirsSync(dirname: string): boolean { + if (fs.existsSync(dirname)) { + return true; + } else { + if (mkdirsSync(path.dirname(dirname))) { + fs.mkdirSync(dirname); + return true; + } + } + return false; +} + +/** + * + * 统一路径分隔符 主要是为了后续生成模块ID方便 + * @param {*} path + * @returns + */ +export function toUnixPath(data: string): string { + if (/^win/.test(require('os').platform())) { + const fileTmps: string[] = data.split(path.sep); + const newData: string = path.posix.join(...fileTmps); + return newData; + } + return data; +} + +export function writeFileSync(node: ts.SourceFile) { + const newStatements = []; + const tsIgnoreNode: ts.Node = ts.factory.createExpressionStatement(ts.factory.createIdentifier(TS_NOCHECK)); + newStatements.push(tsIgnoreNode); + if (node.statements && node.statements.length) { + newStatements.push(...node.statements); + } + + node = ts.factory.updateSourceFile(node, newStatements); + const printer: ts.Printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed }); + const result: string = printer.printFile(node); + const content: string = result.replace(`${TS_NOCHECK};`, TS_NOCHECK); + const temporaryFile: string = genTemporaryPath(node.fileName, projectConfig.projectPath, projectConfig.buildPath); + + mkdirsSync(path.dirname(temporaryFile)); + fs.writeFileSync(temporaryFile, content); +} diff --git a/compiler/webpack.config.js b/compiler/webpack.config.js index c7f148a01e5b538938c37fbcbeb1a4f7f52c8a02..62cbebcb064346fd823f64e80ce037c2707456c7 100644 --- a/compiler/webpack.config.js +++ b/compiler/webpack.config.js @@ -231,8 +231,8 @@ module.exports = (env, argv) => { if (env.nodeJs) { nodeJs = env.nodeJs; } - config.plugins.push(new GenAbcPlugin(projectConfig.buildPath, arkDir, nodeJs, - env.buildMode === 'debug')); + config.plugins.push(new GenAbcPlugin(projectConfig.buildPath, projectConfig.projectPath, + arkDir, nodeJs, env.buildMode === 'debug')); } } else { projectConfig.isPreview = true;