如何开发支持SFC方式的Vue2和Vue3通用库,实现Vue Demi的跨版本兼容?

2026-04-01 13:531阅读0评论SEO基础
  • 内容介绍
  • 文章标签
  • 相关推荐

本文共计2564个文字,预计阅读时间需要11分钟。

如何开发支持SFC方式的Vue2和Vue3通用库,实现Vue Demi的跨版本兼容?

目录 + 背景 + 技术要点 + Vue-Demi + SFC + Compiler + 实现方式 + Vue 2.6 + Vue 3 + Vite + Vue-Demi + package.json + vite.config.ts + main.ts + postinstall + Vue 2.7 + Vue 3 + Vite + Vue-Demi + Yarn Workspaces + 前提:未找到以 Vue 3 为主包的开发模式 + 注意

目录
  • 背景
  • 技术要点
    • vue-demi
    • sfc compiler
  • 实现方式
    • vue2.6 + vue3 + vite + vue-demi
    • package.json
    • vite.config.ts
    • main.ts
    • postinstall
    • vue2.7 + vue3 + vite + vue-demi + yarn workspaces
    • 目前没找到vue3为主包的开发方式
  • 注意点
    • 1、@vue/composition-api重复引用问题
    • 2、由于要兼容vue2,vue3的 setup sfc语法糖不兼容
  • 最后

    背景

    随着vue3的逐渐成熟,公司项目逐渐会存在vue2和vue3项目共存的情况,兼容vue2和vue3的公共组件开发能让老项目较好地过渡到vue3。研究了vue-demi的源码和demo,发现vue-demi只是简单地根据vue版本生成对应的类似中间件的东西,而且render函数也只是做了简单的中转处理;

    如何开发支持SFC方式的Vue2和Vue3通用库,实现Vue Demi的跨版本兼容?

    国外大佬写了一个h-demi解决了vue2/vue3的render函数attrs属性的问题,这里我就直接贴issue链接,不做过多说明了: github.com/vueuse/vue-…

    虽然vue-demi没有提供sfc的兼容方案,但是其实仔细想一下,sfc的解析处理也不应该是由vue-demi来解决,应该是交给打包工具将template转成render,而vue-demi只需要关注composition-api就行;于是往着这个思路,花了几天时间研究一下vue2.6、vue2.7和vue3的sfc-compiler,得到以下开发方案。

    技术要点

    vue-demi

    查看源码可以发现,vue-demi的工作是通过postinstall和 npx vue-demi-fix指令,判断当前项目安装的vue版本,然后将对应版本的插件复制到lib的根目录,其插件的功能就是抹平vue2和vue3版本使用composition-api时的差异;

    <=2.6: exports fromvue+@vue/composition-apiwith plugin auto installing.

    2.7: exports fromvue(Composition API is built-in in Vue 2.7).

    >=3.0: exports fromvue, with polyfill of Vue 2'ssetanddelAPI.

    sfc compiler

    在日常开发中写的vue template,实际上最后是通过sfc-compiler转成render函数输出的,而vue2和vue3的sfc-compiler是互不兼容的。尤大大已经提供了vue2.6.x,vue2.7和vue3的compiler,其实我们只需要在打包工具写判断不同的vue版本使用不同的compiler逻辑即可,本文是基于vite开发,以下对应的打包插件:

    • vue2.6: vite-plugin-vue2@2.6.14 + vue-template-compiler@2.6.14
    • vue2.7: vite-plugin-vue2@2.7.9 + vue-template-compiler@2.7.9; 或者@vitejs/plugin-vue2 + @vue/compiler-sfc
    • vue3: @vitejs/plugin-vue + @vue/compiler-sfc

    实现方式

    以下实现方式均是基于vite开发,换成webpack和rollup原理上也是替换对应的插件即可。

    vue2.6 + vue3 + vite + vue-demi

    以vue2.6为主包,开发vue2/vue3组件,该方式能做到通过一个package.json的scripts同时调试和打包vue2、vue3环境,以下讲一下重点;

    package.json

    package.json中的vue包是固定了2.6.14版本,这里要注意vue-template-compiler要和vue的版本对齐;

    scripts中的switch:2 指令没有按照文档说的使用npx vue-demi-switch,是因为在实际调试过程中,由于vite是会缓存依赖的,dev调试时vue-demi-switch会出现一些莫名其妙的问题,具体原因我还没搞明白,所以就改成用npx vue-demi-fix。

    //package.json部分片段 "main": "./lib/vue-demi-sfc-component.umd.cjs", "exports": { ".": { "import": "./lib/vue-demi-sfc-component.js", "require": "./lib/vue-demi-sfc-component.umd.cjs" } }, "scripts": { "postinstall": "node ./scripts/postinstall.mjs", "dev": "vite", "dev:3": "npm run switch:3 && vite --force", "dev:2": "npm run switch:2 && vite", "switch:2": "npx vue-demi-fix", "switch:3": "npx vue-demi-switch 3 vue3", "build:3": "npm run switch:3 && vue-tsc --noEmit && vite build", "build:2": "npm run switch:2 && vue-tsc --noEmit && vite build", "build": "rimraf lib && npm run build:2 && npm run build:3", "preview": "vite preview", "lint:fix": "eslint . --ext .js,.ts,.vue --fix", "prepare": "husky install", "pub": "npm publish --access=public" }, "dependencies": { "@vue/composition-api": "^1.7.0", "vue-demi": "^0.13.8" }, "peerDependencies": { "@vue/composition-api": "^1.7.0", "vue": "^2.0.0 || >=3.0.0" }, "peerDependenciesMeta": { "@vue/composition-api": { "optional": true } }, "peerDependencies": { "@vue/composition-api": "^1.7.0", "vue": "^2.0.0 || >=3.0.0" }, "peerDependenciesMeta": { "@vue/composition-api": { "optional": true } }, "devDependencies": { // ...其他依赖,这里就不复制了 "@vitejs/plugin-vue": "^3.0.3", "vite": "^3.0.7", "vite-plugin-vue2": "^2.0.2", "vue": "2.6.14", "vue-eslint-parser": "^9.0.3", "vue-template-compiler": "2.6.14", "vue-tsc": "^0.39.5", "vue2": "npm:vue@2.6.14", "vue3": "npm:vue@^3.2.36" }

    vite.config.ts

    import { defineConfig } from 'vite' import { createVuePlugin } from 'vite-plugin-vue2' import * as compiler from '@vue/compiler-sfc' import vue3 from '@vitejs/plugin-vue' import path from 'path' import { getLibDir } from './scripts/utils.mjs' import { isVue2, version } from 'vue-demi' console.log({ version }) const resolve = (str: string) => { return path.resolve(__dirname, str) } // vitejs.dev/config/ export default defineConfig({ resolve: { alias: { '@': resolve('src'), vue: isVue2 ? resolve('/node_modules/vue2') : resolve('/node_modules/vue3') } }, build: { lib: { entry: resolve('./src/components/index.ts'), name: 'vueDemiSfcComponent', fileName: 'vue-demi-sfc-component' }, cssTarget: 'chrome61', rollupOptions: { external: ['vue-demi', 'vue'], output: { dir: getLibDir(version), globals: { vue: 'Vue', 'vue-demi': 'VueDemi' } } } }, optimizeDeps: { exclude: ['vue-demi'] }, plugins: [ isVue2 ? createVuePlugin() : vue3({ compiler: compiler }) ] })

    这个文件有几个关键逻辑:

    1、使用vue-demi的isVue2来判断当前打包环境

    import { isVue2, version } from 'vue-demi'

    2、alias要根据环境切换地址

    alias: { '@': resolve('src'), vue: isVue2 ? resolve('/node_modules/vue2') : resolve('/node_modules/vue3') }

    3、在以vue2.6为主包的时候,如果直接使用@vitejs/plugin-vue, 打包时会报错

    error when starting dev server:

    Error: Failed to resolve vue/compiler-sfc.

    @vitejs/plugin-vue requires vue (>=3.2.25) to be present in the dependency tree.

    这是因为@vitejs/plugin-vue源码中是直接找vue/compiler-sfc目录的,如果以vue2为主包,这个时候nod_modules/vue是vue2的目录结构,并没有vue/compiler-sfc;

    function resolveCompiler(root) { const compiler = tryRequire("vue/compiler-sfc", root) || tryRequire("vue/compiler-sfc"); if (!compiler) { throw new Error( `Failed to resolve vue/compiler-sfc. @vitejs/plugin-vue requires vue (>=3.2.25) to be present in the dependency tree.` ); } return compiler; }

    所以就去寻找一下@vitejs/plugin-vue的options

    interface Options { include?: string | RegExp | (string | RegExp)[]; exclude?: string | RegExp | (string | RegExp)[]; isProduction?: boolean; script?: Partial<Pick<SFCScriptCompileOptions, 'babelParserPlugins'>>; template?: Partial<Pick<SFCTemplateCompileOptions, 'compiler' | 'compilerOptions' | 'preprocessOptions' | 'preprocessCustomRequire' | 'transformAssetUrls'>>; style?: Partial<Pick<SFCStyleCompileOptions, 'trim'>>; /** * Transform Vue SFCs into custom elements. * - `true`: all `*.vue` imports are converted into custom elements * - `string | RegExp`: matched files are converted into custom elements * * @default /\.ce\.vue$/ */ customElement?: boolean | string | RegExp | (string | RegExp)[]; /** * Enable Vue reactivity transform (experimental). * github.com/vuejs/core/tree/master/packages/reactivity-transform * - `true`: transform will be enabled for all vue,js(x),ts(x) files except * those inside node_modules * - `string | RegExp`: apply to vue + only matched files (will include * node_modules, so specify directories in necessary) * - `false`: disable in all cases * * @default false */ reactivityTransform?: boolean | string | RegExp | (string | RegExp)[]; /** * Use custom compiler-sfc instance. Can be used to force a specific version. */ compiler?: typeof _compiler; }

    发现option中是有自定义compiler-sfc的参数,于是就得到以下方案:

    // vite.config.ts import * as compiler from '@vue/compiler-sfc' export default defineConfig({ // ... plugins: [ isVue2 ? createVuePlugin() : vue3({ compiler: compiler }) ] })

    main.ts

    main.ts需要判断isVue2后,区分vue2和vue3的依赖

    import { isVue2 } from 'vue-demi' import { createApp } from 'vue3' import Vue2 from 'vue2' import './style.css' import App from './App.vue' if (isVue2) { const app = new Vue2({ render: (h) => h(App) }) app.$mount('#app') } else { const app = createApp(App) app.mount('#app') }

    postinstall

    这里是模仿vue-demi的原理,在安装时利用postinstall钩子执行node脚本,复制lib中的v2/v3目录,具体可直接看文章最后的项目链接;这里有一个地方要注意,由于我是使用vite + ts 构建的项目,package.json中的"type": "module"需要我把所有js改成mjs文件,这个时候,其他项目安装这个项目时,会找不到 __dirname,因此utils.mjs加了以下逻辑。

    import { fileURLToPath } from 'url' const __filename = fileURLToPath(import.meta.url) const __dirname = path.dirname(__filename)

    vue2.7 + vue3 + vite + vue-demi + yarn workspaces

    以vue2.7为主包开发时,没办法像vue2.6可以在一个package.json项目下调试和打包,主要是因为vue2.7的代码方式已经是monorepo项目,因此在安装vue2.7的时候,会重新下载@vue/compuler-sfc的2.7.x版本。

    所以没办法直接使用@vue/compiler-sfc 包作为vue3的compiler;

    那么我们就要换一个思路,做node_modules隔离,而node_modules隔离的方案现在主流的就是yarn workspaces、lerna和pnpm,这里我就以yarn workspaces来简单讲一下思路;

    (ps: 该方式我并没有上传到github)

    开启yarn workspaces之后,新建packages文件夹

    然后再packages下分别新建v2和v3目录,这两个目录存放对应vue2和vue3的package.json和vite.config.ts

    // v2/package.json "scripts": { "dev": "vite", "build": "rimraf lib/v2 && vue-tsc --noEmit && vite build", "preview": "vite preview", "lint:fix": "eslint . --ext .js,.ts,.vue --fix", "prepare": "husky install", "pub": "npm publish --access=public" }, "devDependencies": { "@vitejs/plugin-vue2": "^2.7.9", "vite": "^3.0.7", "vite-plugin-vue2": "^2.0.2", "vue": "2.7.9", "vue-eslint-parser": "^9.0.3", "vue-template-compiler": "2.7.9", "vue-tsc": "^0.39.5", "vue2": "npm:vue@2.7.9", "vue3": "npm:vue@^3.2.36" } // v3/package.json "scripts": { "dev": "vite", "build": "rimraf lib/v3 && vue-tsc --noEmit && vite build", "preview": "vite preview", "lint:fix": "eslint . --ext .js,.ts,.vue --fix", "prepare": "husky install", "pub": "npm publish --access=public" }, "devDependencies": { "@vitejs/plugin-vue": "^3.0.3", "vite": "^3.0.7", "vite-plugin-vue2": "^2.0.2", "vue": "3.2.26", "vue-eslint-parser": "^9.0.3", "vue-template-compiler": "2.6.14", "vue-tsc": "^0.39.5", "vue2": "npm:vue@2.6.14", "vue3": "npm:vue@^3.2.26" }

    vite.config.ts的区别主要是 rollupOptions.output.dir,和对应的plugin,然后alias不需要再指定vue路径,main.ts也不需要区分vue2和vue3的依赖;

    // v2/vite.config.ts import { defineConfig } from 'vite' import { createVuePlugin } from 'vite-plugin-vue2' // or import vue2 from '@vitejs/plugin-vue2' import path from 'path' const resolve = (str: string) => { return path.resolve(__dirname, str) } // vitejs.dev/config/ export default defineConfig({ // ... resolve: { alias: { '@': resolve('src'), } }, build: { // ... rollupOptions: { external: ['vue-demi', 'vue'], output: { dir: resolve('../../lib/v2'), // 区别在这 globals: { vue: 'Vue', 'vue-demi': 'VueDemi' } } } }, optimizeDeps: { exclude: ['vue-demi'] }, plugins: [createVuePlugin()] // or vue2() }) // v3/vite.config.ts import { defineConfig } from 'vite' import vue3 from '@vitejs/plugin-vue' import path from 'path' const resolve = (str: string) => { return path.resolve(__dirname, str) } // vitejs.dev/config/ export default defineConfig({ // ... resolve: { alias: { '@': resolve('src'), } }, build: { rollupOptions: { external: ['vue-demi', 'vue'], output: { dir: resolve('../../lib/v3'), // 区别在这 globals: { vue: 'Vue', 'vue-demi': 'VueDemi' } } } }, optimizeDeps: { exclude: ['vue-demi'] }, plugins: [vue3()] })

    main.ts

    // main.ts import { createApp } from 'vue-demi' import './style.css' const app = createApp(App) app.mount('#app')

    整体目录结构如下,最后通过node脚本去同时构建v2和v3即可。

    目前没找到vue3为主包的开发方式

    文章看到这里,大概能知道整个方案其实是基于vue-demi处理composition-api和使用vue3的自定义compiler处理分别打包vue2、vue3;而vite-plugin-vue2是没有对应自定义compiler的options,并且在vue3为主包的情况下,会报vue-template-compiler与vue版本不一致的错误;而@vitejs/plugin-vue2存在跟vue3冲突的情况;

    目前如果要基于vue3为主包的方式开发,我想到如下2个思路,待后续有时间再去验证:

    • vite-plugin-vue2增加自定义compiler选项
    • 开发rollup插件,支持修改vue-template-compiler在读取require(vue)时,重定向到"vue2": "npm:vue@2.6.14"对应的路径

    注意点

    1、@vue/composition-api重复引用问题

    由于vue-demi在v2.6的场景下,会自动install @vue/composition-api,,如果项目自身也在需要在入口时注册@vue/composition-api,会出现多次注册@vue/composition-api实例的情况,导致出setup相关的报错,这时需要在项目的alias加上以下代码:

    alias: { '@vue/compostion-api': resolve('./node_modules/@vue/composition-api') },

    2、由于要兼容vue2,vue3的 setup sfc语法糖不兼容

    这一点无法解决,写组件template的时候,还是只能用vue2的template写法,包括template还是需要有唯一的跟节点;

    最后

    写到最后,其实我发现去写兼容vue2和vue3的template代码,并不能完全解决vue2到vue3过渡的问题。希望vue3社区以后越来越完善~

    贴上项目地址(vue2.6 + vue3 + vite + vue-demi):vue-demi-sfc-component

    以上就是vue demi支持sfc方式的vue2vue3通用库开发详解的详细内容,更多关于vue demi支持sfc通用库的资料请关注易盾网络其它相关文章!

    标签:vue2vue3

    本文共计2564个文字,预计阅读时间需要11分钟。

    如何开发支持SFC方式的Vue2和Vue3通用库,实现Vue Demi的跨版本兼容?

    目录 + 背景 + 技术要点 + Vue-Demi + SFC + Compiler + 实现方式 + Vue 2.6 + Vue 3 + Vite + Vue-Demi + package.json + vite.config.ts + main.ts + postinstall + Vue 2.7 + Vue 3 + Vite + Vue-Demi + Yarn Workspaces + 前提:未找到以 Vue 3 为主包的开发模式 + 注意

    目录
    • 背景
    • 技术要点
      • vue-demi
      • sfc compiler
    • 实现方式
      • vue2.6 + vue3 + vite + vue-demi
      • package.json
      • vite.config.ts
      • main.ts
      • postinstall
      • vue2.7 + vue3 + vite + vue-demi + yarn workspaces
      • 目前没找到vue3为主包的开发方式
    • 注意点
      • 1、@vue/composition-api重复引用问题
      • 2、由于要兼容vue2,vue3的 setup sfc语法糖不兼容
    • 最后

      背景

      随着vue3的逐渐成熟,公司项目逐渐会存在vue2和vue3项目共存的情况,兼容vue2和vue3的公共组件开发能让老项目较好地过渡到vue3。研究了vue-demi的源码和demo,发现vue-demi只是简单地根据vue版本生成对应的类似中间件的东西,而且render函数也只是做了简单的中转处理;

      如何开发支持SFC方式的Vue2和Vue3通用库,实现Vue Demi的跨版本兼容?

      国外大佬写了一个h-demi解决了vue2/vue3的render函数attrs属性的问题,这里我就直接贴issue链接,不做过多说明了: github.com/vueuse/vue-…

      虽然vue-demi没有提供sfc的兼容方案,但是其实仔细想一下,sfc的解析处理也不应该是由vue-demi来解决,应该是交给打包工具将template转成render,而vue-demi只需要关注composition-api就行;于是往着这个思路,花了几天时间研究一下vue2.6、vue2.7和vue3的sfc-compiler,得到以下开发方案。

      技术要点

      vue-demi

      查看源码可以发现,vue-demi的工作是通过postinstall和 npx vue-demi-fix指令,判断当前项目安装的vue版本,然后将对应版本的插件复制到lib的根目录,其插件的功能就是抹平vue2和vue3版本使用composition-api时的差异;

      <=2.6: exports fromvue+@vue/composition-apiwith plugin auto installing.

      2.7: exports fromvue(Composition API is built-in in Vue 2.7).

      >=3.0: exports fromvue, with polyfill of Vue 2'ssetanddelAPI.

      sfc compiler

      在日常开发中写的vue template,实际上最后是通过sfc-compiler转成render函数输出的,而vue2和vue3的sfc-compiler是互不兼容的。尤大大已经提供了vue2.6.x,vue2.7和vue3的compiler,其实我们只需要在打包工具写判断不同的vue版本使用不同的compiler逻辑即可,本文是基于vite开发,以下对应的打包插件:

      • vue2.6: vite-plugin-vue2@2.6.14 + vue-template-compiler@2.6.14
      • vue2.7: vite-plugin-vue2@2.7.9 + vue-template-compiler@2.7.9; 或者@vitejs/plugin-vue2 + @vue/compiler-sfc
      • vue3: @vitejs/plugin-vue + @vue/compiler-sfc

      实现方式

      以下实现方式均是基于vite开发,换成webpack和rollup原理上也是替换对应的插件即可。

      vue2.6 + vue3 + vite + vue-demi

      以vue2.6为主包,开发vue2/vue3组件,该方式能做到通过一个package.json的scripts同时调试和打包vue2、vue3环境,以下讲一下重点;

      package.json

      package.json中的vue包是固定了2.6.14版本,这里要注意vue-template-compiler要和vue的版本对齐;

      scripts中的switch:2 指令没有按照文档说的使用npx vue-demi-switch,是因为在实际调试过程中,由于vite是会缓存依赖的,dev调试时vue-demi-switch会出现一些莫名其妙的问题,具体原因我还没搞明白,所以就改成用npx vue-demi-fix。

      //package.json部分片段 "main": "./lib/vue-demi-sfc-component.umd.cjs", "exports": { ".": { "import": "./lib/vue-demi-sfc-component.js", "require": "./lib/vue-demi-sfc-component.umd.cjs" } }, "scripts": { "postinstall": "node ./scripts/postinstall.mjs", "dev": "vite", "dev:3": "npm run switch:3 && vite --force", "dev:2": "npm run switch:2 && vite", "switch:2": "npx vue-demi-fix", "switch:3": "npx vue-demi-switch 3 vue3", "build:3": "npm run switch:3 && vue-tsc --noEmit && vite build", "build:2": "npm run switch:2 && vue-tsc --noEmit && vite build", "build": "rimraf lib && npm run build:2 && npm run build:3", "preview": "vite preview", "lint:fix": "eslint . --ext .js,.ts,.vue --fix", "prepare": "husky install", "pub": "npm publish --access=public" }, "dependencies": { "@vue/composition-api": "^1.7.0", "vue-demi": "^0.13.8" }, "peerDependencies": { "@vue/composition-api": "^1.7.0", "vue": "^2.0.0 || >=3.0.0" }, "peerDependenciesMeta": { "@vue/composition-api": { "optional": true } }, "peerDependencies": { "@vue/composition-api": "^1.7.0", "vue": "^2.0.0 || >=3.0.0" }, "peerDependenciesMeta": { "@vue/composition-api": { "optional": true } }, "devDependencies": { // ...其他依赖,这里就不复制了 "@vitejs/plugin-vue": "^3.0.3", "vite": "^3.0.7", "vite-plugin-vue2": "^2.0.2", "vue": "2.6.14", "vue-eslint-parser": "^9.0.3", "vue-template-compiler": "2.6.14", "vue-tsc": "^0.39.5", "vue2": "npm:vue@2.6.14", "vue3": "npm:vue@^3.2.36" }

      vite.config.ts

      import { defineConfig } from 'vite' import { createVuePlugin } from 'vite-plugin-vue2' import * as compiler from '@vue/compiler-sfc' import vue3 from '@vitejs/plugin-vue' import path from 'path' import { getLibDir } from './scripts/utils.mjs' import { isVue2, version } from 'vue-demi' console.log({ version }) const resolve = (str: string) => { return path.resolve(__dirname, str) } // vitejs.dev/config/ export default defineConfig({ resolve: { alias: { '@': resolve('src'), vue: isVue2 ? resolve('/node_modules/vue2') : resolve('/node_modules/vue3') } }, build: { lib: { entry: resolve('./src/components/index.ts'), name: 'vueDemiSfcComponent', fileName: 'vue-demi-sfc-component' }, cssTarget: 'chrome61', rollupOptions: { external: ['vue-demi', 'vue'], output: { dir: getLibDir(version), globals: { vue: 'Vue', 'vue-demi': 'VueDemi' } } } }, optimizeDeps: { exclude: ['vue-demi'] }, plugins: [ isVue2 ? createVuePlugin() : vue3({ compiler: compiler }) ] })

      这个文件有几个关键逻辑:

      1、使用vue-demi的isVue2来判断当前打包环境

      import { isVue2, version } from 'vue-demi'

      2、alias要根据环境切换地址

      alias: { '@': resolve('src'), vue: isVue2 ? resolve('/node_modules/vue2') : resolve('/node_modules/vue3') }

      3、在以vue2.6为主包的时候,如果直接使用@vitejs/plugin-vue, 打包时会报错

      error when starting dev server:

      Error: Failed to resolve vue/compiler-sfc.

      @vitejs/plugin-vue requires vue (>=3.2.25) to be present in the dependency tree.

      这是因为@vitejs/plugin-vue源码中是直接找vue/compiler-sfc目录的,如果以vue2为主包,这个时候nod_modules/vue是vue2的目录结构,并没有vue/compiler-sfc;

      function resolveCompiler(root) { const compiler = tryRequire("vue/compiler-sfc", root) || tryRequire("vue/compiler-sfc"); if (!compiler) { throw new Error( `Failed to resolve vue/compiler-sfc. @vitejs/plugin-vue requires vue (>=3.2.25) to be present in the dependency tree.` ); } return compiler; }

      所以就去寻找一下@vitejs/plugin-vue的options

      interface Options { include?: string | RegExp | (string | RegExp)[]; exclude?: string | RegExp | (string | RegExp)[]; isProduction?: boolean; script?: Partial<Pick<SFCScriptCompileOptions, 'babelParserPlugins'>>; template?: Partial<Pick<SFCTemplateCompileOptions, 'compiler' | 'compilerOptions' | 'preprocessOptions' | 'preprocessCustomRequire' | 'transformAssetUrls'>>; style?: Partial<Pick<SFCStyleCompileOptions, 'trim'>>; /** * Transform Vue SFCs into custom elements. * - `true`: all `*.vue` imports are converted into custom elements * - `string | RegExp`: matched files are converted into custom elements * * @default /\.ce\.vue$/ */ customElement?: boolean | string | RegExp | (string | RegExp)[]; /** * Enable Vue reactivity transform (experimental). * github.com/vuejs/core/tree/master/packages/reactivity-transform * - `true`: transform will be enabled for all vue,js(x),ts(x) files except * those inside node_modules * - `string | RegExp`: apply to vue + only matched files (will include * node_modules, so specify directories in necessary) * - `false`: disable in all cases * * @default false */ reactivityTransform?: boolean | string | RegExp | (string | RegExp)[]; /** * Use custom compiler-sfc instance. Can be used to force a specific version. */ compiler?: typeof _compiler; }

      发现option中是有自定义compiler-sfc的参数,于是就得到以下方案:

      // vite.config.ts import * as compiler from '@vue/compiler-sfc' export default defineConfig({ // ... plugins: [ isVue2 ? createVuePlugin() : vue3({ compiler: compiler }) ] })

      main.ts

      main.ts需要判断isVue2后,区分vue2和vue3的依赖

      import { isVue2 } from 'vue-demi' import { createApp } from 'vue3' import Vue2 from 'vue2' import './style.css' import App from './App.vue' if (isVue2) { const app = new Vue2({ render: (h) => h(App) }) app.$mount('#app') } else { const app = createApp(App) app.mount('#app') }

      postinstall

      这里是模仿vue-demi的原理,在安装时利用postinstall钩子执行node脚本,复制lib中的v2/v3目录,具体可直接看文章最后的项目链接;这里有一个地方要注意,由于我是使用vite + ts 构建的项目,package.json中的"type": "module"需要我把所有js改成mjs文件,这个时候,其他项目安装这个项目时,会找不到 __dirname,因此utils.mjs加了以下逻辑。

      import { fileURLToPath } from 'url' const __filename = fileURLToPath(import.meta.url) const __dirname = path.dirname(__filename)

      vue2.7 + vue3 + vite + vue-demi + yarn workspaces

      以vue2.7为主包开发时,没办法像vue2.6可以在一个package.json项目下调试和打包,主要是因为vue2.7的代码方式已经是monorepo项目,因此在安装vue2.7的时候,会重新下载@vue/compuler-sfc的2.7.x版本。

      所以没办法直接使用@vue/compiler-sfc 包作为vue3的compiler;

      那么我们就要换一个思路,做node_modules隔离,而node_modules隔离的方案现在主流的就是yarn workspaces、lerna和pnpm,这里我就以yarn workspaces来简单讲一下思路;

      (ps: 该方式我并没有上传到github)

      开启yarn workspaces之后,新建packages文件夹

      然后再packages下分别新建v2和v3目录,这两个目录存放对应vue2和vue3的package.json和vite.config.ts

      // v2/package.json "scripts": { "dev": "vite", "build": "rimraf lib/v2 && vue-tsc --noEmit && vite build", "preview": "vite preview", "lint:fix": "eslint . --ext .js,.ts,.vue --fix", "prepare": "husky install", "pub": "npm publish --access=public" }, "devDependencies": { "@vitejs/plugin-vue2": "^2.7.9", "vite": "^3.0.7", "vite-plugin-vue2": "^2.0.2", "vue": "2.7.9", "vue-eslint-parser": "^9.0.3", "vue-template-compiler": "2.7.9", "vue-tsc": "^0.39.5", "vue2": "npm:vue@2.7.9", "vue3": "npm:vue@^3.2.36" } // v3/package.json "scripts": { "dev": "vite", "build": "rimraf lib/v3 && vue-tsc --noEmit && vite build", "preview": "vite preview", "lint:fix": "eslint . --ext .js,.ts,.vue --fix", "prepare": "husky install", "pub": "npm publish --access=public" }, "devDependencies": { "@vitejs/plugin-vue": "^3.0.3", "vite": "^3.0.7", "vite-plugin-vue2": "^2.0.2", "vue": "3.2.26", "vue-eslint-parser": "^9.0.3", "vue-template-compiler": "2.6.14", "vue-tsc": "^0.39.5", "vue2": "npm:vue@2.6.14", "vue3": "npm:vue@^3.2.26" }

      vite.config.ts的区别主要是 rollupOptions.output.dir,和对应的plugin,然后alias不需要再指定vue路径,main.ts也不需要区分vue2和vue3的依赖;

      // v2/vite.config.ts import { defineConfig } from 'vite' import { createVuePlugin } from 'vite-plugin-vue2' // or import vue2 from '@vitejs/plugin-vue2' import path from 'path' const resolve = (str: string) => { return path.resolve(__dirname, str) } // vitejs.dev/config/ export default defineConfig({ // ... resolve: { alias: { '@': resolve('src'), } }, build: { // ... rollupOptions: { external: ['vue-demi', 'vue'], output: { dir: resolve('../../lib/v2'), // 区别在这 globals: { vue: 'Vue', 'vue-demi': 'VueDemi' } } } }, optimizeDeps: { exclude: ['vue-demi'] }, plugins: [createVuePlugin()] // or vue2() }) // v3/vite.config.ts import { defineConfig } from 'vite' import vue3 from '@vitejs/plugin-vue' import path from 'path' const resolve = (str: string) => { return path.resolve(__dirname, str) } // vitejs.dev/config/ export default defineConfig({ // ... resolve: { alias: { '@': resolve('src'), } }, build: { rollupOptions: { external: ['vue-demi', 'vue'], output: { dir: resolve('../../lib/v3'), // 区别在这 globals: { vue: 'Vue', 'vue-demi': 'VueDemi' } } } }, optimizeDeps: { exclude: ['vue-demi'] }, plugins: [vue3()] })

      main.ts

      // main.ts import { createApp } from 'vue-demi' import './style.css' const app = createApp(App) app.mount('#app')

      整体目录结构如下,最后通过node脚本去同时构建v2和v3即可。

      目前没找到vue3为主包的开发方式

      文章看到这里,大概能知道整个方案其实是基于vue-demi处理composition-api和使用vue3的自定义compiler处理分别打包vue2、vue3;而vite-plugin-vue2是没有对应自定义compiler的options,并且在vue3为主包的情况下,会报vue-template-compiler与vue版本不一致的错误;而@vitejs/plugin-vue2存在跟vue3冲突的情况;

      目前如果要基于vue3为主包的方式开发,我想到如下2个思路,待后续有时间再去验证:

      • vite-plugin-vue2增加自定义compiler选项
      • 开发rollup插件,支持修改vue-template-compiler在读取require(vue)时,重定向到"vue2": "npm:vue@2.6.14"对应的路径

      注意点

      1、@vue/composition-api重复引用问题

      由于vue-demi在v2.6的场景下,会自动install @vue/composition-api,,如果项目自身也在需要在入口时注册@vue/composition-api,会出现多次注册@vue/composition-api实例的情况,导致出setup相关的报错,这时需要在项目的alias加上以下代码:

      alias: { '@vue/compostion-api': resolve('./node_modules/@vue/composition-api') },

      2、由于要兼容vue2,vue3的 setup sfc语法糖不兼容

      这一点无法解决,写组件template的时候,还是只能用vue2的template写法,包括template还是需要有唯一的跟节点;

      最后

      写到最后,其实我发现去写兼容vue2和vue3的template代码,并不能完全解决vue2到vue3过渡的问题。希望vue3社区以后越来越完善~

      贴上项目地址(vue2.6 + vue3 + vite + vue-demi):vue-demi-sfc-component

      以上就是vue demi支持sfc方式的vue2vue3通用库开发详解的详细内容,更多关于vue demi支持sfc通用库的资料请关注易盾网络其它相关文章!

      标签:vue2vue3