@loom-js/create 0.0.0-stage → 0.1.0
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/README.md +51 -2
- package/bin/create.mjs +18 -0
- package/dist/index.mjs +1202 -0
- package/dist/templates/biome/js/base/public/styles/base.css +17 -0
- package/dist/templates/biome/js/base/src/app.js +23 -0
- package/dist/templates/biome/js/base/src/pages/about.js +12 -0
- package/dist/templates/biome/js/base/src/pages/home.js +22 -0
- package/dist/templates/biome/js/base/src/routes.js +3 -0
- package/dist/templates/biome/js/server/src/prerender.entry.js +22 -0
- package/dist/templates/biome/js/tailwind/scripts/dev.mjs +66 -0
- package/dist/templates/biome/js/tailwind/src/app.js +23 -0
- package/dist/templates/biome/js/ui/pink/src/pages/home.js +21 -0
- package/dist/templates/biome/ts/base/public/styles/base.css +17 -0
- package/dist/templates/biome/ts/base/src/app.ts +23 -0
- package/dist/templates/biome/ts/base/src/pages/about.ts +12 -0
- package/dist/templates/biome/ts/base/src/pages/home.ts +22 -0
- package/dist/templates/biome/ts/base/src/routes.ts +3 -0
- package/dist/templates/biome/ts/base/src/types.d.ts +4 -0
- package/dist/templates/biome/ts/server/src/prerender.entry.ts +26 -0
- package/dist/templates/biome/ts/tailwind/scripts/dev.mjs +66 -0
- package/dist/templates/biome/ts/tailwind/src/app.ts +23 -0
- package/dist/templates/biome/ts/ui/pink/src/pages/home.ts +21 -0
- package/dist/templates/prettier/js/base/public/styles/base.css +17 -0
- package/dist/templates/prettier/js/base/src/app.js +23 -0
- package/dist/templates/prettier/js/base/src/pages/about.js +12 -0
- package/dist/templates/prettier/js/base/src/pages/home.js +22 -0
- package/dist/templates/prettier/js/base/src/routes.js +3 -0
- package/dist/templates/prettier/js/server/src/prerender.entry.js +22 -0
- package/dist/templates/prettier/js/tailwind/scripts/dev.mjs +66 -0
- package/dist/templates/prettier/js/tailwind/src/app.js +23 -0
- package/dist/templates/prettier/js/ui/pink/src/pages/home.js +21 -0
- package/dist/templates/prettier/ts/base/public/styles/base.css +17 -0
- package/dist/templates/prettier/ts/base/src/app.ts +23 -0
- package/dist/templates/prettier/ts/base/src/pages/about.ts +12 -0
- package/dist/templates/prettier/ts/base/src/pages/home.ts +22 -0
- package/dist/templates/prettier/ts/base/src/routes.ts +3 -0
- package/dist/templates/prettier/ts/base/src/types.d.ts +4 -0
- package/dist/templates/prettier/ts/server/src/prerender.entry.ts +26 -0
- package/dist/templates/prettier/ts/tailwind/scripts/dev.mjs +66 -0
- package/dist/templates/prettier/ts/tailwind/src/app.ts +23 -0
- package/dist/templates/prettier/ts/ui/pink/src/pages/home.ts +21 -0
- package/package.json +51 -3
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { activity, component } from '@loom-js/core';
|
|
2
|
+
import { PinkButton, PinkCard } from '@loom-js/pink';
|
|
3
|
+
|
|
4
|
+
const count = activity(0);
|
|
5
|
+
|
|
6
|
+
export default component(
|
|
7
|
+
(html) => html`
|
|
8
|
+
<section>
|
|
9
|
+
<h1>Welcome to loom</h1>
|
|
10
|
+
<${PinkCard} id="welcome-card">
|
|
11
|
+
<p>Edit <code>src/pages/home.ts</code> and save to reload.</p>
|
|
12
|
+
<${PinkButton}
|
|
13
|
+
icon="icon-check"
|
|
14
|
+
onClick=${() => count.update(count.value() + 1)}
|
|
15
|
+
>
|
|
16
|
+
${count.effect(({ value }) => `Clicked ${value} times`)}
|
|
17
|
+
</>
|
|
18
|
+
</>
|
|
19
|
+
</section>
|
|
20
|
+
`
|
|
21
|
+
);
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/* Layered, so a UI kit's or Tailwind's rules win over these defaults. */
|
|
2
|
+
@layer base {
|
|
3
|
+
body {
|
|
4
|
+
font-family: system-ui, sans-serif;
|
|
5
|
+
margin: 0;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
.app-nav {
|
|
9
|
+
display: flex;
|
|
10
|
+
gap: 1rem;
|
|
11
|
+
padding: 1rem 1.25rem;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
.app-main {
|
|
15
|
+
padding: 0 1.25rem;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { component, createRoutes, RouteLink } from '@loom-js/core';
|
|
2
|
+
|
|
3
|
+
// Pages load on demand. Registering routes touches no DOM, so module scope
|
|
4
|
+
// is safe on the server too.
|
|
5
|
+
const Routes = createRoutes({
|
|
6
|
+
config: {
|
|
7
|
+
'/': () => import('@app/pages/home'),
|
|
8
|
+
'/about': () => import('@app/pages/about')
|
|
9
|
+
}
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
/** The composed app: the browser entry and the server render both call it. */
|
|
13
|
+
export const App = component(
|
|
14
|
+
(html, props) => html`
|
|
15
|
+
<div class="app">
|
|
16
|
+
<nav class="app-nav">
|
|
17
|
+
<${RouteLink} href="/">Home</>
|
|
18
|
+
<${RouteLink} href="/about">About</>
|
|
19
|
+
</nav>
|
|
20
|
+
<main class="app-main">${Routes(props)}</main>
|
|
21
|
+
</div>
|
|
22
|
+
`
|
|
23
|
+
);
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { activity, component } from '@loom-js/core';
|
|
2
|
+
|
|
3
|
+
const count = activity(0);
|
|
4
|
+
|
|
5
|
+
export default component(
|
|
6
|
+
(html) => html`
|
|
7
|
+
<section>
|
|
8
|
+
<h1>Welcome to loom</h1>
|
|
9
|
+
<p>
|
|
10
|
+
Edit
|
|
11
|
+
<code>src/pages/home.ts</code>
|
|
12
|
+
and save to reload.
|
|
13
|
+
</p>
|
|
14
|
+
<button
|
|
15
|
+
$click=${() => count.update(count.value() + 1)}
|
|
16
|
+
type="button"
|
|
17
|
+
>
|
|
18
|
+
${count.effect(({ value }) => `Clicked ${value} times`)}
|
|
19
|
+
</button>
|
|
20
|
+
</section>
|
|
21
|
+
`
|
|
22
|
+
);
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// The server module: built next to the client so markup and state match the
|
|
2
|
+
// shipped app. `loom build` calls `prerenderRoute` once per route.
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
dehydrate,
|
|
6
|
+
renderToString,
|
|
7
|
+
serializeState
|
|
8
|
+
} from '@loom-js/core/server';
|
|
9
|
+
|
|
10
|
+
import { App } from './app';
|
|
11
|
+
|
|
12
|
+
export { routes } from './routes';
|
|
13
|
+
|
|
14
|
+
// The server entry's types duplicate core's, so the same runtime value needs
|
|
15
|
+
// a cast at this one boundary.
|
|
16
|
+
type ServerRenderable = Parameters<typeof renderToString>[0];
|
|
17
|
+
|
|
18
|
+
/** Renders one route: its markup plus the state the client primes from. */
|
|
19
|
+
export const prerenderRoute = async (url: string, window: object) => {
|
|
20
|
+
const html = await renderToString(App() as unknown as ServerRenderable, {
|
|
21
|
+
url,
|
|
22
|
+
window
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
return { html, state: serializeState(dehydrate(window)) };
|
|
26
|
+
};
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
// `dev`: the Tailwind CLI in watch mode next to `loom dev`. When either one
|
|
2
|
+
// exits, the other is stopped, and its exit code is passed on.
|
|
3
|
+
|
|
4
|
+
import { spawn } from 'node:child_process';
|
|
5
|
+
import { pathToFileURL } from 'node:url';
|
|
6
|
+
|
|
7
|
+
const TASKS = [
|
|
8
|
+
[
|
|
9
|
+
'tailwindcss',
|
|
10
|
+
// `always`: plain `--watch` exits when stdin closes (any non-TTY run).
|
|
11
|
+
[
|
|
12
|
+
'-i',
|
|
13
|
+
'./src/styles.css',
|
|
14
|
+
'-o',
|
|
15
|
+
'./.generated/styles.css',
|
|
16
|
+
'--watch=always'
|
|
17
|
+
]
|
|
18
|
+
],
|
|
19
|
+
['loom', ['dev']]
|
|
20
|
+
];
|
|
21
|
+
|
|
22
|
+
/** Starts every task; resolves with the first exit code once all stopped. */
|
|
23
|
+
export const runTogether = (tasks, start = spawn) =>
|
|
24
|
+
new Promise((resolve) => {
|
|
25
|
+
let exitCode;
|
|
26
|
+
const stopped = new Set();
|
|
27
|
+
const children = tasks.map(([command, args]) =>
|
|
28
|
+
start(command, args, {
|
|
29
|
+
// Package binaries are `.cmd` shims on Windows.
|
|
30
|
+
shell: process.platform === 'win32',
|
|
31
|
+
stdio: 'inherit'
|
|
32
|
+
})
|
|
33
|
+
);
|
|
34
|
+
const onStopped = (child, code) => {
|
|
35
|
+
if (stopped.has(child)) {
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
stopped.add(child);
|
|
40
|
+
exitCode ??= code ?? 1;
|
|
41
|
+
|
|
42
|
+
for (const other of children) {
|
|
43
|
+
if (!stopped.has(other)) {
|
|
44
|
+
other.kill();
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (stopped.size === children.length) {
|
|
49
|
+
resolve(exitCode);
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
for (const child of children) {
|
|
54
|
+
child.on('exit', (code) => onStopped(child, code));
|
|
55
|
+
// A binary that can't start stops the other task too.
|
|
56
|
+
child.on('error', () => onStopped(child, 1));
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
// Runs only as the `dev` script, not when imported.
|
|
61
|
+
if (
|
|
62
|
+
process.argv[1] &&
|
|
63
|
+
import.meta.url === pathToFileURL(process.argv[1]).href
|
|
64
|
+
) {
|
|
65
|
+
process.exitCode = await runTogether(TASKS);
|
|
66
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { component, createRoutes, RouteLink } from '@loom-js/core';
|
|
2
|
+
|
|
3
|
+
// Pages load on demand. Registering routes touches no DOM, so module scope
|
|
4
|
+
// is safe on the server too.
|
|
5
|
+
const Routes = createRoutes({
|
|
6
|
+
config: {
|
|
7
|
+
'/': () => import('@app/pages/home'),
|
|
8
|
+
'/about': () => import('@app/pages/about')
|
|
9
|
+
}
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
/** The composed app: the browser entry and the server render both call it. */
|
|
13
|
+
export const App = component(
|
|
14
|
+
(html, props) => html`
|
|
15
|
+
<div class="mx-auto flex max-w-3xl flex-col gap-6 px-5 py-6">
|
|
16
|
+
<nav class="flex gap-4 font-medium" id="app-nav">
|
|
17
|
+
<${RouteLink} href="/">Home</>
|
|
18
|
+
<${RouteLink} href="/about">About</>
|
|
19
|
+
</nav>
|
|
20
|
+
<main>${Routes(props)}</main>
|
|
21
|
+
</div>
|
|
22
|
+
`
|
|
23
|
+
);
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { activity, component } from '@loom-js/core';
|
|
2
|
+
import { PinkButton, PinkCard } from '@loom-js/pink';
|
|
3
|
+
|
|
4
|
+
const count = activity(0);
|
|
5
|
+
|
|
6
|
+
export default component(
|
|
7
|
+
(html) => html`
|
|
8
|
+
<section>
|
|
9
|
+
<h1>Welcome to loom</h1>
|
|
10
|
+
<${PinkCard} id="welcome-card">
|
|
11
|
+
<p>Edit <code>src/pages/home.ts</code> and save to reload.</p>
|
|
12
|
+
<${PinkButton}
|
|
13
|
+
icon="icon-check"
|
|
14
|
+
onClick=${() => count.update(count.value() + 1)}
|
|
15
|
+
>
|
|
16
|
+
${count.effect(({ value }) => `Clicked ${value} times`)}
|
|
17
|
+
</>
|
|
18
|
+
</>
|
|
19
|
+
</section>
|
|
20
|
+
`
|
|
21
|
+
);
|
package/package.json
CHANGED
|
@@ -1,6 +1,54 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@loom-js/create",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"author": "Claudio Nunez Jr",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"description": "Create a loom app: `npm create @loom-js` scaffolds a standalone, server-first project that builds with @loom-js/build.",
|
|
7
|
+
"keywords": [
|
|
8
|
+
"loomjs",
|
|
9
|
+
"create",
|
|
10
|
+
"scaffold",
|
|
11
|
+
"cli",
|
|
12
|
+
"starter"
|
|
13
|
+
],
|
|
14
|
+
"bin": {
|
|
15
|
+
"create": "./bin/create.mjs"
|
|
16
|
+
},
|
|
17
|
+
"files": [
|
|
18
|
+
"bin",
|
|
19
|
+
"dist"
|
|
20
|
+
],
|
|
21
|
+
"type": "module",
|
|
22
|
+
"engines": {
|
|
23
|
+
"node": ">=20.12"
|
|
24
|
+
},
|
|
25
|
+
"publishConfig": {
|
|
26
|
+
"access": "public"
|
|
27
|
+
},
|
|
28
|
+
"repository": {
|
|
29
|
+
"type": "git",
|
|
30
|
+
"url": "git+https://github.com/claudio-darkkenergy/loom-js.git",
|
|
31
|
+
"directory": "packages/create"
|
|
32
|
+
},
|
|
33
|
+
"devDependencies": {
|
|
34
|
+
"@biomejs/biome": "^2.6.5",
|
|
35
|
+
"@loom-js/build": "0.4.0",
|
|
36
|
+
"@loom-js/core": "0.22.0",
|
|
37
|
+
"@loom-js/devtools": "0.2.0",
|
|
38
|
+
"@loom-js/pink": "0.10.2",
|
|
39
|
+
"@loom-js/typescript-config": "0.1.1",
|
|
40
|
+
"@tailwindcss/cli": "^4.3.3",
|
|
41
|
+
"@types/node": "^26.5.1",
|
|
42
|
+
"esbuild": "^0.28.2",
|
|
43
|
+
"prettier": "^3.9.6",
|
|
44
|
+
"tailwindcss": "^4.3.3",
|
|
45
|
+
"turbo": "^2.10.13",
|
|
46
|
+
"typescript": "^7.0.2"
|
|
47
|
+
},
|
|
48
|
+
"scripts": {
|
|
49
|
+
"build-package": "node --disable-warning=ExperimentalWarning scripts/build.mjs",
|
|
50
|
+
"format": "prettier",
|
|
51
|
+
"test-ci": "pnpm build-package && node --test \"tests/*.test.mjs\"",
|
|
52
|
+
"type-check": "tsc --noEmit"
|
|
53
|
+
}
|
|
6
54
|
}
|