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