Gulp 教程
Gulp 教程
本指南将教你如何使用 Gulp 构建 TypeScript 项目,并演示如何在 Gulp 管道中集成 Browserify、Uglify 和 Watchify。此外,还会介绍如何通过 Babelify 为项目添加 Babel 功能。
创建简单工程
首先创建一个新目录,命名为 proj(或任意你喜欢的名字)。
text
mkdir proj
cd proj
我们将以如下目录结构开始工程:
text
proj/
├─ src/
└─ dist/
TypeScript 源文件放在 src 目录下,编译器生成的目标文件输出到 dist 目录。
创建目录:
text
mkdir src
mkdir dist
初始化工程
将当前目录初始化为 npm 包:
text
npm init
按照提示操作,除入口文件外,其余均可使用默认值。入口文件设置为 ./dist/main.js。你也可以随时在生成的 package.json 中修改配置。
安装依赖项
全局安装 gulp-cli(如果你使用 Unix 系统,可能需要 sudo):
text
npm install -g gulp-cli
然后安装 typescript、gulp 和 gulp-typescript 作为开发依赖。gulp-typescript 是 TypeScript 的 Gulp 插件。
text
npm install --save-dev typescript gulp@4.0.0 gulp-typescript
编写一个简单示例
在 src 目录下创建 main.ts:
typescript
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:
javascript
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'));
});
测试应用
text
gulp
node dist/main.js
程序应输出 Hello from TypeScript!。
向代码中添加模块
在使用 Browserify 之前,我们先调整代码结构,引入模块。这个结构更接近真实应用。
创建 src/greet.ts:
typescript
export function sayHello(name: string) {
return `Hello from ${name}`;
}
修改 src/main.ts,从 greet.ts 导入 sayHello:
typescript
import { sayHello } from './greet';
console.log(sayHello('TypeScript'));
更新 tsconfig.json,将 src/greet.ts 加入 files:
json
{
"files": [
"src/main.ts",
"src/greet.ts"
],
"compilerOptions": {
"noImplicitAny": true,
"target": "es5"
}
}
运行 gulp 并在 Node.js 下验证模块是否工作:
text
gulp
node dist/main.js
注意:虽然我们使用了 ES2015 模块语法,TypeScript 仍会生成 Node.js 使用的 CommonJS 模块。本教程中我们继续使用 CommonJS 模块,你可以通过修改 compilerOptions.module 来调整模块生成方式。
集成 Browserify
现在将工程从 Node.js 环境迁移到浏览器环境,我们需要将模块打包为单个 JavaScript 文件。Browserify 支持 Node.js 的 CommonJS 模块,而 TypeScript 默认生成的正是 CommonJS 格式,因此无需修改 TypeScript 和 Node.js 的配置即可直接移植。
安装 Browserify、tsify 和 vinyl-source-stream:
text
npm install --save-dev browserify tsify vinyl-source-stream
tsify是 Browserify 的插件,相当于 gulp-typescript 的角色,为 Browserify 提供 TypeScript 编译器支持。vinyl-source-stream将 Browserify 的输出转换为 Gulp 可识别的 vinyl 流格式。
创建页面
在 src 目录下创建 index.html:
markup
<!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 以更新页面内容:
typescript
import { sayHello } from './greet';
function showHello(divName: string, name: string) {
const elt = document.getElementById(divName);
elt.innerText = sayHello(name);
}
showHello('greeting', 'TypeScript');
更新 gulpfile.js:
javascript
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 改用 Browserify 搭配 tsify 插件,而不是 gulp-typescript。两者向 TypeScript 编译器传递的参数对象完全兼容。
调用 bundle() 后,通过 source(vinyl-source-stream 的别名)将输出文件命名为 bundle.js。
测试页面:
text
gulp
然后在浏览器中打开 dist/index.html,页面上应显示 Hello from TypeScript。
我们为 Browserify 指定了 debug: true,这会生成 source maps。source maps 允许浏览器直接调试 TypeScript 源码,而不是压缩后的 JavaScript 文件。可以在浏览器调试器中对 main.ts 设置断点验证,刷新页面后代码会停在断点处,同时可以调试 greet.ts。
集成 Watchify、Babel 和 Uglify
现在已通过 Browserify 和 tsify 完成了打包,接下来可以为构建流程添加更多特性:
- Watchify:启动 Gulp 后保持监听,文件保存时自动编译,加速编辑-保存-刷新循环。
- Babel:灵活的编译器,可将 ES2015+ 转换为 ES5/ES3,支持大量自定义转换器。
- Uglify:压缩代码,减小下载体积。
集成 Watchify
安装依赖:
text
npm install --save-dev watchify fancy-log
修改 gulpfile.js:
javascript
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);
主要变化有三点:
- 将
browserify实例包裹在watchify中,用于监听文件变化。 - 通过
watchedBrowserify.on('update', bundle)在 TypeScript 文件发生变化时自动执行bundle函数。 - 通过
watchedBrowserify.on('log', fancy_log)将日志输出到控制台。
由于 Watchify 和 Gulp 都需要调用 bundle,我们将 Browserify 的创建移出了 default 任务并命名为独立函数。第 3 点虽然不是必须的,但对于调试很有帮助。
现在运行 gulp,它会保持进程监听。尝试修改 main.ts 中的代码并保存,控制台会输出类似信息:
text
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)
集成 Uglify
安装 Uglify 以及用于支持 source maps 的辅助包:
text
npm install --save-dev gulp-uglify vinyl-buffer gulp-sourcemaps
修改 gulpfile.js:
javascript
var gulp = require('gulp');
var browserify = require('browserify');
var source = require('vinyl-source-stream');
var tsify = require('tsify');
var uglify = require('gulp-uglify');
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(uglify())
.pipe(sourcemaps.write('./'))
.pipe(gulp.dest('dist'));
})
);
buffer() 将流转换为缓冲,sourcemaps.init({ loadMaps: true }) 加载已有的 source maps,uglify() 压缩代码,最后 sourcemaps.write('./') 输出独立的 sourcemap 文件,替代之前的内嵌方式。
运行 gulp 并检查压缩后的 bundle.js:
text
gulp
cat dist/bundle.js
集成 Babel
安装 Babelify 和 ES2015 预设。与 Uglify 类似,Babelify 也会混淆代码,因此需要 vinyl-buffer 和 gulp-sourcemaps。Babelify 默认只处理 .js、.es、.es6、.jsx 文件,需要添加 .ts 扩展名。
text
npm install --save-dev babelify@8 babel-core babel-preset-es2015 vinyl-buffer gulp-sourcemaps
修改 gulpfile.js:
javascript
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 直接生成的结果类似。
帮助我们改进文档
发现翻译问题或内容错误?请告诉我们。
