aplosjs 0.15.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.
Files changed (56) hide show
  1. package/README.md +28 -0
  2. package/aplos.config.dist.js +30 -0
  3. package/bin/aplos +60 -0
  4. package/create-aplos/index.js +95 -0
  5. package/create-aplos/package.json +29 -0
  6. package/create-aplos/templates/minimal/README.md +38 -0
  7. package/create-aplos/templates/minimal/_gitignore +7 -0
  8. package/create-aplos/templates/minimal/aplos.config.js +13 -0
  9. package/create-aplos/templates/minimal/package.json +22 -0
  10. package/create-aplos/templates/minimal/public/favicon.svg +4 -0
  11. package/create-aplos/templates/minimal/src/pages/_app.tsx +6 -0
  12. package/create-aplos/templates/minimal/src/pages/about.tsx +24 -0
  13. package/create-aplos/templates/minimal/src/pages/index.tsx +40 -0
  14. package/create-aplos/templates/minimal/src/styles/global.css +53 -0
  15. package/create-aplos/templates/minimal/tsconfig.json +18 -0
  16. package/package.json +92 -0
  17. package/postcss.config.js +9 -0
  18. package/rspack.config.js +306 -0
  19. package/rspack.ssr.config.js +129 -0
  20. package/src/build/config.js +42 -0
  21. package/src/build/css-noop-loader.cjs +3 -0
  22. package/src/build/router.js +609 -0
  23. package/src/build/ssg.js +198 -0
  24. package/src/client/public/index.html +8 -0
  25. package/src/command/build.js +105 -0
  26. package/src/command/create.js +91 -0
  27. package/src/command/devServer.js +198 -0
  28. package/src/command/router.js +137 -0
  29. package/src/components/head.jsx +65 -0
  30. package/src/components/navigation.jsx +11 -0
  31. package/src/config.js +5 -0
  32. package/src/pages/_app.tsx +9 -0
  33. package/src/pages/blog/[slug].tsx +6 -0
  34. package/src/pages/crash.tsx +6 -0
  35. package/src/pages/index.tsx +10 -0
  36. package/src/pages/test.tsx +5 -0
  37. package/src/runtime/DefaultErrorPage.jsx +76 -0
  38. package/src/runtime/ErrorBoundary.jsx +40 -0
  39. package/src/runtime/MiddlewareGate.jsx +149 -0
  40. package/src/runtime/app-ssr.jsx +42 -0
  41. package/src/runtime/app.jsx +126 -0
  42. package/src/runtime/default-middleware.js +10 -0
  43. package/src/runtime/default-not-found.jsx +3 -0
  44. package/src/runtime/passthrough-layout.jsx +5 -0
  45. package/src/runtime/redirect.js +46 -0
  46. package/src/runtime/ssr-entry.jsx +104 -0
  47. package/templates/minimal/README.md +38 -0
  48. package/templates/minimal/_gitignore +7 -0
  49. package/templates/minimal/aplos.config.js +13 -0
  50. package/templates/minimal/package.json +22 -0
  51. package/templates/minimal/public/favicon.svg +4 -0
  52. package/templates/minimal/src/pages/_app.tsx +6 -0
  53. package/templates/minimal/src/pages/about.tsx +24 -0
  54. package/templates/minimal/src/pages/index.tsx +40 -0
  55. package/templates/minimal/src/styles/global.css +53 -0
  56. package/templates/minimal/tsconfig.json +18 -0
@@ -0,0 +1,126 @@
1
+ import React, { createElement, useEffect } from 'react';
2
+ import { createRoot } from 'react-dom/client';
3
+ import { Routes, BrowserRouter, Route } from 'react-router-dom';
4
+
5
+ import { routeTree } from '@aplos_routes';
6
+ import { CustomError, NoMatch } from '@aplos_pages';
7
+ import headConfig, { reactStrictMode } from '@aplos_head';
8
+
9
+ import ErrorBoundary from './ErrorBoundary.jsx';
10
+ import DefaultErrorPage from './DefaultErrorPage.jsx';
11
+ import MiddlewareGate from './MiddlewareGate.jsx';
12
+
13
+ const MANAGED_ATTR = "data-head-default";
14
+
15
+ function renderRoutes(nodes) {
16
+ return nodes.map((node, i) => {
17
+ if (node.children) {
18
+ return (
19
+ <Route key={i} element={createElement(node.element)}>
20
+ {renderRoutes(node.children)}
21
+ </Route>
22
+ );
23
+ }
24
+ return <Route key={i} path={node.path} element={createElement(node.element)} />;
25
+ });
26
+ }
27
+
28
+ function HeadDefaults() {
29
+ useEffect(() => {
30
+ if (!headConfig) return;
31
+
32
+ const { defaultTitle, meta = [], link = [], script = [] } = headConfig;
33
+
34
+ if (defaultTitle) {
35
+ document.title = defaultTitle;
36
+ }
37
+
38
+ const elements = [];
39
+
40
+ meta.forEach((m) => {
41
+ const el = document.createElement('meta');
42
+ Object.entries(m).forEach(([key, value]) => el.setAttribute(key, value));
43
+ el.setAttribute(MANAGED_ATTR, 'true');
44
+ document.head.appendChild(el);
45
+ elements.push(el);
46
+ });
47
+
48
+ link.forEach((l) => {
49
+ const el = document.createElement('link');
50
+ Object.entries(l).forEach(([key, value]) => el.setAttribute(key, value));
51
+ el.setAttribute(MANAGED_ATTR, 'true');
52
+ document.head.appendChild(el);
53
+ elements.push(el);
54
+ });
55
+
56
+ script.forEach((s) => {
57
+ const el = document.createElement('script');
58
+ Object.entries(s).forEach(([key, value]) => {
59
+ if (key === 'innerHTML') {
60
+ el.textContent = value;
61
+ } else if (typeof value === 'boolean') {
62
+ if (value) el.setAttribute(key, '');
63
+ } else {
64
+ el.setAttribute(key, value);
65
+ }
66
+ });
67
+ el.setAttribute(MANAGED_ATTR, 'true');
68
+ document.head.appendChild(el);
69
+ elements.push(el);
70
+ });
71
+
72
+ return () => {
73
+ elements.forEach((el) => {
74
+ if (el.parentNode) el.parentNode.removeChild(el);
75
+ });
76
+ };
77
+ }, []);
78
+
79
+ return null;
80
+ }
81
+
82
+ function App() {
83
+ const ErrorComponent = CustomError || DefaultErrorPage;
84
+
85
+ return (
86
+ <>
87
+ <HeadDefaults />
88
+ <ErrorBoundary errorComponent={ErrorComponent}>
89
+ <BrowserRouter>
90
+ <MiddlewareGate>
91
+ <Routes>
92
+ {renderRoutes(routeTree)}
93
+ <Route path="*" element={createElement(NoMatch)} />
94
+ </Routes>
95
+ </MiddlewareGate>
96
+ </BrowserRouter>
97
+ </ErrorBoundary>
98
+ </>
99
+ );
100
+ }
101
+
102
+ const container = document.getElementById('root');
103
+
104
+ // Reuse the React root across hot updates so HMR re-renders instead of
105
+ // recreating the root (which would force a full reload).
106
+ const root =
107
+ (module.hot && module.hot.data && module.hot.data.root) || createRoot(container);
108
+
109
+ function render() {
110
+ if (reactStrictMode) {
111
+ const { StrictMode } = React;
112
+ root.render(<StrictMode><App /></StrictMode>);
113
+ } else {
114
+ root.render(<App />);
115
+ }
116
+ }
117
+
118
+ render();
119
+
120
+ if (module.hot) {
121
+ // Accept updates here so React Refresh commits them without a full reload.
122
+ module.hot.accept();
123
+ module.hot.dispose((data) => {
124
+ data.root = root;
125
+ });
126
+ }
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Default route middleware used when a project does not define
3
+ * `src/middleware.{ts,tsx,js,jsx}`. It never redirects, so navigation always
4
+ * proceeds unchanged.
5
+ *
6
+ * @returns {undefined}
7
+ */
8
+ export default function middleware() {
9
+ return undefined;
10
+ }
@@ -0,0 +1,3 @@
1
+ export default function DefaultNotFound() {
2
+ return <div>Not found</div>;
3
+ }
@@ -0,0 +1,5 @@
1
+ import { Outlet } from "react-router-dom";
2
+
3
+ export default function PassthroughLayout() {
4
+ return <Outlet />;
5
+ }
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Sentinel returned by a route middleware to ask Aplos to navigate elsewhere
3
+ * before the matched route renders.
4
+ *
5
+ * A middleware returns the result of `redirect(...)` to short-circuit
6
+ * navigation. Returning `undefined` (or nothing) lets navigation proceed.
7
+ *
8
+ * @example
9
+ * import { redirect } from 'aplos/redirect';
10
+ *
11
+ * export default function middleware({ pathname }) {
12
+ * const token = localStorage.getItem('token');
13
+ * if (!token && pathname !== '/login') {
14
+ * return redirect('/login');
15
+ * }
16
+ * }
17
+ *
18
+ * @param {string} to - Destination path (e.g. `/login`).
19
+ * @param {{ replace?: boolean }} [options] - `replace: true` (default) swaps
20
+ * the current history entry so the guarded URL is not kept in history.
21
+ * Pass `replace: false` to push a new entry instead.
22
+ * @returns {{ __aplosRedirect: true, to: string, replace: boolean }}
23
+ */
24
+ export function redirect(to, options = {}) {
25
+ if (typeof to !== 'string' || to.length === 0) {
26
+ throw new TypeError(`redirect(): "to" must be a non-empty string, received ${typeof to}`);
27
+ }
28
+ return {
29
+ __aplosRedirect: true,
30
+ to,
31
+ replace: options.replace !== false,
32
+ };
33
+ }
34
+
35
+ /**
36
+ * @param {unknown} value
37
+ * @returns {value is { __aplosRedirect: true, to: string, replace: boolean }}
38
+ */
39
+ export function isRedirect(value) {
40
+ return (
41
+ typeof value === 'object' &&
42
+ value !== null &&
43
+ value.__aplosRedirect === true &&
44
+ typeof value.to === 'string'
45
+ );
46
+ }
@@ -0,0 +1,104 @@
1
+ import React from 'react';
2
+ import { renderToString } from 'react-dom/server';
3
+ import AppSSR from './app-ssr.jsx';
4
+ import { routeTree } from '@aplos_routes';
5
+
6
+ export function render(url) {
7
+ return renderToString(<AppSSR url={url} />);
8
+ }
9
+
10
+ function isStaticPath(p) {
11
+ if (!p) {
12
+ return false;
13
+ }
14
+ if (p.includes(':')) {
15
+ return false;
16
+ }
17
+ if (p.includes('*')) {
18
+ return false;
19
+ }
20
+ return true;
21
+ }
22
+
23
+ function walk(nodes, acc, forceAll) {
24
+ for (const node of nodes) {
25
+ if (node.path !== undefined && isStaticPath(node.path)) {
26
+ if (forceAll || node.static === true) {
27
+ acc.push(node.path);
28
+ }
29
+ }
30
+ if (node.children) {
31
+ walk(node.children, acc, forceAll);
32
+ }
33
+ }
34
+ }
35
+
36
+ export function getStaticRoutes({ forceAll = false } = {}) {
37
+ const acc = [];
38
+ walk(routeTree, acc, forceAll);
39
+ return Array.from(new Set(acc));
40
+ }
41
+
42
+ function findRouteModule(nodes, url) {
43
+ for (const node of nodes) {
44
+ if (node.children) {
45
+ const found = findRouteModule(node.children, url);
46
+ if (found) return found;
47
+ }
48
+ if (node.path !== undefined && node.path === url) {
49
+ return node;
50
+ }
51
+ }
52
+ return null;
53
+ }
54
+
55
+ function extractParams(sourcePath, url) {
56
+ if (!sourcePath) {
57
+ return {};
58
+ }
59
+ const sourceSegments = sourcePath.split('/').filter(Boolean);
60
+ const urlSegments = url.split('/').filter(Boolean);
61
+ const params = {};
62
+ for (let i = 0; i < sourceSegments.length; i++) {
63
+ const seg = sourceSegments[i];
64
+ const catchAll = seg.match(/^\[\.\.\.(.+)\]$/);
65
+ if (catchAll) {
66
+ params[catchAll[1]] = urlSegments.slice(i).join('/');
67
+ return params;
68
+ }
69
+ const dynamic = seg.match(/^\[(.+)\]$/);
70
+ if (dynamic) {
71
+ params[dynamic[1]] = urlSegments[i];
72
+ }
73
+ }
74
+ return params;
75
+ }
76
+
77
+ /**
78
+ * Return the `meta` export of the page module matching `url`, if any.
79
+ * Pages opt in by exporting `export const meta = { title, description, ... }`
80
+ * or `export const meta = (url, params) => ({ ... })` for per-instance values.
81
+ * When the matched node carries an inline `meta` (from a `paths` entry), that
82
+ * value wins over the component-level export.
83
+ * @param {string} url
84
+ * @returns {object|null}
85
+ */
86
+ export function getRouteMeta(url) {
87
+ const node = findRouteModule(routeTree, url);
88
+ if (!node || node.meta === undefined || node.meta === null) {
89
+ return null;
90
+ }
91
+ if (typeof node.meta === 'function') {
92
+ const params = extractParams(node.sourcePath, url);
93
+ try {
94
+ const result = node.meta(url, params);
95
+ return result && typeof result === 'object' ? result : null;
96
+ } catch (err) {
97
+ console.error(`meta() threw for ${url}:`, err.message);
98
+ return null;
99
+ }
100
+ }
101
+ return node.meta;
102
+ }
103
+
104
+ export default { render, getStaticRoutes, getRouteMeta };
@@ -0,0 +1,38 @@
1
+ # {{NAME}}
2
+
3
+ Built with [Aplos](https://aplos.alpacode.io) — a fast, file-based React framework powered by Rspack.
4
+
5
+ ## Getting started
6
+
7
+ ```bash
8
+ bun install
9
+ bun dev
10
+ ```
11
+
12
+ Open [http://localhost:3000](http://localhost:3000) to view the app.
13
+
14
+ ## Available scripts
15
+
16
+ - `bun dev` — start the development server with HMR
17
+ - `bun build` — build for production
18
+ - `bun build:static` — build with static pre-rendering for opt-in pages
19
+
20
+ ## Project structure
21
+
22
+ ```
23
+ src/
24
+ pages/ # File-based routes
25
+ _app.tsx # Root layout
26
+ index.tsx # Home page (/)
27
+ about.tsx # About page (/about)
28
+ styles/
29
+ global.css
30
+ aplos.config.js
31
+ ```
32
+
33
+ Add files in `src/pages/` to create new routes automatically.
34
+
35
+ ## Learn more
36
+
37
+ - [Aplos documentation](https://aplos.alpacode.io)
38
+ - [GitHub repository](https://github.com/alpac0de/aplos)
@@ -0,0 +1,7 @@
1
+ node_modules
2
+ .aplos
3
+ public/dist
4
+ dist
5
+ .env.local
6
+ *.log
7
+ .DS_Store
@@ -0,0 +1,13 @@
1
+ export default {
2
+ reactStrictMode: true,
3
+ server: {
4
+ port: 3000,
5
+ },
6
+ head: {
7
+ defaultTitle: '{{NAME}}',
8
+ meta: [
9
+ { name: 'viewport', content: 'width=device-width, initial-scale=1' },
10
+ { name: 'description', content: 'Built with Aplos' },
11
+ ],
12
+ },
13
+ };
@@ -0,0 +1,22 @@
1
+ {
2
+ "name": "{{NAME}}",
3
+ "private": true,
4
+ "version": "0.1.0",
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "aplos server",
8
+ "build": "aplos build",
9
+ "build:static": "aplos build --static"
10
+ },
11
+ "dependencies": {
12
+ "aplosjs": "^0.14.0",
13
+ "react": "^19.2.0",
14
+ "react-dom": "^19.2.0",
15
+ "react-router-dom": "^7.9.5"
16
+ },
17
+ "devDependencies": {
18
+ "typescript": "^5.9.3",
19
+ "@types/react": "^19.0.0",
20
+ "@types/react-dom": "^19.0.0"
21
+ }
22
+ }
@@ -0,0 +1,4 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
2
+ <rect width="64" height="64" rx="12" fill="#0066cc"/>
3
+ <text x="50%" y="58%" text-anchor="middle" fill="#fff" font-family="system-ui, sans-serif" font-size="32" font-weight="700">A</text>
4
+ </svg>
@@ -0,0 +1,6 @@
1
+ import { Outlet } from 'aplos/navigation';
2
+ import '@/styles/global.css';
3
+
4
+ export default function App() {
5
+ return <Outlet />;
6
+ }
@@ -0,0 +1,24 @@
1
+ import Head from 'aplos/head';
2
+ import { Link } from 'aplos/navigation';
3
+
4
+ export default function About() {
5
+ return (
6
+ <>
7
+ <Head>
8
+ <title>About</title>
9
+ </Head>
10
+
11
+ <main className="container">
12
+ <h1>About</h1>
13
+ <p>
14
+ This is an example page. Aplos uses file-based routing — this page
15
+ lives at <code>src/pages/about.tsx</code> and is served at{' '}
16
+ <code>/about</code>.
17
+ </p>
18
+ <p>
19
+ <Link to="/">← Back home</Link>
20
+ </p>
21
+ </main>
22
+ </>
23
+ );
24
+ }
@@ -0,0 +1,40 @@
1
+ import Head from 'aplos/head';
2
+ import { Link } from 'aplos/navigation';
3
+
4
+ export default function Home() {
5
+ return (
6
+ <>
7
+ <Head>
8
+ <title>Welcome — {{NAME}}</title>
9
+ </Head>
10
+
11
+ <main className="container">
12
+ <h1>Welcome to Aplos</h1>
13
+ <p>
14
+ Your project is up and running. Edit <code>src/pages/index.tsx</code>{' '}
15
+ to start building.
16
+ </p>
17
+
18
+ <ul className="links">
19
+ <li>
20
+ <Link to="/about">About page example</Link>
21
+ </li>
22
+ <li>
23
+ <a href="https://aplos.alpacode.io" target="_blank" rel="noreferrer">
24
+ Documentation
25
+ </a>
26
+ </li>
27
+ <li>
28
+ <a
29
+ href="https://github.com/alpac0de/aplos"
30
+ target="_blank"
31
+ rel="noreferrer"
32
+ >
33
+ GitHub repository
34
+ </a>
35
+ </li>
36
+ </ul>
37
+ </main>
38
+ </>
39
+ );
40
+ }
@@ -0,0 +1,53 @@
1
+ *,
2
+ *::before,
3
+ *::after {
4
+ box-sizing: border-box;
5
+ }
6
+
7
+ html,
8
+ body {
9
+ margin: 0;
10
+ padding: 0;
11
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
12
+ "Helvetica Neue", Arial, sans-serif;
13
+ color: #1a1a1a;
14
+ background: #fafafa;
15
+ line-height: 1.6;
16
+ }
17
+
18
+ .container {
19
+ max-width: 720px;
20
+ margin: 0 auto;
21
+ padding: 4rem 1.5rem;
22
+ }
23
+
24
+ h1 {
25
+ font-size: 2.25rem;
26
+ margin-bottom: 1rem;
27
+ }
28
+
29
+ code {
30
+ background: #eee;
31
+ padding: 0.15rem 0.4rem;
32
+ border-radius: 4px;
33
+ font-size: 0.9em;
34
+ }
35
+
36
+ .links {
37
+ list-style: none;
38
+ padding: 0;
39
+ margin-top: 2rem;
40
+ }
41
+
42
+ .links li {
43
+ margin: 0.5rem 0;
44
+ }
45
+
46
+ a {
47
+ color: #0066cc;
48
+ text-decoration: none;
49
+ }
50
+
51
+ a:hover {
52
+ text-decoration: underline;
53
+ }
@@ -0,0 +1,18 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "ESNext",
5
+ "moduleResolution": "bundler",
6
+ "jsx": "react-jsx",
7
+ "strict": true,
8
+ "esModuleInterop": true,
9
+ "skipLibCheck": true,
10
+ "forceConsistentCasingInFileNames": true,
11
+ "baseUrl": ".",
12
+ "paths": {
13
+ "@/*": ["./src/*"],
14
+ "~/*": ["./src/*"]
15
+ }
16
+ },
17
+ "include": ["src"]
18
+ }