低代码组件最终被发布成 npm 包供低代码平台消费,组件被如何消费,这取决于组件提供了哪些消费方式。不论开源组件还是内源组件,开发方在编码之前都需仔细考量组件的消费方式,如果其方式与使用方的预期严重不符,那么使用方很可能不会选择该组件。本小节介绍组件有哪些消费方式,组件要提供这些消费方式该如何实现。总体上而言,组件有静态导入和动态加载这两种消费方式:
静态导入 : 使用 ES6 规范引入的 import 关键字导入组件,该规范要求组件是一个 ES6 规范的模块。静态导入适用于导入确定的模块,
动态加载 : 动态加载指的是程序运行过程中按需加载组件。浏览器自身支持的动态加载有两种。第一种,动态嵌入脚本标签,引用远程资源的 URL,例如 JavaScript 或 CSS 文件;第二种,ES 动态导入,用法是 import('模块路径')。
组件怎么被消费?这里有一个不得不谈的问题,即:组件被打包成什么样的模块格式。在 JavaScript 中模块格式有如下 4 种:
AMD
AMD 的全称是 Asynchronous Module Definition,即异步模块定义,它是为浏览器环境设计的,采用异步方式加载模块,但浏览器自身并不支持它,要加载 AMD 模块,必须使用特定的模块加载器,例如 RequireJS 和 curl.js 等。
常见的打包工具,比如 webpack,rollup 等都能通过配置项将模块自动打包成 AMD 格式,借助这些工具,组件的开发者在编码时无须关注如何将模块定义为 AMD 格式,但组件使用方消费组件时需要引入特定的模块加载器,用特定的语法去加载组件。使用 RequireJS 加载 AMD 格式的模块,代码如下:
javascript
require(['foo'], function(foo) {
// 在这里使用 foo 模块中导出的内容。
});
加载AMD模块除了需要在 js 文件中写入上述代码,还需要在 HTML 文件中用 script 标签引入 RequireJS 的 js 资源。
CommonJS
CommonJS 是为服务端 JavaScript 设计的模块规范,其一大特性是同步加载模块,该特性适用于服务器端应用程序,Node.js 是主要实践者。
回到本图书介绍的案例,低代码组件应该选用 AMD 规范还是 CommonJS 规范,这取决于组件是否需要支持服务端渲染。本图书不涉及服务端渲染的知识,但不意味着低代码 APP 不能实现服务端渲染。目前,客户端渲染是 Web 应用常见的展示方式,不过,有些企业考虑到搜索引擎优化或首屏渲染时长,会采用服务端渲染,因此在这里不断言低代码组件一定只在客户端使用。
UMD
介绍完前面 2 类模块,一些读者可能会思考是否存在一种既能这样又能那样的模块,UMD 模块就是这类模块。UMD 的全称是 Universal Module Definition,即通用模块定义,它主要用来解决 CommonJS 规范和 AMD 规范不能通用的问题,同时还支持老式的全局变量。 UMD 模块的代码特征如下:
javascript
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined'
? module.exports = factory(require('react'))
: typeof define === 'function' && define.amd
? define(['react'], factory)
: (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.vitisLowcodeInput = factory(global.React));
}(this, (function (React) { 'use strict';
// 组件模块的代码
return component;
})));
UMD 规范的实现原理很简单:
- 先判断是否存在 exports 和 module ,存在则认为这是 CommonJS 模块。
- 再判断是否有 define 函数并且 define.amd 为真,是则认为这是 AMD 模块。
- 如果前两步都不满足,则将模块公开到全局。
ES modules
ES modules 是 ECMAScript 推出的模块规范,可以将其认为是 JavaScript 的"官方"模块规范,客户端 JavaScript 环境和服务器 JavaScript 环境已相继支持。不管低代码组件需要在服务端使用还是只在客户端使用,选择 ES modules 模块规范将是一个大趋势,值得注意的是,选择它需要考虑兼容性问题。
导入 ES 模块用到的关键字是 import,它根据模块路径将模块导入当前模块,模块路径也称为模块说明符。关于模块说明符有一些令人混淆的点,模块说明符一共有 3 种类型,分别是:相对路径、绝对路径和 bare(裸露) 模式。
(1) 相对路径
模块说明符是相对路径时,导入 ES 模块的代码如下:
javascript
import foo from './myModule.js'
import { sayName } from '../other_module.js'
相对路径的说明符以 / 、./ 、../ 开头,此时不能省略文件的扩展名。也许有读者会问,为何他开发 Web 项目时,导入模块省略了文件扩展名,程序依然能够运行?那是因为项目中使用了如 Webpack 这样的模块打包工具,它对源代码的写法做了处理。
(2) 绝对路径
模块说明符是绝对路径时,导入 ES 模块的代码如下:
javascript
import React from 'https://cdn.skypack.dev/react'
上述代码表示从 CDN 导入模块,绝对路径是否能省略文件扩展名,这与服务器的配置相关。
(3) bare(裸露)模式
模块说明符是bare模式时,导入ES模块的代码如下:
javascript
import React from 'react'
import Foo from 'react/lib.js'
bare 模式会从 node_module 中导入模块,在 Web 项目中,用 bare 模式导入模块很常见,但 ES modules 本身并不支持它。在代码里之所以能这样写,是因为使用了如 Webpack 这样的模块打包工具,它对源代码的写法做了处理。
前面已经提到,我们不断言低代码组件的使用环境,也就是说它既能在服务端使用也能在客户端使用,如果不考虑兼容性问题,你完全可以选用 ES modules 规范。本图书作为参考资料,希望覆盖更多的知识,因此作者在设计低代码组件的方案时,选择将组件既打包成 UMD 格式,也打包成 ES modules 格式。
从组件模块的打包结果来看,它有一个 index.min.js 文件也有一个 index.esm.js 文件,前者是符合 UMD 规范的模块,后者是符合 ES modules 规范的模块。打包组件的工作由蚂蚁金服的开源项目 father 负责,组件开发者不必过多关注,只需给 father 的特定配置项赋值即可。
组件的代码以不同的模块规范被打包在两个不同的文件中,使用方消费组件时究竟该引入哪个文件呢?
这与 package.json 文件中 main 和 module 字段相关。将 index.min.js 赋给 main 字段,而将 index.esm.js 赋给 module 字段。如果组件的使用方用 ES modules 的语法引入组件,例如:import MyComponent from '组件路径',模块系统将认为 module 字段对应的文件是组件的入口;如果用AMD/commonjs 的语法引入组件,模块系统将认为 main 字段对应的文件是组件的入口。也就说只要使用方配置好程序的运行环境,模块系统将依据组件 npm 包的 package.json 中的字段去加载最合适的文件。