彻底搞懂Vite+Vue如何通过CDN加载资源

背景

目前在研究低代码引擎 lowcode engine,要基于这个开源项目定制化更符合公司业务需求的低代码平台(支持vue3)。有一个功能是,将低代码平台的产物组装成一个个独立的项目,因此需再开发一个系统,用来展示低代码平台构建的页面。

首先,在低代码平台构建页面,如下图所示(官方体验demo):
彻底搞懂Vite+Vue如何通过CDN加载资源

然后,低代码平台生成的页面,保存后,可以变成一个个完整的项目,然后再开发一个系统A,用来展示这些项目,这样我们就能把低代码平台的产物变成一个个普通的后台管理系统。

在构建这个系统A时,我使用的构建工具是 Vite,然而在将lowcode engine的产物集成到A系统时,由于产物中依赖的第三方库,比如ant-design-vue,需要通过CDN引入。

这就带来一个问题:ant-design-vue依赖Vue,导致Vue也需要通过CDN的方式引入。所以不论是在本地调试,还是最后构建上线,都需要把Vue及依赖Vue相关的库以CDN的方式加载进来。

接下来,先回顾分析过程,最后再给出Vite使用CDN加载资源的具体方案。

分析过程回放

首先,我们明确一些前提条件:

  1. 低代码平台产物依赖的UI组件(ant-design-vue),要求以CDN的方式引入。低代码平台提供了vue-renderer插件(打包成UMD的格式),完整实现了渲染逻辑。
  2. 系统A,用来集成低代码平台产物,使用Vue + Vite。

在系统A中,我们使用Vue的方式,就是正常import进来:

import { createApp } from 'vue'
...

假设,我们在开发调试时,Vue从本地加载,不引入CDN,会有什么表现?

  1. 提示找不到defineComponent,因为我们使用了UI组件库ant-design-vue,它通过CDN加载,需要依赖全局Vue(以CDN方式加载)
    彻底搞懂Vite+Vue如何通过CDN加载资源
  2. 看看vue-renderer插件引入vue的部分是如何处理的?通过下图,可以看到,Vue最终指向本地安装的包:
    彻底搞懂Vite+Vue如何通过CDN加载资源
  3. 系统A中引入Vue部分的代码处理,同样读取本地Vue
    彻底搞懂Vite+Vue如何通过CDN加载资源

彻底搞懂Vite+Vue如何通过CDN加载资源

彻底搞懂Vite+Vue如何通过CDN加载资源

因为ant-design-vue组件加载时,没有全局引入Vue,现在页面肯定没法正常渲染。那我们先来解决这个问题.

之所以找不到defineComponent,就是因为Vue没有全局加载,我们直接在index.html中加一个script标签,引入vue

<script src="https://cdn.jsdelivr.net/npm/vue@3.4.15"></script>

再运行项目,果然没有错误提示了,但页面依然没有正常加载,为什么呢?

根本原因:UI组件无法正常渲染

现在有一个冲突:UI组件依赖全局Vue,但用来渲染这些UI组件的vue-renderer插件,依赖的却是本地Vue。不论UI组件还是vue-renderer插件,它们依赖Vue,在初始化页面时,必然也会初始化一些数据,现在因为各自依赖的Vue不同,导致UI组件和vue-renderer插件没法建立正常的数据关系,自然页面也无法正常渲染。

那怎么解决这个问题呢?很简单,就是让他们依赖同一个Vue。

因为低代码平台实现机制的原因,我们没法改变UI组件依赖Vue的方式,所以只能让系统A以及vue-renderer插件也同样依赖全局Vue,而不是本地Vue。

这就涉及到我们开篇说的问题,在Vite + Vue的项目中,如何以CDN的方式来加载Vue?

Vite如何使用CDN加载资源?

一般,我们在项目中需要通过CDN的方式引入资源,目的是为了做优化,比如减少打包体积等。这种情况,通常不需要考虑开发环境。

但我们这次,在开发环境也需要通过CDN加载资源,所以在方案上要同时兼顾开发和生产环境。

Vite 想要支持CDN,并不是直接在index.html配置一个script标签就可以了,为什么呢?

我们先了解一下Vite的原理,它由两部分构成:

1)dev server:利用浏览器的ESM能力来提供源文件,具有丰富的内置功能,以及高效的HRM。

2)生产构建:生产环境利用Rollup来构建代码,并提供指令来优化构建过程。

从它的组成部分可以看出,在开发环境和生产环境,Vite行为有比较大差异,开发环境,依赖浏览器自带的能力,生产环境则通过Rollup打包。为什么开发环境和生产环境不保持一致呢?具体原因Vite官方也有说明,详见《为什么生产环境还需打包》。

这意味着,我们在开发和生产环境要使用CDN,但方案上会有些差异。

生产环境

在生产环境要想使用CDN有两步,以Vue为例:

1)引入CDN资源

2)构建时,需要改写项目中import Vue的地方

第1点比较简单,只需要在index.html中引入Vue资源就可以。

<script src="https://cdn.jsdelivr.net/npm/vue@3.4.15"></script>

第2点需要依靠一些插件来处理(rollup-plugin-external-globals)

修改vite.config.js配置文件

export default defineConfig({
    ...
    build: {
        rollupOptions: {
          external: ['vue'],
            plugins: [
              externalGlobals({
                vue: 'Vue',
             }),
          ],
       },
    }
   ...
})

为什么在index.html中引入Vue了,在Build时,还需要使用插件?

我们先看看rollup-plugin-external-globals的作用是什么:将外部导入转换为全局变量,例如 Rollup 的 output.globals 选项。

截取构建产物的部分代码,可以看到依赖Vue地方,都变成了全局变量Vue.方法名

彻底搞懂Vite+Vue如何通过CDN加载资源

正因为Vite是基于浏览器原生ES imports 的开发服务器,默认情况下,都是利用浏览器去解析imports,所以会读取import node_modules里的依赖包。

要想改变依赖指向,除了在index.html中引入资源,还需要额外的插件,修改代码中import资源的地方,将原来指向node_modules的依赖,改成全局变量。

生产环境如何配置CDN,更多详细内容,可以参考这篇文章

开发环境

在生产环境,我们是配置vite.config.js中的build属性,这个属性应用于生产环境构建,对开发环境并不起作用,那开发环境应该怎么做呢?

根据生产环境的构建思路,在开发环境是不是也有类似的插件,可以修改import资源的指向呢?

结果还真有,vite-plugin-external就可以做这个事情。

修改vite.config.js文件:

import vue from '@vitejs/plugin-vue'
import createExternal from 'vite-plugin-external'

export default defineConfig({
  ...
  plugins: [
    vue(),
    createExternal({
      externals: {
        vue: 'Vue',
      },
    }),
  ]
  ...
})

我们分析一下这个插件做了什么:

1)vue-renderer插件引入vue的部分是如何处理的:

彻底搞懂Vite+Vue如何通过CDN加载资源

彻底搞懂Vite+Vue如何通过CDN加载资源

我们可以看到,引入Vue的地方,已经被vite-plugin-external插件处理成引用全局Vue。

2)系统A中引入vue的部分如何处理:

彻底搞懂Vite+Vue如何通过CDN加载资源

彻底搞懂Vite+Vue如何通过CDN加载资源

彻底搞懂Vite+Vue如何通过CDN加载资源

最终Vue和vue-renderer一样,都指向了全局变量。

其实这个方案同样也可以用于生产环境,rollup-plugin-external-globals是Rollup插件,Vite只有生产构建时才使用Rollup,所以也限制了这个插件的使用范围。

vite-plugin-external则是为Vite开发,可以同时作用于开发和生产环境。

不过经过测试,发现这套方案想要在生产环境使用,需要将构建产物的format指定为iife:

import vue from '@vitejs/plugin-vue'
import createExternal from 'vite-plugin-external'

export default defineConfig({
  ...
  plugins: [
    vue(),
    createExternal({
      externals: {
        vue: 'Vue',
      },
    }),
  ],
  build: {
    rollupOptions: { 
      output: {
        format: 'iife'
      }
    },
  },
  ...
})

总结

通过以上方式,最终UI组件、vue-renderer插件、系统A,它们依赖的Vue都变成了全局变量Vue,它们之间可以通过Vue,正常建立关联,最后页面也正常渲染出来。

总结一下,在Vite中怎么通过CDN加载资源,第一,引入CDN资源,可直接在index.html中添加script标签,也可以根据需要写一个脚本动态引进来,或者使用Vite相关插件。第二,修改import资源的指向,这一步一般都是通过Vite插件来实现。

其中,关键就在于第二步如何实现。粗略看了一下vite-plugin-external源码,实现起来也比较简单,后面有时间分享一下Vite插件如何写,以及vite-plugin-external的实现原理。

原文链接:https://juejin.cn/post/7329206771565461531 作者:雨霖

(0)
上一篇 2024年1月29日 下午4:26
下一篇 2024年1月29日 下午4:37

相关推荐

发表回复

登录后才能评论