帶你瞭解webpack

1. 前端工程化項目打包歷史

前端工程化以前的時代略過css

1. 半自動執行腳原本壓縮合並文件

自從xmlhttprequest被挖掘出來,網頁可以和服務端通信,js能作的事愈來愈多,文件體積愈來愈大,互相引用原來越多。
然而網速只有幾兆的帶寬。因而想到,要把js文件狠狠的壓縮,能合併的就給合併起來html

主要有的js壓縮工具:前端

JSMin 使用簡單、靈活,對不一樣語言、環境支持好。通常配合不一樣的環境、語言用命令行執行壓縮
YUI Compressor 雅虎推出。有JAVA版本和.NET版本,需二者環境配合
UglifyJS 基於nodejs,壓縮策略較爲安全,因此不會對源代碼進行大幅度的改造。
Closure Compiler 谷歌出品

在我理解,壓縮主要作了局部變量命名簡化、空格/換行/註釋消除、自動優化可簡化的語法等工做。vue

使用壓縮工具用es6語法寫的js在壓縮測試比較中:node

  1. UglifyJS壓縮率高,能夠自動格式化、優化代碼。因此普及率高。如今也是主流的工具
  2. YUI compressor 好處就是壓縮策略安全,相比UglifyJS,自動優化代碼的程度較保守
  3. Closure Compiler 的Advanced模式直接破壞代碼的結構,bug多

壓縮有了,可是當a文件和b文件都引用了c文件的方法時,若是把c文件分別和a、b合併,這樣就只有兩個文件了。這就是最開始的合併方式。
通常是經過在windows上用bat腳本或者mac/linux上的shell腳原本決定合併哪些文件、用什麼工具壓縮、怎麼壓縮linux

  • 進步:
  1. 解決當時網速廣泛較慢的狀況下網頁加載資源較慢的問題
  2. 代碼混淆了不容易被盜用
  • 存在的問題:
  1. 工程化的項目裏相互依賴關係變得很是複雜
  2. 合併的文件裏可能會有不少無用代碼

2. 自動構建的嘗試階段

經過腳本構建的項目裏的文件相互依賴複雜,命名什麼的徹底有可能一不當心就衝突了,並且依賴多是一層一層依賴下去的,維護起來要命呀。
因此先解決js相互依賴的問題webpack

1. js依賴關係的規範探索
CommonJS規範解決了js模塊化依賴的問題
CommonJs規範的簡單介紹(nodejs版)
  1. 一個文件就是一個模塊
  2. 每一個模塊內部,都有一個module對象,表明當前模塊,有規定好的一些默認屬性
  3. module.exports屬性:初始值是一個空對象{},這個變量把定義的變量、方法暴露出去
  4. exports變量:Node爲每一個模塊提供一個exports變量,指向module.exports。至於二者的區別,不用去了解,日常就用module.exports
  5. require引用:把module.exports定義的變量/方法來過來用
  6. 注意:它是同步的

既然CommonJS提供了模塊化的思路,也已經在服務端(nodejs)裏大展手腳。那麼瀏覽器裏可不可行?
瀏覽器不兼容CommonJS的緣由首先是,缺乏了4個NodeJs環境變量:git

  • module
  • exports
  • require
  • global

那麼只須要提供了這些個環境變量就好了吧。Browerify就是作這的es6

Browserify 是目前最經常使用的 CommonJS 格式轉換的工具

Browserify的核心思路是講module暴露出的模塊放入一個數組,require時根據模塊id找到相應的module執行,總之就是給上面缺乏的變量寫成可執行的es5的策略github

那麼是否是這樣就能在瀏覽器上愉快使用CommonJS?

CommoJS是同步require的方式獲取js模塊,在瀏覽器上會阻斷主線程。頁面會因加載js可能卡住

這確定是不能容忍的

因而AMD(異步模塊定義)誕生
AMD也採用require()語句加載模塊,可是要傳兩個參數require([module], callback)。是的,回調思路

AMD規範的簡單介紹(RequireJS)
  1. 解決兩個問題:

(1)實現js文件的異步加載,避免網頁失去響應
(2)管理模塊之間的依賴性,便於代碼的編寫和維護

  1. 模塊必須採用特定的define()函數來定義
  2. 非AMD的第三方庫加載以前要用require.config()定義固有特徵

CMD規範 和AMD大同小異,具體實現是seajs。沒用過,應該都差很少吧,啊哈哈

2. html/css模塊化的規範

less,sass,stylus 的 css 預處理器簡化css語法
ejs,jade 等html的模板語法

這些真的是前端狗的福音,很少說,css-next來了,繼續啃咯。

這樣html/css/js 就都有了適合自動構建的擴展結構。可是這時候寫一個構建這些依賴的命令太長太複雜,因此打包工具開始流行:

3.Grunt/Gulp 流處理構建工具讓前端構建更容易

grunt 寫法簡單,插件還賊多
gulp 效率更高,可擴展性更強

nodejs配合這倆大佬作web項目的自動化構建用着都挺爽的

var gulp = require('gulp')
var nodemon = require('gulp-nodemon')
var browserSync = require('browser-sync').create()

gulp.task('nodemon', function(cb) {
    var started = false
    return nodemon({
        script: 'mswadmin.js'
        , ext: 'js'
        , env: { 'NODE_ENV': 'default' }  
    }).on('start', function() {
      if (!started) {
          cb();
        started = true;
      }
    })
});
gulp.task('serve', function(){
    browserSync.init({
    proxy: 'http://10.3.10.27:18282',
    browser: 'chrome',
    port: 18282
  })
    gulp.watch('static/**/*.+(scss|jade|ls)', ['inject'])
    .on('change', browserSync.reload);
})
gulp.task('default', ['nodemon','serve']);

上面是一個用nodemon監控本地服務+watch代碼熱更新的配置。能夠看出,以流任務的方式一個個執行。用起來也簡單

2. SPA(Single-page application)來了

js 對應的 AMD 模塊,而後該 AMD 模塊渲染對應的 html 到容器內

這樣網頁再也不是傳統的文檔一類的頁面了。而是更像一個完整的程序。一個主入口,js完成的前端路由,AMD模塊完成頁面內從新渲染。
雖然是作出來這個SPA了,可是小問題多:

  1. 不少成熟的第三方庫不支持AMD規範,引用起來賊麻煩
  2. RequireJS在加載html依賴時,html裏的img路徑要使用絕對路徑
  3. 只能一次性加載全部css文件
  4. 分模塊打包js文件時的通用依賴項很難配置
  5. 最重要的,AMD/CMD CommonJS規範太多形成不少第三方庫對規範支出不夠。。。並且ES6規範都要普及了,你不用???

3. webpack來解救你

首先,webpack是靜態模塊打包器(bundler),grunt/gulp是流任務執行器。
區分二者能夠用grunt-webpack形象說明:你能夠將 webpack 或 webpack-dev-server 做爲一項任務(task)執行

webpack爲啥好用:

  1. webpack 可以爲ES6的 import/export 提供開箱即用般的支持
  2. 還支持CommonJS CMD/AMD模塊規範,作到隨時可用

這兩點是我以爲最突出的地方,詳細對比請參考對比

瀏覽器環境下,用了ES6規範的話,你應該不想用其餘的了

webpack的工做步驟以下:

  1. 從入口文件開始遞歸地創建一個依賴關係圖。
  2. 把全部文件都轉化成模塊函數。
  3. 根據依賴關係,按照配置文件把模塊函數分組打包成若干個bundle。
  4. 經過script標籤把打包的bundle注入到html中,經過manifest文件來管理bundle文件的運行和加載。

打包的規則爲:一個入口文件對應一個bundle。該bundle包括入口文件模塊和其依賴的模塊。按需加載的模塊或需單獨加載的模塊則分開打包成其餘的bundle。

除了這些bundle外,還有一個特別重要的bundle,就是manifest.bundle.js文件,即webpackBootstrap。這個manifest文件是最早加載的,負責解析webpack打包的其餘bundle文件,使其按要求進行加載和執行。
不管你選擇哪一種模塊語法,那些 import 或 require 語句如今都已經轉換爲 webpack_require 方法,此方法指向模塊標識符(module identifier)。經過使用 manifest 中的數據,runtime 將可以查詢模塊標識符,檢索出背後對應的模塊。

webpack 怎麼入門

雖然網上有不少 十分鐘入門webpack 的教程。但仍是推薦去擼一遍webpack官方指南

我的以爲指南里你要注意的細節:

  1. webpack 不會更改代碼中除 import 和 export 語句之外的部分。若是你在使用其它 ES2015 特性,請確保你在 webpack 的 loader 系統中使用了一個像是 Babel 或 Bublé 的轉譯器
  2. npm腳本運行時默承認以使用npx命令
  3. source map要合理使用
  4. 留意webpack-dev-middleware,配合express作服務端渲染要用到哦
  5. HMR(模塊熱替代)通常用你選用的框架自帶的loader(vue-loader)
  6. 用UglifyJsPlugin插件自動移除 JavaScript 上下文中的未引用代碼(dead-code)。webpack4裏使用 mode=production 替代。要結合SideEffects使用,webpack4又提供了SideEffects插件使用的方式
  7. process.env.NODE_ENV === 'production' ? '[name].[hash].bundle.js' : '[name].bundle.js' 這樣的條件語句在配置文件裏沒法使用,用if/else
  8. splitChunks優化,webpack4已經移除了CommonsChunkPlugin。下文會詳細解釋
  9. dynamic imports(動態導入)優化,chunkFilename決定非入口 chunk 的名稱,vue裏的運用實例就是路由懶加載(vue-lazyload),生成了新的bundle

4. webpack的優化點的補充說明

  1. 動態導入在vue裏的時間注意點:

webpack 可使用dynamic imports的方式引用模塊,咱們使用 async/ await 和 dynamic import 來實現。每個dynamic import都將做爲一個單獨的chunk打包。在vue中的一個例子就是路由懶加載+babel-plugin-dynamic-import-node的構建方案。使用babel-plugin-dynamic-import-node是由於開發環境下觸發熱更新很慢,這個插件講import異步所有改爲require同步

  1. 打包生成的文件模塊標識符的問題

通常來講咱們在dist生成了一下三種bundle

main bundle 會隨着自身的新增內容的修改,而發生變化。
vendor bundle 會隨着自身的 module.id 的修改,而發生變化。
manifest bundle 會由於當前包含一個新模塊的引用,而發生變化。

然而咱們並不但願vendor每次構建都生成新的hash,畢竟咱們但願用到緩存的。解決方法官方有兩個插件NamedModulesPlugin和HashedModuleIdsPlugin
vue裏使用的是HashedModuleIdsPlugin

相信不少人從webpack3升級到4會碰到問題,接下來
### 5. 升級到webpack4你該搞明白

1. 零配置的概念把配置門檻下降了

主要使用了模式的概念。

development 模式下,默認開啓了NamedChunksPlugin 和NamedModulesPlugin方便調試,提供了更完整的錯誤信息,更快的從新編譯的速度
production 模式下,因爲提供了splitChunks和minimizer,因此基本零配置,代碼就會自動分割、壓縮、優化,同時 webpack 也會自動幫你 Scope hoisting(做用域提高) 和 Tree-shaking

至關於把一些基本的配置當成默認配置。只須要在命令行運行時帶上mode參數就搞定
#### 2. 一些插件的廢除和替換

廢棄了 頂替者(用optimization屬性) 變化
uglifyjs-webpack-plugin minimizer 壓縮優化
CommonsChunkPlugin splitChunks 代碼分割,下面詳解

還有一些新的插件:Tree Shaking,SideEffects。我還不知道怎麼用--

3. 要注意的新的優化點

  1. extract-text-webpack-plugin -> mini-css-extract-plugin

它與extract-text-webpack-plugin最大的區別是:它在code spliting的時候會將原先內聯寫在每個 js chunk bundle的 css,單獨拆成了一個個 css 文件。js變得更乾淨了,css是根據optimization.splitChunks的配置自動拆分css文件爲單獨的模塊的規則拆分的,不用擔憂過多的httlp資源請求問題

  1. 全部的[chunkhash] ->[contenthash]

這是爲了解決當css與js文件有依賴時,二者有相同的chunkhash。這樣js修改了,css沒改的狀況下chunkhash頁被修改了,無法緩存了呀
contenthash 你能夠簡單理解爲是 moduleId + content 所生成的 hash
相關issue

  1. 代碼的壓縮優化改爲了optimization.minimizer

在optimization.minimizer裏推薦使用optimize-css-assets-webpack-plugin直接配置。可是vue-cli3裏的配置本身配的。嗯...反正也不想看那些配置,就這樣吧~~~

4. 第三方庫和業務代碼分開打包策略

上面多處提到了這個optimization.splitChunks

Webpack 4 最大的改進即是Code Splitting chunk。webpack3是經過CommonsChunkPlugin拆分的。而後如今直接被廢棄了,我能怎麼辦?,跟着學唄。

開啓Code Splitting很簡單,使用production的mode就行,會自動開啓。並有一個設置好了的一個很合理的配置

若是同時知足下列條件,chunk 就會被拆分:

  • 新的 chunk 能被複用,或者模塊是來自 node_modules 目錄
  • 新的 chunk 大於 30Kb(min+gz 壓縮前)
  • 按需加載 chunk 的併發請求數量小於等於 5 個
  • 頁面初始加載時的併發請求數量小於等於 3 個

默認配置已經很合理了,然而當出現以下狀況:
已vue-cli建立的項目爲例。項目用到了第三方的UI組件庫,在main.js入口處依賴了第三方庫。
由於在入口引入了,因此第三方庫會被打包進app.js。這樣,只要我修改了app.js裏的其餘代碼,打出來的包的hash就變了。瀏覽器又得再次緩存app.js。第三庫至關於又被緩存了一次,這顯然不是咱們想要的。

看一下花褲衩的配置

splitChunks: {
  chunks: "all",
  cacheGroups: {
    libs: {
      name: "chunk-libs",
      test: /[\\/]node_modules[\\/]/,
      priority: 10,
      chunks: "initial" // 只打包初始時依賴的第三方
    },
    elementUI: {
      name: "chunk-elementUI", // 單獨將 elementUI 拆包
      priority: 20, // 權重要大於 libs 和 app 否則會被打包進 libs 或者 app
      test: /[\\/]node_modules[\\/]element-ui[\\/]/
    },
    commons: {
      name: "chunk-commons",
      test: resolve("src/components"), // 可自定義拓展你的規則
      minChunks: 2, // 最小共用次數
      priority: 5,
      reuseExistingChunk: true
    }
  }
};

主要思路就是

  1. 把初始化時依賴的第三方打包成基礎類庫,這一類改動小,又被全局須要
  2. 把相似elementUI這一類的比較大、改動較小的抽出來
  3. 全局公用的router、函數、svg圖標、layout佈局組件等這些無論,直接扔app.js
  4. 業務裏會常用但沒在main.js引入的的components被打包成一個common
  5. 業務裏常用可是體積至關較小,就直接在main.js引入,打包進app.js
  6. 其餘的低頻使用的組件會自動按默認splitChunks的設置來拆分

提醒: 代碼的拆分必定要結合項目的實際狀況,好比你就用到element裏的一兩個組件,徹底能夠按需加載在main.js,而後直接打包進app.js。因此沒有最合理的拆分規則,只有最適合你的。

5. Prefetching/Preloading modules

支持了Prefetching/Preloading瀏覽器資源加載優化
核心思想是減小JS下載時間
學不動了學不動了,先緩緩

6. 最後推薦http://webpack.wuhaolin.cn/

相關文章
相關標籤/搜索