前端构建工具进化史之Gulp

背景

在刚刚接触前端时候,对于形形色色的前端工具,总是分不清其中的区别。为什么这里是这个工具,那里又是另外一个。已经有了一个,为什么又冒出来一个。这里我们通过对比 Gulp 和 webpack,介绍一下前端构建工具的其中一类:基于任务的构建。这方面在知乎上也有相关讨论。

Gulp

首先就是 Gulp 是什么呢?

A toolkit to automate & enhance your workflow.

这是官方的定义。但是哪怕翻译成中文也不好理解。接下来,我们通过完成一个小例子来理解 Gulp。 假设现在有一个前端小项目,使用 less 编写样式,typescript 编写逻辑,使用 Gulp 来进行构建,应该怎么做呢?

安装

首先我们通过 pnpm init 初始化项目。然后安装相关依赖,gulp-clean-css 和 gulp-less 处理less 文件,gulp-typescript 和 typescript gulp-uglify 处理 typescript 文件, gulp-clean 用于每次构建前删除相关目录。所有的依赖如下。

json 复制代码
"devDependencies": {
    "gulp": "^4.0.2",
    "gulp-clean": "^0.4.0",
    "gulp-clean-css": "^4.3.0",
    "gulp-concat": "^2.6.1",
    "gulp-htmlmin": "^5.0.1",
    "gulp-less": "^5.0.0",
    "gulp-typescript": "6.0.0-alpha.1",
    "gulp-uglify": "^3.0.2",
    "typescript": "^5.2.2"
}

项目内容

在 src 目录下,新建项目的入口文件 index.html

xml 复制代码
|--src
  |--index.html

// index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link href="./css/style.min.css" rel="stylesheet" />
    <title>Gulp Demo</title>
</head>
<body>
    <div class="one">
        hello
    </div>
    <div class="two">
        world
    </div>
    <div class="container">
    </div>
    <script src="./js/main.min.js"></script>
</body>
</html>

新建相关的 less 文件

less 复制代码
|--src
  |--css
    |--one.less
    |--two.less
    |--var.less

// one.less
@import './var.less';
.one {
    color: @one-color;
}

// two.less
@import './var.less';
.two {
    color: @two-color;
}

// var.less
@one-color: green;
@two-color: red;

新建项目相关的 typescript 文件

typescript 复制代码
|--src
  |--js
    |--index.ts
 
// index.ts
function add(a: number, b: number): number {
    return a + b;
}

const container = document.querySelector('.container');
container!.innerHTML = `${add(1, 2)}`

最后项目的结构为

css 复制代码
|--src
  |--css
    |--one.less
    |--two.less
    |--var.less
  |--js
    |--index.ts
  |--index.html

在 package.json 中添加一个构建命令

json 复制代码
"scripts": {
    "build": "gulp"
}

Gulp 任务

上面的 index.html 文件是不可以直接在浏览器运行的。 所以接下来,我们使用 Gulp 对项目进行构建处理。在项目的根目录创建 Gulp 配置文件 gulpfile.js,运行 Gulp 的时候,会自动读取这个文件。

在 Gulp 中,一个任务就是一个js 异步函数,首先我们处理 less 文件,将其转化为 css 文件,并压缩。

scss 复制代码
function lessProcess() {
  return src("src/**/*.less")
    .pipe(less())
    .pipe(concat("style.min.css"))
    .pipe(cleanCss())
    .pipe(dest("dist/css"));
} 

这里首先读取 src 目录下的所有 less 文件,然后传给 Gulp 的 less 插件处理,处理以后,使用 concat 插件进行拼接,然后使用 cleanCss 压缩,最后输出到 dist/css 目录下。

接下来处理 typescript 文件。同样新建一个 typescript 的处理任务。

less 复制代码
function tsProcess() {
  return src("src/**/*.ts")
    .pipe(
      ts({
        lib: ["es2015", "dom"],
        module: "es2015",
        target: "es2015",
      })
    )
    .pipe(concat("main.min.js"))
    .pipe(uglify())
    .pipe(dest("dist/js"));
}

任务的处理流程同 less 文件差不多。读取 src 目录下的 ts 文件,传给 ts 插件,最后压缩输出到 dist/js 目录下。

对于 html 文件,我们使用 htmlMin 对其进行压缩处理。

php 复制代码
function htmlProcess() {
  return src("src/**/*.html")
    .pipe(htmlMin({ collapseWhitespace: true }))
    .pipe(dest("dist"));
}

由于,我们会对项目进行多次构建,所以需要在每次构建前将 dist 目录进行删除。这样才能保证不会出现构建之间的干扰。所以,我们添加一个删除 dist 目录的任务。

php 复制代码
function cleanDist() {
  return src("dist", { read: false, allowEmpty: true }).pipe(clean());
}

接下来,使用 Gulp 将四个任务组合起来。

ini 复制代码
exports.default = series(
  cleanDist,
  parallel([lessProcess, tsProcess, htmlProcess])
);

series 和 parallel 是 Gulp 中的两种任务运行方式,series 表示串行,parallel 表示并行。这里的意思中,首先运行任务 cleanDist,然后并行运行任务 lessProcess、 tsProcess 和 htmlProcess。 最后 gulpfile.js 的文件内容是:

php 复制代码
const { series, parallel, src, dest } = require("gulp");
const less = require("gulp-less");
const concat = require("gulp-concat");
const cleanCss = require("gulp-clean-css");
const ts = require("gulp-typescript");
const uglify = require("gulp-uglify");
const htmlMin = require("gulp-htmlmin");
const clean = require("gulp-clean");

function cleanDist() {
  return src("dist", { read: false, allowEmpty: true }).pipe(clean());
}

function lessProcess() {
  return src("src/**/*.less")
    .pipe(less())
    .pipe(concat("style.min.css"))
    .pipe(cleanCss())
    .pipe(dest("dist/css"));
}

function tsProcess() {
  return src("src/**/*.ts")
    .pipe(
      ts({
        lib: ["es2015", "dom"],
        module: "es2015",
        target: "es2015",
      })
    )
    .pipe(concat("main.min.js"))
    .pipe(uglify())
    .pipe(dest("dist/js"));
}

function htmlProcess() {
  return src("src/**/*.html")
    .pipe(htmlMin({ collapseWhitespace: true }))
    .pipe(dest("dist"));
}

exports.default = series(
  cleanDist,
  parallel([lessProcess, tsProcess, htmlProcess])
);

运行pnpm build 命令,会在 dist 目录生成以下文件

css 复制代码
|--dist
  |--css
    |--style.min.css
  |--js
    |--main.min.js
  index.html

运行 index.html 文件以后,就可以看到项目结果。

Gulp 小结

可以感受到,Gulp 的使用方式,就是建立一个一个的任务,将这些任务组合起来,交给 Gulp 自动执行。对于 Gulp 本身,只是负责执行这些任务,而任务的具体内容,则需要开发者自己编写或使用已有插件。

webpack

在这一小节中,我们使用 webpack 来构建上面的小项目。首先修改一下 index.html 和 index.ts。 删除 index.html 中对 css 和 js 的引用。

xml 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>webpack Demo</title>
</head>
<body>
    <div class="one">
        hello
    </div>
    <div class="two">
        world
    </div>
    <div class="container">
    </div>
</body>
</html>

然后修改 index.ts,在这里引入样式文件。

typescript 复制代码
import '../css/one.less';
import '../css/two.less';

function add(a: number, b: number): number {
    return a + b;
}
const container = document.querySelector('.container');
container!.innerHTML = `${add(1, 2)}`

接下来安装相关依赖。

json 复制代码
"devDependencies": {
    "css-loader": "^6.8.1",
    "html-webpack-plugin": "^5.5.3",
    "less": "^4.2.0",
    "less-loader": "^11.1.3",
    "style-loader": "^3.3.3",
    "ts-loader": "^9.5.0",
    "typescript": "^5.2.2",
    "webpack": "^5.89.0",
    "webpack-cli": "^5.1.4"
}

修改构建脚步命令:

json 复制代码
"scripts": {
    "build": "webpack"
},

在项目根目录下,新建 webpack.config.js 文件

javascript 复制代码
// webpack.config.js
const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");

module.exports = {
  entry: "./src/js/index.ts",
  output: {
    path: path.resolve(__dirname, "dist"),
    filename: "main.min.js",
    clean: true, // 清空输出目录
  },
  module: {
    rules: [
      {
        test: /.less$/,
        use: ["style-loader", "css-loader", "less-loader"],
      },
      {
        test: /\.ts$/,
        use: "ts-loader",
        exclude: /node_modules/,
      },
    ],
  },
  resolve: {
    extensions: [".ts"],
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: "./src/index.html",
    }),
  ],
};

由于我们使用了 typescript 和 ts-loader,需要新建一个 tsconfig.json 文件,这里内容为空就可以了。

运行pnpm build 命令,会在 dist 目录下生成 index.html 和 main.min.js 文件,浏览器打开 index.html,运行结果同上一小节的结果一样。

webpack 的方式中,我们在 webpack.config.js 文件中配置了入口entry: "./src/js/index.ts",webpack 会从这个文件开始,递归收集所有的依赖文件,对收集的文件使用 loader 处理,最后打包成一个文件,输出到 dist 目录下。

对比

实际上,在上面的小项目中,我们做了一个取巧,就是只有一个 indext.ts 文件。如果存在多个 typescript 文件,文件之间通过 import 引用。那么上面的 Gulp 任务是无法处理的。下面我们试一下。

在src 目录下新建一个 subract.ts

typescript 复制代码
// subract.ts
const subtract = (a: number, b: number): number => {
    return a - b;
}

export default subtract;

然后在 index.ts 文件中引入

typescript 复制代码
// index.ts
import '../css/one.less';
import '../css/two.less';
import sub from './subtract';

function add(a: number, b: number): number {
    return a + b;
}

const container = document.querySelector('.container');
container!.innerHTML = `${add(1, 2)}`

console.log(sub(1, 2));

使用 Gulp 的方式,只会将 index.ts 和 subract.ts 分别编译为 js 代码,然后简单拼接起来,依然不能运行。使用 webpack 的方式,则可以生成可运行的代码。

如果我们想要使 Gulp 的方式生效,则需要使用插件解决 typescript 文件之间的引用问题。

所以可以看到,Gulp 和 webpack 完全就不是一类东西。虽然都可以用来处理前端项目,但是使用的场景却不太一样。在需要处理引用依赖的情况下,比如需要合并多个通过 import 关联的文件,可能更适合 webpack。如果是一些文件复制,文件内容的替换,文件拼接,文件压缩,这些情况下,就可以考虑使用 Gulp。

还有一种用法就是,可以将 webpack 直接集成到 Gulp 里面使用。比如下面这段来自 @ant-design/tools 的代码。

我们可以感受到,Gulp 并不算是一个前端的构建工具,而是一个任务运行器。我们可以将一些重复性的工作,转为 Gulp 的一个任务,然后让 Gulp 来重复执行这个工作。

在前端构建工具快速发展的今天,大家在一般的业务开发中,基本很少会使用到 Gulp。所以就会有种 Gulp 已经过时的感觉。但是在某些场景下面,Gulp 依然在被广泛使用,甚至不可替代。比如当我最近在项目上对组件库的样式进行处理的时候,需要使用tailwind 生成样式文件,添加对 tailwind 生成文件的引用,需要对所有的 less 文件进行复制,还需要使用 babel 将 js 代码转为 ast,然后对代码内容修改。这些任务都可以转化为一个一个的 Gulp 任务,使用 Gulp 执行。

在开源方面,也可以看到一些 Gulp 的使用。比如,下面的代码是 antd 使用 Gulp 对文件内容进行更改,将 less 引用替换为 css 引用。

总结

我们首先创建了一个小项目,然后使用 Gulp 和 webpack 分别对其处理,最后对比了两种方式的区别。Gulp 同之前的 AMDBrowserify 有一点不一样就是,Gulp 虽然用的少,但是在某些情况下,还是有使用场景。而 AMD 和 Browserify 在现在的 web 项目开发中,感觉基本没有使用场景了。除了 Gulp,还有一个类似的工具 Grunt,因为功能类似就不展开说了。构建工具的历史内容就这么多了,后续我们看看 webpack 以及 rollup 又是怎么一回事呢?

相关推荐
百万蹄蹄向前冲2 分钟前
Trae分析Phaser.js游戏《洋葱头捡星星》
前端·游戏开发·trae
朝阳58140 分钟前
在浏览器端使用 xml2js 遇到的报错及解决方法
前端
GIS之路1 小时前
GeoTools 读取影像元数据
前端
ssshooter1 小时前
VSCode 自带的 TS 版本可能跟项目TS 版本不一样
前端·面试·typescript
你的人类朋友1 小时前
【Node.js】什么是Node.js
javascript·后端·node.js
Jerry2 小时前
Jetpack Compose 中的状态
前端
dae bal3 小时前
关于RSA和AES加密
前端·vue.js
柳杉3 小时前
使用three.js搭建3d隧道监测-2
前端·javascript·数据可视化
lynn8570_blog3 小时前
低端设备加载webp ANR
前端·算法
LKAI.3 小时前
传统方式部署(RuoYi-Cloud)微服务
java·linux·前端·后端·微服务·node.js·ruoyi