rollup-plugin-concurrent-top-level-await 0.0.5 → 0.0.7
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 +35 -1
- package/dist/index.d.mts +4 -5
- package/dist/index.mjs +93 -58
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -24,12 +24,46 @@ import concurrentTopLevelAwait from "rollup-plugin-concurrent-top-level-await";
|
|
|
24
24
|
export default {
|
|
25
25
|
plugins: [
|
|
26
26
|
concurrentTopLevelAwait({
|
|
27
|
-
include: "**/*.
|
|
27
|
+
include: "**/*.js",
|
|
28
28
|
}),
|
|
29
29
|
],
|
|
30
30
|
};
|
|
31
31
|
```
|
|
32
32
|
|
|
33
|
+
### Which modules to include
|
|
34
|
+
|
|
35
|
+
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
|
+
|
|
37
|
+
```mermaid
|
|
38
|
+
flowchart LR
|
|
39
|
+
app[app.js]
|
|
40
|
+
moduleA[moduleA.js]
|
|
41
|
+
moduleB[moduleB.js]
|
|
42
|
+
moduleC[moduleC.js]
|
|
43
|
+
tla1[tla1.js]
|
|
44
|
+
tla2[tla2.js]
|
|
45
|
+
tla3[tla3.js]
|
|
46
|
+
other[other.js]
|
|
47
|
+
|
|
48
|
+
app --> moduleA
|
|
49
|
+
moduleA --> moduleB
|
|
50
|
+
moduleA --> moduleC
|
|
51
|
+
moduleB --> tla1
|
|
52
|
+
moduleC --> tla2
|
|
53
|
+
moduleC --> tla3
|
|
54
|
+
app --> other
|
|
55
|
+
|
|
56
|
+
classDef tla fill:#ffe6e6,stroke:#ff0000,color:#660000
|
|
57
|
+
classDef ancestor fill:#fff4cc,stroke:#ffcc00,color:#663300
|
|
58
|
+
classDef unaffected fill:#e6ffe6,stroke:#00cc00,color:#006600
|
|
59
|
+
|
|
60
|
+
class tla1,tla2,tla3 tla
|
|
61
|
+
class moduleA,moduleB,moduleC ancestor
|
|
62
|
+
class app,other unaffected
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
If the red modules contain top level awaits, these and their yellow ancestors should be included in the plugin's `include` option.
|
|
66
|
+
|
|
33
67
|
## Known Limitations
|
|
34
68
|
|
|
35
69
|
### Execution Order
|
package/dist/index.d.mts
CHANGED
|
@@ -1,24 +1,23 @@
|
|
|
1
1
|
import { FilterPattern } from "@rollup/pluginutils";
|
|
2
|
+
import * as magic_string0 from "magic-string";
|
|
2
3
|
import * as rollup0 from "rollup";
|
|
3
4
|
|
|
4
5
|
//#region src/index.d.ts
|
|
5
6
|
declare function concurrentTopLevelAwait(options?: {
|
|
6
7
|
include?: FilterPattern;
|
|
7
8
|
exclude?: FilterPattern;
|
|
9
|
+
sourceMap?: boolean;
|
|
8
10
|
}): {
|
|
9
11
|
name: string;
|
|
10
12
|
apply: "build";
|
|
11
13
|
transform: {
|
|
12
14
|
handler(this: rollup0.TransformPluginContext, code: string, id: string): Promise<{
|
|
13
15
|
code: string;
|
|
14
|
-
|
|
15
|
-
async: true;
|
|
16
|
-
};
|
|
16
|
+
map: magic_string0.SourceMap | null;
|
|
17
17
|
} | undefined>;
|
|
18
18
|
};
|
|
19
19
|
resolveImportMeta(this: rollup0.PluginContext, property: string | null, {
|
|
20
|
-
moduleId
|
|
21
|
-
chunkId
|
|
20
|
+
moduleId
|
|
22
21
|
}: {
|
|
23
22
|
chunkId: string;
|
|
24
23
|
format: rollup0.InternalModuleFormat;
|
package/dist/index.mjs
CHANGED
|
@@ -31,69 +31,92 @@ function withResolvers() {
|
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
//#endregion
|
|
34
|
-
//#region src/
|
|
34
|
+
//#region src/AsyncModuleTracker.ts
|
|
35
35
|
var AwaitableCache = class {
|
|
36
36
|
#store = /* @__PURE__ */ new Map();
|
|
37
37
|
#getPromise(key) {
|
|
38
38
|
const entry = this.#store.get(key);
|
|
39
39
|
if (entry) return entry;
|
|
40
|
-
const { promise, resolve } = withResolvers();
|
|
41
|
-
this.#store.set(key, [
|
|
42
|
-
|
|
40
|
+
const { promise, resolve, reject } = withResolvers();
|
|
41
|
+
this.#store.set(key, [
|
|
42
|
+
promise,
|
|
43
|
+
resolve,
|
|
44
|
+
reject
|
|
45
|
+
]);
|
|
46
|
+
return [
|
|
47
|
+
promise,
|
|
48
|
+
resolve,
|
|
49
|
+
reject
|
|
50
|
+
];
|
|
43
51
|
}
|
|
44
|
-
|
|
52
|
+
resolve(key, value) {
|
|
45
53
|
const [_, resolve] = this.#getPromise(key);
|
|
46
54
|
resolve(value);
|
|
47
55
|
}
|
|
56
|
+
reject(key) {
|
|
57
|
+
const [_, __, reject] = this.#getPromise(key);
|
|
58
|
+
reject();
|
|
59
|
+
}
|
|
48
60
|
get(key) {
|
|
49
61
|
const [promise] = this.#getPromise(key);
|
|
50
62
|
return promise;
|
|
51
63
|
}
|
|
52
64
|
};
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
65
|
+
/**
|
|
66
|
+
* AsyncModuleTracker tracks whether modules must be treated as async due to
|
|
67
|
+
* containing top-level await or being an ancestor of an async module.
|
|
68
|
+
*/
|
|
69
|
+
var AsyncModuleTracker = class {
|
|
70
|
+
#entryCache = new AwaitableCache();
|
|
71
|
+
#subtreeCache = new AwaitableCache();
|
|
72
|
+
#resultCache = /* @__PURE__ */ new Map();
|
|
73
|
+
#seen = /* @__PURE__ */ new Set();
|
|
74
|
+
#resolved = /* @__PURE__ */ new Set();
|
|
75
|
+
#childrenSeen = /* @__PURE__ */ new Set();
|
|
76
|
+
#cycleResolver;
|
|
77
|
+
#resolveCycles;
|
|
78
|
+
constructor() {
|
|
79
|
+
this.#createCycleResolver();
|
|
60
80
|
}
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
this.#
|
|
64
|
-
this.#
|
|
65
|
-
this.#resultCache.set(key, true);
|
|
66
|
-
setTimeout(() => {
|
|
67
|
-
if (this.#unseen.size == 0) {
|
|
68
|
-
this.#unresolved.forEach((e) => this.#resultCache.set(e, false));
|
|
69
|
-
this.#all.forEach((e) => this.#resultCache.set(e, false));
|
|
70
|
-
this.#unresolved.clear();
|
|
71
|
-
}
|
|
72
|
-
}, 0);
|
|
81
|
+
#createCycleResolver() {
|
|
82
|
+
const { promise, resolve } = withResolvers();
|
|
83
|
+
this.#cycleResolver = promise;
|
|
84
|
+
this.#resolveCycles = () => resolve(false);
|
|
73
85
|
}
|
|
74
|
-
|
|
75
|
-
if (
|
|
76
|
-
this.#
|
|
77
|
-
this.#
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
children.forEach((children$1) => {
|
|
81
|
-
if (!this.#all.has(children$1)) {
|
|
82
|
-
this.#all.add(children$1);
|
|
83
|
-
this.#unresolved.add(key);
|
|
84
|
-
this.#unseen.add(children$1);
|
|
85
|
-
}
|
|
86
|
-
this.#resultCache.get(children$1).then((value) => {
|
|
87
|
-
if (value) this.setMarked(key, true);
|
|
86
|
+
#checkForCycles() {
|
|
87
|
+
if (this.#childrenSeen.size === this.#seen.size && this.#resolved.size === this.#seen.size) {
|
|
88
|
+
const resolveCycles = this.#resolveCycles;
|
|
89
|
+
this.#createCycleResolver();
|
|
90
|
+
setTimeout(() => {
|
|
91
|
+
resolveCycles();
|
|
88
92
|
});
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
#bindResultCache(key) {
|
|
96
|
+
if (!this.#resultCache.has(key)) this.#resultCache.set(key, this.#subtreeCache.get(key).then(() => false).catch(() => true));
|
|
97
|
+
}
|
|
98
|
+
isAsync(key) {
|
|
99
|
+
this.#bindResultCache(key);
|
|
100
|
+
return this.#resultCache.get(key);
|
|
101
|
+
}
|
|
102
|
+
setEntryAsync(key, value) {
|
|
103
|
+
this.#seen.add(key);
|
|
104
|
+
this.#resolved.add(key);
|
|
105
|
+
if (value) {
|
|
106
|
+
this.#entryCache.reject(key);
|
|
107
|
+
this.#entryCache.get(key).catch(() => {});
|
|
108
|
+
} else this.#entryCache.resolve(key, void 0);
|
|
109
|
+
this.#checkForCycles();
|
|
110
|
+
}
|
|
111
|
+
setDependencies(key, children) {
|
|
112
|
+
this.#seen.add(key);
|
|
113
|
+
this.#childrenSeen.add(key);
|
|
114
|
+
children.forEach((child) => {
|
|
115
|
+
this.#seen.add(child);
|
|
89
116
|
});
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
this.#all.forEach((e) => this.#resultCache.set(e, false));
|
|
94
|
-
this.#unresolved.clear();
|
|
95
|
-
}
|
|
96
|
-
}, 0);
|
|
117
|
+
Promise.race([this.#cycleResolver, Promise.all([this.#entryCache.get(key), ...children.map((child) => this.#subtreeCache.get(child))])]).then(() => this.#subtreeCache.resolve(key, void 0)).catch(() => this.#subtreeCache.reject(key));
|
|
118
|
+
this.#bindResultCache(key);
|
|
119
|
+
this.#checkForCycles();
|
|
97
120
|
}
|
|
98
121
|
};
|
|
99
122
|
|
|
@@ -136,10 +159,11 @@ function tansformAndMoveDeclarationsToModuleScope(s, ast, asyncImports) {
|
|
|
136
159
|
if (node.declaration?.type === "VariableDeclaration") {
|
|
137
160
|
s = s.appendLeft(moduleScopeEnd, ";export ");
|
|
138
161
|
s = s.remove(node.start, node.declaration.start);
|
|
139
|
-
s =
|
|
162
|
+
s = moveVariableDeclarationToModuleScope(s, node.declaration, moduleScopeEnd);
|
|
140
163
|
}
|
|
141
164
|
}
|
|
142
|
-
if (node.type === "VariableDeclaration") s =
|
|
165
|
+
if (node.type === "VariableDeclaration") if (node.kind.endsWith("using")) s = moveVariableDeclarationWithUsingToModuleScope(s, node, moduleScopeEnd);
|
|
166
|
+
else s = moveVariableDeclarationToModuleScope(s, node, moduleScopeEnd);
|
|
143
167
|
if (isDeclaration(node.type) || node.type === "ImportDeclaration" || node.type === "ExportNamedDeclaration" && isDeclaration(node.declaration?.type) || node.type === "ExportNamedDeclaration" && node.declaration == null || node.type === "ExportDefaultDeclaration" || node.type === "ExportAllDeclaration") if (node.start > moduleScopeEnd) {
|
|
144
168
|
s = s.appendRight(node.start, ";\n");
|
|
145
169
|
s = s.move(node.start, node.end, moduleScopeEnd);
|
|
@@ -150,15 +174,26 @@ function tansformAndMoveDeclarationsToModuleScope(s, ast, asyncImports) {
|
|
|
150
174
|
function isDeclaration(type) {
|
|
151
175
|
return type === "ClassDeclaration" || type === "FunctionDeclaration";
|
|
152
176
|
}
|
|
153
|
-
function
|
|
177
|
+
function moveVariableDeclarationToModuleScope(s, node, declarationsEnd) {
|
|
154
178
|
const kind = replaceConstWithLet(node.kind);
|
|
155
179
|
const names = node.declarations.flatMap((decl) => getNames(decl.id)).join(", ");
|
|
156
|
-
s = s.appendRight(node.declarations[0].start, "(");
|
|
180
|
+
s = s.appendRight(node.declarations[0].start, ";(");
|
|
157
181
|
s = s.appendLeft(node.declarations[node.declarations.length - 1].end, ")");
|
|
158
182
|
s = s.appendLeft(declarationsEnd, `\n${kind} ${names};\n`);
|
|
159
183
|
s = s.remove(node.start, node.declarations[0].start);
|
|
160
184
|
return s;
|
|
161
185
|
}
|
|
186
|
+
function moveVariableDeclarationWithUsingToModuleScope(s, node, declarationsEnd) {
|
|
187
|
+
node.declarations.forEach((declaration) => {
|
|
188
|
+
const id = declaration.id;
|
|
189
|
+
if (id.type !== "Identifier") throw new Error("'using' declarations may not have binding patterns.");
|
|
190
|
+
const name = id.name;
|
|
191
|
+
s = s.appendRight(id.start, `__tla_using_`);
|
|
192
|
+
s = s.appendLeft(node.end, `;\n${name} = __tla_using_${name};`);
|
|
193
|
+
s = s.appendLeft(declarationsEnd, `\nlet ${name};\n`);
|
|
194
|
+
});
|
|
195
|
+
return s;
|
|
196
|
+
}
|
|
162
197
|
function replaceConstWithLet(value) {
|
|
163
198
|
if (value === "const") return "let";
|
|
164
199
|
return value;
|
|
@@ -183,41 +218,41 @@ function getNames(pattern) {
|
|
|
183
218
|
//#region src/index.ts
|
|
184
219
|
function concurrentTopLevelAwait(options = {}) {
|
|
185
220
|
const filter = createFilter(options.include, options.exclude);
|
|
186
|
-
const
|
|
221
|
+
const asyncTracker = new AsyncModuleTracker();
|
|
187
222
|
return {
|
|
188
223
|
name: "rollup-plugin-concurrent-tla-plugin",
|
|
189
224
|
apply: "build",
|
|
190
225
|
transform: { async handler(code, id) {
|
|
191
226
|
if (!filter(id)) return;
|
|
192
|
-
const ast = this.parse(code
|
|
227
|
+
const ast = this.parse(code);
|
|
193
228
|
const importDeclarations = ast.body.filter((a) => a.type === "ImportDeclaration");
|
|
194
229
|
const hasAwait = hasTopLevelAwait(ast);
|
|
195
|
-
|
|
230
|
+
asyncTracker.setEntryAsync(id, hasAwait);
|
|
231
|
+
if (hasAwait) asyncTracker.setDependencies(id, []);
|
|
196
232
|
else {
|
|
197
233
|
const childrenIds = (await Promise.all(importDeclarations.map(async (declaration) => {
|
|
198
234
|
const importId = await this.resolve(declaration.source.value, id);
|
|
199
235
|
if (!importId || !filter(importId.id)) return null;
|
|
200
236
|
return importId.id;
|
|
201
237
|
}))).filter((a) => a != null);
|
|
202
|
-
|
|
238
|
+
asyncTracker.setDependencies(id, childrenIds);
|
|
203
239
|
}
|
|
204
240
|
const asyncImports = (await Promise.all(importDeclarations.map(async (declaration) => {
|
|
205
241
|
const importId = await this.resolve(declaration.source.value, id);
|
|
206
242
|
if (!importId || !filter(importId.id)) return null;
|
|
207
243
|
this.load(importId);
|
|
208
|
-
if (!await
|
|
244
|
+
if (!await asyncTracker.isAsync(importId.id)) return null;
|
|
209
245
|
return declaration;
|
|
210
246
|
}))).filter(Boolean);
|
|
211
|
-
|
|
212
|
-
if (!isAsyncModule) return;
|
|
247
|
+
if (!(asyncImports.length > 0 || hasAwait)) return;
|
|
213
248
|
let s = new MagicString(code);
|
|
214
249
|
s = transform(s, ast, asyncImports, hasAwait);
|
|
215
250
|
return {
|
|
216
251
|
code: s.toString(),
|
|
217
|
-
|
|
252
|
+
map: options.sourceMap !== false ? s.generateMap({ hires: true }) : null
|
|
218
253
|
};
|
|
219
254
|
} },
|
|
220
|
-
resolveImportMeta(property, { moduleId
|
|
255
|
+
resolveImportMeta(property, { moduleId }) {
|
|
221
256
|
if (property !== "useTla") return;
|
|
222
257
|
const moduleInfo = this.getModuleInfo(moduleId);
|
|
223
258
|
const importers = moduleInfo?.importers;
|
package/package.json
CHANGED