@ttsc/playground 0.18.4 → 0.19.1

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.
Files changed (43) hide show
  1. package/README.md +1 -1
  2. package/lib/src/compiler/createWorkerCompiler.js +7 -191
  3. package/lib/src/compiler/createWorkerCompiler.js.map +1 -1
  4. package/lib/src/compiler/internal/createWorkerCompilerService.d.ts +18 -0
  5. package/lib/src/compiler/internal/createWorkerCompilerService.js +272 -0
  6. package/lib/src/compiler/internal/createWorkerCompilerService.js.map +1 -0
  7. package/lib/src/npm/collectExternalPackageNames.js +253 -9
  8. package/lib/src/npm/collectExternalPackageNames.js.map +1 -1
  9. package/lib/src/react/ConsoleViewer.js +17 -17
  10. package/lib/src/react/ConsoleViewer.js.map +1 -1
  11. package/lib/src/react/DependencyProgressModal.js +1 -1
  12. package/lib/src/react/DependencyProgressModal.js.map +1 -1
  13. package/lib/src/react/DiagnosticsPanel.js +2 -2
  14. package/lib/src/react/DiagnosticsPanel.js.map +1 -1
  15. package/lib/src/react/ExamplePicker.js +2 -2
  16. package/lib/src/react/ExamplePicker.js.map +1 -1
  17. package/lib/src/react/LintPane.js +3 -3
  18. package/lib/src/react/LintPane.js.map +1 -1
  19. package/lib/src/react/OptionsPanel.js +1 -1
  20. package/lib/src/react/OptionsPanel.js.map +1 -1
  21. package/lib/src/react/PlaygroundShell.js +14 -13
  22. package/lib/src/react/PlaygroundShell.js.map +1 -1
  23. package/lib/src/react/ResultViewer.d.ts +2 -2
  24. package/lib/src/react/ResultViewer.js +3 -3
  25. package/lib/src/react/ResultViewer.js.map +1 -1
  26. package/lib/src/react/SourceEditor.js +1 -1
  27. package/lib/src/react/SourceEditor.js.map +1 -1
  28. package/lib/src/sandbox/createSandboxRequire.js +34 -9
  29. package/lib/src/sandbox/createSandboxRequire.js.map +1 -1
  30. package/package.json +3 -3
  31. package/src/compiler/createWorkerCompiler.ts +8 -236
  32. package/src/compiler/internal/createWorkerCompilerService.ts +358 -0
  33. package/src/npm/collectExternalPackageNames.ts +262 -9
  34. package/src/react/ConsoleViewer.tsx +18 -18
  35. package/src/react/DependencyProgressModal.tsx +11 -11
  36. package/src/react/DiagnosticsPanel.tsx +11 -11
  37. package/src/react/ExamplePicker.tsx +7 -7
  38. package/src/react/LintPane.tsx +5 -5
  39. package/src/react/OptionsPanel.tsx +10 -10
  40. package/src/react/PlaygroundShell.tsx +64 -74
  41. package/src/react/ResultViewer.tsx +4 -4
  42. package/src/react/SourceEditor.tsx +2 -3
  43. package/src/sandbox/createSandboxRequire.ts +32 -9
@@ -15,7 +15,6 @@ import type { IPlaygroundDependencyProgress } from "../structures/IPlaygroundDep
15
15
  import type { IPlaygroundShellProps } from "../structures/IPlaygroundShellProps";
16
16
  import type { ITransformOptions } from "../structures/ITransformOptions";
17
17
  import { ConsoleViewer } from "./ConsoleViewer";
18
- import { createCompilerClient } from "./createCompilerClient";
19
18
  import { DEFAULT_OPTION_TOGGLES } from "./DEFAULT_OPTION_TOGGLES";
20
19
  import { DependencyProgressModal } from "./DependencyProgressModal";
21
20
  import { DiagnosticsPanel } from "./DiagnosticsPanel";
@@ -24,6 +23,7 @@ import { LintPane } from "./LintPane";
24
23
  import { OptionsPanel } from "./OptionsPanel";
25
24
  import { ResultViewer } from "./ResultViewer";
26
25
  import { SourceEditor } from "./SourceEditor";
26
+ import { createCompilerClient } from "./createCompilerClient";
27
27
 
28
28
  const DEFAULT_OPTIONS: ITransformOptions = {
29
29
  typia: true,
@@ -64,9 +64,7 @@ export function PlaygroundShell({
64
64
  const [optionsOpen, setOptionsOpen] = useState(false);
65
65
  const [running, setRunning] = useState(false);
66
66
  const [shareToast, setShareToast] = useState(false);
67
- const [consoleMessages, setConsoleMessages] = useState<IConsoleMessage[]>(
68
- [],
69
- );
67
+ const [consoleMessages, setConsoleMessages] = useState<IConsoleMessage[]>([]);
70
68
  const [executing, setExecuting] = useState(false);
71
69
  const [bootError, setBootError] = useState<unknown>(null);
72
70
  const [bootPhase, setBootPhase] = useState<"booting" | "ready" | "failed">(
@@ -284,8 +282,7 @@ export function PlaygroundShell({
284
282
  }, 2400);
285
283
  return error;
286
284
  } finally {
287
- if (dependencyAbort.current === abort)
288
- dependencyAbort.current = null;
285
+ if (dependencyAbort.current === abort) dependencyAbort.current = null;
289
286
  }
290
287
  });
291
288
  dependencyInstallChain.current = task.then(() => {});
@@ -302,11 +299,7 @@ export function PlaygroundShell({
302
299
  // pane is rendered. Re-running the wasm-heavy pipeline on every tab
303
300
  // click would burn multiple seconds of work per click.
304
301
  const run = useCallback(
305
- async (
306
- input: string,
307
- opts: ITransformOptions,
308
- version: number,
309
- ) => {
302
+ async (input: string, opts: ITransformOptions, version: number) => {
310
303
  const epoch = ++compileEpoch.current;
311
304
  setRunning(true);
312
305
  try {
@@ -544,7 +537,12 @@ export function PlaygroundShell({
544
537
  // React state, so including `source` here would re-create the
545
538
  // callback per keystroke and propagate into the global keydown
546
539
  // useEffect, churning event-listener add/remove every character.
547
- }, [createCompilerService, executeBundle, installDependenciesForSource, options]);
540
+ }, [
541
+ createCompilerService,
542
+ executeBundle,
543
+ installDependenciesForSource,
544
+ options,
545
+ ]);
548
546
 
549
547
  const allDiagnostics = useMemo(() => {
550
548
  const fromCompile: ICompilerService.IDiagnostic[] = [];
@@ -602,10 +600,10 @@ export function PlaygroundShell({
602
600
 
603
601
  if (bootPhase === "failed") {
604
602
  return (
605
- <div className="flex flex-col h-screen w-full items-center justify-center bg-neutral-950 text-neutral-200 gap-5 px-6 text-center">
603
+ <div className="flex h-screen w-full flex-col items-center justify-center gap-5 bg-[#f7fbff] px-6 text-center text-[#102a43]">
606
604
  <span className="text-red-400 text-3xl">⚠</span>
607
605
  <h1 className="text-lg font-mono">Playground failed to boot.</h1>
608
- <pre className="max-w-xl text-[12px] font-mono text-neutral-400 whitespace-pre-wrap break-words">
606
+ <pre className="max-w-xl whitespace-pre-wrap break-words font-mono text-[12px] text-slate-500">
609
607
  {(() => {
610
608
  const e = bootError;
611
609
  if (e instanceof Error) return `${e.name}: ${e.message}`;
@@ -639,7 +637,7 @@ export function PlaygroundShell({
639
637
  }
640
638
  })();
641
639
  }}
642
- className="px-5 py-2 text-xs font-mono text-neutral-900 bg-white rounded-md hover:shadow-[0_0_30px_rgba(255,255,255,0.2)] transition-shadow"
640
+ className="rounded-md bg-[#3178c6] px-5 py-2 font-mono text-xs text-white shadow-[0_8px_22px_rgba(49,120,198,0.24)] transition-colors hover:bg-[#235a97]"
643
641
  >
644
642
  Retry
645
643
  </button>
@@ -648,54 +646,28 @@ export function PlaygroundShell({
648
646
  }
649
647
 
650
648
  return (
651
- <div className="flex flex-col h-screen w-full bg-neutral-950 text-neutral-200 overflow-hidden">
652
- {/* ── Toolbar ── */}
653
- <div className="flex items-center gap-3 px-4 py-2.5 border-b border-neutral-800/70 bg-neutral-950 shrink-0">
654
- {brand}
655
- <span className="text-neutral-700">/</span>
656
- <span className="text-sm text-neutral-400">Playground</span>
657
-
658
- <div className="ml-auto flex items-center gap-2">
659
- <ExamplePicker
660
- examples={examples}
661
- onPick={onPickExample}
662
- groupLabels={exampleGroupLabels}
663
- />
664
- <button
665
- onClick={() => setOptionsOpen((v) => !v)}
666
- className="px-3 py-1.5 text-xs font-mono text-neutral-300 border border-neutral-800 rounded-md hover:border-neutral-600 hover:bg-neutral-900 transition-colors"
667
- >
668
- Options
669
- </button>
670
- <button
671
- onClick={onReset}
672
- className="px-3 py-1.5 text-xs font-mono text-neutral-400 hover:text-neutral-200 transition-colors"
673
- >
674
- Reset
675
- </button>
676
- <button
677
- onClick={onShare}
678
- className="px-3 py-1.5 text-xs font-mono text-neutral-900 bg-white rounded-md hover:shadow-[0_0_30px_rgba(255,255,255,0.2)] transition-shadow"
679
- >
680
- {shareToast ? "Copied ✓" : "Share"}
681
- </button>
682
- </div>
683
- </div>
684
-
649
+ <div className="flex h-screen w-full flex-col overflow-hidden bg-white text-[#102a43]">
685
650
  {sourceFromURL && (
686
- <div className="shrink-0 px-4 py-1.5 text-[11px] font-mono text-amber-200 bg-amber-500/10 border-b border-amber-700/40">
651
+ <div className="shrink-0 border-b border-amber-300 bg-amber-50 px-4 py-1.5 font-mono text-[11px] text-amber-800">
687
652
  Source loaded from share URL. Hit Reset to return to the default
688
653
  example.
689
654
  </div>
690
655
  )}
691
656
 
692
657
  {shareWarn && (
693
- <div className="shrink-0 px-4 py-1.5 text-[11px] font-mono text-amber-200 bg-amber-500/10 border-b border-amber-700/40">
658
+ <div className="shrink-0 border-b border-amber-300 bg-amber-50 px-4 py-1.5 font-mono text-[11px] text-amber-800">
694
659
  {shareWarn}
695
660
  </div>
696
661
  )}
697
662
 
698
- <div className="flex items-center gap-0 border-b border-neutral-800/70 bg-neutral-950 shrink-0">
663
+ <div className="flex shrink-0 flex-wrap items-center gap-0 border-b border-[#c7dff4] bg-white">
664
+ {brand ? (
665
+ <div className="flex items-center gap-2 border-r border-[#c7dff4] px-4 py-2">
666
+ {brand}
667
+ <span className="text-[#9db6cb]">/</span>
668
+ <span className="text-sm text-[#526b82]">Playground</span>
669
+ </div>
670
+ ) : null}
699
671
  {(
700
672
  [
701
673
  { id: "javascript", label: "Compiled JS" },
@@ -707,29 +679,47 @@ export function PlaygroundShell({
707
679
  onClick={() => setTarget(tab.id)}
708
680
  className={`px-4 py-2 text-[12px] font-mono border-b-2 transition-colors ${
709
681
  target === tab.id
710
- ? "text-white border-blue-400"
711
- : "text-neutral-500 border-transparent hover:text-neutral-300"
682
+ ? "border-[#3178c6] text-[#235a97]"
683
+ : "border-transparent text-slate-400 hover:text-[#3178c6]"
712
684
  }`}
713
685
  >
714
686
  {tab.label}
715
687
  </button>
716
688
  ))}
717
- <div className="ml-auto px-4 text-[10px] font-mono text-neutral-600">
718
- {bootPhase === "booting"
719
- ? "booting wasm…"
720
- : running
721
- ? "compiling…"
722
- : "ready"}
689
+ <div className="ml-auto flex w-full items-center justify-end gap-2 border-t border-[#d8e7f4] px-4 py-1.5 sm:w-auto sm:border-t-0 sm:pl-0">
690
+ <ExamplePicker
691
+ examples={examples}
692
+ onPick={onPickExample}
693
+ groupLabels={exampleGroupLabels}
694
+ />
695
+ <button
696
+ onClick={() => setOptionsOpen((v) => !v)}
697
+ className="rounded-md border border-[#b9d5ee] bg-white px-3 py-1.5 font-mono text-xs text-[#235a97] transition-colors hover:border-[#3178c6] hover:bg-[#eaf4ff]"
698
+ >
699
+ Options
700
+ </button>
701
+ <button
702
+ onClick={onReset}
703
+ className="px-3 py-1.5 font-mono text-xs text-slate-500 transition-colors hover:text-[#235a97]"
704
+ >
705
+ Reset
706
+ </button>
707
+ <button
708
+ onClick={onShare}
709
+ className="rounded-md bg-[#3178c6] px-3 py-1.5 font-mono text-xs text-white shadow-[0_6px_16px_rgba(49,120,198,0.22)] transition-colors hover:bg-[#235a97]"
710
+ >
711
+ {shareToast ? "Copied ✓" : "Share"}
712
+ </button>
723
713
  </div>
724
714
  </div>
725
715
 
726
716
  <div className="flex flex-1 min-h-0 flex-col md:flex-row">
727
- <div className="flex-1 min-w-0 md:border-r border-neutral-800/70 flex flex-col h-1/2 md:h-full">
728
- <div className="flex items-center justify-between px-4 py-1.5 border-b border-neutral-800/70 bg-neutral-950">
729
- <span className="text-[11px] font-mono text-neutral-500">
717
+ <div className="flex h-1/2 min-w-0 flex-1 flex-col border-[#c7dff4] md:h-full md:border-r">
718
+ <div className="flex items-center justify-between border-b border-[#c7dff4] bg-[#eef6ff] px-4 py-1.5">
719
+ <span className="font-mono text-[11px] text-slate-500">
730
720
  src/playground.ts
731
721
  </span>
732
- <span className="text-[10px] font-mono text-neutral-700">
722
+ <span className="font-mono text-[10px] text-slate-400">
733
723
  {source.split("\n").length} lines
734
724
  </span>
735
725
  </div>
@@ -742,14 +732,14 @@ export function PlaygroundShell({
742
732
  </div>
743
733
  </div>
744
734
 
745
- <div className="flex-1 min-w-0 flex flex-col h-1/2 md:h-full border-t md:border-t-0 border-neutral-800/70">
746
- <div className="flex items-center justify-between px-4 py-1.5 border-b border-neutral-800/70 bg-neutral-950">
747
- <span className="text-[11px] font-mono text-neutral-500">
735
+ <div className="flex h-1/2 min-w-0 flex-1 flex-col border-t border-[#c7dff4] md:h-full md:border-t-0">
736
+ <div className="flex items-center justify-between border-b border-[#c7dff4] bg-[#eef6ff] px-4 py-1.5">
737
+ <span className="font-mono text-[11px] text-slate-500">
748
738
  {target === "javascript"
749
739
  ? resultCaption(options)
750
740
  : "lint diagnostics"}
751
741
  </span>
752
- <span className="text-[10px] font-mono text-neutral-700">
742
+ <span className="font-mono text-[10px] text-slate-400">
753
743
  {result?.type === "error" ? "error" : ""}
754
744
  </span>
755
745
  </div>
@@ -774,22 +764,22 @@ export function PlaygroundShell({
774
764
  </div>
775
765
 
776
766
  {bundleError && (
777
- <div className="shrink-0 px-4 py-1.5 text-[11px] font-mono text-red-300 bg-red-500/10 border-t border-red-700/40">
767
+ <div className="shrink-0 border-t border-red-300 bg-red-50 px-4 py-1.5 font-mono text-[11px] text-red-700">
778
768
  Bundle failed — {bundleError}
779
769
  </div>
780
770
  )}
781
771
 
782
772
  {executeBundle && (
783
- <div className="shrink-0 border-t border-neutral-800/70 bg-neutral-950 flex flex-col h-48">
784
- <div className="flex items-center justify-between px-4 py-1.5 border-b border-neutral-800/70">
785
- <span className="text-[11px] font-mono text-neutral-500">
773
+ <div className="flex h-48 shrink-0 flex-col border-t border-[#c7dff4] bg-[#f7fbff]">
774
+ <div className="flex items-center justify-between border-b border-[#d8e7f4] px-4 py-1.5">
775
+ <span className="font-mono text-[11px] text-slate-500">
786
776
  console output
787
777
  </span>
788
778
  <div className="flex items-center gap-2">
789
779
  {consoleMessages.length > 0 && (
790
780
  <button
791
781
  onClick={() => setConsoleMessages([])}
792
- className="px-2 py-1 text-[10px] font-mono text-neutral-500 hover:text-neutral-200 transition-colors"
782
+ className="px-2 py-1 font-mono text-[10px] text-slate-500 transition-colors hover:text-[#235a97]"
793
783
  >
794
784
  Clear
795
785
  </button>
@@ -797,7 +787,7 @@ export function PlaygroundShell({
797
787
  <button
798
788
  onClick={onExecute}
799
789
  disabled={executing}
800
- className="px-3 py-1 text-[11px] font-mono text-neutral-900 bg-emerald-400 rounded-md hover:bg-emerald-300 transition-colors disabled:opacity-50"
790
+ className="rounded-md bg-[#3178c6] px-3 py-1 font-mono text-[11px] text-white transition-colors hover:bg-[#235a97] disabled:opacity-50"
801
791
  title="Cmd/Ctrl+Enter"
802
792
  >
803
793
  ▶ {executing ? "Executing…" : "Execute"}
@@ -9,8 +9,8 @@ interface ResultViewerProps {
9
9
  }
10
10
 
11
11
  /**
12
- * Read-only Monaco pane used to render the compiled / transformed output
13
- * with a copy button. Wraps `<Editor readOnly>` and adds the toast UI.
12
+ * Read-only Monaco pane used to render the compiled / transformed output with a
13
+ * copy button. Wraps `<Editor readOnly>` and adds the toast UI.
14
14
  */
15
15
  export function ResultViewer({ language, value }: ResultViewerProps) {
16
16
  const [copied, setCopied] = useState(false);
@@ -39,7 +39,7 @@ export function ResultViewer({ language, value }: ResultViewerProps) {
39
39
  {value && (
40
40
  <button
41
41
  onClick={onCopy}
42
- className="absolute top-2 right-3 z-10 px-2 py-1 text-[10px] font-mono text-neutral-300 bg-neutral-900/80 border border-neutral-700 rounded-md hover:bg-neutral-800 transition-colors"
42
+ className="absolute right-3 top-2 z-10 rounded-md border border-[#b9d5ee] bg-white/90 px-2 py-1 font-mono text-[10px] text-[#235a97] shadow-sm transition-colors hover:bg-[#eaf4ff]"
43
43
  >
44
44
  {copied ? "Copied ✓" : "Copy"}
45
45
  </button>
@@ -47,7 +47,7 @@ export function ResultViewer({ language, value }: ResultViewerProps) {
47
47
  <Editor
48
48
  height="100%"
49
49
  language={language}
50
- theme="vs-dark"
50
+ theme="vs"
51
51
  value={value}
52
52
  path={`output.${
53
53
  language === "typescript"
@@ -45,8 +45,7 @@ export function SourceEditor({
45
45
  tsd.setCompilerOptions({
46
46
  target: monaco.languages.typescript.ScriptTarget.ESNext,
47
47
  module: monaco.languages.typescript.ModuleKind.ESNext,
48
- moduleResolution:
49
- monaco.languages.typescript.ModuleResolutionKind.NodeJs,
48
+ moduleResolution: monaco.languages.typescript.ModuleResolutionKind.NodeJs,
50
49
  esModuleInterop: DEFAULT_PLAYGROUND_COMPILER_OPTIONS.esModuleInterop,
51
50
  strict: DEFAULT_PLAYGROUND_COMPILER_OPTIONS.strict,
52
51
  experimentalDecorators:
@@ -73,7 +72,7 @@ export function SourceEditor({
73
72
  <Editor
74
73
  height="100%"
75
74
  defaultLanguage="typescript"
76
- theme="vs-dark"
75
+ theme="vs"
77
76
  value={value}
78
77
  onChange={(v) => onChange(v ?? "")}
79
78
  onMount={handleMount}
@@ -64,15 +64,13 @@ export function createSandboxRequire(
64
64
  return null;
65
65
  }
66
66
  if (subpath === null) {
67
- // bare "name": honor exports["."] string, else main, else index.
68
- const exportsAny = pj.exports;
69
- if (typeof exportsAny === "object" && exportsAny !== null) {
70
- const root = (exportsAny as Record<string, unknown>)["."];
71
- const r = pickConditionalExport(root);
72
- if (r) {
73
- const resolved = `${pkg}/${stripDotSlash(r)}`;
74
- return has(resolved) ? resolved : null;
75
- }
67
+ // bare "name": honor the root `exports` entry (string, subpath table
68
+ // "." key, or bare condition map) → CJS target, else main, else index.
69
+ const root = rootExportTarget(pj.exports);
70
+ const r = pickConditionalExport(root);
71
+ if (r) {
72
+ const resolved = `${pkg}/${stripDotSlash(r)}`;
73
+ return has(resolved) ? resolved : null;
76
74
  }
77
75
  if (typeof pj.main === "string") {
78
76
  return tryPaths(
@@ -220,6 +218,31 @@ export function createSandboxRequire(
220
218
  };
221
219
  }
222
220
 
221
+ /**
222
+ * Reduce a `package.json` `exports` field to the value describing its ROOT
223
+ * (".") entry, ready for {@link pickConditionalExport}. Node accepts three valid
224
+ * root shapes and they must all resolve consistently:
225
+ *
226
+ * - A bare string target — `"exports": "./index.cjs"`;
227
+ * - A subpath table keyed by "." — `{ ".": <target>, "./sub": ... }`;
228
+ * - A bare condition map whose keys are all conditions, not subpaths — `{
229
+ * "require": "./index.cjs", "default": "./index.cjs" }`.
230
+ *
231
+ * Node forbids mixing subpath keys with condition keys, so the presence of any
232
+ * "."-prefixed key decides the interpretation: a subpath table exposes its root
233
+ * as `exports["."]`, while a condition map is itself the root target. Returns
234
+ * null when `exports` is absent, empty, or describes no root entry.
235
+ */
236
+ function rootExportTarget(exports: unknown): unknown {
237
+ if (typeof exports === "string") return exports;
238
+ if (!exports || typeof exports !== "object") return null;
239
+ const obj = exports as Record<string, unknown>;
240
+ const keys = Object.keys(obj);
241
+ if (keys.length === 0) return null;
242
+ const hasSubpathKey = keys.some((k) => k === "." || k.startsWith("./"));
243
+ return hasSubpathKey ? obj["."] : obj;
244
+ }
245
+
223
246
  function pickConditionalExport(value: unknown): string | null {
224
247
  if (typeof value === "string") return value;
225
248
  if (value && typeof value === "object") {