- webpack是构建在node.js基础之上的
- npm:node package manage(node包管理工具)
- 在终端输入pwd回车可以查看当前目录
- 可以在全局安装webpack,也可以在本地的工作目录下安装webpack,安装webpack需要安装两个包,一个是webpack主包,另一个是webpack-cli(表示我们可以在命令行里边执行webpack命令)
- 全局安装webpack:npm install webpack webpack-cli --global
不推荐全局安装webpack,因为这样会使项目的webpack锁定到某个版本里,并且在使用不同的webpack版本的项目里边可能会导致构建失败,如果是一个团队协作的项目,你的小伙伴不知道是在全局里安装webpack,构建也会有问题,因此推荐在本地的工作目录下去安装webpack。 - 在安装本地的webpack之前,需要安装一个npm包管理的配置文件,执行一下npm init -y会在本地工作目录下产生一个package.json文件。
工作目录安装webpack:npm init -y
npm install webpack webpack-cli --save-dev - 在项目的根目录下创建webpack.config.js文件,由于这个文件是在nodejs里面运行的,因此我们定义模块的时候得使用nodejs的CommonJs模块,使用module.exports来去定义,把他的值赋值为一个对象,这是一个配置对象
- webpack就像一条生产线,他要经过一系列的处理流程以后才能将源文件(我么称之为入口文件)转化成输出的结果,入口文件的js还可以依赖于其它的js,被依赖的js模块可能还依赖其它的js模块,并且这个js也可能会引用css文件,这个css文件的引入需要使用webpack loaders(加载器),webpack会把这个依赖的关系都记录下来,然后交给webpack编译器,webpack编译器经过加工以后会生成目标文件,比如css和js文件,webpack编译的过程需要应用一些工具来帮忙,这些工具可以帮助webpack来执行一些特定的任务,比如打包优化,资源管理等,这些工具就是我们所谓的plugins插件。
- 引入node的path模块可以用path.resolve(_dirname,'./src/js/index.js')获取以当前目录下文件的路径
module.exports包含的一些比较常用的配置说明:
const HtmlWebpackPlugin = require('html-webpack-plugin')
// import HtmlWebpackPlugin from 'html-webpack-plugin'//与上面一行等价
const path = require('path') // path模块主要是为了解决绝对路径问题的,就是他要找的这个文件必须得有一个绝对路径,但你自己写的绝对路径肯定就很麻烦,所以我们可以使用path下面的resolve()方法
const MiniClassExtractPlugin = require('mini-css-extract-plugin')
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin')
module.exports = {
mode: 'production', // webpack编译的模式:development, production
entry: path.resolve(__dirname,'./src/js/index.js'), // 单页面,js文件的地址
// entry:{ // 多页面
// index: path.resolve(__dirname,'./src/js/index.js'),// 点.代表根目录
// jquery: path.resolve(__dirname,'./src/js/jquery.js')
// },
// entry:{// 多页面,公共部分抽离出来
// index:{
// import: './src/js/index.js',
// dependOn: 'shared'
// },
// another: {
// import: './src/js/another.js',
// dependOn: 'shared'
// },
// shared: 'lodash'
// },
output: {//输出,打包
path: path.resolve(__dirname + '/dist'),//打包到哪个文件夹下,加+代表找一个文件夹
filename: 'js/index.js',//哪个文件加下面的哪个js,dist文件夹下面的js文件夹下面的index.js文件//指定输出文件的文件名
// filename:'[name].bundle'.js,//入口文件有多个时,为了避免输出文件名重复
// publicPath:'./',//基本路径
// outputDir:'dist',//输出文件目录
clean:true,//清除没有用的dist文件
assetModuleFilename:'images/test.png',// 资源模块的文件名或者路径
// assetModuleFilename:'images/[contenthash][ext]',//contenthash:利用哈希值当文件名;ext:文件原本的扩展名
},//输出,打包
devtool:'inline-source-map',//生产环境不配置source map
watch:true, //实现编译时自动监测文件变化的功能,需要手动刷新浏览器查看更改后的效果
plugins:[ // 装的是实例化对象
new HtmlWebpackPlugin({
filename:'index.html',//打包后的filename是什么
template:path.resolve(__dirname,'./src/index.html'),//打包的是谁
chunks:['index'],
// chunks:['jquery','index'],//入口文件是哪一个,可以写多个入口文件
excludeChunks:['node_modules'] // 需要排除哪个文件
}),
new MiniClassExtractPlugin({
filename: 'styles/[contenthash].css',// 打包的css放到styles文件夹下面(styles文件夹会自己创建)
})
],
module: {//配置规则
rules: [{
test: /\.png$/,
type: 'asset/resource',//资源类型
generator:{ //等价于上面的assetModuleFilename,generator的优先级比assetModuleFilename高
// filename: 'images/test.png',
filename: 'images/[contenthash][ext]',
}
},{
// 在编译转换的时候要排除node-modules文件夹里面的js文件
// package.json里面安装的那些依赖怎么用,设置规则
test: /\.js$/,//匹配一个什么文件,后缀名以什么结尾的所有文件
// loader: 'babel-loader',//要使用什么样的loader
use:{
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env'],//babel的预设
plugins:[[
'@babel/plugin-transform-runtime'
]]
}
},
exclude: /node_modules/
// exclude: path.resolve(__dirname,'node_modules'),//这个规则排除哪些文件,不对哪些文件生效,例如node-module里面的不要打包
},
// {
// test: /\.css $/,
// use: [
// 'style.loader',
// 'css-loader'
// ],//如果是多个loader就不能用loader了,用use,数组
// //webpack处理数组的时候是倒着处理的,从下往上处理:先处理css-loader,再处理style-loader(横着写的话是从右到左处理的)
// },
{
test: /\.(css|sass)$/,
use: [
MiniClassExtractPlugin.loader,//把css放置到页面上
'css-loader',//用于打包没有问题识别css文件
'sass/loader'
]
},{
test: /\.tpl$/,
loader:'ejs.loader'
},{
//加载字体资源
test: /\.(woff|woff2|eot|ttf|otf)$/,
type: 'asset/resource'
}]
},
optimization:{//优化的配置
minimizer:[//压缩代码的配置,还需要将mode得值改为production
new CssMinimizerPlugin()
],
splitChunks:{
// 缓存组
cachGroups:{
vendor:{
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
chunks: 'all'
}
}
}
},
resolve:{
alias: {
'@': path.resolve(__dirname,'./src')
},
extensions:['.json','.js','.vue'],//同名文件不同类型,先匹配json文件,再匹配js文件,再匹配vue文件
},
devServer:{//具有实时重新加载的功能,当我们的页面修改了,编译以后,浏览器会侦听到文件的修改来自动刷新(自动刷新浏览器展示更改后的效果)
open:true,//当我在启动项目的时候自动打开浏览器
host:'localhost',//主机
port:'3300',//端口
static:'./dist',//devServer指向的物理路径
compress:'true',//增加一个压缩的头部
historyApiFallback:true,// 找不到页面的时候跳转到首页
hot:true,// 启动模块热更新
headers:{
'X-Access-Token':'abc123'
}
}
}
其实webpack-dev-server真正的没有输出任何的物理文件,他把输出的打包以后的文件放到了内存里,比如我们把打包生成的dist文件夹删除,再重新去浏览器访问,并不会发生任何问题,或者关闭服务,再重新启动服务,在浏览器里还是可以正常访问的,修改代码内容也会在浏览器里自动刷新,这样不但我们的开发效率提高了,连webpack的编译效率也同时提高了。
资源模块
- webpack最出色的功能之一就是除了可以引入js还可以是用内置的资源模块,使用asset modules来引入任何其它的类型资源,asset modules是一种模块类型,允许我们使用webpack来打包其它的资源文件(字体文件,图标文件等),资源模块的类型称为asset module type,会通过四种新的类型模块来替换所有的loader,这四种资源模块类型分别为:
- asset/resource:会发送一个单独的文件并导出URL。
- asset/inline:会导出一个资源的Data URL(比如可以把一个svg的图片转成一个base64的字符串,在代码里面可以直接引用这个字符串)。
- asset/source:会导出资源的源代码。
- asset:会在导出一个Data URL和发送一个单独的文件之间自动进行选择。(选择的依据事资源的大小,默认的资源大小事8kb,我们可以在定义资源类型的时候可以在parser对象下设置一个dataUrl condition这个属性,然后在这个属性下面设置一个MaxSize属性,就可以改变资源的默认大小了,资源小的时候选择 asset/inline类型,资源大的时候选择asset/resource类型)
loader
- 安装css的loader:npm install class-loader -D;npm install style-loader -D;npm install sass-loader -D
D:安装在开发环境中
webpack处理数组的时候是倒着处理的,从下往上,或者从右往左,所以css的loader要先解析css-loader,再解析style-loader,因为css-loader是用于识别css文件使打包没有问题,style-loader用于把css文件放置到页面的hear里
webpack支持loader的链式调用,链式的每一个loader都可以对我们的源进行转换,而且转换是逆序的,第一个loader(css-loader)会将转换后的结果或者源代传递给下一个loader(style-loader),最后webpack希望最后一个loader返回一个js。
sass-loader要放在最后,因为要用sass-loader去解析我们的css文件,然后再把解析好的文件交给css-loader,然后css-loader再把结果通过style-loader放置到页面的标签的head里。
抽离和压缩css
- 通过loader加载的css是跟html在一起的,如果想把style标签里的代码放置到一个单独的文件里,通过link标签去加载它,需要用到插件mini-css-extract-plugin:npm install mini-css-extract-plugin -D
mini-css-extract-plugin是基于webpack5构建的,想使用的话必须在webpack5的环境下使用
需要压缩css代码需要安装css-minimizer-webpack-plugin插件:npm install css-minimizer-webpack-plugin -D - inner.HTML:识别html标签 W3C标准 保留空格和换行的;inner.Text: 不识别html标签 (非标准),去除空格和换行
小结:除了使用四种资源模块来引入外部资源以外,还可以使用loader来引入几大类型的文件,loader可以让webpack去处理其它类型的文件,并且将他们转化为有效的模块供应用程序使用
babel-loader
- balel-loader用于将ES6转换成低版本的浏览器能够识别的ES代码,需要安装三个包:
npm install -D babel-loader @babel/core @babel/preset-env
- babel-loader:在webpack里应用babel解析ES6的桥梁
- @babel/core:babel的核心模块
- @babel/preset-env:babel预设,一组babel插件的集合
预设就是一组插件的集合,理论上我们每一个babel的解析都需要一个babel的插件,preset-env省的我们安装很多插件,她会把一组插件整合到一个文件里。(less-loader编译less文件,sass-loader编译sass文件,xml-loader编译xml文件,es6,es7文件等也需要编译)
- regeneratorRuntime是webpack打包生成的全局辅助函数,由babel生成,用于兼容async/await的语法,使用如下:npm install @babel/runtime -D;npm install @babel/plugin-transform-runtime -D
@babel/plugin-transform-runtime这个插件会在需要regeneratorRuntime的地方自动require打包,然后编译的时候就会需要它。
小结:webpack天生可以自带加载js模块的功能,但是它只能做js模块化的打包,并不能转化js里的代码,比如将es6转化为es5,有时候我们的代码能正常运行,那纯靠浏览器解析,如果浏览器的版本比较低的话,运行的时候可能会发生错误,因此我们写代码的时候是需要babel来进行转化的,babel和webpack的结合就需要一个babel-loader。
代码分离
- 代码分离是webpack最引人注目的特性之一,这个特性能够把代码分离到不同的bundle中,所以bundle就是我们打包分离出来的文件,然后我们把这些文件按需加载,或者是并行加载,代码分离可以用于获取更小的bundle,以及控制资源加载的优先级,如果我们使用合理,会极大的影响加载时间,常用的代码分离方法有三种,分别是:
- 配置入口起点:我们可以使用entry来配置手动的分离代码,这种方式有个问题,就是如果是多个入口,那么这些多个入口共享的文件会分别在每个包里边去重复打包(如果我们在入口的chunk之间包含一些重复的代码,那么这些重复的模块会被引入到各自得bundle中)。
- 使用Entry dependencies或者SplitChunsPlugin去重和分离代码:防止重复打包。
- 动态导入:通过模块的内联函数import调用来分离代码。
缓存
- 将第三方库(例如lodash)提取到单独的vendor chunk文件中,是比较推荐的做法,这是因为我们很少像本地的源代码那样频繁修改,因此通过以上步骤,利用浏览器的长效缓存机制,命中缓存来消除请求,并减少向server获取资源,同时还能保证浏览器代码和服务器代码版本一致,在optimization.splitChunks添加如下cacheGroups参数并构建。
当我们的项目部署到服务器上的时候,浏览器加载完我们服务器上的文件,会缓存我们打包好的模块,如果我们修改了业务代码,文件名如果没有变,浏览器会使用用户本地缓存的内容,就获取不到新的内容了,因此我们得通过修改输出文件的文件名来解决这个问题,我们使用的是可替换模板字符串的方法来定义了contenthash,他能实现只要文件的内容不变哈希的字符串就不变;除了缓存业务代码,第三方的代码同样需要缓存,通过修改optimization.splitChunks这个属性来实现,我们可以定义一个cachGroups缓存组,将我们的业务代码引用的第三方的文件都打包到一个文件中,在浏览器中缓存,由于这类文件不频繁更新,所以我们可以提高首屏的打开速度节省网络流量。
开发环境没有必要设置缓存,生产环境需要设置公共路径。
模块热更新
- 模块热替换“模块热替换功能会在应用程序运行过程中,替换、添加或删除模块,而无需重新加载整个页面,启用webpack的热模块替换特性,需要配置devServer.hot参数
webpack解析原理
- webpack通过Resolvers实现了模块之间的依赖和引用。所引用的模块可以是来自应用程序的代码,也可以是第三方库。resolver帮助webpack从每个require或者import语句中,找到需要引入到bundle中的模块代码,当打包模块时,webpack使用enhanced-resolve来解析文件路径。