本文仅仅记载公司屎山代码是怎么一点点被修正成未来可期的代码

什么是aicc-components?

aicc-components 是一个集大成者的库房。服务于aicc的各个项目。

为什么要做渐进式晋级?

  • 时刻跨度长,前史包袱技能债务

  • 功用定位不明晰。组件库?事务组件库?东西库?接口库?store库?

  • 难以快速呼应交互和UI的晋级。

    • 组件封装水平层次补齐,拓展才能差。魔改和定制开发太多。

当时最迫切的问题是?

  • 没有文档,不知道用哪个,不知道怎么用

  • 组件完成水平良莠不齐,许多hardcode,拓展性不高

  • UI和交互的优化,组件无法快速支撑,乃至无法支撑

明晰方针

  • 让 aicc-components 好用

  • 让 aicc-components 好改

怎么做?

  1. 搞文档

  2. 写组件

运用 Storybook 编写和办理文档

什么是Storybook 0x1 基本概念

Storybook 是一个用于UI开发的开源东西,经过隔离组件,使开发更快、更简略。它供给了一套完好的开发流程,你能够不必装备一个杂乱的开发环境、不必和数据库交互,也不需求和你的运用程序相关。

Storybook能够帮你记载组件的文档,以便重复运用,并主动对你的组件进行视觉测验来避免呈现bug。一同Storybook还有一个插件的生态体系来扩展本身的才能,如微调呼应式布局或验证可拜访性。支撑多种前端结构,乃至目前还在进行试验性的支撑服务端渲染组件结构。

选型进程 

布景

现阶段的事务组件库aicc-components是依据Element-ui的二次封装,包括恳求api文件,装备脚本,以及事务组件;该项目是一个独立的项目,其他项目运用时需求经过git下载到项目中,完成组件运用,但存在以下缺陷。

  1. 可保护性不强,项目无法独立发动。
  2. 易用性不高,无包括组件示例的在线文档。

方针:

经过加入在线文档的办法提高组件库的保护性及易用性,协助新人及更多开发成员快速熟悉事务,便利开发。

计划

东西选型

社区中有许多组件文档计划,挑选三个比较抢手的,社区比较活跃的东西做一个比照

  1. Storybook
  2. Docz
  3. Dumi

Storybook

Storybook 是一个用于UI开发的开源东西,经过隔离组件,使开发更快、更简略。它供给了一套完好的开发流程,你能够不必装备一个杂乱的开发环境、不必和数据库交互,也不需求和你的运用程序相关。

Storybook能够帮你记载组件的文档,以便重复运用,并主动对你的组件进行视觉测验来避免呈现bug。一同Storybook还有一个插件的生态体系来扩展本身的才能,如微调呼应式布局或验证可拜访性。支撑多种前端结构,乃至目前还在进行试验性的支撑服务端渲染组件结构。

Dozc

Docz 是一个高效、零装备的作业记载东西。Docz 依据 MDX ,有许多内置的组件能够协助你记载你的作业。它一同支撑增加插件

Dumi

[dumi](https://www.6hu.cc/go/?target=https://d.umijs.org/zh-CN) 是一款为组件开发场景而生的文档东西。其具备开箱即用,将注意力会集在组件开发和文档编写上、依据 TypeScript 类型界说,主动生成组件 API、移动端组件库编写及多言语支撑。

经过调研,整理出如下表格:

结构

start数量(2022-4-19)

支撑编写的组件库类型

文档格局

插件体系

主动化测验

Storybook

70.2k

Vue, React, Angular…

.js,.md,.mdx

支撑

支撑

Docz

22.5k

Vue, React, Angular…

mdx

支撑

不支撑

Dumi

2.2k

React

mdx

支撑

不支撑

  1. Dumi首要扫除,社区活跃度不高。出自阿里,简略成坑。Docz和Storybook来自社区,拥有活跃的社区。
  2. 从社区的活跃视点来看Storybook是首选。一同Storybook除了文档的展现之外,还供给了完好的数据调试和UI主动化测验的才能,开箱即用。
  3. 可按需开发定制的addon,以Storybook为载体,办理和呈现AICC的规划体系。
  4. 一同Storybook支撑多种前端结构,在AICC组验证之后,能够复用到SCRM项目。

综上所述,所以最终挑选Storybook。

和规划团队一同落地规划标准

什么是规划体系、原子规划

规划体系的界说是一系列文档元素、组件和区域、规划和前端攻略的等完好的标准。有了规划体系,公司内各部门能够轻松地在运用程序的多个实例中重复运用款式和组件,快速构建一个或多个产品,然后简化大规模规划。

A design system is a collection of reusable components, guided by clear standards, that can be assembled together to build any number of applications ——Design better

简略来说,规划体系便是一套完好的标准,供给可复用的组件和办法来办理和简化规划和开发。

业界现已有许多成功的事例,比方像Google的Material Design,Spotify 的 Encore System 抖音的Semi Design等等。这些公司凭借他们的规划体系,成功的改动了他们规划和开发产品的办法,经过一组可重用的组件,以及一套辅导这些组件运用的标准,能够快速的完结规划和开发作业。

为什么需求规划体系

规划的标准化带来的是更高的功率和更好的质量:

  • 能够快速、大规模地创立和仿制规划(和开发)作业。

    • 规划体系的首要优点是它们能够利用预制的 UI 组件和元素快速仿制规划。团队能够持续一遍又一遍地运用相同的元素,极大削减重复开发的作业,一同还能确保输出的共同性。
  • 减轻了规划资源的压力,专心于更大、更杂乱的问题。

    • 由于现已创立了更简略的 UI 元素而且能够重用,因此规划资源能够更少地重视调整视觉外观,而更多地重视更杂乱的问题(如信息优先级、作业流优化和数据办理等)。
  • 在跨功用团队内部和之间创立了一种共同的言语。

    • 共同的言语能够削减因交流不畅而糟蹋的规划或开发时刻。例如,由于在规划体系中明晰界说了某组件或许某交互的办法,咱们对此构成默契,削减了剩余的争议。
  • 确保产品的视觉共同性。

    • 规划体系供给了组件、办法和款式的单一来源,并共同了脱节的体验,使它们在视觉上具有凝聚力,而且似乎是同一生态体系的一部分。作为额定的奖赏,任何首要的视觉品牌重塑或从头规划都能够经过规划体系进行大规模办理。
  • 它能够作为规划师和内容贡献者的教育东西和参阅

    • 明晰编写的运用攻略和款式攻略可协助不熟悉 UI 规划或内容创立的个人贡献者入职,并为其他贡献者供给提醒。

什么是原子规划(Atomic Design)

在2013年网页规划师Brad Frost从化学中遭到启发提出了原子规划的概念。

化学反应由化学方程式表明,化学方程式通常显现原子元素怎么结合在一同构成分子。在上面的比方中,咱们看到了氢和氧怎么结合在一同构成水分子。

在世界中,原子元素结合在一同构成分子。这些分子能够进一步结合构成相对杂乱的有机体。反过来世界中的一切物质都能够分化为一组有限的原子元素。

  • 原子是一切物质的基本组成部分。每种化学元素都有不同的特性,它们不能被进一步分化而不会失去意义。

  • 分子是两个或多个原子经过化学键结合在一同的基团。这些原子的组合具有它们自己独特的特性,而且比原子更加有形和可操作。

  • 有机体是作为一个单元一同发挥作用的分子的集合体。这些相对杂乱的结构能够从单细胞生物一直到人类等极端杂乱的生物。

类比Web开发,任何一个网页都是由许许多多相同和不同的HTML元素组成。

同理的,原子规划应运而生。原子规划是一种办法论,由原子、分子、安排、模板和页面共同协作以创造出更有用的用户界面体系的一种规划办法。

就像在化学中相同,原子是咱们体系中最小的组成部分。而不是像氧或氢这样的原子,在规划中咱们有按钮、输入、标签和其他在咱们的规划中运用的小元素。

什么是 Design Token

而今天介绍的Design Token 则归于比原子还小的力度,是规划师能够创立的最小款式值。token映射的是构建和保护规划体系所需的一切值——距离、颜色、排版、目标款式、动画等。用于替代硬编码值,直接集成到咱们的组件库和 UI 东西包,以确保一切产品体验的灵活性和共同性。

Design Token 这个概念最初由Salesforce规划体系团队创立。他们发现,假如在现有规划元素之上树立一个新的数据层并从一个地方办理它们,能够运用一个体系将其共同地扩展到一切渠道。Salesforce 将 Design Token 运用在公司的 Lightning Design System 中,乃至开发了Theo来协助他们更便利地运用Design Token。Salesforce 界说的 Design Tokens 包括:

  • spacing

  • sizing

  • rounded corners

  • font (字体) weights

  • line heights

  • font (字体) families

  • border colors

  • background colors

  • text color

  • shadows

  • animation durations等

从前端开发的视点来看,Design Token 便是一个变量。它是可存储的,可安排的,会集式的,可传播的。

  • 可存储的。在开发人员将Design Token 转化为对应的CSS之前,能够经过JSON或许YAML的文件办法将Token保存在文件中。

  • 可安排的。经过合理的办法办理Token, 界说一些规矩和优秀的办法。比方将 #2b7de8 保存在名为 blue-base 的token中,咱们能够将token与实践组件或许事务加上相关,让token更加语义化。使比方 header-color、primary-color等等。

  • 会集式的。Design Token 是规划体系中的根底一环,能够在标准和流程中会集办理。对一切人来说都能很便利的运用。

  • 可传播的。Design Token 是规划和开发进程中的一部分。在这个进程中,能够用各种格局进行转化,例如CSS变量,Less变量,亦或许Andriod 或许IOS的变量。这样一来,能够被不同的产品、运用程序和技能运用,并确保可保护性和共同性。

自 Design Token 这个概念诞生出来,许多团队都在研讨并实践。不同团队的 Design Token 的规划和分类,以及运用的各类东西都是不尽相同。例如常见的规划东西包括 Photoshop,Sketch,Figma等。Token翻译东西也有许多,Theo,Style Dictionary,Dize,Specify 等。

在2019年七月,W3C社区发起了 Design Tokens Community Group 。方针是供给一套标准,让产品和规划东西能够依照这个标准同享规划体系的规划风格。

在W3C的现阶段的标准中,将 Design Token 界说为一种办法论:

Design tokens are a methodology for expressing design decisions in a platform-agnostic way so that they can be shared across different disciplines, tools, and technologies. They help establish a common vocabulary across organisations.

规划令牌是一种办法论,用于以与渠道无关的办法表达规划决议计划,以便它们能够在不同的学科、东西和技能之间同享。它们有助于树立跨安排的通用词汇。

由于尚处于草案阶段 design-tokens.github.io/community-g… ,就不在此赘述太多文档上的内容,有爱好的朋友可自行阅读。

说了这么多,信任咱们对 Design Token 应该有自己的了解了。现已知道“是什么“,接下来看看”该怎么做“。

创立和交给 Design Token

规划师运用的是 Figma Tokens 插件,它是一款依据 Figma 的插件,相对于 Figma 右侧面板原生自带的款式外,能够完成多层级的 Token 办理,一同插件内容能够与 Figma 规划文件完成实时联动。

运用插件后,规划师能够导出Token的JSON文件,文件内容大致如下:

{
  "sizing": {
    "10": {
      "value": "10",
      "type": "sizing"
    },
    "12": {
      "value": "12",
      "type": "sizing"
    },
    "14": {
      "value": "14",
      "type": "sizing"
    },
    "16": {
      "value": "16",
      "type": "sizing"
    },
    "18": {
      "value": "18",
      "type": "sizing"
    },
    "20": {
      "value": "20",
      "type": "sizing"
    },
    "22": {
      "value": "22",
      "type": "sizing"
    },
    "24": {
      "value": "24",
      "type": "sizing"
    },
    "28": {
      "value": "28",
      "type": "sizing"
    },
    "32": {
      "value": "32",
      "type": "sizing"
    },
    "36": {
      "value": "36",
      "type": "sizing"
    },
    "40": {
      "value": "40",
      "type": "sizing"
    },
    "48": {
      "value": "48",
      "type": "sizing"
    }
  }
}

随后将JSON文件交给给开发人员,开发人员将JSON转化成需求的文件格局,比方Web端能够转化成Scss文件、Less文件或许CSS自界说特点的文件。

运用Style Dictionary 处理Design Token

社区上处理和解析Design Token的东西许多,这有一个可参阅的目录。比较受欢迎的有 Style Dictionary、Theo 等。我挑选 Style Dictionary,由于社区受重视度更高,更新也比较及时,最近正在计划做4.0版别。

Style Dictionary 是一个构建体系。运用它能够让你一次性界说风格,供任何渠道或言语运用。在一个地方能够创立和编辑你的款式,经过一个指令就能够把这些规矩导出到你需求的一切地方,iOS、Android、CSS、JS、HTML、Sketch 文件、款式文档,或许任何你能想得到的地方。它能够经过npm作为CLI运用,也能够像普通的Node.js模块相同运用。

简略事例,快速上手

为了协助开发者快速上手,官方很贴心地供给了一些实例。让咱们来看一下简略的实例,从中了解Style Dictionary 究竟做了什么作业。随意找个目录,履行以下指令。

$ mkdir MyStyleD
$ cd MyStyleD 
$ style-dictionary init basic

这个指令先将库房中的实例文件仿制到本地目录,然后履行 style-dictionary build ,生成构建的产出物。不出意外的话,你会在你的操控台中看到这些输出:

Copying starter files...
Source style dictionary starter files created!
Running `style-dictionary build` for the first time to generate build artifacts.
scss
✔︎ build/scss/_variables.scss
android
✔︎ build/android/font_dimens.xml
✔︎ build/android/colors.xml
compose
✔︎ build/compose/StyleDictionaryColor.kt
✔︎ build/compose/StyleDictionarySize.kt
ios
✔︎ build/ios/StyleDictionaryColor.h
✔︎ build/ios/StyleDictionaryColor.m
✔︎ build/ios/StyleDictionarySize.h
✔︎ build/ios/StyleDictionarySize.m
ios-swift
✔︎ build/ios-swift/StyleDictionary+Class.swift
✔︎ build/ios-swift/StyleDictionary+Enum.swift
✔︎ build/ios-swift/StyleDictionary+Struct.swift

这意味着你现已成功运转了这个实践的比方。回头看看当时你操作的文件目录,它应该长这样:

├── README.md
├── config.json
├── tokens/
│   ├── color/
│       ├── base.json
│       ├── font.json
│   ├── size/
│       ├── font.json
├── build/
│   ├── android/
│      ├── font_dimens.xml
│      ├── colors.xml
│   ├── compose/
│      ├── StyleDictionaryColor.kt
│      ├── StyleDictionarySize.kt
│   ├── scss/
│      ├── _variables.scss
│   ├── ios/
│      ├── StyleDictionaryColor.h
│      ├── StyleDictionaryColor.m
│      ├── StyleDictionarySize.h
│      ├── StyleDictionarySize.m
│   ├── ios-swift/
│      ├── StyleDictionary.swift
│      ├── StyleDictionaryColor.swift
│      ├── StyleDictionarySize.swift

Style Dictionary 由装备驱动,有必要包括一份config.json和装备中引证的design token对应的文件。

  • config.json:style dictionary 依靠的装备。告知 style dictionary去哪里找design tokens,以及怎么转化和格局化输出文件。

    {
    “source”: [“tokens/**/*.json”],
    “platforms”: {
    “scss”: {
    “transformGroup”: “scss”,
    “prefix”: “sd”,
    “buildPath”: “build/scss/”,
    “files”: [{
    “destination”: “_variables.scss”,
    “format”: “scss/variables”
    }],
    “actions”: [“copy_assets”]
    },
    “android”: {
    “transforms”: [
    “attribute/cti”,
    “name/cti/snake”,
    “color/hex”,
    “size/remToSp”,
    “size/remToDp”
    ],
    “buildPath”: “build/android/src/main/res/values/”,
    “files”: [{
    “destination”: “style_dictionary_colors.xml”,
    “format”: “android/colors”
    }]
    }
    }
    }

  • design tokens: 界说了 design token的一系列JSON或许JavaScript Module文件。会在config.json 中的source特点中运用。

    {
    “size”: {
    “font”: {
    “small”: {
    “value”: “10px”
    },
    “medium”: {
    “value”: “16px”
    },
    “large”: {
    “value”: “24px”
    },
    “xl”: {
    “value”: “34px”
    },
    “xxl”: {
    “value”: “46px”
    },
    “base”: {
    “value”: “{size.font.medium.value}”,
    “attributes”: {
    “comment”: “All about that base”
    }
    },
    “heading”: {
    “1”: {
    “value”: “{size.font.xxl.value}”
    },
    “2”: {
    “value”: “{size.font.xl.value}”
    }
    }
    }
    }
    }

Style Dictionary 的架构

为了更好地了解style dictionary的才能和作业原理,让咱们来看看它的架构规划。下面是官方给到的架构图。

一图胜千言,这张图现已很直观地展现其作业原理。

  • 第一步:解析装备文件。

  • 第二步:找到一切 token 文件。config中的 includesource 组合决议了搜索查找的范围。

  • 第三步:将一切的token文件进行一个深度合并。相同的结构将会掩盖。

  • 第四步:遍历config中界说一切platform,履行一下操作:

    • 履行 token转化,提取token中的value。这会深度遍历每一个目标,直到找到value这个key。

    • 解析别号和引证。找到value之后,假如其值是别号或许引证,比方"{size.font.base.value}” ,就用转化后的值替换之。

    • 依据每一个platform装备中的format格局,输出不同的文件。

    • 在输出文件之后,还能够履行自界说的Actions。

当上述四个进程都成功履行并完毕后,你就能得到你想要的输出产品了。

与规划师协作

规划师能够经过 Figma 等规划东西,以文件的办法将 design token 供给给开发人员。现在,身为开发人员的咱们,能够运用 Style Dictionary 对token进行处理,输出咱们想要的文件。

咱们将一切的tokens文件保管到内部的gitlab库房中。规划师界说好token之后,将文件上传到库房,然后告知到对应的工程师。工程师运用依据Node.js开发的脚本,完成token的下载和转化输出。token共同保护在这里

运用 Node API 增强定制才能

style dictionary 供给了比较强壮的 Node API,你能够运用这些API完成一些更杂乱的才能,满意更多的需求场景,这里是API的文档,感爱好的朋友能够仔细阅读。接下来我会结合实践事例,向咱们演示怎么运用Node API。

先从一个简略的比方开始,新建一个JavaScript文件。引进依靠之后,调用extend 传入装备。然后调用回来实例的buildAllPlatforms 办法。

// build.js
const StyleDictionary = require('style-dictionary').extend('config.json');
StyleDictionary.buildAllPlatforms();

extend办法的参数能够是一个文件路径,也能够是个目标。

// build.js
const StyleDictionary = require('style-dictionary').extend({
  source: ['properties/**/*.json'],
  platforms: {
    scss: {
      transformGroup: 'scss',
      buildPath: 'build/',
      files: [{
        destination: 'variables.scss',
        format: 'scss/variables'
      }]
    }
    // ...
  }
});
StyleDictionary.buildAllPlatforms();

你能够多次调用extend和buildAllPlatforms办法,能够用在输出嵌套主题或许多品牌主题等类似的场景。

// build.js
const StyleDictionary = require('style-dictionary');
const brands = [`brand-1`, `brand-2`, `brand-3`];
brands.forEach(brand => {
  StyleDictionary.extend({
    include: [`tokens/default/**/*.json`],
    source: [`tokens/${brand}/**/*.json`],
    // ...
  }).buildAllPlatforms();
});

假设下面是咱们的 token.json。

// token.json
{
  "sizing": {
    "10": {
      "value": "10",
      "type": "sizing"
    },
    "12": {
      "value": "12",
      "type": "sizing"
    },
    "14": {
      "value": "14",
      "type": "sizing"
    },
    "16": {
      "value": "16",
      "type": "sizing"
    },
    "18": {
      "value": "18",
      "type": "sizing"
    },
    "20": {
      "value": "20",
      "type": "sizing"
    },
    "22": {
      "value": "22",
      "type": "sizing"
    },
    "24": {
      "value": "24",
      "type": "sizing"
    },
    "28": {
      "value": "28",
      "type": "sizing"
    },
    "32": {
      "value": "32",
      "type": "sizing"
    },
    "36": {
      "value": "36",
      "type": "sizing"
    },
    "40": {
      "value": "40",
      "type": "sizing"
    },
    "48": {
      "value": "48",
      "type": "sizing"
    }
  }
}

先创立Style Dictionary 需求的 config.json。

// config.json
{
  "source": ["./token.json"],
  "platforms": {
    "scss": {
      "transformGroup": "scss",
      "buildPath": "build/scss/",
      "files": [
        {
        "destination": "sizing.scss",
        "format": "scss/variables"
        }
      ]
    }
  }
}

然后试着履行 node build.js 。不出意外的话,履行完毕之后,会在当时目录中创立 build/sizing.scss

// Do not edit directly
// Generated on Thu, 04 Aug 2022 07:08:59 GMT
$sizing-10: 10;
$sizing-12: 12;
$sizing-14: 14;
$sizing-16: 16;
$sizing-18: 18;
$sizing-20: 20;
$sizing-22: 22;
$sizing-24: 24;
$sizing-28: 28;
$sizing-32: 32;
$sizing-36: 36;
$sizing-40: 40;
$sizing-48: 48;

尽管输出了咱们想要的文件,可是文件的内容如同和预期不符,每一个 Scss 变量应该带上单位。要想定制这个输出成果,咱们需求运用自界说的Transform。

Transform 和 TransformGroup

Style Dictionary 中的Transform是用来修正token的函数。运用 Transform能够对token的name、value 或许 attribute 进行转化,然后完成适配输出不同渠道的才能。比方,将pixel转化成point。能够运用内置的Transforms,也能够运用registerTransform办法注册自界说Transform。

StyleDictionary.registerTransform({
  name: 'time/seconds',
  type: 'value',
  matcher: function(token) {
    return token.attributes.category === 'time';
  },
  transformer: function(token) {
    // Note the use of prop.original.value,
    // before any transforms are performed, the build system
    // clones the original token to the 'original' attribute.
    return (parseInt(token.original.value) / 1000).toString() + 's';
  }
});

TransformGroup便是一组Transform。有开箱即用的内置的TransformGroup,也能够经过registerTransformGroup办法注册自界说TransformGroup。

StyleDictionary.registerTransformGroup({
  name: 'Swift',
  transforms: [
    'attribute/cti',
    'size/pt',
    'name/cti'
  ]
});

为了给sizing加上单位pixel,咱们先注册一个自界说 Transform 和 TransformGroup。

StyleDictionary.registerTransform({
  name: 'size/px', //界说transform的名称,作为引证标记
  type: 'value', // transform 转化的目标,咱们需求转化token对应的值
  matcher: token => { // token 匹配函数,回来boolean。
    return token.type === 'sizing' && token.value !== 0
  },
  transformer: token => { // transform函数,承受token,回来想要的内容
    return `${token.value}px`
  }
})
StyleDictionary.registerTransformGroup({
  name: 'custom/scss', 
  transforms: StyleDictionary.transformGroup['scss'].concat([
    'size/px',
    'size/percent'
  ])
})

接着在config.json中运用叫做custom/scss 的 TransformGroup。

{
  "source": ["./token.json"],
  "platforms": {
    "scss": {
      "transformGroup": "custom/scss",
      "buildPath": "build/scss/",
      "files": [
        {
        "destination": "sizing.scss",
        "format": "scss/variables"
        }
      ]
    }
  }
}

最终查看输出内容。

// Do not edit directly
// Generated on Thu, 04 Aug 2022 07:08:59 GMT
$sizing-10: 10px;
$sizing-12: 12px;
$sizing-14: 14px;
$sizing-16: 16px;
$sizing-18: 18px;
$sizing-20: 20px;
$sizing-22: 22px;
$sizing-24: 24px;
$sizing-28: 28px;
$sizing-32: 32px;
$sizing-36: 36px;
$sizing-40: 40px;
$sizing-48: 48px;

掌握东西的运用,配合主动化脚本,能够让token2css的进程变得十分丝滑流畅。

// 同步最新token,生成文件
yarn parse-token

后续动作

现阶段的几件作业

  • 产出linter标准和对应的linter插件,aicc项目复用

  • 组件标准,包括规划、开发、发布等,并履行

  • 按优先级,该优化的优化,该开发的开发。

  • 将研发流程工程化,主动化。

依据PNPM的项目改造

布景

由于前史原因,前端项意图代码依照事务模块,分散在不同的代码库房中。能够复用的事务代码以npm package的办法同享。每个事务模块都是依据Nuxt.js的SSG,布置办法是将各模块构建产出物上传至服务器上,运用Nginx署理接口和保管HTML等静态资源。

看起来如同是一个很惯例的操作。但实践上在各方面都存在一些让人十分不痛快的点,且不说:

  1. npm包的日常开发调试流程繁琐。

  2. 同享代码的更新迫使依靠的事务模块有必要更新。

  3. 各项目冗余了构建相关的装备。

当这套流程在技能水平普遍不高的团队中运用时,还会有更多应战:

  1. 代码安排没有可参阅的标准。什么样的代码有必要提高至同享,什么样的代码归于强事务?

  2. 前史原因和开发人员技能才能问题导致的代码腐败。

  3. 事务模块运用的Webpack、Vue等根底依靠及其生态版别,没有强的共同性束缚,导致版别良莠不齐。

  4. 引进Nuxt.js的SSG想做微前端。效果没达成,反而增加了项目保护的难度。

  5. 项目间构成了孤岛,无法大局观,限制了开发人员的想象力,必定程度上促进了项目代码的腐败。

其他更多应战就不一一列举了,总而言之便是很“痛”。

当遇到本地化项意图时候,苦楚更上一层楼。我需求一同保护一切项意图代码版别。在众多项目和它们的分支中来回切换,逐渐迷失了自己。

在针对这些场景和痛点进行了一番技能调研之后,我决议测验依据PNPM对现有项目进行Monorepo的改造。

Monorepo VS Polyrepo

Nrwl 团队创立了monorepo.tools/向咱们解说Monorepo相关的概念和东西。

Monorepo是一个包括了多个独立项目,且项目间有明晰的相关联系的库房。能够看到两个重点:多个独立项目,项目间有明晰联系。假如仅仅将项目放在同一个库房,彼此之间没有明晰联系,那这个不能称之为Monorepo架构;假如库房只包括多个项目,没有拆分出来的封装和复用的代码,那仅仅一个大库,只能算是MonoLith架构。

与Monorepo相反的计划能够称之为“Polyrepo”,也是当下标准的开发办法:每个库房都应一个模块、运用或许项目。彼此经过其他的库房来同享可复用的代码,每个项目都有自己的构建流程和布置流程。这也是当时团队的开发办法。

Polyrepo办法存在代码同享难,重复代码多,依靠更新烦,装备晋级乱等问题。Monorepo办法下只会有一个项目库房,在文件夹之间同享代码十分简略;项目能够坚持同一个版别,削减晋级的心智负担;复用同一套装备,晋级和保护都很便利。

文章中提到了 Monorepo的东西比照,相比照较全面,感爱好的朋友能够看看。我挑选的是PNPM的计划,由于这是当下改造本钱最低的计划。未来或许会再研讨 TurboRepo 和 Rush。

依据 PNPM 的 Monorepo

PNPM项意图初衷是节省磁盘空间并提高装置速度。PNPM将一切文件都存储在同一个方位,当软件包被被装置时,包里的文件会硬链接到这一方位,而不会占用额定的磁盘空间,能够跨项目地同享同一版别的依靠。

PNPM 内置的 Workspace 才能供给了对 Monorepo 的支撑。Workspace 的创立十分简略,包括[pnpm-workspace.yaml](<https://pnpm.io/zh/pnpm-workspace_yaml>) 文件的目录便是一个PNPM的Workspace。它界说了Workspace的根目录,能够从workspace中包括或许扫除你挑选的目录。

packages:
  # all packages in direct subdirs of packages/
  - 'packages/*'
  # all packages in subdirs of components/
  - 'components/**'
  # exclude packages that are inside test directories
  - '!**/test/**'

保存前史记载,渐进搬迁

团队的各个项目都在进行正常的开发迭代,技能改造不能对需求上线造成影响,需求有一个计划能够让技能改造和事务迭代并行。

当下的难点在于:

  1. Monorepo的改造,必定是要有一个新库房的,原有项目库房中的代码和新库房之间的代码怎么时刻坚持同步

  2. 项意图依靠包版别没有对齐,合并的进程必定有共同版别的进程,怎么共同项意图依靠。

  3. 怎么打造一套全新的构建流程。

针对难点1我想到的计划是:Git Submodule。Git Submodule 允许你将一个 Git 库房作为另一个 Git 库房的子目录。 它能让你将另一个库房克隆到自己的项目中,一同还坚持提交的独立。完美处理了代码同步的问题。

在改造阶段,对每个项意图修正首要包括:

  1. 依靠晋级之后的或许存在的兼容性改动

  2. 编译打包相关的装备改动

这些作业能够独自拉去分支进行改造,经过验证之后合并回主干分支。当一切项目改造完结后,全体的晋级也算是顺利完结了。

依靠晋级

抱负很夸姣,可是现实很残暴。当 PNPM 和 Git Submodule 装备之后,第一步的依靠晋级就挡住前进的道路。各项目依靠的Vue和Webpack版别形形色色。Vue的版别有2.6.x和2.7.x,Webpack的版别有3.x、4.x和5.x。

Webpack

装置完依靠之后,第一次发动便遇到了正告

显然,需求将Webpack生态相关的依靠晋级到共同的版别。能够运用pnpm why -r快速查看指定包的依靠联系,一切项意图依靠扫了一遍,一个个晋级实在过于繁琐。pnpm update依据指定的范围更新软件包的最新版别,装备项--recursive同允许咱们一同在一切子目录中运用运转更新,就像刚才运用pnpm why时相同。

pnpm --recursive update
# 更新子目录深度为 100 以内的一切包
pnpm --recursive update --depth 100
# 将每个包中的 typescript 更新为最新版别
pnpm --recursive update typescript@latest

在指定更新某些包时,必定要记住带上版别,只要包名是不会更新的。

pnpm update --recursive webpack@^4 css-loader@^3 sass-loader@^10 webpack-merge@^5

Vue 和 Nuxt

更新了Webpack版别后,又遇到了预料之中的Vue packages version mismatch 。查看发现,实践项目中运用的Vue版别都是v2.6.12,v2.7.10的版别来自一些第三方库的依靠。考虑到晋级Vue或许带来的影响,决议先保存Vue的版别为v2.6.12。

再次履行 nuxt dev时,得到了新的过错。

✖ Nuxt Fatal Error
Error:                                                                                   
Vue packages version mismatch:                                                           
- vue@2.6.12                                                                             
- vue-server-renderer@2.7.10                                                             
This may cause things to work incorrectly. Make sure to use the same version for both.

运用 pnpm why 查看 vue-server-renderer ,会发现最终的顶级依靠是 nuxt,我试图将其版别锁定在项目运用的v2.11.0,可是依旧是相同的过错。将原先的 yarn.lock文件和 pnpm-lock.yml进行比对后发现,后者的版别更新到了v2.7.10。从 node_module s中一层层从 nuxt 到 vue-server-renderer 的引证联系如下

nuxt@2.11.0
        -> @nuxt/core@2.11.0 
                -> @nuxt/vue-renderer@2.11.0 
                        ->vue-server-renderer@^2.6.11

由于^的联系,vue-server-renderer 会装置到最新的v2.7.10。

为什么老项目本身能够运转? 项目中的 yarn.lock 文件创立的时刻很早,锁定的vue-server-renderer是v2.6.12,与vue版别共同,一切能够运转。

看起来需求将Vue的版别锁定为v2.7.10。社区中有不少关于将Vue晋级到2.7的踩坑文章,接下来结合项目当时对Vue的运用情况,梳理出一个可行的改造计划。

pnpm update -r vue@^2.7.14 nuxt@^2.15.8

Babel

相同的,运用 update -r 批量更新vue@2.7.10、nuxt@2.15.8,游戏进入到下一关。

/.nuxt/client.js: Unknown option: base.configFile. Check out <http://babeljs.io/docs/usage/options/> for more information about options.
A common cause of this error is the presence of a configuration options object without the corresponding preset name. Example:
Invalid:
  `{ presets: [{option: value}] }`
Valid:
  `{ presets: [['presetName', {option: value}]] }`
For more detailed information on preset configuration, please see <http://babeljs.io/docs/plugins/#pluginpresets-options>.

这是 babel-core 和将 babel 晋级到 v7.x,bable-loader 晋级到 v8.x 就能处理这个问题。我直接将它们晋级到latest版别。

 pnpm update --recursive babel-loader@^8 @babel/core@^7 @babel/preset-env@latest

由于项目中用到了jsx,对应的还需求晋级相关的插件,详细细节能够查看这里。

pnpm add -D -r @vue/babel-preset-jsx @vue/babel-helper-vue-jsx-merge-props

现在 nuxt dev能够顺利运转,可是页面上呈现了下图中的过错。

考虑到本次晋级将nuxt从v2.11.0晋级到了v2.15.8,猜测或许是nuxt导致的问题。

Nuxt

经过打断点发现,在加载Layout目录中的组件时,呈现了一个 undefined。查看发现是Layout目录中包括了一个microEvent.js,文件内容是导出了几个东西函数,没有默许导出目标(export default)。

/*
* 界说一个作业体系,用于让微运用和主运用通讯
*/
const microEvents = {}
export const addMicroAppEventListener = (appName, ev, cb) => {
  if (!microEvents[appName]) {
    microEvents[appName] = {}
  }
  if (!microEvents[appName][ev]) {
    microEvents[appName][ev] = []
  }
  microEvents[appName][ev].push(cb)
}
export const removeMicroAppEventListener = (appName, ev, cb) => {
  if (microEvents[appName] && microEvents[appName][ev]) {
    microEvents[appName][ev] = microEvents[appName][ev].filter(it => it !== cb)
  }
}
export const dispatchMicroAppEvent = appName => (ev, ...args) => {
  if (microEvents[appName] && microEvents[appName][ev]) {
    microEvents[appName][ev].forEach(fn => fn.apply(null, args))
  }
}

比照了 v2.10.1 和 v2.15.8 的产品后发现,两个版别中,microEvent.js都被解析为 undefined,在2.10.1版别下,nuxt在解析了layouts目录中的文件后,直接构建了一个layouts目标。

而在2.15.8版别中,则调用了sanitizeComponent 办法。

sanitizeComponent(Component) 中没有对入参为 undefined 时做兼容处理,导致呈现报错。

export function sanitizeComponent (Component) {
  // If Component already sanitized
  if (Component.options && Component._Ctor === Component) {
    return Component
  }
  if (!Component.options) {
    Component = Vue.extend(Component) // fix issue #6
    Component._Ctor = Component
  } else {
    Component._Ctor = Component
    Component.extendOptions = Component.options
  }
  // If no component name defined, set file path as name, (also fixes #5703)
  if (!Component.options.name && Component.options.__file) {
    Component.options.name = Component.options.__file
  }
  return Component
}

在nuxtjs的releases记载中找到了对应改动的版别在v2.13.0,这里是提交记载。

行动计划

pnpm的改造本钱不算太高,首要的本钱体现在项目依靠的晋级这件作业上。不过好在版别的跨度没有太大,整个进程较为平滑。可是这么多项目,假如一次性批量晋级依靠,危险仍是太大了。接下来要做的作业是,在每个项意图迭代中逐渐晋级依靠,削减晋级带来的危险。与此一同进行的是,将项意图构建布置和东西包的发布等工程化相关的流程梳理清楚,收敛为一套标准的流程。一切的作业串起来后,完好的monorepo计划就能顺利落地。

如上所述:那么aicc的代码都被整合在一个代码库中,只不过分为不同的子库去处理,能够独自发动子库跑代码

而且多个子库的代码标准,代码查看都能够共同办理起来

微运用:突破技能封闭,不局限于结构

原来老旧的代码都是vue项目,新进入的前端同学只能被迫运用vue去开发,可是运用微运用后,咱们能够以页面为单位,嵌入不同的子运用进去,子运用的技能能够随意选型,能够大大活跃前端代码结构的呆板,提高咱们学习新技能的积极性,详细嵌入办法能够参阅qiankun

总结:

原先的代码库是项目内部运用aicc-components这个npm包,这个包内事务组件+根底组件+一大堆东西,界说完全不明晰,而且后来者也不清楚哪些组件被开发过,导致重复使命大大提高;

然后,从组件化思维出发,选型storybook去改动此包,写组件运用文档,区分隔根底组件和事务组件,然后在详细网址上去装备文档网页,便于后续同学开发项目。

组件化时,借鉴的是Arco Design,在查看组件款式时,发现都是**款式变量,然后想到了css原子化,运用到了Style Dictionary,**而且于UI同学一同改造,绘制页面时,UI同学也会运用款式变量给予提示。

在aicc的各个体系中,如外呼体系,呼入体系,都是独自的项目,各个项意图代码查看标准,ts,eslint等都不是共同的,所以采用monorepo去整合项目,然后运用qinkun嵌入;而且pnpm正好新出后,运用pnpm来优化node_modules包,在子库引进其它公共东西库时,正好pnpm的workspace功用能够满意;

在微运用嵌入时,正好能够突破结构的封闭,在同一项目不同页面运用不同结构,能够提高开发同学爱好和满意大体同学都想运用react的心思。

全体架构下来,原来屎山项目,一点点的改成了未来可期,渐渐努力,整个公司代码会越来越好!