@webjsdev/server 0.8.77 → 0.8.79
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 +1 -1
- package/src/actions.js +2 -4
- package/src/api.js +2 -4
- package/src/component-elision.js +7 -2
- package/src/dev/handler.js +2 -4
- package/src/dev/serve.js +4 -8
- package/src/dev-import.js +34 -0
- package/src/env-schema.js +2 -4
- package/src/form-action-identity.js +2 -1
- package/src/instrumentation.js +2 -4
- package/src/listener-core.js +2 -4
- package/src/ssr/render.js +2 -4
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@webjsdev/server",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.79",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "The server for WebJs, a full-stack JavaScript framework built on web components with server-side rendering and no build step. Provides the file-based router, SSR, server actions, route handlers, middleware, and live reload on Node 24+ or Bun.",
|
|
6
6
|
"main": "index.js",
|
package/src/actions.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { digestHex } from './crypto-utils.js';
|
|
2
|
-
import { pathToFileURL } from 'node:url';
|
|
3
2
|
import { readFile } from 'node:fs/promises';
|
|
4
3
|
import { join, sep } from 'node:path';
|
|
5
4
|
import { walk } from './fs-walk.js';
|
|
@@ -21,6 +20,7 @@ import { getBodyLimits } from './context.js';
|
|
|
21
20
|
import { basePath } from './importmap.js';
|
|
22
21
|
import { withBasePath } from './base-path.js';
|
|
23
22
|
import { FORM_ACTION_ID_KEY } from '@webjsdev/core';
|
|
23
|
+
import { devImportSpecifier } from './dev-import.js';
|
|
24
24
|
|
|
25
25
|
/**
|
|
26
26
|
* The JSON / RPC body cap in effect for the current request: the per-request
|
|
@@ -638,7 +638,5 @@ async function actionErrorResponse(err, dev) {
|
|
|
638
638
|
* @param {boolean} dev
|
|
639
639
|
*/
|
|
640
640
|
async function loadModule(file, dev) {
|
|
641
|
-
|
|
642
|
-
const bust = dev ? `?t=${Date.now()}-${Math.random().toString(36).slice(2)}` : '';
|
|
643
|
-
return import(url + bust);
|
|
641
|
+
return import(devImportSpecifier(file, dev));
|
|
644
642
|
}
|
package/src/api.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { pathToFileURL } from 'node:url';
|
|
2
1
|
import { makeThenable } from './thenable-params.js';
|
|
2
|
+
import { devImportSpecifier } from './dev-import.js';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Dispatch an incoming request to a matched API route.
|
|
@@ -14,9 +14,7 @@ import { makeThenable } from './thenable-params.js';
|
|
|
14
14
|
* @returns {Promise<Response>}
|
|
15
15
|
*/
|
|
16
16
|
export async function handleApi(route, params, webRequest, dev) {
|
|
17
|
-
const
|
|
18
|
-
const bust = dev ? `?t=${Date.now()}-${Math.random().toString(36).slice(2)}` : '';
|
|
19
|
-
const mod = await import(url + bust);
|
|
17
|
+
const mod = await import(devImportSpecifier(route.file, dev));
|
|
20
18
|
const method = webRequest.method.toUpperCase();
|
|
21
19
|
const handler = mod[method];
|
|
22
20
|
if (!handler) {
|
package/src/component-elision.js
CHANGED
|
@@ -1608,6 +1608,9 @@ export function lazyComponentFiles(components, elidable) {
|
|
|
1608
1608
|
return out;
|
|
1609
1609
|
}
|
|
1610
1610
|
|
|
1611
|
+
/** A test module (the same rule the component scanner skips). */
|
|
1612
|
+
const TEST_FILE_RE = /\.(test|spec)\.m?[jt]sx?$/;
|
|
1613
|
+
|
|
1611
1614
|
/** `import <bindings> from 'x'` (the bindings part is captured). */
|
|
1612
1615
|
const BINDING_IMPORT_RE = /\bimport\s+(type\s+)?([\w$*{}\s,]+?)\s+from\s+(['"])([^'"]+)\3/g;
|
|
1613
1616
|
/** `export { a } from 'x'` / `export * from 'x'` (a re-export needs the module too). */
|
|
@@ -1636,7 +1639,9 @@ function typeOnlyClause(typeKw, clause) {
|
|
|
1636
1639
|
* skip a module the browser still fetches up front, which only makes it slower.
|
|
1637
1640
|
*
|
|
1638
1641
|
* Only importers whose graph edges reach a lazy file are read. A server file is
|
|
1639
|
-
* never served,
|
|
1642
|
+
* never served, and a test file (`*.test.*` / `*.spec.*`) never ships to a
|
|
1643
|
+
* page, so their imports do not count: a component's own browser test imports
|
|
1644
|
+
* it with bindings, and that must not make it eager in the app.
|
|
1640
1645
|
*
|
|
1641
1646
|
* @param {Map<string, string[]>} lazyFiles
|
|
1642
1647
|
* @param {import('./module-graph.js').ModuleGraph | undefined} moduleGraph
|
|
@@ -1650,7 +1655,7 @@ export async function bindingImportedLazyFiles(lazyFiles, moduleGraph, readFileF
|
|
|
1650
1655
|
const out = new Set();
|
|
1651
1656
|
if (!lazyFiles.size || !moduleGraph) return out;
|
|
1652
1657
|
for (const [importer, deps] of moduleGraph) {
|
|
1653
|
-
if (SERVER_FILE_RE.test(importer)) continue;
|
|
1658
|
+
if (SERVER_FILE_RE.test(importer) || TEST_FILE_RE.test(importer)) continue;
|
|
1654
1659
|
let reaches = false;
|
|
1655
1660
|
for (const d of deps) if (lazyFiles.has(d) && !out.has(d)) { reaches = true; break; }
|
|
1656
1661
|
if (!reaches) continue;
|
package/src/dev/handler.js
CHANGED
|
@@ -2,7 +2,6 @@ import { readFile } from 'node:fs/promises';
|
|
|
2
2
|
import { existsSync, readFileSync, realpathSync } from 'node:fs';
|
|
3
3
|
import { createHash } from 'node:crypto';
|
|
4
4
|
import { join, resolve, sep } from 'node:path';
|
|
5
|
-
import { pathToFileURL } from 'node:url';
|
|
6
5
|
|
|
7
6
|
// Server-side `.ts` imports are handled natively by Node 24+'s default
|
|
8
7
|
// type-stripping (`process.features.typescript === 'strip'`) or by Bun. The
|
|
@@ -73,6 +72,7 @@ import {
|
|
|
73
72
|
import {
|
|
74
73
|
handleCore, loadMiddleware, tryServeFrameworkStatic, tryServePublicAsset,
|
|
75
74
|
} from './serve.js';
|
|
75
|
+
import { devImportSpecifier } from '../dev-import.js';
|
|
76
76
|
|
|
77
77
|
/**
|
|
78
78
|
* A short content digest of a single file's bytes for the app-source deploy
|
|
@@ -858,9 +858,7 @@ export async function createRequestHandler(opts) {
|
|
|
858
858
|
}
|
|
859
859
|
if (!file) { readinessFn = null; return null; }
|
|
860
860
|
try {
|
|
861
|
-
const
|
|
862
|
-
const bust = dev ? `?t=${Date.now()}-${Math.random().toString(36).slice(2)}` : '';
|
|
863
|
-
const mod = await import(url + bust);
|
|
861
|
+
const mod = await import(devImportSpecifier(file, dev));
|
|
864
862
|
readinessFn = typeof mod.default === 'function' ? mod.default : null;
|
|
865
863
|
} catch (e) {
|
|
866
864
|
logger.error?.(`[webjs] failed to load readiness.{js,ts}`, { err: String(e) });
|
package/src/dev/serve.js
CHANGED
|
@@ -9,7 +9,6 @@
|
|
|
9
9
|
*/
|
|
10
10
|
import { stat, readFile } from 'node:fs/promises';
|
|
11
11
|
import { join, extname, resolve, relative, sep } from 'node:path';
|
|
12
|
-
import { pathToFileURL } from 'node:url';
|
|
13
12
|
|
|
14
13
|
import { matchPage, matchApi } from '../router.js';
|
|
15
14
|
import { ssrPage, ssrNotFound } from '../ssr.js';
|
|
@@ -35,6 +34,7 @@ import { versionModuleImports, withAssetHash } from '../asset-hash.js';
|
|
|
35
34
|
import { toUrlPath } from '../ssr/preloads.js';
|
|
36
35
|
import { BUFFERED_MARKER } from '../conditional-get.js';
|
|
37
36
|
import { MIME, TS_CACHE_MAX, exists, reloadClientJs, reloadWorkerJs } from './helpers.js';
|
|
37
|
+
import { devImportSpecifier } from '../dev-import.js';
|
|
38
38
|
|
|
39
39
|
/**
|
|
40
40
|
* Serve framework-internal static assets that depend on NEITHER the whole-app
|
|
@@ -363,7 +363,7 @@ export async function handleCore(req, ctx) {
|
|
|
363
363
|
const meta = state.routeTable.metadataRoutes.find((r) => r.urlPath === path);
|
|
364
364
|
if (meta) {
|
|
365
365
|
try {
|
|
366
|
-
const mod = await import(
|
|
366
|
+
const mod = await import(devImportSpecifier(meta.file, dev));
|
|
367
367
|
if (mod.default) {
|
|
368
368
|
const result = await mod.default();
|
|
369
369
|
// If the function returns a Response, use it directly.
|
|
@@ -582,9 +582,7 @@ export async function runWithSegmentMiddleware(req, files, terminal, dev) {
|
|
|
582
582
|
const handlers = [];
|
|
583
583
|
for (const f of files) {
|
|
584
584
|
try {
|
|
585
|
-
const
|
|
586
|
-
const bust = dev ? `?t=${Date.now()}-${Math.random().toString(36).slice(2)}` : '';
|
|
587
|
-
const mod = await import(url + bust);
|
|
585
|
+
const mod = await import(devImportSpecifier(f, dev));
|
|
588
586
|
if (typeof mod.default === 'function') handlers.push(mod.default);
|
|
589
587
|
} catch {
|
|
590
588
|
// Bad middleware file: skip; top-level error handler will catch real problems.
|
|
@@ -627,10 +625,8 @@ export async function loadMiddleware(appDir, dev, logger) {
|
|
|
627
625
|
if (await exists(candidate)) { file = candidate; break; }
|
|
628
626
|
}
|
|
629
627
|
if (!file) return null;
|
|
630
|
-
const url = pathToFileURL(file).toString();
|
|
631
|
-
const bust = dev ? `?t=${Date.now()}-${Math.random().toString(36).slice(2)}` : '';
|
|
632
628
|
try {
|
|
633
|
-
const mod = await import(
|
|
629
|
+
const mod = await import(devImportSpecifier(file, dev));
|
|
634
630
|
return typeof mod.default === 'function' ? mod.default : null;
|
|
635
631
|
} catch (e) {
|
|
636
632
|
logger.error('failed to load root middleware', { file, err: String(e) });
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The import specifier for a module the dev server re-imports on every request
|
|
3
|
+
* (#1550): a page, layout, route handler, action module, middleware, and so on.
|
|
4
|
+
*
|
|
5
|
+
* In dev the specifier carries a fresh `?t=` query, so each request gets a new
|
|
6
|
+
* module instance and a just-edited top-level file is served without waiting
|
|
7
|
+
* for a restart. Node keys its ESM cache by the full file URL, query included.
|
|
8
|
+
* Bun keys its cache by path and DROPS the query of a `file://` specifier, so
|
|
9
|
+
* the same URL-plus-query is a silent no-op there: on Bun the query must ride a
|
|
10
|
+
* plain absolute path, which Bun loads fresh (and hands to a `Bun.plugin`
|
|
11
|
+
* `onLoad` with the query intact).
|
|
12
|
+
*
|
|
13
|
+
* A path that already contains `?` or `#` cannot carry a query unambiguously as
|
|
14
|
+
* a plain path, so on Bun it falls back to the file URL (no fresh import; the
|
|
15
|
+
* dev supervisor's restart still picks the edit up).
|
|
16
|
+
*
|
|
17
|
+
* Outside dev the specifier is the plain file URL, exactly as before.
|
|
18
|
+
*/
|
|
19
|
+
import { pathToFileURL } from 'node:url';
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* @param {string} file absolute file path
|
|
23
|
+
* @param {boolean} dev whether to cache-bust
|
|
24
|
+
* @param {{ bun?: boolean, now?: () => number }} [opts] test seams: the runtime (default: detected) and the clock
|
|
25
|
+
* @returns {string}
|
|
26
|
+
*/
|
|
27
|
+
export function devImportSpecifier(file, dev, opts = {}) {
|
|
28
|
+
const url = pathToFileURL(file).toString();
|
|
29
|
+
if (!dev) return url;
|
|
30
|
+
const bun = opts.bun ?? Boolean(typeof process !== 'undefined' && process.versions && process.versions.bun);
|
|
31
|
+
const bust = `?t=${(opts.now ?? Date.now)()}-${Math.random().toString(36).slice(2)}`;
|
|
32
|
+
if (bun && !/[?#]/.test(file)) return file + bust;
|
|
33
|
+
return url + bust;
|
|
34
|
+
}
|
package/src/env-schema.js
CHANGED
|
@@ -32,8 +32,8 @@
|
|
|
32
32
|
* the Node-version preflight).
|
|
33
33
|
*/
|
|
34
34
|
import { join } from 'node:path';
|
|
35
|
-
import { pathToFileURL } from 'node:url';
|
|
36
35
|
import { stat } from 'node:fs/promises';
|
|
36
|
+
import { devImportSpecifier } from './dev-import.js';
|
|
37
37
|
|
|
38
38
|
/** Field type names a schema may declare. */
|
|
39
39
|
const KNOWN_TYPES = new Set(['string', 'number', 'boolean', 'url', 'enum']);
|
|
@@ -223,9 +223,7 @@ export async function loadEnvSchema(appDir, opts = {}) {
|
|
|
223
223
|
}
|
|
224
224
|
}
|
|
225
225
|
if (!file) return null;
|
|
226
|
-
const
|
|
227
|
-
const bust = opts.dev ? `?t=${Date.now()}-${Math.random().toString(36).slice(2)}` : '';
|
|
228
|
-
const mod = await import(url + bust);
|
|
226
|
+
const mod = await import(devImportSpecifier(file, Boolean(opts.dev)));
|
|
229
227
|
return mod.default ?? null;
|
|
230
228
|
}
|
|
231
229
|
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
|
|
17
17
|
import { actionIdentitiesOf, identityHookInstalled } from './action-seed.js';
|
|
18
18
|
import { pathToFileURL } from 'node:url';
|
|
19
|
+
import { devImportSpecifier } from './dev-import.js';
|
|
19
20
|
|
|
20
21
|
/**
|
|
21
22
|
* Resolved identities for functions the hook never registered. Only ever
|
|
@@ -282,7 +283,7 @@ export async function lookupActionIdentity(idx, id) {
|
|
|
282
283
|
if (!file) return { ok: false, reason: 'skew' };
|
|
283
284
|
let module;
|
|
284
285
|
try {
|
|
285
|
-
module = await import(
|
|
286
|
+
module = await import(devImportSpecifier(file, Boolean(idx.dev)));
|
|
286
287
|
} catch (error) {
|
|
287
288
|
return { ok: false, reason: 'load-failed', error };
|
|
288
289
|
}
|
package/src/instrumentation.js
CHANGED
|
@@ -16,9 +16,9 @@
|
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
18
|
import { join } from 'node:path';
|
|
19
|
-
import { pathToFileURL } from 'node:url';
|
|
20
19
|
import { stat } from 'node:fs/promises';
|
|
21
20
|
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
21
|
+
import { devImportSpecifier } from './dev-import.js';
|
|
22
22
|
|
|
23
23
|
/**
|
|
24
24
|
* Per-boot handoff channel for a `register()`-registered error sink. An
|
|
@@ -86,9 +86,7 @@ export async function runInstrumentation(appDir, opts = {}) {
|
|
|
86
86
|
const store = { onError: null };
|
|
87
87
|
await _als.run(store, async () => {
|
|
88
88
|
try {
|
|
89
|
-
const
|
|
90
|
-
const bust = dev ? `?t=${Date.now()}-${Math.random().toString(36).slice(2)}` : '';
|
|
91
|
-
const mod = await import(url + bust);
|
|
89
|
+
const mod = await import(devImportSpecifier(file, dev));
|
|
92
90
|
const register = typeof mod.default === 'function'
|
|
93
91
|
? mod.default
|
|
94
92
|
: (typeof mod.register === 'function' ? mod.register : null);
|
package/src/listener-core.js
CHANGED
|
@@ -15,7 +15,6 @@
|
|
|
15
15
|
* supplies its own client wrapper to `SseHub`, its own socket-to-`Request` bridge,
|
|
16
16
|
* and its own `closeServer` thunk, and reuses everything else verbatim.
|
|
17
17
|
*/
|
|
18
|
-
import { pathToFileURL } from 'node:url';
|
|
19
18
|
import {
|
|
20
19
|
createBrotliCompress, createGzip, createDeflate,
|
|
21
20
|
brotliCompressSync, gzipSync, deflateSync,
|
|
@@ -24,6 +23,7 @@ import {
|
|
|
24
23
|
import { stripBasePath } from './base-path.js';
|
|
25
24
|
import { randomUUID } from 'node:crypto';
|
|
26
25
|
import { isWatchError } from './dev/watch-tree.js';
|
|
26
|
+
import { devImportSpecifier } from './dev-import.js';
|
|
27
27
|
|
|
28
28
|
/** The dev live-reload SSE path (matched after base-path stripping). */
|
|
29
29
|
export const EVENTS_PATH = '/__webjs/events';
|
|
@@ -329,9 +329,7 @@ export async function readBufferedOrStream(web, maxBytes) {
|
|
|
329
329
|
* @param {boolean} dev
|
|
330
330
|
*/
|
|
331
331
|
export function loadWsModule(file, dev) {
|
|
332
|
-
|
|
333
|
-
const bust = dev ? `?t=${Date.now()}-${Math.random().toString(36).slice(2)}` : '';
|
|
334
|
-
return import(url + bust);
|
|
332
|
+
return import(devImportSpecifier(file, dev));
|
|
335
333
|
}
|
|
336
334
|
|
|
337
335
|
/**
|
package/src/ssr/render.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { pathToFileURL } from 'node:url';
|
|
2
1
|
import { resolve } from 'node:path';
|
|
3
2
|
import { renderToString, isNotFound, isRedirect, isForbidden, isUnauthorized } from '@webjsdev/core';
|
|
4
3
|
import { vendorPreloadTargets } from '../importmap.js';
|
|
@@ -18,6 +17,7 @@ import { escapeHtml } from './escape.js';
|
|
|
18
17
|
import {
|
|
19
18
|
cachedHtmlResponse, getNonce, htmlResponse, streamingHtmlResponse,
|
|
20
19
|
} from './responses.js';
|
|
20
|
+
import { devImportSpecifier } from '../dev-import.js';
|
|
21
21
|
|
|
22
22
|
|
|
23
23
|
/**
|
|
@@ -107,9 +107,7 @@ export function privateFragment(res) {
|
|
|
107
107
|
* @param {boolean} dev
|
|
108
108
|
*/
|
|
109
109
|
async function loadModule(file, dev) {
|
|
110
|
-
|
|
111
|
-
const bust = dev ? `?t=${Date.now()}-${Math.random().toString(36).slice(2)}` : '';
|
|
112
|
-
return import(url + bust);
|
|
110
|
+
return import(devImportSpecifier(file, dev));
|
|
113
111
|
}
|
|
114
112
|
|
|
115
113
|
/**
|