From 71a3e3487337699f0f0bc0c38036ad0e309c564f Mon Sep 17 00:00:00 2001 From: hufeng Date: Tue, 3 May 2022 23:55:42 +0800 Subject: [PATCH] compile with ohm_url module Signed-off-by: hufeng Change-Id: I84ebf3292aae496a0e2acf546328063eb91d9c33 --- compiler/src/ets_checker.ts | 23 ++++ compiler/src/resolve_ohm_url.ts | 62 ++++++++++ compiler/src/validate_ui_syntax.ts | 186 ++++++++++++++++++++++------- compiler/webpack.config.js | 2 + 4 files changed, 230 insertions(+), 43 deletions(-) create mode 100644 compiler/src/resolve_ohm_url.ts diff --git a/compiler/src/ets_checker.ts b/compiler/src/ets_checker.ts index db1c6ef..ec79efb 100644 --- a/compiler/src/ets_checker.ts +++ b/compiler/src/ets_checker.ts @@ -36,6 +36,7 @@ import { JS_BIND_COMPONENTS } from './component_map'; import { getName } from './process_component_build'; import { INNER_COMPONENT_NAMES } from './component_map'; import { props } from './compile_info'; +import { resolveSourceFile } from './resolve_ohm_url'; function readDeaclareFiles(): string[] { const declarationsFileNames: string[] = []; @@ -109,6 +110,21 @@ export function createLanguageService(rootFileNames: string[]): ts.LanguageServi return ts.createLanguageService(servicesHost, ts.createDocumentRegistry()); } +function getOhmUrlFile(moduleName: string): {modulePath: string, suffix: string} { + let modulePath = resolveSourceFile(moduleName); + let suffix: string; + if (modulePath.endsWith('.ets')) { + suffix = '.ets'; + } else if (modulePath.endsWith('.ts')) { + suffix = '.ts'; + } else { + modulePath = modulePath.replace('.js', '.d.ts'); + suffix = '.d.ts'; + } + + return {modulePath, suffix}; +} + function resolveModuleNames(moduleNames: string[], containingFile: string): ts.ResolvedModuleFull[] { const resolvedModules: ts.ResolvedModuleFull[] = []; for (const moduleName of moduleNames) { @@ -122,6 +138,13 @@ function resolveModuleNames(moduleNames: string[], containingFile: string): ts.R }); if (result.resolvedModule) { resolvedModules.push(result.resolvedModule); + } else if (/^@bundle:/.test(moduleName.trim())) { + const module = getOhmUrlFile(moduleName.trim()); + if (ts.sys.fileExists(module.modulePath)) { + resolvedModules.push(getResolveModule(module.modulePath, module.suffix)); + } else { + resolvedModules.push(null); + } } else if (/^@(system|ohos)/.test(moduleName.trim())) { const modulePath: string = path.resolve(__dirname, '../../../api', moduleName + '.d.ts'); if (ts.sys.fileExists(modulePath)) { diff --git a/compiler/src/resolve_ohm_url.ts b/compiler/src/resolve_ohm_url.ts new file mode 100644 index 0000000..58bd303 --- /dev/null +++ b/compiler/src/resolve_ohm_url.ts @@ -0,0 +1,62 @@ +import * as fs from 'fs'; +import * as path from 'path'; +import { logger } from './compile_info'; +const { projectConfig } = require('../main'); + +const red: string = '\u001b[31m'; +const reset: string = '\u001b[39m'; + +const REG_OHM_URL = /^@bundle:(\S+)\/(\S+)\/(ets|js|node_modules)\/(\S+)$/; + +export class OHMResolverPlugin { + private source: any; + private target: any; + + constructor(source = 'resolve', target = 'resolve') { + this.source = source; + this.target = target; + } + + apply(resolver) { + const target = resolver.ensureHook(this.target); + resolver.getHook(this.source).tapAsync("OHMResolverPlugin", (request, resolveContext, callback) => { + if (/^@bundle:/.test(request.request)) { + var resolvedSourceFile: string = resolveSourceFile(request.request); + var obj = Object.assign({}, request, { + request: resolvedSourceFile + }); + return resolver.doResolve(target, obj, null, resolveContext, callback); + } + callback(); + }); + } +} + +export function resolveSourceFile(ohmUrl: string): string { + const result = ohmUrl.match(REG_OHM_URL); + // let bundleName = result[1]; + let moduleName = result[2]; + let srcKind = result[3]; + let file = path.join(projectConfig.projectPath, '../../../../../', moduleName, 'src/main', srcKind, result[4]); + if(srcKind == 'ets') { + if (!file.endsWith('.ets') && !file.endsWith('ts')) { + if (fs.existsSync(file + '.ets') && fs.statSync(file + '.ets').isFile()) { + file += '.ets'; + } else { + file += '.ts'; + } + } + } else if(srcKind == 'js') { + if (!file.endsWith('.js')) { + file += '.js'; + } + } else { + // Todo: node_modules + } + + if (!fs.existsSync(file) || !fs.statSync(file).isFile()) { + logger.error(red, `ETS:ERROR Failed to resolve existed file by this ohm url ${ohmUrl} `, reset); + } + + return file; +} \ No newline at end of file diff --git a/compiler/src/validate_ui_syntax.ts b/compiler/src/validate_ui_syntax.ts index 318cf08..b4d9087 100644 --- a/compiler/src/validate_ui_syntax.ts +++ b/compiler/src/validate_ui_syntax.ts @@ -126,6 +126,7 @@ export const localStoragePropCollection: Map>> = export const isStaticViewCollection: Map = new Map(); +export const packageCollection: Map> = new Map(); export const moduleCollection: Set = new Set(); export const useOSFiles: Set = new Set(); @@ -829,56 +830,155 @@ export function preprocessNewExtend(content: string, extendCollection?: Set { + if (packageCollection.has(configFile)) { + return packageCollection.get(configFile); + } + + const rawData: string = fs.readFileSync(configFile, 'utf-8'); + const data = JSON.parse(rawData); + const bundleName = data.app.bundleName; + const moduleName = data.module.distro.moduleName; + packageCollection.set(configFile, [bundleName, moduleName]); + return [bundleName, moduleName]; +} + +function replaceSystemApi(item: string, systemValue: string, moduleType: string, systemKey: string) { + moduleCollection.add(`${moduleType}.${systemKey}`); + if (NATIVE_MODULE.has(`${moduleType}.${systemKey}`)) { + item = `var ${systemValue} = globalThis.requireNativeModule('${moduleType}.${systemKey}')`; + } else if (moduleType === SYSTEM_PLUGIN) { + item = `var ${systemValue} = isSystemplugin('${systemKey}', '${SYSTEM_PLUGIN}') ? ` + + `globalThis.systemplugin.${systemKey} : globalThis.requireNapi('${systemKey}')`; + } else if (moduleType === OHOS_PLUGIN) { + item = `var ${systemValue} = globalThis.requireNapi('${systemKey}') || ` + + `(isSystemplugin('${systemKey}', '${OHOS_PLUGIN}') ? ` + + `globalThis.ohosplugin.${systemKey} : isSystemplugin('${systemKey}', '${SYSTEM_PLUGIN}') ` + + `? globalThis.systemplugin.${systemKey} : undefined)`; + } + return item; +} + +function replaceLibSo(importValue: string, libSoKey: string, sourcePath: string = null) { + if (sourcePath) { + useOSFiles.add(sourcePath); + } + return `var ${importValue} = globalThis.requireNapi("${libSoKey}", true);`; +} + +function replaceOhmUrl(isSystemModule: boolean, item: string, importValue: string, moduleRequest: string, sourcePath: string = null) { + const result = moduleRequest.match(/^@(\S+):(\S+)$/) + let urlType: string = result[1]; + let url: string = result[2]; + switch(urlType) { + case 'bundle': { + let urlResult = url.match(/^(\S+)\/(\S+)\/(\S+)\/(\S+)$/); + let moduleKind = urlResult[3]; + if (moduleKind == 'lib') { + item = replaceLibSo(importValue, moduleRequest, sourcePath); + } + break; + } + case 'module': { + let urlResult = url.match(/^(\S+)\/(\S+)\/(\S+)$/); + let moduleName = urlResult[1]; + let moduleKind = urlResult[2]; + let modulePath = urlResult[3]; + const configJsonFile: string = + path.join(projectConfig.projectPath, '../../../../../', moduleName, 'src/main/config.json'); + let bundleName = getPackageInfo(configJsonFile)[0]; + moduleRequest = `@bundle:${bundleName}/${moduleName}/${moduleKind}/${modulePath}`; + item = moduleKind == 'lib' ? + replaceLibSo(importValue, moduleRequest, sourcePath) : item.replace(/['"](\S+)['"]/, moduleRequest); + break; + } + case 'ohos': { + url = url.replace('/', '.'); + let urlResult = url.match(/^system\.(\S+)/) + moduleRequest = urlResult ? `@${url}` : `@ohos.${url}`; + if (!isSystemModule) { + item = item.replace(/['"](\S+)['"]/, moduleRequest); + } else { + let moduleType = urlResult ? 'system' : 'ohos'; + let systemKey = urlResult ? url.substring(7) : url; + item = replaceSystemApi(item, importValue, moduleType, systemKey); + } + break; + } + case 'lib': { + item = replaceLibSo(importValue, url, sourcePath); + break; + } + case 'local': { + let result = sourcePath.match(/(S+)\/src\/main\/(S+)/); + const configJsonFile: string = `${result[1]}/src/main/config.json`; + let packageInfo = getPackageInfo(configJsonFile); + let urlResult = url.match(/^(ets|js|lib)\/(S+)$/); + let moduleKind = urlResult[1]; + let modulePath = urlResult[2]; + if (moduleKind == 'lib') { + item = replaceLibSo(importValue, modulePath, sourcePath); + } else { + moduleRequest = `@bundle:${packageInfo[0]}/${packageInfo[1]}/${moduleKind}/${modulePath}`; + item = item.replace(/['"](\S+)['"]/, moduleRequest); + } + break; + } + default: + console.error("Incorrect OpenHarmony module kind: ", urlType); + } + return item; +} + +function replaceRelativePath(moduleRequest: string, sourcePath: string) { + let filePath = path.resolve(sourcePath, moduleRequest); + let result = filePath.match(/(S+)\/src\/main\/(ets|js)\/(S+)/); + const configJsonFile: string = `${result[1]}/src/main/config.json`; + let packageInfo = getPackageInfo(configJsonFile); + let bundleName = packageInfo[0]; + let moduleName = packageInfo[1]; + + return `@bundle:${bundleName}/${moduleName}/${result[2]}/${result[3]}`; +} + export function processSystemApi(content: string, isProcessAllowList: boolean = false, sourcePath: string = null, isSystemModule: boolean = false): string { - let REG_SYSTEM: RegExp; - if (isProcessAllowList) { - REG_SYSTEM = - /(import|const)\s+(.+)\s*=\s*(\_\_importDefault\()?require\(\s*['"]@(system|ohos)\.(\S+)['"]\s*\)(\))?/g; - } else { - REG_SYSTEM = - /import\s+(.+)\s+from\s+['"]@(system|ohos)\.(\S+)['"]|import\s+(.+)\s*=\s*require\(\s*['"]@(system|ohos)\.(\S+)['"]\s*\)/g; - } - const REG_LIB_SO: RegExp = - /import\s+(.+)\s+from\s+['"]lib(\S+)\.so['"]|import\s+(.+)\s*=\s*require\(\s*['"]lib(\S+)\.so['"]\s*\)/g; const systemValueCollection: Set = new Set(); - const newContent: string = content.replace(REG_LIB_SO, (_, item1, item2, item3, item4) => { - const libSoValue: string = item1 || item3; - const libSoKey: string = item2 || item4; - if (sourcePath) { - useOSFiles.add(sourcePath); + const REG_IMPORT_DECL = isProcessAllowList ? + /(import|const)\s+(.+)\s*=\s*(\_\_importDefault\()?require\(\s*['"]@(system|ohos)\.(\S+)['"]\s*\)(\))?/g : + /import\s+(.+)\s+from\s+['"](\S+)['"]|import\s+(.+)\s*=\s*require\(\s*['"](\S+)['"]\s*\)/g; + + const processedContent: string = content.replace(REG_IMPORT_DECL, (item, item1, item2, item3, item4, item5) => { + let importValue: string = isProcessAllowList ? item2 : item1 || item3; + + if (isProcessAllowList) { + systemValueCollection.add(importValue); + return replaceSystemApi(item, importValue, item4, item5); } - return `var ${libSoValue} = globalThis.requireNapi("${libSoKey}", true);`; - }).replace(REG_SYSTEM, (item, item1, item2, item3, item4, item5, item6, item7) => { - let moduleType: string = item2 || item5; - let systemKey: string = item3 || item6; - let systemValue: string = item1 || item4; - if (!VALIDATE_MODULE.includes(systemValue)) { - importModuleCollection.add(systemValue); - } - if (!isProcessAllowList && !isSystemModule) { - return item; - } else if (isProcessAllowList) { - systemValue = item2; - moduleType = item4; - systemKey = item5; - systemValueCollection.add(systemValue); - } - moduleCollection.add(`${moduleType}.${systemKey}`); - if (NATIVE_MODULE.has(`${moduleType}.${systemKey}`)) { - item = `var ${systemValue} = globalThis.requireNativeModule('${moduleType}.${systemKey}')`; - } else if (moduleType === SYSTEM_PLUGIN) { - item = `var ${systemValue} = isSystemplugin('${systemKey}', '${SYSTEM_PLUGIN}') ? ` + - `globalThis.systemplugin.${systemKey} : globalThis.requireNapi('${systemKey}')`; - } else if (moduleType === OHOS_PLUGIN) { - item = `var ${systemValue} = globalThis.requireNapi('${systemKey}') || ` + - `(isSystemplugin('${systemKey}', '${OHOS_PLUGIN}') ? ` + - `globalThis.ohosplugin.${systemKey} : isSystemplugin('${systemKey}', '${SYSTEM_PLUGIN}') ` + - `? globalThis.systemplugin.${systemKey} : undefined)`; + + let moduleRequest: string = item2 || item4; + if (/^@(\S+):/.test(moduleRequest)) { // ohmURL + return replaceOhmUrl(isSystemModule, item, importValue, moduleRequest, sourcePath); + } else if (/^@(system|ohos)\./.test(moduleRequest)) { //ohos/system.api + // ets & ts file need compile with .d.ts, so do not replace at the phase of pre_process + if (!isSystemModule) { + return item; + } + let result = moduleRequest.match(/^@(system|ohos)\.(\S+)$/); + let moduleType: string = result[1]; + let apiName: string = result[2]; + return replaceSystemApi(item, importValue, moduleType, apiName); + } else if (/^(\.|\.\.)\//.test(moduleRequest)) { // relativePath + return replaceRelativePath(moduleRequest, sourcePath); + } else if (/^lib(\S+)\.so$/.test(moduleRequest)) { // libxxx.so + let result = moduleRequest.match(/^lib(\S+)\.so$/); + let libSoKey = result[1]; + return replaceLibSo(importValue, libSoKey, sourcePath); } + // node_modules return item; }); - return processInnerModule(newContent, systemValueCollection); + return isProcessAllowList ? processInnerModule(processedContent, systemValueCollection) : processedContent; } function processInnerModule(content: string, systemValueCollection: Set): string { diff --git a/compiler/webpack.config.js b/compiler/webpack.config.js index 3c6758a..ad3881a 100644 --- a/compiler/webpack.config.js +++ b/compiler/webpack.config.js @@ -18,6 +18,7 @@ const fs = require('fs'); const CopyPlugin = require('copy-webpack-plugin'); const Webpack = require('webpack'); const { GenAbcPlugin } = require('./lib/gen_abc_plugin'); +const { OHMResolverPlugin } = require('./lib/resolve_ohm_url'); const buildPipeServer = require('./server/build_pipe_server'); const { @@ -100,6 +101,7 @@ function initConfig(config) { global: false }, resolve: { + plugins: [new OHMResolverPlugin()], extensions: ['.js', '.ets', '.ts', '.d.ts'], modules: [ projectPath,