1
0
mirror of https://github.com/SquidDev-CC/CC-Tweaked synced 2025-08-29 16:47:56 +00:00

Use Preact for static rendering of components

We already use preact for the copy-cat integration, so it makes sense to
use it during the static pass too. This allows us to drop a dependency
on react.
This commit is contained in:
Jonathan Coates
2023-09-20 22:09:58 +01:00
parent 6c8b391dab
commit 3188197447
9 changed files with 61 additions and 136 deletions

View File

@@ -5,13 +5,14 @@
import { readFileSync } from "fs";
import path from "path";
import typescript from "@rollup/plugin-typescript";
import url from '@rollup/plugin-url';
import terser from "@rollup/plugin-terser";
import typescript from "@rollup/plugin-typescript";
import url from "@rollup/plugin-url";
const input = "src";
const requirejs = readFileSync("../../node_modules/requirejs/require.js");
/** @type import("rollup").RollupOptions */
export default {
input: [`${input}/index.tsx`],
output: {
@@ -54,7 +55,7 @@ export default {
async transform(code, file) {
// Allow loading files in /mount.
const ext = path.extname(file);
return ext != '.dfpwm' && path.dirname(file) === path.resolve(`${input}/mount`)
return ext != ".dfpwm" && path.dirname(file) === path.resolve(`${input}/mount`)
? `export default ${JSON.stringify(code)};\n`
: null;
},

View File

@@ -2,8 +2,7 @@
//
// SPDX-License-Identifier: MPL-2.0
import type { FunctionComponent } from "react";
import { createElement as h } from "react";
import { h, type FunctionComponent, type JSX } from "preact";
import useExport from "./WithExport";
const Item: FunctionComponent<{ item: string }> = ({ item }) => {

View File

@@ -2,7 +2,8 @@
//
// SPDX-License-Identifier: MPL-2.0
import { createElement as h, useContext, createContext, type FunctionComponent, type ReactNode } from "react";
import { h, createContext, type FunctionComponent, type VNode } from "preact";
import { useContext } from "preact/hooks";
export type DataExport = {
readonly itemNames: Record<string, string>,
@@ -23,5 +24,5 @@ const DataExport = createContext<DataExport>({
export const useExport = () => useContext(DataExport);
export default useExport;
export const WithExport: FunctionComponent<{ data: DataExport, children: ReactNode }> =
({ data, children }) => <DataExport.Provider value={data}> {children}</DataExport.Provider >;
export const WithExport: FunctionComponent<{ data: DataExport, children: VNode }> =
({ data, children }) => <DataExport.Provider value={data}> {children}</DataExport.Provider>;

View File

@@ -2,7 +2,7 @@
//
// SPDX-License-Identifier: MPL-2.0
import type { FunctionComponent } from "react";
import { type FunctionComponent } from "preact";
/**
* Wrap a component and ensure that no children are passed to it.
@@ -20,7 +20,7 @@ export const noChildren = function <T>(component: FunctionComponent<T>): Functio
const name = component.displayName ?? component.name;
const wrapped: FunctionComponent<T> = props => {
if ((props as any).children) throw Error("Unexpected children in " + name);
if (props.children) throw Error("Unexpected children in " + name);
return component(props);
};

View File

@@ -2,18 +2,18 @@
//
// SPDX-License-Identifier: MPL-2.0
import { render, h, Component, Computer, type PeripheralKind } from "copycat/embed";
import type { ComponentChild } from "preact";
import { Component, Computer, h, render, type PeripheralKind } from "copycat/embed";
import type { ComponentChild, FunctionalComponent } from "preact";
import settingsFile from "./mount/.settings";
import startupFile from "./mount/startup.lua";
import exprTemplate from "./mount/expr_template.lua";
import exampleAudioUrl from "./mount/example.dfpwm";
import exampleAudioLicense from "./mount/example.dfpwm.license";
import exampleNfp from "./mount/example.nfp";
import exampleNft from "./mount/example.nft";
import exampleAudioLicense from "./mount/example.dfpwm.license";
import exampleAudioUrl from "./mount/example.dfpwm";
import exprTemplate from "./mount/expr_template.lua";
import startupFile from "./mount/startup.lua";
const defaultFiles: { [filename: string]: string } = {
const defaultFiles: Record<string, string> = {
".settings": settingsFile,
"startup.lua": startupFile,
@@ -36,13 +36,13 @@ const download = async (url: string): Promise<Uint8Array> => {
let dfpwmAudio: Promise<Uint8Array> | null = null;
const Click = (options: { run: () => void }) =>
<button type="button" class="example-run" onClick={options.run}>Run </button>
const Click: FunctionalComponent<{ run: () => void }> = ({ run }) =>
<button type="button" class="example-run" onClick={run}>Run </button>
type WindowProps = {};
type Example = {
files: { [file: string]: string | Uint8Array },
files: Record<string, string | Uint8Array>,
peripheral: PeripheralKind | null,
}
@@ -105,8 +105,8 @@ class Window extends Component<WindowProps, WindowState> {
</div>
<div class="computer-container">
<Computer key={exampleIdx} files={{
...defaultFiles, ...example!.files,
}} peripherals={{ back: example!.peripheral }} />
...defaultFiles, ...example.files,
}} peripherals={{ back: example.peripheral }} />
</div>
</div>
</div> : <div class="example-window example-window-hidden" />;
@@ -120,7 +120,7 @@ class Window extends Component<WindowProps, WindowState> {
this.top = 20;
}
const files: { [file: string]: string | Uint8Array } = { "example.lua": example };
const files: Record<string, string | Uint8Array> = { "example.lua": example };
if (mount !== null) {
for (const toMount of mount.split(",")) {
const [name, path] = toMount.split(":", 2);

View File

@@ -10,20 +10,20 @@
*
* Yes, this would be so much nicer with next.js.
*/
import * as fs from "fs/promises";
import fs from "fs/promises";
import { glob } from "glob";
import * as path from "path";
import { createElement as h } from 'react';
import runtime from 'react/jsx-runtime';
import { renderToStaticMarkup } from "react-dom/server";
import rehypeHighlight from "rehype-highlight";
import rehypeParse from 'rehype-parse';
import rehypeReact, { type Options as ReactOptions } from 'rehype-react';
import { unified } from 'unified';
import path from "path";
import { h, type JSX } from "preact";
import renderToStaticMarkup from "preact-render-to-string";
import * as runtime from "preact/jsx-runtime";
import rehypeHighlight, { type Options as HighlightOptions } from "rehype-highlight";
import rehypeParse from "rehype-parse";
import rehypeReact, { type Options as ReactOptions } from "rehype-react";
import { unified, type Plugin } from "unified";
// Our components
import Recipe from "./components/Recipe";
import { noChildren } from "./components/support";
import { type DataExport, WithExport } from "./components/WithExport";
import { WithExport, type DataExport } from "./components/WithExport";
(async () => {
const base = "build/illuaminate";
@@ -31,19 +31,19 @@ import { type DataExport, WithExport } from "./components/WithExport";
const reactOptions: ReactOptions = {
...(runtime as ReactOptions),
components: {
['mc-recipe']: noChildren(Recipe),
['mcrecipe']: noChildren(Recipe),
["mc-recipe"]: noChildren(Recipe),
["mcrecipe"]: noChildren(Recipe),
// Wrap example snippets in a <div class="lua-example">...</div>, so we can inject a
// Run button into them.
['pre']: (args: JSX.IntrinsicElements["pre"] & { "data-lua-kind"?: undefined }) => {
["pre"]: (args: JSX.IntrinsicElements["pre"] & { "data-lua-kind"?: undefined }) => {
const element = <pre {...args} />;
return args["data-lua-kind"] ? <div className="lua-example">{element}</div> : element
}
} as any
}
};
const processor = unified()
.use(rehypeParse, { emitParseErrors: true })
.use(rehypeHighlight, { prefix: "" })
.use(rehypeHighlight as unknown as Plugin<[HighlightOptions], import("hast").Root>, { prefix: "" })
.use(rehypeReact, reactOptions);
const dataExport = JSON.parse(await fs.readFile("src/export/index.json", "utf-8")) as DataExport;