本快速入门指南将教您如何使用 gulp 构建 TypeScript,以及如何将 Browserify、terser 或 Watchify 添加到 gulp 流中。本指南还展示了如何使用 Babelify 添加 Babel 功能。
最小化项目
让我们从创建一个新目录开始。我们暂时将其命名为 proj,但您可以根据需要更改名称。
shellmkdir projcd proj
首先,我们将按以下方式构建项目结构
proj/├─ src/└─ dist/
TypeScript 文件将从 src 文件夹开始,经过 TypeScript 编译器处理,最终生成到 dist 文件夹中。
让我们搭建起这个结构
shellmkdir srcmkdir dist
初始化项目
现在我们将此文件夹转换为一个 npm 包。
shellnpm init
系统会提示您回答一系列问题。除入口点外,您可以使用默认值。对于入口点,请输入 ./dist/main.js。您随时可以在生成的 package.json 文件中返回并更改这些设置。
安装依赖项
现在我们可以使用 npm install 来安装包。首先全局安装 gulp-cli(如果您使用的是 Unix 系统,可能需要在本指南的 npm install 命令前加上 sudo)。
shellnpm install -g gulp-cli
然后在项目的开发依赖中安装 typescript、gulp 和 gulp-typescript。Gulp-typescript 是用于 TypeScript 的 gulp 插件。
shellnpm install --save-dev typescript gulp@4.0.0 gulp-typescript
编写一个简单的示例
让我们编写一个 Hello World 程序。在 src 中,创建文件 main.ts
tsfunction hello(compiler: string) {console.log(`Hello from ${compiler}`);}hello("TypeScript");
在项目根目录 proj 中,创建文件 tsconfig.json
{"": ["src/main.ts"],"": {"": true,"": "es5"}}
创建 gulpfile.js
在项目根目录中,创建文件 gulpfile.js
jsvar 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"));});
测试最终应用
shellgulpnode dist/main.js
该程序应该打印“Hello from TypeScript!”。
为代码添加模块
在进入 Browserify 之前,让我们先构建代码并添加模块。这是您在实际应用中更可能使用的结构。
创建一个名为 src/greet.ts 的文件
tsexport function sayHello(name: string) {return `Hello from ${name}`;}
现在更改 src/main.ts 中的代码,以从 greet.ts 导入 sayHello
tsimport { sayHello } from "./greet";console.log(sayHello("TypeScript"));
最后,将 src/greet.ts 添加到 tsconfig.json
{"": ["src/main.ts", "src/greet.ts"],"": {"": true,"": "es5"}}
通过运行 gulp 然后在 Node 中测试,确保模块正常工作
shellgulpnode dist/main.js
请注意,尽管我们使用了 ES2015 模块语法,但 TypeScript 发出了 Node 使用的 CommonJS 模块。在本教程中,我们将沿用 CommonJS,但您可以设置选项对象中的 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 的格式。
shellnpm 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 以更新页面
tsimport { sayHello } from "./greet";function showHello(divName: string, name: string) {const elt = document.getElementById(divName);elt.innerText = sayHello(name);}showHello("greeting", "TypeScript");
调用 showHello 会调用 sayHello 来更改段落的文本。现在将您的 gulpfile 更改为以下内容
jsvar 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 更改为调用 Browserify 与 tsify 插件,而不是 gulp-typescript。方便的是,它们都允许我们将相同的选项对象传递给 TypeScript 编译器。
在调用 bundle 后,我们使用 source(我们对 vinyl-source-stream 的别名)将我们的输出包命名为 bundle.js。
通过运行 gulp 并然后在浏览器中打开 dist/index.html 来测试页面。您应该会在页面上看到“Hello from TypeScript”。
请注意,我们为 Browserify 指定了 debug: true。这会导致 tsify 在打包的 JavaScript 文件中发出源映射 (source maps)。源映射允许您在浏览器中调试原始的 TypeScript 代码,而不是打包后的 JavaScript。您可以通过打开浏览器的调试器并在 main.ts 中设置断点来测试源映射是否正常工作。当您刷新页面时,断点应该会暂停页面,并允许您调试 greet.ts。
Watchify、Babel 和 Terser
既然我们正在使用 Browserify 和 tsify 打包代码,我们可以通过 Browserify 插件为构建添加各种特性。
-
Watchify 会启动 gulp 并保持运行,每当您保存文件时都会进行增量编译。这使您能够在浏览器中保持编辑-保存-刷新的循环。
-
Babel 是一个非常灵活的编译器,它将 ES2015 及更高版本转换为 ES5 和 ES3。这使您能够添加 TypeScript 不支持的广泛且自定义的转换。
-
Terser 会压缩您的代码,从而减少下载时间。
Watchify
我们将从 Watchify 开始,以提供后台编译。
shellnpm install --save-dev watchify fancy-log
现在将您的 gulpfile 更改为以下内容
jsvar 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);
这里基本上有三处更改,但它们需要您稍微重构一下代码。
- 我们将
browserify实例包装在对watchify的调用中,并保留结果。 - 我们调用了
watchedBrowserify.on('update', bundle);,这样每当您的任何 TypeScript 文件发生变化时,Browserify 就会运行bundle函数。 - 我们调用了
watchedBrowserify.on('log', fancy_log);来向控制台记录日志。
合起来,(1) 和 (2) 意味着我们必须将对 browserify 的调用移出 default 任务。并且我们必须给 default 的函数起一个名字,因为 Watchify 和 Gulp 都需要调用它。添加 (3) 中的日志记录是可选的,但对于调试您的设置非常有用。
现在,当您运行 Gulp 时,它应该会启动并保持运行。尝试更改 main.ts 中 showHello 的代码并保存。您应该看到类似于这样的输出
shellproj$ 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 以保持源映射正常工作。
shellnpm install --save-dev gulp-terser vinyl-buffer gulp-sourcemaps
现在将您的 gulpfile 更改为以下内容
jsvar 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 的调用是为了确保源映射保持工作。这些调用为我们提供了一个单独的源映射文件,而不是像以前那样使用内联源映射。现在您可以运行 Gulp,并检查 bundle.js 是否被压缩成了不可读的混乱代码
shellgulpcat dist/bundle.js
Babel
首先安装 Babelify 和用于 ES2015 的 Babel 预设。像 Terser 一样,Babelify 会混淆代码,因此我们需要 vinyl-buffer 和 gulp-sourcemaps。默认情况下,Babelify 将仅处理扩展名为 .js、.es、.es6 和 .jsx 的文件,因此我们需要将 .ts 扩展名添加为 Babelify 的选项。
shellnpm install --save-dev babelify@8 babel-core babel-preset-es2015 vinyl-buffer gulp-sourcemaps
现在将您的 gulpfile 更改为以下内容
jsvar 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
{"": ["src/main.ts"],"": {"": true,"": "es2015"}}
Babel 的 ES5 输出对于这样一个简单的脚本应该与 TypeScript 的输出非常相似。