Skip to content
赞助商
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待

Gulp ​

本快速入门指南将教你如何使用 gulp 构建 TypeScript,然后在 gulp 管道中添加 Browserify、terser 或 Watchify。本指南还展示了如何使用 Babelify 添加 Babel 功能。

我们假设你已经在使用 Node.js 和 npm。

最小项目 ​

让我们从一个新目录开始。 我们暂时将其命名为 proj,但你可以根据需要更改它。

shell
mkdir proj
cd proj

首先,我们将按以下方式组织项目结构:

proj/
   ├─ src/
   └─ dist/

TypeScript 文件将放在 src 文件夹中,经过 TypeScript 编译器处理后,最终输出到 dist。

让我们创建这些目录:

shell
mkdir src
mkdir dist

初始化项目 ​

现在我们将把这个文件夹转换为 npm 包。

shell
npm init

你会看到一系列提示。 除了入口点,你可以使用默认值。 对于入口点,使用 ./dist/main.js。 你随时可以在生成的 package.json 文件中修改这些设置。

安装依赖项 ​

现在我们可以使用 npm install 安装包。 首先全局安装 gulp-cli(如果你使用 Unix 系统,可能需要在本文中的 npm install 命令前加上 sudo)。

shell
npm install -g gulp-cli

然后在项目的开发依赖中安装 typescript、gulp 和 gulp-typescript。 Gulp-typescript 是 TypeScript 的 gulp 插件。

shell
npm install --save-dev typescript gulp@4.0.0 gulp-typescript

编写一个简单的示例 ​

让我们编写一个 Hello World 程序。 在 src 中创建文件 main.ts:

ts
function hello(compiler: string) {
  console.log(`Hello from ${compiler}`);
}
hello("TypeScript");

在项目根目录 proj 中创建文件 tsconfig.json:

json
{
  "files": ["src/main.ts"],
  "compilerOptions": {
    "noImplicitAny": true,
    "target": "es5"
  }
}

创建 gulpfile.js ​

在项目根目录中创建文件 gulpfile.js:

js
var gulp = require("gulp");
var ts = require("gulp-typescript");
var tsProject = ts.createProject("tsconfig.json");

gulp.task("default", function () {
  return tsProject.src().pipe(tsProject()).js.pipe(gulp.dest("dist"));
});

测试生成的应用程序 ​

shell
gulp
node dist/main.js

程序应打印出 "Hello from TypeScript!"。

为代码添加模块 ​

在我们使用 Browserify 之前,让我们先扩展代码并加入模块。 这是你在真实应用中更可能使用的结构。

创建文件 src/greet.ts:

ts
export function sayHello(name: string) {
  return `Hello from ${name}`;
}

现在修改 src/main.ts 中的代码,从 greet.ts 导入 sayHello:

ts
import { sayHello } from "./greet";

console.log(sayHello("TypeScript"));

最后,将 src/greet.ts 添加到 tsconfig.json:

json
{
  "files": ["src/main.ts", "src/greet.ts"],
  "compilerOptions": {
    "noImplicitAny": true,
    "target": "es5"
  }
}

通过运行 gulp 然后在 Node 中测试,确保模块能正常工作:

shell
gulp
node dist/main.js

注意,即使我们使用了 ES2015 模块语法,TypeScript 仍然输出 Node 使用的 CommonJS 模块。 在本教程中,我们将继续使用 CommonJS,但你可以在 options 对象中设置 module 来更改这一点。

Browserify ​

现在让我们把这个项目从 Node 迁移到浏览器。 为此,我们希望将所有模块打包成一个 JavaScript 文件。 幸运的是,这正是 Browserify 所做的。 更棒的是,它允许我们使用 Node 使用的 CommonJS 模块系统,这也是 TypeScript 默认的输出格式。 这意味着我们的 TypeScript 和 Node 设置基本上可以不经修改地迁移到浏览器。

首先,安装 browserify、tsify 和 vinyl-source-stream。 tsify 是一个 Browserify 插件,像 gulp-typescript 一样,可以访问 TypeScript 编译器。 vinyl-source-stream 允许我们将 Browserify 的文件输出适配回 gulp 能够理解的称为 vinyl 的格式。

shell
npm install --save-dev browserify tsify vinyl-source-stream

创建一个页面 ​

在 src 中创建文件 index.html:

html
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <title>Hello World!</title>
  </head>
  <body>
    <p id="greeting">Loading ...</p>
    <script src="bundle.js"></script>
  </body>
</html>

现在修改 main.ts 以更新页面:

ts
import { sayHello } from "./greet";

function showHello(divName: string, name: string) {
  const elt = document.getElementById(divName);
  elt.innerText = sayHello(name);
}

showHello("greeting", "TypeScript");

调用 showHello 会调用 sayHello 来改变段落的文本。 现在将你的 gulpfile 修改为如下内容:

js
var gulp = require("gulp");
var browserify = require("browserify");
var source = require("vinyl-source-stream");
var tsify = require("tsify");
var paths = {
  pages: ["src/*.html"],
};

gulp.task("copy-html", function () {
  return gulp.src(paths.pages).pipe(gulp.dest("dist"));
});

gulp.task(
  "default",
  gulp.series(gulp.parallel("copy-html"), function () {
    return browserify({
      basedir: ".",
      debug: true,
      entries: ["src/main.ts"],
      cache: {},
      packageCache: {},
    })
      .plugin(tsify)
      .bundle()
      .pipe(source("bundle.js"))
      .pipe(gulp.dest("dist"));
  })
);

这里添加了 copy-html 任务,并将其作为 default 的依赖项。 这意味着每次运行 default 时,都必须先运行 copy-html。 我们还将 default 改为使用带有 tsify 插件的 Browserify,而不是 gulp-typescript。 方便的是,它们都允许我们向 TypeScript 编译器传递相同的 options 对象。

调用 bundle 后,我们使用 source(vinyl-source-stream 的别名)将输出包命名为 bundle.js。

通过运行 gulp 然后在浏览器中打开 dist/index.html 来测试页面。 你应该在页面上看到 "Hello from TypeScript"。

注意,我们向 Browserify 指定了 debug: true。 这会使 tsify 在打包的 JavaScript 文件中内联 source maps。 Source maps 允许你在浏览器中调试原始 TypeScript 代码,而不是打包后的 JavaScript。 你可以通过打开浏览器的调试器并在 main.ts 中设置断点来测试 source maps 是否正常工作。 刷新页面时,断点应暂停页面并允许你调试 greet.ts。

Watchify、Babel 和 Terser ​

现在我们已经使用 Browserify 和 tsify 打包代码,我们可以通过 browserify 插件向构建中添加各种功能。

  • Watchify 启动 gulp 并使其保持运行,在你保存文件时增量编译。 这让你能够在浏览器中保持编辑-保存-刷新的循环。

  • Babel 是一个极其灵活的编译器,可将 ES2015 及更高版本转换为 ES5 和 ES3。 这让你可以添加 TypeScript 不支持的大量自定义转换。

  • Terser 压缩你的代码,使其下载时间更短。

Watchify ​

我们从 Watchify 开始,以提供后台编译:

shell
npm install --save-dev watchify fancy-log

现在将你的 gulpfile 修改为如下内容:

js
var gulp = require("gulp");
var browserify = require("browserify");
var source = require("vinyl-source-stream");
var watchify = require("watchify");
var tsify = require("tsify");
var fancy_log = require("fancy-log");
var paths = {
  pages: ["src/*.html"],
};

var watchedBrowserify = watchify(
  browserify({
    basedir: ".",
    debug: true,
    entries: ["src/main.ts"],
    cache: {},
    packageCache: {},
  }).plugin(tsify)
);

gulp.task("copy-html", function () {
  return gulp.src(paths.pages).pipe(gulp.dest("dist"));
});

function bundle() {
  return watchedBrowserify
    .bundle()
    .on("error", fancy_log)
    .pipe(source("bundle.js"))
    .pipe(gulp.dest("dist"));
}

gulp.task("default", gulp.series(gulp.parallel("copy-html"), bundle));
watchedBrowserify.on("update", bundle);
watchedBrowserify.on("log", fancy_log);

这里基本上有三处变化,但它们要求你稍微重构一下代码。

  1. 我们将 browserify 实例包装在 watchify 调用中,并保存结果。
  2. 我们调用了 watchedBrowserify.on('update', bundle);,这样每当你的 TypeScript 文件发生变化时,Browserify 就会运行 bundle 函数。
  3. 我们调用了 watchedBrowserify.on('log', fancy_log); 以便将日志输出到控制台。

结合 (1) 和 (2) 意味着我们必须将对 browserify 的调用移出 default 任务。 并且我们必须为 default 的函数命名,因为 Watchify 和 Gulp 都需要调用它。 (3) 中添加日志是可选的,但对于调试你的设置非常有用。

现在当你运行 Gulp 时,它应该启动并保持运行。 尝试修改 main.ts 中的 showHello 代码并保存。 你应该会看到类似这样的输出:

shell
proj$ gulp
[10:34:20] Using gulpfile ~/src/proj/gulpfile.js
[10:34:20] Starting 'copy-html'...
[10:34:20] Finished 'copy-html' after 26 ms
[10:34:20] Starting 'default'...
[10:34:21] 2824 bytes written (0.13 seconds)
[10:34:21] Finished 'default' after 1.36 s
[10:35:22] 2261 bytes written (0.02 seconds)
[10:35:24] 2808 bytes written (0.05 seconds)

Terser ​

首先安装 Terser。 由于 Terser 的目的是压缩代码,我们还需要安装 vinyl-buffer 和 gulp-sourcemaps 以保持 source maps 正常工作。

shell
npm install --save-dev gulp-terser vinyl-buffer gulp-sourcemaps

现在将你的 gulpfile 修改为如下内容:

js
var gulp = require("gulp");
var browserify = require("browserify");
var source = require("vinyl-source-stream");
var terser = require("gulp-terser");
var tsify = require("tsify");
var sourcemaps = require("gulp-sourcemaps");
var buffer = require("vinyl-buffer");
var paths = {
  pages: ["src/*.html"],
};

gulp.task("copy-html", function () {
  return gulp.src(paths.pages).pipe(gulp.dest("dist"));
});

gulp.task(
  "default",
  gulp.series(gulp.parallel("copy-html"), function () {
    return browserify({
      basedir: ".",
      debug: true,
      entries: ["src/main.ts"],
      cache: {},
      packageCache: {},
    })
      .plugin(tsify)
      .bundle()
      .pipe(source("bundle.js"))
      .pipe(buffer())
      .pipe(sourcemaps.init({ loadMaps: true }))
      .pipe(terser())
      .pipe(sourcemaps.write("./"))
      .pipe(gulp.dest("dist"));
  })
);

注意 terser 本身只有一次调用——而调用 buffer 和 sourcemaps 是为了确保 source maps 继续工作。 这些调用为我们生成了一个单独的 source map 文件,而不是像以前那样使用内联 source maps。 现在你可以运行 Gulp 并检查 bundle.js 是否被压缩成了难以阅读的乱码:

shell
gulp
cat dist/bundle.js

Babel ​

首先安装 Babelify 和用于 ES2015 的 Babel preset。 与 Terser 类似,Babelify 会转换代码,因此我们还需要 vinyl-buffer 和 gulp-sourcemaps。 默认情况下,Babelify 只处理扩展名为 .js、.es、.es6 和 .jsx 的文件,因此我们需要将 .ts 扩展名作为选项传递给 Babelify。

shell
npm install --save-dev babelify@8 babel-core babel-preset-es2015 vinyl-buffer gulp-sourcemaps

现在将你的 gulpfile 修改为如下内容:

js
var gulp = require("gulp");
var browserify = require("browserify");
var source = require("vinyl-source-stream");
var tsify = require("tsify");
var sourcemaps = require("gulp-sourcemaps");
var buffer = require("vinyl-buffer");
var paths = {
  pages: ["src/*.html"],
};

gulp.task("copy-html", function () {
  return gulp.src(paths.pages).pipe(gulp.dest("dist"));
});

gulp.task(
  "default",
  gulp.series(gulp.parallel("copy-html"), function () {
    return browserify({
      basedir: ".",
      debug: true,
      entries: ["src/main.ts"],
      cache: {},
      packageCache: {},
    })
      .plugin(tsify)
      .transform("babelify", {
        presets: ["es2015"],
        extensions: [".ts"],
      })
      .bundle()
      .pipe(source("bundle.js"))
      .pipe(buffer())
      .pipe(sourcemaps.init({ loadMaps: true }))
      .pipe(sourcemaps.write("./"))
      .pipe(gulp.dest("dist"));
  })
);

我们还需要让 TypeScript 以 ES2015 为目标。 然后 Babel 将从 TypeScript 输出的 ES2015 代码生成 ES5。 让我们修改 tsconfig.json:

json
{
  "files": ["src/main.ts"],
  "compilerOptions": {
    "noImplicitAny": true,
    "target": "es2015"
  }
}

对于这样一个简单的脚本,Babel 的 ES5 输出应该与 TypeScript 的输出非常相似。