@jsenv/core 25.0.0-alpha.0 → 25.0.0-alpha.1

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 (57) hide show
  1. package/dist/browser_runtime/asset-manifest.json +2 -2
  2. package/dist/browser_runtime/{browser_runtime_a8097085.js → browser_runtime_91c5a3b8.js} +137 -26
  3. package/dist/browser_runtime/browser_runtime_91c5a3b8.js.map +1089 -0
  4. package/dist/build_manifest.js +5 -5
  5. package/dist/compile_proxy/asset-manifest.json +2 -2
  6. package/dist/compile_proxy/{compile_proxy_e16d7de8.html → compile_proxy_7ad5faa6.html} +119 -26
  7. package/dist/compile_proxy/{compile_proxy_e3b0c442_9e168143.js.map → compile_proxy_e3b0c442_809f35f7.js.map} +6 -6
  8. package/dist/redirector/asset-manifest.json +2 -2
  9. package/dist/redirector/{redirector_e3b0c442_3a34a156.js.map → redirector_e3b0c442_e391410e.js.map} +6 -6
  10. package/dist/redirector/{redirector_2e0c8abe.html → redirector_eb92e8a7.html} +119 -26
  11. package/dist/toolbar/asset-manifest.json +11 -11
  12. package/dist/toolbar/{toolbar.main_a5ef2c60.js.map → toolbar.main2_6c1b3d82.js.map} +8 -8
  13. package/dist/toolbar/{toolbar_412abb83.html → toolbar_04ba410c.html} +127 -32
  14. package/dist/toolbar_injector/asset-manifest.json +2 -2
  15. package/dist/toolbar_injector/{toolbar_injector_4f9c19e5.js → toolbar_injector_4a48bc53.js} +2 -2
  16. package/dist/toolbar_injector/{toolbar_injector_4f9c19e5.js.map → toolbar_injector_4a48bc53.js.map} +2 -2
  17. package/package.json +5 -4
  18. package/readme.md +22 -89
  19. package/src/buildProject.js +28 -15
  20. package/src/dev_server.js +8 -2
  21. package/src/execute.js +7 -1
  22. package/src/executeTestPlan.js +6 -0
  23. package/src/internal/browser_feature_detection/browser_feature_detection.js +18 -25
  24. package/src/internal/browser_runtime/browser_runtime.js +2 -2
  25. package/src/internal/browser_runtime/createBrowserRuntime.js +1 -1
  26. package/src/internal/browser_runtime/displayErrorInDocument.js +2 -0
  27. package/src/internal/browser_runtime/displayErrorNotification.js +1 -1
  28. package/src/internal/building/buildUsingRollup.js +3 -8
  29. package/src/internal/building/ressource_builder.js +35 -42
  30. package/src/internal/building/rollup_plugin_jsenv.js +195 -186
  31. package/src/internal/building/url_versioning.js +36 -42
  32. package/src/internal/compiling/createCompiledFileService.js +28 -40
  33. package/src/internal/compiling/html_source_file_service.js +66 -51
  34. package/src/internal/compiling/js-compilation-service/babel_plugin_systemjs_prepend.js +23 -0
  35. package/src/internal/compiling/js-compilation-service/jsenvTransform.js +16 -12
  36. package/src/internal/compiling/js-compilation-service/transformJs.js +2 -0
  37. package/src/internal/compiling/jsenvCompilerForHtml.js +43 -44
  38. package/src/internal/compiling/jsenvCompilerForImportmap.js +15 -76
  39. package/src/internal/compiling/jsenvCompilerForJavaScript.js +9 -0
  40. package/src/internal/compiling/startCompileServer.js +29 -5
  41. package/src/internal/dev_server/toolbar/compilation/toolbar.compilation.js +9 -9
  42. package/src/internal/executing/executePlan.js +6 -0
  43. package/src/internal/generateGroupMap/{jsenvBabelPluginCompatMap.js → babel_plugins_compatibility.js} +0 -0
  44. package/src/internal/generateGroupMap/{featuresCompatMap.js → features_compatibility.js} +9 -1
  45. package/src/internal/generateGroupMap/generateGroupMap.js +6 -35
  46. package/src/internal/generateGroupMap/one_runtime_compat.js +9 -12
  47. package/src/internal/generateGroupMap/runtime_compat.js +10 -15
  48. package/src/internal/generateGroupMap/runtime_compat_composition.js +2 -2
  49. package/src/internal/generateGroupMap/shake_babel_plugin_map.js +21 -0
  50. package/src/internal/import-resolution/importmap_default.js +52 -0
  51. package/src/internal/node_feature_detection/node_feature_detection.js +25 -19
  52. package/src/internal/runtime/s.js +101 -6
  53. package/src/internal/unevalException.js +1 -1
  54. package/src/jsenvServiceWorkerFinalizer.js +3 -1
  55. package/dist/browser_runtime/browser_runtime_a8097085.js.map +0 -1067
  56. package/src/internal/generateGroupMap/jsenvPluginCompatMap.js +0 -1
  57. package/src/internal/import-resolution/importmap-default.js +0 -34
@@ -1290,6 +1290,8 @@ html[data-toolbar-visible] #toolbar-trigger {
1290
1290
 
1291
1291
  var importMapPromise = Promise.resolve();
1292
1292
  var importMap = { imports: {}, scopes: {}, depcache: {}, integrity: {} };
1293
+ systemJSPrototype.importMap = importMap;
1294
+ systemJSPrototype.baseUrl = baseUrl;
1293
1295
 
1294
1296
  // Scripts are processed immediately, on the first System.import, and on DOMReady.
1295
1297
  // Import map scripts are processed only once (by being marked) and in order for each phase.
@@ -1345,6 +1347,7 @@ html[data-toolbar-visible] #toolbar-trigger {
1345
1347
  return fetchPromise;
1346
1348
  }).then(function (text) {
1347
1349
  extendImportMap(importMap, text, script.src || baseUrl);
1350
+ return importMap
1348
1351
  });
1349
1352
  }
1350
1353
  });
@@ -1359,6 +1362,7 @@ html[data-toolbar-visible] #toolbar-trigger {
1359
1362
  }
1360
1363
  resolveAndComposeImportMap(newMap, newMapUrl, importMap);
1361
1364
  }
1365
+ System.extendImportMap = extendImportMap
1362
1366
 
1363
1367
  /*
1364
1368
  * Script instantiation loading
@@ -1512,23 +1516,33 @@ html[data-toolbar-visible] #toolbar-trigger {
1512
1516
  * Supports loading System.register in workers
1513
1517
  */
1514
1518
 
1515
- if (hasSelf && typeof importScripts === 'function')
1519
+ if (hasSelf && typeof importScripts === 'function') {
1516
1520
  systemJSPrototype.instantiate = function (url) {
1517
1521
  var loader = this;
1518
- return Promise.resolve().then(function () {
1519
- importScripts(url);
1522
+ return self.fetch(url, {
1523
+ credentials: 'same-origin',
1524
+ }).then(function (response) {
1525
+ if (!response.ok) {
1526
+ throw Error(errMsg(7, [response.status, response.statusText, url].join(', ') ));
1527
+ }
1528
+ return response.text()
1529
+ }).then(function (source) {
1530
+ if (source.indexOf('//# sourceURL=') < 0) source += '\n//# sourceURL=' + url;
1531
+ (0, eval)(source);
1520
1532
  return loader.getRegister(url);
1521
- });
1533
+ })
1522
1534
  };
1535
+ }
1523
1536
 
1524
1537
  }());
1525
1538
 
1526
1539
  (function(){
1527
1540
  var envGlobal = typeof self !== 'undefined' ? self : global;
1528
1541
  var System = envGlobal.System;
1529
- var register = System.register;
1530
- var registerRegistry = Object.create(null)
1531
1542
 
1543
+ var registerRegistry = Object.create(null)
1544
+ var register = System.register;
1545
+ System.registerRegistry = registerRegistry;
1532
1546
  System.register = function (name, deps, declare) {
1533
1547
  if (typeof name !== 'string') return register.apply(this, arguments);
1534
1548
  var define = [deps, declare];
@@ -1558,6 +1572,87 @@ html[data-toolbar-visible] #toolbar-trigger {
1558
1572
  var result = registerRegistry[url] || register;
1559
1573
  return result;
1560
1574
  };
1575
+ }());
1576
+
1577
+ (function () {
1578
+ // worker or service worker
1579
+ if (typeof WorkerGlobalScope === 'function' && self instanceof WorkerGlobalScope) {
1580
+ var importMapFromParentPromise = new Promise((resolve) => {
1581
+ var importmapMessageCallback = function (e) {
1582
+ if (e.data === "__importmap_init__") {
1583
+ self.removeEventListener("message", importmapMessageCallback)
1584
+ e.ports[0].onmessage = (message) => {
1585
+ resolve(message.data)
1586
+ }
1587
+ e.ports[0].postMessage('__importmap_request__')
1588
+ }
1589
+ };
1590
+ self.addEventListener("message", importmapMessageCallback)
1591
+ })
1592
+ // var prepareImport = System.prepareImport
1593
+ System.prepareImport = function () {
1594
+ return importMapFromParentPromise.then(function (importmap) {
1595
+ System.extendImportMap(System.importMap, JSON.stringify(importmap), System.baseUrl)
1596
+ })
1597
+ }
1598
+
1599
+ // auto import first register
1600
+ var messageEvents = []
1601
+ var messageCallback = (event) => {
1602
+ messageEvents.push(event)
1603
+ }
1604
+ self.addEventListener('message', messageCallback)
1605
+ var register = System.register;
1606
+ System.register = function(deps, declare) {
1607
+ System.register = register;
1608
+ System.registerRegistry[self.location.href] = [deps, declare];
1609
+ System.import(self.location.href).then(() => {
1610
+ self.removeEventListener('message', messageCallback)
1611
+ messageEvents.forEach((messageEvent) => {
1612
+ self.dispatchEvent(messageEvent)
1613
+ })
1614
+ messageEvents = null
1615
+ })
1616
+ }
1617
+ }
1618
+ else if (typeof window === 'object') {
1619
+ var WorkerConstructor = window.Worker;
1620
+ if (typeof WorkerConstructor === 'function') {
1621
+ window.Worker = function (url, options) {
1622
+ var worker = new WorkerConstructor(url, options);
1623
+ var importmapChannel = new MessageChannel();
1624
+ importmapChannel.port1.onmessage = function (message) {
1625
+ System.prepareImport().then(function (importmap) {
1626
+ message.target.postMessage(importmap);
1627
+ });
1628
+ }
1629
+ worker.postMessage('__importmap_init__', [importmapChannel.port2]);
1630
+ return worker
1631
+ }
1632
+ }
1633
+
1634
+ var serviceWorker = navigator.serviceWorker;
1635
+ if (serviceWorker) {
1636
+ var register = serviceWorker.register;
1637
+ serviceWorker.register = function(url, options) {
1638
+ var registrationPromise = register.call(this, url, options);
1639
+ registrationPromise.then(function(registration) {
1640
+ var installing = registration.installing;
1641
+ var waiting = registration.waiting;
1642
+ var active = registration.active;
1643
+ var worker = installing || waiting || active;
1644
+ var importmapChannel = new MessageChannel();
1645
+ importmapChannel.port1.onmessage = function (message) {
1646
+ System.prepareImport().then(function (importmap) {
1647
+ message.target.postMessage(importmap)
1648
+ });
1649
+ }
1650
+ worker.postMessage('__importmap_init__', [importmapChannel.port2]);
1651
+ })
1652
+ return registrationPromise
1653
+ }
1654
+ }
1655
+ }
1561
1656
  }());</script>
1562
1657
  </head>
1563
1658
  <body>
@@ -4125,16 +4220,16 @@ html[data-toolbar-visible] #toolbar-trigger {
4125
4220
  failFastOnFeatureDetection: failFastOnFeatureDetection,
4126
4221
  inlineImportMapIntoHTML: inlineImportMapIntoHTML
4127
4222
  }), function () {
4128
- return _await(pluginRequiredNamesFromGroupInfo(groupInfo, {
4223
+ return _await(adjustMissingFeatureNames(groupInfo, {
4129
4224
  featuresReport: featuresReport,
4130
4225
  coverageHandledFromOutside: coverageHandledFromOutside
4131
- }), function (pluginRequiredNameArray) {
4132
- var canAvoidCompilation = customCompilerPatterns.length === 0 && pluginRequiredNameArray.length === 0 && featuresReport.importmap && featuresReport.dynamicImport && featuresReport.topLevelAwait;
4226
+ }), function (missingFeatureNames) {
4227
+ var canAvoidCompilation = customCompilerPatterns.length === 0 && missingFeatureNames.length === 0 && featuresReport.importmap && featuresReport.dynamicImport && featuresReport.topLevelAwait;
4133
4228
  return {
4134
4229
  canAvoidCompilation: canAvoidCompilation,
4135
4230
  featuresReport: featuresReport,
4136
4231
  customCompilerPatterns: customCompilerPatterns,
4137
- pluginRequiredNameArray: pluginRequiredNameArray,
4232
+ missingFeatureNames: missingFeatureNames,
4138
4233
  inlineImportMapIntoHTML: inlineImportMapIntoHTML,
4139
4234
  outDirectoryRelativeUrl: outDirectoryRelativeUrl,
4140
4235
  compileId: compileId,
@@ -4145,17 +4240,17 @@ html[data-toolbar-visible] #toolbar-trigger {
4145
4240
  });
4146
4241
  });
4147
4242
 
4148
- var pluginRequiredNamesFromGroupInfo = _async$1(function (groupInfo, _ref3) {
4243
+ var adjustMissingFeatureNames = _async$1(function (groupInfo, _ref3) {
4149
4244
  var featuresReport = _ref3.featuresReport,
4150
4245
  coverageHandledFromOutside = _ref3.coverageHandledFromOutside;
4151
- var pluginRequiredNameArray = groupInfo.pluginRequiredNameArray;
4152
- var requiredPluginNames = pluginRequiredNameArray.slice();
4246
+ var missingFeatureNames = groupInfo.missingFeatureNames;
4247
+ var missingFeatureNamesCopy = missingFeatureNames.slice();
4153
4248
 
4154
- var markPluginAsSupported = function markPluginAsSupported(name) {
4155
- var index = requiredPluginNames.indexOf(name);
4249
+ var markAsSupported = function markAsSupported(name) {
4250
+ var index = missingFeatureNamesCopy.indexOf(name);
4156
4251
 
4157
4252
  if (index > -1) {
4158
- requiredPluginNames.splice(index, 1);
4253
+ missingFeatureNamesCopy.splice(index, 1);
4159
4254
  }
4160
4255
  }; // When instrumentation CAN be handed by playwright
4161
4256
  // https://playwright.dev/docs/api/class-chromiumcoverage#chromiumcoveragestartjscoverageoptions
@@ -4163,30 +4258,30 @@ html[data-toolbar-visible] #toolbar-trigger {
4163
4258
 
4164
4259
 
4165
4260
  if (coverageHandledFromOutside) {
4166
- markPluginAsSupported("transform-instrument");
4261
+ markAsSupported("transform-instrument");
4167
4262
  }
4168
4263
 
4169
4264
  return _invoke(function () {
4170
- if (pluginRequiredNameArray.includes("transform-import-assertions")) {
4265
+ if (missingFeatureNames.includes("transform-import-assertions")) {
4171
4266
  return _call$1(supportsJsonImportAssertions, function (jsonImportAssertions) {
4172
4267
  featuresReport.jsonImportAssertions = jsonImportAssertions;
4173
4268
  return _call$1(supportsCssImportAssertions, function (cssImportAssertions) {
4174
4269
  featuresReport.cssImportAssertions = cssImportAssertions;
4175
4270
 
4176
4271
  if (jsonImportAssertions && cssImportAssertions) {
4177
- markPluginAsSupported("transform-import-assertions");
4272
+ markAsSupported("transform-import-assertions");
4178
4273
  }
4179
4274
  });
4180
4275
  });
4181
4276
  }
4182
4277
  }, function () {
4183
- if (pluginRequiredNameArray.includes("new-stylesheet-as-jsenv-import")) {
4278
+ if (missingFeatureNames.includes("new-stylesheet-as-jsenv-import")) {
4184
4279
  var newStylesheet = supportsNewStylesheet();
4185
4280
  featuresReport.newStylesheet = newStylesheet;
4186
- markPluginAsSupported("new-stylesheet-as-jsenv-import");
4281
+ markAsSupported("new-stylesheet-as-jsenv-import");
4187
4282
  }
4188
4283
 
4189
- return requiredPluginNames;
4284
+ return missingFeatureNamesCopy;
4190
4285
  });
4191
4286
  });
4192
4287
 
@@ -4361,7 +4456,7 @@ html[data-toolbar-visible] #toolbar-trigger {
4361
4456
  var canAvoidCompilation = _ref2.canAvoidCompilation,
4362
4457
  featuresReport = _ref2.featuresReport,
4363
4458
  customCompilerPatterns = _ref2.customCompilerPatterns,
4364
- pluginRequiredNameArray = _ref2.pluginRequiredNameArray,
4459
+ missingFeatureNames = _ref2.missingFeatureNames,
4365
4460
  inlineImportMapIntoHTML = _ref2.inlineImportMapIntoHTML,
4366
4461
  outDirectoryRelativeUrl = _ref2.outDirectoryRelativeUrl,
4367
4462
  compileId = _ref2.compileId;
@@ -4377,7 +4472,7 @@ html[data-toolbar-visible] #toolbar-trigger {
4377
4472
  missingOnly: true,
4378
4473
  featuresReport: featuresReport,
4379
4474
  customCompilerPatterns: customCompilerPatterns,
4380
- pluginRequiredNameArray: pluginRequiredNameArray,
4475
+ missingFeatureNames: missingFeatureNames,
4381
4476
  inlineImportMapIntoHTML: inlineImportMapIntoHTML
4382
4477
  })));
4383
4478
  };
@@ -4387,7 +4482,7 @@ html[data-toolbar-visible] #toolbar-trigger {
4387
4482
  window.alert("Source files (except html) can be executed directly in this browser because: ".concat(getBrowserSupportMessage({
4388
4483
  featuresReport: featuresReport,
4389
4484
  customCompilerPatterns: customCompilerPatterns,
4390
- pluginRequiredNameArray: pluginRequiredNameArray,
4485
+ missingFeatureNames: missingFeatureNames,
4391
4486
  inlineImportMapIntoHTML: inlineImportMapIntoHTML
4392
4487
  })));
4393
4488
  };
@@ -4397,7 +4492,7 @@ html[data-toolbar-visible] #toolbar-trigger {
4397
4492
  window.alert("Source files can be executed directly in this browser because: ".concat(getBrowserSupportMessage({
4398
4493
  featuresReport: featuresReport,
4399
4494
  customCompilerPatterns: customCompilerPatterns,
4400
- pluginRequiredNameArray: pluginRequiredNameArray,
4495
+ missingFeatureNames: missingFeatureNames,
4401
4496
  inlineImportMapIntoHTML: inlineImportMapIntoHTML
4402
4497
  })));
4403
4498
  };
@@ -4435,7 +4530,7 @@ html[data-toolbar-visible] #toolbar-trigger {
4435
4530
  var missingOnly = _ref3.missingOnly,
4436
4531
  featuresReport = _ref3.featuresReport,
4437
4532
  customCompilerPatterns = _ref3.customCompilerPatterns,
4438
- pluginRequiredNameArray = _ref3.pluginRequiredNameArray,
4533
+ missingFeatureNames = _ref3.missingFeatureNames,
4439
4534
  inlineImportMapIntoHTML = _ref3.inlineImportMapIntoHTML;
4440
4535
  var parts = [];
4441
4536
 
@@ -4467,14 +4562,14 @@ html[data-toolbar-visible] #toolbar-trigger {
4467
4562
  parts.push("top level await is not supported");
4468
4563
  }
4469
4564
 
4470
- var pluginRequiredCount = pluginRequiredNameArray.length;
4565
+ var missingFeatureCount = missingFeatureNames.length;
4471
4566
 
4472
- if (pluginRequiredCount === 0) {
4567
+ if (missingFeatureCount === 0) {
4473
4568
  if (!missingOnly) {
4474
- parts.push("all plugins are natively supported");
4569
+ parts.push("all features are natively supported");
4475
4570
  }
4476
4571
  } else {
4477
- parts.push("".concat(pluginRequiredCount, " plugins are mandatory: ").concat(pluginRequiredNameArray));
4572
+ parts.push("".concat(missingFeatureCount, " features are missing: ").concat(missingFeatureNames));
4478
4573
  }
4479
4574
 
4480
4575
  var customCompilerCount = customCompilerPatterns.length;
@@ -4915,7 +5010,7 @@ html[data-toolbar-visible] #toolbar-trigger {
4915
5010
  };
4916
5011
  });
4917
5012
 
4918
- //# sourceMappingURL=toolbar.main_a5ef2c60.js.map</script>
5013
+ //# sourceMappingURL=toolbar.main2_6c1b3d82.js.map</script>
4919
5014
 
4920
5015
 
4921
5016
  </body></html>
@@ -1,5 +1,5 @@
1
1
  {
2
2
  ".jsenv/build/best/src/internal/dev_server/toolbar/jsenv-logo.svg": "assets/jsenv-logo_188b9ca6.svg",
3
- "toolbar_injector.js": "toolbar_injector_4f9c19e5.js",
4
- "toolbar_injector.js.map": "toolbar_injector_4f9c19e5.js.map"
3
+ "toolbar_injector.js": "toolbar_injector_4a48bc53.js",
4
+ "toolbar_injector.js.map": "toolbar_injector_4a48bc53.js.map"
5
5
  }
@@ -746,7 +746,7 @@
746
746
  return then ? value.then(then) : value;
747
747
  }
748
748
 
749
- var TOOLBAR_BUILD_RELATIVE_URL = "dist/toolbar/toolbar_412abb83.html";
749
+ var TOOLBAR_BUILD_RELATIVE_URL = "dist/toolbar/toolbar_04ba410c.html";
750
750
 
751
751
  function _call(body, then, direct) {
752
752
  if (direct) {
@@ -970,4 +970,4 @@
970
970
 
971
971
  })();
972
972
 
973
- //# sourceMappingURL=toolbar_injector_4f9c19e5.js.map
973
+ //# sourceMappingURL=toolbar_injector_4a48bc53.js.map
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "version": 3,
3
- "file": "toolbar_injector_4f9c19e5.js",
3
+ "file": "toolbar_injector_4a48bc53.js",
4
4
  "sources": [
5
5
  "../../helpers/babel/typeof/typeof.js",
6
6
  "../../helpers/babel/defineProperty/defineProperty.js",
@@ -25,7 +25,7 @@
25
25
  "import { fetchUrl } from \"./fetch-browser.js\"\n\nexport const fetchJson = async (url, options = {}) => {\n const response = await fetchUrl(url, options)\n const object = await response.json()\n return object\n}\n",
26
26
  "import { fetchJson } from \"../../browser_utils/fetchJson.js\"\n\nexport const fetchExploringJson = async ({ signal } = {}) => {\n try {\n const exploringInfo = await fetchJson(\"/.jsenv/exploring.json\", {\n signal,\n })\n return exploringInfo\n } catch (e) {\n if (signal && signal.aborted && e.name === \"AbortError\") {\n throw e\n }\n throw new Error(\n `Cannot communicate with exploring server due to a network error\n--- error stack ---\n${e.stack}`,\n )\n }\n}\n",
27
27
  "export const updateIframeOverflowOnParentWindow = () => {\n if (!window.parent) {\n // can happen while parent iframe reloads\n return\n }\n\n const aTooltipIsOpened =\n document.querySelector(\"[data-tooltip-visible]\") ||\n document.querySelector(\"[data-tooltip-auto-visible]\")\n const settingsAreOpened = document.querySelector(\"#settings[data-active]\")\n\n if (aTooltipIsOpened || settingsAreOpened) {\n enableIframeOverflowOnParentWindow()\n } else {\n disableIframeOverflowOnParentWindow()\n }\n}\n\nlet iframeOverflowEnabled = false\nconst enableIframeOverflowOnParentWindow = () => {\n if (iframeOverflowEnabled) return\n iframeOverflowEnabled = true\n\n const iframe = getToolbarIframe()\n const transitionDuration = iframe.style.transitionDuration\n setStyles(iframe, { \"height\": \"100%\", \"transition-duration\": \"0ms\" })\n if (transitionDuration) {\n setTimeout(() => {\n setStyles(iframe, { \"transition-duration\": transitionDuration })\n })\n }\n}\n\nconst disableIframeOverflowOnParentWindow = () => {\n if (!iframeOverflowEnabled) return\n iframeOverflowEnabled = false\n\n const iframe = getToolbarIframe()\n const transitionDuration = iframe.style.transitionDuration\n setStyles(iframe, { \"height\": \"40px\", \"transition-duration\": \"0ms\" })\n if (transitionDuration) {\n setTimeout(() => {\n setStyles(iframe, { \"transition-duration\": transitionDuration })\n })\n }\n}\n\nexport const getToolbarIframe = () => {\n const iframes = Array.from(window.parent.document.querySelectorAll(\"iframe\"))\n return iframes.find((iframe) => iframe.contentWindow === window)\n}\n\nexport const forceHideElement = (element) => {\n element.setAttribute(\"data-force-hide\", \"\")\n}\n\nexport const removeForceHideElement = (element) => {\n element.removeAttribute(\"data-force-hide\")\n}\n\nexport const setStyles = (element, styles) => {\n const elementStyle = element.style\n const restoreStyles = Object.keys(styles).map((styleName) => {\n let restore\n if (styleName in elementStyle) {\n const currentStyle = elementStyle[styleName]\n restore = () => {\n elementStyle[styleName] = currentStyle\n }\n } else {\n restore = () => {\n delete elementStyle[styleName]\n }\n }\n\n elementStyle[styleName] = styles[styleName]\n\n return restore\n })\n return () => {\n restoreStyles.forEach((restore) => restore())\n }\n}\n\nexport const setAttributes = (element, attributes) => {\n Object.keys(attributes).forEach((name) => {\n element.setAttribute(name, attributes[name])\n })\n}\n\nexport const getDocumentScroll = () => {\n return {\n x: document.documentElement.scrollLeft,\n y: document.documentElement.scrollTop,\n }\n}\n\nexport const toolbarSectionIsActive = (element) => {\n return element.hasAttribute(\"data-active\")\n}\n\nexport const activateToolbarSection = (element) => {\n element.setAttribute(\"data-active\", \"\")\n}\n\nexport const deactivateToolbarSection = (element) => {\n element.removeAttribute(\"data-active\")\n}\n",
28
- "import { fetchExploringJson } from \"@jsenv/core/src/internal/dev_server/exploring/fetchExploringJson.js\"\nimport { setAttributes, setStyles } from \"./util/dom.js\"\n\n// eslint-disable-next-line no-undef\nconst TOOLBAR_BUILD_RELATIVE_URL = \"dist/toolbar/toolbar_412abb83.html\"\nconst jsenvLogoSvgUrl = new URL(\"./jsenv-logo.svg\", import.meta.url)\n\nconst injectToolbar = async () => {\n await new Promise((resolve) => {\n if (window.requestIdleCallback) {\n window.requestIdleCallback(resolve)\n } else {\n window.requestAnimationFrame(resolve)\n }\n })\n\n const { jsenvDirectoryRelativeUrl } = await fetchExploringJson()\n const jsenvDirectoryServerUrl = resolveUrl(\n jsenvDirectoryRelativeUrl,\n document.location.origin,\n )\n\n const placeholder = getToolbarPlaceholder()\n\n const iframe = document.createElement(\"iframe\")\n setAttributes(iframe, {\n tabindex: -1,\n // sandbox: \"allow-forms allow-modals allow-pointer-lock allow-popups allow-presentation allow-same-origin allow-scripts allow-top-navigation-by-user-activation\",\n // allow: \"accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; microphone; midi; payment; vr\",\n allowtransparency: true,\n })\n setStyles(iframe, {\n \"position\": \"fixed\",\n \"zIndex\": 1000,\n \"bottom\": 0,\n \"left\": 0,\n \"width\": \"100%\",\n \"height\": 0,\n /* ensure toolbar children are not focusable when hidden */\n \"visibility\": \"hidden\",\n \"transition-duration\": \"300ms\",\n \"transition-property\": \"height, visibility\",\n \"border\": \"none\",\n })\n const iframeLoadedPromise = iframeToLoadedPromise(iframe)\n const jsenvToolbarHtmlServerUrl = resolveUrl(\n TOOLBAR_BUILD_RELATIVE_URL,\n jsenvDirectoryServerUrl,\n )\n // set iframe src BEFORE putting it into the DOM (prevent firefox adding an history entry)\n iframe.setAttribute(\"src\", jsenvToolbarHtmlServerUrl)\n placeholder.parentNode.replaceChild(iframe, placeholder)\n\n await iframeLoadedPromise\n iframe.removeAttribute(\"tabindex\")\n\n const div = document.createElement(\"div\")\n div.innerHTML = `\n<div id=\"jsenv-toolbar-trigger\">\n <svg id=\"jsenv-toolbar-trigger-icon\">\n <use xlink:href=\"${jsenvLogoSvgUrl}#jsenv-logo\"></use>\n </svg>\n <style>\n #jsenv-toolbar-trigger {\n display: block;\n overflow: hidden;\n position: fixed;\n z-index: 1000;\n bottom: -32px;\n right: 20px;\n height: 40px;\n width: 40px;\n padding: 0;\n margin: 0;\n border-radius: 5px 5px 0 0;\n border: 1px solid rgba(0, 0, 0, 0.33);\n border-bottom: none;\n box-shadow: 0px 0px 6px 2px rgba(0, 0, 0, 0.46);\n background: transparent;\n text-align: center;\n transition: 600ms;\n }\n\n #jsenv-toolbar-trigger:hover {\n cursor: pointer;\n }\n\n #jsenv-toolbar-trigger[data-expanded] {\n bottom: 0;\n }\n\n #jsenv-toolbar-trigger-icon {\n width: 35px;\n height: 35px;\n opacity: 0;\n transition: 600ms;\n }\n\n #jsenv-toolbar-trigger[data-expanded] #jsenv-toolbar-trigger-icon {\n opacity: 1;\n }\n </style>\n</div>`\n const toolbarTrigger = div.firstElementChild\n iframe.parentNode.appendChild(toolbarTrigger)\n\n let timer\n toolbarTrigger.onmouseenter = () => {\n toolbarTrigger.setAttribute(\"data-animate\", \"\")\n timer = setTimeout(expandToolbarTrigger, 500)\n }\n toolbarTrigger.onmouseleave = () => {\n clearTimeout(timer)\n collapseToolbarTrigger()\n }\n toolbarTrigger.onfocus = () => {\n toolbarTrigger.removeAttribute(\"data-animate\")\n expandToolbarTrigger()\n }\n toolbarTrigger.onblur = () => {\n toolbarTrigger.removeAttribute(\"data-animate\")\n clearTimeout(timer)\n collapseToolbarTrigger()\n }\n toolbarTrigger.onclick = () => {\n sendCommandToToolbar(iframe, \"showToolbar\")\n }\n\n const showToolbarTrigger = () => {\n toolbarTrigger.style.display = \"block\"\n }\n\n const hideToolbarTrigger = () => {\n toolbarTrigger.style.display = \"none\"\n }\n\n const expandToolbarTrigger = () => {\n toolbarTrigger.setAttribute(\"data-expanded\", \"\")\n }\n\n const collapseToolbarTrigger = () => {\n toolbarTrigger.removeAttribute(\"data-expanded\", \"\")\n }\n\n hideToolbarTrigger()\n addToolbarEventCallback(iframe, \"toolbar-visibility-change\", (visible) => {\n if (visible) {\n hideToolbarTrigger()\n } else {\n showToolbarTrigger()\n }\n })\n addToolbarEventCallback(iframe, \"toolbar_ready\", () => {\n sendCommandToToolbar(iframe, \"renderToolbar\")\n })\n\n return iframe\n}\n\nconst addToolbarEventCallback = (iframe, eventName, callback) => {\n const messageEventCallback = (messageEvent) => {\n const { data } = messageEvent\n if (typeof data !== \"object\") {\n return\n }\n const { __jsenv__ } = data\n if (!__jsenv__) {\n return\n }\n if (__jsenv__.event !== eventName) {\n return\n }\n callback(__jsenv__.data)\n }\n\n window.addEventListener(\"message\", messageEventCallback, false)\n return () => {\n window.removeEventListener(\"message\", messageEventCallback, false)\n }\n}\n\nconst sendCommandToToolbar = (iframe, command, ...args) => {\n iframe.contentWindow.postMessage(\n {\n __jsenv__: {\n command,\n args,\n },\n },\n window.origin,\n )\n}\n\nconst getToolbarPlaceholder = () => {\n const placeholder = queryPlaceholder()\n if (placeholder) {\n if (document.body.contains(placeholder)) {\n return placeholder\n }\n // otherwise iframe would not be visible because in <head>\n console.warn(\n \"element with [data-jsenv-toolbar-placeholder] must be inside document.body\",\n )\n return createTooolbarPlaceholder()\n }\n return createTooolbarPlaceholder()\n}\n\nconst queryPlaceholder = () => {\n return document.querySelector(\"[data-jsenv-toolbar-placeholder]\")\n}\n\nconst createTooolbarPlaceholder = () => {\n const placeholder = document.createElement(\"span\")\n document.body.appendChild(placeholder)\n return placeholder\n}\n\nconst iframeToLoadedPromise = (iframe) => {\n return new Promise((resolve) => {\n const onload = () => {\n iframe.removeEventListener(\"load\", onload, true)\n resolve()\n }\n iframe.addEventListener(\"load\", onload, true)\n })\n}\n\nconst resolveUrl = (url, baseUrl) => String(new URL(url, baseUrl))\n\nif (document.readyState === \"complete\") {\n injectToolbar()\n} else {\n window.addEventListener(\"load\", injectToolbar)\n}\n"
28
+ "import { fetchExploringJson } from \"@jsenv/core/src/internal/dev_server/exploring/fetchExploringJson.js\"\nimport { setAttributes, setStyles } from \"./util/dom.js\"\n\n// eslint-disable-next-line no-undef\nconst TOOLBAR_BUILD_RELATIVE_URL = \"dist/toolbar/toolbar_04ba410c.html\"\nconst jsenvLogoSvgUrl = new URL(\"./jsenv-logo.svg\", import.meta.url)\n\nconst injectToolbar = async () => {\n await new Promise((resolve) => {\n if (window.requestIdleCallback) {\n window.requestIdleCallback(resolve)\n } else {\n window.requestAnimationFrame(resolve)\n }\n })\n\n const { jsenvDirectoryRelativeUrl } = await fetchExploringJson()\n const jsenvDirectoryServerUrl = resolveUrl(\n jsenvDirectoryRelativeUrl,\n document.location.origin,\n )\n\n const placeholder = getToolbarPlaceholder()\n\n const iframe = document.createElement(\"iframe\")\n setAttributes(iframe, {\n tabindex: -1,\n // sandbox: \"allow-forms allow-modals allow-pointer-lock allow-popups allow-presentation allow-same-origin allow-scripts allow-top-navigation-by-user-activation\",\n // allow: \"accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; microphone; midi; payment; vr\",\n allowtransparency: true,\n })\n setStyles(iframe, {\n \"position\": \"fixed\",\n \"zIndex\": 1000,\n \"bottom\": 0,\n \"left\": 0,\n \"width\": \"100%\",\n \"height\": 0,\n /* ensure toolbar children are not focusable when hidden */\n \"visibility\": \"hidden\",\n \"transition-duration\": \"300ms\",\n \"transition-property\": \"height, visibility\",\n \"border\": \"none\",\n })\n const iframeLoadedPromise = iframeToLoadedPromise(iframe)\n const jsenvToolbarHtmlServerUrl = resolveUrl(\n TOOLBAR_BUILD_RELATIVE_URL,\n jsenvDirectoryServerUrl,\n )\n // set iframe src BEFORE putting it into the DOM (prevent firefox adding an history entry)\n iframe.setAttribute(\"src\", jsenvToolbarHtmlServerUrl)\n placeholder.parentNode.replaceChild(iframe, placeholder)\n\n await iframeLoadedPromise\n iframe.removeAttribute(\"tabindex\")\n\n const div = document.createElement(\"div\")\n div.innerHTML = `\n<div id=\"jsenv-toolbar-trigger\">\n <svg id=\"jsenv-toolbar-trigger-icon\">\n <use xlink:href=\"${jsenvLogoSvgUrl}#jsenv-logo\"></use>\n </svg>\n <style>\n #jsenv-toolbar-trigger {\n display: block;\n overflow: hidden;\n position: fixed;\n z-index: 1000;\n bottom: -32px;\n right: 20px;\n height: 40px;\n width: 40px;\n padding: 0;\n margin: 0;\n border-radius: 5px 5px 0 0;\n border: 1px solid rgba(0, 0, 0, 0.33);\n border-bottom: none;\n box-shadow: 0px 0px 6px 2px rgba(0, 0, 0, 0.46);\n background: transparent;\n text-align: center;\n transition: 600ms;\n }\n\n #jsenv-toolbar-trigger:hover {\n cursor: pointer;\n }\n\n #jsenv-toolbar-trigger[data-expanded] {\n bottom: 0;\n }\n\n #jsenv-toolbar-trigger-icon {\n width: 35px;\n height: 35px;\n opacity: 0;\n transition: 600ms;\n }\n\n #jsenv-toolbar-trigger[data-expanded] #jsenv-toolbar-trigger-icon {\n opacity: 1;\n }\n </style>\n</div>`\n const toolbarTrigger = div.firstElementChild\n iframe.parentNode.appendChild(toolbarTrigger)\n\n let timer\n toolbarTrigger.onmouseenter = () => {\n toolbarTrigger.setAttribute(\"data-animate\", \"\")\n timer = setTimeout(expandToolbarTrigger, 500)\n }\n toolbarTrigger.onmouseleave = () => {\n clearTimeout(timer)\n collapseToolbarTrigger()\n }\n toolbarTrigger.onfocus = () => {\n toolbarTrigger.removeAttribute(\"data-animate\")\n expandToolbarTrigger()\n }\n toolbarTrigger.onblur = () => {\n toolbarTrigger.removeAttribute(\"data-animate\")\n clearTimeout(timer)\n collapseToolbarTrigger()\n }\n toolbarTrigger.onclick = () => {\n sendCommandToToolbar(iframe, \"showToolbar\")\n }\n\n const showToolbarTrigger = () => {\n toolbarTrigger.style.display = \"block\"\n }\n\n const hideToolbarTrigger = () => {\n toolbarTrigger.style.display = \"none\"\n }\n\n const expandToolbarTrigger = () => {\n toolbarTrigger.setAttribute(\"data-expanded\", \"\")\n }\n\n const collapseToolbarTrigger = () => {\n toolbarTrigger.removeAttribute(\"data-expanded\", \"\")\n }\n\n hideToolbarTrigger()\n addToolbarEventCallback(iframe, \"toolbar-visibility-change\", (visible) => {\n if (visible) {\n hideToolbarTrigger()\n } else {\n showToolbarTrigger()\n }\n })\n addToolbarEventCallback(iframe, \"toolbar_ready\", () => {\n sendCommandToToolbar(iframe, \"renderToolbar\")\n })\n\n return iframe\n}\n\nconst addToolbarEventCallback = (iframe, eventName, callback) => {\n const messageEventCallback = (messageEvent) => {\n const { data } = messageEvent\n if (typeof data !== \"object\") {\n return\n }\n const { __jsenv__ } = data\n if (!__jsenv__) {\n return\n }\n if (__jsenv__.event !== eventName) {\n return\n }\n callback(__jsenv__.data)\n }\n\n window.addEventListener(\"message\", messageEventCallback, false)\n return () => {\n window.removeEventListener(\"message\", messageEventCallback, false)\n }\n}\n\nconst sendCommandToToolbar = (iframe, command, ...args) => {\n iframe.contentWindow.postMessage(\n {\n __jsenv__: {\n command,\n args,\n },\n },\n window.origin,\n )\n}\n\nconst getToolbarPlaceholder = () => {\n const placeholder = queryPlaceholder()\n if (placeholder) {\n if (document.body.contains(placeholder)) {\n return placeholder\n }\n // otherwise iframe would not be visible because in <head>\n console.warn(\n \"element with [data-jsenv-toolbar-placeholder] must be inside document.body\",\n )\n return createTooolbarPlaceholder()\n }\n return createTooolbarPlaceholder()\n}\n\nconst queryPlaceholder = () => {\n return document.querySelector(\"[data-jsenv-toolbar-placeholder]\")\n}\n\nconst createTooolbarPlaceholder = () => {\n const placeholder = document.createElement(\"span\")\n document.body.appendChild(placeholder)\n return placeholder\n}\n\nconst iframeToLoadedPromise = (iframe) => {\n return new Promise((resolve) => {\n const onload = () => {\n iframe.removeEventListener(\"load\", onload, true)\n resolve()\n }\n iframe.addEventListener(\"load\", onload, true)\n })\n}\n\nconst resolveUrl = (url, baseUrl) => String(new URL(url, baseUrl))\n\nif (document.readyState === \"complete\") {\n injectToolbar()\n} else {\n window.addEventListener(\"load\", injectToolbar)\n}\n"
29
29
  ],
30
30
  "names": [
31
31
  "nativeTypeOf",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jsenv/core",
3
- "version": "25.0.0-alpha.0",
3
+ "version": "25.0.0-alpha.1",
4
4
  "description": "Tool to develop, test and build js projects",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -11,8 +11,7 @@
11
11
  "node": ">=14.9.0"
12
12
  },
13
13
  "publishConfig": {
14
- "access": "public",
15
- "registry": "https://registry.npmjs.org"
14
+ "access": "public"
16
15
  },
17
16
  "type": "module",
18
17
  "exports": {
@@ -35,11 +34,13 @@
35
34
  "test": "node --experimental-import-meta-resolve ./script/test/test.js",
36
35
  "test-with-coverage": "npm run test -- --coverage",
37
36
  "dev": "node ./script/dev/start_dev_server.js",
37
+ "start_file_server": "node ./script/dev/start_file_server.js",
38
38
  "performances": "node --expose-gc ./script/performance/generate_performance_report.js --log --once",
39
39
  "file-size": "node ./script/file_size/file_size.mjs --log",
40
40
  "prettier": "prettier --write .",
41
41
  "playwright-install": "npx playwright install-deps && npx playwright install",
42
42
  "packages-install": "node ./script/packages/packages_install.js",
43
+ "certificate-install": "node ./script/dev/install_certificate_authority.mjs",
43
44
  "prepublishOnly": "npm run build"
44
45
  },
45
46
  "optionalDependencies": {
@@ -136,4 +137,4 @@
136
137
  "redux": "4.1.2",
137
138
  "rollup-plugin-import-assert": "1.1.1"
138
139
  }
139
- }
140
+ }
package/readme.md CHANGED
@@ -1,12 +1,8 @@
1
1
  # jsenv [![npm package](https://img.shields.io/npm/v/@jsenv/core.svg?logo=npm&label=package)](https://www.npmjs.com/package/@jsenv/core) [![github main worflow](https://github.com/jsenv/jsenv-core/workflows/main/badge.svg)](https://github.com/jsenv/jsenv-core/actions?workflow=main) [![codecov coverage](https://codecov.io/gh/jsenv/jsenv-core/branch/master/graph/badge.svg)](https://codecov.io/gh/jsenv/jsenv-core)
2
2
 
3
- _@jsenv/core_ was first created to write tests that could be executed in different browsers AND Node.js. In the end it became a tool covering the core needs of a JavaScript project:
3
+ _@jsenv/core_ is a quick start pack to launch a js project.
4
4
 
5
- - A test runner to execute test files
6
- - A development server
7
- - A build tool to optimize files for production
8
-
9
- Jsenv integrates naturally with standard html, css and js. It can be configured to work with React and JSX.
5
+ You don't have to pick a JavaScript framework: jsenv integrates naturally with standard HTML, CSS and JS. It provides what you need from the beginning: a dev server, a build tool and a test "framework", all in one.
10
6
 
11
7
  # Test runner overview
12
8
 
@@ -251,6 +247,12 @@ To read more about jsenv build tool, check [jsenv build documentation](./docs/bu
251
247
 
252
248
  # About
253
249
 
250
+ Jsenv was first created to write tests that could be executed in different runtimes. It has naturally evolved to cover the core needs of a JavaScript project:
251
+
252
+ - A development server
253
+ - A test runner to execute test files
254
+ - A build tool to optimize files for production
255
+
254
256
  Jsenv relies on standard web features. Each standard listed below is potentially supported natively by the browser. When browser supports all of them, jsenv will use source files without modification. Otherwise, the files are compiled to be executable in the browser.
255
257
 
256
258
  - `<script type="module">`
@@ -260,7 +262,7 @@ Jsenv relies on standard web features. Each standard listed below is potentially
260
262
  - dynamic imports
261
263
  - import assertions
262
264
 
263
- ## When to use it
265
+ ## When to use it?
264
266
 
265
267
  Amongst other use cases, the ease of use and flexibility of jsenv makes it a great tool to start and learn web development.
266
268
 
@@ -286,7 +288,7 @@ An overview of the main dependencies used by _@jsenv/core_.
286
288
 
287
289
  ## Name
288
290
 
289
- The name "jsenv" stands for JavaScript environments. This is because the original purpose of jsenv was to bring closer two JavaScript runtimes: web browsers and Node.js. This aspect is not highlighted in the documentation but it exists.
291
+ The name "jsenv" stands for JavaScript environments. This is because the original purpose of jsenv was to bring closer two JavaScript runtimes: web browsers and Node.js.
290
292
 
291
293
  Maybe "jsenv" should be written "JSEnv"? That makes typing the name too complex:
292
294
 
@@ -362,86 +364,17 @@ module.exports = {
362
364
  }
363
365
  ```
364
366
 
365
- ## CommonJS
366
-
367
- CommonJS module format rely on `module.exports` and `require`. It was invented by Node.js and is not standard JavaScript. If your code or one of your dependency uses it, it requires some configuration. The jsenv config below makes jsenv compatible with a package named _"whatever"_ that would be written in CommonJS.
368
-
369
- _jsenv.config.mjs to use code written in CommonJS_:
370
-
371
- ```js
372
- import { commonJsToJavaScriptModule } from "@jsenv/core"
373
-
374
- export const customCompilers = {
375
- "./node_modules/whatever/index.js": commonJsToJavaScriptModule,
376
- }
377
- ```
378
-
379
- ## React
380
-
381
- When your code imports react, it needs to be configured as shown below.
382
-
383
- _jsenv.config.mjs for react:_
384
-
385
- ```js
386
- import { commonJsToJavaScriptModule } from "@jsenv/core"
387
-
388
- // "react" and "react-dom" are written in commonJs, they
389
- // must be converted to javascript modules
390
- // see https://github.com/jsenv/jsenv-core/blob/master/docs/shared-parameters.md#customCompilers
391
- export const customCompilers = {
392
- "./node_modules/react/index.js": commonJsToJavaScriptModule,
393
- "./node_modules/react-dom/index.js": (options) => {
394
- return commonJsToJavaScriptModule({ ...options, external: ["react"] })
395
- },
396
- }
397
- ```
398
-
399
- You must also add an [importmap](https://github.com/WICG/import-maps#import-maps) file in your html to remap react imports.
400
-
401
- ```html
402
- <script type="importmap">
403
- {
404
- "imports": {
405
- "react": "./node_modules/react/index.js",
406
- "react-dom": "./node_modules/react-dom/index.js"
407
- }
408
- }
409
- </script>
410
- ```
411
-
412
- The import mappings can be generated programmatically, you can use https://github.com/jsenv/importmap-node-module to do that.
413
-
414
- ## JSX
415
-
416
- If you want to use jsx, you need [@babel/plugin-transform-react-jsx](https://babeljs.io/docs/en/next/babel-plugin-transform-react-jsx.html) in your babel config file.
417
-
418
- ```console
419
- npm i --save-dev @babel/plugin-transform-react-jsx
420
- ```
421
-
422
- _babel.config.cjs for JSX_:
423
-
424
- ```js
425
- module.exports = {
426
- presets: ["@jsenv/babel-preset"],
427
- plugins: [
428
- [
429
- "@babel/plugin-transform-react-jsx",
430
- {
431
- pragma: "React.createElement",
432
- pragmaFrag: "React.Fragment",
433
- },
434
- ],
435
- ],
436
- }
437
- ```
438
-
439
367
  # See also
440
368
 
441
- | Link | Description |
442
- | ------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------- |
443
- | [@jsenv/template-pwa](https://github.com/jsenv/jsenv-template-pwa) | GitHub repository template for a progressive web application |
444
- | [@jsenv/template-node-package](https://github.com/jsenv/jsenv-template-node-package) | GitHub repository template for a node package |
445
- | [@jsenv/assert](https://github.com/jsenv/assert) | Test anything using one assertion |
446
- | [I am too lazy for a test framework](https://medium.com/@DamienMaillard/i-am-too-lazy-for-a-test-framework-ca08d216ee05) | Article presenting a straightforward testing experience |
447
- | [Jsenv compile server](./docs/jsenv-compile-server.md) | Documentation about progressive compilation with a filesystem cache |
369
+ | Link | Description |
370
+ | ------------------------------------------------------------------------------------ | ------------------------------------------------------------------------- |
371
+ | [Browser support](./docs/config/browser_support.md) | Document how to configure browser support |
372
+ | [Web workers](./docs/config/web_workers.md) | Document how to use web workers |
373
+ | [React](./docs/config/react.md) | Document how to enable react/preact and JSX |
374
+ | [CommonJS modules](./docs/config/commonjs.md) | Document how to use code written in CommonJS |
375
+ | [TypeScript (Experimental)](./docs/config/typescript.md) | Document how to enable TypeScript |
376
+ | [@jsenv/template-pwa](https://github.com/jsenv/jsenv-template-pwa) | GitHub repository template for a progressive web application |
377
+ | [@jsenv/template-node-package](https://github.com/jsenv/jsenv-template-node-package) | GitHub repository template for a node package |
378
+ | [@jsenv/assert](https://github.com/jsenv/assert) | NPM package to write assertions |
379
+ | [I am too lazy for a test framework](shorturl.at/rFY39) | Article presenting a straightforward testing experience |
380
+ | [Jsenv compile server](./docs/jsenv-compile-server.md) | Documentation about jsenv progressive compilation with a filesystem cache |