前端构建工具简介

1.什么是构建工具

比如我们执行一些例如CoffeeScript/ES6去代替JavascriptJSCSS压缩、用Less去写CSS、用Jade去写HTML、用Browserify去模块化、为非覆盖式部署的资源加MD5戳等,这些操作如果我们一遍遍手动执行,非常耗费时间和精力,所以前端构建工具,或者较前端自动化构建工具,就是用来让我们不再做机械重复的事情,解放我们的双手的。

  • 以gulp为例,编写gulpfile.js

例如:

gulp = require('gulp')
coffee = require('gulp-coffee')
uglify = require('gulp-uglify')
rename = require('gulp-rename')

file = './src/js/a.coffee'

gulp.task('coffe',function(){
  gulp.src(file)
      .pipe(coffee())   //编译
      .pipe(uglify())    //压缩
      .pipe(rename({
        extname:".min.js"    //重命名
      }))
  .pipe(gulp.dest('./build/js'))
})

gulp.task('watch',function(
  gulp.watch(file,['coffee'])
))
gulp.task('default',['coffee'])

  • 这样,我只要执行一下gulp watch,它就可以自动监视a.coffee的变化,每次修改a.coffee并保存后,它就会自动执行编译->压缩丑化->重命名这一系列动作了。

2.构建工具的发展

构建其实是工程化、自动化思想在前端开发中的体现,将一系列流程用代码去实现,让代码自动化地执行这一系列复杂的流程。

构建可以实现如下内容:

  • 代码转换: 将 TypeScript/es6 编译成JavaScript、将 SCSS 编译成 CSS等。
  • 文件优化: 压缩JavaScript、CSS、HTML 代码,压缩合并图片等。
  • 代码分割: 提取多个页面的公共代码,提取首屏不需要执行部分代码让其异步记在。
  • 模块合并: 在采用模块化的项目里会有很多个模块和文件,需要通过构建功能将模块分类合并成一个文件。
  • 自动刷新: 监听本地源代码变化,自动重新构建、刷新浏览器。
  • 代码校验: 在代码被提交到仓库前需要校验代码是否符合规范,以及单元测试是否通过。
  • 自动发布: 更新代码后,自动构建出线上发布代码并传输给发布系统。

构建工具发展:Npm Scripts、Grunt、Gulp、FIS 3、Webpack、Rollup、Parcel

Grunt:

Grunt is the older project. It relies on plugin specific configuration. This is fine up to a point but believe me, you don't want to end up having to maintain a 300 line Gruntfile. The approach simply turns against itself at some point. Just in case youare curious what the configuration looks like, here's an examplefrom Grunt documentation:

Grunt是相比后面几个更早的项目,他依赖于各种插件的配置。这是一个很好的解决方案,但是请相信我,你不会想看到一个 300 行的 Gruntfile。如果你好奇 Grunt 的配置会如何,那么这里是有个从 Grunt 文档 的例子

module.exports = function(grunt){
  grunt.initCongfig({
    jshint:{
      files:['Gruntgile.js','src/**/*.js','test/**/*.js'],
      options:{
        globals:{
          jQuery:true
        }  
      }
    },
    watch:{
      files:['<%=jshint.files%>'],
      tasks:['jshint']
    }
  });
  grunt.loadNpmTasks('grunt-contrib-jshint');
  grunt.loadNpmTasks('grunt-contrib-watch');
  grunt.registerTask('defaule',['jshint']);
}

Grunt 的优点是:

  • 灵活,它只负责执行我们定义好的任务;
  • 大量可复用插件封装好了常见的构建任务。

Grunt 的缺点是:
集成度不高,要写很多配置后才可以用,无法做到开箱即用。

Grunt 相当于进化版的 Npm scripts,它的诞生其实是为了弥补 Npm Scripts 的不足。

Gulp:

Gulp takes a different approach. Instead of relying on configuration per plugin you deal with actual code. Gulp builds on top of the tried and true concept of piping. If you are familiar with Unix, it's the same here. You simply have sources, filters and sinks. In this case sources happen to match to some files, filters perform some operations on those (ie. convert to JavaScript) and then output to sinks (your build directory etc.). Here's a sample Gulpfileto give you a better idea of theapproach taken from the project README and abbreviated a bit:

Gulp 提供了一个不一样的解决方案,而不是依赖于各种插件的配置。Gulp 使用了一个文件流的概念。如果你熟悉 Unix,那么 Gulp 对你来说会差不多,Gulp 会提供你一些简单化的操作。在这个解决方案中,是去匹配一些文件然后操作(就是说和JavaScript 相反)然后输出结果(比如输出在你设置的编译路径等)。这里有一个简单的 Gulpfile 的例子:

var gulp = require('gulp');
var coffee= require('gulp-coffee');
var concat= require('gulp-concat');
var uglify= require('gulp-uglify');
var sourcemaps= require('gulp-sourcemaps');
var del= require('del');
var paths= {
  script:[
    'client/js/**/*.coffee',
    '!client/external/**/*.coffee',
  ],
};
// 不是左右的任务需要使用streams
// 一个gulpfile,只是另一个node的程序,所以你可以使用所有npm的包
gulp.task('clean',function(cb){
  // 你可以用`gulp.src`来使用多重通配符模式
  del(['build'],cb);
});
gulp.task('scripts',['clean'],function(){
  // 压缩和复制所有JavaScript(除了第三方库)
  // 加上sourcemaps
  return gulp.src(paths.scripts)
    .pipe(sourcemaps.init())
      .pipe(coffee())
      .pipe(uglify())
      .pipe(concat('build/js'))
    .pipe(sourcemaps.write())
    .pipe(gulp.dest('build/js'));
});
// 监听文件修改
gulp.task('watch',function(){
  gulp.watch(paths.scripts,['scripts'])
})
// 默认任务(就是你在命令行输入`gulp`时运行)
gulp.task('default',['watch','scripts']);

Given the configuration is code you canalways just hack it if you run into troubles. You can wrap existing Node.jsmodules as Gulp plugins and so on. You still end up writing a lot ofboilerplate for casual tasks, though.

这些配置都是代码,所以当你遇到问题也可以修改,你也可以使用已经存在的 Gulp 插件,但是你还是需要写一堆模板任务。

Gulp 是一个基于流的自动化构建工具。除了可以管理任务和执行任务,还支持监听文件、读写文件。Gulp 被设计的非常简单,只通过下面5个方法就可以支持几乎所有构建场景:

  • 通过 gulp.task 注册一个任务;
  • 通过 gulp.run 执行任务;
  • 通过 gulp.watch 监听文件变化;
  • 通过 gulp.src 读取文件;
  • 通过 gulp.dest 写完文件。
// 引入 Gulp
var gulp = require("gulp");
// 引入插件
var jshint = require("gulp-jshint");
var sass = require("gulp-sass");
var concat = require("gulp-concat");
....
// 便宜SCSS任务
gulp.task('scss', function() {
  // 读取文件,通过管道喂给插件
  gulp.src('./scss/*.scss')
  // SCSS 插件将 scss 文件编译成 css
      .pipe(sass())
      // 输出文件
      .pipe(guilp.dest('./css'));
});
// 合并压缩 JavaScript 文件
gulp.task('scripts', function() {
  gulp.src('./js/*.js')
      .pipe(concat('all.js'))
      .pipe(uglify())
      .pipe(gulp.dest('./dest'));
});
// 监听文件变化
gulp.task('watch', function() {
  // 当 SCSS 文件被编辑时执行 SCSS 任务
  gulp.watch('./scss/*.scss', ['sass']);
  gulp.watch('./js/*.js', ['scripts']);
});

Gulp 的优点:
好用又不失灵活,既可以单独完成构建,也可以和其他工具搭配使用。

缺点:
和Grunt 类似。集成度不高,要写很多配置后才可以用,无法做到开箱即用。

可以将Gulp 看做是 Grunt 的加强版。相对于 Grunt ,Gulp 增加了文件监听、读写文件、流式处理的功能。

FIS 3:

Fis3是一个来自百度的优秀国产构建工具。相对于 GruntGulp 这些只提供了基本功能的工具。Fis3集成了开发者常用的构建功能,如下所述。

  • 读写文件:通过 fis.match 读文件,release 配置文件输出路径。
  • 资源定位:解析文件之间的依赖关系和文件位置。
  • 文件指纹:在通过 useHash 配置输出文件时为文件 URL加上 md5 戳,来优化浏览器的缓存。
  • 文件编译:通过 parser 配置文件解析器做文件转换,例如将 ES6 编译成 ES5。
  • 压缩资源:通过 optimizer 配置代码压缩方法。
  • 图片合并:通过 spriter 配置合并 CSS 里导入的图片到一个文件中,来减少 HTTP 请求数。

大致使用如下:

// 加 md5
fis.match('*.{js,css,png}', {
  useHash: true
});
// 通过fis3-parse-typescript插件可将 TypeScript 文件转换成 JavaScript 文件
fis.match('*.ts', {
  parser: fis.plugin('typescript')
});
// 对CSS进行雪碧图合并
fis.match('*.css', {
  // 为匹配到的文件分配属性 useSprite
  useSprite: true
});
// 压缩 JavaScript
fis.match('*.js', {
  optimizer: fis.plugin('uglify-js')
});
// 压缩CSS
fis.match('*.css', {
  optimizer: fis.plugin('clean-css')
});
// 压缩图片
fis.match('*.png', {
  optimizer: fis.plugin('png-compressor')
});

可以看出 Fis3 很强大,内置了许多功能,无需做太多配置就能完成大量工作。

Fis3的优点:
集成了各种Web老发所需的构建功能,配置简单,开箱即用。其缺点是目前官方已经不再更新和维护,不支持最新版本的Node。

browserify

Dealing with JavaScript modules has alwaysbeen a bit of a problem given the language actually doesn't have a concept ofmodule till ES6\. Ergo we are stuck with the 90s when it comes to browserenvironment. Various solutions, including AMD, have been proposed. Inpractice it can be useful just to use CommonJS, the Node.js format, and lettooling deal with the rest. The advantage is that you can often hook into NPMand avoid reinventing the wheel.

处理 JavaScript 模块一直是一个大问题,因为这个语言在ES6 之前没有这方面的概念。因此我们还是停留在90年代,各种解决方案,比如提出了 AMD。在实践中只使用 CommonJSNode.js 所采用的格式)会比较有帮助,而让工具去处理剩下的事情。它的优势是你可以发布到 NPM 上来避免重新发明轮子。

Browserify solves this problem. Itprovides a way to bundle CommonJS modules together. You can hook it up withGulp. In addition there are tons of smaller transformation tools that allow youto move beyond the basic usage (ie. watchify provides a file watcherthat creates bundles for you during development automatically). This will savesome effort and no doubt is a good solution up to a point.

Browserify 解决了这个问题,它提供了一种可以把模块集合到一起的方式。你可以用 Gulp 调用它,此外有很多转换小工具可以让你更兼容的使用(比如 watchify 提供了一个文件监视器帮助你在开发过程中更加自动化地把文件合并起来),这样会省下很多精力。毋庸置疑,一定程度来讲,这是一个很好的解决方案。

  • 服务器端NodeJS自带模块功能,可以使用require和module.exports构建项目
  • 随着项目的增大,浏览器端任务越来越重,依赖关系越来越复杂,需要使用工具实现模块化。
  • Browserify通过require和module.exports来导入和导出。
  • Browserify的原理:部署时处理代码依赖,将模块打包为一个文件。

Webpack:

Webpack expands on the idea of hooking into CommonJS require. What if you could just requirewhatever you needed in your code, be it CoffeeScript, Sass, Markdown orsomething? Well, Webpack does just this. It takes your dependencies, puts themthrough loaders and outputs browser compatible static assets. All of this isbased on configuration. Here is a sample configuration from the officialWebpack tutorial:

Webpack 扩展了 CommonJsrequire 的想法,比如你想在 CoffeeScriptSassMarkdown 或者其他什么代码中 require 你想要的任何代码的话?那么 Webpack 正是做这方面的工作。它会通过配置来取出代码中的依赖,然后把他们通过加载器把代码兼容地输出到静态资源中。这里是一个 Webpack 官网 上的例子:

module.exports = {
  entry:'./entry.js',
  output:{
    path:_dirname,
    filename:'bundle.js'
  },
  module:{
    loaders:[
      {test:/\.css$/,loader:'style!css'}
    ]
  }
}

Webpack 是一个打包模块化的JavaScript的工具,在Webpack里一切文件皆模块,通过 loader 转换文件,通过Plugin注入钩子,最后输出由多个模块组合成的文件。Webpack 专注于构建模块化项目。

其官网的首页图很形象的展示了 Webpack 的定义,如下图:

一切文件,如JavaScript、CSS、SCSS、图片、模板,对于Webpack 来说都是一个个模块,这样的好处是能清晰地描绘各个模块之间的依赖关系,以方便Webpack进行组合和打包,经过Webpack的处理,最终会输出浏览器能使用的静态资源。

Webpack具有很大的灵活性,能配置处理文件的方式,使用方法大致如下:

module.exports = {
  // 所有模块的入口,webpack从入口开始递归解析出所有依赖的模块
  entry: './app.js',
  output: {
    // 将入口所依赖的所有模块打包成一个文件 bundle.js 输出
    filename: 'bundle.js'
  }
}

Webpack的优点是:

  • 专注于处理模块化的项目,能做到开箱即用、一步到位;
  • 可通过 Plugin 扩展,完整好用又不失灵活性;
  • 使用场景不局限于Web开发;
  • 社区庞大活跃,经常引入紧跟时代发展的新特性,能为大多数场景找到已有的开源扩展;
  • 良好的开发体验;

Webpack的缺点是:
只能用于采用模块化开发的项目。

Rollup:

Rollup 是一个和 Webpack 很类似但专注于ES6的模块打包工具。它的亮点在于,针对ES6源码进行 Tree Shaking,以去除那些已经被定义但没被使用的代码并进行Scope Hoisting,以减少输出文件的大小和提升运行性能。然而 Rollup 的这些亮点随后就被Webpack模仿和实现了。

Parcel:

Parcel 是 最近新起的Web 应用打包工具,适用于经验不同的开发者。它利用多核处理提供了极快的速度,并且不需要任何配置。

Parcel的优点:

  • 极速打包。Parcel 使用 worker 进程去启用多核编译。同时有文件系统缓存,即使在重启构建后也能快速再编译。
  • 开箱即用。对 JS, CSS, HTML, 文件 及更多的支持,而且不需要插件。
  • 自动转换。如若有需要,Babel, PostCSS, 和PostHTML甚至 node_modules 包会被用于自动转换代码。
  • 热模块替换。Parcel 无需配置,在开发环境的时候会自动在浏览器内随着你的代码更改而去更新模块。
  • 友好的错误日志。当遇到错误时,Parcel 会输出 语法高亮的代码片段,帮助你定位问题。

缺点:

  • 不支持SourceMap:在开发模式下,Parcel也不会输出SourceMap,目前只能去调试可读性极低的代码;
  • 不支持剔除无效代码(TreeShaking):很多时候我们只用到了库中的一个函数,结果Parcel把整个库都打包了进来;
  • 一些依赖会让Parcel出错:当你的项目依赖了一些Npm上的模块时,有些Npm模块会让Parcel运行错误;
  • Parcel需要为零配置付出代价。零配置其实是把各种常见的场景做为默认值来实现的,

这虽然能节省很多工作量,快速上手,但这同时会带来一些问题:

  • 不守规矩的node_module:有些依赖的库在发布到Npm上时可能不小心把.babelrcpostcss.config.js tsconfig.json这些配置文件也一起发布上去了,
  • 不灵活的配置:零配置的Parcel关闭了很多配置项,在一些需要的配置的场景下无法改变。

Parcel使用场景受限。目前Parcel只能用来构建用于运行在浏览器中的网页,这也是他的出发点和专注点。在软件行业不可能存在即使用简单又可以适应各种场景的方案,就算所谓的人工智能也许能解决这个问题,但人工智能不能保证100%的正确性。

反观Webpack除了用于构建网页,还可以做:

  • 打包发布到Npm上的库
  • 构建Node.js应用(同构应用)
  • 构建Electron应用
  • 构建离线应用(ServiceWorkers)

3.为什么选择Webpack

上面介绍的构建工具是按照他们的诞生时间排序的,他们是时代的产物,侧面反映出 Web 开发的发展趋势

如下所述:

  • 在 Npm Scripts 和 Grunt 时代,Web 开发要做的事情变多,流程复杂,自动化思想被引入,用于简化流程;
  • 在 Gulp 时代,开始出现一些新语言用于提高开发效率,流程处理思想的出现是为了简化文件转换的流程,例如将ES6转换为ES5;
  • 在Webpack时代,由于单页应用的流行,网页的功能和实现代码变的复杂、庞大,Web开发向模块化改进。

这些构建工具都有各自的定位和专注点,它们之间既可以单独完成任务,也可以互相搭配起来弥补各自的不足。在了解这些常见的构建工具后,我们需要根据自己的需求去判断应该如何进行选择和搭配它们才能更好的满足自己的需求。

经过多年的额发展,Webpack 已经成为构建工具中的首选,这是因为:

  • 大多数团队在开发新项目时会采用紧跟时代的技术,这些技术几乎都会采用“模块化+新语言+新框架”,Webpack可以为这些新项目提供一站式的解决方案;
  • Webpack有良好的生态和维护团队,能提供良好的开发体验并保证质量;
  • Webpack 被全世界大量的Web开发者使用和验证,能找到各个层面所需要的教程和经验分享。

4.Gulp和Webpack的区别

常有人拿gulpwebpack来比较,知道这两个构建工具功能上有重叠的地方,可单用,也可一起用,但本质的区别就没有那么清晰。

Gulp强调的是前端开发的工作流程,我们可以通过配置一系列的task,定义task处理的事务(例如文件压缩合并、雪碧图、启动server、版本控制等),然后定义执行顺序,来让gulp执行这些task,从而构建项目的整个前端开发流程。 简单说就一个Task Runner,就是用来跑一个一个任务的。

Gulp没发解决的是 js module 的问题,是你写代码时候如何组织代码结构的问题。

Webpack是一个前端模块化方案,更侧重模块打包,我们可以把开发中的所有资源(图片、js文件、css文件等)都看成模块,通过loader(加载器)和plugins(插件)对资源进行处理,打包成符合生产环境部署的前端资源。

  • 相同点: 文件合并与压缩(css,js),sass/less预编译,启动server,版本控制。

  • 不同点: 虽然都是前端自动化构建工具,但看他们的定位就知道不是对等的。

  • gulp严格上讲,模块化不是他强调的东西,他旨在规范前端开发流程。

  • webpack更是明显强调模块化开发,而那些文件压缩合并、预处理等功能,不过是他附带的功能。

你可能感兴趣的:(前端构建工具简介)