webpack 是一个资源打包工具。
对于 webpack 来说,每一个资源,都被看成是一个模块,而不仅仅只是 js 文件。
得益于 webpack 的这个特性,我们可以将一个组件的所有资源,以模块的形式组合成为一个整体,成为组件。
也正因为得益于 webpack 等打包工具,让前端工程化得以持续推进。create react app 也是基于 webpack 集成的脚手架工具。因此,webpack,是前端进阶的必备技能。
本文会给大家介绍一下 webpack,为深入学习它抛砖引玉。
本文项目地址:点击这里
安装
首先我们创建一个目录,并在该努力下初始化项目,以及安装 webpack,webpack-cli
1// 初始化 npm 项目2npm init -y3npm install webpack webpack-cli --save-dev
新增 .gitignore
文件,文件内容如下,忽略掉一些不需要提交到原创仓库的代码。
10# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.2030# dependencies40/node_modules50/.pnp60.pnp.js7080# testing90/coverage1011# production12/build1314# misc15.DS_Store16.env.local17.env.development.local18.env.test.local19.env.production.local2021npm-debug.log*22yarn-debug.log*23yarn-error.log*
我们案例中,需要工具模块 lodash ,因此提前安装它
npm install lodash --save
在根目录下新增入口 html 文件 index.html
10<!DOCTYPE html>20<html>30<head>40<meta charset="utf-8" />50<title>webpack 起步</title>60</head>7080<body>90<script src="./src/index.js"></script>10</body>11</html>12
然后在根目录创建 src
目录,并在 src 目录下,创建入口 js 文件 index.js
10// src/index.js20import _ from 'lodash'3040function component() {50const element = document.createElement('div');60element.innerHTML = _.join(['Hello', 'webpack'], ' ');7080return element;90}1011document.body.appendChild(component());
我们可以直接执行如下指令,开始打包。
1npx webpack23// 该指令运行的是 `./node_modules/.bin/webpack` 文件,4// 该文件为一个二进制文件
此时会将 src/index.js
作为入口起点,并将打包的结果放入 dist
目录中「打包时自动生成」。
此时没有自动生成 html 文件,因此我们手动创建一个,内容跟上面的 index.html
一样,但是引入的 js 文件,要修改成为打包生成的 main.js
dist/index.html
10<!DOCTYPE html>20<html>30<head>40<meta charset="utf-8" />50<title>webpack 起步</title>60</head>7080<body>90<script src="./main.js"></script>10</body>11</html>
我们在浏览器中运行 dist/index.html
,如果发现页面显示结果为 Hello webpack
,表示我们的目的已经达到。打包结果符合预期。
配置文件
我们仍然需要一个配置文件去明确的告诉 webpack,打包开始时,入口文件是什么,打包之后应该放在哪里,用什么样的方式打包等。
根目录下创建 webpack.config.js
1const path = require('path');23module.exports = {4entry: './src/index.js',5output: {6filename: 'main.js',7path: path.resolve(__dirname, 'dist'),8},9};
entry:表示入口,用于告诉 webpack 应该使用哪个模块来作为构建其内部依赖图的开始。默认值是 src/index.js
。
output:表示打包结果,用于告诉 webpack 在哪里输出它创建的 bundle 文件,以及如何命名这些文件。默认值为 dist/main.js
,其他生成的文件默认放置在 dist
目录中。
执行如下指令就可以开始依据配置文件打包
npx webpack --config webpack.config.js
但是这样太麻烦了,我们可以利用 package.json
中的 scripts
字段,来配置快捷指令。
10{20"name": "7.2webpack",30"version": "1.0.0",40"description": "",50"main": "index.js",60"scripts": {70"test": "echo \"Error: no test specified\" && exit 1",80+ "build": "webpack --config webpack.config.js"90},10"keywords": [],11"author": "",12"license": "ISC",13"devDependencies": {14"webpack": "^5.15.0",15"webpack-cli": "^4.3.1"16},17"dependencies": {18"lodash": "^4.17.20"19}20}
然后,我们就可以使用 npm run build
来代替之前的指令打包了。
目前打包仍然不会自动创建一个 html 文件,因此使用同样的方式手动创建并验证打包结果。
借助一个插件 HtmlWebpackPlugin
可以自动生成 html 文件
先在项目中安装他
npm install html-webpack-plugin --save-dev
然后修改 webpack.config.js
中的逻辑
10const path = require('path');20const HtmlWebpackPlugin = require('html-webpack-plugin');3040module.exports = {50entry: './src/index.js',60output: {70filename: '[name].bundle.js',80path: path.resolve(__dirname, 'dist'),90},10plugins: [11new HtmlWebpackPlugin({12title: '管理输出',13}),14]15};
此时我们修改了两个地方,一是新增了 HtmlWebpackPlugin
插件的逻辑,用于自动生成入口 html 文件。二是调整了 output 中 filename 的规则。
执行 npm run build
打包观察结果。
我们会发现 dist 中自动生成了 main.bundle.js
文件 与 index.html
文件。并且 index.html
中还正确的引入了 main.bundle.js
。
我们还可以修改 output 中 filename 的规则,加入 hash 值,确保每次打包的文件名都不一样,避免浏览器缓存。
10const path = require('path');20const HtmlWebpackPlugin = require('html-webpack-plugin');3040module.exports = {50entry: './src/index.js',60output: {70filename: '[name].[chunkhash:8].js',80path: path.resolve(__dirname, 'dist'),90},10plugins: [11new HtmlWebpackPlugin({12title: '管理输出',13}),14]15};
[name].[chunkhash:8].js
生成 8 位 hash 值。对应的结果可能为 main.b99bd253.js
多打包几次,我们会发现 dist 中的内容越来越多,因此我们需要一个清理 dist 目录的插件。
每次打包之前,需要修改模块的内容,否则 webpack 感知到模块没有变化,就会生成hash值相同的文件,文件数量就不会越来越多
安装 clean-webpack-plugin
npm install --save-dev clean-webpack-plugin
然后修改 webpack.config.js
加入该插件的执行逻辑
10const path = require('path');20const HtmlWebpackPlugin = require('html-webpack-plugin');30+ const { CleanWebpackPlugin } = require('clean-webpack-plugin');4050module.exports = {60entry: './src/index.js',70output: {80filename: '[name].[chunkhash:8].js',90path: path.resolve(__dirname, 'dist'),10},11plugins: [12+ new CleanWebpackPlugin(),13new HtmlWebpackPlugin({14title: '管理输出',15}),16]17};
现在,重新 build 看看效果。
Loader
Loader 在 webpack 充当了重要的角色。它是一个资源文件加载器。Loader 应该在模块加载之前,对该文件进行编译、压缩等。
Plugin 插件主要用于扩展 webpack 的能力,改变 webpack 的最终输出结果。
新增 src/index.css
,我们希望在 index.js 中引入该模块
1body {2background-color: orange;3color: #FFF;4}
10// src/index.js20import _ from 'lodash'30+import './index.css'4050function component() {60const element = document.createElement('div');70element.innerHTML = _.join(['Hello', 'webpack'], ' ');8090return element;10}1112document.body.appendChild(component());
如果我们直接打包的话,会出现错误,因为 webpack 目前还不认识 .css
后缀的模块。因此需要 css-loader
来帮助我们识别它
安装 css-loader
npm install --save-dev css-loader
除此之外,我们还需要一个能够帮助 css 写入 html 文件中 style 的 Loader
npm install --save-dev style-loader
然后修改 webpack.config.js,新增 相关 Loader 的配置规则
10const path = require('path');20const HtmlWebpackPlugin = require('html-webpack-plugin');30const { CleanWebpackPlugin } = require('clean-webpack-plugin');4050module.exports = {60entry: './src/index.js',70output: {80filename: '[name].[chunkhash:8].js',90path: path.resolve(__dirname, 'dist'),10},11module: {12rules: [13{14test: /\.css$/,15use: [16{loader: 'style-loader'},17{18loader: 'css-loader',19options: {20modules: true21}22}23]24}25]26},27plugins: [28new CleanWebpackPlugin(),29new HtmlWebpackPlugin({30title: '管理输出',31}),32]33};
重新 build 之后发现,css 样式已经写入进来了
如果我们要加载 .scss
文件,需要安装一个新的 Loader,sass-loader
,由于 sass-loader
还依赖 node-sass
,因此这两个依赖包一起安装
npm i node-sass sass-loader --save-dev
然后针对 .scss
文件配置规则,webpack.config.js 修改如下
10const path = require('path');20const HtmlWebpackPlugin = require('html-webpack-plugin');30const { CleanWebpackPlugin } = require('clean-webpack-plugin');4050module.exports = {60entry: './src/index.js',70output: {80filename: '[name].[chunkhash:8].js',90path: path.resolve(__dirname, 'dist'),10},11module: {12rules: [13{14test: /\.css$/,15use: [16{loader: 'style-loader'},17{18loader: 'css-loader',19options: {20modules: true21}22}23]24},25{26test: /\.(sc|sa)ss$/,27use: [ 'style-loader', 'css-loader', 'sass-loader']28},29]30},31plugins: [32new CleanWebpackPlugin(),33new HtmlWebpackPlugin({34title: '管理输出',35}),36]37};
随便写一个 test.scss
,并在 index.js 中引入,打包后验证一下结果
1body {2font-size: 50px;3}
1// src/index.js2...3import './test.scss'4...
最后结果如图,字体确实生效。
我们还可以利用内置的 Asset Modules
,将图片作为一个模块进行引入。
修改 webpack.config.js
配置文件如下
10const path = require('path');20const HtmlWebpackPlugin = require('html-webpack-plugin');30const { CleanWebpackPlugin } = require('clean-webpack-plugin');4050module.exports = {60entry: './src/index.js',70output: {80filename: '[name].[chunkhash:8].js',90path: path.resolve(__dirname, 'dist'),10},11module: {12rules: [13{14test: /\.css$/,15use: [16{loader: 'style-loader'},17{18loader: 'css-loader',19options: {20modules: true21}22}23]24},25{26test: /\.(sc|sa)ss$/,27use: [ 'style-loader', 'css-loader', 'sass-loader']28},29+ {30+ test: /\.(png|svg|jpg|jpeg|gif)$/,31+ type: 'asset/resource'32+ }33]34},35plugins: [36new CleanWebpackPlugin(),37new HtmlWebpackPlugin({38title: '管理输出',39}),40]41};
然后在 src
目录下放置一张图片,inbox.jpg
,并修改 src/index.js
此时,我们把该图片当成了一个模块在处理
10import _ from 'lodash'20import './index.css'30import './test.scss'40+import Inbox from './inbox.jpg'5060function component() {70const element = document.createElement('div');80element.innerHTML = _.join(['Hello', 'webpack'], ' ');9010+ const img = new Image()11+ img.src = Inbox12+ element.appendChild(img)1314return element;15}1617document.body.appendChild(component());
build 之后发现,图片已经正常的进入到了页面。
其他的很多资源,包括 字体文件,.xml/.json/.csv
等格式的数据,都可以通过对应的方法,最终在逻辑里当成模块进行处理。
资源的处理我们此处只是抛砖引玉,大家要深入学习,还需要结合官方文档进一步加强,包括配置规则具体详情等。
dev server
每次我修改了代码,如果每次都只能执行 npm run build
之后,我才能看到我改变的效果,这样开发效率就太低了。
我希望能够修改代码之后,就能够快速看到效果,浏览器自动更新视图或者逻辑。
webpack-dev-server
能够满足我的需求。
先安装它
npm install --save-dev webpack-dev-server
然后新增配置如下:
10const path = require('path');20const HtmlWebpackPlugin = require('html-webpack-plugin');30const { CleanWebpackPlugin } = require('clean-webpack-plugin');4050module.exports = {60mode: 'development',70entry: './src/index.js',80output: {90filename: '[name].[chunkhash:8].js',10path: path.resolve(__dirname, 'dist'),11},12+ devServer: {13+ contentBase: './dist',14+ inline: true15+ },16module: {17rules: [18{19test: /\.css$/,20use: [21{loader: 'style-loader'},22{23loader: 'css-loader',24options: {25modules: true26}27}28]29},30{31test: /\.(sc|sa)ss$/,32use: [ 'style-loader', 'css-loader', 'sass-loader']33},34{35test: /\.(png|svg|jpg|jpeg|gif)$/i,36type: 'asset/resource'37}38]39},40plugins: [41new CleanWebpackPlugin(),42new HtmlWebpackPlugin({43title: '管理输出',44}),45]46};
并且在 package.json
中新增快捷指令运行 dev server。
10{20"name": "7.2webpack",30"version": "1.0.0",40"description": "",50"main": "index.js",60"scripts": {70"test": "echo \"Error: no test specified\" && exit 1",80+ "start": "webpack serve --open",90"build": "webpack --config webpack.config.js"10},11"keywords": [],12"author": "",13"license": "ISC",14"devDependencies": {15"clean-webpack-plugin": "^3.0.0",16"css-loader": "^5.0.1",17"html-webpack-plugin": "^4.5.1",18"node-sass": "^5.0.0",19"sass-loader": "^10.1.1",20"style-loader": "^2.0.0",21"webpack": "^5.15.0",22"webpack-cli": "^4.3.1",23"webpack-dev-server": "^3.11.2"24},25"dependencies": {26"lodash": "^4.17.20"27}28}
现在,我们只需要执行指令 npm start
,浏览器就会自动加载页面,访问 http://localhost:8080/
,并且当我们有任何修改时,浏览器都会自动刷新。
小结
本文只是简单的给大家分享了 webpack 的强大能力,最关键的是我们要通过实际操作,去体会在 webpack 中模块的真实含义。以增强对于组件化的思维理解。
create-react-app 是更健全更为强大的脚手架工具,我们后面的案例依然会使用它,但是它也是基于 webpack 完善而来。如果我们要深入学习 webpack,大家可以通过官方文档进一步学习