为什么要代码分离?

代码分离是 webpack 中最引人注目的特性之一。此特性能够把代码分离到不同的 bundle 中,然后可以按需加载或并行加载这些文件。代码分离可以用于获取更小的 bundle,以及控制资源加载优先级,如果使用合理,会极大影响加载时间。(webpack的优化,减少加载时间)

常用的代码分离方法有三种:

  • 入口起点:使用 entry 配置手动地分离代码。
  • 防止重复:使用 Entry dependencies 或者 SplitChunksPlugin 去重和分离 chunk。
  • 动态导入:通过模块的内联函数调用来分离代码。

1. entry配置手动分离

操作步骤请看官网:https://webpack.docschina.org/guides/code-splitting/#entry-points

在another-moudle.js内引入lodash后打包
image.png
如果我们在index.js内也引入lodash
image.png
原本的12.6k 变成了 1.38M ,并且两个代码都引入了lodash,重复的包打包了两次,浪费了性能

不足:

(1)重复引用:如果入口 chunk 之间包含一些重复的模块,那些重复模块都会被引入到各个 bundle 中。
(2)不能动态导入:这种方法不够灵活,并且不能动态地将核心应用程序逻辑中的代码拆分出来。
下面让我们来一步步解决这些不足

2.防止重复

(1)添加入口依赖

  1. const path = require("path")
  2. const HtmlWebpackPulgin = require("html-webpack-plugin")
  3. // 本插件会将 CSS 提取到单独的文件中,为每个包含 CSS 的 JS 文件创建一个 CSS 文件,并且支持 CSS 和 SourceMaps 的按需加载。
  4. const MiniCssExtractPlugin = require("mini-css-extract-plugin")
  5. // 这个插件使用 cssnano 优化和压缩 CSS
  6. const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
  7. const toml = require('toml');
  8. const yaml = require('yamljs');
  9. const json5 = require('json5');
  10. module.exports = {
  11. mode: 'development',
  12. entry: {
  13. index: {
  14. import: './src/index.js',
  15. dependOn: 'shared',
  16. },
  17. another: {
  18. import: './src/another-module.js',
  19. dependOn: 'shared',
  20. },
  21. shared: 'lodash',
  22. },
  23. output: {
  24. filename: '[name].bundle.js',
  25. path: path.resolve(__dirname, 'dist'),
  26. },
  27. devtool: 'inline-source-map',
  28. plugins: [
  29. new HtmlWebpackPulgin({
  30. template: './index.html',
  31. filename: 'app.html',
  32. inject: 'body'
  33. }),
  34. ],
  35. devServer: {
  36. static: './dist',
  37. hot: true,
  38. },
  39. module: {
  40. rules: [
  41. {
  42. test: /\.js$/,
  43. exclude: /(node_modules|bower_components)/,
  44. use: {
  45. loader: 'babel-loader',
  46. options: {
  47. // presets预设
  48. presets: ['@babel/preset-env'],
  49. plugins: ['@babel/plugin-transform-runtime']
  50. }
  51. }
  52. },
  53. ]
  54. },
  55. optimization: {
  56. // runtimeChunk: 'single',
  57. // SplitChunksPlugin 插件
  58. optimization: {
  59. splitChunks: {
  60. chunks: 'all',
  61. },
  62. },
  63. }
  64. }

image.png

(2)SplitChunksPlugin 插件

image.png

3. 动态导入

(1)使用import

使用符合 ECMAScript 提案import()语法 来实现动态导入。
在src下创建async-module.js,并使用import

  1. // async-module.js
  2. async function getComponent() {
  3. const element = document.createElement("div")
  4. const { default: _ } = await import('lodash');
  5. element.innerHTML = _.join(['Hello', 'webpack'], ' ');
  6. return element;
  7. }
  8. getComponent().then((component) => {
  9. document.body.appendChild(component);
  10. });
  1. import { helloworld } from "./helloworld";
  2. import _ from 'lodash';
  3. // 引入async-module.js
  4. import "./async-module"
  5. helloworld()
  6. console.log(_.join(['index', 'module', 'loaded!'], ' '));

去除静态导入的部分
image.pngimage.png
加入静态导入的部分后打包
名字都变为了vender-node ………….
所以动态导入是可以和静态导入一起使用的
image.png
image.png

4. 应用场景

(1)懒加载

懒加载或者按需加载,是一种很好的优化网页或应用的方式。这种方式实际上是先把你的代码在一些逻辑断点处分离开,然后在一些代码块中完成某些操作后,立即引用或即将引用另外一些新的代码块。这样加快了应用的初始加载速度,减轻了它的总体体积,因为某些代码块可能永远不会被加载。
(也就是说,只有这个模块被用到时,他才会开始 请求服务器并加载 其内容)

我们在 src 下创建一个 getImg.js 文件

  1. import imgsrc_1 from './asset/云谨.jpg'
  2. export function getImg() {
  3. const img_1 = document.createElement('img')
  4. img_1.src = imgsrc_1
  5. img_1.style.cssText = 'width:100px; height:100px;'
  6. document.body.appendChild(img_1)
  7. }

然后在 index.js 中加入

  1. const button = document.createElement('button')
  2. button.textContent = "add"
  3. button.addEventListener('click', () => {
  4. // 魔法注释:修改被打包之后的文件名
  5. import(/* webpackChunkName:'img' */ './getImg').then(({ getImg }) => {
  6. getImg()
  7. })
  8. })
  9. document.body.appendChild(button)

不修改打包前的文件名 则为:src_math_js.bundle.js
image.png
魔法注释修改后:
image.png
我们打开app.html,f12 打开网络查看,并刷新一下
这是点击add之前
image.png
点击add之后(第一次点击,返回状态码为200)
image.png
当我们刷新页面,再次点击时,状态码为304(由于我们懒加载的一般都是静态资源,所以浏览器会把资源缓存下来,当我们再次刷新的时候,他就不会请求服务器,而是直接找我们的缓存了)(当我们的资源更新后,重新刷新,他就会请求新的资源,这时状态码又会是200)
image.png

(2)预获取 / 预加载

  1. // math.js
  2. export function add(a, b){
  3. return a + b;
  4. }
  5. export function minus(a, b){
  6. return a - b;
  7. }

什么是预获取?

prefetch是一种利用浏览器的空闲时间加载页面将来可能用到的资源的一种机制;通常可以用于加载非首页的其他页面所需要的资源,以便加快后续页面的首屏速度;(在首屏渲染后,如果网络有空闲,则把设置了预获取的资源从服务器上获取)

prefetch特点

pretch加载的资源可以获取非当前页面所需要的资源,并且将其放入缓存至少5分钟(无论资源是否可以缓存);并且,当页面跳转时,未完成的prefetch请求不会被中断;

我们在刚刚的index.js 上的魔法注释上加上:webpackPrefetch: true

  1. const button = document.createElement('button')
  2. button.textContent = "add"
  3. button.addEventListener('click', () => {
  4. import(/* webpackChunkName:'math', webpackPrefetch: true */ './math').then(({ add }) => {
  5. console.log(add(4, 5));
  6. })
  7. })
  8. document.body.appendChild(button)

再回来看界面,没点击add之前
image.png
点击add后
image.png

什么是预加载?

preload 是一个声明式 fetch,可以强制浏览器在不阻塞 document 的 onload 事件的情况下请求资源。 preload 顾名思义就是一种预加载的方式,它通过声明向浏览器声明一个需要提交加载的资源,当资源真正被使用的时候立即执行,就无需等待网络的消耗。(在浏览器首屏渲染之前,就去请求服务器加载的资源,不阻塞onload)

preload特点

  1. preload加载的资源是在浏览器渲染机制之前进行处理的,并且不会阻塞onload事件;
  2. preload可以支持加载多种类型的资源,并且可以加载跨域资源;
  3. preload加载的js脚本其加载和执行的过程是分离的。即preload会预加载 相应的脚本代码,待到需要时自行调用;

可以指明哪些资源是在页面加载完成后即刻需要的。 对于这种即刻需要的资源,在页面加载的生命周期的早期阶段就希望开始获取,在浏览器的主渲染机制介入前就进行预加载。这一机制使得资源可以更早的得到加载并可用,且更不易阻塞页面的首屏渲染,进而提升性能。提供的好处主要是:

  • 将加载和执行分离开,可不阻塞渲染和 document 的 onload 事件
  • 提前加载指定资源,不再出现依赖的 font 字体隔了一段时间才刷出
  • Preload 使开发能够自定义资源的加载逻辑,且无需忍受基于脚本的资源加载器带来的性能损失。可以使用 Preload 进行 CSS 资源的预加载、并且同时具备:高优先级、不阻塞渲染等特性。
  • 在首屏渲染(关键CSS)中用到一些隐藏资源,比如字体,较大的图片资源等。我们可以内联关键CSS的前面使用 标签提前预加载这些重要资源:
  1. import(/* webpackChunkName:'img_1', webpackPreload: true */ './asset/优菈.jpg');

在我们点击 add 之前,图片就已经加载好了
image.png

(3)懒加载 和 预加载 的区别

1)概念:

懒加载:当这个资源没有被使用时,这个资源不会被加载,当这个资源通过事件被调用时,才从服务器 请求资源加载
预加载:提前从服务器获取资源,当用户需要使用时可直接从本地缓存中加载并渲染。

2)区别:

两种技术的本质:两者的行为是相反的,一个是提前加载,一个是迟缓甚至不加载。 懒加载:迟缓加载甚至不加载,减少服务器的压力。 预加载:提前加载需要的资源,会增加服务器压力,但首屏渲染快,用户体验好。

3)懒加载的意义及实现方式有:
意义:

懒加载的主要目的是作为服务器前端的优化,减少请求数或延迟请求数。

实现方式::

1.第一种是纯粹的延迟加载,使用setTimeOut或setInterval进行加载延迟.
2.第二种是条件加载,符合某些条件,或触发了某些事件才开始异步下载。
3.第三种是可视区加载,即仅加载用户可以看到的区域,这个主要由监控滚动条来实现,一般会在距用户看到某图片前一定距离遍开始加载,这样能保证用户拉下时正好能看到图片。

4)预加载的意义及实现方式有:
意义:

预加载可以说是牺牲服务器前端性能,换取更好的用户体验,这样可以使用户的操作得到最快的反映。

实现方式:

实现预载的方法非常多,比如:用CSS和JavaScript实现预加载;仅使用JavaScript实现预加载;使用Ajax实现预加载。
常用的是new Image();设置其src来实现预载,再使用onload方法回调预载完成事件。只要浏览器把图片下载到本地,同样的src就会使用缓存,这是最基本也是最实用的预载方法。当Image下载完图片头后,会得到宽和高,因此可以在预载前得到图片的大小(方法是用记时器轮循宽高变化)。