问题描述
我正在尝试借助Module Federation(webpack 5功能)在运行时获得动态系统。一切正常,但是当我将钩子添加到“生产者”模块(主机应用程序从中动态导入组件的模块)时,会出现大量“钩子无效规则”错误。
Warning: Do not call Hooks inside useEffect(...),useMemo(...),or other built-in Hooks. You can only call Hooks at the top level of your React function. For more information,see [LINK RULES OF HOOKS]
Warning: React has detected a change in the order of Hooks called by PluginHolder. This will lead to bugs and errors if not fixed. For more information,read the Rules of Hooks: [LINK RULES OF HOOKS]
我已经使用了externals字段并在html文件中添加了脚本标签,我使用了共享选项并添加了singleton字段:true并指定react和react-dom版本 每次控制台吐出大量错误
这是我直接从文档中加载模块的方法
const loadComponent = (scope: string,module: string) => async (): Promise<any> => {
// @ts-ignore
await __webpack_init_sharing__('default');
// @ts-ignore
const container = window[scope];
// @ts-ignore
await container.init(__webpack_share_scopes__.default);
// @ts-ignore
const factory = await window[scope].get(module);
return factory();
};
要加载remoteEntry.js文件,我将makeAsyncScriptLoader HOC与react-async-script一起使用,如下所示:
const withScript = (name: string,url: string) => {
const LoadingElement = () => {
return <div>Loading...</div>;
};
return () => {
const [scriptLoaded,setScriptLoaded] = useState<boolean>(false);
const AsyncScriptLoader = makeAsyncScriptLoader(url,{
globalName: name,})(LoadingElement);
if (scriptLoaded) {
return <PluginHolder name={name}/>;
}
return (
<AsyncScriptLoader
asyncScriptOnLoad={() => {
setScriptLoaded(true);
}}
/>
);
};
};
PluginHolder是一个简单的组件,它包装了已加载脚本中的加载模块(加载实际上已完成)
useEffect((): void => {
(async () => {
const c = await loadComponent(name,'./Plugin')();
setComponent(c.default);
})();
},[]);
return cloneElement(component);
最重要的是入门程序:
const [plugins,setPlugins] = useState<PluginFunc[]>([]);
useEffect((): void => {
pluginNames.forEach(desc => {
const loaded = withScript(desc.name,desc.url);
setPlugins([...plugins,loaded]);
});
},[]);
我不使用React.Lazy,因为我不能使用import()。此外,在主机应用程序中,我设置了渴望的字段:react和react-dom
为true下面的我的webpack.config.js(主机):
require('tslib');
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const { DefinePlugin } = require('webpack');
const { ModuleFederationPlugin } = require('webpack').container;
// @ts-ignore
const AutomaticvendorFederation = require('@module-federation/automatic-vendor-federation');
const packageJson = require('./package.json');
const exclude = ['babel','plugin','preset','webpack','loader','serve'];
const ignoreversion = ['react','react-dom'];
const automaticvendorFederation = AutomaticvendorFederation({
exclude,ignoreversion,packageJson,shareFrom: ['dependencies','peerDependencies'],ignorePatchVersion: false,});
module.exports = {
mode: 'none',entry: {
app: path.join(__dirname,'src','index.tsx'),},target: 'web',resolve: {
extensions: ['.ts','.tsx','.js'],module: {
rules: [
{
test: /\.tsx?$/,exclude: '/node_modules/',use: 'ts-loader',{
test: /\.(s[ac]|c)ss$/i,use: [
'style-loader','css-loader','sass-loader',],plugins: [
new HtmlWebpackPlugin({
template: path.join(__dirname,'public','index.html'),favicon: path.join(__dirname,'favicon.ico'),}),new DefinePlugin({
'process.env.NODE_ENV': JSON.stringify('development'),new ModuleFederationPlugin({
name: 'host',remotes: {},exposes: {},shared: {
...automaticvendorFederation,react: {
eager: true,singleton: true,requiredVersion: packageJson.dependencies.react,'react-dom': {
eager: true,requiredVersion: packageJson.dependencies['react-dom'],output: {
filename: '[name].js',path: path.resolve(__dirname,'dist'),publicPath: 'http://localhost:3001/',devServer: {
contentBase: path.join(__dirname,port: 3001,};
还有第二个模块中的webpack.config.js:
require('tslib');
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const { DefinePlugin } = require('webpack');
const { ModuleFederationPlugin } = require('webpack').container;
// @ts-ignore
const AutomaticvendorFederation = require('@module-federation/automatic-vendor-federation');
const packageJson = require('./package.json');
const exclude = ['babel',});
module.exports = (env,argv) => {
const { mode } = argv;
const isDev = mode !== 'production';
return {
mode,entry: {
plugin: path.join(__dirname,resolve: {
extensions: ['.ts',module: {
rules: [
{
test: /\.tsx?$/,{
test: /\.(s[ac]|c)ss$/i,use: [
'style-loader',plugins: [
new HtmlWebpackPlugin({
template: path.join(__dirname,new DefinePlugin({
'process.env.NODE_ENV': JSON.stringify('development'),new ModuleFederationPlugin({
name: 'example',library: { type: 'var',name: 'example' },filename: 'remoteEntry.js',exposes: {
'./Plugin': './src/Plugin',shared: {
...automaticvendorFederation,react: {
eager: isDev,'react-dom': {
eager: isDev,output: {
path: path.resolve(__dirname,publicPath: 'http://localhost:3002/',devServer: {
contentBase: path.join(__dirname,port: 3002,};
};
您是否有任何经验或任何线索-我想关键是2个应用程序使用2个react实例,但这是我的猜测。 我的配置有问题吗?
解决方法
确保将共享依赖项添加到 webpack.config 文件中。
请参见以下示例:
plugins: [
new ModuleFederationPlugin(
{
name: 'MFE1',filename:
'remoteEntry.js',exposes: {
'./Button':'./src/Button',},shared: { react: { singleton: true },"react-dom": { singleton: true } },}
),new HtmlWebpackPlugin({
template:
'./public/index.html',}),],};
我使用此共享属性设置了主机和远程项目。当钩子破坏了我的主机应用程序时为我修复了它。因为有重复的react依赖,不管版本是否一样都会报错。