雲計算

從生產到消費,基於物料的前端開發鏈路

梧忌

image.png

什麼是物料?

物料(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>);
}

image.png
基礎組件粒度小,強調通用性,難以覆蓋所有場景。對於垂直業務而言,會有特定交互邏輯或數據處理邏輯,例如阿里的花名選擇器、釘釘的喚醒圖標、淘寶的小蜜機器人等等,它們就是業務組件(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>);
}

image.png
組件是一個高內聚的封裝,有單獨的開發週期,外部可以通過傳遞屬性(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>);
}

image.png
對區塊進行組合,就形成了頁面(Page)。頁面是一個瀏覽器窗口中所有功能的集合。一個或多個頁面則組成了應用(Application)。開發應用的組織模式,就是前端項目(Project)
image.png
自此,我們完成了對前端應用的析構,組件(基礎/業務)、區塊、頁面這些構成應用的不同粒度單元就是物料

為什麼要基於物料?

基於組件進行前端開發已經是業界的共識了。實際場景中,區塊、頁面、腳手架在業務域中也有廣泛的複用價值,各業務域存在的設計和交互規範,可以讓前端對 UI 的抽象到更高更細的層次。因此如果能夠抽絲剝繭,將業務域的物料進行整合,形成一套物料源,在業務域內進行流通,能夠在業務域內有廣泛的覆蓋度進行復用和組合。物料這一層抽象,對於團隊的分工協作,提升前端系統的可維護性,也大有裨益。

如何基於物料呢?需要有一定的開發模式。

我們對不同粒度的物料進行整合形成物料源(Material Collection),把物料源的組織模式稱為物料項目(Material Project),它和前端項目中的物料對應關係如下:
image.png
然後設計基於物料的前端開發鏈路和角色,藉助工具保障和提效各環節,從而保障生產質量、提升生產效率:
image.png

如何開發物料?

在物料開發環節,需要的是規範物料源的開發流程以及配套的開發工具進行保障和提效。我們定義的一個物料源的開發流程如下:
image.png

初始化物料項目

物料項目中包含業務組件、區塊、頁面和腳手架多種粒度的物料。前面講到了需要配套物料開發工具,我們提供的是物料開發命令行工具: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 文件夾是單種物料類型的集合,其子文件夾是一個個單獨的物料包。
image.png
項目的 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 向物料項目添加單個物料包。
image.png

業務組件

業務組件的開發與常見的 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 發佈

業務組件示例:Anchor
或參考《業務組件開發教程》

頁面模板

頁面物料和區塊物料一樣,是以源代碼複製的方式被前端項目使用的。執行 $ 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 下載該源代碼並使用。

自此,完成了頁面物料的創建、開發和發佈全流程。

頁面模板示例:UserLogin
或參考《頁面模板開發指南》

更多

受限於文章的篇幅,區塊和腳手架的開發在此不再展開,其中腳手架物料的開發與組件類似,區塊物料的開發與頁面物料類似。有興趣的同學可以參考:

發佈物料源

生成數據

所有物料包開發併發布完成,在物料項目根目錄執行:$ 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/[email protected]/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/[email protected]/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 進行了集成,只需要 安裝插件,就可以在編輯器中可視化地使用物料搭建前端應用。使用流程如下:
image.png

設置物料源

Iceworks 默認提供了官方的物料源來快速開始。對於打造定製物料源的業務鏈路,可以通過設置將定製物料源添加到 Iceworks。

  1. 有兩種方式打開設置面板:

    • 點擊 VS Code 左側活動欄的 Iceworks 圖標,再點擊快速入口視圖中的「設置」;
    • 通過快捷鍵 ⇧⌘P 喚起 VS Code 命名面板,在輸入框中輸入:Iceworks: 設置。
  2. 在設置面板中點擊自定義物料源右側的「添加」按鈕,輸入物料源名稱和地址,點擊「確定」。

物料源的使用高度依賴 npm ,因此設置中提供了 npm 客戶端和鏡像源的設置。

組件&區塊

日常開發需要查找需要的組件,可以通過以下方式快速查找組件的文檔和示例:
image.png

  • 查找所有組件的文檔和示例:

    • 點擊 VS Code 左側活動欄的 Iceworks 圖標,再點擊快速入口視圖中的「查找組件」;
    • 通過快捷鍵 ⇧⌘P 喚起 VS Code 命名面板,在輸入框中輸入:Iceworks: 查找組件;
  • 查找當前文件中用到的組件的文檔和示例:

    • 在編輯窗口點擊鼠標右鍵,在右鍵列表中選擇「Iceworks: 查找當前文件的組件」;
    • 通過快捷鍵 ⇧⌘P 喚起 VS Code 命名面板,在輸入框中輸入:Iceworks: 查找當前文件的組件;
  • 查找當前代碼行組件的文檔和示例:鼠標停留在組件使用處,在出現的懸浮框中點擊「xxx 文檔」。

image.png
然後是區塊的使用。區塊在物料源中,是以源代碼的形式存在的。在開發工具側,我們提供了物料面板,一鍵將區塊(組件)添加到項目,操作流程和演示:
image.png

  • 區塊的上一級粒度是頁面,因此當前限定區塊只能添加到頁面文件中,所以需要先打開頁面的入口文件 page//index.
  • 當前物料面板中默認展示了所有的物料源,需要先選擇需要使用的物料源。

Iceworks 將插入組件&區塊代碼並自動引用區塊&組件,下載區塊源代碼,安裝需要的依賴包。下面的流程圖描述了 Icework 在添加區塊的內部處理過程。通過流程中,即可理解到為什麼在物料源中,區塊不需要通過 npm install 的方式進行使用,還是需要發佈 npm :原因是開發工具將 npm 鏡像源當做了代碼託管平臺。
image.png
頁面是通過區塊組成的。因此除了能夠一鍵把區塊添加到頁面的代碼文件中,還可以通過區塊組裝的方式來生成頁面,操作流程和演示如下:

image.png
Iceworks 將自動下載區塊源代碼並處理,然後再生成頁面的入口文件。

頁面模板

頁面級物料是通過模板的形式來承載的。在開發物料時,編寫的是模板和配置描述。在實際使用時,物料源中的頁面模板配置將在前臺生成表單,開發者通過填寫表單的方式配置模板,使用模板代碼來生成頁面。使用流程和演示如下:
image.png
640.gif

案例

在阿里集團內部,已經有非常多的團隊運用物料開發鏈路到業務場景中,以下是一些案例:
image.png

參考


image.png
關注「Alibaba F2E」
把握阿里巴巴前端新動向

Leave a Reply

Your email address will not be published. Required fields are marked *