Skip to content

Commit 757577f

Browse files
authored
feat(framework): introduce runtimes and version info (#4491)
1 parent b9d31bc commit 757577f

File tree

11 files changed

+215
-7
lines changed

11 files changed

+215
-7
lines changed

packages/base/.eslintignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,4 @@ postcss.config.js
1212
package-scripts.js
1313
.eslintrc.js
1414
src/renderer/directives/style-map.js
15+
src/util/metaUrl.js

packages/base/hash.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
o94uov3PET7YqHV/VdedxOnCg5M=
1+
RNSU7YzpfGMOwn0wc5+tQL7ZLGs=
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
const fs = require('fs');
2+
const mkdirp = require('mkdirp');
3+
4+
const version = JSON.parse(fs.readFileSync("package.json")).version;
5+
6+
// Parse version
7+
const matches = version.match(/^([0-9]+)\.([0-9]+)\.([0-9]+)(.*)$/);
8+
if (!matches) {
9+
throw new Error("Unsupported version format");
10+
}
11+
12+
const isNext = version.match(/[a-f0-9]{9}$/);
13+
const buildTime = Math.floor(new Date().getTime() / 1000);
14+
15+
const fileContent = `const VersionInfo = {
16+
version: "${version}",
17+
major: ${matches[1]},
18+
minor: ${matches[2]},
19+
patch: ${matches[3]},
20+
suffix: "${matches[4]}",
21+
isNext: ${isNext ? "true" : "false"},
22+
buildTime: ${buildTime},
23+
};
24+
export default VersionInfo;`;
25+
26+
mkdirp.sync("dist/generated/");
27+
fs.writeFileSync("dist/generated/VersionInfo.js", fileContent);

packages/base/package-scripts.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ const resolve = require("resolve");
22

33
const assetParametersScript = resolve.sync("@ui5/webcomponents-base/lib/generate-asset-parameters/index.js");
44
const stylesScript = resolve.sync("@ui5/webcomponents-base/lib/generate-styles/index.js");
5+
const versionScript = resolve.sync("@ui5/webcomponents-base/lib/generate-version-info/index.js");
56
const serve = resolve.sync("@ui5/webcomponents-tools/lib/serve/index.js");
67
const generateHash = resolve.sync("@ui5/webcomponents-tools/lib/hash/generate.js");
78
const hashIsUpToDate = resolve.sync("@ui5/webcomponents-tools/lib/hash/upToDate.js");
@@ -13,7 +14,7 @@ const UP_TO_DATE = `node "${hashIsUpToDate}" dist/ hash.txt && echo "Up to date.
1314
const scripts = {
1415
clean: "rimraf dist && rimraf .port",
1516
lint: "eslint . --config config/.eslintrc.js",
16-
prepare: "nps clean integrate copy generateAssetParameters generateStyles",
17+
prepare: "nps clean integrate copy generateAssetParameters generateVersionInfo generateStyles",
1718
integrate: {
1819
default: "nps integrate.copy-used-modules integrate.copy-overlay integrate.replace-amd integrate.replace-export-true integrate.replace-export-false integrate.amd-to-es6 integrate.replace-global-core-usage integrate.esm-abs-to-rel integrate.third-party",
1920
"copy-used-modules": `node "${copyUsedModules}" ./used-modules.txt dist/`,
@@ -40,6 +41,7 @@ const scripts = {
4041
test: `copy-and-watch "test/**/*.*" dist/test-resources`,
4142
},
4243
generateAssetParameters: `node "${assetParametersScript}"`,
44+
generateVersionInfo: `node "${versionScript}"`,
4345
generateStyles: `node "${stylesScript}"`,
4446
watch: {
4547
default: 'concurrently "nps watch.test" "nps watch.src" "nps watch.bundle" "nps watch.styles"',

packages/base/src/Boot.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import insertFontFace from "./FontFace.js";
44
import insertSystemCSSVars from "./SystemCSSVars.js";
55
import { getTheme } from "./config/Theme.js";
66
import applyTheme from "./theming/applyTheme.js";
7+
import { registerCurrentRuntime } from "./Runtimes.js";
78
import { getFeature } from "./FeaturesRegistry.js";
89

910
let booted = false;
@@ -23,6 +24,8 @@ const boot = async () => {
2324
return;
2425
}
2526

27+
registerCurrentRuntime();
28+
2629
const OpenUI5Support = getFeature("OpenUI5Support");
2730
if (OpenUI5Support) {
2831
await OpenUI5Support.init();

packages/base/src/CustomElementsRegistry.js

Lines changed: 60 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,18 @@
11
import setToArray from "./util/setToArray.js";
2+
import getSharedResource from "./getSharedResource.js";
3+
import { getCurrentRuntimeIndex, compareRuntimes, getAllRuntimes } from "./Runtimes.js";
4+
5+
const Tags = getSharedResource("Tags", new Map());
26

37
const Definitions = new Set();
4-
const Failures = new Set();
8+
let Failures = {};
59
let failureTimeout;
610

11+
const UNKNOWN_RUNTIME = "unknown";
12+
713
const registerTag = tag => {
814
Definitions.add(tag);
15+
Tags.set(tag, getCurrentRuntimeIndex());
916
};
1017

1118
const isTagRegistered = tag => {
@@ -17,18 +24,67 @@ const getAllRegisteredTags = () => {
1724
};
1825

1926
const recordTagRegistrationFailure = tag => {
20-
Failures.add(tag);
27+
let tagRegRuntimeIndex = Tags.get(tag);
28+
if (tagRegRuntimeIndex === undefined) {
29+
tagRegRuntimeIndex = UNKNOWN_RUNTIME; // If the tag is taken, but not registered in Tags, then a version before 1.1.0 defined it => use the "unknown" key
30+
}
31+
Failures[tagRegRuntimeIndex] = Failures[tagRegRuntimeIndex] || new Set();
32+
Failures[tagRegRuntimeIndex].add(tag);
33+
2134
if (!failureTimeout) {
2235
failureTimeout = setTimeout(() => {
2336
displayFailedRegistrations();
37+
Failures = {};
2438
failureTimeout = undefined;
2539
}, 1000);
2640
}
2741
};
2842

2943
const displayFailedRegistrations = () => {
30-
console.warn(`The following tags have already been defined by a different UI5 Web Components version: ${setToArray(Failures).join(", ")}`); // eslint-disable-line
31-
Failures.clear();
44+
const allRuntimes = getAllRuntimes();
45+
const currentRuntimeIndex = getCurrentRuntimeIndex();
46+
const currentRuntime = allRuntimes[currentRuntimeIndex];
47+
48+
let message = `Multiple UI5 Web Components instances detected.`;
49+
50+
if (allRuntimes.length > 1) {
51+
message = `${message}\nLoading order (versions before 1.1.0 not listed): ${allRuntimes.map(runtime => `\n${runtime.description} ${runtime.url}`).join("")}`;
52+
}
53+
54+
Object.keys(Failures).forEach(otherRuntimeIndex => {
55+
let comparison;
56+
let otherRuntime;
57+
58+
if (otherRuntimeIndex === UNKNOWN_RUNTIME) { // version < 1.1.0 defined the tag
59+
comparison = 1; // the current runtime is considered newer
60+
otherRuntime = {
61+
description: `Older unknown runtime`,
62+
};
63+
} else {
64+
comparison = compareRuntimes(currentRuntimeIndex, otherRuntimeIndex);
65+
otherRuntime = allRuntimes[otherRuntimeIndex];
66+
}
67+
68+
let compareWord;
69+
if (comparison > 0) {
70+
compareWord = "an older";
71+
} else if (comparison < 0) {
72+
compareWord = "a newer";
73+
} else {
74+
compareWord = "the same";
75+
}
76+
message = `${message}\n\n"${currentRuntime.description}" failed to define ${Failures[otherRuntimeIndex].size} tag(s) as they were defined by a runtime of ${compareWord} version "${otherRuntime.description}": ${setToArray(Failures[otherRuntimeIndex]).sort().join(", ")}.`;
77+
78+
if (comparison > 0) {
79+
message = `${message}\nWARNING! If your code uses features of the above web components, unavailable in ${otherRuntime.description}, it might not work as expected!`;
80+
} else {
81+
message = `${message}\nSince the above web components were defined by the same or newer version runtime, they should be compatible with your code.`;
82+
}
83+
});
84+
85+
message = `${message}\n\nTo prevent other runtimes from defining tags that you use, consider using scoping or have third-party libraries use scoping: https://github.com/SAP/ui5-webcomponents/blob/master/docs/2-advanced/03-scoping.md.`;
86+
87+
console.warn(message); // eslint-disable-line
3288
};
3389

3490
export {

packages/base/src/Runtimes.js

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
import VersionInfo from "./generated/VersionInfo.js";
2+
import getSharedResource from "./getSharedResource.js";
3+
import metaUrl from "./util/metaUrl.js"; // eslint-disable-line
4+
5+
let currentRuntimeIndex;
6+
let currentRuntimeAlias = "";
7+
8+
const compareCache = new Map();
9+
10+
/**
11+
* Central registry where all runtimes register themselves by pushing an object.
12+
* The index in the registry servers as an ID for the runtime.
13+
* @type {*}
14+
*/
15+
const Runtimes = getSharedResource("Runtimes", []);
16+
17+
/**
18+
* Registers the current runtime in the shared runtimes resource registry
19+
*/
20+
const registerCurrentRuntime = () => {
21+
if (currentRuntimeIndex === undefined) {
22+
currentRuntimeIndex = Runtimes.length;
23+
Runtimes.push({
24+
...VersionInfo,
25+
url: metaUrl,
26+
alias: currentRuntimeAlias,
27+
description: `Runtime ${currentRuntimeIndex} - ver ${VersionInfo.version}${currentRuntimeAlias ? ` (${currentRuntimeAlias})` : ""}`,
28+
});
29+
}
30+
};
31+
32+
/**
33+
* Returns the index of the current runtime's object in the shared runtimes resource registry
34+
* @returns {*}
35+
*/
36+
const getCurrentRuntimeIndex = () => {
37+
return currentRuntimeIndex;
38+
};
39+
40+
/**
41+
* Compares two runtimes and returns 1 if the first is of a bigger version, -1 if the second is of a bigger version, and 0 if equal
42+
* @param index1 The index of the first runtime to compare
43+
* @param index2 The index of the second runtime to compare
44+
* @returns {number}
45+
*/
46+
const compareRuntimes = (index1, index2) => {
47+
const cacheIndex = `${index1},${index2}`;
48+
if (compareCache.has(cacheIndex)) {
49+
return compareCache.get(cacheIndex);
50+
}
51+
52+
const runtime1 = Runtimes[index1];
53+
const runtime2 = Runtimes[index2];
54+
55+
if (!runtime1 || !runtime2) {
56+
throw new Error("Invalid runtime index supplied");
57+
}
58+
59+
// If any of the two is a next version, bigger buildTime wins
60+
if (runtime1.isNext || runtime2.isNext) {
61+
return runtime1.buildTime - runtime2.buildTime;
62+
}
63+
64+
// If major versions differ, bigger one wins
65+
const majorDiff = runtime1.major - runtime2.major;
66+
if (majorDiff) {
67+
return majorDiff;
68+
}
69+
70+
// If minor versions differ, bigger one wins
71+
const minorDiff = runtime1.minor - runtime2.minor;
72+
if (minorDiff) {
73+
return minorDiff;
74+
}
75+
76+
// If patch versions differ, bigger one wins
77+
const patchDiff = runtime1.patch - runtime2.patch;
78+
if (patchDiff) {
79+
return patchDiff;
80+
}
81+
82+
// Bigger suffix wins, f.e. rc10 > rc9
83+
// Important: suffix is alphanumeric, must use natural compare
84+
const collator = new Intl.Collator(undefined, { numeric: true, sensitivity: "base" });
85+
const result = collator.compare(runtime1.suffix, runtime2.suffix);
86+
87+
compareCache.set(cacheIndex, result);
88+
return result;
89+
};
90+
91+
/**
92+
* Set an alias for the the current app/library/microfrontend which will appear in debug messages and console warnings
93+
* @param alias
94+
*/
95+
const setRuntimeAlias = alias => {
96+
currentRuntimeAlias = alias;
97+
};
98+
99+
const getAllRuntimes = () => {
100+
return Runtimes;
101+
};
102+
103+
export {
104+
getCurrentRuntimeIndex,
105+
registerCurrentRuntime,
106+
compareRuntimes,
107+
setRuntimeAlias,
108+
getAllRuntimes,
109+
};

packages/base/src/util/metaUrl.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
const metaUrl = import.meta.url;
2+
3+
export default metaUrl;

packages/main/csp.js renamed to packages/main/bundle.common.bootstrap.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
1+
import { setRuntimeAlias } from "@ui5/webcomponents-base/dist/Runtimes.js";
12
import { setPackageCSSRoot, setUseLinks } from "@ui5/webcomponents-base/dist/CSP.js";
23

4+
setRuntimeAlias("UI5 Web Components Playground");
5+
36
setUseLinks(false); // !document.adoptedStyleSheets
47
setPackageCSSRoot("@ui5/webcomponents-base", "/resources/css/base/");
58
setPackageCSSRoot("@ui5/webcomponents-theming", "/resources/css/theming/");

packages/main/bundle.common.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { addCustomCSS, attachThemeLoaded, detachThemeLoaded } from "@ui5/webcomponents-base/dist/Theming.js";
22
// import "./customI18n.js";
33

4-
import "./csp.js"; // import from a separate module to ensure that the code has executed before other modules are executed
4+
import "./bundle.common.bootstrap.js"; // code that needs to be executed before other modules
55

66
// Calendars
77
import "@ui5/webcomponents-localization/dist/features/calendar/Buddhist.js";

0 commit comments

Comments
 (0)