微前端架构ModuleFederationPlugin源码解析

目录
  • 序言
  • 背景
  • MF 基本介绍
  • 应用场景
    • 微前端架构
    • 服务化的 library 和 components
  • ModuleFederationPlugin 源码解析
    • 入口源码
    • Exposes
    • Remotes
    • Shared
    • 小结
  • 总结

序言

本文是 Webpack ModuleFederationPlugin(后面简称 MF) 源码解析 文章中的第一篇,在此系列文章中,我将带领大家抽丝剥茧、一步步地去解析 MF 源码。当然为了帮助大家理解,可能中间也会涉及到 Webpack 源码中的其它实现,我会根据情况或浅或深的一并进行讲解。因为看 Webpack 源码需要掌握的知识量非常大,所以为了更好理解文章中的内容,你最好有如下 Webpack 相关的背景知识:

  • 对 Webpack 核心的数据结构:Dependency、Module、Chunk 等有基本的认识
  • 了解 Webpack 中的插件机制,对基于 tabpable 的 Hooks 机制有一定的了解,如果写过 Webpack 插件就更好了
  • 看过 MF 的官方文档,对其带来的关键性作用有基本的认识,如果了解一些其应用场景就更好了

话不多说,让我们开始正文。

背景

先简单说一下为什么要去阅读 MF 的源码,我个人理解阅读源码有两个原因:

一,它的实现非常优秀,通过阅读源码能学习一些设计思想和编程技巧;

二,工作或者自己的项目使用到了,但是官方给的文档不太够,遇到问题无论最后有没有解决,都有点摸不着头脑,阅读源码是为了更好地了解其内部实现,遇到问题更容易 debug。

而我阅读 MF 的源码,主要是出于第二种目的,当然我个人对 Webpack 也是非常感兴趣。目前我们部门 B 端的产品是基于 MF 实现的微前端架构,而我主要负责 B 端的开发以及参与 B 端性能优化专项,今年大部分时间都是跟 MF “搏斗”。

虽然到目前为止,性能优化已经获得了一些阶段性的胜利,但是实际上在这个过程中,我们还是走了很多弯路。这些弯路不少是由于对 MF 内部的实现细节不够了解导致的,当然除此之外,我们还需要建立一套规范的 MF 标准化开发流程。所以,阅读 MF 源码对于我个人来说非常有必要。

首先,我们先简单了解下 MF 相关知识。

MF 基本介绍

首先,MF 是一个 Webpack 的官方插件,在 Webpack 生态中有茫茫多的插件中,好像一个插件有点微不足道。但是,MF 的作者称其为 “A game-changer in JavaScript architecture”,当然从构建工具的角度来讲,有点言过其实,因为它只能用于 Webpack 中。

但是从它带来的 JavaScript 架构设计上的理念:远程依赖共享(复用组件或者其它逻辑), 我觉得其实是给前端带来新的思考视角。

以前我们复用组件或者逻辑主要的方式有:

  • 抽离一个 NPM 包,从维护性和复用性角度来讲,是目前最常见的方式。缺点在于,在微前端架构中,如果 fix 了一个 NPM 包问题,那么每一个应用都需要升级版本,重新构建打包部署上线,多团队开发的时候非常低效;
  • 将产物打包成 UMD 的格式,然后通过 CDN 的方式能一定程度解决重新构建打包上线的问题,但是随着复用的组件和逻辑越多,可能会引入很多多余的 chunk 问题(如果对性能有很高的要求) 。比如 A 和 B 组件同时依赖了 lodash,那么打包成 UMD 格式有多余的 lodash chunk,没法复用。

我们来看下 MF 是怎么解决这个问题的。首先看一个简单的 MF 使用的例子,假设我们现在有两个应用 app1 和 app2:

// app1 webpack.config.js
module.exports = {
  // 省略其它配置
	plugins: [
    new ModuleFederationPlugin({
      name: 'app1',
      filename: 'remoteEntry.js',
      remotes: {
        app2: 'app2@http://localhost:3002/remoteEntry.js',
      },
      exposes: {
        './input': './src/components/Input'
      },
      shared: {
        'react': {
          singleton: true,
          requiredVersion: require('./package.json').dependencies.react
        },
        'react-dom': {
          singleton: true,
          requiredVersion: require('./package.json').dependencies['react-dom']
        },
        'lodash': {
          requiredVersion: require('./package.json').dependencies['lodash'],
          singleton: true,
        }
      }
    }),
  ]
}
// app1 src/components/Input.tsx
import * as React from 'react'
import { Input } from 'antd'
export default function WrapperInput () {
  return (
    <div>
      app1 input: <Input />
    </div>
  )
}
// app1 src/App.tsx
import { Input } from 'antd';
import * as React from 'react';
const RemoteButton = React.lazy(() => import('app2/Button'));
const App = () => (
  <div>
    <h1>Typescript</h1>
    <h2>App 1</h2>
    <React.Suspense fallback="Loading Button">
      <RemoteButton />
    </React.Suspense>
    <div>
      <Input />
    </div>
  </div>
);
export default App;

app2 的部分代码:

// app2 webpack.config.js
module.exports = {
  // 省略其它配置
  plugins: [
    new ModuleFederationPlugin({
      name: 'app2',
      filename: 'remoteEntry.js',
      exposes: {
        './Button': './src/Button',
      },
      remotes: {
        app1: 'app1@http://localhost:3001/remoteEntry.js',
      },
  }),
}
// app2 src/Button.tsx
import * as React from 'react';
const Button = () => <button>App 3 Button</button>;
export default Button;
// app2 src/App.tsx
import * as React from 'react';
import LocalButton from './Button';
import RemoteInput from 'app1/input';
const App = () => (
  <div>
    <h1>Typescript</h1>
    <h2>App 3</h2>
    <LocalButton />
    <React.Suspense fallback={null}>
      <RemoteInput />
    </React.Suspense>
  </div>
);
export default App;

最后实现的效果:

简单的 Webpack 配置,我们就可以实现 app1 和 app2 两个应用之间的组件远程共享,从代码看,我们知道 app1 依赖了 app2 的 Button 组件,而 app2 依赖了 app1 的 Input 组件。

当然不止如此,MF 还可以做到:

  • 依赖复用, app1 和 app2 同时依赖了 react 和 react-dom,那我们可以在双方的 Webpack 配置中,将两个依赖配置成 shared,而且通过 requiredVersion 指定版本;
  • 微前端架构,微前端架构有很多实现的方式,比如 iframe、web-component 等,但是 MF 的出现,使得实现一套微前端的架构更加简单,也能非常容易解决微前端架构中的一些组件复用问题、频繁构建部署上线问题;
  • 支持服务端渲染MF 的实现不依赖浏览器,同样的代码,只需要将 Webpack 配置中的target改成

node,那么构建的产物就能支持 SSR。

到这里,读者已经对 MF 的使用和定位有了基本的印象,根据 MF 带来的全新的复用能力,我们可以做一些应用场景的思考。

应用场景

微前端架构

微前端是这几年比较火的一个前端应用架构方案,其中比较核心的一点是各子应用之间要做到独立开发,独立构建部署上线。 从上一节对 MF 的介绍中,我们发现它天然就已经有这个优势,因此为了设计一个基于 MF 的微前端架构,我们要解决的第一点是子应用之间需要有个类似中心化的服务,将其它子应用的服务地址下发给需要消费的子应用;第二点,我们要解决子应用之间的一些通信问题,例如共享的一些用户状态。 当然还有一些其它问题,例如 UI 一致性问题。

基于以上的问题,我们可以很容易想到一种非常经典的微前端架构方案,那就是基于一个基座服务的中心化的架构方式。

每个 APP 都是一个子应用,这里可以有两种方式:如果完全不需要依赖基座的状态,则可以做成一个更加通用的前端服务,只作为提供方,在 MF 中也称为 remotes 应用。如果需要依赖主应用的状态,或者说只导出路由让基座帮忙注册,这样就可以共享基座的所有状态,这种方式与我们现在 B 端的架构方式类似。这样的架构方式,也能通过 MF shared 机制锁定 UI 库的版本,保证所有子应用 UI 的一致性。

服务化的 library 和 components

跳出微前端架构,假设我们现在的场景是维护一个巨型前端应用,我们发现随着页面和依赖的第三方依赖逐渐增多,那么每次开发构建部署上线的时长也会不断增加。虽然 Webpack v5+ 版本已经做了很多优化例如本地缓存,但是对于巨型应用,我们还是发现构建还是非常低效。于是,基于 MF 的能力,我们可以做这样的一个架构设计:

我们可以将平时使用的第三方库和组件库,分别做成一个单独的服务,如果部门技术栈统一的项目可以通过 MF 插件远程使用这两个服务,这样无论是开发时还是上线构建都可以省掉这部分的构建时间,一定程度上提高了开发效率。

MF 的使用姿势非常灵活,你可以根据开发需要,充分挖掘更多的使用场景。MF 介绍的部分就到这里,下面我们正式进入源码解析的内容。

ModuleFederationPlugin 源码解析

入口源码

MF 插件相关的源码放在 lib/container 下,我们首先看下 lib/container/ModuleFedration.js的代码:

// 省略一些 import 代码
class ModuleFederationPlugin {
	/**
	 * @param {ModuleFederationPluginOptions} options options
	 */
	constructor(options) {
		validate(options);
		this._options = options;
	}
	/**
	 * Apply the plugin
	 * @param {Compiler} compiler the compiler instance
	 * @returns {void}
	 */
	apply(compiler) {
		const { _options: options } = this;
		// expose 模块编译产物导出的类型,选项有 var、umd、commonjs、module 等,跟 output 配置中的 library 作用是一样的
		// var 代表输出的模块是挂在 window 对象上
		const library = options.library || { type: "var", name: options.name };
		// remote 模的类型,选项有 var、umd、commonjs、module 等,跟 output 配置中的 library 作用是一样的
		const remoteType =
			options.remoteType ||
			(options.library && isValidExternalsType(options.library.type)
				? /** @type {ExternalsType} */ (options.library.type)
				: "script");
		// 	enabledLibraryTypes 专门存储 entry 需要输出的 library 类型,然后被 EnableLibraryPlugin 插件消费,
		if (
			library &&
			!compiler.options.output.enabledLibraryTypes.includes(library.type)
		) {
			compiler.options.output.enabledLibraryTypes.push(library.type);
		}
		// 在完成所有内部插件注册后处理 MF 插件
		compiler.hooks.afterPlugins.tap("ModuleFederationPlugin", () => {
			if (
				options.exposes &&
				(Array.isArray(options.exposes)
					? options.exposes.length > 0
					: Object.keys(options.exposes).length > 0)
			) {
				// 如果有 expose 配置,则注册一个 ContainerPlugin
				new ContainerPlugin({
					name: options.name,
					library,
					filename: options.filename,
					runtime: options.runtime,
					shareScope: options.shareScope,
					exposes: options.exposes
				}).apply(compiler);
			}
			if (
				options.remotes &&
				(Array.isArray(options.remotes)
					? options.remotes.length > 0
					: Object.keys(options.remotes).length > 0)
			) {
				// 如果有 expose 配置,则初始化一个 ContainerReferencePlugin
				new ContainerReferencePlugin({
					remoteType,
					shareScope: options.shareScope,
					remotes: options.remotes
				}).apply(compiler);
			}
			if (options.shared) {
				// 如果有 shared 配置,则初始化一个 SharePlugin
				new SharePlugin({
					shared: options.shared,
					shareScope: options.shareScope
				}).apply(compiler);
			}
		});
	}
}

从代码中可以看出,MF 插件入口的代码其实不复杂,核心的代码不到 100 行,我们首先把焦点放在插件初始化的 options参数上,它的类型为 ModuleFederationPluginOptions

这里有个细节可以注意下,因为 Webpack 的源码是用纯 JS 写的,为了弥补如像 TypeScript 的类型注释的优势使得源码更加可读的问题,Webpack 使用了 JSDoc 配合 VS Code,在大多数场景下也能起到类型注释的效果,而 Webpack 根目录下的 declarations目录使用了 TS 定义了核心的一些数据类型,然后导出给其它 JS 文件在使用 JSDoc 时使用。

我们回到主题,我们看下ModuleFederationPluginOptions的类型定义:

export interface ModuleFederationPluginOptions {
	/**
	 * container 应用导出的模块配置,一般是一个对象
	 */
	exposes?: Exposes;
	/**
	 * 打包产物的文件名称
	 */
	filename?: string;
	/**
	 * 构建产物的类型,里面的 type 配置可以是 umd、commonjs、var 等类型
	 */
	library?: LibraryOptions;
	/**
	 * container 的名称
	 */
	name?: string;
	/**
	 * 依赖的 remote 应用 library 类型,配置的值可以是 umd、commonjs、script、var 等
	 */
	remoteType?: ExternalsType;
	/**
	 * container 应用依赖的远程应用
	 */
	remotes?: Remotes;
	/**
	 * 配置了该选项,会为模块split 一个以该名称命名的 chunk
	 */
	runtime?: EntryRuntime;
	/**
	 * 所有共享模块的作用域名称,默认为 default,很少会修改
	 */
	shareScope?: string;
	/**
	 * 应用之间需要共享的模块
	 */
	shared?: Shared;
}

每个选项我都用注释做了简单的介绍,我们重点关注几个常用的配置,对于 libraryruntime

remoteType等配置平时很少使用,这里先不过多介绍,后面看到相关的源码可以再回顾。

filenamename比较好理解,以上一小节的 app1 的 Webpack 配置为例,我们可以看到其配置如下:

   new ModuleFederationPlugin({
      name: 'app1',
      filename: 'remoteEntry.js',
      // 省略其它配置
   })

如果这样配置,app1 正好expose了一些模块给其它应用消费,例如 app2,则 app2 首先要通过 app1 的

name去找到它,也就是会在 remotes的配置中添加 app1 的指向,这里等会介绍 remotes选项时再细说。而 app2 在运行时就会加载到 app1 的构建产物 remoteEntry.js,访问 app2 的服务,打开 network,我们可以看到其加载了 app1 的 remoteEntry.js

我们重点介绍下 exposesremotesshared等选项。

在上面的 MF 插件源码中,其核心的几行代码就是,在 afterPlugins hook触发后(完成其它所有内置插件初始化后),根据是否有上面三个配置,来决定是否要注册 ContainerPlugin

ContainerReferencePluginSharePlugin等插件。所以更加核心的实现,是分别交给了上面三个插件去完成。

Exposes

exposes的配置是告诉 Webpack 当前应用导出给其它应用消费的模块,首先我们来看下 exposes配置的类型定义:

export type Exposes = (ExposesItem | ExposesObject)[] | ExposesObject;
export type ExposesItem = string;
export type ExposesItems = ExposesItem[];
export interface ExposesObject {
	[k: string]: ExposesConfig | ExposesItem | ExposesItems;
}
export interface ExposesConfig {
	import: ExposesItem | ExposesItems;
	name?: string;
}

上面的类型定义相对来说比较简单,只是套娃比较多,还是以上面 app1 的 Webpack 配置为例,据我平时了解到的,最常见的配置方式还是:

exposes: {
	'./input': './src/components/Input'
},

但是你也可以配置:

exposes: {
	'./input': {
    name: 'input',
    import: './src/components/Input'
  }
},

这种方式配置会有什么不一样了?这里会留一个悬念,在看后续的源码中,我们再详细介绍。

Remotes

remotes配置是告诉 Webpack 当前应用依赖了哪些远程应用,我们来看下其类型定义:

export type Remotes = (RemotesItem | RemotesObject)[] | RemotesObject;
export type RemotesItem = string;
export type RemotesItems = RemotesItem[];
export interface RemotesObject {
	[k: string]: RemotesConfig | RemotesItem | RemotesItems;
}
export interface RemotesConfig {
	/**
	 * 共享模块需要依赖的其它模块
	 */
	external: RemotesItem | RemotesItems;
	// 共享作用域的名称,默认为 default
	shareScope?: string;
}

还是以 app1 为例,我们回顾其 remote 的配置:

remotes: {
  app2: 'app2@http://localhost:3002/remoteEntry.js',
},

告诉了 Webpack 如果需要消费 app2 导出的模块,那么则需要加载 app2 服务的 remoteEntry.js文件,所以 app1 在初始化的时候就会加载此文件,然后通过下面的方式加载 app2 导出的模块:

import RemoteButton from 'app2/Button';

是不是有点神奇,这里面的实现用了什么黑魔法,简单的几个配置,然后启动服务,就能消费其它远程应用的模块。保持耐心,后续我们将慢慢揭开其神秘的面纱。

Shared

MF 关于 shared配置部分是我个人觉得最复杂的部分,当然 SharedPlugin的实现也是相对来说比较复杂,因为这里牵扯到一些需要 shared配置延伸出的例如单例问题。 先留个悬念,稍后解释单例问题,我们还是先看 shared配置类型定义:

export type Shared = (SharedItem | SharedObject)[] | SharedObject;
export type SharedItem = string;
export interface SharedObject {
	[k: string]: SharedConfig | SharedItem;
}
export interface SharedConfig {
  // 配置了 eager 是告诉 webpack 该模块是作为一个 initial chunk,无论怎么样,初始化都需要加载该模块
	eager?: boolean;
	// 共享模块依赖的模块
	import?: false | SharedItem;
	// 共享模块的包名
	packageName?: string;
  // 共享模块的版本
	requiredVersion?: false | string;
	// 如果配置了 key,查找共享模块的时候,会在当前共享作用域查找配置的 key
	shareKey?: string;
	// 共享作用域
	shareScope?: string;
	// 是否需要保持单例
	singleton?: boolean;
	// 是否需要严格校验共享模块的版本,只有配置了 requiredVersion 配置该选型才有效
	strictVersion?: boolean;
  // 指定提供的模块的版本,将会替代低版本的模块,但是不会替代版本更好的模块
	version?: false | string;
}

SharedConfig类型我们就可以看到 shared配置有很多的场景需要适配,每个配置我都做了简单注释来介绍。当然可能这个时候,不熟悉 MF 的小伙伴看到这些配置可能是懵逼的状态。不用着急,这些配置项,在后面更加具体的源码使用场景,我会再进行介绍,这里先留个印象。

我们还是看下 app1 的配置:

shared: {
	'react': {
   	 singleton: true,
     requiredVersion: require('./package.json').dependencies.react
   },
   'react-dom': {
      singleton: true,
      requiredVersion: require('./package.json').dependencies['react-dom']
    },
    'lodash': {
      requiredVersion: require('./package.json').dependencies['lodash'],
      singleton: true,
   }
}

这里分别将 reactreact-domlodash等三方包配置成了 shared,这样有什么作用了?

实际上 shared配置是告诉 Webpack 这些依赖需要共享(复用) ,因为在 MF 的远程模块消费机制里面,多个应用之间可能会依赖相同的三方包,如果没有一个共享机制,那么一定会导致多余的 chunk 加载,而且还有其它需要解决的问题。

以前面的 app1 和 app2 为例,app1 本身是一个 react 应用,它依赖了 app2 的一个组件,而 app2 同样也依赖了 react ,那么如果没有这个共享模块的机制,那么 app1 消费 app2 的组件可能就还需要加载 app2 的构建的 react 依赖。而且我们知道,react 的运行机制是在同一个JS runtime 里面,是不能同时存在两个 react 实例的,这也是

singleton配置的由来,它的作用就是为了解决类似这样的场景。

当然,要讲清楚这部分的原理,还有运行机制,除了需要一定的 MF 使用经验外,还需要对其源码有一定的了解,我们后续在剖析 SharedPlugin插件源码时再详细聊。

小结

虽然 MF 插件入口的源码部分相对来说还是不复杂的,所以本小节我们聚焦在其配置上。实际上对于 上面提到的一些配置,例如 MF 插件的 libraryremoteType等配置在官网是没有提到的,包括 exposes

remotesshared等配置的一些更加高级的选项,这也是 Webpack 配置复杂然后官网又不完全介绍一直被人诟病的地方。

总结

本文我们从 MF 插件主入口出发,分析了其插件的注册时机,并且通过阅读这部分的源码,我们了解到:

  • 插件的配置选项除了常用的 exposesremotessharedfilenamename 等之外还有

libraryremoteTypesharedScope等配置项,可以指定 exposesremotes模块的

library类型;

  • MF 核心的源码实现是通过其它三个插件 ContainerPlugin

ContainerReferencePluginSharePlugin 等来实现,然后根据是否传入 exposes

remotesshared来决定是否需要初始化各个插件;

  • exposesremotesshared等选项有很多进阶的配置,特别是 shared配置比较复杂,从共享三方依赖、单例、版本锁定等角度思考,就可以想象这里面的设计不简单。

后续文章

下一篇文章,我们开始逐渐进入深水区,首先深入到 CotainerPlugin的源码,一步步揭开其神秘的面纱。为了更好理解后续的文章,建议读者了解一下 Webpack 构建流程,特别是核心的构建阶段DependencyModule之间的转换流程,更多关于微前端架构ModuleFederationPlugin的资料请关注我们其它相关文章!

(0)

相关推荐

  • JS微前端MicroApp基础使用

    目录 1. 介绍 2. 主应用 2.1 路由配置和基础页面 2.2 全局生命周期配置 2.3 主应用插件系统 3. 子应用 3.1 Webpack + Vue 子应用 3.2 Webpack + React 子应用 4. 应用路由配置说明 4.1 主应用路由 4.2 子应用路由 1. 介绍 MicroApp 是“京东零售”团队在2021年7月正式发布的一个微前端框架,并且抛弃了 Single SPA 的实现理念,基于 CustomElement 和 ShadowDom 来实现. MicroAPP

  • 微前端之Web组件自定义元素示例详解

    目录 我们知道的 Web组件使用 名称规范 组件传参数并可以写模板包括js和css Shadow Dom 影子节点 类中的构造函数和钩子函数 getter/setter属性和属性反射 扩展原生 HTML 我们知道的 第一:我们熟知的HTML标签有 a, p, div, section, ul, li, h2, article, head, body, strong, video, audio 等等 第二:我们知道,a标签是链接,p标签是段落,div是块级,h2是字体,strong 是粗体,vid

  • Vue qiankun微前端实现详解

    目录 引言 What:微前端是什么 Why:为什么选择微前端 微前端能做到什么 为什么不使用iFrame How:微前端实践 在主应用中注册微应用 在子应用导出相应的生命周期钩子 结尾 引言 前端时间有个契机,让我们团队开始进行微前端的相关实践. 最近正好有些成果了,来一个阶段性的总结,也方便后续进一步的开发. 可能第一次听说微前端的同学都会不明觉厉,那么ta到底是个啥?本章会从以下3个角度阐述我的理解: What:微前端是什么 Why:为什么选择微前端 How:微前端实践 What:微前端是什

  • 微前端之 js隔离 样式隔离 元素隔离问题详解

    目录 WebComponent 介绍 js隔离 问题 解决 方法一用 Proxy 代理 方法二 用快照 样式隔离 问题 方法一 样式增加不同前缀 方法二 ShadawDom 元素隔离 WebComponent 介绍 微前端框架中,js隔离.样式隔离.元素隔离是必须解决的三个问题,下面我们就来分别说说这三个问题是什么?怎么解决? 涉及的核心点是 Proxy,WebComponent,shadowDOM WebComponent 不在这三个问题中,但是我们做个简单介绍 浏览器默认的标签有 div,a

  • 微前端qiankun改造日渐庞大的项目教程

    项目背景 很多小伙伴在工作中都碰到过和我一样的场景,手上的某个项目越来越大,眼看着每次build时间越来越长,吐了.在杭州某独角兽我碰到了这样的一个项目,他叫运营后台,听名字就知道,他的主要用户是运营人员.问题就是随着公司业务的越来越多,这个运营后台承担的已经不是某一块业务了,而是所有业务的运营操作的中后台都在这上面.你可以这样理解,这个系统的每个一级菜单都是一块独立的业务,相互之间没有任何瓜葛:按常规的理解,这应该是单独的每一个project比较合理,但是正因为他的用户又都是公司的同一群人,他

  • 微前端qiankun沙箱实现源码解读

    目录 前言 LegacySandbox单实例沙箱 ProxySandbox多实例沙箱 SapshotSandbox 快照沙箱 结束语 前言 上篇我们介绍了微前端实现沙箱的几种方式,没看过的可以下看下JS沙箱这篇内容,扫盲一下.接下来我们通过源 码详细分析下qiankun沙箱实现,我们clone下qiankun代码,代码主要在sandbox文件夹下,目录结构为 ├── common.ts ├── index.ts // 入口文件 ├── legacy │ └── sandbox.ts // 代理沙

  • 微前端架构ModuleFederationPlugin源码解析

    目录 序言 背景 MF 基本介绍 应用场景 微前端架构 服务化的 library 和 components ModuleFederationPlugin 源码解析 入口源码 Exposes Remotes Shared 小结 总结 序言 本文是 Webpack ModuleFederationPlugin(后面简称 MF) 源码解析 文章中的第一篇,在此系列文章中,我将带领大家抽丝剥茧.一步步地去解析 MF 源码.当然为了帮助大家理解,可能中间也会涉及到 Webpack 源码中的其它实现,我会根

  • 微前端框架qiankun源码剖析之下篇

    目录 引言 四.沙箱隔离 4.1 JS隔离 1. Snapshot沙箱 2. Legacy沙箱 3. Proxy沙箱 4.2 CSS隔离 1. ShadowDOM 2. Scoped CSS 五.通信方式 六.结语 引言 承接上文  微前端框架qiankun源码剖析之上篇 注意: 受篇幅限制,本文中所粘贴的代码都是经过作者删减梳理后的,只为讲述qiankun框架原理而展示,并非完整源码.如果需要阅读相关源码可以自行打开文中链接. 四.沙箱隔离 在基于single-spa开发的微前端应用中,子应用

  • JS前端操作 Cookie源码示例解析

    目录 引言 源码分析 使用 源码 分析 set get remove withAttributes & withConverter 总结 引言 前端操作Cookie的场景其实并不多见,Cookie也因为各种问题被逐渐淘汰,但是我们不用Cookie也可以学习一下它的思想,或者通过这次的源码来学习其他的一些知识. 今天带来的是:js-cookie 源码分析 使用 根据README,我们可以看到js-cookie的使用方式: // 设置 Cookies.set('name', 'value'); //

  • Gateway网关源码解析

    目录 工作原理 配置类分析 路由模式源码分析 解决前后端的跨域问题 工作原理 客户端向 Spring Cloud Gateway 发出请求. 如果网关处理程序映射确定请求与路由匹配,则将其发送到网关 Web 处理程序. 此处理程序通过特定于请求的过滤器链运行请求. 过滤器用虚线划分的原因是过滤器可以在发送代理请求之前和之后运行逻辑. 执行所有“预”过滤器逻辑. 然后发出代理请求. 发出代理请求后,将运行“发布”过滤器逻辑. 配置类分析 jar包中加载的配置类,会注入到IOC容器中. 1.Gate

  • Java 线程池ThreadPoolExecutor源码解析

    目录 引导语 1.整体架构图 1.1.类结构 1.2.类注释 1.3.ThreadPoolExecutor重要属性 2.线程池的任务提交 3.线程执行完任务之后都在干啥 4.总结 引导语 线程池我们在工作中经常会用到.在请求量大时,使用线程池,可以充分利用机器资源,增加请求的处理速度,本章节我们就和大家一起来学习线程池. 本章的顺序,先说源码,弄懂原理,接着看一看面试题,最后看看实际工作中是如何运用线程池的. 1.整体架构图 我们画了线程池的整体图,如下: 本小节主要就按照这个图来进行 Thre

  • java底层AQS实现类kReentrantLock锁的构成及源码解析

    目录 引导语 1.类注释 2.类结构 3.构造器 4.Sync同步器 4.1.nonfairTryAcquire 4.2.tryRelease 5.FairSync公平锁 6.NonfairSync非公平锁 7.如何串起来 7.1lock加锁 7.2tryLock尝试加锁 7.3unlock释放锁 7.4Condition 8.总结 引导语 本章的描述思路是先描述清楚 ReentrantLock 的构成组件,然后使用加锁和释放锁的方法把这些组件串起来. 1.类注释 ReentrantLock 中

  • 源码解析gtoken替换jwt实现sso登录

    目录 jwt的问题 jwt的请求流程图 gtoken的优势 注意问题 演示demo 分析源码 刷新token GfToken结构体 思考题 总结 jwt的问题 首先说明一个jwt存在的问题,也就是要替换jwt的原因: jwt无法在服务端主动退出的问题 jwt无法作废已颁布的令牌,只能等到令牌过期问题 jwt携带大量用户扩展信息导致降低传输效率问题 jwt的请求流程图 gtoken的优势 gtoken的请求流程和jwt的基本一致. gtoken的优势就是能帮助我们解决jwt的问题,另外还提供好用的

  • Mango Cache缓存管理库TinyLFU源码解析

    目录 介绍 整体架构 初始化流程 读流程 写流程 事件处理机制 主流程 write 清理工作 缓存管理 什么是LRU? 什么是SLRU? 什么是TinyLFU? mango Cache中的TinyLFU counter counter的初始化 counter的使用 lruCache slruCache filter TinyLFU的初始化 TinyLFU写入 TinyLFU访问 增加entry的访问次数 估计entry访问次数 总结 介绍 据官方所述,mango Cache是对Guava Cac

  • Android10 启动Zygote源码解析

    目录 app_main ZygoteInit preload preloadClasses preloadResources preloadSharedLibraries forkSystemServer app_main 上一篇文章: # Android 10 启动分析之servicemanager篇 (二) 在init篇中有提到,init进程会在在Trigger 为late-init的Action中,启动Zygote服务,这篇文章我们就来具体分析一下Zygote服务,去挖掘一下Zygote负

  • Vue中$nextTick实现源码解析

    目录 正文 先看一个简单的问题 内部实现 先看第一块: 再看第二块: 然后是第三块: 最后是第四块: 正文 先看一个简单的问题 <template> <div @click="handleClick" ref="div">{{ text }}</div </template> <script> export default { data() { return { text: 'old' } }, methods:

随机推荐