@ui5/webcomponents-tools 0.0.0-f734ea2da → 0.0.0-f79db712b

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 (108) hide show
  1. package/CHANGELOG.md +808 -0
  2. package/README.md +6 -6
  3. package/assets-meta.js +18 -3
  4. package/bin/dev.js +1 -5
  5. package/components-package/eslint.js +36 -0
  6. package/components-package/nps.js +108 -66
  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 -344
  11. package/components-package/wdio.sync.js +368 -0
  12. package/icons-collection/nps.js +51 -18
  13. package/lib/cem/custom-elements-manifest.config.mjs +499 -0
  14. package/lib/cem/event.mjs +135 -0
  15. package/lib/cem/schema-internal.json +1354 -0
  16. package/lib/cem/schema.json +1098 -0
  17. package/lib/cem/types-internal.d.ts +801 -0
  18. package/lib/cem/types.d.ts +736 -0
  19. package/lib/cem/utils.mjs +354 -0
  20. package/lib/cem/validate.js +67 -0
  21. package/lib/copy-and-watch/index.js +24 -1
  22. package/lib/copy-list/index.js +17 -17
  23. package/lib/create-icons/index.js +124 -58
  24. package/lib/create-illustrations/index.js +165 -0
  25. package/lib/create-new-component/index.js +72 -97
  26. package/lib/create-new-component/jsFileContentTemplate.js +73 -0
  27. package/lib/create-new-component/tsFileContentTemplate.js +71 -0
  28. package/lib/css-processors/css-processor-components.mjs +77 -0
  29. package/lib/css-processors/css-processor-themes.mjs +79 -0
  30. package/lib/css-processors/scope-variables.mjs +46 -0
  31. package/lib/css-processors/shared.mjs +76 -0
  32. package/lib/dev-server/custom-hot-update-plugin.js +39 -0
  33. package/lib/dev-server/dev-server.js +66 -0
  34. package/lib/dev-server/virtual-index-html-plugin.js +52 -0
  35. package/lib/esm-abs-to-rel/index.js +17 -10
  36. package/lib/generate-custom-elements-manifest/index.js +327 -0
  37. package/lib/generate-js-imports/illustrations.js +86 -0
  38. package/lib/generate-json-imports/i18n.js +43 -31
  39. package/lib/generate-json-imports/themes.js +36 -24
  40. package/lib/hbs2lit/src/compiler.js +24 -4
  41. package/lib/hbs2lit/src/includesReplacer.js +5 -5
  42. package/lib/hbs2lit/src/litVisitor2.js +95 -24
  43. package/lib/hbs2lit/src/svgProcessor.js +12 -5
  44. package/lib/hbs2ui5/RenderTemplates/LitRenderer.js +33 -11
  45. package/lib/hbs2ui5/index.js +56 -23
  46. package/lib/i18n/defaults.js +65 -57
  47. package/lib/i18n/toJSON.js +12 -11
  48. package/lib/jsdoc/configTypescript.json +29 -0
  49. package/lib/jsdoc/plugin.js +41 -0
  50. package/lib/jsdoc/preprocess.js +146 -0
  51. package/lib/jsdoc/template/publish.js +21 -2
  52. package/lib/postcss-combine-duplicated-selectors/index.js +185 -0
  53. package/lib/replace-global-core/index.js +13 -8
  54. package/lib/scoping/get-all-tags.js +2 -9
  55. package/lib/scoping/lint-src.js +1 -1
  56. package/lib/scoping/missing-dependencies.js +65 -0
  57. package/lib/scoping/report-tags-usage.js +28 -0
  58. package/lib/scoping/scope-test-pages.js +2 -1
  59. package/lib/test-runner/test-runner.js +71 -0
  60. package/package.json +36 -38
  61. package/bin/init-ui5-package.js +0 -3
  62. package/components-package/rollup.js +0 -216
  63. package/lib/documentation/index.js +0 -165
  64. package/lib/documentation/templates/api-component-since.js +0 -3
  65. package/lib/documentation/templates/api-css-variables-section.js +0 -24
  66. package/lib/documentation/templates/api-events-section.js +0 -35
  67. package/lib/documentation/templates/api-methods-section.js +0 -26
  68. package/lib/documentation/templates/api-properties-section.js +0 -40
  69. package/lib/documentation/templates/api-slots-section.js +0 -28
  70. package/lib/documentation/templates/template.js +0 -38
  71. package/lib/hash/config.js +0 -10
  72. package/lib/hash/generate.js +0 -19
  73. package/lib/hash/upToDate.js +0 -31
  74. package/lib/init-package/index.js +0 -136
  75. package/lib/init-package/resources/.eslintignore +0 -3
  76. package/lib/init-package/resources/bundle.es5.js +0 -25
  77. package/lib/init-package/resources/bundle.esm.js +0 -31
  78. package/lib/init-package/resources/config/.eslintrc.js +0 -1
  79. package/lib/init-package/resources/config/postcss.components/postcss.config.js +0 -1
  80. package/lib/init-package/resources/config/postcss.themes/postcss.config.js +0 -1
  81. package/lib/init-package/resources/config/rollup.config.js +0 -1
  82. package/lib/init-package/resources/config/wdio.conf.js +0 -1
  83. package/lib/init-package/resources/package-scripts.js +0 -11
  84. package/lib/init-package/resources/src/Assets.js +0 -5
  85. package/lib/init-package/resources/src/MyFirstComponent.hbs +0 -1
  86. package/lib/init-package/resources/src/MyFirstComponent.js +0 -56
  87. package/lib/init-package/resources/src/i18n/messagebundle.properties +0 -2
  88. package/lib/init-package/resources/src/i18n/messagebundle_de.properties +0 -1
  89. package/lib/init-package/resources/src/i18n/messagebundle_en.properties +0 -1
  90. package/lib/init-package/resources/src/i18n/messagebundle_es.properties +0 -1
  91. package/lib/init-package/resources/src/i18n/messagebundle_fr.properties +0 -1
  92. package/lib/init-package/resources/src/themes/MyFirstComponent.css +0 -11
  93. package/lib/init-package/resources/src/themes/sap_belize/parameters-bundle.css +0 -3
  94. package/lib/init-package/resources/src/themes/sap_belize_hcb/parameters-bundle.css +0 -3
  95. package/lib/init-package/resources/src/themes/sap_belize_hcw/parameters-bundle.css +0 -3
  96. package/lib/init-package/resources/src/themes/sap_fiori_3/parameters-bundle.css +0 -3
  97. package/lib/init-package/resources/src/themes/sap_fiori_3_dark/parameters-bundle.css +0 -3
  98. package/lib/init-package/resources/src/themes/sap_fiori_3_hcb/parameters-bundle.css +0 -3
  99. package/lib/init-package/resources/src/themes/sap_fiori_3_hcw/parameters-bundle.css +0 -3
  100. package/lib/init-package/resources/test/pages/index.html +0 -56
  101. package/lib/init-package/resources/test/specs/Demo.spec.js +0 -12
  102. package/lib/polyfill-placeholder/index.js +0 -5
  103. package/lib/postcss-css-to-esm/index.js +0 -45
  104. package/lib/postcss-css-to-json/index.js +0 -31
  105. package/lib/postcss-new-files/index.js +0 -36
  106. package/lib/serve/index.js +0 -46
  107. package/lib/serve/serve.json +0 -3
  108. package/package-lock.json +0 -48
@@ -0,0 +1,86 @@
1
+ const fs = require("fs").promises;
2
+ const path = require("path");
3
+
4
+ const generateDynamicImportLines = async (fileNames, location, exclusionPatterns = []) => {
5
+ const packageName = JSON.parse(await fs.readFile("package.json")).name;
6
+ return fileNames
7
+ .filter((fileName) => !exclusionPatterns.some((pattern) => fileName.startsWith(pattern)))
8
+ .map((fileName) => {
9
+ const illustrationName = fileName.replace(".js", "");
10
+ const illustrationPath = `${location}/${illustrationName}`;
11
+ return `\t\tcase "${fileName.replace('.js', '')}": return (await import(/* webpackChunkName: "${packageName.replace("@", "").replace("/", "-")}-${illustrationName.toLowerCase()}" */ "${illustrationPath}.js")).default;`;
12
+ })
13
+ .join("\n");
14
+ };
15
+
16
+ const generateAvailableIllustrationsArray = (fileNames, exclusionPatterns = []) => {
17
+ return JSON.stringify(
18
+ fileNames
19
+ .filter((fileName) => !exclusionPatterns.some((pattern) => fileName.startsWith(pattern)))
20
+ .map((fileName) => fileName.replace(".js", ""))
21
+ );
22
+ };
23
+
24
+ const generateDynamicImportsFileContent = (dynamicImports, availableIllustrations, collection, set, prefix = "") => {
25
+ return `// @ts-nocheck
26
+ import { registerIllustrationLoader } from "@ui5/webcomponents-base/dist/asset-registries/Illustrations.js";
27
+
28
+ export const loadIllustration = async (illustrationName) => {
29
+ const collectionAndPrefix = "${set}/${collection}/${prefix}";
30
+ const cleanIllustrationName = illustrationName.startsWith(collectionAndPrefix) ? illustrationName.replace(collectionAndPrefix, "") : illustrationName;
31
+ switch (cleanIllustrationName) {
32
+ ${dynamicImports}
33
+ default:
34
+ throw new Error("[Illustrations] Illustration not found: " + illustrationName);
35
+ }
36
+ };
37
+
38
+ const loadAndCheck = async (illustrationName) => {
39
+ const data = await loadIllustration(illustrationName);
40
+ return data;
41
+ };
42
+
43
+ ${availableIllustrations}.forEach((illustrationName) =>
44
+ registerIllustrationLoader(\`${set}/${collection}/${prefix}\${illustrationName}\`, loadAndCheck)
45
+ );
46
+ `;
47
+ };
48
+
49
+ const getMatchingFiles = async (folder, pattern) => {
50
+ const dir = await fs.readdir(folder);
51
+ return dir.filter((fileName) => fileName.match(pattern));
52
+ };
53
+
54
+ const generateIllustrations = async (config) => {
55
+ const { inputFolder, outputFile, collection, location, prefix, filterOut, set } = config;
56
+
57
+ const normalizedInputFolder = path.normalize(inputFolder);
58
+ const normalizedOutputFile = path.normalize(outputFile);
59
+
60
+ const illustrations = await getMatchingFiles(normalizedInputFolder, /^.*\.js$/);
61
+
62
+ const dynamicImports = await generateDynamicImportLines(illustrations, location, filterOut);
63
+ const availableIllustrations = generateAvailableIllustrationsArray(illustrations, filterOut);
64
+
65
+ const contentDynamic = generateDynamicImportsFileContent(dynamicImports, availableIllustrations, collection, set, prefix);
66
+
67
+ await fs.mkdir(path.dirname(normalizedOutputFile), { recursive: true });
68
+ await fs.writeFile(normalizedOutputFile, contentDynamic);
69
+
70
+ console.log(`Generated ${normalizedOutputFile}`);
71
+ };
72
+
73
+ // Parse configuration from command-line arguments
74
+ const config = {
75
+ inputFolder: process.argv[2],
76
+ outputFile: process.argv[3],
77
+ set: process.argv[4],
78
+ collection: process.argv[5],
79
+ location: process.argv[6],
80
+ filterOut: process.argv.slice[7],
81
+ };
82
+
83
+ // Run the generation process
84
+ generateIllustrations(config).catch((error) => {
85
+ console.error("Error generating illustrations:", error);
86
+ });
@@ -1,37 +1,42 @@
1
- const fs = require("fs");
1
+ const fs = require("fs").promises;
2
2
  const path = require('path');
3
- const mkdirp = require("mkdirp");
4
3
 
5
- const packageName = JSON.parse(fs.readFileSync("package.json")).name;
4
+ const isTypeScript = process.env.UI5_TS;
5
+ const ext = isTypeScript ? 'ts' : 'js';
6
6
 
7
- const inputFolder = path.normalize(process.argv[2]);
8
- const outputFile = path.normalize(`${process.argv[3]}/i18n-static.js`);
9
- const outputFileDynamic = path.normalize(`${process.argv[3]}/i18n.js`);
7
+ const generate = async () => {
8
+
9
+ const packageName = JSON.parse(await fs.readFile("package.json")).name;
10
+
11
+ const inputFolder = path.normalize(process.argv[2]);
12
+ const outputFile = path.normalize(`${process.argv[3]}/i18n-static.${ext}`);
13
+ const outputFileDynamic = path.normalize(`${process.argv[3]}/i18n.${ext}`);
10
14
 
11
15
  // All languages present in the file system
12
- const files = fs.readdirSync(inputFolder);
13
- const languages = files.map(file => {
14
- const matches = file.match(/messagebundle_(.+?).json$/);
15
- return matches ? matches[1] : undefined;
16
- }).filter(key => !!key);
16
+ const files = await fs.readdir(inputFolder);
17
+ const languages = files.map(file => {
18
+ const matches = file.match(/messagebundle_(.+?).json$/);
19
+ return matches ? matches[1] : undefined;
20
+ }).filter(key => !!key);
17
21
 
18
- let contentStatic, contentDynamic;
22
+ let contentStatic, contentDynamic;
19
23
 
20
24
  // No i18n - just import dependencies, if any
21
- if (languages.length === 0) {
22
- contentStatic = "";
23
- contentDynamic = "";
25
+ if (languages.length === 0) {
26
+ contentStatic = "";
27
+ contentDynamic = "";
24
28
  // There is i18n - generate the full file
25
- } else {
26
- // Keys for the array
27
- const languagesKeysString = languages.map(key => `"${key}": _${key},`).join("\n\t");
28
- const languagesKeysStringArray = languages.map(key => `"${key}",`).join("\n\t");
29
+ } else {
30
+ // Keys for the array
31
+ const languagesKeysString = languages.map(key => `"${key}": _${key},`).join("\n\t");
32
+ const languagesKeysStringArray = languages.map(key => `"${key}",`).join("\n\t");
29
33
 
30
- // Actual imports for json assets
31
- const assetsImportsString = languages.map(key => `import _${key} from "../assets/i18n/messagebundle_${key}.json";`).join("\n");
34
+ // Actual imports for json assets
35
+ const assetsImportsString = languages.map(key => `import _${key} from "../assets/i18n/messagebundle_${key}.json";`).join("\n");
32
36
 
33
- // static imports
34
- contentStatic = `import { registerI18nLoader } from "@ui5/webcomponents-base/dist/asset-registries/i18n.js";
37
+ // static imports
38
+ contentStatic = `// @ts-nocheck
39
+ import { registerI18nLoader } from "@ui5/webcomponents-base/dist/asset-registries/i18n.js";
35
40
 
36
41
  ${assetsImportsString}
37
42
 
@@ -54,11 +59,12 @@ localeIds.forEach(localeId => {
54
59
  });
55
60
  `;
56
61
 
57
- // Actual imports for json assets
58
- const dynamicImportsString = languages.map(key => ` case "${key}": return (await import("../assets/i18n/messagebundle_${key}.json")).default;`).join("\n");
62
+ // Actual imports for json assets
63
+ const dynamicImportsString = languages.map(key => ` case "${key}": return (await import(/* webpackChunkName: "${packageName.replace("@", "").replace("/", "-")}-messagebundle-${key}" */ "../assets/i18n/messagebundle_${key}.json")).default;`).join("\n");
59
64
 
60
- // Resulting file content
61
- contentDynamic = `import { registerI18nLoader } from "@ui5/webcomponents-base/dist/asset-registries/i18n.js";
65
+ // Resulting file content
66
+ contentDynamic = `// @ts-nocheck
67
+ import { registerI18nLoader } from "@ui5/webcomponents-base/dist/asset-registries/i18n.js";
62
68
 
63
69
  const importMessageBundle = async (localeId) => {
64
70
  switch (localeId) {
@@ -83,9 +89,15 @@ localeIds.forEach(localeId => {
83
89
  `;
84
90
 
85
91
 
86
- }
92
+ }
87
93
 
94
+ await fs.mkdir(path.dirname(outputFile), { recursive: true });
95
+ return Promise.all([
96
+ fs.writeFile(outputFile, contentStatic),
97
+ fs.writeFile(outputFileDynamic, contentDynamic),
98
+ ]);
99
+ }
88
100
 
89
- mkdirp.sync(path.dirname(outputFile));
90
- fs.writeFileSync(outputFile, contentStatic);
91
- fs.writeFileSync(outputFileDynamic, contentDynamic);
101
+ generate().then(() => {
102
+ console.log("Generated i18n JSON imports.");
103
+ });
@@ -1,32 +1,36 @@
1
- const fs = require("fs");
1
+ const fs = require("fs").promises;
2
2
  const path = require('path');
3
- const mkdirp = require("mkdirp");
4
3
  const assets = require("../../assets-meta.js");
5
4
 
6
- const inputFolder = path.normalize(process.argv[2]);
7
- const outputFile = path.normalize(`${process.argv[3]}/Themes-static.js`);
8
- const outputFileDynamic = path.normalize(`${process.argv[3]}/Themes.js`);
5
+ const isTypeScript = process.env.UI5_TS;
6
+ const ext = isTypeScript ? 'ts' : 'js';
7
+
8
+ const generate = async () => {
9
+ const inputFolder = path.normalize(process.argv[2]);
10
+ const outputFile = path.normalize(`${process.argv[3]}/Themes-static.${ext}`);
11
+ const outputFileDynamic = path.normalize(`${process.argv[3]}/Themes.${ext}`);
9
12
 
10
13
  // All supported optional themes
11
- const allThemes = assets.themes.all;
14
+ const allThemes = assets.themes.all;
12
15
 
13
16
  // All themes present in the file system
14
- const dirs = fs.readdirSync(inputFolder);
15
- const themesOnFileSystem = dirs.map(dir => {
16
- const matches = dir.match(/sap_.*$/);
17
- return matches ? dir : undefined;
18
- }).filter(key => !!key && allThemes.includes(key));
17
+ const dirs = await fs.readdir(inputFolder);
18
+ const themesOnFileSystem = dirs.map(dir => {
19
+ const matches = dir.match(/sap_.*$/);
20
+ return matches ? dir : undefined;
21
+ }).filter(key => !!key && allThemes.includes(key));
19
22
 
20
- const packageName = JSON.parse(fs.readFileSync("package.json")).name;
23
+ const packageName = JSON.parse(await fs.readFile("package.json")).name;
21
24
 
22
- const importLines = themesOnFileSystem.map(theme => `import ${theme} from "../assets/themes/${theme}/parameters-bundle.css.json";`).join("\n");
23
- const themeUrlsByName = "{\n" + themesOnFileSystem.join(",\n") + "\n}";
24
- const availableThemesArray = `[${themesOnFileSystem.map(theme => `"${theme}"`).join(", ")}]`;
25
- const dynamicImportLines = themesOnFileSystem.map(theme => `\t\tcase "${theme}": return (await import("../assets/themes/${theme}/parameters-bundle.css.json")).default;`).join("\n");
25
+ const importLines = themesOnFileSystem.map(theme => `import ${theme} from "../assets/themes/${theme}/parameters-bundle.css.json";`).join("\n");
26
+ const themeUrlsByName = "{\n" + themesOnFileSystem.join(",\n") + "\n}";
27
+ const availableThemesArray = `[${themesOnFileSystem.map(theme => `"${theme}"`).join(", ")}]`;
28
+ const dynamicImportLines = themesOnFileSystem.map(theme => `\t\tcase "${theme}": return (await import(/* webpackChunkName: "${packageName.replace("@", "").replace("/", "-")}-${theme.replace("_", "-")}-parameters-bundle" */"../assets/themes/${theme}/parameters-bundle.css.json")).default;`).join("\n");
26
29
 
27
30
 
28
31
  // static imports file content
29
- const contentStatic = `import { registerThemePropertiesLoader } from "@ui5/webcomponents-base/dist/asset-registries/Themes.js";
32
+ const contentStatic = `// @ts-nocheck
33
+ import { registerThemePropertiesLoader } from "@ui5/webcomponents-base/dist/asset-registries/Themes.js";
30
34
 
31
35
  ${importLines}
32
36
 
@@ -39,7 +43,7 @@ const loadThemeProperties = async (themeName) => {
39
43
  throw new Error("[themes] Inlined JSON not supported with static imports of assets. Use dynamic imports of assets or configure JSON imports as URLs");
40
44
  }
41
45
  return (await fetch(themeUrlsByName[themeName])).json();
42
- }
46
+ };
43
47
 
44
48
  ${availableThemesArray}
45
49
  .forEach(themeName => registerThemePropertiesLoader("${packageName}", themeName, loadThemeProperties));
@@ -47,14 +51,15 @@ ${availableThemesArray}
47
51
 
48
52
 
49
53
  // dynamic imports file content
50
- const contentDynamic = `import { registerThemePropertiesLoader } from "@ui5/webcomponents-base/dist/asset-registries/Themes.js";
54
+ const contentDynamic = `// @ts-nocheck
55
+ import { registerThemePropertiesLoader } from "@ui5/webcomponents-base/dist/asset-registries/Themes.js";
51
56
 
52
57
  const loadThemeProperties = async (themeName) => {
53
58
  switch (themeName) {
54
59
  ${dynamicImportLines}
55
60
  default: throw "unknown theme"
56
61
  }
57
- }
62
+ };
58
63
 
59
64
  const loadAndCheck = async (themeName) => {
60
65
  const data = await loadThemeProperties(themeName);
@@ -62,12 +67,19 @@ const loadAndCheck = async (themeName) => {
62
67
  throw new Error(\`[themes] Invalid bundling detected - dynamic JSON imports bundled as URLs. Switch to inlining JSON files from the build or use 'import ".../Assets-static.js"'. Check the \"Assets\" documentation for more information.\`);
63
68
  }
64
69
  return data;
65
- }
70
+ };
66
71
 
67
72
  ${availableThemesArray}
68
73
  .forEach(themeName => registerThemePropertiesLoader("${packageName}", themeName, loadAndCheck));
69
74
  `;
70
75
 
71
- mkdirp.sync(path.dirname(outputFile));
72
- fs.writeFileSync(outputFile, contentStatic);
73
- fs.writeFileSync(outputFileDynamic, contentDynamic);
76
+ await fs.mkdir(path.dirname(outputFile), { recursive: true });
77
+ return Promise.all([
78
+ fs.writeFile(outputFile, contentStatic),
79
+ fs.writeFile(outputFileDynamic, contentDynamic)
80
+ ]);
81
+ };
82
+
83
+ generate().then(() => {
84
+ console.log("Generated themes JSON imports.");
85
+ });
@@ -12,15 +12,25 @@ const removeWhiteSpaces = (source) => {
12
12
  .replace(/}}\s+{{/g, "}}{{"); // Remove whitespace between }} and {{
13
13
  };
14
14
 
15
- const hbs2lit = (file) => {
16
- let sPreprocessed = includesReplacer.replace(file);
15
+ const hbs2lit = async (file, componentName) => {
16
+ let sPreprocessed = await includesReplacer.replace(file);
17
17
 
18
18
  sPreprocessed = removeWhiteSpaces(sPreprocessed);
19
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
+
20
30
  const ast = Handlebars.parse(sPreprocessed);
21
31
 
22
32
  const pv = new PartialsVisitor();
23
- const lv = new HTMLLitVisitor();
33
+ const lv = new HTMLLitVisitor(componentName);
24
34
 
25
35
  let result = "";
26
36
 
@@ -30,7 +40,17 @@ const hbs2lit = (file) => {
30
40
  lv.accept(ast);
31
41
 
32
42
  for (let key in lv.blocks) {
33
- 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";
34
54
  }
35
55
 
36
56
  result = svgProcessor.process(result);
@@ -1,9 +1,9 @@
1
1
  const path = require("path");
2
- const fs = require("fs");
2
+ const fs = require("fs").promises;
3
3
 
4
- function replaceIncludes(file) {
4
+ const replaceIncludes = async (file) => {
5
5
  const filePath = path.dirname(file);
6
- let fileContent = fs.readFileSync(file, "utf-8");
6
+ let fileContent = await fs.readFile(file, "utf-8");
7
7
 
8
8
  const inclRegex = /{{>\s*include\s*["'](.+?)["']}}/g;
9
9
  let match;
@@ -20,11 +20,11 @@ function replaceIncludes(file) {
20
20
  targetFile = require.resolve(targetFile);
21
21
  }
22
22
 
23
- fileContent = fileContent.replace(match[0], replaceIncludes(targetFile));
23
+ fileContent = fileContent.replace(match[0], await replaceIncludes(targetFile));
24
24
  }
25
25
 
26
26
  return fileContent;
27
- }
27
+ };
28
28
 
29
29
  module.exports = {
30
30
  replace: replaceIncludes
@@ -8,6 +8,9 @@ let skipIfDefined = false;
8
8
  // when true => an HTML node value, when false => an attribute value
9
9
  let isNodeValue = false;
10
10
 
11
+ // when true => the current attribute is "style"
12
+ let isStyleAttribute = false;
13
+
11
14
  // matches event handlers @click= and boolean attrs ?disabled=
12
15
  const dynamicAttributeRgx = /\s(\?|@)([a-zA-Z|-]+)="?\s*$/;
13
16
 
@@ -25,14 +28,18 @@ if (!String.prototype.replaceAll) {
25
28
  };
26
29
  }
27
30
 
28
- function HTMLLitVisitor(debug) {
31
+ function HTMLLitVisitor(componentName, debug) {
29
32
  this.blockCounter = 0;
30
33
  this.keys = [];
31
34
  this.blocks = {};
32
35
  this.result = "";
33
36
  this.mainBlock = "";
34
- this.blockPath = "context";
35
- 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"];
36
43
  this.paths = []; //contains all normalized relative paths
37
44
  this.debug = debug;
38
45
  if (this.debug) {
@@ -48,18 +55,18 @@ HTMLLitVisitor.prototype.Program = function(program) {
48
55
  this.keys.push(key);
49
56
  this.debug && this.blockByNumber.push(key);
50
57
 
51
- 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(", ")}) { `;
52
60
 
53
61
  if (this.keys.length > 1) { //it's a nested block
54
- this.blocks[this.prevKey()] += this.currentKey() + "(" + this.blockParameters.join(", ") + ")";
62
+ this.blocks[this.prevKey()] += this.currentKey() + ".call(" + this.blockParametersUsage.join(", ") + ")";
55
63
  } else {
56
64
  this.mainBlock = this.currentKey();
57
- this.paths.push(this.blockPath);
58
65
  }
59
66
 
60
- this.blocks[this.currentKey()] += "html`";
67
+ this.blocks[this.currentKey()] += "return html`";
61
68
  Visitor.prototype.Program.call(this, program);
62
- this.blocks[this.currentKey()] += "`; };";
69
+ this.blocks[this.currentKey()] += "`;}";
63
70
 
64
71
  this.keys.pop(key);
65
72
  };
@@ -77,6 +84,12 @@ HTMLLitVisitor.prototype.ContentStatement = function(content) {
77
84
  isNodeValue = closingIndex > openingIndex;
78
85
  }
79
86
 
87
+ isStyleAttribute = !isNodeValue && contentStatement.match(/style *= *["']? *$/);
88
+
89
+ if (!isStyleAttribute && contentStatement.match(/style=/)) {
90
+ console.log("WARNING: style hard-coded", contentStatement);
91
+ }
92
+
80
93
  // Scope custom element tags
81
94
  contentStatement = contentStatement.replaceAll(/(<\/?\s*)([a-zA-Z0-9_]+-[a-zA-Z0-9_-]+)/g, "$1\${scopeTag(\"$2\", tags, suffix)}");
82
95
 
@@ -90,16 +103,19 @@ HTMLLitVisitor.prototype.MustacheStatement = function(mustache) {
90
103
  this.blocks[this.currentKey()] += "${index}";
91
104
  } else {
92
105
  const path = normalizePath.call(this, mustache.path.original);
93
- const hasCalculatingClasses = path.includes("context.classes");
94
- const hasStylesCalculation = path.includes("context.styles");
106
+ const hasCalculatingClasses = path.includes("this.classes");
95
107
 
96
108
  let parsedCode = "";
97
109
 
98
110
  if (isNodeValue && !mustache.escaped) {
99
111
  parsedCode = `\${unsafeHTML(${path})}`;
100
112
  } else if (hasCalculatingClasses) {
101
- parsedCode = `\${classMap(${path})}`;
102
- } else if (hasStylesCalculation) {
113
+ if (process.env.UI5_TS) {
114
+ parsedCode = `\${classMap(${path} as ClassMapValue)}`;
115
+ } else {
116
+ parsedCode = `\${classMap(${path})}`;
117
+ }
118
+ } else if (isStyleAttribute) {
103
119
  parsedCode = `\${styleMap(${path})}`;
104
120
  } else if (skipIfDefined){
105
121
  parsedCode = `\${${path}}`;
@@ -163,22 +179,37 @@ function visitEachBlock(block) {
163
179
  var bParamAdded = false;
164
180
  visitSubExpression.call(this, block);
165
181
 
166
- this.blocks[this.currentKey()] += "${ repeat(" + normalizePath.call(this, block.params[0].original) + ", (item, index) => item._id || index, (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 + " => ";
167
186
  this.paths.push(normalizePath.call(this, block.params[0].original));
168
- this.blockPath = "item";
187
+ this.blockLevel++;
169
188
 
170
- 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
171
193
  bParamAdded = true;
172
- this.blockParameters.unshift("index");
173
- 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");
174
203
  }
175
204
  this.acceptKey(block, "program");
176
205
  if (bParamAdded) {
177
- this.blockParameters.shift("item");
178
- 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();
179
211
  }
180
- this.blockPath = "context";
181
-
212
+ this.blockLevel--;
182
213
  this.blocks[this.currentKey()] += ") }";
183
214
  }
184
215
 
@@ -187,12 +218,52 @@ function normalizePath(sPath) {
187
218
 
188
219
  //read carefully - https://github.com/wycats/handlebars.js/issues/1028
189
220
  //kpdecker commented on May 20, 2015
190
- if (result.indexOf("../") === 0) {
191
- 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
+
192
245
  result = replaceAll(path.normalize(absolutePath), path.sep, ".");
246
+
193
247
  } else {
194
- 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;
195
265
  }
266
+
196
267
  return result;
197
268
  }
198
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,18 +1,40 @@
1
- const buildRenderer = (controlName, litTemplate) => {
2
- return `
3
- /* eslint no-unused-vars: 0 */
4
- import { ifDefined } from "lit-html/directives/if-defined.js";
5
- import { html, svg, repeat, classMap, styleMap, unsafeHTML, scopeTag } from '@ui5/webcomponents-base/dist/renderer/LitRenderer.js';
1
+ const tsImports = (controlName, hasTypes) => {
2
+ if (!process.env.UI5_TS) {
3
+ return "";
4
+ }
6
5
 
7
- const main = (context, tags, suffix) => {
8
- ${litTemplate}
6
+ const importPrefix = process.env.UI5_BASE ? "../../../" : "@ui5/webcomponents-base/dist/"
9
7
 
10
- return block0(context);
11
- };
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
+ }
12
25
 
13
- export default main;`;
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)}
33
+ ${litTemplate}
34
+
35
+ export default block0;`;
14
36
  };
15
37
 
16
38
  module.exports = {
17
39
  generateTemplate: buildRenderer
18
- };
40
+ };