-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: generate remote components & remote elements automatically
- Loading branch information
Showing
196 changed files
with
18,025 additions
and
459 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
export const remoteComponentGeneratorConfig = { | ||
ignoreComponents: [""], | ||
ignoreProps: ["tunnelId"], | ||
}; |
76 changes: 76 additions & 0 deletions
76
dev/remote-components-generator/generateRemoteComponents.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
import { ComponentFileLoader } from "./loading/ComponentFileLoader"; | ||
import { ComponentFileContentLoader } from "./loading/ComponentFileContentLoader"; | ||
import { | ||
generateRemoteReactComponentFile, | ||
generateRemoteReactComponentIndexFile, | ||
} from "./generation/generateRemoteReactComponentFile"; | ||
import jetpack from "fs-jetpack"; | ||
import { prepareTypeScriptOutput } from "./generation/prepareTypeScriptOutput"; | ||
import { generateRemoteElementFile } from "./generation/generateRemoteElementFile"; | ||
import { remoteComponentGeneratorConfig } from "./config"; | ||
|
||
const componentFileLoader = new ComponentFileLoader(); | ||
const componentFileContentLoader = new ComponentFileContentLoader( | ||
componentFileLoader, | ||
); | ||
|
||
async function generate() { | ||
const config = remoteComponentGeneratorConfig; | ||
|
||
console.log("🤓 Read component specification file"); | ||
const componentSpecificationFile = await componentFileLoader.loadFile(); | ||
console.log("✅ Done"); | ||
console.log(""); | ||
|
||
console.log("🧐 Parse component specification file"); | ||
let components = await componentFileContentLoader.parseJson( | ||
componentSpecificationFile, | ||
); | ||
console.log("✅ Done"); | ||
console.log(""); | ||
|
||
console.log("💣 Remove ignored components"); | ||
config.ignoreComponents.map((ignoredComponent) => { | ||
components = components.filter( | ||
(item) => item.displayName != ignoredComponent, | ||
); | ||
}); | ||
console.log("✅ Done"); | ||
console.log(""); | ||
|
||
console.log("📝️ Generating remote-react-component files"); | ||
for (const component of components) { | ||
const remoteReactComponentFile = | ||
generateRemoteReactComponentFile(component); | ||
await jetpack.writeAsync( | ||
`packages/remote-react-components/src/${component.displayName}.ts`, | ||
await prepareTypeScriptOutput(remoteReactComponentFile), | ||
); | ||
} | ||
const remoteReactComponentsIndexFile = | ||
generateRemoteReactComponentIndexFile(components); | ||
await jetpack.writeAsync( | ||
"packages/remote-react-components/src/index.ts", | ||
await prepareTypeScriptOutput(remoteReactComponentsIndexFile), | ||
); | ||
console.log("✅ Done"); | ||
console.log(""); | ||
|
||
console.log("📝️ Generating remote-element files"); | ||
for (const component of components) { | ||
const remoteElementFile = generateRemoteElementFile(component); | ||
await jetpack.writeAsync( | ||
`packages/remote-elements/src/${component.displayName}.ts`, | ||
await prepareTypeScriptOutput(remoteElementFile), | ||
); | ||
} | ||
await jetpack.writeAsync( | ||
"packages/remote-elements/src/index.ts", | ||
await prepareTypeScriptOutput(remoteReactComponentsIndexFile), | ||
); | ||
console.log("✅ Done"); | ||
console.log(""); | ||
console.log("✅ Generation finished successfully"); | ||
} | ||
|
||
void generate(); |
59 changes: 59 additions & 0 deletions
59
dev/remote-components-generator/generation/generateRemoteElementFile.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
import type { ComponentDoc } from "react-docgen-typescript"; | ||
import { kebabize } from "../lib/kebabize"; | ||
import { remoteComponentGeneratorConfig } from "../config"; | ||
|
||
export function generateRemoteElementFile( | ||
componentSpecification: ComponentDoc, | ||
) { | ||
const config = remoteComponentGeneratorConfig; | ||
const componentProps = componentSpecification.props; | ||
|
||
config.ignoreProps.map((prop) => delete componentProps[prop]); | ||
|
||
const t = { | ||
element: `Remote${componentSpecification.displayName}Element`, | ||
propsType: `${componentSpecification.displayName}Props`, | ||
name: componentSpecification.displayName, | ||
props: Object.keys(componentProps) | ||
.filter((propName) => !propName.startsWith("on")) | ||
.map((propName) => { | ||
const key = propName.includes("-") ? `'${propName}'` : propName; | ||
return `${key}: {}`; | ||
}) | ||
.join(",\n"), | ||
events: Object.keys(componentProps) | ||
.filter((propName) => propName.startsWith("on")) | ||
.map((propName) => { | ||
const formattedName = propName[2].toLowerCase() + propName.slice(3); | ||
return `${formattedName}: {}`; | ||
}) | ||
.join(",\n"), | ||
}; | ||
|
||
return `\ | ||
import { createRemoteElement } from "@remote-dom/core/elements"; | ||
import type { ${t.propsType} } from "@mittwald/flow-react-components/${t.name}"; | ||
export type { ${t.propsType} } from "@mittwald/flow-react-components/${t.name}"; | ||
export const ${t.element} = createRemoteElement<${t.propsType}>({ | ||
properties: { | ||
${t.props} | ||
}, | ||
${ | ||
t.events && t.events.length > 0 | ||
? `events: { | ||
${t.events} | ||
},` | ||
: "events: {}," | ||
} | ||
}); | ||
declare global { | ||
interface HTMLElementTagNameMap { | ||
"flr-${kebabize(t.name)}": InstanceType<typeof ${t.element}>; | ||
} | ||
} | ||
customElements.define("flr-${kebabize(t.name)}", ${t.element}); | ||
`; | ||
} |
49 changes: 49 additions & 0 deletions
49
dev/remote-components-generator/generation/generateRemoteReactComponentFile.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
import type { ComponentDoc } from "react-docgen-typescript"; | ||
import { kebabize } from "../lib/kebabize"; | ||
import { remoteComponentGeneratorConfig } from "../config"; | ||
|
||
export function generateRemoteReactComponentFile( | ||
componentSpecification: ComponentDoc, | ||
) { | ||
const config = remoteComponentGeneratorConfig; | ||
const componentProps = componentSpecification.props; | ||
|
||
config.ignoreProps.map((prop) => delete componentProps[prop]); | ||
|
||
const t = { | ||
component: `Remote${componentSpecification.displayName}Element`, | ||
name: componentSpecification.displayName, | ||
events: Object.keys(componentProps) | ||
.filter((propName) => propName.startsWith("on")) | ||
.map((propName) => { | ||
const formattedName = propName[2].toLowerCase() + propName.slice(3); | ||
return `${propName}: { event: "${formattedName}" } as never`; | ||
}) | ||
.join(",\n"), | ||
}; | ||
|
||
return `\ | ||
import { createRemoteComponent } from "@remote-dom/react"; | ||
import { ${t.component} } from "@mittwald/flow-remote-elements"; | ||
export const ${t.name} = createRemoteComponent("flr-${kebabize(t.name)}", ${t.component}, {${ | ||
t.events && t.events.length > 0 | ||
? `eventProps: { | ||
${t.events} | ||
},` | ||
: "" | ||
}}); | ||
`; | ||
} | ||
|
||
export function generateRemoteReactComponentIndexFile( | ||
componentSpecifications: ComponentDoc[], | ||
) { | ||
let indexFile = ""; | ||
|
||
componentSpecifications.map((component) => { | ||
indexFile += `export * from "./${component.displayName}";`; | ||
}); | ||
|
||
return indexFile; | ||
} |
14 changes: 14 additions & 0 deletions
14
dev/remote-components-generator/generation/prepareTypeScriptOutput.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
import { format } from "../lib/format"; | ||
|
||
const header = `\ | ||
/* eslint-disable */ | ||
/* prettier-ignore */ | ||
/* This file is auto-generated with the remote-components-generator */ | ||
`; | ||
|
||
export const prepareTypeScriptOutput = async ( | ||
content: string, | ||
): Promise<string> => { | ||
const formatted = await format(content); | ||
return header + formatted; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
import prettier from "prettier"; | ||
import VError from "verror"; | ||
import { makeError } from "./makeError"; | ||
|
||
export const format = async (ts: string): Promise<string> => { | ||
try { | ||
return await prettier.format(ts, { | ||
plugins: [], | ||
parser: "typescript", | ||
}); | ||
} catch (error) { | ||
throw new VError( | ||
{ | ||
cause: makeError(error), | ||
name: "CodeFormattingError", | ||
}, | ||
"Failed to format the generated code. This usually happens, when the generated code has syntax errors. Please file an issue.", | ||
); | ||
} | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
export const kebabize = (str: string): string => | ||
str.replace( | ||
/[A-Z]+(?![a-z])|[A-Z]/g, | ||
($, ofs) => (ofs ? "-" : "") + $.toLowerCase(), | ||
); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
import VError from "verror"; | ||
import { getProperty } from "dot-prop"; | ||
|
||
export const makeError = (error: unknown): Error => | ||
error instanceof Error | ||
? error | ||
: new VError( | ||
{ | ||
name: getProperty(error, "name") ?? "Error", | ||
}, | ||
getProperty(error, "message") ?? "", | ||
); |
40 changes: 40 additions & 0 deletions
40
dev/remote-components-generator/loading/ComponentFileContentLoader.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
import { parseAsync } from "yieldable-json"; | ||
import VError from "verror"; | ||
import { makeError } from "../lib/makeError"; | ||
import type { FileContentLoader, FileLoader } from "./types"; | ||
import type { ComponentDoc } from "react-docgen-typescript"; | ||
|
||
export class ComponentFileContentLoader implements FileContentLoader { | ||
public readonly fileLoader: FileLoader; | ||
|
||
public constructor(fileLoader: FileLoader) { | ||
this.fileLoader = fileLoader; | ||
} | ||
|
||
public async load() { | ||
try { | ||
const fileContent = await this.fileLoader.loadFile(); | ||
return await this.parseJson(fileContent); | ||
} catch (error) { | ||
throw new VError( | ||
{ | ||
cause: makeError(error), | ||
name: "ComponentFileContentLoaderError", | ||
}, | ||
"Failed loading content", | ||
); | ||
} | ||
} | ||
|
||
public async parseJson(json: string): Promise<ComponentDoc[]> { | ||
return new Promise((res, rej) => { | ||
return parseAsync(json, (err: Error | null, data: unknown) => { | ||
if (err) { | ||
rej(err); | ||
} else { | ||
res(data as ComponentDoc[]); | ||
} | ||
}); | ||
}); | ||
} | ||
} |
26 changes: 26 additions & 0 deletions
26
dev/remote-components-generator/loading/ComponentFileLoader.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
import jetpack from "fs-jetpack"; | ||
import VError from "verror"; | ||
import { makeError } from "../lib/makeError"; | ||
import type { FileLoader } from "./types"; | ||
|
||
export class ComponentFileLoader implements FileLoader { | ||
public async loadFile(): Promise<string> { | ||
try { | ||
const file = await jetpack.readAsync( | ||
"./packages/components/out/doc-properties.json", | ||
); | ||
if (file === undefined || file === "") { | ||
throw new Error(`doc-properties.json file not found`); | ||
} | ||
return file; | ||
} catch (error) { | ||
throw new VError( | ||
{ | ||
cause: makeError(error), | ||
name: "ComponentFileLoaderError", | ||
}, | ||
"File loading failed", | ||
); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
import type { ComponentDoc } from "react-docgen-typescript"; | ||
|
||
export interface FileLoader { | ||
loadFile(): Promise<string>; | ||
} | ||
|
||
export interface FileContentLoader { | ||
parseJson(json: string): Promise<ComponentDoc[]>; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.