嗨~那个少年,快来做一个跨vue2和vue3的组件库_Emperor灬fly的博客-程序员宅基地

技术标签: vue  前端  vue.js  javascript  

       前端时间,由于公司业务广度深度的拓展以及想要对之前网站的布局进行革新,洋洋洒洒的进行了多次的UI改版,致使很多前端同学是苦不堪言,但是总体来说最后的结果是好的,也得到了相应的好评。但是随着改版的范围越来越广,涉及的项目越来越多,面临的挑战也是越来越大。这个时候我们迫切的需要一个组件库来承接所有的设计规范UI物料库和支撑公司各种形形色色项目的后续的改版(ps: 其实就是手痒,想做会死,搞出点动静来,颇有心计)。
       于是罗列了公司目前项目的现状,还好技术栈还是比较统一用的vue,但是问题在于版本幅度有点大,我一听心中窃喜,活不就来了吗?不跨版本的活我还不干呢。小伙子,有点狂啊,好了,铺(废)垫(话)就先到这里了,直接开干。
       想到跨版本,一定就会想起antfu大神的vue-demi,没错后续就是基于这个包来开展一系列的动作,这个时候有朋友就会问道为什么不用web-component去实现,且听我徐徐道来。

基于vue-demi的几种方案

  1. 直接上最原始的组件,依赖宿主项目的编译能力。(pass,这也太耍无赖了)
  2. 判断当前版本,利用jsx以及render等去动态替换逻辑。(我就是只想用个vue3而已,你给我捆绑vue2)
  3. 构建时走多份配置(vue 2、vue2.7、vue3),构建多版本产物。

显而易见,我采取了第三种,当然其实也是都可以,(ps:又不是不能用)。
确定了后续的方向,那么我们就需要去分析选择哪个版本做主版本的问题,由于我是革新派毫无疑问选择了vue3作为主版本,以及再次借助了antfu大神写的unplugin-vue2-script-setup,(ps:感谢祖师爷赏饭吃),直接上了setup语法,小孩子才做选择,而我全都要。

依赖冲突

       这个时候就会有朋友就会问了,这么版本这么多依赖杂糅在一个项目不会有冲突吗?不得不说这个朋友你真聪明,还真让你说对了,不过庆幸的是我们目前只有vue2vue3的项目,而2和3里面并没有太多的依赖交集,所以哈哈哈,完美避免。想知道vue2,vue2.7,vue3如何共存的同学请听后续我娓娓道来。
       说是没有依赖交集,但还是会存在一个冲突,那就是执着的我是以vue3作为主版本,那么vue2专用的vue-template-compiler在读取版本的时候可能会出错,我们来打开它的源码看下它如何来引入vue的。

try {
    
var vueVersion = require('vue').version
} catch (e) {
    }

var packageName = require('./package.json').name
var packageVersion = require('./package.json').version
if (vueVersion && vueVersion !== packageVersion) {
    
var vuePath = require.resolve('vue')
var packagePath = require.resolve('./package.json')
throw new Error(
  '\n\nVue packages version mismatch:\n\n' +
  '- vue@' + vueVersion + ' (' + vuePath + ')\n' +
  '- ' + packageName + '@' + packageVersion + ' (' + packagePath + ')\n\n' +
  'This may cause things to work incorrectly. Make sure to use the same version for both.\n' +
  'If you are using vue-loader@>=10.0, simply update vue-template-compiler.\n' +
  'If you are using vue-loader@<10.0 or vueify, re-installing vue-loader/vueify should bump ' + packageName + ' to the latest.\n'
)
}

module.exports = require('./build')

喔嚯,一看源码那么事情就大了肯定是会读错的,难道我们就这样放弃了吗?不,点上一首孤勇者,谁说污泥满身的不算英雄,就在情绪的在高点时,脑海中涌现了一个库patch-package,救星来了,我们可以在安装依赖的时候利用postinstall钩子,覆盖它的源码强制指定vue的版本。顿时一顿操作猛如虎,却发现库太老了,已经不兼容pnpm安装的依赖结构了(不过听小道消息最近作者又‘活’过来了,准备更新下去适配pnpm这些新兴势力),有兴趣的同学可以持续关注下。
       其实原理我们是知道的,那就自己写个脚本动态替换下吧,虽然做不到patch-package那么高级,好歹能用(ps:又不是不能用)。直接上代码,多的就不提了。

import {
     getPackageInfo, resolveModule } from "local-pkg";
import fs from "node:fs/promises";

const patch = async () => {
    
 const {
     name, version, rootPath } = await getPackageInfo("vue-template-compiler");
 console.log(`检测到当前${
      name}版本为${
      version}`);
 const packagePath = await resolveModule("vue-template-compiler");

 const content = await fs.readFile(packagePath, "utf8");

 try {
    
   await fs.writeFile(
     packagePath,
     content.replace(
       "require('vue').version",
       `require('vue@${
      version}').version`
     )
   );
   console.log(`vue@${
      version}版本写入成功`);
   console.log('写入路径:', rootPath);
 } catch (e) {
    
   console.log(`vue@${
      version}版本写入失败,请检测包脚本是否需要更新`);
 }
};

patch();

完活,前期的冲突问题到这里就完结了。

构建工具

       接下来就是选构建工具了,鉴于小本生意,没啥研发经费,那就选vite吧,开箱即用。整体的思路就是借助vue-demi提供的vue-demi-switch命令,在构建的时候动态的切换vue的版本,并根据当前vue的版本按需去注册相关构建依赖。核心代码大概就如下部分:

// package.json
scripts": {
    
  "switch:v3": "vue-demi-switch 3 vue3",
  "switch:v2": "vue-demi-switch 2 vue2",
 }
// build.ts
import {
     build } from "vite";
import glob from "glob";
import {
     isVue3, version } from "vue-demi";
import dts from "vite-plugin-dts";
import {
     resolve, workRoot, getVuePlugins } from "./utils";

export const start = async () => {
    
console.log("当前vue版本", version);
const name = isVue3 ? "vue3" : "vue2";

const vuePlugins: any[] = await getVuePlugins();

glob("src/components/**/**.{vue,ts,js}", {
    
  cwd: process.cwd(),
  absolute: true,
  onlyFiles: true,
}, async (err, files) => {
    
  if (err) return;
  files.forEach(async (file) => {
    
    const plugins = [...vuePlugins,];

    plugins.push(
      dts({
    
        entryRoot: `${
      workRoot}/src/components`,
        outputDir: [resolve(`./dist/${
      name}/es`), resolve(`./dist/${
      name}/cjs`)],
        exclude: ['src/vite-env.d.ts'],
        cleanVueFileName: true,
        staticImport: true,
        compilerOptions: isVue3
          ? {
    }
          : {
    
            baseUrl: ".",
            paths: {
    
              vue: ["node_modules/vue2"],
              "vue/*": ["node_modules/vue2/*"],
              "@vue/composition-api": ["node_modules/@vue/composition-api"],
              "@vue/runtime-dom": ["node_modules/@vue/runtime-dom"],
            },
          },
      })
    );
    await build({
    
      plugins,
      resolve: {
    
        alias: isVue3
          ? {
    }
          : {
    
            vue: resolve("./node_modules/vue2"),
            "@vue/composition-api": resolve(
              "./node_modules/@vue/composition-api"
            ),
          },
      },
      build: {
    
        assetsDir: resolve(`./dist/${
      name}/es/`),
        emptyOutDir: false,
        minify: 'esbuild',
        sourcemap: true,
        lib: {
    
          entry: resolve(file),
          name: "vue-ui"
        },
        rollupOptions: {
    
          external: ["vue", "vue-demi"],
          output: [
            {
    
              format: "es",
              dir: resolve(`./dist/${
      name}/es`),
              preserveModules: true,
              preserveModulesRoot: `${
      workRoot}/src/components`,
              entryFileNames: `[name].mjs`,
            },
            {
    
              format: "cjs",
              dir: resolve(`./dist/${
      name}/cjs`),
              preserveModules: true,
              preserveModulesRoot: `${
      workRoot}/src/components`,
              exports: "named",
              entryFileNames: `[name].js`
            },
          ],
        },
      },
    });
  });
});
};

start();

到这里我们的组件构建过程应该是没啥太大的问题了,这个时候又有聪明的同学会问:你这组件生成了,能不能再生成相关的ts类型文件。好的,老板没问题,这个时候得请出vite-plugin-dts,重新构建一遍发现还好,能用。想更加细致的去定制化ts类型文件生成的同学可以去了解下ts-morph

样式构建

       上面只能构建了组件,但是样式应该咋办呢?脑海中模拟了无数场景,最终我们还是借鉴下element-plus,直接用gulp基于文件流去简单的构建一下。大致思路如下:

// gulpfile.ts
import path from 'path'
import chalk from 'chalk'
import {
     dest, parallel, series, src } from 'gulp'
import gulpSass from 'gulp-sass'
import dartSass from 'sass'
import autoprefixer from 'gulp-autoprefixer'
import cleanCSS from 'gulp-clean-css'
import rename from 'gulp-rename'
import consola from 'consola'

const distFolder = path.resolve(__dirname, 'style')

function buildThemeChalk() {
    
 const sass = gulpSass(dartSass)
 const noElPrefixFile = /(index|base|display)/
 return src(path.resolve(__dirname, 'src/components/**/*.scss'))
   .pipe(sass.sync())
   .pipe(autoprefixer({
     cascade: false }))
   .pipe(
     cleanCSS({
    }, (details) => {
    
       consola.success(
         `${
      chalk.cyan(details.name)}: ${
      chalk.yellow(
           details.stats.originalSize / 1000
         )} KB -> ${
      chalk.green(details.stats.minifiedSize / 1000)} KB`
       )
     })
   )
   .pipe(
     rename((path) => {
    
       if (!noElPrefixFile.test(path.basename)) {
    
         path.basename = `vue-${
      path.basename}`
       }
     })
   )
   .pipe(dest(distFolder))
}

export const build: any = parallel(buildThemeChalk)

export default build

本地开发

由于基于vite,那么本地开就很舒服了,拉一个vite模板文件,配置一下vite.config.ts就行了。不得不说这个经费一下子省到位了,舒服。

// vite.config.ts
import {
     defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import {
     resolve } from './build/utils';
import {
     isVue3 } from 'vue-demi';
import {
     createVuePlugin } from 'vite-plugin-vue2';
import setUp from 'unplugin-vue2-script-setup/vite';

// https://vitejs.dev/config/
export default defineConfig(process.env.NODE_ENV === 'development' ? {
    
  plugins: isVue3 ? [vue()] : [createVuePlugin(), setUp()],
  resolve: {
    
    alias: {
    
      '@': resolve("./src"),
      '@component': resolve("./src/components"),
      vue: resolve(`./node_modules/vue${
       isVue3 ? 3 : 2}`)
    }
  },
} : {
    });

我们在写组件的时候可以先切vue3的跑起来,再切vue2的看下兼容性问题,具体main.ts可以这样写:

import {
     createApp, version, Vue2, isVue3 } from 'vue-demi'
import App from './App.vue'

/** 自动搜索全局样式进行引入 **/
const styles = import.meta.glob('./**/*.scss');

Object.keys(styles).forEach((k) => {
    
    console.log('注入css:', k);
    styles[k]()
})

console.log('当前vue版本:', version);

if (isVue3) {
    
    createApp(App).mount('#app')
} else {
    
    new Vue2({
    
        render: h => h(App as any)
    }).$mount("#app")
}

自动引入

有不少同学会说,人家都已经开上宝马了,你搁这骑自行车。好的,收到,问题不大,那就让我们来扒一下unplugin-vue-components的源码,多的地方不用细看,只需要找到相关的resolver就行,照着写就行。当然你可以向这个库提交你的resolver,不过一般情况下demo是不会通过的,你可以自己重新构建一个自用的npm包就行,列如:unplugin-component-resolvers
另外一些pnpm,postinstall,还有用到一些包就不在这里赘述了。

搞定完事,这个季度的kpi有了。

最后献上demo地址,没错就是它,给我狠狠的点它。

哦哦,忘了填上面挖的两个坑了。对于web-component可以阅读下:
https://web.dev/declarative-shadow-dom/#styling
https://css-tricks.com/using-web-components-with-next-or-any-ssr-framework/
目前在ssr方面还比较薄弱,可以去国外的论坛或者推特上面看看后续对这块的规划,很多优化的案例还在起草中,就先不作死,让大佬们先走。

对于想要做到兼容vue2,vue2.7和vue3,就需要做相应的依赖隔离,因为vue2和vue2.7,vue2.7和vue3都有共同相交的依赖只是版本不同,比如可以分两个仓库去构建等去做隔离。

好了,最后让我们一起加油,去做一个天选打工人吧!!!

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/theoneEmperor/article/details/128353791

智能推荐

神奇的口袋(背包问题)_目描述有一个神奇的口袋,总的容积是40,用这个口袋可以变出一些物品,这些物品-程序员宅基地

05 神奇的口袋(背包问题)描述​ 有一个神奇的口袋,总的容积是40,用这个口袋可以变出一些物品,这些物品的总体积必须是40。John现在有n个想要得到的物品,每个物品的体积分别是a1,a2……an。John可以从这些物品中选择一些,如果选出的物体的总体积是40,那么利用这个神奇的口袋,John就可以得到这些物品。现在的问题是,John有多少种不同的选择物品的方式。输入​ 输入的第一行是正..._目描述有一个神奇的口袋,总的容积是40,用这个口袋可以变出一些物品,这些物品

用c语言画爱心_c语言心形编程代码-程序员宅基地

一、代码示例#include <stdio.h>int main(){ for(double y = 1.5; y > -1.5; y -= 0.1) { for(double x = -1.5; x < 1.5; x += 0.05) { double a = x * x + y * y - 1; putchar(a * a * a - x * x * y * y * y <= 0.0f? '+' : ' '); } putchar_c语言心形编程代码

【BP数据预测】基于matlab萤火虫算法优化BP神经网络数据预测【含Matlab源码 1313期】_海神之光的博客-程序员宅基地

1 介绍萤火虫(firefly)种类繁多,主要分布在热带地区。大多数萤火虫在短时间内产生有节奏的闪光。这种闪光是由于生物发光的一种化学反应,萤火虫的闪光模式因种类而异。萤火虫算法(FA)是基于萤火虫的闪光行为,它是一种用于全局优化问题的智能随机算法,由Yang Xin-She(2009)[1]提出。萤火虫通过下腹的一种化学反应-生物发(bioluminescence)发光。_萤火虫算法优化bp神经网络

leetcode题之整数转罗马数字(中等)_输入一个整数,输出该整数的罗马数字(该整数在 1 到 3999 范围内)。-程序员宅基地

固定符号一般采取编码题目描述:罗马数字包含以下七种字符: I, V, X, L,C,D 和 M。字符 数值I 1V 5X 10L 50C 100D 500M 1000例如, 罗马数字 2 写做 II ,即为两个并列的 1。12 写做 XII ,即为 X + II 。 27 写做 XXVII, 即为._输入一个整数,输出该整数的罗马数字(该整数在 1 到 3999 范围内)。

SDUWH2019-2020寒假python实训--Chp4.3_python完成实验书p34 3.4.3学费计算的程序-程序员宅基地

import requests_htmlsess = requests_html.HTMLSession()word='李小龙'pn=30addr_list = []for i in range(1,3): url = f'http://image.baidu.com/search/index?tn=baiduimage&ie=utf-8&word={word}..._python完成实验书p34 3.4.3学费计算的程序

计算机水平软件应用怎么填,个人简历计算机水平书写格式_秦老猫的博客-程序员宅基地

个人简历计算机水平书写格式导语:现在求职简历内容中都离不开计算机水平,因为现在很职位都是要用到计算机的那么你对计算机操作到了一个什么程度呢?在求职个人简历中就要表明你的能力水平。个人简历计算机水平要怎样写呢,这个让很多求职者都不会写,下面是小编为大家整理的个人简历计算机水平书写格式,欢迎大家阅读!个人简历计算机水平怎么写 (简单写法)1.不熟悉 2.基本操作 3.熟练 4.专业填写这四种,代表了你..._办公软件水平怎么填

随便推点

第六章课后习题-程序员宅基地

import java.util.Scanner;public class A6 { public static void main(String[] args) { //(题目:1-10之间的所有偶数之和) /* int sum=0; //总和 for (int i = 0; i &lt;=10; i++) { if (i%2==1) { conti...

使用 Sping Boot 创建一个自定义的 Auto-Configuration_springboot customautoconfig-程序员宅基地

正文Create a Custom Auto-Configuration with Spring Boot1. OverviewSimply put, the Spring Boot autoconfiguration represents a way to automatically configure a Spring application based on the depend..._springboot customautoconfig

毕业论文html代码查重吗,毕业论文中的代码内容重复了怎么办? 毕业论文代码重复率高..._布拉达勇士的博客-程序员宅基地

毕业论文中的代码内容重复了怎么办? 毕业论文代码重复率高发布时间:2021-04-17 09:00:09作者:知网小编多理科的学生在写毕业论文的时候,可能会涉及到论文中代码的内容。例如,在计算机、设计等相关专业领域,代码是他们论文中必不可少的内容。这样专业的学生写毕业论文的时候如果没有代码的话,很难通过指导者的这种关系。代码的制作对我们今后论文的验证有什么影响?论文上写的代码被标记重复了怎么办?一..._毕业论文代码什么格式才能避免查重

对oracle的心得体会,学习心得征文活动精选一:Oracle学习的心得体会-程序员宅基地

详情:经常遇到朋友问oracle学习难不难,怎么才能成为高手等等,我想结合我的个人经验简单说几点:1、打好基础,由浅入深学习Oracle不能急于求成,寄希望于一天成为一个大侠。学习有个过程,应该由浅入深,在学习的过程中打好基础,这样在以后的学习中就能触类旁通,举一反三。就我个人经验而言,建议先学习OCA培训的Oracle体系结构等内容,再学习OCP的RMAN、数据泵内容,最后学习OCM的RAC、 ..._oracle学习心得体会

06-Linux网络编程-网络基础(华清创客)-程序员宅基地

文章目录网络分层Link Layer-网络接口与物理层Internet-网络层Transport Layer-传输层Application Layer-应用层TCP/IP协议通信模型封包/拆包TCP/IP网络编程预备知识Socketsocket类型IP地址端口号字节序网络分层Link Layer-网络接口与物理层MAC地址:48位 全球唯一,网络设备身份标识ARP/RARPARP:IP...

推荐文章

热门文章

相关标签