tosijs-ui 1.9.5 → 1.9.6
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 +77 -0
- package/dist/data-table.d.ts +17 -0
- package/dist/data-table.js +14 -4
- package/dist/doc-system/site/dev-server.d.ts +4 -0
- package/dist/doc-system/site/dev-server.js +117 -3
- package/dist/doc-system/site/orchestrator.js +19 -0
- package/dist/iife.js +70 -70
- package/dist/iife.js.map +7 -6
- package/dist/make-sorter.js +15 -0
- package/dist/natural-compare.d.ts +16 -0
- package/dist/natural-compare.js +103 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/llms.txt +3 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,82 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.9.6
|
|
4
|
+
|
|
5
|
+
**`<tosi-table>` sorts correctly** (#62, reported by snowfox). Two independent defects, one
|
|
6
|
+
in each half of the column comparator.
|
|
7
|
+
|
|
8
|
+
**Numeric strings sorted by first digit.** The comparator used a raw `>`, which compares
|
|
9
|
+
strings lexically — so `'9'` sorted after `'399'`, and `'1200'` before `'3.5'`. Real data
|
|
10
|
+
is full of numeric strings: CSV and TSV imports, BigQuery exports, JSON where numbers
|
|
11
|
+
arrived as text, anything id-shaped. Sorting now compares naturally:
|
|
12
|
+
|
|
13
|
+
- numeric values (including numeric strings) compare as numbers, decimals and negatives
|
|
14
|
+
included
|
|
15
|
+
- very long integers stay exact — two 20-digit ids differing in the last digit order
|
|
16
|
+
correctly, where a plain `parseFloat` comparison calls them equal
|
|
17
|
+
- text compares with a locale collator, so accented letters order the way the reader
|
|
18
|
+
expects rather than the way ASCII does
|
|
19
|
+
- blank cells sort **last in both directions** — a descending sort that opens on a
|
|
20
|
+
screenful of empty cells is never what was clicked for
|
|
21
|
+
|
|
22
|
+
The old comparator also never returned `0`, so two *equal* values each claimed to be
|
|
23
|
+
greater than the other. `Array.sort` is entitled to turn an inconsistent comparator into
|
|
24
|
+
arbitrary output, not merely wrong output.
|
|
25
|
+
|
|
26
|
+
**New `ColumnOptions.sortValue`** — sort by what the cell *shows*, when that differs from
|
|
27
|
+
what the row stores. A column with a custom `dataCell` renders whatever it likes while the
|
|
28
|
+
sort keyed on `prop`, so the rows reordered by an invisible value:
|
|
29
|
+
|
|
30
|
+
```typescript
|
|
31
|
+
{
|
|
32
|
+
name: 'Invoice #',
|
|
33
|
+
prop: 'Customer invoice ID', // what the row stores — CSV export, lookups
|
|
34
|
+
dataCell: invoiceCell, // what the reader sees
|
|
35
|
+
sortValue: (row) => row['Invoice number'] || row['Customer invoice ID'],
|
|
36
|
+
}
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Purely additive; `table.sort` still overrides everything for cases a per-column value
|
|
40
|
+
cannot express.
|
|
41
|
+
|
|
42
|
+
**New `naturalCompare` / `naturalSorter` / `isBlank`** exported from `tosijs-ui`, since the
|
|
43
|
+
same comparison is wanted outside tables.
|
|
44
|
+
|
|
45
|
+
### Build system
|
|
46
|
+
|
|
47
|
+
- **Code-split chunks land in `_chunks/` instead of the web root** (#64). `--splitting`
|
|
48
|
+
emits one hashed chunk per dynamic import, flat beside `index.html` — for a corpus that
|
|
49
|
+
pulls something large, that is thousands of files: tosijs-3d reported **2,473 hashed
|
|
50
|
+
chunks among 2,604 files**, all tracked in git because `docs/` is the Pages source.
|
|
51
|
+
|
|
52
|
+
The cost that hurts is not legibility. Any dependency bump rewrites every hash, so
|
|
53
|
+
upgrading one package became a multi-thousand-file commit — which makes the standing
|
|
54
|
+
"never commit `docs/` from a feature push" hazard far worse, since a stray `git add -A`
|
|
55
|
+
moves thousands of files and the diff is unreviewable. Assets move to `_assets/` for the
|
|
56
|
+
same reason.
|
|
57
|
+
|
|
58
|
+
- **The dev server no longer times out mid-transfer on LAN loads** (#63). `Bun.serve`
|
|
59
|
+
defaults to a 10s `idleTimeout`, sized for small API responses — but a doc site's bundle
|
|
60
|
+
can be multiple MB, and over wifi to a phone or a second laptop that legitimately takes
|
|
61
|
+
longer, so the connection closed part-way. It never reproduces on loopback, which makes
|
|
62
|
+
it look like a client-side stall. Now 120s, overridable with
|
|
63
|
+
`DEV_REQUEST_TIMEOUT_SECONDS`. Testing on real devices over the LAN is exactly what the
|
|
64
|
+
dev cert covers `<host>.local` for.
|
|
65
|
+
|
|
66
|
+
### Also
|
|
67
|
+
|
|
68
|
+
- **The release lane now checks, against the packed manifest, that each peer dependency we
|
|
69
|
+
also develop against is pinned within the range we publish** (#57). A floor the library
|
|
70
|
+
is not itself built against is a contract nobody tests, and the only signal was an
|
|
71
|
+
adopter's install warning. `tosijs`'s `^1.7.8` floor is not arbitrary — it encodes
|
|
72
|
+
tosijs#20 (the `parts` proxy crossing into nested components) and #21 — and that reason
|
|
73
|
+
is now recorded in `CLAUDE.md` rather than only in an old changelog entry.
|
|
74
|
+
|
|
75
|
+
`makeSorter` deliberately keeps its existing comparison: switching it would change *case*
|
|
76
|
+
ordering (`'Zed'` before `'alice'` today) in a general-purpose utility where that has not
|
|
77
|
+
been reported as a problem. Coerce numeric strings in the valuator, or pass values through
|
|
78
|
+
`naturalCompare` yourself.
|
|
79
|
+
|
|
3
80
|
## 1.9.5
|
|
4
81
|
|
|
5
82
|
**`localize()` no longer destroys literal `#`** (#55, reported by snowfox).
|
package/dist/data-table.d.ts
CHANGED
|
@@ -10,6 +10,23 @@ export interface ColumnOptions {
|
|
|
10
10
|
type?: ValueRendererType;
|
|
11
11
|
pinned?: 'left' | 'right';
|
|
12
12
|
sort?: false | 'ascending' | 'descending';
|
|
13
|
+
/**
|
|
14
|
+
* What this column SORTS by, when that differs from what it stores.
|
|
15
|
+
*
|
|
16
|
+
* Defaults to `row[prop]`. A column with a custom `dataCell` renders whatever it likes
|
|
17
|
+
* while the sort keys on `prop` — so when those differ, clicking "Sort Ascending"
|
|
18
|
+
* reorders rows by a value the reader cannot see, which reads as "sorting is broken"
|
|
19
|
+
* rather than "sorting a different field" (tosijs-ui#62).
|
|
20
|
+
*
|
|
21
|
+
* { name: 'Invoice #', prop: 'Customer invoice ID', dataCell: invoiceCell,
|
|
22
|
+
* sortValue: (row) => row['Invoice number'] || row['Customer invoice ID'] }
|
|
23
|
+
*
|
|
24
|
+
* Every other escape hatch costs more: `table.sort` is table-wide (one derived column
|
|
25
|
+
* means reimplementing sorting for all of them), replacing `headerCell` means
|
|
26
|
+
* reimplementing the header menu, and changing `prop` breaks CSV export and anything
|
|
27
|
+
* else keyed on it.
|
|
28
|
+
*/
|
|
29
|
+
sortValue?: (row: any) => unknown;
|
|
13
30
|
headerCell?: (options: ColumnOptions) => HTMLElement;
|
|
14
31
|
dataCell?: (options: ColumnOptions) => HTMLElement;
|
|
15
32
|
}
|
package/dist/data-table.js
CHANGED
|
@@ -566,6 +566,7 @@ As well as any column names you want localized.
|
|
|
566
566
|
/*{ "parent": "Components" }*/
|
|
567
567
|
import { Component as WebComponent, elements, vars, varDefault, tosiValue, getListItem, getListBinding, tosi, } from 'tosijs';
|
|
568
568
|
import { trackDrag } from './track-drag.js';
|
|
569
|
+
import { naturalSorter } from './natural-compare.js';
|
|
569
570
|
import { icons } from './icons.js';
|
|
570
571
|
import { valueRenderer } from './value-renderer.js';
|
|
571
572
|
import { popMenu } from './menu.js';
|
|
@@ -889,10 +890,19 @@ export class TosiTable extends WebComponent {
|
|
|
889
890
|
if (!sortColumn) {
|
|
890
891
|
return undefined;
|
|
891
892
|
}
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
893
|
+
/*
|
|
894
|
+
Two defects lived in these three lines (tosijs-ui#62).
|
|
895
|
+
|
|
896
|
+
It keyed on `prop`, so a column whose `dataCell` shows something else sorted by a value
|
|
897
|
+
the reader cannot see. And `>` is a LEXICAL compare, so any column of numeric strings
|
|
898
|
+
sorted by first digit — `'9' > '399'` is true, and real data is full of numeric strings
|
|
899
|
+
from CSV, BigQuery and JSON. Worse, `a > b ? 1 : -1` never returns 0, so two equal
|
|
900
|
+
values each claimed to be greater than the other; `Array.sort` is entitled to turn an
|
|
901
|
+
inconsistent comparator into arbitrary output rather than merely wrong output.
|
|
902
|
+
*/
|
|
903
|
+
const { prop, sortValue } = sortColumn;
|
|
904
|
+
const key = sortValue ?? ((row) => row[prop]);
|
|
905
|
+
return naturalSorter(key, sortColumn.sort === 'ascending');
|
|
896
906
|
}
|
|
897
907
|
set sort(sortFunc) {
|
|
898
908
|
if (this._sort !== sortFunc) {
|
|
@@ -68,6 +68,10 @@ export declare function resolveLimitMb(configMb: number | undefined, envMb: stri
|
|
|
68
68
|
export { isLoopbackAddress };
|
|
69
69
|
export declare function haltijaIsDrivable(stdout: string): boolean;
|
|
70
70
|
export declare function haltijaLoaderSnippet(httpsPort: number): string;
|
|
71
|
+
/** br if the client takes it, else gzip, else nothing. */
|
|
72
|
+
export declare function negotiateEncoding(accept: string | null): 'br' | 'gzip' | null;
|
|
73
|
+
/** Is this worth compressing? Already-compressed formats only get bigger. */
|
|
74
|
+
export declare function isCompressible(filePath: string): boolean;
|
|
71
75
|
export declare function devServer(config: SiteConfig, opts?: {
|
|
72
76
|
test?: boolean;
|
|
73
77
|
build?: () => unknown | Promise<unknown>;
|
|
@@ -10,7 +10,8 @@ Build-time only (Bun APIs). Never import this from browser code.
|
|
|
10
10
|
import * as path from 'path';
|
|
11
11
|
import { statSync, existsSync, readFileSync } from 'fs';
|
|
12
12
|
import { tmpdir } from 'os';
|
|
13
|
-
import { $, spawn } from 'bun';
|
|
13
|
+
import { $, spawn, gzipSync } from 'bun';
|
|
14
|
+
import { brotliCompressSync, constants as zlibConstants } from 'node:zlib';
|
|
14
15
|
import { buildSite } from './orchestrator.js';
|
|
15
16
|
import { preflight } from './preflight.js';
|
|
16
17
|
import { auditDependencies, reportAudit } from './audit-guard.js';
|
|
@@ -66,6 +67,12 @@ then shipped an instance of. A range is not a pin, and a cached range is not eve
|
|
|
66
67
|
is something an adopter controls with the tool they already use. `HALTIJA_VERSION` still
|
|
67
68
|
overrides everything.
|
|
68
69
|
*/
|
|
70
|
+
/**
|
|
71
|
+
* Seconds `Bun.serve` will wait on a slow transfer before closing it. Bun's default is 10,
|
|
72
|
+
* sized for API responses rather than multi-MB bundles over LAN wifi (#63). Override with
|
|
73
|
+
* `DEV_REQUEST_TIMEOUT_SECONDS`; Bun caps this at 255.
|
|
74
|
+
*/
|
|
75
|
+
const DEV_IDLE_TIMEOUT_SECONDS = Math.min(255, Number(process.env.DEV_REQUEST_TIMEOUT_SECONDS) || 120);
|
|
69
76
|
const HALTIJA_PKG = process.env.HALTIJA_VERSION ?? 'haltija@^1.11.2';
|
|
70
77
|
/*
|
|
71
78
|
Which haltija do we actually run, and can we say so out loud?
|
|
@@ -335,6 +342,70 @@ async function ensureDevCerts() {
|
|
|
335
342
|
'install instructions if it is missing.\n');
|
|
336
343
|
process.exit(1);
|
|
337
344
|
}
|
|
345
|
+
/*
|
|
346
|
+
Compress text-shaped assets on the way out.
|
|
347
|
+
|
|
348
|
+
The dev server serves REAL DEVICES over the LAN — that is what the mkcert cert covers
|
|
349
|
+
`<host>.local` for — and a doc site's bundle can be multiple MB. Everything text-shaped
|
|
350
|
+
here compresses to about 30%: this repo's 1.21MB iife goes to 0.39MB gzipped, 0.36MB
|
|
351
|
+
brotli; tosijs-3d's 10MB one goes to roughly 3MB. That is the real fix for the LAN stalls
|
|
352
|
+
behind #63 — raising `idleTimeout` stopped the connection dying, this stops it needing the
|
|
353
|
+
extra time.
|
|
354
|
+
|
|
355
|
+
Deliberately DEV-SERVER ONLY. Distribution is a host's job — Cloudflare, Firebase and
|
|
356
|
+
friends do this better and for free — so the build emits nothing precompressed.
|
|
357
|
+
|
|
358
|
+
Brotli at quality 5, not 11: measured on the 1.21MB iife, q5 takes 19ms for 0.36MB while
|
|
359
|
+
q11 takes **1169ms** for 0.32MB. Sixty times the cost for four percent.
|
|
360
|
+
*/
|
|
361
|
+
const COMPRESSIBLE = /\.(js|mjs|css|html|json|svg|map|txt|xml|md|wasm)$/i;
|
|
362
|
+
/*
|
|
363
|
+
A BOUNDED cache, because this process lives for days.
|
|
364
|
+
|
|
365
|
+
Recompressing a 10MB bundle per request would cost more than it saves, so the bytes have
|
|
366
|
+
to be kept — but an unbounded cache in a long-lived dev server is precisely the failure
|
|
367
|
+
that took this machine down twice, and it is invisible to the JS heap until it is not.
|
|
368
|
+
Keyed on path + mtime so a rebuild invalidates naturally; capped in total bytes with
|
|
369
|
+
oldest-out eviction.
|
|
370
|
+
*/
|
|
371
|
+
const COMPRESS_CACHE_MAX_BYTES = 64 * 1024 * 1024;
|
|
372
|
+
const compressCache = new Map();
|
|
373
|
+
let compressCacheBytes = 0;
|
|
374
|
+
function cacheCompressed(key, bytes) {
|
|
375
|
+
// A single asset bigger than the whole budget is not worth evicting everything for.
|
|
376
|
+
if (bytes.byteLength > COMPRESS_CACHE_MAX_BYTES / 2)
|
|
377
|
+
return;
|
|
378
|
+
while (compressCacheBytes + bytes.byteLength > COMPRESS_CACHE_MAX_BYTES &&
|
|
379
|
+
compressCache.size) {
|
|
380
|
+
const oldest = compressCache.keys().next().value;
|
|
381
|
+
compressCacheBytes -= compressCache.get(oldest).byteLength;
|
|
382
|
+
compressCache.delete(oldest);
|
|
383
|
+
}
|
|
384
|
+
compressCache.set(key, bytes);
|
|
385
|
+
compressCacheBytes += bytes.byteLength;
|
|
386
|
+
}
|
|
387
|
+
function compressBytes(bytes, encoding) {
|
|
388
|
+
return encoding === 'br'
|
|
389
|
+
? new Uint8Array(brotliCompressSync(bytes, {
|
|
390
|
+
params: { [zlibConstants.BROTLI_PARAM_QUALITY]: 5 },
|
|
391
|
+
}))
|
|
392
|
+
: gzipSync(bytes);
|
|
393
|
+
}
|
|
394
|
+
/** br if the client takes it, else gzip, else nothing. */
|
|
395
|
+
export function negotiateEncoding(accept) {
|
|
396
|
+
if (!accept)
|
|
397
|
+
return null;
|
|
398
|
+
const a = accept.toLowerCase();
|
|
399
|
+
if (a.includes('br'))
|
|
400
|
+
return 'br';
|
|
401
|
+
if (a.includes('gzip'))
|
|
402
|
+
return 'gzip';
|
|
403
|
+
return null;
|
|
404
|
+
}
|
|
405
|
+
/** Is this worth compressing? Already-compressed formats only get bigger. */
|
|
406
|
+
export function isCompressible(filePath) {
|
|
407
|
+
return COMPRESSIBLE.test(filePath);
|
|
408
|
+
}
|
|
338
409
|
export async function devServer(config, opts = {}) {
|
|
339
410
|
// PORT env wins over the config, so a test harness can bring up its own instance on
|
|
340
411
|
// its own port instead of adopting (or killing — killStrayServer takes the port)
|
|
@@ -532,15 +603,42 @@ export async function devServer(config, opts = {}) {
|
|
|
532
603
|
async function respondFile(filePath, request, viaTunnel = false) {
|
|
533
604
|
const extras = (haltijaDev ? HALTIJA_SNIPPET : '') +
|
|
534
605
|
(testMode ? '' : statusSnippet(request, viaTunnel));
|
|
606
|
+
const encoding = negotiateEncoding(request?.headers.get('accept-encoding') ?? null);
|
|
535
607
|
if (extras && filePath.endsWith('.html')) {
|
|
536
608
|
const html = await Bun.file(filePath).text();
|
|
537
609
|
const injected = html.includes('</body>')
|
|
538
610
|
? html.replace('</body>', `${extras}</body>`)
|
|
539
611
|
: html + extras;
|
|
540
|
-
|
|
541
|
-
|
|
612
|
+
// Injected HTML is built per request, so it is compressed but never cached — the
|
|
613
|
+
// cache key would have to include the injected extras, and HTML is small anyway.
|
|
614
|
+
const body = encoding
|
|
615
|
+
? compressBytes(new TextEncoder().encode(injected), encoding)
|
|
616
|
+
: injected;
|
|
617
|
+
return new Response(body, {
|
|
618
|
+
headers: {
|
|
619
|
+
'Content-Type': 'text/html; charset=utf-8',
|
|
620
|
+
...(encoding ? { 'Content-Encoding': encoding, Vary: 'Accept-Encoding' } : {}),
|
|
621
|
+
},
|
|
622
|
+
});
|
|
623
|
+
}
|
|
624
|
+
if (encoding && isCompressible(filePath)) {
|
|
625
|
+
const file = Bun.file(filePath);
|
|
626
|
+
const key = `${encoding}:${filePath}:${file.lastModified}`;
|
|
627
|
+
let bytes = compressCache.get(key);
|
|
628
|
+
if (!bytes) {
|
|
629
|
+
bytes = compressBytes(new Uint8Array(await file.arrayBuffer()), encoding);
|
|
630
|
+
cacheCompressed(key, bytes);
|
|
631
|
+
}
|
|
632
|
+
return new Response(bytes, {
|
|
633
|
+
headers: {
|
|
634
|
+
'Content-Type': file.type || 'application/octet-stream',
|
|
635
|
+
'Content-Encoding': encoding,
|
|
636
|
+
Vary: 'Accept-Encoding',
|
|
637
|
+
},
|
|
542
638
|
});
|
|
543
639
|
}
|
|
640
|
+
// Everything else streams untouched — images, fonts, epubs and glb are already
|
|
641
|
+
// compressed, and re-encoding them only makes them bigger.
|
|
544
642
|
return new Response(Bun.file(filePath));
|
|
545
643
|
}
|
|
546
644
|
async function handleTestReport(request) {
|
|
@@ -1144,8 +1242,22 @@ export async function devServer(config, opts = {}) {
|
|
|
1144
1242
|
and the OS closes the sockets, and calling `.stop()` first is what segfaulted on the
|
|
1145
1243
|
idle path (#47). A handle we would never legitimately use is an invitation to use it.
|
|
1146
1244
|
*/
|
|
1245
|
+
/*
|
|
1246
|
+
A generous idleTimeout, because this serves BUNDLES to REAL DEVICES.
|
|
1247
|
+
|
|
1248
|
+
`Bun.serve` defaults to 10s, which is sized for small API responses. A doc site's iife
|
|
1249
|
+
can be multiple MB — tosijs-3d ships ~10MB unminified on purpose, so its source stays
|
|
1250
|
+
browseable — and over LAN wifi to a phone or a second laptop that legitimately takes
|
|
1251
|
+
longer than 10s. Bun then closes the connection mid-transfer and the page half-loads.
|
|
1252
|
+
|
|
1253
|
+
It never reproduces on loopback, so it presents as a mysterious client-side stall; the
|
|
1254
|
+
reporter first suspected a bundle-size regression (tosijs-ui#63). Serving real devices
|
|
1255
|
+
over the LAN is exactly what the mkcert cert covers `<host>.local` FOR, so the default
|
|
1256
|
+
is wrong for this server specifically.
|
|
1257
|
+
*/
|
|
1147
1258
|
Bun.serve({
|
|
1148
1259
|
port: PORT,
|
|
1260
|
+
idleTimeout: DEV_IDLE_TIMEOUT_SECONDS,
|
|
1149
1261
|
tls: {
|
|
1150
1262
|
key: Bun.file('./tls/key.pem'),
|
|
1151
1263
|
cert: Bun.file('./tls/certificate.pem'),
|
|
@@ -1184,6 +1296,8 @@ export async function devServer(config, opts = {}) {
|
|
|
1184
1296
|
try {
|
|
1185
1297
|
Bun.serve({
|
|
1186
1298
|
port: tunnelPort,
|
|
1299
|
+
// Same reasoning as the main listener — a tunnelled load is slower, not faster.
|
|
1300
|
+
idleTimeout: DEV_IDLE_TIMEOUT_SECONDS,
|
|
1187
1301
|
hostname: '127.0.0.1',
|
|
1188
1302
|
fetch: (request, srv) => handleRequest(request, srv, true),
|
|
1189
1303
|
});
|
|
@@ -599,6 +599,25 @@ export async function buildSite(config, opts = {}) {
|
|
|
599
599
|
'--minify',
|
|
600
600
|
'--entry-naming',
|
|
601
601
|
'hydrate.js',
|
|
602
|
+
/*
|
|
603
|
+
Code-split chunks go in a SUBDIRECTORY, not the web root.
|
|
604
|
+
|
|
605
|
+
`--splitting` emits one hashed chunk per dynamic import, and they landed flat
|
|
606
|
+
beside `index.html`. For a corpus that pulls something large — Babylon's glTF
|
|
607
|
+
extensions, shader chunks, audio engines — that is thousands of files in the root:
|
|
608
|
+
tosijs-3d reported **2,473 hashed chunks** among 2,604 files (tosijs-ui#64).
|
|
609
|
+
|
|
610
|
+
Three costs, and the second is the dangerous one. `ls docs` stops being usable.
|
|
611
|
+
Any dependency bump rewrites every hash, so a Babylon upgrade becomes a
|
|
612
|
+
multi-thousand-file commit — which makes the standing "don't commit docs/ from a
|
|
613
|
+
feature push" hazard far worse, because a stray `git add -A` now moves thousands
|
|
614
|
+
of files and the diff is unreviewable. (They took their doc site down exactly that
|
|
615
|
+
way.) And a genuinely stale or missing artifact is invisible among the lookalikes.
|
|
616
|
+
*/
|
|
617
|
+
'--chunk-naming',
|
|
618
|
+
'_chunks/[name]-[hash].[ext]',
|
|
619
|
+
'--asset-naming',
|
|
620
|
+
'_assets/[name]-[hash].[ext]',
|
|
602
621
|
...externals.flatMap((ext) => ['--external', ext]),
|
|
603
622
|
], { stdout: 'inherit', stderr: 'inherit' });
|
|
604
623
|
if ((await esm.exited) !== 0) {
|