plgg-bundle 0.0.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 (235) hide show
  1. package/README.md +68 -0
  2. package/bin/appAliasHook.mjs +89 -0
  3. package/bin/hook.mjs +94 -0
  4. package/bin/plgg-bundle.mjs +16 -0
  5. package/dist/Dev/fixtures/devPoc.spec.d.ts +2 -0
  6. package/dist/Dev/fixtures/devPoc.spec.d.ts.map +1 -0
  7. package/dist/Dev/fixtures/devPoc.spec.js +145 -0
  8. package/dist/Dev/fixtures/devPoc.spec.js.map +1 -0
  9. package/dist/Dev/model/Fetch.d.ts +20 -0
  10. package/dist/Dev/model/Fetch.d.ts.map +1 -0
  11. package/dist/Dev/model/Fetch.js +8 -0
  12. package/dist/Dev/model/Fetch.js.map +1 -0
  13. package/dist/Dev/model/ModuleGraph.d.ts +27 -0
  14. package/dist/Dev/model/ModuleGraph.d.ts.map +1 -0
  15. package/dist/Dev/model/ModuleGraph.js +2 -0
  16. package/dist/Dev/model/ModuleGraph.js.map +1 -0
  17. package/dist/Dev/model/Protocol.d.ts +22 -0
  18. package/dist/Dev/model/Protocol.d.ts.map +1 -0
  19. package/dist/Dev/model/Protocol.js +31 -0
  20. package/dist/Dev/model/Protocol.js.map +1 -0
  21. package/dist/Dev/node/devServer.d.ts +21 -0
  22. package/dist/Dev/node/devServer.d.ts.map +1 -0
  23. package/dist/Dev/node/devServer.js +298 -0
  24. package/dist/Dev/node/devServer.js.map +1 -0
  25. package/dist/Dev/node/httpAdapter.d.ts +19 -0
  26. package/dist/Dev/node/httpAdapter.d.ts.map +1 -0
  27. package/dist/Dev/node/httpAdapter.js +76 -0
  28. package/dist/Dev/node/httpAdapter.js.map +1 -0
  29. package/dist/Dev/node/scanGraph.d.ts +21 -0
  30. package/dist/Dev/node/scanGraph.d.ts.map +1 -0
  31. package/dist/Dev/node/scanGraph.js +145 -0
  32. package/dist/Dev/node/scanGraph.js.map +1 -0
  33. package/dist/Dev/node/watch.d.ts +10 -0
  34. package/dist/Dev/node/watch.d.ts.map +1 -0
  35. package/dist/Dev/node/watch.js +44 -0
  36. package/dist/Dev/node/watch.js.map +1 -0
  37. package/dist/Dev/usecase/allowedHost.d.ts +16 -0
  38. package/dist/Dev/usecase/allowedHost.d.ts.map +1 -0
  39. package/dist/Dev/usecase/allowedHost.js +33 -0
  40. package/dist/Dev/usecase/allowedHost.js.map +1 -0
  41. package/dist/Dev/usecase/allowedHost.spec.d.ts +2 -0
  42. package/dist/Dev/usecase/allowedHost.spec.d.ts.map +1 -0
  43. package/dist/Dev/usecase/allowedHost.spec.js +13 -0
  44. package/dist/Dev/usecase/allowedHost.spec.js.map +1 -0
  45. package/dist/Dev/usecase/buildGraph.d.ts +12 -0
  46. package/dist/Dev/usecase/buildGraph.d.ts.map +1 -0
  47. package/dist/Dev/usecase/buildGraph.js +24 -0
  48. package/dist/Dev/usecase/buildGraph.js.map +1 -0
  49. package/dist/Dev/usecase/buildGraph.spec.d.ts +2 -0
  50. package/dist/Dev/usecase/buildGraph.spec.d.ts.map +1 -0
  51. package/dist/Dev/usecase/buildGraph.spec.js +16 -0
  52. package/dist/Dev/usecase/buildGraph.spec.js.map +1 -0
  53. package/dist/Dev/usecase/decorateDevHtml.d.ts +12 -0
  54. package/dist/Dev/usecase/decorateDevHtml.d.ts.map +1 -0
  55. package/dist/Dev/usecase/decorateDevHtml.js +15 -0
  56. package/dist/Dev/usecase/decorateDevHtml.js.map +1 -0
  57. package/dist/Dev/usecase/decorateDevHtml.spec.d.ts +2 -0
  58. package/dist/Dev/usecase/decorateDevHtml.spec.d.ts.map +1 -0
  59. package/dist/Dev/usecase/decorateDevHtml.spec.js +19 -0
  60. package/dist/Dev/usecase/decorateDevHtml.spec.js.map +1 -0
  61. package/dist/Dev/usecase/invalidate.d.ts +17 -0
  62. package/dist/Dev/usecase/invalidate.d.ts.map +1 -0
  63. package/dist/Dev/usecase/invalidate.js +57 -0
  64. package/dist/Dev/usecase/invalidate.js.map +1 -0
  65. package/dist/Dev/usecase/invalidate.spec.d.ts +2 -0
  66. package/dist/Dev/usecase/invalidate.spec.d.ts.map +1 -0
  67. package/dist/Dev/usecase/invalidate.spec.js +64 -0
  68. package/dist/Dev/usecase/invalidate.spec.js.map +1 -0
  69. package/dist/Dev/usecase/parseImports.d.ts +24 -0
  70. package/dist/Dev/usecase/parseImports.d.ts.map +1 -0
  71. package/dist/Dev/usecase/parseImports.js +40 -0
  72. package/dist/Dev/usecase/parseImports.js.map +1 -0
  73. package/dist/Dev/usecase/parseImports.spec.d.ts +2 -0
  74. package/dist/Dev/usecase/parseImports.spec.d.ts.map +1 -0
  75. package/dist/Dev/usecase/parseImports.spec.js +24 -0
  76. package/dist/Dev/usecase/parseImports.spec.js.map +1 -0
  77. package/dist/Dev/usecase/reloadDecision.d.ts +20 -0
  78. package/dist/Dev/usecase/reloadDecision.d.ts.map +1 -0
  79. package/dist/Dev/usecase/reloadDecision.js +52 -0
  80. package/dist/Dev/usecase/reloadDecision.js.map +1 -0
  81. package/dist/Dev/usecase/reloadDecision.spec.d.ts +2 -0
  82. package/dist/Dev/usecase/reloadDecision.spec.d.ts.map +1 -0
  83. package/dist/Dev/usecase/reloadDecision.spec.js +17 -0
  84. package/dist/Dev/usecase/reloadDecision.spec.js.map +1 -0
  85. package/dist/domain/model/BundleConfig.d.ts +136 -0
  86. package/dist/domain/model/BundleConfig.d.ts.map +1 -0
  87. package/dist/domain/model/BundleConfig.js +18 -0
  88. package/dist/domain/model/BundleConfig.js.map +1 -0
  89. package/dist/domain/model/BundleConfig.spec.d.ts +2 -0
  90. package/dist/domain/model/BundleConfig.spec.d.ts.map +1 -0
  91. package/dist/domain/model/BundleConfig.spec.js +8 -0
  92. package/dist/domain/model/BundleConfig.spec.js.map +1 -0
  93. package/dist/domain/usecase/asBundleConfig.d.ts +17 -0
  94. package/dist/domain/usecase/asBundleConfig.d.ts.map +1 -0
  95. package/dist/domain/usecase/asBundleConfig.js +179 -0
  96. package/dist/domain/usecase/asBundleConfig.js.map +1 -0
  97. package/dist/domain/usecase/asBundleConfig.spec.d.ts +2 -0
  98. package/dist/domain/usecase/asBundleConfig.spec.d.ts.map +1 -0
  99. package/dist/domain/usecase/asBundleConfig.spec.js +119 -0
  100. package/dist/domain/usecase/asBundleConfig.spec.js.map +1 -0
  101. package/dist/domain/usecase/build.d.ts +14 -0
  102. package/dist/domain/usecase/build.d.ts.map +1 -0
  103. package/dist/domain/usecase/build.js +177 -0
  104. package/dist/domain/usecase/build.js.map +1 -0
  105. package/dist/domain/usecase/collectModules.d.ts +53 -0
  106. package/dist/domain/usecase/collectModules.d.ts.map +1 -0
  107. package/dist/domain/usecase/collectModules.js +118 -0
  108. package/dist/domain/usecase/collectModules.js.map +1 -0
  109. package/dist/domain/usecase/deriveExternal.d.ts +25 -0
  110. package/dist/domain/usecase/deriveExternal.d.ts.map +1 -0
  111. package/dist/domain/usecase/deriveExternal.js +79 -0
  112. package/dist/domain/usecase/deriveExternal.js.map +1 -0
  113. package/dist/domain/usecase/deriveExternal.spec.d.ts +2 -0
  114. package/dist/domain/usecase/deriveExternal.spec.d.ts.map +1 -0
  115. package/dist/domain/usecase/deriveExternal.spec.js +56 -0
  116. package/dist/domain/usecase/deriveExternal.spec.js.map +1 -0
  117. package/dist/domain/usecase/discoverWorkspace.d.ts +27 -0
  118. package/dist/domain/usecase/discoverWorkspace.d.ts.map +1 -0
  119. package/dist/domain/usecase/discoverWorkspace.js +116 -0
  120. package/dist/domain/usecase/discoverWorkspace.js.map +1 -0
  121. package/dist/domain/usecase/discoverWorkspace.spec.d.ts +2 -0
  122. package/dist/domain/usecase/discoverWorkspace.spec.d.ts.map +1 -0
  123. package/dist/domain/usecase/discoverWorkspace.spec.js +23 -0
  124. package/dist/domain/usecase/discoverWorkspace.spec.js.map +1 -0
  125. package/dist/domain/usecase/emitBundle.d.ts +43 -0
  126. package/dist/domain/usecase/emitBundle.d.ts.map +1 -0
  127. package/dist/domain/usecase/emitBundle.js +157 -0
  128. package/dist/domain/usecase/emitBundle.js.map +1 -0
  129. package/dist/domain/usecase/emitBundle.spec.d.ts +2 -0
  130. package/dist/domain/usecase/emitBundle.spec.d.ts.map +1 -0
  131. package/dist/domain/usecase/emitBundle.spec.js +80 -0
  132. package/dist/domain/usecase/emitBundle.spec.js.map +1 -0
  133. package/dist/domain/usecase/emitDts.d.ts +32 -0
  134. package/dist/domain/usecase/emitDts.d.ts.map +1 -0
  135. package/dist/domain/usecase/emitDts.js +99 -0
  136. package/dist/domain/usecase/emitDts.js.map +1 -0
  137. package/dist/domain/usecase/isExternal.d.ts +12 -0
  138. package/dist/domain/usecase/isExternal.d.ts.map +1 -0
  139. package/dist/domain/usecase/isExternal.js +16 -0
  140. package/dist/domain/usecase/isExternal.js.map +1 -0
  141. package/dist/domain/usecase/isExternal.spec.d.ts +2 -0
  142. package/dist/domain/usecase/isExternal.spec.d.ts.map +1 -0
  143. package/dist/domain/usecase/isExternal.spec.js +16 -0
  144. package/dist/domain/usecase/isExternal.spec.js.map +1 -0
  145. package/dist/domain/usecase/resolveSpecifier.d.ts +35 -0
  146. package/dist/domain/usecase/resolveSpecifier.d.ts.map +1 -0
  147. package/dist/domain/usecase/resolveSpecifier.js +83 -0
  148. package/dist/domain/usecase/resolveSpecifier.js.map +1 -0
  149. package/dist/domain/usecase/resolveSpecifier.spec.d.ts +2 -0
  150. package/dist/domain/usecase/resolveSpecifier.spec.d.ts.map +1 -0
  151. package/dist/domain/usecase/resolveSpecifier.spec.js +44 -0
  152. package/dist/domain/usecase/resolveSpecifier.spec.js.map +1 -0
  153. package/dist/domain/usecase/resolveWorkspaceSpecifier.d.ts +29 -0
  154. package/dist/domain/usecase/resolveWorkspaceSpecifier.d.ts.map +1 -0
  155. package/dist/domain/usecase/resolveWorkspaceSpecifier.js +89 -0
  156. package/dist/domain/usecase/resolveWorkspaceSpecifier.js.map +1 -0
  157. package/dist/domain/usecase/resolveWorkspaceSpecifier.spec.d.ts +2 -0
  158. package/dist/domain/usecase/resolveWorkspaceSpecifier.spec.d.ts.map +1 -0
  159. package/dist/domain/usecase/resolveWorkspaceSpecifier.spec.js +27 -0
  160. package/dist/domain/usecase/resolveWorkspaceSpecifier.spec.js.map +1 -0
  161. package/dist/domain/usecase/rewriteDtsAliases.d.ts +25 -0
  162. package/dist/domain/usecase/rewriteDtsAliases.d.ts.map +1 -0
  163. package/dist/domain/usecase/rewriteDtsAliases.js +71 -0
  164. package/dist/domain/usecase/rewriteDtsAliases.js.map +1 -0
  165. package/dist/domain/usecase/rewriteDtsAliases.spec.d.ts +2 -0
  166. package/dist/domain/usecase/rewriteDtsAliases.spec.d.ts.map +1 -0
  167. package/dist/domain/usecase/rewriteDtsAliases.spec.js +35 -0
  168. package/dist/domain/usecase/rewriteDtsAliases.spec.js.map +1 -0
  169. package/dist/entrypoints/cli.d.ts +2 -0
  170. package/dist/entrypoints/cli.d.ts.map +1 -0
  171. package/dist/entrypoints/cli.js +79 -0
  172. package/dist/entrypoints/cli.js.map +1 -0
  173. package/dist/index.d.ts +21 -0
  174. package/dist/index.d.ts.map +1 -0
  175. package/dist/index.js +28 -0
  176. package/dist/index.js.map +1 -0
  177. package/dist/vendors/runner.d.ts +20 -0
  178. package/dist/vendors/runner.d.ts.map +1 -0
  179. package/dist/vendors/runner.js +60 -0
  180. package/dist/vendors/runner.js.map +1 -0
  181. package/dist/vendors/runner.spec.d.ts +2 -0
  182. package/dist/vendors/runner.spec.d.ts.map +1 -0
  183. package/dist/vendors/runner.spec.js +20 -0
  184. package/dist/vendors/runner.spec.js.map +1 -0
  185. package/dist/vendors/transpiler.d.ts +19 -0
  186. package/dist/vendors/transpiler.d.ts.map +1 -0
  187. package/dist/vendors/transpiler.js +42 -0
  188. package/dist/vendors/transpiler.js.map +1 -0
  189. package/package.json +31 -0
  190. package/src/Dev/fixtures/devPoc.spec.ts +235 -0
  191. package/src/Dev/model/Fetch.ts +31 -0
  192. package/src/Dev/model/ModuleGraph.ts +30 -0
  193. package/src/Dev/model/Protocol.ts +35 -0
  194. package/src/Dev/node/devServer.ts +503 -0
  195. package/src/Dev/node/httpAdapter.ts +162 -0
  196. package/src/Dev/node/scanGraph.ts +203 -0
  197. package/src/Dev/node/watch.ts +54 -0
  198. package/src/Dev/usecase/allowedHost.spec.ts +50 -0
  199. package/src/Dev/usecase/allowedHost.ts +39 -0
  200. package/src/Dev/usecase/buildGraph.spec.ts +30 -0
  201. package/src/Dev/usecase/buildGraph.ts +31 -0
  202. package/src/Dev/usecase/decorateDevHtml.spec.ts +39 -0
  203. package/src/Dev/usecase/decorateDevHtml.ts +21 -0
  204. package/src/Dev/usecase/invalidate.spec.ts +88 -0
  205. package/src/Dev/usecase/invalidate.ts +67 -0
  206. package/src/Dev/usecase/parseImports.spec.ts +58 -0
  207. package/src/Dev/usecase/parseImports.ts +50 -0
  208. package/src/Dev/usecase/reloadDecision.spec.ts +50 -0
  209. package/src/Dev/usecase/reloadDecision.ts +65 -0
  210. package/src/domain/model/BundleConfig.spec.ts +35 -0
  211. package/src/domain/model/BundleConfig.ts +162 -0
  212. package/src/domain/usecase/asBundleConfig.spec.ts +230 -0
  213. package/src/domain/usecase/asBundleConfig.ts +257 -0
  214. package/src/domain/usecase/build.ts +259 -0
  215. package/src/domain/usecase/collectModules.ts +226 -0
  216. package/src/domain/usecase/deriveExternal.spec.ts +125 -0
  217. package/src/domain/usecase/deriveExternal.ts +108 -0
  218. package/src/domain/usecase/discoverWorkspace.spec.ts +63 -0
  219. package/src/domain/usecase/discoverWorkspace.ts +183 -0
  220. package/src/domain/usecase/emitBundle.spec.ts +162 -0
  221. package/src/domain/usecase/emitBundle.ts +215 -0
  222. package/src/domain/usecase/emitDts.ts +156 -0
  223. package/src/domain/usecase/isExternal.spec.ts +53 -0
  224. package/src/domain/usecase/isExternal.ts +20 -0
  225. package/src/domain/usecase/resolveSpecifier.spec.ts +112 -0
  226. package/src/domain/usecase/resolveSpecifier.ts +127 -0
  227. package/src/domain/usecase/resolveWorkspaceSpecifier.spec.ts +79 -0
  228. package/src/domain/usecase/resolveWorkspaceSpecifier.ts +120 -0
  229. package/src/domain/usecase/rewriteDtsAliases.spec.ts +123 -0
  230. package/src/domain/usecase/rewriteDtsAliases.ts +133 -0
  231. package/src/entrypoints/cli.ts +105 -0
  232. package/src/index.ts +28 -0
  233. package/src/vendors/runner.spec.ts +50 -0
  234. package/src/vendors/runner.ts +78 -0
  235. package/src/vendors/transpiler.ts +48 -0
@@ -0,0 +1,162 @@
1
+ import {
2
+ test,
3
+ check,
4
+ all,
5
+ toBe,
6
+ } from "plgg-test";
7
+ import {
8
+ emitCjsBundle,
9
+ emitEsmBundle,
10
+ } from "plgg-bundle/domain/usecase/emitBundle";
11
+ import { type Graph } from "plgg-bundle/domain/usecase/collectModules";
12
+
13
+ const graph: Graph = {
14
+ entryId: "index.ts",
15
+ modules: [
16
+ {
17
+ id: "index.ts",
18
+ code: 'exports.x = require("dep.ts").x;',
19
+ externals: [],
20
+ },
21
+ {
22
+ id: "dep.ts",
23
+ code: "exports.x = 1;",
24
+ externals: [],
25
+ },
26
+ ],
27
+ };
28
+
29
+ test("emitCjsBundle wires the registry and entry export", () =>
30
+ all([
31
+ check(
32
+ emitCjsBundle(graph).includes(
33
+ "function __require(id)",
34
+ ),
35
+ toBe(true),
36
+ ),
37
+ check(
38
+ emitCjsBundle(graph).includes(
39
+ 'module.exports = __require("index.ts")',
40
+ ),
41
+ toBe(true),
42
+ ),
43
+ check(
44
+ emitCjsBundle(graph).includes('"dep.ts":'),
45
+ toBe(true),
46
+ ),
47
+ ]));
48
+
49
+ test("emitEsmBundle emits static named exports", () =>
50
+ all([
51
+ check(
52
+ emitEsmBundle(graph, ["x"]).includes(
53
+ "export default __entry;",
54
+ ),
55
+ toBe(true),
56
+ ),
57
+ check(
58
+ emitEsmBundle(graph, ["x"]).includes(
59
+ "} = __entry;",
60
+ ),
61
+ toBe(true),
62
+ ),
63
+ check(
64
+ emitEsmBundle(graph, ["x"]).includes(" x"),
65
+ toBe(true),
66
+ ),
67
+ ]));
68
+
69
+ test("the emitted CJS bundle actually runs", () => {
70
+ // Evaluate the emitted CJS in a fresh module scope
71
+ // and confirm the entry's export resolves through the
72
+ // registry runtime.
73
+ const code = emitCjsBundle(graph);
74
+ const mod: {
75
+ exports: Record<string, unknown>;
76
+ } = {
77
+ exports: {},
78
+ };
79
+ new Function(
80
+ "module",
81
+ "exports",
82
+ "require",
83
+ code,
84
+ )(mod, mod.exports, () => ({}));
85
+ return check(mod.exports.x, toBe(1));
86
+ });
87
+
88
+ // A graph whose entry keeps an un-bundled external
89
+ // require (e.g. a node builtin).
90
+ const externalGraph: Graph = {
91
+ entryId: "index.ts",
92
+ modules: [
93
+ {
94
+ id: "index.ts",
95
+ code: 'exports.read = require("node:fs").readFileSync;',
96
+ externals: ["node:fs"],
97
+ },
98
+ ],
99
+ };
100
+
101
+ test("emitEsmBundle imports externals and routes the registry to them", () =>
102
+ all([
103
+ check(
104
+ emitEsmBundle(externalGraph, [
105
+ "read",
106
+ ]).includes(
107
+ 'import * as __ext0 from "node:fs";',
108
+ ),
109
+ toBe(true),
110
+ ),
111
+ check(
112
+ emitEsmBundle(externalGraph, [
113
+ "read",
114
+ ]).includes('"node:fs": __ext0'),
115
+ toBe(true),
116
+ ),
117
+ check(
118
+ emitEsmBundle(externalGraph, [
119
+ "read",
120
+ ]).includes(
121
+ "if (id in __externals) return __externals[id];",
122
+ ),
123
+ toBe(true),
124
+ ),
125
+ ]));
126
+
127
+ test("emitEsmBundle falls back to native dynamic import for unresolved ids", () =>
128
+ all([
129
+ // A runtime-only id (e.g. a config file path from a
130
+ // transpiled dynamic import) resolves natively …
131
+ check(
132
+ emitEsmBundle(externalGraph, [
133
+ "read",
134
+ ]).includes("return import(id);"),
135
+ toBe(true),
136
+ ),
137
+ // … instead of throwing as a build mistake.
138
+ check(
139
+ emitEsmBundle(externalGraph, [
140
+ "read",
141
+ ]).includes("Cannot resolve external"),
142
+ toBe(false),
143
+ ),
144
+ ]));
145
+
146
+ test("emitCjsBundle leaves externals to the host require", () =>
147
+ all([
148
+ // No external import machinery in CJS …
149
+ check(
150
+ emitCjsBundle(externalGraph).includes(
151
+ "__externals",
152
+ ),
153
+ toBe(false),
154
+ ),
155
+ // … the un-rewritten require falls through to host.
156
+ check(
157
+ emitCjsBundle(externalGraph).includes(
158
+ "return require(id);",
159
+ ),
160
+ toBe(true),
161
+ ),
162
+ ]));
@@ -0,0 +1,215 @@
1
+ import { type Format } from "plgg-bundle/domain/model/BundleConfig";
2
+ import {
3
+ type Graph,
4
+ type Module,
5
+ } from "plgg-bundle/domain/usecase/collectModules";
6
+
7
+ /**
8
+ * Render a resolved {@link Graph} into one bundle's
9
+ * source text for the given {@link Format}, using an
10
+ * in-house module-registry runtime: every module body
11
+ * is wrapped in its own `(module, exports, require)`
12
+ * closure keyed by id, and a cached `__require` links
13
+ * them at runtime. This is collision-free by
14
+ * construction (each module keeps its own scope), so it
15
+ * needs none of rolldown's scope-hoisting/renaming and
16
+ * stays pure JS.
17
+ *
18
+ * Externals (specifiers left out of the bundle —
19
+ * `node:*`, sibling `plgg*` packages, …) are handled
20
+ * per format:
21
+ * - CJS: an un-rewritten `require("<external>")` in a
22
+ * module body falls through the registry to the host
23
+ * `require`.
24
+ * - ESM: there is no host `require`, so the bundle emits
25
+ * a real top-level `import * as __extN from
26
+ * "<external>"` for each distinct external and the
27
+ * registry routes `__require("<external>")` to that
28
+ * namespace.
29
+ */
30
+ export const emitCjsBundle = (
31
+ graph: Graph,
32
+ ): string =>
33
+ [
34
+ `"use strict";`,
35
+ runtime(graph, `return require(id);`),
36
+ `module.exports = __require(${entryArg(
37
+ graph,
38
+ )});`,
39
+ ].join("\n");
40
+
41
+ /**
42
+ * ESM bundle. `exportNames` is the exact public surface
43
+ * (the entry's enumerable export keys), supplied by the
44
+ * orchestrator from the CJS bundle's runtime keys —
45
+ * ESM cannot declare exports dynamically. Externals are
46
+ * imported as namespaces at the top and resolved by the
47
+ * registry; an id that is neither a bundled module nor a
48
+ * declared external falls back to a native dynamic
49
+ * `import(id)` (see {@link externalFallback}).
50
+ */
51
+ export const emitEsmBundle = (
52
+ graph: Graph,
53
+ exportNames: ReadonlyArray<string>,
54
+ ): string => {
55
+ const externals = collectExternals(graph);
56
+ return [
57
+ externalImports(externals),
58
+ externalTable(externals),
59
+ runtime(graph, externalFallback()),
60
+ `const __entry = __require(${entryArg(
61
+ graph,
62
+ )});`,
63
+ `export default __entry;`,
64
+ namedExports(exportNames),
65
+ ].join("\n");
66
+ };
67
+
68
+ /**
69
+ * Dispatch on format for callers that hold the names
70
+ * regardless.
71
+ */
72
+ export const emitBundle = (
73
+ graph: Graph,
74
+ format: Format,
75
+ exportNames: ReadonlyArray<string>,
76
+ ): string =>
77
+ format === "es"
78
+ ? emitEsmBundle(graph, exportNames)
79
+ : emitCjsBundle(graph);
80
+
81
+ /**
82
+ * The shared registry runtime: a module table plus a
83
+ * caching `__require` that runs a module body once and
84
+ * memoizes its `exports`. An id with no bundled module
85
+ * runs `unknownBody` (the format-specific external
86
+ * fallback).
87
+ */
88
+ const runtime = (
89
+ graph: Graph,
90
+ unknownBody: string,
91
+ ): string =>
92
+ [
93
+ `const __modules = {`,
94
+ graph.modules.map(moduleEntry).join(",\n"),
95
+ `};`,
96
+ `const __cache = {};`,
97
+ `function __require(id) {`,
98
+ ` if (id in __cache) return __cache[id].exports;`,
99
+ ` const fn = __modules[id];`,
100
+ ` if (!fn) { ${unknownBody} }`,
101
+ ` const module = { exports: {} };`,
102
+ ` __cache[id] = module;`,
103
+ ` fn(module, module.exports, __require);`,
104
+ ` return module.exports;`,
105
+ `}`,
106
+ ].join("\n");
107
+
108
+ /**
109
+ * The ESM fallback body for an id with no bundled
110
+ * module: resolve it from the external namespace table
111
+ * (interop: a CJS consumer reads named members off the
112
+ * namespace; `default` carries the whole module), or
113
+ * fall back to a native dynamic `import(id)`. Static
114
+ * imports are always bundled or declared external, so
115
+ * only a transpiled dynamic `import(x)` — emitted as
116
+ * `Promise.resolve(x).then(s => __require(s))`, an
117
+ * async context where the returned Promise flattens —
118
+ * ever reaches the fallback; it lets bundled code load
119
+ * runtime files (e.g. a user's config) the registry
120
+ * cannot know. The CJS runtime keeps its host
121
+ * `require(id)` fall-through instead: its unknown-id
122
+ * branch is also reached by synchronous requires, where
123
+ * a returned Promise would corrupt the module value.
124
+ */
125
+ const externalFallback = (): string =>
126
+ [
127
+ `if (id in __externals) return __externals[id];`,
128
+ `return import(id);`,
129
+ ].join(" ");
130
+
131
+ /**
132
+ * Distinct external specifiers across the whole graph,
133
+ * sorted for deterministic output.
134
+ */
135
+ const collectExternals = (
136
+ graph: Graph,
137
+ ): ReadonlyArray<string> =>
138
+ [
139
+ ...new Set(
140
+ graph.modules.flatMap((m) => m.externals),
141
+ ),
142
+ ].sort();
143
+
144
+ /**
145
+ * Top-level namespace imports, one per external, named
146
+ * `__ext0`, `__ext1`, … in the externals' sorted order.
147
+ */
148
+ const externalImports = (
149
+ externals: ReadonlyArray<string>,
150
+ ): string =>
151
+ externals
152
+ .map(
153
+ (spec, i) =>
154
+ `import * as ${extVar(i)} from ${JSON.stringify(
155
+ spec,
156
+ )};`,
157
+ )
158
+ .join("\n");
159
+
160
+ /**
161
+ * The `__externals` lookup mapping each specifier to its
162
+ * imported namespace var.
163
+ */
164
+ const externalTable = (
165
+ externals: ReadonlyArray<string>,
166
+ ): string =>
167
+ [
168
+ `const __externals = {`,
169
+ externals
170
+ .map(
171
+ (spec, i) =>
172
+ ` ${JSON.stringify(spec)}: ${extVar(i)}`,
173
+ )
174
+ .join(",\n"),
175
+ `};`,
176
+ ].join("\n");
177
+
178
+ /**
179
+ * The namespace variable name for the i-th external.
180
+ */
181
+ const extVar = (i: number): string => `__ext${i}`;
182
+
183
+ /**
184
+ * One module's registry entry: `"id": function (module,
185
+ * exports, require) { <body> }`.
186
+ */
187
+ const moduleEntry = (m: Module): string =>
188
+ [
189
+ `${JSON.stringify(m.id)}: `,
190
+ `function (module, exports, require) {\n`,
191
+ m.code,
192
+ `\n}`,
193
+ ].join("");
194
+
195
+ /**
196
+ * Static named re-exports of the entry namespace. Each
197
+ * name is a valid identifier (a JS export name), so the
198
+ * `export const` list is itself valid ESM.
199
+ */
200
+ const namedExports = (
201
+ names: ReadonlyArray<string>,
202
+ ): string =>
203
+ names.length === 0
204
+ ? ``
205
+ : [
206
+ `export const {`,
207
+ names.map((n) => ` ${n}`).join(",\n"),
208
+ `} = __entry;`,
209
+ ].join("\n");
210
+
211
+ /**
212
+ * The JSON-quoted entry id used as `__require`'s arg.
213
+ */
214
+ const entryArg = (graph: Graph): string =>
215
+ JSON.stringify(graph.entryId);
@@ -0,0 +1,156 @@
1
+ import { spawnSync } from "node:child_process";
2
+ import {
3
+ join,
4
+ dirname,
5
+ basename,
6
+ relative,
7
+ } from "node:path";
8
+ import {
9
+ existsSync,
10
+ writeFileSync,
11
+ rmSync,
12
+ } from "node:fs";
13
+ import { createRequire } from "node:module";
14
+ import { rewriteDtsAliases } from "plgg-bundle/domain/usecase/rewriteDtsAliases";
15
+
16
+ /**
17
+ * Emit the per-file `.d.ts` tree for a package — the
18
+ * single declaration mode every package uses. Rides on
19
+ * the project's own `tsc` in declaration-only mode (no
20
+ * new dependency); a single rolled-up `index.d.ts` was
21
+ * a vite-plugin-dts stylistic choice, not a correctness
22
+ * requirement, so it is dropped (breaking-changes-OK)
23
+ * to keep the bundler at zero new dependencies. Throws
24
+ * on a tsc failure.
25
+ *
26
+ * `emitDeclarationOnly` guarantees no `.js`/`.js.map`
27
+ * leaks into `dist` (the reproduction trap). The
28
+ * synthesized config reuses the package's
29
+ * `tsconfig.build.json` when present, else
30
+ * `tsconfig.json`, and adds the two things the
31
+ * declaration build needs but the package config must
32
+ * not carry permanently:
33
+ * - `emitDeclarationOnly` (the package config drives
34
+ * `tsc --noEmit` type-checking),
35
+ * - exclusion of `*.spec.ts` and `dist`. Spec files are
36
+ * not published API and, in a clean build, would pull
37
+ * the package's own not-yet-built dist types through
38
+ * plgg-test; excluding them also avoids the TS5055
39
+ * "overwrite input" collision with a prior `dist`.
40
+ */
41
+ export const emitDts = (args: {
42
+ root: string;
43
+ rootDir: string;
44
+ outDir: string;
45
+ aliasPrefix: string;
46
+ }): void => {
47
+ const project = tsconfigFor(args.root);
48
+ const dtsConfig = join(
49
+ args.root,
50
+ "tsconfig.dts.json",
51
+ );
52
+ // tsc must emit into the SAME staging dir the JS bundles
53
+ // went to (args.outDir), overriding the `outDir: "dist"`
54
+ // the package config carries, so the whole `dist` is
55
+ // published atomically by the caller's single swap.
56
+ writeFileSync(
57
+ dtsConfig,
58
+ dtsTsconfig(
59
+ basename(project),
60
+ relative(args.root, args.outDir),
61
+ ),
62
+ "utf8",
63
+ );
64
+ // tsc invocation seam: run the package's OWN pinned
65
+ // `tsc` directly via `node` — NOT `npx`. `npx` re-runs
66
+ // its own resolution each call and, on a loaded
67
+ // machine, can intermittently resolve/launch `tsc`
68
+ // differently (the non-deterministic dts-emit flake
69
+ // Planner caught). Resolving the local binary makes
70
+ // the compiler, its version, and its module resolution
71
+ // identical every run.
72
+ const r = spawnSync(
73
+ process.execPath,
74
+ [tscBin(args.root), "--project", dtsConfig],
75
+ { cwd: args.root, encoding: "utf8" },
76
+ );
77
+ rmSync(dtsConfig, { force: true });
78
+ if (r.status !== 0) {
79
+ throw new Error(
80
+ `DtsError: tsc declaration emit failed\n${
81
+ r.stdout ?? ""
82
+ }${r.stderr ?? ""}`,
83
+ );
84
+ }
85
+ // Relativize the self-alias in the declarations (tsc
86
+ // leaves `paths` aliases verbatim; consumers have no
87
+ // such alias).
88
+ rewriteDtsAliases(
89
+ args.outDir,
90
+ args.aliasPrefix,
91
+ );
92
+ };
93
+
94
+ /**
95
+ * The synthesized declaration-emit tsconfig, extending
96
+ * the package's base config.
97
+ */
98
+ const dtsTsconfig = (
99
+ baseName: string,
100
+ outRel: string,
101
+ ): string =>
102
+ JSON.stringify(
103
+ {
104
+ extends: `./${baseName}`,
105
+ compilerOptions: {
106
+ noEmit: false,
107
+ declaration: true,
108
+ emitDeclarationOnly: true,
109
+ // Emit into the caller's staging dir, not the
110
+ // live `dist` (overrides the package config).
111
+ outDir: outRel,
112
+ // No incremental cache: a `.tsbuildinfo` could
113
+ // otherwise persist a stale module resolution
114
+ // across runs. Each emit is a clean resolution.
115
+ incremental: false,
116
+ composite: false,
117
+ },
118
+ exclude: [
119
+ "**/*.spec.ts",
120
+ "**/*.spec.tsx",
121
+ "dist",
122
+ `${outRel}`,
123
+ "node_modules",
124
+ ],
125
+ },
126
+ null,
127
+ 2,
128
+ );
129
+
130
+ /**
131
+ * Absolute path to the package's OWN pinned `tsc`
132
+ * launcher, resolved from its `typescript` dependency
133
+ * (derived from the package main so it works regardless
134
+ * of `typescript`'s `exports` map). Run via `node`.
135
+ */
136
+ const tscBin = (root: string): string =>
137
+ join(
138
+ dirname(
139
+ createRequire(
140
+ join(root, "package.json"),
141
+ ).resolve("typescript"),
142
+ ),
143
+ "..",
144
+ "bin",
145
+ "tsc",
146
+ );
147
+
148
+ /**
149
+ * Pick the tsconfig that drives declaration emit:
150
+ * `tsconfig.build.json` when the package carries one,
151
+ * otherwise `tsconfig.json`.
152
+ */
153
+ const tsconfigFor = (root: string): string =>
154
+ existsSync(join(root, "tsconfig.build.json"))
155
+ ? join(root, "tsconfig.build.json")
156
+ : join(root, "tsconfig.json");
@@ -0,0 +1,53 @@
1
+ import {
2
+ test,
3
+ check,
4
+ all,
5
+ toBe,
6
+ } from "plgg-test";
7
+ import { isExternal } from "plgg-bundle/domain/usecase/isExternal";
8
+
9
+ test("isExternal handles a string-array external", () =>
10
+ all([
11
+ check(
12
+ isExternal(["plgg", "node:fs"], "plgg"),
13
+ toBe(true),
14
+ ),
15
+ check(
16
+ isExternal(["plgg"], "plgg-server"),
17
+ toBe(false),
18
+ ),
19
+ check(
20
+ isExternal([], "anything"),
21
+ toBe(false),
22
+ ),
23
+ ]));
24
+
25
+ test("isExternal handles a RegExp external", () =>
26
+ all([
27
+ check(
28
+ isExternal(/^node:/, "node:stream"),
29
+ toBe(true),
30
+ ),
31
+ check(
32
+ isExternal(/^node:/, "plgg/index"),
33
+ toBe(false),
34
+ ),
35
+ ]));
36
+
37
+ test("isExternal handles a predicate external", () =>
38
+ all([
39
+ check(
40
+ isExternal(
41
+ (id) => id.startsWith("plgg"),
42
+ "plgg-server/node",
43
+ ),
44
+ toBe(true),
45
+ ),
46
+ check(
47
+ isExternal(
48
+ (id) => id.startsWith("plgg"),
49
+ "./relative",
50
+ ),
51
+ toBe(false),
52
+ ),
53
+ ]));
@@ -0,0 +1,20 @@
1
+ import { type External } from "plgg-bundle/domain/model/BundleConfig";
2
+
3
+ /**
4
+ * Decide whether a specifier is external given the
5
+ * config's {@link External} declaration, dispatching on
6
+ * its three shapes: a string array (exact match), a
7
+ * `RegExp` (test), or a predicate function (call). This
8
+ * is the single place the three vite-config external
9
+ * forms — string[], `/^node:/`, and plgg-fetch's
10
+ * `isFrameworkDep` predicate — converge.
11
+ */
12
+ export const isExternal = (
13
+ external: External,
14
+ specifier: string,
15
+ ): boolean =>
16
+ typeof external === "function"
17
+ ? external(specifier)
18
+ : external instanceof RegExp
19
+ ? external.test(specifier)
20
+ : external.includes(specifier);
@@ -0,0 +1,112 @@
1
+ import {
2
+ test,
3
+ check,
4
+ all,
5
+ toBe,
6
+ } from "plgg-test";
7
+ import { join } from "node:path";
8
+ import { resolveSpecifier } from "plgg-bundle/domain/usecase/resolveSpecifier";
9
+
10
+ // Resolve against this package's own src tree on disk.
11
+ const srcRoot = join(
12
+ import.meta.dirname,
13
+ "..",
14
+ "..",
15
+ );
16
+ const aliasArgs = {
17
+ aliasPrefix: "plgg-bundle",
18
+ aliasSrcRoot: srcRoot,
19
+ };
20
+
21
+ test("resolveSpecifier resolves an alias to a file", () =>
22
+ check(
23
+ resolveSpecifier({
24
+ specifier:
25
+ "plgg-bundle/domain/usecase/isExternal",
26
+ fromFile: join(srcRoot, "index.ts"),
27
+ ...aliasArgs,
28
+ }),
29
+ toBe(
30
+ join(
31
+ srcRoot,
32
+ "domain",
33
+ "usecase",
34
+ "isExternal.ts",
35
+ ),
36
+ ),
37
+ ));
38
+
39
+ test("resolveSpecifier returns undefined for an alias dir with no index", () =>
40
+ // domain/model has no index.ts → unresolvable.
41
+ check(
42
+ resolveSpecifier({
43
+ specifier: "plgg-bundle/domain/model",
44
+ fromFile: join(srcRoot, "index.ts"),
45
+ ...aliasArgs,
46
+ }),
47
+ toBe(undefined),
48
+ ));
49
+
50
+ test("resolveSpecifier resolves a relative path", () =>
51
+ check(
52
+ resolveSpecifier({
53
+ specifier: "./isExternal",
54
+ fromFile: join(
55
+ srcRoot,
56
+ "domain",
57
+ "usecase",
58
+ "collectModules.ts",
59
+ ),
60
+ ...aliasArgs,
61
+ }),
62
+ toBe(
63
+ join(
64
+ srcRoot,
65
+ "domain",
66
+ "usecase",
67
+ "isExternal.ts",
68
+ ),
69
+ ),
70
+ ));
71
+
72
+ test("resolveSpecifier maps a .js specifier to its .ts source (NodeNext)", () =>
73
+ check(
74
+ resolveSpecifier({
75
+ specifier: "./isExternal.js",
76
+ fromFile: join(
77
+ srcRoot,
78
+ "domain",
79
+ "usecase",
80
+ "collectModules.ts",
81
+ ),
82
+ ...aliasArgs,
83
+ }),
84
+ toBe(
85
+ join(
86
+ srcRoot,
87
+ "domain",
88
+ "usecase",
89
+ "isExternal.ts",
90
+ ),
91
+ ),
92
+ ));
93
+
94
+ test("resolveSpecifier returns undefined for an external specifier", () =>
95
+ all([
96
+ check(
97
+ resolveSpecifier({
98
+ specifier: "plgg",
99
+ fromFile: join(srcRoot, "index.ts"),
100
+ ...aliasArgs,
101
+ }),
102
+ toBe(undefined),
103
+ ),
104
+ check(
105
+ resolveSpecifier({
106
+ specifier: "node:fs",
107
+ fromFile: join(srcRoot, "index.ts"),
108
+ ...aliasArgs,
109
+ }),
110
+ toBe(undefined),
111
+ ),
112
+ ]));