• 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
16const path = require('path')
17const { Compilation } = require('webpack')
18
19/**
20 * Webpack plugin that processes assets after compilation completes
21 * Modifies assets before final emission, excluding the main ability file
22 */
23class AfterEmitPlugin {
24  constructor() {
25  }
26
27  /**
28   * Apply method required by webpack plugin interface
29   * @param {Object} compiler - Webpack compiler instance
30   */
31  apply(compiler) {
32    compiler.hooks.thisCompilation.tap('card', (compilation) => {
33      compilation.hooks.processAssets.tapAsync(
34        {
35          name: 'MyPlugin',
36          stage: Compilation.PROCESS_ASSETS_STAGE_REPOR,
37        },
38        (assets, back) => {
39          const keys = Object.keys(assets)
40          keys.forEach(key => {
41            if ('./' + process.env.abilityType + '.js' !== key) {
42              sourceChange(key, assets, compilation);
43            }
44          });
45          back && back();
46        }
47      );
48    })
49  }
50}
51
52/**
53 * Processes JavaScript assets to extract card components and convert them to JSON format
54 *
55 * @param {string} key - Asset file path key
56 * @param {Object} assets - Webpack assets object
57 * @param {Object} compilation - Webpack compilation object
58 */
59function sourceChange(key, assets, compilation) {
60  try {
61    const extName = path.extname(key);
62    if (extName === '.js') {
63      const jsonOut = {};
64      const source = assets[key].source();
65      const str = source.match(/card_start((\s||\S)*)card_end/)[1];
66      const array = str.split('\n');
67      array.forEach(element => {
68        elementChange(element, source, jsonOut);
69      });
70      const assetReplace = {};
71      Object.keys(jsonOut).forEach(function (jsonOutKey) {
72        toAddJson(assetReplace, jsonOutKey, JSON.parse(jsonOut[jsonOutKey]), compilation, path.join(process.env.projectPath, key));
73      });
74      assets[key.replace(extName, '') + '.json'] = {
75        source: function () {
76          return JSON.stringify(assetReplace, null, 2);
77        },
78        size: function () {
79          return JSON.stringify(assetReplace, null, 2).size;
80        }
81      }
82      delete assets[key];
83    }
84  } catch (e) {
85    compilation.errors.push({ message: 'errorStartERROR File:' + key +
86      '\n' + ` ${e.message}` + 'errorEnd' });
87  }
88}
89
90/**
91 * Processes individual element declarations in source code and extracts their values
92 *
93 * @param {string} element - The line of code containing the element declaration
94 * @param {string} source - The full source code of the file
95 * @param {Object} jsonOut - The output object that collects extracted values
96 */
97function elementChange(element, source, jsonOut) {
98  if (element.trim() === '' || element.trim() === '//') {
99  } else {
100    const key = element.match(/var ((\s||\S)*) =/)[1];
101    const value = element.match(/"((\s||\S)*)"/)[1];
102    const replaceSource = source.replace(element, '').toString();
103    const partStart = replaceSource.indexOf(value);
104    const subSource = replaceSource.substr(partStart);
105    const partEnd = subSource.indexOf('/***/ })');
106    let out = subSource.substr(0, partEnd).match(/module\.exports \= ((\s||\S)*)/)[1].trim();
107    if (out.indexOf('JSON.parse(') === 0) {
108      out = JSON.stringify(eval(out));
109    }
110    if (out.substr(out.length - 1, 1) === ';') {
111      out = out.substr(0, out.length - 1);
112    }
113    jsonOut[key] = out;
114  }
115}
116
117/**
118 * Organizes extracted card components into a structured JSON object
119 * Validates and processes different types of card components
120 *
121 * @param {Object} assetReplace - The output object being built
122 * @param {string} key - The component type key (e.g., 'card_template')
123 * @param {Object} value - The component value to process
124 * @param {Object} compilation - Webpack compilation object for error reporting
125 * @param {string} sourceKey - Source file path for error context
126 */
127function toAddJson(assetReplace, key, value, compilation, sourceKey) {
128  assetReplace['template'] = assetReplace['template'] || {};
129  assetReplace['styles'] = assetReplace['styles'] || {};
130  assetReplace['data'] = assetReplace['data'] || {};
131  assetReplace['actions'] = assetReplace['actions'] || {};
132  assetReplace['apiVersion'] = assetReplace['apiVersion'] || {};
133
134  switch(key) {
135    case 'card_template':
136      assetReplace['template'] = value;
137      break;
138    case 'card_style':
139      assetReplace['styles'] = value;
140      break;
141    case 'card_json':
142      if (value) {
143        if (value.data) {
144          assetReplace['data'] = validateData(value.data, compilation, sourceKey);
145        }
146        if (value.actions) {
147          assetReplace['actions'] = processActions(value.actions, compilation, sourceKey);
148        }
149        if (value.apiVersion) {
150          assetReplace['apiVersion'] = validateData(value.apiVersion, compilation, sourceKey);
151        }
152        if (value.props) {
153          assetReplace['props'] = replacePropsArray(value.propsValue, compilation, sourceKey);
154        }
155      }
156      break;
157    default:
158      addElement(assetReplace, key, value, compilation, sourceKey);
159      break;
160  }
161}
162
163/**
164 * Processes and organizes nested element components into the final JSON structure
165 * Handles templates, styles, and data for individual card elements
166 *
167 * @param {Object} assetReplace - The main output object being constructed
168 * @param {string} key - The element identifier key (e.g., 'card_element_template_header')
169 * @param {Object} value - The element's content to be processed
170 * @param {Object} compilation - Webpack compilation object for error handling
171 * @param {string} sourceKey - Source file path for error context
172 */
173function addElement(assetReplace, key, value, compilation, sourceKey) {
174  const keyName = key.substr(key.lastIndexOf('_') + 1, key.length - key.lastIndexOf('_') + 1);
175  assetReplace[keyName] = assetReplace[keyName] || {};
176  assetReplace[keyName]['template'] = assetReplace[keyName]['template'] || {};
177  assetReplace[keyName]['styles'] = assetReplace[keyName]['styles'] || {};
178  assetReplace[keyName]['data'] = assetReplace[keyName]['data'] || {};
179  assetReplace[keyName]['actions'] = assetReplace[keyName]['actions'] || {};
180  assetReplace[keyName]['apiVersion'] = assetReplace[keyName]['apiVersion'] || {};
181
182  switch(key.replace(keyName, '')) {
183    case 'card_element_template_':
184      assetReplace[keyName]['template'] = value;
185      break;
186    case 'card_element_style_':
187      assetReplace[keyName]['styles'] = value;
188      break;
189    case 'card_element_json_':
190      if (value) {
191        if (value.data) {
192          assetReplace[keyName]['data'] = validateData(value.data, compilation, sourceKey);
193        }
194        if (value.actions) {
195          assetReplace[keyName]['actions'] = processActions(value.actions, compilation, sourceKey);
196        }
197        if (value.apiVersion) {
198          assetReplace[keyName]['apiVersion'] = validateData(value.apiVersion, compilation, sourceKey);
199        }
200        if (value.props) {
201          assetReplace[keyName]['props'] = replacePropsArray(value.propsValue, compilation, sourceKey);
202        }
203      }
204      break;
205    default:
206      break;
207  }
208}
209
210/**
211 * Normalizes and validates component props configuration for custom elements
212 * Converts array format to object format and ensures proper structure
213 *
214 * @param {Array|Object} propsValue - The props configuration to process
215 * @param {Object} _this - Webpack compilation context for warnings
216 * @param {string} key - Source file path for warning messages
217 * @returns {Object} Normalized props configuration object
218 */
219function replacePropsArray(propsValue, _this, key) {
220  if (!propsValue) {
221    return propsValue;
222  }
223  if (Array.isArray(propsValue)) {
224    const propsObject = {};
225    propsValue.forEach(item => {
226      if (typeof(item) !== 'string') {
227        _this.warnings.push({message: 'warnStartWARNING File:' + key +
228          '\n' + `The props value type should be 'string', not '${typeof(item)}' in props array in custom elements.` + 'warnEnd'});
229      }
230      propsObject[item] = { 'default': '' };
231    });
232    propsValue = propsObject;
233  } else if (Object.prototype.toString.call(propsValue) === '[object Object]') {
234    Object.keys(propsValue).forEach(item => {
235      if (Object.prototype.toString.call(propsValue[item]) !== '[object Object]') {
236        _this.warnings.push({message: 'warnStartWARNING File:' + key +
237          '\n' + 'The props default value type can only be Object in custom elements.' + 'warnEnd'});
238      }
239      if (!propsValue[item].hasOwnProperty('default')) {
240        propsValue[item] = { 'default': '' }
241      }
242    });
243  } else {
244    _this.warnings.push({message: 'warnStartWARNING File:' + key +
245      '\n' + 'The props type can only be Array or Object in custom elements.' + 'warnEnd'});
246  }
247  return propsValue;
248}
249
250/**
251 * Validates and normalizes actions configuration for components
252 * Ensures actions follow required format and conventions
253 *
254 * @param {Object} actionsValue - The actions configuration to process
255 * @param {Object} _this - Webpack compilation context for warnings
256 * @param {string} key - Source file path for warning messages
257 * @returns {Object} Validated actions configuration
258 */
259function processActions(actionsValue, _this, key) {
260  if (Object.prototype.toString.call(actionsValue) === '[object Object]') {
261    Object.keys(actionsValue).forEach(item => {
262      if (actionsValue[item].method) {
263        if (typeof(actionsValue[item].method) === 'string') {
264          if (actionsValue[item].method.toLowerCase() !== actionsValue[item].method) {
265            _this.warnings.push({message: 'warnStartWARNING File:' + key +
266              '\n' + `WARNING: The key method '${actionsValue[item].method}' in the actions don't support uppercase letters.` + 'warnEnd'});
267            actionsValue[item].method = actionsValue[item].method.toLowerCase();
268          }
269        } else {
270          _this.warnings.push({message: 'warnStartWARNING File:' + key +
271            '\n' + `WARNING: The key method type in the actions should be 'string', not '${typeof(actionsValue[item].method)}'.` + 'warnEnd'});
272        }
273      }
274    })
275  } else {
276    if (actionsValue) {
277      _this.warnings.push({message: 'warnStartWARNING File:' + key +
278        '\n' + 'WARNING: The actions value type can only be Object.' + 'warnEnd'});
279    }
280  }
281  return actionsValue;
282}
283
284/**
285 * Validates that data configuration is in the correct object format
286 * Issues warnings if invalid data format is detected
287 *
288 * @param {Object} dataValue - The data configuration to validate
289 * @param {Object} _this - Webpack compilation context for warnings
290 * @param {string} key - Source file path for warning messages
291 * @returns {Object} The validated data configuration (unchanged if valid)
292 */
293function validateData(dataValue, _this, key) {
294  if (dataValue && Object.prototype.toString.call(dataValue) !== '[object Object]') {
295    _this.warnings.push({message: 'warnStartWARNING File:' + key +
296      '\n' + 'WARNING: The data value type can only be Object.' + 'warnEnd'});
297  }
298  return dataValue;
299}
300
301module.exports = {
302  AfterEmitPlugin: AfterEmitPlugin
303}