本快速入門指南將教您如何使用 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 的輸出非常相似。