原文连接:http://mrzhang123.github.io/2016/09/07/gulpUse/
项目连接:https://github.com/MrZhang123/Web_Project_Build/tree/master/gulpcss
上个月月底在公司提出关于先后端分离的想法,而且开始研究关于先后端分离,前端工程化,模块化的一些东西,上周开始我准备本身开始写基于Gulp流的前端工程文件,这两天有时间,着手开始实现这个想法,可是写的过程当中,遇到了一些问题,正是由于这些问题的解决让我对Gulp的流式处理有了更深的理解,写下这篇文章,分享一下这俩天我在写Gulp的时候学到的一些东西。html
首先Gulp是基于Nodejs的,因此安装Nodejs是前提,Node能够说是前端神器,基于Node有各类各样的工具,正是由于这些工具让咱们很是方便的构建前端工程。前端
我本身通常不喜欢在C盘状太多与系统无关的东西,而经过Node自带的npm安装的插件默认在C盘,可是我将Node安装到D盘后,想让插件就安装在Nodejs的主目录下,怎么办呢?node
在Node主目录下新建"node_global"及"node_cache"两个文件夹git
启动cmd,输入github
//后面的设置目录根据你的目录结构自行更改 npm config set prefix "D:\Program\nodejs\node_global" npm config set cache "D:\Program\nodejs\node_cache"
关闭cmd,打开系统对话框,“个人电脑”右键“属性”-“高级系统设置”-“高级”-“环境变量”。chrome
进入环境变量对话框,在系统变量下新建"NODE_PATH",输入"D:Programnodejsnode_globalnode_module"。 因为改变了module的默认地址,因此上面的用户变量都要跟着改变一下(用户变量"PATH"修改成"D:Programnodejsnode_global"),要不使用module的时候会致使输入命令出现“xxx不是内部或外部命令,也不是可运行的程序或批处理文件”这个错误。npm
通过这四步的设置就可让安装的Node插件放在Nodejs的主目录了。json
//全局安装Gulp npm install -g gulp //在项目中安装Gulp npm install --save-dev gulp
运行gulp -v
,若是不报错,表示安装成功gulp
而后在命令行运行
npm init
让项目生产package.json
文件
众所周知,在开发工程中有开发和上线两个过程,在开发中,咱们通常须要自动刷新以及实时编译,可是若是上线,咱们就须要考虑不少优化的东西,好比文件编译压缩,静态资源放缓存处理等等问题,我本身搭的这个工程只涉及到文件编译压缩,实时刷新,静态资源放缓存这三个基本的流程。
在项目的目录结构以下
-------------------project | | | |--------------dist (该文件夹为打包生成的) | | | | | |----------css | | | | | | | |------index-9dcc24fe2e.css | | | | | |----------js | | | | | | | |------index-9dcc24fe2e.js | | |----------index.html | | | |--------------src | | | | | |----------scss | | | |------index.scss | | | | | |----------js | | | | | | | |------index.js | | | | | |----------index.html | |--------------gulpfile.js | |--------------package.json
在工程中准备使用scss做为css的预编译,因此须要利用gulp对scss进行编译,因此首先安装gulp-sass。
npm install --save-dev gulp-sass
安装完成以后,直接在gulpfile.js引用配置
const sass = require('gulp-sass'); //scss编译 gulp.task('scss:dev',()=>{ gulp.src('src/scss/*.scss') .pipe(sass()) .pipe(gulp.dest('dist/css')); //将生成好的css文件放到dist/css文件夹下 });
这里简单介绍下gulp的两个api:
gulp.src()输入符合所提供的匹配模式或者匹配模式的数组的文件。将返回一个stream或者能够被piped到别的插件中。读文件
gulp.dest()能被pipe进来,而且将会写文件。并从新输出(emits)全部数据,所以能够将它pipe到多个文件夹,若是文件夹不存在则将会自动建立。写文件
实现实时刷新的工具备不少,我本身使用browser-sync,这个工具的功能很是强大,想了解它更多的用法能够查看官网:http://www.browsersync.cn/。
首先咱们在项目中安装该模块
npm install --save-dev browser-sync
根据官网的browser-sync与gulp的配置,获得以下配置:
const browserSync = require('browser-sync').create(); //实时刷新 const reload = browserSync.reload; gulp.task('dev',['scss:dev'],function () { browserSync.init({ server:{ baseDir:'./' //设置服务器的根目录 }, logLevel: "debug", logPrefix:"dev", browser:'chrome', notify:false //开启静默模式 }); //使用gulp的监听功能,实现编译修改事后的文件 gulp.watch('src/scss/*.scss',['scss:dev']); gulp.watch(('*.html')).on('change',reload); });
这样,一个简单的gulp开发流程就出来了,仅仅只是一个编译scss和一个实时刷新。
打包上线,咱们更多的是考虑,静态资源防缓存,优化。对css,js的压缩,对图片的处理,我写的这个简单的流程中并无涉及对图片的处理,因此这里仅针对css,js,html处理。
压缩css咱们使用gulp-sass就能够,由于它在编译scss的时候有一个配置选项能够直接输出被压缩的css。压缩js我使用了gulp-uglify,静态资源防缓存使用gulp-rev和gulp-rev-collector。
//scss编译 gulp.task('css',()=> { gulp.src('src/scss/*.scss') .pipe(sass({ outputStyle: 'compressed' //编译并输出压缩过的文件 })) .pipe(rev()) //给css添加哈希值 .pipe(gulp.dest('dist/css')) .pipe(rev.manifest()) //给添加哈希值的文件添加到清单中 .pipe(gulp.dest('rev/css')); }); //压缩js gulp.task('js', ()=> { gulp.src('src/js/*js') .pipe(uglify()) .pipe(rev()) //给js添加哈希值 .pipe(gulp.dest('dist/js')) .pipe(rev.manifest()) //给添加哈希值的文件添加到清单中 .pipe(gulp.dest('rev/js')); });
其中gulp-rev是为css文件名添加哈希值,而rev.manifest()会生成一个json文件,这个json文件中记录了原文件名和添加哈希值后的文件名的一个对应关系,这个对应关系在最后对应替换html的引用的时候会用到。
生成的json文件以下:
{ "index.css": "index-9dcc24fe2e.css" }
因为给文件添加了哈希值,因此每次编译出来的css和js都是不同的,这会致使有不少冗余文件,因此咱们能够每次在生成文件以前,先将原来的文件所有清空。
gulp中也有作这个工做的插件---gulp-clean,所以咱们能够在编译压缩添加哈希值以前先将原文将清空。
const clean = require('gulp-clean'); //清空文件夹里全部的文件 //每次打包时先清空原有的文件夹 gulp.task('clean', ()=> { gulp.src(['dist', 'rev'], {read: false}) //这里设置的dist表示删除dist文件夹及其下全部文件 .pipe(clean()); });
前面提到的gulp-rev实现了给文件名添加哈希编码,可是在打包完成后如何让原来未添加哈希值的引用自动变为已经添加哈希值的引用,这里用到gulp-rev的一个插件gulp-rev-collector,配置以下:
//将处理过的css,js引入html gulp.task('reCollector',()=>{ gulp.src(['rev/**/*.json','src/*.html']) .pipe(reCollector({ replaceReved: true, //模板中已经被替换的文件是否还能再被替换,默认是false dirReplacements: { //标识目录替换的集合, 由于gulp-rev建立的manifest文件不包含任何目录信息, 'css/': '/dist/css/', 'js/': '/dist/js/' } })) .pipe(gulp.dest('dist')) });
在我本身写的时候,出现这个问题,运行完成该任务后,html中的css和js引用并无发生变化,网上搜了半天,才知道是因为本身用了gulp-rename插件,而后将文件名都添加了.min(至于为何添加,仅仅是由于是压缩过的,应该写个)而在本身写的html里面引用的文件并无.min,因为gulp-rev-collector在替换的时候根据生成的json文件替换,在json中,文件都有了.min而在html中没有,因此没法匹配,天然也就不能实现替换了,因此在替换的时候必定要注意gulp-rev生成的json文件中的css,js与html中的引用的同样,不然没法实现替换。
<font color="red">在gulp-rev-collector的api中有一个revSuffix,这个看起来能够实现相似于gulp-rename的功能,可是不知道该怎么用,你们若是知道的话请告诉我...</font>
完成上面几个步骤后咱们将全部任务串起来,让其能够一条命令而后所有执行
gulp.task('build',['clean', 'css', 'js', 'reCollector']);
本觉得到这里,就算是写完了,运行,完美,打包生成文件,再运行一次,报错了!!!!
[19:04:57] Finished 'default' after 7.38 μs stream.js:74 throw er; // Unhandled stream error in pipe. ^ Error: ENOENT: no such file or directory, stat 'D:\project\dist\js\index-6045b384e6.min.js' at Error (native)
提示我找不到这个文件,这让我很郁闷啊,而后我分开执行,很ok,能够肯定是执行顺序有问题,极可能在没有清理完成就执行后面了,查了gulp的官网文档才知道自己gulp的pipe是一个一个任务进行的,是同步的,可是每一个task之间是不一样步的,是一块儿进行的,这也验证了个人猜测,因此在网上找如何解决这个问题,找到一个叫run-sequence的npm插件,配置文件以下:
//进行打包上线 gulp.task('build', ()=> { runSequence('clean', ['css', 'js'], 'reCollector'); });
本觉得运行就ok,结果,仍是报错,这里就涉及到对gulp的另外一个理解
在用这个插件让任务有序进行后,我想进一步直观的看到它对任务的序列化,本身写了一个demo,以下:
gulp.task('a',function(){ setTimeout(function () { console.log(1); },30); }); gulp.task('b',function() { console.log(2); }); gulp.task('ab',function(){ runSequence('a','b'); });
可是这里就出现问题了,runSequence无论用了,找插件的说明和gulp官方文档,原来异步任务,像setTimeout,readFile等,须要添加一个callback的执行,这里的callback()就会返回一个promise的resolve(),告诉后面的任务,当前任务已经完成,后面能够继续执行了,因此在task a里面执行callback。
gulp.task('a',function(cb){ setTimeout(function () { console.log(1); cb(); },30); });
那为何前面写的那些任务不须要添加一个callback呢?因为gulp的pipe流让每个task中的小任务(每个pipe)顺序执行,从而整个pipe流是同步的,并非异步任务,因此并不须要手动让其返回promise,run-sequence会自动帮咱们管理。
至此,咱们就完成了一个简易的基于gulp的前端工程的搭建,不少东西确实,想着并不难,作起来会出现各类各样意想不到的问题,gulp很早就知道,都是单个任务在写,而后用哪一个执行哪一个命令,直到本身写完这个这个简单的工程,才对gulp有了更深刻的理解。