核心概念
- Entry:入口,Webpack执行构建的第一步将从Entry开始,可抽象成输入。
- Module:模块,在Webpack里一切皆模块,一个模块对应一个文件。Webpack会从配置的Entry开始递归找出所有依赖的模块。
- Chunk:代码块,一个Chunk由多个模块组合而成,用于代码合并与分割。
- Loader:模块转换器,用于将模块的原内容按照需求转换成新内容。
- Plugin:拓展插件,在wp构建流程中的特定时机注入拓展逻辑。
- Output:输出结果,在Webpack经过一系列处理并得出最终想要的代码后输出结果.
Entry
入口,Webpack执行构建的第一步将从Entry开始,可抽象成输入。
单页面单入口文件配置
module.exports = {
entry: './path/to/my/entry/file.js'
}
多页面多入口文件配置
module.exports = {
entry: {
one: './path/one.js',
two: './path/two.js'
},
plugins:[
new HtmlWebpackPlugin({
title:'第一个页面',
template: './pages/one.html', // 指定第一个页面的模板
filename: './pages/one.html', // 指定第一个页面打包完成后的文件名
chunks: ['one','two'] // 指定第一个页面要打包进入的js
}),
new HtmlWebpackPlugin({
title:'第二个页面',
template: './pages/two.html', // 指定第二个页面的模板
filename: './pages/two.html', // 指定第二个页面打包完成后的文件名
chunks: ['one','two'] // 指定第二个页面要打包进入的js
}),
]
}
Webpack构建单页面和多页面的实例
网上关于单页面和多页面的优点和缺点都有比较详细的描述,具体需要应用单页面还是多页面得根据项目的需求来选择。
一般单页面的配置都有相应的脚手架,比如vue-cli,集成了wp,减少了配置webpack的很多繁琐的工作
多页面应用现在没有脚手架,可以进行配置,具体的实例可参考这篇:
多页面想向单页面实现组件共用和封装,可以参考这篇进行配置,需要引入ejs模板,参考这篇:
Module
模块,在Webpack里一切皆模块,一个模块对应一个文件。Webpack会从配置的Entry开始递归找出所有依赖的模块。 ####配置Loader
-
test:匹配要进行转换的文件,使用正则表达式来匹配。
-
include: 只包含指定目录的文件进行转换,加快webpack的编译速度。
-
exclude: 排除某个文件的转换,加快编译和搜索速度。
-
use:对use后面加参数,比如进行缓存和压缩,也可以加快编译的速度。
module:{ rules: [ { //解析js文件 test: /\.js$/, // 用babel-loader转换js文件 // ?cacheDirectory表示传给babel-loader的参数,用于缓存babel的编译结果,加快重新编译的速度 use: ['babel-loader?cacheDirectory'] // 只命中src目录里的Js文件,加快webpack的编译速度 include: path.resolve(_dirname,'src') }, { //解析Scss文件 test: /\.scss$/, // 使用一组loader去处理scss文件 // 处理顺序为从后到前,即先交给scss-loader处理,再将结果交给css-loader,最后交给style-loader use: ['style-loader','css-loader','sass-loader'], // 排除node_modules目录下的文件 exclude: path.resolve(__dirname,'node_modules') }, { // 对非文本文件采用file-loader加载 test: /\.(gif|png|jpe?g|eot|woff|ttf|svg|pdf)$/, use: ['file-loader'] } ] }
在上面的例子中,test、include、exclude只传入了一个字符串或正则,其实他们也支持数组类型
{
test:[
/\.jsx?$/,
/\.tsx?$/
],
include:[
path.resolve(__dirname,'src'),
path.resolve(__dirname,'tests')
],
exclude:[
path.resolve(__dirname, 'node_modiles'),
path.resolve(__dirname, 'bower_modules')
]
}
noParse
noPaese配置项可以让Webpack忽略对部分没采用模块化的文件的递归解析和处理,这样做的好处是能提高构建性能。 原因是一些库如jQuery,ChartJS庞大又没有采用模块化的标准,让Webpack去解析这些文件既耗时又没有意义。
noParse: /jquery|chartjs/
使用函数,从Webpack3.0.0开始支持
noParse: (content) => {
//content代表一个模块的文件路径
//返回true或false
return /jquery|chartjs/.test(content)
}
parse
因为Webpack是以模块化的js文件为入口的,所以内置了对模块化js的解析功能,支持AMD,CommonJS,SystemJS,ES6 parse属性可以更细粒度地配置哪些模块语法被解析,哪些不被解析。
同noParse配置项的区别在于.parser可以精确到语法层面,而noParse只能控制哪些文件不被解析。
parse的使用方法如下:
modele:{
rules:[
test: /\.js$/,
use: ['babel-loader'],
parser: {
amd: false, //禁用AMD
commonjs: false, // 禁用CommonJS
system: false, // 禁用 SystemJS
harmony: false, // 禁用ES6 import/export
requireInclude: false, // 禁用requireInclude
requireEnsure: false, // 禁用requireEnsure
requireContext: false, // 禁用requireContext
browserify: false, // 禁用browserify
requireJs: false // 禁用requirejs
}
]
}
Loader
模块转换器,用于将模块的原内容按照需求转换成新内容。
-
Loader的执行顺序是由后到前的。
-
每Loader都可以通过URL querystring的方式传入参数,例如 css-loader?minimize中的minimize告诉css-loader要开启css压缩。
-
向loader中传入属性的方式除了可以通过querystring实现,还可以通过object实现。
user:[ 'style-loader',{ loader:'css-loader', options:{ minimize:true } } ]
在Loader需要传入很多参数时,我们还可以通过一个Object来描述,例如在上面的babel-loader配置中有如下代码
use:[
{
loader:'babel-loader',
options:{
cacheDirectory:true
},
// enforce: 'post'的含义是将该loader的执行顺序放到最后
// enforce: 'pre'的含义是将loader的执行顺序放到最前面
}
]
Plugin
拓展插件,在Webpack构建流程中的特定时机注入拓展逻辑。
Plugin的配置很简单,plugins的配置项接受一个数组,数组里的每一项都是一个要使用的Plugin的实例,Plugin需要的参数通过构造函数传入。
const CommonsChunkPlugin = require('webpack/lib/optimize/CommonsChunkPlugin')
module.exports = {
plugins: [
// 所有页面都会用到的公共代码被提取到common代码块中
new CommonsChunkPlugin({
name: 'common',
chunks: ['a','b']
})
]
}
将css文件单独提取出来
const ExtractTextPluginn = require('extract-text-webpack-plugin')
module:{
reules:[
{
textL /\.css$/,
loaders: ExtractTextPlugin.extract({
use:[
'css-loader'
]
}),
}
]
},
plugins: [
new ExtractTextPlugin({
//从.js文件中提取出来的.css文件的名称
filename: `[name]_[contenthash:8].css`
})
]
Resolve
webpack在启动后会从配置的入口模块出发找出所有依赖的模块,resolve配置webpack如何查找模块所对应的文件
alias
resolve.alias配置项通过别名来将原导入路径映射成一个新的导入路径。例如使用一下配置:
resolve:{
alias:{
components: './src/components'
}
}
当通过**import Button from 'components/button'**导入时
实际上被alias等价替换成了import Button from './src/components/button'
extensions
在导入语句没带文件后缀时,Webpack会自动带上后缀后去尝试访问文件是否存在。
resolve.extensions用于配置在尝试过程中用到的后缀列表,默认是:
extensionsL ['.js','.json']
也就是说,当遇到require('./data')这样的导入语句时,Webpack会先寻找./data.js文件,如果该文件不存在,就去找./data.json文件,如果还是找不到就报错。
escriptionFiles
如果resolve.enforceExtension被配置为true,则所有导入语句都必须带文件后缀,例如开启前import './foo'能正常工作,开启后就必须写成**import './foo.js' **
Devtool
devltool配置Webpack如何生成source map,默认值是false,即不生成source map,若想构建出的代码生成source map以方便调试,则可以这样配置:
module.export = {
devtool: 'source-map'
}
开启source-map会方便我们开发中的调试,方便我们定位到具体的代码问题,当也会影响一下相关的构建性能问题,所有要做出多配置文件,开发环境配置和生产环境配置
source-map模式下会输出质量最高且最详细的Source Map,这会造成构建速度缓慢,特别是在开发过程中需要频繁修改时会增加等待时间
在Source-Map模式下会将Source Map暴露,若构建发布到线上的代码的source map暴力,就等同于源码被泄露
为了解决以上两个问题,可以这样做,如下所述
-
在开发环境下devtool设置成cheap-module-eval-source-map,因为生成这种source map的速度最快,能加速构建。由于在开发环境下不会做代码压缩,所以在source map的即使没有列信息,也不会影响断电调试.
-
在生产环境下将devtool设置成hidden-source-map,意思是生成最详细的source map,但不会将source map暴露出去。由于生产环境下会做代码压缩,一个js文件只有一行,所以需要列信息。
在生产环境下通常不会将Source Map上传到http服务器让用户获取,而是上传到JavaScript错误收集系统,在错误收集系统上根据Source Map和收集到的JavaScript运行错误队栈,计算出错误所在源码的位置。
不要在生产环境下使用inline模式的Source Map,因为这会使JavaSctipt文件变的很大,而且会泄露源码。
Externals
External用来告诉Webpack要构建的代码中使用了哪些不用被打包的模块,也就是说这些模板是外部环境提供的,Webpack在打包时可以忽略它们
通过externals可以告诉Webpack在js运行环境中已经内置了哪些全局变量,不用将这些全局变量打包到代码中而是直接使用它们。
moudle.export = {
externals: {
//将导入语句里的jquery替换成运行环境里的全局变量jQuery
jquery: 'jQuery'
}
}
Webpack优化
-
优化开发体验
优化开发体验的目的是提升开发效率,减少每次构建的耗时 1. 优化构建速度 2. 优化使用体验,通过自动化手段完成一些重复的工资哦,让我们专注于解决问题本身。
-
优化输出质量
呈现用户体验更好的网页,减少首屏加载时间,提升性能流畅度。 1. 缩小文件的搜索范围 2. 优化Loader的配置,通过include去命中 只有哪些文件去处理,通过exclude去去除哪些文件不需要处理,比如node_module 3. 优化resolve.modules配置 resolve.modules用于配置Webpack去哪些目录下寻找第三方模块。 resolve.modelus的默认值是['node_modules']含义是先去当前目录的./node_modules目录下去找我们想找的模块,如果没找到,就去上一级目录../node_modules中找,再没有就去../../node_modules中找,以此类推 当安装的第三方模块都放在项目根目录的./node_modules目录下时,就没有必要按照默认的方式去一层层的寻找,可以指明存放第三方模块的绝对路径,以减少寻找,配置如下: module.exports = { resolve: { // 使用绝对路径指明第三方模块存放的位置,以减少搜索步骤 // 其中,__dirname表示当前工作目录,也就是项目根目录 modules: [path.resolve(__dirname,'node_modules')] } } 4.优化resolve.alias配置,跳过递归解析操作 5.优化resolve.extensions配置减少后缀,后缀要尽可能少,提升速度 6.优化noParse配置
使用DllPlugin
包含大量复用模块的动态链接库只需被编译一次,在之后的构建过程中被动态链接库包含的模块将不会被重新编译,而是直接使用动态链接库中的代码。由于动态链接库中大多数包含的是常用的第三方模块,例如react,react-dom,所以只要不升级这些模块的版本,动态链接库就不用重新编译。
Webpack已经内置了对动态链接库的支持,需要通过以下两个内置的额插件接入。
- DllPlugin插件:用于打包粗一个个单独的鼎泰链接库文件.
- DllReferecePlugin插件:用于在主要的配置文件中引入DllPlugin插件打包好的动态链接库文件
HappyPack
运行在Node.js之上的Webpack是单线程模型,Happy Pack将任务分解给多个子进程去并发执行,子进程处理完后再讲结果发送给主进程,由于js是单线程模型,所以想要发挥多核cpu的功能,就只能通过多进程实现,而无法通过多线程实现。
整个Webpack构建流程中,最耗时的流程可能就是loader对文件的转换操作了,因为要转换的文件数据量巨大,而且这些转换操作都只能一个一个地处理。HappyPack的核心原理就是将这部分任务分解到多个进程中去并行处理,从而减少总的构事件。
ParallelUglifyPlugin
原本会使用Uglifyjs去一个一个压缩再输出
Paralleuglifyplugin会开启多个子线程,将对多个文件的压缩工作分配给多个子进程完成,每个子进程其实还是通过uglify去压缩代码,但是变成了并行执行,所以Paralleuglifyplugin能更快地完成对多个文件的压缩工作
文件监听
文件监听是发现源码发生变化时,自动重新构建出新的输出文件.
让Webpack开启监听模式,有如下两种方式。
- 在配置文件webpack.config.js中设置watch:true
- 在执行启动webpack的命令时带上--watch参数,完成的命令是webpack--watch
优化文件的性能
忽略node_modules
module.export = {
watchOptions:{
ignored: /node_modules/
}
}
自动刷新浏览器
开启模块热替换
##区分环境 区分开发环境和生产环境,指定对用的不同调试模式Source Map,是否开启压缩,是否提取公共代码等
提取公共代码
相同的资源被重复加载,浪费用户的流量和服务器成本
页面需要加载的资源太大,会导致网页首屏加载缓慢,影响用户体验。
webpackchunkplugin
代码分割,按需加载。
如何按需加载
-
在为单页应用做按需加载优化时,一般采用以下原则
-
将整个网站划分成一个个小功能,再按照每个功能的相关程度将它们分成几类
-
将每一类合并为一个chunk,按需加载对应的chunk.
-
不要按需加载用户首次打开网站是需要看到的画面所对应的功能,将其放到执行入口所在的Chunk中,以减少用户能感知的网页加载时间。