1/* 2 * Licensed to the Apache Software Foundation (ASF) under one 3 * or more contributor license agreements. See the NOTICE file 4 * distributed with this work for additional information 5 * regarding copyright ownership. The ASF licenses this file 6 * to you under the Apache License, Version 2.0 (the 7 * "License"); you may not use this file except in compliance 8 * with the License. You may obtain a copy of the License at 9 * 10 * http://www.apache.org/licenses/LICENSE-2.0 11 * 12 * Unless required by applicable law or agreed to in writing, 13 * software distributed under the License is distributed on an 14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 * KIND, either express or implied. See the License for the 16 * specific language governing permissions and limitations 17 * under the License. 18 */ 19 20import path from 'path' 21import fs from 'fs' 22import loaderUtils from 'loader-utils' 23const crypto = require("crypto") 24const JSON5 = require('json5'); 25import { 26 SourceMapGenerator, 27 SourceMapConsumer 28} from 'source-map' 29 30const { systemModules } = require('../main.product') 31const { DEVICE_LEVEL } = require('./lite/lite-enum') 32export const useOSFiles = new Set(); 33export const elements = {}; 34 35export function getNameByPath (resourcePath) { 36 return path.basename(resourcePath).replace(/\..*$/, '') 37} 38 39export function getFileNameWithHash (resourcePath, content) { 40 const filename = path.relative('.', resourcePath) 41 const hash = crypto.createHash('sha256') 42 hash.update((filename + content).toString()) 43 const cacheKey = hash.digest('hex') 44 return `./${filename}?${cacheKey}` 45} 46 47export function getFilenameByPath (filepath) { 48 return path.relative('.', filepath) 49} 50 51export const FUNC_START = '#####FUN_S#####' 52export const FUNC_START_REG = new RegExp('["\']' + FUNC_START, 'g') 53export const FUNC_END = '#####FUN_E#####' 54export const FUNC_END_REG = new RegExp(FUNC_END + '["\']', 'g') 55 56export function stringifyFunction (key, value) { 57 if (typeof value === 'function') { 58 return FUNC_START + value.toString() + FUNC_END 59 } 60 return value 61} 62 63export function logWarn (loader, logs) { 64 // add flag to determine if there is an error log 65 let flag = false 66 if (process.env.logLevel > 0) { 67 if (logs && logs.length) { 68 logs.forEach(log => { 69 if (log.reason.startsWith('NOTE') && parseInt(process.env.logLevel) <= 1) { 70 if (log.line && log.column) { 71 loader.emitWarning('noteStartNOTE File:' + loader.resourcePath + ':' + 72 log.line + ':' + log.column + '\n ' + log.reason.replace('NOTE: ', '') + 'noteEnd') 73 } else { 74 loader.emitWarning('noteStartNOTE File:' + loader.resourcePath + 75 '\n ' + log.reason.replace('NOTE: ', '') + 'noteEnd') 76 } 77 } else if (log.reason.startsWith('WARN') && parseInt(process.env.logLevel) <= 2) { 78 if (log.line && log.column) { 79 loader.emitWarning('warnStartWARNING File:' + loader.resourcePath + ':' + 80 log.line + ':' + log.column + '\n ' + log.reason.replace('WARNING: ', '') + 'warnEnd') 81 } else { 82 loader.emitWarning('warnStartWARNING File:' + loader.resourcePath + 83 '\n ' + log.reason.replace('WARNING: ', '') + 'warnEnd') 84 } 85 } else if (log.reason.startsWith('ERROR') && parseInt(process.env.logLevel) <= 3) { 86 flag = true 87 if (log.line && log.column) { 88 loader.emitError('errorStartERROR File:' + loader.resourcePath + ':' + 89 log.line + ':' + log.column + '\n ' + log.reason.replace('ERROR: ', '') + 'errorEnd') 90 } else { 91 loader.emitError('errorStartERROR File:' + loader.resourcePath + 92 '\n ' + log.reason.replace('ERROR: ', '') + 'errorEnd') 93 } 94 } 95 }) 96 } 97 } 98 return flag 99} 100 101export function getRequireString (loaderContext, loader, filepath) { 102 return 'require(' + 103 loaderUtils.stringifyRequest( 104 loaderContext, 105 loader ? 106 `!!${loader}!${filepath}` : 107 `${filepath}` 108 ) + 109 ')\n' 110} 111 112export function stringifyLoaders (loaders) { 113 return loaders.map(loader => { 114 if (typeof loader === 'string') { 115 return loader 116 } 117 else { 118 const name = loader.name 119 const query = [] 120 if (loader.query) { 121 for (const k in loader.query) { 122 const v = loader.query[k] 123 if (v != null) { 124 if (v === true) { 125 query.push(k) 126 } 127 else if (v instanceof Array) { 128 query.push(`${k}[]=${v.join(',')}`) 129 } 130 else { 131 query.push(`${k}=${v}`) 132 } 133 } 134 } 135 } 136 return `${name}${query.length ? ('?' + query.join('&')) : ''}` 137 } 138 }).join('!') 139} 140 141export function generateMap (loader, source, iterator) { 142 const filePath = loader.resourcePath 143 144 const fileNameWithHash = getFileNameWithHash(filePath) 145 const sourceRoot = path.resolve('.') 146 147 const map = new SourceMapGenerator({ 148 sourceRoot, 149 skipValidation: true 150 }) 151 map.setSourceContent(fileNameWithHash, source) 152 153 for (const { original, generated } of iterator) { 154 map.addMapping({ 155 source: fileNameWithHash, 156 original, 157 generated 158 }) 159 } 160 161 return map 162} 163 164export function consumeMap (loader, target, map) { 165 const smc = new SourceMapConsumer(map) 166 let source 167 const original = [] 168 const generated = [] 169 const mapping = {} 170 171 splitSourceLine(target) 172 .forEach((input, line) => { 173 const column = 0 174 line = line + 1 175 176 const pos = smc.originalPositionFor({ 177 line, 178 column 179 }) 180 181 if (pos.source) { 182 source = pos.source 183 original.push({ 184 line: pos.line, 185 column: pos.column 186 }) 187 generated.push({ 188 line, 189 column 190 }) 191 mapping[`line-${line}-column-${column}`] = { 192 line: pos.line, 193 column: pos.column 194 } 195 } 196 }) 197 198 return { 199 source, 200 original, 201 generated, 202 mapping, 203 sourcesContent: smc.sourcesContent 204 } 205} 206 207const LINE_REG = /\r?\n/g 208export function splitSourceLine (source) { 209 return source.split(LINE_REG) 210} 211 212export function printSourceWithLine (source) { 213 console.log('>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>') 214 source = splitSourceLine(source) 215 .map((input, line) => { 216 console.log(line + 1 + ':', input) 217 }) 218 console.log('<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<') 219} 220 221export function loadBabelModule (moduleName) { 222 try { 223 const filePath = require.resolve(moduleName) 224 return filePath.slice(0, filePath.indexOf(moduleName.replace(/\//g, path.sep)) + moduleName.length) 225 } 226 catch (e) { 227 return moduleName 228 } 229} 230 231const methodForLite = 232` 233function requireModule(moduleName) { 234 return requireNative(moduleName.slice(1)); 235} 236` 237const methodForOthers = 238` 239function requireModule(moduleName) { 240 const systemList = ['system.router', 'system.app', 'system.prompt', 'system.configuration', 241 'system.image', 'system.device', 'system.mediaquery', 'ohos.animator', 'system.grid', 'system.resource'] 242 var target = '' 243 if (systemList.includes(moduleName.replace('@', ''))) { 244 target = $app_require$('@app-module/' + moduleName.substring(1)); 245 return target; 246 } 247 var shortName = moduleName.replace(/@[^.]+\.([^.]+)/, '$1'); 248 target = requireNapi(shortName); 249 if (typeof target !== 'undefined' && /@ohos/.test(moduleName)) { 250 return target; 251 } 252 if (typeof ohosplugin !== 'undefined' && /@ohos/.test(moduleName)) { 253 target = ohosplugin; 254 for (let key of shortName.split('.')) { 255 target = target[key]; 256 if(!target) { 257 break; 258 } 259 } 260 if (typeof target !== 'undefined') { 261 return target; 262 } 263 } 264 if (typeof systemplugin !== 'undefined') { 265 target = systemplugin; 266 for (let key of shortName.split('.')) { 267 target = target[key]; 268 if(!target) { 269 break; 270 } 271 } 272 if (typeof target !== 'undefined') { 273 return target; 274 } 275 } 276 return target; 277} 278` 279export function parseRequireModule (source, resourcePath) { 280 const requireMethod = process.env.DEVICE_LEVEL === DEVICE_LEVEL.LITE ? methodForLite : methodForOthers 281 source = `${source}\n${requireMethod}` 282 const requireReg = /require\(['"]([^()]+)['"]\)/g 283 const libReg = /^lib(.+)\.so$/ 284 const REG_SYSTEM = /@(system|ohos)\.(\S+)/g; 285 let requireStatements = source.match(requireReg) 286 if (requireStatements && requireStatements.length) { 287 for (let requireStatement of requireStatements) { 288 const requireStatementExec = /\((\"|\')(.+)(\"|\')\)/.exec(requireStatement); 289 checkModuleIsVaild(requireStatementExec, resourcePath); 290 if (requireStatement.match(REG_SYSTEM) && requireStatementExec && requireStatementExec.length > 3) { 291 if (systemModules.length === 0 || systemModules.includes(requireStatementExec[2] + '.d.ts') || 292 process.env.DEVICE_LEVEL === 'lite') { 293 source = source.replace(requireStatement, requireStatement.replace('require', 'requireModule')); 294 } 295 } 296 } 297 } 298 source = source.replace(requireReg, (item, item1) => { 299 if (libReg.test(item1)) { 300 item = `requireNapi("${item1.replace(libReg, '$1')}", true)` 301 if (resourcePath) { 302 useOSFiles.add(resourcePath); 303 } 304 } 305 return item 306 }) 307 return source 308} 309 310export function jsonLoaders (type, customLoader, isVisual, queryType) { 311 let loaders = [] 312 313 switch (type) { 314 case "template": 315 loaders = [{ 316 name: path.resolve(__dirname, 'json.js') 317 }, { 318 name: path.resolve(__dirname, 'template.js') 319 }] 320 break 321 case "style": 322 loaders = [{ 323 name: path.resolve(__dirname, 'json.js') 324 }, { 325 name: path.resolve(__dirname, 'style.js') 326 }] 327 break 328 case "json": 329 loaders = [{ 330 name: path.resolve(__dirname, 'json.js') 331 }] 332 break 333 default: 334 break 335 } 336 337 if (customLoader) { 338 loaders.push({ 339 name: path.resolve(__dirname, `../node_modules/${customLoader}`) 340 }) 341 } 342 343 if (isVisual) { 344 loaders.push({ 345 name: path.resolve(__dirname, 'extgen.js'), 346 query: { 347 type: queryType 348 } 349 }) 350 } 351 352 return stringifyLoaders(loaders) 353} 354 355export function circularFile(inputPath, outputPath) { 356 if ((!inputPath) || (!outputPath)) { 357 return; 358 } 359 fs.readdir(inputPath,function(err, files){ 360 if (!files) { 361 return; 362 } 363 files.forEach(file => { 364 const inputFile = path.resolve(inputPath, file); 365 const outputFile = path.resolve(outputPath, file); 366 const fileStat = fs.statSync(inputFile); 367 if (fileStat.isFile()) { 368 copyFile(inputFile, outputFile); 369 } else { 370 circularFile(inputFile, outputFile); 371 } 372 }); 373 }) 374} 375 376function copyFile(inputFile, outputFile) { 377 try { 378 const parent = path.join(outputFile, '..'); 379 if (!(fs.existsSync(parent) && fs.statSync(parent).isDirectory())) { 380 mkDir(parent); 381 } 382 if (path.parse(parent).name === 'i18n' && path.parse(inputFile).ext === '.json' && 383 fs.existsSync(outputFile)) { 384 copyJsonFile(inputFile, outputFile); 385 } else if (!fs.existsSync(outputFile)){ 386 fs.writeFileSync(outputFile, fs.readFileSync(inputFile)); 387 } 388 } catch (err) { 389 throw err; 390 } 391} 392 393function isPathUnderBase(fullPath, basePath) { 394 const normalizedFullPath = fullPath.replace(/\\/g, '/').toLowerCase(); 395 const normalizedBasePath = basePath.replace(/\\/g, '/').toLowerCase() + '/'; 396 397 return normalizedFullPath.startsWith(normalizedBasePath); 398} 399 400function checkModuleIsVaild(requireStatementExec, resourcePath) { 401 if (process.env.DEVICE_LEVEL !== 'lite' || !requireStatementExec || requireStatementExec.length <= 3) { 402 return; 403 } 404 405 const appJSPath = path.dirname(path.resolve(process.env.projectPath, 'app.js')); 406 if (!isPathUnderBase(resourcePath, appJSPath)) { 407 return; 408 } 409 410 const json5Path = path.join(process.env.projectPath, '../../../../', 'oh-package.json5'); 411 const dependencies = []; 412 if (fs.existsSync(json5Path)) { 413 const json5Content = fs.readFileSync(json5Path, 'utf8'); 414 const content = JSON5.parse(json5Content); 415 if (content['dependencies']) { 416 Object.keys(content['dependencies']).forEach(element =>{ 417 dependencies.push(element); 418 }) 419 } 420 } 421 422 if (dependencies.length === 0) { 423 return; 424 } 425 426 const moduleName = requireStatementExec[2]; 427 if (moduleName.includes('../')) { 428 return; 429 } 430 if (!dependencies.includes(moduleName)) { 431 throw new Error(`Cannot find module ${moduleName} or its corresponding type declarations.`); 432 } 433} 434 435function copyJsonFile(inputFile, outputFile) { 436 try { 437 const contentInput = JSON.parse(fs.readFileSync(inputFile, 'utf-8')); 438 const contentOutput = JSON.parse(fs.readFileSync(outputFile, 'utf-8')); 439 Object.keys(contentInput).forEach(function (key) { 440 const contentElementMerge = mergeJson(contentInput[key], contentOutput[key]); 441 contentOutput[key] = contentElementMerge; 442 }); 443 fs.writeFileSync(outputFile, JSON.stringify(contentOutput, null, '\t')); 444 } catch (err) { 445 throw err; 446 } 447} 448 449function mergeJson(inputValue, outputValue) { 450 if (outputValue === null || outputValue === undefined) { 451 return inputValue; 452 } 453 const typeInput = typeof inputValue; 454 if (typeInput === typeof outputValue && typeInput === 'object') { 455 Object.keys(inputValue).forEach(function (key) { 456 const contentElementMerge = mergeJson(inputValue[key], outputValue[key]); 457 outputValue[key] = contentElementMerge; 458 }) 459 } 460 return outputValue; 461} 462 463export function mkDir(path_) { 464 const parent = path.join(path_, '..'); 465 if (!(fs.existsSync(parent) && !fs.statSync(parent).isFile())) { 466 mkDir(parent); 467 } 468 fs.mkdirSync(path_); 469}