webpack在PC項目中的應用
好東西,總是要使用的。
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
module.exports = function(){
var version = 1.0.0;
console.log(version)
}
main.js
var query = require('./query');
query();
創建一個webpack.config.js文件
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類型的文件,當做一個模塊來載入。
npm install raw-loader
module:{
loaders:[
{
test:/\.html$/,
loader:'raw',
exclude:/(node_modules)/
}
]
}
每一個loader都可以用一個對象來描述,test是你的匹配規則,loader是你要載入的loader,exclude是你在執行規則是想忽略的目錄。
plugins
:
webpack的插件機制也非常的重要,其內置了多種插件,比如混淆,壓縮等等。插件列表可以訪問:list of plugins。
正常情況下可以使用官方自帶的插件:
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');
resolve:{
alias:{
"RequestModel":path.resolve(__dirname,'src/lib/request.model')
}
},
還可以設置訪問路徑,以及模塊載入后綴。
resolve:{
root:path.resolve(filePath,'/src'),
extensions:['','.js']
}
externals
:
此項配置可以將某些庫設置為外部引用,內部不會打包合并進去。
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。
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文件中設置:
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和插件。
列表:
"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:
// 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:
//利用了entry的對象寫法
{
"index.main":"./src/index.mian.js"
}
//然后在輸出是用[name]代替之前的'index.main.js'
提取JS文件中的公共部分:
webpack自帶的一個插件,可以提取合并打包時的公共部分,只要在頁面載入時,放置在合并打包后文件的前面。
new optimize.CommonsChunkPlugin('common.js')
完整的dev構建腳本
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用習慣了。)
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,問題解決。
來源:github.com/icepy/_posts/issues/25