概述
Webpack是一款用戶打包前端模塊的工具。主要是用來打包在瀏覽器端使用的javascript的。同時也能轉換、捆綁、打包其他的靜態資源,包括css、image、font file、template等。個人認為它的優點就是易用,而且常用功能基本都有,另外可以通過自己開發loader和plugin來滿足自己的需求。這里就盡量詳細的來介紹下一些基本功能的使用。
安裝
npm install webpack
運行webpack
webpack需要編寫一個config文件,然后根據這個文件來執行需要的打包功能。我們現在來編寫一個最簡單的config。新建一個文件,命名為webpack-config.js。config文件實際上就是一個Commonjs的模塊。內容如下:
var webpack = require('webpack'); var path = require('path'); var buildPath = path.resolve(__dirname,"build"); var nodemodulesPath = path.resolve(__dirname,'node_modules'); var config = { //入口文件配置 entry:path.resolve(__dirname,'src/main.js'), resolve:{ extentions:["","js"]//當requrie的模塊找不到時,添加這些后綴 }, //文件導出的配置 output:{ path:buildPath, filename:"app.js" } } module.exports = config;
我的目錄結構是這樣的:
webpack
|---index.html |---webpack-config.js |---src |---main.js |---js |---a.js
main.js文件內容如下:
var a = require('./js/a'); a(); console.log('hello world'); document.getElementById("container").innerHTML = "<p>hello world</p>";
a.js文件內容如下:
module.exports = function(){ console.log('it is a '); }
然后我們執行如下的命令:
webpack --config webpack-config.js --colors
這樣我們就能在目錄里面看到一個新生成的目錄build,目錄結構如下:
webpack
|---index.html |---webpack-config.js |---build |---app.js
然后引用app.js就Ok啦。main.js和模塊a.js的內容就都打包到app.js中了。這就演示了一個最簡單的把模塊的js打包到一個文件的過程了。
介紹webpack config文件
webpack是根據config里面描述的內容對一個項目進行打包的。接着我們來解釋下config文件中的節點分別代表什么意思。一個config文件,基本都是由以下幾個配置項組成的。
entry
配置要打包的文件的入口;可以配置多個入口文件,下面會有介紹。
resolve
配置文件后綴名(extensions),除了js,還有jsx、coffee等等。alias配置項,可以為常用模塊配置改屬性,可以節省編譯的搜索時間。例如:
resolve:{ extensions:['.js','.jsx'], alias:{ 'react':path.join(nodeModulesPath,'react/react.js') } }
除了這個功能還可以配置其他有用的功能,由於我還不完全了解,有知道的朋友歡迎指教。
output
配置輸出文件的路徑,文件名等。
module(loaders)
配置要使用的loader。對文件進行一些相應的處理。比如babel-loader可以把es6的文件轉換成es5。大部分的對文件的處理的功能都是通過loader實現的。loader就相當於gulp里的task。loader可以用來處理在入口文件中require的和其他方式引用進來的文件。loader一般是一個獨立的node模塊,要單獨安裝。
loader配置項:
test: /\.(js|jsx)$/, //注意是正則表達式,不要加引號,匹配要處理的文件 loader: 'eslint-loader', //要使用的loader,"-loader"可以省略 include: [path.resolve(__dirname, "src/app")], //把要處理的目錄包括進來 exclude: [nodeModulesPath] //排除不處理的目錄
目前已有的loader列表:https://webpack.github.io/docs/list-of-loaders.html
一個module的例子:
module: { preLoaders: [ { test: /\.(js|jsx)$/, loader: 'eslint-loader', include: [path.resolve(__dirname, "src/app")], exclude: [nodeModulesPath] }, ], loaders: [ { test: /\.(js|jsx)$/, //正則表達式匹配 .js 和 .jsx 文件 loader: 'babel-loader?optional=runtime&stage=0',//對匹配的文件進行處理的loader exclude: [nodeModulesPath]//排除node module中的文件 } ] }
plugins
顧名思義,就是配置要使用的插件。plugin是比loader功能更強大的插件,能使用更多的wepack api。來看一個使用plugin的例子:
plugins: [
//壓縮打包的文件 new webpack.optimize.UglifyJsPlugin({ compress: { //supresses warnings, usually from module minification warnings: false } }), //允許錯誤不打斷程序 new webpack.NoErrorsPlugin(), //把指定文件夾xia的文件復制到指定的目錄 new TransferWebpackPlugin([ {from: 'www'} ], path.resolve(__dirname,"src")) ]
目前已有的plugins列表:http://webpack.github.io/docs/list-of-plugins.html
如何壓縮輸出的文件
plugins: [ //壓縮打包的文件 new webpack.optimize.UglifyJsPlugin({ compress: { //supresses warnings, usually from module minification warnings: false } })]
如何copy目錄下的文件到輸出目錄
copy文件需要通過插件"transfer-webpack-plugin"來完成。
安裝:
npm install transfer-webpack-plugin -save
配置:
var TransferWebpackPlugin = require('transfer-webpack-plugin'); //其他節點省略 plugins: [ //把指定文件夾下的文件復制到指定的目錄 new TransferWebpackPlugin([ {from: 'www'} ], path.resolve(__dirname,"src")) ]
打包javascript模塊
支持的js模塊化方案包括:
-
ES6 模塊
import MyModule from './MyModule.js';
-
CommonJS
var MyModule = require('./MyModule.js');
-
AMD
define(['./MyModule.js'], function (MyModule) { });
上面已經演示了打包js模塊,這里不再重復。ES6的模塊需要配置babel-loader來先把處理一下js文件。下面展示下打包ES模塊的配置文件:
var webpack = require('webpack'); var path = require('path'); var buildPath = path.resolve(__dirname, 'build'); var nodeModulesPath = path.resolve(__dirname, 'node_modules'); var TransferWebpackPlugin = require('transfer-webpack-plugin'); var config = { entry: [path.join(__dirname, 'src/main.js')], resolve: { extensions: ["", ".js", ".jsx"] //node_modules: ["web_modules", "node_modules"] (Default Settings) }, output: { path: buildPath, filename: 'app.js' }, plugins: [ new webpack.optimize.UglifyJsPlugin({ compress: { warnings: false } }), new webpack.NoErrorsPlugin(), new TransferWebpackPlugin([ {from: 'www'} ], path.resolve(__dirname,"src")) ], module: { preLoaders: [ { test: /\.(js|jsx)$/, loader: 'eslint-loader', include: [path.resolve(__dirname, "src/app")], exclude: [nodeModulesPath] }, ], loaders: [ { test: /\.js$/, //注意是正則表達式,不要加引號 loader: 'babel-loader?optional=runtime&stage=0',//babel模塊相關的功能請自查,這里不做介紹 exclude: [nodeModulesPath] } ] }, //Eslint config eslint: { configFile: '.eslintrc' //Rules for eslint }, }; module.exports = config;
打包靜態資源
css/sass/less
安裝css-loader和style-loader
npm install css-loader --save -dev npm install style-loader --save -dev
config配置:
var config = { entry:path.resolve(__dirname,'src/main.js'), resolve:{ extentions:["","js"] }, output:{ path:buildPath, filename:"app.js" }, module:{ loaders:[{ test:/\.css$/, loader:'style!css', exclude:nodemodulesPath }] } }
style-loader會把css文件嵌入到html的style標簽里,css-loader會把css按字符串導出,這兩個基本都是組合使用的。打包完成的文件,引用執行后,會發現css的內容都插入到了head里的一個style標簽里。如果是sass或less配置方式與上面類似。
images
可以通過url-loader把較小的圖片轉換成base64的字符串內嵌在生成的文件里。安裝:
npm install url-loader --save -dev
config配置:
var config = { entry:path.resolve(__dirname,'src/main.js'), resolve:{ extentions:["","js"] }, output:{ path:buildPath, filename:"app.js" }, module:{ loaders:[{ test:/\.css$/, loader:'style!css',// exclude:nodemodulesPath }, { test:/\.png$/,loader:'url-loader?limit=10000'}//限制大小小於10k的 ] } }
css文件內容:
#container{ color: #f00; background:url(images/logo-201305.png); /*生成完圖片會被處理成base64的字符串 注意:不要寫'/images/logo-201305.png',否則圖片不被處理*/ }
iconfont
內嵌iconfont的使用方法其實和上述處理png圖片的方法一致。通過url-loader來處理。
config配置:
var config = { entry:path.resolve(__dirname,'src/main.js'), resolve:{ extentions:["","js"] }, output:{ path:buildPath, filename:"app.js" }, module:{ loaders:[{ test:/\.css$/, loader:'style!css',// exclude:nodemodulesPath }, { test:/\.(png|woff|svg|ttf|eot)$/,loader:'url-loader?limit=10000'}//限制大小小於10k的 ] } }
css文件內容:
@font-face {font-family: 'iconfont'; src: url('fonts/iconfont.eot'); /* IE9*/ src: url('fonts/iconfont.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */ url('fonts/iconfont.woff') format('woff'), /* chrome、firefox */ url('fonts/iconfont.ttf') format('truetype'), /* chrome、firefox、opera、Safari, Android, iOS 4.2+*/ url('fonts/iconfont.svg#iconfont') format('svg'); /* iOS 4.1- */ }
執行打包后會把字體文件都轉換成base64字符串內容到文件里.這里有個頭疼的問題,就是每個瀏覽器支持的字體格式不一樣,由於把全部格式的字體打包進去,造成不必要的資源浪費。
打包template
我一大包handlebars的模塊為例,來演示下打包模塊的過程。有的模板對應的loader,有可能沒有現車的,恐怕要自己實現loader。
先安裝必須的node模塊
npm install handlebars-loader --save -dev npm install handlebars -save//是必須的
config配置:
var config = { entry:path.resolve(__dirname,'src/main.js'), resolve:{ extentions:["","js"] }, output:{ path:buildPath, filename:"app.js" }, module:{ loaders:[ { test: /\.html$/, loader: "handlebars-loader" } ] } }
新建一個模板文件tb.html,目錄結構:
webpack
|---index.html |---webpack-config.js |---src |---template | |---tb.html |---main.js
main.js中調用模塊的代碼如下:
var template = require("./template/tp.html"); var data={say_hello:"it is handlebars"}; var html = template(data); document.getElementById('tmpl_container').innerHTML = html;
公用的模塊分開打包
這需要通過插件“CommonsChunkPlugin”來實現。這個插件不需要安裝,因為webpack已經把他包含進去了。接着我們來看配置文件:
var config = { entry:{app:path.resolve(__dirname,'src/main.js'), vendor: ["./src/js/common"]},//【1】注意這里 resolve:{ extentions:["","js"] }, output:{ path:buildPath, filename:"app.js" }, module:{ loaders:[{ test:/\.css$/, loader:'style!css', exclude:nodemodulesPath } ] }, plugins:[ new webpack.optimize.UglifyJsPlugin({ compress: { warnings: false } }), //【2】注意這里 這兩個地方市用來配置common.js模塊單獨打包的 new webpack.optimize.CommonsChunkPlugin({ name: "vendor",//和上面配置的入口對應 filename: "vendor.js"//導出的文件的名稱 }) ] }
目錄結構現在是這樣的:
webpack
|---index.html |---webpack-config.js |---src |---main.js |---js |---a.js //a里面require了common |---common.js
執行webpack會生成app.js和common.js兩個文件.
多個入口
config配置:
var config = { entry:{ m1:path.resolve(__dirname,'src/main.js'), m2:path.resolve(__dirname,'src/main1.js') },//注意在這里添加文件的入口 resolve:{ extentions:["","js"] }, output:{ path:buildPath, filename:"[name].js"//注意這里使用了name變量 } }
webpack-dev-server
在開發的過程中個,我們肯定不希望,每次修改完都手動執行webpack命令來調試程序。所以我們可以用webpack-dev-server這個模塊來取代煩人的執行命令。它會監聽文件,在文件修改后,自動編譯、刷新瀏覽器的頁面。另外,編譯的結果是保存在內存中的,而不是實體的文件,所以是看不到的,因為這樣會編譯的更快。它就想到與一個輕量的express服務器。安裝:
npm install webpack-dev-server --save -dev
config配置:
var config = { entry:path.resolve(__dirname,'src/main.js'), resolve:{ extentions:["","js"] }, //Server Configuration options devServer:{ contentBase: '', //靜態資源的目錄 相對路徑,相對於當前路徑 默認為當前config所在的目錄 devtool: 'eval', hot: true, //自動刷新 inline: true, port: 3005 }, devtool: 'eval', output:{ path:buildPath, filename:"app.js" }, plugins: [ new webpack.HotModuleReplacementPlugin(),//這個好像也是必須的,雖然我還沒搞懂它的作用 new webpack.NoErrorsPlugin() ] }
我的目錄結構:
webpack
|---index.html |---webpack-config.js//我把靜態資源目錄配置在了這里 |---src |---main.js |---js |---a.js |---common.js
執行命令:
webpack-dev-server --config webpack-dev-config.js --inline --colors
默認訪問地址:
http://localhost:3000/index.html(根據配置會不一樣)
有一點需要聲明,在index.html(引用導出結果的html文件)里直接引用“app.js”,不要加父級目錄,因為此時app.js在內存里與output配置的目錄無關:
<script type="text/javascript" src="app.js"></script>
詳細文檔在這里查看:http://webpack.github.io/docs/webpack-dev-server.html
如何區分開發及生產環境
在webpack.config.js使用process.env.NODE_ENV進行判斷在package.json里面的script設置環境變量,注意mac與windows的設置方式不一樣
"scripts": { "publish-mac": "export NODE_ENV=prod&&webpack -p --progress --colors", "publish-win": "set NODE_ENV=prod&&webpack -p --progress --colors", "dev-mac": "export NODE_ENV=dev&&webpack-dev-server", "dev-win": "set NODE_ENV=dev&&webpack-dev-server }
常用plugins
-
代碼熱替換, HotModuleReplacementPlugin
-
將css成生文件,而非內聯,ExtractTextPlugin
-
報錯但不退出webpack進程,NoErrorsPlugin
-
代碼丑化,UglifyJsPlugin,開發過程中不建議打開
-
多個 html共用一個js文件(chunk),可用CommonsChunkPlugin
-
清理文件夾,Clean
-
調用模塊的別名ProvidePlugin,例如想在js中用$,如果通過webpack加載,需要將$與jQuery對應起來