修改增長了demo地址 gulp-webpack-demojavascript
以前在使用gulp和webpack對項目進行構建的時候遇到了一些問題,最終算是搭建了一套比較完整的解決方案,接下來這篇文章以一個實際項目爲例子,講解多頁面項目中如何利用gulp和webpack進行工程化構建。本文是本身的實踐經驗,因此有些解決方案並非最優的,仍在探索優化中。因此有什麼錯誤疏漏請隨時指出。css
使用gulp過程當中的一些問題,我已經在另一篇文章講到了 grunt or gulphtml
如今爲何又整了一個webpack進來呢?前端
咱們知道webpack近來都比較火,那他火的緣由是什麼,有什麼特別屌的功能嗎?帶着這些疑問,繼續看下去。java
在使用gulp進行項目構建的時候,咱們一開始的策略是將全部js打包爲一個文件,全部css打包爲一個文件。而後每一個頁面都將只加載一個js和一個css,也就是咱們一般所說的 ==all in one== 打包模式。這樣作的目的就是減小http請求。這個方案對於簡單的前端項目來講的是一個萬金油。由於一般頁面依賴的js,css並不會太大,經過壓縮和gzip等方法更加減少了文件的體積。在項目最開始的一段時間內(幾個月甚至更長),一個前端團隊都能經過這種辦法達到以不變應萬變的效果。node
然而,做爲一個有追求(愛折騰)的前端,難道就知足於此嗎?react
媽媽說我不只要請求合併,還要按需加載,我要模塊化開發,還要自動監聽文件更新,支持圖片自動合併....jquery
等等!你真的須要這些功能嗎?是項目真的遇到了性能問題?否則你整這些幹嗎?webpack
對於pc端應用來講,性能每每不是最突出的問題,由於pc端的網速,瀏覽器性能都有比較好,因此很長一段時間咱們要考慮的是開發效率的問題而不是性能問題,得在前端框架的選型上下功夫。至於加載文件的大小或文件個數,都難以造成性能瓶頸。git
對於wap端來講,限制於手機的慢網速(仍然有不少用不上4g,wifi的人),對網站的性能要求就比較苛刻了,這時候就不只僅要考慮開發效率的問題了。(移動網絡的性能問題可參考《web性能權威指南》)
在《高性能網站建設進階指南》中也講到:不要過早地考慮網站的性能問題。
這點我有不同的見解。若是咱們在項目搭建的時候就能考慮得多一點,把基本能作的先作了。所花的成本絕對比之後去重構代碼的成本要低不少,並且咱們可以同時保證開發效率和網站性能,何樂而不爲呢。
居然要作,那要作到什麼程度呢,每每「度」是最難把握的東西。
之前在作wap網站的時候,遇到的最大的問題按需加載和請求合併的權衡。
經過純前端的方法不能同時知足請求合併和按需加載,這裏面的原理和難點已經有大牛講得很清楚了 前端工程與模塊化框架
實現的方法概括起來主要有如下步驟:
經過工具分析出前端靜態文件依賴表
頁面經過模塊化工具加載入口文件,並將所依賴的全部文件合併爲combo請求。
後端返回combo文件,瀏覽器將模塊緩存起來,跳頁面的時候執行步驟2,只請求沒有緩存過的文件。
如此經過依賴分析和後端combo實現了按需加載和請求合併。
這種實現方式的缺陷就是須要後端的支持,若是前端團隊自己不是本身實現的後端路由層,須要後端同窗加以配合,就須要更多溝通成本。
在沒有後端支持的狀況下,很難實現按需加載和請求合併。
針對這個問題webpack有沒有什麼解決方案?而webpack和gulp又是怎麼協做的呢?請看下去。
webpack能夠說是一個大而全的前端構建工具。它實現了模塊化開發和靜態文件處理兩大問題。
以往咱們要在項目中支持模塊化開發,須要引入requirejs,seajs等模塊加載框架。而webpack天生支持AMD,CommonJS, ES6 module等模塊規範。不用思考加載器的選型,能夠直接像寫nodejs同樣寫模塊。
而webpack這種萬物皆模塊的思想好像就是爲React而生的,在React組件中能夠直接引入css或圖片,而作到這一切只須要一個require語句和loader的配置。
webpack的功能之多和繁雜的配置項會讓初學者感到眼花繚亂,網上的不少資料也是隻介紹功能不教人實用技巧。這裏有一篇文章就講解了webpack開發的workflow, 雖然該教程是基於React的,可是比較完整地講了webpack的開發流程。下面我也用一個實例講解使用中遇到的問題和解決方案。
咱們的項目是一個多頁面項目,即每一個頁面爲一個html,訪問不一樣的頁面須要跳轉連接。
項目目錄結構大概是這樣的,app放html文件,css爲樣式文件,images存放圖片,js下有不一樣的文件夾,裏面的子文件夾爲一些核心文件和一些庫文件,ui組件。js的根目錄爲頁面入口文件。
├── app │ ├── header.inc │ ├── help-charge.inc │ ├── index.html │ ├── news-detail.html │ └── news-list.html ├── css │ ├── icon.less │ └── slider.css ├── images └── js ├── core ├── lib ├── ui ├── news-detail.js ├── news-list.js └── main.js
該項目中咱們只用webpack處理js文件的合併壓縮。其餘任務交給gulp。關於多頁面項目和單頁面項目中js處理的差別請看這裏。
module.exports = { devtool: "source-map", //生成sourcemap,便於開發調試 entry: getEntry(), //獲取項目入口js文件 output: { path: path.join(__dirname, "dist/js/"), //文件輸出目錄 publicPath: "dist/js/", //用於配置文件發佈路徑,如CDN或本地服務器 filename: "[name].js", //根據入口文件輸出的對應多個文件名 }, module: { //各類加載器,即讓各類文件格式可用require引用 loaders: [ // { test: /\.css$/, loader: "style-loader!css-loader"}, // { test: /\.less$/, loader: "style-loader!csss-loader!less-loader"} ] }, resolve: { //配置別名,在項目中可縮減引用路徑 alias: { jquery: srcDir + "/js/lib/jquery.min.js", core: srcDir + "/js/core", ui: srcDir + "/js/ui" } }, plugins: [ //提供全局的變量,在模塊中使用無需用require引入 new webpack.ProvidePlugin({ jQuery: "jquery", $: "jquery", // nie: "nie" }), //將公共代碼抽離出來合併爲一個文件 new CommonsChunkPlugin('common.js'), //js文件的壓縮 new uglifyJsPlugin({ compress: { warnings: false } }) ] };
該配置方案的思路是每一個頁面一個入口文件,文件中能夠經過require引入其餘模塊,而這些模塊webpack會自動跟入口文件合併爲一個文件。經過getEntry獲取入口文件:
function getEntry() { var jsPath = path.resolve(srcDir, 'js'); var dirs = fs.readdirSync(jsPath); var matchs = [], files = {}; dirs.forEach(function (item) { matchs = item.match(/(.+)\.js$/); if (matchs) { files[matchs[1]] = path.resolve(srcDir, 'js', item); } }); return files; }
該方法將生成文件名到文件絕對路徑的map, 好比
entry:{ news-detail: /../Document/project/.../news-detail.js }
而後output就會在output.path路徑下生成[name].js,即news-detail.js,文件名保持相同。
module 的做用是添加loaders, 那loaders有什麼做用呢?
若是咱們想要在js文件中經過require引入模塊,好比css或image,那麼就須要在這裏配置加載器,這一點對於React來講至關方便,由於能夠在組件中使用模塊化CSS。而通常的項目中能夠不用到這個加載器。
resolve 中的alias能夠用於定義別名,用過seajs等模塊工具的都知道alias的做用,好比咱們在這裏定義了ui這個別名,那麼在模塊中想引用ui目錄下的文件,就能夠直接這樣寫
require('ui/dialog.js');
不用加上前面的更長的文件路徑。
plugin 用於引入一些插件,常見的有 這些
咱們這裏使用了CommonsChunkPlugin用於生成公用代碼,不僅能夠生成一個,還能根據不一樣頁面的文件關係,自由生成多個,例如:
var CommonsChunkPlugin = require("webpack/lib/optimize/CommonsChunkPlugin"); module.exports = { entry: { p1: "./page1", p2: "./page2", p3: "./page3", ap1: "./admin/page1", ap2: "./admin/page2" }, output: { filename: "[name].js" }, plugins: [ new CommonsChunkPlugin("admin-commons.js", ["ap1", "ap2"]), new CommonsChunkPlugin("commons.js", ["p1", "p2", "admin-commons.js"]) ] }; // 在不一樣頁面用<script>標籤引入以下js: // page1.html: commons.js, p1.js // page2.html: commons.js, p2.js // page3.html: p3.js // admin-page1.html: commons.js, admin-commons.js, ap1.js // admin-page2.html: commons.js, admin-commons.js, ap2.js
這種用法有點像gulp或grunt中手動將多個js合併爲common, 可是在webpack裏,這個過程是全自動生成的,不用咱們本身分析代碼的依賴關係。可是這種按需加載的弊端也十分明顯,須要人工配置須要提取的文件。
另一個插件是uglifyJsPlugin,用於壓縮js代碼。
咱們還用到一個字段是 devtool, 用於配置開發工具。‘source-map’就是在生成的代碼中加入sourceMap的支持。可以直接定位到出錯代碼的具體位置,對sourcemap的使用和原理還不瞭解的能夠看下這篇文章。
另外,devtool的配置參數使用在這裏。
在pc開發中咱們一般會用到jQuery庫。如何很好地處理這類文件呢?這裏有兩種辦法。
方法一 是在html中用script標籤引入js文件,如
<script src="https://code.jquery.com/jquery-git2.min.js"></script>
而後再配置文件中添加externals
externals: { jquery: "jQuery" }
該字段的做用是將加jQuery全局變量變爲模塊可引入。而後在各個模塊中,就能夠以下使用:
var $ = require("jquery");
我我的以爲既然已經將加jQuery經過script引入了,那麼就直接使用$標籤就好了。沒必要再將其轉化爲模塊。
方法二 是將jQuery代碼保存到本地,在配置文件中添加:
resolve: { alias: { jquery: "/path/to/jquery-git2.min.js" } }
即爲jquery添加了別名,而後在模塊中也是這樣使用:
var $ = require("jquery");
還能夠配合使用ProvidePlugin,其做用是提供全局變量給每一個模塊,這樣就不須要在模塊中經過require引入,例如:
使用前:
var _ = require("underscore"); _.size(...);
使用後:
plugins: [ new webpack.ProvidePlugin({ "_": "underscore" }) ] // If you use "_", underscore is automatically required _.size(...)
總的來講,若是文件來自CDN,那麼使用方法一,若是文件在本地,則用方法二。
首先確定要安裝webpack-dev-server,安裝方法請自行腦補。
接着在webpack.config.js中添加配置
entry: [ 'webpack-dev-server/client?http://0.0.0.0:9090',//資源服務器地址 'webpack/hot/only-dev-server', './static/js/entry.js' ]
output的發佈路徑改成本地服務器
output: { publicPath: "http://127.0.0.1:9090/static/dist/", path: './static/dist/', filename: "bundle.js" }
在plugin中添加
new webpack.HotModuleReplacementPlugin()
html中經過資源服務器的絕對路徑引入js
<script src="http://127.0.0.1:9090/static/dist/bundle.js"></script>
最後經過命令行啓動
$ webpack-dev-server --hot --inline
配置參數的解釋在這裏。
因爲webpack服務器配置比較繁瑣,因此咱們的項目仍是採用gulp來啓動本地服務器...
目前來講,咱們只利用webpack進行了js方面的打包,其餘功能用gulp就足夠了。gulp主要作了下面幾個工做:
css轉化合並壓縮
圖片的雪碧圖合併和base64
文件md5計算與替換
熱啓動,瀏覽器自動刷新
下列是依賴的npm模塊:
"devDependencies": { "gulp": "^3.8.10", "gulp-clean": "0.3.1", "gulp-concat": "2.6.0", "gulp-connect": "2.2.0", "gulp-css-base64": "^1.3.2", "gulp-css-spriter": "^0.3.3", "gulp-cssmin": "0.1.7", "gulp-file-include": "0.13.7", "gulp-less": "3.0.3", "gulp-md5-plus": "0.1.8", "gulp-open": "1.0.0", "gulp-uglify": "1.4.2", "gulp-util": "~2.2.9", "gulp-watch": "4.1.0", "webpack": "~1.0.0-beta6" },
我對gulp-css-spriter和gulp-css-base64的源碼作了一點修改,使其支持下面的語法:
.icon_corner_new{ background-image: url(../images/new-ico.png?__sprite); }
若是在url的後面加上__sprite後綴,則插件將會把該圖片合併到雪碧圖裏。能夠支持一個css文件合併爲一個雪碧圖,也能夠整站合併。
.icon_corner_new{ background-image: url(../images/new-ico.png?__inline); }
若是加上後綴__inline,則會將圖片轉化爲base64,直接添加到css文件中,對於幾k的小文件能夠直接使用inline操做。具體配置代碼以下:
gulp.task('sprite', function (done) { var timestamp = +new Date(); gulp.src('dist/css/style.min.css') .pipe(spriter({ spriteSheet: 'dist/images/spritesheet' + timestamp + '.png', pathToSpriteSheetFromCSS: '../images/spritesheet' + timestamp + '.png', spritesmithOptions: { padding: 10 } })) .pipe(base64()) // .pipe(cssmin()) .pipe(gulp.dest('dist/css')) .on('end', done); });
src爲須要處理的css文件,spriteSheet爲雪碧圖生成的目標文件夾,pathToSpriteSheetFromCSS爲css文件中url的替換字符串,spritesmithOptions是生成雪碧圖的間隙。
發版本的時候爲了不瀏覽器讀取了舊的緩存文件,須要爲其添加md5戳。
這裏採用了gulp-md5-plus
gulp.task('md5:js', function (done) { gulp.src('dist/js/*.js') .pipe(md5(10, 'dist/app/*.html')) .pipe(gulp.dest('dist/js')) .on('end', done); });
該代碼會將dist/js下面全部的js計算md5戳,並將dist/app/下的html中script中的src引用文件名替換爲加了md5的文件名,再將md5文件替換到目標目錄dist/js。css的md5操做跟js無異。
關於服務器啓動和代碼轉換的功能點,這裏就不展開講了。
該方案總結下來作了下面幾件事:
將css直接合併爲一個文件,在head中經過link標籤引入,提升網頁渲染速度。
將js打包爲不一樣的入口文件,並自動合併依賴關係。將跨頁面的公用代碼抽離爲獨立文件,益於瀏覽器緩存。
增長圖片雪碧圖,base64的支持,開發者能夠手動配置__sprite和__inline,靈活性較高。
靜態文件md5打包,並自動更改html引用路徑,方便發佈。
提供開發調試所須要的環境,包括熱啓動,瀏覽器自動刷新,sourceMap。
該方案之因此針對多頁面應用,區別在於對js和css的處理方式。在單頁面應用中,經過哈希跳轉來實現靜態文件的異步加載,打包策略又有所不一樣。但webpack中已經提供了處理異步加載的接口require.ensure,能夠發揮無窮的力量。
基於以上理論的一個demo, 主要是提供一些思路。地址:gulp-webpack-demo