把Webpack放在歷史情景下能更好的理解它。這能讓你知道它實現了多么強大的功能。過去,把一些腳本組合到一起就足夠了。如今時代變了,現在分發你的JavaScript代碼是一個復雜的過程。
這個問題隨著單頁應用的增加而升級。他們依靠很多沉重的庫。最后你想做的是一次性加載它們。這是一個好主意,Webpack能與這些解決方案一起工作。
隨著Node.js和[npm](https://www.npmjs.com/)的流行,Node.js的包管理工具,提供了更多的上下文。在npm之前,管理依賴是很困難的。現在npm在前端開發很流行,管理依賴的解決方案已經改變。現在我有許多棒棒噠的方式來管理我們項目的依賴。
歷史告訴我們有許多的構建系統,[Make](https://en.wikipedia.org/wiki/Make_%28software%29)也許是最出名的一個,而且任然是一個可行方案。在前端的世界[Grunt](http://gruntjs.com/)和[Gulp](http://gulpjs.com/)已經特別的流行了。通過npm實現的插件機制使它們都很強大。
[Browserify](http://browserify.org/)領先了一步。它基于npm打包提供了非常強大的功能。你可以用很多小工具補充它。這是與Webpack的實現是一個很好的對比.
[JSPM](http://jspm.io/)走在了前面,它推動了包管理直接到了瀏覽器。它建立在[System.js](https://github.com/systemjs/systemjs)之上,System.js是一個動態的模塊加載器。不像Browserify和Webpack,它跳過了在開發階段把代碼打包到一起的步驟。然而你也可以產生一個生產環境的包來使用它。Glen Maddern在它的[關于JSPM的視頻](https://www.youtube.com/watch?t=33&v=iukBMY4apvI)里有詳細講述。
##Make
你可以說Make讓你回到了童年時代。它首次發布時1977年。盡管它是一個老工具,但卻很有意義。Make允許你寫一些單獨的任務來達到不同的目的。在下面的例子中,你將有幾個單獨的任務來創建一個生產環境構建,壓縮你的JavaScript和運行測試。你可以在許多其他工具里看到同樣的主意。
雖然Make通常在C項目里使用,但它沒有被束縛在里面。James Coglan有關于[如何在JavaScript使用Make](https://blog.jcoglan.com/2014/02/05/building-javascript-projects-with-make/)的詳細講解.思考一些下面由James提供的簡短代碼。
```
PATH := node_modules/.bin:$(PATH)
SHELL := /bin/bash
source_files := $(wildcard lib/*.coffee)
build_files := $(source_files:%.coffee=build/%.js)
app_bundle := build/app.js
spec_coffee := $(wildcard spec/*.coffee)
spec_js := $(spec_coffee:%.coffee=build/%.js)
libraries := vendor/jquery.js
.PHONY: all clean test
all: $(app_bundle)
build/%.js: %.coffee
coffee -co $(dir $@) $<
$(app_bundle): $(libraries) $(build_files)
uglifyjs -cmo $@ $^
test: $(app_bundle) $(spec_js)
phantomjs phantom.js
clean:
rm -rf build
```
使用Make來需要使用Make的特殊語法和終端命令來構建任務。這可方式可以很容易和Webpack一起工作。
##Grunt

Grunt在Gulp之前是主流構建工具。它的插件構架促使了它占領的市場。同時這也是它致命的弱點。從經驗來看,你不希望維護一個300多行的`Gruntfile`。下面是一個[Grunt文檔](http://gruntjs.com/sample-gruntfile)里的一個例子。
```
module.exports = function(grunt) {
grunt.initConfig({
jshint: {
files: ['Gruntfile.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('default', ['jshint']);
};
```
在這個列子中,我們定義了兩個和*jshint*有關的任務。jshint是一個代碼規范工具,幫助你發現源代碼里可能存在的語法和使用不規范問題。我們有一個獨立運行jshint的任務,同時又有一個監視任務。當我們運行Grunt時,在我們編輯和保存源代碼時就能實時看到警告信息。
在實踐中,你可能為了各種各樣的目的需要很多小的任務。比如打包一個項目。這個列子向我們展示了任務是如何構建的。Grunt非常重要的一點是為我們隱藏了很多細節。當走的太遠時會是一個問題。很難明白這些配置下到底發生了什么。
*提醒:grunt-webpack插件允許你在Grunt環境中使用Webpack。你可以把這個重任交給Webpack。*
##Gulp

Gulp使用了不同實現方式。用真正的代碼替代了對每個插件的配置。Gulp構建在可靠和管道之上。如果你熟悉Unix,這是相同的思想。你會很熟悉數據源,過濾器,水槽(sinks)。
文件就是數據源,過濾器在數據源上執行操作(比如轉換JavaScript),最后結果被送到水槽(比如,你的發布目錄)。這里有一個`Gulpfile`讓你對這種實現更多認識,這個文件來著這個項目的README。下面刪減了部分。
```
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 = {
scripts: ['client/js/**/*.coffee', '!client/external/**/*.coffee']
};
// Not all tasks need to use streams
// A gulpfile is just another node program and you can use all packages available on npm
gulp.task('clean', function(cb) {
// You can use multiple globbing patterns as you would with `gulp.src`
del(['build'], cb);
});
gulp.task('scripts', ['clean'], function() {
// Minify and copy all JavaScript (except vendor scripts)
// with sourcemaps all the way down
return gulp.src(paths.scripts)
.pipe(sourcemaps.init())
.pipe(coffee())
.pipe(uglify())
.pipe(concat('all.min.js'))
.pipe(sourcemaps.write())
.pipe(gulp.dest('build/js'));
});
// Rerun the task when a file changes
gulp.task('watch', function() {
gulp.watch(paths.scripts, ['scripts']);
});
// The default task (called when you run `gulp` from cli)
gulp.task('default', ['watch', 'scripts']);
```
配置文件全變成代碼了,當你遇到問題時,你可以馬上修改(hack)它。你可以包裝現成的Node.js模塊作為Gulp插件。和Grunt對比,你比較明白這到底發生了什么。不過你任然需要為臨時的任務寫很多樣板代碼,這也就是為什么新的工具會出現。
[gulp-webpack](https://www.npmjs.com/package/gulp-webpack)能讓你在Gulp環境中使用Webpack
##Browserify

處理JavaScript的模塊一直以來都是一個問題。知道ES6,這門語言都沒有模塊的概覽。因此在90年代進入瀏覽器環境時,我們就卡主了。很多解決方案被提出來,包括[AMD](http://requirejs.org/docs/whyamd.html)
在實踐中,使用CommonJS是很實用的。它是Node.js的格式,采用同步加載的方式。它的優點時可以利用npm而不必重復造輪子。
[Browserify](http://browserify.org/)解決了這個問題,它提供了把CommonJS打包到一起的方法。你可以把它和Gulp集成。這里有一些小的轉換工具能讓你超越基本的使用,比如[watchify](https://www.npmjs.com/package/watchify)提供了一個文件監聽器,能夠在你開發期間進行打包。毫無疑問,這回節省一些努力,并在某種程度上這是一個不錯的方案。
Browserify的生態系統由許許多多的小模塊組成。在這方面,它始終堅持Unix的哲學。Browserify比Webpack更易用,選擇使用它也是非常好的。
##Webpack

你可能會說Webpack(或者webpack)比起Browerify實現的更集成一些。你獲得了更多的開箱即用。Webpack擴展了`require`,允許你使用加載器自定義它的行為。你可以通過這個機制加載任意的內容。也適用于css文件(`@import`)。Webpack為任務也提供了插件,比如壓縮,本地化,熱加載等等。
這一切都基于配置。這里有一個例子,改編自[Webpack的官方文檔](http://webpack.github.io/docs/tutorials/getting-started/)。
###webpack.config.js
```
var webpack = require('webpack');
module.exports = {
entry: './entry.js',
output: {
path: __dirname,
filename: 'bundle.js'
},
module: {
loaders: [
{
test: /\.css$/,
loaders: ['style', 'css']
}
]
},
plugins: [
new webpack.optimize.UglifyJsPlugin()
]
};
```
上面的配置是使用JavaScript寫的,且具有很好的擴展性。只要是JavaScript,Webpack就能很好的和它在一起。
有時,配置文件會使Webpack看起來有一些不透明。很難理解其中發生了什么。在復雜的項目中尤其明顯。我和Christian Alfoni 完成了[Webpack手冊](https://christianalfoni.github.io/react-webpack-cookbook/),里面對具體的問題有更詳細的描述。
#JSPM

使用JSPM會與前面介紹的工具不同。它自帶了一些命令行工具來安裝新包到項目里,和創建線上壓縮包等。它支持[System plugins](https://github.com/systemjs/systemjs#plugins),能允許在你的項目里加載各種格式的js包。
鑒于JSPM是一個年輕的項目,它肯定有很多坑現在。如果你是一個冒險者,值得去試一試。就像你現在知道的那樣,在前端開發里工具更新的很快JSPM就是一個有價值的競爭者。
#為什么使用Webpack?
為什么你會使用Webpack代替Gulp或者Grunt?這不是非此即彼的問題。Webpack不僅處理打包的困難問題,還處理其他很多問題。我選擇Webpack是因為它支持模塊熱替換(HMR)。這是react-hot-loader使用的特性。我將在后續像你展示。
你或許已經對LiveReload或者Browsersync很熟悉。當你做出改變時這些工具自動刷新瀏覽器。HMR把這個事情更進了一步。在React的案例中,它允許應用保存自己的狀態。這聽起來簡單,但是在實踐中卻大不一樣。
拋開HMR這個特性。Webpack的打包能力是很廣泛的。它允許你使用各種方法分開打包。你可以在你的應用執行時動態加載。這個延時加載很方便,特別是面對大型程序時。你可以在你需要時才加載依賴。
使用Webpack你可以很容易給包的名字加上hash。這能讓你在客服端改變時將起失效。在理想情況下包分割能讓客服端僅僅重新加載很小一部分代碼。
用其他工具也能完成這些任務,問題是肯定需要做很多工作。在Webpack里主要的就是配置。記住你可以通過livereactload使用HMR,所以這個特性不是只有Webpack才有。
通過集成這些小的特性。Webpack能讓你驚訝并完成很多事情。如果你覺得什么事情缺失了,加載器和插件可以幫助你找到他們。Webpack有一個意味深長的學習曲線,盡管如此,它仍是一個值得學習的的工具,能節省很多時間和精力,在長期看來。
獲得更多的Webpack和其他工具的比較,請翻閱[官方對比](https://webpack.github.io/docs/comparison.html)。
#Webpack支持的模塊格式
Webpack允許你使用不同的模塊格式,不過在帽子(hood)下它們以同樣方式工作。
##CommonJS
如果你使用Node.js,那么你應該很熟悉CommonJS了。下面是簡短的例子:
```
var MyModule = require('./MyModule');
// export at module root
module.exports = function() { ... };
// alternatively, export individual functions
exports.hello = function() {...};
```
##ES6
ES6這個格式我們從1995年就開始等待了。正如你看到那樣,它很像CommonJS并且更簡單。
```
import MyModule from './MyModule.js';
// export at module root
export default function () { ... };
// or export as module function,
// you can have multiple of these per module
export function hello() {...};
```
##AMD
AMD是異步模塊定義格式。作為一種變通方案被發明,它引進了一個`define`包裝器:
```
define(['./MyModule.js'], function (MyModule) {
// export at module root
return function() {};
});
// or
define(['./MyModule.js'], function (MyModule) {
// export as module function
return {
hello: function() {...}
};
});
```
順便提一下,它可能和`require`一起使用,想下面這個樣子:
```
define(['require'], function (require) {
var MyModule = require('./MyModule.js');
return function() {...};
});
```
這些實現方法確實減少了一些混亂。你最終還是要使用一些多余的代碼。現在有了ES6,你應該不會再有理由使用AMD了,除非你真的需要它。
##UMD
UMD,同意模塊定義。需要更上一層樓。這是一個怪物格式,它的目標是讓上面的格式互相兼容。只是讓你看一看,千萬不要寫在代碼里,把這個工作留給工具吧。如果你沒有被嚇到。你可以試一試[官方文檔](https://github.com/umdjs/umd)
Webpack能生成UMD包裝器給你(output.libraryTarget: 'umd')。這對于庫的作者特別有用。當討論npm和庫作者是時我們會回來討論關于這個的細節。
#總結
我希望這個章節能幫助你明白為什么Webpack是一個值得學習的工具。它解決了一個常見的web開發問題。當你使用好它時,它可以節省大量的處理時間。在接下來的章節里我們會更深入的考察Webpack。你將學習開一個簡單的開發配置。我們會用它來開始我們的Kanban應用。
你可以,也許是應該,使用Webpack時和其他一些工具一起使用。它不能解決所有的問題。它能解決打包中困難的問題。它減少了開發中的一些負擔。僅僅使用`package.json, scripts`,和Webpack會帶你走很遠,你馬上就能看到。