@bleedingdev/modern-js-server 3.5.0-ultramodern.98 → 3.8.1-ultramodern.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.
- package/dist/cjs/createDevServer.js +66 -20
- package/dist/cjs/dev-tools/reloadManager.js +226 -0
- package/dist/cjs/dev-tools/runtimeOptions.js +73 -0
- package/dist/cjs/dev-tools/watcher/dependencyTree.js +14 -2
- package/dist/cjs/dev.js +67 -34
- package/dist/cjs/helpers/index.js +26 -65
- package/dist/cjs/helpers/mock.js +21 -9
- package/dist/esm/createDevServer.mjs +67 -21
- package/dist/esm/dev-tools/reloadManager.mjs +185 -0
- package/dist/esm/dev-tools/runtimeOptions.mjs +35 -0
- package/dist/esm/dev-tools/watcher/dependencyTree.mjs +4 -2
- package/dist/esm/dev.mjs +64 -34
- package/dist/esm/helpers/index.mjs +5 -38
- package/dist/esm/helpers/mock.mjs +15 -9
- package/dist/esm-node/createDevServer.mjs +67 -21
- package/dist/esm-node/dev-tools/reloadManager.mjs +186 -0
- package/dist/esm-node/dev-tools/runtimeOptions.mjs +36 -0
- package/dist/esm-node/dev-tools/watcher/dependencyTree.mjs +4 -2
- package/dist/esm-node/dev.mjs +64 -34
- package/dist/esm-node/helpers/index.mjs +5 -38
- package/dist/esm-node/helpers/mock.mjs +15 -9
- package/dist/types/dev-tools/reloadManager.d.ts +88 -0
- package/dist/types/dev-tools/runtimeOptions.d.ts +27 -0
- package/dist/types/dev.d.ts +61 -2
- package/dist/types/helpers/index.d.ts +9 -4
- package/dist/types/helpers/mock.d.ts +4 -2
- package/package.json +8 -8
- package/rstest.config.mts +1 -0
package/dist/esm-node/dev.mjs
CHANGED
|
@@ -1,41 +1,15 @@
|
|
|
1
1
|
import "node:module";
|
|
2
2
|
import { connectMid2HonoMid } from "@modern-js/server-core/node";
|
|
3
|
-
import { API_DIR, SHARED_DIR } from "@modern-js/utils";
|
|
4
|
-
import { getDevOptions, getMockMiddleware, initFileReader, onRepack, startWatcher } from "./helpers/index.mjs";
|
|
5
|
-
const
|
|
3
|
+
import { API_DIR, SHARED_DIR, logger } from "@modern-js/utils";
|
|
4
|
+
import { getDevOptions, getMockMiddleware, initFileReader, isPathInsideDirectory, onRepack, resolveMockDirectory, startWatcher } from "./helpers/index.mjs";
|
|
5
|
+
const devRuntimeMiddlewarePlugin = (options, compiler)=>({
|
|
6
6
|
name: '@modern-js/plugin-dev',
|
|
7
7
|
setup (api) {
|
|
8
|
-
const {
|
|
9
|
-
const closeCb = [];
|
|
8
|
+
const { pwd, builderDevServer } = options;
|
|
10
9
|
const dev = getDevOptions(options.dev);
|
|
11
10
|
api.onPrepare(async ()=>{
|
|
12
|
-
const { middlewares: builderMiddlewares
|
|
13
|
-
|
|
14
|
-
const { middlewares, distDirectory, nodeServer, apiDirectory, sharedDirectory, serverBase } = api.getServerContext();
|
|
15
|
-
connectWebSocket && nodeServer && connectWebSocket({
|
|
16
|
-
server: nodeServer
|
|
17
|
-
});
|
|
18
|
-
const hooks = api.getHooks();
|
|
19
|
-
builder?.onDevCompileDone(({ stats })=>{
|
|
20
|
-
if ('server' !== stats.toJson({
|
|
21
|
-
all: false
|
|
22
|
-
}).name) onRepack(distDirectory, hooks);
|
|
23
|
-
});
|
|
24
|
-
const { watchOptions } = config.server;
|
|
25
|
-
const watcher = startWatcher({
|
|
26
|
-
pwd,
|
|
27
|
-
distDir: distDirectory,
|
|
28
|
-
apiDir: apiDirectory || API_DIR,
|
|
29
|
-
sharedDir: sharedDirectory || SHARED_DIR,
|
|
30
|
-
watchOptions,
|
|
31
|
-
server: serverBase
|
|
32
|
-
});
|
|
33
|
-
closeCb.push(watcher.close.bind(watcher));
|
|
34
|
-
closeCb.length > 0 && nodeServer?.on('close', ()=>{
|
|
35
|
-
closeCb.forEach((cb)=>{
|
|
36
|
-
cb();
|
|
37
|
-
});
|
|
38
|
-
});
|
|
11
|
+
const { middlewares: builderMiddlewares } = builderDevServer || {};
|
|
12
|
+
const { middlewares } = api.getServerContext();
|
|
39
13
|
const before = [];
|
|
40
14
|
const after = [];
|
|
41
15
|
const { setupMiddlewares = [] } = dev;
|
|
@@ -53,7 +27,7 @@ const devPlugin = (options, compiler)=>({
|
|
|
53
27
|
handler: connectMid2HonoMid(middleware)
|
|
54
28
|
});
|
|
55
29
|
});
|
|
56
|
-
const mockMiddleware = await getMockMiddleware(pwd);
|
|
30
|
+
const mockMiddleware = await getMockMiddleware(pwd, options.config?.dev?.mockDir);
|
|
57
31
|
middlewares.push({
|
|
58
32
|
name: 'mock-dev',
|
|
59
33
|
handler: mockMiddleware
|
|
@@ -75,4 +49,60 @@ const devPlugin = (options, compiler)=>({
|
|
|
75
49
|
});
|
|
76
50
|
}
|
|
77
51
|
});
|
|
78
|
-
|
|
52
|
+
function setupDevInfra({ config, pwd, distDir, apiDir, sharedDir, builder, builderDevServer, getRuntimeServer, onFileChange, onClose, nodeServer }) {
|
|
53
|
+
const { close, connectWebSocket } = builderDevServer || {};
|
|
54
|
+
const closeCb = [];
|
|
55
|
+
close && closeCb.push(close);
|
|
56
|
+
connectWebSocket && nodeServer && connectWebSocket({
|
|
57
|
+
server: nodeServer
|
|
58
|
+
});
|
|
59
|
+
builder?.onDevCompileDone(({ stats })=>{
|
|
60
|
+
if ('server' !== stats.toJson({
|
|
61
|
+
all: false
|
|
62
|
+
}).name) {
|
|
63
|
+
const runtimeServer = getRuntimeServer();
|
|
64
|
+
if (runtimeServer) onRepack(distDir, runtimeServer.hooks);
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
const { watchOptions } = config.server;
|
|
68
|
+
const mockDir = config.dev?.mockDir;
|
|
69
|
+
const mockPath = resolveMockDirectory(pwd, mockDir);
|
|
70
|
+
const watcher = startWatcher({
|
|
71
|
+
pwd,
|
|
72
|
+
distDir,
|
|
73
|
+
apiDir: apiDir || API_DIR,
|
|
74
|
+
sharedDir: sharedDir || SHARED_DIR,
|
|
75
|
+
mockDir,
|
|
76
|
+
watchOptions,
|
|
77
|
+
onChange: (filepath, event)=>{
|
|
78
|
+
const runtimeServer = getRuntimeServer();
|
|
79
|
+
if (runtimeServer && !isPathInsideDirectory(filepath, mockPath)) {
|
|
80
|
+
const fileChangeEvent = {
|
|
81
|
+
type: 'file-change',
|
|
82
|
+
payload: [
|
|
83
|
+
{
|
|
84
|
+
filename: filepath,
|
|
85
|
+
event
|
|
86
|
+
}
|
|
87
|
+
]
|
|
88
|
+
};
|
|
89
|
+
Promise.resolve(runtimeServer.hooks.onReset.call({
|
|
90
|
+
event: fileChangeEvent
|
|
91
|
+
})).catch((error)=>logger.error(error));
|
|
92
|
+
}
|
|
93
|
+
onFileChange(filepath, event);
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
closeCb.push(watcher.close.bind(watcher));
|
|
97
|
+
onClose && closeCb.push(onClose);
|
|
98
|
+
const close$ = ()=>{
|
|
99
|
+
closeCb.forEach((cb)=>{
|
|
100
|
+
cb();
|
|
101
|
+
});
|
|
102
|
+
};
|
|
103
|
+
closeCb.length > 0 && nodeServer?.on('close', close$);
|
|
104
|
+
return {
|
|
105
|
+
close: close$
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
export { devRuntimeMiddlewarePlugin, setupDevInfra };
|
|
@@ -1,44 +1,16 @@
|
|
|
1
1
|
import __rslib_shim_module__ from "node:module";
|
|
2
2
|
const require = /*#__PURE__*/ __rslib_shim_module__.createRequire(/*#__PURE__*/ (()=>import.meta.url)());
|
|
3
|
-
import {
|
|
4
|
-
import { SERVER_BUNDLE_DIRECTORY, SERVER_DIR, logger } from "@modern-js/utils";
|
|
3
|
+
import { SERVER_BUNDLE_DIRECTORY, SERVER_DIR } from "@modern-js/utils";
|
|
5
4
|
import path from "path";
|
|
6
5
|
import dev_tools_watcher, { mergeWatchOptions } from "../dev-tools/watcher/index.mjs";
|
|
7
|
-
import {
|
|
8
|
-
import { debug } from "./utils.mjs";
|
|
6
|
+
import { resolveMockDirectory } from "./mock.mjs";
|
|
9
7
|
export * from "./devOptions.mjs";
|
|
10
8
|
export * from "./fileReader.mjs";
|
|
11
9
|
export * from "./mock.mjs";
|
|
12
10
|
export * from "./repack.mjs";
|
|
13
|
-
|
|
14
|
-
const { mock } = AGGRED_DIR;
|
|
15
|
-
const mockPath = path.normalize(path.join(pwd, mock));
|
|
16
|
-
const { hooks } = server;
|
|
17
|
-
if (filepath.startsWith(mockPath)) {
|
|
18
|
-
await initOrUpdateMockMiddlewares(pwd);
|
|
19
|
-
logger.info('Finish update the mock handlers');
|
|
20
|
-
} else try {
|
|
21
|
-
const fileChangeEvent = {
|
|
22
|
-
type: 'file-change',
|
|
23
|
-
payload: [
|
|
24
|
-
{
|
|
25
|
-
filename: filepath,
|
|
26
|
-
event
|
|
27
|
-
}
|
|
28
|
-
]
|
|
29
|
-
};
|
|
30
|
-
await hooks.onReset.call({
|
|
31
|
-
event: fileChangeEvent
|
|
32
|
-
});
|
|
33
|
-
debug(`Finish reload server, trigger by ${filepath} ${event}`);
|
|
34
|
-
} catch (e) {
|
|
35
|
-
logger.error(e);
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
function startWatcher({ pwd, distDir, apiDir, sharedDir, watchOptions, server }) {
|
|
39
|
-
const { mock } = AGGRED_DIR;
|
|
11
|
+
function startWatcher({ pwd, distDir, apiDir, sharedDir, mockDir, watchOptions, onChange }) {
|
|
40
12
|
const defaultWatched = [
|
|
41
|
-
|
|
13
|
+
path.join(resolveMockDirectory(pwd, mockDir), '**/*'),
|
|
42
14
|
`${SERVER_DIR}/**/*`,
|
|
43
15
|
`${apiDir}/**`,
|
|
44
16
|
`${sharedDir}/**/*`,
|
|
@@ -55,12 +27,7 @@ function startWatcher({ pwd, distDir, apiDir, sharedDir, watchOptions, server })
|
|
|
55
27
|
if (filepath.includes('-server-loaders.js')) return void delete require.cache[filepath];
|
|
56
28
|
watcher.updateDepTree();
|
|
57
29
|
watcher.cleanDepCache(filepath);
|
|
58
|
-
|
|
59
|
-
pwd,
|
|
60
|
-
filepath,
|
|
61
|
-
event,
|
|
62
|
-
server
|
|
63
|
-
});
|
|
30
|
+
onChange(filepath, event);
|
|
64
31
|
});
|
|
65
32
|
return watcher;
|
|
66
33
|
}
|
|
@@ -2,10 +2,15 @@ import "node:module";
|
|
|
2
2
|
import node_path from "node:path";
|
|
3
3
|
import { AGGRED_DIR } from "@modern-js/server-core";
|
|
4
4
|
import { connectMockMid2HonoMid } from "@modern-js/server-core/node";
|
|
5
|
-
import { fs } from "@modern-js/utils";
|
|
5
|
+
import { compatibleRequire, fs } from "@modern-js/utils";
|
|
6
6
|
import { match } from "path-to-regexp";
|
|
7
7
|
let mockAPIs = [];
|
|
8
8
|
let mockConfig;
|
|
9
|
+
const resolveMockDirectory = (pwd, mockDir = AGGRED_DIR.mock)=>node_path.resolve(pwd, mockDir);
|
|
10
|
+
const isPathInsideDirectory = (filepath, directory)=>{
|
|
11
|
+
const relativePath = node_path.relative(directory, node_path.resolve(filepath));
|
|
12
|
+
return '' === relativePath || '..' !== relativePath && !relativePath.startsWith(`..${node_path.sep}`) && !node_path.isAbsolute(relativePath);
|
|
13
|
+
};
|
|
9
14
|
const parseKey = (key)=>{
|
|
10
15
|
const _blank = ' ';
|
|
11
16
|
const splitted = key.split(_blank).filter(Boolean);
|
|
@@ -21,21 +26,22 @@ const parseKey = (key)=>{
|
|
|
21
26
|
path: key
|
|
22
27
|
};
|
|
23
28
|
};
|
|
24
|
-
const getMockModule = async (pwd)=>{
|
|
29
|
+
const getMockModule = async (pwd, mockDir)=>{
|
|
25
30
|
const exts = [
|
|
26
31
|
'.ts',
|
|
27
32
|
'.js'
|
|
28
33
|
];
|
|
29
34
|
let mockFilePath = '';
|
|
35
|
+
const mockDirectory = resolveMockDirectory(pwd, mockDir);
|
|
30
36
|
for (const ext of exts){
|
|
31
|
-
const maybeMatch = node_path.join(
|
|
37
|
+
const maybeMatch = node_path.join(mockDirectory, `index${ext}`);
|
|
32
38
|
if (await fs.pathExists(maybeMatch)) {
|
|
33
39
|
mockFilePath = maybeMatch;
|
|
34
40
|
break;
|
|
35
41
|
}
|
|
36
42
|
}
|
|
37
43
|
if (!mockFilePath) return;
|
|
38
|
-
const { default: mockHandlers, config } = await
|
|
44
|
+
const { default: mockHandlers, config } = await compatibleRequire(mockFilePath, false);
|
|
39
45
|
const enable = config?.enable;
|
|
40
46
|
if (false === enable) return;
|
|
41
47
|
if (!mockHandlers) throw new Error(`Mock file ${mockFilePath} parsed failed!`);
|
|
@@ -55,8 +61,8 @@ const getMatched = (request, mockApis)=>{
|
|
|
55
61
|
});
|
|
56
62
|
return matched;
|
|
57
63
|
};
|
|
58
|
-
async function initOrUpdateMockMiddlewares(pwd) {
|
|
59
|
-
const mockModule = await getMockModule(pwd);
|
|
64
|
+
async function initOrUpdateMockMiddlewares(pwd, mockDir) {
|
|
65
|
+
const mockModule = await getMockModule(pwd, mockDir);
|
|
60
66
|
mockConfig = mockModule?.config;
|
|
61
67
|
mockAPIs = Object.entries(mockModule?.mockHandlers || {}).map(([key, handler])=>{
|
|
62
68
|
const { method, path } = parseKey(key);
|
|
@@ -67,8 +73,8 @@ async function initOrUpdateMockMiddlewares(pwd) {
|
|
|
67
73
|
};
|
|
68
74
|
});
|
|
69
75
|
}
|
|
70
|
-
async function getMockMiddleware(pwd) {
|
|
71
|
-
await initOrUpdateMockMiddlewares(pwd);
|
|
76
|
+
async function getMockMiddleware(pwd, mockDir) {
|
|
77
|
+
await initOrUpdateMockMiddlewares(pwd, mockDir);
|
|
72
78
|
const mockMiddleware = async (c, next)=>{
|
|
73
79
|
if ('function' == typeof mockConfig?.enable) {
|
|
74
80
|
const isEnabled = mockConfig.enable(c.env.node.req, c.env.node.res);
|
|
@@ -84,4 +90,4 @@ async function getMockMiddleware(pwd) {
|
|
|
84
90
|
};
|
|
85
91
|
return mockMiddleware;
|
|
86
92
|
}
|
|
87
|
-
export { getMatched, getMockMiddleware, initOrUpdateMockMiddlewares };
|
|
93
|
+
export { getMatched, getMockMiddleware, initOrUpdateMockMiddlewares, isPathInsideDirectory, resolveMockDirectory };
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A request handle compatible with `ServerBase.handle` (Hono's `app.fetch`)
|
|
3
|
+
* and the handler accepted by `createNodeServer`.
|
|
4
|
+
*/
|
|
5
|
+
export type ReloadableHandle = (request: Request, ...args: any[]) => Response | Promise<Response>;
|
|
6
|
+
export interface ReloadManagerOptions {
|
|
7
|
+
/**
|
|
8
|
+
* The handle used before the first successful build. Optional: when omitted
|
|
9
|
+
* the manager serves a 503 "starting" response until the first build swaps a
|
|
10
|
+
* real handle in (use `setHandle()` for a fail-fast initial boot).
|
|
11
|
+
*/
|
|
12
|
+
initialHandle?: ReloadableHandle;
|
|
13
|
+
/**
|
|
14
|
+
* Build a fresh handle (e.g. a brand new runtime `ServerBase`).
|
|
15
|
+
* If this throws, the previously active handle is retained (rollback).
|
|
16
|
+
*/
|
|
17
|
+
build: () => Promise<ReloadableHandle>;
|
|
18
|
+
/** Debounce window (ms) used to coalesce rapid `schedule()` calls. */
|
|
19
|
+
debounceMs?: number;
|
|
20
|
+
/**
|
|
21
|
+
* Called after a handle is successfully built and swapped in (e.g. to clean
|
|
22
|
+
* up the previous runtime). The swap has already been committed when this
|
|
23
|
+
* runs, so throwing here does NOT roll back — it is reported via
|
|
24
|
+
* `onReloadError` instead of `onError`.
|
|
25
|
+
*/
|
|
26
|
+
onReload?: (handle: ReloadableHandle) => void;
|
|
27
|
+
/**
|
|
28
|
+
* Called when `build()` throws. The previous handle is kept serving. This is
|
|
29
|
+
* the only path that counts as a failed reload.
|
|
30
|
+
*/
|
|
31
|
+
onError?: (error: unknown) => void;
|
|
32
|
+
/**
|
|
33
|
+
* Called when the `onReload` callback throws. The new handle is already
|
|
34
|
+
* active; this is a post-swap cleanup/callback failure, never a rollback.
|
|
35
|
+
*/
|
|
36
|
+
onReloadError?: (error: unknown) => void;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Owns the single mutable request handle behind a stable forwarding listener.
|
|
40
|
+
*
|
|
41
|
+
* Guarantees required by the dev-server hot-reload design:
|
|
42
|
+
* - Atomic swap: the active handle is replaced by a single assignment, so
|
|
43
|
+
* in-flight requests always run against a consistent handle.
|
|
44
|
+
* - Failure isolation: if `build()` rejects, the previous handle stays active
|
|
45
|
+
* (the dev server never degrades to an empty / broken handle).
|
|
46
|
+
* - Serial execution: reloads never overlap. Requests that arrive while a
|
|
47
|
+
* reload is running are coalesced into a single trailing reload so that the
|
|
48
|
+
* final state always reflects the latest source ("last write wins").
|
|
49
|
+
*/
|
|
50
|
+
export declare class ReloadManager {
|
|
51
|
+
#private;
|
|
52
|
+
constructor(options: ReloadManagerOptions);
|
|
53
|
+
/**
|
|
54
|
+
* Replace the active handle directly, bypassing `build()`. Used to seed the
|
|
55
|
+
* initial known-good handle (a fail-fast boot builds the first runtime
|
|
56
|
+
* explicitly so build errors propagate, then seeds it here).
|
|
57
|
+
*/
|
|
58
|
+
setHandle(handle: ReloadableHandle): void;
|
|
59
|
+
/**
|
|
60
|
+
* Stable forwarding handle. Pass this to `createNodeServer` once; it always
|
|
61
|
+
* dispatches to the latest active handle, so the Node server never needs to
|
|
62
|
+
* be recreated on reload.
|
|
63
|
+
*/
|
|
64
|
+
get handle(): ReloadableHandle;
|
|
65
|
+
/** The currently active resolved handle (introspection / tests). */
|
|
66
|
+
get currentHandle(): ReloadableHandle;
|
|
67
|
+
/** Whether a reload is currently running (introspection / tests). */
|
|
68
|
+
get isReloading(): boolean;
|
|
69
|
+
/**
|
|
70
|
+
* Request a reload, debounced. Multiple calls within the debounce window
|
|
71
|
+
* collapse into a single reload.
|
|
72
|
+
*/
|
|
73
|
+
schedule(): void;
|
|
74
|
+
/**
|
|
75
|
+
* Run a reload immediately. If one is already running, mark a trailing
|
|
76
|
+
* reload and return the in-flight promise (which will include the trailing
|
|
77
|
+
* run) so callers can await final settlement.
|
|
78
|
+
*/
|
|
79
|
+
reloadNow(): Promise<void>;
|
|
80
|
+
/**
|
|
81
|
+
* Stop the manager: cancel any pending debounced reload and reject all
|
|
82
|
+
* further `schedule()` / `reloadNow()` calls. Wired into the dev server's
|
|
83
|
+
* close chain so a debounce timer that fires after teardown can never
|
|
84
|
+
* rebuild a runtime once the watcher / builder dev server are gone.
|
|
85
|
+
*/
|
|
86
|
+
close(): void;
|
|
87
|
+
}
|
|
88
|
+
export declare function createReloadManager(options: ReloadManagerOptions): ReloadManager;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
interface RuntimeOptionsBase {
|
|
2
|
+
config: Record<string, any>;
|
|
3
|
+
serverConfig?: Record<string, any>;
|
|
4
|
+
plugins?: any[];
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Produce a fully-isolated options object for a single runtime build.
|
|
8
|
+
*
|
|
9
|
+
* `buildRuntimeServer` runs for the initial boot AND for every hot reload, so
|
|
10
|
+
* each build must own every container that a plugin or the apply pipeline
|
|
11
|
+
* could append to / rewrite. A shallow `{ ...base }` is NOT enough: `config`,
|
|
12
|
+
* `serverConfig` and `plugins` would still share references, letting one
|
|
13
|
+
* runtime's appended middlewares / plugins leak into the next reload.
|
|
14
|
+
*
|
|
15
|
+
* This clones:
|
|
16
|
+
* - a fresh top-level options object
|
|
17
|
+
* - a fresh `config` with a fresh `config.output` (so writing `assetPrefix`
|
|
18
|
+
* never mutates the caller's original `options.config`)
|
|
19
|
+
* - a fresh `serverConfig` whose `middlewares` / `renderMiddlewares` /
|
|
20
|
+
* `plugins` are new arrays
|
|
21
|
+
* - a fresh top-level `plugins` array
|
|
22
|
+
*
|
|
23
|
+
* Nested config sub-objects that are only read (never appended to) stay shared
|
|
24
|
+
* by reference, which is intentional and cheap.
|
|
25
|
+
*/
|
|
26
|
+
export declare function createRuntimeServerOptions<T extends RuntimeOptionsBase>(base: T, assetPrefix?: string): T;
|
|
27
|
+
export {};
|
package/dist/types/dev.d.ts
CHANGED
|
@@ -1,9 +1,68 @@
|
|
|
1
|
+
import type { Server as NodeServer } from 'node:http';
|
|
2
|
+
import type { Http2SecureServer } from 'node:http2';
|
|
3
|
+
import type { Server as NodeHttpsServer } from 'node:https';
|
|
1
4
|
import type { BuilderInstance, Rspack } from '@modern-js/builder';
|
|
2
|
-
import type { ServerBaseOptions, ServerPlugin } from '@modern-js/server-core';
|
|
5
|
+
import type { ServerBase, ServerBaseOptions, ServerPlugin } from '@modern-js/server-core';
|
|
6
|
+
import type { WatchEvent } from './dev-tools/watcher';
|
|
3
7
|
import type { ModernDevServerOptions } from './types';
|
|
4
8
|
type BuilderDevServer = Awaited<ReturnType<BuilderInstance['createDevServer']>>;
|
|
5
9
|
export type DevPluginOptions = ModernDevServerOptions<ServerBaseOptions> & {
|
|
6
10
|
builderDevServer?: BuilderDevServer;
|
|
7
11
|
};
|
|
8
|
-
|
|
12
|
+
/**
|
|
13
|
+
* Runtime-level dev middleware injection.
|
|
14
|
+
*
|
|
15
|
+
* This plugin is added to EVERY runtime `ServerBase` that `buildRuntimeServer`
|
|
16
|
+
* creates, so it must be safe to run repeatedly and must NOT touch any
|
|
17
|
+
* process-level resource (the file watcher / websocket / builder hooks / close
|
|
18
|
+
* callbacks all live in `setupDevInfra`). It only pushes request middlewares
|
|
19
|
+
* into the current server's middleware list:
|
|
20
|
+
* - user `dev.setupMiddlewares` (before / after)
|
|
21
|
+
* - the mock middleware
|
|
22
|
+
* - the rsbuild/builder dev middleware (a stable reference, just re-registered)
|
|
23
|
+
* - the file-reader middleware
|
|
24
|
+
*/
|
|
25
|
+
export declare const devRuntimeMiddlewarePlugin: (options: DevPluginOptions, compiler: Rspack.Compiler | Rspack.MultiCompiler | null) => ServerPlugin;
|
|
26
|
+
export interface DevInfraOptions {
|
|
27
|
+
config: DevPluginOptions['config'];
|
|
28
|
+
pwd: string;
|
|
29
|
+
distDir: string;
|
|
30
|
+
apiDir?: string;
|
|
31
|
+
sharedDir?: string;
|
|
32
|
+
builder?: BuilderInstance;
|
|
33
|
+
builderDevServer?: BuilderDevServer;
|
|
34
|
+
compiler: Rspack.Compiler | Rspack.MultiCompiler | null;
|
|
35
|
+
nodeServer?: NodeServer | NodeHttpsServer | Http2SecureServer;
|
|
36
|
+
/** Accessor for the currently-active runtime ServerBase (a mutable ref). */
|
|
37
|
+
getRuntimeServer: () => ServerBase | undefined;
|
|
38
|
+
/**
|
|
39
|
+
* Triggered when a watched user server file changes (require cache already
|
|
40
|
+
* busted). Wired to the runtime reload scheduler.
|
|
41
|
+
*/
|
|
42
|
+
onFileChange: (filepath: string, event: WatchEvent) => void;
|
|
43
|
+
/**
|
|
44
|
+
* Extra teardown run as part of the dev server close chain (e.g. stopping the
|
|
45
|
+
* reload scheduler so a pending debounced reload can't rebuild after close).
|
|
46
|
+
*/
|
|
47
|
+
onClose?: () => void;
|
|
48
|
+
}
|
|
49
|
+
export interface DevInfra {
|
|
50
|
+
/** Tear down every process-level resource. Called when the dev server stops. */
|
|
51
|
+
close: () => void;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Process-level dev infrastructure, created EXACTLY ONCE for the lifetime of
|
|
55
|
+
* the dev server. None of these resources are recreated or torn down by a
|
|
56
|
+
* runtime hot reload:
|
|
57
|
+
* - the rsbuild/builder dev server websocket connection
|
|
58
|
+
* - the builder `onDevCompileDone` -> SSR cache reset hook
|
|
59
|
+
* - the file watcher
|
|
60
|
+
* - close callbacks registered on the Node server's `close` event
|
|
61
|
+
*
|
|
62
|
+
* The watcher / onRepack reach the LIVE runtime hooks through
|
|
63
|
+
* `getRuntimeServer()` (a mutable ref) instead of closing over the initial
|
|
64
|
+
* runtime, so a later phase can swap the trigger to the reload scheduler
|
|
65
|
+
* without leaking a stale closure to a dead runtime.
|
|
66
|
+
*/
|
|
67
|
+
export declare function setupDevInfra({ config, pwd, distDir, apiDir, sharedDir, builder, builderDevServer, getRuntimeServer, onFileChange, onClose, nodeServer, }: DevInfraOptions): DevInfra;
|
|
9
68
|
export {};
|
|
@@ -1,15 +1,20 @@
|
|
|
1
|
-
import { type ServerBase } from '@modern-js/server-core';
|
|
2
1
|
import { type WatchOptions } from '@modern-js/utils';
|
|
3
|
-
import Watcher from '../dev-tools/watcher';
|
|
2
|
+
import Watcher, { type WatchEvent } from '../dev-tools/watcher';
|
|
4
3
|
export * from './devOptions';
|
|
5
4
|
export * from './fileReader';
|
|
6
5
|
export * from './mock';
|
|
7
6
|
export * from './repack';
|
|
8
|
-
export declare function startWatcher({ pwd, distDir, apiDir, sharedDir, watchOptions,
|
|
7
|
+
export declare function startWatcher({ pwd, distDir, apiDir, sharedDir, mockDir, watchOptions, onChange, }: {
|
|
9
8
|
pwd: string;
|
|
10
9
|
distDir: string;
|
|
11
10
|
apiDir: string;
|
|
12
11
|
sharedDir: string;
|
|
12
|
+
mockDir?: string;
|
|
13
13
|
watchOptions?: WatchOptions;
|
|
14
|
-
|
|
14
|
+
/**
|
|
15
|
+
* Called after the require cache for a changed user server file has been
|
|
16
|
+
* busted, so the next runtime build re-imports fresh code. Server loader
|
|
17
|
+
* bundles are handled inline (cache drop only) and never reach this.
|
|
18
|
+
*/
|
|
19
|
+
onChange: (filepath: string, event: WatchEvent) => void;
|
|
15
20
|
}): Watcher;
|
|
@@ -10,7 +10,9 @@ type MockAPI = {
|
|
|
10
10
|
path: string;
|
|
11
11
|
handler: MockHandler;
|
|
12
12
|
};
|
|
13
|
+
export declare const resolveMockDirectory: (pwd: string, mockDir?: string) => string;
|
|
14
|
+
export declare const isPathInsideDirectory: (filepath: string, directory: string) => boolean;
|
|
13
15
|
export declare const getMatched: (request: InternalRequest, mockApis: MockAPI[]) => MockAPI | undefined;
|
|
14
|
-
export declare function initOrUpdateMockMiddlewares(pwd: string): Promise<void>;
|
|
15
|
-
export declare function getMockMiddleware(pwd: string): Promise<Middleware>;
|
|
16
|
+
export declare function initOrUpdateMockMiddlewares(pwd: string, mockDir?: string): Promise<void>;
|
|
17
|
+
export declare function getMockMiddleware(pwd: string, mockDir?: string): Promise<Middleware>;
|
|
16
18
|
export {};
|
package/package.json
CHANGED
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"modern",
|
|
18
18
|
"modern.js"
|
|
19
19
|
],
|
|
20
|
-
"version": "3.
|
|
20
|
+
"version": "3.8.1-ultramodern.1",
|
|
21
21
|
"types": "./dist/types/index.d.ts",
|
|
22
22
|
"main": "./dist/cjs/index.js",
|
|
23
23
|
"exports": {
|
|
@@ -38,11 +38,11 @@
|
|
|
38
38
|
}
|
|
39
39
|
},
|
|
40
40
|
"dependencies": {
|
|
41
|
-
"@modern-js/runtime-utils": "npm:@bleedingdev/modern-js-runtime-utils@3.
|
|
42
|
-
"@modern-js/server-core": "npm:@bleedingdev/modern-js-server-core@3.
|
|
43
|
-
"@modern-js/server-utils": "npm:@bleedingdev/modern-js-server-utils@3.
|
|
44
|
-
"@modern-js/types": "npm:@bleedingdev/modern-js-types@3.
|
|
45
|
-
"@modern-js/utils": "npm:@bleedingdev/modern-js-utils@3.
|
|
41
|
+
"@modern-js/runtime-utils": "npm:@bleedingdev/modern-js-runtime-utils@3.8.1-ultramodern.1",
|
|
42
|
+
"@modern-js/server-core": "npm:@bleedingdev/modern-js-server-core@3.8.1-ultramodern.1",
|
|
43
|
+
"@modern-js/server-utils": "npm:@bleedingdev/modern-js-server-utils@3.8.1-ultramodern.1",
|
|
44
|
+
"@modern-js/types": "npm:@bleedingdev/modern-js-types@3.8.1-ultramodern.1",
|
|
45
|
+
"@modern-js/utils": "npm:@bleedingdev/modern-js-utils@3.8.1-ultramodern.1",
|
|
46
46
|
"@swc/helpers": "^0.5.23",
|
|
47
47
|
"axios": "^1.18.1",
|
|
48
48
|
"connect-history-api-fallback": "^2.0.0",
|
|
@@ -52,11 +52,11 @@
|
|
|
52
52
|
"ws": "^8.21.0"
|
|
53
53
|
},
|
|
54
54
|
"devDependencies": {
|
|
55
|
-
"@modern-js/builder": "npm:@bleedingdev/modern-js-builder@3.
|
|
55
|
+
"@modern-js/builder": "npm:@bleedingdev/modern-js-builder@3.8.1-ultramodern.1",
|
|
56
56
|
"@rslib/core": "0.23.2",
|
|
57
57
|
"@scripts/rstest-config": "2.66.0",
|
|
58
58
|
"@types/connect-history-api-fallback": "^1.5.4",
|
|
59
|
-
"@types/node": "^26.
|
|
59
|
+
"@types/node": "^26.2.0",
|
|
60
60
|
"@types/ws": "^8.18.1",
|
|
61
61
|
"@typescript/native-preview": "7.0.0-dev.20260707.2",
|
|
62
62
|
"node-mocks-http": "^1.18.0",
|