Skip to content

Commit

Permalink
refactor: isolate remix specifics in cli
Browse files Browse the repository at this point in the history
We wanna support other frameworks than remix.
At least to support SSG we need to additionally deploy vike.dev.

Here split all remix specifics into separate module.
It provides the list of components (including overrides)
and templates getter.

Templates allow to provide multiple module per route which is essential
for many frameworks like svelte and vike which do not rely on
treeshaking for client/server splitting.
  • Loading branch information
TrySound committed Jul 17, 2024
1 parent b0726f1 commit 94526c4
Show file tree
Hide file tree
Showing 3 changed files with 157 additions and 91 deletions.
90 changes: 90 additions & 0 deletions packages/cli/src/framework-remix.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import { join } from "node:path";
import { readFile, rm } from "node:fs/promises";
import {
generateRemixRoute,
namespaceMeta,
WsComponentMeta,
} from "@webstudio-is/react-sdk";
import * as baseComponentMetas from "@webstudio-is/sdk-components-react/metas";
import * as remixComponentMetas from "@webstudio-is/sdk-components-react-remix/metas";
import * as radixComponentMetas from "@webstudio-is/sdk-components-react-radix/metas";
import type { Framework } from "./framework";

export const createFramework = async (): Promise<Framework> => {
const routeTemplatesDir = join("app", "route-templates");

const htmlTemplate = await readFile(
join(routeTemplatesDir, "html.tsx"),
"utf8"
);
const xmlTemplate = await readFile(
join(routeTemplatesDir, "xml.tsx"),
"utf8"
);
const defaultSitemapTemplate = await readFile(
join(routeTemplatesDir, "default-sitemap.tsx"),
"utf8"
);
const redirectTemplate = await readFile(
join(routeTemplatesDir, "redirect.tsx"),
"utf8"
);

// cleanup route templates after reading to not bloat generated code
await rm(routeTemplatesDir, { recursive: true, force: true });

const radixComponentNamespacedMetas: Record<string, WsComponentMeta> = {};
for (const [name, meta] of Object.entries(radixComponentMetas)) {
const namespace = "@webstudio-is/sdk-components-react-radix";
radixComponentNamespacedMetas[`${namespace}:${name}`] = namespaceMeta(
meta,
namespace,
new Set(Object.keys(radixComponentMetas))
);
}

return {
components: [
{
source: "@webstudio-is/sdk-components-react",
metas: baseComponentMetas,
},
{
source: "@webstudio-is/sdk-components-react-radix",
metas: radixComponentNamespacedMetas,
},
{
source: "@webstudio-is/sdk-components-react-remix",
metas: remixComponentMetas,
},
],
html: ({ pagePath }: { pagePath: string }) => [
{
file: join("app", "routes", `${generateRemixRoute(pagePath)}.tsx`),
template: htmlTemplate,
},
],
xml: ({ pagePath }: { pagePath: string }) => [
{
file: join("app", "routes", `${generateRemixRoute(pagePath)}.tsx`),
template: xmlTemplate,
},
],
redirect: ({ pagePath }: { pagePath: string }) => [
{
file: join("app", "routes", `${generateRemixRoute(pagePath)}.ts`),
template: redirectTemplate,
},
],
defaultSitemap: () => [
{
file: join(
"app",
"routes",
`${generateRemixRoute("/sitemap.xml")}.tsx`
),
template: defaultSitemapTemplate,
},
],
};
};
19 changes: 19 additions & 0 deletions packages/cli/src/framework.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import type { WsComponentMeta } from "@webstudio-is/react-sdk";

type FrameworkComponentEntry = {
source: string;
metas: Record<string, WsComponentMeta>;
};

type FrameworkTemplateEntry = {
file: string;
template: string;
};

export type Framework = {
components: FrameworkComponentEntry[];
html: (params: { pagePath: string }) => FrameworkTemplateEntry[];
xml: (params: { pagePath: string }) => FrameworkTemplateEntry[];
redirect: (params: { pagePath: string }) => FrameworkTemplateEntry[];
defaultSitemap: () => FrameworkTemplateEntry[];
};
139 changes: 48 additions & 91 deletions packages/cli/src/prebuild.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { basename, dirname, join, normalize } from "node:path";
import { basename, dirname, join, normalize, relative } from "node:path";
import { createWriteStream } from "node:fs";
import {
rm,
Expand All @@ -19,7 +19,6 @@ import {
generateCss,
generateWebstudioComponent,
getIndexesWithinAncestors,
namespaceMeta,
type Params,
type WsComponentMeta,
normalizeProps,
Expand Down Expand Up @@ -50,9 +49,6 @@ import {
} from "@webstudio-is/sdk";
import type { Data } from "@webstudio-is/http-client";
import { createImageLoader } from "@webstudio-is/image";
import * as baseComponentMetas from "@webstudio-is/sdk-components-react/metas";
import * as remixComponentMetas from "@webstudio-is/sdk-components-react-remix/metas";
import * as radixComponentMetas from "@webstudio-is/sdk-components-react-radix/metas";
import { LOCAL_DATA_FILE } from "./config";
import {
createFileIfNotExists,
Expand All @@ -62,6 +58,7 @@ import {
} from "./fs-utils";
import type * as sharedConstants from "../templates/defaults/app/constants.mjs";
import { htmlToJsx } from "./html-to-jsx";
import { createFramework } from "./framework-remix";

const limit = pLimit(10);

Expand Down Expand Up @@ -263,6 +260,8 @@ export const prebuild = async (options: {
await copyTemplates(template);
}

const framework = await createFramework();

const constants: typeof sharedConstants = await import(
pathToFileURL(join(cwd(), "app/constants.mjs")).href
);
Expand All @@ -284,28 +283,15 @@ export const prebuild = async (options: {
throw new Error(`Project domain is missing from the project data`);
}

const radixComponentNamespacedMetas = Object.entries(
radixComponentMetas
).reduce(
(r, [name, meta]) => {
const namespace = "@webstudio-is/sdk-components-react-radix";
r[`${namespace}:${name}`] = namespaceMeta(
meta,
namespace,
new Set(Object.keys(radixComponentMetas))
);
return r;
},
{} as Record<string, WsComponentMeta>
);

const metas = new Map(
Object.entries({
...baseComponentMetas,
...radixComponentNamespacedMetas,
...remixComponentMetas,
})
);
// collect all possible component metas
const metas = new Map<string, WsComponentMeta>();
const componentSources = new Map<string, string>();
for (const entry of framework.components) {
for (const [componentName, meta] of Object.entries(entry.metas)) {
metas.set(componentName, meta);
componentSources.set(componentName, entry.source);
}
}

const projectMetas = new Map<Instance["component"], WsComponentMeta>();
const componentsByPage: ComponentsByPage = {};
Expand Down Expand Up @@ -494,22 +480,6 @@ export const prebuild = async (options: {

await createFileIfNotExists(join(generatedDir, "index.css"), cssText);

// MARK: - Route templates read
const routeTemplatesDir = join(cwd(), "app/route-templates");

const routeTemplatePath = normalize(join(routeTemplatesDir, "html.tsx"));
const routeXmlTemplatePath = normalize(join(routeTemplatesDir, "xml.tsx"));
const defaultSiteMapXmlPath = normalize(
join(routeTemplatesDir, "default-sitemap.tsx")
);
const redirectPath = normalize(join(routeTemplatesDir, "redirect.tsx"));

const routeFileTemplate = await readFile(routeTemplatePath, "utf8");
const routeXmlFileTemplate = await readFile(routeXmlTemplatePath, "utf8");
const defaultSiteMapTemplate = await readFile(defaultSiteMapXmlPath, "utf8");
const redirectTemplate = await readFile(redirectPath, "utf8");
await rm(routeTemplatesDir, { recursive: true, force: true });

for (const [pageId, pageComponents] of Object.entries(componentsByPage)) {
const scope = createScope([
// manually maintained list of occupied identifiers
Expand All @@ -524,30 +494,18 @@ export const prebuild = async (options: {
string,
Set<[shortName: string, componentName: string]>
>();

const BASE_NAMESPACE = "@webstudio-is/sdk-components-react";
const REMIX_NAMESPACE = "@webstudio-is/sdk-components-react-remix";

for (const component of pageComponents) {
const parsed = parseComponentName(component);
let [namespace] = parsed;
const [_namespace, shortName] = parsed;

const namespace = componentSources.get(component);
if (namespace === undefined) {
// use base as fallback namespace and consider remix overrides
if (shortName in remixComponentMetas) {
namespace = REMIX_NAMESPACE;
} else {
namespace = BASE_NAMESPACE;
}
continue;
}

if (namespaces.has(namespace) === false) {
namespaces.set(
namespace,
new Set<[shortName: string, componentName: string]>()
);
}
const [_namespace, shortName] = parseComponentName(component);
namespaces.get(namespace)?.add([shortName, component]);
}

Expand Down Expand Up @@ -728,37 +686,33 @@ export const prebuild = async (options: {

const generatedBasename = generateRemixRoute(pagePath);

const routeFileContent = (
documentType === "html" ? routeFileTemplate : routeXmlFileTemplate
)
.replaceAll("__CLIENT__", `../__generated__/${generatedBasename}`)
.replaceAll("__SERVER__", `../__generated__/${generatedBasename}.server`)
.replaceAll("__CSS__", `../__generated__/index.css`);

await createFileIfNotExists(
join(routesDir, `${generateRemixRoute(pagePath)}.tsx`),
routeFileContent
);

await createFileIfNotExists(
join(generatedDir, `${generatedBasename}.tsx`),
pageExports
);

await createFileIfNotExists(
join(generatedDir, `${generatedBasename}.server.tsx`),
serverExports
);
const clientFile = join(generatedDir, `${generatedBasename}.tsx`);
await createFileIfNotExists(clientFile, pageExports);

const serverFile = join(generatedDir, `${generatedBasename}.server.tsx`);
await createFileIfNotExists(serverFile, serverExports);

const getTemplates =
documentType === "html" ? framework.html : framework.xml;
for (const { file, template } of getTemplates({ pagePath })) {
const base = relative(dirname(file), generatedDir);
const content = template
.replaceAll("__CLIENT__", `${base}/${generatedBasename}`)
.replaceAll("__SERVER__", `${base}/${generatedBasename}.server`)
.replaceAll("__CSS__", `${base}/index.css`);
await createFileIfNotExists(file, content);
}
}

// MARK: - Default sitemap.xml
await createFileIfNotExists(
join(routesDir, `${generateRemixRoute("/sitemap.xml")}.tsx`),
defaultSiteMapTemplate.replaceAll(
for (const { file, template } of framework.defaultSitemap()) {
const base = relative(dirname(file), generatedDir);
const content = template.replaceAll(
"__SITEMAP__",
`../__generated__/$resources.sitemap.xml`
)
);
`${base}/$resources.sitemap.xml`
);
await createFileIfNotExists(file, content);
}

await createFileIfNotExists(
join(generatedDir, "$resources.sitemap.xml.ts"),
Expand All @@ -783,13 +737,16 @@ export const prebuild = async (options: {
`
);

await createFileIfNotExists(
join(routesDir, `${generateRemixRoute(redirect.old)}.ts`),
redirectTemplate.replaceAll(
for (const { file, template } of framework.redirect({
pagePath: redirect.old,
})) {
const base = relative(dirname(file), generatedDir);
const content = template.replaceAll(
"__REDIRECT__",
`../__generated__/${generatedBasename}`
)
);
`${base}/${generatedBasename}`
);
await createFileIfNotExists(file, content);
}
}
}

Expand Down

0 comments on commit 94526c4

Please sign in to comment.