在Webpack中动态加载外部模块失败

时间:2019-06-20 17:48:04

标签: javascript reactjs webpack module dynamic-import

我正在尝试建立以下架构:带有一些基本功能构建的核心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'));

我的猜测是找不到模块。我不知道它在哪里寻找模块,或者如何获取更多信息进行调试。

1 个答案:

答案 0 :(得分:0)

事实证明,我对Webpack和动态加载有两个错误的假设。

我在两件事上遇到问题-我正在加载的模块类型以及我加载模块的方式。

  1. 动态导入还不是标准的ES功能-它是due to be standardized in ES 2020。如果您要加载的模块对象是ES6模块(也就是包含“ export ModuleName”的东西),则此动态导入将返回一个模块。如果您尝试加载打包为CommonJS模块(AMD,UMD)的东西,导入将成功,但是您将获得一个空对象。 Webpack似乎不支持创建ES6格式的捆绑软件-它可以创建各种模块类型,并且在上面的配置文件中,我实际上是在创建UMD模块(通过libraryTarget设置配置)。

  2. 我对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' )
    })
  ]
}