rollup-plugin-iife-split 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.
package/README.md ADDED
@@ -0,0 +1,119 @@
1
+ # rollup-plugin-iife-split
2
+
3
+ A Rollup plugin that enables intelligent code-splitting for IIFE output.
4
+
5
+ ## The Problem
6
+
7
+ Rollup's native IIFE output doesn't support code-splitting. With ESM, Rollup automatically creates shared chunks that are imported transparently. But with IIFE, you'd need to manually add `<script>` tags for each chunk, and those chunk names change between builds.
8
+
9
+ ## The Solution
10
+
11
+ This plugin:
12
+ 1. Uses Rollup's ESM code-splitting internally
13
+ 2. Merges all shared code into a "primary" entry point
14
+ 3. Converts everything to IIFE at the last moment
15
+ 4. Satellite entries access shared code via a global variable
16
+
17
+ The result: You get code-splitting benefits with only one `<script>` tag per entry point.
18
+
19
+ ## Installation
20
+
21
+ ```bash
22
+ npm install rollup-plugin-iife-split
23
+ ```
24
+
25
+ ## Usage
26
+
27
+ ```js
28
+ // rollup.config.js
29
+ import iifeSplit from 'rollup-plugin-iife-split';
30
+
31
+ export default {
32
+ input: {
33
+ main: 'src/main.js',
34
+ admin: 'src/admin.js',
35
+ widget: 'src/widget.js'
36
+ },
37
+ plugins: [
38
+ iifeSplit({
39
+ primary: 'main', // Which entry gets the shared code
40
+ primaryGlobal: 'MyLib', // Browser global: window.MyLib
41
+ secondaryProps: {
42
+ admin: 'Admin', // Browser global: window.MyLib.Admin
43
+ widget: 'Widget', // Browser global: window.MyLib.Widget
44
+ },
45
+ sharedProp: 'Shared', // Shared code at: window.MyLib.Shared
46
+ })
47
+ ],
48
+ output: {
49
+ dir: 'dist'
50
+ }
51
+ };
52
+ ```
53
+
54
+ ## Output
55
+
56
+ **main.js** (primary entry):
57
+ ```js
58
+ var MyLib = (function (exports) {
59
+ // Shared code from all common dependencies
60
+ function sharedUtil() { /* ... */ }
61
+
62
+ // Main entry code
63
+ function mainFeature() { /* ... */ }
64
+
65
+ exports.mainFeature = mainFeature;
66
+ exports.Shared = { sharedUtil };
67
+ return exports;
68
+ })({});
69
+ ```
70
+
71
+ **admin.js** (satellite entry):
72
+ ```js
73
+ MyLib.Admin = (function (exports, shared) {
74
+ // Uses shared code via parameter
75
+ function adminFeature() {
76
+ return shared.sharedUtil();
77
+ }
78
+
79
+ exports.adminFeature = adminFeature;
80
+ return exports;
81
+ })({}, MyLib.Shared);
82
+ ```
83
+
84
+ ## HTML Usage
85
+
86
+ ```html
87
+ <!-- Load primary first (contains shared code) -->
88
+ <script src="dist/main.js"></script>
89
+
90
+ <!-- Then load any satellite entries you need -->
91
+ <script src="dist/admin.js"></script>
92
+
93
+ <script>
94
+ MyLib.mainFeature();
95
+ MyLib.Admin.adminFeature();
96
+ </script>
97
+ ```
98
+
99
+ ## Options
100
+
101
+ | Option | Type | Required | Description |
102
+ |--------|------|----------|-------------|
103
+ | `primary` | `string` | Yes | Name of the primary entry (must match a key in Rollup's `input`). Shared code is merged into this entry. |
104
+ | `primaryGlobal` | `string` | Yes | Browser global variable name for the primary entry. Example: `'MyLib'` → `window.MyLib` |
105
+ | `secondaryProps` | `Record<string, string>` | Yes | Maps secondary entry names to their property name on the primary global. Example: `{ admin: 'Admin' }` → `window.MyLib.Admin` |
106
+ | `sharedProp` | `string` | Yes | Property name on the global where shared exports are attached. Example: `'Shared'` → `window.MyLib.Shared` |
107
+ | `debug` | `boolean` | No | Enable debug logging to see intermediate transformation steps. |
108
+
109
+ ## How It Works
110
+
111
+ 1. **Build phase**: Rollup builds with ESM format, using `manualChunks` to consolidate all shared modules into one chunk
112
+ 2. **Transform phase**: In `generateBundle`, the plugin:
113
+ - Merges the shared chunk into the primary entry
114
+ - Converts each chunk from ESM to IIFE using a nested Rollup instance
115
+ - Deletes the shared chunk from output
116
+
117
+ ## License
118
+
119
+ MIT
@@ -0,0 +1,34 @@
1
+ import { Plugin } from 'rollup';
2
+
3
+ interface IifeSplitOptions {
4
+ /**
5
+ * The name of the primary entry point (must match a key in Rollup's input map).
6
+ * The shared chunk will be merged into this entry.
7
+ */
8
+ primary: string;
9
+ /**
10
+ * The global variable name for the primary entry's exports.
11
+ * Example: 'MyLib' results in `window.MyLib = ...`
12
+ */
13
+ primaryGlobal: string;
14
+ /**
15
+ * Maps secondary entry names to their property name on the primary global.
16
+ * Example: { admin: 'Admin', widget: 'Widget' } results in:
17
+ * - `window.MyLib.Admin` for the 'admin' entry
18
+ * - `window.MyLib.Widget` for the 'widget' entry
19
+ */
20
+ secondaryProps: Record<string, string>;
21
+ /**
22
+ * The property name on the global where shared exports are attached.
23
+ * Example: 'Shared' results in `window.MyLib.Shared = { ... }`
24
+ */
25
+ sharedProp: string;
26
+ /**
27
+ * Enable debug logging to see intermediate transformation steps.
28
+ */
29
+ debug?: boolean;
30
+ }
31
+
32
+ declare function iifeSplit(options: IifeSplitOptions): Plugin;
33
+
34
+ export { type IifeSplitOptions, iifeSplit as default };
package/dist/index.js ADDED
@@ -0,0 +1,564 @@
1
+ // src/chunk-analyzer.ts
2
+ function isOutputChunk(item) {
3
+ return item.type === "chunk";
4
+ }
5
+ var SHARED_CHUNK_NAME = "__shared__";
6
+ function analyzeChunks(bundle, primaryEntryName) {
7
+ const chunks = Object.values(bundle).filter(isOutputChunk);
8
+ const sharedChunk = chunks.find(
9
+ (chunk) => !chunk.isEntry && chunk.name === SHARED_CHUNK_NAME
10
+ ) || null;
11
+ const primaryChunk = chunks.find(
12
+ (chunk) => chunk.isEntry && chunk.name === primaryEntryName
13
+ );
14
+ if (!primaryChunk) {
15
+ const availableEntries = chunks.filter((c) => c.isEntry).map((c) => c.name).join(", ");
16
+ throw new Error(
17
+ `Primary entry "${primaryEntryName}" not found in bundle. Available entries: ${availableEntries}`
18
+ );
19
+ }
20
+ const satelliteChunks = chunks.filter(
21
+ (chunk) => chunk.isEntry && chunk.name !== primaryEntryName
22
+ );
23
+ return { sharedChunk, primaryChunk, satelliteChunks };
24
+ }
25
+
26
+ // src/esm-to-iife.ts
27
+ import { rollup } from "rollup";
28
+ import { walk } from "estree-walker";
29
+ import MagicString from "magic-string";
30
+ var VIRTUAL_ENTRY = "\0virtual:entry";
31
+ function createVirtualPlugin(code) {
32
+ return {
33
+ name: "virtual-entry",
34
+ resolveId(id) {
35
+ if (id === VIRTUAL_ENTRY) {
36
+ return id;
37
+ }
38
+ return { id, external: true };
39
+ },
40
+ load(id) {
41
+ if (id === VIRTUAL_ENTRY) {
42
+ return code;
43
+ }
44
+ return null;
45
+ }
46
+ };
47
+ }
48
+ function extractSharedImportMappings(code, parse) {
49
+ const ast = parse(code);
50
+ const mappings = [];
51
+ walk(ast, {
52
+ enter(node) {
53
+ if (node.type === "ImportDeclaration") {
54
+ const importNode = node;
55
+ const source = importNode.source.value;
56
+ if (typeof source === "string" && source.includes(SHARED_CHUNK_NAME)) {
57
+ for (const spec of importNode.specifiers) {
58
+ if (spec.type === "ImportSpecifier" && spec.imported) {
59
+ mappings.push({
60
+ imported: spec.imported.name,
61
+ local: spec.local.name
62
+ });
63
+ } else if (spec.type === "ImportDefaultSpecifier") {
64
+ mappings.push({
65
+ imported: "default",
66
+ local: spec.local.name
67
+ });
68
+ }
69
+ }
70
+ }
71
+ }
72
+ }
73
+ });
74
+ return mappings;
75
+ }
76
+ function stripNamespaceGuards(code) {
77
+ let result = code.replace(/^this\.\w+\s*=\s*this\.\w+\s*\|\|\s*\{\};\n/gm, "");
78
+ result = result.replace(/^this\.(\w+\.\w+)\s*=/gm, "$1 =");
79
+ return result;
80
+ }
81
+ function destructureSharedParameter(code, mappings, parse) {
82
+ const ast = parse(code);
83
+ const ms = new MagicString(code);
84
+ let sharedParamStart = -1;
85
+ let sharedParamEnd = -1;
86
+ let sharedParamName = null;
87
+ walk(ast, {
88
+ enter(node) {
89
+ if (sharedParamName === null && node.type === "FunctionExpression") {
90
+ const fn = node;
91
+ const params = fn.params;
92
+ if (params.length > 0) {
93
+ const lastParam = params[params.length - 1];
94
+ if (lastParam.type === "Identifier") {
95
+ const acornParam = lastParam;
96
+ sharedParamStart = acornParam.start;
97
+ sharedParamEnd = acornParam.end;
98
+ sharedParamName = lastParam.name;
99
+ }
100
+ }
101
+ }
102
+ }
103
+ });
104
+ if (sharedParamName === null) {
105
+ return code;
106
+ }
107
+ const propertyAccesses = [];
108
+ walk(ast, {
109
+ enter(node) {
110
+ if (node.type === "MemberExpression") {
111
+ const memberNode = node;
112
+ const obj = memberNode.object;
113
+ if (obj.type === "Identifier" && obj.name === sharedParamName && !memberNode.computed) {
114
+ const prop = memberNode.property;
115
+ propertyAccesses.push({
116
+ start: memberNode.start,
117
+ end: memberNode.end,
118
+ propName: prop.name
119
+ });
120
+ }
121
+ }
122
+ }
123
+ });
124
+ let effectiveMappings = mappings;
125
+ if (effectiveMappings.length === 0) {
126
+ const propNames = new Set(propertyAccesses.map((a) => a.propName));
127
+ effectiveMappings = Array.from(propNames).map((prop) => ({
128
+ imported: prop,
129
+ local: prop
130
+ }));
131
+ }
132
+ if (effectiveMappings.length === 0) {
133
+ return code;
134
+ }
135
+ const importToLocal = new Map(effectiveMappings.map((m) => [m.imported, m.local]));
136
+ const destructureEntries = effectiveMappings.map(
137
+ (m) => m.imported === m.local ? m.imported : `${m.imported}: ${m.local}`
138
+ );
139
+ const destructurePattern = `{ ${destructureEntries.join(", ")} }`;
140
+ ms.overwrite(sharedParamStart, sharedParamEnd, destructurePattern);
141
+ for (const { start, end, propName } of propertyAccesses) {
142
+ const localName = importToLocal.get(propName) ?? propName;
143
+ ms.overwrite(start, end, localName);
144
+ }
145
+ walk(ast, {
146
+ enter(node) {
147
+ if (node.type === "ExpressionStatement") {
148
+ const exprStmt = node;
149
+ if (exprStmt.expression.type === "Literal" && exprStmt.expression.value === "use strict") {
150
+ ms.remove(exprStmt.start, exprStmt.end);
151
+ }
152
+ }
153
+ }
154
+ });
155
+ return ms.toString();
156
+ }
157
+ async function convertToIife(options) {
158
+ const { code, globalName, globals, sharedGlobalPath, sharedChunkFileName, parse, debug } = options;
159
+ const importMappings = sharedGlobalPath ? extractSharedImportMappings(code, parse) : [];
160
+ if (debug && sharedGlobalPath) {
161
+ console.log("\n=== DEBUG convertToIife ===");
162
+ console.log("globalName:", globalName);
163
+ console.log("sharedGlobalPath:", sharedGlobalPath);
164
+ console.log("sharedChunkFileName:", sharedChunkFileName);
165
+ console.log("--- ESM code (first 500 chars) ---");
166
+ console.log(code.slice(0, 500));
167
+ console.log("--- Import mappings ---");
168
+ console.log(importMappings);
169
+ }
170
+ const rollupGlobals = (id) => {
171
+ if (sharedGlobalPath) {
172
+ if (id.includes(SHARED_CHUNK_NAME)) {
173
+ return sharedGlobalPath;
174
+ }
175
+ if (sharedChunkFileName) {
176
+ const fileNameWithoutExt = sharedChunkFileName.replace(/\.js$/, "");
177
+ if (id.includes(fileNameWithoutExt)) {
178
+ return sharedGlobalPath;
179
+ }
180
+ }
181
+ }
182
+ return globals[id] ?? id;
183
+ };
184
+ const bundle = await rollup({
185
+ input: VIRTUAL_ENTRY,
186
+ plugins: [createVirtualPlugin(code)],
187
+ onwarn: () => {
188
+ }
189
+ // Suppress warnings
190
+ });
191
+ const { output } = await bundle.generate({
192
+ format: "iife",
193
+ name: globalName,
194
+ globals: rollupGlobals,
195
+ exports: "named"
196
+ });
197
+ await bundle.close();
198
+ let result = output[0].code;
199
+ if (debug && sharedGlobalPath) {
200
+ console.log("--- IIFE before destructuring (first 800 chars) ---");
201
+ console.log(result.slice(0, 800));
202
+ }
203
+ if (sharedGlobalPath) {
204
+ result = stripNamespaceGuards(result);
205
+ result = destructureSharedParameter(result, importMappings, parse);
206
+ if (debug) {
207
+ console.log("--- IIFE after destructuring (first 800 chars) ---");
208
+ console.log(result.slice(0, 800));
209
+ console.log("=== END DEBUG ===\n");
210
+ }
211
+ }
212
+ return result;
213
+ }
214
+
215
+ // src/chunk-merger.ts
216
+ import MagicString2 from "magic-string";
217
+ import { walk as walk2 } from "estree-walker";
218
+ function extractTopLevelDeclarations(code, parse) {
219
+ const ast = parse(code);
220
+ const declarations = /* @__PURE__ */ new Set();
221
+ for (const node of ast.body) {
222
+ if (node.type === "VariableDeclaration") {
223
+ const varDecl = node;
224
+ for (const decl of varDecl.declarations) {
225
+ if (decl.id.type === "Identifier") {
226
+ declarations.add(decl.id.name);
227
+ }
228
+ }
229
+ } else if (node.type === "FunctionDeclaration") {
230
+ const funcDecl = node;
231
+ if (funcDecl.id) {
232
+ declarations.add(funcDecl.id.name);
233
+ }
234
+ } else if (node.type === "ClassDeclaration") {
235
+ const classDecl = node;
236
+ if (classDecl.id) {
237
+ declarations.add(classDecl.id.name);
238
+ }
239
+ } else if (node.type === "ExportNamedDeclaration") {
240
+ const exportNode = node;
241
+ if (exportNode.declaration) {
242
+ if (exportNode.declaration.type === "VariableDeclaration") {
243
+ for (const decl of exportNode.declaration.declarations) {
244
+ if (decl.id.type === "Identifier") {
245
+ declarations.add(decl.id.name);
246
+ }
247
+ }
248
+ } else if (exportNode.declaration.type === "FunctionDeclaration" && exportNode.declaration.id) {
249
+ declarations.add(exportNode.declaration.id.name);
250
+ } else if (exportNode.declaration.type === "ClassDeclaration" && exportNode.declaration.id) {
251
+ declarations.add(exportNode.declaration.id.name);
252
+ }
253
+ }
254
+ }
255
+ }
256
+ return declarations;
257
+ }
258
+ function renameIdentifiers(code, renameMap, parse) {
259
+ if (renameMap.size === 0) return code;
260
+ const ast = parse(code);
261
+ const s = new MagicString2(code);
262
+ walk2(ast, {
263
+ enter(node) {
264
+ if (node.type === "Identifier") {
265
+ const id = node;
266
+ const newName = renameMap.get(id.name);
267
+ if (newName) {
268
+ s.overwrite(id.start, id.end, newName);
269
+ }
270
+ }
271
+ }
272
+ });
273
+ return s.toString();
274
+ }
275
+ function extractExports(code, parse) {
276
+ const ast = parse(code);
277
+ const exports = [];
278
+ let hasDefault = false;
279
+ walk2(ast, {
280
+ enter(node) {
281
+ if (node.type === "ExportNamedDeclaration") {
282
+ const exportNode = node;
283
+ if (exportNode.declaration) {
284
+ if (exportNode.declaration.type === "VariableDeclaration") {
285
+ for (const decl of exportNode.declaration.declarations) {
286
+ if (decl.id.type === "Identifier") {
287
+ exports.push({ exportedName: decl.id.name, localName: decl.id.name });
288
+ }
289
+ }
290
+ } else if (exportNode.declaration.type === "FunctionDeclaration" && exportNode.declaration.id) {
291
+ const name = exportNode.declaration.id.name;
292
+ exports.push({ exportedName: name, localName: name });
293
+ } else if (exportNode.declaration.type === "ClassDeclaration" && exportNode.declaration.id) {
294
+ const name = exportNode.declaration.id.name;
295
+ exports.push({ exportedName: name, localName: name });
296
+ }
297
+ }
298
+ if (exportNode.specifiers) {
299
+ for (const spec of exportNode.specifiers) {
300
+ const exported = spec.exported;
301
+ const local = spec.local;
302
+ exports.push({ exportedName: exported.name, localName: local.name });
303
+ }
304
+ }
305
+ }
306
+ if (node.type === "ExportDefaultDeclaration") {
307
+ hasDefault = true;
308
+ }
309
+ }
310
+ });
311
+ return { exports, hasDefault };
312
+ }
313
+ function stripExports(code, parse) {
314
+ const ast = parse(code);
315
+ const s = new MagicString2(code);
316
+ walk2(ast, {
317
+ enter(node) {
318
+ const n = node;
319
+ if (node.type === "ExportNamedDeclaration") {
320
+ const exportNode = node;
321
+ if (exportNode.declaration) {
322
+ const declNode = exportNode.declaration;
323
+ s.remove(exportNode.start, declNode.start);
324
+ } else {
325
+ s.remove(n.start, n.end);
326
+ }
327
+ }
328
+ if (node.type === "ExportDefaultDeclaration") {
329
+ const exportNode = node;
330
+ const declNode = exportNode.declaration;
331
+ s.overwrite(exportNode.start, declNode.start, "const __shared_default__ = ");
332
+ }
333
+ }
334
+ });
335
+ return s.toString();
336
+ }
337
+ function isSharedChunkSource(source, sharedChunkFileName) {
338
+ return source.includes(SHARED_CHUNK_NAME) || source.includes(sharedChunkFileName.replace(/\.js$/, ""));
339
+ }
340
+ function removeSharedImportsAndRewriteRefs(code, sharedChunkFileName, sharedExportToLocal, parse) {
341
+ const ast = parse(code);
342
+ const s = new MagicString2(code);
343
+ const namespaceNames = /* @__PURE__ */ new Set();
344
+ walk2(ast, {
345
+ enter(node) {
346
+ if (node.type === "ImportDeclaration") {
347
+ const importNode = node;
348
+ const source = importNode.source.value;
349
+ if (typeof source === "string" && isSharedChunkSource(source, sharedChunkFileName)) {
350
+ for (const spec of importNode.specifiers) {
351
+ if (spec.type === "ImportNamespaceSpecifier") {
352
+ namespaceNames.add(spec.local.name);
353
+ }
354
+ }
355
+ }
356
+ }
357
+ }
358
+ });
359
+ walk2(ast, {
360
+ enter(node) {
361
+ if (node.type === "ImportDeclaration") {
362
+ const importNode = node;
363
+ const source = importNode.source.value;
364
+ if (typeof source === "string" && isSharedChunkSource(source, sharedChunkFileName)) {
365
+ s.remove(importNode.start, importNode.end);
366
+ }
367
+ }
368
+ if (node.type === "ExportNamedDeclaration") {
369
+ const exportNode = node;
370
+ if (exportNode.source) {
371
+ const source = exportNode.source.value;
372
+ if (typeof source === "string" && isSharedChunkSource(source, sharedChunkFileName)) {
373
+ const exportParts = [];
374
+ for (const spec of exportNode.specifiers) {
375
+ const exportedName = spec.exported.name;
376
+ const importedName = spec.local.name;
377
+ const localName = sharedExportToLocal.get(importedName) ?? importedName;
378
+ if (localName === exportedName) {
379
+ exportParts.push(localName);
380
+ } else {
381
+ exportParts.push(`${localName} as ${exportedName}`);
382
+ }
383
+ }
384
+ s.overwrite(exportNode.start, exportNode.end, `export { ${exportParts.join(", ")} };`);
385
+ }
386
+ }
387
+ }
388
+ if (node.type === "MemberExpression") {
389
+ const memberNode = node;
390
+ if (memberNode.object.type === "Identifier" && memberNode.object.name && namespaceNames.has(memberNode.object.name) && memberNode.property.type === "Identifier" && memberNode.property.name && !memberNode.computed) {
391
+ const propertyName = memberNode.property.name;
392
+ const localName = sharedExportToLocal.get(propertyName) ?? propertyName;
393
+ s.overwrite(memberNode.start, memberNode.end, localName);
394
+ }
395
+ }
396
+ }
397
+ });
398
+ return s.toString();
399
+ }
400
+ function extractSharedImports(code, sharedChunkFileName, parse) {
401
+ const ast = parse(code);
402
+ const imports = /* @__PURE__ */ new Set();
403
+ walk2(ast, {
404
+ enter(node) {
405
+ if (node.type === "ImportDeclaration") {
406
+ const importNode = node;
407
+ const source = importNode.source.value;
408
+ if (typeof source === "string" && isSharedChunkSource(source, sharedChunkFileName)) {
409
+ for (const spec of importNode.specifiers) {
410
+ if (spec.type === "ImportSpecifier" && spec.imported) {
411
+ imports.add(spec.imported.name);
412
+ } else if (spec.type === "ImportDefaultSpecifier") {
413
+ imports.add("default");
414
+ } else if (spec.type === "ImportNamespaceSpecifier") {
415
+ }
416
+ }
417
+ }
418
+ }
419
+ }
420
+ });
421
+ return imports;
422
+ }
423
+ function mergeSharedIntoPrimary(primaryChunk, sharedChunk, sharedProperty, neededExports, parse) {
424
+ const { exports: sharedExports, hasDefault } = extractExports(sharedChunk.code, parse);
425
+ const sharedDeclarations = extractTopLevelDeclarations(sharedChunk.code, parse);
426
+ const primaryDeclarations = extractTopLevelDeclarations(primaryChunk.code, parse);
427
+ const renameMap = /* @__PURE__ */ new Map();
428
+ for (const name of sharedDeclarations) {
429
+ if (primaryDeclarations.has(name)) {
430
+ renameMap.set(name, `__shared$${name}`);
431
+ }
432
+ }
433
+ let processedSharedCode = sharedChunk.code;
434
+ if (renameMap.size > 0) {
435
+ processedSharedCode = renameIdentifiers(processedSharedCode, renameMap, parse);
436
+ }
437
+ const strippedSharedCode = stripExports(processedSharedCode, parse);
438
+ const sharedExportToLocal = /* @__PURE__ */ new Map();
439
+ for (const exp of sharedExports) {
440
+ const renamedLocal = renameMap.get(exp.localName) ?? exp.localName;
441
+ sharedExportToLocal.set(exp.exportedName, renamedLocal);
442
+ }
443
+ if (hasDefault) {
444
+ sharedExportToLocal.set("default", "__shared_default__");
445
+ }
446
+ const primaryWithoutSharedImports = removeSharedImportsAndRewriteRefs(
447
+ primaryChunk.code,
448
+ sharedChunk.fileName,
449
+ sharedExportToLocal,
450
+ parse
451
+ );
452
+ const sharedExportEntries = [
453
+ ...sharedExports.filter((exp) => neededExports.has(exp.exportedName)).map((exp) => {
454
+ const renamedLocal = renameMap.get(exp.localName) ?? exp.localName;
455
+ return exp.exportedName === renamedLocal ? renamedLocal : `${exp.exportedName}: ${renamedLocal}`;
456
+ }),
457
+ ...hasDefault && neededExports.has("default") ? ["default: __shared_default__"] : []
458
+ ];
459
+ const sharedExportObject = `const ${sharedProperty} = { ${sharedExportEntries.join(", ")} };`;
460
+ primaryChunk.code = [
461
+ "// === Shared code (merged by rollup-plugin-iife-split) ===",
462
+ strippedSharedCode.trim(),
463
+ "",
464
+ "// === Primary entry code ===",
465
+ primaryWithoutSharedImports.trim(),
466
+ "",
467
+ "// === Shared exports object ===",
468
+ sharedExportObject,
469
+ `export { ${sharedProperty} };`
470
+ ].join("\n");
471
+ }
472
+
473
+ // src/index.ts
474
+ function iifeSplit(options) {
475
+ const { primary, primaryGlobal, secondaryProps, sharedProp, debug } = options;
476
+ let outputGlobals = {};
477
+ const manualChunks = (id, { getModuleInfo }) => {
478
+ const moduleInfo = getModuleInfo(id);
479
+ if (!moduleInfo) return void 0;
480
+ if (moduleInfo.isEntry) return void 0;
481
+ const importers = moduleInfo.importers || [];
482
+ if (importers.length > 1) {
483
+ return SHARED_CHUNK_NAME;
484
+ }
485
+ return void 0;
486
+ };
487
+ return {
488
+ name: "iife-split",
489
+ // Hook into outputOptions to capture globals and configure chunking
490
+ outputOptions(outputOptions) {
491
+ outputGlobals = outputOptions.globals ?? {};
492
+ return {
493
+ ...outputOptions,
494
+ format: "es",
495
+ manualChunks
496
+ };
497
+ },
498
+ // Main transformation hook - convert ESM chunks to IIFE
499
+ async generateBundle(outputOptions, bundle) {
500
+ const parse = this.parse.bind(this);
501
+ const analysis = analyzeChunks(bundle, primary);
502
+ const sharedChunkFileName = analysis.sharedChunk?.fileName ?? null;
503
+ if (analysis.sharedChunk) {
504
+ const neededExports = /* @__PURE__ */ new Set();
505
+ for (const satellite of analysis.satelliteChunks) {
506
+ const imports = extractSharedImports(satellite.code, analysis.sharedChunk.fileName, parse);
507
+ for (const imp of imports) {
508
+ neededExports.add(imp);
509
+ }
510
+ }
511
+ mergeSharedIntoPrimary(
512
+ analysis.primaryChunk,
513
+ analysis.sharedChunk,
514
+ sharedProp,
515
+ neededExports,
516
+ parse
517
+ );
518
+ delete bundle[analysis.sharedChunk.fileName];
519
+ }
520
+ const conversions = [];
521
+ conversions.push(
522
+ convertToIife({
523
+ code: analysis.primaryChunk.code,
524
+ globalName: primaryGlobal,
525
+ globals: outputGlobals,
526
+ sharedGlobalPath: null,
527
+ // Primary doesn't need to import shared
528
+ sharedChunkFileName: null,
529
+ parse,
530
+ debug
531
+ }).then((code) => {
532
+ analysis.primaryChunk.code = code;
533
+ })
534
+ );
535
+ for (const satellite of analysis.satelliteChunks) {
536
+ const satelliteProp = secondaryProps[satellite.name];
537
+ const hasExports = satellite.exports.length > 0;
538
+ if (!satelliteProp && hasExports) {
539
+ throw new Error(
540
+ `Secondary entry "${satellite.name}" not found in secondaryProps. Available entries: ${Object.keys(secondaryProps).join(", ") || "(none)"}`
541
+ );
542
+ }
543
+ const satelliteGlobalName = satelliteProp ? `${primaryGlobal}.${satelliteProp}` : void 0;
544
+ conversions.push(
545
+ convertToIife({
546
+ code: satellite.code,
547
+ globalName: satelliteGlobalName,
548
+ globals: outputGlobals,
549
+ sharedGlobalPath: `${primaryGlobal}.${sharedProp}`,
550
+ sharedChunkFileName,
551
+ parse,
552
+ debug
553
+ }).then((code) => {
554
+ satellite.code = code;
555
+ })
556
+ );
557
+ }
558
+ await Promise.all(conversions);
559
+ }
560
+ };
561
+ }
562
+ export {
563
+ iifeSplit as default
564
+ };
package/package.json ADDED
@@ -0,0 +1,40 @@
1
+ {
2
+ "name": "rollup-plugin-iife-split",
3
+ "version": "0.0.1",
4
+ "type": "module",
5
+ "description": "Rollup plugin for intelligent IIFE code-splitting",
6
+ "main": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "files": [
9
+ "dist"
10
+ ],
11
+ "peerDependencies": {
12
+ "rollup": "^3.0.0 || ^4.0.0"
13
+ },
14
+ "dependencies": {
15
+ "magic-string": "^0.30.0",
16
+ "estree-walker": "^3.0.0"
17
+ },
18
+ "devDependencies": {
19
+ "rollup": "^4.0.0",
20
+ "typescript": "^5.0.0",
21
+ "tsup": "^8.0.0",
22
+ "vitest": "^1.0.0",
23
+ "@types/node": "^20.0.0"
24
+ },
25
+ "keywords": [
26
+ "rollup",
27
+ "plugin",
28
+ "iife",
29
+ "code-splitting",
30
+ "bundle"
31
+ ],
32
+ "license": "MIT",
33
+ "scripts": {
34
+ "clean": "rm -rf dist",
35
+ "build": "tsup src/index.ts --format esm --dts",
36
+ "typecheck": "tsc --noEmit",
37
+ "test": "vitest",
38
+ "test:run": "vitest run"
39
+ }
40
+ }