1.什么是構(gòu)建工具
比如我們執(zhí)行一些例如
CoffeeScript/ES6
去代替Javascript
、JS
或CSS
壓縮雏亚、用Less
去寫CSS
缨硝、用Jade
去寫HTML
、用Browserify
去模塊化评凝、為非覆蓋式部署的資源加MD5
戳等追葡,這些操作如果我們一遍遍手動執(zhí)行,非常耗費時間和精力奕短,所以前端構(gòu)建工具宜肉,或者較前端自動化構(gòu)建工具,就是用來讓我們不再做機械重復的事情翎碑,解放我們的雙手的谬返。
- 以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'])
- 這樣日杈,我只要執(zhí)行一下
gulp watch
遣铝,它就可以自動監(jiān)視a.coffee
的變化佑刷,每次修改a.coffee
并保存后,它就會自動執(zhí)行編譯->壓縮丑化->重命名這一系列動作了酿炸。
2.構(gòu)建工具的發(fā)展
構(gòu)建其實是工程化瘫絮、自動化思想在前端開發(fā)中的體現(xiàn),將一系列流程用代碼去實現(xiàn)填硕,讓代碼自動化地執(zhí)行這一系列復雜的流程麦萤。
構(gòu)建可以實現(xiàn)如下內(nèi)容:
- 代碼轉(zhuǎn)換: 將 TypeScript/es6 編譯成JavaScript、將 SCSS 編譯成 CSS等扁眯。
- 文件優(yōu)化: 壓縮JavaScript壮莹、CSS、HTML 代碼姻檀,壓縮合并圖片等命满。
- 代碼分割: 提取多個頁面的公共代碼,提取首屏不需要執(zhí)行部分代碼讓其異步記在绣版。
- 模塊合并: 在采用模塊化的項目里會有很多個模塊和文件胶台,需要通過構(gòu)建功能將模塊分類合并成一個文件。
- 自動刷新: 監(jiān)聽本地源代碼變化僵娃,自動重新構(gòu)建概作、刷新瀏覽器腋妙。
- 代碼校驗: 在代碼被提交到倉庫前需要校驗代碼是否符合規(guī)范默怨,以及單元測試是否通過。
- 自動發(fā)布: 更新代碼后骤素,自動構(gòu)建出線上發(fā)布代碼并傳輸給發(fā)布系統(tǒng)匙睹。
構(gòu)建工具發(fā)展: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 的優(yōu)點是:
- 靈活,它只負責執(zhí)行我們定義好的任務与殃;
- 大量可復用插件封裝好了常見的構(gòu)建任務单山。
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
相反)然后輸出結(jié)果(比如輸出在你設置的編譯路徑等)者吁。這里有一個簡單的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'));
});
// 監(jiān)聽文件修改
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.
這些配置都是代碼复凳,所以當你遇到問題也可以修改,你也可以使用已經(jīng)存在的
Gulp
插件灶泵,但是你還是需要寫一堆模板任務育八。
Gulp
是一個基于流的自動化構(gòu)建工具。除了可以管理任務和執(zhí)行任務赦邻,還支持監(jiān)聽文件髓棋、讀寫文件。Gulp
被設計的非常簡單惶洲,只通過下面5個方法就可以支持幾乎所有構(gòu)建場景:
- 通過 gulp.task 注冊一個任務按声;
- 通過 gulp.run 執(zhí)行任務;
- 通過 gulp.watch 監(jiān)聽文件變化恬吕;
- 通過 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'));
});
// 監(jiān)聽文件變化
gulp.task('watch', function() {
// 當 SCSS 文件被編輯時執(zhí)行 SCSS 任務
gulp.watch('./scss/*.scss', ['sass']);
gulp.watch('./js/*.js', ['scripts']);
});
Gulp 的優(yōu)點:
好用又不失靈活渐裂,既可以單獨完成構(gòu)建,也可以和其他工具搭配使用钠惩。
缺點:
和Grunt 類似柒凉。集成度不高,要寫很多配置后才可以用妻柒,無法做到開箱即用扛拨。
可以將Gulp 看做是 Grunt 的加強版。相對于 Grunt 举塔,Gulp 增加了文件監(jiān)聽绑警、讀寫文件求泰、流式處理的功能。
FIS 3:
Fis3
是一個來自百度的優(yōu)秀國產(chǎn)構(gòu)建工具计盒。相對于Grunt
渴频、Gulp
這些只提供了基本功能的工具。Fis3
集成了開發(fā)者常用的構(gòu)建功能北启,如下所述卜朗。
- 讀寫文件:通過 fis.match 讀文件,release 配置文件輸出路徑咕村。
- 資源定位:解析文件之間的依賴關系和文件位置场钉。
- 文件指紋:在通過 useHash 配置輸出文件時為文件 URL加上 md5 戳,來優(yōu)化瀏覽器的緩存懈涛。
- 文件編譯:通過 parser 配置文件解析器做文件轉(zhuǎn)換逛万,例如將 ES6 編譯成 ES5。
- 壓縮資源:通過 optimizer 配置代碼壓縮方法批钠。
- 圖片合并:通過 spriter 配置合并 CSS 里導入的圖片到一個文件中宇植,來減少 HTTP 請求數(shù)。
大致使用如下:
// 加 md5
fis.match('*.{js,css,png}', {
useHash: true
});
// 通過fis3-parse-typescript插件可將 TypeScript 文件轉(zhuǎn)換成 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 很強大埋心,內(nèi)置了許多功能指郁,無需做太多配置就能完成大量工作。
Fis3的優(yōu)點:
集成了各種Web老發(fā)所需的構(gòu)建功能拷呆,配置簡單闲坎,開箱即用。其缺點是目前官方已經(jīng)不再更新和維護洋腮,不支持最新版本的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
伙狐。在實踐中只使用CommonJS
(Node.js
所采用的格式)會比較有幫助,而讓工具去處理剩下的事情瞬欧。它的優(yōu)勢是你可以發(fā)布到NPM
上來避免重新發(fā)明輪子贷屎。
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
調(diào)用它唉侄,此外有很多轉(zhuǎn)換小工具可以讓你更兼容的使用(比如watchify
提供了一個文件監(jiān)視器幫助你在開發(fā)過程中更加自動化地把文件合并起來),這樣會省下很多精力野建。毋庸置疑属划,一定程度來講恬叹,這是一個很好的解決方案。
- 服務器端NodeJS自帶模塊功能同眯,可以使用require和module.exports構(gòu)建項目
- 隨著項目的增大绽昼,瀏覽器端任務越來越重,依賴關系越來越復雜须蜗,需要使用工具實現(xiàn)模塊化硅确。
- 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
擴展了CommonJs
的require
的想法,比如你想在CoffeeScript
柿估、Sass
大莫、Markdown
或者其他什么代碼中require
你想要的任何代碼的話?那么Webpack
正是做這方面的工作官份。它會通過配置來取出代碼中的依賴只厘,然后把他們通過加載器把代碼兼容地輸出到靜態(tài)資源中。這里是一個Webpack
官網(wǎng) 上的例子:
module.exports = {
entry:'./entry.js',
output:{
path:_dirname,
filename:'bundle.js'
},
module:{
loaders:[
{test:/\.css$/,loader:'style!css'}
]
}
}
Webpack
是一個打包模塊化的JavaScript
的工具舅巷,在Webpack
里一切文件皆模塊羔味,通過loader
轉(zhuǎn)換文件,通過Plugin
注入鉤子钠右,最后輸出由多個模塊組合成的文件赋元。Webpack
專注于構(gòu)建模塊化項目。
其官網(wǎng)的首頁圖很形象的展示了 Webpack 的定義飒房,如下圖:
一切文件搁凸,如JavaScript、CSS狠毯、SCSS护糖、圖片、模板嚼松,對于Webpack 來說都是一個個模塊嫡良,這樣的好處是能清晰地描繪各個模塊之間的依賴關系,以方便Webpack進行組合和打包献酗,經(jīng)過Webpack的處理寝受,最終會輸出瀏覽器能使用的靜態(tài)資源。
Webpack具有很大的靈活性罕偎,能配置處理文件的方式很澄,使用方法大致如下:
module.exports = {
// 所有模塊的入口,webpack從入口開始遞歸解析出所有依賴的模塊
entry: './app.js',
output: {
// 將入口所依賴的所有模塊打包成一個文件 bundle.js 輸出
filename: 'bundle.js'
}
}
Webpack的優(yōu)點是:
- 專注于處理模塊化的項目,能做到開箱即用甩苛、一步到位蹂楣;
- 可通過 Plugin 擴展,完整好用又不失靈活性浪藻;
- 使用場景不局限于Web開發(fā)捐迫;
- 社區(qū)龐大活躍,經(jīng)常引入緊跟時代發(fā)展的新特性爱葵,能為大多數(shù)場景找到已有的開源擴展施戴;
- 良好的開發(fā)體驗;
Webpack的缺點是:
只能用于采用模塊化開發(fā)的項目萌丈。
Rollup:
Rollup
是一個和Webpack
很類似但專注于ES6
的模塊打包工具赞哗。它的亮點在于,針對ES6
源碼進行Tree Shaking
,以去除那些已經(jīng)被定義但沒被使用的代碼并進行Scope Hoisting
辆雾,以減少輸出文件的大小和提升運行性能肪笋。然而Rollup
的這些亮點隨后就被Webpack
模仿和實現(xiàn)了。
Parcel:
Parcel
是 最近新起的Web
應用打包工具度迂,適用于經(jīng)驗不同的開發(fā)者藤乙。它利用多核處理提供了極快的速度,并且不需要任何配置惭墓。
Parcel的優(yōu)點:
- 極速打包坛梁。Parcel 使用 worker 進程去啟用多核編譯。同時有文件系統(tǒng)緩存腊凶,即使在重啟構(gòu)建后也能快速再編譯划咐。
- 開箱即用。對 JS, CSS, HTML, 文件 及更多的支持钧萍,而且不需要插件褐缠。
- 自動轉(zhuǎn)換。如若有需要风瘦,Babel, PostCSS, 和PostHTML甚至 node_modules 包會被用于自動轉(zhuǎn)換代碼队魏。
- 熱模塊替換。Parcel 無需配置弛秋,在開發(fā)環(huán)境的時候會自動在瀏覽器內(nèi)隨著你的代碼更改而去更新模塊器躏。
- 友好的錯誤日志。當遇到錯誤時蟹略,Parcel 會輸出 語法高亮的代碼片段,幫助你定位問題遏佣。
缺點:
- 不支持SourceMap:在開發(fā)模式下挖炬,Parcel也不會輸出SourceMap,目前只能去調(diào)試可讀性極低的代碼状婶;
- 不支持剔除無效代碼(TreeShaking):很多時候我們只用到了庫中的一個函數(shù)意敛,結(jié)果Parcel把整個庫都打包了進來馅巷;
- 一些依賴會讓Parcel出錯:當你的項目依賴了一些Npm上的模塊時,有些Npm模塊會讓Parcel運行錯誤草姻;
- Parcel需要為零配置付出代價钓猬。零配置其實是把各種常見的場景做為默認值來實現(xiàn)的,
這雖然能節(jié)省很多工作量撩独,快速上手敞曹,但這同時會帶來一些問題:
- 不守規(guī)矩的node_module:有些依賴的庫在發(fā)布到Npm上時可能不小心把.babelrcpostcss.config.js tsconfig.json這些配置文件也一起發(fā)布上去了,
- 不靈活的配置:零配置的Parcel關閉了很多配置項综膀,在一些需要的配置的場景下無法改變澳迫。
Parcel
使用場景受限。目前Parcel
只能用來構(gòu)建用于運行在瀏覽器中的網(wǎng)頁剧劝,這也是他的出發(fā)點和專注點橄登。在軟件行業(yè)不可能存在即使用簡單又可以適應各種場景的方案,就算所謂的人工智能也許能解決這個問題讥此,但人工智能不能保證100%的正確性拢锹。
反觀Webpack除了用于構(gòu)建網(wǎng)頁,還可以做:
- 打包發(fā)布到Npm上的庫
- 構(gòu)建Node.js應用(同構(gòu)應用)
- 構(gòu)建Electron應用
- 構(gòu)建離線應用(ServiceWorkers)
3.為什么選擇Webpack
上面介紹的構(gòu)建工具是按照他們的誕生時間排序的萄喳,他們是時代的產(chǎn)物卒稳,側(cè)面反映出 Web 開發(fā)的發(fā)展趨勢
如下所述:
- 在 Npm Scripts 和 Grunt 時代,Web 開發(fā)要做的事情變多取胎,流程復雜展哭,自動化思想被引入,用于簡化流程闻蛀;
- 在 Gulp 時代匪傍,開始出現(xiàn)一些新語言用于提高開發(fā)效率,流程處理思想的出現(xiàn)是為了簡化文件轉(zhuǎn)換的流程觉痛,例如將ES6轉(zhuǎn)換為ES5役衡;
- 在Webpack時代,由于單頁應用的流行薪棒,網(wǎng)頁的功能和實現(xiàn)代碼變的復雜手蝎、龐大,Web開發(fā)向模塊化改進俐芯。
這些構(gòu)建工具都有各自的定位和專注點棵介,它們之間既可以單獨完成任務,也可以互相搭配起來彌補各自的不足吧史。在了解這些常見的構(gòu)建工具后邮辽,我們需要根據(jù)自己的需求去判斷應該如何進行選擇和搭配它們才能更好的滿足自己的需求。
經(jīng)過多年的額發(fā)展,Webpack 已經(jīng)成為構(gòu)建工具中的首選吨述,這是因為:
- 大多數(shù)團隊在開發(fā)新項目時會采用緊跟時代的技術岩睁,這些技術幾乎都會采用“模塊化+新語言+新框架”,Webpack可以為這些新項目提供一站式的解決方案揣云;
- Webpack有良好的生態(tài)和維護團隊捕儒,能提供良好的開發(fā)體驗并保證質(zhì)量;
- Webpack 被全世界大量的Web開發(fā)者使用和驗證邓夕,能找到各個層面所需要的教程和經(jīng)驗分享刘莹。
4.Gulp和Webpack的區(qū)別
常有人拿
gulp
與webpack
來比較,知道這兩個構(gòu)建工具功能上有重疊的地方翎迁,可單用栋猖,也可一起用,但本質(zhì)的區(qū)別就沒有那么清晰汪榔。
Gulp
強調(diào)的是前端開發(fā)的工作流程蒲拉,我們可以通過配置一系列的task
,定義task
處理的事務(例如文件壓縮合并痴腌、雪碧圖雌团、啟動server
、版本控制等)士聪,然后定義執(zhí)行順序锦援,來讓gulp
執(zhí)行這些task
,從而構(gòu)建項目的整個前端開發(fā)流程剥悟。 簡單說就一個Task Runner
灵寺,就是用來跑一個一個任務的。
Gulp
沒發(fā)解決的是js module
的問題区岗,是你寫代碼時候如何組織代碼結(jié)構(gòu)的問題略板。
Webpack
是一個前端模塊化方案,更側(cè)重模塊打包慈缔,我們可以把開發(fā)中的所有資源(圖片叮称、js文件、css文件等)都看成模塊藐鹤,通過loader
(加載器)和plugins
(插件)對資源進行處理瓤檐,打包成符合生產(chǎn)環(huán)境部署的前端資源。
相同點: 文件合并與壓縮(css,js)娱节,sass/less預編譯挠蛉,啟動server,版本控制肄满。
不同點: 雖然都是前端自動化構(gòu)建工具碌秸,但看他們的定位就知道不是對等的绍移。
gulp嚴格上講悄窃,模塊化不是他強調(diào)的東西讥电,他旨在規(guī)范前端開發(fā)流程。
webpack更是明顯強調(diào)模塊化開發(fā)轧抗,而那些文件壓縮合并恩敌、預處理等功能,不過是他附帶的功能横媚。