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