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