webpack之代码分离

文章目录

    • 多个entry分离代码
    • 抽离出公共模块
      • dependOn
      • optimization.splitChunks
          • SplitChunksPlugin的默认配置
          • SplitChunksPlugin的实际应用
    • 动态导入
      • 内联注释
        • webpackChunkName
        • webpackPrefetch
        • webpackPreload
            • 遗留问题
    • 参考文章

多个entry分离代码

//math.js
function square(x){
    return x*x;
}

function cube(x){
    return x*x*x;
}


function add(x,y){
    return x+y;
}

function sub(x,y){
    return x-y;
}

function mul(x,y){
    return x*y;
}

function div(x,y){
    return x/y;
}

export {
    square,
    cube,
    add,
    sub,
    mul,
    div
}
//index.js
import {cube} from './math.js';
var elm = document.createElement('code');
elm.textContent = '5*5*5='+cube(5);
document.body.appendChild(elm);
//calculate.js
import {add,sub,mul,div} from './math.js';

var results = [add(0,1),sub(3,1),mul(3,1),div(8,2)];
var fragment = document.createDocumentFragment();
results.forEach(res => {
    var elm = document.createElement('code');
    elm.textContent = res;
    elm.appendChild(document.createElement('br'));
    fragment.appendChild(elm);
});
document.body.appendChild(fragment);
//webpack.config.js
const path = require('path');
const HtmlWebapckPlugin = require("html-webpack-plugin");
const {CleanWebpackPlugin} = require("clean-webpack-plugin");
module.exports = {
   mode:'development',
   devtool:'cheap-source-map',
//    mode:'production',
    devServer:{
        port:3000,
       contentBase:path.join(__dirname,'dist')
    },
    entry:{
        'calculate':'./src/calculate.js',
        'index':"./src/index.js",

    },
    output:{
        filename:"[name].bundle.js",
        path:path.join(__dirname,"dist")
    },
    module:{
        rules:[
            {
                test:/\.js$/,
                include:/src/,
                exclude:/node_modules/,
                use:{
                    loader:'babel-loader',
                    options:{
                       //  presets:['@babel/preset-react']
                       // presets:[
                       //     [
                       //         '@babel/preset-env',{
                       //             modules:false
                       //         }
                       //     ]
                       // ]
                    }
                }
            },
            {
                test:/\.css$/,
                include:/src/,
                use:['style-loader','css-loader'],
            }
        ]
    },
    plugins:[
        new HtmlWebapckPlugin({
            template:'./index.html'
        }),
        new CleanWebpackPlugin()
    ]
}

webpack之代码分离_第1张图片
webpack之代码分离_第2张图片
index.bundle.jscalculate.bundle.js中包含重复模块math.js,所以这里有优化空间。

抽离出公共模块

dependOn

//webpack.config.js
    entry:{
        'calculate':{
            import:'./src/calculate.js',
            dependOn:'shared'
        },
        'index':{
            import:'./src/index.js',
            dependOn:'shared'
        },
        'shared':"./src/math.js"
    },
    output:{
        filename:"[name].bundle.js",
        path:path.join(__dirname,"dist")
    }

webpack之代码分离_第3张图片

optimization.splitChunks

    entry:{
        'calculate':'./src/calculate.js',
        'index':"./src/index.js",
    },
    output:{
        filename:"[name].bundle.js",
        path:path.join(__dirname,"dist")
    },
    optimization:{
        splitChunks:{
            chunks:'all',
            name:'math'
        }
    }

webpack之代码分离_第4张图片

SplitChunksPlugin的默认配置

webpack之代码分离_第5张图片

  • splitChunks.chunks
    有三个有效值: initialasyncall,默认是async
    • initial
      只分割同步引入的模块
    • async
      只分割异步引入的模块
    • all
      不论模块是同步引入还是异步引入,都会分割
  • splitChunks.minSize
    数值,单位是byte,默认是10000,意思是,分割前,该模块的代码体积大于10000 bytes时,才会被分割出来。
  • splitChunks.minChunks
    数值,默认是1,意思是,分割前,该模块至少被引用1次,才会被分割出来。
  • splitChunks.name
    分割得到的chunk名称
  • splitChunks.cacheGroups
    缓存组,可以继承 或者 覆盖splitChunks中的任何配置项。
    testpriorityreuseExistingChunkcacheGroups这一层独有的。
    • splitChunks.cacheGroups.{cacheGroup}.test
      分割匹配条件
    • splitChunks.cacheGroups.{cacheGroup}.priority
      数值。数值越大,优先级越高,会首先被分割出来。
      默认有两个分组:defaultVendorsdefault,它俩的优先级都是负数,优先级都很低。
    • splitChunks.cacheGroups.{cacheGroup}.reuseExistingChunk
      如果当前模块之前已经被分割了,那么使用之前的分割结果就好,没必要再重新分割一次。
SplitChunksPlugin的实际应用

实际开发中,安装在项目根目录的node_modules下的vue、react等第三方模块,很有必要将它们分割出来并进行缓存。

    optimization:{
        splitChunks:{
            minSize:100,
            cacheGroups:{
                vendor:{
                    priority:1,
                    name:"vendor",
                    test:/[\\/]node_modules[\\/]/,
                    // test:/[\\/]node_modules[\\/](react|react-dom)[\\/]/,
                    minChunks:1,
                    chunks:'initial'
                },
                default:{
                    name:"common",
                    minChunks:2,
                    chunks:'initial'
                }
            }
        }
    }

动态导入

为确保babel能正确解析import(),安装插件babel-plugin-syntax-dynamic-import先。

    entry:{
        'index':"./src/index.js"
    },
    output:{
        filename:"[name].bundle.js",
        path:path.join(__dirname,"dist")
    },
//index.js
import('./math.js').then(math => {
    const {cube} = math;
    var elm = document.createElement('code');
    elm.textContent = '5*5*5='+cube(5);
    document.body.appendChild(elm);   
})

在这里插入图片描述
import('./math.js')math.js独立成chunk:src_math_js.bundle.js
可以看到,webpack根据math.js所在路径进行了默认命名。

内联注释

webpackChunkName

import(/*webpackChunkName:"math"*/"./math.js")+output.chunkFilename:"[name].bundle.js",将动态导入的math.js重命名为math.bundle.js

//webpack.config.js
    entry:{
        'index':"./src/index.js"
    },
    output:{
        filename:"[name].bundle.js",
        path:path.join(__dirname,"dist"),
        chunkFilename:"[name].bundle.js"
    },
//index.js
function getComponent(){
    return import(/*webpackChunkName:'math'*/'./math.js').then(math => {
        const {cube} = math;
        var elm = document.createElement('code');
        elm.textContent = '5*5*5='+cube(5);
        return elm;
    })
}

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

// async function getComponent(){
//     const elm = document.createElement('code');
//     const math = await import(/*webpackChunkName:"math"*/"./math.js")
//     const {cube} = math;
//     elm.textContent = '5*5*5='+cube(5);
//     return elm;
// }
// getComponent().then(component => {
//     document.body.appendChild(component);
// })

在这里插入图片描述

webpackPrefetch

先来了解下
rel='prefetch'算是 未雨绸缪,它告诉浏览器说:“这个资源我以后可能会用到,你有空就先帮我拿一下,到时候我就可以直接用了”。
浏览器这方也给力,在当前页面加载完后,一空闲下来就会默默地根据href到指定路径取资源并放入缓存。也就是说,prefetch这个动作是在 onload事件触发后 发生的。
回到/* webpackPrefetch:true*/,它其实就是 往标签里插入了

看个例子来理解。

//index.js
import React from 'react';
import ReactDOM from 'react-dom';
import HomePage from "./components/homePage.js";
ReactDOM.render(<HomePage/>,document.querySelector('#root'));
//homePage.js
import React from 'react';
import Button from './button.js';

class HomePage extends React.Component{
    constructor(props){
        super(props);
        this.state = {
            isLogged:false,
            welcome:null
        }
        this.handleLogin = this.handleLogin.bind(this);
    }
    handleLogin(){
        const getWelcome = async () => {
            const welcomeModule = await import(
                /*webpackChunkName:"welcome"*/
                /*webpackPrefetch:true */
                './welcome.js'
                );
            this.setState({
                welcome:welcomeModule.default(),
                isLogged:true
            })
        } 
        getWelcome();
        // import(
        //     /*webpackChunkName:"welcome"*/
        //     /*webpackPrefetch:true */
        //     './welcome.js').then(welcomeModule => {
        //     this.setState({
        //         welcome:welcomeModule.default(),
        //         isLogged:true
        //     })
        // })
    }
    render(){
        const {isLogged,welcome} = this.state;
        return isLogged ? welcome : <Button handleLogin={this.handleLogin}/>
    }
}

export default HomePage;
//button.js
import React from 'react';

class Button extends React.Component{
    constructor(props){
        super(props);
        this.handleClick = this.handleClick.bind(this);
    }
    handleClick(){
        this.props.handleLogin();
    }
    render(){
        return <button onClick={this.handleClick}>Login</button>
    }
}

export default Button;
//welcome.js
import React from "react";

function Welcome(){
    return <p>Welcome!</p>
}

export default Welcome;

点击Login后,才需要Welcome组件,所以在事件处理程序handleLogin里才 importWelcome组件,实现按需加载。
import()里内联/*webpackChunkName:"welcome"*/则是我们期望的 预拉取。
请注意prefetch只是下载了一个资源,并没有执行。
HomePage组件装载完成后,浏览器才拉取了welcome.bundle.js,但并没有执行该文件。
webpack之代码分离_第6张图片
webpack之代码分离_第7张图片
点击Login后,才执行了welcome.bundle.js
webpack之代码分离_第8张图片

webpackPreload

先来了解下
rel='prefetch'是将以后可能用来的资源预先拉取下来,以备不时之需。
rel='preload' 则稳扎稳打,它知道自己一定需要哪些资源,所以先把它们下载下来,但不会运行,只有用到这些资源的时候才会运行。
举个简单的例子。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>demo</title>
    <link rel="preload" href="./style.css" as='style'>
    <link rel="preload" href="./main.js" as="script" >
</head>
<body>
<div id="root">hello world</div>
</body>
</html>

webpack之代码分离_第9张图片
瞧,style.cssmain.js下载下来了,但style.css里的样式没有应用上,main.js里的js代码也没有执行。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>demo</title>
    <link rel="preload" href="./style.css" as='style'>
    <link rel="preload" href="./main.js" as="script" >
    <link rel="stylesheet" href="./style.css" >

</head>
<body>
<div id="root">hello world</div>
<script src='./main.js'></script>
</body>
</html>

瞧,加了后,背景变蓝了,打印have a nice day了。
在这里插入图片描述
回到/* webpackPreload*/,它其实就是 往标签里插入了

遗留问题

一个有问题的且没有成功的例子:刷新页面后,必须等5秒,这样this.welcome才完成初始化,这时点击按钮才会显示Welcome!。另外,这个例子感受不到webpackPreload的效果。“囧囧”有神~~~~

//homePage.js
import React from 'react';
import Button from './button.js';

class HomePage extends React.Component{
    constructor(props){
        super(props);
        this.state = {
            isLogged:false,
            welcome:null
        }
        this.welcome = null;
        this.handleLogin = this.handleLogin.bind(this);
    }
    componentDidMount(){
        const getWelcome = async () => {
            const welcomeModule = await import(
                /*webpackChunkName:"welcome"*/
                /*webpackPreload:true */
                './welcome'
                );
            welcomeModule.default().then( component => {
                this.welcome = component;
            })
        } 
        getWelcome();
    }
    handleLogin(){
        this.setState({
            welcome:this.welcome,
            isLogged:true
        })
    }
    render(){
        const {isLogged,welcome} = this.state;
        return isLogged ? welcome : <Button handleLogin={this.handleLogin}/>
    }
}

export default HomePage;
//welcome.js
import React from "react";

async function Welcome(){
    const timeout = ms => new Promise(resolve => setTimeout(resolve,ms));
    await timeout(5000);
    return <p>Welcome!</p>;
}
export default Welcome;

参考文章

代码分离
链接类型:prefetch
Link prefetching FAQ
rel=‘preload’
preload有什么好
Webpack之prefetch和preload
prefetch和preload
Webpack优化之prefetch和preload

你可能感兴趣的:(webpack)