梧忌
什么是物料?
物料(Material) 这个概念在前端领域大家都不陌生。让我们从前端应用的构成上说起,在 DOM 中 节点(Node) 是最小单位,再之上是 元素(Element) 。React 带来了 组件(Component) 的概念,一个组件是由一个或多个元素构成的,组件是元素的超集。
// 在项目中定义一个组件
export default function Component(props) {
return (<h1>Hello, {props.name}</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>);
}
组件是一个高内聚的封装,有单独的开发周期,外部可以通过传递属性(Props)的方式对其进行定制。通过对组件进行组合、嵌套,形成了更高阶的前端功能组成单元 —— 区块(Block)。可以简单地把区块理解为「用组件组成的代码片段」,但通常区块内部还包含事件处理、状态管理、数据请求等逻辑。使用方式上,区块与组件不同,区块不对外提供可配置的属性,是通过拷贝代码的形式放到项目中的。这带来的好处是区块与前端项目无依赖关系,对区块定制是通过修改项目中的区块代码来实现的。
// 定义一个区块
import { Form, Input } from '@alifd/next';
import DingTalk from '@ali/ding-talk';
const FormItem = Form.Item;
function LoginForm() {
return (<div>
<Form inline>
<FormItem label="用户名:"><Input name="first"/></FormItem>
<FormItem label="密码:" required><Input htmlType="password" name="inlinePass"/></FormItem>
<FormItem label=" "><Form.Submit>提交</Form.Submit></FormItem>
</Form>
<div>联系我们:<DingTalk userId="foo" /> </div>
</div>);
}
// 在项目中进行使用
import LoginForm from './LoginForm';
import Banner from './Banner';
import Game from './Game';
function Home() {
return (<div>
<Banner />
<LoginForm />
<Game />
</div>);
}
对区块进行组合,就形成了页面(Page)。页面是一个浏览器窗口中所有功能的集合。一个或多个页面则组成了应用(Application)。开发应用的组织模式,就是前端项目(Project)。
自此,我们完成了对前端应用的析构,组件(基础/业务)、区块、页面这些构成应用的不同粒度单元就是物料。
为什么要基于物料?
基于组件进行前端开发已经是业界的共识了。实际场景中,区块、页面、脚手架在业务域中也有广泛的复用价值,各业务域存在的设计和交互规范,可以让前端对 UI 的抽象到更高更细的层次。因此如果能够抽丝剥茧,将业务域的物料进行整合,形成一套物料源,在业务域内进行流通,能够在业务域内有广泛的覆盖度进行复用和组合。物料这一层抽象,对于团队的分工协作,提升前端系统的可维护性,也大有裨益。
如何基于物料呢?需要有一定的开发模式。
我们对不同粒度的物料进行整合形成物料源(Material Collection),把物料源的组织模式称为物料项目(Material Project),它和前端项目中的物料对应关系如下:
然后设计基于物料的前端开发链路和角色,借助工具保障和提效各环节,从而保障生产质量、提升生产效率:
如何开发物料?
在物料开发环节,需要的是规范物料源的开发流程以及配套的开发工具进行保障和提效。我们定义的一个物料源的开发流程如下:
初始化物料项目
物料项目中包含业务组件、区块、页面和脚手架多种粒度的物料。前面讲到了需要配套物料开发工具,我们提供的是物料开发命令行工具:Iceworks CLI,通过 $ iceworks init 创建一个物料项目,其目录结构如下:
基础组件库的存量和增量都比较少,业务开发者通常不会接触到这个领域,因此在本文不进行展开,有兴趣的同学可以参考 alibaba-fusion/next 仓库的组织。
.
├── .eslintrc.js
├── .stylelintrc.js
├── README.md
├── package.json
├── blocks/ 区块集合
│ └── ExampleBlock/ 单个区块包
│ └── package.json
├── components/ 组件集合
| └── ExampleComponent/ 单个业务组件包
| └── package.json
├── pages/ 页面模板集合
| └── ExamplePage/ 单个页面包
| └── package.json
└── scaffolds/ 脚手架集合
└── ExampleScaffold/ 单个脚手架包
└── package.json
完整的物料项目可参考 Fusion Materials
项目内的 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 下载该源代码并使用。
自此,完成了页面物料的创建、开发和发布全流程。
更多
受限于文章的篇幅,区块和脚手架的开发在此不再展开,其中脚手架物料的开发与组件类似,区块物料的开发与页面物料类似。有兴趣的同学可以参考:
发布物料源
生成数据
所有物料包开发并发布完成,在物料项目根目录执行:$ iceworks generate 可生成物料源的数据到 build/material.json 文件。命令行是根据物料项目的 package.sjon 和单个物料中的 package.json 文件来生成这些信息的。生成的数据中包含了开发工具需要用到的物料源信息:
- 物料源的名称 name 和描述 description
- 什么类型的物料源 type:React/Vue/Rax
- 有哪些物料集合:blocks/page/components/scaffolds
单个物料的信息:
- 名称和描述
- 所属分类:categories
- 主页:homepage
- 代码源:source
- 依赖的包:dependencies
- 截图:screenshot
一个物料源数据示例如下:
参考完整的线上示例
{
"name": "@iamalvin/material-collection",
"type": "react",
"template": "@icedesign/ice-react-ts-material-template",
"description": "This is a ice material project",
"author": "alvinhui",
"blocks": [
{
"name": "ExampleBlock",
"title": "demo block",
"category": "Information",
"description": "intro block",
"homepage": "https://unpkg.com/@iamalvin/example-block@0.1.0/build/index.html",
"categories": [
"Information"
],
"source": {
"type": "npm",
"npm": "@iamalvin/example-block",
"version": "0.1.0",
"registry": "https://registry.npmjs.org"
},
"dependencies": {
"prop-types": "^15.5.8"
},
"screenshot": "https://unpkg.com/@iamalvin/example-block@0.1.0/screenshot.png"
}
],
"components": [
],
"scaffolds": [
],
"pages": [
]
}
在《如何使用物料》章节将会描述开发工具是如何使用物料源数据来展示物料、下载物料代码。
发布数据
在物料项目根目录执行 npm publish 将物料源发布到 npm ,得到 npm 的 CDN 托管服务 unpkg 的 URL 地址。发布到 npm 本质是希望将 material.json 托管到 unpkg,这样就能通过 unpkg 服务得到物料源的 URL 地址。
也可以将物料数据发布到 阿里巴巴物料中心。不管是将物料数据托管到阿里巴巴物料中心还是 unpkg,本质上都是要有一个 URL 来让开发工具通过 HTTP GET 请求获取物料数据,因此,除了以上方式,你也可以将物料数据的 JSON 文件放到你的 CDN (例如阿里云 OSS)或在某个后端接口中返回该 JSON。
如何使用物料?
物料使用环节,核心是如何让使用物料更简单更高效。我们的开发工具 Iceworks 对 VS Code 进行了集成,只需要 安装插件,就可以在编辑器中可视化地使用物料搭建前端应用。使用流程如下:
设置物料源
Iceworks 默认提供了官方的物料源来快速开始。对于打造定制物料源的业务链路,可以通过设置将定制物料源添加到 Iceworks。
有两种方式打开设置面板:
- 点击 VS Code 左侧活动栏的 Iceworks 图标,再点击快速入口视图中的「设置」;
- 通过快捷键 ⇧⌘P 唤起 VS Code 命名面板,在输入框中输入:Iceworks: 设置。
- 在设置面板中点击自定义物料源右侧的「添加」按钮,输入物料源名称和地址,点击「确定」。
物料源的使用高度依赖 npm ,因此设置中提供了 npm 客户端和镜像源的设置。
组件&区块
日常开发需要查找需要的组件,可以通过以下方式快速查找组件的文档和示例:
查找所有组件的文档和示例:
- 点击 VS Code 左侧活动栏的 Iceworks 图标,再点击快速入口视图中的「查找组件」;
- 通过快捷键 ⇧⌘P 唤起 VS Code 命名面板,在输入框中输入:Iceworks: 查找组件;
查找当前文件中用到的组件的文档和示例:
- 在编辑窗口点击鼠标右键,在右键列表中选择「Iceworks: 查找当前文件的组件」;
- 通过快捷键 ⇧⌘P 唤起 VS Code 命名面板,在输入框中输入:Iceworks: 查找当前文件的组件;
- 查找当前代码行组件的文档和示例:鼠标停留在组件使用处,在出现的悬浮框中点击「xxx 文档」。
然后是区块的使用。区块在物料源中,是以源代码的形式存在的。在开发工具侧,我们提供了物料面板,一键将区块(组件)添加到项目,操作流程和演示:
- 区块的上一级粒度是页面,因此当前限定区块只能添加到页面文件中,所以需要先打开页面的入口文件 page//index.;
- 当前物料面板中默认展示了所有的物料源,需要先选择需要使用的物料源。
Iceworks 将插入组件&区块代码并自动引用区块&组件,下载区块源代码,安装需要的依赖包。下面的流程图描述了 Icework 在添加区块的内部处理过程。通过流程中,即可理解到为什么在物料源中,区块不需要通过 npm install 的方式进行使用,还是需要发布 npm :原因是开发工具将 npm 镜像源当做了代码托管平台。
页面是通过区块组成的。因此除了能够一键把区块添加到页面的代码文件中,还可以通过区块组装的方式来生成页面,操作流程和演示如下:
Iceworks 将自动下载区块源代码并处理,然后再生成页面的入口文件。
页面模板
页面级物料是通过模板的形式来承载的。在开发物料时,编写的是模板和配置描述。在实际使用时,物料源中的页面模板配置将在前台生成表单,开发者通过填写表单的方式配置模板,使用模板代码来生成页面。使用流程和演示如下:
案例
在阿里集团内部,已经有非常多的团队运用物料开发链路到业务场景中,以下是一些案例:
参考
- 物料开发工具:Iceworks CLI
- 物料使用工具:Iceworks
- 物料项目模板:Material Templates
通用物料源:
- PC Web 场景:Fusion Materials
- 小程序场景:Miniprogram Materials
- 无线跨端场景:Rax Materials
- 阿里巴巴物料中心:Alibaba Material Center
关注「Alibaba F2E」
把握阿里巴巴前端新动向