基础介绍
介绍
Webpack凭借强大的功能,成为最流行和最活跃的打包工具,也是面试时高级程序员必须掌握的“软技能”。本文主要介绍webpack的入口、输出和各种loader、plugins的使用以及开发环境的搭建。
webpack概念
官网定义
本质上,webpack 是一个现代 JavaScript 应用程序的静态模块打包器(module bundler)。当 webpack 处理应用程序时,它会递归地构建一个依赖关系图(dependency graph),其中包含应用程序需要的每个模块,然后将所有这些模块打包成一个或多个bundle。
- 首先webpack是一个静态模块打包器,所谓的静态模块,包括脚本、样式表和图片等等;
- webpack打包时首先遍历所有的静态资源,根据资源的引用,构建出一个依赖关系图,
- 然后再将模块划分,打包出一个或多个bundle。
核心概念
- 入口(entry):指示 webpack 应该使用哪个模块,来作为构建其内部依赖图的开始
- 输出(output):在哪里输出它所创建的 bundles
- loader:让 webpack 能够去处理那些非 JavaScript 文件
- 插件(plugins):用于执行范围更广的任务
打包方式
命令式打包
/*webpack <entry> [<entry>] -o <output>
* entry和output就对应了上述概念中的入口和输入
*/
webpack index.js -o dist/bundle.js
但仅适用于简单的项目
配置文件打包
webpack [--config webpack.config.js]
配置文件默认的名称就是webpack.config.js,但是在Vue的基础项目配置中,根据项目环境将其分为三份配置文件。同时搭配config文件的配置,可以在打包时通过 —config 命令来切换环境的配置。
//生产环境配置
webpack --config webpack.prod.config.js
//开发环境配置
webpack --config webpack.dev.config.js
config配置类型
config的配置内容属性众多,另外单开,本次重点是说明配置文件导出的内容形式。
配置对象
导出内容为一个配置对象,里面通过属性键值对进行配置。
module.exports = {
entry:"",
output:{}
}
配置函数
导出内容为函数形式,可以传入命令行带入的环境变量等参数,可以更方便的在函数中对环境变量进行处理。
module.exports = function(env,argv){
return {
entry:"",
output:{}
}
}
Promise异步配置
导出内容为函数中返回Promsie形式,用于异步加载配置,可以动态加载文件。
module.exports = ()=>{
return new Promise((resolve,reject)=>{
resolve({
entry:"",
output:{},
})
})
}
内容解析
打包入口
打包入口是整个打包流程的起点。
根据项目的内容形式大致可以分为三种类型:
- 单页面单模块
- 第三方资源整合
- 多页面
单页面单模块
即为日常开发最常见的模块
module.exports = {
entry:"./index.js",
/* 等同于
entry:{
main:"./index.js"
}
*
*/
}
第三方资源整合
在打包时将第三方资源文件进行整合,打包后合并为一个文件。
module.exports = {
entry:{
app: './src/app.js',
vendors: ['react','react-dom','react-router'],
}
}
多页面
多页面项目需要将每个页面文件都进行打包
module.exports = {
entry:{
pageOne: './src/pageOne/index.js',
pageTwo: './src/pageTwo/index.js',
}
}
打包输出
output选项用来控制webpack如何输出编译后的文件模块。
虽然可以有多个entry,但是只能配置一个output,所以根据打包入口的区别可以分为两种类型。
单入口打包输出
module.exports = {
entry:"./index.js",
output:{
path: path.resolve(__dirname,"dist"),
filename:"bundle.js",
publicPath:"/"
}
}
打包后将以index.js为起点的项目打包成一个bundle.js文件。
多入口打包输出
与单入口打包输出不同的一点在于:单入口打包形式,打包前只有一个起点入口,打包后也只有一个打包输出文件;而多入口打包输出则是打包前有多个入口属性,打包后也应该有多个不同名称的输出文件。
所以,webpack提供了占位符来保证打包输出的文件名称差异。
module.exports = {
entry:{
home: "./index.js",
person: "./index2.js"
},
output:{
path: path.resolve(__dirname,"dist"),
filename:"[name].bundle.js",
}
}
如此webpack打包出来的文件就会按照入口文件的名称来进行分别打包生成两个不同的bundle文件
名词解释和名词分析
占位符
| 占位符 | 描述 |
|---|---|
| [hash] | 模块标识符的hash值 |
| [chunkhash] | chunk内容的hash值 |
| [name] | 模块名称 |
| [id] | 模块标识符 |
| [query] | 模块的query |
hash、chunkhash、contenthash的区别
- hash:是跟整个项目的构建相关,只要项目里有文件更改,整个项目构建的hash值都会更改,并且全部文件都共用相同的hash值。
- chunkhash:跟入口文件的构建有关,根据入口文件构建对应的chunk,生成每个chunk对应的hash;入口文件更改,对应chunk的hash值会更改。
- contenthash:跟文件内容本身相关,根据文件内容创建出唯一hash,也就是说文件内容更改,hash就更改。
Module、Chunk和Bundle的区别
- module:我们写的源码,无论是commonjs还是amdjs,都可以理解为一个个的module
- chunk:当我们写的module源文件传到webpack进行打包时,webpack会根据文件引用关系生成chunk文件,webpack 会对这些chunk文件进行一些操作
- bundle:webpack处理好chunk文件后,最后会输出bundle文件,这个bundle文件包含了经过加载和编译的最终源文件,所以它可以直接在浏览器中运行。
打包模式
根据项目打包后环境的不同,也产生了不同的打包模式。
module.exports = {
mode:"development" // production
}
// 相当于
module.exports = {
plugins:[
new UglifyJsPlugin(), // 压缩代码
new webpack.DefinePlugin({ // 定义环境变量
"process.env.NODE_ENV": JSON.stringify("development")
}),
]
}
DefinePlugin定义环境变量的时候要用JSON.stringify("production")而不是"producetion",把DefinePlugin这个插件理解为将代码里的所有process.env.NODE_ENV替换为字符串中的内容,但是我们代码中可能并没有定义production变量,因此会导致代码直接报错,所以我们需要通过JSON.stringify来包裹一层。
模式分类
模式也是webpack为了节省开发难度提高开发效率而提供的一个属性,可以根据该属性依据不同的环境来对打包设置进行修改。模式可暂时分为两种: 开发模式和生产模式。
- 开发模式: 开发状态下,要求打包出来的内容要对开发友好,便于代码调试以及实现浏览器实时更新。
- 生产模式:只需要关注打包的性能和生成更小体积的bundle。
loader解析
loader作用
loader用于对模块module的源码进行转换, 默认webpack只能识别commonjs代码,但是我们在代码中会引入比如vue、ts、less等文件,webpack就处理不过来了;
loader拓展了webpack处理多种文件类型的能力,将这些文件转换成浏览器能够渲染的js、css。
loader配置
module.rules允许我们配置多个loader,能够很清晰的看出当前文件类型应用了哪些loader
module:{
rules:[
{
test:/.js$/,
use:{
loader:"babel-loader",
options:{}
}
},
{
test:/.css$/,
use:[
{ loader:"style-loader" },
{ loader:"css-loader" }
]
}
]
}
- rules属性值是一个数组,每个数组对象表示了不同的匹配规则;
- test属性是一个正则表达式,匹配不同的文件后缀;
- use表示匹配了这个文件后调用什么loader来处理,当有多个loader的时候,use就需要用到数组。
loader特性
- 链式处理:多个loader支持链式传递,能够对资源进行流水线处理,上一个loader处理的返回值传递给下一个loader;
- 优先级:loader处理有一个优先级,从右到左,从下到上;
在上面demo中对css的处理就遵从了这个优先级,css-loader先处理,处理好了再给style-loader; 因此我们写loader的时候也要注意前后顺序。
plugins解析
plugins作用
plugins用于对webpack的功能进行扩展,更多的用处是在于对webpack触发的事件进行监听和处理。
二者区别
- loader:由于webpack只能识别js,loader相当于翻译官的角色,帮助webpack对其他类型的资源进行转译的预处理工作。
- plugins:plugins扩展了webpack的功能,在webpack运行时会广播很多事件,plugin可以监听这些事件,然后通过webpack提供的API来改变输出结果。
server服务
写完代码之后每次都是通过命令行来进行打包过程,非常的不方便。如果想要编辑完成后自动执行打包命令,那么就需要一个前端本地的服务器。
webpack-dev-server
webpack-dev-server就是这样一个简单的服务器,能够实现实时加载。
webpack-dev-server相当于将webpack和express服务器联系到了一起,可以通过对webpack.dev.config.js中devServer属性来设置服务器。
module.exports = {
devServer:{
// 启动端口
port:9000,
// 默认为lcoalhost
host:'0.0.0.0',
// 自动打开浏览器
open:false,
// 启用模块热更新
hot:true,
// 启用gzip压缩
compress:true
},
plugins: [
new webpack.HotModuleReplacementPlugin({})
]
}
通过命令行webpack-dev-server来启动服务器,启动后我们发现根目录并没有生成任何文件,因为webpack打包到了内存中,不生成文件的原因在于访问内存中的代码比访问文件中的代码更快。
热更新
(Hot Module Replacemen简称HMR)是在对代码进行修改并保存之后,webpack对代码重新打包,并且将新的模块发送到浏览器端,浏览器通过新的模块替换老的模块,这样就能在不刷新浏览器的前提下实现页面的更新。
原理是浏览器和webpack-dev-server之间通过一个webscokt进行连接,初始化的时候client端保存了一个打包后的hash值;每次更新时server监听文件改动,生成一个最新的hash值再次通过websocket推送给client端,client端对比两次hash值后向服务器发起请求返回更新后的模块文件进行替换。
source-map
通过webpack的模块封装,已经很难理解原来代码的含义了, 因此,我们需要将编译后的代码映射回源码;
devtool中不同的配置有不同的效果和速度,综合性能和品质后, 我们在开发环境使用cheap-module-eval-source-map,在生产环境使用source-map。
devtool: 'cheap-module-eval-source-map'