tosijs-ui 1.15.3 → 1.15.4
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 +47 -0
- package/dist/doc-system/site/index.d.ts +1 -0
- package/dist/doc-system/site/library-bundle.d.ts +26 -0
- package/dist/doc-system/site/library-bundle.js +100 -0
- package/dist/doc-system/site/orchestrator.d.ts +1 -0
- package/dist/doc-system/site/orchestrator.js +64 -5
- package/dist/doc-system/site/site-config.d.ts +18 -4
- package/dist/iife.js +82 -82
- package/dist/iife.js.map +4 -4
- package/dist/tag-list.d.ts +1 -0
- package/dist/tag-list.js +118 -7
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/llms.txt +1 -1
- package/package.json +1 -1
- package/dist/tsconfig.tsbuildinfo +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,52 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.15.4
|
|
4
|
+
|
|
5
|
+
### `libraryBundle`: a library build Node can actually load (#169)
|
|
6
|
+
|
|
7
|
+
`emitLibrary` and `libraryTsconfig` run a bare `tsc`, which copies each import specifier through
|
|
8
|
+
unchanged. Written in the usual bundler style (`export * from './model'`), that produces a
|
|
9
|
+
`dist/` **Node refuses** (`ERR_MODULE_NOT_FOUND`), though Bun and bundlers resolve it, so it
|
|
10
|
+
tends to ship unnoticed. Three projects had each written their own guard for it.
|
|
11
|
+
|
|
12
|
+
The new `libraryBundle` option is the paved road:
|
|
13
|
+
|
|
14
|
+
```ts
|
|
15
|
+
libraryBundle: { entries: ['src/index.ts', 'src/core.ts'] }
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
`bun build` bundles the entries with code-splitting (every declared dependency and peer stays an
|
|
19
|
+
import), `tsc` emits declarations only, and the build **fails** if `dist/` still has a relative
|
|
20
|
+
import without a file extension. Options: `externals` (default: all declared dependencies and
|
|
21
|
+
peers), `tsconfig` for the declaration pass, `target` (default `browser`). It needs `typescript`
|
|
22
|
+
installed, as any TypeScript library has.
|
|
23
|
+
|
|
24
|
+
`emitLibrary` and `libraryTsconfig` keep working, and now **warn** when their output has that
|
|
25
|
+
problem.
|
|
26
|
+
|
|
27
|
+
### Tag colours on chips and in the pick menu (#173)
|
|
28
|
+
|
|
29
|
+
A Tag in `<tosi-tag-list>`'s `availableTags` can carry `background` and `color`. Chips now use
|
|
30
|
+
them (they were ignored), and the pick menu shows each coloured tag's caption as a lozenge in
|
|
31
|
+
those colours, with the checkmark beside it. Without a `color`, the text is black or white,
|
|
32
|
+
whichever contrasts more.
|
|
33
|
+
|
|
34
|
+
### Tag list fixes (#189)
|
|
35
|
+
|
|
36
|
+
- A Tag object without a `caption` (the obvious way to colour chips: `{ value, color, background }`)
|
|
37
|
+
showed as a **blank row** in the pick menu; on a phone, a tall empty column. It now shows its
|
|
38
|
+
value.
|
|
39
|
+
- A selected tag that was also in `availableTags` as an object appeared in the menu **twice**:
|
|
40
|
+
string tags were compared against Tag objects with `includes`. It now compares by value.
|
|
41
|
+
- `<tosi-tag-list>`'s `value` is documented as `string | string[]`, but an array threw
|
|
42
|
+
`split is not a function` on render.
|
|
43
|
+
|
|
44
|
+
### `emitLibrary` no longer ships `tsconfig.tsbuildinfo`
|
|
45
|
+
|
|
46
|
+
Libraries built with `emitLibrary` shipped TypeScript's incremental-build cache in `dist/`: no
|
|
47
|
+
use to consumers, and different on every rebuild. It is gone; rebuild and republish to drop it
|
|
48
|
+
from your package.
|
|
49
|
+
|
|
3
50
|
## 1.15.3
|
|
4
51
|
|
|
5
52
|
### `<tosi-md sanitize="on">`, and a heads-up: it becomes the default in 1.16 (#179)
|
|
@@ -8,6 +8,7 @@ export { openDevBrowser, buildOpenPlan } from './open-browser.js';
|
|
|
8
8
|
export type { OpenPlan, OpenPlanInput } from './open-browser.js';
|
|
9
9
|
export { extractDocs, saveDocsJSON } from './docs.js';
|
|
10
10
|
export type { Doc } from './docs.js';
|
|
11
|
+
export type { LibraryBundle } from './library-bundle.js';
|
|
11
12
|
export { generateSite } from './generate-site.js';
|
|
12
13
|
export type { GenerateSiteConfig } from './generate-site.js';
|
|
13
14
|
export { buildEpub, DEFAULT_BOOK_CSS } from './epub.js';
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export interface LibraryBundle {
|
|
2
|
+
/** Entry points relative to the project root, e.g. `['src/index.ts', 'src/core.ts']`. */
|
|
3
|
+
entries: string[];
|
|
4
|
+
/**
|
|
5
|
+
* Packages left as imports instead of bundled (each also matches its subpaths). Default:
|
|
6
|
+
* every `dependencies`, `peerDependencies` and `optionalDependencies` name in package.json.
|
|
7
|
+
*/
|
|
8
|
+
externals?: string[];
|
|
9
|
+
/** tsconfig for the declaration pass (run with `--emitDeclarationOnly`). Default: the root one. */
|
|
10
|
+
tsconfig?: string;
|
|
11
|
+
/** `bun build --target`. Default `browser`. */
|
|
12
|
+
target?: 'browser' | 'node' | 'bun';
|
|
13
|
+
}
|
|
14
|
+
interface PackageJsonDeps {
|
|
15
|
+
dependencies?: Record<string, string>;
|
|
16
|
+
peerDependencies?: Record<string, string>;
|
|
17
|
+
optionalDependencies?: Record<string, string>;
|
|
18
|
+
}
|
|
19
|
+
/** The externals to use: the configured list, or every declared runtime dependency. */
|
|
20
|
+
export declare function bundleExternals(bundle: LibraryBundle, pkg: PackageJsonDeps): string[];
|
|
21
|
+
/** The `bun build` argv for the JavaScript half. */
|
|
22
|
+
export declare function bundleBuildArgs(bundle: LibraryBundle, pkg: PackageJsonDeps, outdir: string): string[];
|
|
23
|
+
/** The `tsc` argv for the declaration half. */
|
|
24
|
+
export declare function bundleDeclarationArgs(bundle: LibraryBundle, outdir: string): string[];
|
|
25
|
+
export declare function extensionlessImports(dir: string): string[];
|
|
26
|
+
export {};
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/*
|
|
2
|
+
The bundled library build (`SiteConfig.libraryBundle`, #169).
|
|
3
|
+
|
|
4
|
+
`emitLibrary` / `libraryTsconfig` run a bare `tsc`, which copies each source file's import
|
|
5
|
+
specifiers into the output unchanged. Written in bundler style (`export * from './model'`), that
|
|
6
|
+
output is a `dist/` Node refuses: `ERR_MODULE_NOT_FOUND .../dist/model`. Bun resolves it, so
|
|
7
|
+
nothing in a Bun build or test loop notices, and it ships. tosijs-3d, tosijs-3d-ensemble and
|
|
8
|
+
tosijs-virta each grew their own guard for this, in three different shapes.
|
|
9
|
+
|
|
10
|
+
This is the paved road instead: `bun build` bundles the entries with splitting (its output
|
|
11
|
+
imports its own chunks WITH extensions, by construction), every declared dependency and peer
|
|
12
|
+
stays an import rather than being bundled, and `tsc` emits declarations only. The result is
|
|
13
|
+
then scanned for relative imports without an extension, and any hit fails the build — the
|
|
14
|
+
guard the three repos wrote, done once.
|
|
15
|
+
|
|
16
|
+
Every heavy step runs as a CHILD process (`bun build`, `tsc`): this code also runs inside the
|
|
17
|
+
long-lived dev server, where Bun.build() leaks its native arena on every call.
|
|
18
|
+
*/
|
|
19
|
+
import { readdirSync, readFileSync, statSync } from 'fs';
|
|
20
|
+
import * as path from 'path';
|
|
21
|
+
/** The externals to use: the configured list, or every declared runtime dependency. */
|
|
22
|
+
export function bundleExternals(bundle, pkg) {
|
|
23
|
+
const names = bundle.externals ?? [
|
|
24
|
+
...Object.keys(pkg.dependencies ?? {}),
|
|
25
|
+
...Object.keys(pkg.peerDependencies ?? {}),
|
|
26
|
+
...Object.keys(pkg.optionalDependencies ?? {}),
|
|
27
|
+
];
|
|
28
|
+
return [...new Set(names)].sort();
|
|
29
|
+
}
|
|
30
|
+
/** The `bun build` argv for the JavaScript half. */
|
|
31
|
+
export function bundleBuildArgs(bundle, pkg, outdir) {
|
|
32
|
+
return [
|
|
33
|
+
'bun',
|
|
34
|
+
'build',
|
|
35
|
+
...bundle.entries,
|
|
36
|
+
'--outdir',
|
|
37
|
+
outdir,
|
|
38
|
+
'--format',
|
|
39
|
+
'esm',
|
|
40
|
+
'--splitting',
|
|
41
|
+
'--target',
|
|
42
|
+
bundle.target ?? 'browser',
|
|
43
|
+
'--sourcemap=linked',
|
|
44
|
+
...bundleExternals(bundle, pkg).flatMap((name) => [
|
|
45
|
+
'--external',
|
|
46
|
+
name,
|
|
47
|
+
'--external',
|
|
48
|
+
`${name}/*`,
|
|
49
|
+
]),
|
|
50
|
+
];
|
|
51
|
+
}
|
|
52
|
+
/** The `tsc` argv for the declaration half. */
|
|
53
|
+
export function bundleDeclarationArgs(bundle, outdir) {
|
|
54
|
+
return [
|
|
55
|
+
'bun',
|
|
56
|
+
'tsc',
|
|
57
|
+
...(bundle.tsconfig ? ['-p', bundle.tsconfig] : []),
|
|
58
|
+
'--declaration',
|
|
59
|
+
'--emitDeclarationOnly',
|
|
60
|
+
// a root tsconfig with `noEmit: true` would otherwise emit nothing at all
|
|
61
|
+
'--noEmit',
|
|
62
|
+
'false',
|
|
63
|
+
'--outDir',
|
|
64
|
+
outdir,
|
|
65
|
+
];
|
|
66
|
+
}
|
|
67
|
+
const HAS_EXTENSION = /\.(?:m?js|cjs|json)$/;
|
|
68
|
+
/*
|
|
69
|
+
Real imports, from Bun's parser rather than a regex: a regex also matched specifiers quoted in
|
|
70
|
+
COMMENTS — four false hits in tosijs-ui's own dist, one of them this file's own doc comment.
|
|
71
|
+
One Transpiler for the process: each construction costs ~40KB that is never returned, and this
|
|
72
|
+
runs inside the long-lived dev server (see CLAUDE.md, "Dev servers are the most dangerous thing").
|
|
73
|
+
*/
|
|
74
|
+
let transpiler;
|
|
75
|
+
function importsOf(source) {
|
|
76
|
+
transpiler ??= new Bun.Transpiler({ loader: 'js' });
|
|
77
|
+
return transpiler
|
|
78
|
+
.scanImports(source)
|
|
79
|
+
.map((entry) => entry.path);
|
|
80
|
+
}
|
|
81
|
+
export function extensionlessImports(dir) {
|
|
82
|
+
const found = [];
|
|
83
|
+
const walk = (current) => {
|
|
84
|
+
for (const name of readdirSync(current)) {
|
|
85
|
+
const full = path.join(current, name);
|
|
86
|
+
if (statSync(full).isDirectory()) {
|
|
87
|
+
walk(full);
|
|
88
|
+
}
|
|
89
|
+
else if (/\.m?js$/.test(name)) {
|
|
90
|
+
for (const specifier of importsOf(readFileSync(full, 'utf8'))) {
|
|
91
|
+
if (/^\.{1,2}\//.test(specifier) && !HAS_EXTENSION.test(specifier)) {
|
|
92
|
+
found.push(`${path.relative(dir, full)}: ${specifier}`);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
walk(dir);
|
|
99
|
+
return found;
|
|
100
|
+
}
|
|
@@ -28,6 +28,7 @@ export declare function shouldCleanDist(config: {
|
|
|
28
28
|
emitLibrary?: boolean;
|
|
29
29
|
libraryTsconfig?: string;
|
|
30
30
|
libraryBuild?: unknown;
|
|
31
|
+
libraryBundle?: unknown;
|
|
31
32
|
}): boolean;
|
|
32
33
|
export declare function buildSite(config: SiteConfig, opts?: {
|
|
33
34
|
skipAudit?: boolean;
|
|
@@ -16,11 +16,12 @@ import { namedBooks, partitionByBook, DEFAULT_BOOK } from '../book-target.js';
|
|
|
16
16
|
import { listEpubVolumes, renderEpubDownloads } from './epub-volumes.js';
|
|
17
17
|
import { buildSlugMap } from '../routing.js';
|
|
18
18
|
import { computeAssetStamp, missingStampInputWarning } from './asset-stamp.js';
|
|
19
|
-
import { existsSync, mkdirSync, unlinkSync } from 'fs';
|
|
19
|
+
import { existsSync, mkdirSync, readFileSync, unlinkSync } from 'fs';
|
|
20
20
|
import { tmpdir } from 'os';
|
|
21
21
|
import { $, spawn } from 'bun';
|
|
22
22
|
import { extractDocs } from './docs.js';
|
|
23
23
|
import { relocateSourcemaps } from './sourcemap-relocate.js';
|
|
24
|
+
import { bundleBuildArgs, bundleDeclarationArgs, extensionlessImports, } from './library-bundle.js';
|
|
24
25
|
import { checkExamples, formatExampleProblems, } from './check-examples.js';
|
|
25
26
|
import { ensureSections } from './sections.js';
|
|
26
27
|
import { generateLlmsTxt } from './make-llms-txt.js';
|
|
@@ -348,7 +349,9 @@ export function shouldCleanDist(config) {
|
|
|
348
349
|
// even alongside the other flags — the consumer's function is the authority.
|
|
349
350
|
if (config.libraryBuild)
|
|
350
351
|
return false;
|
|
351
|
-
return config.emitLibrary === true ||
|
|
352
|
+
return (config.emitLibrary === true ||
|
|
353
|
+
Boolean(config.libraryTsconfig) ||
|
|
354
|
+
Boolean(config.libraryBundle));
|
|
352
355
|
}
|
|
353
356
|
/*
|
|
354
357
|
Announce-once state for the "writes outside outputDir" warning.
|
|
@@ -714,8 +717,9 @@ export async function buildSite(config, opts = {}) {
|
|
|
714
717
|
directory and may legitimately emit a subset, which is exactly the reported case.
|
|
715
718
|
- nothing configured — NOT cleaned. Nothing here owns it.
|
|
716
719
|
|
|
717
|
-
|
|
718
|
-
|
|
720
|
+
`emitLibrary` used to run `tsc --incremental`, whose `.tsbuildinfo` lived in `dist/`; wiping
|
|
721
|
+
it every run meant incremental compilation never once did anything. It no longer runs
|
|
722
|
+
incrementally at all — see the tsc call below for why moving the cache is not the fix.
|
|
719
723
|
*/
|
|
720
724
|
if (shouldCleanDist(config)) {
|
|
721
725
|
await $ `rm -rf ${DIST}`.text();
|
|
@@ -748,6 +752,36 @@ export async function buildSite(config, opts = {}) {
|
|
|
748
752
|
tsconfig: config.libraryTsconfig,
|
|
749
753
|
});
|
|
750
754
|
}
|
|
755
|
+
else if (config.libraryBundle) {
|
|
756
|
+
// The bundled library build (#169): child processes only, never Bun.build() here.
|
|
757
|
+
const pkg = JSON.parse(readFileSync(path.resolve(PROJECT_ROOT, 'package.json'), 'utf8'));
|
|
758
|
+
for (const [what, argv] of [
|
|
759
|
+
['bun build', bundleBuildArgs(config.libraryBundle, pkg, DIST)],
|
|
760
|
+
[
|
|
761
|
+
'tsc --emitDeclarationOnly',
|
|
762
|
+
bundleDeclarationArgs(config.libraryBundle, DIST),
|
|
763
|
+
],
|
|
764
|
+
]) {
|
|
765
|
+
const child = spawn([...argv], { stdout: 'pipe', stderr: 'pipe' });
|
|
766
|
+
const output = await drainChild(child);
|
|
767
|
+
if ((await child.exited) !== 0) {
|
|
768
|
+
console.error(output);
|
|
769
|
+
console.error(`❌ libraryBundle: ${what} FAILED — dist/ is incomplete. Build marked failed.` +
|
|
770
|
+
(/Script not found "tsc"/.test(output)
|
|
771
|
+
? ' TypeScript is not installed in this project: `bun add -d typescript`.'
|
|
772
|
+
: ''));
|
|
773
|
+
libraryBuildFailed = true;
|
|
774
|
+
break;
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
if (!libraryBuildFailed) {
|
|
778
|
+
const bad = extensionlessImports(DIST);
|
|
779
|
+
if (bad.length) {
|
|
780
|
+
console.error(`❌ libraryBundle: dist/ has relative imports without a file extension, which Node cannot load:\n ${bad.join('\n ')}`);
|
|
781
|
+
libraryBuildFailed = true;
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
}
|
|
751
785
|
else if (config.libraryTsconfig) {
|
|
752
786
|
// Consumer-controlled library build (handles root noEmit, removeComments,
|
|
753
787
|
// outDir, etc.). tsc output is left visible so the errors are readable.
|
|
@@ -759,13 +793,38 @@ export async function buildSite(config, opts = {}) {
|
|
|
759
793
|
}
|
|
760
794
|
}
|
|
761
795
|
else if (config.emitLibrary) {
|
|
762
|
-
|
|
796
|
+
/*
|
|
797
|
+
NOT --incremental. Its .tsbuildinfo lived in dist/, so it shipped in every adopter's
|
|
798
|
+
tarball (and ours), and it differs between a first and a second build — which the publish
|
|
799
|
+
workflow's "shipped files reproduce" check rightly rejects. Moving the cache elsewhere is
|
|
800
|
+
WORSE: dist/ is wiped every build (above), and tsc trusts the cache, sees nothing changed,
|
|
801
|
+
and emits nothing — measured, the second build deleted 883 files from dist/. Since dist/
|
|
802
|
+
is always clean, incremental compilation could never help here anyway.
|
|
803
|
+
*/
|
|
804
|
+
const r = await $ `bun tsc --declaration --outDir dist`.nothrow();
|
|
763
805
|
if (r.exitCode !== 0) {
|
|
764
806
|
console.error(`❌ tsc --declaration FAILED (exit ${r.exitCode}) — emitted dist/*.d.ts may ` +
|
|
765
807
|
`be stale or contain type errors. Build marked failed.`);
|
|
766
808
|
libraryBuildFailed = true;
|
|
767
809
|
}
|
|
768
810
|
}
|
|
811
|
+
/*
|
|
812
|
+
A bare tsc copies bundler-style specifiers into dist/ unchanged, and Node cannot load the
|
|
813
|
+
result (#169). Say so loudly on those two paths. Not a failure: it would break working
|
|
814
|
+
builds for consumers who only ever bundle, on a patch upgrade. libraryBundle fails on it.
|
|
815
|
+
*/
|
|
816
|
+
if (!libraryBuildFailed &&
|
|
817
|
+
!config.libraryBuild &&
|
|
818
|
+
!config.libraryBundle &&
|
|
819
|
+
(config.libraryTsconfig || config.emitLibrary) &&
|
|
820
|
+
existsSync(DIST)) {
|
|
821
|
+
const bad = extensionlessImports(DIST);
|
|
822
|
+
if (bad.length) {
|
|
823
|
+
console.warn(`⚠️ dist/ has ${bad.length} relative import(s) without a file extension — Node rejects them ` +
|
|
824
|
+
`(ERR_MODULE_NOT_FOUND), even though Bun and bundlers resolve them. Use \`libraryBundle\` ` +
|
|
825
|
+
`instead of ${config.libraryTsconfig ? 'libraryTsconfig' : 'emitLibrary'} (tosijs-ui#169). First: ${bad[0]}`);
|
|
826
|
+
}
|
|
827
|
+
}
|
|
769
828
|
// The hydration bundle. If bundleEntry is set we build it (IIFE), else pages
|
|
770
829
|
// load config.scriptUrl (default /iife.js) — a prebuilt/CDN bundle the
|
|
771
830
|
// consumer supplies (e.g. via staticDirs or an absolute URL).
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { LibraryBundle } from './library-bundle.js';
|
|
1
2
|
import type { ProjectLinks, LinkItem } from '../../doc-browser.js';
|
|
2
3
|
import type { DocSystemTheme } from '../doc-system-styles.js';
|
|
3
4
|
import type { Doc } from './docs.js';
|
|
@@ -192,10 +193,23 @@ export interface SiteConfig {
|
|
|
192
193
|
*/
|
|
193
194
|
prebuild?: () => void | Promise<void>;
|
|
194
195
|
/**
|
|
195
|
-
*
|
|
196
|
-
*
|
|
197
|
-
*
|
|
198
|
-
*
|
|
196
|
+
* Build the library as a BUNDLE — the recommended library build (#169). `bun build` bundles
|
|
197
|
+
* `entries` with splitting into `dist/` (declared dependencies and peers stay imports), then
|
|
198
|
+
* `tsc` emits declarations only. The output is checked for relative imports without a file
|
|
199
|
+
* extension, which Node cannot load, and the build fails on any. Owns `dist/` (cleaned each
|
|
200
|
+
* build). Takes precedence over `libraryTsconfig` / `emitLibrary`; a `libraryBuild`
|
|
201
|
+
* function takes precedence over it.
|
|
202
|
+
*
|
|
203
|
+
* ```ts
|
|
204
|
+
* libraryBundle: { entries: ['src/index.ts', 'src/core.ts'] }
|
|
205
|
+
* ```
|
|
206
|
+
*/
|
|
207
|
+
libraryBundle?: LibraryBundle;
|
|
208
|
+
/**
|
|
209
|
+
* Also build the library: `tsc --declaration --outDir dist` (ESM + types). Default false.
|
|
210
|
+
* **Prefer `libraryBundle`:** a bare tsc copies bundler-style specifiers (`from './model'`)
|
|
211
|
+
* into `dist/` unchanged, and Node cannot load that; the build warns when it happens.
|
|
212
|
+
* Ignored when `libraryTsconfig` is set.
|
|
199
213
|
*/
|
|
200
214
|
emitLibrary?: boolean;
|
|
201
215
|
/**
|