「前端」看懂前端腳手架你須要這篇webpack

本文來自尚妝前端團隊南洋html

發表於尚妝github博客,歡迎訂閱。前端

分割webpack配置文件的多種方法

(一)

將你的配置信息寫到多個分散的文件中去,而後在執行webpack的時候利用--config參數指定要加載的配置文件,配置文件利用moduleimports導出。你能夠在webpack/react-starter 看到是使用這種發方法的。vue

// webpack 配置文件

|-- webpack-dev-server.config.js
|-- webpack-hot-dev-server.config.js
|-- webpack-production.config.js
|-- webpack.config.js
// npm 命令

"scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "dev-server": "webpack-dev-server --config webpack-dev-server.config.js --progress --colors --port 2992 --inline",
    "hot-dev-server": "webpack-dev-server --config webpack-hot-dev-server.config.js --hot --progress --colors --port 2992 --inline",
    "build": "webpack --config webpack-production.config.js --progress --profile --colors"
  },

(二)

調用第三方的webpack工具,使用其集成的api,方便進行webpack配置。HenrikJoreteg/hjs-webpack 這個repo就是這麼作的。react

var getConfig = require('hjs-webpack')


module.exports = getConfig({
  // entry point for the app
  in: 'src/app.js',

  // Name or full path of output directory
  // commonly named `www` or `public`. This
  // is where your fully static site should
  // end up for simple deployment.
  out: 'public',

  // This will destroy and re-create your
  // `out` folder before building so you always
  // get a fresh folder. Usually you want this
  // but since it's destructive we make it
  // false by default
  clearBeforeBuild: true
})

(三) Scalable webpack configurations

ones that can be reused and combined with other partial configurationswebpack

在單個配置文件中維護配置,可是區分好條件分支。調用不一樣的npm命令時候設置不一樣的環境變量,而後在分支中匹配,返回咱們須要的配置文件。git

這樣作的好處能夠在一個文件中管理不一樣npm操做的邏輯,而且能夠共用相同的配置。webpack-merge這個模塊能夠起到合併配置的做用。github

const parts = require('./webpack-config/parts');

switch(process.env.npm_lifecycle_event) {
  case 'build': 
    config = merge(common, 
      parts.clean(PATHS.build),
      parts.setupSourceMapForBuild(),
      parts.setupCSS(PATHS.app),
      parts.extractBundle({
        name: 'vendor',
        entries: ['react', 'vue', 'vuex']
      }),
      parts.setFreeVariable('process.env.NODE_ENV', 'production'),
      parts.minify()
      );
    break;
  default: 
    config = merge(common, 
      parts.setupSourceMapForDev(),
      parts.devServer(), 
      parts.setupCSS(PATHS.app));
}
// minify example
exports.minify = function () {
  return {
    plugins: [
      new webpack.optimize.UglifyJsPlugin({
        compress: {
          warnings: false,
          drop_console: true
        },
        comments: false,
        beautify: false
      })
    ]
  }
}

開發環境下的自動刷新

webpack-dev-server

webpack-dev-server在webpack的watch基礎上開啓服務器。web

webpack-dev-server是運行在內存中的開發服務器,支持高級webpack特性hot module replacement。這對於react vue這種組件化開發是很方便的。vuex

使用webpack-dev-server命令開啓服務器,配合HMR及能夠實現代碼更改瀏覽器局部刷新的能力。npm

hot module replacement

Hot Module Replacement (HMR) exchanges, adds, or removes modules while an application is running without a page reload.
當應用在運行期間hmr機制可以修改、添加、或者移除相應的模塊,而不使整個頁面刷新。

hmr機制適用於單頁應用。

要實現hmr機制,須要配合webpack-dev-server服務器,這個服務器自己就實現了監察watch文件改動的能力,再開啓HMR選項,就添加了watch模塊變化的能力。這是HMR機制能生效的基礎。

從webpack編譯器角度

每次修改一個模塊的時候,webpack會生成兩部分,一個是manifest.json,另外一部分是關於此次模塊更新編譯完成的chunks。manifest.json中存着的是chunk更改先後的hash值。

從編譯器webpack的角度來說提供了hmr的原材料。供後續使用。

從模塊的角度

模塊發生變化時,webpack會生成以前講過的兩部分基礎文件,可是什麼時候將變化後的模塊應用到app中去?這裏就須要在應用代碼中編寫handler去接受到模塊變化信息。可是不能在全部模塊中編寫handler吧?這裏就用到了消息冒泡機制。

如圖A.js、C.js沒有相關hmr代碼,B.js有相關hmr代碼,若是c模塊發生了變化,c模塊沒有hmr,那麼就會冒泡到a、b模塊。b模塊捕捉到了消息,hmr運行時會相應的執行一些操做,而a.js捕捉不到信息,會冒泡到entry.js,而一旦有消息冒泡的入口塊,這就表明本次hmr失敗了,hmr會降級進行整個頁面的reload。

從HMR運行時的角度

HMR運行時是一些相關的操做api,運行時支持兩個方法: checkapply

check發起 HTTP 請求去獲取更新的 manifest,以及一些更新事後的chunk。

環境變量的設置

var env = {
  'process.env.NODE_ENV': '"production"'
}
new webpack.DefinePlugin(env)

注意這裏單引號間多了個雙引號 why?

以及webpack.DefinePlugin插件的原理?

開發的時候會想寫不少只在開發環境出現的代碼,好比接口mock等,在build命令後這些代碼不會存在。

這對框架或者插件、組件的開發是頗有幫助的。vue,react等都會這麼作。能夠在這些框架的dev模式提供不少有用的提示信息。

打包文件分割

爲什麼要進行打包文件分割?

對於一個單頁應用項目來講,有分爲業務代碼和第三方代碼,業務代碼會頻繁改動,而第三方代碼通常來說變更的次數較少,若是每次修改業務代碼都須要用戶將整個js文件都從新下載一遍,對於加載性能來說是不可取的,因此通常而言咱們會將代碼分爲業務代碼和第三方代碼分別進行打包,雖然多了一個請求的文件,增長了一些網絡開銷,可是相比於瀏覽器能將文件進行緩存而言,這些開銷是微不足道的。

咱們在entry中定義了app入口,相應的業務邏輯都封裝在這個入口文件裏,若是咱們想要第三方代碼獨立出來,就要再增長一個入口,咱們習慣使用vendor這個命名。

// app.js

require('vue');
require('vuex');
// webpack.config.js


entry: {
    app: 'app/app.js',
    vendor: ['vue', 'vuex'],
  },

vendor入口的傳參是以一個數組的形式傳遞的,這是一種很是方便的注入多個依賴的方式,而且能把多個依賴一塊兒打包到一個chunk中。並且不用手動的建立真實存在的入口文件。

這至關於:

// vendor.js

require('vue');
require('vuex');

// app.js

require('vue');
require('vuex');
// webpack.config.js


entry: {
    app: 'app/app.js',
    vendor: 'app/vendor.js',
  },

可是這樣作只是聲明瞭一個vendor入口而已,對於app這個入口來講,打包完成的文件仍是會有vue和vuex依賴,而新增的入口vendor打包完成的文件也有了vue和vuex兩個依賴。模塊依賴關係以下圖所示。

這裏的A能夠表明vue依賴,最後生成的打包文件是兩個平行關係的文件,且都包含vue的依賴。

此時須要引入CommonsChunkPlugin插件

This is a pretty complex plugin. It fundamentally allows us to extract all the common modules from different bundles and add them to the common bundle. If a common bundle does not exist, then it creates a new one.

這是個至關複雜的插件,他的基礎功能是容許咱們從不一樣的打包文件中抽離出相同的模塊,而後將這些模塊加到公共打包文件中。若是公共打包文件不存在,則新增一個。同時這個插件也會將運行時(runtime)轉移到公共chunk打包文件中。

plugins: [
  new webpack.optimize.CommonsChunkPlugin({
    names: ['vendor', 'manifest']
  })
]

這裏的name能夠選擇已經存在的塊,這裏就選擇了vendor塊,由於咱們原本就是將vendor塊當作管理第三方代碼的入口的。

而names傳入一個數組,數組裏包含兩個trunk name,表示CommonsChunkPlugin插件會執行兩次這個方法,第一次將公共的第三方代碼抽離移到vendor的塊中,這個過程以前也講過會將運行時runtime也轉移到vendor塊中,第二次執行則是將運行時runtime抽離出來轉移到manifest塊中。這步操做解決了緩存問題。

這樣處理,最後會生成3個打包文件chunk,app.js是業務代碼,vendor則是公共的第三方代碼,manifest.js則是運行時。

chunk type 塊的類型大揭祕

webpack1.0官網介紹中的chunk類型讀起來及其拗口chunk type, 因此我這裏解讀一下。

chunk是webpack中最基本的概念之一,且chunk經常會和entry弄混淆。在「打包文件分割部分」咱們定義了兩個入口entry point -- app和vendor,而經過一些配置,webpack會生成最後的一些打包文件,在這個例子中最後生成的文件有app.js 、 vendor.js 、 manifest.js。這些文件便被稱爲塊chunk

entry & chunk 能夠簡單的理解爲一個入口、一個出口

在官方1.0文檔中webpack的chunk類型分爲三種:

  1. entry chunk 入口塊

  2. normal chunk 普通塊

  3. initial chunk 初始塊

entry chunk 入口塊

entry chunk 入口塊不能由字面意思理解爲由入口文件編譯獲得的文件,由官網介紹

An entry chunk contains the runtime plus a bunch of modules

能夠理解爲包含runtime運行時的塊能夠稱爲entry chunk,一旦本來存在運行時(runtime)的entry chunk失去了運行時,這個塊就會轉而變成initial chunk

normal chunk 普通塊

A normal chunk contains no runtime. It only contains a bunch of modules.

普通塊不包含運行時runtime,只包含一系列模塊。可是在應用運行時,普通塊能夠動態的進行加載。一般會以jsonp的包裝方式進行加載。而code splitting主要使用的就是普通塊。

initial chunk 初始塊

An initial chunk is a normal chunk.

官方對initial chunk的定義很是簡單,初始塊就是普通塊,跟普通塊相同的是一樣不包含運行時runtime,不一樣的是初始塊是計算在初始加載過程時間內的。在介紹入口塊entry chunk的時候也介紹過,一旦入口塊失去了運行時,就會變成初始塊。這個轉變常常由CommonsChunkPlugin 插件實現。

例子解釋

仍是拿「打包文件分割」的代碼作例子,

// app.js

require('vue');
require('vuex');
// webpack.config.js


entry: {
    app: 'app/app.js',
    vendor: ['vue', 'vuex'],
  },

沒有使用CommonsChunkPlugin插件以前,兩個entry分別被打包成兩個chunk,而這兩個chunk每一個都包含了運行時,此時被稱爲entry chunk入口塊。

而一旦使用了CommonsChunkPlugin插件,運行時runtime最終被轉移到了manifest.js文件,此時最終打包生成的三個chunkapp.js 、 vendor.js 、 manifest.js,app.js、vendor.js失去了runtime就由入口塊變成初始塊。

code splitting

前文有講到將依賴分割開來有助於瀏覽器緩存,提升用戶加載速度,可是當業務複雜度增長,代碼量大始終是一個問題。這時候就須要normal chunk普通塊的動態加載能力了。

It allows you to split your code into various bundles which you can then load on demand — like when a user navigates to a matching route, or on an event from the user.
code splitting 容許咱們將代碼分割到能夠按需加載的不一樣的打包文件中,當用戶導航到對應的路由上時,或者是用戶觸發一個事件時,異步加載相應的代碼。

咱們須要在業務邏輯中手動添加一些分割點,標明此處事件邏輯以後進行代碼塊的異步加載。

// test
window.addEventListener('click', function () {
  require.ensure(['vue', 'vuex'], function (require) {

  })  
})

這段代碼代表當用戶點擊時,異步請求一個js文件,這個文件中包含該有vue vuex的依賴。

打包後會根據手動分割點的信息生成一個打包文件,就是圖中第一行0開頭的文件。這個文件也就是異步加載的文件。

下面是以前的一個vue項目,採用code splitting將幾個路由抽離出來異步加載以後,文件由212kb減小到了137kb,一樣樣式文件也由58kb減小到了7kb。對於首屏渲染來講,性能是會增長很多的。

參考:

相關文章
相關標籤/搜索