webpack打包優化解決方案

單頁應用首次進入項目會獲取一部分數據,以後將JS包分片,走到那塊再去加載那塊的JS。
這樣跨頁面重複的JS,CSS沒必要再去獲取,跨頁面就不會出現進度條。這樣減小了等待時間,提高了用戶體驗,省去了沒必要要的流量。
可是單頁應用也有一個顯著的問題:首次進入的時候,加載的資源太多,白屏時間太長。css

這裏介紹一些經常使用的webpack打包優化解決方案node

  1. 使用插件查看項目全部包及體積大小
  2. webpack外部擴展
  3. DLL方式

1、查看項目打包

webpack有個插件,能夠查看項目一共打了多少包,每一個包的體積,每一個包裏面的包狀況。
首先下載插件react

npm intall webpack-bundle-analyzer --save-dev

同時在webpack.config.js配置jquery

const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;

webpackConfig.plugins.push(new BundleAnalyzerPlugin());

在package.json中添加命令webpack

「script」: {
    "analyz": "NODE_ENV=production npm_config_report=true npm run deploy:prod"
}

個人webpack是1.X的 webpack 2.X的同窗請看git

而後命令行輸入github

npm run analyz

開始構建,根據項目大小不一樣,時間也不一樣。
過一會輸出結果以下:web

clipboard.png

能夠清晰的看到項目中一共有多少個包,包的體積是多少,裏面加載了哪些東西,大小是多少。
這裏演示的是一個乾淨的demo,打的包不多,體積也都很小,在真正項目中可能截圖以下:npm

clipboard.png

能夠看到五光十色的十分好看。
這裏面 app.js 和 vendor.js是項目的主文件,其中包含了少部分業務代碼和大部分公共依賴。
剩餘的 number.hash.chunk.js是業務分片的代碼,其中包含了大部分業務代碼和少部分公共依賴。
到這裏,咱們就是用工具完成對項目打包的展現。json

2、webpack外部擴展

列出了項目中較大的包,剩下的事情就是想辦法如何減少這些包的體積(將一個大包拆成多個小包)。
項目中產生較大的包的緣由能夠從兩個方面去考慮:

  1. 項目中引入的依賴包過於龐大;
  2. 業務代碼集中在一塊寫,或者是業務代碼寫的比較繁瑣;

對於這兩個問題,咱們能夠從兩個方面着手解決:

  1. 抽離項目中公共依賴的、不常變更的、體積較大的包;
  2. 將一個較大的業務代碼文件,拆成多個較小的文件,異步加載(或者優化業務代碼)。

這裏面第二項涉及到改動業務代碼,具體的狀況就不一樣了,適合查看 如何優化JS代碼。
咱們來討論第一種方法,在不改動業務代碼的狀況下,如何減少公共依賴。

要知道這些依賴是咱們須要的,不可能排除不引入。
可是他們都是全局依賴的,萬年不變的,可使用瀏覽器本身的緩存來實現不重複加載。
具體作法就是:
將項目中須要的一些公共依賴包,而且不常變更的,單獨取出,再也不每次都打包編譯(如React,Redux等)。
而是經過使用script標籤形式cdn引入,這樣在有緩存的狀況下,這些資源均走緩存,沒必要加載。

具體作法:

總結須要抽離的公共依賴。

這些依賴須要知足必定的條件:

  1. 體積較大;
  2. 不常更新;
  3. 依賴較多;
  4. 是前置依賴;

常見的知足這類條件的包有:

  1. react
  2. react-dom
  3. redux
  4. react-redux
  5. moment
  6. jquery

另一些包文件如 Antd庫文件,整個包較大,可是每次用什麼取什麼的話,庫文件也會按需加載,沒必要單獨取出。
還有這類庫文件不建議單獨取出,由於裏面可能會有bug,須要更新。

使用CDN引入資源

以個人demo爲例:我須要抽離出的文件有 react,react-dom,react-router,redux,react-redux,history。
將這些文件放到cnd上,注意,這些文件要是壓縮版本,而且是用ES5編寫的,不然瀏覽器報錯。

<head>
  <title>React Starter Kit</title>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <!-- 體積較大的包 -->
  <script src="https://cdn.bootcss.com/react/15.0.0/react-with-addons.min.js"></script>
  <script src="https://cdn.bootcss.com/react/15.0.0/react-dom.min.js"></script>
  <script src="https://cdn.bootcss.com/react-router/3.0.0/ReactRouter.min.js"></script>
  <script src="https://cdn.bootcss.com/redux/3.6.0/redux.min.js"></script>
  <script src="https://cdn.bootcss.com/react-redux/5.0.1/react-redux.min.js"></script>
  <script src="https://cdn.bootcss.com/history/4.5.0/history.min.js"></script>
</head>

配置webpack.conf.js

資源已經引入,接下來須要配置webpack,使其打包的時候不在將這些資源打包。

const webpackConfig = {
    name: 'client',
    target: 'web',
    devtool: config.compiler_devtool,
    resolve: {
        root: paths.client(),
        extensions: ['', '.js', '.jsx', '.json'],
    },
    externals: {
        'react': 'React',
        'react-dom': 'ReactDOM',
        'react-router': 'ReactRouter',
        'redux': 'Redux',
        'history': 'History'
    },
    module: {},
}

這裏externals告訴webpack那些資源從哪裏尋找。
該對象的鍵表示 require 或者 import 時候的字符串
值表示的當前環境下的變量,好比引入React以後,React被做爲全局對象,webpack就回去尋找React對象。
若是其中有一個找不到,打包就會失敗。

配置vendor.js

接下來配置vendor,使vendor也不打包該些JS

compiler_vendors : [
    // 'react',
    // 'react-redux',
    // 'react-router',
    // 'redux',
],

接下來再次運行 npm run analyz

clipboard.png

對比第一次的效果圖,很明顯app.js由原來的625kb減小到了78kb,
原來第二大的vendor.js如今已經很小了。
可是要注意的是,並非包越小越好,越小的包反而越耗費連接。
應該讓你的包裏面的業務代碼佔大多數。
後來被告知,最大的包的體積壓縮以後80k之內就能夠。

3、DLL方式

dll 全稱是:dynamic link library(動態連接庫)
dll方式也就是經過配置,告訴webpack指定庫在項目中的位置,從而直接引入,不將其打包在內。
上面介紹的方式是將包放到cdn上,build的時候不在引入對應的包;
dll方式就是指定包在項目中,build的時候不在打包對應的包,使用的時候引入。
webpack經過webpack.DllPluginwebpack.DllReferencePlugin兩個內嵌插件實現此功能。

新建webpack.dll.config.js

const webpack = require('webpack');

module.exports = {
    entry: {
        bundle: [
            'react',
            'react-dom',
            //其餘庫
            ],
    },
    output: {
        path: './build',
        filename: '[name].js',
        library: '[name]_library'
    },
    plugins: [
        new webpack.DllPlugin({
            path: './build/bundle.manifest.json',
            name: '[name]_library',
        })
    ]
};

webpack.DllPlugin選項:

  • path:manifest.json文件的輸出路徑,這個文件會用於後續的業務代碼打包;
  • name:dll暴露的對象名,要跟output.library保持一致;
  • context:解析包路徑的上下文,這個要跟接下來配置的 webpack.config.js 一致。

運行文件

運行:webpack --config webpack.dll.config.js

生成兩個文件,一個是打包好的bundlejs,另一個是bundle.mainifest.json,大體內容以下:

{
  "name": "bundle_library",
  "content": {
    "./node_modules/react/react.js": 1,
    "./node_modules/react/lib/React.js": 2,
    "./node_modules/process/browser.js": 3,
    "./node_modules/object-assign/index.js": 4,
    "./node_modules/react/lib/ReactChildren.js": 5,
    "./node_modules/react/lib/PooledClass.js": 6,
    "./node_modules/react/lib/reactProdInvariant.js": 7,
    //其餘引用
}

配置webpack.config.js

const webpack = require('webpack');
var path = require('path');
module.exports = {
  entry: {
    main: './main.js',
  },
  output: {
    path: path.join(__dirname, "build"),
    publicPath: './',
    filename: '[name].js'
  },
  module: {
    loaders:[
      { test: /\.(png|jpg)$/, loader: 'url-loader?limit=8192'},
      {
        test: /\.jsx?$/,
        loaders: ['babel-loader?presets[]=es2015&presets[]=react'],
        include: path.join(__dirname, '.')
      }
    ]
  },
  plugins: [
     new webpack.DllReferencePlugin({
      context: '.',
      manifest: require("./build/bundle.manifest.json"),
        }),
  ]
};

webpack.DllReferencePlugin的選項中:

  • context:須要跟以前保持一致,這個用來指導webpack匹配manifest.json中庫的路徑;
  • manifest:用來引入剛纔輸出的manifest.json文件。

參考連接:

  1. 層層優化webpack打包體積
  2. webpack打包bundle.js體積大小優化
  3. webpack 按需打包加載
  4. webpack dllPlugin 使用

完整的項目demo

相關文章
相關標籤/搜索