Webpack 是一款 JS 模块化开发的技术框架,其运作原理是将多个 JS 文件关联起来构成可运行的应用程序。
Webpack 拥有丰富的 plugins / loaders 插件生态圈,可以让 js 识别不同的语言如 .css, .scss, .sass, .json, .xml, .ts, .vue, .tsx 等等,这也让 Webpack 至今仍然在许多项目中仍有一席之地。
尽管现在的构建工具百花齐放,如 vite, vue-cli, craco, esbuild, glup, rollup
但大部分构建工具的许多概念基本都是相通的,我认为学习 webpack 依然是前端人员必学技能之一。
新建案例项目 webpack-example
安装 Webpack
mkdir webpack-example && cd webpack-example
yarn init -y
yarn add webpack webpack-cli -D
当前 Webpack 版本:
5.88.2
mkdir src && cd src && touch index.js
cd ../ && touch index.html
这里分几个步骤:
index.js
代码改造:const sayHi = () => {
alert('Hello, webpack!')
}
sayHi()
package.json
配置 webpack 脚本并执行。"scripts": {
"build": "webpack"
},
index.html
引入产物DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Documenttitle>
<script src="./dist/main.js">script>
head>
<body>
body>
html>
本地 server 打开 index.html 即可:
你可能会问,这和直接引入 src/index.js 有何区别?别急,还没体现 webpack 能力呢。
继续改造:
src/
新增 date.js
& tools.js
俩文件 ,内容如下:// src/date.js
export const getDate = () => {
return new Date().toLocaleDateString()
}
// src/tools.js
export const add = (a, b) => {
return a + b
}
// 引入
import { getDate } from './date'
import { add } from './tools'
const run = () => {
console.log('The date is: ', getDate())
console.log('10 + 20 is equal', add(10, 20))
}
run()
截至目前,我们依旧未使用 webpack 配置的能力,仅通过一个 build 命令。
现在开始思考下,在不依赖 webpack 的情况下,js 中引入 css 文件可行?
当然没问题,现代浏览器基于 ES6 语法可以直接引入 css 文件。
但你会发现引入后抛出 MIME 错误,这是因为它只负责请求,不负责非 JS 响应资源处理。
下面用 webpack 配置来解决。
yarn add style-loader css-loader -D
解释:
css-loader
将 css 内容打包成 CommonJS ,style-loader
引入 CommonJS 并将其插入到页面 style 中,这两个是必不可少的。
webpack.config.js
文件,内容如下:module.exports = {
module: {
rules: [
{
test: /\.css$/i,
use: ['style-loader', 'css-loader'],
},
],
},
};
解释:
rules
作为引入 loader 的入口,test
表示要处理匹配的文件后缀名。
rules
的选项还有很多,这里仅以最简单的方式作为演示。
use 执行顺序从最后开始: css-loader -> style-loader 。
src/
新增 index.css
文件,样式如下:body {
background-color: cyan;
}
// ...
import './index.css'
// ...
package.json
build 脚本,指定配置:"scripts": {
"build": "webpack --config=webpack.config.js"
},
在 dist/ 产物中可以看到,css 样式是通过 JS 插上去的,你可能会问,这和在 index.html 中直接引入 css 有何不同?
关于这个问题,我想说如果是 100 个 css 呢?毕竟一个项目的 css 文件数量是非常多的。
再比如像 scss, less 这些 CSS 预处理语言的文件呢,总不可能手动引入吧?
还是得靠 webpack ,只需引入对应的插件和 loader 即可;为了让 webpack 入门纯粹点,这里不再详细赘述。
随着越来越多的项目引入 TS,似乎就成了前端必学的“新语言”~
如果还没用过 Typescript 的同学可参考:TypeScript 日常基本语法 再回头看本文。
yarn add typescript ts-loader -D
解释:
typescript
是一款专门将 ts 编译成 js 的工具,本身可以独立存在,而ts-loader
充当 typescript 与 webpack 之间的信号传递者。
webpack.config.js
中配置如下:module.exports = {
module: {
rules: [
{
test: /\.css$/i,
use: ['style-loader', 'css-loader'],
},
// 新增
{
test: /\.tsx?$/,
use: 'ts-loader',
exclude: /node_modules/, // 不处理 node_modules 。
},
],
},
resolve: {
// 调整后缀解析优先级。
extensions: ['.ts', '.tsx', '.js'],
},
};
提示:配置
extensions
的原因是,Webpack 默认以 src/index 作为编译入口,再根据默认后缀优先级顺序: [‘js’, ‘json’, ‘.wasm’] 开始解析,为了避免解析现有的 index.js,这里要手动调整extensions
,当然也可以设置entry
,这里先不演示,后面会讲到。
src/
下新增 index.ts
文件,内容如下:
import { getDate } from './date'
import { add } from './tools'
import './index.css'
// 这里用 TS 提供的 void 语法作为测试。
const run = (): void => {
console.log('The date is: ', getDate())
console.log('10 + 20 is equal', add(10, 20))
}
run()
balbel
是一款是专门将 JS 高级语法编译为可执行的低版本语法编译器,比如 ES6/7/8 -> ES5。
如果还没用过的同学可参考:JS & 介绍 Babel 的使用及 presets & plugins 的概念 再回头看文本。
yarn add babel-loader @babel/core @babel/preset-env -D
解释:
- @babel/core
是编译 JS 的核心逻辑,即编译器。
- @babel/preset-env
基于编译器,集成了各种插件,比如 es6 -> es5, class -> prototype,链式 ?. 语法等。
- balbel-loader
充当 @babel/core 与 webpack 之间的信号传递者。
webpack.config.js
module.exports = {
// 新增:开启本地调试模式,让产物不要压缩。
mode: 'development',
// 新增:指定编译入口,避免与上章节的 index.ts 冲突。
entry: './src/index.js',
module: {
rules: [
{
test: /\.css$/i,
use: ['style-loader', 'css-loader'],
},
{
test: /\.tsx?$/,
use: 'ts-loader',
exclude: /node_modules/,
},
// 新增:
{
test: /\.(?:js|ts)$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: [
['@babel/preset-env']
]
}
}
}
],
},
};
index.js
中使用高版本语法作为演示,比如 ES6 的 const
声明关键字:const run = () => {
console.log('The date is: ', getDate())
console.log('10 + 20 is equal', add(10, 20))
}
run()
entry
引入为 ./src/index.ts
// webpack.config.js
...
entry: './src/index.ts',
...
src/index.ts
语法如下,这里用 void 作为演示:import './index.css'
const run = (): void => {
const obj = { num: 100 }
console.log('Hello, webpack', obj?.num === 100)
}
run()
yarn build
你会发现 babel 并不识别 .ts 文件,直接报错了:
这是因为 babel 默认仅支持 JS,对于 TS 还得单独处理:
yarn add @babel/preset-typescript -D
解释:
babel/preset-typescript
是基于 babel-core 编译器实现出来的插件,就是让 babel 可识别 .ts
后缀。注意:插件本身不负责编译 ts -> js,仅仅对 ts 源码以 js 的角度来进行转换处理。
module.exports = {
mode: 'development',
entry: './src/index.ts',
module: {
rules: [
{
test: /\.css$/i,
use: ['style-loader', 'css-loader'],
},
{
test: /\.tsx?$/,
use: 'ts-loader',
exclude: /node_modules/,
},
{
test: /\.(?:js|ts)$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: [
[
'@babel/preset-env',
],
// 新增:
[
'@babel/preset-typescript',
]
],
}
}
}
],
},
};
咋一看,Webpack 和 Babel 好像都能编译 JS?这两者间有何区别呢?其实区别还是蛮大的:
Webpack
就像一位 JS 文件管理者,拥有着大量的处理不同文件&产物的插件圈,最终将各种文件联起来形成页面应用。Babel
可以将 JS 代码编译为可向下兼容运行的低版本代码,它也有着丰富的插件圈,但它们是专门与 js 源码打交道的,这也是与 webpack 最大的区别。换个角度想,如果 webpack 和 babel 都是干同一份工作,那两者就不可能一起运作,属矛盾关系。
这是完全没必要的,每个人的工作性质都不一样,用到哪看到哪儿即可,过不了多久还是会忘记。
Webpack 从配置概念上看并不过时,它就像构建工具们的老师,可以从中学到许多东西。
完!