amis分包构建(上)

amis是百度开源的一个前端低代码框架,使用 JSON 配置来生成页面,有120+的内置组件。但是组件太齐全也有一个坏处,就是包体积过大,但是实际上一个页面可能只使用了少数几个组件,大多数组件都是没有被使用到的,因此我们尝试了一次组件的分包构建

大致思路

首先肯定是需要将amisamis-ui中的所有组件都使用vite单独打成iife包,然后分析amis schema配置,找到使用了哪些组件,然后只引入这些组件以及其所依赖的组件。下面是具体步骤。

1、amisamis-ui单独打包

1.1 分析amisamis-ui组件

由于每个组件都是一个单独的入口,所以这一步我们从文件系统中找到所有的组件

const amisRootDir = [
  `packages/amis/src/renderers`,
];

const amisUIRootDir = [
  `packages/amis-ui/src/components`,
  `packages/amis-ui/src/hooks`,
  `packages/amis-ui/src/locale`,
  `packages/amis-ui/src/themes`,
];

具体实现就是从上面的入口中递归找到所有的组件,需要注意的是amis-ui/src/hooks/index.tsamis-ui/src/components/index.tsx是汇总文件,需要被忽略

最终这个步骤将会输出一个类似于下面的json

{




  "amis": [
    {
      "name": "amis.Action", // iife的全局变量名称
      "srcPath": "amis/src/renderers/Action.tsx", // 组件在文件系统上的相对路径
      "impPath": "amis/lib/renderers/Action" // 组件的import路径
    }
  ],
  "amisUi": [
    {
      "name": "amisUi.NotFound",
      "srcPath": "amis-ui/src/components/404.tsx",
      "impPath": "amis-ui/lib/components/404"
    }
  ]
}

1.2 分析amis-ui的组件导入方式

这个步骤我们使用es-module-lexer来处理,但是es-module-lexer不能分析ts,所以需要确保amis-uirollup构建过,生成了esm目录,我们来分析packages/amis-ui/esm/index.js中的内容

有这个步骤的原因是因为在amis schema配置中不直接依赖amis-ui,都是通过在amis中导入的amis-ui,但是我们无法通过这种导入语句来找到组件具体位置,也无法确定导入形式。如:无法通过import { Table } form 'amis-ui'找到Table组件的具体位置,找到了位置也无法确定是该用import Table from 'amis-ui/lib/table'还是用import { Table } from 'amis-ui/lib/table'来导入

packages/amis-ui/esm/index.js中有四种组件导入方式

名称 描述 例子
name 命名导入 import { A } from 'B';
rename 重命名导入 import { A as AA } from 'B';
default 默认导入 import A from 'B';
all 导入所有 import * as A from 'B';

最终这个步骤将会输出一个类似于下面的json

{




  "NotFound": {
    "importsWay": "default", // 导入方式
    "impPath": "amis-ui/lib/components/404", // 组件的import路径
    "srcPath": "amis-ui/src/components/404.tsx" // 组件在文件系统上的相对路径
  },
  "AlertComponent": {
    "importsWay": "rename",
    "originName": "FinnalAlert", // 如果是重命名导入,还需要一个原始名称
    "impPath": "amis-ui/lib/components/Alert",
    "srcPath": "amis-ui/src/components/Alert.tsx"
  }
}


1.3 使用viteamisamis-ui分包构建

这个步骤主要就是写rollup插件了

  • 1.3.1 在transform钩子中将所有amis-uiamis的导入路径改为步骤1.1中生成的iife的全局变量名称
  • 1.3.2 在external配置项中将idiife的全局变量名称返回true
  • 1.3.3 还要处理下动态导入,在renderDynamicImport钩子中返回{ left: 'Promise.resolve().then(() => ', right: ')' };

1.4 找到amis组件的iife的全局变量名称和renderer组件类型之间的对应关系

有两个解决方案,我是使用的第二种

  1. 正则表达式。大部分的renderer都是类似下面这样的,少部分的可以特殊处理
@Renderer({
 type: 'avatar'
})
@withBadge
export class AvatarFieldRenderer extends AvatarField {}
  1. moduleParsed钩子中通过ast来寻找

最终这个步骤将会输出一个类似于下面的json

{




  "action": "amis.Action",
  "button": "amis.Action",
  "submit": "amis.Action",
  "reset": "amis.Action",
  "crud": "amis.CRUD",
  "calendar": "amis.Calendar",
  "card2": "amis.Card2",
  "{\"pattern\":\"(^|\\\\/)(?:crud\\\\/body\\\\/grid|cards)$\",\"flags\":\"\"}": "amis.Cards", // 正则表达式
  "collapse-group": "amis.CollapseGroup",
  "collapse": "amis.Collapse",
  "carousel": "amis.Carousel",
}


1.5 分析组件依赖数据

这个步骤主要是在external配置项中,最终生成类似下面的json。

{




  "amis.AnchorNav": [
    "amisUi.AnchorNav"
  ],
  "amis.BarCode": [
    "amisUi.BarCode"
  ],
  "amis.Avatar": [
    "amisUi.Avatar",
    "amisUi.Badge"
  ],
  "amis.Alert": [
    "amisUi.Alert2"
  ],
  "amis.Action": [
    "amisUi.Button",
    "amis.Remark",
    "amisUi.Badge",
    "amisUi.TooltipWrapper"
  ]
}

按理来说这是除了发包外的最后一步了,但在实践中发现amis-ui出现了循环依赖!!组件的依赖链陷入了死循环!!

1.6 解决循环依赖

  1. 找到循环依赖

在这个步骤中我们使用到了类似N叉树的数据结构

class Node {
  constructor(val) {
    this.val = val;
  }
  /** @type {string} */
  val;
  /** @type {Node[]} */
  children;
  /** @type {string[]?} */
  ancestor;
  setChildren(children) {
    this.children = children;
    this.#setParent();
  }
  #setParent() {
    if (this.children) {
      for (const child of this.children) {
        // child.parent = this;
        child.ancestor = [...(this.ancestor || []), this.val]; // 祖先
      }
    }
  }
  getLoopDep() {
    if (this.ancestor) {
      const index = this.ancestor.indexOf(this.val);
      if (index !== -1) {
        const arr = this.ancestor.splice(index);
        arr.push(this.val);
        return arr;
      }
    }
    return null;
  }
}

最终找到了5组循环依赖

amis-ui/src/components/condition-builder/Group.tsx
amis-ui/src/components/condition-builder/GroupOrItem.tsx


amis-ui/src/components/condition-builder/Expression.tsx
amis-ui/src/components/condition-builder/Func.tsx

amis-ui/src/components/formula/Editor.tsx
amis-ui/src/components/formula/plugin.ts

amis-ui/src/components/json-schema/Item.tsx
amis-ui/src/components/json-schema/Array.tsx
amis-ui/src/components/json-schema/Object.tsx

amis-ui/src/components/schema-editor/Item.tsx
amis-ui/src/components/schema-editor/Array.tsx
amis-ui/src/components/schema-editor/Object.tsx
  1. 解决循环依赖

还好不是很多,解决方案为将有循环依赖的组件打包在一起,在amis-ui中新建5个文件分别导入有循环依赖的组件再导出,如:

import * as Expression from '../../condition-builder/Expression';
import * as Func from '../../condition-builder/Func';


export default {
  Expression,
  Func
};

并且生生成一个json文件来标识这些循环依赖项,以便特殊处理

[
  "amisUi.loopImport.conditionBuilder.Group_GroupOrItem",
  "amisUi.loopImport.conditionBuilder.Expression_Func",
  "amisUi.loopImport.formula.Editor_plugin",
  "amisUi.loopImport.jsonSchema.Item_Array_Object",
  "amisUi.loopImport.schemaEditor.Item_Array_Object"
]

终于到了发包前的最后一步了,但事实证明我还是想多了

1.7 再次寻找依赖项

amis代码中还有很多使用render函数而产生的依赖项,类下面这些代码:

image.png
同样有两种解决方案,这次我是使用的第一种

  1. 正则表达式
  2. moduleParsed钩子中通过ast来寻找

最终这个步骤将会输出一个类似于下面的json

{




  "amis.CRUD": [
    "amis.Action",
    "amis.Pagination",
    "amis.Dialog"
  ],
  "amis.App": [
    "amis.Action"
  ],
  "amis.Card": [
    "amis.Action"
  ],
}


1.8 发包

终于来到这个部分的最后一步了,生成package.json,把所有组件发到npm上就完事了

最终的文件目录如下

amis-splitPkg/
├── amis
│   ├── Action
│   │   └── index.iife.js
│   ├── Alert
│   │   └── index.iife.js
│   └── ...
├── amis-ui // amis-ui组件目录
│   ├── Alert
│   │   └── index.iife.js
│   ├── Avatar
│   │   └── index.iife.js
│   └── ...
├── depsData.json
├── globalUmdPkg.json
├── loopImportKey.json
├── metaData.json
├── renderDepsData.json
├── README.md
└── package.json

1.8.1 文件和文件目录说明

  • amis

    amis组件目录

  • amisUi

    amis-ui组件目录

  • depsData.json

    通过import语句分析出来的组件间依赖关系,不允许存在循环依赖,且组件导入有严格的顺序要求

  • renderDepsData.json

    amis组件中通过调用render函数所产生的依赖关系,可以存在循环依赖,且没有组件导入顺序要求

  • metaData.json

    schema中的renderer类型和组件名的对应关系,其中以{开头的key为正则表达式,需要特殊处理

  • loopImportKey.json

    有循环依赖的组件一起打包后的组件名

  • globalUmdPkg.json

    公共组件库的umd包的路径,如:react、react-dom等

针对amis的构建部分终于结束了

© 版权声明
THE END
喜欢就支持一下吧
点赞0

Warning: mysqli_query(): (HY000/3): Error writing file '/tmp/MYGgtpnl' (Errcode: 28 - No space left on device) in /www/wwwroot/583.cn/wp-includes/class-wpdb.php on line 2345
admin的头像-五八三
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

图形验证码
取消
昵称代码图片