前端构建之gulp
前端构建之gulp
为什么需要前端构建
不解释
本文大致分为以下几个内容:
- 规范校验js代码(jslint)
- js解释器(babel)
- 合并js代码(concat)
- 压缩js代码(uglify)
- sourcemap
- 规范校验css代码(csslint)
- css解释器
- 合并css代码
- 压缩css代码
规范校验js代码
1. 用gulp检测js代码规范
js检测工具用来
- 检测自己写的js是否有语法错误
- 是否根据我们设定的规则写的
常用的js检测工具有jshint、jslint、eslint等, 推荐用eslint。
结合gulp的话,用gulp-eslint这个插件。使用eslint时配置一个.eslintrc
文件,用来编写自定义js规则。
如果用到了es模块,需要在.eslintrc
配置中指定parserOptions的sourceType为module
。
{ "root": true, "env": { "browser": true, "node": true, "es6": true }, "parserOptions": { "sourceType": "module" }, "extends": "eslint:recommended", //使用推荐的eslint语法 "rules": { "indent": ["error", 4], //自定义indent为4个空格,级别为error "semi": ["off", "always"], "no-console": "off", "no-unused-vars": "off" } }
2. 使用prettier强制修改js代码
使用prettier, 根据prettier自定义来强制修改js代码。
可以在使用prettier时添加options,或者建立一个config文件来配置options也可以。
参考这里
{ singleQuote: true, trailingComma: "all", bracketSpacing : true, semi: true, tabWidth: 4, //定义indent为4个空格 printWidth: 120 }
3. 当gulp一个task依赖异步任务时
如果gulp一个task依赖一个或者多个异步任务时, 需要判断这个异步是否完成,才能进行下一个任务,
这样才能保证gulp的任务让顺序执行。
异步任务的处理可以参考这里
4. del
通过del模块可以删除指定file
5. gulp中实现目录copy
很简单: gulp.src() 获取目录下所有文件 gulp.dest()输出到指定目录
6. run-sequence
run-sequence
这个模块用来在gulp中控制多个任务让顺序执行
7. 更新代码
1. 是不是经常出现indent不齐 2. 当看到别人用双引号引字符串时想给他改成单引号 3. 有的语句结尾有分号结束,有的语句结尾没有分号结束, 强迫症的你有没有想它的冲动
这些可以通过下面这个思路解决。
- gulp.src()获取想要处理的js文件
- 通过prettier 根据自定义规则强制修改
- eslint检测通过prettier强制修改的js文件
- gulp.dest()输出到一个临时文件夹(比如tempjs)
- copy这个临时文件夹(比如tempjs)的内容到原来的js文件夹下
- 删掉临时文件夹(tempjs)
// js eslint set gulp.task('js_lint', function () { return gulp.src('./src/js/**/*.js') //步骤1 .pipe($.prettier({ //步骤2 singleQuote: true, //自定义选项 trailingComma: "all", bracketSpacing : true, semi: true, tabWidth: 4, printWidth: 120 })) .pipe($.eslint()) //步骤3 .pipe($.eslint.format()) //输出到console .pipe($.eslint.failAfterError()) //出现错误时,指定这个任务不成功 .pipe(gulp.dest('./src/tempjs')) //步骤4 }); // js file copy gulp.task('copy_js', function () { //步骤5 return gulp.src('./src/tempjs/**/*.js') .pipe(gulp.dest('./src/js')) }); // js file clean gulp.task('clean', function (cb) { //步骤6 return del('./src/tempjs', cb); }); // js forma gulp.task('format_js', function (cb) { runSequence('js_lint','copy_js','clean',cb); //指定执行顺序 });
8. precommit
这个插件目的是在commit之前,执行相关操作"precommit": "gulp format_js"
9. husky
这个插件目的是建立和git之间的hooks(钩子), 通常和precommit结合用,
比如使用git commit这个命令时, 会通过hooks调用precommit语句。
这样我们就不需要专门去执行npm run precommit
这个命令了,
当我们git commit的时候,husky会自动调用npm run precommit
。
10. lint-staged
但是还有一个问题, 上面执行完git commit 后,通过prettier修改了所有的js文件,
有些便不是我自己修改的文件,也会被强制修改,
于是可以通过lint-staged这个node模块来指定stage中的文件。
思想很简单,只对git stage中的文件处理。
可以参考这里
通常使用它的思想:
- 执行git commit时,通过husky调用
precommit
precommit
执行lint-staged
,即只对git stage中的文件进行处理- 在
lint-staged
中配置prettier修改js文件(添加一个config文件来配置) - 在
lint-staged
中配置eslintjs检测文件(添加一个eslintrc文件来配置) - 如果正常通过, 则把通过prettier修改和eslint检测的js文件,通过git add 添加
"scripts": { "precommit": "lint-staged" }, "lint-staged": { "*.js": [ // 要处理的js路径 "prettier --write", //要处理的文件上修改 "eslint --fix", // 参数fix的意思是:根据eslint配置文件自动修复js文件 "git add" ] },
prettier 能强制修改js,eslint --fix也能强制修改js, 区别是什么呢???
我的理解prettier更强一点,eslint --fix 强制修改有限。
这里有一个注意点
,prettier
的配置参数, 通过添加一个config文件来配置。
"lint-staged"的配置的意思是,
坑对git stage中的文件的操作,如eslint *.js, git add *.js
, 不能用写gulp *.js
参考这里
我添加的是 prettier.config.js
文件,
module.exports = { printWidth: 120, parser: "flow", singleQuote: true, tabWidth: 4, bracketSpacing : true, semi: true };
eslintrc前面已经写过了。 通过lint-staged就不需要第7步中生成一个临时文件(tempjs)来过渡了, 因为prettier就直接处理stage终端文件了。
第7步的思想可以简化为:
- 通过lint-staged获取想要处理的js文件
- 通过prettier 根据自定义规则强制修改要处理的js文件
- eslint检测通过prettier强制修改的js文件
- eslint检测通过的话, 将处理的js文件添加到git stage中
这样减少了临时文件夹的输出,复制,删除。
这样也不需要写gulp task了,直接通过两个prettier,eslint配置文件就搞定了。
使用es6写js时,转化为各个浏览器能识别的es5
11. babel 编译
babel用来将es6语法转化为es5, 比如es6中的let转化为var, 变量字符串拼接转化为+,等等。 结合gulp用,就是gulp-babel, 它依赖于babel-core,所以需要安装gulp-babel和babel-core。 自己的理解: babel, babel-core提供了babel的运行环境(理解有错误的话,之后改正)。 只有这两个(babel, babel-core)还不能实现转译,需要transform插件(如babel-preset-env) 然后需要安装babel-preset-env来将自己写的es6(源码),根据当前执行环境(浏览器), 转化为es5(当前环境-浏览器能识别的语法) 什么时候用这个babel呢, 获取源码后,合并压缩之前。
gulp.src('*.js') .pipe($.babel({ presets:['env'] })) .pipe(js合并) .pipe(js压缩)
坑 如果使用uglify来压缩js文件, 需要配置babel的options,
添加forceAllTransforms: true
即可以。参考这里
合并js
为什么需要合并js呢,通过合并js文件,可以减少http请求。
12. concat
通过gulp-concat可以将多个js文件合并成一个js文件。 用法相对简单,
gulp.src('src/js/**/*.js') //想要合并的js文件 .pipe($.concat('all.js')) //concat的参数指定合并后的文件名 .pipe(gulp.dest('dist/js/')) //合并后的目录
现在有一个新的需求task1, 需要单独写几个js文件,
但是不想和其他js合并到一起,也就是说想单独合并这几个js文件,然后单独输出,
这个时候, 就要重新写一个gulp task来合并。
gulp.src('src/js/task1/**/*.js') //task1下,想要合并的js文件 .pipe($.concat('task1.js')) //task1下所有js文件合并输出为task1 .pipe(gulp.dest('dist/js/')) //合并后的目录
当这种新的需求很多的时候,就需要不断添加新的gulp task,在大项目多人一起写不同的新需求时很麻烦。
于是可以建立一个中间文件(middle),
在这个中间文件添加想要合并的js文件,及想要输出的js文件。
gulp task只是处理这个中间文件。
这样有新的需求时,修改中间文件就可以了, 就不需要修改gulp的配置文件了。
中间文件,我这里选择yaml。
concat - src: - src/js/task1/top.js - src/js/task1/background.js out: task1.js - src: - src/js/task2/top.js - src/js/task2/index.js out: task2.js
这样就实现了输出到不同js中了。
那么, gulp中task怎么写呢?
因为要处理yaml文件,需要安装js-yaml 这个插件
关于yaml可以参考阮教授的文章
gulp.task('concat', function() { var concatData = yaml.safeLoad( //通过yaml.safeLoad()转化为js语言,可以用console.log(concatData)打出来出来看看 fs.readFileSync('./example.yml', 'utf8') //读取fs模块读取文件内容,可以用console.log()打出来出来看看 ); for (var i = 0; i < doc.concat.length; i++) { gulp.src(concatData.concat[i].src) .pipe($.concat({path: concatData.concat[i].out})) .pipe(gulp.dest('../dist/js/build/')) } })
如果task是上面这样写的话, 还不能达到要求
因为这个任务是比较耗时的,导致我们不能准确判断这个任务什么时候结束,这时需要进行异步处理,这次用promise处理。
安装bluebird
使用promise。
var Promise = require( 'bluebird'); gulp.task('concat', function() { var concatData = yaml.safeLoad( //通过yaml.safeLoad()转化为js语言,可以用console.log(concatData)打出来出来看看 fs.readFileSync('./example.yml', 'utf8') //读取fs模块读取文件内容,可以用console.log()打出来出来看看 ); return new Promise(function (resolve, reject) { setTimeout(function() { for (var i = 0; i < doc.concat.length; i++) { gulp.src(concatData.concat[i].src) .pipe($.concat({path: concatData.concat[i].out})) .pipe(gulp.dest('../dist/js/vendor/')) } resolve(); },500) }) })
压缩js
13. gulp-uglify
gulp-uglify 用来压缩js文件
压缩很简单, 获取js原文件,合并,然后压缩
gulp.task('uglify_js', function () { return gulp .src('src/js/**/*.js') .pipe( $.babel({ presets: [ ['env', { forceAllTransforms: true } ] // forceAllTransforms用来解决uglify不支持es6 ] }) ) .pipe($.concat('all.js')) .pipe($.uglify()) .pipe(gulp.dest('./dist')); })
sourcemap
14. gulp-sourcemaps
sourcemap 的目的:还原源码, 什么意思呢? 当我们将源码经过合并,压缩处理后,成为了一个文件,不方便调试。 特别是压缩后更是面目全非,这样我们在开发阶段不能实现debug,比如打断点功能就不可能实现了。 sorucemap就是解决的这个问题。 使用sourcemap, * sourcemap.init() 初始化 * sourcemap.write('sourcemap') //指定目录,存放一一对应的map文件 经过sourcemap处理后,在目标目录(dest指定的)下,生成一个新文件(与转译前对应的map文件), 目录结构与转译前完全对应。这样我们就很容易找到某个文件,然后实现打断点。
gulp.task('concat_js', function() { return gulp .src('src/js/**/*.js') .pipe($.sourcemaps.init()) .pipe($.concat('all.js')) .pipe($.sourcemaps.write('sourcemap')) .pipe(gulp.dest('./dist')); });
在dist目录下,就能发现sorucemap文件, 里面的目录结构和src/js下的目录结构一模一样。这样和方便查找和操作。
规范校验css代码
规范校验js写法的工具有jslint,jshint,最近的eslint等等, 规范校验css写法的工具有csslint,stylelint等等。
15. stylelint
stylelint用来检测css是否根据配置文件来写的,是基于postcss的一个检测工具。
stylefmt是一个根据stylelint的配置文件来强制修改css文件的插件。有点prettier的意思。
但是可以不用stylefmt, 在stylelint的选项中添加 --fix
也可以修改css文件。
npm install gulp-postcss gulp-stylelint stylelint stylelint-config-standard -save-dev
使用stylelint时,有个标准配置插件, stylelint-config-standard。
以下为 stylelint的配置文件(.stylelintrc)
{ "extends": "stylelint-config-standard", // 使用stylelint的默认标准配置 "rules": { "declaration-block-trailing-semicolon": null, // 各种规则,根据自己需要可以灵活配置 "indentation": 4, "block-no-empty": null, "max-empty-lines": 1, "selector-list-comma-newline-after": never-multi-line, "at-rule-no-unknown": null, "declaration-colon-space-after": null, "no-duplicate-selectors" : null, "no-descending-specificity": null, "selector-pseudo-element-colon-notation": null, "no-empty-source": null } }
结合gulp用。
gulp.task('lint_css', function () { return gulp.src('src/css/**/*.css') .pipe($.stylelint({ reporters: [ {formatter: 'string', console: true} //将错误输出到console ], fix: true })) .pipe(gulp.dest('./dist/css')); });
16. postcss
- 当我们用sass的写法来书写css
- 想在css中用变量,for循环等语法
- 想利用 (cssnext)[http://cssnext.io/] 书写css时
根据不同浏览器自动添加前缀 (autoprefixer)
当我们想在css中实现上述等功能时,可以用postcss。
postcss就是一个平台,可以通过添加各种插件来实现上述等功能。
npm install postcss-cssnext postcss-simple-vars postcss-nested --save-dev
可以写一个postcss的配置文件来专门规范postcss相关的配置,也可以直接写在在gulp配置文件中。
var cssnext = require('postcss-cssnext'); var simpleVars = require('postcss-simple-vars'); var nested = require('postcss-nested') gulp.task('concat_css', function () { var plugins = [ cssnext(), // 处理cssnext simpleVars(), // 处理变量 nested() // 处理向sass那样的内嵌写法 ]; return gulp.src('src/css/**/*.css') .pipe($.sourcemaps.init()) .pipe($.postcss(plugins)) .pipe($.sourcemaps.write('sourceMap/css')) .pipe(gulp.dest('./dist/css')); })
css合并
gulp通过正则匹配很方便的实现多入口多输出的功能。 入口文件(多入口): gulp.src('src/css/**/*.css') 出口文件(多出口): gulp.dest('./dist/css') dist/css目录下的结构和src/css的结构一样。
压缩css代码
17. gulp-clean-css
css 的压缩可以用gulp-clean-css。 `npm install gulp-clean-css --save-dev`
gulp.task('uglify_css', function () { var plugins = [ cssnext(), // 处理cssnext simpleVars(), // 处理变量 nested() // 处理向sass那样的内嵌写法 ]; return gulp.src('src/css/**/*.css') .pipe($.sourcemaps.init()) .pipe($.postcss(plugins)) .pipe($.sourcemaps.write('sourceMap/css')) .pipe($.cleanCss()) .pipe(gulp.dest('./dist/css')); })
于是当我们想要lint css的时候,执行gulp lint_css
,
想要format css的时候执行gulp concat_css
,
想要压缩css的时候,执行uglify_css
。
但是这样很麻烦, 可以在添加一个task,执行所有想要执行的task。
gulp.task('format_js', function (cb) { runSequence('js_lint','copy_js','clean',cb); });
最后附上完整的(github源代码)[https://github.com/zhangchch/...]。
以上都是自己的理解,如有错误的地方,真心求指教。