• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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}