一、全面理解webpack
1、什么是 webpack?
webpack是近期最火的一款模塊加載器兼打包工具,它能把各種資源,例如JS(含JSX)、coffee、樣式(含less/sass)、圖片等都作為模塊來使用和處理,它能有Grunt或Gulp所有基本功能。
webpack的官網是 https://webpack.github.io/
,文檔地址是https://webpack.github.io/docs,官網對webpack的定義是MODULE BUNDLER,他的目的就是把有依賴關系的各種文件打包成一系列的靜態資源。 請看下圖:

2、webpack 的優勢
其優勢主要可以歸類為如下幾個:
- webpack 是以 commonJS 的形式來書寫腳本滴,但對 AMD/CMD 的支持也很全面,方便舊項目進行代碼遷移。
- 支持很多模塊加載器的調用,可以使模塊加載器靈活定制,比如babel-loader加載器,該加載器能使我們使用ES6的語法來編寫代碼;less-loader加載器,可以將less編譯成css文件;
- 開發便捷,能替代部分 grunt/gulp 的工作,比如打包、壓縮混淆、圖片轉base64等。
- 可以通過配置打包成多個文件,有效的利用瀏覽器的緩存功能提升性能。
3、wepback它的目標是是什么?
webpack它能將依賴的模塊轉化成可以代表這些包的靜態文件
- 將依賴的模塊分片化,並且按需加載
- 解決大型項目初始化加載慢的問題
- 每一個靜態文件都可以看成一個模塊
- 可以整合第三方庫
- 能夠在大型項目中運用
- 可以自定義切割模塊的方式
4、webpack較之其他類似工具有什么不同?
- 有同步和異步兩種不同的加載方式
- Loader,加載器可以將其他資源整合到JS文件中,通過這種方式,可以講所有的源文件形成一個模塊
- 優秀的語法分析能力,支持 CommonJs AMD 規范
- 有豐富的開源插件庫,可以根據自己的需求自定義webpack的配置
5、webpack為什么要將所有資源放在一個文件里面?
我們知道,對於瀏覽器來說,加載的資源越少,響應的速度也就越快,所以有時候我們為了優化瀏覽器的性能,會盡可能的將資源合並到一個主文件app.js里面。但是這導致的很大的缺點:
- 當你的項目十分龐大的時候,不同的頁面不能做到按需加載,而是將所有的資源一並加載,耗費時間長,性能降低。
- 會導致依賴庫之間關系的混亂,特別是大型項目時,會變得難以維護和跟蹤。比如:哪些文件是需要A模塊加載完后才能執行的?哪些頁面會受到多個樣式表同時影響的? 等許多問題。
而webpack可以很好的解決以上缺點,因為它是一個十分聰明的模塊打包系統,當你正確配置后,它會比你想象中的更強大,更優秀。
二、開啟wbpack之旅
安裝步驟如下:
1、生成package.json文件;
先裝好node和npm,因為webpack是一個基於node的項目。然后首先我們需要在根目錄下生成package.json文件,需要進入項目文件內根目錄下執行如下命令:npm init

如上通過一問一答的方式后會在根目錄下生成package.json文件,如下所示:
2 . 通過全局安裝webpack
執行命令如下:npm install -g webpack 如下所示:

在c盤下會生成node_modules文件夾中會包含webpack,此時此刻我們可以使用webpack命令了;
在常規項目中把webpack依賴加入到package.json
npm init npm install webpack --save
更詳盡的安裝方法個可以參考
webpack安裝
3. 配置webpack
每個目錄下都必須有一個webpack.config.js,它的作用就好比Gulpfile.js、或者 Gruntfile.js,就是一個項目配置,告訴webpack需要做什么。
首先先貼上一個比較完整的webpack.config.js的代碼,再詳細介紹:
//詳細的webpack.config.js結構分析: var path = require('path'); var webpack = require('webpack'); var HtmlWebpackPlugin = require('html-webpack-plugin'); var TransferWebpackPlugin = require('transfer-webpack-plugin'); module.exports = { devtool: 'source-map',//由於打包后的代碼是合並以后的代碼,不利於排錯和定位,只需要在config中添加,這樣出錯以后就會采用source-map的形式直接顯示你出錯代碼的位置。 //noParse:[/jquery/],//表示跳過jquery,不對其進行編譯,這樣可以提高打包的速度 //頁面入口文件配置 entry: { page1: "./src/index.js", //page2: ["./src/index.js", "./src/main.js"],支持數組形式,將加載數組中的所有模塊,但以最后一個模塊作為輸出 }, //入口文件輸出配置 output: { path: "dist/js/page", filename: "[name].bundle.js",// page1.bundle.js 和 page2.bundle.js,並存放到 ./dist/js/page 文件夾下。 publicPath: "/dist/" //網站運行時的訪問路徑。 }, resolveLoader: { //指定默認的loader路徑,否則依賴走到上游會找不到loader root: path.join(__dirname, 'node_modules'), alias: {//給自己寫的loader設置別名 "seajs-loader": path.resolve( __dirname, "./web_modules/seajs-loader.js" ) } }, //新建一個開發服務器,並且當代碼更新的時候自動刷新瀏覽器。 devServer: { historyApiFallback: true, noInfo: true, hot: true, inline: true, progress: true, port:9090 //端口你可以自定義 }, module: { // module.loaders 是最關鍵的一塊配置。它告知 webpack每一種文件都需要使用什么加載器來處理: loaders: [ { test: /\.css$/, loader: 'style-loader!css-loader' },//.css 文件使用 style-loader 和 css-loader 來處理. //{ test: /\.css$/, loader: 'style!css' },其他寫法1、"-loader"其實是可以省略不寫的,多個loader之間用“!”連接起來。 //{ test: /\.css$/, loaders: ["style", "css"] },其他寫法2、用loaders數組形式; //.scss 文件使用 style-loader、css-loader 和 sass-loader 來編譯處理。 //在chrome中我們通過sourcemap可以直接調試less、sass源文件文件 { test: /\.scss$/, loader: 'style!css!sass?sourceMap'}, { test: /\.less$/, loader: 'style!css!less?sourceMap'},//.less 文件使用 style-loader、css-loader 和 less-loader 來編譯處理 //.js 文件使用babel-loader來編譯處理,設置exclude用來排除node_modules這個文件夾中的代碼 { test: /\.js$/, loader: 'babel!jsx',exclude: /node_modules/ }, { test: /\.jsx$/, loader: "jsx-loader?harmony" },//.jsx 文件使用 jsx-loader 來編譯處理 { test: /\.json$/,loader: 'json'}, //{ test: /\.(png|jpg|jpeg|gif)$/, loader: 'url-loader?limit=8192'}, //圖片文件使用 url-loader 來處理,小於8kb的直接轉為base64 {test: /\.(png|jpg|gif|svg)$/,loader: 'url', query: {limit: 10000,name: '[name].[ext]?[hash]'}//設置圖片名稱擴展名 }, { test: /\.jade$/, loader: "jade-loader" },//.jade 文件使用 jade-loader 來編譯處理 { test: /\.ejs$/, loader: "ejs-loader" },//.ejs 文件使用 ejs-loader 來編譯處理 { test: /\.handlebars$/, loader: "handlebars-loader" },//.handlebars 文件使用handlebars-loader來編譯處理handlebars模板文件 { test: /\.dot$/, loader: "dot-loader" },//.dot 文件使用 dot-loader 來編譯處理dot模板文件 { test: /\.vue$/, loader: "vue-loader" },//.vue 文件使用 vue-loader 來編譯處理 { test: /\.coffee$/, loader: 'coffee-loader' },//.coffee 文件使用 coffee-loader 來編譯處理 { test: /\.html$/,loader: 'vue-html'}, { test: /\.woff$/,loader: "url?limit=10000&minetype=application/font-woff"}, { test: /\.ttf$/,loader: "file"}, { test: /\.eot$/,loader: "file"}, { test: /\.svg$/,loader: "file"} ] }, //分內置插件和外置插件 plugins: [ //使用了一個 CommonsChunkPlugin 的插件,它用於提取多個入口文件的公共腳本部分,然后生成一個common.js來方便多頁面之間進行復用。 new webpack.optimize.CommonsChunkPlugin('common.js'), new webpack.optimize.UglifyJsPlugin({//壓縮文件 compressor: { warnings: false,//supresses warnings, usually from module minification }, except: ['$super', '$', 'exports', 'require'] //排除關鍵字(可選) }), new webpack.DefinePlugin({// definePlugin 接收字符串插入到代碼當中, 所以你需要的話可以寫上 JS 的字符串 __DEV__: JSON.stringify(JSON.parse(process.env.BUILD_DEV || 'true')), __PRERELEASE__: JSON.stringify(JSON.parse(process.env.BUILD_PRERELEASE || 'false')) }), new webpack.ProvidePlugin({//把一個全局變量插入到所有的代碼中,支持jQuery plugin的使用;使用ProvidePlugin加載使用頻率高的模塊 //provide $, jQuery and window.jQuery to every script $: "jquery", jQuery: "jquery", "window.jQuery": "jquery" }), new webpack.NoErrorsPlugin(), //允許錯誤不打斷程序 new TransferWebpackPlugin([ //把指定文件夾下的文件復制到指定的目錄 {from: 'www'} ], path.resolve(__dirname,"src")), new HtmlwebpackPlugin({//用於生產符合要求的html文件; title: 'Hello World app', filename: 'assets/admin.html' }) ], //其它解決方案配置 resolve: { root: 'E:/github/flux-example/src', //絕對路徑, 查找module的話從這里開始查找(可選) extensions: ['', '.js', '.html', '.css', '.scss'], //自動擴展文件后綴名,意味着我們require模塊可以省略不寫后綴名 alias: { //模塊別名定義,方便后續直接引用別名,無須多寫長長的地址//后續直接 require('AppStore') 即可 AppStore : 'js/stores/AppStores.js', ActionType : 'js/actions/ActionType.js', AppAction : 'js/actions/AppAction.js' }, modulesDirectories: [//取相對路徑,所以比起 root ,所以會多很多路徑。查找module(可選) 'node_modules', 'bower_components', 'lib', 'src' ] } }; if (process.env.NODE_ENV === 'production') { module.exports.devtool = '#source-map' // http://vue-loader.vuejs.org/en/workflow/production.html module.exports.plugins = (module.exports.plugins || []).concat([ new webpack.DefinePlugin({ 'process.env': { NODE_ENV: '"production"' } }), new webpack.optimize.UglifyJsPlugin({ compress: { warnings: false } }), //為組件分配ID,通過這個插件webpack可以分析和優先考慮使用最多的模塊,並為它們分配最小的ID new webpack.optimize.OccurenceOrderPlugin() ]) }
plugins中包含很多的內置插件和外部插件,它們都有各自的功能,用來處理相關的文件,這里只是羅列了部分,具體用法請看webpack入門和實戰(二):全面理解和運用plugins和loader。
就像我在前面提到的,webpack.config.js的寫法和在Node里的寫法相同,我們主要看的就是文件中的module.exports里面的內容
- entry 是指入口文件的配置項,它是一個數組的原因是webpack允許多個入口點。
- output是指輸出文件的配置項
- path - 表示輸出文件的路徑
- filename - 表示輸出文件的文件名
- plugins 顧名思義,使用插件可以給webpack添加更多的功能,使webpack更加的靈活和強大,webpack有兩種類型的插件:
- webpack內置的插件
// 首先要先安裝webpack模塊
var webpack = require("webpack"); module.exports = { new webpack.optimize.UglifyJsPlugin({ compressor: { warnings: false, }, }) };
-
- webpack外置插件
//npm install component-webpack-plugin 先要在安裝該模版 var ComponentPlugin = require("component-webpack-plugin"); module.exports = { plugins: [ new ComponentPlugin() ] }
更多的插件以及插件的用法,大家可以到webpack的插件上查看。
- module 配置處理文件的選項
- loaders 一個含有wepback中能處理不同文件的加載器的數組
- test 用來匹配相對應文件的正則表達式
- loaders 告訴webpack要利用哪種加載器來處理test所匹配的文件
- loaders 的安裝方法
$ npm install xxx-loader --save-dev
- resolve:其它解決方案配置;
-
- resolve.root,絕對路徑, 查找module的話從這里開始查找(可選)
- resolve.modulesDirectories,取相對路徑,所以比起 root ,所以會多 parse 很多路徑。查找module(可選)
- resolve.extensions,自動擴展文件后綴名,意味着我們require模塊可以省略不寫后綴名
- resolve.alias,模塊別名定義,方便后續直接引用別名,無須多寫長長的地址
三、利用webpack實現在頁面上合理使用打包過后的js文件和圖片
示例如下:
webpack_test目錄結構如下:


index.html代碼如下:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>demo1</title> </head> <body> <div id="content"></div> <img src="./build/img/demo.png"> <script src="./build/js/index.js"></script> </body> </html>
index.js代碼如下:
require('./index.css');
index.css代碼如下:
#content{ width:121px; height:140px; background-color: red; }
demo.png自己隨便找一張即可;
根據webpack.config.js的配置情況,操作步驟如下:
- 全局安裝webpack,npm install webpack -g
- 進入到webpack_test目錄下,初始化生成package.json文件,npm init
- 需要安裝的loader有css-loader、style-loader、url-loader,webpack, npm install css-loader style-loader url-loader webpack--save-dev
- 執行webpack,生成打包過后的build/js/index.js,build/img/demo.png
- 在index.html中引入即可
效果如下:
源碼地址為:
http://download.csdn.net/detail/wdlhao/9612173,有需要的同學可以自行下載練習;
四、理解webpack支持commonJS和AMD/CMD兩種模塊機制進行打包
1.AMD/CMD模式:
AMD 規范在這里:
https://github.com/amdjs/amdjs-api/wiki/AMD,CMD 規范在這里:
https://github.com/seajs/seajs/issues/242
- AMD(Asynchronous Module Definition) 是 RequireJS 在推廣過程中對模塊定義的規范化產出。(即RequireJS模塊規范)
RequireJS是一個工具庫,主要用於客戶端的模塊管理。它可以讓客戶端的代碼分成一個個模塊,實現異步或動態加載,從而提高代碼的性能和可維護性。它的模塊管理遵守 AMD規范(Asynchronous Module Definition)。RequireJS的基本思想是,通過define方法,將代碼定義為模塊;通過require方法,實現代碼的模塊加載。首先,將require.js嵌入網頁,然后就能在網頁中進行模塊化編程了。<script data-main="scripts/main" src="scripts/require.js"></script>上面代碼的data-main屬性不可省略,用於指定主代碼所在的腳本文件,在上例中為scripts子目錄下的main.js文件。用戶自定義的代碼就放在這個main.js文件中。
- CMD(Common Module Definition )是 SeaJS 在推廣過程中對模塊定義的規范化產出。(即SeaJS模塊規范)
SeaJS是一個遵循CMD規范的JavaScript模塊加載框架,可以實現JavaScript的模塊化開發及加載機制。
- CommonJS Modules/2.0 規范,是 BravoJS 在推廣過程中對模塊定義的規范化產出。
CommonJS API定義很多普通應用程序(主要指非瀏覽器的應用)使用的API,從而填補了這個空白。它的終極目標是提供一個類似Python,Ruby和Java標准庫。這樣的話,開發者可以使用CommonJS API編寫應用程序,然后這些應用可以運行在不同的JavaScript解釋器和不同的主機環境中。在兼容CommonJS的系統中,你可以實用JavaScript程序開發:
- 服務器端JavaScript應用程序
- 命令行工具
- 圖形界面應用程序
- 混合應用程序(如,Titanium或Adobe AIR)
還有不少⋯⋯這些規范的目的都是為了 JavaScript 的模塊化開發,特別是在瀏覽器端的。目前這些規范的實現都能達成瀏覽器端模塊化開發的目的。
2、AMD/CMD模式區別
2.1從官方推薦的寫法上面得出:
CMD ----- 依賴就近
Js代碼 //CMD define(function(require,exports,module){ var a = require('./a'); a.doSomthing(); });
AMD ----- 依賴前置
Js代碼 //AMD define(['./a','./b'],function(a,b){ //...... a.doSomthing(); //...... b.doSomthing(); })
當然AMD也支持CMD的寫法。
2.2、執行順序上:
-
- CMD是延遲執行,推崇的是as lazy as possible
- AMD是提前執行,requireJS從2.0開始可以延遲執行
2.3、api設計角度:
-
- CMD的API推崇職責單一,沒有全局的require
-
- AMD的API默認是一個當多個用:比如require有全局的和局部的