1/* 2 * Copyright (c) 2023 Huawei Device Co., Ltd. 3 * Licensed under the Apache License, Version 2.0 (the "License"); 4 * you may not use this file except in compliance with the License. 5 * You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software 10 * distributed under the License is distributed on an "AS IS" BASIS, 11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 * See the License for the specific language governing permissions and 13 * limitations under the License. 14 */ 15 16import ts from 'typescript'; 17import path from 'path'; 18import fs from 'fs'; 19import { createFilter } from '@rollup/pluginutils'; 20import MagicString from 'magic-string'; 21 22import { 23 LogInfo, 24 componentInfo, 25 emitLogInfo, 26 getTransformLog, 27 genTemporaryPath, 28 writeFileSync 29} from '../../utils'; 30import { 31 preprocessExtend, 32 preprocessNewExtend, 33 validateUISyntax, 34 propertyCollection, 35 linkCollection, 36 resetComponentCollection, 37 componentCollection 38} from '../../validate_ui_syntax'; 39import { 40 processUISyntax, 41 resetLog, 42 transformLog 43} from '../../process_ui_syntax'; 44import { 45 abilityConfig, 46 projectConfig, 47 abilityPagesFullPath 48} from '../../../main'; 49import { ESMODULE, JSBUNDLE } from '../../pre_define'; 50import { 51 CUSTOM_BUILDER_METHOD, 52 GLOBAL_CUSTOM_BUILDER_METHOD, 53 INNER_CUSTOM_BUILDER_METHOD 54} from '../../component_map'; 55 56const filter:any = createFilter(/(?<!\.d)\.(ets|ts)$/); 57const compilerOptions = ts.readConfigFile( 58 path.resolve(__dirname, '../../../tsconfig.json'), ts.sys.readFile).config.compilerOptions; 59compilerOptions['moduleResolution'] = 'nodenext'; 60compilerOptions['module'] = 'es2020'; 61 62let shouldDisableCache: boolean = false; 63const disableCacheOptions = { 64 bundleName: 'default', 65 entryModuleName: 'default', 66 runtimeOS: 'default', 67 resourceTableHash: 'default', 68 etsLoaderVersion: 'default' 69}; 70 71export function etsTransform() { 72 const incrementalFileInHar: Map<string, string> = new Map(); 73 return { 74 name: 'etsTransform', 75 transform: transform, 76 buildStart: judgeCacheShouldDisabled, 77 shouldInvalidCache(options) { 78 return shouldDisableCache; 79 }, 80 moduleParsed(moduleInfo) { 81 if (projectConfig.compileHar) { 82 if (moduleInfo.id && !moduleInfo.id.match(/node_modules/) && !moduleInfo.id.startsWith('\x00')) { 83 const filePath: string = moduleInfo.id; 84 const jsCacheFilePath: string = genTemporaryPath(filePath, projectConfig.moduleRootPath, 85 process.env.cachePath, projectConfig); 86 const jsBuildFilePath: string = genTemporaryPath(filePath, projectConfig.moduleRootPath, 87 projectConfig.buildPath, projectConfig, true); 88 if (filePath.match(/\.e?ts$/)) { 89 incrementalFileInHar.set(jsCacheFilePath.replace(/\.ets$/, '.d.ets').replace(/\.ts$/, '.d.ts'), 90 jsBuildFilePath.replace(/\.ets$/, '.d.ets').replace(/\.ts$/, '.d.ts')); 91 incrementalFileInHar.set(jsCacheFilePath.replace(/\.e?ts$/, '.js'), jsBuildFilePath.replace(/\.e?ts$/, '.js')); 92 } else { 93 incrementalFileInHar.set(jsCacheFilePath, jsBuildFilePath); 94 } 95 } 96 } 97 }, 98 afterBuildEnd() { 99 if (projectConfig.compileHar) { 100 incrementalFileInHar.forEach((jsBuildFilePath, jsCacheFilePath) => { 101 const sourceCode: string = fs.readFileSync(jsCacheFilePath, 'utf-8'); 102 writeFileSync(jsBuildFilePath, sourceCode); 103 }); 104 } 105 shouldDisableCache = false; 106 this.cache.set('disableCacheOptions', disableCacheOptions); 107 } 108 }; 109} 110 111function judgeCacheShouldDisabled() { 112 for (const key in disableCacheOptions) { 113 if (!shouldDisableCache && this.cache.get('disableCacheOptions') && this.share && 114 this.share.projectConfig && this.share.projectConfig[key] && 115 this.cache.get('disableCacheOptions')[key] !== this.share.projectConfig[key]) { 116 shouldDisableCache = true; 117 } 118 if (this.share && this.share.projectConfig && this.share.projectConfig[key]) { 119 disableCacheOptions[key] = this.share.projectConfig[key]; 120 } 121 } 122} 123 124function transform(code: string, id: string) { 125 if (!filter(id)) { 126 return null; 127 } 128 129 if (projectConfig.compileMode === ESMODULE) { 130 compilerOptions['importsNotUsedAsValues'] = 'remove'; 131 } 132 133 const logger = this.share.getLogger('etsTransform'); 134 135 const magicString = new MagicString(code); 136 const newContent: string = preProcess(code, id, this.getModuleInfo(id).isEntry, logger); 137 138 const result: ts.TranspileOutput = ts.transpileModule(newContent, { 139 compilerOptions: compilerOptions, 140 fileName: id, 141 transformers: { before: [ processUISyntax(null) ] } 142 }); 143 144 resetCollection(); 145 if (transformLog && transformLog.errors.length) { 146 emitLogInfo(logger, getTransformLog(transformLog), true, id); 147 resetLog(); 148 } 149 150 return { 151 code: result.outputText, 152 map: result.sourceMapText ? JSON.parse(result.sourceMapText) : magicString.generateMap() 153 }; 154} 155 156function preProcess(code: string, id: string, isEntry: boolean, logger: any): string { 157 if (/\.ets$/.test(id)) { 158 clearCollection(); 159 let content = preprocessExtend(code); 160 content = preprocessNewExtend(content); 161 const fileQuery: string = isEntry && !abilityPagesFullPath.includes(id) ? '?entry' : ''; 162 const log: LogInfo[] = validateUISyntax(code, content, id, fileQuery); 163 if (log.length) { 164 emitLogInfo(logger, log, true, id); 165 } 166 return content; 167 } 168 return code; 169} 170 171function clearCollection(): void { 172 componentCollection.customComponents.clear(); 173 CUSTOM_BUILDER_METHOD.clear(); 174 GLOBAL_CUSTOM_BUILDER_METHOD.clear(); 175 INNER_CUSTOM_BUILDER_METHOD.clear(); 176} 177 178function resetCollection() { 179 componentInfo.id = 0; 180 propertyCollection.clear(); 181 linkCollection.clear(); 182 resetComponentCollection(); 183} 184