webpack是時下十分流行的編譯和打包工具,它提供一種可擴展的loader的方式,簡單的配置,即可以編譯打包各種型的文件,包括js、css、image、font、html,以及各類預編譯語言都不在話下。javascript
在上一節的【入門:十分鐘自動化構建】中咱們講解了如何用gulp去搭建一個工做流。咱們認識到gulp是一個流程管理工具,以單個任務爲基礎單元,組合成爲一套完整的工做流,並且gulp還有不少的以gulp-*
格式命名的工做模塊,用來處理各類資源文件,若是沒有看過上一節內容的同窗,建議先回去看過,再往下閱讀,由於本節內容是跟上一節的知識點緊密聯繫的。css
這一節咱們會講解如何構建具有版本管理能力的項目,什麼是版本管理能力?不是什麼svn或者branch,咱們看這樣一個場景來幫助理解:html
你用上次搭建的工做流開發了一個網站,而後上線。
次日打開發現有bug,心想尼瑪趕忙趁着老闆沒發現修復一下。
改完代碼,打包,發佈,一鼓作氣,完美。
然而十分鐘之後老闆讓你去一趟辦公室,打開頁面跟你說有個bug。
內心一抽,一看!我勒個去,這坑爹的緩存啊。。。java
怎麼去解決這個緩存?,或者說,怎麼保證我上線一個新版本,能夠完徹底全地替代舊版本?這就是版本管理。node
這問題有不少解決方法,包括手動打個戳啊什麼的,像src="a.jpg?201608062315"
,這確實能夠解決,可是若是一次更新的東西不少,你壓根改不過來。react
gulp能幫咱們作這事嗎?能夠,麻煩,有興趣的同窗能夠自行搜索資料。有沒有簡單點的套路?有的,webpack自然支持這一功能。接下來咱們就介紹如何用webpack來搭建這麼一套工做流。webpack
webpack的用法,咱們簡單介紹一下。跟gulp同樣,webpack也是寫好配置文件才能開始工做。git
全局安裝webpackgithub
npm install webpack -g
記得養成好習慣,也本地安裝一下哦web
npm install webpack --save-dev
順帶咱們把接下來要用到的幾個loader一塊兒安裝了:
npm install style-loader css-loader sass-loader swig-loader --save-dev
這裏的*-loader
做用跟gulp-*
差很少,就是一些編譯用的模塊。
緊接着咱們在根目錄下,新建一個webpack.config.js配置文件,咱們直接來看代碼:
var path = require('path') module.exports = { entry: { Index: ['./src/js/index.js'] }, output: { path: path.resolve(__dirname, './dist/static'), publicPath: 'static/', filename: '[name].js' }, resolve: { extensions: ['', '.js', '.scss', '.swig'] }, module: { loaders: [ { test: /\.css$/, loader: 'style!css' }, { test: /\.scss$/, loader: 'style!css!sass' }, { test: /\.swig$/, loader: 'swig' } ] } }
這裏大體分爲四部分的內容:
entry
入口文件,也就是一切工做的起點,你能夠將整個web應用都最終打包成一個js文件,那你只須要定義一個入口,而若是你但願對多個頁面獨立開來,你須要定義多個入口,最終在不一樣的頁面引用不一樣的js。一個entry對應生成一個bundle。
output
定義打包輸出的配置:
dist/static
下的;<script src="index.js"></script>
=> <script src="static/index.js"></script>
;[name].min.js
,最終打包出來文件名就是index.min.js
。resolve
這個配置無關緊要,它是定義一些經常使用的文件拓展名,被定義了的文件格式,在引用的時候能夠不加擴展名,好比我配置了.js
的拓展名以後,在開發中我能夠直接require('./common')
,而不須要require('./common.js')
。
module
最重要的一個部分,咱們在這裏定義針對各類類型文件的loader(加載器/編譯器),能夠看到咱們分別定義了css、scss、swig三種文件對應的loader,多個loader之間用!
隔開,'-loader'能夠省略不寫。注意,編譯的優先次序是從右到左的,好比scss文件是先被sass-loader處理,而後再被css-loader處理,最後再被style-loader處理。
除了這幾點,webpack還有個比較重要的配置項plugins
,這個咱們暫時不介紹,後面會在具體的應用場景裏引入。除此以外,webpack的其餘配置項你們能夠去官網瞭解。
話很少說,咱們仍是取原來的gulp_base項目源碼來作介紹。
- project
|- src // 源文件夾
| |- tpl
| | `- index.swig | |- sass | | `- index.scss | |- js | | `- index.js |- dist // 打包文件夾 `- package.json
webpack的模塊化使咱們能夠很方便地使用commonjs的規範來組織代碼。有關commonjs的內容,你們能夠自行查閱相關文章,這裏不做深刻展開;或者關注我以後的文章,我將會針對模塊化作一些講解。不過這都是後話了。
在這裏咱們只須要知道,咱們經過將頁面劃分紅不少的小模塊,每一個模塊都是單獨的js文件,咱們能夠經過require的方式,去引入一個模塊,進而組織成一個大的web應用。而webpack更甚,在webpack裏,"一切資源皆模塊",無論是圖片仍是css,均可以在js文件中直接require,固然前提是你已經在webpack.config.js的module項裏配置了這類文件的相關loader。
ok,準備就緒,咱們來試着編譯一下
webpack
理論上,咱們若是把js文件放在一個叫作js的文件夾裏,那幾乎百分百肯定這貨就是.js結尾的,擴展名顯得有點多餘。固然若是你硬是要在js文件夾裏放個.jpg的文件我也拿你沒辦法是吧。
我們看看dist目錄裏面是否是有了個static文件夾?文件夾裏是否是有個文件叫作index.js?是否是就成功了?沒成功你找我。
好,咱們試着驗證另一個配置,output.filename
,咱們改一下:
module.exports = { output: { path: path.resolve(__dirname, './dist/static'), publicPath: 'static/', filename: '[name].[chunkhash].js' }, ... }
命令行裏webpack一下:
Hash: 408544aa45e4f298cb49 Version: webpack 1.13.1 Time: 49ms Asset Size Chunks Chunk Names Index.e167a63b2bcf28077701.js 1.53 kB 0 [emitted] Index [0] multi Index 28 bytes {0} [built] [1] ./src/js/index.js 21 bytes {0} [built]
打開dist/static目錄一看,生成了一個Index.e167a63b2bcf28077701.js
文件,chunkhash
是由webpack對index.js這個文件進行 md5 編碼以後獲得的一個戳,這個戳是惟一的,拼接在文件名上,能夠表示這個文件的惟一性,只要index.js裏面的內容被修改一丁點,這個生成的戳就會不同。
這不正是咱們開頭想要的版本管理嗎?太美好了,沒想到這麼容易就解決了這個問題。
有個問題,若是我每修改一點,這個文件名就不同了,那我不就要在html中每一個引用了這個js的地方再修改一下文件名?這根本就沒有減負嘛!
怎麼去解決這個問題?webpack固然提供瞭解決方案,若是咱們用webpack幫咱們打包html,而且自動地去注入這些資源,那問題就解決了。
通常來講webpack就只是處理入口文件,也就是從js去入手,這樣的話怎麼都輪不到html,那怎麼樣才能讓webpack去處理一下html?答案就是咱們以前提過的,plugins!
咱們來修改一下配置:
var path = require('path') var HtmlWebpackPlugin = require('html-webpack-plugin') module.exports = { entry: { Index: ['./src/js/index.js'] }, output: { path: path.resolve(__dirname, './dist/static'), publicPath: 'static/', filename: '[name].[chunkhash].js' }, resolve: { extensions: ['', '.js', '.scss', '.swig'] }, module: { loaders: [ { test: /\.css$/, loader: 'style!css' }, { test: /\.scss$/, loader: 'style!css!sass' }, { test: /\.swig$/, loader: 'swig' } ] }, plugins: [ new HtmlWebpackPlugin({ chunks: ['Index'], filename: '../index.html', // 留意這裏,這裏的路徑是相對來path配置的 template: './src/tpl/index', inject: true }) ] }
請留意兩點修改的地方,一個是var HtmlWebpackPlugin = require('html-webpack-plugin')
,一個是plugins
的配置內容。
webpack容許以插件的方式去擴展功能。html-webpack-plugin是webpack提供的一個簡化html處理的插件,其實本意就是爲了解決這個引用bundle的問題,順便提供一些像壓縮啊注入、變量啊什麼的功能。具體其餘功能你們能夠去官網看文檔。
使用前記得先install啊!
咱們再次編譯一下,而後查看一下dist文件夾,是否是多了個index.html?並且index.js也被編譯後自動注入了頁面
編譯前:
// index.swig
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>index</title> </head> <body> hello, world! </body> </html>
編譯後:
// index.html
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>index</title> </head> <body> hello, world! <script type="text/javascript" src="static/Index.e167a63b2bcf28077701.js"></script></body> </html>
done!
看着這html,有沒有感受少了點什麼?嗯,少了css,咱們來看看css怎麼注入。
其實css更簡單,咱們已經說過了:
在webpack裏,一切資源皆模塊。
咱們只須要在index.js中直接require就能夠了:
// index.js require('../sass/index') console.log('index')
編譯一下,打開index.html:
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>index</title> </head> <body> hello, world! <script type="text/javascript" src="static/index.c04c40a250e85ea100ab.js"></script></body> </html>
沒看到有引入css啊?你個騙紙!
彆着急,試着雙擊index.html文件,在瀏覽器打開試試?是否是有樣式了!神奇,樣式是哪裏來的?原來css都被打包進js裏面了!
雖說目的是達到了,可是總以爲有點很差,咱們仍是習慣外聯css啦,能不能實現?能夠!甩你一個plugin!extract-text-webpack-plugin:
var ExtractTextPlugin = require('extract-text-webpack-plugin') module.exports = { module: { loaders: [ { test: /\.css$/, loader: ExtractTextPlugin.extract('style', ['css']) }, { test: /\.scss$/, loader: ExtractTextPlugin.extract('style', ['css', 'sass']) }, { test: /\.swig$/, loader: 'swig' } ] }, plugins: [ new ExtractTextPlugin('[name].[chunkhash].css'), ... ], ... }
看着就能明白吧,這裏再也不細講,反正也就是這麼用而已。
編譯一遍,看看index.html:
// index.html
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>index</title> <link href="static/index.d4085060ebf342f7a5a1.css" rel="stylesheet"></head> <body> hello, world! <script type="text/javascript" src="static/index.d4085060ebf342f7a5a1.js"></script></body> </html>
完美!
到這裏,咱們的打包工做算是介紹完了,下面咱們試着搭建開發環境。
咱們在使用gulp的時候,搭建開發環境是藉助browser-sync,而對於webpack來講,咱們有更好的選擇:webpack-dev-server!
webpack-dev-server是webpack官網提供的一款本地開發服務器,能夠爲咱們提供一個適用於在webpack下進行開發的環境。
webpack-dev-server的用法其實很簡單,基於webpack的配置,稍稍補充一些內容就能夠了。webpack-dev-server有兩種使用方式,cli和api,咱們分別作介紹。
在開始以前,咱們須要針對開發環境單首創建一份配置文件,有印象的同窗能夠記得,咱們上一節講過,打包與開發的配置文件有一個不一樣點,打包是以產出而且優化爲目的的,而開發則更側重效率,因此咱們須要把一些壓縮和多餘的優化手段給去掉,減輕開發過程當中的編譯負擔。
咱們新建一份配置文件webpack.dev.config.js:
// webpack.dev.config.js var path = require('path') var HtmlWebpackPlugin = require('html-webpack-plugin') module.exports = { entry: { index: ['./src/js/index.js'] }, output: { path: path.resolve(__dirname, './dist/static') filename: '[name].js' }, resolve: { extensions: ['', '.js', '.scss', '.swig'] }, module: { loaders: [ { test: /\.css$/, loader: 'style!css' }, { test: /\.scss$/, loader: 'style!css!sass' }, { test: /\.swig$/, loader: 'swig' } ] }, plugins: [ new HtmlWebpackPlugin({ chunks: ['index'], filename: '../index.html', template: './src/tpl/index', inject: true }) ] }
咱們把extract-text-webpack-plugin
去掉,再把爲了不緩存而在文件名加戳的方式也去掉,這樣就差很少了。
值得注意的是,咱們把path和publicPath兩項都去掉了,這裏是爲了配合下面的cli方案使用,咱們接下去看就會明白。
cli
cli的方式要求咱們全局安裝webpack-dev-server:
npm install webpack-dev-server -g
而後咱們直接在命令行裏輸入:
webpack-dev-server --config ./webpack.dev.config.js --display-error-details --devtool eval --content-base ./ --inline --progress --colors --host 0.0.0.0
天了嚕,這也太長了。這長長的一串命令,都是針對webpack-dev-server初始化的配置,固然,webpack-dev-server的配置也是能夠在webpack.dev.config.js裏定義的,只是咱們這裏主要展現cli的用法,因此就直接這樣輸入了,具體各個參數是什麼功能,咱們能夠沒必要所有了解,由於這裏並無特別關鍵的配置項,大部分都是對終端裏輸出形式的一些設置,咱們挑重點的來講:
熱更新的做用是使得咱們在修改並保存代碼以後,在不刷新瀏覽器的狀況下,自動更新瀏覽器對應部分的代碼!注意,此時頁面是不會刷新的,但變化馬上就能夠反映出來!這一項咱們並無設置,爲何?由於這裏有一點小問題,熱更新的功能目前並不能對.html(或者靜態模板)這樣的文件起做用,也就是說,若是咱們修改的是html,它將會不起任何做用,除非咱們手動刷新,才能看到效果。這就有點撿芝麻丟西瓜了,因此咱們選擇了放棄它。
固然,若是是像react這樣的主要由js來構建的項目,那咱們能夠毫無顧忌地使用熱更新。
咱們在配置文件中去掉了publicPath的設置,其實你能夠試着設置一下,你會發現publicPath是會影響到所有的資源包括index.html,使得咱們全部的文件都被放在服務器的static
目錄下,此時只能經過/static
來訪問到index.html,因此咱們選擇去掉這兩項設置,講文件統一編譯打包到根目錄下。
其餘配置項有興趣的同窗能夠到官網的文檔裏瞭解。
api
cli的方式雖然很方便很簡單,可是若是要使用更多的定製化,api會更靈活一些。
注意,這一部份內容十分重要,由於咱們以後乃至下一節的文章裏,都是基於api來配置工做流的,請務必掌握這一節的內容,若是你還有再深刻研究下去的想法的話,不要知足於cli的便捷方式而對這一部份內容粗略帶過。
使用api的方式,意味着咱們須要本身來手動定義一個開發服務器,而且補充相關配置項,爲了使配置與實現邏輯分開,咱們仍是使用原來那份配置文件webpack.dev.config.js,可是新建一份js文件server.js來編寫這個服務器邏輯。
var WebpackDevServer = require('webpack-dev-server') var webpack = require('webpack') var config = require('./webpack.dev.config.js') var path = require('path') var compiler = webpack(config) var server = new WebpackDevServer(compiler, { stats: { colors: true, chunks: false } }) server.listen(8080, 'localhost', function() {})
ok,咱們先來試着跑一下:
node ./server.js
回車,成功啦!並且運行效果貌似跟cli的同樣。
這裏的內容實在沒什麼可講的,咱們首先使用webpack傳入配置內容config,獲得一個編譯的實例,以後咱們再建立一個webpackDevServer的實例(本地服務器),來跑這份編譯實例,服務器監聽8080端口,因此咱們在瀏覽器中輸入http://localhost:8080就能夠訪問了,搞定!
到這裏就ok了嗎?其實尚未,咱們試着修改index.js,而後保存,看看瀏覽器。
瀏覽器沒有反應,也就是說,這裏尚未實現自動刷新瀏覽器的功能,咱們配置少了一些東西。
好,那咱們接着來。
webpackDevServer的官方文檔裏其實已經說得很清楚了:
Similar to the inline mode the user must make changes to the webpack configuration.
Three changes are needed:
- add an entry point to the webpack configuration:
webpack/hot/dev-server
.- add the
new webpack.HotModuleReplacementPlugin()
to the webpack configuration.- add
hot: true
to the webpack-dev-server configuration to enable HMR on the server.
這裏就算看不懂意思也能猜獲得吧~
webpack/hot/dev-server
new webpack.HotModuleReplacementPlugin()
hot: true
二話不說就開搞,咱們按照指示修改獲得最終的server.js長這樣:
var WebpackDevServer = require('webpack-dev-server') var webpack = require('webpack') var config = require('./webpack.dev.config.js') var path = require('path') for (var key in config.entry) { var entry = config.entry[key] entry.unshift('webpack-dev-server/client?http://localhost:8080', 'webpack/hot/dev-server') } config.plugins.push(new webpack.HotModuleReplacementPlugin()) var compiler = webpack(config) var server = new WebpackDevServer(compiler, { hot: true, stats: { colors: true, chunks: false } }) server.listen(8080, 'localhost', function() {})
都是挺簡單的東西沒啥看不懂的,咱就不細講了,直接跑起來!
修改一下index.js,保存!呀,效果出來了,挺好;
修改一下index.scss,呀,效果出來了,倍兒棒;
修改一下index.swig,保存!呀,你咋就沒反應了啊。。。
還記得咱們用cli來搭建的時候遇到的相似狀況嗎?WebpackDevServer的Hot Module Replacement是不支持html模板的,因此這裏也同樣,怎麼解決?
仍是像原來,咱們把hot: true
的配置項去掉就行了,ok,運行一遍,修改,保存,done!
gulpfile.js文件咱們一看就明白,邏輯清晰井井有條,webpack的配置文件則須要細細咀嚼。
相比gulp,webpack構建工做流是否是會複雜些?固然,咱們只是對比來講,事實上若是隻是從操做來看,也並無多複雜。我描述一個感覺,看你們是否是有共鳴:
gulp是一套流程管理工具,專門管理一個個的任務,同時結合一些編譯模塊來處理各類資源文件;
webpack則是一個大而全的編譯器,主要用於各類資源文件的編譯和打包工做,由此爲中心衍生出了一系列周邊工具,好比開發工具,插件等等。
事實上,webpackDevServer編譯的文件是存放在內存中的,操做起來速度很是的快,整個的項目編譯完成幾乎就是那麼一個刷新的瞬間,而gulp則仍是硬盤上的操做,速度要慢幾個量級,咱們沒有感覺到明顯的差別,那是由於項目還不夠大。
有沒有留意到,咱們這一節並無像上一節那樣介紹到ftp工具?之因此沒有做介紹,是由於webpack並無提供這樣的插件或者模塊去完成這樣的工做,事實上也不該該有,由於webpack自己是一個資源處理器,與項目的上傳部署沒有太多關係。咱們須要藉助其餘的npm包,用一種毫無聯繫的方式去組合這兩個功能,咱們可能須要編寫一個ftp.js,不知你們是否還記得咱們上一節當中的npm run build && npm run upload
命令?差很少就是這樣。這個ftp.js就跟webpack的關係至關微弱了,不像咱們寫gulp那樣,擴展一個功能的時候,只須要寫多一個task。
這裏概念很是抽象,我也不能完完整整地傳達,不知道你們理解多少。老方法,我仍是舉個栗子來講明:
webpack就比如一套很好用的螺絲刀,基本全部的機器的維修工做都能應付。可是若是須要完整一點的工程,咱們可能還須要大錘啊電鑽啊什麼亂七八糟的。可是東西一過來可能無法整理到一塊兒,只能拿個塑料袋粗略地裝起來;
gulp比如一個工具箱,裏面也有幾件比較簡單的螺絲刀,若是有其餘的工具,來了就往裏面放,妥妥的。
嗯,又是一個不怎麼貼切的栗子,湊合着用吧憋唧唧歪歪的。
咱們先理解到這裏,進一步的對比,咱們會在教程最後的章節裏呈現一次比較細緻的說明。
後話:咱們能夠把webpack這把好用的螺絲刀放gulp這個工具箱裏嗎?答案固然是yes!帥(美)的人已經動手了,醜的還在賣萌。下一節【強化:構建易用高可擴展性的工做流】咱們將爲你們講解如何使用gulp結合webpack,各取所長地搭建一套簡單易用、高可擴展性的工做流。
本次演示項目的git地址:webpack_base