查看原文
其他

Vue 多组件仓库搭建

狐友技术团队 搜狐技术产品 2021-01-15

☝点击上方蓝字,关注我们!


本文字数:3072

预计阅读时间:10分钟


导读


组件化是目前前端最为流行的代码复用方式,无论是 React 还是 Vue 也都是以组件为单元进行开发的。 

在我们的工作过程中,随着业务的增长,积累下来的组件也越来越多。这时候,如何高效地组织我们的组件也就成了一个需要解决的问题。



代码存放


在项目创建之前,我们比较了现在主流的两种代码仓库存储方式——单代码仓库(mono-repo)和多代码仓库(multi-repo)。 

多代码仓库中,我们以前端打包工具rollup为例,完整的项目应该是包括rollup主仓库,以及rollup-plugin-aliasrollup-plugin-bublerollup-starter-lib等很多相关仓库。按多代码仓库方式组织代码会有下面一些问题:  

  1. issue管理混乱。用户在使用过程中遇到问题,常常会直接在主仓库下提问,而这些问题很多是属于其他模块的,应该放在其他仓库中;

  2. 版本更新麻烦。被依赖的仓库代码变更之后,需要同步到所有依赖方的代码仓库中;

  3. 版本日志维护麻烦。和版本更新后修改依赖一样,版本日志也需要更新到每个代码仓库中。

单代码仓库中,以JavaScript编译工具babel为例,项目中在packages文件夹下有babel-clibabel-corebabel-helper-*babel-plugin-*等模块。按单代码仓库方式组织代码面临的问题包括:  

  1. 代码仓库会很大,可能会带来版本控制的问题;

  2. 对构建工具要求较高,需要构建工具可以构建packages下的所有模块。

比较过后,我们选择了按单代码仓库的方式来存放我们的组件。主要有两方面的原因,一方面是这些组件存在依赖的情况,并且可能比较频繁,另一方面是目前我们统一了开发库为vue,构建工具本身就是统一的。



项目创建


项目的创建主要使用了@vue/cli,它是官方提供的基于 Vue.js 进行快速开发的完整的脚手架、工具链系统。

创建时,cli 的交互式配置省去了对webpack以及各种loader的繁杂编写;开发过程中,cli 基于webpack-dev-server提供了支持模块热重载的开发服务器;完成后,cli 的构建功能提供了“应用、库、Web组件”等多种构建目标选择,这些可以很好的满足我们组件库的需求。


组件的存放

在使用@vue/cli搭建起项目脚手架后,参照babelElement等单代码仓库,在项目根目录下创建packages文件夹,该文件夹下每个组件对应一个目录,在目录下存放package.jsonREADME.mdCHANGELOG.md等文件,以及srcdistnode_module等目录。


1├── packages
2│   ├── error-page-vue
3│   │   └── ...
4│   ├── feed
5│   │   ├── CHANGELOG.md
6│   │   ├── README.md
7│   │   ├── dist
8│   │   ├── node_modules
9│   │   ├── package-lock.json
10│   │   ├── package.json
11│   │   └── src
12│   ├── feed-edit
13│   │   └──  ...
14│   └──  ...
15└──  ...


因为该项目中存放的都是 Vue 组件,我们可以把每个组件都需要的 Vue 相关的依赖以及构建相关的依赖(比如vuebabelsass等依赖)统一放在项目的根目录下,组件目录下只包含了组件业务的依赖。这样,我们的组件依赖就会纯粹很多。


演示/调试组件

组件包括了对功能和样式的复用,所以在组件开发/调试过程中,需要对组件进行展示。我们在项目根目录下创建了examples文件夹,文件夹下对应每个组件创建 vue 文件作为示例。为了更方便的查看对应的示例,我们维护了一个配置文件components.js,该配置文件用于项目中示例路由以及导航列表的创建等。


1// routes.js
2
3// Navigation.vue是导航组件
4import Navigation from "./Navigation";
5// components.js是维护组件的数组
6import components from "./components";
7
8let routes = components.map(component => ({
9  path`/${component.name}`,
10  component() => import(`../examples/${component.name}`)
11}));
12
13routes.unshift({
14  path"",
15  component: Navigation
16});
17
18export default routes;




创建/构建组件


根据上面创建的目录结构,在创建新的组件时,我们需要修改components.js配置文件,在examplespackages目录下分别创建对应的组件。在完成组件的开发后,需要构建并发布组件,如果同时修改了多个组件(比如有依赖的两个组件),最好是可以同时构建多个组件并发布。 

我们可以通过脚本来实现诸如此类便捷化的需求,在项目根目录下创建scripts文件夹用来存放这些脚本。目前我们项目中包括创建组件脚本new.js、构建脚本build.js、删除脚本delete.js等。  


创建脚本

根据需求,创建脚本就可以分为三部分。


1// scripts/new.js部分
2
3...
4
5const updateConfig = function(path, components) {
6    // 更新 components.js 文件
7    ...
8}
9
10const createPackages = function(componentName) {
11    // 在 packages 目录下创建组件项目目录
12    // 包括创建package.json、src/index.js、README.md、CHANGELOG.md等文件,及初始化文件内容
13    ...
14}
15
16const createExample = function(componentName) {
17    // 在 example 目录下创建组件示例
18    // 包括创建index.vue,及初始化文件内容
19    ...
20}
21
22...


执行创建脚本


创建脚本除了创建目录和文件之外,我们还可以做一些文件内容初始化的工作,比如README.md写入组件名、使用说明、开发说明等标题,examples/<component name>/index.vue中写入 template、对组件的引入等等。 


格式化初始内容

在对文件写入时,我们遇到的一个问题就是文件初始内容格式与项目代码规范不同。

起初我们的解决方案是使用 ES6 的模板字符串,按照格式规范小心翼翼的写入初始化的模板,多余的空格、回车都会导致格式上的问题,不利于后期修改维护。引入模板文件替代模板字符串,会使模板编写相对容易一些,但是因为模板文件中会有一些非规范的标记,导致难以对其格式化,这依然是治标不治本的方案。

更好的方案是在脚本中引入eslinteslint除了可以在命令行中运行,同样也可以在脚本中执行,eslint官网提供在 NodeJS 中运行的 API 文档。这样,我们就可以通过项目中的配置文件(比如.eslintrc.js)格式化文件的初始内容,即使以后我们改变了项目的代码规范,也不需要再变动创建文件的模板。


1// 根据配置初始化cli
2const CLIEngine = eslint.CLIEngine;
3// 需要注意的是比命令行中配置多添加`fix: true`才能格式化文件
4const cli = new CLIEngine({
5    ...require("../.eslintrc.js"),
6    fixtrue
7});
8
9// 执行eslint的fix
10CLIEngine.outputFixes(
11    cli.executeOnFiles([filePath])
12);


构建脚本

我们期望使用@vue/cli进行组件库中组件的构建,毕竟官方对自身更为了解。在官方文档中,只提到了在 shell 中执行 cli 提供构建功能,这样一次只能构建一个组件。 

我们可以通过脚本多次通过child_process.exec执行 shell 命令,来实现同时构建多个组件,这种方式在终端输出中会有文本颜色丢失现象。


颜色丢失


一种方式就像上面使用eslint那样,我们可以在脚本中执行 shell 命令对应的API,麻烦的是@vue/cli文档中并没有提供 Node API 文档。通过阅读分析源码,我们在@vue/cli下发现@vue/cli-service中的run方法可以执行build。 


1// scripts/build.js
2
3...
4// 通过cli执行build命令
5const vueCliService = require("@vue/cli-service");
6
7const buildService = new vueCliService();
8
9async function build() {
10  for (let i = 0, len = components.length; i < len; i++) {
11    const name = components[i].name
12    // 传入对应各项参数
13    await buildService.run(
14      "build",
15      {
16        _: ["build"`${root}/packages/${name}/src/index.js`],
17        target"lib",
18        name`hy-${name}`,
19        dest`${root}/packages/${name}/dist`,
20        // 生成格式: umd格式会同时成功demo.html commonjs,umd,umd-min
21        formats: "commonjs,umd-min"
22      }
23    )
24  }
25}
26
27...


使用 Node API 这种方式打包带来的另一个好处是我们可以实现更精细的控制。在我们的组件中,有一些是需要不同的打包配置的。在源码中,我们发现@vue/cli-service在实例化的时候,可以通过传入不同的路径作为参数,实现使用不同的配置执行build。在对每个组件执行build时,会先检测模块下是否存在配置文件,如果存在则用这个配置文件实例化@vue/cli-service,不存在就用根目录下配置文件。


1...
2
3const packagePath = `${root}/packages/${pkgName}`;
4const packageVueConfigJSPath = `${packagePath}/vue.config.js`;
5
6// cli工作目录,支持package内自定义配置,默认使用根目录下vue.config.js
7let cliWorkDict = fs.existsSync(packageVueConfigJSPath)
8  ? packagePath
9  : root;
10console.log(">> vue.config.js", packageVueConfigJSPath);
11
12let pkgCliService = new vueCliService(cliWorkDict);
13
14...




版本管理


在完成创建、编写、构建过程之后,最后的步骤就是发布组件。

组件的发布版本号在自身目录下的package.json中,组件越来越多,组件之间可能存在相互依赖,这些导致管理维护版本号会是很痛苦的事情,而lerna可以很好的解决这个问题。


Lerna

lerna是一个单代码仓库的管理工具,提供了维护单代码仓库中多个package的创建、管理、发布等功能。在创建和开发过程中,lerna可以帮助我们解决依赖安装问题,省去我们进入每个组件目录下执行npm install的麻烦。

发布时,lerna主要帮助我们维护版本以及执行发布。lerna通过检测哪些组件中代码有变化,让我们选择更新主版本号、次版本号、还是修订版本号,帮助我们更新该组件版本号。之后,会帮我们提交到远程仓库以及 npm 仓库中。


版本选择


Changelog规范

更新日志可以帮助用户和开发人员简单明确地知道项目中不同版本之间的变化。

在我们的项目中,选择了人工以时间倒序的方式来维护CHANGELOG.md文件。内容主要包括版本号和变动列表,其中变动列表包括新增、变更、移除、修复等。新增用Added、变更用Changed、移除用Removed、修复用Fixed。


1<!-- 下面是一次版本变动的片段 -->
2...
3
4## v0.5.9
5#### Added
6增加70-92号表情
7#### Fixed
8没有对应表情时,可以显示对应文本
9
10...




总结


至此,我们的 Vue 多组件仓库基本上搭建完成,最后做个总结:

  • 通过@vue/cli初始化了项目脚手架;

  • 创建了用于调试和演示的examples文件夹、用于存放各组件源码的packages文件夹;

  • 创建了存放创建/构建的脚本,以及存放脚本的scripts文件夹;

  • 使用lerna进行版本管理。

另外,附下我们目前的开发流程:

  1. 执行npm run pkg-new <package name>创建组件;

  2. packages/<package name>下编写组件;

  3. examples/<packages name>/index.vue中添加测试数据,运行调试;

  4. 执行npm run pkg-build [package name]构建组件;

  5. 执行npm run pkg-publish,选择版本号,发布组件。



最后,狐友前端组在类库/组件维护和 NPM 私服搭建上做了很多有趣的探索,如果大家有兴趣可以留言或与我们联系(spcfe@sohu-inc.com)。




狐友技术团队其他精彩文章

(▼点击文章标题或封面查看)

分布式追踪系统概述及主流开源系统对比

2019-06-27

不了解GIF的加载原理?看我就够了!

2019-02-28

安卓系统权限,你真的了解吗?

2019-03-21



加入搜狐技术作者天团

千元稿费等你来!

戳这里!☛


Modified on

    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存