帶你由淺入深探索webpack4(二)

在前一篇文章已經介紹了webpack4從入門到一些核心經常使用的用法,你們能夠從上一篇文章看起。帶你由淺入深探索webpack4(一)css

接着上一章,接下來咱們會繼續探討webpack4中的各類實用用法,讓咱們共同探討認知前端的另外一個世界。html

 

三: webpack中的高級配置前端

 

 3.1代碼分離node

在咱們以前的項目中,打包的文件都是放在一個main.js中,每次使用時,都是加載整個main.js,形成性能上的浪費。jquery

代碼分離是webpack中最引人注目的特性之一。他能夠把代碼分離到不一樣的budle中,而後能夠按需加載或者並行加載這些文件。webpack

代碼分離通常有3個經常使用的分離方法:web

1.入口起點:使用ertry配置手動分離代碼。chrome

2.防止重複:配置optimization去重和分離chunk.npm

3.動態導入:(異步代碼)經過模塊的內聯函數調用來分離代碼。json

 3.1.1入口起點

這個很簡單,就是本身手動分離代碼,將代碼分離出多個js文件,將其配置到打包入口文件,這樣就能夠打包出不一樣模塊。

其缺點很明顯,要咱們手動進行分離不夠靈活,而且重複都模塊都會被引入到打包後的文件中。

 3.1.2防止重複

假設咱們打包多個js入口文件,且他們又引用了同一個第三庫,這樣打包出來的每一個出口文件都含用一份第三方庫,性能消耗是巨大的。

因此咱們可使用CommonsChunkPlugin插件幫助咱們去重,其是將重複的模塊分離出單獨的chunk。

咱們在src下建立兩個入口文件.another1.js、another2.js分別引入第三方模塊lodash

npm install lodash -D

src/another1.js、src/another2.js:

import _ from 'lodash'
console.log(
    _.json(['A','B','C'],' ')
)

webpack.common.js:

entry:{                     //配置入口文件
        another1:'./src/another1.js',
        anotehr2:'./src/another2.js'
    }

直接運行npx webpack打包,能夠看到打包出來的文件都挺大的:

咱們如今webpack.common.js配置一下先:

.......
module.exports = {
    entry:{                     //配置入口文件
        another1:'./src/another1.js',
        anotehr2:'./src/another2.js'
    },
......
output:{ //打包文件的出口 filename:'[name].js', //打包後的文件名 path:path.resolve(__dirname,'dist') //打包後文件存放的位置 }, optimization:{ splitChunks:{ cacheGroups: { commons: { name: 'commons', //+++抽離出公共模塊的名稱 chunks: 'initial', //+++入口文件中的共享代碼 minChunks: 2 //+++最少的入口文件數 } } } } }

而後咱們再運行npx webpack就會發現抽離出了一個commons.js文件,整體代碼減輕了一大截。

在這裏可能查看中文文檔可能有個坑,因爲中文文檔更新有點慢,其推薦的配置是使用CommonsChunkPlugin,但在最新版webpack中其已經被刪除了,採用optimization代替了。

3.13動態導入

首先咱們先移除another2.js先,再刪除optimization配置。

要實現異步打包,須要藉助一個插件:babel-plugin-syntax-dynamic-import。因此咱們先安裝一下:

npm install babel-plugin-syntax-dynamic-import -D

咱們引入了插件,由於其是異步代碼分離,因此咱們先將another1.js修改成異步代碼

src/another1.js:

function getComponent() {
    return import(/* webpackChunkName:"lodash" */ 'lodash').then(({ default: _ }) => {
        var element = document.createElement('div');
       element.innerHTML = _.join(['1', '2'], '-');
        return element;
   })
}

getComponent().then(element => {
    document.body.appendChild(element);
});

由於其實babel的插件,接着咱們還須要修改一下.babelrc文件的配置.

.babelrc:

{
    "presets": [
        [
            "babel-preset-env", {         
                "targets": {         //在這些版本以上的瀏覽器中不轉換爲es5
                    "chrome": "67",
                    "firfox":"60",
                    "edge":"17",
                    "safari":"11.1"
                },
                "useBuiltIns": "usage"
            }
        ]
    ],
    "plugins": ["babel-plugin-syntax-dynamic-import"]    //+++
}

分類方便區分哪些是抽離出的公共模塊,咱們配置一下出口文件名:

output:{                 //打包文件的出口
        filename:'[name].js',          //打包後的文件名
        chunkFilename:'[name].chunk.js',    //+++抽離出的公共模塊的文件名
        path:path.resolve(__dirname,'dist')      //打包後文件存放的位置
    },

最後,咱們直接運行打包,能夠看到lodash被抽離出來了。

 

 

3.2css代碼分割

若是咱們將所有代碼打包到js文件中,這就會顯得什麼臃腫,並且不利於代碼分離按需加載!

咱們要將css分離出來單獨的模塊,就須要用到官方推薦的插件:mini-css-extract-plugin(4.3版本以前不支持模塊熱替換)

首先,咱們先安裝一下這個插件:

npm install mini-css-extract-plugin --save-dev

因此咱們須要修改一下css的翻譯官由於style-loader會與其發生衝突,接着須要配置一下plugin,修改以下

webpack.common.js

......
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = {
  plugins: [
    new MiniCssExtractPlugin({
      // Options similar to the same options in webpackOptions.output
      // both options are optional
      filename: '[name].css',   //分離後css的文件名
      chunkFilename: '[id].css',
    }),
  ],
  module: {
    rules: [
      {
        test: /\.css$/,
        use: [
          {
            loader: MiniCssExtractPlugin.loader,
            options: {
              // you can specify a publicPath here
              // by default it uses publicPath in webpackOptions.output
              publicPath: '../',
              hmr: process.env.NODE_ENV === 'development',   
            },
          },
          'css-loader',
'postcss-loader' ], }, ], }, };

上面這個是建議在生產版本中使用,若是你想要在開發版本中使用,特別是使用HMR,可使用以下配置

const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const devMode = process.env.NODE_ENV !== 'production';

module.exports = {
  plugins: [
    new MiniCssExtractPlugin({
      // Options similar to the same options in webpackOptions.output
      // both options are optional
      filename: devMode ? '[name].css' : '[name].[hash].css',
      chunkFilename: devMode ? '[id].css' : '[id].[hash].css',
    }),
  ],
  module: {
    rules: [
      {
        test: /\.(sa|sc|c)ss$/,
        use: [
          {
            loader: MiniCssExtractPlugin.loader,
            options: {
              hmr: process.env.NODE_ENV === 'development',
            },
          },
          'css-loader',
          'postcss-loader',
          'sass-loader',
        ],
      },
    ],
  },
};

在這裏,咱們成功css文件,而且,當咱們引入多個css文件時,mini-css-extract-plugin插件會幫咱們自動合併爲一個文件,可是,若是咱們須要將css文件壓縮,咱們還須要引用一個插件:

npm install optimize-css-assets-webpack-plugin -D

這時咱們修改一下webpack.common.js:

const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');   //+++
module.exports = {
  optimization: {
    minimizer: [ new OptimizeCSSAssetsPlugin({})],           //+++
  },
  plugins: [
    new MiniCssExtractPlugin({
      filename: '[name].css',
      chunkFilename: '[id].css',
    }),
  ],
  module: {
    rules: [
      {
        test: /\.css$/,
        use: [MiniCssExtractPlugin.loader, 'css-loader'],
      },
    ],
  },
};

 

 

 

 3.3懶加載

在上面的代碼中,咱們須要將公共模塊抽離出來。可是,有時候還沒要用到某些模塊,其都會在頁面加載時請求他們,對性能產生負面影響。

懶加載和tree shaking又是不一樣概念,不要搞混。

tree shaking:刪除掉沒有用到的冗餘代碼。

懶加載:又叫延遲加載,即沒用到該資源的時候不加載該資源,等用到時纔開始加載該資源。

咱們引用官網的實例看,咱們動態添加了一個按鈕,當咱們觸發這個按鈕時,咱們纔開始加載‘lodash'模塊,大大提高了初始加載頁面的速度。

 function component() {
    var element = document.createElement('div');

    var button = document.createElement('button');
    var br = document.createElement('br');

    button.innerHTML = 'Click me and look at the console!';
    element.innerHTML = _.join(['Hello', 'webpack'], ' ');
    element.appendChild(br);
    element.appendChild(button);

    // Note that because a network request is involved, some indication
   // of loading would need to be shown in a production-level site/app.
   button.onclick = e => import(/* webpackChunkName: "print" */ './print').then(module => {
     var print = module.default;

     print();
   });

    return element;
  }


 document.body.appendChild(component());

 

 

3.4 緩存

當咱們加載一次頁面時,瀏覽器會使用一種叫「緩存」的技術,在必定時間保存咱們請求的資源。當咱們再次修改上存而沒有修改資源文件名時,瀏覽器就會認爲它沒有被更新,而請求其以前緩存的資源,致使用戶沒法實時獲取新的資源代碼。

首先咱們上個例子將入口文件換回src/another2.js文件

因此咱們在輸出文件配置中,使其打包後的文件名發生改變。

output:{                 //打包文件的出口
        filename:'[name].[contenthash].js',          //打包後的文件名
        path:path.resolve(__dirname,'dist')      //打包後文件存放的位置
    },

在之前的版本包括如今的webpack中文文檔(估計沒更新)都是使用chunkhash值的,而最新的版本改用爲contenthash值。

這裏插入一下大體講解下hash、chunkhash、contenthash他們之間有什麼區別

      hash:這個跟整個項目構建有關,只要項目文件有更改,整個項目構建的hash值都會改變,而且所有文件都共用相同的hash值。

      chunkhash:它會根據不一樣的入口文件進行依賴文件解析,構建對應的chunk,生成對應的哈希值。且只要咱們不改動公共庫的代碼時,     就能夠保證哈希值不會受到影響。可是其有個問題,就是當咱們將css分離出單獨的模塊(後面會有介紹)時,其hash值會與主入口的文件     公用同一個哈希值,當咱們修改css或主入口文件時,其也會致使主入口文件和css文件的哈希值都發生改變。

      contenthash:它只會根據文件內容的變化而改變其hash值,既即便css文件所處的模塊裏的其餘模塊文件內容文件發生變化,只要css文      件內容不變,其就不會重複構建。

在這裏咱們給他們添加了一個contenthash值,咱們看一下打包後的文件名。

按照常理說當咱們內容文件不改變時,是不會改變其的hash值。當咱們再運行多幾回打包看下它的哈希值會不會改變,然而。。。

(個人電腦運行並不會出現下列問題,官網說多是有些版本差別纔會出現這種問題,但爲了更可靠起見,仍是建議用下面介紹的方法)

在某些版本中遇到上述問題,是由於webpack中包括了某些樣板,特別是runtime和manifest。下面介紹下解決方法:

提取模塊

提取模塊主要用到CommonsChunkPlugin插件,在前面代碼分離第二part咱們已經用過這個插件,咱們主要用來提取公共模塊,這章咱們用其來將代碼拆分紅單獨塊:

optimization:{
        runtimeChunk:'single'
    }

當咱們運行時,其就會幫助咱們將第三方模塊拆分爲單獨的模塊

讓咱們在配置一下其參數,當咱們再將node_modules中的模塊給分離出來

optimization:{
        splitChunks: {
                   cacheGroups: {
                     vendor: {
                       test: /[\\/]node_modules[\\/]/,
                       name: 'vendors',
                       chunks: 'all'
                     }
                   }
                 },
        runtimeChunk:'single'
    }

咱們從新打包一下就會發現,剩下的main.js居然只有2.9KB了!!!

 

 

3.5預取/預加載模塊

當咱們要加載一個組件時,若是使用懶加載,等到觸發的時候纔開始加載時,性能上會有必定損耗,給用戶的體驗感也不是很好。

因此咱們能夠在頁面加載完成後有空閒時間時,再加載一下未來須要用到的模塊,這樣在觸發該功能時,就能夠直接調用緩存中的模塊,這就能夠大大提高用戶的體驗感。

webpack4.6+的版本中webpack添加了預取和預加載模塊。

下面介紹一下怎樣使用預取/預加載模塊,很簡單,只須要在導入模塊中加入一個註釋即可。

import(/* webpackPrefetch: true */ 'LoginModal');

其會在頁面頭部附加<link rel=「prefetch」href=「login modal chunk.js」>

這裏可使用webpackPrefetch和webpackPreload這兩個屬性,其仍是有些區別的:

webpackPrefetch: true :先把主加載流程加載完畢,在空閒時在加載其模塊,等再點擊其餘時,只須要從緩存中讀取便可,性能更好。推薦使用,提升代碼利用率。把一些交互後才能用到的代碼寫到異步組件裏,經過懶加載的形式,去把這塊的代碼邏輯加載進來,性能提高,頁面訪問速度更快。

webpackPreload: true : 和主加載流程一塊兒並行加載。而一個預加載的塊應該在加載完成時當即被其父類調用。

 

3.6Slimming(墊片)

當咱們引入相似lodash、jquery這些第三方庫的時候,這些庫可能回建立一些須要被導出的全局變量,形成了環境的污染。

slimming在這裏就起到了很大的做用,它能夠只導出須要使用到的全局變量。來看下面的例子

咱們添加一個plugin插件的配置以下:

  const path = require('path');
  const webpack = require('webpack');  

  module.exports = {
    entry: './src/index.js',
    output: {
      filename: 'bundle.js',
      path: path.resolve(__dirname, 'dist')
    }
    },
    plugins: [
      new webpack.ProvidePlugin({   //+++
        _: 'lodash',                         //+++引用全局變量_時,自動引用lodash這個庫
        join:['lodash','join']             //+++引動全局變量join時,調用lodash庫中的join方法
      })
    ]
  };

這樣,咱們就無需在每一個模塊中都import這個第三方模塊了,咱們修改一下src/index.js

src/index.js:

 function component() {
    var element = document.createElement('div');

   element.innerHTML = _.join(['Hello', 'webpack'], ' ');  //+++調用lodash
   element.innerHTML = join(['Hello', 'webpack'], ' ');  //+++直接調用lodash中的方法

    return element;
  }

  document.body.appendChild(component());

當咱們只須要使用join方法,就無須要導出整個庫,這樣就能夠很好的和tree shaking相配合。

 在這裏有一個問題就是,一些傳統的模塊依賴中的this指向的是window對象,當模塊以運行在CommonJS環境下就可能指向的是module.exports,因此咱們須要修改一下模塊中this的指向,將其指向window。通常狀況下不須要設置。

  const path = require('path');
  const webpack = require('webpack');

  module.exports = {
    entry: './src/index.js',
    output: {
      filename: 'bundle.js',
      path: path.resolve(__dirname, 'dist')
    },
   module: {
     rules: [
        {
          test: /\.js$/,  //+++當解析index.js的時候
          use: 'imports-loader?this=>window' //+++將this指向window
        }
      ]
    },
    plugins: [
      new webpack.ProvidePlugin({
        join: ['lodash', 'join']
      })
    ]
  };

 

3.7library

 當咱們須要開發一個庫時,咱們但願外包能夠經過各類方式引用咱們的庫,這時,咱們就須要用到library

例如咱們建立一個libray.js的方法類。

[{
  "num": 1,
  "word": "One"
}, {
  "num": 2,
  "word": "Two"
}, {
  "num": 3,
  "word": "Three"
}, {
  "num": 4,
  "word": "Four"
}, {
  "num": 5,
  "word": "Five"
}, {
  "num": 0,
  "word": "Zero"
}]

而後經過src/index.js引入其:

src/index.js

import _ from 'lodash';
import numRef from './ref.json';

export function numToWord(num) {
  return _.reduce(numRef, (accum, ref) => {
    return ref.num === num ? ref.word : accum;
  }, '');
};

export function wordToNum(word) {
  return _.reduce(numRef, (accum, ref) => {
    return ref.word === word && word.toLowerCase() ? ref.num : accum;
  }, -1);
};

而後咱們將其打包,若是咱們想要經過各類方式引入該庫,應該怎麼作:

// ES2015 模塊引入
import * as webpackNumbers from 'webpack-numbers';
// CommonJS 模塊引入
var webpackNumbers = require('webpack-numbers');
// ...
// ES2015 和 CommonJS 模塊調用
webpackNumbers.wordToNum('Two');
// ...
// AMD 模塊引入
require(['webpackNumbers'], function ( webpackNumbers) {
  // ...
  // AMD 模塊調用
  webpackNumbers.wordToNum('Two');
  // ...
});

<script src="index.js">

咱們就須要在入口文件中配置一下:

webpack.config.js:

const path = require('path')

module.exports = {
    mode:'production',
    entry:'./src/index.js',
    output:{
        path:path.resolve(__dirname,'dist'),
        filename:'index.js',
        library:'index',
        libraryTarget:'umd'
    }
}

可是,咱們在src/index.js引入了lodash的庫,若是在外部可能引入了lodash這就顯得什麼耗費性能,因此咱們能夠這樣設置

const path = require('path')

module.exports = {
    mode:'production',
    entry:'./src/index.js',
    output:{
        path:path.resolve(__dirname,'dist'),
        filename:'index.js',
        library:'index',
        libraryTarget:'umd'
    },
   externals: {
       lodash: {
         commonjs: 'lodash',
         commonjs2: 'lodash',
         amd: 'lodash',
         root: '_'
       }
     }
}

這樣,當咱們引入index.js庫時,就必須須要引入一個lodash的依賴,這樣就能防止外部與內部引入重複的庫。

相關文章
相關標籤/搜索