vue-demi 打造vue2&3 UI库

1,816 阅读4分钟

背景:

最近没啥业务需求,团队打算把技术栈升级升级,升级下脚手架把vue2 -> vue3。在不打算重构原有项目的情况下,原有沉淀下来的vue2组件还是得继续维护,同时后面vue3的也需要沉淀组件。所以考虑到组件的维护成本,就打算用vue-demi来实现vue2,vue3都通用的组件库。

思路:

使用vite进行本地开发,rollup进行打包。rollup打包同时生成vue2和vue3的代码产物。在开发者进行依赖安装的时候,根据开发者项目中vue的版本号,利用postinstall钩子,切换对应的依赖代码产物。

1. 安装使用:

npm install vue-demi --S

package.json 修改

{
  "scripts": {
    "dev": "npm run dev:3",
    "build:lib": "npm run clean && npm run build:2 && npm run build:3 && npm run postinstall",
    "dev:2": "npm run switch:2 && cd ./example && vite serve",
    "dev:3": "npm run switch:3 && cd ./example && vite serve",
    "switch:2": "npx vue-demi-switch 2 vue2",
    "switch:3": "npx vue-demi-switch 3 vue3",
    "build:3": "npm run switch:3 && rollup -c",
    "build:2": "npm run switch:2 && rollup -c",
    "postinstall": "node ./scripts/postinstall.mjs"
  },
  "peerDependencies": {
    "@vue/composition-api": "^1.1.4",
    "vue": "^2.0.0 || >=3.0.0"
  },
  "devDependencies": {
    "@babel/preset-env": "^7.18.2",
    "@rollup/plugin-babel": "^5.3.1",
    "@rollup/plugin-commonjs": "^21.0.2",
    "@rollup/plugin-image": "^2.1.1",
    "@rollup/plugin-node-resolve": "^13.1.3",
    "@vitejs/plugin-vue": "^3.0.3",
    "@vue/compiler-sfc": "^3.2.13",
    "@vue/composition-api": "^1.0.0-rc.1",
    "@vuese/markdown-render": "^2.11.3",
    "@vuese/parser": "^2.10.3",
    "cssnano": "^5.1.10",
    "less": "^4.1.2",
    "postcss-nested": "^5.0.6",
    "postcss-px-to-viewport": "^1.1.1",
    "postcss-simple-vars": "^6.0.3",
    "postcss-url": "^10.1.3",
    "rimraf": "^3.0.2",
    "rollup-plugin-postcss": "^4.0.2",
    "rollup-plugin-terser": "^7.0.2",
    "rollup-plugin-vue": "^5.1.9",
    "rollup-plugin-vue2": "npm:rollup-plugin-vue@^5.1.9",
    "rollup-plugin-vue3": "npm:rollup-plugin-vue@^6.0.0-beta.11",
    "unplugin-vue2-script-setup": "0.11.3",
    "vite": "3.0.7",
    "vite-plugin-html": "2.0.7",
    "vite-plugin-vue2": "^2.0.2",
    "vue": "2.6.14",
    "vue-template-compiler": "2.6.14",
    "vue2": "npm:vue@2.6.14",
    "vue3": "npm:vue@^3.2.36"
  },
  "peerDependenciesMeta": {
    "@vue/composition-api": {
      "optional": true
    }
  }
}

@vue/composition-api 提供 组合式API的Vue2插件,peerDependenciesMeta的设置添加可选设置以消除丢失的对等依赖性警告。vue2, vue3作为充当别名,提供给vue-demi切换vue的版本。方便随时切换vue的版本,来调试维护组件。

在依赖安装后,触发postinstall 钩子把/dist文件夹中v2或v3的产物copy到dist的根目录中

scripts/postinstall.mjs

import path from 'path'
import { version } from 'vue-demi'
import { switchVersion } from './utils.mjs'
switchVersion(version)

scripts/utils.mjs

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

function switchVersion(version) {  
   const src = getDistDir(version);  
   const dest = path.join(src, '..');  
   console.log(`[frontend-shared] switch lib to vue version ${version}`);  
   copyDir(src, dest);
}

function getDistDir(version) {  
    const dirname = String(version).startsWith('2') ? 'v2' : 'v3';  
    return path.join(__dirname, `../dist/${dirname}/`);
}

function copyDir(src, dest) {  
    console.log(`copying from ${src} to ${dest}`);  

    // unlink for pnpm, #92  
    try {    
        fs.unlinkSync(dest);  
    } catch (error) {}  

    try {    
        copyRecursiveSync(src, dest);  
    } catch (error) {    
        console.error(error); 
    }
}

function copyRecursiveSync(src, dest) {  
    const exists = fs.existsSync(src);  
    const stats = exists && fs.statSync(src);  
    const isDirectory = stats && stats.isDirectory();  
    if (isDirectory) {    
        !fs.existsSync(dest) && fs.mkdirSync(dest);    
        fs.readdirSync(src).forEach(childItemName => {      
            copyRecursiveSync(path.join(src, childItemName), path.join(dest, childItemName));   
         });  
    } else {   
         fs.copyFileSync(src, dest); 
    }
}

export { getDistDir, copyDir, switchVersion };

2. vite配置:

由于组件库设计本地开发使用vite,打包使用rollup。所以需要两个配置文件

import { defineConfig } from 'vite';
import { injectHtml, minifyHtml } from 'vite-plugin-html';
import { isVue2 } from 'vue-demi';
import vue3 from '@vitejs/plugin-vue';
import { createVuePlugin } from 'vite-plugin-vue2'; // vite对vue2的支持插件
import * as compiler from '@vue/compiler-sfc'; // 解析SFC组件
import ScriptSetup from 'unplugin-vue2-script-setup/vite'; // 给vue2提供<script setup>语法
import path from 'path';

const resolve = str => {  
    console.log(path.resolve(__dirname, str));  
    return path.resolve(__dirname, str);
};

export default defineConfig({  
    resolve: {    
        alias: {      // 别名引入路径      
            vue: isVue2 ? resolve('../../../node_modules/vue2') : resolve('../../../node_modules/vue3')
        }, 
    },  
    root: './',  
    base: './',  
    mode: 'development',  
    plugins: [    
        isVue2 ? createVuePlugin() : vue3({ compiler: compiler }),    
        isVue2 ? ScriptSetup() : undefined,   
        minifyHtml(), // 压缩 HTML    
        injectHtml({      // 入口文件 index.html 的模板注入      
            injectData: {        // 模板注入的数据        
                htmlWebpackPlugin: { options: { isVite: true } },      
            },    
        }),  
    ],  
    server: {    
        open: true, // 是否自动打开浏览器    
        host: '0.0.0.0',  
    },
});

因为vue@2.6.14作为主包,需要给@vitejs/plugin-vue传入compiler;vue3内置compiler-sfc,就不用传入compiler。

3. rollup配置:

// 处理es6代码转换
import babel from '@rollup/plugin-babel'

// 告诉rollup如何查找外部模块并安装
import resolve from '@rollup/plugin-node-resolve'

// 可以导入已有的cjs模块,使rollup可以识别它
import commonjs from '@rollup/plugin-commonjs'

// 样式文件处理
import postcss from 'rollup-plugin-postcss'

// 处理css定义的变量
import simplevars from 'postcss-simple-vars'

// 处理less嵌套样式写法
import nested from 'postcss-nested'

// css代码压缩
import cssnano from 'cssnano'

// 代码压缩
import { terser } from 'rollup-plugin-terser'

// 处理css文件引入的图片
import url from 'postcss-url'

// 处理template图片
import image from '@rollup/plugin-image'

// vue2打包
import createVuePlugin2 from 'rollup-plugin-vue2'

// vue3打包
import createVuePlugin3 from 'rollup-plugin-vue3'

// vue2 支持 setup语法
import ScriptSetup from 'unplugin-vue2-script-setup/rollup'

import { getDistDir } from './scripts/utils.mjs'
import { isVue2, version } from 'vue-demi'

// 入口文件
const entry = 'src/index.js'

// babel配置
const babelOptions = {  
    presets: ['@babel/preset-env'],  
    exclude: '**/node_modules/**',
}

// rollup配置
export default {  
    // 入口  
    input: entry,  

    // 打包信息  
    output: [    
        {      
            file: getDistDir(version) + 'index.es.js',      
            format: 'es',    
        },    
        {      
            file: getDistDir(version) + 'index.cjs.js',     
            format: 'cjs',      
            exports: 'default',    
        },    
        {      
            file: getDistDir(version) + 'index.umd.js',      
            format: 'umd',      
            name: 'bundle',    
        },  
    ],  
    // 将模块视为外部模块,不会打包在库中,这里视项目具体情况自行调整 如: '@ant-design/icons',  
    external: ['vue', 'vue-demi'],  

    // 插件配置  
    plugins: [    
        isVue2 ? ScriptSetup() : undefined,    
        isVue2 ? createVuePlugin2({ css: false }) : createVuePlugin3({ preprocessStyles: true }),   
        image(),    
        postcss({      
            plugins: [        
                url({ url: 'inline', maxSize: 10 }),        
                simplevars(),        
                nested(),        
                cssnano(),     
             ],      
             extensions: ['.css', 'less'],    
        }),    
        resolve({      
            extensions: ['.vue'], // 无后缀名引用时,需要识别 .vue 文件     
            exclude: '**/node_modules/**', // 排除node_modules    
        }),    
        commonjs(),    
        babel(babelOptions),    
        terser(),  
    ],
}

rollup-plugin-vue插件需指定版本,@5.1.9对应的是vue2.x,@6.0.0以上对应的是vue3.x

遇到的问题:

1. rollup打包的时候报错

Vue packages version mismatch: - vue@2.6.14 - vue-template-compiler@2.7.10

vue2.x版本打包时,vue的版本需要和vue-template-compiler的版本保持一致

2. npx vue-demi-switch xx 不生效

rollup.config.js 没有设置 external: ['vue', 'vue-demi'], 导致打包的时候把vue也打包进去了,固定了vue的版本

3. 打包后,vue3环境下样式不生效

后面看了下打包的文件,发现css的作用域貌似有问题

.Navigation[data-v-2f2833eb]-bg[data-v-2f2833eb]__title

需要在rollup.config.js 中rollup-plugin-vue3设置 preprocessStyles: true

补充了下demo的栗子,可以参考下

栗子🌰:github.com/jayyoonn/vu…