From b0710aa89204d2351879353d5b6ebf921905a9b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=8D=A1=E5=93=A5?= Date: Fri, 27 Jun 2025 15:00:56 +0800 Subject: [PATCH] lizhouze@huawei.com MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: 卡哥 --- ace-loader/src/card-loader.js | 298 ++++++++++++++++++++++------------ 1 file changed, 192 insertions(+), 106 deletions(-) diff --git a/ace-loader/src/card-loader.js b/ace-loader/src/card-loader.js index 8f23da4..4df3aea 100644 --- a/ace-loader/src/card-loader.js +++ b/ace-loader/src/card-loader.js @@ -23,132 +23,218 @@ import { from './util' import { parseFragment } from './parser' +/** + * Webpack loader for card components that processes templates, styles, and JSON files. + * @param {string} source - Source file content + * @returns {string} Processed output with all required resources + */ function loader(source) { - this.cacheable && this.cacheable() + this.cacheable?.(); const options = { lang: { - sass:['sass-loader'], - scss:['sass-loader'], - less:['less-loader'] + sass: ['sass-loader'], + scss: ['sass-loader'], + less: ['less-loader'] } + }; + + const resourcePath = this.resourcePath; + const outputParts = ['//card_start']; + + // Process main file resources + processMainFile(this, resourcePath, options, outputParts); + + // Process custom elements + const frag = parseFragment(source); + if (frag.element) { + processCustomElements(this, frag, resourcePath, options, outputParts); } - const customLang = options.lang || {} - const resourcePath = this.resourcePath - const fileName = resourcePath.replace(path.extname(resourcePath).toString(), '') - let output = '//card_start\n' - output += 'var card_template =' + getRequireString(this, jsonLoaders('template'), resourcePath) - const styleInfo = findStyleFile(fileName) - if (styleInfo.extStyle == true) { - output += 'var card_style =' + - getRequireString(this, jsonLoaders('style', customLang[styleInfo.type]), styleInfo.styleFileName) + + return outputParts.join('') + '\n//card_end'; +} + +/** + * Processes the main file resources (template, style, JSON) + * @param {Object} context - Loader context + * @param {string} resourcePath - Path to the main resource file + * @param {Object} options - Loader options + * @param {Array} outputParts - Array to collect output parts + */ +function processMainFile(context, resourcePath, options, outputParts) { + const fileName = resourcePath.replace(path.extname(resourcePath), ''); + + outputParts.push(`var card_template=${getRequireString(context, jsonLoaders('template'), resourcePath)}`); + + const styleInfo = findStyleFile(fileName); + if (styleInfo.extStyle) { + const loaders = jsonLoaders('style', options.lang[styleInfo.type]); + outputParts.push(`var card_style=${getRequireString(context, loaders, styleInfo.styleFileName)}`); } - output = addJson(this, output, fileName, '') - const frag = parseFragment(source) - const nameSet = new Set() - if (frag.element) { - frag.element.forEach(item => { - let customElementName - if (!item.src) { - logWarn(this, [{ - reason: `ERROR: The attribute 'src' must be set in the custom element.`, - line: item.node.__location.line, - column: item.node.__location.col - }]) - return - } - if (!item.src.match(/\.hml$/)) { - item.src = item.src.concat('.hml') - } - const compResourcepath = path.join(resourcePath, '..', item.src) - if (!fs.existsSync(compResourcepath)) { - logWarn(this, [{ - reason: `ERROR: The custom element '${compResourcepath}' can not be found.`, - line: item.node.__location.line, - column: item.node.__location.col - }]) - return - } - if (!item.name) { - customElementName = path.parse(item.src).name.toLowerCase() - } else { - customElementName = item.name.toLowerCase() - } - if (nameSet.has(customElementName)) { - logWarn(this, [{ - reason: `ERROR: The custom elements cannot have the same attribute 'name' or file name (case insensitive).`, - line: item.node.__location.line, - column: item.node.__location.col - }]) - return - } else { - nameSet.add(customElementName) - } - const compFileName = compResourcepath.replace(path.extname(compResourcepath).toString(), '') - const elementLastName = path.basename(compResourcepath).replace(path.extname(compResourcepath).toString(), '') - output += `var card_element_template_${elementLastName} =` + getRequireString(this, jsonLoaders('template'), - compResourcepath + `?${customElementName}#${fileName}`) - const compStyleInfo = findStyleFile(compFileName) - if (compStyleInfo.extStyle == true) { - output += `var card_element_style_${elementLastName} =` + - getRequireString(this, jsonLoaders('style', customLang[compStyleInfo.type]), - compStyleInfo.styleFileName + `?${customElementName}#${fileName}`) + outputParts.push(addJson(context, '', fileName, '')); +} + +/** + * Processes all custom elements in the fragment + * @param {Object} context - Loader context + * @param {Object} frag - Parsed fragment containing elements + * @param {string} resourcePath - Path to the main resource file + * @param {Object} options - Loader options + * @param {Array} outputParts - Array to collect output parts + */ +function processCustomElements(context, frag, resourcePath, options, outputParts) { + const nameSet = new Set(); + const baseFileName = path.basename(resourcePath.replace(path.extname(resourcePath), '')); + + for (const item of frag.element) { + const { node } = item; + const location = node.__location; + + // Validate and process element + const elementResult = processSingleElement(context, item, resourcePath, baseFileName, nameSet, location); + if (elementResult) { + const { output, compFileName, customElementName, elementLastName } = elementResult; + + // Process element resources + const query = `?${customElementName}#${baseFileName}`; + outputParts.push( + `var card_element_template_${elementLastName}=` + + getRequireString(context, jsonLoaders('template'), `${output}${query}`) + ); + + const compStyleInfo = findStyleFile(compFileName); + if (compStyleInfo.extStyle) { + const loaders = jsonLoaders('style', options.lang[compStyleInfo.type]); + outputParts.push( + `var card_element_style_${elementLastName}=` + + getRequireString(context, loaders, `${compStyleInfo.styleFileName}${query}`) + ); } - output = addJson(this, output, compFileName, `?${customElementName}#${fileName}`, elementLastName) - }) + + outputParts.push(addJson(context, '', compFileName, query, elementLastName)); + } } - output = output + '\n//card_end' - return output } -function findStyleFile (fileName) { - let extStyle = false - let styleFileName = fileName + '.css' - let type = 'css' - if (fs.existsSync(styleFileName)) { - extStyle = true - type = 'css' - } else { - styleFileName = fileName + '.less' +/** + * Processes a single custom element with validation + * @returns {Object|null} Element processing result or null if invalid + */ +function processSingleElement(context, item, resourcePath, baseFileName, nameSet, location) { + if (!item.src) { + logWarn(context, [{ + reason: 'ERROR: The attribute \'src\' must be set in the custom element.', + line: location.line, + column: location.col + }]); + return null; + } + + const src = item.src.endsWith('.hml') ? item.src : `${item.src}.hml`; + const compResourcePath = path.join(path.dirname(resourcePath), src); + + if (!fs.existsSync(compResourcePath)) { + logWarn(context, [{ + reason: `ERROR: The custom element '${compResourcePath}' cannot be found.`, + line: location.line, + column: location.col + }]); + return null; + } + + const customElementName = (item.name || path.parse(src).name).toLowerCase(); + if (nameSet.has(customElementName)) { + logWarn(context, [{ + reason: 'ERROR: Custom elements cannot have the same attribute \'name\' or file name (case insensitive).', + line: location.line, + column: location.col + }]); + return null; + } + + nameSet.add(customElementName); + const compFileName = compResourcePath.replace(path.extname(compResourcePath), ''); + const elementLastName = path.basename(compFileName); + + return { + output: compResourcePath, + compFileName, + customElementName, + elementLastName + }; +} + +/** + * Finds the corresponding style file for a given file name. + * @param {string} fileName - Base file name without extension + * @returns {Object} Result containing existence flag, file path and type + */ +function findStyleFile(fileName) { + const styleExtensions = [ + { ext: '.css', type: 'css' }, + { ext: '.less', type: 'less' }, + { ext: '.sass', type: 'sass' }, + { ext: '.scss', type: 'scss' } + ]; + + for (const { ext, type } of styleExtensions) { + const styleFileName = fileName + ext; if (fs.existsSync(styleFileName)) { - extStyle = true - type = 'less' - } else { - styleFileName = fileName + '.sass' - if (fs.existsSync(styleFileName)) { - extStyle = true - type = 'sass' - } else { - styleFileName = fileName + '.scss' - if (fs.existsSync(styleFileName)) { - extStyle = true - type = 'sass' - } else { - extStyle = false - } - } + return { + extStyle: true, + styleFileName, + type + }; } } - return {extStyle: extStyle, styleFileName: styleFileName, type: type} + + return { + extStyle: false, + styleFileName: fileName + '.css', + type: 'css' + }; } +/** + * Adds JSON content to output with appropriate warnings and fallbacks + * @param {Object} _this - Context reference + * @param {string} output - Current output string + * @param {string} fileName - Base file name without extension + * @param {string} query - Query string to append + * @param {string} [elementLastName] - Optional element name suffix + * @returns {string} Updated output string + */ function addJson(_this, output, fileName, query, elementLastName) { - const content = `${elementLastName ? 'var card_element_json_' + elementLastName : 'var card_json'} =` - if (fs.existsSync(fileName + '.json') && !fs.existsSync(fileName + '.js')) { - output += content + getRequireString(_this, jsonLoaders('json'), fileName + '.json' + query) - } else if (fs.existsSync(fileName + '.js') && !fs.existsSync(fileName + '.json')) { + const content = `${elementLastName ? `var card_element_json_${elementLastName}` : 'var card_json'}=`; + const jsonFile = `${fileName}.json`; + const jsFile = `${fileName}.js`; + + const jsonExists = fs.existsSync(jsonFile); + const jsExists = fs.existsSync(jsFile); + + if (!jsonExists && !jsExists) { + return output; + } + + // Determine which file to use with appropriate warnings + let targetFile; + if (jsonExists && !jsExists) { + targetFile = jsonFile; + } else if (jsExists && !jsonExists) { logWarn(_this, [{ - reason: `WARNING: The JS file '${fileName}.js' will be discarded in future version, ` + - `use the JSON file '${fileName}.json' instead.`, - }]) - output += content + getRequireString(_this, jsonLoaders('json'), fileName + '.js' + query) - } else if (fs.existsSync(fileName + '.json') && fs.existsSync(fileName + '.js')) { + reason: `WARNING: The JS file '${jsFile}' will be discarded in future version, ` + + `use the JSON file '${jsonFile}' instead.` + }]); + targetFile = jsFile; + } else { logWarn(_this, [{ - reason: `WARNING: '${fileName}' cannot have the same name files '.json' and '.js', otherwise '.json' in default.`, - }]) - output += content + getRequireString(_this, jsonLoaders('json'), fileName + '.json' + query) + reason: `WARNING: '${fileName}' cannot have same name files '.json' and '.js', ` + + `using '.json' by default.` + }]); + targetFile = jsonFile; } - return output + + return output + content + getRequireString(_this, jsonLoaders('json'), `${targetFile}${query}`); } module.exports = loader \ No newline at end of file -- Gitee