使用 Vuepress 搭建 Vue2 组件库文档——补坑(二)解决 vuepress2 版本不支持 vue2 引发的各
前言
vue-template-compiler 版本冲突
vue-template-compiler 的版本需要与 Vue2 的版本一致。
// packages/components/package.json
"dependencies": {
"vue-template-compiler": "2.6.14"
}
Vuepress 版本问题
写一个 Toast 插件试试
// packages/components/src/plugins/toast/core/main.ts
import Toast from './Toast.vue';
toast = (options = {}) => {
// 创建构造器
const ToastConstructor = Vue.extend(Toast);
if (typeof options === 'string') {
options = {
message: options,
};
}
// 创建组件实例
const toastInstance = new ToastConstructor({
props: options,
});
// 渲染组件
const vm = toastInstance.$mount();
// 挂载组件
document.body.appendChild(vm.$el);
// 返回一个关闭方法
return () => {
vm.close = true;
}
}
// ./Toast.vue
<template>
<div>Test Toast</div>
</template>
// packages\components\src\plugins\toast\index.ts
import { VueConstructor } from "vue";
import {
toast,
} from './core/main.js';
class ToastPlugin {
static install (Vue: VueConstructor) {
Vue.prototype.$toast = toast;
}
}
export default ToastPlugin;
// packages\components\src\main.ts
export * from './plugins/toast/core/main';
export { default as ToastPlugin } from './plugins/toast/index.ts';
这只是简单地写了写页面内容,细节方面在此略过,重要的是看 vuepress 的版本问题。
docs 项目报错了:
main.ts:1 Uncaught SyntaxError: The requested module '/packages/docs/docs/.vuepress/.cache/deps/vue.js?v=45011705' does not provide an export named 'default' (at main.ts:1:8)
根据这个报错,目测是 Vue 的问题。
进一步查找原因,发现 vuepress 项目中安装的 Vue 版本是 3.x,这造成了 Vue 的版本不兼容。
之前打包、启动都正常,所以忽视了这个问题。
看来还是 vuepress 的版本装高了,那就降低版本试试。
降低 Vuepress 版本
前:
// packages\docs\package.json
{
"devDependencies": {
"@vuepress/bundler-vite": "^2.0.0-rc.7",
"@vuepress/theme-default": "^2.0.0-rc.11",
"vue": "^3.4.0",
"vuepress": "^2.0.0-rc.7"
},
}
后:
// packages\docs\package.json
{
"devDependencies": {
"vuepress": "1.9.10"
},
}
修改 docs 目录
cd packages/docs
rm -rf node_modules docs/.vuepress
rm -rf docs/pages
vue-server-renderer 版本问题
pnpm docs:dev
这时报了这个错误,那么就安装同版本的 vue-server-renderer
试试
// packages\docs\package.json
"dependencies": {
"vue-server-renderer": "2.6.14"
}
这里需要注意下,如果不小心把这个"vue-server-renderer"
删除了,再安装的时候,它还是可以启动成功的,因为它的版本被锁在了根目录的 pnpm-lock.yaml
文件中。
上传代码后,别人拉你代码启动的时候会有"vue-server-renderer"版本问题,所以需要手动装上 "vue-server-renderer": "2.6.14"
锁定版本。
模块化问题
启动项目的时候会出现不支持 "type": "module"
,这是由于之前使用的 vuepress2 内部使用了打包工具 vite
,在 package.json 配置了 "type": "module"。
而现在降低版本后,vuepress 内置的打包器是 webpack,不支持 ESM。
把 package.json 中的这个设置去掉就行。
// packages\docs\package.json
{
"type": "module", // 把这个去掉
}
再重启下,发现就可以启动成功了:
docs 主页属性变更
这里没显示配置的 docs/README.md 文件的内容,而是显示了 vuepress 默认的启动界面,是因为更改了 vuepress 版本后,主页 md 文件的部分属性需要做些修改:
前:
// packages\docs\docs\README.md
---
home: true
title: Home
heroImage: https://vuejs.press/images/hero.png
actions:
- text: 快速上手
link: /pages/Button/
type: primary
后:
// packages\docs\docs\README.md
---
home: true
# heroImage: /home-icon.png
heroText: 组件库文档
tagline: 复用、集中管理
actionText: 快速上手 →
actionLink: /guide/
features:
- title: Vue2
details: 支持 Vue2 项目
footer: MIT Licensed | Copyright © 2024 | jiayinkong
---
这样就可以正常显示了:
调整 .vuepress 配置
还是一样,因为 vuepress 版本差异,客户端的配置需要做一些调整。 vuepress 官网
config.js
// packages\docs\docs\.vuepress\config.js
module.exports = {
title: '组件库文档',
description: '',
themeConfig: {
logo: '',
nav: [
{
text: '指南',
link: '/guide/'
},
{
text: '源码',
link: '',
},
],
},
}
添加 guide 目录
mkdir docs/guide && echo 'guide' > docs/guide/index.md
enhanceApp.js
// packages\docs\docs\.vuepress\enhanceApp.js
import MyLibUI from '@my-lib-ui/components'
// 使用异步函数也是可以的
export default ({
Vue, // VuePress 正在使用的 Vue 构造函数
options, // 附加到根实例的一些选项
router, // 当前应用的路由实例
siteData, // 站点元数据
isServer // 当前应用配置是处于 服务端渲染 或 客户端
}) => {
// 注册组件库
Vue.use(MyLibUI);
}
未编译报 TS 问题
回顾上次实现的实时更新代码,引入组件库,是指向的 packages/components/main.ts,这是未被编译的ts文件,在 vuepress2 中这样是没问题的,但是 vuepress1.x 不支持这样使用。
但这个问题还是可以解决的,我目前想到的办法是,引入组件库的打包产物。
这样一来又有问题,那就是如何实时监听组件库代码的变化更新到 docs 项目中。
办法还是有的,那就是以监听打包的方式得到打包产物,打包之后的是编译后的js文件,当修改组件库代码时,会重新打包得到新的打包产物,以获得实时编译结果。
在组件库项目 package.json 添加以下的监听打包命令:
// packages/components/package.json
{
"scripts": {
"build:watch": "tsc && vite build --watch",
}
}
修改引入组件库路径为 dist 产物:
// packages\docs\docs\.vuepress\enhanceApp.js
import MyLibUI from '@my-lib-ui/components/dist/my-lib-ui'
// 使用异步函数也是可以的
export default ({
Vue, // VuePress 正在使用的 Vue 构造函数
}) => {
// 注册组件库
Vue.use(MyLibUI);
}
至此,页面终于又可以正常显示了。
调整 docs 侧边栏配置
config.js
// packages\docs\docs\.vuepress\config.js
const COMPONENT_SIDEBAR = require('./constants/sidebar/components');
module.exports = {
title: '组件库文档',
description: '',
themeConfig: {
logo: '',
nav: [
{
text: '指南',
link: '/guide/'
},
{
text: '源码',
link: '',
},
],
sidebar: {
'/guide/': [
{
title: '快速上手', // 必要的
path: '/guide/',
collapsable: false, // 可选的, 默认值是 true,
sidebarDepth: 0, // 可选的, 默认值是 1
// children: ['index.md']
},
{
title: '组件',
collapsable: false, // 可选的, 默认值是 true,
sidebarDepth: 0, // 可选的, 默认值是 1
children: COMPONENT_SIDEBAR,
// initialOpenGroupIndex: -1 // 可选的, 默认值是 0
}
]
}
},
}
guide 的 sidebar
// packages\docs\docs\.vuepress\constants\sidebar\components.js
const COMPONENT_SIDEBAR = [
{
title: 'Button',
path: '/guide/components/button/'
},
{
title: 'Toast',
path: '/guide/components/toast/'
}
];
module.exports = COMPONENT_SIDEBAR;
guide 的子目录 components
// packages\docs\docs\guide\components\button\index.md
## 基本使用
button
// packages\docs\docs\guide\components\toast\index.md
## 基本使用
toast
访问 http://localhost:8080/guide/ ,此时能正常显示侧边栏了:
使用 Toast 插件
// packages\docs\docs\.vuepress\enhanceApp.js
import MyLibUI, { ToastPlugin } from '@my-lib-ui/components/dist/my-lib-ui'
// 使用异步函数也是可以的
export default ({
Vue, // VuePress 正在使用的 Vue 构造函数
options, // 附加到根实例的一些选项
router, // 当前应用的路由实例
siteData, // 站点元数据
isServer // 当前应用配置是处于 服务端渲染 或 客户端
}) => {
// 注册组件库
Vue.use(MyLibUI);
// 注册插件
Vue.use(ToastPlugin);
}
// packages\docs\docs\guide\components\toast\index.md
## 基础使用
### 按需引入使用
<template>
<button @click="handleToast">toast</button>
</template>
### 绑定到 Vue 原型使用
<template>
<button @click="handlePropertyToast">toast</button>
</template>
<script>
import { toast } from '@my-lib-ui/components/dist/my-lib-ui';
export default {
data() {
return {
};
},
mounted() {
},
methods: {
handleToast() {
toast('This is Toast')
},
handlePropertyToast() {
this.$toast?.('Toast-Property');
},
}
}
</script>
访问 http://localhost:8080/guide/components/toast/
可以看到写在 toast 组件里的内容能显示到页面上了:
因为没有做任何样式处理,需要打开 F12 查看元素 或 手机模式查看DOM的挂载情况:
vuepress 使用 css 预处理器
如果使用 less 预处理器,会报错,提示缺少 less-loader
根据 vuepress 的官网,是这样说的:
VuePress 对以下预处理器已经内置相关的 webpack 配置:
sass
、scss
、less
、stylus
和pug
。要使用它们你只需要在项目中安装对应的依赖即可。
那么,就安装一下相关依赖叭:
不过需要注意版本不能过高,坑踩过了,以下的版本可行:
"less": "3.12.0",
"less-loader": "7.3.0",
装完重启下,界面就正常显示了,样式也生效了:
## 基本使用
<template>
<div class="normal-button">normal button</div>
</template>
<style scoped lang="less">
.normal-button {
color: red;
}
</style>
至此,已经发现的坑都补完了。
再来说说 monorepo 的项目打包、启动。
因为 monorepo 有根目录,里面有子项目,每次都需要切换目录进行打包、启动不太方便,所以针对这个问题,我添加了一些简单便携的能提高开发体验的脚本。
只需要在根目录执行命令,就可以进行子项目的打包、启动。
添加脚本
以下脚本都只需要在根目录执行,省去了根目录、子目录来回切换的麻烦。
根目录脚本命令
// package.json
"scripts": {
"build:comp": "node ./scripts/build-comp.js",
"install:all": "node ./scripts/install-all.js",
"watch:comp": "node ./scripts/watch-comp.js",
"start:docs": "node ./scripts/start-docs.js"
},
文件路径映射
// scripts/shared/index.js
const path = require('path');
const childProcess = require('child_process');
const filePathMap = {
'compoents': path.resolve(process.cwd(), 'packages/components'),
'docs': path.resolve(process.cwd(), 'packages/docs'),
};
module.exports = {
filePathMap,
childProcess,
};
一个命令安装所有依赖
命令:
pnpm install:all
脚本:
// scripts/install.js
const { filePathMap, childProcess } = require('./shared/index.js');
// 安装所有依赖
installAll();
function installAll() {
// 安装根目录依赖
childProcess.spawnSync('rm -rf node_modules && pnpm i', { stdio: 'inherit', shell: true });
// 安装组件库依赖
childProcess.spawnSync('rm -rf node_modules && pnpm i', {
cwd: filePathMap.compoents,
stdio: 'inherit',
shell: true,
});
// 安装docs文档依赖
childProcess.spawnSync('rm -rf node_modules && pnpm i', {
cwd: filePathMap.docs,
stdio: 'inherit',
shell: true,
});
}
以监听方式打包组件库
命令:
pnpm watch:comp
脚本:
// scripts/watch-comp.js
const { childProcess, filePathMap } = require('./shared/index.js');
// 这个命令在开发组件库时是必须的,且需要在启动 docs 文档前执行
runWatchComp();
function runWatchComp() {
// 以监听的方式打包组件库,这里为了能够实现实时 npm link 到 docs 文档
childProcess.spawnSync('pnpm run build:watch', {
cwd: filePathMap.compoents,
stdio: 'inherit',
shell: true,
});
}
启动组件库文档
命令:
pnpm start:docs
脚本:
const { childProcess, filePathMap } = require('./shared/index.js');
// 启动 docs 文档,在启动前确保已经执行 pnpm watch:comp 命令
runStartDocs();
function runStartDocs() {
// 启动docs文档
childProcess.spawnSync('pnpm run docs:dev', {
cwd: filePathMap.docs,
stdio: 'inherit',
shell: true,
});
}
打包组件库输出产物
命令:
这个命令是直接打包,不 watch
// packages/components/package.json
{
"scripts": {
"build": "tsc && vite build && npm run copy",
"copy": "rm -rf ./../../output/ && copyfiles -u 1 ./dist/* ./../../output/"
}
}
脚本:
// scripts/build-comp.js
const { filePathMap, childProcess } = require('./shared/index.js');
const componentsDir = filePathMap.compoents;
// 打包组件库,产出 output 到根目录
runBuildComp();
function runBuildComp() {
childProcess.spawnSync(
'pnpm run build', {
cwd: componentsDir,
stdio: 'inherit',
shell: true,
});
}
// childProcess.exec(`cd ${componentsDir} && rm -rf node_modules && npm i && npm run build`, (error, stdout) => {
// if (error) {
// console.log(error);
// return;
// }
// });
pnpm 命令
其实还有一种更懒便捷的方式,就是用 pnpm run -C
命令。
-C
是指在指定的路径下执行命令而不是在当前目录。
// 根目录 package.json
{
"scripts": {
"build:components": "pnpm run -C packages/components build",
"build:docs": "pnpm run -C packages/docs docs:build",
}
}
在使用 pnpm 的时候注意 Node 的版本,这是官网列出的不同 pnpm 版本 对 Node 版本的要求:
子进程对比
child_process
child_process
模块赋予了node可以随意创建子进程的能力 ,创建子进程的方法有:
- spawn
- exec
- execFile
- fork
以下是它们的区别:
类型 | 回调/异常 | 进程类型 | 执行类型 | 可设置超时 |
---|---|---|---|---|
spwan() | × | 任意 | 命令 | × |
exec() | √ | 任意 | 命令 | √ |
execFile() | √ | 任意 | 可执行文件 | √ |
fork() | × | Node | Javascript文件 | × |
如果考虑同步、异步,又可以分为:
异步
同步
child_process.execFileSync(file[, args][, options])
child_process.execSync(command[, options])
child_process.spawnSync(command[, args][, options])
spawn 与 exec 的区别
spawn
child_process.spawn()
返回的是一个 stream,当子进程需要返回大量数据时,spawn更安全。
exec
child_process.exec()
返回的是一个 buffer,这个buffer 默认大小是200K。当返回的数据超过默认值会产生“Error: maxBuffer exceeded”异常。
调大exec的maxBuffer选项虽然可以解决这个问题,不过当子进程返回的数据太过巨大的时候,这个问题依然会有。
exec
方法将会生成一个子shell,然后在该 shell 中执行命令,并缓冲产生的数据,当子流程完成后,并将子进程的输出以回调函数参数的形式一次性返回。
总结:
- 用shell语法、返回数据量小,用 exec
- 返回数据量大、显示执行进度,用 spawn
参考
源码
转载自:https://juejin.cn/post/7406482061731020819