jyzwf / blog

在Issues里记录技术得点滴
17 stars 3 forks source link

聊一聊Code Splitting #71

Open jyzwf opened 5 years ago

jyzwf commented 5 years ago

Code Splitting (代码分隔)可以将代码分隔到不同的bundle中,然后可以按需加载或者并行加载这个文件。从而在网页加载时获得更好的加载体验。 该文章主要以实践为主,因为之前自己在配置这方面折腾的比较少,😑😑

SplitChunksPlugin

首先简单搭建下webpack开发配置:

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer')
    .BundleAnalyzerPlugin;

module.exports = {
    entry: {
        index: './src',
        another: './src/another.jsx',
    },
    output: {
        filename: '[name].js',
        chunkFilename: '[name].js',
        path: path.resolve(__dirname, 'dist'),
    },
    module: {
        rules: [
            {
                test: /\.jsx?$/,
                exclude: /node_modules/,
                use: {
                    loader: 'babel-loader',
                },
            },
        ],
    },
    resolve: {
        extensions: ['.js', '.jsx', '.css'],
    },
    optimization: {
        splitChunks: {},
    },
    plugins: [
        new CleanWebpackPlugin(),
        new BundleAnalyzerPlugin(),
        new HtmlWebpackPlugin({
            template: path.resolve(__dirname, 'src/index.html'),
        }),
    ],
};

开发目录如下: image

index.jsx:

import React from 'react';

import ReactDOM from 'react-dom';

import moment from 'moment';
import App from './App';

ReactDOM.render(<App />, document.getElementById('root'));

anathor.jsx:

import React from 'react';
import ReactDOM from 'react-dom';
import Container from './components/container';

ReactDOM.render(<Container />, document.getElementById('root'));

App.jsx:

import React from 'react';
import Container from './components/container';

export default function App() {
    return (
        <div>
            <Container />
        </div>
    );
}

container.jsx:

import React from 'react';
import moment from 'moment';
export default function Container() {
    return <div>容器</div>;
}

Selector.jsx:

import React from 'react';
import { Select } from 'antd';

const { Option } = Select;

const mapStateToProps = rootState => {
    return { global: rootState.global };
};

function RegionSelect(props) {
    const { global } = props;

    return (
        <div className="region-select">
            <span>选择大区</span>
            <Select
                value={global.region}
                onChange={handleSelectChange}
                style={{ width: 104 }}>
                <Option value="all">全部</Option>
                <Option value="east">东区</Option>
                <Option value="south">南区</Option>
                <Option value="west">西区</Option>
                <Option value="north">北区</Option>
            </Select>
        </div>
    );
}

export default RegionSelect;

现在直接跑 npm run build(webpack --mode production),看下打包情况:

image

可以看出公共的依赖包并没有被单独拿出来,这时我们需要依靠 SplitChunksPlugin 来做这件事,而webpack 已内置了 SplitChunksPlugin,并且给设定了初始配置:

splitChunks: {
    chunks: "async",
    minSize: 30000,
    minChunks: 1,
    maxAsyncRequests: 5,
    maxInitialRequests: 3,
    automaticNameDelimiter: '~',
    name: true,
    cacheGroups: {
        vendors: {
            test: /[\\/]node_modules[\\/]/,
            priority: -10
        },
    default: {
            minChunks: 2,
            priority: -20,
            reuseExistingChunk: true
        }
    }
}

上面的每个字段的意思参见Webpack4之SplitChunksPlugin。因为 chunks 被设置为了 async,所以只对动态加载的文件进行分块。这里我们先将其改成 all image

相较于之前,momentreact相关代码已被单独成包了。 但现在如果我想吧react以及react-dom单独拿出来呢?我们可以设置一个 cacheGroup:

splitChunks: {
            chunks: 'all',
            cacheGroups: {
                react: {
                    test: (module, chunks) => /react/.test(module.context),
                    priority: 1,
                },
            },
        },

image 好,react相关代码也被单独拿出来了,,这时可能会问,为何react的代码不会被放到vendors中呢?这时因为 react这个cacheGroup的优先级(1)大于vendors的优先级(-10)。 之前我们讲到,chunks的默认值为 async,现在我们来试试该效果,在another.jsx引入如下代码,然后不设置splitChunks

import(/* webpackChunkName:"antd" */ './components/Selector');

image

看的出来,,antd 被单独打包了,而moment还是在各个入口文件中各有一份,此时在设置为all试试:

image

关于react路由动态加载

如果我们一开始就加载整个应用的代码,那会在首页即加载很多无关的代码,所以我们可以按照路由级别来按需加载相关代码。 通过上面的了解,我们可以对每个路由对应的组件使用 import() 这个方法来动态引入,然后webpack会根据此来分隔代码,从而实现按需加载。 但是 import() 方法返回的是一个Promise,并不能直接给react-router来使用,所以我们需要一个组件。可以想一下,我们封装一个 加载组件,在组件未加载完成时,给个loading来提示,设置一个state,在组件加载好后,setState一下,不就ok 了? 可以看看 umi/dynamic,它是基于 react-loadable来实现的,而 react-loadable 的大致思路也是上面我说的这种。

未完待续......

参考

SplitChunksPlugin
Webpack Code Splitting