tosijs-ui 1.6.20 → 1.6.22
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/CHANGELOG.md +36 -0
- package/dist/code-editor.d.ts +9 -28
- package/dist/code-editor.js +62 -68
- package/dist/doc-browser.js +13 -5
- package/dist/doc-system/site/dev-server.js +52 -0
- package/dist/doc-system/site/epub-cli.d.ts +1 -0
- package/dist/doc-system/site/epub-cli.js +22 -0
- package/dist/doc-system/site/epub.js +6 -0
- package/dist/doc-system/site/index.d.ts +1 -1
- package/dist/doc-system/site/orchestrator.js +123 -32
- package/dist/doc-system/site/site-config.d.ts +38 -0
- package/dist/iife.js +109 -137
- package/dist/iife.js.map +8 -31
- package/dist/live-example/code-transform.js +1 -1
- package/dist/live-example/component.js +12 -7
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/llms.txt +2 -2
- package/package.json +3 -18
- package/dist/code-editor-cm.d.ts +0 -30
- package/dist/code-editor-cm.js +0 -132
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,41 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.6.22
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
|
|
7
|
+
- **The dev server no longer leaks memory on every rebuild — update as a priority if
|
|
8
|
+
you use `tosijs-ui/site`.** `buildSite()` called `Bun.build()` in-process, and Bun's
|
|
9
|
+
bundler never returns its native arena: RSS grows monotonically per call with no
|
|
10
|
+
plateau (40 sequential builds of one entry = **+367MB**, still climbing ~5MB/build at
|
|
11
|
+
the end), while the JS heap stays flat — so it is invisible to `Bun.gc()` and to any
|
|
12
|
+
heap profiler. `devServer()` calls it once per rebuild in a process that runs for
|
|
13
|
+
days, so it compounds: a ~2-day watch session reached **136GB RSS** and took the
|
|
14
|
+
machine down with it. Filed upstream as
|
|
15
|
+
[oven-sh/bun#34053](https://github.com/oven-sh/bun/issues/34053).
|
|
16
|
+
|
|
17
|
+
The bundle now builds in a **child process** (`bun build`), whose memory the OS
|
|
18
|
+
reclaims on exit — the same 15 bundles leave the parent **+0.5MB** instead of +192MB.
|
|
19
|
+
`buildEpub()` moved to a child too (happy-dom + `@resvg/resvg-js` are both native and
|
|
20
|
+
retaining, and it runs on every rebuild), with a 120s timeout kill so a hung or failed
|
|
21
|
+
ePub warns instead of wedging the rebuild. Measured on this repo's own dev server:
|
|
22
|
+
**baseline RSS 503MB → 150MB, and per-rebuild growth 26–59MB → ~2.7MB.**
|
|
23
|
+
|
|
24
|
+
Bundle output is byte-for-byte identical; this is purely where the work runs.
|
|
25
|
+
|
|
26
|
+
### Added
|
|
27
|
+
|
|
28
|
+
- **Dev-server memory watchdog.** `devServer()` now samples RSS after each rebuild and,
|
|
29
|
+
past a ceiling (`memoryLimitMb` in the site config, or `DEV_MEMORY_LIMIT_MB`; default
|
|
30
|
+
4096), prints the growth-per-rebuild and exits rather than let a leak thrash the
|
|
31
|
+
machine. It distinguishes real growth (a leak — report it) from a build whose baseline
|
|
32
|
+
simply exceeds the ceiling (raise the ceiling).
|
|
33
|
+
|
|
34
|
+
## 1.6.21
|
|
35
|
+
|
|
36
|
+
- Memory-routed nav-toggle fix; `buildSite` `.tjs` hooks (`libraryBuild`,
|
|
37
|
+
`generateCssPreload` — see BUILD-TJS-HOOK.md); tjs-lang 0.9.0.
|
|
38
|
+
|
|
3
39
|
## 1.6.20
|
|
4
40
|
|
|
5
41
|
### Fixed
|
package/dist/code-editor.d.ts
CHANGED
|
@@ -1,15 +1,7 @@
|
|
|
1
|
-
import { Component as WebComponent, ElementCreator
|
|
2
|
-
|
|
3
|
-
interface CodeEditorParts extends PartsMap {
|
|
4
|
-
host: HTMLDivElement;
|
|
5
|
-
}
|
|
6
|
-
export declare class CodeEditor extends WebComponent<CodeEditorParts> {
|
|
1
|
+
import { Component as WebComponent, ElementCreator } from 'tosijs';
|
|
2
|
+
export declare class CodeEditor extends WebComponent {
|
|
7
3
|
static preferredTagName: string;
|
|
8
4
|
private source;
|
|
9
|
-
private _handle;
|
|
10
|
-
private _loadPromise;
|
|
11
|
-
private _appliedMode;
|
|
12
|
-
private _appliedDisabled;
|
|
13
5
|
get value(): string;
|
|
14
6
|
set value(text: string);
|
|
15
7
|
private _original;
|
|
@@ -20,16 +12,16 @@ export declare class CodeEditor extends WebComponent<CodeEditorParts> {
|
|
|
20
12
|
showDiff(on: boolean): void;
|
|
21
13
|
static initAttributes: {
|
|
22
14
|
mode: string;
|
|
15
|
+
theme: string;
|
|
23
16
|
disabled: boolean;
|
|
24
17
|
};
|
|
25
18
|
role: string;
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
content: () => HTMLDivElement[];
|
|
19
|
+
private _ace;
|
|
20
|
+
private _editor;
|
|
21
|
+
private _editorPromise;
|
|
22
|
+
options: any;
|
|
23
|
+
get ace(): any;
|
|
24
|
+
get editor(): any;
|
|
33
25
|
static shadowStyleSpec: {
|
|
34
26
|
':host': {
|
|
35
27
|
display: string;
|
|
@@ -37,20 +29,9 @@ export declare class CodeEditor extends WebComponent<CodeEditorParts> {
|
|
|
37
29
|
width: string;
|
|
38
30
|
height: string;
|
|
39
31
|
};
|
|
40
|
-
'[part="host"]': {
|
|
41
|
-
height: string;
|
|
42
|
-
};
|
|
43
|
-
'.cm-editor': {
|
|
44
|
-
height: string;
|
|
45
|
-
};
|
|
46
|
-
'.cm-scroller': {
|
|
47
|
-
outline: string;
|
|
48
|
-
fontFamily: string;
|
|
49
|
-
};
|
|
50
32
|
};
|
|
51
33
|
onResize(): void;
|
|
52
34
|
connectedCallback(): void;
|
|
53
35
|
render(): void;
|
|
54
36
|
}
|
|
55
37
|
export declare const codeEditor: ElementCreator<CodeEditor>;
|
|
56
|
-
export {};
|
package/dist/code-editor.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
/*#
|
|
2
2
|
# code
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
An [ACE Editor](https://ace.c9.io/) wrapper.
|
|
5
5
|
|
|
6
6
|
Sometimes, it's nice to be able to just toss a code-editor in a web-page.
|
|
7
7
|
|
|
8
|
-
`<tosi-code>`'s `value` is the code it contains. Its `mode` attribute sets the
|
|
9
|
-
|
|
8
|
+
`<tosi-code>`'s `value` is the code it contains. Its `mode` attribute sets the language, and you can further configure
|
|
9
|
+
the ACE editor instance via its `options` property.
|
|
10
10
|
|
|
11
11
|
```html
|
|
12
12
|
<tosi-code style="width: 100%; height: 100%" mode="css">
|
|
@@ -16,33 +16,51 @@ body {
|
|
|
16
16
|
</tosi-code>
|
|
17
17
|
```
|
|
18
18
|
|
|
19
|
-
The `<tosi-code>` element has an `editor` property that gives you its
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
CodeMirror is loaded lazily on first use — a page with no `<tosi-code>` bundles
|
|
24
|
-
none of it.
|
|
19
|
+
The `<tosi-code>` element has an `editor` property that gives you its ACE editor instance,
|
|
20
|
+
and an `ace` property that returns the `ace` module, giving you complete access to the
|
|
21
|
+
[Ace API](https://ace.c9.io/api/index.html).
|
|
25
22
|
*/
|
|
26
23
|
/*{ "parent": "Components" }*/
|
|
27
|
-
import { Component as WebComponent
|
|
24
|
+
import { Component as WebComponent } from 'tosijs';
|
|
25
|
+
import { scriptTag } from './via-tag';
|
|
28
26
|
import { tosiDiff } from './diff';
|
|
29
|
-
const
|
|
27
|
+
const ACE_BASE_URL = 'https://cdnjs.cloudflare.com/ajax/libs/ace/1.23.2/';
|
|
28
|
+
const DEFAULT_THEME = 'ace/theme/tomorrow';
|
|
29
|
+
const getAce = async () => {
|
|
30
|
+
const { ace } = await scriptTag(`${ACE_BASE_URL}ace.min.js`);
|
|
31
|
+
return ace;
|
|
32
|
+
};
|
|
33
|
+
const makeCodeEditor = async (codeElement, mode = 'html', options = {}, theme = DEFAULT_THEME) => {
|
|
34
|
+
const ace = await getAce();
|
|
35
|
+
ace.config.set('basePath', ACE_BASE_URL);
|
|
36
|
+
const editor = ace.edit(codeElement, {
|
|
37
|
+
mode: `ace/mode/${mode}`,
|
|
38
|
+
tabSize: 2,
|
|
39
|
+
useSoftTabs: true,
|
|
40
|
+
useWorker: false,
|
|
41
|
+
// Wrap wide lines instead of scrolling horizontally; indentedSoftWrap keeps
|
|
42
|
+
// wrapped continuations aligned under the line's indent.
|
|
43
|
+
wrap: true,
|
|
44
|
+
indentedSoftWrap: true,
|
|
45
|
+
...options,
|
|
46
|
+
});
|
|
47
|
+
editor.setTheme(theme);
|
|
48
|
+
return { ace, editor };
|
|
49
|
+
};
|
|
30
50
|
export class CodeEditor extends WebComponent {
|
|
31
51
|
static preferredTagName = 'tosi-code';
|
|
32
52
|
source = '';
|
|
33
|
-
_handle;
|
|
34
|
-
_loadPromise;
|
|
35
|
-
_appliedMode = '';
|
|
36
|
-
_appliedDisabled;
|
|
37
53
|
get value() {
|
|
38
|
-
return this.
|
|
54
|
+
return this.editor === undefined ? this.source : this.editor.getValue();
|
|
39
55
|
}
|
|
40
56
|
set value(text) {
|
|
41
|
-
if (this.
|
|
42
|
-
this.
|
|
57
|
+
if (this.editor === undefined) {
|
|
58
|
+
this.source = text;
|
|
43
59
|
}
|
|
44
60
|
else {
|
|
45
|
-
this.
|
|
61
|
+
this.editor.setValue(text);
|
|
62
|
+
this.editor.clearSelection();
|
|
63
|
+
this.editor.session.getUndoManager().reset();
|
|
46
64
|
}
|
|
47
65
|
}
|
|
48
66
|
// Baseline for `showDiff()` — the version to diff the current `value` against.
|
|
@@ -54,9 +72,9 @@ export class CodeEditor extends WebComponent {
|
|
|
54
72
|
set original(text) {
|
|
55
73
|
this._original = text;
|
|
56
74
|
}
|
|
57
|
-
// Diff overlay — built only on the editor's public surface
|
|
58
|
-
// and the tosi-diff component, never the underlying
|
|
59
|
-
// editor
|
|
75
|
+
// Diff overlay — deliberately built only on the editor's public surface
|
|
76
|
+
// (`value` + `original`) and the tosi-diff component, never the underlying
|
|
77
|
+
// editor's API, so it survives a future Ace → CodeMirror swap untouched.
|
|
60
78
|
diffOverlay;
|
|
61
79
|
get showingDiff() {
|
|
62
80
|
return this.diffOverlay !== undefined && !this.diffOverlay.hidden;
|
|
@@ -85,27 +103,20 @@ export class CodeEditor extends WebComponent {
|
|
|
85
103
|
}
|
|
86
104
|
static initAttributes = {
|
|
87
105
|
mode: 'javascript',
|
|
106
|
+
theme: DEFAULT_THEME,
|
|
88
107
|
disabled: false,
|
|
89
108
|
};
|
|
90
109
|
role = 'code editor';
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
this._handle?.undo();
|
|
98
|
-
}
|
|
99
|
-
redo() {
|
|
100
|
-
this._handle?.redo();
|
|
101
|
-
}
|
|
102
|
-
canUndo() {
|
|
103
|
-
return this._handle?.canUndo() ?? false;
|
|
110
|
+
_ace;
|
|
111
|
+
_editor;
|
|
112
|
+
_editorPromise;
|
|
113
|
+
options = {};
|
|
114
|
+
get ace() {
|
|
115
|
+
return this._ace;
|
|
104
116
|
}
|
|
105
|
-
|
|
106
|
-
return this.
|
|
117
|
+
get editor() {
|
|
118
|
+
return this._editor;
|
|
107
119
|
}
|
|
108
|
-
content = () => [div({ part: 'host' })];
|
|
109
120
|
static shadowStyleSpec = {
|
|
110
121
|
':host': {
|
|
111
122
|
display: 'block',
|
|
@@ -113,49 +124,32 @@ export class CodeEditor extends WebComponent {
|
|
|
113
124
|
width: '100%',
|
|
114
125
|
height: '100%',
|
|
115
126
|
},
|
|
116
|
-
'[part="host"]': { height: '100%' },
|
|
117
|
-
'.cm-editor': { height: '100%' },
|
|
118
|
-
'.cm-scroller': {
|
|
119
|
-
outline: 'none',
|
|
120
|
-
fontFamily: "Menlo, Monaco, Consolas, 'Courier New', monospace",
|
|
121
|
-
},
|
|
122
127
|
};
|
|
123
128
|
onResize() {
|
|
124
|
-
this.
|
|
129
|
+
if (this.editor !== undefined) {
|
|
130
|
+
this.editor.resize(true);
|
|
131
|
+
}
|
|
125
132
|
}
|
|
126
133
|
connectedCallback() {
|
|
127
134
|
super.connectedCallback();
|
|
128
135
|
if (this.source === '') {
|
|
129
136
|
this.value = this.textContent !== null ? this.textContent.trim() : '';
|
|
130
137
|
}
|
|
131
|
-
if (this.
|
|
132
|
-
|
|
133
|
-
this.
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
onChange: (value) => this.dispatchEvent(new CustomEvent('change', { detail: { value } })),
|
|
140
|
-
});
|
|
141
|
-
this._handle = handle;
|
|
142
|
-
this._appliedMode = this.mode;
|
|
143
|
-
this._appliedDisabled = this.disabled;
|
|
144
|
-
return handle;
|
|
138
|
+
if (this._editorPromise === undefined) {
|
|
139
|
+
this._editorPromise = makeCodeEditor(this, this.mode, this.options, this.theme);
|
|
140
|
+
this._editorPromise.then(({ ace, editor }) => {
|
|
141
|
+
this._ace = ace;
|
|
142
|
+
this._editor = editor;
|
|
143
|
+
editor.setValue(this.source, 1);
|
|
144
|
+
editor.clearSelection();
|
|
145
|
+
editor.session.getUndoManager().reset();
|
|
145
146
|
});
|
|
146
147
|
}
|
|
147
148
|
}
|
|
148
149
|
render() {
|
|
149
150
|
super.render();
|
|
150
|
-
if (this.
|
|
151
|
-
|
|
152
|
-
this._handle.setReadOnly(this.disabled);
|
|
153
|
-
this._appliedDisabled = this.disabled;
|
|
154
|
-
}
|
|
155
|
-
if (this.mode !== this._appliedMode) {
|
|
156
|
-
this._handle.setMode(this.mode);
|
|
157
|
-
this._appliedMode = this.mode;
|
|
158
|
-
}
|
|
151
|
+
if (this._editorPromise !== undefined) {
|
|
152
|
+
this._editorPromise.then(({ editor }) => editor.setReadOnly(this.disabled));
|
|
159
153
|
}
|
|
160
154
|
}
|
|
161
155
|
}
|
package/dist/doc-browser.js
CHANGED
|
@@ -472,6 +472,11 @@ export function createDocBrowser(options) {
|
|
|
472
472
|
}
|
|
473
473
|
});
|
|
474
474
|
}
|
|
475
|
+
// This instance's own sidenav. Captured from its own change event (below), so the
|
|
476
|
+
// header nav toggle drives THIS doc-browser — not `document.querySelector('tosi-
|
|
477
|
+
// sidenav')`, which returns the FIRST sidenav in the document (an OUTER instance
|
|
478
|
+
// when this one is an embedded, memory-routed browser).
|
|
479
|
+
let sidenav = null;
|
|
475
480
|
const headerContent = [
|
|
476
481
|
button({
|
|
477
482
|
class: 'iconic',
|
|
@@ -489,8 +494,8 @@ export function createDocBrowser(options) {
|
|
|
489
494
|
},
|
|
490
495
|
},
|
|
491
496
|
onClick() {
|
|
492
|
-
|
|
493
|
-
|
|
497
|
+
if (sidenav)
|
|
498
|
+
sidenav.contentVisible = !sidenav.contentVisible;
|
|
494
499
|
},
|
|
495
500
|
}, icons.menu()),
|
|
496
501
|
span({ style: { flex: '0 0 10px' } }),
|
|
@@ -1027,9 +1032,12 @@ export function createDocBrowser(options) {
|
|
|
1027
1032
|
flex: '1 1 auto',
|
|
1028
1033
|
overflow: 'hidden',
|
|
1029
1034
|
},
|
|
1030
|
-
onChange() {
|
|
1031
|
-
|
|
1032
|
-
|
|
1035
|
+
onChange(event) {
|
|
1036
|
+
// Scope to the sidenav that fired — and capture it so the header toggle
|
|
1037
|
+
// targets THIS instance, not the first sidenav in the document.
|
|
1038
|
+
sidenav = event.target.closest(TosiSidenav.tagName);
|
|
1039
|
+
if (sidenav)
|
|
1040
|
+
app.compact = sidenav.compact;
|
|
1033
1041
|
},
|
|
1034
1042
|
}, searchField, navContent, div({
|
|
1035
1043
|
style: {
|
|
@@ -223,6 +223,57 @@ export async function devServer(config, opts = {}) {
|
|
|
223
223
|
// never regenerated — leaving the page's /iife.js to 404 into the SPA
|
|
224
224
|
// fallback (it "loads as html"). Serialize builds and coalesce bursts.
|
|
225
225
|
const runBuild = opts.build ?? (() => buildSite(config));
|
|
226
|
+
// Memory watchdog.
|
|
227
|
+
//
|
|
228
|
+
// The build hands work to native code (the bundler, the HTML parser, the SVG
|
|
229
|
+
// rasterizer) which can strand memory the JS heap never sees — so nothing
|
|
230
|
+
// here can GC it away, and `heapUsed` stays flat while RSS climbs. A watch
|
|
231
|
+
// process lives for DAYS across thousands of rebuilds, so a per-rebuild leak
|
|
232
|
+
// compounds until the machine swaps itself to death. That is not
|
|
233
|
+
// hypothetical: a 2-day session reached 136GB RSS and took the machine down
|
|
234
|
+
// (Bun.build's native arena — see orchestrator.ts and oven-sh/bun#34053).
|
|
235
|
+
// Dying loudly beats being the reason a laptop overheats: the dev server is
|
|
236
|
+
// one keystroke to restart, the machine is not.
|
|
237
|
+
const rssMb = () => Math.round(process.memoryUsage().rss / 1e6);
|
|
238
|
+
const limitMb = Number(process.env.DEV_MEMORY_LIMIT_MB ?? config.memoryLimitMb ?? 4096);
|
|
239
|
+
let baselineMb = 0;
|
|
240
|
+
let rebuilds = 0;
|
|
241
|
+
let warned = false;
|
|
242
|
+
const checkMemory = () => {
|
|
243
|
+
const mb = rssMb();
|
|
244
|
+
rebuilds += 1;
|
|
245
|
+
if (!baselineMb)
|
|
246
|
+
baselineMb = mb;
|
|
247
|
+
const growth = mb - baselineMb;
|
|
248
|
+
const each = rebuilds > 1 ? growth / (rebuilds - 1) : 0;
|
|
249
|
+
if (mb >= limitMb) {
|
|
250
|
+
// Distinguish the two ways to get here, because the advice is opposite:
|
|
251
|
+
// memory that GREW across rebuilds is a leak (report it); a build that was
|
|
252
|
+
// simply born bigger than the ceiling just needs a bigger ceiling.
|
|
253
|
+
const leaking = rebuilds > 1 && each >= 1;
|
|
254
|
+
const diagnosis = leaking
|
|
255
|
+
? ` ${rebuilds} rebuilds, +${growth}MB since the first ` +
|
|
256
|
+
`(~${each.toFixed(1)}MB per rebuild).\n\n` +
|
|
257
|
+
` Growth per rebuild should be ~0, so this is a leak, not your project\n` +
|
|
258
|
+
` getting bigger. Restarting reclaims it — please report the numbers\n` +
|
|
259
|
+
` above. If this build genuinely needs the headroom, raise the ceiling\n` +
|
|
260
|
+
` with DEV_MEMORY_LIMIT_MB=<mb> or memoryLimitMb in your site config.\n`
|
|
261
|
+
: ` ${rebuilds} rebuild${rebuilds === 1 ? '' : 's'}, ` +
|
|
262
|
+
`+${growth}MB since the first — i.e. it is not growing.\n\n` +
|
|
263
|
+
` This build's baseline footprint is simply above the ceiling, which is\n` +
|
|
264
|
+
` not a leak. Raise it with DEV_MEMORY_LIMIT_MB=<mb> or memoryLimitMb in\n` +
|
|
265
|
+
` your site config.\n`;
|
|
266
|
+
console.error(`\n🛑 dev server stopping: ${mb}MB RSS, over the ${limitMb}MB limit.\n\n` +
|
|
267
|
+
diagnosis);
|
|
268
|
+
process.exit(1);
|
|
269
|
+
}
|
|
270
|
+
if (!warned && mb > limitMb * 0.6) {
|
|
271
|
+
warned = true;
|
|
272
|
+
console.warn(`⚠️ dev server at ${mb}MB RSS after ${rebuilds} rebuilds (+${growth}MB, ` +
|
|
273
|
+
`~${each.toFixed(1)}MB each; limit ${limitMb}MB). Growth per rebuild ` +
|
|
274
|
+
`should be ~0 — if this keeps climbing, restart and report it.`);
|
|
275
|
+
}
|
|
276
|
+
};
|
|
226
277
|
let building = false;
|
|
227
278
|
let pending = false;
|
|
228
279
|
const rebuild = async () => {
|
|
@@ -239,6 +290,7 @@ export async function devServer(config, opts = {}) {
|
|
|
239
290
|
}
|
|
240
291
|
finally {
|
|
241
292
|
building = false;
|
|
293
|
+
checkMemory();
|
|
242
294
|
if (pending) {
|
|
243
295
|
pending = false;
|
|
244
296
|
void rebuild();
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Standalone ePub build — the subprocess entry point for buildEpub().
|
|
3
|
+
|
|
4
|
+
buildEpub() drives happy-dom (HTML→XHTML for 50+ chapters) and @resvg/resvg-js
|
|
5
|
+
(cover rasterization). Both hold native/retained memory that the JS heap can't
|
|
6
|
+
see, and the ePub is rebuilt on EVERY dev-server rebuild — so in-process it
|
|
7
|
+
strands memory in a watch process that lives for days. Running it in a child
|
|
8
|
+
hands all of it back to the OS on exit. Same reasoning as the bundle step; see
|
|
9
|
+
the Bun.build note in orchestrator.ts.
|
|
10
|
+
|
|
11
|
+
bun epub-cli.ts <payload.json> # payload = { config, opts }
|
|
12
|
+
|
|
13
|
+
Build-time only. Never import this from browser code.
|
|
14
|
+
*/
|
|
15
|
+
import { buildEpub } from './epub';
|
|
16
|
+
const payloadPath = process.argv[2];
|
|
17
|
+
if (!payloadPath) {
|
|
18
|
+
console.error('epub-cli: expected a payload file path');
|
|
19
|
+
process.exit(1);
|
|
20
|
+
}
|
|
21
|
+
const { config, opts } = JSON.parse(await Bun.file(payloadPath).text());
|
|
22
|
+
await buildEpub(config, opts ?? {});
|
|
@@ -535,6 +535,12 @@ export async function buildEpub(config, opts = {}) {
|
|
|
535
535
|
const output = path.resolve(opts.output ?? path.join(outDir, `${slugify(meta.title)}.epub`));
|
|
536
536
|
await zipEpub(buildDir, output);
|
|
537
537
|
fs.rmSync(buildDir, { recursive: true, force: true });
|
|
538
|
+
// Release the parser window: an unclosed happy-dom Window holds its whole
|
|
539
|
+
// document (every chapter parsed) plus its timers. Hygiene, not a proven win —
|
|
540
|
+
// buildEpub still grows ~1.5MB per call with this in place, so something here
|
|
541
|
+
// is still retained. buildEpub now runs in a child process (see orchestrator),
|
|
542
|
+
// which is what actually bounds it.
|
|
543
|
+
win?.close?.();
|
|
538
544
|
console.log(`epub: ${output} (${chapters.length} chapters)`);
|
|
539
545
|
return output;
|
|
540
546
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { defineSiteConfig } from './site-config';
|
|
2
|
-
export type { SiteConfig, SiteHost } from './site-config';
|
|
2
|
+
export type { SiteConfig, SiteHost, LibraryBuildContext } from './site-config';
|
|
3
3
|
export { buildSite } from './orchestrator';
|
|
4
4
|
export { devServer } from './dev-server';
|
|
5
5
|
export { extractDocs, saveDocsJSON } from './docs';
|