Webpack抽離第三方類庫以及common解決方案


前端構建場景有兩種,一種是單頁面構建,另一種是多入口構建多頁面應用程序(我視野比較小,目前就知道這兩種),下面我們針對這兩種場景總結了幾種抽離第三方類庫以及公共文件的解決方案。

如果有哪些地方優化不周到,請指點一二,另外求關注求星星,么么噠

單頁面構建:

常規配置

const path = require('path');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin')

module.exports = {
    mode: "development",
    entry: {
        app: './app.js'
    },
    output: {
        path: path.resolve(__dirname, './build/'),
        filename: "bundle-[chunkhash:8].js"
    },
    devtool: "source-map",
    module: {
        rules: [
            {
                test: /\.js$/,
                use: {
                    loader: 'babel-loader',
                },
                exclude: /node_modules/
            },
            {
                test: /\.css$/,
                use: [
                    MiniCssExtractPlugin.loader,
                    'css-loader'
                ],
                exclude: /node_modules/
            },
            {
                test: /\.less$/,
                use: [
                    MiniCssExtractPlugin.loader,
                    'css-loader',    // translates CSS into CommonJS
                    'less-loader',     // compiles Less to CSS
                ],
                exclude: /node_modules/
            },
            {
                test: /\.(jpe?g|png|gif|svg)$/i,
                use: [{
                    loader: 'file-loader',
                    options: {
                        limit: 1024,
                    }
                }],
            }
        ]
    },
    plugins: [
        new MiniCssExtractPlugin({
            filename: "[name].[chunkhash:8].css",
            chunkFilename: "[id].[chunkhash:8].css"
        }),
        new HtmlWebpackPlugin({
            title: 'webpack',
            template: './index.html',
            chunks: ['app']
        }),
        new CleanWebpackPlugin()
    ],
}

在腳本種我們常規寫法是這樣的

require('./main-less.less');
require('./main-css.css');
const ReactDOM = require('react-dom');
const React = require('react');
import Main from './main.js';
// /**
//  *  引入 scope hisiting test
//  */
// import B from './ScopeHisitingTest/b';
ReactDOM.render(
    <Main />,
    document.getElementById('app')
)

我們看下構建輸出結果

 

 

 現在我們看到這個應該思考三個問題

  1.腳本部分,難道每個頁面都要寫一邊import React&ReactDOM 嗎

  2.構建體積能不能再縮小一點

  3.構建速度能不能在快一點
以上三個問題都會在開發過程中耽誤開發效率,我們開始處理這三個問題

方案1

html全局引用第三方類庫,比如React,因為React源碼中將React掛在到了window上,這么做解決了什么呢,腳本里面我們不用在每一個頁面中引用第三方類庫了,我們看下代碼

<!DOCTYPE html>
<html>

<head>
    <meta charset="UTF-8">
</head>

<body>
    <div id='app'></div>
    <script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
    <script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
</body>

</html>

好了解決了腳本部分不用每個頁面都需要import一次了

構建體積怎么減小呢

這里我們可以借助webpack插件,下面我們上代碼

 externals: {
        'react': 'react',
        'react-dom': 'react-dom'
    },

我們將兩個第三方類庫打包的時候不依賴進去,通過這種方式告訴webpack,在構建過程中引用react以從內存中引用的方式,我們看下打包效果

 

 

 可以明顯的看到,采用這種用法之后會有一個問題就是,我們在目標腳本文件里面就不能在引用第三方類庫了,不然打包進去,external會以具體文件形式引用第三方類庫,從而導致找不到而報錯,直接用就好了,我們畢竟要解決的就是這個問題嘛。

以上就是第一種解決方案,當然這並不是根本的解決方案,只是個大體思路,我們可以再改進一下。

第二種

通過組織模塊的形式包裝第三方類庫,從而打到第三方類庫掛載到全局的效果

首先創建一個用來專門打包第三方的webpack

const path = require('path');
module.exports = {
    mode: 'development',
    entry: {
        app: './vendorentry.js',
    },
    output: {
        path: path.resolve(__dirname, './vendor/'),
        filename: "dll.js",
        library:'util',
        libraryTarget: 'window'
    },
}

這里就不做任何優化了,畢竟打包第三方的,我們也不需要在提取什么文件了。這里注意的是通過組織模塊打包方式libraryTarget,以及暴露給全局的名稱util。

創建一個入口文件,來引用第三方類庫的集合

import $ from "jquery";
import React from "react";
import ReactDOM from "react-dom";

window.React = React;
window.ReactDOM = ReactDOM;
window.$ = $;

export { $, React, ReactDOM };

輸出結果

 

 所有的第三方都在util下,其實我們只是規定了在業務邏輯的webpack中要以某種形式去加載,而不是打到最終包里,這就是我們的核心目的。

html中可以動態替換目標dll,配置如下

  new HtmlWebpackPlugin({
            templateContent: () => {
                let template = path.resolve(__dirname, './index.html');
                return read(template)
                    .toString()
                    .replace('$dll$', '../vendor/dll.js');
            },
            title: 'index',
            template: './index.html',
            filename: 'index.html',
            chunks: ['app'],
            hash: true
        }),

業務邏輯webpack配置

   externals: {
        'react': 'window.util.React',
        'react-dom': 'window.util.ReactDOM',
        'jQuery': 'window.util.jquery',
        '$': 'window.util.jquery'
    }

第三種

第三種方式采用將第三方類庫打包到指定的dll中,通過webpack構建應用時引用

涉及兩個Plugin,分別是DllReferencePlugin,DllPlugin

首先創建一個專門針對dll的webpack配置文件

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

module.exports = {
    entry: {
        react: [
            'react',
            'react-dom'
        ]
    },
    output: {
        filename: '[name].dll.js',
        path: path.resolve(__dirname, './distDll/dll/'),
        library: '[name]_dll_[hash]'
    },
    plugins: [
        new webpack.DllPlugin({
            name: '[name]_dll_[hash]',
            context: __dirname,
            path: path.join(__dirname, 'distDll/dll', '[name].manifest.json')
        })
    ]
}

然后執行這個webpack,生成dll以及描述模塊運行依賴的manifest.json,我們應用的webpack需要引用這個dll

 new webpack.DllReferencePlugin({
            context: __dirname,
            manifest: require('./distDll/dll/react.manifest.json')
        }),

到這里就結束了嗎,並不是,我們執行下webpack會發現,React找不到了,我們首先考慮到什么,難道是external的問題嗎,你會發現跟它一點關系沒有,難道我們每次寫還要導入第三方類庫嗎,解決方案

ProvidePlugin

   new webpack.ProvidePlugin({
            'React': 'react',
            'ReactDOM': 'react-dom'
        })

這樣就解決了這個問題,我們看下構建效果

同樣也達到了我們的目的。

多入口

多入口采用optimization分離,其實與多頁面分離第三方與common部分的用法是一樣的,我們就跟多頁面一起具體了。

多頁面解決方案

基本配置

const path = require('path');
const webpack = require('webpack');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
    mode: 'development',
    entry: {
        app1: './app1.js',
        app2: './app2.js'
    },
    output: {
        path: path.resolve(__dirname, './build/'),
        filename: "[name]-[chunkhash].js"
    },
    devtool: "source-map",
    module: {
        rules: [
            {
                test: /\.less$/,
                use: [
                    MiniCssExtractPlugin.loader,
                    {
                        loader: 'css-loader',
                    },
                    'less-loader',

                ],
                exclude: /node_modules/
            },
            {
                test: /\.js$/,
                use: [
                    'cache-loader',
                    {
                        loader: 'babel-loader',
                    }
                ],
                exclude: /node_modules/
            }
        ]
    },
    plugins: [
        new MiniCssExtractPlugin({
            filename: '[name].[hash:5].css',
        }),
        new CleanWebpackPlugin(),
        new HtmlWebpackPlugin({
            title: 'index1',
            template: './index1.html',
            filename: 'index1.html',
            chunks: ['app1', 'common'],
            // hash: true
        }),
        new HtmlWebpackPlugin({
            title: 'index2',
            template: './index2.html',
            filename: 'index2.html',
            chunks: ['app2', 'common'],
            // hash: true
        }),
        new webpack.HashedModuleIdsPlugin(),
    ],

}

打包效果

 問題很明顯,速度慢,體積過大,這里還有個問題就是,app1的與app2引用共同的文件導致的體積過大,也就是我們要解決的如何提取common部分的問題,這里我們就不討論腳本import 第三方的問題了,上面有解決方案了。

提取common部分

optimization: {
        runtimeChunk: {
            "name": "manifest"
        },
        splitChunks: {
            chunks: 'all',
            cacheGroups: {
                default: false,
                vendors: false,
                common: {
                    test: /\.(s*)js$/,
                    chunks: 'all',
                    minChunks: 2,
                    minSize: 0,
                    name: 'common',
                    enforce: true,
                    priority: -11
                },
                vendors: {
                    test: /[\\/]node_modules[\\/]/,
                    name: "vendors",
                    priority: -10,
                    chunks: 'all',
                    reuseExistingChunk: true,
                    enforce: true
                },
                style: {
                    name: 'style',
                    test: /\.less$/,
                    chunks: 'all',
                    enforce: true
                }
            }
        },
        runtimeChunk:{
            name:'manifest'
        }
    },

這里我們要做的是,提供commonjs,合並css(提取common部分也是可以的,畢竟頁面也不可能用全部的css,做法與提取commonjs是一樣的,配置minChunks最小為2就可以了),提供第三方類庫。

我們看下打包效果

 速度提升了,同時生成了common文件以及提三方文件集合verndors文件,嗯,目前解決了我們要解決的問題,上面單頁面場景同樣適用,瀏覽器緩存這個一般不會變得vendors,也達到了提升效率問題,

但是有沒有想過一個問題,就是每一次webpack構建過程,是不是都要打一次這個包呢,浪費時間了吧,於是我們采用什么方式呢,沒錯~采用DllPlugin與DllReferencePlugin來提取一次第三方,

剩下common部分每一次構建都去重新打一次。

代碼同樣引用dll

 new webpack.DllReferencePlugin({
            context: __dirname,
            manifest: require('./distDll/dll/react.manifest.json')
        })

構建效果

 

 效果就是大幅度提升構建速度。

最終配置文件

const path = require('path');
const webpack = require('webpack');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
    mode: 'development',
    entry: {
        app1: './app1.js',
        app2: './app2.js'
    },
    output: {
        path: path.resolve(__dirname, './build/'),
        filename: "[name]-[chunkhash].js"
    },
    devtool: "source-map",
    module: {
        rules: [
            {
                test: /\.less$/,
                use: [
                    MiniCssExtractPlugin.loader,
                    {
                        loader: 'css-loader',
                        // options: {
                        //     sourceMap: true,
                        //     modules: true,
                        //     localIdentName: '[name]---[local]---[hash:base64:5]'
                        // }
                    },
                    'less-loader',

                ],
                exclude: /node_modules/
            },
            {
                test: /\.js$/,
                use: [
                    'cache-loader',
                    {
                        loader: 'babel-loader',
                        options: {
                            // cacheDirectory: path.join(__dirname,'./build/', 'babel_cache')
                            // happyPackMode: true,
                            // transpileOnly: true
                        }
                    }
                ],
                exclude: /node_modules/
            }
        ]
    },
    optimization: {
        runtimeChunk: {
            "name": "manifest"
        },
        splitChunks: {
            chunks: 'all',
            cacheGroups: {
                default: false,
                vendors: false,
                common: {
                    test: /\.(s*)js$/,
                    chunks: 'all',
                    minChunks: 2,
                    minSize: 0,
                    name: 'common',
                    enforce: true,
                    priority: -11
                },
                // vendors: {
                //     test: /[\\/]node_modules[\\/]/,
                //     name: "vendors",
                //     priority: -10,
                //     chunks: 'all',
                //     reuseExistingChunk: true,
                //     enforce: true
                // },
                style: {
                    name: 'style',
                    test: /\.less$/,
                    chunks: 'all',
                    enforce: true
                }
            }
        },
        runtimeChunk:{
            name:'manifest'
        }
    },
    plugins: [
        new MiniCssExtractPlugin({
            filename: '[name].[hash:5].css',
        }),
        new CleanWebpackPlugin(),
        new HtmlWebpackPlugin({
            title: 'index1',
            template: './index1.html',
            filename: 'index1.html',
            chunks: ['app1', 'common'],
            // hash: true
        }),
        new HtmlWebpackPlugin({
            title: 'index2',
            template: './index2.html',
            filename: 'index2.html',
            chunks: ['app2', 'common'],
            // hash: true
        }),
        new webpack.HashedModuleIdsPlugin(),
        new webpack.DllReferencePlugin({
            context: __dirname,
            manifest: require('./distDll/dll/react.manifest.json')
        })
    ],

}

以上就是針對webpack構建優化全部總結,涉及第三方抽取,common抽取等問題。

求個關注~謝謝,么么噠~

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM