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