背景:
最近没啥业务需求,团队打算把技术栈升级升级,升级下脚手架把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