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