webpack-federation-types-plugin 1.2.2 → 1.2.4
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/FederationTypesPlugin.js +196 -196
- package/package.json +1 -1
package/FederationTypesPlugin.js
CHANGED
|
@@ -1,196 +1,196 @@
|
|
|
1
|
-
const validate = require("schema-utils").validate
|
|
2
|
-
const ts = require("typescript")
|
|
3
|
-
const axios = require("axios")
|
|
4
|
-
const fs = require("fs-extra")
|
|
5
|
-
const path = require("path")
|
|
6
|
-
const ms = require("ms")
|
|
7
|
-
|
|
8
|
-
let isFirstCompilation = true
|
|
9
|
-
const PLUGIN_NAME = "FederationTypesPlugin"
|
|
10
|
-
const MF_TYPES_DIR = "federation-types"
|
|
11
|
-
const DECLARATION_FILE_EXT = ".d.ts"
|
|
12
|
-
|
|
13
|
-
const tscOptions = {
|
|
14
|
-
allowJs: true,
|
|
15
|
-
declaration: true,
|
|
16
|
-
emitDeclarationOnly: true,
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
const optionsSchema = {
|
|
20
|
-
type: "object",
|
|
21
|
-
properties: {
|
|
22
|
-
excludeRemotes: {
|
|
23
|
-
type: "array",
|
|
24
|
-
},
|
|
25
|
-
exposeTypes: {
|
|
26
|
-
type: "boolean",
|
|
27
|
-
},
|
|
28
|
-
importTypes: {
|
|
29
|
-
type: "boolean",
|
|
30
|
-
},
|
|
31
|
-
getTypesInterval: {
|
|
32
|
-
type: "string",
|
|
33
|
-
},
|
|
34
|
-
},
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
const getAssetData = (content) => {
|
|
38
|
-
const fileBuffer = Buffer.from(content, "utf-8")
|
|
39
|
-
const fileData = {
|
|
40
|
-
source: () => fileBuffer,
|
|
41
|
-
size: () => fileBuffer.length,
|
|
42
|
-
}
|
|
43
|
-
return fileData
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
class FederationTypesPlugin {
|
|
47
|
-
constructor(options = {}) {
|
|
48
|
-
validate(optionsSchema, options)
|
|
49
|
-
this._options = options
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* @param {import("webpack").Compiler} compiler
|
|
54
|
-
*/
|
|
55
|
-
apply(compiler) {
|
|
56
|
-
let getTypesInterval
|
|
57
|
-
const federationPlugin =
|
|
58
|
-
compiler.options.plugins && compiler.options.plugins.find((plugin) => plugin.constructor.name === "ModuleFederationPlugin")
|
|
59
|
-
if (!federationPlugin) throw new Error("No ModuleFederationPlugin found.")
|
|
60
|
-
const logger = compiler.getInfrastructureLogger(PLUGIN_NAME)
|
|
61
|
-
|
|
62
|
-
const exposes = Object.fromEntries(Object.entries(federationPlugin._options.exposes || {}).map(([moduleName, currentPath]) => {
|
|
63
|
-
let isIndexFile = false
|
|
64
|
-
const absPath = path.resolve(process.cwd(), currentPath)
|
|
65
|
-
const hasExtension = !!path.extname(currentPath)
|
|
66
|
-
const extension = hasExtension ? "" : fs.existsSync(absPath + ".jsx") ? ".jsx" : fs.existsSync(absPath + ".js") ? ".js" : ""
|
|
67
|
-
if (!hasExtension && extension === "") {
|
|
68
|
-
if ( fs.existsSync(path.resolve(absPath, "index.js"))) {
|
|
69
|
-
isIndexFile = true
|
|
70
|
-
}
|
|
71
|
-
else logger.error(`Couldn't find ${currentPath}`)
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
return [moduleName.replace("./", ""), currentPath + (isIndexFile ? "/index.js" : "") + extension]
|
|
75
|
-
}
|
|
76
|
-
))
|
|
77
|
-
|
|
78
|
-
const modulesPathsMap = Object.fromEntries(
|
|
79
|
-
Object.entries(exposes).map(([name, currentPath]) => {
|
|
80
|
-
return [name, path.join(process.cwd(), currentPath).replace(/\\/g, "/")]
|
|
81
|
-
})
|
|
82
|
-
)
|
|
83
|
-
|
|
84
|
-
const declarationFileToExposeNameMap = Object.entries(modulesPathsMap).reduce(
|
|
85
|
-
(result, [key, val]) => Object.assign(result, {[val.replace(/\.jsx?$/, DECLARATION_FILE_EXT)]: key}),
|
|
86
|
-
{}
|
|
87
|
-
)
|
|
88
|
-
|
|
89
|
-
const modulesPaths = Object.values(modulesPathsMap)
|
|
90
|
-
|
|
91
|
-
const createTypesDefinitions = (compilation) => {
|
|
92
|
-
const host = ts.createCompilerHost(tscOptions)
|
|
93
|
-
|
|
94
|
-
const createdFiles = {}
|
|
95
|
-
host.writeFile = (fileName, fileContent) => {
|
|
96
|
-
const isExposedModules = modulesPaths.some((current) => current.startsWith(fileName.replace(/\.d\.ts/, "")))
|
|
97
|
-
if (isExposedModules) {
|
|
98
|
-
createdFiles[fileName] = fileContent
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
const program = ts.createProgram(modulesPaths, tscOptions, host)
|
|
103
|
-
program.emit()
|
|
104
|
-
|
|
105
|
-
// create definitions files
|
|
106
|
-
modulesPaths.forEach((modulePath) => {
|
|
107
|
-
const typeDefFilePath = modulePath.replace(/\.jsx?$/, DECLARATION_FILE_EXT)
|
|
108
|
-
const typeDefFileContent = createdFiles[typeDefFilePath]
|
|
109
|
-
if (typeDefFileContent) {
|
|
110
|
-
compilation.emitAsset(
|
|
111
|
-
path.join(MF_TYPES_DIR, declarationFileToExposeNameMap[typeDefFilePath] + DECLARATION_FILE_EXT),
|
|
112
|
-
getAssetData(typeDefFileContent)
|
|
113
|
-
)
|
|
114
|
-
} else {
|
|
115
|
-
logger.warn(`failed to create ${typeDefFilePath}`)
|
|
116
|
-
}
|
|
117
|
-
})
|
|
118
|
-
|
|
119
|
-
// create index.json
|
|
120
|
-
const modulesNames = Object.keys(exposes).map((moduleName) => moduleName + DECLARATION_FILE_EXT)
|
|
121
|
-
compilation.emitAsset(path.join(MF_TYPES_DIR, "index.json"), getAssetData(JSON.stringify(modulesNames)))
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
const getRemoteDeclareDirPath = (remoteName) => path.resolve(process.cwd(), "node_modules", "@types", remoteName)
|
|
125
|
-
|
|
126
|
-
const getTypesDefinitions = async () => {
|
|
127
|
-
const remotes = federationPlugin._options.remotes
|
|
128
|
-
if (!remotes) return
|
|
129
|
-
|
|
130
|
-
const excludeRemotes = this._options.excludeRemotes || []
|
|
131
|
-
const wantedRemotes = Object.entries(remotes).filter((remote) => !excludeRemotes.find((c) => c === remote[0]))
|
|
132
|
-
|
|
133
|
-
for (const [remoteName, remoteEntryUri] of wantedRemotes) {
|
|
134
|
-
const {origin, pathname} = new URL(remoteEntryUri.split("@")[1])
|
|
135
|
-
const remotePublicUrl = origin + pathname.slice(0, pathname.lastIndexOf("/") + 1)
|
|
136
|
-
const remoteDeclareDirPath = getRemoteDeclareDirPath(remoteName)
|
|
137
|
-
const federationTypesUrl = remotePublicUrl + MF_TYPES_DIR + "/"
|
|
138
|
-
|
|
139
|
-
axios
|
|
140
|
-
// get types index from the current remote
|
|
141
|
-
.get(federationTypesUrl + "index.json")
|
|
142
|
-
.catch((error) => {
|
|
143
|
-
if (error.response?.status === 404) logger.warn(`WARNING: The remote ${remoteName} has no types`)
|
|
144
|
-
else logger.
|
|
145
|
-
})
|
|
146
|
-
.then((response) => response?.data)
|
|
147
|
-
.then((modulesNames) => {
|
|
148
|
-
if (!modulesNames) return
|
|
149
|
-
// for each remote module get his types
|
|
150
|
-
modulesNames.forEach((moduleName) => {
|
|
151
|
-
const moduleDeclarationFileUrl = federationTypesUrl + moduleName
|
|
152
|
-
axios
|
|
153
|
-
.get(moduleDeclarationFileUrl)
|
|
154
|
-
.catch((error) => {
|
|
155
|
-
logger.
|
|
156
|
-
})
|
|
157
|
-
.then((declarationFileResponse) => {
|
|
158
|
-
if (!declarationFileResponse) return
|
|
159
|
-
const declarationFileContent = declarationFileResponse.data
|
|
160
|
-
const decFilePath = path.join(remoteDeclareDirPath, moduleName)
|
|
161
|
-
fs.promises
|
|
162
|
-
.mkdir(path.dirname(decFilePath), {recursive: true})
|
|
163
|
-
.catch((error) => logger.error(`Failed to write dir: ${decFilePath}`, error))
|
|
164
|
-
.then(() => fs.writeFile(decFilePath, declarationFileContent, {recursive: true}))
|
|
165
|
-
.catch((error) => logger.error(`Failed to write declaration file: ${decFilePath}`, error))
|
|
166
|
-
})
|
|
167
|
-
.catch((error) => {
|
|
168
|
-
logger.
|
|
169
|
-
})
|
|
170
|
-
})
|
|
171
|
-
})
|
|
172
|
-
.catch((error) => {
|
|
173
|
-
logger.error("Failed to add declaration file", error.message)
|
|
174
|
-
})
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
compiler.hooks.compilation.tap(PLUGIN_NAME, (compilation) => {
|
|
179
|
-
const pluginOptions = this._options || {}
|
|
180
|
-
compilation.hooks.beforeCodeGeneration.tap(PLUGIN_NAME, () => {
|
|
181
|
-
if (!isFirstCompilation) return
|
|
182
|
-
isFirstCompilation = false
|
|
183
|
-
if (pluginOptions.exposeTypes !== false) createTypesDefinitions(compilation)
|
|
184
|
-
if (compilation.options.mode === "development" && pluginOptions.importTypes !== false) {
|
|
185
|
-
getTypesDefinitions()
|
|
186
|
-
if (pluginOptions.getTypesInterval) {
|
|
187
|
-
clearInterval(getTypesInterval)
|
|
188
|
-
getTypesInterval = setInterval(() => getTypesDefinitions(compilation), ms(pluginOptions.getTypesInterval))
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
})
|
|
192
|
-
})
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
module.exports = FederationTypesPlugin
|
|
1
|
+
const validate = require("schema-utils").validate
|
|
2
|
+
const ts = require("typescript")
|
|
3
|
+
const axios = require("axios")
|
|
4
|
+
const fs = require("fs-extra")
|
|
5
|
+
const path = require("path")
|
|
6
|
+
const ms = require("ms")
|
|
7
|
+
|
|
8
|
+
let isFirstCompilation = true
|
|
9
|
+
const PLUGIN_NAME = "FederationTypesPlugin"
|
|
10
|
+
const MF_TYPES_DIR = "federation-types"
|
|
11
|
+
const DECLARATION_FILE_EXT = ".d.ts"
|
|
12
|
+
|
|
13
|
+
const tscOptions = {
|
|
14
|
+
allowJs: true,
|
|
15
|
+
declaration: true,
|
|
16
|
+
emitDeclarationOnly: true,
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const optionsSchema = {
|
|
20
|
+
type: "object",
|
|
21
|
+
properties: {
|
|
22
|
+
excludeRemotes: {
|
|
23
|
+
type: "array",
|
|
24
|
+
},
|
|
25
|
+
exposeTypes: {
|
|
26
|
+
type: "boolean",
|
|
27
|
+
},
|
|
28
|
+
importTypes: {
|
|
29
|
+
type: "boolean",
|
|
30
|
+
},
|
|
31
|
+
getTypesInterval: {
|
|
32
|
+
type: "string",
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const getAssetData = (content) => {
|
|
38
|
+
const fileBuffer = Buffer.from(content, "utf-8")
|
|
39
|
+
const fileData = {
|
|
40
|
+
source: () => fileBuffer,
|
|
41
|
+
size: () => fileBuffer.length,
|
|
42
|
+
}
|
|
43
|
+
return fileData
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
class FederationTypesPlugin {
|
|
47
|
+
constructor(options = {}) {
|
|
48
|
+
validate(optionsSchema, options)
|
|
49
|
+
this._options = options
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* @param {import("webpack").Compiler} compiler
|
|
54
|
+
*/
|
|
55
|
+
apply(compiler) {
|
|
56
|
+
let getTypesInterval
|
|
57
|
+
const federationPlugin =
|
|
58
|
+
compiler.options.plugins && compiler.options.plugins.find((plugin) => plugin.constructor.name === "ModuleFederationPlugin")
|
|
59
|
+
if (!federationPlugin) throw new Error("No ModuleFederationPlugin found.")
|
|
60
|
+
const logger = compiler.getInfrastructureLogger(PLUGIN_NAME)
|
|
61
|
+
|
|
62
|
+
const exposes = Object.fromEntries(Object.entries(federationPlugin._options.exposes || {}).map(([moduleName, currentPath]) => {
|
|
63
|
+
let isIndexFile = false
|
|
64
|
+
const absPath = path.resolve(process.cwd(), currentPath)
|
|
65
|
+
const hasExtension = !!path.extname(currentPath)
|
|
66
|
+
const extension = hasExtension ? "" : fs.existsSync(absPath + ".jsx") ? ".jsx" : fs.existsSync(absPath + ".js") ? ".js" : ""
|
|
67
|
+
if (!hasExtension && extension === "") {
|
|
68
|
+
if ( fs.existsSync(path.resolve(absPath, "index.js"))) {
|
|
69
|
+
isIndexFile = true
|
|
70
|
+
}
|
|
71
|
+
else logger.error(`Couldn't find ${currentPath}`)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return [moduleName.replace("./", ""), currentPath + (isIndexFile ? "/index.js" : "") + extension]
|
|
75
|
+
}
|
|
76
|
+
))
|
|
77
|
+
|
|
78
|
+
const modulesPathsMap = Object.fromEntries(
|
|
79
|
+
Object.entries(exposes).map(([name, currentPath]) => {
|
|
80
|
+
return [name, path.join(process.cwd(), currentPath).replace(/\\/g, "/")]
|
|
81
|
+
})
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
const declarationFileToExposeNameMap = Object.entries(modulesPathsMap).reduce(
|
|
85
|
+
(result, [key, val]) => Object.assign(result, {[val.replace(/\.jsx?$/, DECLARATION_FILE_EXT)]: key}),
|
|
86
|
+
{}
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
const modulesPaths = Object.values(modulesPathsMap)
|
|
90
|
+
|
|
91
|
+
const createTypesDefinitions = (compilation) => {
|
|
92
|
+
const host = ts.createCompilerHost(tscOptions)
|
|
93
|
+
|
|
94
|
+
const createdFiles = {}
|
|
95
|
+
host.writeFile = (fileName, fileContent) => {
|
|
96
|
+
const isExposedModules = modulesPaths.some((current) => current.startsWith(fileName.replace(/\.d\.ts/, "")))
|
|
97
|
+
if (isExposedModules) {
|
|
98
|
+
createdFiles[fileName] = fileContent
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const program = ts.createProgram(modulesPaths, tscOptions, host)
|
|
103
|
+
program.emit()
|
|
104
|
+
|
|
105
|
+
// create definitions files
|
|
106
|
+
modulesPaths.forEach((modulePath) => {
|
|
107
|
+
const typeDefFilePath = modulePath.replace(/\.jsx?$/, DECLARATION_FILE_EXT)
|
|
108
|
+
const typeDefFileContent = createdFiles[typeDefFilePath]
|
|
109
|
+
if (typeDefFileContent) {
|
|
110
|
+
compilation.emitAsset(
|
|
111
|
+
path.join(MF_TYPES_DIR, declarationFileToExposeNameMap[typeDefFilePath] + DECLARATION_FILE_EXT),
|
|
112
|
+
getAssetData(typeDefFileContent)
|
|
113
|
+
)
|
|
114
|
+
} else {
|
|
115
|
+
logger.warn(`failed to create ${typeDefFilePath}`)
|
|
116
|
+
}
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
// create index.json
|
|
120
|
+
const modulesNames = Object.keys(exposes).map((moduleName) => moduleName + DECLARATION_FILE_EXT)
|
|
121
|
+
compilation.emitAsset(path.join(MF_TYPES_DIR, "index.json"), getAssetData(JSON.stringify(modulesNames)))
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const getRemoteDeclareDirPath = (remoteName) => path.resolve(process.cwd(), "node_modules", "@types", remoteName)
|
|
125
|
+
|
|
126
|
+
const getTypesDefinitions = async () => {
|
|
127
|
+
const remotes = federationPlugin._options.remotes
|
|
128
|
+
if (!remotes) return
|
|
129
|
+
|
|
130
|
+
const excludeRemotes = this._options.excludeRemotes || []
|
|
131
|
+
const wantedRemotes = Object.entries(remotes).filter((remote) => !excludeRemotes.find((c) => c === remote[0]))
|
|
132
|
+
|
|
133
|
+
for (const [remoteName, remoteEntryUri] of wantedRemotes) {
|
|
134
|
+
const {origin, pathname} = new URL(remoteEntryUri.split("@")[1])
|
|
135
|
+
const remotePublicUrl = origin + pathname.slice(0, pathname.lastIndexOf("/") + 1)
|
|
136
|
+
const remoteDeclareDirPath = getRemoteDeclareDirPath(remoteName)
|
|
137
|
+
const federationTypesUrl = remotePublicUrl + MF_TYPES_DIR + "/"
|
|
138
|
+
|
|
139
|
+
axios
|
|
140
|
+
// get types index from the current remote
|
|
141
|
+
.get(federationTypesUrl + "index.json")
|
|
142
|
+
.catch((error) => {
|
|
143
|
+
if (error.response?.status === 404) logger.warn(`WARNING: The remote ${remoteName} has no types`)
|
|
144
|
+
else logger.warn(`Failed to get remote types from ${remotePublicUrl}.`, error.message)
|
|
145
|
+
})
|
|
146
|
+
.then((response) => response?.data)
|
|
147
|
+
.then((modulesNames) => {
|
|
148
|
+
if (!modulesNames) return
|
|
149
|
+
// for each remote module get his types
|
|
150
|
+
modulesNames.forEach((moduleName) => {
|
|
151
|
+
const moduleDeclarationFileUrl = federationTypesUrl + moduleName
|
|
152
|
+
axios
|
|
153
|
+
.get(moduleDeclarationFileUrl)
|
|
154
|
+
.catch((error) => {
|
|
155
|
+
logger.warn(`Failed to get ${moduleDeclarationFileUrl} ${error.message}`)
|
|
156
|
+
})
|
|
157
|
+
.then((declarationFileResponse) => {
|
|
158
|
+
if (!declarationFileResponse) return
|
|
159
|
+
const declarationFileContent = declarationFileResponse.data
|
|
160
|
+
const decFilePath = path.join(remoteDeclareDirPath, moduleName)
|
|
161
|
+
fs.promises
|
|
162
|
+
.mkdir(path.dirname(decFilePath), {recursive: true})
|
|
163
|
+
.catch((error) => logger.error(`Failed to write dir: ${decFilePath}`, error))
|
|
164
|
+
.then(() => fs.writeFile(decFilePath, declarationFileContent, {recursive: true}))
|
|
165
|
+
.catch((error) => logger.error(`Failed to write declaration file: ${decFilePath}`, error))
|
|
166
|
+
})
|
|
167
|
+
.catch((error) => {
|
|
168
|
+
logger.warn(`Failed to get ${moduleDeclarationFileUrl} ${error.message}`)
|
|
169
|
+
})
|
|
170
|
+
})
|
|
171
|
+
})
|
|
172
|
+
.catch((error) => {
|
|
173
|
+
logger.error("Failed to add declaration file", error.message)
|
|
174
|
+
})
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
compiler.hooks.compilation.tap(PLUGIN_NAME, (compilation) => {
|
|
179
|
+
const pluginOptions = this._options || {}
|
|
180
|
+
compilation.hooks.beforeCodeGeneration.tap(PLUGIN_NAME, () => {
|
|
181
|
+
if (!isFirstCompilation) return
|
|
182
|
+
isFirstCompilation = false
|
|
183
|
+
if (pluginOptions.exposeTypes !== false) createTypesDefinitions(compilation)
|
|
184
|
+
if (compilation.options.mode === "development" && pluginOptions.importTypes !== false) {
|
|
185
|
+
getTypesDefinitions()
|
|
186
|
+
if (pluginOptions.getTypesInterval) {
|
|
187
|
+
clearInterval(getTypesInterval)
|
|
188
|
+
getTypesInterval = setInterval(() => getTypesDefinitions(compilation), ms(pluginOptions.getTypesInterval))
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
})
|
|
192
|
+
})
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
module.exports = FederationTypesPlugin
|
package/package.json
CHANGED