@touk/federated-types 1.3.0-beta.6 → 1.3.0-beta.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 +14 -0
- package/cli.js +77 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -82,6 +82,20 @@ scripts: {
|
|
|
82
82
|
}
|
|
83
83
|
```
|
|
84
84
|
|
|
85
|
+
## Pruning unreachable modules
|
|
86
|
+
|
|
87
|
+
By default the generated typings contain every module of the TypeScript program built from your exposes, including modules that are only used by their implementation. Pass `--prune` to keep only the modules reachable from the exposed modules through their types (`import`/`export ... from`, `import("...")`, `import "..."`, `require("...")`):
|
|
88
|
+
|
|
89
|
+
```javascript
|
|
90
|
+
//package.json
|
|
91
|
+
|
|
92
|
+
scripts: {
|
|
93
|
+
"make-types": "make-federated-types --prune"
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Modules that augment the global scope (`declare global`) or other modules are always kept. With `--prune`, non-exposed modules can no longer be imported by their deep name (e.g. `myApp/utils/internal`) unless they are reachable from an exposed module.
|
|
98
|
+
|
|
85
99
|
## Inline configuration (without config file)
|
|
86
100
|
|
|
87
101
|
If you prefer not to maintain a separate `federation.config.json` file, you can provide the configuration directly via CLI arguments using `--name` and `--exposes`:
|
package/cli.js
CHANGED
|
@@ -68,6 +68,7 @@ const configPath = configPathArg ? path.resolve(configPathArg) : null;
|
|
|
68
68
|
const nameArg = getArg('--name');
|
|
69
69
|
const exposesArgs = getAllArgs('--exposes');
|
|
70
70
|
const verboseArg = hasArg('--verbose') || process.env.npm_config_loglevel === 'verbose';
|
|
71
|
+
const pruneArg = hasArg('--prune');
|
|
71
72
|
|
|
72
73
|
const log = (...args) => console.log(...args);
|
|
73
74
|
const logDebug = (...args) => verboseArg && console.debug(chalk.gray.dim(...args));
|
|
@@ -155,6 +156,73 @@ function getModuleDeclareName(exposeName) {
|
|
|
155
156
|
return path.join(federationConfig.name, exposeName).replace(/[\\/]/g, '/');
|
|
156
157
|
}
|
|
157
158
|
|
|
159
|
+
// Matches the module specifier of `from "x"`, `import("x")`, `import "x"` and `require("x")`.
|
|
160
|
+
const moduleReferenceRegex = /\b(?:from|import|require)\s*\(?\s*(["'])([^"'\n]+)\1/g;
|
|
161
|
+
// Global or module augmentations take effect without being imported, so such modules are always kept.
|
|
162
|
+
// Inside a `declare module` block tsc emits them without the `declare` keyword.
|
|
163
|
+
const augmentationRegex = /^\s+(?:declare\s+)?(?:global\s*\{|module\s+["'])/m;
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Drops the top-level `declare module` blocks that cannot be reached from the exposed modules
|
|
167
|
+
* through module references. Text outside the blocks is kept as is.
|
|
168
|
+
*/
|
|
169
|
+
function pruneUnreachableModules(typing, isExposedModule) {
|
|
170
|
+
const blockRegex = /^declare module "([^"]+)" \{\n[\s\S]*?^\}\n?/gm;
|
|
171
|
+
const segments = [];
|
|
172
|
+
const modules = new Map();
|
|
173
|
+
let lastIndex = 0;
|
|
174
|
+
let match;
|
|
175
|
+
|
|
176
|
+
while ((match = blockRegex.exec(typing)) !== null) {
|
|
177
|
+
segments.push(typing.slice(lastIndex, match.index));
|
|
178
|
+
const block = { name: match[1], text: match[0] };
|
|
179
|
+
segments.push(block);
|
|
180
|
+
modules.set(block.name, block);
|
|
181
|
+
lastIndex = blockRegex.lastIndex;
|
|
182
|
+
}
|
|
183
|
+
segments.push(typing.slice(lastIndex));
|
|
184
|
+
|
|
185
|
+
const roots = Array.from(modules.values()).filter(
|
|
186
|
+
(block) => isExposedModule(block.name) || augmentationRegex.test(block.text)
|
|
187
|
+
);
|
|
188
|
+
|
|
189
|
+
if (!roots.some((block) => isExposedModule(block.name))) {
|
|
190
|
+
console.warn(
|
|
191
|
+
chalk.yellow('WARNING:'),
|
|
192
|
+
'No exposed module found in the emitted typings, skipping --prune'
|
|
193
|
+
);
|
|
194
|
+
return typing;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const reachable = new Set();
|
|
198
|
+
const queue = [];
|
|
199
|
+
const visit = (text) => {
|
|
200
|
+
for (const [, , name] of text.matchAll(moduleReferenceRegex)) {
|
|
201
|
+
const block = modules.get(name);
|
|
202
|
+
if (block && !reachable.has(block)) {
|
|
203
|
+
reachable.add(block);
|
|
204
|
+
queue.push(block);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
segments.filter((segment) => typeof segment === 'string').forEach(visit);
|
|
210
|
+
roots.forEach((block) => {
|
|
211
|
+
reachable.add(block);
|
|
212
|
+
queue.push(block);
|
|
213
|
+
});
|
|
214
|
+
while (queue.length > 0) {
|
|
215
|
+
visit(queue.pop().text);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
logDebug(`--prune: kept ${reachable.size} of ${modules.size} modules`);
|
|
219
|
+
|
|
220
|
+
return segments
|
|
221
|
+
.filter((segment) => typeof segment === 'string' || reachable.has(segment))
|
|
222
|
+
.map((segment) => (typeof segment === 'string' ? segment : segment.text))
|
|
223
|
+
.join('');
|
|
224
|
+
}
|
|
225
|
+
|
|
158
226
|
try {
|
|
159
227
|
fs.mkdirSync(outputDir, { recursive: true });
|
|
160
228
|
const outFile = path.resolve(outputDir, `${federationConfig.name}.d.ts`);
|
|
@@ -188,6 +256,14 @@ try {
|
|
|
188
256
|
key,
|
|
189
257
|
federationConfig.exposes[key].replace(/\.(tsx?|jsx?)$/, ''),
|
|
190
258
|
]);
|
|
259
|
+
const findExposeKeys = (name) =>
|
|
260
|
+
exposedPaths
|
|
261
|
+
.filter(([, normalizedPath]) => normalizedPath.endsWith(name))
|
|
262
|
+
.map(([key]) => key);
|
|
263
|
+
|
|
264
|
+
if (pruneArg) {
|
|
265
|
+
typing = pruneUnreachableModules(typing, (name) => findExposeKeys(name).length > 0);
|
|
266
|
+
}
|
|
191
267
|
|
|
192
268
|
// module name emitted by tsc -> name it is declared under in the federated typings
|
|
193
269
|
const renames = new Map();
|
|
@@ -201,9 +277,7 @@ try {
|
|
|
201
277
|
}
|
|
202
278
|
|
|
203
279
|
// exposeName - relative name of exposed component (if not found - just take moduleName)
|
|
204
|
-
const [exposeName = name, ...aliases] =
|
|
205
|
-
.filter(([, normalizedPath]) => normalizedPath.endsWith(name))
|
|
206
|
-
.map(([key]) => key);
|
|
280
|
+
const [exposeName = name, ...aliases] = findExposeKeys(name);
|
|
207
281
|
|
|
208
282
|
const moduleDeclareName = getModuleDeclareName(exposeName);
|
|
209
283
|
renames.set(name, moduleDeclareName);
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@touk/federated-types",
|
|
3
3
|
"description": "share typings for your federated typescript packages around your mono-repo",
|
|
4
|
-
"version": "1.3.0-beta.
|
|
4
|
+
"version": "1.3.0-beta.7",
|
|
5
5
|
"author": "Julian Wielga <j@touk.pl>",
|
|
6
6
|
"bin": {
|
|
7
7
|
"make-federated-types": "./cli.js"
|