Think in Component

Bit是组件驱动架构,基于组件的现代应用开发。在Bit的世界里,一切皆组件。

组件可以组合成其他组件,最终组成一个应用APP,即APP也是组件的一种。

这为我们开发提供一个新的思路:我们构建可以整合成不同应用的组件,而不是构建包含组件的应用。

Bit帮我们构建模块化、稳固的、可测试、可复用的代码。

Bit Cloud是组件的云托管服务。它为开发人员和团队提供端到端的解决方案,用于托管、组织、检索、使用、更新和协作处理组件。

Bit优势

  • 以组件架构的思想帮助我们构建模块化、稳固的、可测试、可复用的代码。
  • 从现有代码结构中分离组件,无需更改结构,或维护新的项目。
  • 可更改依赖组件,并创建自己的版本独立管理,无需担忧污染其它环境。

初始化Bit工作区

安装BVM & Bit

BVM是Bit版本管理工具,雷同NVM

// node版本12.22.0以上
npm i -g @teambit/bvm

执行bvm -h检验是否安装成功,若提醒bvm命令不可用,需要设置环境变量:

# MacOs Bash
echo 'export PATH=$HOME/bin:$PATH' >> ~/.bashrc && source ~/.bashrc

# zsh
echo 'export PATH=$HOME/bin:$PATH' >> ~/.zshrc && source ~/.zshrc

# windows
setx path "%path%;%LocalAppData%\.bvm"

安装最新版bit:

bvm install

执行bit -h检验是否安装成功,若提醒bit命令不可用,需要按上述流程设置一下环境变量。

bit new命令初始化工作区

$ bit new  <env> <project>
$ cd <project>
$ bit install

bit init命令初始化工作区

  1. 先初始化环境
$ cd <project>
$ bit init --harmony
  1. 手动配置开发环境

以react环境为例,修改workspace.jsonc文件:

"teambit.workspace/variants": {
  "*": {
    "teambit.react/react": { }
  }
}
  1. 安装必要的peer依赖
$ bit install react --type peer
$ bit install react-dom --type peer

初始化Git

需要将workspace.jsonc和.bitmap 上传到Git。

创建组件

使用内置组件创建

以react为例:

  1. 以内置模版创建组件bit create <built-in-template> <component>
$ bit templates # 查看所有的内置模版
$ bit create react-component ui/button     # TypeScript
$ bit create react-component-js ui/button  # JavaScript

注意:其中,<component>可以是个路径,前置路径为命名空间,上述示例等同于bit create react-component button --namespace ui。

  1. 添加测试用例
$ bit install @testing-library/react
  1. 编译并起服务
$ bit compile
$ bit start

自定义组件

  1. 已有组件结构与代码
  2. 通过bit add <relative-path> --namespace <namespace>添加组件

查看组件信息

$ bit show <component-id>

输出信息示例:

  ┌───────────────┬────────────────────────────────────────────────────────────────────┐
  │ id            │ my-scope/ui/button                                                 │
  ├───────────────┼────────────────────────────────────────────────────────────────────┤
  │ scope         │ my-scope                                                           │
  ├───────────────┼────────────────────────────────────────────────────────────────────┤
  │ name          │ ui/button                                                          │
  ├───────────────┼────────────────────────────────────────────────────────────────────┤
  │ env           │ teambit.react/react                                                │
  ├───────────────┼────────────────────────────────────────────────────────────────────┤
  │ package name  │ @my-scope/ui.button                                                │
  ├───────────────┼────────────────────────────────────────────────────────────────────┤
  │ main file     │ index.ts                                                           │
  ├───────────────┼────────────────────────────────────────────────────────────────────┤
  │ files         │ button.composition.tsx                                             │
  │               │ button.docs.mdx                                                    │
  │               │ button.tsx                                                         │
  │               │ button.spec.tsx                                                    │
  │               │ index.ts                                                           │
  ├───────────────┼────────────────────────────────────────────────────────────────────┤
  │ dev files     │ button.docs.mdx (teambit.docs/docs)                                │
  │               │ button.spec.tsx (teambit.defender/tester)                          │
  │               │ button.composition.tsx (teambit.compositions/compositions)         │
  ├───────────────┼────────────────────────────────────────────────────────────────────┤
  │ extensions    │ teambit.react/react                                                │
  │               │ teambit.component/dev-files                                        │
  │               │ teambit.compositions/compositions                                  │
  │               │ teambit.pkg/pkg                                                    │
  │               │ teambit.docs/docs                                                  │
  │               │ teambit.envs/envs                                                  │
  │               │ teambit.dependencies/dependency-resolver                           │
  ├───────────────┼────────────────────────────────────────────────────────────────────┤
  │ dependencies  │ [email protected] (package)                                           │
  ├───────────────┼────────────────────────────────────────────────────────────────────┤
  │ dev           │ @testing-library/[email protected] (package)                           │
  │ dependencies  │ @babel/[email protected] (package)                           │
  │               │ @types/[email protected] (package)                           │
  │               │ @types/[email protected] (package)                           │
  │               │ @types/[email protected] (package)                           │
  │               │ @types/[email protected] (package)                           │
  ├───────────────┼────────────────────────────────────────────────────────────────────┤
  │ peer          │ [email protected] (package)                                       │
  │ dependencies  │ [email protected] (package)                                       │
  └───────────────┴────────────────────────────────────────────────────────────────────┘

查看组件状态

$ bit status

查看组件所有版本

$ bit log <component-id>

查看本地所有组件列表

$ bit list

启动测试服务器

$ bit compile
$ bit start

使用组件

要将组件作为依赖项导入,必须使用模块链接。

Bit 为工作区中的每个组件创建一个模块,这些模块链接在 node_modules 目录中,并包含它的构建输出和自动生成的 package.json。

要为组件重新生成模块链接,请运行该bit link命令。

将组件安装为NPM包

install命令安装组件,以NPM包的形式使用。

作为Vendor组件

通过import命令安装组件,示例如下:

$ bit import <component-id>

更新import的组件到最新版本

$ bit import

将Vendor组件转为NPM包依赖

$ bit eject <component-id>

Scope

Remote Scope

特色

在远程服务器上设置Scope以共享组件,如Bit.dev或自托管 Bit 服务器。

将组件存储在Remote Scope上,可以使它们在其他项目中重复使用。

  • 使用import命令从Remote Scope获取组件。
  • 使用export命令将组件推送到Remote Scope。

注意:Remote Scope会缓存组件依赖,例如其他Scope的组件。这样做的好处是,即使依赖组件不可用,还能确保当前组件可执行。

使用

在Bit Server创建Remote Scope后,需要更改workspace.jsonc文件:

{
  "teambit.workspace/workspace": {
    "defaultScope": "<bit-username>.<remote-scope-name>"
  }
}

workspace.jsonc文件中的任何更改都需要重新启动本地开发服务器。

$ bit start

Workspace Scope

特色

开发人员的工作区都在本地 Scope 中保存了组件及其历史记录的工作副本。这允许我们浏览历史记录、比较版本和检查组件的过去修订。

Workspace Scope也可能包含来自各异Remote Scope的组件。

共享组件

  1. 为已修改的组件更新版本号
$ bit tag --all --message "first version"
  1. 共享组件
$ bit export

注意:当共享上传流程结束,.bitmap文件将更新以反映该新状态。

安装组件

注册Scope源

$ npm config set '@YourUserName:registry' https://node.bit.dev

安装依赖

$ npm install @orgName/componentScopeName.componentID

Bit Component vs. NPM包

  • 生成NPM包只是Bit Component构建流程的部分,Bit称之为版本工件。

Configuration

teambit.workspace/variants提供一个统一的方式,可以为每个组件设置不同的配置项,而无需修改每个组件文件下的 package.json 。

{
  "teambit.workspace/variants": {
    "design/theme": {
      "defaultScope": "acme.theme",
    },
    "cart": {
      "defaultScope": "acme.cart",
      "teambit.react/react": {}
    }
  }
}

查看配置

  • bit env - 打印一个简单的表格,其中包含工作区中的所有组件及其环境
  • bit show <component> - 打印组件的所有信息,包括环境
  • bit start- 通过浏览器可视化浏览组件树以查看组件的环境

移除组件

移除本地组建

$ bit remove <component-id>

产生的影响:

  • 一个未追踪的组件依赖 删除组件 —— 没有影响

    • 因为Bit还没有隔离未追踪的组件,不会检测其依赖
  • 一个已追踪的组件依赖 删除组件 —— 会警告,使用--force强制删除
  • 引入的远程组件依赖 删除组件 —— 没有影响

    • 因为远程组件是已经隔离且不可更改的
    • 本地引入远程组件且更改会创建另一个版本

移除远程组件

$ bit remove <username.your-scope/ui/button> --remote

以一个例子描述产生的影响:

  • button组件在远程uiScope中
  • card组件依赖button组件,也在uiScope中
  • login组件依赖button组件,在adminScope中

删除button组件后的影响:

  • 因为card组件与button组件在同一个Scope中,因此删除button组件会有个警告。

    • 可追加---force强制删除
    • 删除后,card组件缺少依赖,为保证其正常工作需要重构
  • login组件没有影响

    • Bit会在Scope中维护依赖
  • 其他项目依赖login组件时,安装会报错

    • 溯源button组件,缺失

编译组件

而Bit 的编译器是一个环境服务。

编译器的选择(Babel、TypeScript 等)及其配置由其服务的各种环境决定。

编译器永远不会直接运行,而只能通过 Compiler 服务运行。

单个工作区可能会针对不同的组件运行不同的编译器,每个编译器都根据自己的环境。

$ bit compile <component-id> # 编译特定组件
$ bit compile # 编译工作区全部组件

组件依赖关系图

Bit 的一个关键特性是能够根据组件的源代码自动创建依赖关系图。

Javascript 可以使用 require 或 import 声明依赖两种类型的依赖项:

  • 作为 node_modules 安装的软件包
  • 项目内部的文件和目录,或在装饰器中引用(例如在 Angular 中)

node_modules依赖

Bit解析包(即node_modules)的流程:

  • 可以通过bit show <component-id>来检查 Bit 为每个包解析的依赖项(Packages):
$ bit show hello/world
┌───────────────────┬─────────────────────────────────────────────────────────────────────┐
│        ID         │                            hello/world                              │
├───────────────────┼─────────────────────────────────────────────────────────────────────┤
│     Language      │                             javascript                              │
├───────────────────┼─────────────────────────────────────────────────────────────────────┤
│     Main File     │                      src/hello-world/index.js                       │
├───────────────────┼─────────────────────────────────────────────────────────────────────┤
│     Packages      │                           left-pad@^2.1.0                           │
├───────────────────┼─────────────────────────────────────────────────────────────────────┤
│       Files       │       src/hello-world/hello-world.js, src/hello-world/index.js      │
└───────────────────┴─────────────────────────────────────────────────────────────────────┘

如果 Bit 无法解析所有包的依赖项,它会提示missing package dependencies。我们需要验证 package.json 中是否确实存在所有包。

文件依赖

注意:Bit 使用静态代码分析,因此仅支持静态导入import,不支持require。

Bit解析文件依赖的流程

当 Bit 遇到需要跟踪的文件时,它会尝试检查该文件是否已经在另一个组件中进行了跟踪,在这种情况下,Bit 将使另一个组件成为该组件的依赖项。

如果文件未被跟踪,Bit 将untracked file dependencies在检查组件状态时发出警告。

隔离问题

要解决隔离问题,您可以:

  • 将未跟踪的文件依赖项添加到现有组件
  • 将文件作为新组件进行跟踪

采取以上何种方法基于文件的上下文。如果该文件被多个其他组件使用,则将其放入一个单独的组件中是有意义的。

但是,如果此文件仅仅是被跟踪文件的内部文件,则可以将其添加为组件的文件。

文件添加到现有组件

运行bit add指向要添加文件的组件的 Id:

// 示例
$ bit add src/utils/noop.js --id hello/world

运行bit status ,检查是否成功:

$ bit status
new components
    > component/hello-world... ok

文件作为新组件进行跟踪

可以bit add添加新组件

// 示例
$ bit add src/utils/noop.js --namespace utils

执行结果是一个新组件。

私有化部署v15

硬件条件

  • Linux/Mac系统
  • 内存4G+

前置条件

  • Docker
  • Git
# 卸载旧版docker
$ yum remove docker  docker-common docker-selinux docker-engine
# 安装docker依赖
$ yum install -y yum-utils device-mapper-persistent-data lvm2
# 设置docker源
$ yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
$ yum install docker-ce
# 启动docker
$ systemctl start docker
# 加入开机启动
$ systemctl enable docker
# 安装Git
$ yum install git

部署流程

$ git clone https://github.com/teambit/bit.git
$ cd bit/scripts/docker-teambit-bit
$ docker build -f ./Dockerfile-bit -t bitcli/bit:latest .
$ docker build -f ./Dockerfile-bit-server -t bitcli/bit-server:latest .
$ docker run -dit bitcli/bit:latest /bin/bash # 运行
$ docker run -dit -p <port>:3000 bitcli/bit-server:latest
  • Dockerfile-bit:

    • 安装 bvm 然后使用 bvm 安装 bit 的 docker 文件。
    • 这个 docker 通常对在 CI 机器上运行像 tag 和 export 这样的Bit命令很有用
  • Dockerfile-bit-server:

    • 一个基于Dockerfile-bit(使用 from)的docker 文件
    • 该docker文件创建一个空白Scope,并在其上通过bit start初始化Bit服务器
  • Dockerfile-symphony:

    • 仅供内部使用

相关问题

Mac电脑ssh链接: Permission denied

$ sudo ssh root@<ip>

ssh链接时报警告WARNING: REMOTE HOST IDENTIFICATION HAS CHANGE

$ sudo ssh-keygen -R <ip>

bvm install安装不了Bit

临时更改terminal代理

$ export http_proxy=http://127.0.0.1:1087
$ export https_proxy=$http_proxy

注意:

  • 需要有VPN
  • 保证浏览器可以访问外网
  • 开启VPN,即使全局,终端也是无法被代理

永久修改代理

# 修改~/.bashrc设置永久管理脚本
function proxy_on() {
    export http_proxy=http://127.0.0.1:1087
    export https_proxy=$http_proxy
    echo -e "终端代理已开启。"
}

function proxy_off(){
    unset http_proxy https_proxy
    echo -e "终端代理已关闭。"
}

注意:修改后,通过source ~/.bashrc立即生效。

通过proxy_on启动代理,proxy_off关闭代理。

发布

注册远程Scope

# 客户端
$ cd <my-project>
$ bit init
$ bit remote add http://<host>:<port>

workspace.jsonc

配置teambit.workspace/workspace

  "teambit.workspace/workspace": {
    /**
     * the name of the component workspace. used for development purposes.
     **/
    "name": "my-workspace-name",
    /**
     * set the icon to be shown on the Bit server.
     **/
    "icon": "https://static.bit.dev/bit-logo.svg",
    /**
     * default directory to place a component during `bit import` and `bit create`.
     * the following placeholders are available:
     * name - component name includes namespace, e.g. 'ui/button'.
     * scopeId - full scope-id includes the owner, e.g. 'teambit.compilation'.
     * scope - scope name only, e.g. 'compilation'.
     * owner - owner name in bit.dev, e.g. 'teambit'.
     **/
    "defaultDirectory": "{scope}/{name}",
    /**
     * default scope for all components in workspace.
     **/
    "defaultScope": "remote-scope"
  },

打Tag

监听文件变化,才能标识。

若文件无变化,无法进行标识。

$ bit tag --all  --message "first version"

举例:

组件B依赖组件A,二者初始版本皆为0.0.1。

若组件A由0.0.1进行更改,通过bit tag --all会A0.0.1 —> 0.0.2,B0.0.1 —> 0.0.2。

若继续更改组件B,通过bit tag --all,B0.0.2 —> 0.0.3,A的版本不变。

Bit部署

部署的前提是有新的标识,否则,无法部署。

$ bit export

扩展Bit

我们通过创建Aspect和接入Bit的API来扩展Bit。

扩展Workspace UI

以新增Tab为例:

初始化Bit环境

$ bit init

会自动新建.bit/、.bitmap、workspace.jsonc文件(夹)。

修改DefaultScope

{
  ...
  "teambit.workspace/workspace": {
    /**
     * the name of the component workspace. used for development purposes.
     **/
    "name": "my-workspace-name",
    /**
     * set the icon to be shown on the Bit server.
     **/
    "icon": "https://static.bit.dev/bit-logo.svg",
    /**
     * default directory to place a component during `bit import` and `bit create`.
     * the following placeholders are available:
     * name - component name includes namespace, e.g. 'ui/button'.
     * scopeId - full scope-id includes the owner, e.g. 'teambit.compilation'.
     * scope - scope name only, e.g. 'compilation'.
     * owner - owner name in bit.dev, e.g. 'teambit'.
     **/
    "defaultDirectory": "{scope}/{name}",
    /**
     * default scope for all components in workspace.
     **/
    "defaultScope": "me"
  },
  ...
}

新建Aspect

$ bit create aspect aspects/hello-world

生成目录结构:

.
└──me
  └── aspects
    └── hello-world
      ├── hello-world.aspect.ts
      ├── hello-world.main.runtime.ts
      └── index.ts

其中,hello-world.main.runtime.ts代码如下:

// hello-world.main.runtime.ts
import { MainRuntime } from '@teambit/cli';
import { HelloWorldAspect } from './hello-world.aspect';

export class HelloWorldMain {

  static slots = [];
  static dependencies = [];
  static runtime = MainRuntime;
  static async provider() {
    return new HelloWorldMain();
  }
}

HelloWorldAspect.addRuntime(HelloWorldMain);

注意:hello-world.main.runtime是负责扩展workspace CLI和 workspace Server的。

为了在组件详情页创建一个新的菜单,我们需要参考hello-world.main.runtime.ts文件新建hello-world.ui.runtime.tsx文件:

// hello-world.ui.runtime.tsx
import React, { useContext } from 'react';
import { UIRuntime } from '@teambit/ui';
import { ComponentUI, ComponentAspect } from '@teambit/component';
import { HelloWorldAspect } from './hello-world.aspect';

export class HelloWorldUI extends React.Component<any> {
  static slots = [];
  static dependencies = [ComponentAspect];
  static runtime = UIRuntime;
  static async provider([component]: [ComponentUI]) {
    return new HelloWorldUI();
  }
}

HelloWorldAspect.addRuntime(HelloWorldUI);

注意:这里引入了ComponentAspect,它是Bit核心Aspect,负责组建页面所有的组件和操作。将ComponentAspect作为依赖,我们能在provider中获取到它并使用它提供的API。

// 更新hello-world.ui.runtime.tsx
// 注册registerNavigation导航
import React, { useContext } from 'react';
import { UIRuntime } from '@teambit/ui';
import { ComponentUI, ComponentAspect } from '@teambit/component';
import { HelloWorldAspect } from './hello-world.aspect';

export class HelloWorldUI extends React.Component<any> {
  static slots = [];
  static dependencies = [ComponentAspect];
  static runtime = UIRuntime;
  static async provider([component]: [ComponentUI]) {
     component.registerNavigation({
       href: '~hello',
       children: 'Hello'
     });
    return new HelloWorldUI();
  }
}

HelloWorldAspect.addRuntime(HelloWorldUI);

这里,我们通过ComponentAspect依赖提供的registerNavigation注册了导航,手动切换导航会渲染Hello。

// 更新hello-world.ui.runtime.tsx
// 注册registerRoute路由
import React, { useContext } from 'react';
import { UIRuntime } from '@teambit/ui';
import { ComponentUI, ComponentAspect } from '@teambit/component';
import { HelloWorldAspect } from './hello-world.aspect';

export class HelloWorldUI extends React.Component<any> {
  static slots = [];
  static dependencies = [ComponentAspect];
  static runtime = UIRuntime;
  static async provider([component]: [ComponentUI]) {
     component.registerRoute({
       children: () => <div>hello world</div>,
       path: '~hello'
     });
     component.registerNavigation({
       href: '~hello',
       children: 'Hello'
     });
    return new HelloWorldUI();
  }
}

HelloWorldAspect.addRuntime(HelloWorldUI);

这里,我们通过ComponentAspect依赖提供的registerRoute注册了路由,该路由会承接上述注册的导航,简单的渲染了hello world。

注册自定义Aspect

在执行Aspect之前,要为其配置解析环境,该环境会将Aspect最终转译为浏览器、nodejs可识别的代码。

{
  ...
  "teambit.workspace/variants": {
    "{me/aspects/*}": {
      "teambit.harmony/aspect":{}
    }
  },
  "me/aspects/hello-world": {}
  ...
}

安装依赖

$ bit install

不安装依赖,bit start也是正常运行的,只是看不到增加的UI。

效果展示

运行bit start查看效果~

注意:若要更新展示,则要删除.bit/、node_modules、public/,再次执行bit install和bit start。

查看Aspect信息

中途可通过bit show me/aspects/hello-world查看信息

通过内置模板创建扩展

  • 通过bit templates查看内置模板
  • 通过`bit create <template> <custom-name> [--scope scope-name]
  • 通过bit install安装模板相关依赖
  • 通过bit status查看自定义扩展状态
  • 若有依赖缺失报错,将缺失依赖添加到:
  "teambit.dependencies/dependency-resolver": {
    /**
     * choose the package manager for Bit to use. you can choose between 'yarn', 'pnpm'
     */
    "packageManager": "teambit.dependencies/pnpm",
    "policy": {
      "dependencies": {},
      "peerDependencies": {
        "react": "~17.0.2",
        "@testing-library/react": "~12.1.2"
      }
    }
  },

bit install补充安装依赖。

  • 通过bit start --dev测试。
03-05 16:32