webpack是一個基於node.js編寫的資源整合打包器(官方原稱:MODULE BUNDLER),經過指定入口文件,他能將該入口文件中引用的全部前端資源都合併打包,並最終輸出到你指定的輸出目錄。webpack進階比gulp要複雜得多,本文只以從零開始上手使用爲指導。javascript
本文所示代碼,可經過git@osc的這個項目獲取到:http://git.oschina.net/janpoem/webpack-tutorial。php
本文中可能在一些細節上表述得不夠清晰,徹底按照文章的順序去閱讀和執行,可能會碰到一些問題(也是webpack有太多的細節),因此建議先獲取到源代碼,先執行個npm install,讓他慢慢跑,而後一邊看, 一邊參考着代碼來閱讀,能更好的幫助理解文章說到的一些東西。css
而且若是有碰到本文中執行不下去,出問題的,歡迎提出嚴厲的批評和指責!html
如下部分若是你已經瞭解node.js和npm,請忽略跳過,直接進入《webpack安裝》。前端
首先,你要確保你的電腦安裝了node.js,點擊這裏找到適合你係統的版本下載並安裝。並確保你的系統內能正確執行如下命令:java
npm -v node -v
node.js是什麼?node
Node.js是一個Javascript運行環境(runtime)。實際上它是對Google V8引擎進行了封裝。V8引 擎執行Javascript的速度很是快,性能很是好。Node.js對一些特殊用例進行了優化,提供了替代的API,使得V8在非瀏覽器環境下運行得更好。react
npm是什麼?webpack
NPM的全稱是Node Package Manager ,是一個NodeJS包管理和分發工具,已經成爲了非官方的發佈Node模塊(包)的標準。git
若是你對webpack的基本安裝和使用已經十分了解,並已經安裝,能夠跳過本章節,並直接進入《webpack進階》章節。
接下來你須要在全局環境內安裝webpack和webpack-dev-server,固然後者並非必須的,但仍是強烈推薦你安裝。
npm install webpack webpack-dev-server -g
隨便創建一個測試的目錄,好比:webpack-first,用命令行進入,或者webstorm/phpstorm/atom等打開這個目錄。
在目錄中添加一個文件:webpack.config.js
module.exports = { entry: [ "./main.js" ], output: { path: './output', filename: 'app.js' } };
在這個文件中,咱們聲明瞭一個入口文件,爲當前目錄下的main.js,而且輸出目錄的基礎路徑爲當前目錄下的output,輸出的文件名爲app.js。
接着,咱們往下添加test.js和main.js文件,main.js如上所述,爲項目的入口文件,test.js爲須要引入在main.js中的模塊。
test.js
module.exports = [ 'a', 'b', 'c' ];
main.js
// 引入test.js文件,並將其輸出的內容(module.exports)賦值到test變量上 var test = require('./test'); // 在瀏覽器的console中輸出test變量的內容 console.log(test);
進入命令行模式(若是是phpstorm/webstorm能夠打開他的Terminal工具),輸入指令:webpack,會看到他執行的結果:
該命令執行完,webpack會在你的項目內添加一個output的目錄,打開這個目錄,你會看到根據你的webpack.config.js配置,他生成了一個app.js文件。
這裏特別說明一下,若是你僅僅只是須要用webpack來打包最原始的js文件,是不須要在這個項目內安裝webpack和webpack-dev-server的,他會使用你全局(就是剛纔npm install -g的)安裝的版本,這點比gulp好多了。
webpack-dev-server,是webpack提供的一個插件,他提供了一個http服務器環境,給你實時預覽打包合併的結果。
特別提早說明的是,使用webpack-dev-server指令,你必須指定--content-base指令,--content-base指令,用於指定http服務器的document root目錄。
在剛纔的項目根目錄中,執行如下命令:webpack-dev-server --content-base ./public,咱們以當前目錄下的public目錄做爲http服務器的根目錄:
特別說明:在webpack.config.js中指定的output目錄和--content-base並無必然的關係,output指定的是你輸出的路徑。
在項目中添加public目錄,並添加一個用於測試的文件:public/index.html。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>webpack first sample</title> </head> <body> <!--引入output中的app.js文件--> <script type="text/javascript" src="app.js"></script> </body> </html>
訪問http://localhost:8080/,並打開你的瀏覽器調試工具,你將能看到在main.js中輸出的信息。
除此以外,你還能夠訪問:http://localhost:8080/webpack-dev-server/,這裏提供了一個基於iframe的方式加載index.html的頁面,而且當你每次修改完js文件時,他都會自動刷新這個頁面以實時預覽。
上述能夠做爲一個基本的上手說明,這裏要說的,就是一些比較實際——和項目實際關聯的內容。
要使用進階功能,就須要在項目內安裝webpack和webpack-dev-server了,若是你還沒執行npm init,能夠先執行一次npm init,他會須要你回答一些問題,並生成一個與當前項目相關的package.json文件。package.json文件,能有效的管理你的node.js項目的版本,已經依賴庫,執行腳本,等信息,尤爲當你將項目提交到代碼倉庫,別人也須要執行你的項目時,經過package.json文件,可以很好的克服不一樣的系統環境下,缺失的依賴類庫。
npm init執行之後,請執行下面的命令:
npm install webpack webpack-dev-server --save-dev
這個指令的含義是,在當前項目內安裝最新版本的webpack和webpack-dev-server。
--save-dev參數,用於告訴npm這兩個類庫的信息須要寫入到package.json中,並做爲開發依賴庫。npm還有一個--save指令,與--save-dev相似,但他關聯的是直接依賴。--save-dev和--save,決定了別人在引用你的項目時,你項目依賴庫是否也安裝在對方的node_modules目錄中,這個詳細就不在這裏展開了。
執行完這兩個命令,打開你的package.json文件,你會看到以下內容:
npm命令須要訪問國外的npm庫,若是你感受你當前的網絡環境出國外不是那麼穩定,能夠考慮使用cnpm,點擊這裏看教程,這裏就再也不介紹了。
在webpack-dev-server包含了一些比較經常使用的參數,能夠大大提升咱們的開發效率,這裏介紹幾個最經常使用的:
--port <端口號>: 指定http服務的端口號
--host <主機名>: 指定http服務的主機名,這在局域網內使用實時調試很是有用。
--compress: 啓用gzip壓縮
--inline: 將webpack-dev-server的運行時文件合併打包到輸出的文件中
--hot: 使用HotModuleReplacementPlugin插件(已經整合在webpack中,無需npm安裝),並將http服務器切換到hot模式,其實所謂hot模式,就是當你更改了某個被引用的文件,會hot replace,並從新合併到輸出文件中。
通常來講,--hot --inline會合並使用,這個方式會合並將webpack/hot/dev-server打包到輸出文件中。這個webpack/hot/dev-server,實際上就是這個http服務器的hot replace的核心邏輯,而這個東西,其實在你就是你當前項目的node_modules/webpack/hot/dev-server.js文件。打開node_modules/webpack/hot/目錄,實際上你會看到這裏還有其餘的幾種模式,好比only-dev-server等。事實上你是能夠顯式的將這些文件配置在你的webpack.config.js文件中裏的,好比:
module.exports = { entry: [ "webpack/hot/only-dev-server", "./main.js" ], output: { path: './output', filename: 'app.js' } };
當你指定了only-dev-server之後,執行:webpack-dev-server --content-base ./public --hot時,注意輸出的變化:
而若是我把webpack.config.js中的webpack/hot/only-dev-server註釋掉,再執行:webpack-dev-server --content-base ./public --hot --inline
這是官方文檔很是隱晦的地方,沒有明確說明的地方。
而webpack自己比較經常使用的參數有:
--devtool : 調試工具的模式,eval是將你的css和js代碼變爲eval的方式合併打包。
--config : 指定配置文件
--progress: 在命令行終端輸出編譯合併的過程信息
--colors: 在命令行終端中顯示帶顏色的信息
更多更詳細的信息,能夠看這裏,webpack CLI模式的說明。
webpack還提供了一個經過你本身書寫代碼來啓動http服務的功能,這個應該說是最高階的內容了,本文就不討論了,有興趣的能夠去官方文檔看:webpack-dev-server,這篇文檔的下半部分,就是徹底由你本身實現一個http服務的簡單教程。詳細的能夠去看webpack node.js API。
除此以外,這篇文章是你使用webpack之前,必須閱讀的:webpack config配置參數說明,每一個項目有自身的特殊性,本文只是但願能將一些基本經常使用的東西能作一個涵蓋,因此徹底靠本文的內容,確定沒法解決每一個項目實際碰到的問題,因此熟讀這個配置文檔,應該是最起碼的要求。
在實用層面的第二個問題就是各類loaders,這裏分兩個部分,一個是js部分,包括如js近親系的es六、jsx、babel tranplier、coffee等,第二部分爲CSS以及其近親,如less、stylus,同時包括其餘前端的靜態資源的引用問題。
基於webpack打包,你能夠比以往過去更加放心大膽的使用各類奇葩,前所未見的各類腳本語言——固然這裏的前提是你能本身處理好他們的轉譯問題。而目前babel已經提供了大多數常見的語言、css、html模板的loaders,不過根據我實際經驗,webpack主要的優點仍是在於處理js的合併打包。
如下就以es6和jsx爲例(使用babel-loader):
npm install babel-loader babel-preset-es2015 babel-preset-react --save-dev
若是你須要使用到babel的一些插件,也須要經過npm來進行安裝,關於babel如何使用的問題,能夠參考我寫的這篇文章:《Babel指南 - 基本環境搭建》。
npm install babel-plugin-transform-class-properties babel-plugin-transform-es2015-block-scoping babel-plugin-transform-es2015-computed-properties --save-dev
在webpack.config.js中,咱們修改以下配置:
module.exports = { entry: [ "./main.js" ], output: { path: './output', filename: 'app.js' }, module: { loaders: [ { test: /\.(es6|jsx)?$/, exclude: /(node_modules|bower_components)/, loader: 'babel', // 'babel-loader' is also a legal name to reference query: { presets: ['es2015', 'react'], plugins: [ "transform-es2015-block-scoping", "transform-class-properties", "transform-es2015-computed-properties" ] } } ] } };
其實也很簡單,經過這樣,你就能夠隨意的使用es6和jsx了,好比咱們添加一個例子hello_world.jsx:
注意,要使用到react,你仍是須要先安裝:
npm install react react-dom --save-dev
hello_world.jsx代碼以下
var React = require('react'); class HelloWorld extends React.Component { constructor(props) { super(props); this.state = { url: '', value: 'http://tool.oschina.net/' }; } getUrl() { let url = this.state.value; if (/^\/\//.test(url)) { url = 'http:' + url; } else if (!/^https?\:\/\//i.test(url)) { url = 'http://' + url; } return url; } changeUrl(value) { this.setState({value: value}); } goUrl(value, event) { if (event && event.preventDefault) event.preventDefault(); this.setState({url: value}); } renderFrame() { if (this.state.url) { return } } componentDidMount() { this.goUrl(this.getUrl()); } render() { return <div> <h1>Hello world!!</h1> <form onSubmit={(e) => this.goUrl(this.getUrl(), e)}> <input type="text" value={this.state.value} onChange={(e) => this.changeUrl(e.target.value)}/> <button>Hello world</button> </form> {this.renderFrame()} </div>; } } module.exports = HelloWorld;
接着修改main.js:
var test = require('./test'); var ReactDOM = require('react-dom'); var React = require('react'); var HelloWorld = require('./hello_world.jsx'); var doc = document, body = doc.body; body.onload = function() { var el = doc.createElement('div'); el.style.opacity = 0; el.style.marginTop = '-100px'; el.style.transitionProperty = 'opacity margin-top'; el.style.transitionDuration = '800ms'; el.style.transitionTimingFunction = 'cubic-bezier(0.65,-0.1, 0.24, 1.47)'; body.appendChild(el); ReactDOM.render(React.createElement(HelloWorld), el); setTimeout(function() { el.style.opacity = 1; el.style.marginTop = 0; }, 1); };
訪問http://localhost:8080/就會看到:
要在webpack中引入css文件,須要簡單的作一個梳理。
經過css-loader,是能夠將一個css文件用require函數,在代碼中被引用,而且返回這個css中樣式定義的文字內容。固然首先你必須安裝css-loader:
npm install css-loader --save-dev
而後咱們就能夠在js中引入css
var css = require('css!./style.css')
但這樣,只是將css的內容引入,並無加載到頁面上,因此咱們須要修改main.js:
var test = require('./test'); var ReactDOM = require('react-dom'); var React = require('react'); var HelloWorld = require('./hello_world.jsx'); var css = require('css!./style.css'); var doc = document, body = doc.body; body.onload = function() { // 在head中把樣式加載 var style = doc.createElement('style'); style.innerText = css.toString(); doc.head.appendChild(style); var el = doc.createElement('div'); el.style.opacity = 0; el.style.marginTop = '-100px'; el.style.transitionProperty = 'opacity margin-top'; el.style.transitionDuration = '800ms'; el.style.transitionTimingFunction = 'cubic-bezier(0.65,-0.1, 0.24, 1.47)'; body.appendChild(el); ReactDOM.render(React.createElement(HelloWorld), el); setTimeout(function() { el.style.opacity = 1; el.style.marginTop = 0; }, 1); };
這樣,咱們會看到http://localhost:8080/已經加載到style.css的樣式了。關於css-loader的詳細用法,能夠去css-loader的github看看。
注意,目前咱們還沒去修改webpack.config.js。
style-loader:能夠將一個已經輸出的內容變爲一個style的DOM標籤輸出。安裝:
npm install style-loader --save-dev
這時候去掉剛纔在body.onload中增長的在head加入輸出css的代碼,修改main.js以下:
var test = require('./test'); var ReactDOM = require('react-dom'); var React = require('react'); var HelloWorld = require('./hello_world.jsx'); var css = require('style!css!./style.css'); var doc = document, body = doc.body; body.onload = function() { var el = doc.createElement('div'); el.style.opacity = 0; el.style.marginTop = '-100px'; el.style.transitionProperty = 'opacity margin-top'; el.style.transitionDuration = '800ms'; el.style.transitionTimingFunction = 'cubic-bezier(0.65,-0.1, 0.24, 1.47)'; body.appendChild(el); ReactDOM.render(React.createElement(HelloWorld), el); setTimeout(function() { el.style.opacity = 1; el.style.marginTop = 0; }, 1); };
注意:style!css!./style.css。這時候獲得的效果,和剛纔是同樣的。
爲何要不厭其煩的將css和style的loader拆開兩個章節來介紹呢,由於全部less、stylus實際上都是最終都是基於這個機制在運做的。
若是須要在樣式中加載圖片,那麼就須要url-loader,而如字體,則須要使用file-loader,仍是安裝:
npm install url-loader file-loader --save-dev
注意,這裏就是webpack和gulp最大的區別,webpack中,只要在你的源碼中存在被引用的資源,你都須要說明這些資源須要被如何加載。事實上,webpack還有如base64-loader,你也徹底能夠將一個圖片做爲base64-loader來處理。固然這裏咱們就以url-loader處理。
使用不一樣的loader,將決定了合併打包後的處理方式,若是使用base64-loader,他固然會將圖片的內容打包成base64編碼合併在js中。而url-loader,則會在輸出的目錄生成對應的文件(只有本地文件,會輸出到output目錄下)。
修改webpack.config.js:
module.exports = { entry: [ "./main.js" ], output: { path: './output', filename: 'app.js' }, module: { loaders: [ { test: /\.(es6|jsx)?$/, exclude: /(node_modules|bower_components)/, loader: 'babel', // 'babel-loader' is also a legal name to reference query: { presets: ['es2015', 'react'], plugins: [ "transform-es2015-block-scoping", "transform-class-properties", "transform-es2015-computed-properties" ] } }, { test: /\.(png|jpg|jpeg|gif|(woff|woff2)?(\?v=[0-9]\.[0-9]\.[0-9])?)$/, loader: 'url-loader?limit=1000' }, { test: /\.(ttf|eot|svg)(\?[\s\S]+)?$/, loader: 'file' } ] } };
而後重啓webpack-dev-server,你就能看到圖片、svg等資源已經能正確的加載。
關於合併打包,咱們最後會說到。
這裏以stylus爲例,其餘less、scss等的也都是同理的。
npm install stylus stylus-loader --save-dev
一樣的,你只要使用require便可在頁面引入styl文件:
require('style!css!stylus!./hello.styl');
若是你以爲使用style!css!stylus!的方式加載文件,過於怪異,那麼你能夠修改webpack.config.js文件,增長兩個loader:
{ test: /\.styl$/, loader: "style!css!stylus" }, { test: /\.css$/, loader: "style!css" }
這樣,你就可使用順眼一點的方式來加載:
require('./style.css'); require('./hello.styl');
到此,咱們已經完成了所有的編碼。接下來就要將內容打包輸出了,這時候你只要在項目的根目錄下執行一下webpack便可。
這時候咱們會看到,在output目錄下,他輸出了一個app.js和svg文件。而本例子中的兩個css文件,他所有合併到打包到了app.js文件中。
若是你但願將css文件從js文件中分離出來,須要一個額外的插件:
npm install extract-text-webpack-plugin --save-dev
這個插件能夠指定攔截特定加載器輸出的文本內容,並最終合併輸出到你指定的文件上去,但要特別注意,使用這個插件後,webpack-dev-server自動輸出樣式調試就會由於這個插件而失效,因此建議構建的時候,使用單獨的配置文件,好比添加一個webpack.build.js,使用指令:
webpack --config webpack.build.js
webpack.build.js內容以下:
const ExtractTextPlugin = require('extract-text-webpack-plugin'); module.exports = { entry: [ "./main.js" ], output: { path: './output', filename: 'app.js' }, module: { loaders: [ { test: /\.(es6|jsx)?$/, exclude: /(node_modules|bower_components)/, loader: 'babel', query: { presets: ['es2015', 'react'], plugins: [ "transform-es2015-block-scoping", "transform-class-properties", "transform-es2015-computed-properties" ] } }, { test: /\.(png|jpg|jpeg|gif|(woff|woff2)?(\?v=[0-9]\.[0-9]\.[0-9])?)$/, loader: 'url-loader?limit=1000' }, { test: /\.(ttf|eot|svg)(\?[\s\S]+)?$/, loader: 'file' }, { test: /\.styl$/, // loader: "style!css!stylus" loader: ExtractTextPlugin.extract('style', 'css!stylus') }, { test: /\.css$/, // loader: "style!css" loader: ExtractTextPlugin.extract('style', 'css') } ] }, plugins: [ new ExtractTextPlugin('app.css') ] };
此次就能將css文件分離出來了。
webpack還有不少可調節、可優化的配置,可是礙於篇幅,在這裏就再也不詳細展開了。可能有一些細節的地方,本文沒有介紹的很清楚,也請多多見諒。
本文所示代碼,可經過git@osc的這個項目獲取到:http://git.oschina.net/janpoem/webpack-tutorial。
最後補一張示例代碼的最終的效果圖: