@jsenv/core 38.4.16 → 38.4.18

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jsenv/core",
3
- "version": "38.4.16",
3
+ "version": "38.4.18",
4
4
  "description": "Tool to develop, test and build js projects",
5
5
  "license": "MIT",
6
6
  "author": {
@@ -48,7 +48,6 @@
48
48
  "dev": "node --conditions=development ./scripts/dev/dev.mjs",
49
49
  "test": "node --conditions=development ./scripts/test/test.mjs",
50
50
  "test:workspace": "npm run test --workspaces --if-present -- --workspace",
51
- "test:snapshots_clear": "npx @jsenv/snapshot clear **/tests/**/snapshots/",
52
51
  "test:only_dev_server_errors": "node --conditions=development ./tests/dev_server/errors/dev_errors_snapshots.test.mjs",
53
52
  "build": "node --conditions=development ./scripts/build/build.mjs",
54
53
  "build:file_size": "node ./scripts/build/build_file_size.mjs --log",
@@ -66,20 +65,20 @@
66
65
  "@financial-times/polyfill-useragent-normaliser": "1.10.2",
67
66
  "@jsenv/abort": "4.3.0",
68
67
  "@jsenv/ast": "6.0.7",
69
- "@jsenv/filesystem": "4.6.9",
68
+ "@jsenv/filesystem": "4.7.1",
70
69
  "@jsenv/humanize": "1.1.3",
71
70
  "@jsenv/importmap": "1.2.1",
72
71
  "@jsenv/integrity": "0.0.1",
73
72
  "@jsenv/js-module-fallback": "1.3.16",
74
73
  "@jsenv/node-esm-resolution": "1.0.2",
75
- "@jsenv/plugin-bundling": "2.6.10",
74
+ "@jsenv/plugin-bundling": "2.6.11",
76
75
  "@jsenv/plugin-minification": "1.5.4",
77
- "@jsenv/plugin-supervisor": "1.4.10",
76
+ "@jsenv/plugin-supervisor": "1.4.11",
78
77
  "@jsenv/plugin-transpilation": "1.3.16",
79
78
  "@jsenv/runtime-compat": "1.3.0",
80
- "@jsenv/server": "15.2.6",
79
+ "@jsenv/server": "15.2.7",
81
80
  "@jsenv/sourcemap": "1.2.10",
82
- "@jsenv/url-meta": "8.4.0",
81
+ "@jsenv/url-meta": "8.4.1",
83
82
  "@jsenv/urls": "2.2.7",
84
83
  "@jsenv/utils": "2.1.1"
85
84
  },
@@ -1,10 +1,11 @@
1
- import { readFileSync } from "node:fs";
1
+ import { existsSync, readFileSync } from "node:fs";
2
2
  import { URL_META } from "@jsenv/url-meta";
3
3
  import {
4
4
  assertAndNormalizeDirectoryUrl,
5
5
  bufferToEtag,
6
6
  } from "@jsenv/filesystem";
7
7
  import { createLogger, createTaskLog } from "@jsenv/humanize";
8
+ import { urlToRelativeUrl } from "@jsenv/urls";
8
9
  import {
9
10
  jsenvAccessControlAllowedHeaders,
10
11
  startServer,
@@ -91,11 +92,18 @@ export const startDevServer = async ({
91
92
  sourceDirectoryUrl,
92
93
  "sourceDirectoryUrl",
93
94
  );
95
+ if (!existsSync(new URL(sourceDirectoryUrl))) {
96
+ throw new Error(`ENOENT on sourceDirectoryUrl at ${sourceDirectoryUrl}`);
97
+ }
94
98
  if (typeof sourceMainFilePath !== "string") {
95
99
  throw new TypeError(
96
100
  `sourceMainFilePath must be a string, got ${sourceMainFilePath}`,
97
101
  );
98
102
  }
103
+ sourceMainFilePath = urlToRelativeUrl(
104
+ new URL(sourceMainFilePath, sourceDirectoryUrl),
105
+ sourceDirectoryUrl,
106
+ );
99
107
  if (outDirectoryUrl === undefined) {
100
108
  if (!process.env.CI) {
101
109
  const packageDirectoryUrl = lookupPackageDirectory(sourceDirectoryUrl);
@@ -148,9 +148,12 @@ export const createTransformUrlContentError = ({
148
148
  if (code === "PARSE_ERROR") {
149
149
  transformError.reason = `parse error on ${urlInfo.type}`;
150
150
  transformError.cause = error;
151
+ let line = error.line;
152
+ if (urlInfo.type === "js_module") {
153
+ line = line - 1;
154
+ }
151
155
  if (urlInfo.isInline) {
152
- transformError.trace.line =
153
- urlInfo.firstReference.trace.line + error.line - 1;
156
+ transformError.trace.line = urlInfo.firstReference.trace.line + line;
154
157
  transformError.trace.column =
155
158
  urlInfo.firstReference.trace.column + error.column;
156
159
  transformError.trace.codeFrame = generateContentFrame({
@@ -167,16 +170,16 @@ export const createTransformUrlContentError = ({
167
170
  } else {
168
171
  transformError.trace = {
169
172
  url: urlInfo.url,
170
- line: error.line,
173
+ line,
171
174
  column: error.column,
172
175
  codeFrame: generateContentFrame({
173
- line: error.line - 1,
176
+ line,
174
177
  column: error.column,
175
178
  content: urlInfo.content,
176
179
  }),
177
180
  message: stringifyUrlSite({
178
181
  url: urlInfo.url,
179
- line: error.line - 1,
182
+ line,
180
183
  column: error.column,
181
184
  content: urlInfo.content,
182
185
  }),
@@ -72,13 +72,14 @@ export const createDependencies = (ownerUrlInfo) => {
72
72
  const parentContent = isOriginalPosition
73
73
  ? ownerUrlInfo.originalContent
74
74
  : ownerUrlInfo.content;
75
+ const trace = traceFromUrlSite({
76
+ url: parentUrl,
77
+ content: parentContent,
78
+ line: specifierLine,
79
+ column: specifierColumn,
80
+ });
75
81
  const reference = createResolveAndFinalize({
76
- trace: traceFromUrlSite({
77
- url: parentUrl,
78
- content: parentContent,
79
- line: specifierLine,
80
- column: specifierColumn,
81
- }),
82
+ trace,
82
83
  isOriginalPosition,
83
84
  specifierLine,
84
85
  specifierColumn,
@@ -6,134 +6,134 @@ import {
6
6
  getDOMNodesUsingUrl,
7
7
  } from "./reload.js";
8
8
 
9
- let debug = false;
10
- const reloader = {
11
- urlHotMetas,
12
- status: {
13
- value: "idle",
14
- onchange: () => {},
15
- goTo: (value) => {
16
- reloader.status.value = value;
17
- reloader.status.onchange();
9
+ export const initAutoreload = ({ mainFilePath }) => {
10
+ let debug = false;
11
+ const reloader = {
12
+ urlHotMetas,
13
+ status: {
14
+ value: "idle",
15
+ onchange: () => {},
16
+ goTo: (value) => {
17
+ reloader.status.value = value;
18
+ reloader.status.onchange();
19
+ },
18
20
  },
19
- },
20
- autoreload: {
21
- enabled: ["1", null].includes(window.localStorage.getItem("autoreload")),
22
- onchange: () => {},
23
- enable: () => {
24
- reloader.autoreload.enabled = true;
25
- window.localStorage.setItem("autoreload", "1");
26
- reloader.autoreload.onchange();
21
+ autoreload: {
22
+ enabled: ["1", null].includes(window.localStorage.getItem("autoreload")),
23
+ onchange: () => {},
24
+ enable: () => {
25
+ reloader.autoreload.enabled = true;
26
+ window.localStorage.setItem("autoreload", "1");
27
+ reloader.autoreload.onchange();
28
+ },
29
+ disable: () => {
30
+ reloader.autoreload.enabled = false;
31
+ window.localStorage.setItem("autoreload", "0");
32
+ reloader.autoreload.onchange();
33
+ },
27
34
  },
28
- disable: () => {
29
- reloader.autoreload.enabled = false;
30
- window.localStorage.setItem("autoreload", "0");
31
- reloader.autoreload.onchange();
32
- },
33
- },
34
- changes: {
35
- value: [],
36
- onchange: () => {},
37
- add: (reloadMessage) => {
38
- if (debug) {
39
- console.debug("received reload message", reloadMessage);
40
- }
41
- reloader.changes.value.push(reloadMessage);
42
- reloader.changes.onchange();
43
- if (reloader.autoreload.enabled) {
44
- reloader.reload();
45
- } else {
46
- reloader.status.goTo("can_reload");
47
- }
48
- },
49
- remove: (reloadMessage) => {
50
- const index = reloader.changes.value.indexOf(reloadMessage);
51
- if (index > -1) {
52
- reloader.changes.value.splice(index, 1);
53
- if (reloader.changes.value.length === 0) {
54
- reloader.status.goTo("idle");
35
+ changes: {
36
+ value: [],
37
+ onchange: () => {},
38
+ add: (reloadMessage) => {
39
+ if (debug) {
40
+ console.debug("received reload message", reloadMessage);
55
41
  }
42
+ reloader.changes.value.push(reloadMessage);
56
43
  reloader.changes.onchange();
44
+ if (reloader.autoreload.enabled) {
45
+ reloader.reload();
46
+ } else {
47
+ reloader.status.goTo("can_reload");
48
+ }
49
+ },
50
+ remove: (reloadMessage) => {
51
+ const index = reloader.changes.value.indexOf(reloadMessage);
52
+ if (index > -1) {
53
+ reloader.changes.value.splice(index, 1);
54
+ if (reloader.changes.value.length === 0) {
55
+ reloader.status.goTo("idle");
56
+ }
57
+ reloader.changes.onchange();
58
+ }
59
+ },
60
+ },
61
+ currentExecution: null,
62
+ reload: () => {
63
+ const someEffectIsFullReload = reloader.changes.value.some(
64
+ (reloadMessage) => reloadMessage.type === "full",
65
+ );
66
+ if (someEffectIsFullReload) {
67
+ reloadHtmlPage();
68
+ return;
57
69
  }
70
+ reloader.status.goTo("reloading");
71
+ const onApplied = (reloadMessage) => {
72
+ reloader.changes.remove(reloadMessage);
73
+ };
74
+ const setReloadMessagePromise = (reloadMessage, promise) => {
75
+ promise.then(
76
+ () => {
77
+ onApplied(reloadMessage);
78
+ reloader.currentExecution = null;
79
+ },
80
+ (e) => {
81
+ reloader.status.goTo("failed");
82
+ if (typeof window.reportError === "function") {
83
+ window.reportError(e);
84
+ } else {
85
+ console.error(e);
86
+ }
87
+ console.error(
88
+ `[jsenv] Hot reload failed after ${reloadMessage.reason}.
89
+ This could be due to syntax errors or importing non-existent modules (see errors in console)`,
90
+ );
91
+ reloader.currentExecution = null;
92
+ },
93
+ );
94
+ };
95
+ reloader.changes.value.forEach((reloadMessage) => {
96
+ if (reloadMessage.type === "hot") {
97
+ const promise = addToHotQueue(() => {
98
+ return applyHotReload(reloadMessage);
99
+ });
100
+ setReloadMessagePromise(reloadMessage, promise);
101
+ } else {
102
+ setReloadMessagePromise(reloadMessage, Promise.resolve());
103
+ }
104
+ });
58
105
  },
59
- },
60
- currentExecution: null,
61
- reload: () => {
62
- const someEffectIsFullReload = reloader.changes.value.some(
63
- (reloadMessage) => reloadMessage.type === "full",
64
- );
65
- if (someEffectIsFullReload) {
66
- reloadHtmlPage();
106
+ };
107
+
108
+ let pendingCallbacks = [];
109
+ let running = false;
110
+ const addToHotQueue = async (callback) => {
111
+ pendingCallbacks.push(callback);
112
+ dequeue();
113
+ };
114
+ const dequeue = async () => {
115
+ if (running) {
67
116
  return;
68
117
  }
69
- reloader.status.goTo("reloading");
70
- const onApplied = (reloadMessage) => {
71
- reloader.changes.remove(reloadMessage);
72
- };
73
- const setReloadMessagePromise = (reloadMessage, promise) => {
74
- promise.then(
75
- () => {
76
- onApplied(reloadMessage);
77
- reloader.currentExecution = null;
78
- },
79
- (e) => {
80
- reloader.status.goTo("failed");
81
- if (typeof window.reportError === "function") {
82
- window.reportError(e);
83
- } else {
84
- console.error(e);
85
- }
86
- console.error(
87
- `[jsenv] Hot reload failed after ${reloadMessage.reason}.
88
- This could be due to syntax errors or importing non-existent modules (see errors in console)`,
89
- );
90
- reloader.currentExecution = null;
91
- },
92
- );
93
- };
94
- reloader.changes.value.forEach((reloadMessage) => {
95
- if (reloadMessage.type === "hot") {
96
- const promise = addToHotQueue(() => {
97
- return applyHotReload(reloadMessage);
98
- });
99
- setReloadMessagePromise(reloadMessage, promise);
100
- } else {
101
- setReloadMessagePromise(reloadMessage, Promise.resolve());
118
+ const callbacks = pendingCallbacks.slice();
119
+ pendingCallbacks = [];
120
+ running = true;
121
+ try {
122
+ await callbacks.reduce(async (previous, callback) => {
123
+ await previous;
124
+ await callback();
125
+ }, Promise.resolve());
126
+ } finally {
127
+ running = false;
128
+ if (pendingCallbacks.length) {
129
+ dequeue();
102
130
  }
103
- });
104
- },
105
- };
106
-
107
- let pendingCallbacks = [];
108
- let running = false;
109
- const addToHotQueue = async (callback) => {
110
- pendingCallbacks.push(callback);
111
- dequeue();
112
- };
113
- const dequeue = async () => {
114
- if (running) {
115
- return;
116
- }
117
- const callbacks = pendingCallbacks.slice();
118
- pendingCallbacks = [];
119
- running = true;
120
- try {
121
- await callbacks.reduce(async (previous, callback) => {
122
- await previous;
123
- await callback();
124
- }, Promise.resolve());
125
- } finally {
126
- running = false;
127
- if (pendingCallbacks.length) {
128
- dequeue();
129
131
  }
130
- }
131
- };
132
+ };
132
133
 
133
- const applyHotReload = async ({ cause, hotInstructions }) => {
134
- await hotInstructions.reduce(
135
- async (previous, { type, boundary, acceptedBy }) => {
136
- await previous;
134
+ const applyHotReload = async ({ cause, hotInstructions }) => {
135
+ for (const instruction of hotInstructions) {
136
+ const { type, boundary, acceptedBy } = instruction;
137
137
 
138
138
  const hot = Date.now();
139
139
  const urlToFetch = new URL(boundary, `${window.location.origin}/`).href;
@@ -141,7 +141,6 @@ const applyHotReload = async ({ cause, hotInstructions }) => {
141
141
  // there is no url hot meta when:
142
142
  // - code was not executed (code splitting with dynamic import)
143
143
  // - import.meta.hot.accept() is not called (happens for HTML and CSS)
144
-
145
144
  if (type === "prune") {
146
145
  if (urlHotMeta) {
147
146
  delete urlHotMetas[urlToFetch];
@@ -154,9 +153,8 @@ const applyHotReload = async ({ cause, hotInstructions }) => {
154
153
  console.groupEnd();
155
154
  }
156
155
  }
157
- return null;
156
+ continue;
158
157
  }
159
-
160
158
  if (acceptedBy === boundary) {
161
159
  console.groupCollapsed(`[jsenv] hot reloading ${boundary} (${cause})`);
162
160
  } else {
@@ -167,7 +165,7 @@ const applyHotReload = async ({ cause, hotInstructions }) => {
167
165
  if (type === "js_module") {
168
166
  if (!urlHotMeta) {
169
167
  // code was not executed, no need to re-execute it
170
- return null;
168
+ continue;
171
169
  }
172
170
  if (urlHotMeta.disposeCallback) {
173
171
  console.log(`call dispose callback`);
@@ -184,18 +182,23 @@ const applyHotReload = async ({ cause, hotInstructions }) => {
184
182
  }
185
183
  console.log(`js module import done`);
186
184
  console.groupEnd();
187
- return namespace;
185
+ continue;
188
186
  }
189
187
  if (type === "html") {
190
- const isRootHtmlFile =
191
- window.location.pathname === "/" &&
192
- new URL(urlToFetch).pathname.slice(1).indexOf("/") === -1;
188
+ let isRootHtmlFile;
189
+ if (window.location.pathname === "/") {
190
+ if (new URL(urlToFetch).pathname.slice(1).indexOf("/") === -1) {
191
+ isRootHtmlFile = true;
192
+ } else if (new URL(urlToFetch).pathname === mainFilePath) {
193
+ isRootHtmlFile = true;
194
+ }
195
+ }
193
196
  if (
194
197
  !isRootHtmlFile &&
195
198
  !compareTwoUrlPaths(urlToFetch, window.location.href)
196
199
  ) {
197
200
  // we are not in that HTML page
198
- return null;
201
+ continue;
199
202
  }
200
203
  const urlToReload = new URL(acceptedBy, `${window.location.origin}/`)
201
204
  .href;
@@ -213,18 +216,16 @@ const applyHotReload = async ({ cause, hotInstructions }) => {
213
216
  });
214
217
  }
215
218
  console.groupEnd();
216
- return null;
219
+ continue;
217
220
  }
218
221
  console.warn(`unknown update type: "${type}"`);
219
- return null;
222
+ }
223
+ };
224
+
225
+ window.__reloader__ = reloader;
226
+ window.__server_events__.listenEvents({
227
+ reload: (reloadServerEvent) => {
228
+ reloader.changes.add(reloadServerEvent.data);
220
229
  },
221
- Promise.resolve(),
222
- );
230
+ });
223
231
  };
224
-
225
- window.__reloader__ = reloader;
226
- window.__server_events__.listenEvents({
227
- reload: (reloadServerEvent) => {
228
- reloader.changes.add(reloadServerEvent.data);
229
- },
230
- });
@@ -26,12 +26,21 @@ export const jsenvPluginAutoreloadClient = () => {
26
26
  expectedType: "js_module",
27
27
  specifier: autoreloadClientFileUrl,
28
28
  });
29
+ const paramsJson = JSON.stringify(
30
+ {
31
+ mainFilePath: `/${htmlUrlInfo.kitchen.context.mainFilePath}`,
32
+ },
33
+ null,
34
+ " ",
35
+ );
29
36
  injectHtmlNodeAsEarlyAsPossible(
30
37
  htmlAst,
31
38
  createHtmlNode({
32
39
  tagName: "script",
33
40
  type: "module",
34
- src: autoreloadClientReference.generatedSpecifier,
41
+ textContent: `import { initAutoreload } from "${autoreloadClientReference.generatedSpecifier}";
42
+
43
+ initAutoreload(${paramsJson});`,
35
44
  }),
36
45
  "jsenv:autoreload_client",
37
46
  );
@@ -38,7 +38,10 @@ export const jsenvPluginAutoreloadServer = ({
38
38
  : `a dependent file accepts hot reload`,
39
39
  };
40
40
  }
41
- if (urlInfo.data.hotDecline) {
41
+ if (
42
+ urlInfo.data.hotDecline ||
43
+ urlInfo.firstReference?.type === "http_request"
44
+ ) {
42
45
  return {
43
46
  declined: true,
44
47
  reason: `file declines hot reload`,
@@ -0,0 +1,20 @@
1
+ <!doctype html>
2
+ <html>
3
+ <head>
4
+ <title>Directory explorer</title>
5
+ <meta charset="utf-8" />
6
+ <link rel="icon" href="data:," />
7
+ </head>
8
+
9
+ <body>
10
+ <h1>
11
+ <a jsenv-ignore href="/${directoryRelativeUrl}"
12
+ >/${directoryRelativeUrl}</a
13
+ >
14
+ directory content:
15
+ </h1>
16
+ <ul>
17
+ ${directoryContent}
18
+ </ul>
19
+ </body>
20
+ </html>
@@ -0,0 +1,21 @@
1
+ <!doctype html>
2
+ <html>
3
+ <head>
4
+ <title>404 ENOENT</title>
5
+ <meta charset="utf-8" />
6
+ <link rel="icon" href="data:," />
7
+ </head>
8
+
9
+ <body>
10
+ <p>No entry on the filesystem for <strong>/${fileRelativeUrl}</strong></p>
11
+ <p>
12
+ <a jsenv-ignore href="/${parentDirectoryRelativeUrl}"
13
+ >/${parentDirectoryRelativeUrl}</a
14
+ >
15
+ directory content:
16
+ </p>
17
+ <ul>
18
+ ${parentDirectoryContent}
19
+ </ul>
20
+ </body>
21
+ </html>
@@ -0,0 +1,18 @@
1
+ <!doctype html>
2
+ <html>
3
+ <head>
4
+ <title>404 ENOENT</title>
5
+ <meta charset="utf-8" />
6
+ <link rel="icon" href="data:," />
7
+ </head>
8
+
9
+ <body>
10
+ <p>No entry on the filesystem for <strong>/${fileRelativeUrl}</strong></p>
11
+ <p>
12
+ <a jsenv-ignore href="/${parentDirectoryRelativeUrl}"
13
+ >/${parentDirectoryRelativeUrl}</a
14
+ >
15
+ directory is empty.
16
+ </p>
17
+ </body>
18
+ </html>