• 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 loaderUtils from 'loader-utils'
21import path from 'path'
22import fs from 'fs'
23
24import * as legacy from './legacy'
25import {
26  parseFragment
27}
28from './parser'
29import {
30  getNameByPath,
31  getRequireString,
32  stringifyLoaders,
33  logWarn,
34  loadBabelModule,
35  elements
36}
37from './util'
38import { isReservedTag } from './templater/component_validator'
39
40const { DEVICE_LEVEL } = require('./lite/lite-enum')
41const loaderPath = __dirname
42const defaultLoaders = {
43  none: '',
44  main: path.resolve(loaderPath, 'loader.js'),
45  template: path.resolve(loaderPath, 'template.js'),
46  style: path.resolve(loaderPath, 'style.js'),
47  script: path.resolve(loaderPath, 'script.js'),
48  json: path.resolve(loaderPath, 'json.js'),
49  babel: loadBabelModule('babel-loader'),
50  manifest: path.resolve(loaderPath, 'manifest-loader.js'),
51  resourceReferenceScript: path.resolve(loaderPath, 'resource-reference-script.js')
52}
53
54function getLoaderString (type, config) {
55  config = config || {}
56  const customLoader = loadCustomLoader(config)
57  let loaders
58  switch (type) {
59    case 'main':
60      return mainLoaderString(loaders)
61    case 'element':
62      return elementLoaderString(loaders, config)
63    case 'template':
64      return templateLoaderString(loaders, config, customLoader)
65    case 'style':
66      return styleLoaderString(loaders, config, customLoader)
67    case 'script':
68      return scriptLoaderString(loaders, config, customLoader)
69    case 'config':
70      return configLoaderString(loaders, config)
71    case 'data':
72      return dataLoaderString(loaders, config)
73  }
74}
75
76function loadCustomLoader (config) {
77  if (config.lang && config.customLang[config.lang]) {
78    return loadBabelModule(config.customLang[config.lang][0])
79  }
80}
81
82function mainLoaderString (loaders) {
83  loaders = [{
84    name: defaultLoaders.main
85  }]
86  return stringifyLoaders(loaders)
87}
88
89function elementLoaderString (loaders, config) {
90  loaders = [{
91    name: defaultLoaders.main,
92    query: {
93      element: config.source ? undefined : true
94    }
95  }]
96  return stringifyLoaders(loaders)
97}
98
99function templateLoaderString (loaders, config, customLoader) {
100  loaders = [{
101    name: defaultLoaders.json
102  }, {
103    name: defaultLoaders.template
104  }]
105  if (customLoader) {
106    loaders = loaders.concat(customLoader)
107  }
108  return stringifyLoaders(loaders)
109}
110
111function styleLoaderString (loaders, config, customLoader) {
112  loaders = [{
113    name: defaultLoaders.json
114  }, {
115    name: defaultLoaders.style
116  }]
117  if (customLoader) {
118    loaders = loaders.concat(customLoader)
119  }
120  return stringifyLoaders(loaders)
121}
122
123function scriptLoaderString (loaders, config, customLoader) {
124  loaders = [{
125    name: defaultLoaders.script
126  }]
127  if (customLoader) {
128    loaders = loaders.concat(customLoader)
129  }
130  else {
131    const isTargets = {
132      'extends': path.resolve(__dirname, "../babel.config.js")
133    }
134    if (process.env.DEVICE_LEVEL === DEVICE_LEVEL.RICH) {
135      isTargets['targets'] = 'node 8';
136    }
137    loaders.push({
138      name: defaultLoaders.babel,
139      query: isTargets,
140    })
141    loaders.push({
142      name: defaultLoaders.resourceReferenceScript
143    })
144  }
145  if (config.app && process.env.abilityType === 'page' &&
146    fs.existsSync(process.env.aceManifestPath)) {
147    loaders.push({
148      name: defaultLoaders.manifest,
149      query: {
150        path: config.source
151      }
152    })
153  }
154  return stringifyLoaders(loaders)
155}
156
157function configLoaderString (loaders, config) {
158  loaders = [{
159    name: defaultLoaders.json
160  }]
161  return stringifyLoaders(loaders)
162}
163
164function dataLoaderString (loaders, config) {
165  loaders = [{
166    name: defaultLoaders.json
167  }]
168  return stringifyLoaders(loaders)
169}
170
171function loader (source) {
172  this.cacheable && this.cacheable()
173
174  const options = {
175    lang: {
176      sass:['sass-loader'],
177      scss:['sass-loader'],
178      less:['less-loader']
179    }
180  }
181  const customLang = options.lang || {}
182  const resourceQuery = this.resourceQuery && loaderUtils.parseQuery(this.resourceQuery) || {}
183  const isEntry = resourceQuery.entry
184  const dirName = path.parse(this.resourcePath)
185  const name = isEntry ? dirName.name : resourceQuery.name || getNameByPath(this.resourcePath)
186  let parentPath = resourceQuery.parentPath || this.resourcePath;
187  if (isEntry) {
188    elements[this.resourcePath] = elements[this.resourcePath] || {};
189    elements[this.resourcePath][name] = true;
190  } else {
191    elements[this.resourcePath] = elements[this.resourcePath] || {};
192    elements[this.resourcePath]["parent"] = parentPath;
193    if (elements[parentPath] && elements[parentPath]["parent"]) {
194      elements[this.resourcePath]["parent"] = elements[elements[parentPath]["parent"]];
195      parentPath = elements[this.resourcePath]["parent"];
196    }
197  }
198  if (isReservedTag(name) && process.env.abilityType === 'page') {
199    logWarn(this, [{
200      reason: 'ERROR: The file name cannot contain reserved tag name: ' + name
201    }])
202    return ''
203  }
204  let output = ''
205  //  import app.js
206  output += loadApp(this, name, isEntry, customLang, source)
207  output += loadPage(this, name, isEntry, customLang, source, parentPath);
208  return output
209}
210
211function checkApp(_this) {
212  if (process.env.abilityType === 'testrunner') {
213    return true;
214  }
215  return _this.resourcePath === path.resolve(process.env.projectPath,
216    process.env.abilityType === 'page' ? 'app.js' : `${process.env.abilityType}.js`)
217}
218
219function loadApp (_this, name, isEntry, customLang, source) {
220  let output = ''
221  let extcss = false
222  if (checkApp(_this)) {
223    const filename = _this.resourcePath.replace(path.extname(_this.resourcePath).toString(), '')
224     // find css
225    const cssFileName = filename + '.css'
226    if (!fs.existsSync(cssFileName)) {
227      extcss = false
228    }
229    else {
230      extcss = true
231      output += 'var $app_style$ = ' + getRequireString(_this, getLoaderString('style', {
232        customLang,
233        lang: undefined,
234        element: undefined,
235        elementName: undefined,
236        source: cssFileName
237      }), cssFileName)
238    }
239    output += 'var $app_script$ = ' + getRequireString(_this, getLoaderString('script', {
240      customLang,
241      lang: undefined,
242      element: undefined,
243      elementName: undefined,
244      source: _this.resourcePath,
245      app: true
246    }), _this.resourcePath)
247
248    if (process.env.DEVICE_LEVEL === DEVICE_LEVEL.RICH || process.env.DEVICE_LEVEL === 'card') {
249      output += `
250      $app_define$('@app-application/${name}', [], function($app_require$, $app_exports$, $app_module$) {
251      ` + `
252      $app_script$($app_module$, $app_exports$, $app_require$)
253      if ($app_exports$.__esModule && $app_exports$.default) {
254        $app_module$.exports = $app_exports$.default
255      }
256      ` + (extcss ? `
257      $app_module$.exports.style = $app_style$
258      ` : '')
259      + `
260      })
261      `
262      if (isEntry) {
263        output += `$app_bootstrap$('@app-application/${name}'` + ',undefined' + ',undefined' + `)`
264      }
265    }
266    if (process.env.DEVICE_LEVEL === DEVICE_LEVEL.LITE) {
267      output += `var options=$app_script$\n if ($app_script$.__esModule) {\n
268        options = $app_script$.default;\n }\n` +
269      (extcss ? `options.styleSheet=$app_style$\n` : ``) +
270      `module.exports=new ViewModel(options);`
271    }
272    return output
273  } else if (/\.js$/.test(_this.resourcePath)) {
274    return source
275  } else {
276    return output
277  }
278}
279
280function loadPage (_this, name, isEntry, customLang, source, parentPath) {
281  let output = ''
282  if (path.extname(_this.resourcePath).match(/\.hml/)) {
283    const filename = _this.resourcePath.replace(path.extname(_this.resourcePath).toString(), '')
284    const resourcePath = _this.resourcePath
285    const loaderQuery = loaderUtils.getOptions(_this) || {}
286    const isElement = loaderQuery.element
287    const frag = parseFragment(source)
288    const elementNames = []
289    const elementLength = frag.element.length
290    output += loadPageCheckElementLength(_this, elementLength, frag, elementNames, resourcePath,
291      customLang, parentPath);
292
293    output += 'var $app_template$ = ' + getRequireString(_this, getLoaderString('template', {
294      customLang,
295      lang: undefined,
296      element: isElement,
297      elementName: isElement ? name : undefined,
298      source: _this.resourcePath
299    }), _this.resourcePath)
300
301    // find css
302    const cssContent = loadPageFindCss(_this, filename, customLang)
303    const extcss = cssContent.extcss
304    output += cssContent.output
305
306    // find js
307    const scriptContent = loadPageFindJs(_this, filename, customLang)
308    const extscript = scriptContent.extscript
309    output += scriptContent.output
310
311    output += process.env.DEVICE_LEVEL === DEVICE_LEVEL.RICH ? loadPageCheckRich(name, extscript, extcss, isEntry) :
312      loadPageCheckLite(extscript, extcss)
313    return output
314  }
315  return output
316}
317
318function loadPageCheckElementLength (_this, elementLength, frag, elementNames, resourcePath,
319  customLang, parentPath) {
320  let output = ''
321  if (elementLength) {
322    for (let i = 0; i < elementLength; i++) {
323      const element = frag.element[i]
324      let src = resourcePath
325      if (element.src) {
326        src = element.src
327        if (!src.match(/\.hml$/)) {
328          src = src.concat('.hml')
329        }
330        const filePath = path.join(path.dirname(resourcePath), src)
331        if (!fs.existsSync(filePath) && src.match(/^(\/|\.)/)) {
332          logWarn(_this, [{
333            reason: 'ERROR: The file path of custom element does not exist, src: ' + src
334          }])
335          return ''
336        }
337        if (!element.name) {
338          element.name = path.parse(src).name
339        }
340        element.name = element.name.toLowerCase();
341        elements[parentPath] = elements[parentPath] || {};
342        if (elements[parentPath][element.name]) {
343          logWarn(_this, [{
344            reason: `ERROR: The element name can not be same with the page ` +
345              `"${element.name}" (ignore case).`
346          }]);
347        } else {
348          elements[parentPath][element.name] = true;
349        }
350        checkEntry(_this, filePath, element.src)
351      }
352      else {
353        logWarn(_this, [{
354          reason: 'ERROR: src attributes must be set for custom elements.'
355        }])
356        return ''
357      }
358      elementNames.push(element.name)
359      output += getRequireString(_this, getLoaderString('element', {
360        customLang,
361        name: element.name,
362        source: src
363      }), `${src}?name=${element.name}&parentPath=${parentPath}`)
364    }
365  }
366  return output
367}
368
369function loadPageFindCss (_this, filename, customLang) {
370  let output = ''
371  let extcss = false
372  const cssFileName = filename + '.css'
373  if (fs.existsSync(cssFileName)) {
374    extcss = true
375    output = 'var $app_style$ = ' + getRequireString(_this, getLoaderString('style', {
376      customLang,
377      lang: undefined,
378      element: undefined,
379      elementName: undefined,
380      source: cssFileName
381    }), cssFileName)
382  }
383  else {
384    // find less
385    const lessFileName = filename + '.less'
386    if (fs.existsSync(lessFileName)) {
387      extcss = true
388      output = 'var $app_style$ = ' + getRequireString(_this, getLoaderString('style', {
389        customLang,
390        lang: 'less',
391        element: undefined,
392        elementName: undefined,
393        source: lessFileName
394      }), lessFileName)
395    }
396    else {
397      // find scss
398      const scssFileName = filename + '.scss'
399      if (fs.existsSync(scssFileName)) {
400        extcss = true
401        output = 'var $app_style$ = ' + getRequireString(_this, getLoaderString('style', {
402          customLang,
403          lang: 'scss',
404          element: undefined,
405          elementName: undefined,
406          source: scssFileName
407        }), scssFileName)
408      }
409      else {
410        // find sass
411        const sassFileName = filename + '.sass'
412        if (fs.existsSync(sassFileName)) {
413          extcss = true
414          output = 'var $app_style$ = ' + getRequireString(_this, getLoaderString('style', {
415            customLang,
416            lang: 'sass',
417            element: undefined,
418            elementName: undefined,
419            source: sassFileName
420          }), sassFileName)
421        }
422        else {
423          extcss = false
424        }
425      }
426    }
427  }
428  return {
429    extcss: extcss,
430    output: output
431  }
432}
433
434function loadPageFindJs (_this, filename, customLang) {
435  let output = ''
436  let extscript = false
437  const jsFileName = filename + '.js'
438  if (!fs.existsSync(jsFileName)) {
439    extscript = false
440    console.log('missing ' + jsFileName)
441  }
442  else {
443    extscript = true
444    output = 'var $app_script$ = ' + getRequireString(_this, getLoaderString('script', {
445      customLang,
446      lang: undefined,
447      element: undefined,
448      elementName: undefined,
449      source: jsFileName
450    }), jsFileName)
451  }
452  return {
453    extscript: extscript,
454    output: output
455  }
456}
457
458function loadPageCheckRich (name, extscript, extcss, isEntry) {
459  let output = ''
460  output += `
461$app_define$('@app-component/${name}', [], function($app_require$, $app_exports$, $app_module$) {
462` + (extscript ? `
463$app_script$($app_module$, $app_exports$, $app_require$)
464if ($app_exports$.__esModule && $app_exports$.default) {
465$app_module$.exports = $app_exports$.default
466}
467` : '') + `
468$app_module$.exports.template = $app_template$
469` + (extcss ? `
470$app_module$.exports.style = $app_style$
471` : '') + `
472})
473`
474  if (isEntry) {
475    output += `$app_bootstrap$('@app-component/${name}'` + ',undefined' + ',undefined' + `)`
476  }
477  return output
478}
479
480function loadPageCheckLite (extscript, extcss) {
481  return (extscript ? `var options=$app_script$\n if ($app_script$.__esModule) {\n
482      options = $app_script$.default;\n }\n` : `var options={}\n`) +
483    (extcss ? `options.styleSheet=$app_style$\n` : ``) +
484    `options.render=$app_template$;\nmodule.exports=new ViewModel(options);`
485}
486
487for (const key in legacy) {
488  loader[key] = legacy[key]
489}
490
491function checkEntry(_this, filePath, elementSrc) {
492  if (_this._compilation.entries) {
493    for (var key of _this._compilation.entries.keys()) {
494      const entryPath = path.join(path.resolve(process.env.projectPath), key + '.hml');
495      if (entryPath === filePath) {
496        logWarn(_this, [{
497          reason: `WARNING: The page "${elementSrc}" configured in 'config.json'` +
498            ` can not be uesd as a custom component.` +
499            `To ensure that the debugging function is normal, please delete this page in 'config.json'.`
500        }]);
501      }
502    }
503  }
504}
505
506module.exports = loader