1 简介
webpack 是一个静态模块打包器。入口js文件(引入JQ、less等chunk块)-->less转为css/es6转为es5-->打包后输出为bundle。
1.1 五个核心概念
入口(Entry)
输出(Output)
Loader :让 webpack 去处理那些非 JavaScript 文件 (webpack 自 身 只 理 解 JavaScript)
插件(Plugins):可以用于执行范围更广的任务
1.2 基本使用
npm install webpack webpack-cli -D
2 开发环境的基本配置
2.1 配置
打包样式资源:less-loader css-loader style-loader
打包html资源: html-webpack-plugin
处理图片资源:url-loader html-loader
处理其他资源:file-loader
devServer:webpack-dev-server
2.2 编码
/*
开发环境配置:能让代码运行
运行项目指令:
webpack 会将打包结果输出出去
npx webpack-dev-server 只会在内存中编译打包,没有输出
*/
const { resolve } = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: './src/js/index.js',
output: {
filename: 'js/built.js',
path: resolve(__dirname, 'build')// 输出路径:__dirname代表当前文件的目录绝对路径
},
module: {
rules: [
// loader的配置
{
// 处理less资源
test: /\.less$/,// 匹配哪些文件
//less-loader:将less文件编译成css文件
//css-loader:将css文件变成commonjs模块加载js中
//style-loader:创建style标签,将js中的样式添加到head中生效
use: ['style-loader', 'css-loader', 'less-loader']// use数组中loader执行顺序:从右到左,从下到上依次执行
},
{
// 处理css资源
test: /\.css$/,
use: ['style-loader', 'css-loader']
},
{
// 处理图片资源
test: /\.(jpg|png|gif)$/,
loader: 'url-loader',//依赖于file-loader
options: {
limit: 8 * 1024,// 图片大小小于8kb,就会被base64处理;减少请求数量,图片体积会更大
name: '[hash:10].[ext]',// [hash:10]取图片的hash的前10位 [ext]取文件原来扩展名
esModule: false,// 关闭es6模块化:因为url-loader默认使用es6模块化解析,而html-loader引入图片是commonjs
outputPath: 'imgs'
}
},
{
// 处理html中img资源
test: /\.html$/,
loader: 'html-loader'//引入图片,从而被url-loader处理
},
{
// 处理其他资源(如图标字体)
exclude: /\.(html|js|css|less|jpg|png|gif)/,
loader: 'file-loader',
options: {
name: '[hash:10].[ext]',
outputPath: 'media'
}
}
]
},
plugins: [
// plugins的配置
new HtmlWebpackPlugin({//默认会创建一个空的HTML,自动引入打包输出的所有资源(JS/CSS)
template: './src/index.html'// 复制 './src/index.html' 文件
})
],
mode: 'development',
devServer: {// 开发服务器 devServer:用来自动化(自动编译,自动打开浏览器,自动刷新浏览器~~)
contentBase: resolve(__dirname, 'build'),// 项目构建后路径
compress: true,// 启动gzip压缩
port: 3000,
open: true// 自动打开浏览器
}
};
3 生产环境的基本配置
3.1 配置
提取css为单独文件:min-css-extract-plugin
css兼容性处理:postcss-loader postcss-preset-env
压缩css:optimize-css-assets-webpack-plugin
js语法检查:eslint-loader eslint eslint-config-airbnb-base eslint-plugin-import
js兼容性处理:
babel-loader @babel/core @babel/preset-env(基本语法)core-js(高级语法按需加载)
@babel/polyfill:promise可以,兼容性代码全部引入,体积太大。入口文件直接引入
压缩html: html-webpack-plugin
3.2 编码
const { resolve } = require('path');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
// 定义nodejs环境变量:决定使用browserslist的哪个环境,默认为生产环境
process.env.NODE_ENV = 'production';
// 复用loader
/*
"browserslist": {
// 开发环境 --> 设置node环境变量:process.env.NODE_ENV = development
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
],
// 生产环境:默认是看生产环境
"production": [
">0.2%",
"not dead",
"not op_mini all"
]
}
*/
const commonCssLoader = [
MiniCssExtractPlugin.loader,// 这个loader取代style-loader。作用:提取js中的css成单独文件。最终通过link引入,减小js文件的体积,还可以避免闪屏
'css-loader',
{
// 还需要在package.json中定义browserslist
loader: 'postcss-loader',
options: {
ident: 'postcss',
plugins: () => [require('postcss-preset-env')()]//帮postcss找到package.json中browserslist里面的配置,通过配置加载指定的css兼容性样式
}
}
];
module.exports = {
entry: './src/js/index.js',
output: {
filename: 'js/built.js',
path: resolve(__dirname, 'build')
},
module: {
rules: [
{
test: /\.css$/,
use: [...commonCssLoader]
},
{
test: /\.less$/,
use: [...commonCssLoader, 'less-loader']
},
/*
1.正常来讲,一个文件只能被一个loader处理。当一个文件要被多个loader处理,那么一定要指定loader执行的先后顺序:先执行eslint 在执行babel。
2.在package.json中eslintConfig设置检查规则 --> airbnb--> eslint-config-airbnb-base eslint-plugin-import eslint
"eslintConfig": {
"extends": "airbnb-base"
}
*/
{//js语法检查
test: /\.js$/,
exclude: /node_modules/,//只检查自己写的源代码
enforce: 'pre',// 优先执行
loader: 'eslint-loader',//依赖于eslint
options: {
fix: true// 自动修复eslint的错误
}
},
{//js兼容性处理
test: /\.js$/,
exclude: /node_modules/,
loader: 'babel-loader',//依赖于@babel/core
options: {
presets: [
[
'@babel/preset-env',//基本js兼容性处理
{
useBuiltIns: 'usage',// 按需加载
corejs: {version: 3},// 指定core-js版本
targets: {// 指定兼容性做到哪个版本浏览器
chrome: '60',
firefox: '50',
ie: '9',
safari: '10',
edge: '17'
}
}
]
],
cacheDirectory: true// 开启babel缓存。第二次构建时,会读取之前的缓存
}
},
{
test: /\.(jpg|png|gif)/,
loader: 'url-loader',
options: {
limit: 8 * 1024,
name: '[hash:10].[ext]',
outputPath: 'imgs',
esModule: false
}
},
{
test: /\.html$/,
loader: 'html-loader'
},
{
exclude: /\.(js|css|less|html|jpg|png|gif)/,
loader: 'file-loader',
options: {
outputPath: 'media'
}
}
]
},
plugins: [
new MiniCssExtractPlugin({//提取css为单独文件
filename: 'css/built.css'
}),
new OptimizeCssAssetsWebpackPlugin(),// 压缩css
new HtmlWebpackPlugin({// 压缩html代码
template: './src/index.html',
minify: {
collapseWhitespace: true,// 移除空格
removeComments: true// 移除注释
}
})
],
mode: 'production'// 压缩js
};
4 性能优化
开发:打包速度HMR、代码调试sourceMap
生产:打包速度oneOf、babel缓存、多进程打包、externals、dll、
代码性能:文件资源缓存、tree shaking、code split、懒加载、pwa
4.1 开发环境
HMR: hot module replacement 热模块替换 / 模块热替换
问题:npx xxx 启动开发环境配置--->修改代码,整个页面会重新刷新
作用:devServer中开启hot--->一个模块发生变化,只会重新打包这一个模块
/*
样式文件:可以使用HMR功能:因为style-loader内部实现了~
js文件:默认不能使用HMR功能 --> 修改index.js中的代码,根据hot属性存在,监听模块文件变化更新
注意:HMR功能只能处理非入口js文件 。
html文件: 默认不能使用HMR功能,同时会导致问题:html文件不能热更新了~ (不用做HMR功能)
解决:修改entry入口,将html文件引入
*/
const { resolve } = require('path');
module.exports = {
entry: ['./src/js/index.js', './src/index.html'],
output: {},
module: {},
plugins: [],
mode: 'development',
devServer: {
contentBase: resolve(__dirname, 'build'),
compress: true,
port: 3000,
open: true,
// 开启HMR功能
// 当修改了webpack配置,新配置要想生效,必须重新webpack服务
hot: true
}
};
index.js
if (module.hot) {
module.hot.accept('./print.js', function() {
// 方法会监听 print.js 文件的变化,一旦发生变化,其他模块不会重新打包构建。
// 会执行后面的回调函数
print();
});
}
source-map: 一种提供源代码到构建后代码映射技术,打包后生成js.map文件,快速定位源代码的错误位置。
开发环境:速度快,调试好 eval-source-map(内联)
生产环境:源代码隐藏,调试友好 source-map(外部js.map)
const { resolve } = require('path');
module.exports = {
entry: ['./src/js/index.js', './src/index.html'],
output: {
filename: 'js/built.js',
path: resolve(__dirname, 'build')
},
module: {
rules: []
},
plugins: [],
mode: 'development',
devServer: {},
devtool: 'eval-source-map'//source-map
};
4.2 生产环境
module中配置oneOf:同类型的文件只匹配一个loader,将eslint-loader提取出去。
const { resolve } = require('path');
// 复用loader
const commonCssLoader = [
MiniCssExtractPlugin.loader,
'css-loader',
{
loader: 'postcss-loader',
options: {
ident: 'postcss',
plugins: () => [require('postcss-preset-env')()]
}
}
];
module.exports = {
entry: './src/js/index.js',
output: {
filename: 'js/built.js',
path: resolve(__dirname, 'build')
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
// 优先执行
enforce: 'pre',
loader: 'eslint-loader',
options: {
fix: true
}
},
{
// 以下loader只会匹配一个
// 注意:不能有两个配置处理同一种类型文件
oneOf: [
{
test: /\.css$/,
use: [...commonCssLoader]
},
{
test: /\.less$/,
use: [...commonCssLoader, 'less-loader']
},
{
test: /\.js$/,
exclude: /node_modules/,
loader: 'babel-loader',
options: {
presets: [
[
'@babel/preset-env',
{
useBuiltIns: 'usage',
corejs: {version: 3},
targets: {
chrome: '60',
firefox: '50'
}
}
]
]
}
},
]
}
]
},
plugins: [],
mode: 'production'
};
babel缓存(第二次构建读取缓存)
文件资源缓存(上线):server.js用express搭建服务器引入built静态资源-->被强制缓存
hash: 每次wepack构建时会生成一个唯一的hash值。
问题: 因为js和css同时使用一个hash值。如果重新打包,会导致所有缓存失效 (只改动一个文件)
chunkhash:根据chunk生成的hash值。如果打包来源于同一个chunk,那么hash值就一样
问题: js和css的hash值还是一样的。因为css是在js中被引入的,所以同属于一个chunk
contenthash: 根据文件的内容生成hash值。不同文件hash值一定不一样
module.exports = {
entry: './src/js/index.js',
output: {
filename: 'js/built.[contenthash:10].js',//js
path: resolve(__dirname, 'build')
},
module: {},
plugins: [
new MiniCssExtractPlugin({
filename: 'css/built.[contenthash:10].css'//css
}),
],
mode: 'production',
};
tree shaking:去除无用代码,减少代码体积
前提:1. 必须使用ES6模块化 2. 开启production环境
code split:a.入口文件有几个,就生成几个bundle;b.配置了optimization中的chunks,将node_modules中代码单独打包一个chunk输出一个bundle;c.import动态导入语法:能将某个文件单独打包成一个chunk。
const { resolve } = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
//方式一:
module.exports = {
// 单入口:输出一个bundle
// entry: './src/js/index.js',
entry: {
// 多入口:输出两个bundle
index: './src/js/index.js',
test: './src/js/test.js'
},
output: {
// [name]:取文件名
filename: 'js/[name].[contenthash:10].js',
path: resolve(__dirname, 'build')
},
plugins: [
new HtmlWebpackPlugin({
template: './src/index.html',
minify: {
collapseWhitespace: true,
removeComments: true
}
})
],
mode: 'production'
};
//方式二:
module.exports = {
// 单入口:输出两个个bundle
// entry: './src/js/index.js',//引入JQ
entry: {
// 多入口:输出三个bundle
index: './src/js/index.js',//引入JQ
test: './src/js/test.js'//引入JQ
},
output: {
// [name]:取文件名
filename: 'js/[name].[contenthash:10].js',
path: resolve(__dirname, 'build')
},
plugins: [
new HtmlWebpackPlugin({
template: './src/index.html',
minify: {
collapseWhitespace: true,
removeComments: true
}
})
],
/*
1. 可以将node_modules中代码单独打包一个chunk最终输出
2. 自动分析多入口chunk中,有没有公共的文件。如果有会打包成单独一个chunk
*/
optimization: {
splitChunks: {
chunks: 'all'
}
},
mode: 'production'
};
//方式三:
//在方式二的基础上,在入口文件中引入test.js
/*
通过js代码,让某个文件被单独打包成一个chunk
import动态导入语法:能将某个文件单独打包
*/
import(/* webpackChunkName: 'test' */'./test')
.then(({ mul, count }) => {
// 文件加载成功~
console.log(mul(2, 5));
})
.catch(() => {
console.log('文件加载失败~');
});
懒加载:当文件需要使用时才加载~
预加载:文件使用之前提前加载,其他资源加载完毕,浏览器空闲时加载。兼容性较差
document.getElementById('btn').onclick = function() {//点击按钮的时候加载index.js中的test.js
//预加载:webpackPrefetch: true
import(/* webpackChunkName: 'test', webpackPrefetch: true */'./test').then(({ mul }) => {
console.log(mul(4, 5));
});
};
PWA: 渐进式网络开发应用程序(离线可访问)
1.安装workbox-webpack-plugin
2.入口文件注册配置文件
const { resolve } = require('path');
const WorkboxWebpackPlugin = require('workbox-webpack-plugin');
/*
PWA: 渐进式网络开发应用程序(离线可访问)
workbox --> workbox-webpack-plugin
*/
module.exports = {
entry: './src/js/index.js',
output: {
filename: 'js/built.[contenthash:10].js',
path: resolve(__dirname, 'build')
},
module: { },
plugins: [
new WorkboxWebpackPlugin.GenerateSW({
/*
1. 帮助serviceworker快速启动
2. 删除旧的 serviceworker
生成一个 serviceworker 配置文件~
*/
clientsClaim: true,
skipWaiting: true
})
],
mode: 'production',
};
/*
1. eslint不认识 window、navigator全局变量
解决:需要修改package.json中eslintConfig配置
"env": {
"browser": true // 支持浏览器端全局变量
}
2. sw代码必须运行在服务器上
--> nodejs
-->
npm i serve -g
serve -s build 启动服务器,将build目录下所有资源作为静态资源暴露出去
*/
// index.js中注册serviceWorker
// 处理兼容性问题
if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker
.register('/service-worker.js')
.then(() => {
console.log('sw注册成功了~');
})
.catch(() => {
console.log('sw注册失败了~');
});
});
}