【问题标题】:Unable to get webpack to fully ignore unnecessary JSX components无法让 webpack 完全忽略不必要的 JSX 组件
【发布时间】:2017-03-14 16:07:41
【问题描述】:

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

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

  1. 我想做的事太疯狂了。
  2. 我正在尝试做的事情很简单,以至于它应该是不费吹灰之力的。

...然而,我在这里。

我正在寻找的简短版本是一种基于环境变量排除某些 JSX 组件被包含在 Webpack 构建/捆绑中的方法。目前,无论依赖关系树应该是什么样子,基于条目 js 向前的 imported,Webpack 似乎都希望将所有内容捆绑在项目文件夹中。

我有点假设这是默认行为,因为它有一定的意义——应用程序可能需要处于初始状态以外的状态的组件。但是,在这种情况下,我们的“应用程序”是完全无状态的。

为了提供一些背景知识,以下是项目的一些粗略要求:

  • 项目包含一组组件,这些组件可以组装到一个页面中,并根据配置文件给出一个主题。
  • 每个组件都包含自己的模块化 CSS,用 SASS 编写。
  • 每个导出的页面都是静态的,并且捆绑包实际上已从导出目录中删除。 (是的,如果项目以简单地渲染一系列单一的、静态的页面结束,React 有点过头了。项目的未来状态将包括在此之上的 UI,React 应该非常适合,但是-- 现在最好用 JSX 编写这些组件。)
  • CSS 是使用 extract-text-webpack-plugin 从包中提取的 - 在最终导出的元素上没有直接内联样式,这不是可以更改的要求。
  • 作为页面主题信息的一部分,设置了 SASS 变量,然后 SASS 将这些变量用于每个 JSX 组件。
  • 只有在给定页面的配置文件中引用的 JSX 组件的 SASS 变量才会被编译并通过 sass-loader 传递以在编译 CSS 时使用。

这就是问题所在:

假设我有 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>
    );
  },
});

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

  • page_1 包含 component_1component_2
  • page_2 包含 component_3component_4component_5

这两个页​​面都是使用通用的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实际上加载的是什么。由于我只为页面上实际使用的组件加载 SASS 变量,这会导致 Webpack 在 sass-loader 尝试处理 SASS 文件时抛出 undefined variable 错误与未使用的模块相关联。

这里有一点需要注意:静态页面渲染得很好,甚至可以在 Webpack 的开发服务器中运行……我只是遇到了很多错误,而 Webpack 会出现问题。

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

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

exclude 条目是新的,并且 似乎工作。

所以这有点像中篇小说,但我想在信息过多而不是缺乏方面犯错。我错过了一些简单的事情,还是想做一些疯狂的事情?两者都有?

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

【问题讨论】:

    标签: sass webpack extract-text-plugin


    【解决方案1】:

    TL;DR

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


    详细解答

    Webpack 似乎想要捆绑项目文件夹中的所有内容。

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

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

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

    你很幸运 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 试图包含我们所有的组件,而不管布局实际加载的是什么。

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

    如果我的理解正确,您有一个多页面应用程序,其中每个页面都应该有一个单独的包,其中只包含必要的组件。但是你真正做的是在运行时只使用所需的组件,所以从技术上讲,你有一个包含所有页面的包,但你只使用其中一个,这对于单页应用程序来说非常好(温泉)。并且不知何故,您希望 webpack 知道您正在使用哪个,它只能通过运行它才能知道,所以它在编译时肯定无法知道。

    问题的根源在于您是在运行时(在程序的执行中)做出决定,而这应该在编译时完成。解决方案是使用多个入口点,如Entry Points - Multi Page Application 所示。您需要做的就是单独创建每个页面并导入您实际需要的内容,而不是花哨的动态导入。因此,您需要将 webpack 配置为每个入口点都是一个独立的包/页面,并且它将使用它们的关联名称生成它们(另请参阅 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()Code Splitting - Using require.ensure,并使用react-router 之类的内容。

    【讨论】:

    • 太好了,这正是我一直在寻找的——感谢您在文档中指出我正确的位置,我完全用解析器寻找错误的位置。快速提问:如果由于某种原因,离散入口点存在问题(例如,由于可扩展性),这是否也可以通过 require.context 解决?
    • 也许,这取决于你到底想做什么,但也可能有更好的解决方案。
    • 最后,我想让用户使用现有布局创建页面,他们可以将组件放入其中,并导出静态页面......整个页面由 JSON 对象定义,可以简单地输入流以生成页面。那时,应用程序将拥有一个包含所有组件和漂亮 UI 的条目。所以也许所有东西都被捆绑并不是那么糟糕,我应该打开我的 SASS 变量的限制并收工......
    • 我个人会根据 JSON 配置生成一个 jsx 文件,作为入口点,然后在其上运行 webpack(最好使用 Node API)。你的 UI 将与那个完全分开(完全不同的应用程序),所以如果你想展示这些组件,它们都会最终出现在那个应用程序中。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-04-22
    • 2018-11-20
    相关资源
    最近更新 更多