前端构建工具进化史之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 又是怎么一回事呢?

相关推荐
在路上`7 分钟前
vue3使用AntV X6 (图可视化引擎)历程[二]
javascript·vue.js
diaobusi-881 小时前
HTML5-标签
前端·html·html5
我命由我123451 小时前
CesiumJS 案例 P34:场景视图(3D 视图、2D 视图)
前端·javascript·3d·前端框架·html·html5·js
就是蠢啊1 小时前
封装/前线修饰符/Idea项目结构/package/impore
java·服务器·前端
SunnyRivers1 小时前
JavaScript动态渲染页面爬取之Selenium
javascript·selenium
GISer_Jing1 小时前
React中 Reconciliation算法详解
前端·算法·react.js
呵呵哒( ̄▽ ̄)"2 小时前
react-quill 富文本组件编写和应用
前端·javascript·react.js
蔚一2 小时前
运行npm install 时,卡在sill idealTree buildDeps没有反应
前端·spring boot·后端·npm·node.js·vue
喜欢踢足球的老罗2 小时前
搭建一个本地轻量级且好用的学习TypeScript语言的环境
javascript·学习·typescript
answerball2 小时前
跨域?不存在的!JSONP带你“曲线救国” 😎
前端·后端·设计