# 代码分割

安装一个 lodash 插件

yarn add lodash

在 index.js 使用 lodash

import _ from "lodash"
console.log(_.join([1, 2, 3], "***"));

执行打包,发现我们的 app.js1.39 MiB

很明显,我们什么都没有写,仅仅只有几行代码,下次如果更新,我们的app.js 会再次打包也是这么大,这肯定不如我们所愿.

# 拆分 js

新建 lodash.js 并移除 index.js lodash的引入

import _ from 'lodash'
window._ = _;

修改 webpack 配置 entry

entry: {
    lodash: './src/lodash.js',
    app: './src/index.js',
}

再次执行打包,发现 dist 目录下生成 app.js 和 lodash.js, 这是我们的 app.js 只有 31.8kb, lodash.js 有1.39m

lodash 作为一个库文件肯定是不会经常变更的。对于我们的业务代码 app.js 来说,如果下次更新了,用户只需要拉去最新的 app.js 代码即可, lodash 则根据浏览器的强缓存 而不需要再次请求服务器,大大的提升了性能

# 使用 splitChunks 自动拆分

上面的代码分割是我们自己手动进行的,其实 webpack splitChunks 会自动进行,下面我们来进行配置下.

删除 lodash.js,index.js 还是正常引入 lodash,修改下 webpack 配置

entry:'./src/index.js',
...
optimization: {
    splitChunks: {
        chunks: 'all'
    }
},

再次执行打包,发现 webpack 打包出来了一个 vendor-main.js 自动帮我们进行了代码分割

# 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
        }
    }
}
  • async 只会对异步才进行代码分割,对同步无效,需要设置为 all
  • cacheGroups 缓存组 抽取公共模块那个地方的
  • priority 优先级权重
cacheGroups: {
    vendor: {
        name: "vendor",
        test: /[\\/]node_modules[\\/]/,
        chunks: "all",
        priority: 10 // 优先级
    },
    common: {
        name: "common",
        test: /[\\/]src[\\/]/,
        minSize: 1024,
        chunks: "all",
        priority: 5
    }
}

如上配置了两个缓存组 vendor 和 common

  • vendor: 抽取来自 node_modules 文件夹下的第三方代码,优先级权重为10
  • common: 抽取来自 src 文件夹下的代码,优先级权重为5
  • vendor比common的优先级权重高,所以先提取 vendor,再提取 common

# chunks 为什么默认是 async

我们现在需要做这样一个需求,点击 body 创建一个div

import _ from "lodash"
function getComponent() {
    var element = document.createElement('div')
    element.innerHTML = _.join([1, 2, 3], "***")
    return element
}
document.addEventListener('click', () => {
    const component = getComponent()
     document.body.appendChild(component)
})

讨论这个问题之前,我们首先应该检查一下 页面代码的 Coverage, 在 Chorem 中 找到 Coverage 进行页面录制,发现页面代码使用率

code1.png

很明显 getComponent里面的代码在首次页面渲染得时候根本用不上,因此代码使用率不高,我们懒加载再来测试一下

document.addEventListener('click', () => {
    import(/* webpackChunkName: "click" */ './click.js').then(({ default: func }) => {
        const component = func()
        document.body.appendChild(component)
    })
})

click.js

import _ from "lodash"
function getComponent() {
    var element = document.createElement('div')
    element.innerHTML = _.join([1, 2, 3], "***")
    return element
}

export default getComponent

执行打包 发现报错,提示不识别 /* webpackChunkName: "click" */ 这样得写法,安装插件

npm install --save-dev @babel/plugin-syntax-dynamic-import

配置 .babelrc

"plugins": ["@babel/plugin-syntax-dynamic-import"]

code2.jpg

打开 Network 我们发现 首次只加载了 main.js 执行点击后分别加载了 vendors.js 和 click.js

webpack 希望我们是使用异步懒加载来进行编程,因此 chunks 默认 也就是 async. 对于webpack来说 同步提取意义也不是很大

# 利用 prefetch 进行优化

上面我们虽然对代码进行了分割懒加载,我们在点击得时候才会去加载对应得 js 文件,那么这个时候就会给人一种反应 很慢得感觉。 那能不能在进入首页后,在浏览器的空闲时间提前下好我们页面需要的js 文件呢

webpack 给我们提供了 prefetch ,会在浏览器空闲时,下载相应文件,我们再次修改下代码

import(/* webpackPrefetch: true */ './click.js')

# 对 CSS 进行代码分割

安装插件

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

配置 webpack.prod.js

const MiniCssExtractPlugin = require('mini-css-extract-plugin');

module: {
    rules: [
        {
            test: /\.css$/,
            use: [
                MiniCssExtractPlugin.loader,
                'css-loader'
            ]
        }
      ]
}
plugins: [new MiniCssExtractPlugin({
        filename: '[name].css',
        chunkFilename: '[id].css',
    })],    

在 index.js 中引入 index.css 和 index1.css 执行 prod 打包。发现 会生成一个 main.css

# Production 压缩

webpack5可能会内置CSS 压缩器,webpack4需要自己使用压缩器,可以使用 optimize-css-assets-webpack-plugin 插件

const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');

optimization: {
    minimizer: [
        new OptimizeCSSAssetsPlugin({}),
    ],
},

再次打包 发现 css 文件被压缩合并了

# 把 CSS 提取到一个文件

可以利用 splitChunks.cacheGroups 将css提取到一个CSS中

optimization: {
...
    splitChunks: {
        cacheGroups: {
            styles: {
                name: 'styles',
                test: /\.css$/,
                chunks: 'all',
                enforce: true,
            },
        },
    },
}

# 根据 entry 提取 CSS

可以根据 webpack 的entry name来提取CSS,这对动态引入路由,并且动态加载对于的css这种情况十分有用.

const path = require('path');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');

function recursiveIssuer(m) {
  if (m.issuer) {
    return recursiveIssuer(m.issuer);
  } else if (m.name) {
    return m.name;
  } else {
    return false;
  }
}

module.exports = {
  entry: {
    foo: path.resolve(__dirname, 'src/foo'),
    bar: path.resolve(__dirname, 'src/bar'),
  },
  optimization: {
    splitChunks: {
      cacheGroups: {
        fooStyles: {
          name: 'foo',
          test: (m, c, entry = 'foo') =>
            m.constructor.name === 'CssModule' && recursiveIssuer(m) === entry,
          chunks: 'all',
          enforce: true,
        },
        barStyles: {
          name: 'bar',
          test: (m, c, entry = 'bar') =>
            m.constructor.name === 'CssModule' && recursiveIssuer(m) === entry,
          chunks: 'all',
          enforce: true,
        },
      },
    },
  },
  plugins: [
    new MiniCssExtractPlugin({
      filename: '[name].css',
    }),
  ],
  module: {
    rules: [
      {
        test: /\.css$/,
        use: [MiniCssExtractPlugin.loader, 'css-loader'],
      },
    ],
  },
};

# shimming

shimming 垫片的使用

//现在有两个JS文件:

//一个index.js,内容如下
import _ from 'lodash';
import $ from 'jquery';
import {ui} from './jquery-ui';
ui();

/**其他业务代码*/

//一个我们模仿的第三方模块jqurey-ui.js文件:
import $ from 'jquery';
export function ui() {
    $('body').css('background', 'red');
}
两个文件的关系:jqurey-ui.js是一个第三方模块,jqurey-ui.js模块依赖于jQuery,
但是这个模块中并没有导入jQuery;我们在index.js中导入了这个模块,
并且在index.js中我们还引入了lodash模块和jquery模块;
此时,我们去执行导入的UI函数可以运行吗?
答案是不可以执行,会报错,这是因为前段模块化存在变量隔离的原因,
在index.js中导入的jQuery并不能在jquery-ui.js中生效;
也就是本模块导入的变量、类、函数,只在本模块生效,不会对导入模块生效;

这个时候我们可以用到 webpack.ProvidePlugin

const webpack = require('webpack');
plugins: [
    new webpack.ProvidePlugin({
        '_': 'lodash',
    })
]

# 使用imports-loader实现垫片

现在我们有这样一个场景,想让所有的 this 都指向 windows 这个时候可以使用 imports-loader

npm install imports-loader --save-dev

webpack 配置

{
    test: /\.js$/,
    exclude: /node_modules/,
    use: [
        { loader: "babel-loader" },
        {
        loader: "imports-loader? ",
        options: "this=>window"
        }
      ],
 }
更新时间: 8/14/2020, 5:05:55 PM