前言
无论你是使用Vue还是使用React,它们的cli都是选用webpack作为构建工具。而在平常的开发中我们也需要自己配置一些与webpack相关的配置项,所以学习webpack是很有必要的。
1. 什么是webpack
webpack是一个用于现代JavaScript应用程序的静态模块打包工具,其最为核心的功能是解决模块之间的依赖,将各个模块按照特定的规划和顺序组织在一块,最终合并成一个或多个静态文件,以供浏览器加载和运行。
我们可以将webpack理解为一个模块处理工厂,并且我们可以按照自己的想法设计这个工厂。然后将我们编写的源代码交给webpack,它将会按照我们的设想进行加工、拼装处理,产出最终的资源文件。
2. 为什么使用webpack
- 快速开发:当一个项目落到你手上时,我们不可能从零开始搭建项目。而是使用成熟的框架和工程化的工具链进行开发,而webpack就是一个优秀的工程化打包工具。
- 强大的生态:webpack 拥有一个强大且活跃的生态系统,有大量的插件、加载器和工具可供选择。
- 主流框架的脚手架:许多主流的前端框架和工具链,如 React、Vue、Angular 等,都使用 webpack 作为构建工具和打包工具。学习 webpack 可以使你更好地理解和使用这些框架的脚手架工具。
3. 如何使用webpack
接下来我将会讲述有关webpack的一些基础知识,并且一边敲一边编写下面的内容,所以如果你也不是很熟悉的话建议一起敲一遍。
3.1 准备
首先新建一个文件夹webpack-exercise
cd webpack-exercise
初始化项目
npm init -y
安装webpack
npm install webpack@5.87.0 webpack-cli@5.1.4 -D
新建src与config文件夹分别做为项目的根目录和有关webpack配置的目录,然后在src下建立一个index.js文件做为程序的入口,在config下建立webpack.config.js文件做为webpack的配置文件。具体目录如下:
|-- package-lock.json',
|-- package.json',
|-- config',
| |-- webpack.config.js',
|-- src',
|-- index.js',
3.2 context(上下文)
type:string
default:配置文件所在目录
作用:指定 webpack 在解析模块时的基础目录。
显然我们这里不能将config目录作为基础目录,而是应该将src作为基本目录。所以我们需要通过context来指定我们的基本目录。代码如下:
// webpack.config.js
const path = require('path');
module.exports = {
context: path.resolve(__dirname, '../src'),
};
3.3 entry(入口)
type:string|string[]|object
default:./src/index.js
作用:指定 webpack 构建过程中的入口文件或入口模块的配置选项。它用于告诉 webpack 从哪个文件开始构建依赖图,并生成打包后的输出文件。
entry支持单入口文件和多入口文件,这里就不演示多入口文件了。
// index.js
// 编写测试代码
console.log("Hello Webpack!")
配置webpack指定入口文件
// webpack.config.js
const path = require('path');
module.exports = {
context: path.resolve(__dirname, '../src'),
entry: './index.js',
};
编写打包命令,
// package.json
"scripts": {
// webpack是webpack的打包命令,后面的是指定webpack的配置文件位置
"build": "webpack --config ./config/webpack.config.js"
},
然后执行打包命令,最后会生成一个dist文件夹和一个main.js文件。
npm run build
3.4 占位符与hash值
使用[]括起来的就是占位符,具体占位符如下:
| 占位符 | 解释 |
|---|---|
| name | 名称 |
| ext | 文件后缀名 |
| hash | 表示构建过程中整个编译的哈希值 |
| chunkhash | 表示特定块(Chunk)的哈希值 |
| contenthash | 表示特定文件的哈希值 |
使用hash值的原因是为了解决浏览器缓存的问题,当文件名没有变化时,浏览器会使用缓存的文件,这在开发中可能导致问题,因为新的更改可能不会被及时加载。
- hash:任何一个文件改动,整个项目的构建hash值都会改变。
- chunkhash:每个块的哈希值独立于其他块,只有当该块的内容发生变化时,才会更改对应模块的哈希值。
- contenthash:只有当文件的内容发生变化时,才会更改对应文件的哈希值。
3.5 output(输出)
type:object
default:与src目录同级的dist目录
作用:用于指定构建过程中生成的打包文件的配置选项。它告诉 webpack 如何将打包后的代码输出到文件系统中。
常用配置项:
| 选项 | 描述 |
|---|---|
| path | 打包后输出的文件目录 |
| filename | 输出文件的名称 |
| publicPath | 指定输出文件的公共路径,用于指定在浏览器中引用这些文件的 URL 地址 |
// webpack.config.js
module.exports = {
context: path.resolve(__dirname, '../src'),
entry: './index.js',
output: {
path: path.resolve(__dirname, '../build'), // 输出文件目录
filename: '[name].[hash:6].js',// 输出文件名
},
}
打包后的文件目录结构如下:
|-- package-lock.json',
|-- package.json',
|-- build',
| |-- main.cb5bac.js',
|-- config',
| |-- webpack.config.js',
|-- src',
|-- index.js',
3.6 loader
简介:webpcak中的loader是用于对文件进行预处理的。它们用于将不同类型的文件转换为模块,以便在应用程序中使用。
3.6.1 引入css
作用:css-loader 会对 @import 和 url() 进行处理,就像 js 解析 import/require() 一样。
首先我们在src目录下新建一个css目录,然后再css目录下新建一个index.css文件,并编写测试代码。
// index.css
body {
margin: 0px;
padding: 0px;
}
然后在index.js中引入css。
import './css/index.css';
console.log('Hello Webpack!');
然后打包后报如下错误:
报错的原因是webpack默认只能解析js与json文件,如果想要解析css文件需要使用对应的loader对其进行预处理。
npm install css-loader style-loader -D
然后配置webpack
-
css-loader:处理 CSS 文件的加载和转换 -
style-loader:将 CSS 代码注入到页面中
const path = require('path');
module.exports = {
...
module: {
rules: [
{
test: /\.css$/,// 匹配到的css文件使用下面loader进行预编译
use: ['style-loader', 'css-loader'],// 从右向左进行解析
},
],
},
};
打包之后,可以在与src目录同级下建立一个index.html将打包后的js文件引入然后运行查看css是否生效了。
<!-- index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<script src="./build/main.508656.js"></script>
</head>
<body></body>
</html>
3.6.2 引入sass与less
想要sass与less都起作用无非就是多做一次预处理。将其转换为css然后由css-loader进行加载和转换,最后由style-loader将css注入到页面中。
npm install less sass -D
在src目录下新建index.less与index.scss文件,并编写测试代码进行测试
// index.less
body{
font-size: 20px;
}
// index.scss
body{
font-weight: 800;
}
// index.js
import './css/index.css';
import './css/index.less';
import './css/index.scss';
console.log('Hello Webpack!');
// 目录结构
|-- index.html',
|-- package-lock.json',
|-- package.json',
|-- build',
| |-- main.47c1b9.js',
|-- config',
| |-- webpack.config.js',
|-- src',
|-- index.js',
|-- css',
|-- index.css',
|-- index.less',
|-- index.scss',
安装less和sass对应的预处理loader
npm install less-loader sass-loader -D
配置webpack
// webpack.config.js
module.exports = {
...
module: {
rules: [
{
test: /\.less$/,
use: ['style-loader', 'css-loader', 'less-loader'],
},
{
test: /\.scss$/,
use: ['style-loader', 'css-loader', 'sass-loader'],
},
],
},
};
3.6.3 打包图片和文字
webpack5 新增资源模块,它允许使用资源文件(字体,图标等)而无需配置额外 loader。
| 模块类型 | 作用 |
|---|---|
| asset/resource | 将资源分割为单独的文件,并导出 url,类似之前的 file-loader 的功能 |
| asset/inline | 将资源导出为 dataUrl 的形式,类似之前的 url-loader 的小于 limit 参数时功能 |
| asset/source | 将资源导出为源码(source code). 类似的 raw-loader 功能 |
| asset | 会根据文件大小来选择使用哪种类型,当文件小于 8 KB(默认) 的时候会使用 asset/inline,否则会使用 asset/resource |
首先在src目录下新建一个assets文件夹用于存放静态资源,然后在assets分别创建images与fonts目录用于存放图片和字体资源,并放入一张图片和一个字体资源。
字体资源可以去阿里巴巴图标库中寻找。
引入图片
/* index.css */
body {
margin: 0px;
padding: 0px;
transform: translate(0 , 0);
background-image: url(../assets/images/logo.jpg);
}
引入字体
// index.js
import './assets/fonts/iconfont.css';
...
<!-- index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title><%= htmlWebpackPlugin.options.title %></title>
</head>
<body>
<div class="iconfont icon-remen"></div>
</body>
</html>
配置webpack
module.exports = {
module: {
rules: [
{
test: /\.(jpe?g|png|gif)$/,
type: 'asset',
generator: {
// 文件的打包路径的文件名
filename: 'assets/images/[name][hash:4][ext]',
},
parser: {
dataUrlCondition: {
//超过4kb不转 base64
maxSize: 4 * 1024,
},
},
},
{
test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/i,
type: 'asset',
generator: {
filename: 'assets/fonts/[name][hash:4][ext]',
},
parser: {
dataUrlCondition: {
maxSize: 4 * 1024,
},
},
},
],
},
};
打包后的目录结构如下:
|-- index.html
|-- package-lock.json
|-- package.json
|-- build
| |-- index.html
| |-- main.e049a3.js
| |-- assets
| | |-- fonts
| | | |-- iconfont201a.ttf
| | |-- images
| | |-- logo26bd.jpg
|-- config
| |-- webpack.config.js
|-- src
|-- index.js
|-- assets
| |-- fonts
| | |-- iconfont.css
| | |-- iconfont.js
| | |-- iconfont.json
| | |-- iconfont.ttf
| | |-- iconfont.woff
| | |-- iconfont.woff2
| |-- images
| |-- logo.jpg
|-- css
|-- index.css
|-- index.less
|-- index.scss
3.6.4 为css添加浏览器前缀
为了让打包后的css能够兼容多个浏览器,这时候我们可以通过使用插件autoprefixer来兼容其它浏览器。
npm install postcss-loader autoprefixer -D
// index.css
body {
margin: 0px;
padding: 0px;
transform: translate(0 , 0);
}
配置webpack
// webpack.config.js
module.exports = {
...
module: {
rules: [
{
test: /\.css$/,
use: [
'style-loader',
'css-loader',
{
loader: 'postcss-loader',
options: {
postcssOptions: {
plugins: [
[
'autoprefixer',
{
// css需要加前缀的浏览器版本
overrideBrowserslist: [
'last 10 Chrome versions',
'last 5 Firefox versions',
'Safari >= 6',
'ie > 8',
],
},
],
],
},
},
},
],
},
],
},
};
上面的overrideBrowserslist指定的浏览器版本我们还可以在package.json或.browserslistrc文件中配置。如下:
// package.json
"browserslist":[
"last 10 Chrome versions",
"last 5 Firefox versions",
"Safari >= 6",
"ie > 8"
]
还有比较推荐的一种是在与src同级目录下新建一个.browserslistrc文件,在里面进行配置。
last 10 Chrome versions
last 5 Firefox versions
Safari >= 6
ie > 8
3.6.5 JS兼容
为了使我们打包后的代码能够兼容低版本的浏览器,所以我们可以通过babel进行处理。
npm install -D babel-loader @babel/core @babel/preset-env
在index.js文件中添加需要兼容的js代码
// index.js
import './css/index.css';
import './css/index.less';
import './css/index.scss';
console.log('Hello Webpack!');
console.log([1, 2, 3].map((n) => n + 1));
配置webpack
// webpack.config.js
module.exports = {
...
module: {
rules: [
{
test: /\.js$/,
// node_modules不需要进行js转化
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env'],
},
},
},
],
},
};
上面的babel-loader只会将 ES6/7/8语法转换为ES5语法,但是对新api并不会转换 例如(map、promise、Generator、Set、Maps、Proxy等)。此时我们需要借助babel-polyfill来帮助我们转换
npm install -S @babel/polyfill
配置webpack
// webpack.config.js
module.exports = {
entry: ['@babel/polyfill', './index.js'],
}
打包后
3.6.6 CSS兼容
postcss-preset-env让你可以将现代 CSS 转换成大多数浏览器可以理解的内容,根据您的目标浏览器或运行时环境确定您需要的 polyfill。
npm install postcss-preset-env -D
CSS设置16进制颜色的时候一般跟#加六位数字,但是下面写8位数字在谷歌浏览器中也能生效。但是一些低版本的浏览器却无法解析。
/* index.css */
body {
color: #12345678;
...
}
在与src同级目录下新建postcss.config.js文件,并且配置如下:
// postcss.config.js
module.exports = {
plugins: [require('postcss-preset-env')],
};
打包后发现将16进制颜色转为了rgba
注:postcss-preset-env中自带autoprefixer,验证如下:
npm uninstall autoprefixer
修改webpack.config.js配置
// webpack.config.js
module.exports = {
module: {
rules: [
{
test: /\.css$/,
use: [
MiniCssExtractPlugin.loader,
'css-loader',
'postcss-loader',
// {
// loader: 'postcss-loader',
// options: {
// postcssOptions: {
// plugins: [['autoprefixer']],
// },
// },
// },
],
},
],
},
};
打包查看打包后的css文件,发现也有浏览器前缀。
3.6.7 引入Vue
npm install vue@2.6.14
npm install vue-template-compiler@2.6.14 vue-loader@15.9.8
vue-template-compiler:用于将Vue模板转换为渲染函数vue-loader:用于解析.vue文件
注意:
vue-template-compiler与vue的版本需要一致- 在编译
.vue文件时,还需要一个VueLoaderPlugin插件。而这个插件来自于vue-loader/lib/plugin这个文件下,查看仓库发现自从@16版本开始就没有lib文件夹了。所以这里我选择了@15.9.8版本。
在src目录下新建App.vue文件,用于测试
// App.vue
<template>
<div class="App">
{{ message }}
</div>
</template>
<script>
export default {
data() {
return {
message: 'Hello World!',
};
},
};
</script>
引入
// index.js
import App from './App.vue';
import Vue from 'vue';
new Vue({
render: (h) => h(App),
}).$mount('#app');
配置webpack
// webpack.config.js
const VueLoaderPlugin = require('vue-loader/lib/plugin');
module.exports = {
...
module: {
rules: [
{
test: /\.vue$/,
use: ['vue-loader'],
},
],
},
plugins: [
new VueLoaderPlugin(),
],
};
打包后运行
3.7 plugins
Webpack 拥有丰富的插件接口。webpack 自身的大部分功能都使用这些插件接口。这使得 webpack 很灵活。
3.7.1 html-webpack-plugin
不便:打包之后的js文件需要我们手动插入到html中这样很麻烦!我们可以通过html-webpack-plugin来准备一个html模板。它会将打包好的JS文件自动插入页面中,还可以灵活的自定义一些模板中的内容。
npm install html-webpack-plugin -D
配置webpack
// webpack.config.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
...
plugins: [
new HtmlWebpackPlugin({
// 指定模板
template: path.resolve(__dirname, '../index.html'),
// 要用于生成的HTML文档的标题
title: 'webpack-exercise',
}),
],
};
<!-- index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title><%= htmlWebpackPlugin.options.title %></title>
</head>
<body></body>
</html>
打包后发现JS会被自动引入到模板中
3.7.2 自动清除打包目录
不便:到这里你应该发现了,每当我们修改index.js文件中的代码,然后打包就会生成新的打包文件。而不是覆盖掉之前打包的文件,为了维护打包后项目的整洁我们每次都是手动删除。
例如我在index.js中新增了一行代码,然后打包。
console.log('这是新增的一行代码');
使用插件clean-webpack-plugin就能解决上面那一问题。
npm install clean-webpack-plugin -D
配置webpack
// webpack.config.js
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
module.exports = {
plugins: [
new CleanWebpackPlugin(),
],
};
再一次打包发现之前的旧文件就被清除了。
3.7.3 拆分css
我们之前引入的css都是被打包到JS文件中,这样使得打包后的文件既有逻辑代码又有CSS代码。则显然不是很好,所以我们可以通过插件mini-css-extract-plugin将css拆分出来。
npm install mini-css-extract-plugin -D
配置webpack
// webpcak.config.js
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = {
...
module: {
rules: [
{
test: /\.css$/,
// MiniCssExtractPlugin.loader用于提取 CSS 代码到单独文件的加载器
use: [MiniCssExtractPlugin.loader, 'css-loader'],
},
{
test: /\.less$/,
use: [MiniCssExtractPlugin.loader, 'css-loader', 'less-loader'],
},
{
test: /\.scss$/,
use: [MiniCssExtractPlugin.loader, 'css-loader', 'sass-loader'],
},
],
},
plugins: [
new MiniCssExtractPlugin({
// 打包在css文件中
filename: 'css/[name].[hash:6].css',
}),
],
};
打包之后的目录结构如下:
|-- .browserslistrc
|-- index.html
|-- package-lock.json
|-- package.json
|-- build
| |-- index.html
| |-- main.e049a3.js
| |-- assets
| | |-- fonts
| | | |-- iconfont201a.ttf
| | |-- images
| | |-- logo26bd.jpg
| |-- css
| |-- main.e049a3.css
|-- config
| |-- webpack.config.js
|-- src
|-- index.js
|-- assets
| |-- fonts
| | |-- iconfont.css
| | |-- iconfont.js
| | |-- iconfont.json
| | |-- iconfont.ttf
| | |-- iconfont.woff
| | |-- iconfont.woff2
| |-- images
| |-- logo.jpg
|-- css
|-- index.css
|-- index.less
|-- index.scss
3.8 mode
type:string
作用:用于指定webpack的构建模式
| 选项 | 描述 |
|---|---|
| development | 开发者模式,重点是提供更好的开发体验和调试能力 |
| production | 生产者模式,重点是生成优化的、适用于生产环境的最终输出文件 |
| none | 不使用任何默认优化选项 |
你会每次打包都会有一个警告,这个警告就是提示你没有指定webpack的打包模式。
指定打包模式
// webpack.config.js
module.exports = {
mode: 'development',
}
3.8.1 devServer
npm install webpack-dev-server -D
配置打包命令
"scripts": {
...
// webpack serve是启动webpack-dev-server的指令
"dev": "webpack serve --config ./config/webpack.config.js"
},
执行
npm run dev
3.8.2 devtool
作用:用于增强调试过程
devtool的属性值非常多,这里就不一一介绍了。可以去官网上查看每个值的表现,这里只给予个人推荐。
开发环境:eval-cheap-module-source-map与eval-cheap-source-map
- 原因:在开发环境时,注重的是构建速度与错误定位。而上面的两个值都比较合适。
生产环境:none
- 原因:none的构建和重构速度最快,并且不希望别人看到源码。
配置webpack
module.exports = {
...
devtool: 'eval-cheap-module-source-map',
};
在index.js中报错
// index.js
console.log(a);
3.8.3 环境区分
在开发中通常分为开发环境和生产环境,不同的环境采用不同的打包方式。所以我们需要准备两种相应合适的打包配置。
webpack-merge用于合并webpack的配置文件。
npm install webpack-merge -D
修改打包命令
"scripts": {
"build": "webpack --config ./config/webpack.prod.js --mode production",
"dev": "webpack serve --config ./config/webpack.dev.js --mode development"
},
将webpack.config.js改为webpack.common.js用于编写公共配置,如下:
// webpack.common.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const VueLoaderPlugin = require('vue-loader/lib/plugin');
const isDevMode = process.argv.includes('development');
module.exports = {
context: path.resolve(__dirname, '../src'),
entry: ['@babel/polyfill', './index.js'],
output: {
path: path.resolve(__dirname, '../build'),
filename: '[name].[contenthash].js',
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env'],
},
},
},
{
test: /\.css$/,
use: [
isDevMode ? 'style-loader' : MiniCssExtractPlugin.loader,
'css-loader',
'postcss-loader'
],
},
{
test: /\.less$/,
use: [
isDevMode ? 'style-loader' : MiniCssExtractPlugin.loader,
'css-loader',
'less-loader',
'postcss-loader'
],
},
{
test: /\.scss$/,
use: [
isDevMode ? 'style-loader' : MiniCssExtractPlugin.loader,
'css-loader',
'sass-loader',
'postcss-loader'
],
},
{
test: /\.vue$/,
use: ['vue-loader'],
},
{
test: /\.(jpe?g|png|gif)$/,
type: 'asset',
generator: {
filename: 'assets/images/[name][contenthash][ext]',
},
parser: {
dataUrlCondition: {
//超过4kb不转 base64
maxSize: 4 * 1024,
},
},
},
{
test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/i,
type: 'asset',
generator: {
filename: 'assets/fonts/[name][contenthash][ext]',
},
parser: {
dataUrlCondition: {
maxSize: 2 * 1024,
},
},
},
],
},
plugins: [
new HtmlWebpackPlugin({
title: 'webpack-exercise',
template: path.resolve(__dirname, '../index.html'),
}),
new MiniCssExtractPlugin({
filename: 'css/[name].[contenthash].css',
}),
new VueLoaderPlugin(),
],
};
在config目录下新建webpack.dev.js用于编写开发环境的配置。
// webpack.dev.js
const commonConfig = require('./webpack.common');
const { merge } = require('webpack-merge');
module.exports = merge(commonConfig, {
mode: 'development',
devtool: 'eval-cheap-module-source-map',
// 用于开发中跨域代理
devServer: {
hot: true,
port: 9000,
proxy: {
'/api': {
target: 'http://127.0.0.1:4523',
changeOrigin: true,
pathRewrite: {
'^/api': '',
},
},
},
},
});
在config目录下新建webpack.prod.js用于编写开发环境的配置。
// webpack.prod.js
const commonConfig = require('./webpack.common');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const { merge } = require('webpack-merge');
module.exports = merge(commonConfig, {
mode: 'production',
//生产环境需要进行优化构建输出、压缩文件、优化性能等。
});
基础篇就到这里了,进阶篇主要以各种优化为主,用于生产环境之中。