@reckona/mreact-next 0.0.66 → 0.0.67
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +4 -3
- package/src/cli.ts +12 -0
- package/src/index.ts +166 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@reckona/mreact-next",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.67",
|
|
4
4
|
"description": "Experimental helpers for consuming compiled mreact components from Next.js.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"compiler",
|
|
@@ -24,7 +24,8 @@
|
|
|
24
24
|
"dist/**/*.js",
|
|
25
25
|
"dist/**/*.js.map",
|
|
26
26
|
"dist/**/*.d.ts",
|
|
27
|
-
"dist/**/*.d.ts.map"
|
|
27
|
+
"dist/**/*.d.ts.map",
|
|
28
|
+
"src/**/*"
|
|
28
29
|
],
|
|
29
30
|
"type": "module",
|
|
30
31
|
"sideEffects": false,
|
|
@@ -39,6 +40,6 @@
|
|
|
39
40
|
"access": "public"
|
|
40
41
|
},
|
|
41
42
|
"dependencies": {
|
|
42
|
-
"@reckona/mreact-compiler": "0.0.
|
|
43
|
+
"@reckona/mreact-compiler": "0.0.67"
|
|
43
44
|
}
|
|
44
45
|
}
|
package/src/cli.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { resolve } from "node:path";
|
|
4
|
+
import {
|
|
5
|
+
formatGeneratedMreactComponents,
|
|
6
|
+
generateMreactComponents,
|
|
7
|
+
} from "./index.js";
|
|
8
|
+
|
|
9
|
+
const rootDir = resolve(process.argv[2] ?? "app");
|
|
10
|
+
const generated = await generateMreactComponents({ rootDir });
|
|
11
|
+
|
|
12
|
+
console.log(formatGeneratedMreactComponents(generated, rootDir));
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import { readdir, readFile, writeFile } from "node:fs/promises";
|
|
2
|
+
import { basename, dirname, join, relative } from "node:path";
|
|
3
|
+
import { transform } from "@reckona/mreact-compiler";
|
|
4
|
+
|
|
5
|
+
export interface GenerateMreactComponentsOptions {
|
|
6
|
+
rootDir: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface GeneratedMreactComponent {
|
|
10
|
+
source: string;
|
|
11
|
+
output: string;
|
|
12
|
+
domOutput: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface CompiledMreactComponentModule {
|
|
16
|
+
wrapperCode: string;
|
|
17
|
+
domCode: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export async function generateMreactComponents(
|
|
21
|
+
options: GenerateMreactComponentsOptions,
|
|
22
|
+
): Promise<GeneratedMreactComponent[]> {
|
|
23
|
+
const sources = await findMreactSources(options.rootDir);
|
|
24
|
+
const generated: GeneratedMreactComponent[] = [];
|
|
25
|
+
|
|
26
|
+
for (const source of sources) {
|
|
27
|
+
const code = await readFile(source, "utf8");
|
|
28
|
+
const output = outputPathForSource(source);
|
|
29
|
+
const domOutput = domOutputPathForSource(source);
|
|
30
|
+
const generatedCode = compileMreactComponentModule(code, source, {
|
|
31
|
+
domImportPath: importPathForGeneratedModule(output, domOutput),
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
await writeFile(output, generatedCode.wrapperCode);
|
|
35
|
+
await writeFile(domOutput, generatedCode.domCode);
|
|
36
|
+
generated.push({ source, output, domOutput });
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return generated;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface CompileMreactComponentModuleOptions {
|
|
43
|
+
domImportPath: string;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function compileMreactComponentModule(
|
|
47
|
+
code: string,
|
|
48
|
+
filename: string,
|
|
49
|
+
options: CompileMreactComponentModuleOptions,
|
|
50
|
+
): CompiledMreactComponentModule {
|
|
51
|
+
const compiled = transform({
|
|
52
|
+
code,
|
|
53
|
+
filename,
|
|
54
|
+
target: "client",
|
|
55
|
+
dev: true,
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
if (compiled.metadata.components.length === 0) {
|
|
59
|
+
throw new Error(`${filename} must export at least one mreact JSX component.`);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const components = compiled.metadata.components
|
|
63
|
+
.filter((component) => hasCompiledExport(compiled.code, component.name, component.exportName))
|
|
64
|
+
.map((component) => ({
|
|
65
|
+
name: component.name,
|
|
66
|
+
exportName: component.exportName,
|
|
67
|
+
moduleAccess:
|
|
68
|
+
component.exportName === "default" ? "module.default" : `module.${component.name}`,
|
|
69
|
+
}));
|
|
70
|
+
|
|
71
|
+
if (components.length === 0) {
|
|
72
|
+
throw new Error(`${filename} must export at least one mreact JSX component.`);
|
|
73
|
+
}
|
|
74
|
+
const wrappers = components
|
|
75
|
+
.map(
|
|
76
|
+
(component) => `const ${component.name}$mounted = new WeakMap<Element, Node>();
|
|
77
|
+
const ${component.name}$mounting = new WeakSet<Element>();
|
|
78
|
+
${emitExportFunction(component.exportName, component.name)}(props: Record<string, unknown>): never {
|
|
79
|
+
return (
|
|
80
|
+
<span
|
|
81
|
+
data-mreact-component=${JSON.stringify(component.name)}
|
|
82
|
+
ref={(node: Element | null) => {
|
|
83
|
+
if (node === null || ${component.name}$mounted.has(node) || ${component.name}$mounting.has(node)) {
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
${component.name}$mounting.add(node);
|
|
88
|
+
void import(${JSON.stringify(options.domImportPath)}).then((module) => {
|
|
89
|
+
const rendered = (${component.moduleAccess} as (props: Record<string, unknown>) => Node)(props);
|
|
90
|
+
node.replaceChildren(rendered);
|
|
91
|
+
${component.name}$mounted.set(node, rendered);
|
|
92
|
+
});
|
|
93
|
+
}}
|
|
94
|
+
/>
|
|
95
|
+
) as never;
|
|
96
|
+
}`,
|
|
97
|
+
)
|
|
98
|
+
.join("\n\n");
|
|
99
|
+
|
|
100
|
+
return {
|
|
101
|
+
wrapperCode: `// @ts-nocheck
|
|
102
|
+
"use client";
|
|
103
|
+
${wrappers}
|
|
104
|
+
`,
|
|
105
|
+
domCode: `// @ts-nocheck
|
|
106
|
+
${compiled.code}`,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
async function findMreactSources(rootDir: string): Promise<string[]> {
|
|
111
|
+
const entries = await readdir(rootDir, { withFileTypes: true });
|
|
112
|
+
const sources: string[] = [];
|
|
113
|
+
|
|
114
|
+
for (const entry of entries) {
|
|
115
|
+
const path = join(rootDir, entry.name);
|
|
116
|
+
|
|
117
|
+
if (entry.isDirectory()) {
|
|
118
|
+
sources.push(...(await findMreactSources(path)));
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (entry.isFile() && entry.name.endsWith(".mreact.tsx")) {
|
|
123
|
+
sources.push(path);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return sources.sort();
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function outputPathForSource(source: string): string {
|
|
131
|
+
return join(dirname(source), `${basename(source, ".mreact.tsx")}.tsx`);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function domOutputPathForSource(source: string): string {
|
|
135
|
+
return join(dirname(source), `${basename(source, ".mreact.tsx")}.mreact-dom.ts`);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function importPathForGeneratedModule(from: string, to: string): string {
|
|
139
|
+
const extensionless = relative(dirname(from), to).replace(/\.ts$/, "");
|
|
140
|
+
|
|
141
|
+
return extensionless.startsWith(".") ? extensionless : `./${extensionless}`;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function emitExportFunction(exportName: string, name: string): string {
|
|
145
|
+
return exportName === "default" ? `export default function ${name}` : `export function ${name}`;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function hasCompiledExport(code: string, name: string, exportName: string): boolean {
|
|
149
|
+
return code.includes(`${emitExportFunction(exportName, name)}(`);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export function formatGeneratedMreactComponents(
|
|
153
|
+
generated: readonly GeneratedMreactComponent[],
|
|
154
|
+
rootDir: string,
|
|
155
|
+
): string {
|
|
156
|
+
if (generated.length === 0) {
|
|
157
|
+
return "No .mreact.tsx components found.";
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return generated
|
|
161
|
+
.map(
|
|
162
|
+
(item) =>
|
|
163
|
+
`${relative(rootDir, item.source)} -> ${relative(rootDir, item.output)}, ${relative(rootDir, item.domOutput)}`,
|
|
164
|
+
)
|
|
165
|
+
.join("\n");
|
|
166
|
+
}
|