0

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

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

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

...然而,我在这里。

我正在寻找的简短版本是一种基于环境变量将某些 JSX 组件排除在 Webpack 构建/捆绑包中的方法。目前,无论依赖关系树根据imported条目 js 向前的内容应该是什么样子,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_3, component_4, 和component_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对每个此类组件执行 a 的对象,并使用组件名称作为键存储结果。
  3. 单步执行该对象并将这些 JSX 组件中的每一个都包含到layout.

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

这里需要注意的一点是:静态页面渲染得很好,甚至可以在 Webpack 的开发服务器中工作……我只是遇到了很多错误,而 Webpack 则很适合。

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

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

那里的exclude条目是新的,并且似乎不起作用

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

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

4

1 回答 1

0

TL;博士

不要使用表达式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 和表达式

Webpack 试图包含我们所有的组件,无论布局实际加载的是什么。

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

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

问题的根源是您在运行时(在程序的执行中)做出决定,而这应该在编译时完成。解决方案是使用多个入口点,如入口点 - 多页应用程序所示。您需要做的就是单独创建每个页面并导入您实际需要的内容,而不是花哨的动态导入。因此,您需要将 webpack 配置为每个入口点都是一个独立的包/页面,并且它将使用它们的关联名称生成它们(另请参阅output.filename):

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

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


最后一点:尽量不要对动态导入过于聪明(完全避免在导入中使用表达式),但是如果您决定制作单页应用程序并且只想加载当前页面所需的内容,您应该阅读代码拆分 - 使用 import()代码拆分 - 使用 require.ensure,并使用类似react-router 的东西。

于 2017-03-14T18:25:16.900 回答