@jsenv/core 41.1.0 → 41.2.0

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.
@@ -8735,6 +8735,39 @@ const jsenvPluginCacheControl = ({
8735
8735
 
8736
8736
  const SECONDS_IN_30_DAYS = 60 * 60 * 24 * 30;
8737
8737
 
8738
+ const jsenvPluginCustomElementsRedefine = () => {
8739
+ const customElementsRedefineClientFileUrl = import.meta
8740
+ .resolve("../client/custom_elements_redefine/custom_elements_redefine.js");
8741
+
8742
+ return {
8743
+ name: "jsenv:custom_elements_redefine",
8744
+ appliesDuring: "dev",
8745
+ transformUrlContent: {
8746
+ html: (urlInfo) => {
8747
+ const htmlAst = parseHtml({ html: urlInfo.content, url: urlInfo.url });
8748
+ const reference = urlInfo.dependencies.inject({
8749
+ type: "script",
8750
+ subtype: "js_module",
8751
+ expectedType: "js_module",
8752
+ specifier: customElementsRedefineClientFileUrl,
8753
+ });
8754
+ injectJsenvScript(htmlAst, {
8755
+ type: "module",
8756
+ src: reference.generatedSpecifier,
8757
+ initCall: {
8758
+ callee: "allowCustomElementsRedefine",
8759
+ },
8760
+ pluginName: "jsenv:custom_elements_redefine",
8761
+ });
8762
+ const htmlModified = stringifyHtmlAst(htmlAst);
8763
+ return {
8764
+ content: htmlModified,
8765
+ };
8766
+ },
8767
+ },
8768
+ };
8769
+ };
8770
+
8738
8771
  const jsenvPluginRibbon = ({
8739
8772
  rootDirectoryUrl,
8740
8773
  htmlInclude = "/**/*.html",
@@ -9206,6 +9239,7 @@ const getCorePlugins = ({
9206
9239
  scenarioPlaceholders = true,
9207
9240
  ribbon = true,
9208
9241
  dropToOpen = true,
9242
+ customElementsRedefine = true,
9209
9243
  packageSideEffects = false,
9210
9244
  } = {}) => {
9211
9245
  if (cacheControl === true) {
@@ -9302,6 +9336,7 @@ const getCorePlugins = ({
9302
9336
  ...(cacheControl ? [jsenvPluginCacheControl(cacheControl)] : []),
9303
9337
  ...(ribbon ? [jsenvPluginRibbon({ rootDirectoryUrl, ...ribbon })] : []),
9304
9338
  ...(dropToOpen ? [jsenvPluginDropToOpen()] : []),
9339
+ ...(customElementsRedefine ? [jsenvPluginCustomElementsRedefine()] : []),
9305
9340
  jsenvPluginCleanHTML(),
9306
9341
  ...(packageSideEffects
9307
9342
  ? [jsenvPluginPackageSideEffects({ packageDirectory })]
@@ -0,0 +1,208 @@
1
+ /*
2
+ * https://github.com/lit/lit/issues/1844
3
+ * https://github.com/vegarringdal/custom-elements-hmr-polyfill/tree/master
4
+ */
5
+
6
+ const allowCustomElementsRedefine = ({
7
+ updateWholeDOMOnRedefine = false,
8
+ } = {}) => {
9
+ let timeoutId;
10
+ const onCustomElementChange = (
11
+ customElementName,
12
+ // customElementClass,
13
+ // options,
14
+ ) => {
15
+ if (!updateWholeDOMOnRedefine) {
16
+ return;
17
+ }
18
+ clearTimeout(timeoutId);
19
+ timeoutId = setTimeout(() => {
20
+ if (document.body) {
21
+ requestAnimationFrame(() => {
22
+ // re-render the whole DOM
23
+ // this will make less calls to connectedCallback/disconnectedCallback on replaced child node when created.
24
+ const oldBodyHtml = document.body.innerHTML;
25
+ document.body.innerHTML = "";
26
+ document.body.innerHTML = oldBodyHtml;
27
+ });
28
+ }
29
+ }, 250);
30
+ };
31
+
32
+ const customElementClassMap = new Map();
33
+ const defineOriginal = customElements.define;
34
+ customElements.define = (customElementName, customElementClass, options) => {
35
+ const registeredCustomElement = customElements.get(customElementName);
36
+ customElementClassMap.set(customElementName, customElementClass);
37
+ if (registeredCustomElement) {
38
+ onCustomElementChange();
39
+ return;
40
+ }
41
+ const CustomElementFacade = createCustomElementFacade(
42
+ customElementName,
43
+ customElementClass,
44
+ );
45
+ defineOriginal.call(
46
+ customElements,
47
+ customElementName,
48
+ CustomElementFacade,
49
+ options,
50
+ );
51
+ };
52
+
53
+ const observerSymbol = Symbol.for("observedAttributesObserver");
54
+ const createCustomElementFacade = (
55
+ customElementName,
56
+ customElementFirstClass,
57
+ ) => {
58
+ class CustomElementFacade extends customElementFirstClass {
59
+ static get observedAttributes() {
60
+ return [];
61
+ }
62
+
63
+ constructor(...args) {
64
+ const CustomElementClass = customElementClassMap.get(customElementName);
65
+
66
+ if (CustomElementClass !== customElementFirstClass) {
67
+ {
68
+ let ClassCandidate = CustomElementClass;
69
+ while (ClassCandidate) {
70
+ const nextClassCandidate = Object.getPrototypeOf(ClassCandidate);
71
+ if (!nextClassCandidate) {
72
+ break;
73
+ }
74
+ const name = nextClassCandidate.name;
75
+ if (name) {
76
+ const constructor = window[name];
77
+ if (constructor && constructor.prototype instanceof Element) {
78
+ patchProperties(
79
+ CustomElementFacade.prototype,
80
+ ClassCandidate.prototype,
81
+ );
82
+ break;
83
+ }
84
+ }
85
+ ClassCandidate = nextClassCandidate;
86
+ }
87
+ }
88
+ {
89
+ const CustomElementPrototype = CustomElementClass.prototype;
90
+ patchProperties(
91
+ CustomElementFacade.prototype,
92
+ CustomElementPrototype,
93
+ );
94
+ }
95
+ }
96
+ const customElementInstance = Reflect.construct(
97
+ CustomElementClass,
98
+ args,
99
+ CustomElementFacade,
100
+ );
101
+ // eslint-disable-next-line no-constructor-return
102
+ return customElementInstance;
103
+ }
104
+
105
+ connectedCallback(...args) {
106
+ const CustomElementClass = customElementClassMap.get(customElementName);
107
+ const CustomElementPrototype = CustomElementClass.prototype;
108
+ const observedAttributes = CustomElementClass.observedAttributes;
109
+
110
+ // call initial callback when class is created
111
+ if (observedAttributes) {
112
+ if (Array.isArray(observedAttributes)) {
113
+ observedAttributes.forEach((observedAttributeName) => {
114
+ const haveAtt = this.getAttributeNode(observedAttributeName);
115
+ if (haveAtt) {
116
+ CustomElementPrototype.attributeChangedCallback.call(
117
+ this,
118
+ observedAttributeName,
119
+ null,
120
+ this.getAttribute(observedAttributeName),
121
+ null,
122
+ );
123
+ }
124
+ });
125
+ } else {
126
+ console.warn(
127
+ `observedAttributes in ${customElementName} is not array, please fix`,
128
+ );
129
+ }
130
+ }
131
+ const mutationObserver = new MutationObserver((mutationList) => {
132
+ mutationList.forEach((mutation) => {
133
+ if (
134
+ CustomElementPrototype.attributeChangedCallback &&
135
+ observedAttributes &&
136
+ observedAttributes.includes(mutation.attributeName)
137
+ ) {
138
+ CustomElementPrototype.attributeChangedCallback.call(
139
+ this,
140
+ mutation.attributeName,
141
+ mutation.oldValue,
142
+ this.getAttribute(mutation.attributeName),
143
+ null,
144
+ );
145
+ }
146
+ });
147
+ });
148
+ this[observerSymbol] = mutationObserver;
149
+ mutationObserver.observe(this, {
150
+ childList: false,
151
+ attributes: true,
152
+ attributeOldValue: true,
153
+ subtree: false,
154
+ });
155
+
156
+ if (CustomElementPrototype.connectedCallback) {
157
+ CustomElementPrototype.connectedCallback.call(this, ...args);
158
+ }
159
+ }
160
+
161
+ disconnectedCallback(...args) {
162
+ this[observerSymbol].disconnect();
163
+ this[observerSymbol] = null;
164
+
165
+ const CustomElementClass = customElementClassMap.get(customElementName);
166
+ const CustomElementPrototype = CustomElementClass.prototype;
167
+ if (CustomElementPrototype.disconnectedCallback) {
168
+ CustomElementPrototype.disconnectedCallback.call(this, ...args);
169
+ }
170
+ }
171
+
172
+ adoptedCallback(...args) {
173
+ const CustomElementClass = customElementClassMap.get(customElementName);
174
+ const CustomElementPrototype = CustomElementClass.prototype;
175
+ if (CustomElementPrototype.adoptedCallback) {
176
+ CustomElementPrototype.adoptedCallback.call(this, ...args);
177
+ }
178
+ }
179
+ }
180
+ return CustomElementFacade;
181
+ };
182
+ const patchProperties = (into, from) => {
183
+ const ownPropertyNames = Object.getOwnPropertyNames(from);
184
+ ownPropertyNames.forEach((ownPropertyName) => {
185
+ if (PROPERTY_NAMES_TO_SKIP.includes(ownPropertyName)) {
186
+ return;
187
+ }
188
+ const propertyDescriptor = Object.getOwnPropertyDescriptor(
189
+ from,
190
+ ownPropertyName,
191
+ );
192
+ if (!propertyDescriptor) {
193
+ return;
194
+ }
195
+ if (!propertyDescriptor.configurable) {
196
+ console.warn(
197
+ "[custom-elements-redefined]",
198
+ `${ownPropertyName} is not configurable, skipping`,
199
+ );
200
+ return;
201
+ }
202
+ Object.defineProperty(into, ownPropertyName, propertyDescriptor);
203
+ });
204
+ };
205
+ const PROPERTY_NAMES_TO_SKIP = ["name", "prototype", "length"];
206
+ };
207
+
208
+ export { allowCustomElementsRedefine };
@@ -5687,6 +5687,39 @@ const jsenvPluginCacheControl = ({
5687
5687
 
5688
5688
  const SECONDS_IN_30_DAYS = 60 * 60 * 24 * 30;
5689
5689
 
5690
+ const jsenvPluginCustomElementsRedefine = () => {
5691
+ const customElementsRedefineClientFileUrl = import.meta
5692
+ .resolve("../client/custom_elements_redefine/custom_elements_redefine.js");
5693
+
5694
+ return {
5695
+ name: "jsenv:custom_elements_redefine",
5696
+ appliesDuring: "dev",
5697
+ transformUrlContent: {
5698
+ html: (urlInfo) => {
5699
+ const htmlAst = parseHtml({ html: urlInfo.content, url: urlInfo.url });
5700
+ const reference = urlInfo.dependencies.inject({
5701
+ type: "script",
5702
+ subtype: "js_module",
5703
+ expectedType: "js_module",
5704
+ specifier: customElementsRedefineClientFileUrl,
5705
+ });
5706
+ injectJsenvScript(htmlAst, {
5707
+ type: "module",
5708
+ src: reference.generatedSpecifier,
5709
+ initCall: {
5710
+ callee: "allowCustomElementsRedefine",
5711
+ },
5712
+ pluginName: "jsenv:custom_elements_redefine",
5713
+ });
5714
+ const htmlModified = stringifyHtmlAst(htmlAst);
5715
+ return {
5716
+ content: htmlModified,
5717
+ };
5718
+ },
5719
+ },
5720
+ };
5721
+ };
5722
+
5690
5723
  const jsenvPluginRibbon = ({
5691
5724
  rootDirectoryUrl,
5692
5725
  htmlInclude = "/**/*.html",
@@ -6158,6 +6191,7 @@ const getCorePlugins = ({
6158
6191
  scenarioPlaceholders = true,
6159
6192
  ribbon = true,
6160
6193
  dropToOpen = true,
6194
+ customElementsRedefine = true,
6161
6195
  packageSideEffects = false,
6162
6196
  } = {}) => {
6163
6197
  if (cacheControl === true) {
@@ -6254,6 +6288,7 @@ const getCorePlugins = ({
6254
6288
  ...(cacheControl ? [jsenvPluginCacheControl(cacheControl)] : []),
6255
6289
  ...(ribbon ? [jsenvPluginRibbon({ rootDirectoryUrl, ...ribbon })] : []),
6256
6290
  ...(dropToOpen ? [jsenvPluginDropToOpen()] : []),
6291
+ ...(customElementsRedefine ? [jsenvPluginCustomElementsRedefine()] : []),
6257
6292
  jsenvPluginCleanHTML(),
6258
6293
  ...(packageSideEffects
6259
6294
  ? [jsenvPluginPackageSideEffects({ packageDirectory })]
@@ -9608,15 +9643,17 @@ const parseSecChUaHeader = (secChUa) => {
9608
9643
  brand = brands[0];
9609
9644
  }
9610
9645
  const runtimeName = brandNameToRuntimeName(brand.name);
9611
- const runtimeVersion = brand.version;
9646
+ const runtimeVersion = `${brand.version}.0.0`;
9612
9647
  return { runtimeName, runtimeVersion };
9613
9648
  };
9614
-
9615
9649
  const brandNameToRuntimeName = (brandName) => {
9616
9650
  const lower = brandName.toLowerCase();
9617
9651
  if (lower === "google chrome") {
9618
9652
  return "chrome";
9619
9653
  }
9654
+ if (lower === "headlesschrome") {
9655
+ return "chrome";
9656
+ }
9620
9657
  if (lower === "microsoft edge") {
9621
9658
  return "edge";
9622
9659
  }
@@ -9629,7 +9666,8 @@ const brandNameToRuntimeName = (brandName) => {
9629
9666
  if (lower === "chromium") {
9630
9667
  return "chrome";
9631
9668
  }
9632
- return lower;
9669
+ // other Chromium-based browsers share Chrome's compatibility
9670
+ return "chrome";
9633
9671
  };
9634
9672
 
9635
9673
  const parseUserAgentHeader = (userAgent) => {
@@ -9673,7 +9711,9 @@ const parseUserAgentHeader = (userAgent) => {
9673
9711
  return { runtimeName: "firefox", runtimeVersion: `${major}.${minor}.0` };
9674
9712
  }
9675
9713
  // generic Chromium-based fallback (should normally be handled by sec-ch-ua)
9676
- const chromeMatch = userAgent.match(/\bChrome\/(\d+)\.(\d+)\b/);
9714
+ const chromeMatch = userAgent.match(
9715
+ /(?:HeadlessChrome|Chrome)\/(\d+)\.(\d+)\b/,
9716
+ );
9677
9717
  if (chromeMatch) {
9678
9718
  const major = chromeMatch[1];
9679
9719
  const minor = chromeMatch[2] || "0";
@@ -10145,6 +10185,7 @@ const startDevServer = async ({
10145
10185
  cacheControl = true,
10146
10186
  ribbon = true,
10147
10187
  dropToOpen = true,
10188
+ customElementsRedefine = true,
10148
10189
  // toolbar = false,
10149
10190
  onKitchenCreated = () => {},
10150
10191
  spa,
@@ -10260,6 +10301,7 @@ const startDevServer = async ({
10260
10301
  cacheControl,
10261
10302
  ribbon,
10262
10303
  dropToOpen,
10304
+ customElementsRedefine,
10263
10305
  }),
10264
10306
  ]);
10265
10307
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jsenv/core",
3
- "version": "41.1.0",
3
+ "version": "41.2.0",
4
4
  "description": "Tool to develop, test and build js projects",
5
5
  "repository": {
6
6
  "type": "git",
@@ -93,6 +93,7 @@
93
93
  "@jsenv/assert": "workspace:*",
94
94
  "@jsenv/cli": "workspace:*",
95
95
  "@jsenv/core": "./",
96
+ "@jsenv/custom-elements-redefine": "workspace:*",
96
97
  "@jsenv/database-manager": "workspace:*",
97
98
  "@jsenv/eslint-config-relax": "workspace:*",
98
99
  "@jsenv/file-size-impact": "workspace:*",
@@ -47,15 +47,17 @@ const parseSecChUaHeader = (secChUa) => {
47
47
  brand = brands[0];
48
48
  }
49
49
  const runtimeName = brandNameToRuntimeName(brand.name);
50
- const runtimeVersion = brand.version;
50
+ const runtimeVersion = `${brand.version}.0.0`;
51
51
  return { runtimeName, runtimeVersion };
52
52
  };
53
-
54
53
  const brandNameToRuntimeName = (brandName) => {
55
54
  const lower = brandName.toLowerCase();
56
55
  if (lower === "google chrome") {
57
56
  return "chrome";
58
57
  }
58
+ if (lower === "headlesschrome") {
59
+ return "chrome";
60
+ }
59
61
  if (lower === "microsoft edge") {
60
62
  return "edge";
61
63
  }
@@ -68,7 +70,8 @@ const brandNameToRuntimeName = (brandName) => {
68
70
  if (lower === "chromium") {
69
71
  return "chrome";
70
72
  }
71
- return lower;
73
+ // other Chromium-based browsers share Chrome's compatibility
74
+ return "chrome";
72
75
  };
73
76
 
74
77
  const parseUserAgentHeader = (userAgent) => {
@@ -112,7 +115,9 @@ const parseUserAgentHeader = (userAgent) => {
112
115
  return { runtimeName: "firefox", runtimeVersion: `${major}.${minor}.0` };
113
116
  }
114
117
  // generic Chromium-based fallback (should normally be handled by sec-ch-ua)
115
- const chromeMatch = userAgent.match(/\bChrome\/(\d+)\.(\d+)\b/);
118
+ const chromeMatch = userAgent.match(
119
+ /(?:HeadlessChrome|Chrome)\/(\d+)\.(\d+)\b/,
120
+ );
116
121
  if (chromeMatch) {
117
122
  const major = chromeMatch[1];
118
123
  const minor = chromeMatch[2] || "0";
@@ -90,6 +90,7 @@ export const startDevServer = async ({
90
90
  cacheControl = true,
91
91
  ribbon = true,
92
92
  dropToOpen = true,
93
+ customElementsRedefine = true,
93
94
  // toolbar = false,
94
95
  onKitchenCreated = () => {},
95
96
  spa,
@@ -206,6 +207,7 @@ export const startDevServer = async ({
206
207
  cacheControl,
207
208
  ribbon,
208
209
  dropToOpen,
210
+ customElementsRedefine,
209
211
  }),
210
212
  ]);
211
213
 
@@ -0,0 +1,34 @@
1
+ import { injectJsenvScript, parseHtml, stringifyHtmlAst } from "@jsenv/ast";
2
+
3
+ export const jsenvPluginCustomElementsRedefine = () => {
4
+ const customElementsRedefineClientFileUrl = import.meta
5
+ .resolve("@jsenv/custom-elements-redefine/src/main.js");
6
+
7
+ return {
8
+ name: "jsenv:custom_elements_redefine",
9
+ appliesDuring: "dev",
10
+ transformUrlContent: {
11
+ html: (urlInfo) => {
12
+ const htmlAst = parseHtml({ html: urlInfo.content, url: urlInfo.url });
13
+ const reference = urlInfo.dependencies.inject({
14
+ type: "script",
15
+ subtype: "js_module",
16
+ expectedType: "js_module",
17
+ specifier: customElementsRedefineClientFileUrl,
18
+ });
19
+ injectJsenvScript(htmlAst, {
20
+ type: "module",
21
+ src: reference.generatedSpecifier,
22
+ initCall: {
23
+ callee: "allowCustomElementsRedefine",
24
+ },
25
+ pluginName: "jsenv:custom_elements_redefine",
26
+ });
27
+ const htmlModified = stringifyHtmlAst(htmlAst);
28
+ return {
29
+ content: htmlModified,
30
+ };
31
+ },
32
+ },
33
+ };
34
+ };
@@ -21,6 +21,7 @@ import { jsenvPluginImportMetaCss } from "./import_meta_css/jsenv_plugin_import_
21
21
  import { jsenvPluginImportMetaHot } from "./import_meta_hot/jsenv_plugin_import_meta_hot.js";
22
22
  import { jsenvPluginAutoreload } from "./autoreload/jsenv_plugin_autoreload.js";
23
23
  import { jsenvPluginCacheControl } from "./cache_control/jsenv_plugin_cache_control.js";
24
+ import { jsenvPluginCustomElementsRedefine } from "./custom_elements_redefine/jsenv_plugin_custom_elements_redefine.js";
24
25
  // other
25
26
  import { jsenvPluginRibbon } from "./ribbon/jsenv_plugin_ribbon.js";
26
27
  import { jsenvPluginDropToOpen } from "./drop_to_open/jsenv_plugin_drop_to_open.js";
@@ -58,6 +59,7 @@ export const getCorePlugins = ({
58
59
  scenarioPlaceholders = true,
59
60
  ribbon = true,
60
61
  dropToOpen = true,
62
+ customElementsRedefine = true,
61
63
  packageSideEffects = false,
62
64
  } = {}) => {
63
65
  if (cacheControl === true) {
@@ -154,6 +156,7 @@ export const getCorePlugins = ({
154
156
  ...(cacheControl ? [jsenvPluginCacheControl(cacheControl)] : []),
155
157
  ...(ribbon ? [jsenvPluginRibbon({ rootDirectoryUrl, ...ribbon })] : []),
156
158
  ...(dropToOpen ? [jsenvPluginDropToOpen()] : []),
159
+ ...(customElementsRedefine ? [jsenvPluginCustomElementsRedefine()] : []),
157
160
  jsenvPluginCleanHTML(),
158
161
  ...(packageSideEffects
159
162
  ? [jsenvPluginPackageSideEffects({ packageDirectory })]