plgg-bundle 0.0.5 → 0.0.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "plgg-bundle",
3
- "version": "0.0.5",
3
+ "version": "0.0.6",
4
4
  "description": "In-house minimal library bundler for the plgg monorepo (ESM+CJS dual output, per-file .d.ts tree), zero new dependencies — reuses the project's own TypeScript, no native binding.",
5
5
  "type": "module",
6
6
  "files": [
@@ -13,6 +13,10 @@ import {
13
13
  import { tmpdir } from "node:os";
14
14
  import { join } from "node:path";
15
15
  import { collectModules } from "plgg-bundle/domain/usecase/collectModules";
16
+ import {
17
+ emitCjsBundle,
18
+ emitEsmBundle,
19
+ } from "plgg-bundle/domain/usecase/emitBundle";
16
20
 
17
21
  test("collectModules records external specifiers without walking them", () => {
18
22
  const root = mkdtempSync(
@@ -20,7 +24,9 @@ test("collectModules records external specifiers without walking them", () => {
20
24
  );
21
25
  try {
22
26
  const entry = join(root, "src", "main.ts");
23
- mkdirSync(join(root, "src"), { recursive: true });
27
+ mkdirSync(join(root, "src"), {
28
+ recursive: true,
29
+ });
24
30
  writeFileSync(
25
31
  entry,
26
32
  'import { readFileSync } from "node:fs";\nexport const read = readFileSync;\n',
@@ -40,21 +46,26 @@ test("collectModules records external specifiers without walking them", () => {
40
46
  ),
41
47
  ]);
42
48
  } finally {
43
- rmSync(root, { recursive: true, force: true });
49
+ rmSync(root, {
50
+ recursive: true,
51
+ force: true,
52
+ });
44
53
  }
45
54
  });
46
55
 
47
56
  test("collectModules throws on an unresolvable non-external specifier", () => {
48
57
  const root = mkdtempSync(
49
- join(tmpdir(), "plgg-bundle-collect-missing-"),
58
+ join(
59
+ tmpdir(),
60
+ "plgg-bundle-collect-missing-",
61
+ ),
50
62
  );
51
63
  try {
52
64
  const entry = join(root, "src", "main.ts");
53
- mkdirSync(join(root, "src"), { recursive: true });
54
- writeFileSync(
55
- entry,
56
- 'import "missing";\n',
57
- );
65
+ mkdirSync(join(root, "src"), {
66
+ recursive: true,
67
+ });
68
+ writeFileSync(entry, 'import "missing";\n');
58
69
  return check(
59
70
  rejects(() =>
60
71
  collectModules({
@@ -68,7 +79,10 @@ test("collectModules throws on an unresolvable non-external specifier", () => {
68
79
  toBe(true),
69
80
  );
70
81
  } finally {
71
- rmSync(root, { recursive: true, force: true });
82
+ rmSync(root, {
83
+ recursive: true,
84
+ force: true,
85
+ });
72
86
  }
73
87
  });
74
88
 
@@ -92,13 +106,21 @@ test("collectModules inlines an installed prebundled dist entry without resolvin
92
106
  "dist",
93
107
  "index.es.js",
94
108
  );
95
- mkdirSync(join(root, "src"), { recursive: true });
96
- mkdirSync(join(root, "node_modules", "dep", "dist"), {
97
- recursive: true,
98
- });
99
- mkdirSync(join(root, "node_modules", "plgg", "dist"), {
109
+ mkdirSync(join(root, "src"), {
100
110
  recursive: true,
101
111
  });
112
+ mkdirSync(
113
+ join(root, "node_modules", "dep", "dist"),
114
+ {
115
+ recursive: true,
116
+ },
117
+ );
118
+ mkdirSync(
119
+ join(root, "node_modules", "plgg", "dist"),
120
+ {
121
+ recursive: true,
122
+ },
123
+ );
102
124
  writeFileSync(
103
125
  entry,
104
126
  'import dep from "dep";\nexport const value = dep.value;\n',
@@ -159,7 +181,10 @@ test("collectModules inlines an installed prebundled dist entry without resolvin
159
181
  ),
160
182
  ]);
161
183
  } finally {
162
- rmSync(root, { recursive: true, force: true });
184
+ rmSync(root, {
185
+ recursive: true,
186
+ force: true,
187
+ });
163
188
  }
164
189
  });
165
190
 
@@ -190,11 +215,17 @@ test("collectModules resolves node_modules source files without dist-registry fi
190
215
  aliasPrefix: "app",
191
216
  aliasSrcRoot: join(root, "src"),
192
217
  external: /^node:/,
193
- resolve: importFixtureResolver(dep, internal),
218
+ resolve: importFixtureResolver(
219
+ dep,
220
+ internal,
221
+ ),
194
222
  });
195
223
  return check(graph.modules.length, toBe(3));
196
224
  } finally {
197
- rmSync(root, { recursive: true, force: true });
225
+ rmSync(root, {
226
+ recursive: true,
227
+ force: true,
228
+ });
198
229
  }
199
230
  });
200
231
 
@@ -225,11 +256,134 @@ test("collectModules resolves non-js dist source files without dist-registry fil
225
256
  aliasPrefix: "app",
226
257
  aliasSrcRoot: join(root, "src"),
227
258
  external: /^node:/,
228
- resolve: importFixtureResolver(dep, internal),
259
+ resolve: importFixtureResolver(
260
+ dep,
261
+ internal,
262
+ ),
229
263
  });
230
264
  return check(graph.modules.length, toBe(3));
231
265
  } finally {
232
- rmSync(root, { recursive: true, force: true });
266
+ rmSync(root, {
267
+ recursive: true,
268
+ force: true,
269
+ });
270
+ }
271
+ });
272
+
273
+ test("an app bundling two dists with identical inner module paths keeps each dist's externals resolvable", () => {
274
+ // Regression for the plggmatic blank-page defect:
275
+ // pkg-a and pkg-b are plgg-bundle ESM dists whose
276
+ // inner registries both key "src/index.ts", and
277
+ // pkg-b requires pkg-a through its inner
278
+ // __externals table. The outer rewrite must keep
279
+ // that table's key in step with the rewritten
280
+ // require, or pkg-b's inner lookup falls into the
281
+ // async import fallback and yields a Promise where
282
+ // a namespace is expected.
283
+ const root = mkdtempSync(
284
+ join(tmpdir(), "plgg-bundle-collect-nested-"),
285
+ );
286
+ try {
287
+ const entry = join(root, "src", "main.ts");
288
+ const pkgA = join(
289
+ root,
290
+ "node_modules",
291
+ "pkg-a",
292
+ "dist",
293
+ "index.es.js",
294
+ );
295
+ const pkgB = join(
296
+ root,
297
+ "node_modules",
298
+ "pkg-b",
299
+ "dist",
300
+ "index.es.js",
301
+ );
302
+ mkdirSync(join(root, "src"), {
303
+ recursive: true,
304
+ });
305
+ mkdirSync(join(pkgA, ".."), {
306
+ recursive: true,
307
+ });
308
+ mkdirSync(join(pkgB, ".."), {
309
+ recursive: true,
310
+ });
311
+ writeFileSync(
312
+ entry,
313
+ [
314
+ 'import { box } from "pkg-a";',
315
+ 'import { useBox } from "pkg-b";',
316
+ "export const direct = box(1);",
317
+ "export const viaB = useBox();",
318
+ ].join("\n"),
319
+ );
320
+ // Both dists come from the real emitter, so the
321
+ // fixture cannot drift from the emitted shape.
322
+ writeFileSync(
323
+ pkgA,
324
+ emitEsmBundle(
325
+ {
326
+ entryId: "src/index.ts",
327
+ modules: [
328
+ {
329
+ id: "src/index.ts",
330
+ code: "exports.box = (v) => v * 2;",
331
+ externals: [],
332
+ },
333
+ ],
334
+ },
335
+ ["box"],
336
+ ),
337
+ );
338
+ writeFileSync(
339
+ pkgB,
340
+ emitEsmBundle(
341
+ {
342
+ entryId: "src/index.ts",
343
+ modules: [
344
+ {
345
+ id: "src/index.ts",
346
+ code: 'exports.useBox = () => require("pkg-a").box(21);',
347
+ externals: ["pkg-a"],
348
+ },
349
+ ],
350
+ },
351
+ ["useBox"],
352
+ ),
353
+ );
354
+ const graph = collectModules({
355
+ entryFile: entry,
356
+ root,
357
+ aliasPrefix: "app",
358
+ aliasSrcRoot: join(root, "src"),
359
+ external: /^node:/,
360
+ resolve: (specifier, _fromFile) =>
361
+ specifier === "pkg-a"
362
+ ? pkgA
363
+ : specifier === "pkg-b"
364
+ ? pkgB
365
+ : undefined,
366
+ });
367
+ const mod: {
368
+ exports: Record<string, unknown>;
369
+ } = {
370
+ exports: {},
371
+ };
372
+ new Function(
373
+ "module",
374
+ "exports",
375
+ "require",
376
+ emitCjsBundle(graph),
377
+ )(mod, mod.exports, () => ({}));
378
+ return all([
379
+ check(mod.exports.direct, toBe(2)),
380
+ check(mod.exports.viaB, toBe(42)),
381
+ ]);
382
+ } finally {
383
+ rmSync(root, {
384
+ recursive: true,
385
+ force: true,
386
+ });
233
387
  }
234
388
  });
235
389
 
@@ -238,9 +392,13 @@ const writeImportFixture = (
238
392
  dep: string,
239
393
  internal: string,
240
394
  ): void => {
241
- mkdirSync(join(entry, ".."), { recursive: true });
395
+ mkdirSync(join(entry, ".."), {
396
+ recursive: true,
397
+ });
242
398
  mkdirSync(join(dep, ".."), { recursive: true });
243
- mkdirSync(join(internal, ".."), { recursive: true });
399
+ mkdirSync(join(internal, ".."), {
400
+ recursive: true,
401
+ });
244
402
  writeFileSync(
245
403
  entry,
246
404
  'import { value } from "dep";\nexport const out = value;\n',
@@ -135,7 +135,10 @@ const linkModule = (
135
135
  cjs: string,
136
136
  acc: Map<string, Module>,
137
137
  ): ReadonlyArray<string> => {
138
- const specifiers = inlineRequireSpecifiers(file, cjs);
138
+ const specifiers = inlineRequireSpecifiers(
139
+ file,
140
+ cjs,
141
+ );
139
142
  const externals: string[] = [];
140
143
  const deps: string[] = [];
141
144
  let rewritten = cjs;
@@ -156,6 +159,13 @@ const linkModule = (
156
159
  spec,
157
160
  idOf(ctx.root, resolved),
158
161
  );
162
+ if (isInstalledDist(file)) {
163
+ rewritten = replaceExternalKey(
164
+ rewritten,
165
+ spec,
166
+ idOf(ctx.root, resolved),
167
+ );
168
+ }
159
169
  }
160
170
  acc.set(id, {
161
171
  id,
@@ -185,14 +195,18 @@ const inlineRequireSpecifiers = (
185
195
  return specifiers;
186
196
  }
187
197
  const internal = bundledModuleIds(cjs);
188
- return specifiers.filter((spec) => !internal.has(spec));
198
+ return specifiers.filter(
199
+ (spec) => !internal.has(spec),
200
+ );
189
201
  };
190
202
 
191
203
  /**
192
204
  * Whether a file is a registry-installed built JS entry.
193
205
  * Source packages under the monorepo are never matched.
194
206
  */
195
- const isInstalledDist = (file: string): boolean => {
207
+ const isInstalledDist = (
208
+ file: string,
209
+ ): boolean => {
196
210
  const normalized = file.split("\\").join("/");
197
211
  return (
198
212
  normalized.includes("/node_modules/") &&
@@ -262,6 +276,29 @@ const requireSpecifiers = (
262
276
  m[1] === undefined ? [] : [m[1]],
263
277
  );
264
278
 
279
+ /**
280
+ * Rewrite an inlined dist's inner `__externals` table
281
+ * key from the original specifier to the outer module
282
+ * id. A plgg-bundle ESM dist resolves an external
283
+ * required inside its inner module bodies through this
284
+ * table; {@link replaceRequire} rewrites those bodies'
285
+ * `require("<spec>")` calls to the outer id, so the key
286
+ * must follow or the inner lookup misses and falls into
287
+ * the transpiled dynamic-import fallback — a Promise
288
+ * where the consumer expects a namespace ("plgg_1.box
289
+ * is not a function"). Matches the exact
290
+ * `"<spec>": __extN` shape the TS printer emits for the
291
+ * table, so ordinary code never collides with it.
292
+ */
293
+ const replaceExternalKey = (
294
+ cjs: string,
295
+ spec: string,
296
+ id: string,
297
+ ): string =>
298
+ cjs
299
+ .split(`${JSON.stringify(spec)}: __ext`)
300
+ .join(`${JSON.stringify(id)}: __ext`);
301
+
265
302
  /**
266
303
  * Rewrite every `require("spec")` occurrence to
267
304
  * `require("id")`. Operates on the exact specifier
@@ -126,12 +126,12 @@ test("discoverWorkspace finds installed deps of a sibling source package", () =>
126
126
  const local = join(root, "packages", "local");
127
127
  writePackage(local, "local", true);
128
128
  writePackage(
129
- join(local, "node_modules", "plgg-ui"),
130
- "plgg-ui",
129
+ join(local, "node_modules", "plgg-cms"),
130
+ "plgg-cms",
131
131
  false,
132
132
  );
133
133
  const found = discoverWorkspace(app).find(
134
- (p) => p.name === "plgg-ui",
134
+ (p) => p.name === "plgg-cms",
135
135
  );
136
136
  return check(found?.kind, toBe("dist"));
137
137
  } finally {