create-mithril-lynx 0.0.1

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 carlos-sweb
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,43 @@
1
+ # create-mithril-lynx
2
+
3
+ Scaffolds a new [`mithril-lynx`](https://github.com/carlos-sweb/mithril-lynx) app — the Mithril.js analog of Lynx's official React hello-world template ([`lynx-examples/examples/hello-world`](https://github.com/lynx-family/lynx-examples/tree/main/examples/hello-world)). Same layout, same "tap the logo" interaction, running through Mithril hyperscript instead of JSX.
4
+
5
+ ## Usage
6
+
7
+ ```bash
8
+ npm create mithril-lynx@latest
9
+ ```
10
+
11
+ Prompts for a project name and a variant (TypeScript or JavaScript), scaffolds it, and offers to install dependencies for you.
12
+
13
+ ### Non-interactive
14
+
15
+ ```bash
16
+ npx create-mithril-lynx my-app --ts
17
+ npx create-mithril-lynx my-app --js --no-install
18
+ ```
19
+
20
+ ## What it scaffolds
21
+
22
+ | Variant | Adds over the base template |
23
+ |---|---|
24
+ | **TypeScript** (recommended) | `.ts` source files, `tsconfig.json` (project references, matching `@lynx-js/rspeedy`'s own generated config), `@rsbuild/plugin-type-check`, `@types/mithril` for real editor autocomplete on `m()` calls. |
25
+ | **JavaScript** | Same app, plain `.js` with ES module `import`/`export` — no type-checking, no `tsconfig.json`. |
26
+
27
+ Both variants share `template-common/`: the logo/arrow image assets, `style.css`, `.gitignore`, and the project's own `README.md`.
28
+
29
+ ## Package structure
30
+
31
+ ```
32
+ create-mithril-lynx/
33
+ src/index.js the CLI itself
34
+ template-common/ shared across both variants
35
+ template-js/ JavaScript-only files
36
+ template-ts/ TypeScript-only files
37
+ ```
38
+
39
+ `src/index.js` copies `template-common/` then `template-<variant>/` into the target directory, renames `gitignore` to `.gitignore` (npm doesn't publish dotfiles reliably otherwise), and replaces `{{PROJECT_NAME}}`/`{{MITHRIL_LYNX_VERSION}}` placeholders in `package.json` and `README.md`.
40
+
41
+ ## License
42
+
43
+ MIT
package/package.json ADDED
@@ -0,0 +1,30 @@
1
+ {
2
+ "name": "create-mithril-lynx",
3
+ "version": "0.0.1",
4
+ "description": "Scaffolds a new mithril-lynx app — the Mithril.js analog of Lynx's official React hello-world template.",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/carlos-sweb/create-mithril-lynx.git"
10
+ },
11
+ "homepage": "https://github.com/carlos-sweb/create-mithril-lynx#readme",
12
+ "bugs": {
13
+ "url": "https://github.com/carlos-sweb/create-mithril-lynx/issues"
14
+ },
15
+ "bin": {
16
+ "create-mithril-lynx": "src/index.js"
17
+ },
18
+ "files": [
19
+ "src",
20
+ "template-common",
21
+ "template-js",
22
+ "template-ts"
23
+ ],
24
+ "engines": {
25
+ "node": "^20.19.0 || >=22.12.0"
26
+ },
27
+ "dependencies": {
28
+ "@clack/prompts": "^1.8.0"
29
+ }
30
+ }
package/src/index.js ADDED
@@ -0,0 +1,149 @@
1
+ #!/usr/bin/env node
2
+ import fs from "node:fs";
3
+ import path from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+ import { execSync } from "node:child_process";
6
+
7
+ import { cancel, confirm, intro, isCancel, outro, select, text } from "@clack/prompts";
8
+
9
+ const scriptDir = path.dirname(fileURLToPath(import.meta.url));
10
+ const packageRoot = path.join(scriptDir, "..");
11
+ const cwd = process.cwd();
12
+
13
+ const MITHRIL_LYNX_VERSION = "0.0.1";
14
+
15
+ function isValidPackageName(name) {
16
+ return /^(?:@[a-z0-9-*~][a-z0-9-*._~]*\/)?[a-z0-9-~][a-z0-9-._~]*$/.test(name);
17
+ }
18
+
19
+ function copyDir(from, to) {
20
+ fs.mkdirSync(to, { recursive: true });
21
+ for (const entry of fs.readdirSync(from, { withFileTypes: true })) {
22
+ const src = path.join(from, entry.name);
23
+ const dest = path.join(to, entry.name);
24
+ if (entry.isDirectory()) copyDir(src, dest);
25
+ else fs.copyFileSync(src, dest);
26
+ }
27
+ }
28
+
29
+ function replaceInFile(filePath, replacements) {
30
+ if (!fs.existsSync(filePath)) return;
31
+ let content = fs.readFileSync(filePath, "utf8");
32
+ for (const [from, to] of replacements) content = content.split(from).join(to);
33
+ fs.writeFileSync(filePath, content);
34
+ }
35
+
36
+ function targetDirHasConflict(value) {
37
+ const targetDir = path.join(cwd, value);
38
+ return fs.existsSync(targetDir) && fs.readdirSync(targetDir).length > 0;
39
+ }
40
+
41
+ async function main() {
42
+ // Non-interactive escape hatch for scripting/CI:
43
+ // create-mithril-lynx my-app --ts
44
+ // create-mithril-lynx my-app --js --no-install
45
+ const args = process.argv.slice(2);
46
+ const positional = args.find((a) => !a.startsWith("-"));
47
+ const variantFlag = args.includes("--ts") ? "ts" : args.includes("--js") ? "js" : undefined;
48
+ const noInstall = args.includes("--no-install");
49
+ const nonInteractive = positional != null && variantFlag != null;
50
+
51
+ intro("create-mithril-lynx");
52
+
53
+ let rawName;
54
+ if (positional != null) {
55
+ rawName = positional;
56
+ if (targetDirHasConflict(rawName)) {
57
+ cancel(`Directory "${rawName}" already exists and is not empty.`);
58
+ process.exit(1);
59
+ }
60
+ } else {
61
+ rawName = await text({
62
+ message: "Project name",
63
+ placeholder: "my-mithril-app",
64
+ validate(value) {
65
+ if (!value) return "Please enter a project name.";
66
+ if (targetDirHasConflict(value)) return "Directory already exists and is not empty.";
67
+ },
68
+ });
69
+ if (isCancel(rawName)) return bail();
70
+ }
71
+
72
+ const projectName = isValidPackageName(rawName)
73
+ ? rawName
74
+ : rawName.trim().toLowerCase().replace(/\s+/g, "-").replace(/[^a-z0-9-~]/g, "-");
75
+
76
+ let variant = variantFlag;
77
+ if (variant == null) {
78
+ variant = await select({
79
+ message: "Select a variant",
80
+ options: [
81
+ { value: "ts", label: "TypeScript", hint: "recommended" },
82
+ { value: "js", label: "JavaScript" },
83
+ ],
84
+ });
85
+ if (isCancel(variant)) return bail();
86
+ }
87
+
88
+ const targetDir = path.join(cwd, rawName);
89
+ fs.mkdirSync(targetDir, { recursive: true });
90
+
91
+ copyDir(path.join(packageRoot, "template-common"), targetDir);
92
+ copyDir(path.join(packageRoot, `template-${variant}`), targetDir);
93
+
94
+ const gitignorePath = path.join(targetDir, "gitignore");
95
+ if (fs.existsSync(gitignorePath)) {
96
+ fs.renameSync(gitignorePath, path.join(targetDir, ".gitignore"));
97
+ }
98
+
99
+ replaceInFile(path.join(targetDir, "package.json"), [
100
+ ["{{PROJECT_NAME}}", projectName],
101
+ ["{{MITHRIL_LYNX_VERSION}}", MITHRIL_LYNX_VERSION],
102
+ ]);
103
+ replaceInFile(path.join(targetDir, "README.md"), [["{{PROJECT_NAME}}", projectName]]);
104
+
105
+ let shouldInstall = !noInstall;
106
+ if (!nonInteractive && !noInstall) {
107
+ shouldInstall = await confirm({
108
+ message: "Install dependencies now?",
109
+ initialValue: true,
110
+ });
111
+ if (isCancel(shouldInstall)) return bail();
112
+ }
113
+
114
+ if (shouldInstall) {
115
+ const manager = detectPackageManager();
116
+ try {
117
+ execSync(`${manager} install`, { cwd: targetDir, stdio: "inherit" });
118
+ } catch {
119
+ outro(`Dependency install failed — run "${manager} install" yourself inside ${rawName}/.`);
120
+ return;
121
+ }
122
+ }
123
+
124
+ const relativeDir = path.relative(cwd, targetDir) || ".";
125
+ const steps = [
126
+ `cd ${relativeDir}`,
127
+ ...(shouldInstall ? [] : ["npm install"]),
128
+ "npm run dev",
129
+ ];
130
+ outro(`Done! Next steps:\n\n ${steps.join("\n ")}\n\nThen scan the printed QR code with LynxExplorer.`);
131
+ }
132
+
133
+ function detectPackageManager() {
134
+ const userAgent = process.env.npm_config_user_agent ?? "";
135
+ if (userAgent.startsWith("bun")) return "bun";
136
+ if (userAgent.startsWith("pnpm")) return "pnpm";
137
+ if (userAgent.startsWith("yarn")) return "yarn";
138
+ return "npm";
139
+ }
140
+
141
+ function bail() {
142
+ cancel("Cancelled.");
143
+ process.exit(0);
144
+ }
145
+
146
+ main().catch((error) => {
147
+ console.error(error);
148
+ process.exit(1);
149
+ });
@@ -0,0 +1,17 @@
1
+ # {{PROJECT_NAME}}
2
+
3
+ A [Lynx](https://lynxjs.org) app built with [Mithril.js](https://mithril.js.org), via [`mithril-lynx`](https://github.com/carlos-sweb/mithril-lynx).
4
+
5
+ ## Getting started
6
+
7
+ ```bash
8
+ npm install
9
+ npm run dev # scan the printed QR code with LynxExplorer
10
+ npm run build # production bundle, in dist/
11
+ ```
12
+
13
+ ## Learn more
14
+
15
+ - `src/main-thread.{js,ts}` wires the app up using **main-thread-owned mode**, the simplest of `mithril-lynx`'s three rendering modes.
16
+ - `src/index.{js,ts}` is the app itself — plain Mithril hyperscript, no JSX, no virtual DOM beyond what Mithril already does.
17
+ - Everything about the framework — the three rendering modes, refs, gestures, list virtualization, cross-thread calls — is documented in [`mithril-lynx`'s own README](https://github.com/carlos-sweb/mithril-lynx#readme).
@@ -0,0 +1,4 @@
1
+ node_modules/
2
+ dist/
3
+ *.tsbuildinfo
4
+ .DS_Store
@@ -0,0 +1,119 @@
1
+ :root {
2
+ background-color: #000;
3
+ --color-text: #fff;
4
+ }
5
+
6
+ .Background {
7
+ position: fixed;
8
+ background: radial-gradient(
9
+ 71.43% 62.3% at 46.43% 36.43%,
10
+ rgba(91, 76, 214, 0) 15%,
11
+ rgba(143, 94, 214, 0.3) 56.35%,
12
+ #5b4cd6 100%
13
+ );
14
+ box-shadow: 0px 12.93px 28.74px 0px #8f5ed6b2 inset;
15
+ border-radius: 50%;
16
+ width: 200vw;
17
+ height: 200vw;
18
+ top: -60vw;
19
+ left: -14.27vw;
20
+ transform: rotate(15.25deg);
21
+ }
22
+
23
+ .App {
24
+ position: relative;
25
+ min-height: 100vh;
26
+ display: flex;
27
+ flex-direction: column;
28
+ align-items: center;
29
+ justify-content: center;
30
+ }
31
+
32
+ text {
33
+ color: var(--color-text);
34
+ }
35
+
36
+ .Banner {
37
+ flex: 5;
38
+ display: flex;
39
+ flex-direction: column;
40
+ align-items: center;
41
+ justify-content: center;
42
+ z-index: 100;
43
+ }
44
+
45
+ .Logo {
46
+ flex-direction: column;
47
+ align-items: center;
48
+ justify-content: center;
49
+ margin-bottom: 8px;
50
+ }
51
+
52
+ .Logo--mithril {
53
+ width: 100px;
54
+ height: 100px;
55
+ animation: Logo--spin infinite 20s linear;
56
+ }
57
+
58
+ .Logo--lynx {
59
+ width: 100px;
60
+ height: 100px;
61
+ animation: Logo--shake infinite 0.5s ease;
62
+ }
63
+
64
+ @keyframes Logo--spin {
65
+ from {
66
+ transform: rotate(0deg);
67
+ }
68
+ to {
69
+ transform: rotate(360deg);
70
+ }
71
+ }
72
+
73
+ @keyframes Logo--shake {
74
+ 0% {
75
+ transform: scale(1);
76
+ }
77
+ 50% {
78
+ transform: scale(0.9);
79
+ }
80
+ 100% {
81
+ transform: scale(1);
82
+ }
83
+ }
84
+
85
+ .Content {
86
+ display: flex;
87
+ flex-direction: column;
88
+ align-items: center;
89
+ justify-content: center;
90
+ }
91
+
92
+ .Arrow {
93
+ width: 24px;
94
+ height: 24px;
95
+ }
96
+
97
+ .Title {
98
+ font-size: 36px;
99
+ font-weight: 700;
100
+ }
101
+
102
+ .Subtitle {
103
+ font-style: italic;
104
+ font-size: 22px;
105
+ font-weight: 600;
106
+ margin-bottom: 8px;
107
+ }
108
+
109
+ .Description {
110
+ font-size: 20px;
111
+ color: rgba(255, 255, 255, 0.85);
112
+ margin: 15rpx;
113
+ }
114
+
115
+ .Hint {
116
+ font-size: 12px;
117
+ margin: 5px;
118
+ color: rgba(255, 255, 255, 0.65);
119
+ }
@@ -0,0 +1,37 @@
1
+ import path from "node:path";
2
+ import { fileURLToPath } from "node:url";
3
+
4
+ import { pluginLynxConfig } from "@lynx-js/config-rsbuild-plugin";
5
+ import { pluginQRCode } from "@lynx-js/qrcode-rsbuild-plugin";
6
+ import { defineConfig } from "@lynx-js/rspeedy";
7
+
8
+ import { pluginMithrilLynx } from "mithril-lynx/plugin";
9
+
10
+ const projectRoot = path.dirname(fileURLToPath(import.meta.url));
11
+
12
+ export default defineConfig({
13
+ source: {
14
+ entry: {
15
+ "main-thread": path.join(projectRoot, "src/main-thread.js"),
16
+ },
17
+ },
18
+ output: {
19
+ distPath: {
20
+ root: path.join(projectRoot, "dist"),
21
+ },
22
+ filename: "[name].bundle",
23
+ // Lynx bundles are self-contained — image imports must be inlined as
24
+ // data URIs rather than left as separate file references.
25
+ dataUriLimit: Infinity,
26
+ },
27
+ plugins: [
28
+ pluginMithrilLynx(),
29
+ pluginLynxConfig({}),
30
+ pluginQRCode({
31
+ schema(url) {
32
+ // Opens the page in LynxExplorer in full screen mode.
33
+ return `${url}?fullscreen=true`;
34
+ },
35
+ }),
36
+ ],
37
+ });
@@ -0,0 +1,26 @@
1
+ {
2
+ "name": "{{PROJECT_NAME}}",
3
+ "version": "0.0.1",
4
+ "private": true,
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "rspeedy dev",
8
+ "build": "rspeedy build",
9
+ "preview": "rspeedy preview"
10
+ },
11
+ "dependencies": {
12
+ "mithril": "2.3.8",
13
+ "mithril-lynx": "{{MITHRIL_LYNX_VERSION}}"
14
+ },
15
+ "devDependencies": {
16
+ "@lynx-js/config-rsbuild-plugin": "^0.2.0",
17
+ "@lynx-js/qrcode-rsbuild-plugin": "^0.7.0",
18
+ "@lynx-js/rspeedy": "^0.17.0",
19
+ "@lynx-js/template-webpack-plugin": "^0.16.0",
20
+ "@lynx-js/type-element-api": "0.0.9",
21
+ "@lynx-js/types": "4.1.0"
22
+ },
23
+ "engines": {
24
+ "node": "^20.19.0 || >=22.12.0"
25
+ }
26
+ }
@@ -0,0 +1,47 @@
1
+ import shim from "mithril-lynx";
2
+ import m from "mithril";
3
+
4
+ import lynxLogo from "./assets/lynx-logo.png";
5
+ import mithrilLogo from "./assets/mithril-logo.png";
6
+ import arrow from "./assets/arrow.png";
7
+
8
+ let alterLogo = false;
9
+
10
+ const App = {
11
+ view() {
12
+ return [
13
+ m("view", { class: "Background" }),
14
+ m("view", { class: "App" }, [
15
+ m("view", { class: "Banner" }, [
16
+ m("view", {
17
+ class: "Logo",
18
+ // shim.redraw() — NOT mithril's own m.redraw(), which is a
19
+ // no-op in this shim-based architecture.
20
+ ontap: () => {
21
+ alterLogo = !alterLogo;
22
+ shim.redraw();
23
+ },
24
+ }, [
25
+ alterLogo
26
+ ? m("image", { src: mithrilLogo, class: "Logo--mithril" })
27
+ : m("image", { src: lynxLogo, class: "Logo--lynx" }),
28
+ ]),
29
+ m("text", { class: "Title" }, "Mithril"),
30
+ m("text", { class: "Subtitle" }, "on Lynx"),
31
+ ]),
32
+ m("view", { class: "Content" }, [
33
+ m("image", { src: arrow, class: "Arrow" }),
34
+ m("text", { class: "Description" }, "Tap the logo and have fun!"),
35
+ m("text", { class: "Hint" }, [
36
+ "Edit ",
37
+ m("text", { style: { fontStyle: "italic", color: "rgba(255, 255, 255, 0.85)" } }, "src/index.js"),
38
+ " to see updates!",
39
+ ]),
40
+ ]),
41
+ m("view", { style: { flex: 1 } }),
42
+ ]),
43
+ ];
44
+ },
45
+ };
46
+
47
+ export default { App, root: m(App) };
@@ -0,0 +1,20 @@
1
+ import shim from "mithril-lynx";
2
+ import app from "./index.js";
3
+
4
+ // The native engine unconditionally invokes a global processData(initData)
5
+ // hook on every __RenderPage/__UpdatePage — install a pass-through default.
6
+ // (mithril-lynx/main-thread's setupApp() does this for you in data-channel
7
+ // mode; this bare main-thread-owned pattern doesn't go through that module.)
8
+ Object.assign(globalThis, {
9
+ processData: (data) => data,
10
+ });
11
+
12
+ // Main-thread-owned mode — the simplest of mithril-lynx's three rendering
13
+ // modes: no background.js, no dual-bundle build. The app renders directly
14
+ // on the main thread, synchronously, on first paint; every later update
15
+ // flows through shim.redraw(), called from event handlers bound via
16
+ // Mithril's own on* attrs (see src/index.js).
17
+ lynx.getEngine().addEventListener("__RenderPage", () => {
18
+ const page = __CreatePage("0", 0);
19
+ shim.renderToPage(page, app.root);
20
+ });
@@ -0,0 +1,39 @@
1
+ import path from "node:path";
2
+ import { fileURLToPath } from "node:url";
3
+
4
+ import { pluginLynxConfig } from "@lynx-js/config-rsbuild-plugin";
5
+ import { pluginQRCode } from "@lynx-js/qrcode-rsbuild-plugin";
6
+ import { defineConfig } from "@lynx-js/rspeedy";
7
+ import { pluginTypeCheck } from "@rsbuild/plugin-type-check";
8
+
9
+ import { pluginMithrilLynx } from "mithril-lynx/plugin";
10
+
11
+ const projectRoot = path.dirname(fileURLToPath(import.meta.url));
12
+
13
+ export default defineConfig({
14
+ source: {
15
+ entry: {
16
+ "main-thread": path.join(projectRoot, "src/main-thread.ts"),
17
+ },
18
+ },
19
+ output: {
20
+ distPath: {
21
+ root: path.join(projectRoot, "dist"),
22
+ },
23
+ filename: "[name].bundle",
24
+ // Lynx bundles are self-contained — image imports must be inlined as
25
+ // data URIs rather than left as separate file references.
26
+ dataUriLimit: Infinity,
27
+ },
28
+ plugins: [
29
+ pluginMithrilLynx(),
30
+ pluginLynxConfig({}),
31
+ pluginQRCode({
32
+ schema(url) {
33
+ // Opens the page in LynxExplorer in full screen mode.
34
+ return `${url}?fullscreen=true`;
35
+ },
36
+ }),
37
+ pluginTypeCheck(),
38
+ ],
39
+ });
@@ -0,0 +1,29 @@
1
+ {
2
+ "name": "{{PROJECT_NAME}}",
3
+ "version": "0.0.1",
4
+ "private": true,
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "rspeedy dev",
8
+ "build": "rspeedy build",
9
+ "preview": "rspeedy preview"
10
+ },
11
+ "dependencies": {
12
+ "mithril": "2.3.8",
13
+ "mithril-lynx": "{{MITHRIL_LYNX_VERSION}}"
14
+ },
15
+ "devDependencies": {
16
+ "@lynx-js/config-rsbuild-plugin": "^0.2.0",
17
+ "@lynx-js/qrcode-rsbuild-plugin": "^0.7.0",
18
+ "@lynx-js/rspeedy": "^0.17.0",
19
+ "@lynx-js/template-webpack-plugin": "^0.16.0",
20
+ "@lynx-js/type-element-api": "0.0.9",
21
+ "@lynx-js/types": "4.1.0",
22
+ "@rsbuild/plugin-type-check": "^1.6.0",
23
+ "@types/mithril": "^2.2.9",
24
+ "typescript": "~5.9.0"
25
+ },
26
+ "engines": {
27
+ "node": "^20.19.0 || >=22.12.0"
28
+ }
29
+ }
@@ -0,0 +1,47 @@
1
+ import shim from "mithril-lynx";
2
+ import m from "mithril";
3
+
4
+ import lynxLogo from "./assets/lynx-logo.png";
5
+ import mithrilLogo from "./assets/mithril-logo.png";
6
+ import arrow from "./assets/arrow.png";
7
+
8
+ let alterLogo = false;
9
+
10
+ const App: m.Component = {
11
+ view() {
12
+ return [
13
+ m("view", { class: "Background" }),
14
+ m("view", { class: "App" }, [
15
+ m("view", { class: "Banner" }, [
16
+ m("view", {
17
+ class: "Logo",
18
+ // shim.redraw() — NOT mithril's own m.redraw(), which is a
19
+ // no-op in this shim-based architecture.
20
+ ontap: () => {
21
+ alterLogo = !alterLogo;
22
+ shim.redraw();
23
+ },
24
+ }, [
25
+ alterLogo
26
+ ? m("image", { src: mithrilLogo, class: "Logo--mithril" })
27
+ : m("image", { src: lynxLogo, class: "Logo--lynx" }),
28
+ ]),
29
+ m("text", { class: "Title" }, "Mithril"),
30
+ m("text", { class: "Subtitle" }, "on Lynx"),
31
+ ]),
32
+ m("view", { class: "Content" }, [
33
+ m("image", { src: arrow, class: "Arrow" }),
34
+ m("text", { class: "Description" }, "Tap the logo and have fun!"),
35
+ m("text", { class: "Hint" }, [
36
+ "Edit ",
37
+ m("text", { style: { fontStyle: "italic", color: "rgba(255, 255, 255, 0.85)" } }, "src/index.ts"),
38
+ " to see updates!",
39
+ ]),
40
+ ]),
41
+ m("view", { style: { flex: 1 } }),
42
+ ]),
43
+ ];
44
+ },
45
+ };
46
+
47
+ export default { App, root: m(App) };
@@ -0,0 +1,20 @@
1
+ import shim from "mithril-lynx";
2
+ import app from "./index.js";
3
+
4
+ // The native engine unconditionally invokes a global processData(initData)
5
+ // hook on every __RenderPage/__UpdatePage — install a pass-through default.
6
+ // (mithril-lynx/main-thread's setupApp() does this for you in data-channel
7
+ // mode; this bare main-thread-owned pattern doesn't go through that module.)
8
+ Object.assign(globalThis, {
9
+ processData: (data: unknown) => data,
10
+ });
11
+
12
+ // Main-thread-owned mode — the simplest of mithril-lynx's three rendering
13
+ // modes: no background.ts, no dual-bundle build. The app renders directly
14
+ // on the main thread, synchronously, on first paint; every later update
15
+ // flows through shim.redraw(), called from event handlers bound via
16
+ // Mithril's own on* attrs (see src/index.ts).
17
+ lynx.getEngine().addEventListener("__RenderPage", () => {
18
+ const page = __CreatePage("0", 0);
19
+ shim.renderToPage(page, app.root);
20
+ });
@@ -0,0 +1,3 @@
1
+ /// <reference types="@lynx-js/rspeedy/client" />
2
+ /// <reference types="@lynx-js/types" />
3
+ /// <reference types="@lynx-js/type-element-api" />
@@ -0,0 +1,12 @@
1
+ {
2
+ "extends": "../tsconfig.json",
3
+ "compilerOptions": {
4
+ "composite": true,
5
+
6
+ "module": "ESNext",
7
+ "moduleResolution": "Bundler",
8
+
9
+ "noEmit": true
10
+ },
11
+ "include": ["./**/*.ts", "./**/*.tsx"]
12
+ }
@@ -0,0 +1,21 @@
1
+ {
2
+ "compilerOptions": {
3
+ "strict": true,
4
+ "isolatedModules": true,
5
+ "verbatimModuleSyntax": true,
6
+
7
+ "esModuleInterop": true,
8
+ "skipLibCheck": true,
9
+
10
+ // Explicit, not left to the TS-version-dependent default (which can be
11
+ // as old as ES3 and lacks e.g. Object.assign) — confirmed necessary
12
+ // by a real build failure with a newer/older typescript version.
13
+ "target": "ES2022",
14
+ "lib": ["ES2023"]
15
+ },
16
+ "references": [
17
+ { "path": "./tsconfig.node.json" },
18
+ { "path": "./src" }
19
+ ],
20
+ "files": []
21
+ }
@@ -0,0 +1,16 @@
1
+ {
2
+ "extends": "./tsconfig.json",
3
+ "compilerOptions": {
4
+ "composite": true,
5
+
6
+ "module": "node16",
7
+ "moduleResolution": "node16",
8
+ "erasableSyntaxOnly": true,
9
+
10
+ "lib": ["es2023"],
11
+ "target": "es2022",
12
+
13
+ "noEmit": true
14
+ },
15
+ "include": ["./lynx.config.ts"]
16
+ }