1
0
mirror of https://github.com/SquidDev-CC/CC-Tweaked synced 2025-01-12 10:20:28 +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
No known key found for this signature in database
GPG Key ID: B9E431FF07C98D06
9 changed files with 61 additions and 136 deletions

112
package-lock.json generated
View File

@ -17,10 +17,8 @@
"@rollup/plugin-typescript": "^11.0.0",
"@rollup/plugin-url": "^8.0.1",
"@types/glob": "^8.1.0",
"@types/react-dom": "^18.0.5",
"glob": "^10.3.4",
"react": "^18.1.0",
"react-dom": "^18.1.0",
"preact-render-to-string": "^6.2.1",
"rehype": "^13.0.0",
"rehype-highlight": "^7.0.0",
"rehype-react": "^8.0.0",
@ -635,38 +633,6 @@
"integrity": "sha512-Y+/1vGBHV/cYk6OI1Na/LHzwnlNCAfU3ZNGrc1LdRe/LAIbdDPTTv/HU3M7yXN448aTVDq3eKRm2cg7iKLb8gw==",
"dev": true
},
"node_modules/@types/prop-types": {
"version": "15.7.5",
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz",
"integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==",
"dev": true
},
"node_modules/@types/react": {
"version": "18.2.21",
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.21.tgz",
"integrity": "sha512-neFKG/sBAwGxHgXiIxnbm3/AAVQ/cMRS93hvBpg8xYRbeQSPVABp9U2bRnPf0iI4+Ucdv3plSxKK+3CW2ENJxA==",
"dev": true,
"dependencies": {
"@types/prop-types": "*",
"@types/scheduler": "*",
"csstype": "^3.0.2"
}
},
"node_modules/@types/react-dom": {
"version": "18.2.7",
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.2.7.tgz",
"integrity": "sha512-GRaAEriuT4zp9N4p1i8BDBYmEyfo+xQ3yHjJU4eiK5NDa1RmUZG+unZABUTK4/Ox/M+GaHwb6Ow8rUITrtjszA==",
"dev": true,
"dependencies": {
"@types/react": "*"
}
},
"node_modules/@types/scheduler": {
"version": "0.16.3",
"resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.3.tgz",
"integrity": "sha512-5cJ8CB4yAx7BH1oMvdU0Jh9lrEXyPkar6F9G/ERswkCuvP4KQZfZkSjcMbAICCpQTN4OuZn8tz0HiKv9TGZgrQ==",
"dev": true
},
"node_modules/@types/unist": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.0.tgz",
@ -824,12 +790,6 @@
"node": ">= 8"
}
},
"node_modules/csstype": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz",
"integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==",
"dev": true
},
"node_modules/dequal": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz",
@ -1288,24 +1248,6 @@
"@pkgjs/parseargs": "^0.11.0"
}
},
"node_modules/js-tokens": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
"dev": true
},
"node_modules/loose-envify": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
"integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
"dev": true,
"dependencies": {
"js-tokens": "^3.0.0 || ^4.0.0"
},
"bin": {
"loose-envify": "cli.js"
}
},
"node_modules/lowlight": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/lowlight/-/lowlight-3.0.0.tgz",
@ -1554,6 +1496,24 @@
"url": "https://opencollective.com/preact"
}
},
"node_modules/preact-render-to-string": {
"version": "6.2.1",
"resolved": "https://registry.npmjs.org/preact-render-to-string/-/preact-render-to-string-6.2.1.tgz",
"integrity": "sha512-5t7nFeMUextd53igL3GAakAAMaUD+dVWDHaRYaeh1tbPIjQIBtgJnMw6vf8VS/lviV0ggFtkgebatPxvtJsXyQ==",
"dev": true,
"dependencies": {
"pretty-format": "^3.8.0"
},
"peerDependencies": {
"preact": ">=10"
}
},
"node_modules/pretty-format": {
"version": "3.8.0",
"resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-3.8.0.tgz",
"integrity": "sha512-WuxUnVtlWL1OfZFQFuqvnvs6MiAGk9UNsBostyBOB0Is9wb5uRESevA6rnl/rkksXaGX3GzZhPup5d6Vp1nFew==",
"dev": true
},
"node_modules/property-information": {
"version": "6.3.0",
"resolved": "https://registry.npmjs.org/property-information/-/property-information-6.3.0.tgz",
@ -1573,31 +1533,6 @@
"safe-buffer": "^5.1.0"
}
},
"node_modules/react": {
"version": "18.2.0",
"resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz",
"integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==",
"dev": true,
"dependencies": {
"loose-envify": "^1.1.0"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/react-dom": {
"version": "18.2.0",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz",
"integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==",
"dev": true,
"dependencies": {
"loose-envify": "^1.1.0",
"scheduler": "^0.23.0"
},
"peerDependencies": {
"react": "^18.2.0"
}
},
"node_modules/rehype": {
"version": "13.0.1",
"resolved": "https://registry.npmjs.org/rehype/-/rehype-13.0.1.tgz",
@ -1751,15 +1686,6 @@
}
]
},
"node_modules/scheduler": {
"version": "0.23.0",
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz",
"integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==",
"dev": true,
"dependencies": {
"loose-envify": "^1.1.0"
}
},
"node_modules/semver": {
"version": "6.3.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",

View File

@ -14,13 +14,11 @@
"@rollup/plugin-typescript": "^11.0.0",
"@rollup/plugin-url": "^8.0.1",
"@types/glob": "^8.1.0",
"@types/react-dom": "^18.0.5",
"glob": "^10.3.4",
"react-dom": "^18.1.0",
"react": "^18.1.0",
"preact-render-to-string": "^6.2.1",
"rehype": "^13.0.0",
"rehype-highlight": "^7.0.0",
"rehype-react": "^8.0.0",
"rehype": "^13.0.0",
"requirejs": "^2.3.6",
"rollup": "^3.19.1",
"tsx": "^3.12.10",

View File

@ -687,7 +687,7 @@ settings.define("paint.default_extension", {
settings.define("list.show_hidden", {
default = false,
description = [[Show hidden files (those starting with "." in the Lua REPL).]],
description = [[Whether the list program show hidden files (those starting with ".").]],
type = "boolean",
})

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;