安装webpack
npm webpack webpack-cli -g
webpack-cli是命令行工具,安装以后可以在命令行执行webpack的相关命令, -g直接在全局安装的话后续可以在任意目录下使用webpack,全局安装仅适用于学习使用,实际开发环境下使用局部安装,即在当前项目的工作目录下安装。
使用webpack打包js文件
使用局部安装的webpack命令打包js时需要使用npx webpack,直接运行webpack的话会报找不到bash命令的错误,npx会在npm包路径下寻找bash命令供使用
在零配置的情况下直接执行webpack命令,打包入口是src/index.js,出口是dist/main.js
自定义webpack配置
使用webpack --help可以查看配置命令
–entry 指定打包入口
–mode 指定打包模式,可选生产模式和开发模式
在命令行使用命令进行配置需要每次都手动写入很繁琐,我们可以使用配置文件来固定webpack的常用配置
自定义webpack配置文件
在项目根路径下创建文件:webpack.config.js,会被webpack自动解析成配置文件读取其配置,使用commonjs语法导出一个默认配置对象即可如下:注意出口配置path时必须使用绝对路径否则打包报错
const path = require('path')
module.exports = {
entry: './src/index.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, './dist')
}
}
使用自动打包html资源的插件-html-webpack-plugin
在webpack中使用插件可以实现各种功能,比如自动打包html资源,此前我们需要手动将打包输出的js文件引入到html文件,使用html-webpack-plugin可以实现将html资源打包输出到出口路径且自动引入打包后的js文件
首先安装,-D表示安装为开发环境的依赖,因为我们生产环境并不需要这个插件
npm i html-webpack-plugin -D
在配置文件中进行配置
template配置项目的html模板文件路径
filename配置打包输出后的html文件名称
inject配置引入的js文件放在何处,可以是head或body
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
plugins: [
new HtmlWebpackPlugin({
template: './index.html',
filename: 'app.html',
inject: 'body'
})
]
}
清理dist目录
我们往往希望每次打包先把上一次的打包结果清除掉,此时可以在配置文件进行配置,只需在output配置项下面增加一个配置属性:clean: true即可
使用source map
webpack打包后的js往往和我们开发中写的js的行数无法一一对应,所以在浏览器调试中无法直接定位到错误行数,这里可以使用source map进行一个打包前后的代码映射来实现错误行数的准确提示,使用方法很简单,只需在配置文件中增加一个配置项:devtool: inline-source-map 即可
使用webpack-dev-server
在开发环境我们每次修改代码后都要手动进行命令式的打包,现在使用webpack-dev-server可以在开发环境启动一个web server,实现热更新,当我们修改代码后web server会自动进行更新。
首先执行安装命令
npm i webpack-dev-server -D
在配置文件进行相关配置,新增一个配置项:devServer
devServer: {
static: './dist'
}
资源模块
实际开发中往往需要打包图片,字体等资源,使用webpack可以将这些资源视为js模块进行打包,使用时使用import语法引入即可
- 打包png图片资源
配置文件中天机module配置项,编写.png文件的loder规则如下:test是资源后缀匹配,type是资源类型,generator是打包输出路径和文件名
module: {
rules: [
{
test: /\.png$/,
type: 'asset/resource',
generator: {
filename: 'images/[contenthash][ext]'
}
}
]
}
asset/resource类型会把资源模块直接复制到输出路径并导出资源的URL
type还可配置的选项有asset/inline,该配置会把资源模块打包成base64格式内嵌在html模板中,不会单独输出到打包路径下,对于一些小文件(比如svg图)可以使用inline打包,将来在生产环境可以减少http请求次数
type还支持一种类型是asset/source,该模式会把资源模块的源代码导出但不会单独导出资源模块到打包路径,不如txt文件
asset资源类型会在asset/resource和asset/inline中自动选择一种,选择的依据在于配置的资源大小,超过一定大小即单独打包,不超过则打包成inline,即把base64,配置方法如下:
maxSize代表选择临界值,大于4k则会单独打包,否则打包成把base64
module: {
rules: [
test: /\.png$/,
type: 'asset',
parser: {
dataUrlCondition: {
maxSize: 4 * 1024
}
}
]
}
loader的使用
webpack原本只能理解编译js和json文件资源,这是开箱自带的能力,需要打包css等其他资源文件就需要loader进行加载转译
- 使用loader加载css资源
需要安装css-loader和style-loader,分别负责编译css和将其以内部样式的形式插入到html模板中
npm i css-loader style-loader -D
其次要增加配置项如下:
module: {
rules: [
{
test: /\.css$/,
use: ['style-loader', 'css-loader']
}
]
}
完成配置即可在js中以模块化语法导入css资源,若需要使用less预编译器,再安装less、less-loader并配置即可
npm i less less-loader
module: {
rules: [
{
test: /\.(css|less)$/,
use: ['style-loader', 'css-loader', 'less-loader']
}
]
}
- 抽离和压缩css
使用style-loader加载的样式资源是在html模板中使用style标签插入的,我们往往期望css是单独存在且被使用link标签外部引入,这样就需要抽离css,此外往往还需要压缩css
需要安装以下插件
npm i mini-css-extract-plugin css-minimizer-webpack-plugin
配置如下:
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const CssMiniMizerWebpackPlugin = require('css-minimizer-webpack-plugin')
module.exports = {
module: [
rules: [
{
test: /\.(css|less)$/,
use: [MiniCssExtractPlugin.loader, 'css-loader', 'less-loader']
}
]
],
plugins: [
new MiniCssExtractPlugin({
filename: 'css/[contenthash].css'
})
],
optimization: {
minimizer: {
new CssMiniMizerWebpackPlugin()
}
},
mode: 'production'
}
需要配置的点就是在plugins中初始化抽离css的插件对象,在loader中将style-loader替换为插件的loader,压缩配置是在optimization配置项中的minimizer配置属性下初始化插件对象,并且需要在mode为production下才会启动压缩css
- 加载字体文件
使用资源模块即可加载字体文件
如下配置:
module: {
rules: [
{
test: /\.(woff|ttf|woff2|eot|otf)$/,
type: 'asset/resource'
}
]
}
配置完即可在css中引入对应的字体文件并使用了,如下:
@font-face {
font-family: 'iconfont';
src: url('./asste/font/iconfont.ttf') format(truetype)
}
.icon {
font-family: iconfont;
}
- 加载数据文件
webpack原本支持json格式的数据引入,但是比如csv、xml这种数据文件则需要借助插件实现引入
需要安装2个插件
npm i csv-laoder xml-loader
在loader配置中增加配置项如下:
{
test: /\.(csv|tsv)$/,
use: ['csv-loader']
},
{
test: /\.xml$/,
use: ['xml-loader']
}
完成配置即可在js文件中使用模块化语法引入xml和csv数据文件了,引入后xml会被解析为对象,csv被解析为数组
- toml、yaml、json5格式数据文件的加载
需要安装3个对应的插件
npm i toml yaml json5
配置loader
const toml = require('toml')
const yaml = require('yaml')
const json5 = require('json5')
{
test: /\.toml$/,
type: 'json',
parser: {
parse: toml.parse
}
},
{
test: /\.yaml$/,
type: 'json',
parser: {
parse: yaml.parse
}
}
,{
test: /\.json5$/,
type: 'json',
parser: {
parse: json5.parse
}
}
完成配置后即可在js文件中使用模块化语法导入上述三种数据文件了,导入后全部是js对象
- babel-loader的使用
webpack打包js文件时不会对代码进行编译降级,只会原封不动的打包原js文件的内容,我们往往需要对es6+的代码进行编译降级以碱溶低版本浏览器,此时就需要使用babel
需要安装5个对应的插件
npm i babel-loader @babel/core @babel/preset-env @babel/plugin-transform-runtime @abbel/runtime -D
loader配置如下:
{
test: '/\.js$/',
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env'],
plugins: [
['@babel/plugin-transform-runtime']
]
}
}
}
代码分离
- 使用内置插件splitChunkPlugin即可将公共js资源单独打包
在配置文件的optimization配置项做如下配置
optimization: {
splitChunks: {
chunks: 'all'
}
}
- 动态导入进行代码分离和懒加载
button.addEventListener('click', () => {
import(/* webpackChunkName: 'math' */'./math.js').then(({add}) => {
console.log(add(100, 200))
})
})
如上代码,math.js文件动态导入,即只有在button被点击后才会导入math.js,返回一个promise对象,在then回调中即可接收math.js中导出的对象
打包以后会把index.js和math.js分离打包成2个js文件,在前端打开首屏后,只有在点击了button时才会请求math.js文件资源,使用此类懒加载可以减小首屏加载时请求的js体积,加快首屏加载速度,按需加载需要的js资源
import内部的注释称之为魔法注释,可以指定math.js打包输出的文件名称
需要注意的是修改打包名称的话需要在output配置项中配置filename配置属性为:[name].bundle.js,打包后就会把name替换成魔法注释中设置的名称
预获取和预加载
button.addEventListener('click', () => {
import(/* webpackChunkName: 'math', webpackPrefetch: true */'./math.js').then(({add}) => {
console.log(add(100, 200))
})
})
如上代码对math.js加上魔法注释:webpackPrefetch: true以后,在前端访问首屏时,会使用浏览器的预获取功能,在首屏加载完成后会在网络空闲时预获取math.js文件,可以加快在点击button时的响应
预加载:webpackPreload效果与懒加载类似
修改输出文件的文件名
每次打包后我们往往期望js文件名会被修改,因为文件名不变的话很可能浏览器端会认为服务端资源没有修改会直接取用缓存。我们可以利用webpack提供的contenthash来给打包文件赋一个hash值作文件名即可
配置如下:
output: {
filename: '[name].[contenthash].js'
}
缓存第三方库
因为第三方库基本不会经常改变, 所以将第三方库单独打包,利用浏览器端的长效缓存,来消除每次对第三方库的请求可以减少浏览器向服务端请求资源
在optimization配置项进行如下配置:
splitChunks: {
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
chunks: 'all'
}
}
}
完成上述配置后就会把第三方库打包到一个文件当中
将所有js文件打包到一个目录
只需在output配置项的filename配置属性上添加一个目录名即可如下:
output: {
filename: 'scripts/[name].[contenthash].js'
}
压缩js文件
原本webpack5是在生产环境下默认支持js压缩的,但是如果我们在optimization配置项做了css压缩的设置后,就会把默认的js压缩配置覆盖,所以需要额外再配置一次
安装terser-webpack-plugin
npm i terser-webpack-plugin -D
配置如下:
const TerserPlugin = require('terser-webpack-plugin')
optimization: {
minimizer: [
new TerserPlugin()
]
}
公共路径
在output配置项的publicPath配置属性可以指定应用程序中所有资源的基础路径,譬如在开发环境我们想把静态资源托管至cdn,然后在生产环境使用的话,就可以做如下配置:
output: {
publicPath: 'http://localhost:8080/'
}
配置的域名可以是前端域名或者cdn域名
环境变量
执行如下命令可以在nodejs中设置环境变量
webpack --env production --env goal=local
修改配置文件如下:
module.exports = (env) => {
return {
//配置对象
}
}
通过函数传参即可接收到环境变量,是一个js对象,可以读取到production和goal环境变量
有了环境变量我们就可以动态设置配置文件,依据开发或生产环境下配置不同的配置项
拆分配置文件
上述通过判断环境变量的方式编写配置文件,当配置文件较大时,不易于管理和阅读,我们也可以分开编写开发环境和生产环境的配置文件
- 开发环境配置文件
webpack.config.dev.js
webpack --config webpack.config.dev.js
开发环境启动devServer
webpack serve --config webpack.config.dev.js
- 生产环境配置文件
webpack.config.prod.js
webpack --config webpack.config.prod.js
npm脚本
可以在项目的package.json文件中进行如下的npm脚本编写
这样的话我们可以直接执行npm脚本项目来启动和构建
"scripts": {
"start": "npx webpack serve --config webpack.config.dev.js",
"build": "npx webpack --config webpack.config.prod.js"
}
开发服务器启动:npm run start
项目打包构建:npm run build
source-map的配置
在开发环境我需要配置source-map,这样可以帮助我们调试程序,其功能就是在开发环境的代码和打包后的代码之间做一个映射,这样浏览器调试时报错的代码行数会映射到我们开发环境的代码行数具体配置如下:
devtool: 'cheap-module-source-map'
使用上述的映射方法可以单独生成map映射文件,而且不记录代码列数可以增加一些效率,并且在使用babel-loader时也不会产生映射问题,所以在开发环境下往往使用cheap-module-source-map模式进行代码映射
在生产环境我们一般不会使用代码映射,由于使用bundle和sourcemap可以反编译出源码,这意味着在线上产物有sourcemap的话会有暴露源码的风险,此外sourcemap文件体积一般较大,这与我们生产环境追求更小更轻量的bundle原则相悖
使用开发服务器 devServer
配置如下:
const path = require('path')
{
devServer: {
static: path.resolve(__dirname, './dist'),
compress: true,
port: 3000,
headers: {
'X-Access-Token': 'abc123'
},
proxy: {
'/api': 'http://localhost:9000'
},
https: true,
historyApiFallback: true,
host: '0.0.0.0'
}
}
-
static配置属性指定开发服务器的静态资源路径
-
compress指定服务器传输资源到浏览器时是否启用gzip压缩
-
port指定开发服务器的端口号
-
headers配置响应头,可以任意设置一些想要传输到浏览器的头信息
-
proxy可以配置代理用于解决一些开发阶段的跨域问题,当跨域请求/api开头的url时,会由开发服务器代理发送请求到http://localhost:9000服务器,以避开浏览器的同源策略
-
https或http2(自带https自签名证书)可以配置开发服务器启用https协议
-
historyApiFallback可以配置兜底路由,当我们开发的是单页面应用且使用history模式路由时,在某一路由下刷新页面会报404错误,配置该项为true的话会自动跳转至根路由页面
-
host设置成0.0.0.0,可以启动一个开发服务器主机,在同一局域网内就可以通过ip访问到我们的开发服务器
模块热替换和热加载
开启模块热替换功能可以在开发服务器运行过程中替换删除或增加模块而不用重新加载整个页面即可局部更新,热加载则可以在修改代码时自动更新页面。配置如下:
{
devServer: {
hot: true,
liveReload: true
}
}
以上配置在webpack5中默认为true,开箱即可用
Eslint的配置和使用
eslint是用来扫描我们开发环境编写的代码是否符合团队预先设定好的规范
需要安装eslint
npm i eslint -D
其次需要生成eslint配置文件
npx eslint --init
根据命令行交互选择对应的配置即可自动生成配置文件
之后在vscode中安装eslint插件,即可在开发中提示规范错误
如果说不使用vscode的插件进行eslint检查,则可以结合webpack开发服务器进行eslint检查,需要安装eslint-loader进行配置如下:
{
module: {
rules: [
{
test: /\.js$/,
use: ['babel-loader', 'eslint-loader']
}
]
}
}
完成以上配置即可在开发服务器启动时报出eslint检查结果
我们在实际开发往往不希望在开发中开启eslint检查,那样很容易把我们累死,非常危险!我们经常是在commit提交时才启用eslint检查。我们可以在.git/hooks/pre-commit文件中编写git钩子,在提交之前运行eslint检查工具即可,譬如在pre-commit文件中写入如下命令:
npx eslint ./src
这样配置以后在我们提交代码时就会先执行上述命令,当eslint检查通过时才会提交代码,enjoy!,这样我们就不容易累死了~
但是上述方法会修改我们仓库的hooks文件,我们也可以在自己的项目根路径下创建一个hooks目录.mygithooks下编写pre-commit文件来执行eslint检查,并且在命令行执行命令配置git如下:
git config core.hooksPath .mygithooks
配置路径别名
我们可以使用webpack提供的配置项配置一个src的路径别名,以便访问,如下:
const path = require('path')
{
resolve: {
alias: {
'@': path.resolve(__dirname, './src')
}
}
}
模块扩展名配置
配置如下:
{
resolve: {
extensions: ['js', 'json', 'vue']
}
}
如上配置后,webpack在引入模块时若省略模块扩展名的话则会依次查找js、json和vue扩展名的模块
外部模块扩展
我们很多时候想把不变的第三方库托管在cdn,这样可以减小我们项目打包后的体积只需如下配置:
{
externalsType: 'script',
externals: {
jquery: [
'https://cdn.bootcdn.net/ajax/libs/jquery/3.6.1/jquery.js', '$'
]
}
}
完成以上配置后,会在index.html中以script标签的形式引入jquery,我们就可以在开发环境引入jquery了
import $ from jquery
依赖图构建
使用webpack-bundle-analyzer可以构建出项目的依赖图,方法如下:
安装插件
npm i webpack-bundle-analyzer -D
配置如下:
const {BundleAnalyzerPlugin} = require('webpack-bundle-analyzer')
{
plugins: [
new BundleAnalyzerPlugin()
]
}
之后启动开发服务器即会自动打开依赖图的页面
PostCSS
PostCSS插件可以自动进行css资源的兼容性处理
安装
npm i postcss-loader autoprefixer -D
创建postcss插件的配置文件:postcss.congig.js
module.exports = {
plugins: [
require('autoprefixer')
]
}
还需要在package.json文件中添加支持浏览器的配置信息:
{
"browserslist": [
"> 1%",
"last 2 versions"
]
}
webapck配置:
{
module: {
rules: [
{
test: /\.css$/,
use: [
'style-loader',
'css-loader',
'postcss-loader'
]
}
]
}
}
完成以上配置打包后就会在css资源中进行兼容性前缀自动添加
web works的使用
web works提供了js后台处理线程的api,允许我们将耗时复杂单纯的js处理逻辑放在后台线程进行处理
worker线程(./work.js):
self.onmessage = message => {
sele.postMessage({
answer: 200
})
}
主线程(main.js):
const worker = new Worker(new URL('./work.js', import.meta.url))
worker.postMessage({
question: 'hi, worker线程,你的计算结果是多少?'
})
worker.onmessage = message => {
console.log(message.data.answer)
}
进行上述配置会在打包后生成主线程js文件和worker线程js文件,worker线程会在后台进行js逻辑运算,结束后发送给主线程结果
typescript
前端生态中ts扮演者越来越重要的角色,我们可以在webpack工程化环境中集成ts
安装编译器和loader
npm i typescript ts-loader -D
在项目根路径生成ts配置文件
tsc --init
webapck配置
{
entry: './src/index.ts',
module: {
rules: [
{
test: /\.ts$/,
use: ['ts-loader'],
exclude: /node_modules/
}
]
},
resolve: {
extensions: ['.ts', '.js']
}
}
需要注意的是ts中引用第三方模块还需要安装对应的类型声明文件,去ts官网检索对应的声明文件然后npm安装即可
造轮子(插件)发布到npm仓库
webpack配置:
const path = require('path')
{
mode: 'production',
entry: './src/index.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'mylib.js',
library: {
name: 'mylib',
type: 'commonjs'
},
globalObject: 'globalThis'
}
}
打包后注意要在当前项目的package.json文件中设置包名和main,main即为打包后的入口文件
type可选为window、commonjs、module分别对应script标签引入、commonjs引入、es6模块引入
发布到npm仓库需要在npm官网注册账号
另外我们必须先将npm源切换至官方源
使用nrm插件可以执行命令:nrm use npm切换至官方源
然后执行命令登录用户:npm adduser
接着根据提示输入用户名、密码、邮箱
最后执行命令:npm publish 即可发布,注意包名不能有大写字母
修改版本号后再执行npm publish即可更新包
提升构建性能
通用环境性能提升
- 更新到webpack和nodejs的最新稳定版本
- 使用include限制loader作用范围,譬如仅编译src下的模块
- 持久化缓存,配置cache选项,缓存打包结果
- 使用DLLPlugin为不经常更改的代码生成单独的编译结果
- 使用work-pool将特别消耗资源的loader分流给另一个cpu核心处理(thread-loader)
- 避免在开发环境使用生产环境才必要的工具譬如压缩代码的plugin
- 生产环境不启用source-map