无法让 webpack 完全忽略不必要的 JSX 组件
Unable to get webpack to fully ignore unnecessary JSX components
在一个项目的古老技术堆栈中呆了几年之后,我终于有机会探索更多当前的框架并涉足 React 和 Webpack。到目前为止,这在很大程度上是一种令人耳目一新和愉快的体验,但我 运行 遇到了 Webpack 的一些困难,我希望蜂巢思维可以帮助解决。
我一直在仔细研究 Webpack 2.0 文档并进行了非常详尽的搜索,结果很简短(我只找到了 问题,这个问题很接近,但我不确定它适用)。由于缺乏信息,我认为我正在考虑以下两种情况之一:
- 我想做的事很疯狂。
- 我正在尝试做的事情很简单,应该是显而易见的。
...然而,我在这里。
我正在寻找的简短版本是一种根据环境变量排除 某些 JSX 组件不被包含在 Webpack build/bundle 中的方法。目前,无论基于从入口 js 开始 imported
的依赖关系树应该是什么样子,Webpack 似乎都想捆绑项目文件夹中的所有内容。
我假设这是默认行为,因为它有一定道理——应用程序可能需要处于非初始状态的组件。不过,在这种情况下,我们的 'application' 是完全无状态的。
为了提供一些背景知识,这里有一些粗略的项目要求:
- 项目包含一桶组件,这些组件可以组合成一个页面并根据配置文件指定一个主题。
- 这些组件中的每一个都包含自己的模块 CSS,用 SASS 编写。
- 每个导出的页面都是静态的,并且实际上从导出目录中删除了包。 (是的,如果项目以简单地渲染一系列单个静态页面而结束,那么 React 有点矫枉过正。项目的未来状态将在此之上包含一个 UI,对此 React 应该是一个漂亮的不过很合适——所以最好现在就用 JSX 编写这些组件。)
- CSS 使用
extract-text-webpack-plugin
从包中提取 - 在最终导出中没有样式直接内联在元素上,这 不是 的要求这可以改变。
- 作为页面主题信息的一部分,设置了 SASS 变量,然后 SASS 将这些变量用于每个 JSX 组件。
- 只有 SASS 给定页面配置文件中引用的 JSX 组件的变量被编译并通过
sass-loader
以供在编译 CSS. 时使用
这是问题所在:
假设我有 5 个 JSX 组件,方便地命名为 component_1
、component_2
、component_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_1
和 component_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;
}
所以一般流程是:
- 从页面配置中收集包含的组件列表。
- 创建一个对象,为每个这样的组件执行
require
,并使用组件名称作为键存储结果。
- 遍历此对象并将这些 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.js
、pageTwo.bundle.js
等等。
作为最后的评论:尽量不要对动态导入太聪明(几乎完全避免在导入中使用表达式),但是如果你决定制作一个单页应用程序并且你只想加载您应该阅读的当前页面 Code Splitting - Using import() and Code Splitting - Using require.ensure, and use something like react-router.
在一个项目的古老技术堆栈中呆了几年之后,我终于有机会探索更多当前的框架并涉足 React 和 Webpack。到目前为止,这在很大程度上是一种令人耳目一新和愉快的体验,但我 运行 遇到了 Webpack 的一些困难,我希望蜂巢思维可以帮助解决。
我一直在仔细研究 Webpack 2.0 文档并进行了非常详尽的搜索,结果很简短(我只找到了
- 我想做的事很疯狂。
- 我正在尝试做的事情很简单,应该是显而易见的。
...然而,我在这里。
我正在寻找的简短版本是一种根据环境变量排除 某些 JSX 组件不被包含在 Webpack build/bundle 中的方法。目前,无论基于从入口 js 开始 imported
的依赖关系树应该是什么样子,Webpack 似乎都想捆绑项目文件夹中的所有内容。
我假设这是默认行为,因为它有一定道理——应用程序可能需要处于非初始状态的组件。不过,在这种情况下,我们的 'application' 是完全无状态的。
为了提供一些背景知识,这里有一些粗略的项目要求:
- 项目包含一桶组件,这些组件可以组合成一个页面并根据配置文件指定一个主题。
- 这些组件中的每一个都包含自己的模块 CSS,用 SASS 编写。
- 每个导出的页面都是静态的,并且实际上从导出目录中删除了包。 (是的,如果项目以简单地渲染一系列单个静态页面而结束,那么 React 有点矫枉过正。项目的未来状态将在此之上包含一个 UI,对此 React 应该是一个漂亮的不过很合适——所以最好现在就用 JSX 编写这些组件。)
- CSS 使用
extract-text-webpack-plugin
从包中提取 - 在最终导出中没有样式直接内联在元素上,这 不是 的要求这可以改变。 - 作为页面主题信息的一部分,设置了 SASS 变量,然后 SASS 将这些变量用于每个 JSX 组件。
- 只有 SASS 给定页面配置文件中引用的 JSX 组件的变量被编译并通过
sass-loader
以供在编译 CSS. 时使用
这是问题所在:
假设我有 5 个 JSX 组件,方便地命名为 component_1
、component_2
、component_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_1
和component_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;
}
所以一般流程是:
- 从页面配置中收集包含的组件列表。
- 创建一个对象,为每个这样的组件执行
require
,并使用组件名称作为键存储结果。 - 遍历此对象并将这些 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.js
、pageTwo.bundle.js
等等。
作为最后的评论:尽量不要对动态导入太聪明(几乎完全避免在导入中使用表达式),但是如果你决定制作一个单页应用程序并且你只想加载您应该阅读的当前页面 Code Splitting - Using import() and Code Splitting - Using require.ensure, and use something like react-router.