記得2004年的時候,互聯網開發就是作網頁,那時也沒有前端和後端的區分,有時一個網站就是一些純靜態的html,經過連接組織在一塊兒。用過Dreamweaver的都知道,作網頁就像用word編輯文檔同樣。一個html頁面,夾雜着css,javascript是再常見不過的事了。javascript
隨着前端的不斷髮展,特別是單頁應用的興起,這種所見即所得的IDE工具,就漸漸地退出了前端的主流。一個應用,一般只有一個靜態頁面(index.html),甚至這個頁面的body只有一個或少數幾個div組成。這時有大量的css和javascript代碼須要編寫。如何組織他們,就是如今前端所面臨和要解決的問題。css
一些很好的前端框架(像angularjs,React,Vue)能夠幫咱們如何合理的組織代碼,保持代碼的可維護性和擴展性。這在開發階段是頗有用的,但是要把應用發佈到線上的時候,須要把代碼進行合併壓縮,以減少代碼體積,和文件數量,人爲的對代碼進行醜化。因而就有了grunt,gulp,webpack等前端工程化打包工具。html
使用webpack以前,須要安裝node.js,而後經過npm 安裝webpack.具體的安裝過程移步官網。本着從入門到精通的順序,先來看一個最簡單的應用。前端
場景一:vue
在demo1目錄下,有兩個文件,app.js,cats.js,須要把它們合併成一個bundle.js文件. demo01java
cats.js:node
var cats = ['dave', 'henry', 'martha']; module.exports = cats;
app.js:react
cats = require('./cats.js'); console.log(cats);
若是是全局安裝的webpack,那麼直接在命令行窗口中輸入webpack app.js bundle.js就能夠了:jquery
要獲得壓縮版的也很容易,在後面追加一個-p參數:webpack
bundle.js由原來的1.58kb縮小到304b.
若是每改一次代碼就要輸一次命令,就太沒意思了,這時就須要追加一個" -w " 參數 (watch) 監視代碼的改動。
webpack app.js bundle.js -p -w
注意:若是是clone的代碼,試驗時,請移除目錄下的webpack.config.js文件。
雖然簡單,可是這裏有一個重要的概念要說一下:官方文檔中把app.js這個文件稱爲「entry point」,即「入口」。表明着webpack從哪開始。webpack會順着這個入口文件自動尋找裏邊所依賴的文件,好比demo01中的cats.js會自動被載入。而bundle.js 是咱們指定打包以後輸出的文件名,默認的輸出目錄就是命令運行時所在的目錄,也能夠在指定輸出目錄,如./dist/bundle.js ,這樣webpack就會自動建立dist目錄,而後把bundle.js寫在dist目錄下。因爲app.js這個入口文件是純js,webpack直接就能夠支持,若是是其它類型的文件,好比css,就須要用到"loader",即「加載器」,後面會有詳細介紹。
除了直接用webpack命令指定入口文件打包以外,還能夠經過配置webpack.config.js文件實現一樣的功能:
webpack.config.js :
//最簡單的webpack配置 module.exports = { entry: './app.js', //入口文件地址 output: { filename: 'bundle.js', //打包後的文件名 } };
經過配置webpack.config.js以後,在命令行下只須要簡單的輸入webpack就能夠了。若是是這麼簡單的應用,顯然體現不出webpack.config.js存在的價值。一般咱們的網站都會有多個頁面,好比index,home,about等等,每一個頁面都是一個獨立的入口,因而就產生了多入口的狀況,下面就看看多入口的狀況下,webpack怎麼輸出不一樣的打包文件。demo02
//webpack.config.js //多入口示例 module.exports = { entry: { bundle1: './main1.js', //入口1 bundle2: './main2.js'//入口2 }, output: { filename: '[name].js' // [name]是一個變量,會自動替換成entry的key } };
和demo01相比,此次的入口(entry)是一個對象, 用鍵值對的形式指定了多個入口文件,輸出的文件名用了變量表示。事實上,入口文件的值還能夠是數組。如:
//webpack.config.js //多入口示例 module.exports = { entry: { bundle1: ['./main1.js'], //入口1 bundle2: ['./main2.js']//入口2 }, output: { filename: '[name].js' // [name]是一個變量,會自動替換成entry的key } };
這種用法,對於入口文件須要指定多種類型的文件時比較有用。好比['./main1.js','./main1.css'],後面用到再細講。小結一下:對於entry一共展現了三種形式:
1. entry:'app.js' 直接寫入口文件
2. entry:{bundle:'./main1.js'} 對象形式
3. entry:{bundle:['./main1.js']} 對象中的值用數組表示
接下來的demo03將展現webpack在jsx, es6 中的用法。這一節內容會稍稍有點多。首先是package.json文件,它不是webpack的組成部分,可是常和webpack項目出雙入對,先看一下它的大概模樣:
{ "name": "demo01", "version": "1.0.0", "description": "sample to use webpack", "main": "index.html", "scripts": { "test": "echo \"Error: no test specified\" && exit 1", "start": "webpack" }, "keywords": [ "webpack" ], "author": "frog", "license": "MIT",
」dependencies":{}, "devDependencies": { "babel-core": "^6.20.0", "babel-loader": "^6.2.10", "babel-preset-es2015": "^6.18.0", "babel-preset-react": "^6.16.0", "react": "^15.4.1", "react-dom": "^15.4.1", "webpack": "^1.13.0" } }
關於這個文件的更多介紹,請移步官方內容 這裏我只重點介紹一下如下內個內容:
1. scripts 命令行腳本經過key:value的方式描述。key是腳本名,value是腳本執行的內容,經過在命令行中輸入npm run 腳本名 就能夠執行。這一塊的內容是實際開發中很實用的,這裏不詳情展開,參考地址
常見的腳本名有:npm run start , npm run test 。 內置的腳本名(好比start),能夠省略run。
2. devDependencies 開發依賴,相應的還有一個dependencies(能夠理解爲生產環境依賴)
經過npm install 包名 --save-dev (保存到devDependencies),或 --save 保存到(dependencies)
package.json是用來配合包的管理和發佈用的,若是你不想發佈這個項目,彷佛以上內容對項目開發並無什麼好處,可是做爲團隊協做,它能夠方便本身和同事快速搭建項目,管理項目中用到的第三方包。
下面回到webpack.config.js這個文件來。因爲jsx是react專用的語法,超出了js的語法範圍,要想加載jsx文件,須要藉助一個loader(加載器), 不一樣類型的文件有不一樣的加載器,好比jsx,es6要用到babel-loader
加載css要用到css-loader,加載html要用到html-loader等等. 下面是具體的用法:
module.exports = { entry: './main.jsx', output: { filename: 'bundle.js' }, module: { loaders:[ { test: /\.js[x]?$/, exclude: /node_modules/, loader: 'babel-loader', query:{ presets:['react','es2015'] } }, ] } };
全部的loader都放在module下面的loaders裏邊.一般有如下內容:
1. test:是對該類文件的正則表達式,用來判斷採用這個loader的條件。
2. exclude是排除的目錄,好比node_modules中的文件,一般都是編譯好的js,能夠直接加載,所以爲了優化打包速度,能夠排除。做爲優化手段它不是必須的。
3. loader: 加載器的名稱,每個加載器都有屬於它本身的用法,具體要參考官方說明。
4. query: 傳遞給加載器的附加參數或配置信息,有些也能夠經過在根目錄下生成特殊的文件來單獨配置,好比.babelrc
這裏配置好,還不能用,須要安裝對應的加載器到項目中來,安裝方式很簡單,經過命令行,輸入npm install 加載器的名稱 --save-dev 或 --save
加--save或--save-dev的目的是爲了把該插件記錄到package.json中去,方便經過npm install的時候自動安裝。
經過npm3.0+版本安裝的時候,它不會自動安裝依賴,須要手動去安裝,好比安裝這個babel-loader的時候,它提示要安裝babel-core和webpack,依次安裝便可。demo03比較激進,直接用了jsx和es6的語法,因此要安裝的插件比較多,但這也是實際開發中常常用到的。
"devDependencies": { "babel-core": "^6.20.0", "babel-loader": "^6.2.10", "babel-preset-es2015": "^6.18.0",//es6轉普通js用 "babel-preset-react": "^6.16.0", //解析jsx用 "react": "^15.4.1", "react-dom": "^15.4.1", "webpack": "^1.13.0" }
因爲咱們在package.json的script中加了一個start腳本,因此此次,我不打算老套的用法,此次來點新鮮的嘗試。直接運行npm start,看看是否大力出奇跡。
這和直接運行webpack是同樣的結果,可是顯得更高大上一些。若是你一半會不覺用不到react或es6這麼新潮的東西,那就請忽略前面的內容,下面看一點更加簡單更加經常使用的加載器
demo04 css-loader 樣式加載器
module.exports = { entry: './main.js', output: { filename: 'bundle.js' }, module: { loaders:[ { test: /\.css$/, loader: 'style-loader!css-loader' }, ] } };
這裏有兩個要注意的地方:
1。 對於有多個加載器串聯的狀況,webpack,它是從右向左依賴加載的,也就是說先用css-loader,再用style-loader.
2. 爲何會有一個style-loader, 由於webpack默認是把css文件插在html文件內,經過style標籤加載樣式的。因此須要用style-loader這個加載器。
若是想要把css用文件的形式link到html中,也是能夠的,後面會講到。
因爲咱們用了css加載器,因此入口文件其實也能夠直接寫成:entry:'./app.css'; 效果是同樣的。這就體現了,入口文件,不必定要是js格式,只要有對應的加載器,就能夠直接在入口中使用,甚至多種類型混合使用,好比['./app.js','app.css'],都是能夠的。
樣式中,經常會用到圖片,好比background:url('../images/logo.jpg'); 若是沒有指定加載器,就會報錯(you may need an appropriate loader to handle this file type),這時,就須要用到圖片加載器了,不要覺得,只有在入口中用到的文件纔要加載器,只要是在webpack工做期間加載到的文件,只要不是js文件,就須要指定加載器,並在webpack.config.js中正確配置。
module.exports = { entry: './main.js', output: { filename: 'bundle.js' }, module: { loaders:[ { test: /\.(png|jpg)$/, loader: 'url-loader?limit=8192&name=[name][ext]'} ] } };
圖片加載-demo05 示例中用的是url-loader,並非指望的image-loader, 緣由是url-loader就能夠加載圖片字體這些文件了,所以不須要重複造輪子,事實上,url-loader還不是最終的加載器,它只不過是對file-loader的進一步封裝。經過在url-loader後面加?來掛載更多的配置參數,能夠實現定製化的需求,好比對於圖片小於8192字節的圖片,採用base64的方式,直接輸出在css中,能夠減小http請求。對於大這個限制的圖片,經過name指定輸出的文件名,在前面指定路徑也是能夠的。好比/images/[name][ext] ,這裏的[name]和[ext]都是變量的表示,前面有講過,用在這裏,表示用原來輸入時的文件名和擴展名。須要注意的是,這個路徑是參考默認的輸出路徑的來的。若是要指定輸出路徑怎麼處理呢?
請參考如下方法:
1. 經過在webpack.config.js 中指定,output:{path:'./dist',...}
module.exports = { entry: './src/app.js', output: { path: './dist',//新的輸出路徑 filename: 'app.bundle.js' } };
'./'表明項目的當前目錄,一般指根目錄,這是一種相對路徑的表示,也能夠用絕對路徑,經過path.resolve(__dirname,'./')來指定,這時,webpack所生成的js,css文件都會變成./dist目錄下,而對於本例中的圖片,則仍是在./目錄下,
並無把圖片生成在dist目錄下,試試 loader: 'url?publicPath=./dist/'
module.exports = { entry: ['./main.js','./icon.css'], output: { path:'./dist', filename: 'bundle.js' }, module: { loaders:[ { test: /\.(png|jpg)$/, loader: 'url-loader?limit=8192&publicPath=./dist/'}, { test: /\.css$/, loader: 'style-loader!css-loader' }, ] } };
經過指定這個publicPath實現了圖片生成到指定的目錄。一樣的,經過在output中指定這個值也是一樣的做用。
output: { path:'./dist', publicPath:'./dist/', //在這裏指定一樣生效 filename: 'bundle.js' },
這個publicPath本來是用來配置虛擬目錄用的,也就是經過http方式訪問時的路徑,或者經過webpack HMR方式加載時的輸出目錄。在這裏只能算是一種hack用法。說到output,就要提一下文件緩存[hash]的用法:
output: { path:'./dist', publicPath:'./dist/', filename: 'bundle_[hash:8].js' //經過:8截取has值的前8位 },
這個[hash]做用不多被提到,在實際開發中,是很常見的功能,原樣輸出的hash我以爲太長,能夠經過[hash:加數字]的方式進行截取,很方便。
對於webpack.config.js ,前面已經介紹了entry,output,module,下面以代碼醜化爲例,說說plugins,webpack的插件的用法:demo07
var webpack = require('webpack'); var uglifyJsPlugin = webpack.optimize.UglifyJsPlugin; module.exports = { entry: './main.js', output: { filename: 'bundle.js' }, plugins: [ new uglifyJsPlugin({ compress: { warnings: false } }) ] }
plugins:的值是一個數組,全部插件都經過npm install進行安裝,而後在plugins數組中添加對應的插件配置。有三個插件須要提一下:
1. HtmlwebpackPlugin 這個插件能夠把生成的css ,js 插入到html頁中的head,body中,這對於加了hash值的輸出頗有用。這個功能也能夠用gulp的insject插件作。不過既然用webpack了,就暫時忘了gulp吧。在具備類似功能的不一樣的工具之間切換,並非一個好主意。不過html這款插件有一個小小的問題,它對html中的img不會像css中那樣解析。形成dist目錄下的html文件,img下的src報錯。解決辦法是添加html-withimg-loader這個插件。
{ test:/.html$/, loader:'html-withimg-loader?min=false' },
2. CommonsChunkPlugin 提取公共代碼,這個不須要安裝,webpack集成有。
new webpack.optimize.CommonsChunkPlugin({ name:'vendor', filename:'js/vendor.js', chunks:['chunk1','chunk2','chunk3']//不寫爲全部的chunk, }),
chunk (塊), webpack中另外一個很是重要的概念,和entry對應。有三種狀況:
2.1 若是entry經過字符串的方式指定的入口文件,那麼chunk就是指入口文件,好比entry: './app.js'; 那麼能夠確定chunk和'./app.js'一一對應。
2.2 若是是entry:['./app1.js','app2.js']那麼chunk就是這兩個文件之和。
* 以上chunk的[name]就是默認的"main".
2.3 若是是下面這種形式:
entry:{ index:'./index.js', error:'./error.js', vendor:['react','redux'] }
那麼就會產生多個chunk,[name]分別和index,error,vendor對應。
3. ExtractTextPlugin 這個插件就是開頭提到的,從html中分離css的插件。npm install extract-text-webpack-plugin --save-dev
plugins: [ new ExtractTextPlugin("[name].css"), ]
須要注意的是,若是在[name].css前面加了子路徑,如css/[name].css 那麼就要當心樣式中的圖片路徑出錯,特別是在沒有指定publicPah的狀況下。background:url(這個地方的圖片默認是和chunk的輸出路徑同級的,若是指定了publicPath,則以publicPath代替,不存在這個問題),可是因爲咱們人爲的指定了打包後的樣式放在css/目錄下,而圖片默認還在原來的目錄,這就致使css中引用的圖片路徑失效。看下面的例子:
var ExtractTextPlugin = require("extract-text-webpack-plugin"); module.exports = { entry: ['./main.js','./icon.css'], output: { path:'./dist', //publicPath:'./dist/', filename: 'bundle_[hash:8].js' }, module: { loaders:[ { test: /\.(png|jpg)$/, loader: 'url-loader?limit=8192'}, { test: /\.css$/, loader: ExtractTextPlugin.extract('style', 'css') }, ] }, plugins: [ new ExtractTextPlugin("css/[name].css"), ] };
從圖上看到,添了css子目錄以後,樣式中的圖片,仍是原來的路徑,而圖片並不存在css目錄下。
解決辦法,要麼不加子目錄,保持樣式文件和圖片文件始終在同一層級,要麼添加publicPath,至關於使用絕對路徑。因爲這個是在webpack.config.js中配置的,要更換也很容易。
看起來一切都很美好,但是當咱們的html中也用了img標籤的時候,問題就來了,還記得html-withimg-loader這個插件嗎?它際實上也是調用的url-loader,因此,它最終的圖片輸出路徑也樣受publicPath的影響。考慮一下這樣的目錄結構:
樣式文件是位於css子目錄,而html則是和圖片保持同級的。樣式中的圖片須要指定爲"../",而html中的圖片須要指定成"./",這在同一個publicPath中,顯示是衝突的。這時就須要權衡一下,要麼全部的文件都堆在根目錄下,要麼html中的圖片用別的插件進行處理。總之,不能讓這種相沖突的狀況發生。
最後再簡單說一下webpack.config.js 中的 resolve;
resolve: { extensions: ['', '.js', '.vue', '.json'] },
一般咱們都知道經過配置這個屬性下的extensions,能夠省略擴展名,彷佛沒有什麼能夠介紹的,直到有一次我在項目中經過npm install vue --save 安裝了vue ,而後我在代碼中用import Vue from 'vue' 導入了vue。到這一步都是正常的,但是當我打算進一步使用vue的時候,代碼就報錯了,而後去查官方文檔(出錯的時候不要驚慌,大多數狀況都會在官方找到解決的辦法,好比github中的issue等)。官方介紹以下:
「 There are two builds available, the standalone build and the runtime-only build. The difference being that the former includes the template compiler and the latter does not.By default, the NPM package exports the runtime-only build. To use the standalone build, add the following alias to your Webpack config:」
resolve: { alias: { 'vue$': 'vue/dist/vue.common.js' } },
大意是說:vue有兩種構建方式,獨立構建和運行構建。它們的區別在於前者包含模板編譯器然後者不包含。而默認 NPM 包導出的是 運行時 構建。爲了使用獨立構建,要在 webpack 配置中添加下面的別名:添加alias:{'vue$':'vue/dist/vue.common.js'}. 這個別名,一樣適用於jquery,zepto這些庫。
對於vue來講,若是不用別名,也能夠從node_modules/vue/dist/複製vue.common.js到開發目錄下,好比./src/vue下面,而後像普通的js文件同樣引用, import vue from './src/js/vue.common.js' 這也是能夠的。只是和使用別名相比,顯的很lower
使用webpack,分爲兩種方式,一種是CLI(命令行方式),一種是API方式(new webpack(config)),兩種方式均可以經過webpack.config.js 來配置。因此學習webpack,就是掌握webpack.config.js配置的過程。我相繼介紹了entry,output,module,plugins,resolve,原本還想寫寫webpack的熱加載HMR, 以及webpack-dev-server,browser-sync結合webpack的用法,感受要寫的內容有點多,這些內容也是實際開發中很是有用的技術。越寫到後面,越以爲難於下筆。想起一句話,要想給別人一滴水,本身至少要有一桶水。前端工程自動化方案更新很快,webpack尚未來的極普及,webpack2,rollup等又出來了. 學習這些工具,是爲了減輕重複勞動,提升效率。選擇適合本身的方案,而不是在追尋技術的路上迷失了方向。