gulp进阶构建项目由浅入深
2016-06-06 01:06 龙恩0707 阅读(5427) 评论(4) 编辑 收藏 举报gulp进阶构建项目由浅入深
阅读目录
- gulp基本安装和使用
- gulp API介绍
- gulp一些常用插件
- gulp构建小型项目的基本过程
gulp基本安装和使用
Gulp的构建过程:gulp是使用nodejs中的stream(流),首先通过gulp.src()方法获取到我们需要的文件流(stream),然后把文件流通过pipe()方法把流导入到gulp的插件中,最后通过插件处理后的流再通过pipe()方法导入到gulp.dest()中,gulp.dest()方法把流中的内容写入到文件中。
1. Gulp安装:
首先我们需要安装nodejs,然后进行全局安装;安装如下:
sudo npm install gulp –g
全局安装后,还需要切换到项目的根目录下,单独为单个项目进行安装下;安装如下:
sudo npm install gulp
如果想在安装的时候把gulp写进package.json文件的依赖中,则可以加上 –save-dev
sudo npm install –save-dev gulp
2. 如何使用gulp?
在项目的根目录下新建一个 gulpfile.js文件,之后就可以定义一个任务了;
比如如下简单的任务:代码如下:
var gulp = require('gulp'); gulp.task('default',function(){ console.log('hello world'); });
现在我项目的目录结构假如是如下样子:
最后我们进行命令行切换到项目的根目录下,运行gulp命令后,就可以在控制台看到consoe.log的打印的消息了;
gulp API介绍
最常见的我们使用四个API,gulp.task() gulp.src() gulp.dest() gulp.watch();
该方法是获取我们需要的文件流,这个流里面的内容不是原始的文件流,而是一个虚拟文件对象流(Vinyl files);该方法有2个参数
globs类型是 String 或 Array , 该文件流可以是一个单独的字符串形式,也可以是一个数组形式;
options是一个对象类型;该对象类型有一个我们常用的base字段配置 options.base是经常会使用的到;
比如如下代码:
var gulp = require('gulp'); var uglify = require('gulp-uglify'); gulp.task("uglify-js",function(){ return gulp.src("src/js/*.js") .pipe(uglify()) .pipe(gulp.dest('build')); }); gulp.task('default',['uglify-js']); // 写入到 build/a.js 和 build/index.js
如下目录结构:
我们使用base字段来继续编写如下代码:
var gulp = require('gulp'); var uglify = require('gulp-uglify'); gulp.task("uglify-js",function(){ return gulp.src("src/js/*.js",{ base: 'src' }) .pipe(uglify()) .pipe(gulp.dest('build')); }); gulp.task('default',['uglify-js']);
我们再在项目的根目录下面运行gulp命令可以看到项目的目录结构变为如下:
因此我们可以理解base字段为相对于路径来进行打包,最后生成 build/js/*.js文件;
看看Gulp用到的glob的匹配规则:
Gulp内部使用了node-glob模块来实现文件匹配功能。该文件匹配类似于JS中的正则表达式;如下匹配:
* 匹配文件路径中的0个或者多个字符,但是不会匹配路径分隔符。比如: 可以匹配 abc.js,x.js,aaa,abc/(路径分隔符出现在末尾也可以匹配);但是不能匹配类似于这样的路径分隔符 abc/aa.js
*.* 可以匹配a.xxx; xxxx.yyyy;等
*/*/*.js 可以匹配a/b/c.js,但不是不能匹配 a/b.js 或者 a/b/c/d.js
** 可以匹配路径中的0个或者多个目录及其子目录。比如:能匹配abc,a/b.js,
a/b/c.js,x/y/z等等;
**/*.js 能够匹配a.js , a/a.js,a/aaa/aaaa/a.js等等;也就是说只要以.js结尾的,不管前面有多少个文件或者分隔符都可以匹配;
a/**/z 能匹配a/z,a/b/z, a/b/c/z等等。
a/**b/z 能匹配a/b/z,a/sb/z, 不是不能匹配a/x/y/xxb/z;
?.js 能匹配a.js,b.js,c.js,相当于js正则里面的一样匹配0个或者1个,优先匹配;
[xyz].js 能匹配x.js,y.js,z.js,类似于js正则一样,中括号中的任意一个字符;
[^xyz].js 除了中括号中的x,y,z中的其他的任意一个字符;
当有多种匹配模式的时候可以使用数组,如下:
gulp.src([‘js/*.js’,’css/*.css’]);
我们也可以排除一些文件,可以使用!, 比如如下代码:
gulp.src([‘js/*.js’,’css/*.css’,’!reset.css’]) ; 匹配所有的js/目录下的js文件及匹配css/目录下的css文件,但是不包括reset.css文件;但是不能把排除写在第一个元素位置;
比如如下代码: gulp.src([‘!reset.css’,’css/*.css’]); 这样的是排除不掉的,这种方式我们在css中可以理解为后面的代码覆盖前面的,因此需要写在后面才能排除当前的;
该方法可以理解为把目标的源文件通过pipe方法导入到gulp插件中,最后把文件流写到目标文件中;如果该文件不存在的话,则会自动创建它;比如上面的gulp.src(),目录结构一刚开始build目录是没有的,通过打包后自动创建build文件夹;
字段path: 文件被写入的路径(输出的目录),也可以传入一个函数,在函数中返回相应的路径。
字段options也是一个对象类型;
Gulp.dest(path) 生成的文件路径是相对于gulp.src()中有通配符开始出现的那部分路径。
比如如下代码:
var gulp = require('gulp'); var uglify = require('gulp-uglify'); gulp.task("uglify-js",function(){ return gulp.src("src/js/*.js") .pipe(uglify()) .pipe(gulp.dest('build')); }); gulp.task('default',['uglify-js']);
gulp.src()上面有通配符的是 *.js, 因此最后生成的文件路径是 build/*.js;
但是如果没有出现通配符的情况下,比如如下代码:
gulp.src("src/js/a.js")
.pipe(gulp.dest('build'));
那么最后生成的路径就是 build/a.js 了;
当然我们可以在gulp.src()方法中配置base属性,如果没有配置base属性的话,那么默认生成的路径就是相对于通配符出现的那部分路径;如果设置了base属性的话,那么就相对于base的那个设置的路径;假如现在的源目录结构为src/js/下游很多js文件
比如如下代码:
var gulp = require('gulp'); var uglify = require('gulp-uglify'); gulp.task("uglify-js",function(){ return gulp.src("src/js/*.js",{ base: 'src' }) .pipe(uglify()) .pipe(gulp.dest('build')); }); gulp.task('default',['uglify-js']);
是相对于src文件下的,因此最后生成的路径为 build/js/*.js
该方法是定义一个任务;
name: 是任务的名字;
deps: {Array} 类型是数组类型;一个包含任务列表的数组,这些任务会在你当前任务运行之前完成;比如如下代码:
gulp.task('mytask', ['one', 'two', 'task', 'names'], function() {
// 做一些事
});
比如上面的代码,我们想要完成'mytask'这个任务的话,首先会执行依赖数组中的那些任务,但是如果依赖任务里面又使用了异步的方法,比如使用setTimeout这样的时候,那么这个时候,我再执行mytask这个任务的时候,就不会等待该依赖任务完成后再执行了;比如如下代码:
var gulp = require('gulp'); gulp.task('one',function(){ //one是一个异步执行的任务 setTimeout(function(){ console.log('one is done') },5000); }); //two任务虽然依赖于one任务,但并不会等到one任务中的异步操作完成后再执行 gulp.task('two',['one'], function(){ console.log('two is done'); }); gulp.task('default',['two']);
上面的例子中我们执行two任务时,会先执行one任务,但不会去等待one任务中的异步操作完成后再执行two任务,而是紧接着执行two任务。所以two任务会在one任务中的异步操作完成之前就执行了。
但是如果我们想等待one任务中的setTimeout执行完成后,再执行two这个任务该怎么办呢?
我们可以使用如下方法,代码如下:
var gulp = require('gulp'); gulp.task('one',function(fn){ // fn 为任务函数提供的回调 用来通知该任务已经完成 //one是一个异步执行的任务 setTimeout(function(){ console.log('one is done'); fn(); },1000); }); //two任务虽然依赖于one任务,但并不会等到one任务中的异步操作完成后再执行 gulp.task('two',['one'], function(){ console.log('two is done'); }); gulp.task('default',['two']);
用来监听文件的变化,当文件发生改变的时候,我们可以用它来执行相应的任务;
参数如下:
glob: 为要监听的文件匹配模式,规则和gulp.src中的glob相同;
opts: 为一个可选的配置对象,一般不怎么用;
tasks: 为文件变化后要执行的任务,为一个数组;
比如代码如下:
gulp.task('two', function(){
console.log('two is done');
});
gulp.watch('js/**/*.js',['two'])
gulp一些常用插件
用来重命名文件流中的文件。
安装:npm install --save-dev gulp-rename
比如如下代码:
var gulp = require('gulp'), rename = require('gulp-rename'), uglify = require("gulp-uglify"); gulp.task('rename', function () { gulp.src('src/**/*.js') .pipe(uglify()) //压缩 .pipe(rename('index.min.js')) .pipe(gulp.dest('build/js')); }); gulp.task('default',['rename']); //关于gulp-rename的更多强大的用法请参考https://www.npmjs.com/package/gulp-rename
安装:npm install --save-dev gulp-uglify
还是上面的gulpfile.js代码如下:
var gulp = require('gulp'), rename = require('gulp-rename'), uglify = require("gulp-uglify"); gulp.task('rename', function () { gulp.src('src/**/*.js') .pipe(uglify()) //压缩 .pipe(rename('index.min.js')) .pipe(gulp.dest('build/js')); }); gulp.task('default',['rename']);
安装:npm install --save-dev gulp-minify-css
代码如下:
var gulp = require('gulp'), minifyCss = require("gulp-minify-css"); gulp.task('minify-css', function () { gulp.src('src/**/*.css') // 要压缩的css文件 .pipe(minifyCss()) //压缩css .pipe(gulp.dest('build')); }); gulp.task('default',['minify-css']);
安装:npm install --save-dev gulp-minify-html
代码如下:
var gulp = require('gulp'), minifyHtml = require("gulp-minify-html"); gulp.task('minify-html', function () { gulp.src('src/**/*.html') // 要压缩的html文件 .pipe(minifyHtml()) //压缩 .pipe(gulp.dest('build')); }); gulp.task('default',['minify-html']);
安装:npm install --save-dev gulp-concat
代码如下:
var gulp = require('gulp'), concat = require("gulp-concat"); gulp.task('concat', function () { gulp.src('src/**/*.js') //要合并的文件 .pipe(concat('index.js')) // 合并匹配到的js文件并命名为 "index.js" .pipe(gulp.dest('build/js')); }); gulp.task('default',['concat']);
安装:npm install –save-dev gulp-less
Gulpfile.js代码如下:
var gulp = require('gulp'), less = require("gulp-less"); gulp.task('compile-less', function () { gulp.src('src/less/*.less') .pipe(less()) .pipe(gulp.dest('build/css')); }); gulp.task('default',['compile-less']);
安装:npm install –save-dev gulp-sass
代码如下:
var gulp = require('gulp'), sass = require("gulp-sass"); gulp.task('compile-sass', function () { gulp.src('src/sass/*.sass') .pipe(sass()) .pipe(gulp.dest('build/css')); }); gulp.task('default',['compile-sass']);
安装:npm install –save-dev gulp-imagemin
代码如下:
var gulp = require('gulp'); var imagemin = require('gulp-imagemin'); gulp.task('uglify-imagemin', function () { return gulp.src('src/images/*') .pipe(imagemin()) .pipe(gulp.dest('build/images')); }); gulp.task('default',['uglify-imagemin']);
browserify是一个使用node支持的CommonJS模块标准 来为浏览器编译模块的,可以解决模块及依赖管理;
先来看看使用gulp常见的问题:
1. 使用 gulp 过程中,偶尔会遇到 Streaming not supported 这样的错误。这通常是因为常规流与 vinyl 文件对象流有差异、
gulp 插件默认使用了只支持 buffer (不支持 stream)的库。比如,不能把 Node 常规流直接传递给 gulp 及其插件。
比如如下代码:会抛出异常的;
var gulp = require('gulp'); var uglify = require('gulp-uglify'); var concat = require('gulp-concat'); var rename = require('gulp-rename'); var fs = require('fs'); gulp.task('bundle', function() { return fs.createReadStream('./test.txt') .pipe(uglify()) .pipe(rename('bundle.min.js')) .pipe(gulp.dest('dist/')); }); gulp.task('default',['bundle']);
gulp 选择默认使用内容转换成 buffer 的 vinyl 对象流,以方便处理。当然,设置 buffer: false 选项,可以让 gulp 禁用 buffer:
比如如下gulpfile.js代码如下:
var gulp = require('gulp'); var fs = require('fs'); gulp.task('bundle', function() { return gulp.src('./src/js/app.js', {buffer: false}).on('data', function(file) { var stream = file.contents; stream.on('data', function(chunk) { console.log('Read %d bytes of data', chunk.length); }); }); }) gulp.task('default',['bundle']);
运行如下:
2. Stream 和 Buffer 之间转换
基于依赖的模块返回的是 stream, 以及 gulp 插件对 stream 的支持情况,有时需要把 stream 转换为 buffer。比如很多插件只支持 buffer,如 gulp-uglify、使用时可以通过 gulp-buffer 转换:Stream转换Buffer
如下gulpfile.js代码:
var gulp = require('gulp'); var source = require('vinyl-source-stream'); var buffer = require('gulp-buffer'); var uglify = require('gulp-uglify'); var fs = require('fs'); gulp.task('bundle', function() { return fs.createReadStream('./src/js/app.js') .pipe(source('app.min.js')) // 常规流转换成 vinyl 对象 .pipe(buffer()) .pipe(uglify()) .pipe(gulp.dest('dist/')); }) gulp.task('default',['bundle']);
如下所示:
3. 从 Buffer 到 Stream之间转换
也可以通过使用 gulp-streamify(https://www.npmjs.com/package/gulp-streamify) 或者 gulp-stream (https://www.npmjs.com/package/gulp-stream)插件,让只支持 buffer 的插件直接处理 stream。
如下gulpfile.js代码:
var gulp = require('gulp'); var wrap = require('gulp-wrap'); var streamify = require('gulp-streamify'); var uglify = require('gulp-uglify'); var gzip = require('gulp-gzip'); gulp.task('bundle', function() { return gulp.src('./src/js/app.js', {buffer: false}) .pipe(wrap('(function(){<%= contents %>}());')) .pipe(streamify(uglify())) .pipe(gulp.dest('dist')) .pipe(gzip()) .pipe(gulp.dest('dist')); }); gulp.task('default',['bundle']);
如下所示:
4. 使用browserify进行Stream 向 Buffer 转换
vinyl-source-stream + vinyl-buffer
vinyl-source-stream(https://www.npmjs.com/package/vinyl-source-stream) : 将常规流转换为包含 Stream 的 vinyl 对象;
vinyl-buffer(https://www.npmjs.com/package/vinyl-buffer) 将 vinyl 对象内容中的 Stream 转换为 Buffer。
gulpfile.js代码如下:
var browserify = require('browserify'); var gulp = require('gulp'); var uglify = require('gulp-uglify'); var source = require('vinyl-source-stream'); var buffer = require('vinyl-buffer'); gulp.task('browserify', function() { return browserify('./src/js/app.js') .bundle() .pipe(source('bundle.js')) // gives streaming vinyl file object .pipe(buffer()) // convert from streaming to buffered vinyl file object .pipe(uglify()) .pipe(gulp.dest('./dist/js')); }); gulp.task('default',['browserify']);
vinyl-source-stream 使用指定的文件名bundle.js创建了一个 vinyl 文件对象实例,因此可以不再使用 gulp-rename(gulp.dest 将用此文件名写入结果)。
如下所示:
5. 使用browserify多文件操作
5-1. 使用Gulp和Browserify单个文件操作也可以如下:
var gulp = require('gulp'); var browserify = require('browserify'); var source = require('vinyl-source-stream'); gulp.task('browserify', function(){ return browserify( {entrieis:['./src/js/app.js']}) .bundle() .pipe(source("bundle.js")) .pipe(gulp.dest('dist')); }); gulp.task('default',['browserify']);
5-2 多文件操作如下:
var gulp = require('gulp'); var source = require('vinyl-source-stream'); var browserify = require('browserify'); var glob = require('glob'); var es = require('event-stream'); gulp.task('browserify', function(done) { glob('./src/**/*.js', function(err, files) { if(err) { done(err) }; var tasks = files.map(function(entry) { return browserify({ entries: [entry] }) .bundle() .pipe(source(entry)) .pipe(gulp.dest('./dest')); }); es.merge(tasks).on('end', done); }) }); gulp.task('default',['browserify']);
5-3 也可以使用gulp.src和browserify一起使用;代码如下:
var gulp = require('gulp'); var source = require('vinyl-source-stream'); var browserify = require('browserify'); var glob = require('glob'); var es = require('event-stream'); var buffer = require('vinyl-buffer'); gulp.task('browserify', function(done) { gulp.src('./src/**/*.js',function(err,files) { if(err) { done(err) } files.forEach(function(file){ return browserify({ entries: [file] }) .bundle() .pipe(source(file)) .pipe(buffer()) .pipe(gulp.dest('./dest')); }); }); }); gulp.task('default',['browserify']);
browserify深入学习;
1.前言:
之前我们做项目的时候,比如需要jquery框架的话,我们可能需要下载jquery源码,然后引入到我们的项目中,之后在html文件中像如下引入即可:
<script src="path/to/jquery.js"></script>
2.bower学习
之后我们学习了 Bower,因此我们安装了Bower,然后进入我们的项目文件根目录中 在命令行中使用bower安装jquery;如下命令:
bower install jquery
之后会在我们的根目录中生成一个 bower_components文件,里面包含了jquery文件,因此我们需要在我们的html文件中这样引入jquery了;
<script src="bower_components/jquery/dist/jquery.js"></script>
如下所示:
3. npm&Browserify学习
我们现在又可以使用 命令行用npm安装jQuery。进入项目的根目录后,运行如下命令:
npm install --save-dev jquery
接着我们在命令行中全局安装 browserify;命令如下:
sudo npm install -g browserify
现在我们就可以在命令行中使用 browserify命令了;
比如现在我在我的项目目录下的源文件 src/js/a.js 下想要使用jquery的话,我们可以在a.js代码如下引用:
var $ = require('jquery'); $(function(){ // 获取页面中的DOM元素 console.log($("#jquery2")); }); function a() { console.log("a.js"); } a();
再进入命令行相对应的js文件中,进行如下命令:
browserify a.js -o dest.js
执行命令后会在同目录下生成dest.js,该文件包含jquery.js和a.js;然后我们把dest文件引入到我们需要的html文件中即可访问;
4. gulp和Browserify 一起使用
结合gulp一起使用时,我们只需要把Browserify安装到我们的项目内即可;因此进入项目的根目录中,进行如下命令安装:
npm install --save-dev browserify
然后在项目的根目录中在gulpfile.js文件中加入如下代码:
var gulp = require("gulp"); var browserify = require("browserify"); var sourcemaps = require("gulp-sourcemaps"); var source = require('vinyl-source-stream'); var buffer = require('vinyl-buffer'); gulp.task("browserify", function () { var b = browserify({ entries: "./src/js/a.js", debug: true }); return b.bundle() .pipe(source("bundle.js")) .pipe(buffer()) .pipe(sourcemaps.init({loadMaps: true})) .pipe(sourcemaps.write(".")) .pipe(gulp.dest("./dist")); }); gulp.task('default',['browserify']);
a.js代码还是如下:
var $ = require('jquery'); $(function(){ // 获取页面中的DOM元素 console.log($("#jquery2")); }); function a() { console.log("a.js"); } a();
进入项目的根目录中运行命令 gulp即可,在目录中会生成dist目录(包含bundle.js和bundle.js.map)两个文件;之后在html文件页面上引入
dist目录文件下的bundle.js即可;
在上面的代码中,debug: true是告知Browserify在运行同时生成内联sourcemap用于调试。
如果我们把debug设置成false的话;在浏览器中访问页面,可以看到如下:
如果我们把debug设置成true的话,在浏览器中访问页面,可以看到如下:
引入gulp-sourcemaps并设置loadMaps: true是为了读取上一步得到的内联sourcemap,并将其转写为一个单独的sourcemap文件。
如果我们把loadMaps设置成false的话,我们在浏览器访问页面如下图所示:
如果我们把loadMaps设置成true的话,我们在浏览器访问页面如下图所示:
vinyl-source-stream用于将Browserify的bundle()的输出转换为Gulp可用的[vinyl][](一种虚拟文件格式)流。
vinyl-buffer用于将vinyl流转化为buffered vinyl文件(gulp-sourcemaps及大部分Gulp插件都需要这种格式)。
如果代码比较多,可能一次编译需要很长时间。这个时候,我们可以使用[watchify][]。它可以在你修改文件后,
只重新编译需要的部分(而不是Browserify原本的全部编译),这样,只有第一次编译会花些时间,此后的即时变更刷新则十分迅速。
如下代码:
var watchify = require('watchify'); var browserify = require('browserify'); var gulp = require('gulp'); var source = require('vinyl-source-stream'); var buffer = require('vinyl-buffer'); var gutil = require('gulp-util'); var sourcemaps = require('gulp-sourcemaps'); var assign = require('lodash.assign'); // 在这里添加自定义 browserify 选项 var customOpts = { entries: ['./src/js/a.js'], debug: true }; var opts = assign({}, watchify.args, customOpts); var b = watchify(browserify(opts)); // 在这里加入变换操作 // 比如: b.transform(coffeeify); gulp.task('js', bundle); // 这样你就可以运行 `gulp js` 来编译文件了 b.on('update', bundle); // 当任何依赖发生改变的时候,运行打包工具 b.on('log', gutil.log); // 输出编译日志到终端 function bundle() { return b.bundle() // 如果有错误发生,记录这些错误 .on('error', gutil.log.bind(gutil, 'Browserify Error')) .pipe(source('bundle.js')) // 可选项,如果你不需要缓存文件内容,就删除 .pipe(buffer()) // 可选项,如果你不需要 sourcemaps,就删除 .pipe(sourcemaps.init({loadMaps: true})) // 从 browserify 文件载入 map // 在这里将变换操作加入管道 .pipe(sourcemaps.write('./')) // 写入 .map 文件 .pipe(gulp.dest('./dist')); } gulp.task('default',['js']);
5. 使用Browserify来组织JavaScript文件
还是上面那个项目,假如src/js文件内有2个js文件,分别为a.js和b.js;假如现在a.js想引用b.js的模块,就像seajs那样通过require来引用如何做?
现在我们可以在b.js这样编写代码;把我们的代码模块通过module.exports 或 exports模块对外提供接口,和其他的比如seajs一样编写代码
即可:比如现在b.js代码如下:
function b() {
console.log("b.js");
}
module.exports = b;
那么a.js代码如下:
var b = require('./b');
function a() {
b();
console.log("a.js");
}
a();
然后再在gulpfile.js文件代码还是如下:
var gulp = require("gulp"); var browserify = require("browserify"); var sourcemaps = require("gulp-sourcemaps"); var source = require('vinyl-source-stream'); var buffer = require('vinyl-buffer'); gulp.task("browserify", function () { var b = browserify({ entries: "./src/js/a.js", debug: true }); return b.bundle() .pipe(source("bundle.js")) .pipe(buffer()) .pipe(sourcemaps.init({loadMaps: true})) .pipe(sourcemaps.write(".")) .pipe(gulp.dest("./dist")); }); gulp.task('default',['browserify']);
在命令行中运行gulp,即可生成bundle.js文件;引用该文件即可解决模块依赖的问题;我们打开bundle.js文件查看代码如下:
(function e(t,n,r){ /* function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require; if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'"); throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e]; return s(n?n:e)},l,l.exports,e,t,n,r)} return n[o].exports}var i=typeof require=="function"&&require; for(var o=0;o<r.length;o++)s(r[o]);return s */ })({1:[function(require,module,exports){ var b = require('./b'); function a() { b(); console.log("a.js"); } a(); },{"./b":2}],2:[function(require,module,exports){ function b() { console.log("b.js"); } module.exports = b; },{}]},{},[1])
该函数有3个参数,
第一个参数是一个对象;第二个参数是一个空对象{};第三个参数是一个[1];
第一个参数是一个object;它的每一个key都是一个数字,作为模块的id,每一个数字key对应的值是长度为2的数组。可以看下,第一个key数字1
模块中的数组中的第一个元素是a.js代码;数组中第二个元素是a模块的依赖项,第二个key数字2模块中数组第一个元素是b.js代码;数组中的第二个
元素是空对象{};因为b模块没有依赖项;因此为{};
我们的文件代码通过一个匿名函数被包装起来,这样做的好处是:我们的浏览器中并没有require这样的解决依赖的东西,但是我们又想像seajs,
requireJS等一样使用require来引入文件解决模块依赖的文件的时候,因此 Browserify实现了require、module、exports这3个关键字。
第2个参数几乎总是空的{}。它如果有的话,也是一个模块map;
第3个参数是一个数组,指定的是作为入口的模块id。a.js是入口模块,它的id是1,所以这里的数组就是[1]。
那么 Browserify是如何实现了require、module、exports这3个关键字的呢?
我们前面被注释的代码将解析require、module、exports这三个3个参数,然后让一切运行起来。
这段代码是一个函数,来自于browser-pack项目的[prelude.js][]。
上面我们看到在Browserify打包文件的时候,它会自动使用匿名函数进行包装;因此我们在编写代码的时候一般可以不需要考虑全局变量的问题了;
不需要在函数中添加像类型匿名函数的结构 (function(){})();
gulp.watch()方法可以监听文件的动态修改,它接受一个glob或者glob数组(和gulp.src()一样)以及一个任务数组来执行回调。下面我们来看下
gulp.watch()方法的使用;比如现在gulpfile.js任务代码如下:
var gulp = require('gulp'); var concat = require('gulp-concat'); var uglify = require('gulp-uglify'); var paths = { scripts: ['src/js/**/*.js'], css: ['src/css/**/*.css'], // 把源文件html放在src下,会自动打包到指定目录下 html: ['src/html/**/*.html'] }; gulp.task('scripts', function() { return gulp.src(paths.scripts) .pipe(concat('all.js')) .pipe(uglify()) .pipe(gulp.dest('build/js')); }); gulp.task('css', function() { return gulp.src(paths.css) .pipe(concat('all.css')) .pipe(gulp.dest('build/css')); }); // 监听html文件的改变 gulp.task('html',function(){ return gulp.src(paths.html) .pipe(gulp.dest('html/')); }); // Rerun the task when a file changes gulp.task('watch', function() { gulp.watch(paths.scripts, ['scripts']); gulp.watch(paths.css, ['css']); gulp.watch(paths.html, ['html']); }); // The default task (called when you run `gulp` from cli) gulp.task('default', ['scripts', 'css', 'html','watch']);
监听src文件下的js和css及html的文件的变化,我们在相关的项目根目录命令行中运行gulp后,当我们改变css或者js文件或html的时候,
可以监听文件的动态修改,因此保存刷新浏览器即可生效,这是gulp-watch的基本功能;如下所示:
会把src下的文件css和js自动打包到build下,src下的html文件会打包到项目根目录下的html文件下;
上面的gulp.watch 回调函数有一个包含触发回调函数信息的event对象:比如我们把gulp watch任务改成如下:
当每次更改文件的时候 都会触发change事件;代码改为如下:
gulp.task('watch', function() { var watch1 = gulp.watch(paths.scripts, ['scripts']); var watch2 = gulp.watch(paths.css, ['css']); var watch3 = gulp.watch(paths.html, ['html']); watch1.on('change', function (event) { console.log('Event type: ' + event.type); // Event type: changed console.log('Event path: ' + event.path); // Event path: /Users/tugenhua/gulp/src/css/a.css }); watch2.on('change', function (event) { console.log('Event type: ' + event.type); // Event type: changed console.log('Event path: ' + event.path); // Event path: /Users/tugenhua/gulp/src/css/a.css }); watch3.on('change', function (event) { console.log('Event type: ' + event.type); // Event type: changed console.log('Event path: ' + event.path); // Event path: /Users/tugenhua/gulp/src/css/a.css }); });
除了change事件,还可以监听很多其他的事件:
end 在watcher结束时触发
error 在出现error时触发
ready 在文件被找到并正被监听时触发
nomatch 在glob没有匹配到任何文件时触发
Watcher对象也包含了一些可以调用的方法:
watcher.end() 停止watcher(以便停止执行后面的任务或者回调函数)
watcher.files() 返回watcher监听的文件列表
watcher.add(glob) 将与指定glob相匹配的文件添加到watcher
watcher.remove(filepath) 从watcher中移除个别文件
上面是通过gulp-watch来动态监听html,css和js文件的改变,但是需要重新刷新页面才能生效;
该插件的作用是当文件被修改的时候,它能实时刷新网页,这样的话就不需要我们实时刷新了;但是该插件需要在我们服务器下生效;因此
我们需要使用 gulp-connect 创建一个服务器;下面是gulpfile.js代码如下;使用liveReload实现实时刷新;
var gulp = require('gulp'); var connect = require('gulp-connect'); var uglify = require("gulp-uglify"); var concat = require("gulp-concat"); var mincss = require("gulp-minify-css"); //自动刷新 var livereload = require("gulp-livereload"); /* 设置路径 */ var paths = { src : "src/", css : "src/css/", scripts : "src/js/", scss : "src/scss/", img : "src/images/", html : "src/html/", build : "build" } // 创建一个webserver 服务器 gulp.task('webserver', function() { connect.server({ port: 8000, livereload: true }); }); gulp.task('scripts', function() { return gulp.src(paths.scripts+ "**/*.js") .pipe(concat('all.js')) .pipe(uglify()) .pipe(gulp.dest(paths.build + '/js')); }); gulp.task('css', function() { return gulp.src(paths.css+ "**/*.css") .pipe(concat('all.css')) .pipe(mincss()) .pipe(gulp.dest(paths.build + '/css')); }); // 监听html文件的改变 gulp.task('html',function(){ return gulp.src(paths.html + "**/*.html") .pipe(gulp.dest('html/')); }); //reload server gulp.task('reload-dev',['scripts','css','html'],function() { return gulp.src(paths.src + '**/*.*') .pipe(connect.reload()); }); // Watch gulp.task('watch', function() { //监听生产环境目录变化 gulp.watch(paths.src + '**/*.*',['reload-dev']); }) gulp.task('default', ['webserver','reload-dev','watch']);
BroserSync在浏览器中展示变化的功能与LiveReload非常相似,但是它有更多的功能。实现静态服务器,也是能实时刷新页面的;BrowserSync也可以在不同浏览器之间同步点击翻页、表单操作、滚动位置等功能。
安装如下命令:
npm install --save-dev browser-sync
如下gulpfile文件是动态监听js,css和html文件的变化实时更新;如下代码:
var gulp = require('gulp'); var connect = require('gulp-connect'); var uglify = require("gulp-uglify"); var concat = require("gulp-concat"); var mincss = require("gulp-minify-css"); //自动刷新 var browserSync = require('browser-sync').create(); var reload = browserSync.reload; /* 设置路径 */ var paths = { src : "src/", css : "src/css/", scripts : "src/js/", scss : "src/scss/", img : "src/images/", html : "src/html/", build : "build" } gulp.task('scripts', function() { return gulp.src(paths.scripts+ "**/*.js") .pipe(concat('all.js')) .pipe(uglify()) .pipe(gulp.dest(paths.build + '/js')) .pipe(reload({stream:true})); // inject into browsers }); gulp.task('css', function() { return gulp.src(paths.css+ "**/*.css") .pipe(concat('all.css')) .pipe(mincss()) .pipe(gulp.dest(paths.build + '/css')) .pipe(reload({stream:true})); // inject into browsers }); // 监听html文件的改变 gulp.task('html',function(){ return gulp.src(paths.html + "**/*.html") .pipe(gulp.dest('html/')) .pipe(reload({stream:true})); // inject into browsers }); // 创建本地服务器,并且实时更新页面文件 gulp.task('browser-sync', ['scripts','css','html'],function() { var files = [ '**/*.html', '**/*.css', '**/*.js' ]; browserSync.init(files,{ server: { //baseDir: "./html" } }); }); //gulp.task('default', ['webserver','reload-dev','watch']); gulp.task('default', ['browser-sync'], function () { gulp.watch("**/*.css", ['css']); gulp.watch("**/*.html", ['html']); gulp.watch("**/*.js", ['scripts']); });
对 browser-sync 更多的学习 请看文档(http://www.browsersync.cn/docs/api/)
gulp构建小型项目的基本过程
比如我现在一个小项目的基本架构如下所示:
src文件夹:是源文件的目录结构;build文件夹是通过构建后生成的文件;
src存放文件如下:
common(该目录是存放公用的插件css文件和js文件)
html目录是存放目前的html文件
images目录存放所有在项目中用到的图片
js目录是在项目中用到的所有的js文件;
less文件是存放需要预编译成css文件;
这上面几个目录都会通过gulpfile.js打包到build目录下;通过上面的学习browserify(可以解决js的模块化依赖问题)及学习 browserSync(实现自动刷新效果),因此目前该项目打包有2个优点:
1. 可以使用require,exports,和moudle这三个参数实现js模块化组织及加载的问题,它不需要依赖于seajs或者requireJS;
2. 可以实时监听html,css,js文件的修改,从而不需要刷新页面,可以提高工作效率;
现在我把package.json用到的依赖包放到下面来:
{ "name": "testProject", "version": "0.0.1", "description": "", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "author": "", "license": "ISC", "devDependencies": { "browser-sync": "^2.12.10", "browserify": "^13.0.1", "event-stream": "^3.3.2", "glob": "^7.0.3", "gulp": "^3.9.1", "gulp-buffer": "0.0.2", "gulp-clean": "^0.3.2", "gulp-concat": "^2.6.0", "gulp-connect": "^4.0.0", "gulp-gzip": "^1.3.0", "gulp-imagemin": "^3.0.1", "gulp-less": "^3.1.0", "gulp-livereload": "^3.8.1", "gulp-marked": "^1.0.0", "gulp-minify-css": "^1.2.4", "gulp-rename": "^1.2.2", "gulp-sourcemaps": "^1.6.0", "gulp-str-replace": "0.0.4", "gulp-streamify": "^1.0.2", "gulp-uglify": "^1.5.3", "gulp-util": "^3.0.7", "gulp-watch": "^4.3.6", "gulp-wrap": "^0.13.0", "lodash.assign": "^4.0.9", "node-glob": "^1.2.0", "vinyl-buffer": "^1.0.0", "vinyl-source-stream": "^1.1.0", "watchify": "^3.7.0" } }
项目用到的话,直接npm install 就可以把所有的包加载进来;
gulpfile.js代码如下:
var gulp = require('gulp'); var less = require('gulp-less'); var mincss = require('gulp-minify-css'); var concat = require("gulp-concat"); var uglify = require("gulp-uglify"); var clean = require('gulp-clean'); var browserify = require("browserify"); var sourcemaps = require("gulp-sourcemaps"); var source = require('vinyl-source-stream'); var buffer = require('vinyl-buffer'); var replace = require('gulp-str-replace'); var imagemin = require('gulp-imagemin'); //自动刷新 var browserSync = require('browser-sync').create(); var reload = browserSync.reload; var fs = require('fs'); var fileContent = fs.readFileSync('./package.json'); var jsonObj = JSON.parse(fileContent); var argv = process.argv.pop(); var DEBUGGER = (argv === "-D" || argv === "-d") ? true : false; /* 基础路径 */ var paths = { css : 'src/common/css/', less : 'src/less/', scripts : "src/js/", img : "src/images/", html : "src/html/", build : "build", src : 'src' } var resProxy = "项目的真实路径"; var prefix = "项目的真实路径"+jsonObj.name; if(DEBUGGER) { resProxy = "http://localhost:3000/build"; prefix = "http://localhost:3000/build"; } // 先清理文件 gulp.task('clean-css',function(){ return gulp.src(paths.build + "**/*.css") .pipe(clean()); }); gulp.task('testLess', ['clean-css'],function () { return gulp.src([paths.less + '**/*.less',paths.css+'**/*.css']) .pipe(less()) .pipe(concat('index.css')) .pipe(mincss()) .pipe(replace({ original : { resProxy : /\@{3}RESPREFIX\@{3}/g, prefix : /\@{3}PREFIX\@{3}/g }, target : { resProxy : resProxy, prefix : prefix } })) .pipe(gulp.dest(paths.build + "/css")) .pipe(reload({stream:true})); }); // 监听html文件的改变 gulp.task('html',function(){ return gulp.src(paths.html + "**/*.html") .pipe(replace({ original : { resProxy : /\@{3}RESPREFIX\@{3}/g, prefix : /\@{3}PREFIX\@{3}/g }, target : { resProxy : resProxy, prefix : prefix } })) .pipe(gulp.dest(paths.build+'/html')) .pipe(reload({stream:true})); }); // 对图片进行压缩 gulp.task('images',function(){ return gulp.src(paths.img + "**/*") .pipe(imagemin()) .pipe(gulp.dest(paths.build + "/images")); }); // 创建本地服务器,并且实时更新页面文件 gulp.task('browser-sync', ['testLess','html','browserify'],function() { var files = [ '**/*.html', '**/*.css', '**/*.less', '**/*.js' ]; browserSync.init(files,{ server: { //baseDir: "./html" } }); }); // 解决js模块化及依赖的问题 gulp.task("browserify",function () { var b = browserify({ entries: ["./src/js/index.js"], debug: true }); return b.bundle() .pipe(source("index.js")) .pipe(buffer()) .pipe(sourcemaps.init({loadMaps: true})) .pipe(gulp.dest("./build/js")) .pipe(uglify()) .pipe(sourcemaps.write(".")) .pipe(replace({ original : { resProxy : /\@{3}RESPREFIX\@{3}/g, prefix : /\@{3}PREFIX\@{3}/g }, target : { resProxy : resProxy, prefix : prefix } })) .pipe(gulp.dest("./build/js")) .pipe(reload({stream:true})); }); gulp.task('default',['testLess','html','images','browserify'],function () { gulp.watch(["**/*.less","**/*.css"], ['testLess']); gulp.watch("**/*.html", ['html']); gulp.watch("**/*.js", ['browserify']); }); gulp.task('server', ['browser-sync','images'],function () { gulp.watch(["**/*.less","**/*.css"], ['testLess']); gulp.watch("**/*.html", ['html']); gulp.watch("**/*.js", ['browserify']); });
如果我们在命令行中 运行gulp server -d 那就是开发环境,会自动打开一个服务器;如果运行gulp的话,就是线上的正式环境了;代码会通过replace插件替换成线上的环境;
使用replace插件的好处可以这样引入文件
<script src="@@@PREFIX@@@/js/index.js"></script>
<link rel="stylesheet" href="@@@PREFIX@@@/css/index.css"/>
所有的图片都可以使用这样的@@@PREFIX@@@来引入的,这样的话就可以指向本地的环境和线上的环境了;方便开发;