无法让 webpack 完全忽略不必要的 JSX 组件

Unable to get webpack to fully ignore unnecessary JSX components

在一个项目的古老技术堆栈中呆了几年之后,我终于有机会探索更多当前的框架并涉足 React 和 Webpack。到目前为止,这在很大程度上是一种令人耳目一新和愉快的体验,但我 运行 遇到了 Webpack 的一些困难,我希望蜂巢思维可以帮助解决。

我一直在仔细研究 Webpack 2.0 文档并进行了非常详尽的搜索,结果很简短(我只找到了 问题,这个问题很接近,但我不确定它适用)。由于缺乏信息,我认为我正在考虑以下两种情况之一:

  1. 我想做的事很疯狂。
  2. 我正在尝试做的事情很简单,应该是显而易见的。

...然而,我在这里。

我正在寻找的简短版本是一种根据环境变量排除 某些 JSX 组件不被包含在 Webpack build/bundle 中的方法。目前,无论基于从入口 js 开始 imported 的依赖关系树应该是什么样子,Webpack 似乎都想捆绑项目文件夹中的所有内容。

我假设这是默认行为,因为它有一定道理——应用程序可能需要处于非初始状态的组件。不过,在这种情况下,我们的 'application' 是完全无状态的。

为了提供一些背景知识,这里有一些粗略的项目要求:

这是问题所在:

假设我有 5 个 JSX 组件,方便地命名为 component_1component_2component_3,等等。其中每一个都有一个与其关联的匹配 .scss 文件,包含在以下方式中:

import React from 'react';
import styles from './styles.scss';

module.exports = React.createClass({

  propTypes: {
    foo: React.PropTypes.string.isRequired,
  },

  render: function () {

    return (
      <section className={`myClass`}>
        <Stuff {...props} />
      </section>
    );
  },
});

现在假设我们有两个页面:

这些页面都是使用一个共同的 layout 组件构建的,该组件决定以如下方式使用哪些块:

return (
  <html lang='en'>
    <body>
      {this.props.components.map((object, i) => {
        const Block = component_templates[object.component_name];
        return <Block key={i}{...PAGE_DATA.components[i]} />;
      })}
    </body>
  </html>
);

上面遍历了一个包含我所需的 JSX 组件(对于给定页面)的对象,该对象目前是按以下方式创建的:

load_components() {
  let component_templates = {};
  let get_component = function(component_name) {
    return require(`../path/to/components/${component_name}/template.jsx`);
  }
  for (let i = 0; i < this.included_components.length; i++) {
    let component = this.included_components[i];
    component_templates[component] = get_component(component);
  }
  return component_templates;
}

所以一般流程是:

  1. 从页面配置中收集包含的组件列表。
  2. 创建一个对象,为每个这样的组件执行 require,并使用组件名称作为键存储结果。
  3. 遍历此对象并将这些 JSX 组件中的每一个包含到 layout

所以,如果我只遵循依赖关系树,这应该一切正常。然而,Webpack 正试图包含 所有 我们的组件,而不管 layout 实际上是什么 loading。由于我只为页面上实际 使用 的组件加载 SASS 变量,这导致 Webpack 在sass-loader 尝试处理与 未使用的 模块关联的 SASS 文件。

这里要注意一些事情:静态页面呈现得很好,甚至可以在 Webpack 的开发服务器上工作...我只是遇到了一大堆错误,Webpack 就报错了。

我最初的想法是,可以在我用于 JSX 文件的加载程序的配置中找到解决方案,也许我只需要告诉加载程序什么不是 加载,如果 Webpack 正在尝试加载所有内容。我为此使用了 `babel-loader:

{ test: /\.jsx?$/,
  loader: 'babel-loader',
  exclude: [
    './path/to/components/component_1/',
  ],
  query: { presets: ['es2015', 'react'] } 
},

那里的 exclude 条目是新的, 似乎有效。

所以这是一种中篇小说,但我想犯错的是信息太多,而不是信息太少。我错过了一些简单的事情,还是想做一些疯狂的事情?两个?

如何让未使用的组件被Webpack处理?

TL;DR

不要在 require 中使用表达式,而是使用多个入口点,每一页一个。


详细解答

Webpack appears to be wanting to bundle everything in the project folder.

不是这样的,webpack只包含你导入的东西,这是静态确定的。事实上,许多刚接触 webpack 的人一开始都会感到困惑,webpack 并不只是包含整个项目。但在你的情况下,你遇到了一种边缘情况。问题在于您使用 require 的方式,特别是这个函数:

let get_component = function(component_name) {
  return require(`../path/to/components/${component_name}/template.jsx`);
}

您正在尝试根据函数的参数导入组件。 webpack 应该如何在编译时知道应该包含哪些组件?好吧,webpack 不做任何程序流分析,因此唯一的可能性是包含 all 匹配表达式的可能组件。

你很幸运,webpack 允许你首先这样做,因为只传递一个变量到 require 会失败。例如:

function requireExpression(component) {
  return require(component);
}

requireExpression('./components/a/template.jsx');

即使很容易看出应该需要什么组件,也会在编译时给你这个警告(后来在 运行 时失败):

Critical dependency: the request of a dependency is an expression

但是因为 webpack 不进行程序流分析,所以它看不到这一点,即使它看到了,你也可以在任何地方使用该功能,即使有用户输入,这基本上是一个失败的原因。

有关详细信息,请参阅官方文档的 require with expression

Webpack is attempting to include all of our components, regardless of what the layout is actually loading.

既然您知道了为什么 webpack 需要所有组件,那么理解为什么您的想法行不通以及坦率地说,过于复杂的原因也很重要。

如果我没理解错的话,你有一个多页面应用程序,其中每个页面都应该有一个单独的包,其中只包含必要的组件。但是你真正做的是在 运行 时间只使用需要的组件,所以从技术上讲你有一个包含所有页面的包,但你只使用其中一个,这对于单页应用程序 (SPA)。不知何故,你想让 webpack 知道你正在使用哪个,它只能通过 运行ning 知道,所以它在编译时肯定不知道。

问题的根源在于您在 运行 时(在程序执行时)做出决定,而这应该在编译时完成。解决方案是使用多个入口点,如 Entry Points - Multi Page Application. All you need to do is create each page individually and import what you actually need for it, no fancy dynamic imports. So you need to configure webpack that each entry point is a standalone bundle/page and it will generate them with their associated name (see also output.filename):

所示
entry: {
  pageOne: './src/pageOne.jsx',
  pageTwo: './src/pageTwo.jsx',
  // ...
},
output: {
  filename: '[name].bundle.js'
}

就这么简单,它甚至使构建过程更容易,因为它会自动生成 pageOne.bundle.jspageTwo.bundle.js 等等。


作为最后的评论:尽量不要对动态导入太聪明(几乎完全避免在导入中使用表达式),但是如果你决定制作一个单页应用程序并且你只想加载您应该阅读的当前页面 Code Splitting - Using import() and Code Splitting - Using require.ensure, and use something like react-router.