rollup-plugin-concurrent-top-level-await 0.0.9 → 0.2.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/README.md CHANGED
@@ -30,7 +30,16 @@ export default {
30
30
  };
31
31
  ```
32
32
 
33
- ### Which modules to include
33
+ ## Options
34
+
35
+ | Option | Type | Default | Description |
36
+ | ------------------------- | --------------- | ----------- | --------------------------------------------------------------------------------------------------------------------------------- |
37
+ | `include` | `FilterPattern` | `undefined` | A pattern specifying which files to include. See [below](#which-modules-to-include) to determine which modules to include. |
38
+ | `exclude` | `FilterPattern` | `undefined` | A pattern specifying which files to exclude. Must still follow the [same considerations](#which-modules-to-include) as `include`. |
39
+ | `sourceMap` | `boolean` | `true` | Whether to generate source maps for transformed files. |
40
+ | `generatedVariablePrefix` | `string` | `"__tla"` | Prefix used for internal variables generated by the plugin. Change this if it conflicts with variable names in your code. |
41
+
42
+ ### Which modules to include?
34
43
 
35
44
  The plugin needs to handle not only modules that directly contain a top-level `await`, but also their ancestor modules up to the lowest common ancestor. Ancestor modules must be transformed to handle the asynchronous completion of their children concurrently. As an example, consider the following module structure:
36
45
 
@@ -90,6 +99,14 @@ postponed until the subgraph is analyzed. This may lead to slower builds.
90
99
 
91
100
  If you notice significant performance degradation, please open an issue.
92
101
 
102
+ ### Exposed Module Structure
103
+
104
+ Because the execution of modules gets wrapped in functions, the bundled output will contain more information about the source module structure. This may be a consideration for projects where code obfuscation is important.
105
+
106
+ ### Tree Shaking
107
+
108
+ Wrapping code in functions may reduce tree shaking effectiveness. We mitigate this where possible, such as by not wrapping declarations.
109
+
93
110
  ### Changing Variable Types
94
111
 
95
112
  In the process of transforming the code, top level `const` declarations may get replaced with `let` declarations. This
@@ -97,7 +114,6 @@ can lead to `const` variables being assignable at runtime instead of throwing an
97
114
 
98
115
  Additionally, variable declarations may be hoisted, which removes temporal dead zone (TDZ) checks.
99
116
 
100
- ### Class Decorators
117
+ ### Default export class name
101
118
 
102
- Class declarations still get evaluated before any top level await expressions. This means that if a class decorator
103
- relies on a top level await expression, it may not work as expected.
119
+ When using `export default class {}`, the runtime `.name` of the exported value will be `<generatedVariablePrefix>_default` (e.g. `__tla_default`) instead of `default`.
package/dist/index.d.mts CHANGED
@@ -7,6 +7,12 @@ declare function concurrentTopLevelAwait(options?: {
7
7
  include?: FilterPattern;
8
8
  exclude?: FilterPattern;
9
9
  sourceMap?: boolean;
10
+ /**
11
+ * Prefix used for internal variables generated by the plugin.
12
+ * Change this if it conflicts with variable names in your code.
13
+ * @default "__tla"
14
+ */
15
+ generatedVariablePrefix?: string;
10
16
  }): {
11
17
  name: string;
12
18
  apply: "build";
package/dist/index.mjs CHANGED
@@ -128,84 +128,96 @@ var AsyncModuleTracker = class {
128
128
 
129
129
  //#endregion
130
130
  //#region src/transform.ts
131
- function transform(s, ast, asyncImports, hasAwait) {
132
- let declarationsEnd;
133
- [s, declarationsEnd] = tansformAndMoveDeclarationsToModuleScope(s, ast, asyncImports);
134
- s = s.appendRight(declarationsEnd, ";\nasync function __exec() {\n");
135
- s = s.append("}\n");
136
- const tlas = `[${asyncImports.map((_, i) => `__tla${i}`).join()}].flatMap(a => {
137
- try {
138
- const result = a();
139
- if (Array.isArray(result)) {
140
- return result
141
- }
142
- return [a];
143
- } catch {
144
- return []; // happens for cyclic dependencies
145
- }
146
- })`;
147
- const execWrapper = asyncImports.length === 0 ? "__exec();" : `Promise.all(${tlas}.map(e => e())).then(() => __exec());`;
148
- if (hasAwait) s = s.append(`const __tla = ${execWrapper}; const __todo = __tla;`);
149
- else s = s.append(`const __tla = ${tlas};
150
- const __todo = ${execWrapper};`);
151
- s = s.append("if (import.meta.useTla) await __todo;");
152
- s = s.append("export function __tla_access() { return __tla; };");
153
- return s;
131
+ function transform(s, ast, asyncImports, hasAwait, variablePrefix) {
132
+ const declarationsEnd = transformAndMoveDeclarationsToModuleScope(s, ast, asyncImports, variablePrefix);
133
+ s.appendRight(declarationsEnd, `async function ${variablePrefix}_initModuleExports() {\n`);
134
+ s.append("\n}\n");
135
+ const asyncDeps = `[${asyncImports.map((_, i) => `${variablePrefix}${i}`).join()}].flatMap(a => {
136
+ try {
137
+ const result = a();
138
+ if (Array.isArray(result)) {
139
+ return result
140
+ }
141
+ return [a];
142
+ } catch {
143
+ return []; // happens for cyclic dependencies
144
+ }
145
+ })`;
146
+ const initModuleExportsAfterDeps = asyncImports.length === 0 ? `${variablePrefix}_initModuleExports()` : `Promise.all(${asyncDeps}.map(e => e())).then(() => ${variablePrefix}_initModuleExports())`;
147
+ if (hasAwait) s.append(`const ${variablePrefix} = ${initModuleExportsAfterDeps};\nconst ${variablePrefix}_initPromise = ${variablePrefix};\n`);
148
+ else s.append(`const ${variablePrefix} = ${asyncDeps};\nconst ${variablePrefix}_initPromise = ${initModuleExportsAfterDeps};\n`);
149
+ s.append(`if (import.meta.useTla) await ${variablePrefix}_initPromise;\n`);
150
+ s.append(`export function ${variablePrefix}_access() { return ${variablePrefix}; };\n`);
154
151
  }
155
- function tansformAndMoveDeclarationsToModuleScope(s, ast, asyncImports) {
152
+ function transformAndMoveDeclarationsToModuleScope(s, ast, asyncImports, variablePrefix) {
156
153
  let moduleScopeEnd = 0;
157
154
  let i = 0;
158
155
  for (const node of ast.body) {
159
156
  if (asyncImports.includes(node)) {
160
- const tlaImport = `;import { __tla_access as __tla${i}} from '${node.source.value}';`;
161
- s = s.appendLeft(node.end, tlaImport);
157
+ const tlaImport = `\nimport { ${variablePrefix}_access as ${variablePrefix}${i}} from '${node.source.value}';`;
158
+ s.appendLeft(node.end, tlaImport);
162
159
  i++;
163
160
  }
161
+ if (node.type === "ClassDeclaration") {
162
+ s.appendLeft(moduleScopeEnd, `let ${node.id.name};\n`);
163
+ s.appendRight(node.start, `${node.id.name} = `);
164
+ }
164
165
  if (node.type === "ExportNamedDeclaration") {
165
166
  if (node.declaration?.type === "VariableDeclaration") {
166
- s = s.appendLeft(moduleScopeEnd, ";export ");
167
- s = s.remove(node.start, node.declaration.start);
168
- s = moveVariableDeclarationToModuleScope(s, node.declaration, moduleScopeEnd);
167
+ s.appendLeft(moduleScopeEnd, "export ");
168
+ s.remove(node.start, node.declaration.start);
169
+ moveVariableDeclarationToModuleScope(s, node.declaration, moduleScopeEnd);
170
+ } else if (node.declaration?.type === "ClassDeclaration") {
171
+ s.appendLeft(moduleScopeEnd, `export let ${node.declaration.id.name};\n`);
172
+ const declarationStart = getClassDeclarationStart(node.declaration);
173
+ s.remove(node.start, declarationStart);
174
+ s.appendRight(declarationStart, `${node.declaration.id.name} = `);
169
175
  }
170
- } else if (node.type === "VariableDeclaration") if (node.kind.endsWith("using")) s = moveVariableDeclarationWithUsingToModuleScope(s, node, moduleScopeEnd);
171
- else s = moveVariableDeclarationToModuleScope(s, node, moduleScopeEnd);
176
+ } else if (node.type === "VariableDeclaration") if (node.kind.endsWith("using")) moveVariableDeclarationWithUsingToModuleScope(s, node, moduleScopeEnd, variablePrefix);
177
+ else moveVariableDeclarationToModuleScope(s, node, moduleScopeEnd);
172
178
  else visitScope(node, (n) => {
173
- if (n.type === "VariableDeclaration" && n.kind === "var") s = moveVariableDeclarationToModuleScope(s, n, moduleScopeEnd);
179
+ if (n.type === "VariableDeclaration" && n.kind === "var") moveVariableDeclarationToModuleScope(s, n, moduleScopeEnd);
174
180
  return false;
175
181
  });
176
- if (node.type === "ExportDefaultDeclaration" && !isDeclaration(node.declaration.type)) {
177
- s = s.appendLeft(moduleScopeEnd, "let __tla_default;\nexport { __tla_default as default };\n");
178
- s = s.remove(node.start, node.declaration.start);
179
- s = s.appendRight(node.declaration.start, ";__tla_default = (");
180
- s = s.appendLeft(node.declaration.end, ");");
182
+ if (node.type === "ExportDefaultDeclaration" && !isFunctionDeclaration(node.declaration.type)) {
183
+ const variableName = node.declaration.type === "ClassDeclaration" ? node.declaration.id?.name ?? `${variablePrefix}_default` : `${variablePrefix}_default`;
184
+ s.appendLeft(moduleScopeEnd, `let ${variableName};\nexport { ${variableName} as default };\n`);
185
+ const declarationStart = node.declaration.type === "ClassDeclaration" ? getClassDeclarationStart(node.declaration) : node.declaration.start;
186
+ s.remove(node.start, declarationStart);
187
+ s.appendRight(declarationStart, `${variableName} = (`);
188
+ s.appendLeft(node.declaration.end, ");");
181
189
  }
182
- if (isDeclaration(node.type) || node.type === "ImportDeclaration" || node.type === "ExportDefaultDeclaration" && isDeclaration(node.declaration.type) || node.type === "ExportNamedDeclaration" && isDeclaration(node.declaration?.type) || node.type === "ExportNamedDeclaration" && node.declaration == null || node.type === "ExportAllDeclaration") if (node.start > moduleScopeEnd) {
183
- s = s.appendRight(node.start, ";\n");
184
- s = s.move(node.start, node.end, moduleScopeEnd);
190
+ if (isFunctionDeclaration(node.type) || node.type === "ImportDeclaration" || node.type === "ExportDefaultDeclaration" && isFunctionDeclaration(node.declaration.type) || node.type === "ExportNamedDeclaration" && isFunctionDeclaration(node.declaration?.type) || node.type === "ExportNamedDeclaration" && node.declaration == null || node.type === "ExportAllDeclaration") if (node.start > moduleScopeEnd) {
191
+ s.appendLeft(node.end, "\n");
192
+ s.move(node.start, node.end, moduleScopeEnd);
193
+ s.appendLeft(node.start, ";");
185
194
  } else moduleScopeEnd = node.end;
186
195
  }
187
- return [s, moduleScopeEnd];
196
+ return moduleScopeEnd;
197
+ }
198
+ function isFunctionDeclaration(type) {
199
+ return type === "FunctionDeclaration";
188
200
  }
189
- function isDeclaration(type) {
190
- return type === "ClassDeclaration" || type === "FunctionDeclaration";
201
+ function getClassDeclarationStart(node) {
202
+ return node.decorators[0]?.start ?? node.start;
191
203
  }
192
204
  function moveVariableDeclarationToModuleScope(s, node, declarationsEnd) {
193
205
  const kind = replaceConstWithLet(node.kind);
194
206
  const names = node.declarations.flatMap((decl) => getNames(decl.id)).join(", ");
195
- s = s.appendRight(node.declarations[0].start, ";(");
196
- s = s.appendLeft(node.declarations[node.declarations.length - 1].end, ")");
197
- s = s.appendLeft(declarationsEnd, `\n${kind} ${names};\n`);
198
- s = s.remove(node.start, node.declarations[0].start);
207
+ s.appendRight(node.declarations[0].start, ";(");
208
+ s.appendLeft(node.declarations[node.declarations.length - 1].end, ")");
209
+ s.appendLeft(declarationsEnd, `${kind} ${names};\n`);
210
+ s.remove(node.start, node.declarations[0].start);
199
211
  return s;
200
212
  }
201
- function moveVariableDeclarationWithUsingToModuleScope(s, node, declarationsEnd) {
213
+ function moveVariableDeclarationWithUsingToModuleScope(s, node, declarationsEnd, variablePrefix) {
202
214
  node.declarations.forEach((declaration) => {
203
215
  const id = declaration.id;
204
216
  if (id.type !== "Identifier") throw new Error("'using' declarations may not have binding patterns.");
205
217
  const name = id.name;
206
- s = s.appendRight(id.start, `__tla_using_`);
207
- s = s.appendLeft(node.end, `;\n${name} = __tla_using_${name};`);
208
- s = s.appendLeft(declarationsEnd, `\nlet ${name};\n`);
218
+ s.appendRight(id.start, `${variablePrefix}_using_`);
219
+ s.appendLeft(node.end, `\n${name} = ${variablePrefix}_using_${name};`);
220
+ s.appendLeft(declarationsEnd, `let ${name};\n`);
209
221
  });
210
222
  return s;
211
223
  }
@@ -260,8 +272,8 @@ function concurrentTopLevelAwait(options = {}) {
260
272
  return declaration;
261
273
  }))).filter(Boolean);
262
274
  if (!(asyncImports.length > 0 || hasAwait)) return;
263
- let s = new MagicString(code);
264
- s = transform(s, ast, asyncImports, hasAwait);
275
+ const s = new MagicString(code);
276
+ transform(s, ast, asyncImports, hasAwait, options.generatedVariablePrefix ?? "__tla");
265
277
  return {
266
278
  code: s.toString(),
267
279
  map: options.sourceMap !== false ? s.generateMap({ hires: true }) : null
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rollup-plugin-concurrent-top-level-await",
3
- "version": "0.0.9",
3
+ "version": "0.2.0",
4
4
  "description": "Rollup (and Vite) plugin enabling concurrent execution of modules that contain top level await.",
5
5
  "keywords": [
6
6
  "rollup-plugin",