什么是物料?
物料(Material)这个概念在前端领域大家都不陌生。让我们从前端应用的构成上说起,在 DOM 中 节点(Node)是最小单位,再之上是元素(Element)。React 带来了组件(Component) 的概念,一个组件是由一个或多个元素构成的,组件是元素的超集。
-
// 在项目中定义一个组件
-
export default function Component(props) {
-
return (<h1>Hello, {}</h1>);
-
}
-
// 在项目中使用该组件
-
import Component from './Component';
-
function Home() {
-
return (<div><Component name="ICE" /></div>);
-
}
面向特定的前端领域,前端的组成部件在设计和交互上是可枚举、可抽象以及通用的,于是一些组件成为了该领域的前端开发的基石。它们就是基础组件(Base Component)。例如在企业级中后台领域中的组件库 Fusion Design / Ant Design 。
-
// 在基础组件库中定义一个组件
-
export function Button(props)
-
// 一些处理逻辑
-
return (<button {...props}>{props.children}</button>);
-
}
-
// 在项目中使用基础组件库的的组件
-
import { Button } from '@alifd/next';
-
function Home() {
-
return (<div>
-
<Button type="normal">普通按钮</Button>
-
<Button type="primary">主要按钮</Button>
-
<Button type="secondary">次要按钮</Button>
-
</div>);
-
}
基础组件粒度小,强调通用性,难以覆盖所有场景。对于垂直业务而言,会有特定交互逻辑或数据处理逻辑,例如阿里的花名选择器、钉钉的唤醒图标、淘宝的小蜜机器人等等,它们就是业务组件(Business Component)。通常面向一个垂直业务,会有一个业务组件库,例如小二工作台业务组件。
-
import { Button } from '@alifd/next';
-
export default function DingTalk(orginProps) {
-
const props = {
-
...orginProps,
-
/* Business Logic */
-
};
-
return (<Button {...props}>{ /* Business Logic */ }</Button>);
-
}
-
// 在项目中使用业务组件
-
import DingTalk from '@ali/ding-talk';
-
function Home() {
-
return (<div>
-
<DingTalk userId="foo" />
-
</div>);
-
}
对区块进行组合,就形成了页面(Page)。页面是一个浏览器窗口中所有功能的集合。一个或多个页面则组成了应用(Application)。开发应用的组织模式,就是前端项目(Project)。
自此,我们完成了对前端应用的析构,组件(基础/业务)、区块、页面这些构成应用的不同粒度单元就是物料。
为什么要基于物料?
基于组件进行前端开发已经是业界的共识了。实际场景中,区块、页面、脚手架在业务域中也有广泛的复用价值,各业务域存在的设计和交互规范,可以让前端对 UI 的抽象到更高更细的层次。因此如果能够抽丝剥茧,将业务域的物料进行整合,形成一套物料源,在业务域内进行流通,能够在业务域内有广泛的覆盖度进行复用和组合。物料这一层抽象,对于团队的分工协作,提升前端系统的可维护性,也大有裨益。
如何基于物料呢?需要有一定的开发模式。
我们对不同粒度的物料进行整合形成物料源(Material Collection),把物料源的组织模式称为物料项目(Material Project),它和前端项目中的物料对应关系如下:
然后设计基于物料的前端开发链路和角色,借助工具保障和提效各环节,从而保障生产质量、提升生产效率
初始化物料项目
物料项目中包含业务组件、区块、页面和脚手架多种粒度的物料。前面讲到了需要配套物料开发工具,我们提供的是物料开发命令行工具:Iceworks CLI,通过 $ iceworks init
创建一个物料项目,其目录结构如下:
-
.
-
├── .eslintrc.js
-
├── .stylelintrc.js
-
├── README.md
-
├── package.json
-
├── blocks/ 区块集合
-
│ └── ExampleBlock/ 单个区块包
-
│ └── package.json
-
├── components/ 组件集合
-
| └── ExampleComponent/ 单个业务组件包
-
| └── package.json
-
├── pages/ 页面模板集合
-
| └── ExamplePage/ 单个页面包
-
| └── package.json
-
└── scaffolds/ 脚手架集合
-
└── ExampleScaffold/ 单个脚手架包
-
└── package.json
项目内的 components/blocks/pages/scaffolds 文件夹是单种物料类型的集合,其子文件夹是一个个单独的物料包。
项目的 package.json
中重要字段是 materialConfig
,它标明了当前物料项目是使用哪个物料模板资源包初始化的,后续也会用这个物料模板资源包来生成单个物料。
-
{
-
"materialConfig": {
-
"template": "@icedesign/ice-react-ts-material-template",
-
"type": "react"
-
}
-
}
Iceworks CLI 当前默认提供了多语言(TypeScript/JavaScript)以及多 DSL(React/Vue/Rax)的物料模板资源包,如果不满足(例如要开发基于 Angular 的物料项目),则可以通过开发相应的物料模板资源包,然后在初始化物料项目时指定使用的资源包来满足:$ iceworks init material-collection npmName
。
单个物料包的开发
初始化物料项目完成,进入到单个物料包的开发流程。通过 $iceworks add
向物料项目添加单个物料包
业务组件
业务组件的开发与常见的 React 组件开发无太大差别。Iceworks 主要提供了组件本地开发调试和构建的能力。一个业务组件的物料包的组织如下:
-
.
-
├── README.md 文档
-
├── build.json 构建配置
-
├── demo 使用示例,一个 md 文件一个示例
-
│ └── usage.md
-
├── package.json
-
├── src 源代码
-
│ ├── index.scss
-
│ └── index.tsx
-
└── tsconfig.json
需要特别说明的是:组件的 package.json
中的 componentConfig
是 Iceworks 专用的:
-
{
-
"componentConfig": {
-
"name": "ExampleComponent", // 生成代码时使用的导入名
-
"title": "demo component", // 用于展示标题
-
"category": "Information" // 用于展示时进行的分类名,任意值
-
}
-
}
以开发一个业务组件为例的命名行执行过程:
-
$ iceworks add component
-
$ cd components/ExampleComponent
-
$ npm install
-
$ npm run start # 启动本地调试
-
$ npm publish # 开发完成,执行 npm 发布
页面模板
页面物料和区块物料一样,是以源代码复制的方式被前端项目使用的。执行 $ iceworks add page
向物料项目添加一个页面模板资源包,其目录结果如下:
-
.
-
├── README.md
-
├── build.json
-
├── config 模板配置文件
-
│ ├── mock.js 模拟配置
-
│ └── settings.json 配置设置
-
├── package.json
-
├── src 模板源文件
-
│ ├── components
-
│ │ └── User
-
│ │ └── index.tsx.ejs
-
│ └── index.tsx.ejs
-
└── tsconfig.json
在页面资源包中,src 内存放的都是模板文件,模板使用 ejs 语法。一个模板示例(模板里面有 isShowUser 和 title 两个模板变量):
-
import React, { useEffect, useState } from 'react';
-
<% if (isShowUser) { %>
-
import User from './components/User';
-
async function fetchUser() {
-
return { name: 'ICE', age: '18' };
-
}
-
<% } %>
-
export default function() {
-
<% if (isShowUser) { %>
-
const [ user, setUser ] = useState({});
-
useEffect(() => {
-
async function initUser() {
-
setUser(await fetchUser());
-
}
-
initUser();
-
}, []);
-
<% } %>
-
return (
-
<>
-
<div><%= title %></div>
-
<% if (isShowUser) { %>
-
<User {...user} />
-
<% } %>
-
</>
-
);
-
}
在 config/mock.js 中声明本地调试使用的模板变量模拟数据
-
export default {
-
isShowUser: true,
-
title: '标题'
-
};
在 config/setting.json 中声明模板变量的 Schema,Schema 字段使用 Formily Schema 协议,用于生成前台配置化表单:
-
{
-
"schema": {
-
"title": "用户任务列表",
-
"description": "显示用户信息",
-
"type": "object",
-
"required": [
-
"isShowUser"
-
],
-
"properties": {
-
"isShowUser": {
-
"type": "boolean",
-
"title": "是否显示用户信息",
-
"default": true
-
},
-
"title": {
-
"type": "string",
-
"title": "标题"
-
}
-
}
-
}
-
}
页面物料开发完成,执行 npm publish
,将会依次执行:
- 构建测试:检测是否有语法错误。
- 生成缩略图:用于在物料中心和物料面板进行展示。
- 发布 npm 包:发布 npm 目的是为了托管源代码到 npm,后续 Iceworks 将使用 npm 的 tarball 下载该源代码并使用。
自此,完成了页面物料的创建、开发和发布全流程。