webpack是前端打包工具,目的是不需要开发者再去手动打包了,而是通过一系列配置,生成包。
好处是生成的代码运行高效,体积小,对浏览器有一定兼容性(需配置),混淆源码等。
缺点是对初学者这TM是啥?变秃也变强了?
对一个初学还健忘的人来说,还是多多记录一下来得实在。下面开始。
学习视频:尚硅谷2020最新版Webpack5实战教程(从入门到精通)
webpack五个核心概念:
- Entry:入口,指定webpack以哪个文件开始打包,分析构建内部依赖图。
- Output:输出,指定webpack打包后放哪,叫啥名。
- Loader:就是module,引用第三方模块让webpack能去处理非js文件,比如把less、ts转成浏览器能识别的css、js,图片转码,样式兼容等轻量级任务。
- Plugins:引入第三方插件让webpack执行更复杂的操作,比如压缩代码。
- Mode:分为development开发者模式、production生产者模式。
webpack4新增0CJS概念,即0配置,不再必需webpack.config.js,但默认入口为./src/index.js,出口./dist。
entry:
一般是单个js文件,也可以是多个。
entry:{
home: '.src/home.js',
about: '.src/about.js',
other: '.src/other.js',
}
output:
包含属性filename(标识输出js文件名称),path(输出的位置,最好用绝对路径)
const { resolve } = require('path')
module.exports = {
output:{
filename: 'build.js',
path: resolve(__dirname, 'build')
}
}
loader:
需要注意在文件中根属性是module,且引用的loader是自下而上,自右向左顺序执行的。
举个例子:
use: ['style-loader', 'css-loader', 'less-loader'],
//或
//use: [
// 'style-loader',
// 'css-loader',
// 'less-loader'
//],
执行顺序是less-loader,css-loader,style-loader。
下面开始列举几个基础loader
1. css-loader
打包css文件用的,目的是将css文件变成commonjs加载到js中,内容是样式字符串,注意是.css文件中的样式
2. style-loader一般是配合css-loader使用,目的是创建style标签,将js中的样式资源添加到header中,注意是js中的样式
打包css文件用法:
module:{
rules:[
{
test: /\.css$/,
use: ['style-loader','css-loader']
}
]
}
3. less-loader
将less样式转化为css样式,sass-loader同理。
打包less文件用法:
module:{
rules:[
{
test: /\.less$/,
use: ['style-loader','css-loader','less-loader']
}
]
}
4. url-loader
打包样式中引用的图片资源,需要依赖file-loader,还可以对图片文件进行压缩(转base64码),可通过limit、name属性设置压缩范围和压缩文件名。
打包图片用法:
module:{
rules:[
{
test: /\.(png|jpe?g|gif)$/,
loader: 'url-loader', // 当只引用一个依赖时可用loader
options:{
limit: 8*1024, // 8kb以下文件会转base64存放到js中,适用于小文件,减少文件数量,提高效率。
name: '[hash:10].[ext]', // 对压缩后的文件重命名,默认生成32位hash值名称,设置截取10位,ext表示原文件扩展名
// outputPath: '' // 可以指定output文件夹下的文件层级
}
}
]
}
5. html-loader
打包html中引用的图片资源,但需要额外注意,url-loader是用ES6 module去解析,而html-loader是通过commonjs去引入的,所以会冲突报错,需要将url-loader中的ES6 module关闭,统一成commonjs。
用法:
module:{
rules:[
{
test: /\.(png|jpe?g|gif)$/,
loader: 'url-loader', // 当只引用一个依赖时可用loader
options:{
limit: 8*1024, // 8kb以下文件会转base64存放到js中,适用于小文件,减少文件数量,提高效率。
name: '[hash:10].[ext]', // 对压缩后的文件重命名,默认生成32位hash值名称,设置截取10位,ext表示原文件扩展名
esModule: false,
}
},
{
test: /\.html$/,
loader: 'html-loader', // 当只引用一个依赖时可用loader
}
]
}
6. file-loader
一般用于上面文件外的其他文件打包
用法:
module:{
rules:[
{
exclude: /\.(html|css|less|png|jpe?g|gif)$/,
loader: 'file-loader', // 当只引用一个依赖时可用loader
options:{
name: '[hash:10].[ext]'
}
}
]
}
plugins:
需要先用nodejs方式引入插件包,才能使用(loader只需要安装依赖,不需要引入)
下面开始列举几个基础plugins
1. htmlwebpackplugin
打包html主页面的
用法:
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
plugins:[
new HtmlWebpackPlugin({
template: './public/index.html'
})
]
}
2. DefinePlugin
相当于在webpack中定义一些全局变量,这样在打包时,都会动态读取这些变量,方便维护。
用法:
// webpack.config.js
const webpack = require('webpack')
module.exports = {
plugins:[
new webpack.DefinePlugin({
MY_URL: 'www.baidu.com' // 自定义
})
]
}
//业务文件中
console.log(MY_URL) // 直接使用
3. MiniCssExtractPlugin
按上面loader对css的打包,可以知道我们把css合到js中了,如果想分离出来,需要用到此插件。需要注意的是,如果使用此插件,对css的loader解析需要相应修改,用此插件的loader代替style-loader。
用法:
const MiniCssExtractPlugin = require('mini-css-extract-plugin)
module:{
rules:[
{
test: /\.css$/,
use: [
// 'style-loader',
MiniCssExtractPlugin.loader, // 用自带loader代替style-loader,这样会独立成一个css文件,而不是加载到header中
'css-loader'
]
}
],
plugins:[
new MiniCssExtractPlugin({
filename: 'build.css' // 可重命名,默认main.css
})
]
}
其他属性:
1.devServer
开发服务器,主要用于开发模式,通过配置devServer可自动编译、自动打开默认浏览器、自动刷新页面。只会在内存中打包,不会有任何输出。运行指令为 npm
用法:
const { resolve } = require('path')
module.exports = {
devServer: {
contentBase: resolve(__dirname, 'build'), // 选择编译后的目录,而不是原代码
compress: true, // 启动gzip压缩
port: 8090, // 服务的端口号
open: false, // 设置true后,每次运行自动打开默认浏览器
}
}
css兼容性处理
需要用到postcss里面的postcss-loader和postcss-preset-env,其中postcss-preset-env用来帮助postcss-loader找到package.json下的browserslist配置。默认情况下,browserslist读的是生产环境下的配置,而且它不是读的webpack.config下的mode,而是读的node中的环境变量,如果需要设置,需在webpack.config module.exports前,设置process.env.NODE_ENV = 'development'
用法:
// webpack.config.js
module:{
rules:[
{
test: /\.css$/,
use: [
'style-loader',
'css-loader',
{
loader: 'postcss-loader',
options:{
ident: 'postcss',
plugins: ()=>{
require('postcss-preset-env')()
}
}
}
]
}
]
}
//package.json
{
"browserslist": {
"development":[
"last chrome 1 versions", // 兼容chrome上一个版本
"last firefox 1 versions",
"last safari 1 versions",
],
"production":[
"> 0.2%", // 兼容99.8%的浏览器
"not dead", // 不要已经死掉的浏览器
"not op_mini all" // 不要op_mini浏览器?
]
}
}
//{
// "browserslist": [
// "> 1%", // 兼容99%的浏览器
// "last 2 versions", // 兼容上2个版本
// "not dead"
// ]
//}
css压缩
OptimizeCssAssetsWebpackPlugin
用法:
const OtimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin')
module.exports = {
plugins:[
new OtimizeCssAssetsWebpackPlugin()
]
}
JS语法检查
团队协作中,统一各开发者使用规范,需要用到eslint-loader,具体的参考下面的随笔吧。
如果要检查打包后并自动修复的话,在options中设置fix:true。enforce: 'pre',设置优先执行。
eslint-loader要优先于babe-loader
JS兼容
使用babel-loader,需要用到@babel/core @babel/preset-env,分为三种方式:
1.@babel/preset-env只能处理基本js兼容,promise等不能转换
2.@babel/polyfill做全部兼容性处理,是将所有兼容性代码全部引入,体积较大
3.core-js按需加载
用法:
modules:{
rules:[
{
test:/\.js$/,
exclude:/node_modules/,
loader: 'babel-loader',
options: {
// 预设babel做怎样的兼容处理
presets: [
[
'@babel/preset-env',
{
//按需加载
useBuiltIns: 'usage',
// 指定下载的core-js版本
corejs:{
version: 3
},
//指定兼容性做到哪个版本浏览器
targets:{
chrome: '60',
firefox: '60',
ie: '9',
safari: '10',
edge: '17',
}
}
]
]
}
}
]
}
html压缩
在htmlwebpackplugin中移除空格和移除注释
用法:
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
plugins:[
new HtmlWebpackPlugin({
template: './public/index.html',
minify:{
collapseWhitespace: true, //移除空格
removeComments: true, // 移除注释
removeAttributeQuotes: true // 移除双引号
}
})
]
}
source-map
一种提供源代码到构建后代码映射的技术,用于开发环境下调试代码。一句话就是调试的时候能追踪到代码出错的位置并提示。
支持多种形式: [inline-|hidden-|eval-][nosources-][cheap-[module-]]source-map,且可以组合
打包后的文件,根据形式不同,会在打包代码内联或外部生成映射代码,内联构建速度更快。
source-map:外部,提示错误代码准确信息和源代码错误位置
inline-source-map:内联,只生成一个内联source-map,提示错误代码准确信息和源代码错误位置
hidden-source-map:外部,提示错误代码准确信息,不能提示源代码错误位置,只能提示构建后代码错误位置
eval-source-map:内联,每个文件后都生成对应source-map,提示错误代码准确信息和源代码错误位置,位置带hash文件名
nosources-source-map:外部,提示错误代码准确信息,但没有任何原代码信息
cheap-source-map:外部,提示错误代码准确信息和源代码错误位置,但只能精确到行,若多个代码在一行,没法具体到哪一块代码
cheap-module-source-map:外部,同cheap,module会将loader的source-map也加进来
在开发环境下,要求速度快,方便调试
速度快(eval>inline>cheap>...),最优:
eval-cheap-source-map
eval-source-map
方便调试,最优:
source-map
cheap-modules-source-map
cheap-source-map
生产环境下,代码要不要隐藏?调试是否友好?由于内联会使包体积增大,因此推荐外部
要隐藏的话,选hidden-source-map(只隐藏源码,会提示错误信息) / nosources-source-map(全部隐藏)
调试:source-map / cheap-module-source-map
oneOf
用于loader-rules中,表示对oneOf下的loader,只匹配其中一个,相当于else if,如果走其中一个,其他就不用走了
假设css文件,只需要走css的处理loader,不需要走less、sass、js等loader。
用法:
modules:{
rules:[
// 在rules下的,默认每来一个文件都会去匹配
{
test:/\.js$/,
...
},
// 以下loader只会匹配一个,不能有两个loader处理同一类型文件
{
oneOf: [
{
test:/\.css$/
...
},
{
test:/\.js$/
...
}
]
}
]
}
//相当于
if(){}
if(){}else if(){} else if(){}
缓存
缓存包括对js编译的缓存和对项目资源的缓存。
生产环境下,对babel进行缓存,由于babel会对js代码编译,若只修改其中一个,无需重新编译其他js,直接读缓存即可。
用法:
modules:{
rules:[
{
test:/\.js$/,
exclude:/node_modules/,
loader: 'babel-loader',
options: {
// 预设babel做怎样的兼容处理
presets: [
...
],
// 开启babel缓存,第二次构建时会读取之前缓存
cacheDirectory: true
}
}
]
}
项目资源即打包后的文件,给文件名拼接一个唯一值,可避免浏览器因缓存不请求构建代码的问题,分为hash、chunkhash、contenthash
一种是添加hash,有个缺点:css、js共用一个hash值,当重新打包时,导致所有缓存失效,但我可能只修改了其中一个文件
如:
module.exports = {
output:{
filename: 'build.[hash:10].js'
...
},
plugins: [
new MiniCssWebpackPlugin({
filename: 'css/built.[hash:10].css'
})
]
}
另一种是chunkhash,根据chunk生成的hash值,如果打包来源于同一个chunk,那么hash值一样。chunk指代码块,当一个index.js作为入口,引用了其他文件时,这些文件都属于一个chunk。代码参考上图,hash换成chunkhash。
最后一种是contenthash,根据文件内容生成hash值,不同文件的hash值不一样。代码参考上图,hash换成contenthash。使用contenthash最佳。
tree shaking
webpack内置的优化,去除无用代码
前提:1.必须使用ES6模块化 2.开启production模式
使用过程中可能会有问题:可能会无意间将css等代码当无用代码去除掉。
需要在package.json中设置"sideEffects":["*.css"] 等,保证css等文件不会被tree shaking。
code split
代码分割,可以采取多入口方式,配置optimization,或动态导入的方式,打包出多个js文件。
optimization用法:
//webpack.config.js
module.exports = {
//可以将node_modules中代码单独打包成一个chunk
//自动分析多入口chunk中,是否包含公共文件,如果有会打包成单独一个chunk
optimization: {
splitChunks: {
chunks: 'all'
}
}
}
动态导入用法
//业务模块
//webpackChunkName 固定名字
import(/* webpackChunkName: 'test' */'./test')
.then(({add}) => {
add(2, 3)
})
.catch(() => {})
//test.js
export function add(a, b) {
return a+b
}
懒加载
使用代码分割的思路,前提是代码块放在回调函数中,不会立即执行,且要懒加载的代码块被分割出来了。
用法:
//绑定的某事件回调中
//webpackChunkName 固定名字
//webpackPrefetch 预加载:页面运行会先加载js文件,用的时候读缓存,真正执行js文件
//正常加载是顺序加载,预加载是等其他加载完毕,浏览器空闲时再加载
import(/* webpackChunkName: 'test', webpackPrefetch: true */'./test')
.then(({add}) => {
add(2, 3)
})
.catch(() => {})
//test.js
export function add(a, b) {
return a+b
}
PWA
渐进式网络开发应用程序,让网页离线也可以访问(前提是先进入页面,再离线,页面不会跳404)。要求service-worker.js代码必须运行在服务器上
用法:
//webpack.config.js
const WorkboxWebpackPlugin = require('workbox-webpack-plugin')
module.exports = {
plugins:[
new WorkboxWebpackPlugin .GenerateSW({
clientsClaim: true,
skipWaiting: true
})
]
}
//js主入口
//注册serviceworker
//处理兼容性问题
if('serviceWorker' in navigator){
window.addEventListener('load',() => {
navigator.serviceWorker.register('./service.worker.js') // service.worker.js是打包会自动生成的文件
.then(() => {
console.log('')
})
.catch(() => {
console.log('')
})
})
}
//package.json
//通常eslint不识别window、navigator等浏览器对象,需要设置
"eslintConfig":{
"env":{
"browser": true,//支持浏览器端全局变量
//"node": true,//支持nodejs全局变量
}
}
多线程
thread-loader
用法:
module:{
rules:[
{
test: /\.js$/,
use:[
{
loader: 'thread-loader',
options: {
workers: 2 //线程数
},
},
{
loader: ...
}
]
}
]
}
externals
对一些选择从cdn加载的资源禁止本地打包,
用法:
module.exports = {
externals:{
jquery: 'jQuery'
}
}
dll
使用dll技术,对某些(第三方)库进行单独打包
对jquery打dll包用法:
//webpack.dll.js
const {resolve} = require('path)
const webpack = require('webpack')
module.exports = {
entry: {
jquery: ['jquery'] //要打包的库是jquery
},
output: {
filename: '[name].js',
path: resolve(__dirname, 'dll'),
library: '[name]_[hash]' //打包的库里面向外暴露出去的内容叫什么
},
plugins: [
// 打包生成一个manifest.json 提供和jquert映射关系,告诉webpack不需要再打jquery了
new webpack.DllPlugin({
name: '[name]_[hash]', // 映射库的暴露的内容名称
path: resolve(__dirname, 'dll/manifest.json') // 输出文件路径
})
]
}
运行 webpack --config webpack.dll.js
对项目打包时:
//webpack.config.js
const {resolve} = require('path)
const webpack = require('webpack')
const AddAssetHtmlWebpackPlugin= require('add-asset-html-webpack-plugin')
module.exports = {
entry: './src/index/js',
output: {
filename: 'built.js',
path: resolve(__dirname, 'build')
},
plugins: [
// 告诉webpack哪些库不参与打包,同时使用时名称也得变
new webpack.DllReferencePlugin({
manifest: resolve(__dirname, 'dll/manifest.json')
}),
// 将某个文件打包输出去,并在html中自动引入该资源
new AddAssetHtmlWebpackPlugin({
filepath: resolve(__dirname, 'dll/jquery.js')
})
]
}