如果让你编写一个 jssdk,且里面要用到 vue,你会如何在 jssdk 中加载 vue?

你可能会想到使用 webpack 的 external 将 vue 依赖排除,以减少 jssdk 自身的体积。然而,这可能还会存在一些问题。

external 的问题

  • 他要求你的宿主 window 环境,或者宿主预编译环境下提前要有 vue。于是你要通过 peerDependencies 告诉对方。
  • 如果你不让宿主安装,那你得自己装到 jssdk 下,体积又会增大。最可怕的是:可能宿主他又安装了,此时你俩就存在重复冗余代码的问题了。

这是个两难的选择。如果宿主并没有 vue,则浏览器中运行 sdk 时候会报错。这就导致有些场景下,你对宿主环境有了一个“强迫”行为。例如人家宿主根本就用不到 vue,结果你反而要让他装一个 vue。 宿主可能会质疑你:“你 jssdk 想用 vue,凭什么让我给你装,你 jssdk 内部自己装不就行了”。

但反过来,如果你 jssdk 内部自己装了 vue,又会导致万一用到了某个“已经携带 vue”的宿主环境下,你反而 jssdk 内部又多打了一套 vue。造成冗余代码。

明确目标

于是,我们的目标应该是这样的:

  1. 当预编译的宿主环境(或浏览器宿主环境)有 vue 时候,我们就直接用宿主环境下的 vue module,跟宿主项目共用 vue 模块。
  2. 当宿主环境没有 vue 的时候,我们就使用自己的 vue(例如通过 webpack 的 import 动态懒加载来获取 vue)

增强兜底

所以经过实操,我总结出最佳实践是这样的:

  1. 将 vue 声明成 external,且不要声明成 peerDependencies。
  2. 配置 jssdk 的导出模式为 umd,且配置好 external 在不同情形下的获取方式。
  3. 如果宿主环境没有,我们通过 import 懒加载方式来远程加载自己所需要的 vue

external 的配置代码如下:

  externals: {
    vue: {
      module: 'vue',
      commonjs2: "vue",
      commonjs: "vue",
      root: "Vue", // indicates global variable
    },
  },

output 的配置如下:

  output: {
    filename: "guitar-[name]-[fullhash:8].js", // 输出文件名
    path: path.join(import.meta.dirname, "dist"), // 输出文件目录
    libraryTarget: "umd",
    globalObject: "this",
    library: "GdRender",
  },

有了上述配置后,我们还要解决宿主环境缺少 vue 时候的主动加载问题。具体代码如下:

// common/vue.js
import externalVue from "vue";

export async function resolveVue() {
  if (!externalVue && !resolveVue.resolvePromising) {
    resolveVue.resolvePromising = import("vue/dist/vue.esm-bundler.js").then(
      (Vue) => {
        externalVue = Vue;
        return externalVue;
      }
    );
    return resolveVue.resolvePromising;
  }
  return externalVue || resolvePromising;
}

代码中但凡使用到 vue 的地方,就这样引用 vue 即可:

import { resolveVue } from "./common/vue.js";

export default async function render() {
  const vueRes = await resolveVue();
  const { createApp } = vueRes;
  const app = createApp(App);
}

不声明 peerDependencies 的原因:

或许宿主项目根本没有用到 vue,而仅仅是你自己的 jssdk 里面需要用 vue 来渲染东西而已。

当然了,对于某些明知道宿主肯定会有 vue 环境的情形,我们还是建议采用传统套路:直接 peerDependencies 告诉宿主安装。

原理分析

我们看看编译产物直接截图分享下原理:

那如果宿主环境没有呢。则会看到我们 jssdk 中对应调用的地方,就是直接走 webpack 动态懒加载了:

效果收益

基于此,我可以保持暴露的主体 jssdk 的体积足够小。如下图是核心 main.js 的体积(未压缩):

然后万一任何使用我的 jssdk 的宿主环境下有引入 vue(无论是编译宿主,还是浏览器宿主)。则我们的 jssdk 就不会再去加载自身的 vue。

最大的好处是:此方案不需要宿主环境有任何感知。


sheldon
944 声望1.6k 粉丝

echo sheldoncui