我正在尝试建立以下架构:带有一些基本功能构建的核心React应用程序,以及在运行时加载其他React组件的能力。这些附加的React组件可以按需加载,并且在构建时对于核心应用程序不可用(因此它们不能包含在核心应用程序的捆绑软件中,必须单独构建)。经过一段时间的研究,我发现Webpack Externals似乎很合适。我现在使用以下webpack.config.js分别构建模块:
const path = require('path');
const fs = require('fs');
process.env.BABEL_ENV = 'production';
process.env.NODE_ENV = 'production';
const appDirectory = fs.realpathSync(process.cwd());
const resolveApp = relativePath => path.resolve(appDirectory, relativePath);
module.exports = {
entry: './src/MyModule.jsx',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'MyModule.js',
library: 'MyModule',
libraryTarget: 'umd'
},
externals: {
"react": "react",
"semantic-ui-react": "semantic-ui-react"
},
module: {
rules: [
{
test: /\.(js|jsx|mjs)$/,
include: resolveApp('src'),
loader: require.resolve('babel-loader'),
options: {
compact: true,
},
}
]
},
resolve: {
extensions: ['.wasm', '.mjs', '.js', '.json', '.jsx']
}
};
看看生成的MyModule.js文件,它对我来说看起来是正确的。
现在,在我的核心应用程序中,我将按以下方式导入模块:
let myComponent = React.lazy(() => import(componentName + '.js'));
其中componentName
是与我的模块名称匹配的变量,在这种情况下,为“ MyModule”。在构建时名称未知,并且在构建时该文件不在src文件夹中。为了避免在使用未知导入构建此代码时webpack出错,我将以下内容添加到了核心项目的webpack.config.js中:
module.exports = {
externals: function (context, request, callback/*(err, result)*/) {
if (request === './MyModule.js') {
callback(null, "umd " + request);
} else {
callback();
}
}
}
我已经确认在构建过程中会调用外部函数,并且if条件与此模块匹配。构建成功,并且我能够运行我的核心应用程序。
然后,要测试动态加载,我将MyModule.js
放到static / js文件夹中,其中存放了我的核心应用程序的捆绑包,然后导航到我的核心应用程序中通过{{1}请求MyModule的页面}
我在导入行的控制台中看到运行时错误
let myComponent = React.lazy(() => import(componentName + '.js'));
我的猜测是找不到模块。我不知道它在哪里寻找模块,或者如何获取更多信息进行调试。
答案 0 :(得分:0)
事实证明,我对Webpack和动态加载有两个错误的假设。
我在两件事上遇到问题-我正在加载的模块类型以及我加载模块的方式。
动态导入还不是标准的ES功能-它是due to be standardized in ES 2020。如果您要加载的模块对象是ES6模块(也就是包含“ export ModuleName”的东西),则此动态导入将仅返回一个模块。如果您尝试加载打包为CommonJS模块(AMD,UMD)的东西,导入将成功,但是您将获得一个空对象。 Webpack似乎不支持创建ES6格式的捆绑软件-它可以创建各种模块类型,并且在上面的配置文件中,我实际上是在创建UMD模块(通过libraryTarget设置配置)。
我对import语句本身有问题,因为我在Webpack捆绑的应用程序中使用了它。 Webpack重新解释标准的ES导入语句。在标准的webpack配置中(包括从CRA获得的配置),webpack将此语句用作捆绑包的分割点,因此,即使动态导入的模块也应在webpack构建时存在(并且如果它们不可用)。我曾尝试使用webpack外部组件来告诉webpack动态加载模块,这使得在没有模块存在的情况下构建成功。但是,该应用程序在运行时仍使用Webpack的导入功能,而不是标准的JS导入功能。我通过尝试从浏览器控制台运行import('modulename')并得到与我的应用(与webpack捆绑在一起)不同的结果来证实了这一点。
要解决问题2,您可以通过在import语句中添加一些注释来告诉Webpack不要重新解释ES动态导入。
import(/*webpackIgnore: true*/ 'path/to/module.js');
这既可以防止Webpack在构建时尝试查找并捆绑动态导入的模块,也可以尝试在运行时导入它。这将使应用程序中的行为与浏览器控制台中的行为匹配。
问题1的解决难度更大。如前所述,导入非ES6模块将返回一个空对象(如果您等待promise或使用.then())。但是,事实证明,文件本身确实已加载并且代码得以执行。您可以使用Webpack以“窗口”格式导出模块,然后按以下方式加载它。
await import(/*webpackIgnore: true*/`path/to/module.js`);
let myModule = window['module'].default;
另一个避免使用window对象的潜在解决方案是使用能够产生ES6模块的系统(因此,不是Webpack)来构建模块。我最终使用汇总创建了一个ES6模块,该模块将所有依赖项拉到一个文件中,并通过Babel运行输出。这样就产生了一个模块,该模块通过动态ES导入成功加载。以下是我的rollup.config.js(请注意,我在模块中包括了所有需要的外部节点模块-这使模块尺寸过大,但这是我的特定应用程序的要求-您的应用程序可能会有所不同,并且您需要配置汇总以排除模块)
// node-resolve will resolve all the node dependencies
import commonjs from 'rollup-plugin-commonjs';
import resolve from 'rollup-plugin-node-resolve';
import babel from 'rollup-plugin-babel';
import replace from 'rollup-plugin-replace';
export default {
input: 'src/myModule.jsx',
output: {
file: 'dist/bundle.js',
format: 'esm'
},
plugins: [
resolve(),
babel({
exclude: 'node_modules/**'
}),
commonjs({
include: 'node_modules/**',
namedExports: {
'node_modules/react/index.js': ['Children', 'Component', 'PropTypes', 'PureComponent', 'React', 'createElement', 'createRef', 'isValidElement', 'cloneElement', 'Fragment'],
'node_modules/react-dom/index.js': ['render', 'createElement', 'findDOMNode', 'createPortal'],
'node_modules/react-is/index.js': ['isForwardRef']
}
}),
replace({
'process.env.NODE_ENV': JSON.stringify( 'production' )
})
]
}