@dimina-kit/devkit 0.1.2-dev.20260701155140 → 0.1.2-dev.20260702145840
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/README.md +1 -1
- package/dist/compile-worker-entry.d.ts +6 -2
- package/dist/compile-worker-entry.d.ts.map +1 -1
- package/dist/compile-worker-entry.js +6 -4
- package/dist/compile-worker-leak.test.js +13 -3
- package/dist/compile-worker.d.ts.map +1 -1
- package/dist/compile-worker.js +27 -7
- package/fe/dimina-fe-server/index.js +7 -1
- package/fe/index.js +13 -7
- package/fe/live-reload.js +28 -19
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -63,7 +63,7 @@ await session.close()
|
|
|
63
63
|
|
|
64
64
|
## 工作流程
|
|
65
65
|
|
|
66
|
-
1. 在 fork 出的长驻编译子进程中调用 `@dimina/compiler` 编译小程序项目(cwd 隔离:`chdir` 只发生在子进程内,宿主进程 cwd 不被污染;编译器崩溃不会拖垮宿主,下次重编译自动重新 fork 恢复)
|
|
66
|
+
1. 在 fork 出的长驻编译子进程中调用 `@dimina-kit/compiler/pool-node`(dmcc 兼容的常驻编译池)编译小程序项目(cwd 隔离:`chdir` 只发生在子进程内,宿主进程 cwd 不被污染;编译器崩溃不会拖垮宿主,下次重编译自动重新 fork 恢复)
|
|
67
67
|
2. 子进程的 stdout/stderr 按行管道回父进程,经 `filterDmccLogLine` 过滤噪音后通过 `onLog` 回调送出
|
|
68
68
|
3. 启动基于 Express 的 H5 容器预览服务器(含 CORS、SPA fallback、可选 live-reload)
|
|
69
69
|
4. 通过 chokidar 监听项目目录变更,自动触发增量编译和页面刷新;编译进行中的变更不会丢失,会在当前编译结束后合并为恰好一次尾随重编译(trailing rebuild)
|
|
@@ -21,8 +21,12 @@ export interface WorkerResultMessage {
|
|
|
21
21
|
};
|
|
22
22
|
}
|
|
23
23
|
export interface CompileWorkerHandlerDeps {
|
|
24
|
-
/**
|
|
25
|
-
|
|
24
|
+
/**
|
|
25
|
+
* Lazy compiler load — deferred to the first build. May be sync (a plain
|
|
26
|
+
* `require`) or async (a dynamic `import()` of the ESM `@dimina-kit/compiler`
|
|
27
|
+
* pool); the handler awaits it either way. Loaded ONCE and cached.
|
|
28
|
+
*/
|
|
29
|
+
loadCompiler: () => WorkerBuildFn | Promise<WorkerBuildFn>;
|
|
26
30
|
/** `process.chdir` — mutates the CHILD process cwd only. */
|
|
27
31
|
chdir: (dir: string) => void;
|
|
28
32
|
/** `process.send` — replies to the parent. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"compile-worker-entry.d.ts","sourceRoot":"","sources":["../src/compile-worker-entry.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"compile-worker-entry.d.ts","sourceRoot":"","sources":["../src/compile-worker-entry.ts"],"names":[],"mappings":"AAiBA,MAAM,WAAW,aAAa;IAC7B,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,CAAA;CACZ;AAED,MAAM,WAAW,kBAAkB;IAClC,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,gFAAgF;IAChF,SAAS,CAAC,EAAE;QAAE,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,EAAE,CAAA;KAAE,CAAA;CAC5E;AAED,MAAM,MAAM,aAAa,GAAG,CAC3B,SAAS,EAAE,MAAM,EACjB,WAAW,EAAE,MAAM,EACnB,WAAW,EAAE,OAAO,EACpB,OAAO,EAAE,kBAAkB,KACvB,OAAO,CAAC,aAAa,GAAG,IAAI,GAAG,SAAS,CAAC,CAAA;AAE9C,MAAM,WAAW,mBAAmB;IACnC,IAAI,EAAE,QAAQ,CAAA;IACd,OAAO,EAAE,aAAa,GAAG,IAAI,CAAA;IAC7B,KAAK,CAAC,EAAE;QAAE,OAAO,EAAE,MAAM,CAAA;KAAE,CAAA;CAC3B;AAED,MAAM,WAAW,wBAAwB;IACxC;;;;OAIG;IACH,YAAY,EAAE,MAAM,aAAa,GAAG,OAAO,CAAC,aAAa,CAAC,CAAA;IAC1D,4DAA4D;IAC5D,KAAK,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAA;IAC5B,8CAA8C;IAC9C,IAAI,EAAE,CAAC,GAAG,EAAE,mBAAmB,KAAK,IAAI,CAAA;CACxC;AAiBD;;;;;;;GAOG;AACH,wBAAgB,0BAA0B,CACzC,IAAI,EAAE,wBAAwB,GAC5B,CAAC,GAAG,EAAE,OAAO,KAAK,OAAO,CAAC,IAAI,CAAC,CAwBjC;AAED;;;;;;;;GAQG;AACH,wBAAgB,wBAAwB,CACvC,IAAI,EAAE;IACL,EAAE,EAAE,CAAC,KAAK,EAAE,SAAS,GAAG,YAAY,EAAE,QAAQ,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,IAAI,KAAK,OAAO,CAAA;IACxF,IAAI,EAAE,CAAC,IAAI,CAAC,EAAE,MAAM,KAAK,IAAI,CAAA;CAC7B,EACD,OAAO,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,OAAO,CAAC,IAAI,CAAC,GACtC,IAAI,CASN"}
|
|
@@ -12,7 +12,6 @@
|
|
|
12
12
|
* in `createCompileWorkerHandler` (dependency-injected) so it is unit-testable
|
|
13
13
|
* without forking.
|
|
14
14
|
*/
|
|
15
|
-
import { createRequire } from 'node:module';
|
|
16
15
|
import path from 'node:path';
|
|
17
16
|
import { fileURLToPath } from 'node:url';
|
|
18
17
|
function isBuildMessage(msg) {
|
|
@@ -35,7 +34,7 @@ export function createCompileWorkerHandler(deps) {
|
|
|
35
34
|
return;
|
|
36
35
|
try {
|
|
37
36
|
if (!cachedBuild)
|
|
38
|
-
cachedBuild = deps.loadCompiler();
|
|
37
|
+
cachedBuild = await deps.loadCompiler();
|
|
39
38
|
deps.chdir(msg.projectPath);
|
|
40
39
|
const appInfo = await cachedBuild(msg.outputDir, msg.projectPath, true, msg.options);
|
|
41
40
|
deps.send({ type: 'result', appInfo: appInfo ?? null });
|
|
@@ -102,9 +101,12 @@ const isForkedWorkerEntry = typeof process.argv[1] === 'string'
|
|
|
102
101
|
&& path.resolve(stripModuleQuery(process.argv[1])) === selfModulePath
|
|
103
102
|
&& typeof process.send === 'function';
|
|
104
103
|
if (isForkedWorkerEntry) {
|
|
105
|
-
const require = createRequire(import.meta.url);
|
|
106
104
|
const handler = createCompileWorkerHandler({
|
|
107
|
-
|
|
105
|
+
// Load @dimina-kit/compiler's resident Node worker_threads pool (drop-in for
|
|
106
|
+
// dmcc's build(): same signature, same log/error surface, but keeps its 3 stage
|
|
107
|
+
// workers WARM across rebuilds). ESM package → dynamic import (works on Node 20+);
|
|
108
|
+
// its default export is the pooled build fn.
|
|
109
|
+
loadCompiler: async () => (await import('@dimina-kit/compiler/pool-node')).default,
|
|
108
110
|
chdir: dir => process.chdir(dir),
|
|
109
111
|
send: (msg) => {
|
|
110
112
|
process.send?.(msg);
|
|
@@ -56,6 +56,14 @@ function workerEntryPath() {
|
|
|
56
56
|
return js;
|
|
57
57
|
return path.join(__dirname, 'compile-worker-entry.ts');
|
|
58
58
|
}
|
|
59
|
+
/** execArgv for the entry — same rule as compile-worker.ts (a `.ts` entry needs
|
|
60
|
+
* type stripping, default only since Node 22.18; like the suite itself this
|
|
61
|
+
* assumes Node ≥22.6, where the explicit flag exists). */
|
|
62
|
+
function workerExecArgv(entry) {
|
|
63
|
+
return entry.endsWith('.ts')
|
|
64
|
+
? ['--experimental-strip-types', '--disable-warning=ExperimentalWarning']
|
|
65
|
+
: [];
|
|
66
|
+
}
|
|
59
67
|
function pidAlive(pid) {
|
|
60
68
|
try {
|
|
61
69
|
process.kill(pid, 0);
|
|
@@ -147,7 +155,8 @@ function makeFixture() {
|
|
|
147
155
|
}
|
|
148
156
|
describe('① orphan safety net — the worker kills ITSELF when the IPC channel dies', () => {
|
|
149
157
|
it('a forked entry whose parent disconnects the IPC channel exits ON ITS OWN with code 0 (no kill from anyone)', async () => {
|
|
150
|
-
const
|
|
158
|
+
const entry = workerEntryPath();
|
|
159
|
+
const child = fork(entry, [], { execArgv: workerExecArgv(entry), silent: true });
|
|
151
160
|
doomedPids.push(child.pid);
|
|
152
161
|
await once(child, 'spawn');
|
|
153
162
|
const exited = once(child, 'exit');
|
|
@@ -171,16 +180,17 @@ describe('① orphan safety net — the worker kills ITSELF when the IPC channel
|
|
|
171
180
|
// stdout, then SIGKILLs ITSELF. SIGKILL is the harshest parent death —
|
|
172
181
|
// no exit handlers, no kill(child), nothing but the OS closing the
|
|
173
182
|
// IPC pipe. The orphan must notice and exit.
|
|
183
|
+
const entry = workerEntryPath();
|
|
174
184
|
const helperSource = `
|
|
175
185
|
const { fork } = require('node:child_process')
|
|
176
186
|
const fs = require('node:fs')
|
|
177
|
-
const child = fork(process.argv[1], [], { execArgv:
|
|
187
|
+
const child = fork(process.argv[1], [], { execArgv: ${JSON.stringify(workerExecArgv(entry))}, silent: true })
|
|
178
188
|
child.on('spawn', () => {
|
|
179
189
|
fs.writeSync(1, 'CHILD_PID=' + child.pid + '\\n')
|
|
180
190
|
process.kill(process.pid, 'SIGKILL')
|
|
181
191
|
})
|
|
182
192
|
`;
|
|
183
|
-
const parent = spawn(process.execPath, ['-e', helperSource,
|
|
193
|
+
const parent = spawn(process.execPath, ['-e', helperSource, entry], {
|
|
184
194
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
185
195
|
});
|
|
186
196
|
doomedPids.push(parent.pid);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"compile-worker.d.ts","sourceRoot":"","sources":["../src/compile-worker.ts"],"names":[],"mappings":"AAyBA,OAAO,KAAK,EAAE,aAAa,EAAE,kBAAkB,EAAuB,MAAM,2BAA2B,CAAA;AAIvG,MAAM,WAAW,eAAe;IAC/B,MAAM,EAAE,QAAQ,GAAG,QAAQ,CAAA;IAC3B,IAAI,EAAE,MAAM,CAAA;CACZ;AAED,MAAM,WAAW,oBAAoB;IACpC,qEAAqE;IACrE,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,eAAe,KAAK,IAAI,CAAA;CACxC;AAED,MAAM,WAAW,YAAY;IAC5B,WAAW,EAAE,MAAM,CAAA;IACnB,SAAS,EAAE,MAAM,CAAA;IACjB,OAAO,EAAE,kBAAkB,CAAA;CAC3B;AAED,MAAM,WAAW,aAAa;IAC7B;;;;;OAKG;IACH,KAAK,EAAE,CAAC,GAAG,EAAE,YAAY,KAAK,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC,CAAA;IAC3D;;;;;;OAMG;IACH,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;CAC1B;
|
|
1
|
+
{"version":3,"file":"compile-worker.d.ts","sourceRoot":"","sources":["../src/compile-worker.ts"],"names":[],"mappings":"AAyBA,OAAO,KAAK,EAAE,aAAa,EAAE,kBAAkB,EAAuB,MAAM,2BAA2B,CAAA;AAIvG,MAAM,WAAW,eAAe;IAC/B,MAAM,EAAE,QAAQ,GAAG,QAAQ,CAAA;IAC3B,IAAI,EAAE,MAAM,CAAA;CACZ;AAED,MAAM,WAAW,oBAAoB;IACpC,qEAAqE;IACrE,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,eAAe,KAAK,IAAI,CAAA;CACxC;AAED,MAAM,WAAW,YAAY;IAC5B,WAAW,EAAE,MAAM,CAAA;IACnB,SAAS,EAAE,MAAM,CAAA;IACjB,OAAO,EAAE,kBAAkB,CAAA;CAC3B;AAED,MAAM,WAAW,aAAa;IAC7B;;;;;OAKG;IACH,KAAK,EAAE,CAAC,GAAG,EAAE,YAAY,KAAK,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC,CAAA;IAC3D;;;;;;OAMG;IACH,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;CAC1B;AAsCD,wBAAgB,mBAAmB,CAAC,IAAI,GAAE,oBAAyB,GAAG,aAAa,CA6KlF"}
|
package/dist/compile-worker.js
CHANGED
|
@@ -26,9 +26,10 @@ const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
|
26
26
|
/**
|
|
27
27
|
* Resolve the fork target. In the published build this is the compiled
|
|
28
28
|
* `dist/compile-worker-entry.js` sibling; under vitest/dev the module runs
|
|
29
|
-
* from `src/`, where only the `.ts` source exists —
|
|
30
|
-
*
|
|
31
|
-
*
|
|
29
|
+
* from `src/`, where only the `.ts` source exists — the entry is a
|
|
30
|
+
* zero-dependency, erasable-syntax-only shell by design, so Node can run it
|
|
31
|
+
* with type stripping (default since 22.18; behind `--experimental-strip-types`
|
|
32
|
+
* on 22.6–22.17, which `workerExecArgv` passes for a `.ts` entry).
|
|
32
33
|
*/
|
|
33
34
|
function resolveWorkerEntry() {
|
|
34
35
|
const js = path.join(__dirname, 'compile-worker-entry.js');
|
|
@@ -36,6 +37,26 @@ function resolveWorkerEntry() {
|
|
|
36
37
|
return js;
|
|
37
38
|
return path.join(__dirname, 'compile-worker-entry.ts');
|
|
38
39
|
}
|
|
40
|
+
/**
|
|
41
|
+
* execArgv for the fork. A compiled `.js` entry (the published build, engines
|
|
42
|
+
* `node >=20`) needs nothing. A `.ts` entry (vitest/dev only) needs Node type
|
|
43
|
+
* stripping: `--experimental-strip-types` exists since 22.6 — on older Node
|
|
44
|
+
* the child would die at startup with an opaque ERR_UNKNOWN_FILE_EXTENSION /
|
|
45
|
+
* bad-option crash, so fail HERE with an actionable message instead. The
|
|
46
|
+
* ExperimentalWarning is silenced because the child's stderr is the dmcc log
|
|
47
|
+
* transport. On ≥22.18 (stripping on by default) the flag is still accepted.
|
|
48
|
+
*/
|
|
49
|
+
function workerExecArgv(entry) {
|
|
50
|
+
if (!entry.endsWith('.ts'))
|
|
51
|
+
return [];
|
|
52
|
+
const [major = 0, minor = 0] = process.versions.node.split('.').map(Number);
|
|
53
|
+
const hasStripTypesFlag = major > 22 || (major === 22 && minor >= 6);
|
|
54
|
+
if (!hasStripTypesFlag) {
|
|
55
|
+
throw new Error(`running the compile worker from .ts source requires Node >= 22.6 for type stripping `
|
|
56
|
+
+ `(current: ${process.versions.node}); build the package and use the compiled dist entry instead`);
|
|
57
|
+
}
|
|
58
|
+
return ['--experimental-strip-types', '--disable-warning=ExperimentalWarning'];
|
|
59
|
+
}
|
|
39
60
|
export function createCompileWorker(opts = {}) {
|
|
40
61
|
let child = null;
|
|
41
62
|
let closed = false;
|
|
@@ -96,10 +117,9 @@ export function createCompileWorker(opts = {}) {
|
|
|
96
117
|
const worker = fork(entry, [], {
|
|
97
118
|
// Pipe stdout/stderr back to the parent — the dmcc log transport.
|
|
98
119
|
silent: true,
|
|
99
|
-
//
|
|
100
|
-
// leak into a plain Node child).
|
|
101
|
-
|
|
102
|
-
execArgv: [],
|
|
120
|
+
// Explicit execArgv, NOT the parent's (vitest/electron loaders would
|
|
121
|
+
// leak into a plain Node child).
|
|
122
|
+
execArgv: workerExecArgv(entry),
|
|
103
123
|
});
|
|
104
124
|
attachLineReader(worker.stdout, 'stdout');
|
|
105
125
|
attachLineReader(worker.stderr, 'stderr');
|
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
import axios from 'axios'
|
|
2
2
|
import cors from 'cors'
|
|
3
3
|
import express from 'express'
|
|
4
|
-
import { app } from '../server.js'
|
|
5
4
|
|
|
5
|
+
/**
|
|
6
|
+
* Register the proxy middleware (cors, json, /proxy) on the CALLER's express
|
|
7
|
+
* app — registration on a shared module-level app would bind these routes to
|
|
8
|
+
* whichever server imported this module first in the process.
|
|
9
|
+
*/
|
|
10
|
+
export function register(app) {
|
|
6
11
|
// 使用cors中间件来允许所有跨域请求
|
|
7
12
|
// 注意:在实际生产环境中,你应该更具体地配置CORS策略
|
|
8
13
|
app.use(cors())
|
|
@@ -65,4 +70,5 @@ app.post('/proxy', async (req, res) => {
|
|
|
65
70
|
})
|
|
66
71
|
}
|
|
67
72
|
})
|
|
73
|
+
}
|
|
68
74
|
|
package/fe/index.js
CHANGED
|
@@ -3,7 +3,6 @@ import express from 'express'
|
|
|
3
3
|
import path from 'path'
|
|
4
4
|
import fs from 'fs'
|
|
5
5
|
import { fileURLToPath } from 'url'
|
|
6
|
-
import { app } from './server.js'
|
|
7
6
|
|
|
8
7
|
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
|
9
8
|
|
|
@@ -18,7 +17,12 @@ const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
|
|
18
17
|
* @returns {Promise<import('http').Server | { server: import('http').Server, reload: () => void }>}
|
|
19
18
|
*/
|
|
20
19
|
export async function start({ port = 7788, containerDir, outputDir, simulatorDir, liveReload = false, sessionApps = [] } = {}) {
|
|
21
|
-
|
|
20
|
+
// One express app PER start(): routes registered on a shared module-level app
|
|
21
|
+
// accumulate across sessions, and express matches the FIRST registration — a
|
|
22
|
+
// second openProject in the same process would get the first session's
|
|
23
|
+
// appList/static/SPA handlers (its dirs possibly gone) and show "暂无应用".
|
|
24
|
+
const app = express()
|
|
25
|
+
let liveReloadHandle
|
|
22
26
|
const appsDir = outputDir || containerDir
|
|
23
27
|
|
|
24
28
|
app.get('/appList.json', (_req, res) => {
|
|
@@ -58,7 +62,8 @@ export async function start({ port = 7788, containerDir, outputDir, simulatorDir
|
|
|
58
62
|
}
|
|
59
63
|
|
|
60
64
|
if (liveReload) {
|
|
61
|
-
|
|
65
|
+
const { createLiveReload } = await import('./live-reload.js')
|
|
66
|
+
liveReloadHandle = createLiveReload(app)
|
|
62
67
|
// Disable auto index so SPA fallback handles script injection
|
|
63
68
|
app.use(express.static(containerDir, { index: false }))
|
|
64
69
|
}
|
|
@@ -76,8 +81,9 @@ export async function start({ port = 7788, containerDir, outputDir, simulatorDir
|
|
|
76
81
|
})
|
|
77
82
|
}
|
|
78
83
|
|
|
79
|
-
//
|
|
80
|
-
await import('./dimina-fe-server/index.js')
|
|
84
|
+
// Register proxy server middleware (cors, json, /proxy route)
|
|
85
|
+
const { register: registerProxy } = await import('./dimina-fe-server/index.js')
|
|
86
|
+
registerProxy(app)
|
|
81
87
|
|
|
82
88
|
// SPA fallback: serve index.html for navigation-like unmatched routes
|
|
83
89
|
// (no extension or `.html`). Asset-like misses (`.json`, `.js`, `.wxml`,
|
|
@@ -90,7 +96,7 @@ export async function start({ port = 7788, containerDir, outputDir, simulatorDir
|
|
|
90
96
|
// handled promise rejection.
|
|
91
97
|
// Express 5 requires a named wildcard parameter instead of bare '*'.
|
|
92
98
|
const spaFallback = liveReload
|
|
93
|
-
?
|
|
99
|
+
? liveReloadHandle.injectScript(containerDir)
|
|
94
100
|
: (_req, res) => res.sendFile('index.html', { root: containerDir })
|
|
95
101
|
|
|
96
102
|
app.get('/{*path}', (req, res, next) => {
|
|
@@ -107,7 +113,7 @@ export async function start({ port = 7788, containerDir, outputDir, simulatorDir
|
|
|
107
113
|
console.log(`Server is running on port ${port}`)
|
|
108
114
|
console.log('Press Ctrl+C to stop')
|
|
109
115
|
if (!process.env.DIMINA_NO_OPEN_BROWSER) await open(`http://localhost:${port}`)
|
|
110
|
-
resolve(liveReload ? { server, reload:
|
|
116
|
+
resolve(liveReload ? { server, reload: liveReloadHandle.reload } : server)
|
|
111
117
|
})
|
|
112
118
|
server.on('error', (err) => {
|
|
113
119
|
if (err.code === 'EADDRINUSE') {
|
package/fe/live-reload.js
CHANGED
|
@@ -1,30 +1,39 @@
|
|
|
1
1
|
import fs from 'node:fs'
|
|
2
2
|
import path from 'node:path'
|
|
3
|
-
import { app } from './server.js'
|
|
4
3
|
|
|
5
4
|
const LIVERELOAD_SCRIPT = `<script>(function(){var es=new EventSource('/__livereload');es.addEventListener('reload',function(){window.location.reload();});}());</script>`
|
|
6
5
|
|
|
7
|
-
|
|
6
|
+
/**
|
|
7
|
+
* Attach the live-reload SSE endpoint to ONE server's express app and return
|
|
8
|
+
* that server's `reload`/`injectScript`. A module-level client set would
|
|
9
|
+
* broadcast one session's rebuilds into every other session's pages when
|
|
10
|
+
* several servers run in the same process.
|
|
11
|
+
*/
|
|
12
|
+
export function createLiveReload(app) {
|
|
13
|
+
const clients = new Set()
|
|
8
14
|
|
|
9
|
-
app.get('/__livereload', (req, res) => {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
})
|
|
15
|
+
app.get('/__livereload', (req, res) => {
|
|
16
|
+
res.setHeader('Content-Type', 'text/event-stream')
|
|
17
|
+
res.setHeader('Cache-Control', 'no-cache')
|
|
18
|
+
res.setHeader('Connection', 'keep-alive')
|
|
19
|
+
res.flushHeaders()
|
|
20
|
+
clients.add(res)
|
|
21
|
+
req.on('close', () => clients.delete(res))
|
|
22
|
+
})
|
|
17
23
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
24
|
+
function reload() {
|
|
25
|
+
for (const client of clients) {
|
|
26
|
+
client.write('event: reload\ndata: \n\n')
|
|
27
|
+
}
|
|
21
28
|
}
|
|
22
|
-
}
|
|
23
29
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
30
|
+
function injectScript(containerDir) {
|
|
31
|
+
return (_req, res) => {
|
|
32
|
+
const html = fs.readFileSync(path.join(containerDir, 'index.html'), 'utf-8')
|
|
33
|
+
res.setHeader('Content-Type', 'text/html')
|
|
34
|
+
res.send(html.replace('</body>', `${LIVERELOAD_SCRIPT}</body>`))
|
|
35
|
+
}
|
|
29
36
|
}
|
|
37
|
+
|
|
38
|
+
return { reload, injectScript }
|
|
30
39
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dimina-kit/devkit",
|
|
3
|
-
"version": "0.1.2-dev.
|
|
3
|
+
"version": "0.1.2-dev.20260702145840",
|
|
4
4
|
"description": "Development toolkit for Dimina mini-apps with H5 container preview",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"dimina",
|
|
@@ -57,12 +57,12 @@
|
|
|
57
57
|
"@dimina-kit/eslint-config": "0.1.0"
|
|
58
58
|
},
|
|
59
59
|
"dependencies": {
|
|
60
|
-
"@dimina/compiler": "1.0.17",
|
|
61
60
|
"axios": "^1.15.0",
|
|
62
61
|
"chokidar": "^5.0.0",
|
|
63
62
|
"cors": "^2.8.6",
|
|
64
63
|
"express": "^5.2.1",
|
|
65
|
-
"open": "^11.0.0"
|
|
64
|
+
"open": "^11.0.0",
|
|
65
|
+
"@dimina-kit/compiler": "0.0.1"
|
|
66
66
|
},
|
|
67
67
|
"publishConfig": {
|
|
68
68
|
"access": "public"
|