• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/*
2 * Copyright (c) 2021 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 *     http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16import path from 'path'
17import fs from 'fs'
18import {
19  getRequireString,
20  jsonLoaders,
21  logWarn
22}
23from './util'
24import { parseFragment } from './parser'
25
26/**
27 * Webpack loader for processing card components and their dependencies
28 * Handles templates, styles, and nested custom elements with validation
29 *
30 * @param {string} source - The source content of the file being processed
31 * @returns {string} Generated JavaScript code with all component dependencies
32 */
33function loader(source) {
34  this.cacheable && this.cacheable()
35  const options = {
36    lang: {
37      sass:['sass-loader'],
38      scss:['sass-loader'],
39      less:['less-loader']
40    }
41  }
42  const customLang = options.lang || {}
43  const resourcePath = this.resourcePath
44  const fileName = resourcePath.replace(path.extname(resourcePath).toString(), '')
45  let output = '//card_start\n'
46  output += 'var card_template =' + getRequireString(this, jsonLoaders('template'), resourcePath)
47  const styleInfo = findStyleFile(fileName)
48  if (styleInfo.extStyle == true) {
49    output += 'var card_style =' +
50      getRequireString(this, jsonLoaders('style', customLang[styleInfo.type]), styleInfo.styleFileName)
51  }
52  output = addJson(this, output, fileName, '')
53
54  const frag = parseFragment(source)
55  const nameSet = new Set()
56  if (frag.element) {
57    frag.element.forEach(item => {
58      let customElementName
59      if (!item.src) {
60        logWarn(this, [{
61          reason: `ERROR: The attribute 'src' must be set in the custom element.`,
62          line: item.node.__location.line,
63          column: item.node.__location.col
64        }])
65        return
66      }
67      if (!item.src.match(/\.hml$/)) {
68        item.src = item.src.concat('.hml')
69      }
70      const compResourcepath = path.join(resourcePath, '..', item.src)
71      if (!fs.existsSync(compResourcepath)) {
72        logWarn(this, [{
73          reason: `ERROR: The custom element '${compResourcepath}' can not be found.`,
74          line: item.node.__location.line,
75          column: item.node.__location.col
76        }])
77        return
78      }
79      if (!item.name) {
80        customElementName = path.parse(item.src).name.toLowerCase()
81      } else {
82        customElementName = item.name.toLowerCase()
83      }
84      if (nameSet.has(customElementName)) {
85        logWarn(this, [{
86          reason: `ERROR: The custom elements cannot have the same attribute 'name' or file name (case insensitive).`,
87          line: item.node.__location.line,
88          column: item.node.__location.col
89        }])
90        return
91      } else {
92        nameSet.add(customElementName)
93      }
94      const compFileName = compResourcepath.replace(path.extname(compResourcepath).toString(), '')
95      const elementLastName = path.basename(compResourcepath).replace(path.extname(compResourcepath).toString(), '')
96      output += `var card_element_template_${elementLastName} =` + getRequireString(this, jsonLoaders('template'),
97        compResourcepath + `?${customElementName}#${fileName}`)
98      const compStyleInfo = findStyleFile(compFileName)
99      if (compStyleInfo.extStyle == true) {
100        output += `var card_element_style_${elementLastName} =` +
101          getRequireString(this, jsonLoaders('style', customLang[compStyleInfo.type]),
102          compStyleInfo.styleFileName + `?${customElementName}#${fileName}`)
103      }
104      output = addJson(this, output, compFileName, `?${customElementName}#${fileName}`, elementLastName)
105    })
106  }
107  output = output + '\n//card_end'
108  return output
109}
110
111/**
112 * Finds and identifies style files associated with a given base filename
113 * Checks for multiple style file extensions (CSS, LESS, SASS, SCSS)
114 *
115 * @param {string} fileName - The base filename without extension
116 * @returns {Object} An object containing:
117 *            - extStyle: boolean indicating if style file exists
118 *            - styleFileName: full path to the style file (if exists)
119 *            - type: the style file type ('css', 'less', or 'sass')
120 */
121function findStyleFile (fileName) {
122  let extStyle = false
123  let styleFileName = fileName + '.css'
124  let type = 'css'
125  if (fs.existsSync(styleFileName)) {
126    extStyle = true
127    type = 'css'
128  } else {
129    styleFileName = fileName + '.less'
130    if (fs.existsSync(styleFileName)) {
131      extStyle = true
132      type = 'less'
133    } else {
134      styleFileName = fileName + '.sass'
135      if (fs.existsSync(styleFileName)) {
136        extStyle = true
137        type = 'sass'
138      } else {
139        styleFileName = fileName + '.scss'
140        if (fs.existsSync(styleFileName)) {
141          extStyle = true
142          type = 'sass'
143        } else {
144          extStyle = false
145        }
146      }
147    }
148  }
149  return {extStyle: extStyle, styleFileName: styleFileName, type: type}
150}
151
152/**
153 * Adds JSON configuration file loading to the output string
154 * Handles both .json and deprecated .js configuration files with appropriate warnings
155 *
156 * @param {Object} _this - Webpack loader context
157 * @param {string} output - Current output string to append to
158 * @param {string} fileName - Base filename without extension
159 * @param {string} query - Query string to append to the require path
160 * @param {string} [elementLastName] - Optional element name for scoped variables
161 * @returns {string} Updated output string with JSON configuration loading
162 */
163function addJson(_this, output, fileName, query, elementLastName) {
164  const content = `${elementLastName ? 'var card_element_json_' + elementLastName : 'var card_json'} =`
165  if (fs.existsSync(fileName + '.json') && !fs.existsSync(fileName + '.js')) {
166    output += content + getRequireString(_this, jsonLoaders('json'), fileName + '.json' + query)
167  } else if (fs.existsSync(fileName + '.js') && !fs.existsSync(fileName + '.json')) {
168    logWarn(_this, [{
169      reason: `WARNING: The JS file '${fileName}.js' will be discarded in future version, ` +
170        `use the JSON file '${fileName}.json' instead.`,
171    }])
172    output += content + getRequireString(_this, jsonLoaders('json'), fileName + '.js' + query)
173  } else if (fs.existsSync(fileName + '.json') && fs.existsSync(fileName + '.js')) {
174    logWarn(_this, [{
175      reason: `WARNING: '${fileName}' cannot have the same name files '.json' and '.js', otherwise '.json' in default.`,
176    }])
177    output += content + getRequireString(_this, jsonLoaders('json'), fileName + '.json' + query)
178  }
179  return output
180}
181
182module.exports = loader