Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions packages/back-end/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@
},
"homepage": "https://github.com/orchy-mfe/orchy-core#readme",
"devDependencies": {
"@happy-dom/global-registrator": "^9.8.0",
"@orchy-mfe/models": "workspace:^",
"@types/node": "^18.15.11",
"@types/tap": "^15.0.8",
"@typescript-eslint/eslint-plugin": "^5.59.0",
Expand All @@ -52,6 +54,7 @@
"typescript": "^5.0.4"
},
"dependencies": {
"@orchy-mfe/page-builder": "workspace:^",
"@sinclair/typebox": "^0.27.8",
"ajv": "^8.12.0",
"desm": "^1.3.0",
Expand Down
7 changes: 5 additions & 2 deletions packages/back-end/src/plugins/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,14 @@ import {ValidateFunction} from 'ajv/lib/types'
import {FastifyPluginAsync} from 'fastify'
import fp from 'fastify-plugin'

const ENDS_WITH_JSON = '^.+\\.json$'

const ConfigSchema = Type.Strict(
Type.Object({
LOG_LEVEL: Type.String({default: 'silent'}),
SERVER_PORT: Type.Number({default: 3000}),
CONFIG_PATH: Type.String()
CONFIG_PATH: Type.String(),
SSR_CONFIG_NAME: Type.String({default: 'orchy-config.json', pattern: ENDS_WITH_JSON}),
})
)

Expand All @@ -33,7 +36,7 @@ const configPlugin: FastifyPluginAsync = async (server) => {
const valid = validate(process.env)

assertValidEnv(valid, validate)

server.decorate('config', process.env)
}

Expand Down
74 changes: 74 additions & 0 deletions packages/back-end/src/plugins/orchyRoutes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import {GlobalRegistrator} from '@happy-dom/global-registrator'
import {Configuration, MicroPage, PageConfiguration} from '@orchy-mfe/models'
import PageBuilder from '@orchy-mfe/page-builder'
import {FastifyPluginAsync, FastifyReply, FastifyRequest} from 'fastify'
import fp from 'fastify-plugin'
import fs from 'fs'
import {join} from 'path'

const assertConfigurationExists = (configPath: string) => {
if(!fs.existsSync(configPath)) {
throw new Error(`Configuration at path ${configPath} does not exist`)
}
}

const retrieveConfiguration = async (configurationPath: string) => {
const configurationContent = await fs.promises.readFile(configurationPath, 'utf8')

return JSON.parse(configurationContent)
}

const createStylesheetConfiguration = (stylesheetUrl: string): PageConfiguration => ({
type: 'element',
tag: 'link',
attributes: {
rel: 'stylesheet',
href: stylesheetUrl
}
})

const renderConfiguration = (configuration: Configuration, microPage: MicroPage, pageConfiguration: PageConfiguration) => {
const stylesConfiguration: Array<PageConfiguration | string> = configuration.common?.stylesheets?.map(createStylesheetConfiguration) || []
const fullPageConfiguration = stylesConfiguration.concat(pageConfiguration)


return async (request: FastifyRequest, reply: FastifyReply) => {
const orchyProps = {
...microPage.properties,
basePath: new URL(request.url, `${request.protocol}://${request.headers.host}`).pathname,
}

const pageElement = await PageBuilder.pageBuilder(fullPageConfiguration, undefined, orchyProps)

reply.header('Content-Type', 'text/html')
reply.send(pageElement.outerHTML)
}
}


const orchyRoutes: FastifyPluginAsync = async server => {
GlobalRegistrator.register()

const configPath = join(server.config.CONFIG_PATH, server.config.SSR_CONFIG_NAME)
assertConfigurationExists(configPath)

const configurationContent: Configuration = await retrieveConfiguration(configPath)

Object.entries(configurationContent.microPages).forEach(async ([route, microPage]) => {
const pageConfigurationPath = join(server.config.CONFIG_PATH, microPage.pageConfiguration)
assertConfigurationExists(pageConfigurationPath)

const microPageConfiguration = await retrieveConfiguration(pageConfigurationPath)
const configurationHandler = renderConfiguration(configurationContent, microPage, microPageConfiguration)

server.get(route, configurationHandler)
server.get(`${route}/*`, configurationHandler)
})

server.addHook('onClose', () => {
GlobalRegistrator.unregister()
})

}

export default fp(orchyRoutes)
2 changes: 2 additions & 0 deletions packages/back-end/src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import fastify from 'fastify'
import now from 'fastify-now'

import config from './plugins/config.js'
import orchyRoutes from './plugins/orchyRoutes.js'

export const buildServer = async () => {
const server = fastify({
Expand All @@ -19,6 +20,7 @@ export const buildServer = async () => {
})

await server.register(config)
await server.register(orchyRoutes)
await server.register(now, {
routesFolder: join(import.meta.url, 'routes'),
})
Expand Down
9 changes: 2 additions & 7 deletions packages/back-end/src/testConfig/page-config.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,13 @@
"style": "width: 100vw"
},
"content": [{
"type": "micro-frontend",
"tag": "react-mfe",
"url": "//localhost:3001/src/main.jsx",
"type": "ssr-micro-frontend",
"url": "http://localhost:3001",
"properties": {
"foo": "foo"
},
"attributes": {
"style": "background: red"
}
}, {
"type": "micro-frontend",
"tag": "svelte-mfe",
"url": "//localhost:5000/src/main.js"
}]
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import {ElementPageConfiguration} from './ElementPageConfiguration'
import {LayoutPageConfiguration} from './LayoutPageConfiguration'
import {SsrMicroFrontendConfiguration} from './SsrMicroFrontendConfiguration'

type PageConfigurationContent = {
content?: PageConfiguration[]
}

export type PageConfiguration = (ElementPageConfiguration | LayoutPageConfiguration) & PageConfigurationContent
export type PageConfiguration = (ElementPageConfiguration | LayoutPageConfiguration | SsrMicroFrontendConfiguration) & PageConfigurationContent
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import {CommonPageConfiguration} from './CommonPageConfiguration'

export type SsrMicroFrontendConfiguration = CommonPageConfiguration & {
type: 'ssr-micro-frontend',
url: string
}
3 changes: 2 additions & 1 deletion packages/models/src/models/pageConfiguration/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export * from './CommonPageConfiguration'
export * from './ElementPageConfiguration'
export * from './LayoutPageConfiguration'
export * from './PageConfiguration'
export * from './PageConfiguration'
export * from './SsrMicroFrontendConfiguration'
20 changes: 11 additions & 9 deletions packages/page-builder/src/PageBuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,22 +9,24 @@ const stringConfigStrategy = (configuration: string, microFrontendProperties: Mi
return createdNode
}

const jsonConfigStrategy = (configuration: PageConfiguration, microFrontendProperties: MicroFrontendProperties) => {
const createdNode: HTMLElement = createNode(configuration, microFrontendProperties)
pageBuilder(configuration.content || [], createdNode, microFrontendProperties)
const jsonConfigStrategy = async (configuration: PageConfiguration, microFrontendProperties: MicroFrontendProperties) => {
const createdNode: HTMLElement = await createNode(configuration, microFrontendProperties)
await pageBuilder(configuration.content || [], createdNode, microFrontendProperties)
return createdNode
}

export const pageBuilder = (
export const pageBuilder = async (
configurations: Array<PageConfiguration | string>,
root: ParentNode = document.createElement('div'),
microFrontendProperties: MicroFrontendProperties
): ParentNode => {
const childrens = configurations.map(configuration => typeof configuration === 'string' ?
stringConfigStrategy(configuration, microFrontendProperties)
: jsonConfigStrategy(configuration, microFrontendProperties)
): Promise<ParentNode> => {
const childrens = await Promise.all(
configurations.map(configuration => typeof configuration === 'string' ?
stringConfigStrategy(configuration, microFrontendProperties)
: jsonConfigStrategy(configuration, microFrontendProperties)
)
)
if(childrens.length) {
if (childrens.length) {
root.replaceChildren(...childrens)
}
return root
Expand Down
6 changes: 3 additions & 3 deletions packages/page-builder/src/node-creator/CommonNodeCreator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,16 @@ import {CommonPageConfiguration} from '@orchy-mfe/models'
abstract class CommonNodeCreator {
constructor(private configuration: CommonPageConfiguration) { }

public create(): HTMLElement {
public create(): Promise<HTMLElement> {
this.applyAttributes()
this.applyProperties()
return this.currentNode
return Promise.resolve(this.currentNode)
}

private applyAttributes() {
Object.entries(this.configuration.attributes || {})
.forEach(([key, value]) => {
const attributesToAppend = [this.currentNode.getAttribute(key), value].filter(Boolean).join(';')
const attributesToAppend = [this.currentNode.getAttribute(key), value].filter(Boolean).join(';')
this.currentNode.setAttribute(key, attributesToAppend)
})
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ class ElementNodeCreator extends CommonNodeCreator {
this.currentNode = document.createElement(elementConfiguration.tag)
}

public create(): HTMLElement {
public create(): Promise<HTMLElement> {
this.importScript()
return super.create()
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import {MicroFrontendProperties, SsrMicroFrontendConfiguration} from '@orchy-mfe/models'

import CommonNodeCreator from './CommonNodeCreator'

export default class SsrMicroFrontendNodeCreator extends CommonNodeCreator {
currentNode: HTMLElement = document.createElement('div')

constructor(private elementConfiguration: SsrMicroFrontendConfiguration, microFrontendProperties: MicroFrontendProperties) {
const orchyProperties = {...microFrontendProperties, ...elementConfiguration.attributes, ...elementConfiguration.properties}
super({...elementConfiguration, properties: {orchyProperties}})
}

public async create(): Promise<HTMLElement> {
await this.importHtml()
return super.create()
}

private async importHtml() {
const htmlResponse = await fetch(this.elementConfiguration.url).then(res => res.text())
this.currentNode.innerHTML = htmlResponse
}
}
7 changes: 5 additions & 2 deletions packages/page-builder/src/node-creator/index.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
import {MicroFrontendProperties,PageConfiguration} from '@orchy-mfe/models'
import {MicroFrontendProperties, PageConfiguration} from '@orchy-mfe/models'

import ElementNodeCreator from './ElementNodeCreator'
import LayoutNodeCreator from './LayoutNodeCreator'
import MicroFrontendNodeCreator from './MicroFrontendNodeCreator'
import SsrMicroFrontendNodeCreator from './SsrMicroFrontendNodeCreator'

export const createNode = (configuration: PageConfiguration, microFrontendProperties: MicroFrontendProperties): HTMLElement => {
export const createNode = (configuration: PageConfiguration, microFrontendProperties: MicroFrontendProperties): Promise<HTMLElement> => {
switch (configuration.type) {
case 'element':
return new ElementNodeCreator(configuration, microFrontendProperties.eventBus).create()
case 'micro-frontend':
return new MicroFrontendNodeCreator(configuration, microFrontendProperties).create()
case 'ssr-micro-frontend':
return new SsrMicroFrontendNodeCreator(configuration, microFrontendProperties).create()
case 'flex-column':
case 'flex-row':
return new LayoutNodeCreator(configuration).create()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,20 +37,20 @@ const registerRoutes = (configuration: ConfigurationDependency, webComponentStat

lastManagedRoute = routeToManage
configuration.client.abortRetrieve()

const pageConfiguration = await configuration.client.retrieveConfiguration<PageConfiguration>(microPage.pageConfiguration)

const fullPageConfiguration = stylesConfiguration.concat(pageConfiguration)
const pageElement = pageBuilder(fullPageConfiguration, webComponentState.rootElement, orchyProps)
const pageElement = await pageBuilder(fullPageConfiguration, webComponentState.rootElement, orchyProps)
pageContentManager(pageElement)

webComponentState.eventBus.clearBuffer()
})
}
}

const configurationRegister = (configuration: ConfigurationDependency, webComponentState: WebComponentState) => {
installImportMaps(configuration.content)
const configurationRegister = (configuration: ConfigurationDependency, webComponentState: WebComponentState) => {
installImportMaps(configuration.content)
const routesRegister = registerRoutes(configuration, webComponentState)
Object.entries(configuration.content.microPages).forEach(routesRegister)

Expand Down
15 changes: 15 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.