vite+vue3+ts搭建项目八(打包性能优化三:使用CDN)

vite+vue3+ts搭建项目八(打包性能优化三:使用CDN)

使用vite-plugin-cdn-import

下载npm包

官方github:https://github.com/MMF-FE/vite-plugin-cdn-import

1
npm install vite-plugin-cdn-import --save-dev

开发环境使用本地的npm包,cdn是打包时候才生效

在vite.config.ts中通过importToCDN引入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import { autoComplete, Plugin as importToCDN } from "vite-plugin-cdn-import";
import { visualizer } from 'rollup-plugin-visualizer';

// https://vitejs.dev/config/
export default defineConfig({
plugins: [
vue(),
importToCDN({
// prodUrl: 'https://cdn.jsdelivr.net/npm/{name}@{version}/{path}',
modules: [
{
name: 'vue',
var: 'Vue',
path: `https://unpkg.com/vue@3.2.45/dist/vue.global.js`,

},
{
name: 'vue-demi',
var: 'VueDemi',
path: `https://unpkg.com/vue-demi@0.13.11`,
},
{
name: 'vue-router',
var: 'VueRouter',
path: `https://unpkg.com/vue-router@4.1.6`,
},
{
name: 'element-plus',
var: 'ElementPlus',
path: 'https://unpkg.com/element-plus@2.3.3/dist/index.full.js',
// css: 'https://unpkg.com/element-plus@2.3.3/dist/index.css'
},
],
}),
],
// build: {
// rollupOptions: {
// external: ['vue', 'vue-demi', 'element-plus'],
// },
// }
})

注意事项:网上很多教程,还需要在buildrollupOptions添加对应的external,如上注释所示,其实是不需要的,vite-plugin-cdn-import插件会自动帮我们完成这部分工作。

参数获取方式

  • name:npm包的名称

    可以到https://www.jsdelivr.com进行查询,以element-plus为例

  • var:组件(main.ts)引用的名称
    比如ElementPlus

    1
    2
    import ElementPlus from 'element-plus'
    app.use(ElementPlus)
  • path:cdn网站存储对应的js地址
    输入对应名称,会自动跳转到对应的js文件,复制粘贴,需要修改版本,和自己项目的package.json版本一致

    ==允许只写到版本,后面会自动补齐==

    1
    2
    3
    4
    5
    {
    name: 'vue-demi',
    var: 'VueDemi',
    path: `https://unpkg.com/vue-demi@0.13.11`,
    },
  • css:对应位置,参考上图element-plus
    需要注意的是,css可以使用本地的,使用本地的就不要添加css,使用远程cdn的就需要在打包前注释本地的,否则会出现样式重叠。

可用的CDN网址

name pordUrl
jsdelivr https://cdn.jsdelivr.net/npm/vue@3.2.47/dist/vue.global.min.js(例子)
unpkg //unpkg.com/{name}@{version}/{path}
cdnjs //cdnjs.cloudflare.com/ajax/libs/{name}/{version}/{path}

打包并运行

  • 打包后dist/index.html自动添加了cdn链接

  • 打包后放到nginx中运行,查看对应依赖的加载地址

  • 打包后查看包体积
    element-plus已经被排除在外了

报错整理

  • 报错 TypeError: importToCDN is not a function

  • 解决方法,修改import引入方式

    官方issues:https://github.com/MMF-FE/vite-plugin-cdn-import/issues/22

    1
    2
    3
    // import importToCDN from "vite-plugin-cdn-import";

    import { autoComplete, Plugin as importToCDN } from "vite-plugin-cdn-import";

  • 报错 Uncaught TypeError: Cannot read properties of undefined (reading 'createElementVNode')

    可以看到代码里用到了vue
  • 解决:将vue也通过cdn引入即可


  • 报错 Uncaught ReferenceError: Vue is not defined

    vue-router或某些组件需要依赖vue

  • 解决:将vue也通过cdn引入即可


  • 报错 Uncaught TypeError: Failed to resolve module specifier "vue". Relative references must start with either "/", "./", or "../".

    参考1:[https://github.com/MMF-FE/vite-plugin-cdn-import/issues/13](https://github.com/MMF-FE/vite-plugin-cdn-import/issues/13) 参考2:[https://github.com/MMF-FE/vite-plugin-cdn-import/issues/32](https://github.com/MMF-FE/vite-plugin-cdn-import/issues/32) 参考3:[https://blog.csdn.net/qq_51634332/article/details/126325740](https://blog.csdn.net/qq_51634332/article/details/126325740)
  • 解决:importToCDN时在引入vue后添加vue-demi,已在vite.config.ts中给出,其他插件在vue-demi之后(顺序很重要)


  • vite-plugin-cdn-importunplugin-vue-componentsunplugin-auto-import不兼容

    参考:https://github.com/MMF-FE/vite-plugin-cdn-import/issues/13#issuecomment-1226897835

    使用importToCDN时,通过下面两个插件的组件不生效,需要在每个组件单独写import

    1
    2
    import AutoImport from "unplugin-auto-import/vite"
    import Components from 'unplugin-vue-components/vite';
  • 原因
    之所以使用 AutoImport 后就会有问题,是因为 unplugin-auto-import 的 enforce 为 post ,会最后才执行,导致通过 AutoImport 的注入的代码没有被此插件转换

  • 解决方法

    目前只能暂时不同时使用这两组插件,所以如果使用自动引入插件,这个插件不是最佳答案

直接按照下面这张方法是不行的,在vite.config.ts文件中,为importToCDN添加...扩展符,让它在其他所有插件之后再加载

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import { autoComplete, Plugin as importToCDN } from "vite-plugin-cdn-import";
import { visualizer } from 'rollup-plugin-visualizer';
import AutoImport from "unplugin-auto-import/vite"
import Components from 'unplugin-vue-components/vite';

export default defineConfig({
plugins: [
vue(),
AutoImport({
imports: ['vue', 'vue-router'],
dts: "src/auto-import.d.ts",
}),
Components({
//默认存放位置
//dts: "src/components.d.ts",
}),
{
...importToCDN({
// prodUrl: 'https://cdn.jsdelivr.net/npm/{name}@{version}/{path}',
modules: [
{
name: 'vue',
var: 'Vue',
path: `https://unpkg.com/vue@3.2.45/dist/vue.global.js`,
},
...
],
}),
enforce: 'post',
apply: 'build',
},
],
// build: {
// outDir: 'dist', // 指定输出路径
// // minify: 'terser', // 混淆器,terser 构建后文件体积更小,'terser' | 'esbuild' ,默认为esbuild
// rollupOptions: {
// external: ['vue', 'vue-demi', 'element-plus'],
// },
// }
})

使用rollup-plugin-external-globals(推荐)

为了解决上面的问题,externalGlobals是可以用上面的方法延迟加载的
参考1:https://github.com/ttk-cli/vue3-template/tree/test/cdn1
参考2:https://free_pan.gitee.io/freepan-blog

下载npm包

1
npm install -D rollup-plugin-external-globals

在vite.config.ts中引入

  1. 允许设置延迟加载
  2. rollupOptions需要设置external
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    import externalGlobals from 'rollup-plugin-external-globals'

    const externalGlobalsObj = {
    vue: 'Vue',
    'vue-demi': 'VueDemi',
    'vue-router': 'VueRouter',
    'element-plus': 'ElementPlus',
    }

    export default defineConfig({
    plugins: [
    vue(),
    {
    ...externalGlobals(externalGlobalsObj),
    enforce: 'post',
    apply: 'build',
    },
    ],
    build: {
    outDir: 'dist', // 指定输出路径
    rollupOptions: {
    external: Object.keys(externalGlobalsObj),
    },
    }
    })

    手动在打包后的index添加CDN

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="UTF-8" />
    <link rel="icon" type="image/svg+xml" href="/vite.svg" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Vite + Vue + TS</title>
    <script src="https://unpkg.com/vue@3.2.45/dist/vue.global.js"></script>
    <script src="https://unpkg.com/vue-demi@0.13.11"></script>
    <script src="https://unpkg.com/vue-router@4.1.6"></script>
    <script src="https://unpkg.com/element-plus@2.3.3/dist/index.full.js"></script>
    <script type="module" crossorigin src="/assets/index-c24c670c.js"></script>
    <link rel="stylesheet" href="/assets/index-f757e912.css">
    </head>
    <body>
    <div id="app"></div>

    </body>
    </html>

    自动添加CDN

需要用到vite-plugin-html插件

github官方:https://github.com/vbenjs/vite-plugin-html
参考:https://segmentfault.com/q/1010000041958028

  • 安装

    1
    npm i vite-plugin-html -D
  • 在vite.config.ts中引入(完整配置文件内容)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    import { defineConfig } from 'vite'
    import vue from '@vitejs/plugin-vue'
    import { autoComplete, Plugin as importToCDN } from "vite-plugin-cdn-import";
    import { visualizer } from 'rollup-plugin-visualizer';
    // 自动导入vue中hook reactive ref等
    import AutoImport from "unplugin-auto-import/vite"
    // 自动导入ui-组件 比如说ant-design-vue element-plus等
    import Components from 'unplugin-vue-components/vite';
    import externalGlobals from 'rollup-plugin-external-globals'
    import { createHtmlPlugin } from 'vite-plugin-html'

    const cdn = {
    css: [],
    js: [
    'https://unpkg.com/vue@3.2.45/dist/vue.global.js',
    'https://unpkg.com/vue-demi@0.13.11',
    'https://unpkg.com/vue-router@4.1.6',
    'https://unpkg.com/element-plus@2.3.3/dist/index.full.js',
    ],
    }

    const externalGlobalsObj = {
    vue: 'Vue',
    'vue-demi': 'VueDemi',
    'vue-router': 'VueRouter',
    // pinia: 'Pinia',
    'element-plus': 'ElementPlus',
    }

    // https://vitejs.dev/config/
    export default defineConfig(({ mode }) => {
    const isProduction = mode === 'production';

    return {
    plugins: [
    vue(),
    AutoImport({
    //安装两行后,在组件中不用再导入ref,reactive等
    imports: ['vue', 'vue-router'],
    dts: "src/auto-import.d.ts",
    //element
    }),
    Components({
    //element
    //默认存放位置
    //dts: "src/components.d.ts",
    }),
    visualizer({
    open: true, //注意这里要设置为true,否则无效
    gzipSize: true, //从源代码中收集 gzip 大小并将其显示在图表中
    brotliSize: true, //从源代码中收集 brotli 大小并将其显示在图表中
    emitFile: true, //在打包完的dist,否则在项目目录下
    filename: "stats.html", //分析图生成的文件名
    }),
    createHtmlPlugin({
    inject: {
    data: {
    cdnCss: isProduction ? cdn.css : [],
    cdnJs: isProduction ? cdn.js : [],
    },
    },
    }),
    {
    ...externalGlobals(externalGlobalsObj),
    enforce: 'post',
    apply: 'build',
    },
    ],
    build: {
    outDir: 'dist', // 指定输出路径
    // minify: 'terser', // 混淆器,terser 构建后文件体积更小,'terser' | 'esbuild' ,默认为esbuild
    rollupOptions: {
    external: Object.keys(externalGlobalsObj),
    },
    }
    }
    })
  • 在 index.html 中增加 EJS 标签

    需要注意的是:这个index.html不是打包后的,是项目的入口index.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite + Vue + TS</title>
<!-- 使用CDN的CSS文件 -->
<% for (const i of cdnCss) { %>
<link href="<%= i %>" rel="stylesheet" />
<% } %>
<!-- 使用CDN的JS文件 -->
<% for (const i of cdnJs) { %>
<script src="<%= i %>" defer></script>
<% } %>
</head>

<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>

</html>

环境变量:https://www.cnblogs.com/yayuya/p/17035869.html

  • 打包后的结果

这样就不需要手动引入了,但是需要添加一个新的npm包,实际项目中可以自行选择是否添加

element-plus相关问题

使用cdn引入需要全局引入,不要使用AutoImport的按需引入,否则最后生成的包仍然会有本地的element-plus.js

按需引入和CDN只能选择其一进行打包优化

使用CDN则不使用resolvers: [ElementPlusResolver()],在main.ts全局引入element-plus即可

1
2
3
4
5
6
7
8
9
10
11
12
13
AutoImport({
//安装两行后,在组件中不用再导入ref,reactive等
imports: ['vue', 'vue-router'],
dts: "src/auto-import.d.ts",
//element
// resolvers: [ElementPlusResolver()],
}),
Components({
//element
//默认存放位置
//dts: "src/components.d.ts",
// resolvers: [ElementPlusResolver()],
}),
1
2
3
import 'element-plus/dist/index.css'
import ElementPlus from 'element-plus'
app.use(ElementPlus)

==使用CDN未必会加快速度,只能减小打包体积,因为对应js和css需要从远程地址读取==