@aacgn/atomic 中文文档教程
atomic micro-frontends: A JavaScript library for building interfaces with vertical and horizontal micro-frontends
Description
Atomic 是一个基于 iframe 的前端库,它使 javascript 开发人员能够从任何框架生成具有垂直和水平微前端的接口- 本机仅使用基于原子设计的客户端技术和组件。
✨ Features
- Dependency Free: Micro-frontends wrapped by Atomic don't need any extra library dependencies or configuration in their files to be used.
- Shared Runtime: Atomic Router provide a transition page option that is responsable for override temporarily pages that are loading micro-frontends, allowing it to show it content only when the micro-frontends are fully loaded and running at same time as a single product, keeping the UX intact.
- Shared Context: Atomic Pages provide a shared global object of context with all their micro-frontends, keeping a "single source of truth" concept for all of them.
- Clean Communication: Atomic embbed the entire shell application with a MessageEvent interceptor to properly deal with internal events (e.g., navigation and store), turning suh events more user friendly, and parse postMessage events (mostly provided by our micro-frontends) into actual CustomEvents, in order to turn their event listener declaration and data transfer object more semantic and synthetic for our app.
- Atomic Design Components: Atomic provide your own component abstraction to build our pages. Distributed as atoms, molecules, organisms, templates and pages, they are following the Atomic Design methology creating a merge of thoughts for the application composition and re-use of elements between the front-end and UX / UI teams.
Examples
Atomicfy
Atomicfy 是使用 Atomic 构建的 Spotify 克隆,使用来自 Vue.js、React.js、Angular 9 和 Svelte 的微前端。 如果您对制作过程有任何好奇,请查看我们的存储库链接。
Atomic Micro-Frontends Concepts
在开始构建我们自己的应用程序之前,了解微前端架构的一些基本原则非常重要。
Definition
第一个是定义。 您可以通过两种方式定义微前端架构:横向,允许每个页面有多个微前端; 垂直,允许每个业务领域一个微前端。
选择水平设计意味着您将拆分视图分成多个部分,这些部分可能属于也可能不属于同一个团队。 挑战在于确保这些不同的部分具有统一的外观和感觉。 请记住,通过这种方法,我们打破了我们为微前端定义的首要原则之一:围绕业务领域建模。 (尽管我还想说,很难将页脚视为业务领域!)
选择垂直拆分意味着您将从业务而非技术角度看待相同的问题。 您会将每个业务领域(例如身份验证 UI 或目录 UI)分配给一个团队。 这使每个团队都能成为领域专家——正如我之前提到的,当团队有更多的代理机构来运用该专业知识时,这尤其有价值。 事实上,每个领域团队,无论领域由多少视图组成,都可以完全自主地运作。
在这两者中,垂直分割最接近传统的 SPA 或服务器端渲染方法。 它不会严重分散实施,每个团队真正拥有应用程序的特定区域。
Composition
Atomic 使用客户端组合。 该架构可以通过使用应用程序外壳直接从 CDN(如果微前端尚未在 CDN 级别缓存,则从源)加载多个微前端来定义,通常使用 JavaScript 或 HTML 文件作为微前端的入口点。 这样,应用程序外壳可以动态附加 DOM 节点(在 HTML 文件的情况下)或初始化 JavaScript 应用程序(如果入口点是 JavaScript 文件)。
也可以使用 iframe 的组合来加载不同的微前端,Atomic 解决方案就是这种情况。
Routing
与组合一样,Atomic 选择客户端架构。
客户端路由,其中应用程序外壳包含并加载微前端,主要用于垂直切分应用程序时,以便应用程序外壳一次加载一个微前端而不是一次加载多个. 如果您使用的是水平拆分,则应该有一个页面——一个类似于应用程序外壳的容器——包含不同的微前端。 通过更改 URL,您可以加载具有不同视图的不同页面。
Communication between micro-frontends
如果您选择了水平拆分,那么下一步就是定义您的微前端如何相互通信。 一种方法是使用注入到每个微前端的事件发射器。 这将使每个微前端完全不知道其伙伴,并使独立部署它们成为可能。 当一个微前端发出一个事件时,其他订阅该特定事件的微前端会做出适当的反应。
您还可以使用自定义事件。 这些必须冒泡到窗口级别才能被其他微前端听到,这意味着所有微前端都在监听窗口对象内发生的所有事件。 它们还将事件直接派发到窗口对象,或者将事件冒泡到窗口对象,以便进行通信。
如果你正在使用垂直拆分,您需要了解如何跨微前端共享信息。 对于水平和垂直方法,请考虑视图在更改时如何进行通信。 变量可以通过查询字符串传递,或者使用 URL 传递少量数据(并强制新视图从服务器检索一些信息)。 或者,您可以使用网络存储来临时(会话存储)或永久(本地存储)存储要与其他微前端共享的信息。
注意:“Atomic 的工作原理”部分介绍的大多数微前端概念均摘自 Luca Mezzalira (@lucamezzalira) 撰写的文章“上下文中的微前端”。 如果您想了解更多该主题,请使用此链接查看论文的完整内容< /a>。
Usage
Boiler template
Atomic 在我们的 atomic-template 存储库中配置了 webpack、webpack-dev-server 和 babel 可用的锅炉模板。 请开始将它用于新项目。
Documentation
Atomic
import { Atomic } from "@aacgn/atomic";
import Router from "./router";
import "./global.css";
new Atomic(Router, document.getElementById("root"));
Atomic 也被称为应用程序管理器,它的职责是提供库中每个部分之间的通信和集成。 除了将 Atomic Router 链接到一个起始重新登录点,正如可以通过它们的参数轻松预测的那样,它的主要工作是拦截 MessageEvents 并将属性“hasAtomicSignature”设置为 true 并根据它们的消息内容正确处理它们(顺便说一句,将在下一节中解释)。
MessageEvent interceptor
如前所述,Atomic 的主要工作是拦截属性“hasAtomicSignature”设置为 true 的 MessageEvents,并根据消息内容正确处理它们。 但是,您不知道的是如何使用它。
在当前版本中,我们有三个公共功能:
- navigate: Responsible for link you to another page
- store: Reponsible for update the data of global context store
- custom_event: Reponsible for transform MessageEvent into a CustomEvent
要使用它们,您必须首先定义一个具有以下属性的对象:
- hasAtomicSignature: A bool field that is reponsible for enable or disable the interception of Atomic in this event
- event: A string field that is reponsible for identify the public functionality that you wanna use
- data: A object field that store and pass the a needed information of the public functionality that you wanna use
然后,您必须通过 window.postMessage 方法传递该对象并完成。
为了确保您第一次就做对了,我将在下面的内容中提供一些示例:
const postMessageData = {
hasAtomicSignature: true,
event: "navigate",
data: "/"
};
window.postMessage(postMessageData, "*");
const postMessageData = {
hasAtomicSignature: true,
event: "store",
data: {
name: "login",
data: {
oAuthToken: {}
}
}
};
window.postMessage(postMessageData, "*");
const postMessageData = {
hasAtomicSignature: true,
event: "custom_event",
data: {
name: "hello",
data: {
message: "Hello, Antonio Neto. How are you?"
}
}
};
window.postMessage(postMessageData, "*");
Atomic Router
import { AtomicRouter } from "@aacgn/atomic";
import LoginPage from "../pages/LoginPage";
import DashboardPage from "../pages/DashboardPage";
import TransitionPage from "../pages/TransitionPage";
const routes = [
{
path: "/",
page: LoginPage()
},
{
path: "/dashboard",
page DashboardPage()
}
]
const Router = new AtomicRouter(
{
routes: routes,
mode: "history",
transitionPage: TransitionPage()
}
);
export default Router;
Atomic Router 负责控制屏幕上显示的内容以及。 使用它,您可以定义路由、模式和转换页面,当加载到页面中的微前端正在加载时将使用该页面。
Routes
路由它是一个对象数组,用户将在其中声明和构建 URL 路径,从而控制您的应用程序何时呈现以及呈现什么内容。
目前,我们的内部数组对象只有两个必须传递的属性:
- Path: Conditional URL path location where your page will be rendered.
- Page: Component that your router is gonna display when the path setted be reached.
例如,在下面的代码中,当 URL 路径位置到达“dashboard”时,我们的应用程序将渲染 DashboardPage 组件。 根据您选择的路由模式,您的 URL 路径将类似于:http://localhost:8888/dashboard 或 http://localhost:8888/#/dashboard。
const routes = [
{
path: "/",
page: LoginPage()
},
{
path: "/dashboard",
page DashboardPage()
}
]
Mode
模式与您的 URL 路径的工作方式有关。 目前有两种模式可供选择:
- History
- Hash
一般场景下,hash 和 history 都可以,除非你比较在意外观。 # URL 中的符号看起来不是很漂亮。
对于 History,我们使用常规的地址栏 URL。 例如,这个 URL:http://www.abc.com/hello。 路径的值为/hello。
注意:如果您要使用历史记录模式,请记住使用 historyApiFallback 设置您的服务器。
对于 Hash,我们在地址栏 URL 中使用 # 符号。 例如,这个 URL:http://www.abc.com/#/hello。 hash的值为#/hello。特点是hash虽然出现在URL中,但不会包含在HTTP请求中,对后端没有影响,所以改变hash不会重新加载页面。
Transition Page
开发过渡页面选项是为了临时覆盖在加载微前端包装器时必须呈现的页面,这样我们就可以提供单一产品的体验,保持用户体验完整。
Components
组件负责将我们的 javascript 对象转换为浏览器页面的实际 DOM 元素。 目前,它们可以分为以下类型:
- Atoms
- Molecules
- Organisms
- Templates
- Micro-frontends Wrapper
- Pages
它们都遵循原子设计方法并具有独特的责任,以及我们自己的属性在库中声明和使用。
Atoms
原子是物质的基本组成部分。 应用于 Web 界面,原子是我们的 HTML 标签,例如表单标签、输入或按钮。
我们可以在 Atomic 中使用它作为以下实现:
import { createAtom } from "@aacgn/atomic";
import "./index.css";
const BaseButton = (textContent) => createAtom({
tag: "button",
attr: {
className: "base-button"
},
props: {
textContent: textContent
}
});
export default BaseButton;
Molecules
当我们开始将原子组合在一起时,事情开始变得更加有趣和有形。 分子是键合在一起的原子团,是化合物的最小基本单位。 这些分子具有自己的特性,并作为我们设计系统的支柱。
我们可以在 Atomic 中使用它作为以下实现:
import { createMolecule } from "@aacgn/atomic";
import BaseInput from "./BaseInput";
import BaseButton from "./BaseButton";
import "./index.css";
const SearchField = () => createMolecule({
tag: "div",
attr: {
className: "search-field"
},
props: {
children: [
BaseInput("Enter Keyword"),
BaseButton("Search")
]
}
});
export default SearchField;
Organisms
分子为我们提供了一些构建块,我们现在可以将它们组合在一起形成有机体。 有机体是连接在一起形成相对复杂、截然不同的界面部分的分子群。
我们可以在 Atomic 中使用它作为以下实现:
import { createOrganism } from "@aacgn/atomic";
import Logo from "./Logo";
import NavBar from "./NavBar";
import SearchField from "./SearchField";
import "./index.css";
const TopBar = () => createOrganism({
tag: "div",
attr: {
className: "top-bar"
},
props: {
children: [
Logo(),
NavBar(["Home", "About", "Blog", "Contact"]),
SearchField()
]
}
});
export default TopBar;
Templates
在模板阶段,我们打破我们的化学类比以进入对我们的客户和我们的最终输出更有意义的语言。 模板主要由缝合在一起形成页面的有机体组组成。 正是在这里,我们开始看到设计融合在一起,并开始看到诸如布局之类的东西在起作用。
我们可以在 Atomic 中使用它作为以下实现:
import { createTemplate } from "@aacgn/atomic";
import TopBar from "./TopBar";
import CatalogCarousel from "./CatalogCarousel";
import CatalogRecommendation from "./CatalogRecommendation";
import "./index.css";
const DashboardTemplate = (carouselItems, recommendationItems) => createTemplate({
tag: "div",
attr: {
className: "dashboard"
},
props: {
children: [
TopBar(),
CatalogCarousel(carouselItems),
CatalogRecommendation(recommendationItems)
]
}
});
export default DashboardTemplate;
Pages
页面是模板的特定实例。 在这里,占位符内容被替换为真实的代表性内容,以准确描述用户最终将看到的内容。
import { createPage } from "@aacgn/atomic";
import DashboardTemplate from "./DashboardTemplate";
import "./index.css";
const DashboardPage = () => createPage({
name: "dashboard",
context: {
carouselItems: [],
recommendationItems: []
},
mount: function() {
return DashboardTemplate(this.context.carouselItems, this.content.recommendationItems);
},
onMount: function(ref) {
console.log('hello');
},
onUnmount: function(ref) {
console.log('bye');
}
});
export default DashboardPage;
Micro-frontends Wrapper
最后,我们来到了最后一个组件。 它的职责是在我们的页面中插入来自任何原生框架的微前端。它们没有任何层次结构级别,可以在任何级别的原子设计中调用,唯一的限制是它是页面的子级。
以更实际的方式,想象一下之前示例的所有内容,直到将模板放入微前端,下面的示例将演示如何将其包装并使用到我们的页面中。
DashboardMicroFrontendWrapper
import { createMicroFrontendWrapper } from "@aacgn/atomic";
import "./index.css";
const DashboardMicroFrontendWrapper = () => createMicroFrontendWrapper({
attr: {
className: "dashboard-micro-frontend-wrapper"
},
props: {
url: "http://localhost:3000"
}
});
export default DashboardMicroFrontendWrapper;
DashboardPage
import { createPage } from "@aacgn/atomic";
import DashboardMicroFrontendWrapper from "./DashboardMicroFrontendWrapper";
import "./index.css";
const DashboardPage = () => createPage({
name: "dashboard",
context: {
carouselItems: [],
recommendationItems: []
},
mount: function() {
return DashboardMicroFrontendWrapper();
},
onMount: function(ref) {
console.log('hello');
},
onUnmount: function(ref) {
console.log('bye');
}
});
export default DashboardPage;
注意:如果您想在我们的微前端中捕获上下文对象,请在其中使用 window.AtomicContextStore['name-of-your-page'] 或仅通过 postMessage 发送数据,例如 postMessage .
Auxiliar functions
Atomic 还为其 shell 应用程序提供了一些辅助功能。 目前,它们是:
- dispatchEvent: Responsible for send events to micro-frontends declared into our application.
- navigateTo: Reponsible for redirect our application to another path
- storeData: Responsible for storage data into a context store item
- mapContextStore: Responsible for return all context store object
示例:
import { dispatchEvent } from "@aacgn/atomic";
dispatchEvent("carouselItems", 12, ["dashboard-micro-frontend-wrapper"]);
import { navigateTo } from "@aacgn/atomic";
navigateTo("/");
import { storeData } from "@aacgn/atomic";
storeData("dashboard", {carouselItems: [], recommendationItems: []});
import { mapContextStore } from "@aacgn/atomic";
mapContextStore("dashboard");
Contributing
欢迎请求请求。 对于重大更改,请先打开一个问题来讨论您想要更改的内容。
请确保适当地更新测试。
License
MIT License
Copyright (c) 2020 Antonio Neto
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
atomic micro-frontends: A JavaScript library for building interfaces with vertical and horizontal micro-frontends
???? Description
Atomic is a front-end library, based on iframes, that enables javascript developers to generate interfaces with vertical and horizontal micro-frontends from any framework-native using only client-side techniques and components based on Atomic Design.
✨ Features
- Dependency Free: Micro-frontends wrapped by Atomic don't need any extra library dependencies or configuration in their files to be used.
- Shared Runtime: Atomic Router provide a transition page option that is responsable for override temporarily pages that are loading micro-frontends, allowing it to show it content only when the micro-frontends are fully loaded and running at same time as a single product, keeping the UX intact.
- Shared Context: Atomic Pages provide a shared global object of context with all their micro-frontends, keeping a "single source of truth" concept for all of them.
- Clean Communication: Atomic embbed the entire shell application with a MessageEvent interceptor to properly deal with internal events (e.g., navigation and store), turning suh events more user friendly, and parse postMessage events (mostly provided by our micro-frontends) into actual CustomEvents, in order to turn their event listener declaration and data transfer object more semantic and synthetic for our app.
- Atomic Design Components: Atomic provide your own component abstraction to build our pages. Distributed as atoms, molecules, organisms, templates and pages, they are following the Atomic Design methology creating a merge of thoughts for the application composition and re-use of elements between the front-end and UX / UI teams.
???? Examples
Atomicfy
Atomicfy is a Spotify clone that was built with Atomic, using micro-frontends from Vue.js, React.js, Angular 9 and Svelte. If you got any curiosity about how was made, please check our repository link.
???? Atomic Micro-Frontends Concepts
Before start building our own application, it's important to understand some basic principles of micro-frontend architecture.
Definition
The first one it's the definition. You can define the micro-frontends architecture in two ways: horizontal, which allows multiple micro-frontends per page; and vertical, which allows one micro-frontend per business domain.
Opting for a horizontal design means you’ll split a view into multiple parts, which may or may not be owned by the same team. The challenge is to ensure that these different parts have a cohesive look and feel. Bear in mind that with this approach, we’re breaking one of the first principles we’ve defined for micro-frontends: model around a business domain. (Though I’d also say it’s hard to consider a footer a business domain!)
Opting for vertical splitting means you’ll look at the same problem from a business rather than a technical point of view. You’ll assign each business domain, such as authentication UI or the catalog UI, to a team. This enables each team to become domain experts—which, as I noted earlier, is especially valuable when teams have more agency to employ that expertise. In fact, each domain team, despite how many views the domain is composed of, can operate with full autonomy.
Of the two, vertical splitting is closest to the traditional SPA or server-side rendering approach. It doesn’t heavily fragment the implementation, and each team truly owns a specific area of the application.
Composition
Atomic uses a client-side composition. That architecture can be defined by the usage of an application shell to load multiple micro-frontends directly from a CDN (or from the origin if the micro-frontend is not yet cached at the CDN level), often with JavaScript or an HTML file as the micro-frontends’ entry point. This way, the application shell can dynamically append the DOM nodes (in the case of an HTML file) or initialize the JavaScript application (if the entry point is a JavaScript file).
It’s also possible to use a combination of iframes to load different micro-frontends, which is the exatcly case of Atomic solution.
Routing
As with compositions, Atomic choose a client-side architecture.
The client-side routing, in which an app shell contains and loads the micro-frontends, is mainly used when you’ve sliced your applications vertically, so that the app shell loads one micro-frontend at a time instead of a multitude at once. If you’re using a horizontal split, you should have a page—a container like the app shell—that contains the different micro-frontends. By changing the URL, you can load a different page with a different view.
Communication between micro-frontends
If you’ve opted for a horizontal split, your next step is to define how your micro-frontends communicate with each other. One method is to use an event emitter injected into each micro-frontend. This would make each micro-frontend totally unaware of its fellows, and make it possible to deploy them independently. When a micro-frontend emits an event, the other micro-frontends subscribed to that specific event react appropriately.
You can also use custom events. These have to bubble up to the window level in order to be heard by other micro-frontends, which means that all micro-frontends are listening to all events happening within the window object. They also dispatch events directly to the window object, or bubble the event to the window object, in order to communicate.
If you’re working with a vertical split, you’ll need to understand how to share information across micro-frontends. For both horizontal and vertical approaches, think about how views communicate when they change. It’s possible that variables may be passed via query string, or by using the URL to pass a small amount of data (and forcing the new view to retrieve some information from the server). Alternatively, you can use web storage to temporarily (session storage) or permanently (local storage) store the information to be shared with other micro-frontends.
Note: Most of the micro-frontend concepts presented into "How Atomic Works" section, was extract from the article "Micro-frontends in context" wrote by Luca Mezzalira (@lucamezzalira). If you want to learn more this topic, please take a look at the full content of paper using this link.
???? Usage
Boiler template
Atomic has a boiler template available with webpack, webpack-dev-server and babel already configured in our atomic-template repository. Please, starting using it for new projects.
Documentation
Atomic
import { Atomic } from "@aacgn/atomic";
import Router from "./router";
import "./global.css";
new Atomic(Router, document.getElementById("root"));
Atomic is also know as the application manager, it responsability is provide communication and integration between every piece of the library. Beyond link the Atomic Router to a start redenring point, as can be easily predicted by their parameters, it's main job is intercepting MessageEvents with the attribute "hasAtomicSignature" setted as true and properly treating them based on their message content (that, by the way, will be explained in the next section).
MessageEvent interceptor
As previously mentioned, Atomic main job is to intecept MessageEvents with the attribute "hasAtomicSignature" setted as true and properly treating them based on their message content. But, what you don't know it's how you can use it.
In the current version, we have three public funcionalities:
- navigate: Responsible for link you to another page
- store: Reponsible for update the data of global context store
- custom_event: Reponsible for transform MessageEvent into a CustomEvent
To use them, you must first define a object with following attributes:
- hasAtomicSignature: A bool field that is reponsible for enable or disable the interception of Atomic in this event
- event: A string field that is reponsible for identify the public functionality that you wanna use
- data: A object field that store and pass the a needed information of the public functionality that you wanna use
After that, you must pass this object through a window.postMessage method and are done.
Just to make sure that you gonna do right in your first time, I gonna provide some examples in the content bellow:
const postMessageData = {
hasAtomicSignature: true,
event: "navigate",
data: "/"
};
window.postMessage(postMessageData, "*");
const postMessageData = {
hasAtomicSignature: true,
event: "store",
data: {
name: "login",
data: {
oAuthToken: {}
}
}
};
window.postMessage(postMessageData, "*");
const postMessageData = {
hasAtomicSignature: true,
event: "custom_event",
data: {
name: "hello",
data: {
message: "Hello, Antonio Neto. How are you?"
}
}
};
window.postMessage(postMessageData, "*");
Atomic Router
import { AtomicRouter } from "@aacgn/atomic";
import LoginPage from "../pages/LoginPage";
import DashboardPage from "../pages/DashboardPage";
import TransitionPage from "../pages/TransitionPage";
const routes = [
{
path: "/",
page: LoginPage()
},
{
path: "/dashboard",
page DashboardPage()
}
]
const Router = new AtomicRouter(
{
routes: routes,
mode: "history",
transitionPage: TransitionPage()
}
);
export default Router;
Atomic Router is the responsible for controling what is being displayed in the screen and. With it you can define routes, mode, and a transition page that is gonna be used when micro-frontends wrapped into a page are loading.
Routes
Routes it's a array of object where the user will declare and structure the URL paths, controling when and what your application will render.
Currently, our inner array object have only two attributes that must be passed:
- Path: Conditional URL path location where your page will be rendered.
- Page: Component that your router is gonna display when the path setted be reached.
For example, in the code bellow our application will render the DashboardPage component when the URL path location reach "dashboard". Depending on the mode of routing you choosed, your URL path will be something like this: http://localhost:8888/dashboard or http://localhost:8888/#/dashboard.
const routes = [
{
path: "/",
page: LoginPage()
},
{
path: "/dashboard",
page DashboardPage()
}
]
Mode
Mode it's related with how your URL paths will work. Currently, you can choose between two modes:
- History
- Hash
In general scenarios, hash and history can be used, unless you care more about the appearance. # Symbols in URLs do not look very beautiful.
With History, we use conventional address bar URL. For example, this URL: http://www.abc.com/hello. The value of path is /hello.
Note: If you gonna use history mode, please, remember to set your server with historyApiFallback.
With Hash, we use in the address bar URL along side withs # symbols. For example, this URL: http://www.abc.com/#/hello. The value of hash is #/hello。The feature is that although hash appears in the URL, it will not be included in the HTTP request and has no effect on the back end, so changing hash will not reload the page.
Transition Page
The trasition page option was developed to temporarily override a page that must be render when a micro-frontend wrapper are being loaded, that way we can provide a experience of single product, keeping the UX intact.
Components
Components are the responsible for translate our javascript objects into actually DOM elements for our browser pages. Currently, they can be split in the following types:
- Atoms
- Molecules
- Organisms
- Templates
- Micro-frontends Wrapper
- Pages
All of them follows the Atomic Design methodology and have a unique reponsability, along side with our own properties to be declared and used in the library.
Atoms
Atoms are the basic building blocks of matter. Applied to web interfaces, atoms are our HTML tags, such as a form label, an input or a button.
We can use it in Atomic as the following implementation:
import { createAtom } from "@aacgn/atomic";
import "./index.css";
const BaseButton = (textContent) => createAtom({
tag: "button",
attr: {
className: "base-button"
},
props: {
textContent: textContent
}
});
export default BaseButton;
Molecules
Things start getting more interesting and tangible when we start combining atoms together. Molecules are groups of atoms bonded together and are the smallest fundamental units of a compound. These molecules take on their own properties and serve as the backbone of our design systems.
We can use it in Atomic as the following implementation:
import { createMolecule } from "@aacgn/atomic";
import BaseInput from "./BaseInput";
import BaseButton from "./BaseButton";
import "./index.css";
const SearchField = () => createMolecule({
tag: "div",
attr: {
className: "search-field"
},
props: {
children: [
BaseInput("Enter Keyword"),
BaseButton("Search")
]
}
});
export default SearchField;
Organisms
Molecules give us some building blocks to work with, and we can now combine them together to form organisms. Organisms are groups of molecules joined together to form a relatively complex, distinct section of an interface.
We can use it in Atomic as the following implementation:
import { createOrganism } from "@aacgn/atomic";
import Logo from "./Logo";
import NavBar from "./NavBar";
import SearchField from "./SearchField";
import "./index.css";
const TopBar = () => createOrganism({
tag: "div",
attr: {
className: "top-bar"
},
props: {
children: [
Logo(),
NavBar(["Home", "About", "Blog", "Contact"]),
SearchField()
]
}
});
export default TopBar;
Templates
At the template stage, we break our chemistry analogy to get into language that makes more sense to our clients and our final output. Templates consist mostly of groups of organisms stitched together to form pages. It’s here where we start to see the design coming together and start seeing things like layout in action.
We can use it in Atomic as the following implementation:
import { createTemplate } from "@aacgn/atomic";
import TopBar from "./TopBar";
import CatalogCarousel from "./CatalogCarousel";
import CatalogRecommendation from "./CatalogRecommendation";
import "./index.css";
const DashboardTemplate = (carouselItems, recommendationItems) => createTemplate({
tag: "div",
attr: {
className: "dashboard"
},
props: {
children: [
TopBar(),
CatalogCarousel(carouselItems),
CatalogRecommendation(recommendationItems)
]
}
});
export default DashboardTemplate;
Pages
Pages are specific instances of templates. Here, placeholder content is replaced with real representative content to give an accurate depiction of what a user will ultimately see.
import { createPage } from "@aacgn/atomic";
import DashboardTemplate from "./DashboardTemplate";
import "./index.css";
const DashboardPage = () => createPage({
name: "dashboard",
context: {
carouselItems: [],
recommendationItems: []
},
mount: function() {
return DashboardTemplate(this.context.carouselItems, this.content.recommendationItems);
},
onMount: function(ref) {
console.log('hello');
},
onUnmount: function(ref) {
console.log('bye');
}
});
export default DashboardPage;
Micro-frontends Wrapper
Finally, we came to our last component. It responsability is insert the micro-frontend from any framework-native in our pages.They doesn't have any hierarchy level and can be called in any of level of atomic design, the only restriction it's be a child of a page.
Going to a more pratice way, imagine that everything exemplified previously until template as putted into a micro-frontend, the following example bellow will demonstrate how wrap that and use into our page.
DashboardMicroFrontendWrapper
import { createMicroFrontendWrapper } from "@aacgn/atomic";
import "./index.css";
const DashboardMicroFrontendWrapper = () => createMicroFrontendWrapper({
attr: {
className: "dashboard-micro-frontend-wrapper"
},
props: {
url: "http://localhost:3000"
}
});
export default DashboardMicroFrontendWrapper;
DashboardPage
import { createPage } from "@aacgn/atomic";
import DashboardMicroFrontendWrapper from "./DashboardMicroFrontendWrapper";
import "./index.css";
const DashboardPage = () => createPage({
name: "dashboard",
context: {
carouselItems: [],
recommendationItems: []
},
mount: function() {
return DashboardMicroFrontendWrapper();
},
onMount: function(ref) {
console.log('hello');
},
onUnmount: function(ref) {
console.log('bye');
}
});
export default DashboardPage;
Note: If you wanna catch the context object inside our micro-frontends use window.AtomicContextStore['name-of-your-page'] in it or just send the data through postMessage such as postMessage.
Auxiliar functions
Atomic also provide for it's shell application a couple of auxiliar functions. Currently, they are:
- dispatchEvent: Responsible for send events to micro-frontends declared into our application.
- navigateTo: Reponsible for redirect our application to another path
- storeData: Responsible for storage data into a context store item
- mapContextStore: Responsible for return all context store object
Examples:
import { dispatchEvent } from "@aacgn/atomic";
dispatchEvent("carouselItems", 12, ["dashboard-micro-frontend-wrapper"]);
import { navigateTo } from "@aacgn/atomic";
navigateTo("/");
import { storeData } from "@aacgn/atomic";
storeData("dashboard", {carouselItems: [], recommendationItems: []});
import { mapContextStore } from "@aacgn/atomic";
mapContextStore("dashboard");
???? Contributing
Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change.
Please make sure to update tests as appropriate.
???? License
MIT License
Copyright (c) 2020 Antonio Neto
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.