好東西,總是要使用的。
webpack是什麼工具
webpack is a module bundler
正如官網對webpack的描述,它是一種模組化載入器,當然也不僅僅限於此。某種程度上來說,可以代替某些gulp
的功能,至少有些還是無法替代的。在webpack中所有的資源都會被視作模組來處理,為了應對這樣的情況,webpack有對應的loader
機制來處理,另外shim,plugins,和其他構建工具,一樣一樣的,更多的細節,需要你在實際的應用中慢慢去體會了。
webpack的使用方法
安裝:npm install webpack –verbose –save-dev
webpack認為一個專案(或者一個頁面),總有一個入口檔案,就像C語言中總有一個main函式一樣。假設,我們建立兩個檔案./mian.js
和./query.js
,並且將main.js
做為我們專案的入口檔案。
query.js
1 2 3 4 |
module.exports = function(){ var version = 1.0.0; console.log(version) } |
main.js
1 2 |
var query = require('./query'); query(); |
建立一個webpack.config.js檔案
1 2 3 4 5 6 7 8 |
var config = { entry:'./main.js', ouptut:{ path:'./js' filename:'main.js' } } module.exports = config; |
在你的終端上執行webpack
即可。
webpack配置詳解
entry
:
entry屬性做為可配置的入口,比如上面所寫的./main.js
。entry有三種寫法,每一個入口可以稱之為一個chunk。
- 如果為字串,只會打包一個
順序依賴
的模組,輸出則根據output配置而定。 - 如果為陣列,只會打包一個
順序依賴
的模組,合併到最後一個模組時匯出,輸出則根據output配置而定。 - 如果為物件,則會根據入口打包多個
順序依賴
的模組,key名會根據在output的配置輸出。
output
:
輸出規則,在此物件中設定。
- path 設定輸出的檔案路徑
- filename 設定輸出檔名,filename可以有多種配置,比如
main.js
,[id].js
,[name].js
,[hash].js
等 - publicPath 設定資源的訪問路徑
- library 設定模組匯出的類名
- libraryTarget:’umd’ 設定模組相容模式
- umdNamedDefine:true 同上
devtool
:
將devtool設定為source-map
,在開發除錯階段非常有用,它的模式非常多,我有搞的比較暈。
loader
:
loader機制應該是webpack中非常重要的部分了,它是一系列資源的最終執行者。一般情況下,你可以訪問:webpack loader來訪問可用loader列表。
比如現在我想將.html型別的檔案,當做一個模組來載入。
1 |
npm install raw-loader |
1 2 3 4 5 6 7 8 9 |
module:{ loaders:[ { test:/\.html$/, loader:'raw', exclude:/(node_modules)/ } ] } |
每一個loader都可以用一個物件來描述,test是你的匹配規則,loader是你要載入的loader,exclude是你在執行規則是想忽略的目錄。
plugins
:
webpack的外掛機制也非常的重要,其內建了多種外掛,比如混淆,壓縮等等。外掛列表可以訪問:list of plugins。
正常情況下可以使用官方自帶的外掛:
1 2 3 4 5 |
new webpack.optimize.UglifyJsPlugin({ compress: { warnings: false } }) |
當然,我們也可以引入第三方外掛,使用你的npm install吧。
resolve
:
此配置可以對一些常用模組設定別名,比如a.js
放置在./src/module/address/
中,每次載入模組需要var a = require(‘./src/module/address/a’);名字非長,如果設定別名了,只需要var a = require(‘a’);
1 2 3 4 5 |
resolve:{ alias:{ "RequestModel":path.resolve(__dirname,'src/lib/request.model') } }, |
還可以設定訪問路徑,以及模組載入字尾。
1 2 3 4 |
resolve:{ root:path.resolve(filePath,'/src'), extensions:['','.js'] } |
externals
:
此項配置可以將某些庫設定為外部引用,內部不會打包合併進去。
1 2 3 |
externals:{ jquery:'window.jQuery' } |
在我們PC專案中的應用
我們公司內部的專案,也開始應用npm scripts來做執行鉤子,webpack來做構建,首先設計三個命令:
- npm run start
- npm run dev
- npm run build
配置npm run start
本地伺服器的啟動,我們沒有使用webpack官方提供的webpack-dev-server,而是採用了 browser-sync。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
var browser = require('browser-sync'); var browserSync = browser.create(); var PORT = 4000 var loadMap = [ 'modules/*.*', 'src/**/*.*', './*.html', './web/*.html' ]; gulp.task('server',[], function() { // content browserSync.init({ server:'./', port:PORT }); gulp.watch(loadMap, function(file){ console.log(file.path) browserSync.reload() }); }); |
利用gulp寫了一個指令碼任務,在package.json檔案中設定:
1 2 3 |
scripts:{ "start":"gulp server" } |
瞭解我們專案的實際需求
我們的專案是一個多頁面專案,並不像單頁應用一樣(業務程式設計可以打包成一個),首先我們需要設計一個良好的目錄結構,如下:
- web 目錄放置*.html頁面
- style 目錄放置*.css檔案,另外在此目錄中放置了less原始檔
- src 目錄放置了我們的所有*.js檔案
- mock 內建的模擬資料,放置在此
- img 圖片放置目錄
- link npm下載不了的第三方庫放置在此
- YYT_PC_Modules 內部編寫的模組,放置在此
- YYT_PC_Component 內部編寫的元件,放置在此
編寫一個map.json檔案,用來維護多入口的關係。當然我們的webpack.dev.config.js檔案,放置在根目錄。最後的build階段應該輸出一個新的目錄dist
這個目錄中放置的應該是所有build完成的資源,包括*.html檔案。
它應該才是我們最終的釋出目錄。
配置npm run dev
對於CSS我們的期望是一個新的link而不是style內嵌,所以還需要做一些額外的事情,先下載loader和外掛。
列表:
1 2 3 4 5 6 7 |
"less-loader": "^2.2.2", "raw-loader": "^0.5.1", "style-loader": "^0.13.0", "css-loader": "^0.23.1", "eslint": "^2.2.0", "eslint-loader": "^1.3.0", "extract-text-webpack-plugin": "^1.0.1", |
raw-loader
主要用來解決模板載入的問題,模組當做一個變數直接載入到業務程式設計中。
配置我們的CSS:
1 2 3 4 5 6 7 8 9 10 11 |
// webpack.dev.config.js var ExtractTextPlugin = require('extract-text-webpack-plugin'); var extractLESS = new ExtractTextPlugin('../style/[name].css'); { test: /\.less$/i, loader: extractLESS.extract(['css','less']) } //入口js檔案 require('../style/less/index.less') |
ExtractTextPlugin
的作用就是將CSS單獨輸出一個檔案,這個檔名依賴於entry寫的入口檔名。
配置我們的多頁面JS:
1 2 3 4 5 6 |
//利用了entry的物件寫法 { "index.main":"./src/index.mian.js" } //然後在輸出是用[name]代替之前的'index.main.js' |
提取JS檔案中的公共部分:
webpack自帶的一個外掛,可以提取合併打包時的公共部分,只要在頁面載入時,放置在合併打包後檔案的前面。
1 |
new optimize.CommonsChunkPlugin('common.js') |
完整的dev構建指令碼
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 |
var webpack = require('webpack'); var path = require('path'); var fs = require('fs'); var plugins = []; var ExtractTextPlugin = require('extract-text-webpack-plugin'); var optimize = webpack.optimize var extractLESS = new ExtractTextPlugin('../style/css/[name].css'); plugins.push(extractLESS); plugins.push(new optimize.CommonsChunkPlugin('common.js')); var sourceMap = require('./map.json').source; var YYT_PC_Modules = 'link/YYT_PC_Modules/'; var YYT_PC_Component = 'link/YYT_PC_Component/'; var config = { entry: sourceMap, output: { path: path.resolve(__dirname + '/js'), filename: '[name].js' }, devtool: 'source-map', module: { loaders: [ { test: /\.html$/, loader: 'raw', exclude: /(node_modules)/ }, { test: /\.js$/, loader: 'eslint-loader', exclude: /(node_modules)/ }, { test: /\.less$/i, loader: extractLESS.extract(['css', 'less']) }, { test: /\.(png|jpg)$/, loader: 'url-loader?limit=8192' } ] }, plugins: plugins, resolve: { alias: { "tplEng": path.resolve(__dirname, 'link/template'), //模板引擎 "BaseModel": path.resolve(__dirname, YYT_PC_Modules + 'baseModel'), "BaseView": path.resolve(__dirname, YYT_PC_Modules + 'baseView'), "store": path.resolve(__dirname, YYT_PC_Modules + 'store/locationStore'), "cookie": path.resolve(__dirname, YYT_PC_Modules + 'store/cookie'), "url": path.resolve(__dirname, YYT_PC_Modules + 'util/url'), "tools": path.resolve(__dirname, YYT_PC_Modules + 'util/tools'), "FlashAPI": path.resolve(__dirname,YYT_PC_Modules + 'util/FlashAPI'), "DateTime": path.resolve(__dirname, YYT_PC_Modules + 'util/DateTime'), "pwdencrypt": path.resolve(__dirname, YYT_PC_Modules + 'crypto/pwdencrypt'), "secret": path.resolve(__dirname, YYT_PC_Modules + 'crypto/secret'), "UploadFile": path.resolve(__dirname, YYT_PC_Component + 'feature/UploadFile'), "AjaxForm": path.resolve(__dirname, YYT_PC_Component + 'feature/AjaxForm'), "Scrollbar": path.resolve(__dirname, YYT_PC_Component + 'feature/Scrollbar'), "LoginBox": path.resolve(__dirname, YYT_PC_Component + 'business/LoginBox/'), "UserModel": path.resolve(__dirname, YYT_PC_Component + 'business/UserModel/'), "UploadFileDialog": path.resolve(__dirname, YYT_PC_Component + 'business/UploadFileDialog/'), "ui.Dialog": path.resolve(__dirname, YYT_PC_Component + 'ui/dialog/'), "ui.Confirm": path.resolve(__dirname, YYT_PC_Component + 'ui/confirm/'), "ui.MsgBox": path.resolve(__dirname, YYT_PC_Component + 'ui/msgBox/'), "config": path.resolve(__dirname, 'src/lib/config') } }, externals: { jquery: 'window.jQuery', backbone: 'window.Backbone', underscore: 'window._' } }; // console.log(path.resolve(__dirname,'node_modules/jquery/dist/jquery.js')) module.exports = config; |
問題
問題一:在windows機器上如果你要設定環境變數(也許是我沒有找到問題的所在,但是提出來,主要是Mac用習慣了。)
1 2 3 |
scripts:{ "dev":"WEB_PACK=1 webpack --watch --config webpack.dev.config.js " } |
在webpack.dev.config.js檔案中不能正確的獲取環境變數,所以重新寫了一個build檔案,webpack.build.config.js來做最後的構建。
問題二:構建之後的檔案hash化後如何更新HTML中的路徑
這個問題,最後沒有采用webpack來做,而是使用gulp來解決的。(不知道大家有沒有什麼好的方式)
感謝@sharkrice告知 HtmlWebpackPlugin 外掛
問題三:構建系統的shim
我們的PC專案(相容IE8+),依然使用著以前的庫,jQuery.js,underscore.js,backbone.js,以及其他第三方不支援commonjs語法的外掛,有很多相容不是很好,最後還是寫了另外一個入口檔案(包裝),然後在webpack合併打包的檔案之前引入這些外掛,掛載在一個全域性的名稱空間下,利用新寫的一個包裝入口匯出模組。
問題四:CSS依賴重複
感謝@sharkrice 告知 嘗試webpack.optimize.DedupePlugin,問題解決。