webpack實踐最后一篇
溫故而知新
這應該是目前這個階段最后一篇關于webpack的實踐經驗,也許你會學習到該用怎樣的思想去使用webpack,也許你會認為這是一坨屎一樣的文字。不過,我會盡量的描述,我們的實踐以及給出一份在Mac和Win下的Demo,這個項目也是我們應用在PC端的實踐,訪問 https://github.com/sapling-team/generator-sapling-pc 來閱讀我們PC端的腳手架吧。(完美兼容IE8+)
我們對 backbone 也提供了一份有用的擴展,可訪問: https://github.com/sapling-team/base-extend-backbone
更多基礎的配置信息,建議大家閱讀 《webpack在PC項目中的應用》
該用什么樣的思想來使用
你用的始終還是Node.js環境
我覺得你應該要忘記webpack,因為你使用的終歸還是Node.js。為什么這么說,因為如果只使用簡單的配置,你可能只把它做為一個模塊加載器。如果你熟練的使用了Node.Js那么恭喜你,我們將用環境的思維來使用它,并用Node.Js來驅動你的構建流程。(NPM可以使用的模塊,你都可以在構建環境中使用)
首先,我們應該將腳本文件放置在bin目錄下,這是unix編程的常識。接著,對于你的產品,定義兩個環境:dev和product,在NPM Scripts中使用 NODE_ENV=dev webpack --config bin/webpack.config ,然后在腳本文件中使用 process.env.NODE_ENV 來獲取環境變量并區分執行。(很不幸的是,Win用戶無法獲取ENV,所以你需要建立兩個文件來做dev和product)
抽象dir
抽象你的目錄資源,設計一定的規則,可以進行批處理配置信息。
npm install glob --save-dev --verbose
我們的入口文件都放置在src中,根據業務特點來命名,比如: index.js , code.js 。
var path = require('path');
var glob = require('glob');
module.exports = getEntry;
function getEntry(sourcePath){
var entrys = {};
var basename;
glob.sync(sourcePath).forEach(function(entry){
basename = path.basename(entry,path.extname(entry));
entrys[basename] = entry;
});
return entrys;
}
在 webpack.dev.config.js 文件,可以通過getEntry函數來統一處理入口,并得到 entry 配置對象。如果你是多頁面多入口的項目,建議你使用統一的命名規則,比如頁面叫 index.html ,那么你的js和css入口文件也應該叫 index.js 和 index.css 。
include
在編譯期來決定最終呈現什么樣的HTML
在后端語言的模板中 include 是一個非常有用的特性,因為它可以抽象分離不同的HTML結構,來達到復用的目的。
npm install jade-loader --save-dev --verbose
doctype html
html(lang="en")
head
- var titleValue = htmlWebpackPlugin.options.title
title=titleValue
meta(charset="UTF-8")
body
include common/header
include index/container
include common/footer
include common/lib
- var src;
- var map = ['jquery/dist/jquery.min.js','underscore/underscore-min.js','backbone/backbone-min.js']
if htmlWebpackPlugin.options.cdn
- src = 'http://127.0.0.1:3000/www/link/'
else
- src = '/link/'
each val in map
script(type="text/javascript",src=src+val)
{
test:/.jade$/,
loader:'jade-loader',
exclude:/(node_modules)/
}
不僅如此 jade 還可以做更高靈活的配置。
html-webpack-plugin
如果你是多頁面,或者單頁應用都需要這個插件來幫忙處理HTML的內容,比如上述的jade模板的 include 。
var pages = getEntry('./app/web/*.jade');
for(var chunkname in pages){
var conf = {
cdn:false,
filename:chunkname+'.html',
template:pages[chunkname],
inject:true,
minify:{
removeComments:true,
collapseWhitespace: false
},
chunks:['common',chunkname],
hash:false
}
plugins.push(new HtmlWebpackPlugin(conf));
}
根據 抽象dir 的方法,我們可以通過 getEntry 來獲取一個pages對象,并使用chunks來處理每一個入口頁面的依賴。
如果你是單頁應用,你只需要添加一次HtmlWebpackPlugin插件即可。
如何優化
優化,但不要過度的優化
運行 npm run product 來構建你的發布資源。
過度優化的結果:
.title {
margin-left: auto;
margin-right: auto;
.size(margin-top, 15px);
.size(margin-bottom, 15px);
}
最后經過webpack的優化變成了:
.title {
margin: 2.6408vh auto 2.6408vh;
margin-top: 1.5rem;
margin-bottom: 1.5rem;
}
那么問題來了Android4.3以下版本是不支持vw,vh單位的。
我認為對于一個項目,優化的方式應該可以從下列的幾個點中去挖掘:
externals 分離
善用 externals 將過大的文件分離出去,然后再用 script 標簽引用即可。
alias機制
給一些模塊啟用別名,來提高webpack的搜索速度。
將某些對象暴露在全局
- 在loader中使用 expose 將對象暴露出去 loader: 'expose?jQuery'
- 使用 ProvidePlugin 插件的幫助
new webpack.ProvidePlugin({
"M": "mock",
}),
假設 mock 對象中有 set , get 方法,那么此刻你可以在使用 M.set , M.get 方式來調用。
啟用熱替換
- 將代碼內斂到入口js文件中,然后啟動 webpack.HotModuleReplacementPlugin 插件
- 使用自帶的 webpack-dev-server 啟動一個服務器
- 直接在 webpack.config.js 文件中配置
//配置
devServer:{
port:4000,
contentBase:'./app',
historyApiFallback:true
}
合理的使用CommonsChunkPlugin將每一個chunk分割開,并提取
CommonsChunkPlugin 有一些屬性,比如 minChunks 可以設置如果 require 幾次再提取的問題。
個別項目環境變量優化
定義你的 process.env.NODE_ENV 變量,啟用 DefinePlugin 插件來優化警告信息,很多框架,比如react都帶有警告,比如:
if(process.env.NODE_ENV !== 'production'){
}
在prodcut期,我們可以通過 DefinePlugin 來打入環境變量來將這些剔除。
plugins.push(new webpack.DefinePlugin({
'process.env':{
'NODE_ENV':JSON.stringify(process.env.NODE_ENV)
}
}));
構建期間 process.env.NODE_ENV !== 'production' 會變成:
if(false){
}
壓縮工具,會忽略false內的內容,你會發現體積將減少了很多。
清理工作
每次 npm run product 之后,因為設置了 hash 屬性,所以會生成不同的文件,那么問題來了,我無法清理www目錄,那么這時候,就可以換到Node.js的文件系統上了。
var fs = require('fs');
var path = require('path');
var containerPath = path.resolve('./');
module.exports = rmdir;
function rmdir(dirPath){
dirPath = path.resolve(containerPath,dirPath);
var dirs = [];
collection(dirPath,dirs);
dirs.forEach(function(v){
var status = fs.rmdirSync(v);
if(status){
console.log(status);
}
});
}
function collection(url,dirs){
var stat = fs.statSync(url);
if(stat.isDirectory()){
dirs.unshift(url);
recursion(url,dirs);
}else{
if(stat.isFile()){
fs.unlinkSync(url);
}
}
}
function recursion(url,dirs){
var arr = fs.readdirSync(url);
var i = 0;
var le = arr.length;
for(;i<le;i++){
var v = path.resolve(url,arr[i]);
collection(v,dirs);
}
}
運行腳手架Demo
你可以下載腳手架項目跑一跑: https://github.com/sapling-team/generator-sapling-pc
最終的發布可能還是需要gulp的一些輔助,不過這不要緊了,它只是幫助我們挪動了一些文件,最終成為了一個 www 目錄。