0
点赞
收藏
分享

微信扫一扫

从零构建 webpack 脚手架(基础篇)

IT影子 2022-09-29 阅读 129

webpack 是一个现代 JavaScript 应用程序的静态模块打包工具,它对于前端工程师来说可谓是如雷贯耳,基本上现在的大型应用都是通过 webpack 进行构建的。

webpack 具有高度可配置性,它拥有非常丰富的配置。在过去一段时间内曾有人将熟练配置 webpack 的人称呼为 “webapck 工程师”。当然,这称呼只是个玩笑话,但也能从侧面了解到 webpack 配置的灵活与复杂。

为了能够熟练掌握 webpack 的使用,接下来通过几个例子循序渐进的学习如何使用 webpack。

以下 ​​Demo​​​ 都可以在 Github 的 ​​webpack-example​​ 中找到对应的示例,欢迎 star~

本篇文章内容略长,建议先马后看。由于知乎不支持代码折叠,

目录预览

  • 起步
  • 使用配置
  • Plugin
  • loader
  • css
  • image
  • HTML 资源引入
  • lodash template
  • 静态目录
  • html-loader
  • 总结

起步

从 ​​webpack@v4.0.0​​​ 开始,就可以不用再引入配置文件来打包项目。若没有提供配置的话,webpack 将按照默认规则进行打包。默认情况下 ​​src/index​​​ 是项目的源代码入口,打包后的代码会输出到 ​​dist/main.js​​ 上。

首先来初始化一个项目,项目名为 ​​getting-started​​:

# 创建项目文件夹
mkdir getting-started

# 进入项目目录
cd getting-started

# npm 项目
npm init -y

初始化项目后,项目目录会新增一个 ​​package.json​​​,该文件记录了项目依赖的相关信息。若想要使用 webpack 的话需要安装它的依赖: ​​webpack​​​ (本体)和 ​​webpack-cli​​ (可以在命令行操作 webpack 的工具):

# -D 和 --save-dev 选项都可以用于安装开发依赖
# npm i --save-dev webpack webpack-cli
npm i -D webpack webpack-cli

# 或者使用 yarn 安装开发依赖
yarn add -D webpack webpack-cli

接着创建 webpack 所需的默认入口文件 ​​src/index.js​​​ 以及测试模块所用的 ​​src/log.js​​ 文件。此时的项目结构大致如下:

+ ├── src
+ │ ├── index.js
+ │ └── log.js
└── node_modules

javascript:

// src/log.js
export const log = (name) => console.log(`Hello ${name}!`);

// src/index.js
import { log } from './log'

log('anran758');

​src/log.js​​​ 导出了一个工具函数,它负责向控制台发送消息。​​src/index.js​​​ 是默认的入口文件,它引入 ​​log​​ 函数并调用了它。

上面的代码很简单,像这种模块化的代码按照传统 ​​<script src>​​​ 引入的话,浏览器是不能正确执行的。可以在根目录上创建一个 ​​index.html​​ 引入 js 脚本来测试一下:

<!-- /index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Test</title>
</head>
<body>
<!-- 引入脚本 -->
<script src="./src/index.js"></script>
</body>
</html>

创建文件后,将上例代码复制到 `index.html` 中。保存并打开该文件,看看浏览器能否正确处理模块逻辑。不出意外的话,文件在浏览器打开后,浏览器开发者工具会抛出错误信息:

Uncaught SyntaxError: Cannot use import statement outside a module

言下之意就是说浏览器不能正确的解析 ​​ES module​​​ 语句,此时 webpack 就可以派上用场啦~ 在 ​​package.json​​​ 中的 ​​scripts​​ 字段中添加如下命令:

+    "build": "webpack"
- "test": "echo \"Error: no test specified\" && exit 1"
},

在命令行输入 ​​npm run build​​​ 调用 ​​webpack​​​ 对当前项目进行编译,编译后的结果会输出到 ​​dist/main.js​​​ 文件中(即便本地没有 dist 目录,它都会自动创建该目录)。输出文件后,修改 ​​index.html​​ 对 js 的引用:

+   <script src="./dist/main.js"></script>
- <script src="./src/index.js"></script>
</body>

重新刷新页面后就能看到 ​​log​​​ 正确的输出了 ​​Hello anran758!​​​。点击 log 右侧的链接,可以跳转至 ​​Source​​ 面板,将代码格式化后可以清晰地看到编译后 js 的变化:

从零构建 webpack 脚手架(基础篇)_css

使用配置

当然,上例代码只不过是小试牛刀。对于正式的项目会有更复杂的需求,因此需要自定义配置。​​webpack​​ 主要有两种方式接收配置:

第一种: 通过 ​​Node.js​​ API引入 webpack 包,在调用 webpack 函数时传入配置:

const webpack = require("webpack");

const webpackConfig = {
// webpack 配置对象
}

webpack(webpackConfig, (err, stats) => {
if (err || stats.hasErrors()) {
// 在这里处理错误
}

// 处理完成
});

第二种: 通过 ​​webpack-cli​​ 在终端使使用 webpack 时指定配置。

webpack [--config webpack.config.js]

两种方法内配置都是相似的,只是调用的形式不同。本篇先使用 ​​webpack-cli​​ 来做示例。

webpack 接受一个特定的配置文件,配置文件要求导出一个对象、函数、​​Promise​​ 或多个配置对象组成的数组。

现在将上一章的 Demo 复制一份出来,并重命名为 ​​getting-started-config​​​,在该目录下新建 ​​webpack.config.js​​ 文件,文件内容如下:

const path = require('path');

module.exports = {
// 起点或是应用程序的起点入口
entry: "./src/index",
output: {
// 编译后的输出路径
// 注意此处必须是绝对路径,不然 webpack 将会抛错(使用 Node.js 的 path 模块)
path: path.resolve(__dirname, "dist"),

// 输出 bundle 的名称
filename: "bundle.js",
}
}

上面的配置主要是定义了程序入口、编译后的文件输出目录。然后在 ​​src/index.js​​ 中修改一些内容用来打包后测试文件是否被正确被编译:

+ log('本节在测试配置噢');
- log('anran758');

随后在终端输入 ​​num run build​​​ 进行编译,可以看到 ​​dist​​​ 目录下多了个 ​​bundle.js​​。

$ npm run build
> webpack --config ./webpack.config.js

Hash: 3cd5f3bbfaf23f01de37
Version: webpack 4.43.0
Time: 117ms
Built at: 05/06/2020 1:01:37 PM
Asset Size Chunks Chunk Names
bundle.js 1010 bytes 0 [emitted] main
Entrypoint main = bundle.js
[0] ./src/index.js + 1 modules 123 bytes {0} [built]
| ./src/index.js 62 bytes [built]
| ./src/log.js 61 bytes [built]

WARNING in configuration
The 'mode' option has not been set, webpack will fallback to 'production' for this value. Set 'mode' option to 'development' or 'production' to enable defaults for each environment.
You can also set it to 'none' to disable any default behavior. Learn more: https://webpack.js.org/configuration/mode/

由于我们输出的文件名被修改了,此时还得修改 ​​html​​​ 的引入路径。但每改一次输出目录,​​HTML​​中的引入路径也得跟着改,这样替换的话就比较容易出纰漏。那能不能让 webpack 自动帮我们插入资源呢?答案是可以的。

Plugin

webpack 提供插件(plugin)的功能,它可以用于各种方式自定义 webpack 构建过程。

​​html-webpack-plugin​​​ 可以在运行 webpack 时自动生成一个 ​​HTML​​​ 文件,并将打包后的 ​​js​​代码自动插入到文档中。下面来安装它:

npm i --D html-webpack-plugin

安装后在 ​​webpack.config.js​​ 中使用该插件:

const path = require('path');
+ const HtmlWebpackPlugin = require('html-webpack-plugin');
+ plugins: [
+ new HtmlWebpackPlugin({
+ title: 'Test Configuration'
+ })
+ ],
}

重新编译后 ​​HTML​​​ 也被输出到 ​​dist​​​ 目录下。查看 ​​dist/index.html​​​ 的源码可以发现:不仅源码被压缩了,同时 ​​<script>​​​ 标签也正确的引入了 ​​bundle.js​​。

后续目录展示会将 ​​node_modules​​​、​​package-lock.json​​​、​​yarn.lock​​ 这种对项目架构讲解影响不大的目录省略掉..

此时目录结构如下:

.
├── dist
│ ├── bundle.js
│ ├── index.html
│ └── main.js
├── index.html
├── package.json
├── src
│ ├── index.js
│ └── log.js
└── webpack.config.js

处理完资源自动插入的问题后,还有一个问题需要我们处理:虽然 webpack 现在能自动生成 ​​HTML​​​ 并插入脚本,但我们还得在 ​​HTML​​​ 中写其他代码逻辑呀,总不能去改 ​​/dist/index.html​​文件吧?

这个问题也很好解决。​​html-webpack-plugin​​​ 在初始化实例时,传入的配置中可以加上 ​​template​​ 属性来指定模板。配置后直接在指定模板上进行编码就可以解决这个问题了:

+       template: path.resolve(__dirname, "./index.html"),
})
],
}

使用模板后 ​​html-webpack-plugin​​​ 也会自动将脚本插入到模板中。因此可以将模板中的 ​​<script>​​​ 给去掉了。为了测试输出的文件是否使用了模板,在 ​​<body>​​ 内随便插入一句话,重新打包后预览输出的文件是否包含这句话:

+    <title>Test Config</title>
- <title>Test</title>
+ <p>Test Config</p>
- <script src="./dist/main.js"></script>
</body>
</html>

修改文件后,重新打包就能看到模板也被压缩输出至 ​​/dist/index.html​​​ 了,​​script​​ 标签也正常的插入了。

清理目录

现在来看编译后的目录,我们发现 ​​dist/mian.js​​ 这文件是使用配置之前编译出来的文件,现在我们的项目已经不再需要它了。这种历史遗留的旧文件就应该在每次编译之前就被扔进垃圾桶,只输出最新的结果。

​​clean-webpack-plugin​​​ 或 ​​rimraf​​​ 可以完成清理功能。前者是比较流行的 webpack 清除插件,后者是通用的 unix 删除命令(安装该依赖包后 windows 平台也能用)。如果仅是清理 ​​/dist​​​ 目录下文件的话,个人是比较倾向使用 ​​rimraf​​​的,因为它更小更灵活。而 ​​clean-webpack-plugin​​是针对 webpack 输出做的一系列操作。

在终端安装依赖:

npm i -D rimraf

​rimraf​​​ 的命令行的语法是: ​​rimraf <path> [<path> ...]​​​,我们在 ​​package.json​​​ 的 ​​scirpts​​​中修改 ​​build​​ 的命令:

"scripts": {
+ "build": "rimraf ./dist && webpack --config ./webpack.config.js"
- "build": "webpack --config ./webpack.config.js"
}

重新运行脚本:

$ npm run build

> rimraf ./dist && webpack --config ./webpack.config.js

Hash: 763fe4b004e1c33c6876
Version: webpack 4.43.0
Time: 342ms
Built at: 05/06/2020 2:35:49 PM
Asset Size Chunks Chunk Names
bundle.js 1010 bytes 0 [emitted] main
index.html 209 bytes [emitted]
Entrypoint main = bundle.js
[0] ./src/index.js + 1 modules 123 bytes {0} [built]
| ./src/index.js 62 bytes [built]
| ./src/log.js 61 bytes [built]

WARNING in configuration
The 'mode' option has not been set, webpack will fallback to 'production' for this value. Set 'mode' option to 'development' or 'production' to enable defaults for each environment.
You can also set it to 'none' to disable any default behavior. Learn more: https://webpack.js.org/configuration/mode/
Child HtmlWebpackCompiler:
1 asset
Entrypoint HtmlWebpackPlugin_0 = __child-HtmlWebpackPlugin_0
1 module

这样 webpack 输出的 ​​/dist​​ 目录始终是最新的东西。

loader

在正常的页面中,引入 ​​css​​ 样式表会让页面变得更美观。引入图片可以让页面内容更丰富。

然而 webpack 本体只能处理原生的 JavaScript 模块,你让它处理 ​​css​​​ 或图片资源,它是无法直接处理的。为了处理这种问题,webpack 提供了 ​​loader​​ 的机制,用于对模块外的源码进行转换。

​loader​​​ 一般是单独的包,我们可以在社区找到对应 ​​loader​​​ 来处理特定的资源。在使用前通过 ​​npm​​​ 安装到项目的开发依赖中即可。​​loader​​​ 可以通过​​配置​​​、​​内联​​​或 ​​Cli​​​ 这三种方式来使用。下文主要以 ​​配置​​ 的方式来使用。

css

往常引入 ​​css​​​ 样式表无非就是在 ​​html​​​ 中通过 ​​<link>​​​ 标签引入。现在想通过 webpack 来管理依赖得需要安装对应的 ​​loader​​ 来处理这些事。

​​css-loader​​​ 可以让 webpack 可以引入 ​​css​​​ 资源。光有让 webpack 识别 css 的能还不够。为了能将 ​​css​​​ 资源进行导出,还要安装 ​​mini-css-extract-plugin​​ 插件:

现在将上一节的 Demo 复制并重名为 ​​getting-started-loader-css​​。进入新的项目目录后安装依赖:

npm install -D css-loader mini-css-extract-plugin

在更改配置之前,为了使项目结构更清晰,咱们按照文件类型重新调整源码目录结构。将 ​​src​​​ 下的 ​​js​​​ 文件都放进 ​​js​​​ 文件夹中。同时创建 ​​/src/css/style.css​​ 样式表。调整后的目录结构如下:

.
├── package.json
├── src
│ ├── index.html
│ ├── css
│ │ └── style.css
│ └── js
│ ├── index.js
│ └── log.js
└── webpack.config.js

现在将 ​​Flexbox 布局用例​​ 中结尾的 Demo 迁移到项目中,测试一下效果:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Test</title>
</head>
<body>
<div class="panels">
<div class="panel panel1">
<p class="item name">Alice</p>
<p class="item index">I</p>
<p class="item desc">Pixiv Content ID: 65843704</p>
</div>
<div class="panel panel2">
<p class="item name">Birthday</p>
<p class="item index">II</p>
<p class="item desc">Pixiv Content ID: 70487844</p>
</div>
<div class="panel panel3">
<p class="item name">Dream</p>
<p class="item index">III</p>
<p class="item desc">Pixiv Content ID: 65040104</p>
</div>
<div class="panel panel4">
<p class="item name">Daliy</p>
<p class="item index">IV</p>
<p class="item desc">Pixiv Content ID: 64702860</p>
</div>
<div class="panel panel5">
<p class="item name">Schoolyard</p>
<p class="item index">V</p>
<p class="item desc">Pixiv Content ID: 67270728</p>
</div>
</div>
</body>
</html>

CSS 源码:

html {
font-family: 'helvetica neue';
font-size: 20px;
font-weight: 200;
background: #f7f7f7;
}

body,
p {
margin: 0;
}

.panels {
display: flex;
min-height: 100vh;
overflow: hidden;
}

.panel {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
color: white;
background: #ececec;
text-align: center;
box-shadow: inset 0 0 0 5px rgba(255, 255, 255, 0.1);
transition: font-size 0.7s cubic-bezier(0.61, -0.19, 0.7, -0.11),
flex 0.7s cubic-bezier(0.61, -0.19, 0.7, -0.11), background 0.2s;
font-size: 20px;
background-size: cover;
background-position: center;
cursor: pointer;
}

.panel1 {
background-color: #f4f8ea;
}

.panel2 {
background-color: #fffcdd;
}

.panel3 {
background-color: #beddcf;
}

.panel4 {
background-color: #c3cbd8;
}

.panel5 {
background-color: #dfe0e4;
}

.item {
flex: 1 0 auto;
display: flex;
justify-content: center;
align-items: center;
transition: transform 0.5s;
font-size: 1.6em;
font-family: 'Amatic SC', cursive;
text-shadow: 0 0 4px rgba(0, 0

举报

相关推荐

0 条评论