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