nonzzz / vite-plugin-cdn

replace module with CDN. work with vite.
MIT License
73 stars 5 forks source link

[求助]element-plus的CSS如何CDN加载? #30

Closed toimc closed 7 months ago

toimc commented 7 months ago

感谢作者的无私付出,有两个小疑问。

[1] 我想在项目中,让element-plus的css能够像在https://www.npmjs.com/package/vite-plugin-cdn-import 这个库中一样cdn加载,如何实现?

[2] 这个库 https://github.com/posva/unplugin-vue-router ,不知道作者清楚不?文件自动路由,但是配合当前vite-plugin-cdn2,即使设置了'vue-router' cdn加载,依旧在打包之后,会把vue-router打包进项目。

@nonzzz

nonzzz commented 7 months ago

关于加入css的功能的话这个插件和vite-plugin-cdn-import是不一样的。你只需要加入spare属性就行。spare:['css文件即可'],关于vue-router没按照cdn加载的话我得研究下在给你答复 @toimc

nonzzz commented 7 months ago

我目前查看了他的文档。检查你的本地项目是否包含vue-router/autovue-router/auto/routes 如果包含 就设置对应的aliases.

具体配置如下


cdn({
 modules: ['vue', {name:'ElementPlus',aliases:['es'],spare:['https://cdn.jsdelivr.net/npm/element-plus@2.4.2/dist/index.min.css'], {name:'vue-router',aliases:['auto','auto/routes']}}
})
toimc commented 7 months ago

非常感谢上面的回复。

[1]

我尝试了一下,vue-router/auto的问题解决了,但是spare属性未生效,生成的html中未发现css:

这个是新生成的html:

<!doctype html>
<html lang="en">
  <head>
    <script src="https://cdn.jsdelivr.net/npm/vue@3.3.8/dist/vue.global.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/element-plus@2.4.2/dist/index.full.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/vue-demi@0.14.6/lib/index.iife.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/vue-router@4.2.5/dist/vue-router.global.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/pinia@2.1.7/dist/pinia.iife.js"></script>

    <meta charset="UTF-8" />
    <link rel="icon" href="/favicon.ico" />
    <meta
      name="viewport"
      id="viewport"
      content="user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1, width=device-width, viewport-fit=cover"
    />
    <title>Vite App</title>
    <script type="module" crossorigin src="/assets/index-e808e22b.js"></script>
    <link rel="stylesheet" href="/assets/index-f984f7b7.css">
  <link rel="manifest" href="/manifest.webmanifest"></head>
  <body class="m-safe">
    <div id="app"></div>

  </body>
</html>

配置如下:

modules: [
          'vue',
          { name: 'element-plus', aliases: ['es', 'lib'] },
          'vue-demi',
          {
            name: 'ElementPlus',
            aliases: ['es'],
            spare: ['https://cdn.jsdelivr.net/npm/element-plus@2.4.2/dist/index.min.css']
          },
          { name: 'vue-router', aliases: ['auto', 'auto/routes'] },
          'pinia'
        ],
        logLevel: 'slient'

[2] 还有一个构建过程中的日志打印问题:

image

为什么开启了logLevl: 'slient' 还是会打印

nonzzz commented 7 months ago

这个log 应该是我无心之举。后面版本会进行修复

nonzzz commented 7 months ago

我应该是看错了。ElementPlus 这个应该没有用你自己配置的element-plus 是正确的。只需要把spare 移进去。

toimc commented 7 months ago

sorry,我发现问题了,我嵌套错位置了。

新配置如下:

modules: [
          'vue',
          {
            name: 'element-plus',
            aliases: ['es', 'lib'],
            spare: ['https://cdn.jsdelivr.net/npm/element-plus@2.4.2/dist/index.min.css']
          },
          'vue-demi',
          { name: 'vue-router', aliases: ['auto', 'auto/routes'] },
          'pinia'
        ],
image

[1]为什么项目中没有使用.prod的js,而是全部使用的是global.js,这一块如何设置?

[2]如果使用 unpkg.com作为url,我觉得如下写resolve非常low,有没有优化建议?

resolve: (base, { name, version }) => {
          // vue@3.3.8/dist/vue.global.js
          switch (name) {
            case 'vue':
            case 'vue-router':
              return `${base}/${name}@${version}/dist/${name}.global.prod.js`
            case 'element-plus':
              return `${base}/${name}@${version}/dist/index.full.min.js`
            case 'vue-demi':
              return `${base}/${name}@${version}/lib/index.iife.js`
            default:
              return `${base}/${name}@${version}/dist/${name}.iife.prod.js`
          }
        }
nonzzz commented 7 months ago

你可以使用relativeModule


[
  { name: 'vue', relativeModule: './dist/vue.global.prod.js' },
  {
    name: 'element-plus',
    aliases: ['es', 'lib'],
    relativeModule: './dist/index.full.min.js',
    spare: ['https://cdn.jsdelivr.net/npm/element-plus@2.4.2/dist/index.min.css']
  },
  { name: 'vue-demi', relativeModule: './dist/index.iife.js' },
  { name: 'vue-router', aliases: ['auto', 'auto/routes'], relativeModule: './dist/vue-router.global.prod.js' },
  { name: 'pinia', relativeModule: './dist/iife.prod.js' }
]
toimc commented 7 months ago

上面的解决方案非常棒,非常感谢。

有两个问题:

[1] 使用unplugin-vue-router 插件之后,如果使用 { name: 'vue-router', aliases: ['auto', 'auto/routes'], relativeModule: './dist/vue-router.global.prod.js' } 是可以把vue-router使用cdn加载的,但是项目打包会出问题,所以的项目组件都未成功生成:

这个是把vue-router排除在外的情况:

✓ 108 modules transformed.
dist/manifest.webmanifest                          0.29 kB
dist/index.html                                    1.16 kB │ gzip:  0.52 kB
dist/assets/index-f984f7b7.css                    20.77 kB │ gzip:  4.76 kB
dist/assets/404-2711b914.js                        0.38 kB │ gzip:  0.27 kB
dist/assets/zh-cn-2b877b8e.js                      1.61 kB │ gzip:  1.12 kB
dist/assets/zh-cn-476220b1.js                      2.28 kB │ gzip:  0.90 kB
dist/assets/single-page-538c28e2.js                2.46 kB │ gzip:  1.14 kB
dist/assets/en-1fb5af88.js                         2.48 kB │ gzip:  0.66 kB
dist/assets/en-3f1991e3.js                         2.79 kB │ gzip:  1.22 kB
dist/assets/workbox-window.prod.es5-a7b12eab.js    5.29 kB │ gzip:  2.20 kB
dist/assets/index-1091f9ec.js                    209.65 kB │ gzip: 70.56 kB

未排除在外的情况:

✓ 253 modules transformed.
dist/manifest.webmanifest                                               0.29 kB
dist/index.html                                                         1.07 kB │ gzip:  0.51 kB
dist/assets/about-4d995ba2.css                                          0.09 kB │ gzip:  0.10 kB
dist/assets/draggable-85402e9d.css                                      0.12 kB │ gzip:  0.13 kB
dist/assets/ep-icon-picker-7eb084ee.css                                 0.12 kB │ gzip:  0.12 kB
dist/assets/CollapseTransition-e6724fa4.css                             0.12 kB │ gzip:  0.11 kB
dist/assets/avatars-6ef93914.css                                        0.15 kB │ gzip:  0.12 kB
dist/assets/index-4e7f02f1.css                                          0.20 kB │ gzip:  0.12 kB
dist/assets/notice-message-bf637873.css                                 0.35 kB │ gzip:  0.23 kB
dist/assets/index-3dc731e4.css                                         26.58 kB │ gzip:  5.72 kB
dist/assets/route-block-83d24a4e.js                                     0.03 kB │ gzip:  0.05 kB
dist/assets/Popover-8e086f8e.js                                         0.10 kB │ gzip:  0.11 kB
dist/assets/menu2-8a94d0c8.js                                           0.18 kB │ gzip:  0.17 kB
dist/assets/menu1-2-7d501830.js                                         0.19 kB │ gzip:  0.17 kB
dist/assets/menu1-1-1-032dab54.js                                       0.19 kB │ gzip
...  // 省略掉
dist/assets/index-610b58d1.js                                         241.45 kB │ gzip: 81.92 kB

优化index.js大概优化了40kb左右。

尝试下面的配置,解决:

{
    name: 'vue-router',
    aliases: ['auto'],
    relativeModule: './dist/vue-router.global.prod.js'
},

[2] 删除vue-router,在开发模式下,项目能够正常启动,但是build&preview会出错:

Uncaught TypeError: Cannot read properties of undefined (reading 'BaseTransition')
    at index-abc148f6.js:23:55328
(anonymous) @ index-abc148f6.js:23

看了一下,应该全是vue core的内容。

解决尝试1:

image

看了一下vue在head部分导入,没有问题,链接https://unpkg.com/vue@3.3.8/dist/vue.global.prod.js正常访问

解决尝试2:

起初我怀疑是关闭了AutoImport与Components在production模式下的作用,resolvers: isProd ? [] : [ElementPlusResolver()],实际上,无效。

====== 目前卡在这里了,望回复。

nonzzz commented 7 months ago

具体使用了哪些插件呢。能提供一份配置表吗

toimc commented 7 months ago

当然可以,package.json

{
  // ...
  },
  "dependencies": {
    "@unocss/reset": "^0.57.4",
    "@vueuse/core": "^10.6.1",
    "dayjs": "^1.11.10",
    "element-plus": "^2.4.2",
    "gsap": "^3.12.2",
    "pinia": "^2.1.7",
    "pinia-plugin-persistedstate": "^3.2.0",
    "sortablejs": "^1.15.0",
    "vue": "^3.3.8",
    "vue-i18n": "^9.6.5",
    "vue-router": "^4.2.5"
  },
  "devDependencies": {
    "@iconify/json": "^2.2.142",
    "@iconify/vue": "^4.1.1",
    "@intlify/unplugin-vue-i18n": "^1.5.0",
    // ...
    "@unocss/preset-wind": "^0.57.2",
    "@vitejs/plugin-vue": "^4.4.1",
    "@vitejs/plugin-vue-jsx": "^3.0.2",
    "rollup-plugin-visualizer": "^5.9.2",
    "sass": "^1.69.5",
    "unocss": "^0.57.2",
    "unplugin-auto-import": "^0.16.7",
    "unplugin-vue-components": "^0.25.2",
    "unplugin-vue-router": "^0.7.0",
    "vite": "^4.5.0",
    "vite-plugin-cdn2": "^0.15.1",
    "vite-plugin-mock": "^3.0.0",
    "vite-plugin-pwa": "^0.16.7",
    "vite-plugin-svg-icons": "^2.0.1",
    "vite-plugin-vue-layouts": "^0.8.0"
  }
}

vite.config.ts:

// ..
import vue from '@vitejs/plugin-vue'
import vueJsx from '@vitejs/plugin-vue-jsx'

import { VueRouterAutoImports } from 'unplugin-vue-router'

import VueRouter from 'unplugin-vue-router/vite'
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'

// @ts-ignore
import Layouts from 'vite-plugin-vue-layouts'
import UnoCSS from 'unocss/vite'

import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'

import { VitePWA } from 'vite-plugin-pwa'

import { viteMockServe } from 'vite-plugin-mock'
import { createSvgIconsPlugin } from 'vite-plugin-svg-icons'
import I18n from '@intlify/unplugin-vue-i18n/vite'
import { visualizer } from 'rollup-plugin-visualizer'

import { cdn } from 'vite-plugin-cdn2'

// https://vitejs.dev/config/
export default defineConfig(({ mode, command }) => {
  const isProd = mode === 'production'
  return {
    plugins: [
      VueRouter(),
      vue({
        script: {
          defineModel: true,
          propsDestructure: true
        }
      }),
      vueJsx(),
      UnoCSS(),
      AutoImport({
        include: [
          /\.[tj]sx?$/, // .ts, .tsx, .js, .jsx
          /\.vue$/,
          /\.vue\?vue/, // .vue
          /\.md$/ // .md
        ],

        // global imports to register
        imports: [
          // presets
          'vue',
          // 'vue-router'
          VueRouterAutoImports,
          '@vueuse/core'
        ],
        resolvers: isProd ? [] : [ElementPlusResolver()],
        vueTemplate: true
      }),
      Components({
        directoryAsNamespace: false,
        collapseSamePrefixes: true,
        resolvers: isProd ? [] : [ElementPlusResolver()]
      }),
      Layouts({
        layoutsDirs: 'src/layouts',
        defaultLayout: 'default'
      }),
      VitePWA({
        manifest: {
          name: 'Vite App',
          short_name: 'Vite App',
          theme_color: '#ffffff',
          icons: [
            {
              src: '/192x192.png',
              sizes: '192x192',
              type: 'image/png'
            },
            {
              src: '/512x512.png',
              sizes: '512x512',
              type: 'image/png'
            }
          ]
        },
        registerType: 'autoUpdate'
      }),
      viteMockServe({
        mockPath: 'mock',
        enable: false
      }),
      createSvgIconsPlugin({
        // 指定需要缓存的图标文件夹
        iconDirs: [path.resolve(process.cwd(), 'src/assets/icons')],
        // 指定symbolId格式
        symbolId: 'icon-[dir]-[name]'
      }),
      I18n({
        include: [path.resolve(__dirname, './locales/**')],
        // 说明:由于配置了modules/i18n.ts中默认为legacy: false
        // 所以禁止修改
        compositionOnly: true,
        jitCompilation: true
      }),
      visualizer({
        open: true
      }),
      cdn({
        url: 'https://unpkg.com',
        modules: [
          { name: 'vue', relativeModule: './dist/vue.global.prod.js' },
          { name: 'vue-demi', relativeModule: './dist/index.iife.js' },
          {
            name: 'vue-router',
            aliases: ['auto'],
            relativeModule: './dist/vue-router.global.prod.js'
          },
          {
            name: 'element-plus',
            relativeModule: './dist/index.full.min.js',
            aliases: ['es', 'lib'],
            spare: [
              'https://cdn.jsdelivr.net/npm/element-plus@2.4.2/dist/index.min.css',
              'https://cdn.jsdelivr.net/npm/element-plus@2.4.2/theme-chalk/dark/css-vars.css'
            ]
          },
          { name: 'pinia', relativeModule: './dist/iife.prod.js' }
        ],
        logLevel: 'slient'
      })
    ],
    resolve: {
      alias: {
        '@': fileURLToPath(new URL('./src', import.meta.url))
      }
    },
    server: {
      host: '0.0.0.0'
    }
  }
})
nonzzz commented 7 months ago

@toimc 我在本地制作了一个最小复现demo。我觉得你并不需要isProd 这个字段。autoImportcomponents的resolvers照常使用即可。不需要空数组。同时因为按需引入了。这里是题外话我觉得既然按需引入了那么ElementPlus 如果是轻度使用完全可以不用cdn.直接打包进去即可。因为这2个插件会附带引入css 打包进chunk这一点你可以注意一下。关于router的这个我没遇到问题。

toimc commented 7 months ago

我做了一个最小 demo,https://github.com/toimc/vite-vue-cdn-demo ,你可以在你本地试一下,pnpm dev正常,pnpm build-only && pnpm preview,无法运行。

关于:

我觉得你并不需要isProd 这个字段,autoImport 和components的resolvers照常使用即可——说明一下我这么做的原因:

理由一:由于项目中使用到component动态组件,有一些场景下,需要动态从服务端传递过来一些可能在当前resolver下没有打包进来了element-plus中的组件,所以要这么干;

理由二:由于"这2个插件会附带引入css 打包进chunk",正如你所说,我发现这2个插件会导致css会有100kb的增加,非常不nice,我直接考虑全部cdn加载,避免重复打包。

关于router的这个我没遇到问题——我上面已经回复,我目前的解决方案,可以参看最小 demo中的vite配置。

非常感谢!!!

nonzzz commented 7 months ago

关于router的这个问题。 我暂时没什么好思路。这个包不会特别大我会找个时间研究下这个插件具体的工作流。目前你可以先把他移除。以及tryScanGlobalName 去推测全局变量并不是万能的。我在用你的demo的时候发现了警告这时候你应该手动传入globalName。这个tryScanGlobalName我也会进行优化尝试覆盖更多的mangle包

nonzzz commented 7 months ago

Pinia和VueDemi 配置错了应该是{ name: 'vue-demi', relativeModule: './lib/index.iife.js' },{ name: 'pinia', relativeModule: './dist/pinia.iife.prod.js' }

toimc commented 7 months ago

关于router的这个问题。 我暂时没什么好思路。这个包不会特别大我会找个时间研究下这个插件具体的工作流。目前你可以先把他移除。以及tryScanGlobalName 去推测全局变量并不是万能的。我在用你的demo的时候发现了警告这时候你应该手动传入globalName。这个tryScanGlobalName我也会进行优化尝试覆盖更多的mangle包

是的,我还发现,如果这样设置resolvers: isProd ? [] : [ElementPlusResolver()],,打包之后,即使在head部分有加载css,但是依旧,页面加载不成功样式。

image image

network中无请求:

image

见代码:

https://github.com/toimc/vite-vue-cdn-demo/commit/4693e2ea9d25fcf8abbe78088fe7a222811ee8fb?diff=split#diff-6a3b01ba97829c9566ef2d8dc466ffcffb4bdac08706d3d6319e42e0aa6890ddR54

https://github.com/toimc/vite-vue-cdn-demo/commit/4693e2ea9d25fcf8abbe78088fe7a222811ee8fb?diff=split#diff-6a3b01ba97829c9566ef2d8dc466ffcffb4bdac08706d3d6319e42e0aa6890ddR48

nonzzz commented 7 months ago

看起来是插件的bug。我会进行一个修复。 应急措施的话就得看文档了。

toimc commented 7 months ago

你是指vite-plugin-cdn插件有问题,还是指AutoImport及Components这两个插件有问题?

冒昧问一下,有什么思路没有?卡在这里了,想压缩项目打包的体积。

尝试一:rollup插件方式External + html静态资源导入又与vite-plugin-pwa这个插件冲突,打包之后html中无js,css 尝试二:rollup插件方式external + vite-plugin-html静态资源导入,资源导入的位置有问题,样式部分需要另外的js逻辑,无法external vue-router(这个与此插件表现一致) 尝试三:使用vite-plugin-cdn-import,与AutoImport及Components这两个插件不兼容,同时打包的文件,会重复打包i18n的locales翻译 js文件,即使已经external掉。

nonzzz commented 7 months ago

unplugin-vue-router这个插件。 他的实现目前在我看来有点黑盒。这么说吧目前社区里面的external插件多多少少都有些不足 但是由于vite社区热衷于编译魔法导致一些edge case无法被很好的覆盖。

nonzzz commented 7 months ago

因为目前这类的插件实现原理都是大同小异无非是看谁的external做的更好罢了。

nonzzz commented 7 months ago

我发布了0.15.2对插入问题进行了一个修复

toimc commented 7 months ago

我发布了0.15.2对插入问题进行了一个修复

经过我的尝试,已经OK了,感谢开源作者无私的付出。

cdn({
        url: 'https://unpkg.com',
        modules: [
          { name: 'vue', relativeModule: './dist/vue.global.prod.js' },
          { name: 'vue-demi', relativeModule: './lib/index.iife.js'  },
          // {
          //   name: 'vue-router',
          //   aliases: ['auto'],
          //   relativeModule: './dist/vue-router.global.prod.js'
          // }, // 这一块未解决,参考 https://github.com/nonzzz/vite-plugin-cdn/issues/30
          {
            name: 'element-plus',
            relativeModule: './dist/index.full.min.js',
            aliases: ['es', 'lib'],
            spare: [
              'https://unpkg.com/element-plus@2.4.2/dist/index.css',
              'https://unpkg.com/element-plus@2.4.2/theme-chalk/dark/css-vars.css'
            ]
          },
          { name: 'pinia', relativeModule: './dist/pinia.iife.prod.js'  },
          { name: 'vue-i18n', relativeModule: './dist/vue-i18n.global.prod.js' },
          { name: 'sortablejs', global: 'Sortablejs', relativeModule: './Sortable.min.js' }
        ]
      })

最终配置,提供给有缘人。