作者都是各自领域经过审查的专家,并撰写他们有经验的主题. 我们所有的内容都经过同行评审,并由同一领域的Toptal专家验证.
拥有服务科学硕士学位, Management, and Engineering, Andrej为世界各地的客户从事各种规模的项目.
11
模块化的概念是大多数现代编程语言的固有部分. JavaScript, though, 在ECMAScript ES6的最新版本到来之前,还缺乏任何正式的模块化方法吗.
In Node.作为当今最流行的JavaScript框架之一,模块绑定器允许加载 NPM modules in web browsers, 以及面向组件的库(如React)鼓励并促进JavaScript代码的模块化.
Webpack is 可用的模块捆绑器之一 that processes JavaScript 代码以及所有静态资产(如样式表、图像和字体)打包到一个捆绑文件中. 处理可以包括管理和优化Webpack包依赖的所有必要任务, such as compilation, concatenation, minification, and compression.
However, 配置Webpack和它的依赖关系可能会有压力,而且并不总是一个简单的过程, 特别适合初学者.
这篇博文提供了指导方针, with examples, 如何为不同的场景配置Webpack, 并指出了与使用Webpack捆绑项目依赖相关的最常见的陷阱.
这篇博文的第一部分解释了如何简化项目对其Webpack依赖项的定义. Next, 我们讨论并演示了多页和单页应用程序的代码分割配置. Finally, 我们将讨论如何配置Webpack, 如果我们想在项目中包含第三方库.
相对路径与依赖关系没有直接关系, 但我们在定义Webpack依赖时使用它们. 如果项目文件结构复杂,则很难解析相关模块路径. Webpack配置最基本的好处之一是,它有助于简化项目中相对路径的定义.
假设我们有以下项目结构:
- Project
- node_modules
- bower_modules
- src
- script
- components
- Modal.js
- Navigation.js
- containers
- Home.js
- Admin.js
我们可以通过相对路径引用需要的依赖项, 如果我们想在源代码中将组件导入到容器中, 它看起来像这样:
Home.js
Import Modal from ‘../components/Modal’;
导入导航../组件/导航”;
Modal.js
导入{datepicker}../../../../ bower_modules / datepicker / dist / js / datepicker”;
每次我们想要导入脚本或模块时, 我们需要知道当前目录的位置,并找到我们想要导入的文件的相对路径. 我们可以想象,如果我们有一个嵌套文件结构的大项目,这个问题的复杂性会如何升级, 或者我们想重构一个复杂项目结构的某些部分.
我们可以很容易地处理这个问题与Webpack的 resolve.alias
option. 我们可以声明所谓的别名——目录或模块的名称及其位置, 而且我们不依赖于项目源代码中的相对路径.
webpack.config.js
resolve: {
alias: {
“node_modules”:路径.加入(__dirname node_modules),
“bower_modules”:路径.加入(__dirname bower_modules),
}
}
In the Modal.js
文件,我们现在可以更简单地导入datepicker:
从'bower_modules/datepicker/dist/js/datepicker'中导入{datepicker};
在某些情况下,我们需要将脚本附加到最终包中, 或者将最后的捆绑分成两部分, 或者我们想要按需加载单独的包. 为这些场景设置我们的项目和Webpack配置可能并不简单.
在Webpack配置中, Entry
选项告诉Webpack最终包的起始点在哪里. 入口点可以有三种不同的数据类型:字符串、数组或对象.
如果我们有一个单一的起点,我们可以使用这些格式中的任何一种,并得到相同的结果.
如果我们想要附加多个文件,并且它们不依赖于彼此,我们可以使用Array格式. 例如,我们可以追加 analytics.js
to the end of the bundle.js
:
webpack.config.js
module.exports = {
//创建一个bundle.Js,然后追加分析.js
entry: ['./src/script/index.jsx', './ src /脚本/分析.js'],
output: {
path: './build',
filename: bundle.js '
}
};
假设我们有一个包含多个HTML文件的多页面应用程序,例如 index.html
and admin.html
. 通过使用入口点作为Object类型,我们可以生成多个bundle. 下面的配置生成了两个JavaScript包:
webpack.config.js
module.exports = {
entry: {
index: './src/script/index.jsx',
admin: './src/script/admin.jsx'
},
output: {
path: './build',
filename: '[name].Js ' //基于上面条目中的键的模板(index . Js).js & admin.js)
}
};
index.html
admin.html
两个JavaScript包都可以共享公共库和组件. 我们可以用 CommonsChunkPlugin
, 哪个查找出现在多个条目块中的模块,并创建可以在多个页面之间缓存的共享包.
webpack.config.js
var commonplugin =新webpack.optimize.CommonsChunkPlugin(“共同.js');
module.exports = {
entry: {
index: './src/script/index.jsx',
admin: './src/script/admin.jsx'
},
output: {
path: './build',
filename: '[name].Js ' //基于上面条目中的键的模板(index . Js).js & admin.js)
},
插件(commonsPlugin):
};
现在,我们一定不要忘记加 在捆绑脚本之前.
注意:Webpack共享依赖关系的最佳实践在较新的Webpack版本中有所发展. 这本书值得一读 SplitChunksPlugin and ModuleFederationPlugin 并比较它们的优点,除非你坚持使用Webpack的旧版本.
Webpack可以将静态资产分割成更小的块, 这种方法比标准连接更灵活. 如果我们有一个大型单页应用(SPA), 简单地连接到一个bundle中并不是一个好方法,因为加载一个巨大的bundle会很慢, 用户通常不需要每个视图上的所有依赖项.
我们在前面解释了如何将应用程序拆分为多个bundle, 连接公共依赖项, 并受益于浏览器缓存行为. 这种方法适用于多页面应用程序,但不适用于单页面应用程序.
对于SPA,我们应该只提供呈现当前视图所需的那些静态资产. SPA架构中的客户端路由器是处理代码分割的完美场所. 当用户输入路由时,我们只能为生成的视图加载那些需要的依赖项. 或者,我们可以在用户向下滚动页面时加载依赖项.
为此,我们可以使用 require.ensure
or System.import
函数,Webpack可以静态地检测. Webpack可以基于这个分离点生成一个单独的bundle,并根据需要调用它.
In this example, we have two React containers; an admin view and a dashboard view.
admin.jsx
从' React '中导入React, {Component};
导出默认类Admin扩展组件{
render() {
return Admin < /div>;
}
}
dashboard.jsx
从' React '中导入React, {Component};
导出默认类Dashboard扩展组件{
render() {
return Dashboard < /div>;
}
}
如果用户输入 /dashboard
or /admin
URL,则只加载相应的所需JavaScript包. 下面我们可以看到有客户端路由器和没有客户端路由器的例子.
index.jsx
if (window.location.路径名=== '/dashboard') {
require.确保([],function() {
require('./集装箱/仪表盘”).default;
});
} else if (window.location.路径名=== '/admin') {
require.确保([],function() {
require('./集装箱/管理”).default;
});
}
index.jsx
ReactDOM.render(
{props.children}}>
{
require.确保([],function (require) {
cb(null, require('./集装箱/仪表盘”).default)
}, "dashboard")}}
/>
{
require.确保([],function (require) {
cb(null, require('./集装箱/管理”).default)
}, "admin")}}
/>
, document.getElementById(“内容”)
);
将样式提取到单独的bundle中
In Webpack, loaders, like style-loader
and css-loader
, 预处理样式表并将它们嵌入到输出的JavaScript包中, but in some cases, they can cause the 无样式内容的Flash (FOUC).
我们可以用 ExtractTextWebpackPlugin
它允许在单独的CSS包中生成所有样式,而不是将它们嵌入到最终的JavaScript包中.
webpack.config.js
var ExtractTextPlugin = require('extract-text-webpack-plugin');
module.exports = {
module: {
loaders: [{
test: /\.css/,
装载机:ExtractTextPlugin.提取(“风格”,css) '
}],
},
plugins: [
//将提取的CSS输出到文件
新ExtractTextPlugin(“[名称].[chunkhash].css')
]
}
处理第三方库和插件
Many times, 我们需要使用第三方库, various plugins, 或者其他脚本, 因为我们不想花时间从头开始开发相同的组件. 有许多遗留库和插件没有得到积极维护, 不懂JavaScript模块, 并假设在预定义的名称下存在全局依赖项.
下面是一些使用jQuery插件的例子, 并解释了如何正确配置Webpack以生成最终的包.
ProvidePlugin
大多数第三方插件依赖于特定的全局依赖项. 在jQuery中,插件依赖于 $
or jQuery
变量被定义,我们可以通过调用 $(‘div.content’).pluginFunc()
in our code.
我们可以使用Webpack插件 ProvidePlugin
to prepend Var $ = require("jquery")
每次它遇到全球 $
identifier.
webpack.config.js
webpack.ProvidePlugin({
‘$’: ‘jquery’,
})
当Webpack处理代码时,它会查找是否存在 $
,并提供对全局依赖项的引用,而无需导入由 require
function.
Imports-loader
一些jQuery插件假定 $
在全局命名空间中或依赖 this
being the window
object. 为此,我们可以使用 imports-loader
将全局变量注入到模块中.
example.js
$(‘div.content’).pluginFunc();
然后,我们可以注入 $
变量导入到模块中 imports-loader
:
require("imports?$=jquery!./example.js");
这只是一个前缀 Var $ = require("jquery");
to example.js
.
在第二个用例中:
webpack.config.js
module: {
loaders: [{
测试:/ jquery-plugin /,
loader: 'imports?jQuery=jquery,$=jquery,this=>window'
}]
}
By using the =>
符号(不要与 ES6 Arrow functions),我们可以设置任意变量. 最后一个值重新定义了全局变量 this
to point to the window
object. 方法包装文件的整个内容是一样的 (function () { ... }).call(window);
and calling this
function with window
as an argument.
我们也可以要求使用CommonJS或AMD模块格式的库:
// CommonJS
Var $ = require("jquery");
//可以使用jquery
// AMD
定义([' jquery '],函数($){
//可以使用jquery
});
一些库和模块可以支持不同的模块格式.
在下一个例子中, 我们有一个jQuery插件,它使用AMD和CommonJS模块格式,并有一个jQuery依赖:
jquery-plugin.js
(函数(工厂){
If (typeof define === 'function') && define.amd) {
//使用AMD格式
定义((“jquery”)、工厂);
} else if (typeof exports === 'object') {
//使用CommonJS格式
module.Exports = factory(require('jquery'));
} else {
// AMD和CommonJS都没有使用. 使用全局变量.
}
});
webpack.config.js
module: {
loaders: [{
测试:/ jquery-plugin /,
loader: "imports?define=>false,exports=>false"
}]
}
我们可以为特定的库选择要使用的模块格式. If we declare define
to equal false
, Webpack不会解析AMD模块格式的模块,如果我们声明变量 exports
to equal false
, Webpack不会以CommonJS模块格式解析模块.
Expose-loader
如果我们需要将一个模块公开给全局上下文,我们可以使用 expose-loader
. 这很有帮助, for example, 如果我们有不属于Webpack配置的外部脚本,并且依赖于全局命名空间中的符号, 或者我们使用需要访问浏览器控制台中符号的浏览器插件.
webpack.config.js
module: {
loaders: [
test: require.resolve('jquery'),
装载机:“expose-loader?jQuery!expose-loader?$'
]
}
jQuery库现在可以在全局命名空间中用于网页上的其他脚本.
window.$
window.jQuery
配置外部Webpack依赖项
如果我们想包含来自外部托管脚本的模块, 我们需要在构型中定义它们. 否则,Webpack将无法生成最终的bundle.
我们可以使用Webpack来配置外部脚本 externals
配置选项. 例如,我们可以通过一个单独的
标记,同时仍然显式地将其声明为项目中的模块依赖项.
webpack.config.js
externals: {
react: 'React',
“react-dom”:“ReactDOM”
}
支持一个库的多个实例
在前端开发中使用NPM包管理器来管理第三方库和依赖项是非常棒的. However, 有时,我们可以使用不同版本的同一库的多个实例, 他们不能在一个环境中很好地合作.
This could happen, for example, 使用React库, 在那里我们可以从NPM安装React,然后通过一些附加的包或插件可以获得不同版本的React. 我们的项目结构如下所示:
project
|
|-- node_modules
|
|-- react
|-- react-plugin
|
|--node_modules
|
|--react
组件来自 react-plugin
有一个不同的React实例比在项目中的其余组件. 现在我们有两个独立的React副本,它们可以是不同的版本. In our application, 这种情况可能会搞乱我们的全局可变DOM, 我们可以在web控制台日志中看到错误消息. 那么Webpack, node_modules
,和React在这种情况下可以很好地结合在一起?
这个问题的解决方案是在整个项目中使用相同版本的React. 我们可以通过Webpack别名来解决.
webpack.config.js
module.exports = {
resolve: {
alias: {
'react': path.join(__dirname, './ node_modules /反应”),
“反应/插件”:路径.加入(__dirname / node_modules /反应/插件),
}
}
}
When react-plugin
尝试使用React时,它会使用项目中的版本 node_modules
. 如果我们想知道我们使用的是哪个版本的React,我们可以添加 console.log(React.version)
in the source code.
专注于开发,而不是Webpack配置
这篇文章只是触及了Webpack的功能和实用性的表面.
还有许多其他的Webpack loaders and plugins 它将帮助您优化和简化JavaScript捆绑.
即使你是初学者, 本指南为您开始使用Webpack提供了坚实的基础, 哪一个将使您能够更多地关注开发,而不是捆绑配置.
Related:
维护控制:Webpack和React指南. 1
拥有服务科学硕士学位, Management, and Engineering, Andrej为世界各地的客户从事各种规模的项目.
11
世界级的文章,每周发一次.
世界级的文章,每周发一次.
Join the Toptal® community.
\nadmin.html
\n
\n\n两个JavaScript包都可以共享公共库和组件. 我们可以用 CommonsChunkPlugin
, 哪个查找出现在多个条目块中的模块,并创建可以在多个页面之间缓存的共享包.
\nwebpack.config.js
var commonplugin =新webpack.optimize.CommonsChunkPlugin(“共同.js');\n \nmodule.exports = {\n entry: {\n index: './src/script/index.jsx',\n admin: './src/script/admin.jsx'\n },\n output: {\n path: './build',\n filename: '[name].Js ' //基于上面条目中的键的模板(index . Js).js & admin.js)\n },\n 插件(commonsPlugin):\n};\n
\n\n现在,我们一定不要忘记加 在捆绑脚本之前.
注意:Webpack共享依赖关系的最佳实践在较新的Webpack版本中有所发展. 这本书值得一读 SplitChunksPlugin and ModuleFederationPlugin 并比较它们的优点,除非你坚持使用Webpack的旧版本.
\n\nWebpack可以将静态资产分割成更小的块, 这种方法比标准连接更灵活. 如果我们有一个大型单页应用(SPA), 简单地连接到一个bundle中并不是一个好方法,因为加载一个巨大的bundle会很慢, 用户通常不需要每个视图上的所有依赖项.
\n\n我们在前面解释了如何将应用程序拆分为多个bundle, 连接公共依赖项, 并受益于浏览器缓存行为. 这种方法适用于多页面应用程序,但不适用于单页面应用程序.
\n\n对于SPA,我们应该只提供呈现当前视图所需的那些静态资产. SPA架构中的客户端路由器是处理代码分割的完美场所. 当用户输入路由时,我们只能为生成的视图加载那些需要的依赖项. 或者,我们可以在用户向下滚动页面时加载依赖项.
\n\n为此,我们可以使用 require.ensure
or System.import
函数,Webpack可以静态地检测. Webpack可以基于这个分离点生成一个单独的bundle,并根据需要调用它.
In this example, we have two React containers; an admin view and a dashboard view.
\n\n
\nadmin.jsx
从' React '中导入React, {Component};\n \n导出默认类Admin扩展组件{\n render() {\n return Admin < /div>;\n }\n}\n\n\n
\ndashboard.jsx
\n\n从' React '中导入React, {Component};\n \n导出默认类Dashboard扩展组件{\n render() {\n return Dashboard < /div>;\n }\n}\n\n\n如果用户输入 /dashboard
or /admin
URL,则只加载相应的所需JavaScript包. 下面我们可以看到有客户端路由器和没有客户端路由器的例子.
\n\n
\nindex.jsx
\n\nif (window.location.路径名=== '/dashboard') {\n require.确保([],function() {\n require('./集装箱/仪表盘”).default;\n });\n} else if (window.location.路径名=== '/admin') {\n require.确保([],function() {\n require('./集装箱/管理”).default;\n });\n}\n
\n\n
\nindex.jsx
\n\nReactDOM.render(\n \n {props.children}}>\n \n {\n require.确保([],function (require) {\n cb(null, require('./集装箱/仪表盘”).default)\n }, \"dashboard\")}}\n />\n {\n require.确保([],function (require) {\n cb(null, require('./集装箱/管理”).default)\n }, \"admin\")}}\n />\n \n \n , document.getElementById(“内容”)\n);\n
\n\n将样式提取到单独的bundle中
\n\nIn Webpack, loaders, like style-loader
and css-loader
, 预处理样式表并将它们嵌入到输出的JavaScript包中, but in some cases, they can cause the 无样式内容的Flash (FOUC).
\n\n我们可以用 ExtractTextWebpackPlugin
它允许在单独的CSS包中生成所有样式,而不是将它们嵌入到最终的JavaScript包中.
\n\n
\nwebpack.config.js
\n\nvar ExtractTextPlugin = require('extract-text-webpack-plugin');\n \nmodule.exports = {\n module: {\n loaders: [{\n test: /\\.css/,\n 装载机:ExtractTextPlugin.提取(“风格”,css) '\n }],\n },\n plugins: [\n //将提取的CSS输出到文件\n 新ExtractTextPlugin(“[名称].[chunkhash].css')\n ]\n}\n
\n\n处理第三方库和插件
\n\nMany times, 我们需要使用第三方库, various plugins, 或者其他脚本, 因为我们不想花时间从头开始开发相同的组件. 有许多遗留库和插件没有得到积极维护, 不懂JavaScript模块, 并假设在预定义的名称下存在全局依赖项.
\n\n下面是一些使用jQuery插件的例子, 并解释了如何正确配置Webpack以生成最终的包.
\n\nProvidePlugin
\n\n大多数第三方插件依赖于特定的全局依赖项. 在jQuery中,插件依赖于 $
or jQuery
变量被定义,我们可以通过调用 $(‘div.content’).pluginFunc()
in our code.
\n\n我们可以使用Webpack插件 ProvidePlugin
to prepend var $ = require(\"jquery\")
每次它遇到全球 $
identifier.
\n\n
\nwebpack.config.js
\n\nwebpack.ProvidePlugin({\n ‘$’: ‘jquery’,\n})\n
\n\n当Webpack处理代码时,它会查找是否存在 $
,并提供对全局依赖项的引用,而无需导入由 require
function.
\n\nImports-loader
\n\n一些jQuery插件假定 $
在全局命名空间中或依赖 this
being the window
object. 为此,我们可以使用 imports-loader
将全局变量注入到模块中.
\n\n
\nexample.js
\n\n$(‘div.content’).pluginFunc();\n
\n\n然后,我们可以注入 $
变量导入到模块中 imports-loader
:
\n\nrequire(\"imports?$=jquery!./example.js\");\n
\n\n这只是一个前缀 var $ = require(\"jquery\");
to example.js
.
\n\n在第二个用例中:
\n\n
\nwebpack.config.js
\n\nmodule: {\n loaders: [{\n 测试:/ jquery-plugin /,\n loader: 'imports?jQuery=jquery,$=jquery,this=>window'\n }]\n}\n
\n\nBy using the =>
符号(不要与 ES6 Arrow functions),我们可以设置任意变量. 最后一个值重新定义了全局变量 this
to point to the window
object. 方法包装文件的整个内容是一样的 (function () { ... }).call(window);
and calling this
function with window
as an argument.
\n\n我们也可以要求使用CommonJS或AMD模块格式的库:
\n\n// CommonJS\nvar $ = require(\"jquery\"); \n//可以使用jquery\n\n// AMD\n定义([' jquery '],函数($){ \n//可以使用jquery\n});\n
\n\n一些库和模块可以支持不同的模块格式.
\n\n在下一个例子中, 我们有一个jQuery插件,它使用AMD和CommonJS模块格式,并有一个jQuery依赖:
\n\n
\njquery-plugin.js
\n\n(函数(工厂){\n If (typeof define === 'function') && define.amd) {\n //使用AMD格式\n 定义((“jquery”)、工厂);\n } else if (typeof exports === 'object') {\n //使用CommonJS格式\n module.Exports = factory(require('jquery'));\n } else {\n // AMD和CommonJS都没有使用. 使用全局变量.\n }\n});\n
\n\n
\nwebpack.config.js
\n\nmodule: {\n loaders: [{\n 测试:/ jquery-plugin /,\n loader: \"imports?define=>false,exports=>false\"\n }]\n}\n
\n\n我们可以为特定的库选择要使用的模块格式. If we declare define
to equal false
, Webpack不会解析AMD模块格式的模块,如果我们声明变量 exports
to equal false
, Webpack不会以CommonJS模块格式解析模块.
\n\nExpose-loader
\n\n如果我们需要将一个模块公开给全局上下文,我们可以使用 expose-loader
. 这很有帮助, for example, 如果我们有不属于Webpack配置的外部脚本,并且依赖于全局命名空间中的符号, 或者我们使用需要访问浏览器控制台中符号的浏览器插件.
\n\n
\nwebpack.config.js
\n\nmodule: {\n loaders: [\n test: require.resolve('jquery'),\n 装载机:“expose-loader?jQuery!expose-loader?$'\n ]\n}\n
\n\njQuery库现在可以在全局命名空间中用于网页上的其他脚本.
\n\nwindow.$\nwindow.jQuery\n
\n\n配置外部Webpack依赖项
\n\n如果我们想包含来自外部托管脚本的模块, 我们需要在构型中定义它们. 否则,Webpack将无法生成最终的bundle.
\n\n我们可以使用Webpack来配置外部脚本 externals
配置选项. 例如,我们可以通过一个单独的
标记,同时仍然显式地将其声明为项目中的模块依赖项.
\n\n
\nwebpack.config.js
\n\nexternals: {\n react: 'React',\n “react-dom”:“ReactDOM”\n}\n
\n\n支持一个库的多个实例
\n\n在前端开发中使用NPM包管理器来管理第三方库和依赖项是非常棒的. However, 有时,我们可以使用不同版本的同一库的多个实例, 他们不能在一个环境中很好地合作.
\n\nThis could happen, for example, 使用React库, 在那里我们可以从NPM安装React,然后通过一些附加的包或插件可以获得不同版本的React. 我们的项目结构如下所示:
\n\nproject\n|\n|-- node_modules\n |\n |-- react\n |-- react-plugin\n |\n |--node_modules\n |\n |--react\n
\n\n组件来自 react-plugin
有一个不同的React实例比在项目中的其余组件. 现在我们有两个独立的React副本,它们可以是不同的版本. In our application, 这种情况可能会搞乱我们的全局可变DOM, 我们可以在web控制台日志中看到错误消息. 那么Webpack, node_modules
,和React在这种情况下可以很好地结合在一起?
\n\n这个问题的解决方案是在整个项目中使用相同版本的React. 我们可以通过Webpack别名来解决.
\n\n
\nwebpack.config.js
\n\nmodule.exports = {\n resolve: {\n alias: {\n 'react': path.join(__dirname, './ node_modules /反应”),\n “反应/插件”:路径.加入(__dirname / node_modules /反应/插件),\n }\n }\n}\n
\n\nWhen react-plugin
尝试使用React时,它会使用项目中的版本 node_modules
. 如果我们想知道我们使用的是哪个版本的React,我们可以添加 console.log(React.version)
in the source code.
\n\n专注于开发,而不是Webpack配置
\n\n这篇文章只是触及了Webpack的功能和实用性的表面.
\n\n还有许多其他的Webpack loaders and plugins 它将帮助您优化和简化JavaScript捆绑.
\n\n即使你是初学者, 本指南为您开始使用Webpack提供了坚实的基础, 哪一个将使您能够更多地关注开发,而不是捆绑配置.
\n\n\nRelated: \n维护控制:Webpack和React指南. 1\n\n\n","as":"div","isContentFit":true,"sharingWidget":{"url":"http://r0jm.wxzjnt.com/javascript/a-guide-to-managing-webpack-dependencies","title":"The Beginner's Guide to Webpack Dependencies","text":null,"providers":["linkedin","twitter","facebook"],"gaCategory":null,"domain":{"name":"developers","title":"Engineering","vertical":{"name":"developers","title":"Developers","publicUrl":"http://r0jm.wxzjnt.com/developers"},"publicUrl":"http://r0jm.wxzjnt.com/developers/blog"},"hashtags":"JavaScript,Tutorial,Dependencies,Guide,Webpack"}}