tjs-lang 0.5.4 → 0.6.0
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/CLAUDE.md +33 -13
- package/README.md +4 -4
- package/bin/dev.ts +5 -1
- package/demo/docs.json +14 -2
- package/demo/index.html +2 -2
- package/demo/src/capabilities.ts +109 -2
- package/demo/src/demo-nav.ts +137 -203
- package/demo/src/imports.ts +43 -9
- package/demo/src/index.ts +179 -29
- package/demo/src/playground-shared.ts +11 -4
- package/demo/src/playground.ts +2 -2
- package/demo/src/tjs-playground.ts +294 -11
- package/demo/src/ts-playground.ts +239 -0
- package/dist/index.js +135 -127
- package/dist/index.js.map +6 -5
- package/dist/src/cli/commands/emit.d.ts +3 -0
- package/dist/src/lang/emitters/dts.d.ts +48 -0
- package/dist/src/lang/emitters/from-ts.d.ts +2 -0
- package/dist/src/lang/index.d.ts +1 -0
- package/dist/tjs-batteries.js +3 -3
- package/dist/tjs-batteries.js.map +2 -2
- package/dist/tjs-full.js +135 -127
- package/dist/tjs-full.js.map +6 -5
- package/dist/tjs-transpiler.js +2 -349
- package/dist/tjs-transpiler.js.map +4 -19
- package/package.json +1 -1
- package/src/cli/commands/emit.ts +26 -0
- package/src/cli/tjs.ts +4 -1
- package/src/lang/codegen.test.ts +55 -0
- package/src/lang/emitters/dts.test.ts +406 -0
- package/src/lang/emitters/dts.ts +588 -0
- package/src/lang/emitters/from-ts.ts +244 -20
- package/src/lang/index.ts +5 -0
- package/src/lang/typescript-syntax.test.ts +358 -0
package/demo/src/demo-nav.ts
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* - TJS Docs (documentation that opens in floating viewer)
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
import { Component, elements, ElementCreator, vars,
|
|
11
|
+
import { Component, elements, ElementCreator, vars, bind } from 'tosijs'
|
|
12
12
|
import {
|
|
13
13
|
xinFloat,
|
|
14
14
|
XinFloat,
|
|
@@ -75,6 +75,7 @@ interface DocItem {
|
|
|
75
75
|
export class DemoNav extends Component {
|
|
76
76
|
private _docs: DocItem[] = []
|
|
77
77
|
private _appState: any = null // boxed proxy from index.ts
|
|
78
|
+
private _built = false
|
|
78
79
|
private floatViewer: XinFloat | null = null
|
|
79
80
|
private mdViewer: MarkdownViewer | null = null
|
|
80
81
|
|
|
@@ -93,42 +94,7 @@ export class DemoNav extends Component {
|
|
|
93
94
|
set appState(state: any) {
|
|
94
95
|
this._appState = state
|
|
95
96
|
if (!state) return
|
|
96
|
-
|
|
97
|
-
observe(/^app\.(currentView|currentExample)/, () => {
|
|
98
|
-
this.rebuildNav()
|
|
99
|
-
this.updateCurrentIndicator()
|
|
100
|
-
})
|
|
101
|
-
observe('app.openSection', () => {
|
|
102
|
-
this.rebuildNav()
|
|
103
|
-
})
|
|
104
|
-
// Initial sync
|
|
105
|
-
this.rebuildNav()
|
|
106
|
-
this.updateCurrentIndicator()
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
private get _currentView(): string {
|
|
110
|
-
return this._appState?.currentView?.valueOf() ?? 'home'
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
private get _currentExampleName(): string | null {
|
|
114
|
-
const ex = this._appState?.currentExample?.valueOf()
|
|
115
|
-
if (!ex) return null
|
|
116
|
-
return ex.name || ex.title || null
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
private get _openSection(): string | null {
|
|
120
|
-
return this._appState?.openSection?.valueOf() ?? null
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
private updateCurrentIndicator() {
|
|
124
|
-
const exName = this._currentExampleName
|
|
125
|
-
const items = this.querySelectorAll('.nav-item')
|
|
126
|
-
items.forEach((item) => {
|
|
127
|
-
const itemName = item.textContent?.trim()
|
|
128
|
-
item.classList.toggle('current', itemName === exName)
|
|
129
|
-
})
|
|
130
|
-
const homeLink = this.querySelector('.home-link')
|
|
131
|
-
homeLink?.classList.toggle('current', this._currentView === 'home')
|
|
97
|
+
this.tryBuild()
|
|
132
98
|
}
|
|
133
99
|
|
|
134
100
|
get docs(): DocItem[] {
|
|
@@ -137,8 +103,16 @@ export class DemoNav extends Component {
|
|
|
137
103
|
|
|
138
104
|
set docs(value: DocItem[]) {
|
|
139
105
|
this._docs = value
|
|
140
|
-
this.
|
|
141
|
-
|
|
106
|
+
this.tryBuild()
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Build nav once when both docs and appState are available
|
|
110
|
+
private tryBuild() {
|
|
111
|
+
if (this._built || !this._appState || !this._docs.length) return
|
|
112
|
+
const container = this.querySelector('.nav-sections')
|
|
113
|
+
if (!container) return
|
|
114
|
+
this._built = true
|
|
115
|
+
this.buildNav(container)
|
|
142
116
|
}
|
|
143
117
|
|
|
144
118
|
// Light DOM styles (no static styleSpec)
|
|
@@ -273,9 +247,7 @@ export class DemoNav extends Component {
|
|
|
273
247
|
|
|
274
248
|
connectedCallback() {
|
|
275
249
|
super.connectedCallback()
|
|
276
|
-
this.
|
|
277
|
-
// Update indicator after DOM is ready
|
|
278
|
-
this.updateCurrentIndicator()
|
|
250
|
+
this.tryBuild()
|
|
279
251
|
}
|
|
280
252
|
|
|
281
253
|
// Group labels for display
|
|
@@ -316,199 +288,156 @@ export class DemoNav extends Component {
|
|
|
316
288
|
>(examples: T[], renderItem: (ex: T) => HTMLElement): HTMLElement[] {
|
|
317
289
|
const grouped = new Map<string, T[]>()
|
|
318
290
|
|
|
319
|
-
// Group examples
|
|
320
291
|
for (const ex of examples) {
|
|
321
292
|
const group = ex.group || 'other'
|
|
322
|
-
if (!grouped.has(group))
|
|
323
|
-
grouped.set(group, [])
|
|
324
|
-
}
|
|
293
|
+
if (!grouped.has(group)) grouped.set(group, [])
|
|
325
294
|
grouped.get(group)!.push(ex)
|
|
326
295
|
}
|
|
327
296
|
|
|
328
|
-
// Sort groups by GROUP_ORDER
|
|
329
297
|
const sortedGroups = Array.from(grouped.keys()).sort((a, b) => {
|
|
330
298
|
const orderA = DemoNav.GROUP_ORDER.indexOf(a)
|
|
331
299
|
const orderB = DemoNav.GROUP_ORDER.indexOf(b)
|
|
332
300
|
return (orderA === -1 ? 99 : orderA) - (orderB === -1 ? 99 : orderB)
|
|
333
301
|
})
|
|
334
302
|
|
|
335
|
-
|
|
336
|
-
const elements: HTMLElement[] = []
|
|
303
|
+
const elts: HTMLElement[] = []
|
|
337
304
|
for (const group of sortedGroups) {
|
|
338
305
|
const items = grouped.get(group)!
|
|
339
306
|
const label = DemoNav.GROUP_LABELS[group] || group
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
elements.push(div({ class: 'group-header' }, label))
|
|
343
|
-
|
|
344
|
-
// Add items in this group
|
|
345
|
-
for (const ex of items) {
|
|
346
|
-
elements.push(renderItem(ex))
|
|
347
|
-
}
|
|
307
|
+
elts.push(div({ class: 'group-header' }, label))
|
|
308
|
+
for (const ex of items) elts.push(renderItem(ex))
|
|
348
309
|
}
|
|
310
|
+
return elts
|
|
311
|
+
}
|
|
349
312
|
|
|
350
|
-
|
|
313
|
+
// Create a nav item bound to currentExample for highlighting
|
|
314
|
+
private boundNavItem(
|
|
315
|
+
name: string,
|
|
316
|
+
extra: { description?: string; requiresApi?: boolean },
|
|
317
|
+
onClick: () => void
|
|
318
|
+
): HTMLElement {
|
|
319
|
+
const baseClass = extra.requiresApi ? 'nav-item requires-api' : 'nav-item'
|
|
320
|
+
const item = div(
|
|
321
|
+
{
|
|
322
|
+
class: baseClass,
|
|
323
|
+
title: extra.description,
|
|
324
|
+
'data-name': name,
|
|
325
|
+
onClick,
|
|
326
|
+
},
|
|
327
|
+
name
|
|
328
|
+
)
|
|
329
|
+
bind(item, 'app.currentExample', {
|
|
330
|
+
toDOM(el: HTMLElement, example: any) {
|
|
331
|
+
const current = example?.name || example?.title || null
|
|
332
|
+
el.classList.toggle('current', current === name)
|
|
333
|
+
},
|
|
334
|
+
})
|
|
335
|
+
return item
|
|
351
336
|
}
|
|
352
337
|
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
338
|
+
// Create a details section bound to openSection for open/close
|
|
339
|
+
private boundSection(
|
|
340
|
+
sectionId: string,
|
|
341
|
+
icon: Element,
|
|
342
|
+
label: string,
|
|
343
|
+
children: HTMLElement[]
|
|
344
|
+
): HTMLElement {
|
|
345
|
+
const det = details(
|
|
346
|
+
{
|
|
347
|
+
'data-section': sectionId,
|
|
348
|
+
onToggle: this.handleToggle,
|
|
349
|
+
},
|
|
350
|
+
summary(span({ class: 'section-icon' }, icon), label),
|
|
351
|
+
div({ class: 'section-content' }, ...children)
|
|
352
|
+
)
|
|
353
|
+
bind(det, 'app.openSection', {
|
|
354
|
+
toDOM(el: HTMLDetailsElement, section: string | null) {
|
|
355
|
+
el.open = section === sectionId
|
|
356
|
+
},
|
|
357
|
+
})
|
|
358
|
+
return det
|
|
359
|
+
}
|
|
356
360
|
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
)
|
|
361
|
+
// Build the nav once — all reactive updates via bind
|
|
362
|
+
private buildNav(container: Element) {
|
|
363
|
+
const homeLink = div(
|
|
364
|
+
{
|
|
365
|
+
class: 'home-link',
|
|
366
|
+
onClick: () => this.selectHome(),
|
|
367
|
+
},
|
|
368
|
+
span({ class: 'section-icon' }, icons.home({ size: 16 })),
|
|
369
|
+
'Home'
|
|
370
|
+
)
|
|
371
|
+
bind(homeLink, 'app.currentView', {
|
|
372
|
+
toDOM(el: HTMLElement, view: string) {
|
|
373
|
+
el.classList.toggle('current', view === 'home')
|
|
374
|
+
},
|
|
375
|
+
})
|
|
369
376
|
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
'TypeScript Examples'
|
|
380
|
-
),
|
|
381
|
-
div(
|
|
382
|
-
{ class: 'section-content' },
|
|
383
|
-
...this.renderGroupedExamples(tsExamples, (ex) =>
|
|
384
|
-
div(
|
|
385
|
-
{
|
|
386
|
-
class: 'nav-item',
|
|
387
|
-
title: ex.description,
|
|
388
|
-
onClick: () => this.selectTsExample(ex),
|
|
389
|
-
},
|
|
390
|
-
ex.name
|
|
391
|
-
)
|
|
392
|
-
)
|
|
377
|
+
container.append(
|
|
378
|
+
homeLink,
|
|
379
|
+
|
|
380
|
+
this.boundSection(
|
|
381
|
+
'ts-demos',
|
|
382
|
+
icons.code({ size: 16 }),
|
|
383
|
+
'TypeScript Examples',
|
|
384
|
+
this.renderGroupedExamples(tsExamples, (ex) =>
|
|
385
|
+
this.boundNavItem(ex.name, ex, () => this.selectTsExample(ex))
|
|
393
386
|
)
|
|
394
387
|
),
|
|
395
388
|
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
{
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
span({ class: 'section-icon' }, icons.code({ size: 16 })),
|
|
405
|
-
'TJS Examples'
|
|
406
|
-
),
|
|
407
|
-
div(
|
|
408
|
-
{ class: 'section-content' },
|
|
409
|
-
...this.renderGroupedExamples(this.tjsExamples, (ex) =>
|
|
410
|
-
div(
|
|
411
|
-
{
|
|
412
|
-
class: 'nav-item',
|
|
413
|
-
title: ex.description,
|
|
414
|
-
onClick: () => this.selectTjsExample(ex),
|
|
415
|
-
},
|
|
416
|
-
ex.title || ex.name
|
|
417
|
-
)
|
|
418
|
-
)
|
|
419
|
-
)
|
|
389
|
+
this.boundSection(
|
|
390
|
+
'tjs-demos',
|
|
391
|
+
icons.code({ size: 16 }),
|
|
392
|
+
'TJS Examples',
|
|
393
|
+
this.renderGroupedExamples(this.tjsExamples, (ex) => {
|
|
394
|
+
const name = ex.title || ex.name || 'Untitled'
|
|
395
|
+
return this.boundNavItem(name, ex, () => this.selectTjsExample(ex))
|
|
396
|
+
})
|
|
420
397
|
),
|
|
421
398
|
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
{
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
span({ class: 'section-icon' }, icons.code({ size: 16 })),
|
|
431
|
-
'AJS Examples'
|
|
432
|
-
),
|
|
433
|
-
div(
|
|
434
|
-
{ class: 'section-content' },
|
|
435
|
-
...this.renderGroupedExamples(this.ajsExamples, (ex) =>
|
|
436
|
-
div(
|
|
437
|
-
{
|
|
438
|
-
class: ex.requiresApi ? 'nav-item requires-api' : 'nav-item',
|
|
439
|
-
title: ex.description,
|
|
440
|
-
onClick: () => this.selectAjsExample(ex),
|
|
441
|
-
},
|
|
442
|
-
ex.title || ex.name
|
|
443
|
-
)
|
|
444
|
-
)
|
|
445
|
-
)
|
|
399
|
+
this.boundSection(
|
|
400
|
+
'ajs-demos',
|
|
401
|
+
icons.code({ size: 16 }),
|
|
402
|
+
'AJS Examples',
|
|
403
|
+
this.renderGroupedExamples(this.ajsExamples, (ex) => {
|
|
404
|
+
const name = ex.title || ex.name || 'Untitled'
|
|
405
|
+
return this.boundNavItem(name, ex, () => this.selectAjsExample(ex))
|
|
406
|
+
})
|
|
446
407
|
),
|
|
447
408
|
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
{
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
},
|
|
455
|
-
summary(
|
|
456
|
-
span({ class: 'section-icon' }, icons.book({ size: 16 })),
|
|
457
|
-
'TJS Docs'
|
|
458
|
-
),
|
|
459
|
-
div(
|
|
460
|
-
{ class: 'section-content' },
|
|
461
|
-
...this.getTjsDocs().map((doc) =>
|
|
462
|
-
div(
|
|
463
|
-
{
|
|
464
|
-
class: 'nav-item',
|
|
465
|
-
onClick: () => this.selectDoc(doc),
|
|
466
|
-
},
|
|
467
|
-
doc.title
|
|
468
|
-
)
|
|
469
|
-
)
|
|
409
|
+
this.boundSection(
|
|
410
|
+
'tjs-docs',
|
|
411
|
+
icons.book({ size: 16 }),
|
|
412
|
+
'TJS Docs',
|
|
413
|
+
this.getTjsDocs().map((doc) =>
|
|
414
|
+
this.boundNavItem(doc.title, doc, () => this.selectDoc(doc))
|
|
470
415
|
)
|
|
471
416
|
),
|
|
472
417
|
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
{
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
},
|
|
480
|
-
summary(
|
|
481
|
-
span({ class: 'section-icon' }, icons.book({ size: 16 })),
|
|
482
|
-
'AJS Docs'
|
|
483
|
-
),
|
|
484
|
-
div(
|
|
485
|
-
{ class: 'section-content' },
|
|
486
|
-
...this.getAjsDocs().map((doc) =>
|
|
487
|
-
div(
|
|
488
|
-
{
|
|
489
|
-
class: 'nav-item',
|
|
490
|
-
onClick: () => this.selectDoc(doc),
|
|
491
|
-
},
|
|
492
|
-
doc.title
|
|
493
|
-
)
|
|
494
|
-
)
|
|
418
|
+
this.boundSection(
|
|
419
|
+
'ajs-docs',
|
|
420
|
+
icons.book({ size: 16 }),
|
|
421
|
+
'AJS Docs',
|
|
422
|
+
this.getAjsDocs().map((doc) =>
|
|
423
|
+
this.boundNavItem(doc.title, doc, () => this.selectDoc(doc))
|
|
495
424
|
)
|
|
496
425
|
)
|
|
497
426
|
)
|
|
498
427
|
}
|
|
499
428
|
|
|
500
429
|
handleToggle = (event: Event) => {
|
|
501
|
-
const
|
|
502
|
-
const section =
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
430
|
+
const det = event.target as HTMLDetailsElement
|
|
431
|
+
const section = det.getAttribute('data-section')
|
|
432
|
+
if (!this._appState) return
|
|
433
|
+
|
|
434
|
+
const current = this._appState.openSection.valueOf()
|
|
435
|
+
if (det.open && current !== section) {
|
|
436
|
+
// User opened a section — update state (bind handles the rest)
|
|
437
|
+
this._appState.openSection.value = section
|
|
438
|
+
} else if (!det.open && current === section) {
|
|
439
|
+
// User closed the current section
|
|
440
|
+
this._appState.openSection.value = null
|
|
512
441
|
}
|
|
513
442
|
}
|
|
514
443
|
|
|
@@ -602,6 +531,7 @@ export class DemoNav extends Component {
|
|
|
602
531
|
padding: '4px',
|
|
603
532
|
border: 'none',
|
|
604
533
|
background: 'transparent',
|
|
534
|
+
color: vars.textColor,
|
|
605
535
|
cursor: 'pointer',
|
|
606
536
|
},
|
|
607
537
|
},
|
|
@@ -620,7 +550,8 @@ export class DemoNav extends Component {
|
|
|
620
550
|
width: '500px',
|
|
621
551
|
maxWidth: 'calc(100vw - 40px)',
|
|
622
552
|
maxHeight: '80vh',
|
|
623
|
-
background:
|
|
553
|
+
background: vars.background,
|
|
554
|
+
color: vars.textColor,
|
|
624
555
|
borderRadius: '8px',
|
|
625
556
|
boxShadow: '0 4px 20px rgba(0,0,0,0.15)',
|
|
626
557
|
overflow: 'hidden',
|
|
@@ -634,13 +565,16 @@ export class DemoNav extends Component {
|
|
|
634
565
|
display: 'flex',
|
|
635
566
|
alignItems: 'center',
|
|
636
567
|
padding: '6px 12px',
|
|
637
|
-
background:
|
|
638
|
-
borderBottom:
|
|
568
|
+
background: vars.codeBackground,
|
|
569
|
+
borderBottom: `1px solid ${vars.codeBorder}`,
|
|
639
570
|
cursor: 'move',
|
|
640
571
|
},
|
|
641
572
|
},
|
|
642
573
|
span(
|
|
643
|
-
{
|
|
574
|
+
{
|
|
575
|
+
class: 'float-title',
|
|
576
|
+
style: { flex: '1', fontWeight: '500', color: vars.textColor },
|
|
577
|
+
},
|
|
644
578
|
doc.title
|
|
645
579
|
),
|
|
646
580
|
closeBtn
|
package/demo/src/imports.ts
CHANGED
|
@@ -20,19 +20,28 @@ const moduleCache = new Map<string, string>()
|
|
|
20
20
|
// Common packages with pinned versions and ESM paths
|
|
21
21
|
// Packages with proper "exports" or "module" fields in package.json
|
|
22
22
|
// may work without explicit paths, but it's safer to specify them
|
|
23
|
-
|
|
24
|
-
string
|
|
25
|
-
|
|
26
|
-
|
|
23
|
+
interface PinnedPackage {
|
|
24
|
+
version: string
|
|
25
|
+
path?: string
|
|
26
|
+
cdn?: string
|
|
27
|
+
// Transitive deps to include in the import map when this package is imported
|
|
28
|
+
deps?: string[]
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const PINNED_PACKAGES: Record<string, PinnedPackage> = {
|
|
27
32
|
// tjs-lang itself (used by demos like Universal Endpoint)
|
|
28
33
|
'tjs-lang': {
|
|
29
|
-
version: '0.5.
|
|
30
|
-
cdn: 'https://cdn.jsdelivr.net/npm/tjs-lang@0.5.
|
|
34
|
+
version: '0.5.4',
|
|
35
|
+
cdn: 'https://cdn.jsdelivr.net/npm/tjs-lang@0.5.4/dist/index.js',
|
|
31
36
|
},
|
|
32
37
|
|
|
33
38
|
// tosijs ecosystem
|
|
34
39
|
tosijs: { version: '1.2.0', path: '/dist/module.js' },
|
|
35
|
-
'tosijs-ui': {
|
|
40
|
+
'tosijs-ui': {
|
|
41
|
+
version: '1.2.0',
|
|
42
|
+
path: '/dist/index.js',
|
|
43
|
+
deps: ['tosijs', 'marked'],
|
|
44
|
+
},
|
|
36
45
|
|
|
37
46
|
// Utilities - lodash-es is native ESM
|
|
38
47
|
'lodash-es': { version: '4.17.21' },
|
|
@@ -56,13 +65,38 @@ const PINNED_PACKAGES: Record<
|
|
|
56
65
|
'react-dom': {
|
|
57
66
|
version: '18.2.0',
|
|
58
67
|
cdn: 'https://cdn.jsdelivr.net/npm/react-dom@18.2.0/+esm',
|
|
68
|
+
deps: ['react'],
|
|
59
69
|
},
|
|
60
70
|
'react-dom/client': {
|
|
61
71
|
version: '18.2.0',
|
|
62
72
|
cdn: 'https://cdn.jsdelivr.net/npm/react-dom@18.2.0/client/+esm',
|
|
73
|
+
deps: ['react', 'react-dom'],
|
|
63
74
|
},
|
|
64
75
|
}
|
|
65
76
|
|
|
77
|
+
/**
|
|
78
|
+
* Expand a list of specifiers to include transitive deps from PINNED_PACKAGES.
|
|
79
|
+
* Walks the deps graph to collect all needed packages.
|
|
80
|
+
*/
|
|
81
|
+
function expandWithDeps(specifiers: string[]): string[] {
|
|
82
|
+
const all = new Set(specifiers)
|
|
83
|
+
const queue = [...specifiers]
|
|
84
|
+
while (queue.length > 0) {
|
|
85
|
+
const spec = queue.pop()!
|
|
86
|
+
const { name } = parseSpecifier(spec)
|
|
87
|
+
const pinned = PINNED_PACKAGES[spec] || PINNED_PACKAGES[name]
|
|
88
|
+
if (pinned?.deps) {
|
|
89
|
+
for (const dep of pinned.deps) {
|
|
90
|
+
if (!all.has(dep)) {
|
|
91
|
+
all.add(dep)
|
|
92
|
+
queue.push(dep)
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return [...all]
|
|
98
|
+
}
|
|
99
|
+
|
|
66
100
|
/**
|
|
67
101
|
* Extract import specifiers from source code
|
|
68
102
|
*/
|
|
@@ -156,7 +190,7 @@ export function generateImportMap(specifiers: string[]): {
|
|
|
156
190
|
} {
|
|
157
191
|
const imports: Record<string, string> = {}
|
|
158
192
|
|
|
159
|
-
for (const specifier of specifiers) {
|
|
193
|
+
for (const specifier of expandWithDeps(specifiers)) {
|
|
160
194
|
imports[specifier] = getCDNUrl(specifier)
|
|
161
195
|
}
|
|
162
196
|
|
|
@@ -300,7 +334,7 @@ export async function resolveImports(source: string): Promise<{
|
|
|
300
334
|
errors: string[]
|
|
301
335
|
localModules: string[]
|
|
302
336
|
}> {
|
|
303
|
-
const specifiers = extractImports(source)
|
|
337
|
+
const specifiers = expandWithDeps(extractImports(source))
|
|
304
338
|
const errors: string[] = []
|
|
305
339
|
const imports: Record<string, string> = {}
|
|
306
340
|
const localModules: string[] = []
|