Vue组件库文档站点的搭建思路

简介: 本文为Varlet组件库源码主题阅读系列第四篇,读完本篇,可以了解到如何使用`Vite`的`Api`接口来启动服务、如何动态生成多语言的页面路由。
本文为Varlet组件库源码主题阅读系列第四篇,读完本篇,可以了解到如何使用 ViteApi接口来启动服务、如何动态生成多语言的页面路由。

Varlet的文档网站其实就是一个Vue项目,整体分成两个单独的页面:文档页面及手机预览页面。

网站源代码文件默认是放在varlet-cli目录下,也就是脚手架的包里:

执行脚手架提供的dev命令时会把这个目录复制到varlet-ui/.varlet目录下,并且动态生成两个页面的路由配置文件:

然后使用Vite启动服务。

启动命令

先来看一下varlet-cli提供的dev命令都做了些什么。

// varlet-cli/src/index.ts
import { Command } from 'commander'
const program = new Command()

program
  .command('dev')
  .option('-f --force', 'Force dep pre-optimization regardless of whether deps have changed')
  .description('Run varlet development environment')
  .action(dev)

可以看到这个命令是用来运行varlet的开发环境的,还提供了一个参数,用来强制开启Vite依赖预构建功能,处理函数是dev

// varlet-cli/src/commands/dev.ts
export async function dev(cmd: { force?: boolean }) {
  process.env.NODE_ENV = 'development'
  // SRC_DIR:varlet-ui/src,即组件的源码目录
  ensureDirSync(SRC_DIR)
  await startServer(cmd.force)
}

设置了环境变量,确保组件源目录是否存在,最后调用了startServer方法:

// varlet-cli/src/commands/dev.ts
let server: ViteDevServer
let watcher: FSWatcher
async function startServer(force: boolean | undefined) {
    // 如果server实例已经存在了,那么代表是重启
    const isRestart = Boolean(server)
    // 先关闭之前已经存在的实例
    server && (await server.close())
    watcher && (await watcher.close())
    // 构建站点入口
    await buildSiteEntry()
}

构建站点项目

复制站点文件的操作就在buildSiteEntry方法里:

// varlet-cli/src/compiler/compileSiteEntry.ts
export async function buildSiteEntry() {
  getVarletConfig(true)
  await Promise.all([buildMobileSiteRoutes(), buildPcSiteRoutes(), buildSiteSource()])
}

主要执行了四个方法,先看getVarletConfig

// varlet-cli/src/config/varlet.config.ts
export function getVarletConfig(emit = false): Record<string, any> {
  let config: any = {}
  // VARLET_CONFIG:varlet-ui/varlet.config.js,即varlet-ui组件库目录下的配置文件
  if (pathExistsSync(VARLET_CONFIG)) {
    // require方法导入后会进行缓存,下次同样的导入会直接使用缓存,所以当重新启动服务时需要先删除缓存
    delete require.cache[require.resolve(VARLET_CONFIG)]
    config = require(VARLET_CONFIG)
  }
  // 默认配置,varlet-cli/varlet.default.config.js
  delete require.cache[require.resolve('../../varlet.default.config.js')]
  const defaultConfig = require('../../varlet.default.config.js')
  // 合并配置
  const mergedConfig = merge(defaultConfig, config)

  if (emit) {
    const source = JSON.stringify(mergedConfig, null, 2)
    // SITE_CONFIG:resolve(CWD, '.varlet/site.config.json')
    // outputFileSyncOnChange方法会检查内容是否有变化,没有变化不会重新写入文件
    outputFileSyncOnChange(SITE_CONFIG, source)
  }
  return mergedConfig
}

这个方法主要是合并组件库目录varlet-ui下的配置文件和默认的配置文件,然后将合并后的配置写入到站点的目标目录varlet-ui/.varlet/下。

合并完配置后执行了三个build方法:

生成手机页面路由

1.buildMobileSiteRoutes()方法:

// varlet-cli/src/compiler/compileSiteEntry.ts
export async function buildMobileSiteRoutes() {
  const examples: string[] = await findExamples()
  // 拼接路由
  const routes = examples.map(
    (example) => `
  {
    path: '${getExampleRoutePath(example)}',
    // @ts-ignore
    component: () => import('${example}')
  }`
  )

  const source = `export default [\
    ${routes.join(',')}
]`
  // SITE_MOBILE_ROUTES:resolve(CWD, '.varlet/mobile.routes.ts'),站点的手机预览页面路由文件
  await outputFileSyncOnChange(SITE_MOBILE_ROUTES, source)
}

这个方法主要是构建手机预览页面的路由文件,路由其实就是路径到组件的映射,所以先获取了路由组件列表,然后按格式拼接路由的内容,最后写入文件。

findExamples()

// varlet-cli/src/compiler/compileSiteEntry.ts
export function findExamples(): Promise<string[]> {
  // SRC_DIR:varlet-ui/scr目录,即组件库的源码目录
  // EXAMPLE_DIR_NAME:example,即每个组件的示例目录
  // DIR_INDEX:index.vue
  return glob(`${SRC_DIR}/**/${EXAMPLE_DIR_NAME}/${DIR_INDEX}`)
}

从组件库源码目录里获取每个组件的示例组件,每个组件都是一个单独的目录,目录下存在一个example示例文件目录,该目录下的index.vue即示例组件,比如按钮组件Button的目录及示例组件如下:

这个方法获取到的是绝对路径,并不能用作路由的path,所以需要进行一下处理:

// varlet-cli/src/compiler/compileSiteEntry.ts
const EXAMPLE_COMPONENT_NAME_RE = /\/([-\w]+)\/example\/index.vue/
export function getExampleRoutePath(examplePath: string): string {
  return '/' + examplePath.match(EXAMPLE_COMPONENT_NAME_RE)?.[1]
}

提取出example前面的一段,即组件的目录名称,也就是组件的名称,最后生成的路由数据如下:

生成pc页面路由

2.buildPcSiteRoutes()方法:

pc页面的路由稍微会复杂一点:

// varlet-cli/src/compiler/compileSiteEntry.ts
export async function buildPcSiteRoutes() {
  const [componentDocs, rootDocs, rootLocales] = await Promise.all([
    findComponentDocs(),
    findRootDocs(),
    findRootLocales(),
  ])
}

获取了三类文件,第一种:

// varlet-cli/src/compiler/compileSiteEntry.ts
export function findComponentDocs(): Promise<string[]> {
  // SRC_DIR:varlet-ui/scr目录,即组件库的源码目录
  // DOCS_DIR_NAME:docs
  return glob(`${SRC_DIR}/**/${DOCS_DIR_NAME}/*.md`)
}

获取组件目录varlet-ui/src/**/docs/*.md文件,也就是获取每个组件的文档文件,比如Button组件:

文档是markdown格式编写的。

第二种:

// varlet-cli/src/compiler/compileSiteEntry.ts
export function findRootDocs(): Promise<string[]> {
  // ROOT_DOCS_DIR:varlet-ui/docs
  return glob(`${ROOT_DOCS_DIR}/*.md`)
}

获取除组件文档外的其他文档,比如基本介绍、快速开始之类的。

第三种:

// varlet-cli/src/compiler/compileSiteEntry.ts
export async function findRootLocales(): Promise<string[]> {
  // 默认的语言
  const defaultLanguage = get(getVarletConfig(), 'defaultLanguage')
  // SITE:varlet-cli/site/
  // LOCALE_DIR_NAME:locale
  const baseLocales = await glob(`${SITE}/pc/pages/**/${LOCALE_DIR_NAME}/*.ts`)
}

获取默认的语言类型,默认是zh-CN,然后获取站点pc页面的locale文件,继续:

// varlet-cli/src/compiler/compileSiteEntry.ts
const ROOT_LOCALE_RE = /\/pages\/([-\w]+)\/locale\/([-\w]+)\.ts/
export async function findRootLocales(): Promise<string[]> {
    // ...
    const filterMap = new Map()
    baseLocales.forEach((locale) => {
        // 解析出页面path及文件的语言类型
        const [, routePath, language] = locale.match(ROOT_LOCALE_RE) ?? []
        // SITE_PC_DIR:varlet-ui/.varlet/site/pc
        filterMap.set(routePath + language, slash(`${SITE_PC_DIR}/pages/${routePath}/locale/${language}.ts`))
    })
    return Promise.resolve(Array.from(filterMap.values()))
}

返回获取到pc站点的locale文件路径,也就是这些文件:

目前只有一个Index页面,也就是站点的首页:

回到buildPcSiteRoutes()方法,文件路径都获取完了,接下来肯定就是遍历生成路由配置了:

// varlet-cli/src/compiler/compileSiteEntry.ts
export async function buildPcSiteRoutes() {
  // ...
  // 生成站点页面路由
  const rootPagesRoutes = rootLocales.map(
    (rootLocale) => `
  {
    path: '${getRootRoutePath(rootLocale)}',
    // @ts-ignore
    component: () => import('${getRootFilePath(rootLocale)}')
  }\
`
  )
}

有多少种翻译,同一个组件就会生成多少种路由,对于站点首页来说,目前存在en-US.tszh-CN两种翻译文件,那么会生成下面两个路由:

继续:

// varlet-cli/src/compiler/compileSiteEntry.ts
export async function buildPcSiteRoutes() {
  // ...
  // 生成每个组件的文档路由
  const componentDocsRoutes = componentDocs.map(
    (componentDoc) => `
      {
        path: '${getComponentDocRoutePath(componentDoc)}',
        // @ts-ignore
        component: () => import('${componentDoc}')
      }`
  )
  // 生成其他文档路由
  const rootDocsRoutes = rootDocs.map(
    (rootDoc) => `
      {
        path: '${getRootDocRoutePath(rootDoc)}',
        // @ts-ignore
        component: () => import('${rootDoc}')
      }`
  )
}

接下来拼接了组件文档和其他文档的路由,同样也是存在几种翻译,就会生成几个路由:

继续:

// varlet-cli/src/compiler/compileSiteEntry.ts
export async function buildPcSiteRoutes() {
  // ...
  const layoutRoutes = `{
    path: '/layout',
    // @ts-ignore
    component:()=> import('${slash(SITE_PC_DIR)}/Layout.vue'),
    children: [
      ${[...componentDocsRoutes, rootDocsRoutes].join(',')},
    ]
  }`
}

这个路由是干嘛的呢,其实就是真正的文档页面了:

组件文档路由和其他文档路由都是它的子路由,Layout.vue组件提供了组件详情页面的基本骨架,包括页面顶部栏、左边的菜单栏,中间部分就是子路由的出口,即具体的文档,右侧通过iframe引入了手机预览页面。

最后导出路由配置及写入到文件即可:

// varlet-cli/src/compiler/compileSiteEntry.ts
export async function buildPcSiteRoutes() {
  // ...
  const source = `export default [\
  ${rootPagesRoutes.join(',')},
  ${layoutRoutes}
]`
  // SITE_PC_ROUTES:varlet-ui/.varlet/pc.routes.ts
  outputFileSyncOnChange(SITE_PC_ROUTES, source)
}

复制站点文件

3.buildSiteSource()方法:

// varlet-cli/src/compiler/compileSiteEntry.ts
export async function buildSiteSource() {
  return copy(SITE, SITE_DIR)
}

这个方法很简单,就是将站点的项目文件由varlet-cli/site目录复制到varlet-ui/.varlet/site目录下。

总结一下上述操作,就是将站点的源代码文件由cli包复制到ui包,然后动态生成站点项目的路由文件。整个站点分为两个页面pcmobilepc页面主要是提供文档展示及嵌入mobile页面,mobile页面用来展示各个组件的demo

启动服务

项目准备就绪,接下来就是启动服务了,回到startServer方法:

// varlet-ui/src/commands/dev.ts
async function startServer(force: boolean | undefined) {
  await buildSiteEntry()
  // 获取合并后的配置
  const varletConfig = getVarletConfig()
  // 获取Vite的启动配置,部分配置来自于varletConfig
  const devConfig = getDevConfig(varletConfig)
  // 将是否强制进行依赖预构建配置合并到Vite配置
  const inlineConfig = merge(devConfig, force ? { server: { force: true } } : {})
}

生成Vite的启动配置,然后就可以启动服务了:

// varlet-ui/src/commands/dev.ts
async function startServer(force: boolean | undefined) {
    // ...
    // 启动Vite服务
    server = await createServer(inlineConfig)
    await server.listen()
    server.printUrls()
    // VARLET_CONFIG:varlet-ui/varlet.config.js
    // 监听用户配置文件,修改了就重新启动服务
    if (pathExistsSync(VARLET_CONFIG)) {
        watcher = chokidar.watch(VARLET_CONFIG)
        watcher.on('change', () => startServer(force))
    }
}

使用了ViteJavaScript API来启动服务,并且当配置文件发送变化会重启服务。

Vite配置

接下来详细看一下上一步启动服务时的Vite配置:

// varlet-cli/src/config/vite.config.ts
export const VITE_RESOLVE_EXTENSIONS = ['.vue', '.tsx', '.ts', '.jsx', '.js', '.less', '.css']

export function getDevConfig(varletConfig: Record<string, any>): InlineConfig {
  // 默认语言
  const defaultLanguage = get(varletConfig, 'defaultLanguage')
  // 端口
  const host = get(varletConfig, 'host')

  return {
    root: SITE_DIR,// 项目根目录:varlet-ui/.varlet/site
    resolve: {
      extensions: VITE_RESOLVE_EXTENSIONS,// 导入时想要省略的扩展名列表
      alias: {// 导入路径别名
        '@config': SITE_CONFIG,
        '@pc-routes': SITE_PC_ROUTES,
        '@mobile-routes': SITE_MOBILE_ROUTES,
      },
    },
    server: {// 设置要监听的端口号和ip地址
      port: get(varletConfig, 'port'),
      host: host === 'localhost' ? '0.0.0.0' : host,
    },
    publicDir: SITE_PUBLIC_PATH,// 作为静态资源服务的文件夹:varlet-ui/public
    // ...
  }
}

设置了一些基本配置,你可能会有个小疑问,站点项目明明是个多页面项目,但是上面似乎并没有配置任何多页面相关的内容,其实在Vue Cli项目中是需要修改入口配置的,但是在Vite项目中不需要,这可能就是开发环境不需要打包的一个好处吧,不过虽然开发环境不需要配置,但是最后打包的时候是需要的:

接下来还配置了一系列的插件:

import vue from '@vitejs/plugin-vue'
import md from '@varlet/markdown-vite-plugin'
import jsx from '@vitejs/plugin-vue-jsx'
import { injectHtml } from 'vite-plugin-html'

export function getDevConfig(varletConfig: Record<string, any>): InlineConfig {
    // ...
    return {
        // ...
        plugins: [
            // 提供 Vue 3 单文件组件支持
            vue({
                include: [/\.vue$/, /\.md$/],
            }),
            md({ style: get(varletConfig, 'highlight.style') }),
            // 提供 Vue 3 JSX 支持
            jsx(),
            // 给html页面注入数据
            injectHtml({
                data: {
                    pcTitle: get(varletConfig, `pc.title['${defaultLanguage}']`),
                    mobileTitle: get(varletConfig, `mobile.title['${defaultLanguage}']`),
                    logo: get(varletConfig, `logo`),
                    baidu: get(varletConfig, `analysis.baidu`, ''),
                },
            }),
        ],
    }
}

一共使用了四个插件,其中的md插件是Varlet自己编写的,顾名思义,就是用来处理md文件的,具体逻辑我们下一篇再看。

打包

最后就是站点项目的打包了,使用的是varlet-cli提供的build命令:

// varlet-cli/src/index.ts
program.command('build').description('Build varlet site for production').action(build)

处理函数为build

// varlet-cli/src/commands/build.ts
export async function build() {
  process.env.NODE_ENV = 'production'

  ensureDirSync(SRC_DIR)
  await buildSiteEntry()
  const varletConfig = getVarletConfig()
  // 获取Vite的打包配置
  const buildConfig = getBuildConfig(varletConfig)

  await buildVite(buildConfig)
}

逻辑很简单,先设置环境变量为生产环境,然后同样执行了buildSiteEntry方法,最后获取Vite的打包配置进行打包即可:

// varlet-cli/src/config/vite.config.ts
export function getBuildConfig(varletConfig: Record<string, any>): InlineConfig {
  const devConfig = getDevConfig(varletConfig)

  return {
    ...devConfig,
    base: './',// 公共基础路径
    build: {
      outDir: SITE_OUTPUT_PATH,// varlet-ui/site
      brotliSize: false,// 禁用 brotli 压缩大小报告
      emptyOutDir: true,// 输出目录不在root目录下,所以需要手动开启该配置来清空输出目录
      cssTarget: 'chrome61',// https://www.vitejs.net/config/#build-csstarget
      rollupOptions: {
        input: {// 多页面入口配置
          main: resolve(SITE_DIR, 'index.html'),
          mobile: resolve(SITE_DIR, 'mobile.html'),
        },
      },
    },
  }
}

在启动服务的配置基础上增加了几个打包相关的配置。

到这里文档站点的初始化、启动、构建办法就介绍完了,我们下一篇再见~

相关文章
|
2月前
|
JavaScript
vue使用iconfont图标
vue使用iconfont图标
151 1
|
8天前
|
移动开发 JavaScript API
Vue Router 核心原理
Vue Router 是 Vue.js 的官方路由管理器,用于实现单页面应用(SPA)的路由功能。其核心原理包括路由配置、监听浏览器事件和组件渲染等。通过定义路径与组件的映射关系,Vue Router 将用户访问的路径与对应的组件关联,支持哈希和历史模式监听 URL 变化,确保页面导航时正确渲染组件。
|
12天前
|
监控 JavaScript 前端开发
ry-vue-flowable-xg:震撼来袭!这款基于 Vue 和 Flowable 的企业级工程项目管理项目,你绝不能错过
基于 Vue 和 Flowable 的企业级工程项目管理平台,免费开源且高度定制化。它覆盖投标管理、进度控制、财务核算等全流程需求,提供流程设计、部署、监控和任务管理等功能,适用于企业办公、生产制造、金融服务等多个场景,助力企业提升效率与竞争力。
66 12
|
8天前
|
JavaScript 前端开发 开发者
Vue中的class和style绑定
在 Vue 中,class 和 style 绑定是基于数据驱动视图的强大功能。通过 class 绑定,可以动态更新元素的 class 属性,支持对象和数组语法,适用于普通元素和组件。style 绑定则允许以对象或数组形式动态设置内联样式,Vue 会根据数据变化自动更新 DOM。
|
8天前
|
JavaScript 前端开发 数据安全/隐私保护
Vue Router 简介
Vue Router 是 Vue.js 官方的路由管理库,用于构建单页面应用(SPA)。它将不同页面映射到对应组件,支持嵌套路由、路由参数和导航守卫等功能,简化复杂前端应用的开发。主要特性包括路由映射、嵌套路由、路由参数、导航守卫和路由懒加载,提升性能和开发效率。安装命令:`npm install vue-router`。
|
29天前
|
JavaScript 安全 API
iframe嵌入页面实现免登录思路(以vue为例)
通过上述步骤,可以在Vue.js项目中通过 `iframe`实现不同应用间的免登录功能。利用Token传递和消息传递机制,可以确保安全、高效地在主应用和子应用间共享登录状态。这种方法在实际项目中具有广泛的应用前景,能够显著提升用户体验。
61 8
|
3月前
|
缓存 JavaScript UED
Vue3中v-model在处理自定义组件双向数据绑定时有哪些注意事项?
在使用`v-model`处理自定义组件双向数据绑定时,要仔细考虑各种因素,确保数据的准确传递和更新,同时提供良好的用户体验和代码可维护性。通过合理的设计和注意事项的遵循,能够更好地发挥`v-model`的优势,实现高效的双向数据绑定效果。
181 64
|
30天前
|
存储 设计模式 JavaScript
Vue 组件化开发:构建高质量应用的核心
本文深入探讨了 Vue.js 组件化开发的核心概念与最佳实践。
77 1
|
3月前
|
JavaScript 前端开发 开发者
vue 数据驱动视图
总之,Vue 数据驱动视图是一种先进的理念和技术,它为前端开发带来了巨大的便利和优势。通过理解和应用这一特性,开发者能够构建出更加动态、高效、用户体验良好的前端应用。在不断发展的前端领域中,数据驱动视图将继续发挥重要作用,推动着应用界面的不断创新和进化。
111 58
|
2月前
|
JavaScript 关系型数据库 MySQL
基于VUE的校园二手交易平台系统设计与实现毕业设计论文模板
基于Vue的校园二手交易平台是一款专为校园用户设计的在线交易系统,提供简洁高效、安全可靠的二手商品买卖环境。平台利用Vue框架的响应式数据绑定和组件化特性,实现用户友好的界面,方便商品浏览、发布与管理。该系统采用Node.js、MySQL及B/S架构,确保稳定性和多功能模块设计,涵盖管理员和用户功能模块,促进物品循环使用,降低开销,提升环保意识,助力绿色校园文化建设。

热门文章

最新文章