@touk/federated-types 1.2.1 → 1.3.0-beta.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 +42 -8
- package/cli.js +92 -30
- package/package.json +46 -45
package/README.md
CHANGED
|
@@ -14,7 +14,6 @@ npm install @touk/federated-types
|
|
|
14
14
|
|
|
15
15
|
You'll also need to place a `federation.config.json` in each package being federated. It will contain the remote name and exported members. These properties are used in Webpack's `ModuleFederationPlugin` configuration object. An example:
|
|
16
16
|
|
|
17
|
-
|
|
18
17
|
```json
|
|
19
18
|
//federation.config.json
|
|
20
19
|
|
|
@@ -28,11 +27,9 @@ You'll also need to place a `federation.config.json` in each package being feder
|
|
|
28
27
|
|
|
29
28
|
It's recommended that you spread these properties into your ModuleFederationPlugin configuration, like so:
|
|
30
29
|
|
|
31
|
-
|
|
32
30
|
```javascript
|
|
33
31
|
//webpack.config.js
|
|
34
32
|
|
|
35
|
-
const deps = require('../package.json').dependencies;
|
|
36
33
|
const federationConfig = require('./federation.config.json');
|
|
37
34
|
|
|
38
35
|
module.exports = {
|
|
@@ -42,9 +39,7 @@ module.exports = {
|
|
|
42
39
|
new ModuleFederationPlugin({
|
|
43
40
|
...federationConfig,
|
|
44
41
|
filename: "remoteEntry.js",
|
|
45
|
-
shared: {
|
|
46
|
-
...deps,
|
|
47
|
-
},
|
|
42
|
+
shared: { ... },
|
|
48
43
|
}),
|
|
49
44
|
],
|
|
50
45
|
|
|
@@ -55,7 +50,6 @@ module.exports = {
|
|
|
55
50
|
|
|
56
51
|
Then you can call `make-federated-types` from your `scripts` block in your package's `package.json` file:
|
|
57
52
|
|
|
58
|
-
|
|
59
53
|
```javascript
|
|
60
54
|
//package.json
|
|
61
55
|
|
|
@@ -68,7 +62,6 @@ This will write new package to the `node_modules/@types/__federated_types` in yo
|
|
|
68
62
|
|
|
69
63
|
If you would rather specify a directory in which to write the typing files, you can pass an `--outputDir` parameter to the command like so:
|
|
70
64
|
|
|
71
|
-
|
|
72
65
|
```javascript
|
|
73
66
|
//package.json
|
|
74
67
|
|
|
@@ -76,6 +69,7 @@ scripts: {
|
|
|
76
69
|
"make-types": "make-federated-types --outputDir ../../my_types/"
|
|
77
70
|
}
|
|
78
71
|
```
|
|
72
|
+
|
|
79
73
|
You can add an `--saveToNodeModules` parameter to save in both places.
|
|
80
74
|
|
|
81
75
|
If you would like to specify custom path to the config, you can pass `--config` parameter like so:
|
|
@@ -87,3 +81,43 @@ scripts: {
|
|
|
87
81
|
"make-types": "make-federated-types --config ./path/to/my/config.json --outputDir ../../my_types/"
|
|
88
82
|
}
|
|
89
83
|
```
|
|
84
|
+
|
|
85
|
+
## Inline configuration (without config file)
|
|
86
|
+
|
|
87
|
+
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`:
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
make-federated-types \
|
|
91
|
+
--name myApp \
|
|
92
|
+
--exposes App ./src/App.tsx \
|
|
93
|
+
--exposes Button ./src/components/Button.tsx \
|
|
94
|
+
--outputDir ./types
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### Inline config features:
|
|
98
|
+
|
|
99
|
+
- **Multiple exposures**: Use `--exposes` multiple times, each followed by a key and file path
|
|
100
|
+
- **Auto-prefix**: Keys without `./` prefix are automatically normalized (e.g., `App` becomes `./App`)
|
|
101
|
+
- **Shell-friendly**: Works with tab completion for file paths
|
|
102
|
+
- **Priority**: Inline config takes precedence over `--config` and auto-discovery
|
|
103
|
+
|
|
104
|
+
### Examples:
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
# Simple inline config
|
|
108
|
+
make-federated-types --name app2 --exposes Button ./src/Button.tsx
|
|
109
|
+
|
|
110
|
+
# Multiple exposures
|
|
111
|
+
make-federated-types \
|
|
112
|
+
--name app2 \
|
|
113
|
+
--exposes ./Button ./src/Button.tsx \
|
|
114
|
+
--exposes ./Header ./src/Header.tsx
|
|
115
|
+
|
|
116
|
+
# Nested keys (auto-prefixed with ./)
|
|
117
|
+
make-federated-types \
|
|
118
|
+
--name app2 \
|
|
119
|
+
--exposes components/Button ./src/components/Button.tsx \
|
|
120
|
+
--exposes utils/helpers ./src/utils/helpers.ts
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
**Note**: When using inline config, both `--name` and at least one `--exposes` are required.
|
package/cli.js
CHANGED
|
@@ -7,11 +7,16 @@ const ts = require('typescript');
|
|
|
7
7
|
|
|
8
8
|
const formatHost = {
|
|
9
9
|
getCurrentDirectory: ts.sys.getCurrentDirectory,
|
|
10
|
-
getNewLine: () => ts.sys.newLine
|
|
10
|
+
getNewLine: () => ts.sys.newLine,
|
|
11
11
|
};
|
|
12
12
|
|
|
13
13
|
function reportDiagnostic(diagnostic) {
|
|
14
|
-
|
|
14
|
+
console.log(
|
|
15
|
+
'TS Error',
|
|
16
|
+
diagnostic.code,
|
|
17
|
+
':',
|
|
18
|
+
ts.flattenDiagnosticMessageText(diagnostic.messageText, formatHost.getNewLine())
|
|
19
|
+
);
|
|
15
20
|
}
|
|
16
21
|
|
|
17
22
|
const [nodeModules] = findNodeModules({ cwd: process.argv[1], relative: false });
|
|
@@ -24,23 +29,43 @@ const getArg = (argName) => {
|
|
|
24
29
|
const argIndex = process.argv.indexOf(argName);
|
|
25
30
|
return argIndex !== -1 ? process.argv[argIndex + 1] : null;
|
|
26
31
|
};
|
|
32
|
+
const getAllArgs = (argName) => {
|
|
33
|
+
const results = [];
|
|
34
|
+
let i = 2; // process.argv starts with node and script path
|
|
35
|
+
|
|
36
|
+
while (i < process.argv.length) {
|
|
37
|
+
if (process.argv[i] === argName) {
|
|
38
|
+
// Next two arguments are key and value
|
|
39
|
+
if (i + 2 < process.argv.length) {
|
|
40
|
+
results.push([process.argv[i + 1], process.argv[i + 2]]);
|
|
41
|
+
i += 3; // skip argName, key, value
|
|
42
|
+
} else {
|
|
43
|
+
console.error(`ERROR: --exposes requires two arguments (key path)`);
|
|
44
|
+
process.exit(1);
|
|
45
|
+
}
|
|
46
|
+
} else {
|
|
47
|
+
i++;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return results;
|
|
51
|
+
};
|
|
27
52
|
|
|
28
53
|
const nodeModulesOutputDir = path.resolve(nodeModules, '@types/__federated_types/');
|
|
29
54
|
const saveToNodeMoulesArg = hasArg('--saveToNodeModules');
|
|
30
55
|
const outDirArg = getArg('--outputDir');
|
|
31
56
|
|
|
32
|
-
const outputDir = outDirArg
|
|
33
|
-
? path.resolve('./', outDirArg)
|
|
34
|
-
: nodeModulesOutputDir;
|
|
57
|
+
const outputDir = outDirArg ? path.resolve('./', outDirArg) : nodeModulesOutputDir;
|
|
35
58
|
|
|
36
|
-
const outputDirs =
|
|
37
|
-
|
|
38
|
-
|
|
59
|
+
const outputDirs =
|
|
60
|
+
outputDir !== nodeModulesOutputDir && saveToNodeMoulesArg
|
|
61
|
+
? [nodeModulesOutputDir, outputDir]
|
|
62
|
+
: [outputDir];
|
|
39
63
|
|
|
40
64
|
const configPathArg = getArg('--config');
|
|
41
|
-
const configPath = configPathArg
|
|
42
|
-
|
|
43
|
-
|
|
65
|
+
const configPath = configPathArg ? path.resolve(configPathArg) : null;
|
|
66
|
+
|
|
67
|
+
const nameArg = getArg('--name');
|
|
68
|
+
const exposesArgs = getAllArgs('--exposes');
|
|
44
69
|
|
|
45
70
|
const findFederationConfig = (base) => {
|
|
46
71
|
let files = fs.readdirSync(base);
|
|
@@ -56,27 +81,57 @@ const findFederationConfig = (base) => {
|
|
|
56
81
|
}
|
|
57
82
|
}
|
|
58
83
|
|
|
59
|
-
for(
|
|
84
|
+
for (let i = 0; i < queue.length; i++) {
|
|
60
85
|
return findFederationConfig(queue[i]);
|
|
61
86
|
}
|
|
62
87
|
};
|
|
63
88
|
|
|
64
|
-
|
|
65
|
-
console.error(`ERROR: Unable to find a provided config: ${configPath}`);
|
|
66
|
-
process.exit(1);
|
|
67
|
-
}
|
|
89
|
+
let federationConfig;
|
|
68
90
|
|
|
69
|
-
|
|
91
|
+
// Priority: inline config > --config > auto-find
|
|
92
|
+
if (nameArg || exposesArgs.length > 0) {
|
|
93
|
+
// Inline config mode
|
|
94
|
+
if (!nameArg || exposesArgs.length === 0) {
|
|
95
|
+
console.error('ERROR: Both --name and --exposes are required for inline config');
|
|
96
|
+
process.exit(1);
|
|
97
|
+
}
|
|
70
98
|
|
|
99
|
+
if (configPath) {
|
|
100
|
+
console.warn('WARNING: Both inline config and --config provided. Using inline config.');
|
|
101
|
+
}
|
|
71
102
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
103
|
+
const exposes = {};
|
|
104
|
+
|
|
105
|
+
exposesArgs.forEach(([key, value]) => {
|
|
106
|
+
// Normalize key - add ./ if missing
|
|
107
|
+
const normalizedKey = key.startsWith('./') ? key : `./${key}`;
|
|
108
|
+
exposes[normalizedKey] = value;
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
federationConfig = {
|
|
112
|
+
name: nameArg,
|
|
113
|
+
exposes: exposes,
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
console.log('Using inline config:', JSON.stringify(federationConfig, null, 2));
|
|
117
|
+
} else {
|
|
118
|
+
// File-based config (current behavior)
|
|
119
|
+
if (configPath && !fs.existsSync(configPath)) {
|
|
120
|
+
console.error(`ERROR: Unable to find a provided config: ${configPath}`);
|
|
121
|
+
process.exit(1);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const federationConfigPath = configPath || findFederationConfig('./');
|
|
76
125
|
|
|
77
|
-
|
|
126
|
+
if (federationConfigPath === undefined) {
|
|
127
|
+
console.error(`ERROR: Unable to find a federation.config.json file in this package`);
|
|
128
|
+
process.exit(1);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
console.log(`Using config file: ${federationConfigPath}`);
|
|
132
|
+
federationConfig = require(federationConfigPath);
|
|
133
|
+
}
|
|
78
134
|
|
|
79
|
-
const federationConfig = require(federationConfigPath);
|
|
80
135
|
const compileFiles = Object.values(federationConfig.exposes);
|
|
81
136
|
const compileKeys = Object.keys(federationConfig.exposes);
|
|
82
137
|
|
|
@@ -97,16 +152,16 @@ try {
|
|
|
97
152
|
declaration: true,
|
|
98
153
|
emitDeclarationOnly: true,
|
|
99
154
|
skipLibCheck: true,
|
|
100
|
-
jsx:
|
|
155
|
+
jsx: ts.JsxEmit.React,
|
|
101
156
|
esModuleInterop: true,
|
|
102
157
|
});
|
|
103
158
|
|
|
104
159
|
const { emitSkipped, diagnostics } = program.emit();
|
|
105
160
|
|
|
106
|
-
diagnostics.forEach(reportDiagnostic)
|
|
161
|
+
diagnostics.forEach(reportDiagnostic);
|
|
107
162
|
|
|
108
163
|
if (emitSkipped) {
|
|
109
|
-
process.exit(0)
|
|
164
|
+
process.exit(0);
|
|
110
165
|
}
|
|
111
166
|
|
|
112
167
|
let typing = fs.readFileSync(outFile, { encoding: 'utf8', flag: 'r' });
|
|
@@ -114,19 +169,23 @@ try {
|
|
|
114
169
|
const moduleRegex = RegExp(/declare module "(.*)"/, 'g');
|
|
115
170
|
const moduleNames = [];
|
|
116
171
|
|
|
172
|
+
let execResults;
|
|
117
173
|
while ((execResults = moduleRegex.exec(typing)) !== null) {
|
|
118
174
|
moduleNames.push(execResults[1]);
|
|
119
175
|
}
|
|
120
176
|
|
|
121
177
|
moduleNames.forEach((name) => {
|
|
122
178
|
// exposeName - relative name of exposed component (if not found - just take moduleName)
|
|
123
|
-
const [exposeName = name, ...aliases] = compileKeys.filter(key =>
|
|
179
|
+
const [exposeName = name, ...aliases] = compileKeys.filter((key) => {
|
|
180
|
+
const normalizedPath = federationConfig.exposes[key].replace(/\.(tsx?|jsx?)$/, '');
|
|
181
|
+
return normalizedPath.endsWith(name);
|
|
182
|
+
});
|
|
124
183
|
const regex = RegExp(`"${name}"`, 'g');
|
|
125
184
|
|
|
126
185
|
const moduleDeclareName = getModuleDeclareName(exposeName);
|
|
127
186
|
|
|
128
187
|
// language=TypeScript
|
|
129
|
-
const createAliasModule = name => `
|
|
188
|
+
const createAliasModule = (name) => `
|
|
130
189
|
declare module "${getModuleDeclareName(name)}" {
|
|
131
190
|
export * from "${moduleDeclareName}"
|
|
132
191
|
}
|
|
@@ -138,7 +197,7 @@ try {
|
|
|
138
197
|
].join('\n');
|
|
139
198
|
});
|
|
140
199
|
|
|
141
|
-
outputDirs.forEach(_outputDir => {
|
|
200
|
+
outputDirs.forEach((_outputDir) => {
|
|
142
201
|
const _outFile = path.resolve(_outputDir, `${federationConfig.name}.d.ts`);
|
|
143
202
|
|
|
144
203
|
console.log('writing typing file:', _outFile);
|
|
@@ -152,7 +211,10 @@ try {
|
|
|
152
211
|
|
|
153
212
|
if (!fs.existsSync(packageJsonPath)) {
|
|
154
213
|
console.debug('writing package.json:', packageJsonPath);
|
|
155
|
-
fs.copyFileSync(
|
|
214
|
+
fs.copyFileSync(
|
|
215
|
+
path.resolve(__dirname, 'typings.package.tmpl.json'),
|
|
216
|
+
packageJsonPath
|
|
217
|
+
);
|
|
156
218
|
} else {
|
|
157
219
|
console.debug(packageJsonPath, 'already exists');
|
|
158
220
|
}
|
package/package.json
CHANGED
|
@@ -1,47 +1,48 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
"
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
"
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
"
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
"
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
"
|
|
46
|
-
|
|
2
|
+
"name": "@touk/federated-types",
|
|
3
|
+
"description": "share typings for your federated typescript packages around your mono-repo",
|
|
4
|
+
"version": "1.3.0-beta.1",
|
|
5
|
+
"author": "Julian Wielga <j@touk.pl>",
|
|
6
|
+
"bin": {
|
|
7
|
+
"make-federated-types": "./cli.js"
|
|
8
|
+
},
|
|
9
|
+
"contributors": [
|
|
10
|
+
"Jonathan Greene <jgreene@pixability.com>"
|
|
11
|
+
],
|
|
12
|
+
"dependencies": {
|
|
13
|
+
"find-node-modules": "2.1.3"
|
|
14
|
+
},
|
|
15
|
+
"devDependencies": {
|
|
16
|
+
"@commitlint/cli": "^20.4.2",
|
|
17
|
+
"@commitlint/config-conventional": "^20.4.2",
|
|
18
|
+
"@semantic-release/changelog": "^6.0.3",
|
|
19
|
+
"@semantic-release/git": "10.0.1",
|
|
20
|
+
"husky": "^9.1.7",
|
|
21
|
+
"prettier": "^3.8.1",
|
|
22
|
+
"semantic-release": "^25.0.3",
|
|
23
|
+
"typescript": "^5.9.3"
|
|
24
|
+
},
|
|
25
|
+
"files": [
|
|
26
|
+
"cli.js",
|
|
27
|
+
"typings.package.tmpl.json"
|
|
28
|
+
],
|
|
29
|
+
"keywords": [
|
|
30
|
+
"module-federation",
|
|
31
|
+
"mono-repo",
|
|
32
|
+
"typescript",
|
|
33
|
+
"typings"
|
|
34
|
+
],
|
|
35
|
+
"license": "MIT",
|
|
36
|
+
"main": "cli.js",
|
|
37
|
+
"peerDependencies": {
|
|
38
|
+
"typescript": ">4.0.0"
|
|
39
|
+
},
|
|
40
|
+
"repository": "touk/federated-types.git",
|
|
41
|
+
"scripts": {
|
|
42
|
+
"semantic-release": "semantic-release",
|
|
43
|
+
"test": "npm run make-types -workspaces --if-present"
|
|
44
|
+
},
|
|
45
|
+
"workspaces": [
|
|
46
|
+
"./packages/*"
|
|
47
|
+
]
|
|
47
48
|
}
|