@ui5/webcomponents-tools 0.0.0-c6d8872f3 → 0.0.0-c8721b8d2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (93) hide show
  1. package/CHANGELOG.md +896 -1
  2. package/README.md +6 -6
  3. package/assets-meta.js +162 -0
  4. package/bin/dev.js +12 -1
  5. package/components-package/eslint.js +35 -0
  6. package/components-package/nps.js +118 -45
  7. package/components-package/postcss.components.js +1 -21
  8. package/components-package/postcss.themes.js +1 -23
  9. package/components-package/vite.config.js +13 -0
  10. package/components-package/wdio.js +405 -333
  11. package/components-package/wdio.sync.js +368 -0
  12. package/icons-collection/nps.js +71 -28
  13. package/lib/copy-and-watch/index.js +145 -0
  14. package/lib/copy-list/index.js +28 -0
  15. package/lib/create-icons/index.js +124 -54
  16. package/lib/create-illustrations/index.js +175 -0
  17. package/lib/create-new-component/index.js +72 -93
  18. package/lib/create-new-component/jsFileContentTemplate.js +73 -0
  19. package/lib/create-new-component/tsFileContentTemplate.js +80 -0
  20. package/lib/css-processors/css-processor-components.mjs +77 -0
  21. package/lib/css-processors/css-processor-themes.mjs +79 -0
  22. package/lib/css-processors/scope-variables.mjs +46 -0
  23. package/lib/css-processors/shared.mjs +76 -0
  24. package/lib/dev-server/custom-hot-update-plugin.js +39 -0
  25. package/lib/dev-server/dev-server.js +66 -0
  26. package/lib/dev-server/virtual-index-html-plugin.js +52 -0
  27. package/lib/esm-abs-to-rel/index.js +61 -0
  28. package/lib/generate-custom-elements-manifest/index.js +327 -0
  29. package/lib/generate-js-imports/illustrations.js +86 -0
  30. package/lib/generate-json-imports/i18n.js +103 -47
  31. package/lib/generate-json-imports/themes.js +76 -18
  32. package/lib/hbs2lit/index.js +2 -4
  33. package/lib/hbs2lit/src/compiler.js +30 -9
  34. package/lib/hbs2lit/src/includesReplacer.js +23 -17
  35. package/lib/hbs2lit/src/litVisitor2.js +125 -26
  36. package/lib/hbs2lit/src/svgProcessor.js +12 -5
  37. package/lib/hbs2ui5/RenderTemplates/LitRenderer.js +35 -7
  38. package/lib/hbs2ui5/index.js +69 -30
  39. package/lib/i18n/defaults.js +77 -50
  40. package/lib/i18n/toJSON.js +25 -13
  41. package/lib/jsdoc/config.json +1 -1
  42. package/lib/jsdoc/configTypescript.json +29 -0
  43. package/lib/jsdoc/plugin.js +65 -4
  44. package/lib/jsdoc/preprocess.js +146 -0
  45. package/lib/jsdoc/template/publish.js +32 -4
  46. package/lib/postcss-combine-duplicated-selectors/index.js +185 -0
  47. package/lib/replace-global-core/index.js +25 -0
  48. package/lib/scoping/get-all-tags.js +37 -0
  49. package/lib/scoping/lint-src.js +31 -0
  50. package/lib/scoping/missing-dependencies.js +65 -0
  51. package/lib/scoping/report-tags-usage.js +28 -0
  52. package/lib/scoping/scope-test-pages.js +41 -0
  53. package/lib/test-runner/test-runner.js +71 -0
  54. package/package.json +52 -55
  55. package/bin/init-ui5-package.js +0 -3
  56. package/components-package/rollup.js +0 -134
  57. package/components-package/serve.json +0 -3
  58. package/lib/documentation/index.js +0 -143
  59. package/lib/documentation/templates/api-component-since.js +0 -3
  60. package/lib/documentation/templates/api-css-variables-section.js +0 -24
  61. package/lib/documentation/templates/api-events-section.js +0 -35
  62. package/lib/documentation/templates/api-methods-section.js +0 -26
  63. package/lib/documentation/templates/api-properties-section.js +0 -40
  64. package/lib/documentation/templates/api-slots-section.js +0 -28
  65. package/lib/documentation/templates/template.js +0 -38
  66. package/lib/init-package/index.js +0 -119
  67. package/lib/init-package/resources/.eslintignore +0 -3
  68. package/lib/init-package/resources/bundle.es5.js +0 -25
  69. package/lib/init-package/resources/bundle.esm.js +0 -34
  70. package/lib/init-package/resources/config/.eslintrc.js +0 -1
  71. package/lib/init-package/resources/config/postcss.components/postcss.config.js +0 -1
  72. package/lib/init-package/resources/config/postcss.themes/postcss.config.js +0 -1
  73. package/lib/init-package/resources/config/rollup.config.js +0 -1
  74. package/lib/init-package/resources/config/wdio.conf.js +0 -1
  75. package/lib/init-package/resources/package-scripts.js +0 -11
  76. package/lib/init-package/resources/src/Assets.js +0 -6
  77. package/lib/init-package/resources/src/Demo.hbs +0 -1
  78. package/lib/init-package/resources/src/Demo.js +0 -56
  79. package/lib/init-package/resources/src/i18n/messagebundle.properties +0 -2
  80. package/lib/init-package/resources/src/i18n/messagebundle_de.properties +0 -1
  81. package/lib/init-package/resources/src/i18n/messagebundle_en.properties +0 -1
  82. package/lib/init-package/resources/src/i18n/messagebundle_es.properties +0 -1
  83. package/lib/init-package/resources/src/i18n/messagebundle_fr.properties +0 -1
  84. package/lib/init-package/resources/src/themes/Demo.css +0 -11
  85. package/lib/init-package/resources/src/themes/sap_belize/parameters-bundle.css +0 -3
  86. package/lib/init-package/resources/src/themes/sap_belize_hcb/parameters-bundle.css +0 -3
  87. package/lib/init-package/resources/src/themes/sap_belize_hcw/parameters-bundle.css +0 -3
  88. package/lib/init-package/resources/src/themes/sap_fiori_3/parameters-bundle.css +0 -3
  89. package/lib/init-package/resources/src/themes/sap_fiori_3_dark/parameters-bundle.css +0 -3
  90. package/lib/init-package/resources/test/pages/index.html +0 -51
  91. package/lib/init-package/resources/test/specs/Demo.spec.js +0 -12
  92. package/lib/postcss-css-to-esm/index.js +0 -33
  93. package/lib/postcss-css-to-json/index.js +0 -20
@@ -5,19 +5,32 @@ const includesReplacer = require("./includesReplacer");
5
5
  const svgProcessor = require("./svgProcessor");
6
6
 
7
7
  const removeWhiteSpaces = (source) => {
8
- return source.replace(/\n+/g, "").replace(/\s+</g, "<").replace(/}}\s+{{/g, "}}{{");
8
+ return source
9
+ .replace(/\s*\r*\n+\s*/g, " ") // Replace new lines and all whitespace between them with a space
10
+ .replace(/\s*<\s*/g, "<") // Strip whitespace round <
11
+ .replace(/\s*>\s*/g, ">") // Strip whitespace round >
12
+ .replace(/}}\s+{{/g, "}}{{"); // Remove whitespace between }} and {{
9
13
  };
10
14
 
11
- const compileString = async (sInput, config) => {
12
- let sPreprocessed = sInput;
15
+ const hbs2lit = async (file, componentName) => {
16
+ let sPreprocessed = await includesReplacer.replace(file);
13
17
 
14
- sPreprocessed = await includesReplacer.replace(sPreprocessed, config);
15
18
  sPreprocessed = removeWhiteSpaces(sPreprocessed);
16
19
 
20
+ const blockSignature = process.env.UI5_TS ? `this: ${componentName}` : ""
21
+
22
+ // icons hack
23
+ if (sPreprocessed.startsWith("<g ") || sPreprocessed.startsWith("<g>")) {
24
+ return `
25
+ function block0 (${blockSignature}) {
26
+ return svg\`${sPreprocessed}\`
27
+ }`;
28
+ }
29
+
17
30
  const ast = Handlebars.parse(sPreprocessed);
18
31
 
19
32
  const pv = new PartialsVisitor();
20
- const lv = new HTMLLitVisitor();
33
+ const lv = new HTMLLitVisitor(componentName);
21
34
 
22
35
  let result = "";
23
36
 
@@ -27,13 +40,21 @@ const compileString = async (sInput, config) => {
27
40
  lv.accept(ast);
28
41
 
29
42
  for (let key in lv.blocks) {
30
- result += lv.blocks[key] + "\n";
43
+ let block = lv.blocks[key];
44
+
45
+ if (block.match(/scopeTag/)) {
46
+ // const matches = block.match(/^(.*?)( => )(.*?);$/);
47
+ const matches = block.match(/^(function .*? \{ return )(.*?);\}$/);
48
+ const scopedCode = matches[2];
49
+ const normalCode = scopedCode.replace(/\${scopeTag\("/g, "").replace(/", tags, suffix\)}/g, "");
50
+ block = `${matches[1]}suffix ? ${scopedCode} : ${normalCode};}`;
51
+ }
52
+
53
+ result += block + "\n";
31
54
  }
32
55
 
33
56
  result = svgProcessor.process(result);
34
57
  return result;
35
58
  };
36
59
 
37
- module.exports = {
38
- compileString
39
- };
60
+ module.exports = hbs2lit;
@@ -1,25 +1,31 @@
1
1
  const path = require("path");
2
- const {promisify} = require("util");
3
- const nativeFs = require("fs");
2
+ const fs = require("fs").promises;
4
3
 
5
- function replaceIncludes(hbs, config) {
6
- const fs = config.fs || nativeFs;
7
- const readFile = promisify(fs.readFile);
8
- const inclRegex = /{{>\s*include\s*["']([a-zA-Z.\/]+)["']}}/g;
9
-
10
- async function replacer(match, p1) {
11
- const includeContent = await readFile(path.join(config.templatesPath, p1), "utf-8");
12
- hbs = hbs.replace(match, includeContent);
13
- }
4
+ const replaceIncludes = async (file) => {
5
+ const filePath = path.dirname(file);
6
+ let fileContent = await fs.readFile(file, "utf-8");
14
7
 
8
+ const inclRegex = /{{>\s*include\s*["'](.+?)["']}}/g;
15
9
  let match;
16
- const replacers = [];
17
- while ((match = inclRegex.exec (hbs)) !== null) {
18
- replacers.push(replacer(match[0], match[1]));
10
+
11
+ while((match = inclRegex.exec(fileContent)) !== null) {
12
+ inclRegex.lastIndex = 0;
13
+
14
+ let targetFile = match[1];
15
+ if (targetFile.startsWith(".")) {
16
+ // Relative path, f.e. {{>include "./Popup.hbs"}} or {{>include "../partials/Header.hbs"}}
17
+ targetFile = path.join(filePath, targetFile);
18
+ } else {
19
+ // Node module path, f.e. {{>include "@ui5/webcomponents/src/Popup.hbs"}}
20
+ targetFile = require.resolve(targetFile);
21
+ }
22
+
23
+ fileContent = fileContent.replace(match[0], await replaceIncludes(targetFile));
19
24
  }
20
- return Promise.all(replacers).then(() => hbs);
21
- }
25
+
26
+ return fileContent;
27
+ };
22
28
 
23
29
  module.exports = {
24
30
  replace: replaceIncludes
25
- };
31
+ };
@@ -5,17 +5,41 @@ const Visitor = Handlebars.Visitor;
5
5
  // skip ifDefined for event handlers and boolean attrs
6
6
  let skipIfDefined = false;
7
7
 
8
+ // when true => an HTML node value, when false => an attribute value
9
+ let isNodeValue = false;
10
+
11
+ // when true => the current attribute is "style"
12
+ let isStyleAttribute = false;
13
+
8
14
  // matches event handlers @click= and boolean attrs ?disabled=
9
15
  const dynamicAttributeRgx = /\s(\?|@)([a-zA-Z|-]+)="?\s*$/;
10
16
 
11
- function HTMLLitVisitor(debug) {
17
+ if (!String.prototype.replaceAll) {
18
+ String.prototype.replaceAll = function(str, newStr){
19
+
20
+ // If a regex pattern
21
+ if (Object.prototype.toString.call(str).toLowerCase() === '[object regexp]') {
22
+ return this.replace(str, newStr);
23
+ }
24
+
25
+ // If a string
26
+ return this.replace(new RegExp(str, 'g'), newStr);
27
+
28
+ };
29
+ }
30
+
31
+ function HTMLLitVisitor(componentName, debug) {
12
32
  this.blockCounter = 0;
13
33
  this.keys = [];
14
34
  this.blocks = {};
15
35
  this.result = "";
16
36
  this.mainBlock = "";
17
- this.blockPath = "context";
18
- this.blockParameters = ["context"];
37
+ this.blockLevel = 0;
38
+ this.componentName = componentName
39
+ const blockParametersDefinitionTS = [`this: ${componentName}`, "context: UI5Element", "tags: string[]", "suffix: string | undefined"];
40
+ const blockParametersDefinitionJS = ["context", "tags", "suffix"];
41
+ this.blockParametersDefinition = process.env.UI5_TS ? blockParametersDefinitionTS : blockParametersDefinitionJS;
42
+ this.blockParametersUsage = ["this", "context", "tags", "suffix"];
19
43
  this.paths = []; //contains all normalized relative paths
20
44
  this.debug = debug;
21
45
  if (this.debug) {
@@ -31,18 +55,18 @@ HTMLLitVisitor.prototype.Program = function(program) {
31
55
  this.keys.push(key);
32
56
  this.debug && this.blockByNumber.push(key);
33
57
 
34
- this.blocks[this.currentKey()] = "const " + this.currentKey() + " = (" + this.blockParameters.join(", ") + ") => { return ";
58
+ // this.blocks[this.currentKey()] = "function " + this.currentKey() + ` (this: any, ` + this.blockParametersDefinition.join(", ") + ") { ";
59
+ this.blocks[this.currentKey()] = `function ${this.currentKey()} (${this.blockParametersDefinition.join(", ")}) { `;
35
60
 
36
61
  if (this.keys.length > 1) { //it's a nested block
37
- this.blocks[this.prevKey()] += this.currentKey() + "(" + this.blockParameters.join(", ") + ")";
62
+ this.blocks[this.prevKey()] += this.currentKey() + ".call(" + this.blockParametersUsage.join(", ") + ")";
38
63
  } else {
39
64
  this.mainBlock = this.currentKey();
40
- this.paths.push(this.blockPath);
41
65
  }
42
66
 
43
- this.blocks[this.currentKey()] += "html`";
67
+ this.blocks[this.currentKey()] += "return html`";
44
68
  Visitor.prototype.Program.call(this, program);
45
- this.blocks[this.currentKey()] += "`; };";
69
+ this.blocks[this.currentKey()] += "`;}";
46
70
 
47
71
  this.keys.pop(key);
48
72
  };
@@ -51,9 +75,24 @@ HTMLLitVisitor.prototype.ContentStatement = function(content) {
51
75
  Visitor.prototype.ContentStatement.call(this, content);
52
76
  // let content = content.orgiinal; // attribute="__ attribute = "__ attribute ="__
53
77
 
54
- const contentStatement = content.original;
78
+ let contentStatement = content.original;
55
79
  skipIfDefined = !!dynamicAttributeRgx.exec(contentStatement);
56
80
 
81
+ const closingIndex = contentStatement.lastIndexOf(">");
82
+ const openingIndex = contentStatement.lastIndexOf("<");
83
+ if (closingIndex !== -1 || openingIndex !== -1) { // Only change isNodeValue whenever < or > is found in the content statement
84
+ isNodeValue = closingIndex > openingIndex;
85
+ }
86
+
87
+ isStyleAttribute = !isNodeValue && contentStatement.match(/style *= *["']? *$/);
88
+
89
+ if (!isStyleAttribute && contentStatement.match(/style=/)) {
90
+ console.log("WARNING: style hard-coded", contentStatement);
91
+ }
92
+
93
+ // Scope custom element tags
94
+ contentStatement = contentStatement.replaceAll(/(<\/?\s*)([a-zA-Z0-9_]+-[a-zA-Z0-9_-]+)/g, "$1\${scopeTag(\"$2\", tags, suffix)}");
95
+
57
96
  this.blocks[this.currentKey()] += contentStatement;
58
97
  };
59
98
 
@@ -64,14 +103,19 @@ HTMLLitVisitor.prototype.MustacheStatement = function(mustache) {
64
103
  this.blocks[this.currentKey()] += "${index}";
65
104
  } else {
66
105
  const path = normalizePath.call(this, mustache.path.original);
67
- const hasCalculatingClasses = path.includes("context.classes");
68
- const hasStylesCalculation = path.includes("context.styles");
106
+ const hasCalculatingClasses = path.includes("this.classes");
69
107
 
70
108
  let parsedCode = "";
71
109
 
72
- if (hasCalculatingClasses) {
73
- parsedCode = `\${classMap(${path})}`;
74
- } else if (hasStylesCalculation) {
110
+ if (isNodeValue && !mustache.escaped) {
111
+ parsedCode = `\${unsafeHTML(${path})}`;
112
+ } else if (hasCalculatingClasses) {
113
+ if (process.env.UI5_TS) {
114
+ parsedCode = `\${classMap(${path} as ClassMapValue)}`;
115
+ } else {
116
+ parsedCode = `\${classMap(${path})}`;
117
+ }
118
+ } else if (isStyleAttribute) {
75
119
  parsedCode = `\${styleMap(${path})}`;
76
120
  } else if (skipIfDefined){
77
121
  parsedCode = `\${${path}}`;
@@ -135,22 +179,37 @@ function visitEachBlock(block) {
135
179
  var bParamAdded = false;
136
180
  visitSubExpression.call(this, block);
137
181
 
138
- this.blocks[this.currentKey()] += "${ repeat(" + normalizePath.call(this, block.params[0].original) + ", undefined, (item, index) => ";
182
+ const reapeatDirectiveParamsTS = "(item, index) => (item as typeof item & {_id?: any})._id || index, (item, index: number)";
183
+ const reapeatDirectiveParamsJS = "(item, index) => item._id || index, (item, index)";
184
+ const repleatDirectiveParams = process.env.UI5_TS ? reapeatDirectiveParamsTS : reapeatDirectiveParamsJS;
185
+ this.blocks[this.currentKey()] += "${ repeat(" + normalizePath.call(this, block.params[0].original) + ", " + repleatDirectiveParams + " => ";
139
186
  this.paths.push(normalizePath.call(this, block.params[0].original));
140
- this.blockPath = "item";
187
+ this.blockLevel++;
141
188
 
142
- if (this.blockParameters.indexOf("item") === -1) {
189
+ // block params is [this, context, tags, suffix] for top level blocks
190
+ // blcok params is [this, context, tags, suffix, item, index] for nested blocks
191
+ if (!this.blockParametersUsage.includes("index")) {
192
+ // last item is not index, but an each block is processed, add the paramters for further nested blocks
143
193
  bParamAdded = true;
144
- this.blockParameters.unshift("index");
145
- this.blockParameters.unshift("item");
194
+ if (process.env.UI5_TS) {
195
+ this.blockParametersDefinition.push("item: any");
196
+ this.blockParametersDefinition.push("index: number");
197
+ } else {
198
+ this.blockParametersDefinition.push("item");
199
+ this.blockParametersDefinition.push("index");
200
+ }
201
+ this.blockParametersUsage.push("item");
202
+ this.blockParametersUsage.push("index");
146
203
  }
147
204
  this.acceptKey(block, "program");
148
205
  if (bParamAdded) {
149
- this.blockParameters.shift("item");
150
- this.blockParameters.shift("index");
206
+ // if parameters were added at this step, remove the last two
207
+ this.blockParametersDefinition.pop();
208
+ this.blockParametersDefinition.pop();
209
+ this.blockParametersUsage.pop();
210
+ this.blockParametersUsage.pop();
151
211
  }
152
- this.blockPath = "context";
153
-
212
+ this.blockLevel--;
154
213
  this.blocks[this.currentKey()] += ") }";
155
214
  }
156
215
 
@@ -159,12 +218,52 @@ function normalizePath(sPath) {
159
218
 
160
219
  //read carefully - https://github.com/wycats/handlebars.js/issues/1028
161
220
  //kpdecker commented on May 20, 2015
162
- if (result.indexOf("../") === 0) {
163
- let absolutePath = replaceAll(this.paths[this.paths.length - 1], ".", "/") + "/" + result;
221
+
222
+ if (result.indexOf("@root") === 0) {
223
+ // Trying to access root context via the HBS "@root" variable.
224
+ // Example: {{@root.property}} compiles to "context.property" - called from anywhere within the template.
225
+ result = result.replace("@root", "this");
226
+
227
+ } else if (result.indexOf("../") === 0) {
228
+ let absolutePath;
229
+ const levelsUp = (result.match(/..\//g) || []).length;
230
+
231
+ if (this.blockLevel <= levelsUp) {
232
+ // Trying to access root context from nested loops.
233
+ // Example: {{../../property}} compiles to "context.property" - when currently in a nested level loop.
234
+ // Example: {{../../../property}} compile to "context.property" - when requested levels are not present. fallback to root context.
235
+ absolutePath = `this.${replaceAll(result,"../", "")}`;
236
+ } else {
237
+ // Trying to access upper context (one-level-up) and based on the current lelev, that could be "context" or "item".
238
+ // Example: {{../property}} compiles to "context.property" - when called in a top level loop.
239
+ // Example: {{../property}} compiles to "item.property" - when called in a nested level loop.
240
+ // TODO: the second example, although correctly generated to "item.property", "item" will point to the current object within the nested loop,
241
+ // not the upper level loop as intended. So accessing the upper loop from nested loop is currently not working.
242
+ absolutePath = replaceAll(this.paths[this.paths.length - 1 - levelsUp], ".", "/") + "/" + result;
243
+ }
244
+
164
245
  result = replaceAll(path.normalize(absolutePath), path.sep, ".");
246
+
165
247
  } else {
166
- result = result ? replaceAll(this.blockPath + "/" + result, "/", ".") : this.blockPath;
248
+ // When neither "@root", nor "../" are used, use the following contexts:
249
+ // - use "context" - for the top level of execution, e.g "this.blockLevel = 0".
250
+ // - use "item" - for any nested level, e.g "this.blockLevel > 0".
251
+ // Example:
252
+ //
253
+ // {{text}} -> compiles to "context.text"
254
+ // {{#each items}}
255
+ // Item text: {{text}}</div> -> compiles to "item.text"
256
+ // {{#each words}}
257
+ // Word text: {{text}}</div> -> compiles to "item.text"
258
+ // {{/each}}
259
+ // Item text: {{text}}</div> -> compiles to "item.text"
260
+ // {{/each}}
261
+ // {{text}} -> compiles to "context.text"
262
+
263
+ const blockPath = this.blockLevel > 0 ? "item" : "this";
264
+ result = result ? replaceAll(blockPath + "/" + result, "/", ".") : blockPath;
167
265
  }
266
+
168
267
  return result;
169
268
  }
170
269
 
@@ -2,7 +2,7 @@
2
2
  const svgrx = new RegExp(/<svg[\s\S]*?>([\s\S]*?)<\/svg>/, 'g');
3
3
  const blockrx = /block[0-9]+/g;
4
4
 
5
- function process(input) {
5
+ function processSVG(input) {
6
6
  let matches;
7
7
  let template = input;
8
8
  let blockCounter = 0;
@@ -45,9 +45,16 @@ function getSVGMatches(template) {
45
45
  }
46
46
 
47
47
  function getSVGBlock(input, blockCounter) {
48
+ const definitionTS = `\nfunction blockSVG${blockCounter} (this: any, context: UI5Element, tags: string[], suffix: string | undefined) {
49
+ return svg\`${input}\`;
50
+ };`;
51
+ const definitionJS = `\nfunction blockSVG${blockCounter} (context, tags, suffix) {
52
+ return svg\`${input}\`;
53
+ };`;
54
+
48
55
  return {
49
- usage: `\${blockSVG${blockCounter}(context)}`,
50
- definition: `\nconst blockSVG${blockCounter} = (context) => {return svg\`${input}\`};`,
56
+ usage: `\${blockSVG${blockCounter}.call(this, context, tags, suffix)}`,
57
+ definition: process.env.UI5_TS ? definitionTS : definitionJS,
51
58
  };
52
59
  }
53
60
 
@@ -55,7 +62,7 @@ function replaceInternalBlocks(template, svgContent) {
55
62
  const internalBlocks = svgContent.match(blockrx) || [];
56
63
 
57
64
  internalBlocks.forEach(blockName => {
58
- const rx = new RegExp(`const ${blockName}.*(html\`).*};`);
65
+ const rx = new RegExp(`function ${blockName}.*(html\`).*;`);
59
66
  template = template.replace(rx, (match, p1) => {
60
67
  return match.replace(p1, "svg\`");
61
68
  });
@@ -65,5 +72,5 @@ function replaceInternalBlocks(template, svgContent) {
65
72
  }
66
73
 
67
74
  module.exports = {
68
- process: process
75
+ process: processSVG,
69
76
  };
@@ -1,12 +1,40 @@
1
- const buildRenderer = (controlName, litTemplate) => {
2
- return `
3
- /* eslint no-unused-vars: 0 */
4
- import ifDefined from '@ui5/webcomponents-base/dist/renderer/ifDefined.js';
5
- import { html, svg, repeat, classMap, styleMap } from '@ui5/webcomponents-base/dist/renderer/LitRenderer.js';
1
+ const tsImports = (controlName, hasTypes) => {
2
+ if (!process.env.UI5_TS) {
3
+ return "";
4
+ }
5
+
6
+ const importPrefix = process.env.UI5_BASE ? "../../../" : "@ui5/webcomponents-base/dist/"
7
+
8
+ return `import type UI5Element from "${importPrefix}UI5Element.js";
9
+ ${importForControl(controlName, hasTypes)}
10
+ import type { ClassMapValue } from "${importPrefix}types.js";
11
+ `;
12
+ }
13
+ const importForControl = (controlName, hasTypes) => {
14
+
15
+ if (!hasTypes) {
16
+ return `type ${controlName} = any;`;
17
+ }
18
+
19
+ if (process.env.UI5_BASE) {
20
+ // base package has a component in `test/elements` instead of `src`
21
+ return `import type ${controlName} from "../../../../test/elements/${controlName}.js";`
22
+ }
23
+ return `import type ${controlName} from "../../${controlName}.js";`
24
+ }
25
+
26
+ const buildRenderer = (controlName, litTemplate, hasTypes) => {
27
+ const importPrefix = process.env.UI5_BASE ? "../../../" : "@ui5/webcomponents-base/dist/"
28
+
29
+ // typescript cannot process package imports for the same package and the paths are changed to relative for base package templates
30
+ return `/* eslint no-unused-vars: 0 */
31
+ import { html, svg, repeat, classMap, styleMap, ifDefined, unsafeHTML, scopeTag } from "${importPrefix}renderer/LitRenderer.js";
32
+ ${tsImports(controlName, hasTypes)}
6
33
  ${litTemplate}
7
- export default block0;`
34
+
35
+ export default block0;`;
8
36
  };
9
37
 
10
38
  module.exports = {
11
39
  generateTemplate: buildRenderer
12
- };
40
+ };
@@ -1,8 +1,12 @@
1
- const fs = require('fs');
1
+ const fs = require('fs').promises;
2
+ const existsSync = require('fs').existsSync;
2
3
  const getopts = require('getopts');
3
4
  const hbs2lit = require('../hbs2lit');
4
5
  const path = require('path');
5
6
  const litRenderer = require('./RenderTemplates/LitRenderer');
7
+ const recursiveReadDir = require("recursive-readdir");
8
+
9
+ let missingTypesReported = false;
6
10
 
7
11
  const args = getopts(process.argv.slice(2), {
8
12
  alias: {
@@ -18,55 +22,88 @@ const args = getopts(process.argv.slice(2), {
18
22
 
19
23
  const onError = (place) => {
20
24
  console.log(`A problem occoured when reading ${place}. Please recheck passed parameters.`);
21
- }
25
+ };
26
+
22
27
  const isHandlebars = (fileName) => fileName.indexOf('.hbs') !== -1;
23
- const parseFile = (filePath, inputDir, outputDir) => {
24
- fs.readFile(filePath, 'utf-8', (err, content) => {
25
28
 
26
- if (err) {
27
- onError('file');
29
+ const hasTypes = (file, componentName) => {
30
+ const tsFile = path.join(path.dirname(file), componentName + ".ts")
31
+ const dtsFile = path.join(path.dirname(file), componentName + ".d.ts")
32
+ return existsSync(tsFile) || existsSync(dtsFile);
33
+ }
34
+
35
+ const processFile = async (file, outputDir) => {
36
+ const componentNameMatcher = /(\w+)(\.hbs)/gim;
37
+ const componentName = componentNameMatcher.exec(file)[1];
38
+ const componentHasTypes = hasTypes(file, componentName);
39
+ if (!componentHasTypes) {
40
+ if (!missingTypesReported) {
41
+ console.warn("[Warn] The following templates do not have a corresponging .ts or .d.ts file and won't be type checked:")
42
+ missingTypesReported = true;
28
43
  }
44
+ console.log(" -> " + componentName + ".hbs");
45
+ }
46
+ const litCode = await hbs2lit(file, componentName);
47
+ const absoluteOutputDir = composeAbsoluteOutputDir(file, outputDir);
29
48
 
30
- hbs2lit.compileString(content, {
31
- templatesPath: inputDir,
32
- compiledTemplatesPath: outputDir
33
- }).then((litCode) => {
34
- const componentNameMatcher = /(\w+)(\.hbs)/gim;
35
- const componentName = componentNameMatcher.exec(filePath)[1];
49
+ return writeRenderers(absoluteOutputDir, componentName, litRenderer.generateTemplate(componentName, litCode, componentHasTypes));
50
+ };
36
51
 
37
- writeRenderers(outputDir, componentName, litRenderer.generateTemplate(componentName, litCode));
38
- });
39
- });
40
- }
52
+ const composeAbsoluteOutputDir = (file, outputDir) => {
53
+ // (1) Extract the dir structure from the source file path - "src/lvl1/lvl2/MyCompBadge.hbs"
54
+ // - remove the filename - "src/lvl1/lvl2"
55
+ // - remove the leading dir - "lvl1/lvl2"
56
+ const fileDir = file.split(path.sep).slice(1, -1).join(path.sep);
57
+
58
+ // (2) Compose full output dir - "dist/generated/templates/lvl1/lvl2"
59
+ return `${outputDir}${path.sep}${fileDir}`;
60
+ };
41
61
 
42
62
  const wrapDirectory = (directory, outputDir) => {
43
63
  directory = path.normalize(directory);
44
64
  outputDir = path.normalize(outputDir);
45
65
 
46
- fs.readdir(directory, (err, files) => {
66
+ return new Promise((resolve, reject) => {
67
+ recursiveReadDir(directory, (err, files) => {
47
68
 
48
- if (err) {
49
- onError('directory');
50
- }
69
+ if (err) {
70
+ onError('directory');
71
+ reject();
72
+ }
51
73
 
52
- files.forEach(fileName => {
53
- if (isHandlebars(fileName)) {
74
+ const promises = files.map(fileName => {
75
+ if (isHandlebars(fileName)) {
76
+ return processFile(fileName, outputDir);
77
+ }
78
+ }).filter(x => !!x);
54
79
 
55
- // could be refactored a bit
56
- parseFile(directory + fileName, directory, outputDir);
57
- }
80
+ resolve(Promise.all(promises));
58
81
  });
59
- })
82
+ });
60
83
  };
61
84
 
62
- const writeRenderers = (outputDir, controlName, fileContent) => {
85
+ const writeRenderers = async (outputDir, controlName, fileContent) => {
63
86
  try {
64
- const compiledFilePath = `${outputDir}${path.sep}${controlName}Template.lit.js`;
87
+
88
+ await fs.mkdir(outputDir, { recursive: true });
89
+
90
+ const compiledFilePath = `${outputDir}${path.sep}${controlName}Template.lit.${process.env.UI5_TS ? "ts" : "js"}`;
65
91
 
66
92
  // strip DOS line endings because the break the source maps
67
93
  let fileContentUnix = fileContent.replace(/\r\n/g, "\n");
68
94
  fileContentUnix = fileContentUnix.replace(/\r/g, "\n");
69
- fs.writeFileSync(compiledFilePath, fileContentUnix);
95
+
96
+ // Only write to the file system actual changes - each updated file, no matter if the same or not, triggers an expensive operation for rollup
97
+ // Note: .hbs files that include a changed .hbs file will also be recompiled as their content will be updated too
98
+
99
+ let existingFileContent = "";
100
+ try {
101
+ existingFileContent = await fs.readFile(compiledFilePath);
102
+ } catch (e) {}
103
+
104
+ if (existingFileContent !== fileContentUnix) {
105
+ return fs.writeFile(compiledFilePath, fileContentUnix);
106
+ }
70
107
 
71
108
  } catch (e) {
72
109
  console.log(e);
@@ -76,5 +113,7 @@ const writeRenderers = (outputDir, controlName, fileContent) => {
76
113
  if (!args['d'] || !args['o']) {
77
114
  console.log('Please provide an input and output directory (-d and -o)');
78
115
  } else {
79
- wrapDirectory(args['d'], args['o']);
116
+ wrapDirectory(args['d'], args['o']).then(() => {
117
+ console.log("Templates generated");
118
+ });
80
119
  }