一、初始化工程

首先创建一个 gulp 构建的简单工程,具体的目录及说明如下:

|-- dist/
|-- src/
    | -- main.ts
|-- tscofnig.json
|-- gulpfile.js
|-- package.json
|-- .gitignore

上面目录,开发是在 src/ 中进行的,而最终 gulp 处理之后的 bundle 放在 dist 中。

安装 gulp 及相关的依赖

首先需要全局安装 gulp-cli 工具,用于编译。

npm install -g gulp-cli

安装 typescriptgulpgulp-typescript 三个 dev 依赖,其中 gulp-typescript 是 gulp 的一个插件:https://www.npmjs.com/package/gulp-typescript

yarn add -D typescript gulp gulp-typescript

二、简单的例子

1、src/main.ts

下面是一个典型的带 ES6 语法(注意是语法)的示例:

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

2、配置 tsconfig.json

为了编译 typescript,通过 tsconfig.json 是最简单和高效的配置方式,对于后续项目维护也有很大的好处:

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

上述配置中指定了入口文件和编译选型,最终我们希望编译的结果是 es5.

3、此时 tsc

如果在这个时候直接 tsc 编译文件而不借助 gulp,文件直接编译在了 src 目录中,但是这个文件还是可以正常运行的:

1.jpg

2.jpg

4、构建 gulpfile.js

目前还是没有用到 gulp,我们要将代码编译到最终的目录中去并且在这中间还做更多的事情,就需要借助 gulp。

构建项目的 gulpfile.js

const gulp = require('gulp');
const ts = require('gulp-typescript');
const tsProject = ts.createProject('tsconfig.json');

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

上面是一个典型的 gulp pipe 处理逻辑,关于 gulp-typescriptcreateProject({}) 使用 tscofig.json 可以看文档:https://www.npmjs.com/package/gulp-typescript#using-tsconfigjson

5、gulp 构建

构建之后的结果是:

3.jpg

5、运行 dist/main.js

运行之后的结果与上面结果一样就不重复了。

三、使用模块

在上面的例子的基础上引入模块:greet.ts

// path: src/greet.ts
export function sayHello(name: string) :string {
    return `Hello from ${name}`;
}

main.ts 修改如下:

import {sayHello} from './greet';

function hello(compiler: string) : void {
   console.log(sayHello(compiler));
}
hello("TypeScript");

当然,有了新的文件,还需要去 tsconfig.json 指定一下文件,单独编译出 js :

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

编译结果如下:

4.jpg

上面例子可以发现,虽然使用了 ES5 的导入和导出,但是还是转出了 CommonJS 的模块,这是默认行为,如果需要改变,可以修改 module 配置

四、 Browserify

Browserify 和 webpack 差不多也是打包工具,而且相比于 webpack 更早,有些时候也更加灵活,能够和 gulp 完美协作,而且支持 CommonJS 模块加载方式,因此不需要修改 gulp 和 ts 的配置。

yarn add -D browseriy tsify vinyl-source-stream

使用 browserify 需要安装上述依赖,其中 tsify 是 browserify 的一个插件,类似 gulp-typescript,能够访问 TypeScript 的编译器。而 vinyl-source-sream 会把 browserify 的输出文件是配成 gulp 能够解析的格式,这种格式叫做 vinyl

1、新建一个页面

src/index.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>

2、修改 main.ts

主要功能就是获取 id 然后修改 text

import { sayHello } from "./greet";

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

showHello("greeting", "TypeScript");

3、重新修改 gulpfile.js

两个可以并行的任务,分贝时 copy html 和 进行 browserify 处理,需要注意的:gulp v4 中,如果要运行多个 task, tast() 的第二个参数不能是一个数组的形式,比如['copy'] 必须调用 gulp.parallel() 或者是 gulp.series() 两个方法,再把 task 名字当做参数传入即可。两者分别代表 并行执行顺序执行

使用 tsify 插件调用 Browserify,而不是 gulp-typescript, 两者传递相同的参数对象到 TypeScript 编译器。

为 Broswerif y指定了 debug: true。 这会让 tsify 在输出文件里生成 source maps。 source maps允许在浏览器中直接调试 TypeScript 源码,而不是在合并后的 JavaScript 文件上调试。 你要打开调试器并在 main.ts里打一个断点,看看source maps是否能工作。 当你刷新页面时,代码会停在断点处,从而你就能够调试 greet.ts。

const gulp = require('gulp');
const browserify = require("browserify");
const source = require("vinyl-source-stream");
const tsify = require("tsify");

const paths = {
    pages:['src/*.html']
};

gulp.task('copy-html', () => {
    return gulp.src(paths.pages).pipe(gulp.dest('dist'));
});

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

4、打包结果:

打包结果如下所示

5.jpg

bundle.js

(function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][6][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r})()({1:[function(require,module,exports){
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
function sayHello(name) {
    return "Hello from " + name;
}
exports.sayHello = sayHello;
},{}],2:[function(require,module,exports){
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var greet_1 = require("./greet");
function showHello(divName, name) {
    var elt = document.getElementById(divName);
    elt.innerText = greet_1.sayHello(name);
}
showHello("greeting", "TypeScript");
},{"./greet":1}]},{},[2])
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIm5vZGVfbW9kdWxlcy9icm93c2VyLXBhY2svX3ByZWx1ZGUuanMiLCJzcmMvZ3JlZXQudHMiLCJzcmMvbWFpbi50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQTs7O0FDQUEsU0FBZ0IsUUFBUSxDQUFDLElBQVk7SUFDakMsT0FBTyxnQkFBYyxJQUFNLENBQUM7QUFDaEMsQ0FBQztBQUZELDRCQUVDOzs7O0FDRkQsaUNBQWlDO0FBRWpDLFNBQVMsU0FBUyxDQUFDLE9BQWUsRUFBRSxJQUFZO0lBQzdDLElBQU0sR0FBRyxHQUFHLFFBQVEsQ0FBQyxjQUFjLENBQUMsT0FBTyxDQUFDLENBQUM7SUFDN0MsR0FBRyxDQUFDLFNBQVMsR0FBRyxnQkFBUSxDQUFDLElBQUksQ0FBQyxDQUFDO0FBQ2xDLENBQUM7QUFFRCxTQUFTLENBQUMsVUFBVSxFQUFFLFlBQVksQ0FBQyxDQUFDIiwiZmlsZSI6ImdlbmVyYXRlZC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzQ29udGVudCI6WyIoZnVuY3Rpb24oKXtmdW5jdGlvbiByKGUsbix0KXtmdW5jdGlvbiBvKGksZil7aWYoIW5baV0pe2lmKCFlW2ldKXt2YXIgYz1cImZ1bmN0aW9uXCI9PXR5cGVvZiByZXF1aXJlJiZyZXF1aXJlO2lmKCFmJiZjKXJldHVybiBjKGksITApO2lmKHUpcmV0dXJuIHUoaSwhMCk7dmFyIGE9bmV3IEVycm9yKFwiQ2Fubm90IGZpbmQgbW9kdWxlICdcIitpK1wiJ1wiKTt0aHJvdyBhLmNvZGU9XCJNT0RVTEVfTk9UX0ZPVU5EXCIsYX12YXIgcD1uW2ldPXtleHBvcnRzOnt9fTtlW2ldWzBdLmNhbGwocC5leHBvcnRzLGZ1bmN0aW9uKHIpe3ZhciBuPWVbaV1bMV1bcl07cmV0dXJuIG8obnx8cil9LHAscC5leHBvcnRzLHIsZSxuLHQpfXJldHVybiBuW2ldLmV4cG9ydHN9Zm9yKHZhciB1PVwiZnVuY3Rpb25cIj09dHlwZW9mIHJlcXVpcmUmJnJlcXVpcmUsaT0wO2k8dC5sZW5ndGg7aSsrKW8odFtpXSk7cmV0dXJuIG99cmV0dXJuIHJ9KSgpIiwiZXhwb3J0IGZ1bmN0aW9uIHNheUhlbGxvKG5hbWU6IHN0cmluZykgOnN0cmluZyB7XHJcbiAgICByZXR1cm4gYEhlbGxvIGZyb20gJHtuYW1lfWA7XHJcbn0iLCJpbXBvcnQge3NheUhlbGxvfSBmcm9tICcuL2dyZWV0JztcclxuXHJcbmZ1bmN0aW9uIHNob3dIZWxsbyhkaXZOYW1lOiBzdHJpbmcsIG5hbWU6IHN0cmluZykge1xyXG4gICBjb25zdCBlbHQgPSBkb2N1bWVudC5nZXRFbGVtZW50QnlJZChkaXZOYW1lKTtcclxuICAgZWx0LmlubmVyVGV4dCA9IHNheUhlbGxvKG5hbWUpO1xyXG59XHJcblxyXG5zaG93SGVsbG8oXCJncmVldGluZ1wiLCBcIlR5cGVTY3JpcHRcIik7Il19

5、运行结果

通过 liver-server 开启 web 服务,运行之后:

6.jpg

五、 Watchify,Babel 和 Uglify

代码已经能够打包了,既然使用了 Browserify ,能够通过 Browserify 插件添加一些新的特性:

  • Watchify 启动 Gulp 并保持运行状态,当你保存文件时自动编译。
  • Babel 是个十分灵活的编译器,将 ES2015 及以上版本的代码转换成 ES5 和 ES3 。 添加大量自定义的 TypeScript 目前不支持的转换器。
  • Uglify 压缩代码,将花费更少的时间去下载它们。

1、Watchify

安装依赖:

yarn add -D watchify gulp-util

修改 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 gutil = require("gulp-util");
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()
        .pipe(source('bundle.js'))
        .pipe(gulp.dest("dist"));
}

gulp.task("default", ["copy-html"], bundle);
watchedBrowserify.on("update", bundle);
watchedBrowserify.on("log", gutil.log);

上面的修改主要有三点:

  1. 将 browserify 实例包裹在 watchify 的调用里,控制生成的结果。
  2. 调用 watchedBrowserify.on("update", bundle);,每次 TypeScript 文件改变时 Browserify 会执行 bundle 函数。
  3. 调用 watchedBrowserify.on("log", gutil.log); 将日志打印到控制台。

(1)和(2)在一起意味着我们要将 browserify 调用移出 default 任务。 然后给函数起个名字,因为 Watchify 和 Gulp 都要调用它。 (3)是可选的,但是对于调试来讲很有用。

此时执行 gulp,会发现能够实时编译并且在浏览器也能实时体现。

2、uglify

uglify 用于混淆代码,除此之外还需要安装 vinyl-buffergulp-sourcemaps 来支持 sourcemaps。

yarn add -D gulp-uglify vinyl-buffer gulp-sourcemaps

继续修改 gulpfile.js

const gulp = require('gulp');
const browserify = require("browserify");
const source = require("vinyl-source-stream");
const tsify = require("tsify");
const watchify = require('watchify');
var uglify = require('gulp-uglify');
var sourcemaps = require('gulp-sourcemaps');
var buffer = require('vinyl-buffer');

const paths = {
    pages:['src/*.html']
};

const 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()
        .pipe(source('bundle.js'))
        .pipe(buffer())
        .pipe(sourcemaps.init({loadMaps: true}))
        .pipe(uglify())
        .pipe(sourcemaps.write('./'))
        .pipe(gulp.dest("dist"));
}

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

打包之后的结果:

7.jpg

3、Babel

Babel 是用的最多的 ES 编译和转换工具,本身要比 TypeScript 更加专业和精确。

安装依赖:

yarn add -D babelify @babel/core @babel/preser-env vinyl-buffer gulp-sourcemaps

继续修改 gulpfile.js

const gulp = require('gulp');
const browserify = require("browserify");
const source = require("vinyl-source-stream");
const tsify = require("tsify");
const watchify = require('watchify');
var uglify = require('gulp-uglify');
var sourcemaps = require('gulp-sourcemaps');
var buffer = require('vinyl-buffer');

const paths = {
    pages:['src/*.html']
};

const 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
        .transform('babelify', {
            presets: ['env'],
            extensions: ['.ts']
        })
        .bundle()
        .pipe(source('bundle.js'))
        .pipe(buffer())
        .pipe(sourcemaps.init({loadMaps: true}))
        .pipe(uglify())
        .pipe(sourcemaps.write('./'))
        .pipe(gulp.dest("dist"));
}

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

同样的因为使用 Babel 编译 ES6 代码,因此不需要 typescript 去编译 ES6 代码,修改 tsconfig.json 配置:

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

编译结果:

8.jpg