@jsenv/core 25.0.0-alpha.3 → 25.1.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.
@@ -7,6 +7,6 @@ export const COMPILE_PROXY_BUILD_URL = new URL("compile_proxy/compile_proxy_7ad5
7
7
 
8
8
  export const EVENT_SOURCE_CLIENT_BUILD_URL = new URL("event_source_client/event_source_client_80644aee.js", import.meta.url).href
9
9
 
10
- export const TOOLBAR_BUILD_URL = new URL("toolbar/toolbar_04ba410c.html", import.meta.url).href
10
+ export const TOOLBAR_BUILD_URL = new URL("toolbar/toolbar_f7b8a263.html", import.meta.url).href
11
11
 
12
- export const TOOLBAR_INJECTOR_BUILD_URL = new URL("toolbar_injector/toolbar_injector_4a48bc53.js", import.meta.url).href
12
+ export const TOOLBAR_INJECTOR_BUILD_URL = new URL("toolbar_injector/toolbar_injector_49e4756e.js", import.meta.url).href
@@ -8,6 +8,6 @@
8
8
  "assets/settings.css.map": "assets/settings.css_61548139.map",
9
9
  "assets/toolbar.main.css.map": "assets/toolbar.main.css_269d7ce2.map",
10
10
  "assets/tooltip.css.map": "assets/tooltip.css_a94a8bdd.map",
11
- "toolbar.html": "toolbar_04ba410c.html",
12
- "toolbar.main2.js.map": "toolbar.main2_6c1b3d82.js.map"
11
+ "toolbar.html": "toolbar_f7b8a263.html",
12
+ "toolbar.main.js.map": "toolbar.main_6c1b3d82.js.map"
13
13
  }
@@ -5010,7 +5010,7 @@ html[data-toolbar-visible] #toolbar-trigger {
5010
5010
  };
5011
5011
  });
5012
5012
 
5013
- //# sourceMappingURL=toolbar.main2_6c1b3d82.js.map</script>
5013
+ //# sourceMappingURL=toolbar.main_6c1b3d82.js.map</script>
5014
5014
 
5015
5015
 
5016
5016
  </body></html>
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "assets/jsenv-logo.svg": "assets/jsenv-logo_188b9ca6.svg",
3
- "toolbar_injector.js": "toolbar_injector_4a48bc53.js",
4
- "toolbar_injector.js.map": "toolbar_injector_4a48bc53.js.map"
3
+ "toolbar_injector.js": "toolbar_injector_49e4756e.js",
4
+ "toolbar_injector.js.map": "toolbar_injector_49e4756e.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_04ba410c.html";
749
+ var TOOLBAR_BUILD_RELATIVE_URL = "dist/toolbar/toolbar_f7b8a263.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_4a48bc53.js.map
973
+ //# sourceMappingURL=toolbar_injector_49e4756e.js.map
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "version": 3,
3
- "file": "toolbar_injector_4a48bc53.js",
3
+ "file": "toolbar_injector_49e4756e.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_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"
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_f7b8a263.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.3",
3
+ "version": "25.1.0",
4
4
  "description": "Tool to develop, test and build js projects",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -8,7 +8,7 @@
8
8
  "url": "https://github.com/jsenv/jsenv-core"
9
9
  },
10
10
  "engines": {
11
- "node": ">=14.9.0"
11
+ "node": ">=16.13.0"
12
12
  },
13
13
  "publishConfig": {
14
14
  "access": "public"
package/readme.md CHANGED
@@ -1,10 +1,20 @@
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_ is a quick start pack to launch a js project.
3
+ _@jsenv/core_ is a quick start pack to launch a js project. It provides what you need from the beginning: develoment, testing and building all in one.
4
4
 
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.
5
+ Jsenv **integrates naturally with standard** HTML, CSS and JS: you don't have to pick a JavaScript framework.
6
6
 
7
- # Test runner overview
7
+ # Overview
8
+
9
+ This section demos 3 things jsenv provides:
10
+
11
+ 1. A test runner
12
+ 2. A dev server
13
+ 3. A build script
14
+
15
+ Don't be fooled by the apparent simplicity of the following demos, jsenv can be used on more complex scenarios as well.
16
+
17
+ ## Test runner overview
8
18
 
9
19
  Let's assume you want to test `countDogs` exported by _animals.js_ file.
10
20
 
@@ -94,7 +104,7 @@ total duration: 1.2 seconds
94
104
 
95
105
  To read more about testing in jsenv, check [jsenv test runner documentation](./docs/testing/readme.md#jsenv-test-runner).
96
106
 
97
- # Dev server overview
107
+ ## Dev server overview
98
108
 
99
109
  You want to execute the following _main.html_ file in a browser.
100
110
 
@@ -155,7 +165,7 @@ Browser navigates to _main.html_ and execute the file. Hello world is displayed
155
165
 
156
166
  To read more about jsenv dev server, check [jsenv dev server documentation](./docs/dev_server/readme.md#jsenv-dev-server).
157
167
 
158
- # Build overview
168
+ ## Build overview
159
169
 
160
170
  Following the steps below turns a `main.html` into an optimized `dist/main.prod.html`.
161
171
  Only the content of html files is shown below because the content of non-html files is trivial.
@@ -253,7 +263,7 @@ Jsenv was first created to write tests that could be executed in different runti
253
263
  - A test runner to execute test files
254
264
  - A build tool to optimize files for production
255
265
 
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.
266
+ 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.
257
267
 
258
268
  - `<script type="module">`
259
269
  - `<script type="importmap">`
@@ -14,8 +14,8 @@ export const parseJsRessource = async (
14
14
  const jsUrl = jsRessource.url
15
15
  const jsString = String(jsRessource.bufferBeforeBuild)
16
16
  const jsSourcemapUrl = getJavaScriptSourceMappingUrl(jsString)
17
- let sourcemapReference
18
17
 
18
+ let sourcemapReference
19
19
  if (jsSourcemapUrl) {
20
20
  sourcemapReference = notifyReferenceFound({
21
21
  referenceLabel: "js sourcemapping comment",
@@ -76,13 +76,10 @@ export const parseJsRessource = async (
76
76
  code = result.code
77
77
  map = result.map
78
78
  }
79
-
80
79
  jsRessource.buildEnd(code)
81
-
82
80
  if (!map) {
83
81
  return
84
82
  }
85
-
86
83
  // In theory code should never be modified once buildEnd() is called
87
84
  // because buildRelativeUrl might be versioned based on file content
88
85
  // There is an exception for sourcemap because we want to update sourcemap.file
@@ -99,7 +96,6 @@ export const parseJsRessource = async (
99
96
  `${urlToFilename(jsBuildUrl)}.map`,
100
97
  jsBuildUrl,
101
98
  )
102
-
103
99
  map.file = urlToFilename(jsBuildUrl)
104
100
  if (map.sources) {
105
101
  map.sources = map.sources.map((source) => {
@@ -114,7 +110,6 @@ export const parseJsRessource = async (
114
110
  }
115
111
  const mapAsText = JSON.stringify(map, null, " ")
116
112
  sourcemapRessource.buildEnd(mapAsText)
117
-
118
113
  const sourcemapBuildUrl = resolveUrl(
119
114
  sourcemapRessource.buildRelativeUrl,
120
115
  buildDirectoryUrl,
@@ -948,8 +948,14 @@ export const createRessourceBuilder = (
948
948
  }
949
949
  return {
950
950
  ...htmlUrlSite,
951
- line: htmlUrlSite.line + urlSite.line,
952
- column: htmlUrlSite.column + urlSite.column,
951
+ line:
952
+ typeof urlSite.line === "number"
953
+ ? htmlUrlSite.line + urlSite.line
954
+ : htmlUrlSite.line,
955
+ column:
956
+ typeof urlSite.column === "number"
957
+ ? htmlUrlSite.column + urlSite.column
958
+ : htmlUrlSite.column,
953
959
  }
954
960
  }
955
961
 
@@ -690,6 +690,9 @@ export const createRollupPlugins = async ({
690
690
  // we compile for rollup, let top level await untouched
691
691
  // it will be converted, if needed, during "renderChunk" hook
692
692
  topLevelAwait: "ignore",
693
+ // if we put babel helpers, rollup might try to share them and a file
694
+ // might try to import from an inline script resulting in 404.
695
+ babelHelpersInjectionAsImport: false,
693
696
  })
694
697
  let code = transformResult.code
695
698
  let map = transformResult.map
@@ -10,20 +10,7 @@ export const createUrlVersioner = ({
10
10
  asOriginalUrl,
11
11
  lineBreakNormalization,
12
12
  }) => {
13
- const names = []
14
- const getFreeName = (name) => {
15
- let nameCandidate = name
16
- let integer = 1
17
- // eslint-disable-next-line no-constant-condition
18
- while (true) {
19
- if (!names.includes(nameCandidate)) {
20
- names.push(nameCandidate)
21
- return nameCandidate
22
- }
23
- integer++
24
- nameCandidate = `${name}${integer}`
25
- }
26
- }
13
+ const availableNameGenerator = createAvailableNameGenerator()
27
14
 
28
15
  const computeBuildRelativeUrl = (ressource, precomputation) => {
29
16
  const pattern = getFilenamePattern({
@@ -40,7 +27,11 @@ export const createUrlVersioner = ({
40
27
  pattern,
41
28
  getName: precomputation
42
29
  ? () => urlToBasename(ressource.url)
43
- : () => getFreeName(urlToBasename(ressource.url)),
30
+ : () =>
31
+ availableNameGenerator.getAvailableNameForPattern(
32
+ urlToBasename(ressource.url),
33
+ pattern,
34
+ ),
44
35
  contentType: ressource.contentType,
45
36
  lineBreakNormalization,
46
37
  },
@@ -69,6 +60,33 @@ export const createUrlVersioner = ({
69
60
  }
70
61
  }
71
62
 
63
+ const createAvailableNameGenerator = () => {
64
+ const cache = {}
65
+ const getAvailableNameForPattern = (name, pattern) => {
66
+ let names = cache[pattern]
67
+ if (!names) {
68
+ names = []
69
+ cache[pattern] = names
70
+ }
71
+
72
+ let nameCandidate = name
73
+ let integer = 1
74
+ // eslint-disable-next-line no-constant-condition
75
+ while (true) {
76
+ if (!names.includes(nameCandidate)) {
77
+ names.push(nameCandidate)
78
+ return nameCandidate
79
+ }
80
+ integer++
81
+ nameCandidate = `${name}${integer}`
82
+ }
83
+ }
84
+
85
+ return {
86
+ getAvailableNameForPattern,
87
+ }
88
+ }
89
+
72
90
  const getFilenamePattern = ({
73
91
  ressource,
74
92
  entryPointUrls,
@@ -115,7 +133,7 @@ const getFilenamePattern = ({
115
133
  }
116
134
 
117
135
  if (ressource.isJsModule) {
118
- return "[name]_[hash][extname]"
136
+ return "[name]_[hash].js"
119
137
  }
120
138
 
121
139
  return ressource.urlVersioningDisabled
@@ -1,7 +1,14 @@
1
- import { resolveUrl, urlToRelativeUrl } from "@jsenv/filesystem"
1
+ import {
2
+ resolveUrl,
3
+ urlToFilename,
4
+ urlToBasename,
5
+ urlToRelativeUrl,
6
+ urlIsInsideOf,
7
+ } from "@jsenv/filesystem"
2
8
  import { moveImportMap, composeTwoImportMaps } from "@jsenv/importmap"
3
9
  import { createDetailedMessage } from "@jsenv/logger"
4
10
 
11
+ import { jsenvDistDirectoryUrl } from "@jsenv/core/src/internal/jsenvCoreDirectoryUrl.js"
5
12
  import {
6
13
  BROWSER_RUNTIME_BUILD_URL,
7
14
  EVENT_SOURCE_CLIENT_BUILD_URL,
@@ -39,6 +46,7 @@ export const compileHtml = async ({
39
46
  url,
40
47
  compiledUrl,
41
48
  projectDirectoryUrl,
49
+ compileServerOrigin,
42
50
  outDirectoryRelativeUrl,
43
51
  compileId,
44
52
 
@@ -104,248 +112,478 @@ export const compileHtml = async ({
104
112
  ],
105
113
  })
106
114
 
107
- let sources = []
108
- let sourcesContent = []
115
+ const sources = []
116
+ const sourcesContent = []
117
+ const assets = []
118
+ const assetsContent = []
119
+
120
+ const addHtmlSourceFile = ({ url, content }) => {
121
+ sources.push(url)
122
+ sourcesContent.push(content)
123
+ }
124
+ addHtmlSourceFile({ url, content: code })
125
+
109
126
  const { scripts } = parseHtmlAstRessources(htmlAst)
110
- let importmapInfo = null
111
- scripts.forEach((script) => {
112
- const typeAttribute = getHtmlNodeAttributeByName(script, "type")
113
- if (typeAttribute && typeAttribute.value === "importmap") {
114
- if (importmapInfo) {
115
- console.error("HTML file must contain max 1 importmap")
116
- } else {
117
- const srcAttribute = getHtmlNodeAttributeByName(script, "src")
118
- const src = srcAttribute ? srcAttribute.value : ""
119
- if (src) {
120
- importmapInfo = {
121
- script,
122
- url: resolveUrl(src, url),
123
- load: async () => {
124
- const importMapResponse = await fetchUrl(importmapInfo.url)
125
- if (importMapResponse.status === 200) {
126
- const importmapAsText = await importMapResponse.text()
127
- let htmlImportmap = JSON.parse(importmapAsText)
128
- htmlImportmap = moveImportMap(
129
- htmlImportmap,
130
- importmapInfo.url,
131
- url,
132
- )
133
- sources.push(importmapInfo.url)
134
- sourcesContent.push(importmapAsText)
135
- return htmlImportmap
136
- }
137
- logger.warn(
138
- createDetailedMessage(
139
- importMapResponse.status === 404
140
- ? `importmap script file cannot be found.`
141
- : `importmap script file unexpected response status (${importMapResponse.status}).`,
142
- {
143
- "importmap url": importmapInfo.url,
144
- "html url": url,
145
- },
146
- ),
147
- )
148
- return {}
149
- },
150
- }
151
- } else {
152
- importmapInfo = {
153
- script,
154
- url: compiledUrl,
155
- load: () => {
156
- const jsenvImportmap = getDefaultImportmap(compiledUrl, {
157
- projectDirectoryUrl,
158
- compileDirectoryUrl: `${projectDirectoryUrl}${compileId}/${outDirectoryRelativeUrl}`,
159
- })
160
- const htmlImportmap = JSON.parse(
161
- getHtmlNodeTextNode(script).value,
162
- )
163
- const importmap = composeTwoImportMaps(
164
- jsenvImportmap,
165
- htmlImportmap,
166
- )
167
- return importmap
168
- },
169
- }
170
- }
171
- }
172
- }
173
- })
174
- if (importmapInfo) {
175
- const importmap = await importmapInfo.load()
176
- const importmapAsText = JSON.stringify(importmap, null, " ")
177
- replaceHtmlNode(
178
- importmapInfo.script,
179
- `<script type="${
180
- moduleOutFormat === "systemjs" ? "systemjs-importmap" : "importmap"
181
- }">${importmapAsText}</script>`,
182
- {
183
- attributesToIgnore: ["src"],
184
- },
185
- )
186
- importmapInfo.inlinedFrom = importmapInfo.url
187
- importmapInfo.text = importmapAsText
188
- } else {
189
- const defaultImportMap = getDefaultImportmap(compiledUrl, {
190
- projectDirectoryUrl,
191
- compileDirectoryUrl: `${projectDirectoryUrl}${outDirectoryRelativeUrl}${compileId}/`,
127
+ const htmlDependencies = collectHtmlDependenciesFromAst(htmlAst)
128
+
129
+ const htmlAssetGenerators = []
130
+ const htmlMutations = []
131
+ const addHtmlAssetGenerator = (htmlAssetGenerator) => {
132
+ htmlAssetGenerators.push(htmlAssetGenerator)
133
+ }
134
+ const addHtmlMutation = (htmlMutation) => {
135
+ htmlMutations.push(htmlMutation)
136
+ }
137
+ const addHtmlDependency = ({ htmlNode, specifier }) => {
138
+ htmlDependencies.push({
139
+ htmlNode,
140
+ specifier,
192
141
  })
193
- const importmapAsText = JSON.stringify(defaultImportMap, null, " ")
194
- manipulateHtmlAst(htmlAst, {
195
- scriptInjections: [
142
+ }
143
+
144
+ const importmapInfo = await visitImportmapScript({
145
+ logger,
146
+ url,
147
+ compiledUrl,
148
+ projectDirectoryUrl,
149
+ compileId,
150
+ outDirectoryRelativeUrl,
151
+ scripts,
152
+ addHtmlSourceFile,
153
+ })
154
+ const importmap = (await importmapInfo.load()) || {}
155
+ const importmapAsText = JSON.stringify(importmap, null, " ")
156
+ importmapInfo.inlinedFrom = importmapInfo.url
157
+ importmapInfo.text = importmapAsText
158
+ addHtmlMutation(() => {
159
+ if (importmapInfo.needsInjection) {
160
+ manipulateHtmlAst(htmlAst, {
161
+ scriptInjections: [
162
+ {
163
+ type:
164
+ moduleOutFormat === "systemjs"
165
+ ? "systemjs-importmap"
166
+ : "importmap",
167
+ // in case there is no importmap, force the presence
168
+ // so that '@jsenv/core/' are still remapped
169
+ text: importmapAsText,
170
+ },
171
+ ],
172
+ })
173
+ } else {
174
+ replaceHtmlNode(
175
+ importmapInfo.script,
176
+ `<script type="${
177
+ moduleOutFormat === "systemjs" ? "systemjs-importmap" : "importmap"
178
+ }">${importmapAsText}</script>`,
196
179
  {
197
- type:
198
- moduleOutFormat === "systemjs" ? "systemjs-importmap" : "importmap",
199
- // in case there is no importmap, force the presence
200
- // so that '@jsenv/core/' are still remapped
201
- text: importmapAsText,
180
+ attributesToIgnore: ["src"],
202
181
  },
203
- ],
204
- })
205
- importmapInfo = {
206
- url: compiledUrl,
207
- text: importmapAsText,
182
+ )
208
183
  }
209
- }
184
+ })
210
185
  onHtmlImportmapInfo({
211
186
  htmlUrl: url,
212
187
  importmapInfo,
213
188
  })
214
189
 
215
- const htmlDependencies = collectHtmlDependenciesFromAst(htmlAst)
216
- const inlineScriptsContentMap = {}
190
+ await visitScripts({
191
+ logger,
192
+ projectDirectoryUrl,
193
+ compileServerOrigin,
194
+ url,
195
+ compiledUrl,
196
+ scripts,
197
+ addHtmlSourceFile,
198
+ addHtmlAssetGenerator,
199
+ addHtmlMutation,
200
+ addHtmlDependency,
201
+
202
+ babelPluginMap,
203
+ moduleOutFormat,
204
+ importMetaFormat,
205
+ topLevelAwait,
206
+ sourcemapMethod,
207
+ })
208
+ await Promise.all(
209
+ htmlAssetGenerators.map(async (htmlAssetGenerator) => {
210
+ const assetInfos = await htmlAssetGenerator()
211
+ assetInfos.forEach((assetInfo) => {
212
+ assets.push(assetInfo.url)
213
+ assetsContent.push(assetInfo.content)
214
+ })
215
+ }),
216
+ )
217
+ htmlAssetGenerators.length = 0
218
+
219
+ htmlMutations.forEach((htmlMutation) => {
220
+ htmlMutation()
221
+ })
222
+ htmlMutations.length = 0
223
+ const htmlAfterTransformation = stringifyHtmlAst(htmlAst)
224
+
225
+ return {
226
+ contentType: "text/html",
227
+ compiledSource: htmlAfterTransformation,
228
+ sources,
229
+ sourcesContent,
230
+ assets,
231
+ assetsContent,
232
+ dependencies: htmlDependencies.map(({ specifier }) => {
233
+ return specifier
234
+ }),
235
+ }
236
+ }
237
+
238
+ const visitImportmapScript = async ({
239
+ logger,
240
+ url,
241
+ compiledUrl,
242
+ projectDirectoryUrl,
243
+ compileId,
244
+ outDirectoryRelativeUrl,
245
+ scripts,
246
+ addHtmlSourceFile,
247
+ }) => {
248
+ const importmapScripts = scripts.filter((script) => {
249
+ const typeAttribute = getHtmlNodeAttributeByName(script, "type")
250
+ const type = typeAttribute ? typeAttribute.value : "application/javascript"
251
+ return type === "importmap"
252
+ })
253
+ if (importmapScripts.length === 0) {
254
+ return {
255
+ needsInjection: true,
256
+ url: compiledUrl,
257
+ load: () => {
258
+ const defaultImportMap = getDefaultImportmap(compiledUrl, {
259
+ projectDirectoryUrl,
260
+ compileDirectoryUrl: `${projectDirectoryUrl}${outDirectoryRelativeUrl}${compileId}/`,
261
+ })
262
+ return defaultImportMap
263
+ },
264
+ }
265
+ }
266
+
267
+ if (importmapScripts.length > 1) {
268
+ logger.error("HTML file must contain max 1 importmap")
269
+ }
270
+ const firstImportmapScript = importmapScripts[0]
271
+ const srcAttribute = getHtmlNodeAttributeByName(firstImportmapScript, "src")
272
+ const src = srcAttribute ? srcAttribute.value : ""
273
+ if (src) {
274
+ const importmapUrl = resolveUrl(src, url)
275
+ const importmapInfo = {
276
+ script: firstImportmapScript,
277
+ url: importmapUrl,
278
+ load: async () => {
279
+ const importMapResponse = await fetchUrl(importmapUrl)
280
+ if (importMapResponse.status === 200) {
281
+ const importmapAsText = await importMapResponse.text()
282
+ addHtmlSourceFile({
283
+ url: importmapUrl,
284
+ content: importmapAsText,
285
+ })
286
+ let htmlImportmap = JSON.parse(importmapAsText)
287
+ htmlImportmap = moveImportMap(htmlImportmap, importmapUrl, url)
288
+ return htmlImportmap
289
+ }
290
+ logger.warn(
291
+ createDetailedMessage(
292
+ importMapResponse.status === 404
293
+ ? `importmap script file cannot be found.`
294
+ : `importmap script file unexpected response status (${importMapResponse.status}).`,
295
+ {
296
+ "importmap url": importmapInfo.url,
297
+ "html url": url,
298
+ },
299
+ ),
300
+ )
301
+ return null
302
+ },
303
+ }
304
+ return importmapInfo
305
+ }
306
+ const importmapInfo = {
307
+ script: firstImportmapScript,
308
+ url: compiledUrl,
309
+ load: () => {
310
+ const jsenvImportmap = getDefaultImportmap(compiledUrl, {
311
+ projectDirectoryUrl,
312
+ compileDirectoryUrl: `${projectDirectoryUrl}${compileId}/${outDirectoryRelativeUrl}`,
313
+ })
314
+ const htmlImportmap = JSON.parse(
315
+ getHtmlNodeTextNode(firstImportmapScript).value,
316
+ )
317
+ const importmap = composeTwoImportMaps(jsenvImportmap, htmlImportmap)
318
+ return importmap
319
+ },
320
+ }
321
+ return importmapInfo
322
+ }
323
+
324
+ const visitScripts = async ({
325
+ logger,
326
+ projectDirectoryUrl,
327
+ compileServerOrigin,
328
+ url,
329
+ compiledUrl,
330
+ scripts,
331
+ addHtmlSourceFile,
332
+ addHtmlAssetGenerator,
333
+ addHtmlMutation,
334
+ addHtmlDependency,
335
+
336
+ babelPluginMap,
337
+ moduleOutFormat,
338
+ importMetaFormat,
339
+ topLevelAwait,
340
+ sourcemapMethod,
341
+ }) => {
217
342
  scripts.forEach((script) => {
218
343
  const typeAttribute = getHtmlNodeAttributeByName(script, "type")
344
+ const type = typeAttribute ? typeAttribute.value : "application/javascript"
219
345
  const srcAttribute = getHtmlNodeAttributeByName(script, "src")
220
346
  const src = srcAttribute ? srcAttribute.value : ""
221
- // remote module script
222
- if (typeAttribute && typeAttribute.value === "module" && src) {
223
- if (moduleOutFormat === "systemjs") {
224
- removeHtmlNodeAttribute(script, typeAttribute)
347
+ const textNode = getHtmlNodeTextNode(script)
348
+
349
+ if (type === "module") {
350
+ if (src) {
351
+ addHtmlMutation(() => {
352
+ if (moduleOutFormat === "systemjs") {
353
+ removeHtmlNodeAttribute(script, typeAttribute)
354
+ }
355
+ removeHtmlNodeAttribute(script, srcAttribute)
356
+ const jsenvMethod =
357
+ moduleOutFormat === "systemjs"
358
+ ? "executeFileUsingSystemJs"
359
+ : "executeFileUsingDynamicImport"
360
+ setHtmlNodeText(
361
+ script,
362
+ `window.__jsenv__.${jsenvMethod}(${JSON.stringify(src)})`,
363
+ )
364
+ })
365
+ return
225
366
  }
226
- removeHtmlNodeAttribute(script, srcAttribute)
227
- const jsenvMethod =
228
- moduleOutFormat === "systemjs"
229
- ? "executeFileUsingSystemJs"
230
- : "executeFileUsingDynamicImport"
231
- setHtmlNodeText(
367
+
368
+ const inlineScriptName = getUniqueNameForInlineHtmlNode(
232
369
  script,
233
- `window.__jsenv__.${jsenvMethod}(${JSON.stringify(src)})`,
370
+ scripts,
371
+ `[id].js`,
234
372
  )
235
- return
236
- }
237
-
238
- // inline module script
239
- const textNode = getHtmlNodeTextNode(script)
240
- if (typeAttribute && typeAttribute.value === "module" && textNode) {
241
- if (moduleOutFormat === "systemjs") {
242
- removeHtmlNodeAttribute(script, typeAttribute)
243
- }
244
- removeHtmlNodeAttribute(script, srcAttribute)
245
- const scriptAssetUrl = generateCompiledFileAssetUrl(
373
+ const scriptOriginalUrl = resolveUrl(inlineScriptName, url)
374
+ const scriptCompiledUrl = generateCompiledFileAssetUrl(
246
375
  compiledUrl,
247
- getUniqueNameForInlineHtmlNode(script, scripts, `[id].js`),
376
+ inlineScriptName,
248
377
  )
249
- const specifier = `./${urlToRelativeUrl(scriptAssetUrl, compiledUrl)}`
250
- inlineScriptsContentMap[specifier] = textNode.value
251
- const jsenvMethod =
252
- moduleOutFormat === "systemjs"
253
- ? "executeFileUsingSystemJs"
254
- : "executeFileUsingDynamicImport"
255
- setHtmlNodeText(
256
- script,
257
- `window.__jsenv__.${jsenvMethod}(${JSON.stringify(specifier)})`,
258
- )
259
- htmlDependencies.push({
378
+ addHtmlAssetGenerator(async () => {
379
+ return transformHtmlScript({
380
+ projectDirectoryUrl,
381
+ url: scriptOriginalUrl,
382
+ compiledUrl: scriptCompiledUrl,
383
+ code: textNode.value,
384
+
385
+ type: "module",
386
+ babelPluginMap,
387
+ moduleOutFormat,
388
+ importMetaFormat,
389
+ topLevelAwait,
390
+ sourcemapMethod,
391
+ })
392
+ })
393
+ const specifier = `./${urlToRelativeUrl(scriptCompiledUrl, compiledUrl)}`
394
+ addHtmlMutation(() => {
395
+ if (moduleOutFormat === "systemjs") {
396
+ removeHtmlNodeAttribute(script, typeAttribute)
397
+ }
398
+ removeHtmlNodeAttribute(script, srcAttribute)
399
+ const jsenvMethod =
400
+ moduleOutFormat === "systemjs"
401
+ ? "executeFileUsingSystemJs"
402
+ : "executeFileUsingDynamicImport"
403
+ setHtmlNodeText(
404
+ script,
405
+ `window.__jsenv__.${jsenvMethod}(${JSON.stringify(specifier)})`,
406
+ )
407
+ })
408
+ addHtmlDependency({
260
409
  htmlNode: script,
261
410
  specifier,
262
411
  })
263
412
  return
264
413
  }
265
- })
266
- const htmlAfterTransformation = stringifyHtmlAst(htmlAst)
267
414
 
268
- let assets = []
269
- let assetsContent = []
270
- await Promise.all(
271
- Object.keys(inlineScriptsContentMap).map(async (scriptSrc) => {
272
- const scriptAssetUrl = resolveUrl(scriptSrc, compiledUrl)
273
- const scriptBasename = urlToRelativeUrl(scriptAssetUrl, compiledUrl)
274
- const scriptOriginalFileUrl = resolveUrl(scriptBasename, url)
275
- const scriptCompiledFileUrl = resolveUrl(scriptBasename, compiledUrl)
276
-
277
- const scriptBeforeCompilation = inlineScriptsContentMap[scriptSrc]
278
- let scriptTransformResult
279
- try {
280
- scriptTransformResult = await transformJs({
281
- code: scriptBeforeCompilation,
282
- url: scriptOriginalFileUrl,
283
- compiledUrl: scriptCompiledFileUrl,
415
+ if (type === "application/javascript" || type === "text/javascript") {
416
+ if (src) {
417
+ const htmlServerUrl = url.replace(
418
+ projectDirectoryUrl,
419
+ `${compileServerOrigin}/`,
420
+ )
421
+ const scriptOriginalServerUrl = resolveUrl(src, htmlServerUrl)
422
+ const scriptOriginalUrl = scriptOriginalServerUrl.replace(
423
+ `${compileServerOrigin}/`,
284
424
  projectDirectoryUrl,
425
+ )
426
+ const fileIsInsideJsenvDistDirectory = urlIsInsideOf(
427
+ scriptOriginalUrl,
428
+ jsenvDistDirectoryUrl,
429
+ )
430
+ if (fileIsInsideJsenvDistDirectory) {
431
+ return
432
+ }
433
+
434
+ const scriptCompiledUrl = generateCompiledFileAssetUrl(
435
+ compiledUrl,
436
+ urlToFilename(scriptOriginalUrl),
437
+ )
438
+ addHtmlAssetGenerator(async () => {
439
+ // we fetch scriptOriginalUrl on purpose because we do
440
+ // the transformation here and not in compile server
441
+ // (because compile server would think it's a module script
442
+ // and add things like systemjs)
443
+ const scriptResponse = await fetchUrl(scriptOriginalUrl)
444
+ if (scriptResponse.status !== 200) {
445
+ logger.warn(
446
+ createDetailedMessage(
447
+ scriptResponse.status === 404
448
+ ? `script file cannot be found.`
449
+ : `script file unexpected response status (${scriptResponse.status}).`,
450
+ {
451
+ "script url": script.url,
452
+ "html url": url,
453
+ },
454
+ ),
455
+ )
456
+ return []
457
+ }
458
+ const scriptAsText = await scriptResponse.text()
459
+ addHtmlSourceFile({
460
+ url: scriptOriginalUrl,
461
+ content: scriptAsText,
462
+ })
463
+ return transformHtmlScript({
464
+ projectDirectoryUrl,
465
+ url: scriptOriginalUrl,
466
+ compiledUrl: scriptCompiledUrl,
467
+ code: scriptAsText,
285
468
 
469
+ type: "classic",
470
+ babelPluginMap,
471
+ moduleOutFormat,
472
+ importMetaFormat,
473
+ topLevelAwait,
474
+ sourcemapMethod,
475
+ })
476
+ })
477
+ addHtmlMutation(() => {
478
+ srcAttribute.value = `./${urlToRelativeUrl(
479
+ scriptCompiledUrl,
480
+ compiledUrl,
481
+ )}`
482
+ })
483
+ return
484
+ }
485
+ const inlineScriptName = getUniqueNameForInlineHtmlNode(
486
+ script,
487
+ scripts,
488
+ `[id].js`,
489
+ )
490
+ const scriptOriginalUrl = resolveUrl(inlineScriptName, url)
491
+ const scriptCompiledUrl = generateCompiledFileAssetUrl(
492
+ compiledUrl,
493
+ inlineScriptName,
494
+ )
495
+ addHtmlAssetGenerator(async () => {
496
+ const htmlAssets = await transformHtmlScript({
497
+ projectDirectoryUrl,
498
+ url: scriptOriginalUrl,
499
+ compiledUrl: scriptCompiledUrl,
500
+ code: textNode.value,
501
+
502
+ type: "classic",
286
503
  babelPluginMap,
287
504
  moduleOutFormat,
288
505
  importMetaFormat,
289
506
  topLevelAwait,
507
+ sourcemapMethod,
290
508
  })
291
- } catch (e) {
292
- // If there is a syntax error in inline script
293
- // we put the raw script without transformation.
294
- // when systemjs will try to instantiate to script it
295
- // will re-throw this syntax error.
296
- // Thanks to this we see the syntax error in the
297
- // document and livereloading still works
298
- // because we gracefully handle this error
299
- if (e.code === "PARSE_ERROR") {
300
- const code = scriptBeforeCompilation
301
- assets = [...assets, scriptAssetUrl]
302
- assetsContent = [...assetsContent, code]
303
- return
304
- }
305
- throw e
306
- }
307
- const sourcemapFileUrl = resolveUrl(
308
- `${scriptBasename}.map`,
309
- scriptCompiledFileUrl,
310
- )
509
+ addHtmlMutation(() => {
510
+ setHtmlNodeText(script, htmlAssets[0].content)
511
+ })
512
+ return htmlAssets
513
+ })
514
+ return
515
+ }
516
+ })
517
+ }
311
518
 
312
- let { code, map } = scriptTransformResult
313
- const sourcemapFileRelativePathForModule = urlToRelativeUrl(
314
- sourcemapFileUrl,
315
- compiledUrl,
316
- )
519
+ const transformHtmlScript = async ({
520
+ projectDirectoryUrl,
521
+ url,
522
+ compiledUrl,
523
+ code,
524
+ type,
317
525
 
318
- if (sourcemapMethod === "inline") {
319
- code = setJavaScriptSourceMappingUrl(code, sourcemapToBase64Url(map))
320
- } else {
321
- // TODO: respect "sourcemapMethod" parameter
322
- code = setJavaScriptSourceMappingUrl(
323
- code,
324
- sourcemapFileRelativePathForModule,
325
- )
326
- assets = [...assets, scriptAssetUrl, sourcemapFileUrl]
327
- assetsContent = [
328
- ...assetsContent,
329
- code,
330
- JSON.stringify(map, null, " "),
331
- ]
332
- }
333
- }),
334
- )
335
- sources.push(url)
336
- sourcesContent.push(code)
526
+ babelPluginMap,
527
+ moduleOutFormat,
528
+ importMetaFormat,
529
+ topLevelAwait,
530
+ sourcemapMethod,
531
+ }) => {
532
+ let transformResult
533
+ try {
534
+ transformResult = await transformJs({
535
+ code,
536
+ url,
537
+ compiledUrl,
538
+ projectDirectoryUrl,
337
539
 
338
- return {
339
- contentType: "text/html",
340
- compiledSource: htmlAfterTransformation,
341
- sources,
342
- sourcesContent,
343
- assets,
344
- assetsContent,
345
- dependencies: htmlDependencies.map(({ specifier }) => {
346
- return specifier
347
- }),
540
+ babelPluginMap,
541
+ moduleOutFormat: type === "module" ? moduleOutFormat : "global",
542
+ importMetaFormat,
543
+ topLevelAwait: type === "module" ? topLevelAwait : false,
544
+ babelHelpersInjectionAsImport: type === "module" ? undefined : false,
545
+ })
546
+ } catch (e) {
547
+ // If there is a syntax error in inline script
548
+ // we put the raw script without transformation.
549
+ // when systemjs will try to instantiate to script it
550
+ // will re-throw this syntax error.
551
+ // Thanks to this we see the syntax error in the
552
+ // document and livereloading still works
553
+ // because we gracefully handle this error
554
+ if (e.code === "PARSE_ERROR") {
555
+ return [{ url, content: code }]
556
+ }
557
+ throw e
348
558
  }
559
+
560
+ code = transformResult.code
561
+ let map = transformResult.map
562
+ const sourcemapUrl = resolveUrl(
563
+ `${urlToBasename(compiledUrl)}.map`,
564
+ compiledUrl,
565
+ )
566
+ if (sourcemapMethod === "inline") {
567
+ code = setJavaScriptSourceMappingUrl(code, sourcemapToBase64Url(map))
568
+ return [
569
+ {
570
+ url: compiledUrl,
571
+ content: code,
572
+ },
573
+ ]
574
+ }
575
+ const sourcemapSpecifier = urlToRelativeUrl(sourcemapUrl, compiledUrl)
576
+ code = setJavaScriptSourceMappingUrl(code, sourcemapSpecifier)
577
+ return [
578
+ {
579
+ url: compiledUrl,
580
+ content: code,
581
+ },
582
+ {
583
+ url: sourcemapUrl,
584
+ content: JSON.stringify(map, null, " "),
585
+ },
586
+ ]
349
587
  }
350
588
 
351
589
  // transform <link type="modulepreload"> into <link type="preload">
@@ -42,7 +42,10 @@ import {
42
42
  sourcemapMainFileInfo,
43
43
  sourcemapMappingFileInfo,
44
44
  } from "../jsenvInternalFiles.js"
45
- import { jsenvCoreDirectoryUrl } from "../jsenvCoreDirectoryUrl.js"
45
+ import {
46
+ jsenvCoreDirectoryUrl,
47
+ jsenvDistDirectoryUrl,
48
+ } from "../jsenvCoreDirectoryUrl.js"
46
49
  import { babelPluginReplaceExpressions } from "../babel_plugin_replace_expressions.js"
47
50
  import { babelPluginGlobalThisAsJsenvImport } from "./babel_plugin_global_this_as_jsenv_import.js"
48
51
  import { babelPluginNewStylesheetAsJsenvImport } from "./babel_plugin_new_stylesheet_as_jsenv_import.js"
@@ -945,7 +948,7 @@ const createSourceFileService = ({
945
948
  .href
946
949
  const fileIsInsideJsenvDistDirectory = urlIsInsideOf(
947
950
  fileUrl,
948
- new URL("./dist/", jsenvCoreDirectoryUrl),
951
+ jsenvDistDirectoryUrl,
949
952
  )
950
953
 
951
954
  const responsePromise = fetchFileSystem(fileUrl, {
@@ -17,4 +17,6 @@ if (typeof __filename === "string") {
17
17
  )
18
18
  }
19
19
 
20
+ export const jsenvDistDirectoryUrl = new URL("./dist/", jsenvCoreDirectoryUrl)
21
+
20
22
  export { jsenvCoreDirectoryUrl }