├── .github
└── workflows
│ ├── release-please.yml
│ └── semantic-prs.yml
├── .gitignore
├── .npmrc
├── .prettierrc
├── .release-please-manifest.json
├── README.md
├── demo
├── README.md
├── index.html
├── netlify.toml
├── package.json
├── src
│ ├── App.css
│ ├── App.tsx
│ ├── assets
│ │ └── react.svg
│ ├── entry-client.tsx
│ ├── entry-server.tsx
│ ├── index.css
│ ├── routes
│ │ ├── hello.data.ts
│ │ ├── hello.tsx
│ │ ├── index.tsx
│ │ └── world
│ │ │ ├── [id].data.ts
│ │ │ └── [id].tsx
│ └── vite-env.d.ts
├── tsconfig.json
├── tsconfig.node.json
└── vite.config.ts
├── package.json
├── packages
├── core
│ ├── CHANGELOG.md
│ ├── README.md
│ ├── cli.mjs
│ ├── package.json
│ ├── plugin.d.ts
│ ├── src
│ │ ├── cli.ts
│ │ ├── convertPathToURLPattern.ts
│ │ ├── core.ts
│ │ ├── dev.ts
│ │ ├── plugin.ts
│ │ ├── prerender.ts
│ │ ├── shared.ts
│ │ └── types.ts
│ └── tsconfig.json
├── create-impala
│ ├── CHANGELOG.md
│ ├── create-impala.js
│ ├── package-lock.json
│ ├── package.json
│ ├── src
│ │ ├── ambient.d.ts
│ │ └── cli.ts
│ └── tsconfig.json
├── preact
│ ├── CHANGELOG.md
│ ├── README.md
│ ├── client.d.ts
│ ├── head.d.ts
│ ├── package.json
│ ├── src
│ │ ├── client.tsx
│ │ ├── entry-server.tsx
│ │ ├── head-context.tsx
│ │ ├── head.tsx
│ │ └── index.ts
│ └── tsconfig.json
└── react
│ ├── CHANGELOG.md
│ ├── README.md
│ ├── client.d.ts
│ ├── head.d.ts
│ ├── package.json
│ ├── src
│ ├── client.tsx
│ ├── entry-server.tsx
│ ├── head-context.tsx
│ ├── head.tsx
│ └── index.ts
│ └── tsconfig.json
├── pnpm-lock.yaml
├── pnpm-workspace.yaml
├── release-please-config.json
├── scripts
├── compile-template.ts
├── package.json
└── tsconfig.json
└── templates
├── preact-js
├── .gitignore
├── README.md
├── index.html
├── jsconfig.json
├── jsconfig.node.json
├── package.json
├── src
│ ├── App.css
│ ├── App.jsx
│ ├── assets
│ │ └── impala.png
│ ├── entry-client.jsx
│ ├── entry-server.jsx
│ └── routes
│ │ ├── hello.data.js
│ │ ├── hello.jsx
│ │ ├── index.css
│ │ ├── index.jsx
│ │ └── world
│ │ ├── [id].data.js
│ │ └── [id].jsx
└── vite.config.js
├── preact-ts
├── .gitignore
├── README.md
├── index.html
├── package.json
├── src
│ ├── App.css
│ ├── App.tsx
│ ├── assets
│ │ └── impala.png
│ ├── entry-client.tsx
│ ├── entry-server.tsx
│ ├── routes
│ │ ├── hello.data.ts
│ │ ├── hello.tsx
│ │ ├── index.css
│ │ ├── index.tsx
│ │ └── world
│ │ │ ├── [id].data.ts
│ │ │ └── [id].tsx
│ └── vite-env.d.ts
├── tsconfig.json
├── tsconfig.node.json
└── vite.config.ts
├── react-js
├── README.md
├── index.html
├── jsconfig.json
├── jsconfig.node.json
├── package.json
├── src
│ ├── App.css
│ ├── App.jsx
│ ├── assets
│ │ └── impala.png
│ ├── entry-client.jsx
│ ├── entry-server.jsx
│ └── routes
│ │ ├── hello.data.js
│ │ ├── hello.jsx
│ │ ├── index.css
│ │ ├── index.jsx
│ │ └── world
│ │ ├── [id].data.js
│ │ └── [id].jsx
└── vite.config.js
└── react-ts
├── README.md
├── index.html
├── package.json
├── src
├── App.css
├── App.tsx
├── assets
│ └── impala.png
├── entry-client.tsx
├── entry-server.tsx
├── routes
│ ├── hello.data.ts
│ ├── hello.tsx
│ ├── index.css
│ ├── index.tsx
│ └── world
│ │ ├── [id].data.ts
│ │ └── [id].tsx
└── vite-env.d.ts
├── tsconfig.json
├── tsconfig.node.json
└── vite.config.ts
/.github/workflows/release-please.yml:
--------------------------------------------------------------------------------
1 | on:
2 | push:
3 | branches:
4 | - main
5 | name: release-please
6 | jobs:
7 | release-please:
8 | runs-on: ubuntu-latest
9 | permissions:
10 | contents: write
11 | pull-requests: write
12 | steps:
13 | - uses: navikt/github-app-token-generator@v1
14 | id: get-token
15 | with:
16 | private-key: ${{ secrets.APP_PRIVATE_KEY }}
17 | app-id: ${{ secrets.APP_ID }}
18 | - uses: google-github-actions/release-please-action@v3
19 | id: release
20 | with:
21 | command: manifest
22 | token: ${{ steps.get-token.outputs.token }}
23 | - uses: actions/checkout@v3
24 | if: ${{ steps.release.outputs.releases_created }}
25 | - uses: pnpm/action-setup@v2
26 | with:
27 | version: latest
28 | - uses: actions/setup-node@v3
29 | with:
30 | cache: "pnpm"
31 | check-latest: true
32 | registry-url: "https://registry.npmjs.org"
33 | if: ${{ steps.release.outputs.releases_created }}
34 | - name: Install dependencies
35 | run: pnpm install
36 | if: ${{ steps.release.outputs.releases_created }}
37 | - run: pnpm --filter "@impalajs/*" publish
38 | if: ${{ steps.release.outputs.releases_created }}
39 | - name: Authenticate pnpm
40 | run: pnpm config set '//registry.npmjs.org/:_authToken' "${NPM_AUTH_TOKEN}"
41 | if: ${{ steps.release.outputs.releases_created }}
42 | env:
43 | NPM_AUTH_TOKEN: ${{secrets.NPM_TOKEN}}
44 | - run: pnpm --filter "@impalajs/*" publish
45 | if: ${{ steps.release.outputs.releases_created }}
46 |
--------------------------------------------------------------------------------
/.github/workflows/semantic-prs.yml:
--------------------------------------------------------------------------------
1 | name: "Lint PR"
2 |
3 | on:
4 | pull_request_target:
5 | types:
6 | - opened
7 | - edited
8 | - synchronize
9 |
10 | permissions:
11 | pull-requests: write
12 |
13 | jobs:
14 | main:
15 | name: Validate PR title
16 | runs-on: ubuntu-latest
17 | steps:
18 | - uses: amannn/action-semantic-pull-request@v5
19 | with:
20 | types: |
21 | fix
22 | feat
23 | chore
24 | docs
25 | ci
26 | test
27 | revert
28 | id: lint_pr_title
29 | env:
30 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
31 |
32 | - uses: marocchino/sticky-pull-request-comment@v2
33 | # When the previous steps fails, the workflow would stop. By adding this
34 | # condition you can continue the execution with the populated error message.
35 | if: always() && (steps.lint_pr_title.outputs.error_message != null)
36 | with:
37 | header: pr-title-lint-error
38 | message: |
39 | Hey there and thank you for opening this pull request! 👋🏼
40 |
41 | We require pull request titles to follow the [Conventional Commits specification](https://www.conventionalcommits.org/en/v1.0.0/) and it looks like your proposed title needs to be adjusted.
42 | Without this title format, a release will not be triggered
43 |
44 | Details:
45 |
46 | ```
47 | ${{ steps.lint_pr_title.outputs.error_message }}
48 | ```
49 |
50 | # Delete a previous comment when the issue has been resolved
51 | - if: ${{ steps.lint_pr_title.outputs.error_message == null }}
52 | uses: marocchino/sticky-pull-request-comment@v2
53 | with:
54 | header: pr-title-lint-error
55 | delete: true
56 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | pnpm-debug.log*
8 | lerna-debug.log*
9 |
10 | node_modules
11 | dist
12 | dist-ssr
13 | *.local
14 |
15 | # Editor directories and files
16 | .vscode/*
17 | !.vscode/extensions.json
18 | .idea
19 | .DS_Store
20 | *.suo
21 | *.ntvs*
22 | *.njsproj
23 | *.sln
24 | *.sw?
25 |
26 | # Local Netlify folder
27 | .netlify
28 |
--------------------------------------------------------------------------------
/.npmrc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ascorbic/impala/74a4ab04744e8a539a3a7ec1225753c36ac040df/.npmrc
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {}
--------------------------------------------------------------------------------
/.release-please-manifest.json:
--------------------------------------------------------------------------------
1 | {"packages/core":"0.0.14","packages/react":"0.0.14","packages/create-impala":"0.0.8","packages/preact":"0.0.14"}
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Impala
2 |
3 |
4 |
5 |
6 |
7 | Very simple React and Preact static site generator
8 | npm create impala@latest
9 |
10 | Impala is a bare-bones static-site framework, powered by [Vite](https://github.com/vitejs/vite). It currently supports [React](https://react.dev) and [Preact](https://preactjs.com/). Features include:
11 |
12 | - SSG-only, MPA-only. It's iMPAla, not iSPAla.
13 | - File-based routing, with a syntax like [Astro](https://github.com/withastro/astro) and [Solid Start](https://github.com/solidjs/solid-start)
14 | - Static and dynamic routes
15 | - Astro and [Next.js](https://github.com/vercel/next.js/)-inspired data fetching in `getStaticPaths`, and `getRouteData`
16 | - Route-level code-splitting
17 | - Optionally JS-free
18 |
19 | ## Usage
20 |
21 | ## Routing
22 |
23 | Create pages in `src/routes` and they will be available as routes in your site. For example, `src/routes/about.tsx` will be available at `/about`. You can also create dynamic routes, like `src/routes/blog/[slug].tsx`, but you'll need to add a `[slug].data.ts` file with a `getStaticPaths` function to tell Impala what paths to generate and the data to use.
24 |
25 | You can also do catch-all routes, like `src/routes/blog/[...slug].tsx`, which also needs a `[...slug].data.ts` file with a `getStaticPaths` function.
26 |
27 | ## Data fetching
28 |
29 | For dynamic routes you should fetch data in `getStaticPaths`. For static routes you should fetch data in `getRouteData`. For example, if you have a route at `src/routes/blog/[slug].tsx`, you should create a `src/routes/blog/[slug].data.ts` file with a `getStaticPaths` function. This function should return an array of paths to generate, and the data to use for each path.
30 |
31 | See the demo site for more.
32 |
33 | ## FAQ
34 |
35 | ### Why did you build this?
36 |
37 | Mainly to learn, but also because there's no statically-rendered [create-react-app](https://github.com/facebook/create-react-app) equivalent. I often want a simple React site with static rendering but no SSR. Astro is awesome, but I want something that's more vanilla React.
38 |
39 | ### Does it support SSR
40 |
41 | Deliberately not. If you want SSR, use Astro.
42 |
43 | ### Does it support client-side navigation?
44 |
45 | Deliberately not. If you want client-side navigation, use one of the many other SPA frameworks.
46 |
47 | ## License
48 |
49 | Copyright © 2023 Matt Kane. Licenced under the MIT licence.
50 |
51 | Impala logo created by [Freepik - Flaticon](https://www.flaticon.com/free-icons/impala)
52 |
--------------------------------------------------------------------------------
/demo/README.md:
--------------------------------------------------------------------------------
1 | # Impala
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/demo/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/demo/netlify.toml:
--------------------------------------------------------------------------------
1 | [build]
2 | publish = "dist/static"
3 |
4 | [dev]
5 | command = "npm run dev"
--------------------------------------------------------------------------------
/demo/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "impala-demo",
3 | "private": true,
4 | "version": "0.0.0",
5 | "type": "module",
6 | "scripts": {
7 | "dev": "impala dev",
8 | "build:server": "vite build --ssr",
9 | "build:client": "vite build",
10 | "build:prerender": "impala prerender",
11 | "build": "npm run build:client && npm run build:server && npm run build:prerender",
12 | "preview": "vite preview"
13 | },
14 | "dependencies": {
15 | "@impalajs/core": "workspace:*",
16 | "@impalajs/react": "workspace:*",
17 | "react": "^18.2.0",
18 | "react-dom": "^18.2.0"
19 | },
20 | "devDependencies": {
21 | "@types/node": "^18.15.7",
22 | "@types/react": "^18.0.28",
23 | "@types/react-dom": "^18.0.11",
24 | "@vitejs/plugin-react": "^3.1.0",
25 | "create-impala": "workspace:*",
26 | "typescript": "^4.9.3",
27 | "vite": "^6.0.3"
28 | }
29 | }
--------------------------------------------------------------------------------
/demo/src/App.css:
--------------------------------------------------------------------------------
1 | #root {
2 | max-width: 1280px;
3 | margin: 0 auto;
4 | padding: 2rem;
5 | text-align: center;
6 | }
7 |
8 | .logo {
9 | height: 6em;
10 | padding: 1.5em;
11 | will-change: filter;
12 | transition: filter 300ms;
13 | }
14 | .logo:hover {
15 | filter: drop-shadow(0 0 2em #646cffaa);
16 | }
17 | .logo.react:hover {
18 | filter: drop-shadow(0 0 2em #61dafbaa);
19 | }
20 |
21 | @keyframes logo-spin {
22 | from {
23 | transform: rotate(0deg);
24 | }
25 | to {
26 | transform: rotate(360deg);
27 | }
28 | }
29 |
30 | @media (prefers-reduced-motion: no-preference) {
31 | a:nth-of-type(2) .logo {
32 | animation: logo-spin infinite 20s linear;
33 | }
34 | }
35 |
36 | .card {
37 | padding: 2em;
38 | }
39 |
40 | .read-the-docs {
41 | color: #888;
42 | }
43 |
--------------------------------------------------------------------------------
/demo/src/App.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import "./App.css";
3 | import { Head } from "@impalajs/react/head";
4 |
5 | interface AppProps {
6 | title: string;
7 | }
8 |
9 | export const App: React.FC> = ({
10 | children,
11 | title,
12 | }) => {
13 | return (
14 | <>
15 |
16 | {title}
17 |
18 |
19 | {children}
20 | >
21 | );
22 | };
23 |
--------------------------------------------------------------------------------
/demo/src/assets/react.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/demo/src/entry-client.tsx:
--------------------------------------------------------------------------------
1 | import { clientBootstrap, RouteModule } from "@impalajs/react/client";
2 |
3 | const modules = import.meta.glob("./routes/*.{tsx,jsx}");
4 |
5 | clientBootstrap(modules);
6 |
--------------------------------------------------------------------------------
/demo/src/entry-server.tsx:
--------------------------------------------------------------------------------
1 | import type { RouteModule, DataModule } from "@impalajs/react";
2 | export { render } from "@impalajs/react";
3 | export const routeModules = import.meta.glob(
4 | "./routes/**/*.{tsx,jsx}"
5 | );
6 | export const dataModules = import.meta.glob(
7 | "./routes/**/*.data.{ts,js}"
8 | );
9 |
--------------------------------------------------------------------------------
/demo/src/index.css:
--------------------------------------------------------------------------------
1 | :root {
2 | font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
3 | line-height: 1.5;
4 | font-weight: 400;
5 |
6 | color-scheme: light dark;
7 | color: rgba(255, 255, 255, 0.87);
8 | background-color: #242424;
9 |
10 | font-synthesis: none;
11 | text-rendering: optimizeLegibility;
12 | -webkit-font-smoothing: antialiased;
13 | -moz-osx-font-smoothing: grayscale;
14 | -webkit-text-size-adjust: 100%;
15 | }
16 |
17 | a {
18 | font-weight: 500;
19 | color: #646cff;
20 | text-decoration: inherit;
21 | }
22 | a:hover {
23 | color: #535bf2;
24 | }
25 |
26 | body {
27 | margin: 0;
28 | display: flex;
29 | place-items: center;
30 | min-width: 320px;
31 | min-height: 100vh;
32 | }
33 |
34 | h1 {
35 | font-size: 3.2em;
36 | line-height: 1.1;
37 | }
38 |
39 | button {
40 | border-radius: 8px;
41 | border: 1px solid transparent;
42 | padding: 0.6em 1.2em;
43 | font-size: 1em;
44 | font-weight: 500;
45 | font-family: inherit;
46 | background-color: #1a1a1a;
47 | cursor: pointer;
48 | transition: border-color 0.25s;
49 | }
50 | button:hover {
51 | border-color: #646cff;
52 | }
53 | button:focus,
54 | button:focus-visible {
55 | outline: 4px auto -webkit-focus-ring-color;
56 | }
57 |
58 | @media (prefers-color-scheme: light) {
59 | :root {
60 | color: #213547;
61 | background-color: #ffffff;
62 | }
63 | a:hover {
64 | color: #747bff;
65 | }
66 | button {
67 | background-color: #f9f9f9;
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/demo/src/routes/hello.data.ts:
--------------------------------------------------------------------------------
1 | export function getRouteData() {
2 | return {
3 | msg: "hello world",
4 | };
5 | }
6 |
--------------------------------------------------------------------------------
/demo/src/routes/hello.tsx:
--------------------------------------------------------------------------------
1 | import { App } from "../App";
2 | import type { StaticRouteProps } from "@impalajs/core";
3 |
4 | export default function Hello({
5 | path,
6 | routeData,
7 | }: StaticRouteProps) {
8 | return (
9 |
10 |
11 | <>
12 | {routeData?.msg} {path}!
13 | >
14 |
15 |
16 | );
17 | }
18 |
--------------------------------------------------------------------------------
/demo/src/routes/index.tsx:
--------------------------------------------------------------------------------
1 | import type { StaticRouteProps } from "@impalajs/core";
2 | import { useState } from "react";
3 | import { App } from "../App";
4 |
5 | export default function Hello({ path }: StaticRouteProps) {
6 | const [count, setCount] = useState(0);
7 |
8 | return (
9 |
10 | Home {path}!
11 |
12 |
13 |
14 |
15 | );
16 | }
17 |
--------------------------------------------------------------------------------
/demo/src/routes/world/[id].data.ts:
--------------------------------------------------------------------------------
1 | export function getStaticPaths() {
2 | return {
3 | paths: [
4 | { params: { id: "1" }, data: { title: "One", description: "Page one" } },
5 | { params: { id: "2" }, data: { title: "Two", description: "Page two" } },
6 | {
7 | params: { id: "3" },
8 | data: { title: "Three", description: "Page three" },
9 | },
10 | ],
11 | };
12 | }
13 |
--------------------------------------------------------------------------------
/demo/src/routes/world/[id].tsx:
--------------------------------------------------------------------------------
1 | import { DynamicRouteProps } from "@impalajs/core";
2 | import { App } from "../../App";
3 | import { Head } from "@impalajs/react/head";
4 |
5 | export default function Hello({
6 | path,
7 | params,
8 | data,
9 | }: DynamicRouteProps) {
10 | return (
11 |
12 |
13 |
14 |
15 |
16 | Hello {path} {params.id}!
17 |
18 |
19 | );
20 | }
21 |
--------------------------------------------------------------------------------
/demo/src/vite-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/demo/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ESNext",
4 | "useDefineForClassFields": true,
5 | "lib": [
6 | "DOM",
7 | "DOM.Iterable",
8 | "ESNext"
9 | ],
10 | "allowJs": false,
11 | "skipLibCheck": true,
12 | "esModuleInterop": false,
13 | "allowSyntheticDefaultImports": true,
14 | "strict": true,
15 | "forceConsistentCasingInFileNames": true,
16 | "module": "ESNext",
17 | "moduleResolution": "Node",
18 | "resolveJsonModule": true,
19 | "isolatedModules": true,
20 | "noEmit": true,
21 | "jsx": "react-jsx"
22 | },
23 | "include": [
24 | "src"
25 | ],
26 | "references": [
27 | {
28 | "path": "./tsconfig.node.json"
29 | }
30 | ]
31 | }
--------------------------------------------------------------------------------
/demo/tsconfig.node.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "composite": true,
4 | "module": "ESNext",
5 | "moduleResolution": "Node",
6 | "allowSyntheticDefaultImports": true
7 | },
8 | "include": [
9 | "vite.config.ts",
10 | "vite-plugin"
11 | ]
12 | }
--------------------------------------------------------------------------------
/demo/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from "vite";
2 | import react from "@vitejs/plugin-react";
3 | import impala from "@impalajs/core/plugin";
4 |
5 | export default defineConfig({
6 | plugins: [react(), impala()],
7 | });
8 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "impala-workspace",
3 | "version": "0.0.0",
4 | "private": true,
5 | "license": "MIT",
6 | "scripts": {
7 | "build": "pnpm run --filter @impalajs/* build"
8 | },
9 | "packageManager": "pnpm@9.14.4+sha512.c8180b3fbe4e4bca02c94234717896b5529740a6cbadf19fa78254270403ea2f27d4e1d46a08a0f56c89b63dc8ebfd3ee53326da720273794e6200fcf0d184ab",
10 | "pnpm": {
11 | "overrides": {
12 | "@impalajs/core": "workspace:*",
13 | "@impalajs/react": "workspace:*",
14 | "@impalajs/preact": "workspace:*",
15 | "create-impala": "workspace:*"
16 | }
17 | }
18 | }
--------------------------------------------------------------------------------
/packages/core/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | ## [0.0.14](https://github.com/ascorbic/impala/compare/core-v0.0.13...core-v0.0.14) (2024-12-09)
4 |
5 |
6 | ### Bug Fixes
7 |
8 | * support Vite 6 ([#39](https://github.com/ascorbic/impala/issues/39)) ([2221687](https://github.com/ascorbic/impala/commit/2221687544d533570f86b25848cf86d80b34f08d))
9 |
10 | ## [0.0.13](https://github.com/ascorbic/impala/compare/core-v0.0.12...core-v0.0.13) (2023-11-05)
11 |
12 |
13 | ### Bug Fixes
14 |
15 | * use baseUrl for dev routing ([#34](https://github.com/ascorbic/impala/issues/34)) ([8a1e96a](https://github.com/ascorbic/impala/commit/8a1e96a38077bbf93327fd978d35e532f25a90c9))
16 |
17 | ## [0.0.12](https://github.com/ascorbic/impala/compare/core-v0.0.11...core-v0.0.12) (2023-09-30)
18 |
19 |
20 | ### Bug Fixes
21 |
22 | * **core:** use originalUrl for dev routing ([#29](https://github.com/ascorbic/impala/issues/29)) ([4fdd115](https://github.com/ascorbic/impala/commit/4fdd115a1c8c564ee144ce7a16895b9c6dd92636))
23 |
24 | ## [0.0.11](https://github.com/ascorbic/impala/compare/core-v0.0.10...core-v0.0.11) (2023-09-08)
25 |
26 |
27 | ### Bug Fixes
28 |
29 | * ignore query string when routing ([#27](https://github.com/ascorbic/impala/issues/27)) ([d9701ae](https://github.com/ascorbic/impala/commit/d9701ae17393644fe5d5dae489760497777b77c1))
30 |
31 | ## [0.0.10](https://github.com/ascorbic/impala/compare/core-v0.0.9...core-v0.0.10) (2023-04-07)
32 |
33 |
34 | ### Bug Fixes
35 |
36 | * avoid "max call stack" error in asset traversal ([#24](https://github.com/ascorbic/impala/issues/24)) ([c547af6](https://github.com/ascorbic/impala/commit/c547af64f1a810a3a9b26a23fd6f951bddc29f67))
37 |
38 | ## [0.0.9](https://github.com/ascorbic/impala/compare/core-v0.0.8...core-v0.0.9) (2023-04-06)
39 |
40 |
41 | ### Bug Fixes
42 |
43 | * move assets to context ([#22](https://github.com/ascorbic/impala/issues/22)) ([e467cd5](https://github.com/ascorbic/impala/commit/e467cd53e3eab89f56c3a694b8f31fa2a5478272))
44 |
45 | ## [0.0.8](https://github.com/ascorbic/impala/compare/core-v0.0.7...core-v0.0.8) (2023-04-06)
46 |
47 |
48 | ### Features
49 |
50 | * pass assets to render functions ([#20](https://github.com/ascorbic/impala/issues/20)) ([f7792c2](https://github.com/ascorbic/impala/commit/f7792c2a426357b1d16993b878589e638ec68df8))
51 |
52 | ## [0.0.7](https://github.com/ascorbic/impala/compare/core-v0.0.6...core-v0.0.7) (2023-04-05)
53 |
54 |
55 | ### Bug Fixes
56 |
57 | * don't override custom vite config ([#18](https://github.com/ascorbic/impala/issues/18)) ([5dc148a](https://github.com/ascorbic/impala/commit/5dc148aba2575e11fd83d2730d14d0c8ad9fb926))
58 |
59 | ## [0.0.6](https://github.com/ascorbic/impala/compare/core-v0.0.5...core-v0.0.6) (2023-03-28)
60 |
61 |
62 | ### Bug Fixes
63 |
64 | * correctly add links to head in dev mode ([#12](https://github.com/ascorbic/impala/issues/12)) ([5540a3d](https://github.com/ascorbic/impala/commit/5540a3d54edb608bba33e4023650e3eef63e3493))
65 |
66 | ## 0.0.5 (2023-03-28)
67 |
68 |
69 | ### Features
70 |
71 | * add dev server ([#2](https://github.com/ascorbic/impala/issues/2)) ([bab917a](https://github.com/ascorbic/impala/commit/bab917a28df70d9df691f7d1db61bf6e140b7acb))
72 | * add plugin ([544d4fa](https://github.com/ascorbic/impala/commit/544d4fa27d8e5eac8eb2858c8900c8cc7ce44755))
73 | * add support for JavaScript ([#8](https://github.com/ascorbic/impala/issues/8)) ([ea5d1fa](https://github.com/ascorbic/impala/commit/ea5d1fa59623ae70c3ead2b58d5076e5d6605c74))
74 | * load or preload assets in the head ([c5be211](https://github.com/ascorbic/impala/commit/c5be211614a712893e4a9c356850623683bf964d))
75 |
76 |
77 | ### Bug Fixes
78 |
79 | * fix cli wrapper ([80172b2](https://github.com/ascorbic/impala/commit/80172b2cdc146ae2b248b79f20eb4cd98ea89b40))
80 | * pass props to pages properly ([6b18945](https://github.com/ascorbic/impala/commit/6b189453d821ad85fdf828f5d270c754fecb0b26))
81 | * pass props to pages properly ([#3](https://github.com/ascorbic/impala/issues/3)) ([ee3bb82](https://github.com/ascorbic/impala/commit/ee3bb8279987dcdd0655ef02a53bad883ee3413a))
82 |
--------------------------------------------------------------------------------
/packages/core/README.md:
--------------------------------------------------------------------------------
1 | # Impala
2 |
3 |
4 |
5 | 
6 |
7 |
8 |
--------------------------------------------------------------------------------
/packages/core/cli.mjs:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | const currentVersion = process.versions.node;
3 | const currentMajor = parseInt(currentVersion.split(".")[0], 10);
4 | const minimumMajorVersion = 16;
5 |
6 | if (currentMajor < minimumMajorVersion) {
7 | console.error(`Node.js v${currentVersion} is unsupported!`);
8 | console.error(`Please use Node.js v${minimumMajorVersion} or higher.`);
9 | process.exit(1);
10 | }
11 |
12 | import("./dist/cli.mjs").then(({ main }) => main());
13 |
--------------------------------------------------------------------------------
/packages/core/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@impalajs/core",
3 | "version": "0.0.14",
4 | "description": "",
5 | "bin": {
6 | "impala": "./cli.mjs"
7 | },
8 | "scripts": {
9 | "build": "tsup src/core.ts src/cli.ts src/plugin.ts --format esm --dts --clean"
10 | },
11 | "module": "./dist/core.mjs",
12 | "types": "./dist/core.d.ts",
13 | "exports": {
14 | ".": {
15 | "types": "./dist/core.d.ts",
16 | "import": "./dist/core.mjs"
17 | },
18 | "./plugin": {
19 | "types": "./dist/plugin.d.ts",
20 | "import": "./dist/plugin.mjs"
21 | }
22 | },
23 | "keywords": [],
24 | "author": "",
25 | "license": "MIT",
26 | "devDependencies": {
27 | "@types/express": "^4.17.17",
28 | "tsup": "^6.7.0",
29 | "vite": "^6.0.3"
30 | },
31 | "peerDependencies": {
32 | "vite": ">=4"
33 | },
34 | "dependencies": {
35 | "express": "^4.18.2",
36 | "path-to-regexp": "^6.2.1",
37 | "radix3": "^1.0.0"
38 | },
39 | "engines": {
40 | "node": ">=18.0.0"
41 | },
42 | "publishConfig": {
43 | "access": "public"
44 | }
45 | }
--------------------------------------------------------------------------------
/packages/core/plugin.d.ts:
--------------------------------------------------------------------------------
1 | export { default } from "./dist/plugin";
2 |
--------------------------------------------------------------------------------
/packages/core/src/cli.ts:
--------------------------------------------------------------------------------
1 | import { resolve } from "node:path";
2 | import { createServer } from "./dev";
3 | import { prerender } from "./prerender";
4 |
5 | export async function main() {
6 | const [_, __, command, args] = process.argv;
7 |
8 | if (!command) {
9 | console.error("Command not specified");
10 | process.exit(1);
11 | }
12 |
13 | if (command === "prerender") {
14 | const root = args ? resolve(args) : process.cwd();
15 | await prerender(root);
16 | } else if (command === "dev") {
17 | createServer();
18 | } else {
19 | console.error("Command not supported. Supported commands: dev, prerender");
20 | process.exit(1);
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/packages/core/src/convertPathToURLPattern.ts:
--------------------------------------------------------------------------------
1 | export function convertPathToPattern(path: string): string {
2 | return path
3 | .replace(/(\/index)?\.[jt]sx?$/, "")
4 | .replace(/(?:\[(\.{3})?(\w+)\])/g, (_match, isCatchAll, paramName) => {
5 | if (isCatchAll) {
6 | return `:${paramName}*`;
7 | } else {
8 | return `:${paramName}`;
9 | }
10 | });
11 | }
12 |
13 | type Segment = {
14 | content: string;
15 | isDynamic: boolean;
16 | isCatchAll: boolean;
17 | };
18 |
19 | type Pattern = {
20 | segments: Array;
21 | pattern: string;
22 | };
23 |
24 | function convertPatternToSegments(pattern: string): Array {
25 | return pattern.split("/").map((content) => {
26 | const isDynamic = content.startsWith(":");
27 | const isCatchAll = isDynamic && content.endsWith("*");
28 | return {
29 | content,
30 | isDynamic,
31 | isCatchAll,
32 | };
33 | });
34 | }
35 |
36 | function comparePatterns(a: Pattern, b: Pattern): number {
37 | const length = Math.max(a.segments.length, b.segments.length);
38 | for (let index = 0; index < length; index++) {
39 | const p1 = a.segments[index];
40 | const p2 = b.segments[index];
41 |
42 | if (p1 === undefined) {
43 | return 1;
44 | }
45 | if (p2 === undefined) {
46 | return -1;
47 | }
48 | if (p1.isCatchAll && !p2.isCatchAll) {
49 | return 1;
50 | }
51 | if (p2.isCatchAll && !p1.isCatchAll) {
52 | return -1;
53 | }
54 | if (p1.isDynamic && !p2.isDynamic) {
55 | return 1;
56 | }
57 | if (p2.isDynamic && !p1.isDynamic) {
58 | // a is more specific
59 | return -1;
60 | }
61 | }
62 | // if we get here, fall back to string comparison
63 | return a.pattern.localeCompare(b.pattern);
64 | }
65 |
66 | export function sortPatterns(patterns: Array): Array {
67 | return Array.from(new Set(patterns))
68 | .map((pattern) => {
69 | return {
70 | segments: convertPatternToSegments(pattern),
71 | pattern,
72 | };
73 | })
74 | .sort(comparePatterns)
75 | .map((pattern) => pattern.pattern);
76 | }
77 |
--------------------------------------------------------------------------------
/packages/core/src/core.ts:
--------------------------------------------------------------------------------
1 | import { ModuleImports, RouteModule } from "./types";
2 | import { createRouter, RadixRouter } from "radix3";
3 | export * from "./shared";
4 | export * from "./types";
5 |
6 | export function convertPathToPattern(input: string): string {
7 | return input
8 | .replace(/^\.\/routes/, "")
9 | .replace(/(\/index)?\.[jt]sx?$/, "")
10 | .replace(/(?:\[(\.{3})?(\w+)\])/g, (_match, isCatchAll, paramName) => {
11 | if (isCatchAll) {
12 | return `:${paramName}*`;
13 | } else {
14 | return `:${paramName}`;
15 | }
16 | });
17 | }
18 |
19 | /**
20 | * Converts a module path to a radix3 route
21 | */
22 | export function convertPathToRoute(input: string): string {
23 | return input
24 | .replace(/^\.\/routes/, "")
25 | .replace(/(\/index)?\.[jt]sx?$/, "")
26 | .replace(/(?:\[(\.{3})?(\w+)\])/g, (_match, isCatchAll, paramName) => {
27 | if (isCatchAll) {
28 | return `**:${paramName}`;
29 | } else {
30 | return `:${paramName}`;
31 | }
32 | });
33 | }
34 |
35 | export function routerForModules(
36 | modules: ModuleImports
37 | ): RadixRouter<{ chunk: string }> {
38 | const router = createRouter<{ chunk: string }>();
39 | for (const path in modules) {
40 | router.insert(convertPathToRoute(path), { chunk: path });
41 | }
42 | return router;
43 | }
44 |
--------------------------------------------------------------------------------
/packages/core/src/dev.ts:
--------------------------------------------------------------------------------
1 | import express from "express";
2 | import { createServer as createViteServer } from "vite";
3 | import { routerForModules } from "./core";
4 | import { ServerEntry } from "./types";
5 | import { readFileSync } from "node:fs";
6 | import { resolve } from "node:path";
7 | import { renderLinkTagsForModuleNode } from "./shared";
8 |
9 | function isDynamicRoute(route: string) {
10 | return route.includes("/[");
11 | }
12 |
13 | function stripExtension(path: string) {
14 | return path.replace(/\.[^/.]+$/, "");
15 | }
16 |
17 | function shallowCompare(
18 | obj1?: Record,
19 | obj2?: Record
20 | ): boolean {
21 | // Check if both objects are null or undefined
22 | if (obj1 == obj2) {
23 | return true;
24 | }
25 |
26 | if (!obj1 || !obj2) {
27 | return false;
28 | }
29 |
30 | const keys1 = Object.keys(obj1);
31 | const keys2 = Object.keys(obj2);
32 |
33 | if (keys1.length !== keys2.length) {
34 | return false;
35 | }
36 | for (const key of keys1) {
37 | if (obj1[key] !== obj2[key]) {
38 | return false;
39 | }
40 | }
41 |
42 | return true;
43 | }
44 |
45 | /**
46 | * We can't use the vite dev server directly, because we need to do SSR.
47 | * Because of this, we create a custom server and use the vite server as middleware.
48 | */
49 | export async function createServer() {
50 | console.log("Starting dev server!");
51 | const app = express();
52 |
53 | const vite = await createViteServer({
54 | server: { middlewareMode: true },
55 | appType: "custom",
56 | });
57 | app.use(vite.middlewares);
58 |
59 | app.use("*", async (req, res, next) => {
60 | try {
61 | const template = await readFileSync(
62 | resolve(vite.config.root, "index.html"),
63 | "utf-8"
64 | );
65 |
66 | const { render, routeModules, dataModules } = (await vite.ssrLoadModule(
67 | "/src/entry-server"
68 | )) as ServerEntry;
69 |
70 | const router = routerForModules(routeModules);
71 |
72 | const result = router.lookup(req.baseUrl);
73 |
74 | if (!result) {
75 | res.status(404).end("404");
76 | return;
77 | }
78 |
79 | const mod = routeModules[result.chunk];
80 | const baseRoute = stripExtension(result.chunk);
81 | // Try and find a data module for this route
82 | const dataMod =
83 | dataModules[`${baseRoute}.data.ts`] ||
84 | dataModules[`${baseRoute}.data.js`];
85 |
86 | const { getStaticPaths, getRouteData } = (await dataMod?.()) || {};
87 |
88 | const routeData = await getRouteData?.();
89 |
90 | let data: Awaited<
91 | ReturnType
92 | >["paths"][number]["data"];
93 |
94 | if (isDynamicRoute(result.chunk)) {
95 | const { paths } = (await getStaticPaths?.()) || {};
96 | const matched = paths?.find((p) =>
97 | shallowCompare(p.params, result.params)
98 | );
99 | data = matched?.data;
100 | console.log({ matched });
101 |
102 | if (!matched) {
103 | console.log("No match for dynamic route", result.chunk, paths);
104 | res.status(404).end("404");
105 | return;
106 | }
107 | }
108 |
109 | const context = {
110 | path: req.originalUrl,
111 | routeData,
112 | chunk: result.chunk,
113 | params: result.params,
114 | data,
115 | };
116 |
117 | const { body, head } = await render(context, mod, []);
118 | const node = vite.moduleGraph.urlToModuleMap.get(
119 | result.chunk.replace(/^\.\//, "/src/")
120 | );
121 | const links = node ? renderLinkTagsForModuleNode(node) : "";
122 | const transformed = await vite.transformIndexHtml(
123 | req.originalUrl,
124 | template
125 | .replace("", head + links)
126 | .replace("", body)
127 | );
128 |
129 | res.status(200).set({ "Content-Type": "text/html" }).end(transformed);
130 | } catch (e) {
131 | vite.ssrFixStacktrace(e as Error);
132 | next(e);
133 | }
134 | });
135 |
136 | app.listen(5173);
137 | console.log("Listening on http://localhost:5173");
138 | }
139 |
--------------------------------------------------------------------------------
/packages/core/src/plugin.ts:
--------------------------------------------------------------------------------
1 | import { Plugin, ResolvedConfig } from "vite";
2 |
3 | export default function plugin(): Plugin {
4 | // const virtualModuleId = "virtual:route-manifest";
5 |
6 | let config: ResolvedConfig;
7 | // const resolvedVirtualModuleId = "\0" + virtualModuleId;
8 | // console.log("resolvedVirtualModuleId", resolvedVirtualModuleId);
9 | return {
10 | name: "impala-plugin",
11 |
12 | config(config) {
13 | config.build ||= {};
14 | config.build.manifest = true;
15 |
16 | if (config.build.ssr) {
17 | if (config.build.ssr === true) {
18 | config.build.ssr = "src/entry-server";
19 | }
20 | config.build.outDir ||= "dist/server";
21 | } else {
22 | config.build.outDir ||= "dist/static";
23 | config.build.ssrManifest = true;
24 | config.build.rollupOptions ||= {};
25 | config.build.rollupOptions.input ||= "index.html";
26 | }
27 | },
28 |
29 | configResolved(resolvedConfig) {
30 | config = resolvedConfig;
31 | },
32 | // resolveId(id, source) {
33 | // if (id === virtualModuleId) {
34 | // return resolvedVirtualModuleId;
35 | // }
36 | // },
37 | // load(id) {
38 | // // if (id.endsWith("tsx")) {
39 | // // console.log("id", this.getModuleInfo(id));
40 | // // }
41 | // if (id === resolvedVirtualModuleId) {
42 | // return `export const msg = "from virtual module"`;
43 | // }
44 | // },
45 | };
46 | }
47 |
--------------------------------------------------------------------------------
/packages/core/src/prerender.ts:
--------------------------------------------------------------------------------
1 | import { promises as fs, existsSync } from "node:fs";
2 | import path from "node:path";
3 | import { compile } from "path-to-regexp";
4 | import { convertPathToPattern } from "./core";
5 | import {
6 | findAssetsInManifest,
7 | renderAssetLinkTags,
8 | renderLinkTagsForManifestChunk,
9 | } from "./shared";
10 | import { Context, RouteModuleFunction, ServerEntry } from "./types";
11 |
12 | function isDynamicRoute(route: string) {
13 | return route.includes("/[");
14 | }
15 |
16 | function stripExtension(path: string) {
17 | return path.replace(/\.[^/.]+$/, "");
18 | }
19 |
20 | export async function prerender(root: string) {
21 | const { render, routeModules, dataModules } = (await import(
22 | path.resolve(root, "./dist/server/entry-server.js")
23 | )) as ServerEntry;
24 |
25 | const template = await fs.readFile(
26 | path.resolve(root, "./dist/static/index.html"),
27 | "utf-8"
28 | );
29 | const manifestPaths = [
30 | "dist/static/manifest.json",
31 | "dist/static/.vite/manifest.json",
32 | ].map((p) => path.resolve(root, p));
33 |
34 | const manifestPath = manifestPaths.find((p) => existsSync(p));
35 | if (!manifestPath) {
36 | console.error(
37 | `No manifest found at ${manifestPath}. Did you build the app?`
38 | );
39 | return;
40 | }
41 | const manifest = JSON.parse(await fs.readFile(manifestPath, "utf-8"));
42 |
43 | const assetMap = new Map();
44 |
45 | async function prerenderRoute(context: Context, mod: RouteModuleFunction) {
46 | const { body, head } = await render(context, mod, []);
47 |
48 | // If the render function returns false for head, it means that the
49 | // body is the full document, so we don't need to wrap it in the
50 | // template.
51 | const appHtml =
52 | head === false
53 | ? body
54 | : template
55 | .replace(
56 | "",
57 | renderAssetLinkTags(context.assets ?? []) + head
58 | )
59 | .replace("", body);
60 |
61 | const filePath = `dist/static${
62 | context.path === "/" ? "/index" : context.path
63 | }.html`;
64 |
65 | const dir = path.dirname(path.resolve(root, filePath));
66 | if (!existsSync(dir)) {
67 | await fs.mkdir(dir, {
68 | recursive: true,
69 | });
70 | }
71 | if (!(await fs.stat(dir)).isDirectory()) {
72 | console.error(`Cannot create directory: ${dir}`);
73 | return;
74 | }
75 |
76 | await fs.writeFile(filePath, appHtml);
77 | console.log("pre-rendered:", filePath);
78 | }
79 |
80 | for (const route in routeModules) {
81 | if (Object.hasOwnProperty.call(routeModules, route)) {
82 | const mod = routeModules[route];
83 | const baseRoute = stripExtension(route);
84 | const dataMod =
85 | dataModules[`${baseRoute}.data.ts`] ||
86 | dataModules[`${baseRoute}.data.js`];
87 |
88 | const { getStaticPaths, getRouteData } = (await dataMod?.()) || {};
89 |
90 | const routeData = await getRouteData?.();
91 | const routePattern = convertPathToPattern(route) || "/";
92 | const assets = findAssetsInManifest(
93 | manifest,
94 | route.replace(/^\.\//, "src/"),
95 | assetMap
96 | );
97 | if (isDynamicRoute(route)) {
98 | console.log(`pre-rendering dynamic route: ${route}`);
99 | if (!dataMod) {
100 | console.warn(`No data module found for dynamic route: ${route}`);
101 | console.warn(
102 | `You should export 'getStaticPaths' from ${baseRoute}.data.ts`
103 | );
104 | continue;
105 | }
106 |
107 | if (!getStaticPaths) {
108 | console.warn(
109 | `No 'getStaticPaths' export found in ${baseRoute}.data.ts. No pages will be generated.`
110 | );
111 | continue;
112 | }
113 |
114 | const toPath = compile(routePattern, { encode: encodeURIComponent });
115 |
116 | const { paths } = await getStaticPaths();
117 |
118 | await Promise.all(
119 | paths.map((pathInfo) => {
120 | const path = toPath(pathInfo.params);
121 | if (!path) {
122 | console.error(`Invalid path params for route: ${route}`);
123 | return;
124 | }
125 |
126 | return prerenderRoute(
127 | {
128 | path,
129 | routeData,
130 | chunk: route,
131 | params: pathInfo.params,
132 | data: pathInfo.data,
133 | assets,
134 | },
135 | mod
136 | );
137 | })
138 | );
139 | } else {
140 | await prerenderRoute(
141 | { path: routePattern, chunk: route, assets, routeData },
142 | mod
143 | );
144 | }
145 | }
146 | }
147 | }
148 |
--------------------------------------------------------------------------------
/packages/core/src/shared.ts:
--------------------------------------------------------------------------------
1 | import { basename } from "node:path";
2 | import { ModuleNode, Manifest } from "vite";
3 |
4 | /**
5 | * Traverses the module graph and collects assets for a given chunk
6 | *
7 | * @param manifest Client manifest
8 | * @param id Chunk id
9 | * @param assetMap Cache of assets
10 | * @returns Array of asset URLs
11 | */
12 | export const findAssetsInManifest = (
13 | manifest: Manifest,
14 | id: string,
15 | assetMap: Map> = new Map()
16 | ): Array => {
17 | function traverse(id: string): Array {
18 | const cached = assetMap.get(id);
19 | if (cached) {
20 | return cached;
21 | }
22 | const chunk = manifest[id];
23 | if (!chunk) {
24 | return [];
25 | }
26 | const assets = [
27 | ...(chunk.assets || []),
28 | ...(chunk.css || []),
29 | ...(chunk.imports?.flatMap(traverse) || []),
30 | ];
31 | const imports = chunk.imports?.flatMap(traverse) || [];
32 | const all = [...assets, ...imports].filter(
33 | Boolean as unknown as (a: string | undefined) => a is string
34 | );
35 | all.push(chunk.file);
36 | assetMap.set(id, all);
37 | return Array.from(new Set(all));
38 | }
39 | return traverse(id);
40 | };
41 |
42 | export const findAssetsInModuleNode = (moduleNode: ModuleNode) => {
43 | const seen = new Set();
44 | function traverse(node: ModuleNode): Array {
45 | if (seen.has(node.url)) {
46 | return [];
47 | }
48 | seen.add(node.url);
49 |
50 | const imports = [...node.importedModules].flatMap(traverse) || [];
51 | imports.push(node.url);
52 | return Array.from(new Set(imports));
53 | }
54 | return traverse(moduleNode);
55 | };
56 |
57 | /**
58 | * Traverses the module graph and generates link tags to either import or preload asets
59 | */
60 | export function renderLinkTagsForManifestChunk(
61 | manifest: Manifest,
62 | id: string,
63 | cachedAssetMap?: Map>
64 | ): string {
65 | const assets = findAssetsInManifest(manifest, id, cachedAssetMap);
66 | return assets.map((asset) => renderLinkTag(`/${asset}`)).join("");
67 | }
68 |
69 | /**
70 | * Finds all the imported modules for a given module and generates link tags to
71 | * either import or preload them.
72 | */
73 | export const renderLinkTagsForModuleNode = (node: ModuleNode): string => {
74 | const assets = findAssetsInModuleNode(node);
75 | return assets.map(renderLinkTag).join("");
76 | };
77 |
78 | export function renderAssetLinkTags(assets: Array): string {
79 | return assets.map((asset) => renderLinkTag(`/${asset}`)).join("");
80 | }
81 |
82 | interface LinkProps {
83 | rel: string;
84 | as?: string;
85 | type?: string;
86 | crossorigin?: string;
87 | }
88 |
89 | const linkPropsMap: Map = new Map([
90 | ["js", { rel: "modulepreload", crossorigin: "true" }],
91 | ["jsx", { rel: "modulepreload", crossorigin: "true" }],
92 | ["ts", { rel: "modulepreload", crossorigin: "true" }],
93 | ["tsx", { rel: "modulepreload", crossorigin: "true" }],
94 | ["css", { rel: "stylesheet" }],
95 | [
96 | "woff",
97 | { rel: "preload", as: "font", type: "font/woff", crossorigin: "true" },
98 | ],
99 | [
100 | "woff2",
101 | { rel: "preload", as: "font", type: "font/woff2", crossorigin: "true" },
102 | ],
103 | ["gif", { rel: "preload", as: "image", type: "image/gif" }],
104 | ["jpg", { rel: "preload", as: "image", type: "image/jpeg" }],
105 | ["jpeg", { rel: "preload", as: "image", type: "image/jpeg" }],
106 | ["png", { rel: "preload", as: "image", type: "image/png" }],
107 | ["webp", { rel: "preload", as: "image", type: "image/webp" }],
108 | ["svg", { rel: "preload", as: "image", type: "image/svg+xml" }],
109 | ["ico", { rel: "preload", as: "image", type: "image/x-icon" }],
110 | ["avif", { rel: "preload", as: "image", type: "image/avif" }],
111 | ["mp4", { rel: "preload", as: "video", type: "video/mp4" }],
112 | ["webm", { rel: "preload", as: "video", type: "video/webm" }],
113 | ]);
114 |
115 | /**
116 | * Generates a link tag for a given file. This will load stylesheets and preload
117 | * everything else. It uses the file extension to determine the type.
118 | */
119 |
120 | export function renderLinkTag(file: string) {
121 | const ext = basename(file).split(".").pop();
122 | if (!ext) {
123 | return "";
124 | }
125 | const props = linkPropsMap.get(ext);
126 | if (!props) {
127 | return "";
128 | }
129 | const attrs = Object.entries(props)
130 | .map(([key, value]) => `${key}="${value}"`)
131 | .join(" ");
132 | return ``;
133 | }
134 |
--------------------------------------------------------------------------------
/packages/core/src/types.ts:
--------------------------------------------------------------------------------
1 | export type ModuleImports = Record<
2 | string,
3 | () => Promise
4 | >;
5 |
6 | export interface Context {
7 | path: string;
8 | chunk: string;
9 | data?: TData;
10 | routeData?: TRouteData;
11 | params?: Record;
12 | assets?: Array;
13 | }
14 |
15 | export interface RouteModule {
16 | default: TElement;
17 | }
18 |
19 | export interface StaticDataModule {
20 | getRouteData?: () => Promise | TRouteData;
21 | }
22 |
23 | export interface DynamicDataModule<
24 | TPathData extends Record = Record,
25 | TRouteData extends Record = Record
26 | > extends StaticDataModule {
27 | getStaticPaths: () =>
28 | | Promise<{ paths: Array> }>
29 | | { paths: Array> };
30 | }
31 |
32 | export interface PathInfo> {
33 | params: Record;
34 | data?: TData;
35 | }
36 |
37 | export type RouteModuleFunction = () => Promise<
38 | RouteModule
39 | >;
40 |
41 | export interface ServerEntry {
42 | routeModules: ModuleImports>;
43 | dataModules: ModuleImports;
44 | render(
45 | context: Context,
46 | mod: RouteModuleFunction,
47 | bootstrapModules?: Array
48 | ): Promise<{
49 | body: string;
50 | head: string | false;
51 | }>;
52 | }
53 |
54 | export type DataModule = DynamicDataModule;
55 |
56 | export type DataType =
57 | StaticPaths["paths"][number];
58 |
59 | export type ReturnTypeIfDefined any) | undefined> =
60 | T extends undefined ? undefined : ReturnType>;
61 |
62 | export type AwaitedIfPromise = T extends PromiseLike ? Awaited : T;
63 | export type StaticPaths = AwaitedIfPromise<
64 | ReturnTypeIfDefined
65 | >;
66 |
67 | export type RouteData = AwaitedIfPromise<
68 | ReturnTypeIfDefined
69 | >;
70 |
71 | export interface StaticRouteProps<
72 | Mod extends StaticDataModule | undefined = undefined
73 | > {
74 | path: string;
75 | routeData?: Mod extends undefined
76 | ? undefined
77 | : RouteData>;
78 | }
79 |
80 | export interface DynamicRouteProps
81 | extends StaticRouteProps {
82 | params: DataType["params"];
83 | data: DataType["data"];
84 | }
85 |
--------------------------------------------------------------------------------
/packages/core/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ESNext",
4 | "useDefineForClassFields": true,
5 | "lib": [
6 | "ESNext",
7 | "DOM"
8 | ],
9 | "allowJs": false,
10 | "skipLibCheck": true,
11 | "esModuleInterop": false,
12 | "allowSyntheticDefaultImports": true,
13 | "strict": true,
14 | "forceConsistentCasingInFileNames": true,
15 | "module": "ESNext",
16 | "moduleResolution": "Node",
17 | "resolveJsonModule": true,
18 | "isolatedModules": true,
19 | "noEmit": true,
20 | "jsx": "react-jsx"
21 | },
22 | "include": [
23 | "src"
24 | ]
25 | }
--------------------------------------------------------------------------------
/packages/create-impala/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | ## [0.0.8](https://github.com/ascorbic/impala/compare/create-impala-v0.0.7...create-impala-v0.0.8) (2024-12-09)
4 |
5 |
6 | ### Bug Fixes
7 |
8 | * support Vite 6 ([#39](https://github.com/ascorbic/impala/issues/39)) ([2221687](https://github.com/ascorbic/impala/commit/2221687544d533570f86b25848cf86d80b34f08d))
9 |
10 | ## [0.0.7](https://github.com/ascorbic/impala/compare/create-impala-v0.0.6...create-impala-v0.0.7) (2023-04-02)
11 |
12 |
13 | ### Bug Fixes
14 |
15 | * Ensure users can exit from framework prompt step ([#16](https://github.com/ascorbic/impala/issues/16)) ([7c84501](https://github.com/ascorbic/impala/commit/7c84501bf5a06d5672fb95d10ade16bdec8ff4b7))
16 |
17 | ## [0.0.6](https://github.com/ascorbic/impala/compare/create-impala-v0.0.5...create-impala-v0.0.6) (2023-03-28)
18 |
19 |
20 | ### Features
21 |
22 | * add preact support ([#9](https://github.com/ascorbic/impala/issues/9)) ([ef71048](https://github.com/ascorbic/impala/commit/ef710486657819cbf6addaa1aaff671931b5ed4f))
23 |
24 | ## 0.0.5 (2023-03-28)
25 |
26 |
27 | ### Features
28 |
29 | * add support for JavaScript ([#8](https://github.com/ascorbic/impala/issues/8)) ([ea5d1fa](https://github.com/ascorbic/impala/commit/ea5d1fa59623ae70c3ead2b58d5076e5d6605c74))
30 | * **create-impala:** add create-impala cli ([d40b61c](https://github.com/ascorbic/impala/commit/d40b61c469223bc88d62fce156790ecaf2090e49))
31 |
32 |
33 | ### Bug Fixes
34 |
35 | * fix cli wrapper ([80172b2](https://github.com/ascorbic/impala/commit/80172b2cdc146ae2b248b79f20eb4cd98ea89b40))
36 |
--------------------------------------------------------------------------------
/packages/create-impala/create-impala.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | const currentVersion = process.versions.node;
3 | const currentMajor = parseInt(currentVersion.split(".")[0], 10);
4 | const minimumMajorVersion = 16;
5 |
6 | if (currentMajor < minimumMajorVersion) {
7 | console.error(`Node.js v${currentVersion} is unsupported!`);
8 | console.error(`Please use Node.js v${minimumMajorVersion} or higher.`);
9 | process.exit(1);
10 | }
11 |
12 | import("./dist/cli.js").then(({ createImpala }) => createImpala());
13 |
--------------------------------------------------------------------------------
/packages/create-impala/package-lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "create-impala",
3 | "version": "0.0.8",
4 | "lockfileVersion": 2,
5 | "requires": true,
6 | "packages": {
7 | "": {
8 | "name": "create-impala",
9 | "version": "0.0.8",
10 | "license": "MIT",
11 | "bin": {
12 | "create-impala": "dist/cli.js"
13 | },
14 | "devDependencies": {
15 | "@clack/prompts": "^0.6.3",
16 | "degit": "^2.8.4",
17 | "tsup": "^6.7.0"
18 | }
19 | },
20 | "../../node_modules/.pnpm/@clack+prompts@0.6.3/node_modules/@clack/prompts": {
21 | "version": "0.6.3",
22 | "bundleDependencies": [
23 | "is-unicode-supported"
24 | ],
25 | "dev": true,
26 | "license": "MIT",
27 | "dependencies": {
28 | "@clack/core": "^0.3.2",
29 | "picocolors": "^1.0.0",
30 | "sisteransi": "^1.0.5"
31 | },
32 | "devDependencies": {
33 | "is-unicode-supported": "^1.3.0"
34 | }
35 | },
36 | "../../node_modules/.pnpm/@clack+prompts@0.6.3/node_modules/@clack/prompts/node_modules/is-unicode-supported": {
37 | "version": "1.3.0",
38 | "dev": true,
39 | "inBundle": true,
40 | "license": "MIT",
41 | "engines": {
42 | "node": ">=12"
43 | },
44 | "funding": {
45 | "url": "https://github.com/sponsors/sindresorhus"
46 | }
47 | },
48 | "../../node_modules/.pnpm/tsup@6.7.0/node_modules/tsup": {
49 | "version": "6.7.0",
50 | "dev": true,
51 | "license": "MIT",
52 | "dependencies": {
53 | "bundle-require": "^4.0.0",
54 | "cac": "^6.7.12",
55 | "chokidar": "^3.5.1",
56 | "debug": "^4.3.1",
57 | "esbuild": "^0.17.6",
58 | "execa": "^5.0.0",
59 | "globby": "^11.0.3",
60 | "joycon": "^3.0.1",
61 | "postcss-load-config": "^3.0.1",
62 | "resolve-from": "^5.0.0",
63 | "rollup": "^3.2.5",
64 | "source-map": "0.8.0-beta.0",
65 | "sucrase": "^3.20.3",
66 | "tree-kill": "^1.2.2"
67 | },
68 | "bin": {
69 | "tsup": "dist/cli-default.js",
70 | "tsup-node": "dist/cli-node.js"
71 | },
72 | "devDependencies": {
73 | "@rollup/plugin-json": "5.0.1",
74 | "@swc/core": "1.2.218",
75 | "@types/debug": "4.1.7",
76 | "@types/flat": "5.0.2",
77 | "@types/fs-extra": "9.0.13",
78 | "@types/node": "14.18.12",
79 | "@types/resolve": "1.20.1",
80 | "colorette": "2.0.16",
81 | "consola": "2.15.3",
82 | "flat": "5.0.2",
83 | "fs-extra": "10.0.0",
84 | "postcss": "8.4.12",
85 | "postcss-simple-vars": "6.0.3",
86 | "prettier": "2.5.1",
87 | "resolve": "1.20.0",
88 | "rollup-plugin-dts": "5.3.0",
89 | "rollup-plugin-hashbang": "2.2.2",
90 | "strip-json-comments": "4.0.0",
91 | "svelte": "3.46.4",
92 | "terser": "^5.16.0",
93 | "ts-essentials": "9.1.2",
94 | "tsconfig-paths": "3.12.0",
95 | "tsup": "6.6.1",
96 | "typescript": "5.0.2",
97 | "vitest": "0.28.4",
98 | "wait-for-expect": "3.0.2"
99 | },
100 | "engines": {
101 | "node": ">=14.18"
102 | },
103 | "peerDependencies": {
104 | "@swc/core": "^1",
105 | "postcss": "^8.4.12",
106 | "typescript": ">=4.1.0"
107 | },
108 | "peerDependenciesMeta": {
109 | "@swc/core": {
110 | "optional": true
111 | },
112 | "postcss": {
113 | "optional": true
114 | },
115 | "typescript": {
116 | "optional": true
117 | }
118 | }
119 | },
120 | "node_modules/@clack/prompts": {
121 | "resolved": "../../node_modules/.pnpm/@clack+prompts@0.6.3/node_modules/@clack/prompts",
122 | "link": true
123 | },
124 | "node_modules/degit": {
125 | "version": "2.8.4",
126 | "resolved": "https://registry.npmjs.org/degit/-/degit-2.8.4.tgz",
127 | "integrity": "sha512-vqYuzmSA5I50J882jd+AbAhQtgK6bdKUJIex1JNfEUPENCgYsxugzKVZlFyMwV4i06MmnV47/Iqi5Io86zf3Ng==",
128 | "dev": true,
129 | "bin": {
130 | "degit": "degit"
131 | },
132 | "engines": {
133 | "node": ">=8.0.0"
134 | }
135 | },
136 | "node_modules/tsup": {
137 | "resolved": "../../node_modules/.pnpm/tsup@6.7.0/node_modules/tsup",
138 | "link": true
139 | }
140 | },
141 | "dependencies": {
142 | "@clack/prompts": {
143 | "version": "file:../../node_modules/.pnpm/@clack+prompts@0.6.3/node_modules/@clack/prompts",
144 | "requires": {
145 | "@clack/core": "^0.3.2",
146 | "is-unicode-supported": "^1.3.0",
147 | "picocolors": "^1.0.0",
148 | "sisteransi": "^1.0.5"
149 | },
150 | "dependencies": {
151 | "is-unicode-supported": {
152 | "version": "1.3.0",
153 | "bundled": true,
154 | "dev": true
155 | }
156 | }
157 | },
158 | "degit": {
159 | "version": "2.8.4",
160 | "resolved": "https://registry.npmjs.org/degit/-/degit-2.8.4.tgz",
161 | "integrity": "sha512-vqYuzmSA5I50J882jd+AbAhQtgK6bdKUJIex1JNfEUPENCgYsxugzKVZlFyMwV4i06MmnV47/Iqi5Io86zf3Ng==",
162 | "dev": true
163 | },
164 | "tsup": {
165 | "version": "file:../../node_modules/.pnpm/tsup@6.7.0/node_modules/tsup",
166 | "requires": {
167 | "@rollup/plugin-json": "5.0.1",
168 | "@swc/core": "1.2.218",
169 | "@types/debug": "4.1.7",
170 | "@types/flat": "5.0.2",
171 | "@types/fs-extra": "9.0.13",
172 | "@types/node": "14.18.12",
173 | "@types/resolve": "1.20.1",
174 | "bundle-require": "^4.0.0",
175 | "cac": "^6.7.12",
176 | "chokidar": "^3.5.1",
177 | "colorette": "2.0.16",
178 | "consola": "2.15.3",
179 | "debug": "^4.3.1",
180 | "esbuild": "^0.17.6",
181 | "execa": "^5.0.0",
182 | "flat": "5.0.2",
183 | "fs-extra": "10.0.0",
184 | "globby": "^11.0.3",
185 | "joycon": "^3.0.1",
186 | "postcss": "8.4.12",
187 | "postcss-load-config": "^3.0.1",
188 | "postcss-simple-vars": "6.0.3",
189 | "prettier": "2.5.1",
190 | "resolve": "1.20.0",
191 | "resolve-from": "^5.0.0",
192 | "rollup": "^3.2.5",
193 | "rollup-plugin-dts": "5.3.0",
194 | "rollup-plugin-hashbang": "2.2.2",
195 | "source-map": "0.8.0-beta.0",
196 | "strip-json-comments": "4.0.0",
197 | "sucrase": "^3.20.3",
198 | "svelte": "3.46.4",
199 | "terser": "^5.16.0",
200 | "tree-kill": "^1.2.2",
201 | "ts-essentials": "9.1.2",
202 | "tsconfig-paths": "3.12.0",
203 | "tsup": "6.6.1",
204 | "typescript": "5.0.2",
205 | "vitest": "0.28.4",
206 | "wait-for-expect": "3.0.2"
207 | }
208 | }
209 | }
210 | }
211 |
--------------------------------------------------------------------------------
/packages/create-impala/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "create-impala",
3 | "version": "0.0.8",
4 | "description": "",
5 | "bin": {
6 | "create-impala": "create-impala.js"
7 | },
8 | "scripts": {
9 | "build": "tsup --format cjs src/cli.ts --clean"
10 | },
11 | "keywords": [],
12 | "author": "",
13 | "license": "MIT",
14 | "devDependencies": {
15 | "@clack/prompts": "^0.6.3",
16 | "@types/node": "^18.15.7",
17 | "degit": "^2.8.4",
18 | "picocolors": "^1.0.0",
19 | "tsup": "^6.7.0"
20 | },
21 | "engines": {
22 | "node": ">=16.0.0"
23 | }
24 | }
--------------------------------------------------------------------------------
/packages/create-impala/src/ambient.d.ts:
--------------------------------------------------------------------------------
1 | declare module "degit";
2 |
--------------------------------------------------------------------------------
/packages/create-impala/src/cli.ts:
--------------------------------------------------------------------------------
1 | import { intro, outro, spinner, text, isCancel, select } from "@clack/prompts";
2 | import degit from "degit";
3 | import { existsSync, promises as fs } from "node:fs";
4 | import { join } from "node:path";
5 | import color from "picocolors";
6 |
7 | export async function createImpala() {
8 | intro(color.black(color.bgYellow(" create-impala ")));
9 |
10 | const target = await text({
11 | message: "Where would you like to create your project?",
12 | placeholder: "my-impala-app",
13 | initialValue: "my-impala-app",
14 | validate(value) {
15 | if (existsSync(value)) {
16 | return "That folder already exists! Please choose another.";
17 | }
18 | },
19 | });
20 | if (isCancel(target)) {
21 | outro("Cancelled");
22 | return;
23 | }
24 |
25 | const language = await select({
26 | message: "Which language would you like to use?",
27 | options: [
28 | { value: "ts", label: "TypeScript" },
29 | { value: "js", label: "JavaScript" },
30 | ],
31 | });
32 |
33 | if (isCancel(language)) {
34 | outro("Cancelled");
35 | return;
36 | }
37 |
38 | const framework = await select({
39 | message: "Which framework would you like to use?",
40 | options: [
41 | { value: "react", label: "React" },
42 | { value: "preact", label: "Preact" },
43 | ],
44 | });
45 |
46 | if (isCancel(framework)) {
47 | outro("Cancelled");
48 | return;
49 | }
50 |
51 | const s = spinner();
52 |
53 | s.start("Setting up your project...");
54 |
55 | const emitter = degit(`ascorbic/impala/templates/${framework}-${language}`, {
56 | cache: false,
57 | });
58 |
59 | emitter.on("info", (info: any) => {
60 | console.log(info.message);
61 | });
62 |
63 | emitter.on("error", (error: any) => {
64 | console.log(error.message);
65 | });
66 |
67 | await emitter.clone(target);
68 | s.stop("Set up your project");
69 | const packageJsonPath = join(target, "package.json");
70 | const packageJson = JSON.parse(await fs.readFile(packageJsonPath, "utf-8"));
71 | packageJson.name = target.replaceAll(/[^a-zA-Z0-9-]/g, "-");
72 | await fs.writeFile(packageJsonPath, JSON.stringify(packageJson, null, 2));
73 | outro(
74 | `You're all set!\n\nTo get started, run:\n\n cd ${target}\n npm install\n npm run dev`
75 | );
76 | }
77 |
--------------------------------------------------------------------------------
/packages/create-impala/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | /* Visit https://aka.ms/tsconfig to read more about this file */
4 | /* Projects */
5 | // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */
6 | // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
7 | // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */
8 | // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */
9 | // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
10 | // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
11 | /* Language and Environment */
12 | "target": "ES2022", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
13 | // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
14 | // "jsx": "preserve", /* Specify what JSX code is generated. */
15 | // "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */
16 | // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
17 | // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */
18 | // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
19 | // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */
20 | // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */
21 | // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
22 | // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
23 | // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */
24 | /* Modules */
25 | "module": "CommonJS", /* Specify what module code is generated. */
26 | // "rootDir": "./", /* Specify the root folder within your source files. */
27 | // "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */
28 | // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
29 | // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
30 | // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
31 | // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */
32 | // "types": [], /* Specify type package names to be included without being referenced in a source file. */
33 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
34 | // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */
35 | // "resolveJsonModule": true, /* Enable importing .json files. */
36 | // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */
37 | /* JavaScript Support */
38 | // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */
39 | // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
40 | // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */
41 | /* Emit */
42 | // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
43 | // "declarationMap": true, /* Create sourcemaps for d.ts files. */
44 | // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
45 | // "sourceMap": true, /* Create source map files for emitted JavaScript files. */
46 | // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */
47 | // "outDir": "./", /* Specify an output folder for all emitted files. */
48 | // "removeComments": true, /* Disable emitting comments. */
49 | // "noEmit": true, /* Disable emitting files from a compilation. */
50 | // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
51 | // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */
52 | // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
53 | // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
54 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
55 | // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
56 | // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
57 | // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
58 | // "newLine": "crlf", /* Set the newline character for emitting files. */
59 | // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */
60 | // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */
61 | // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
62 | // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */
63 | // "declarationDir": "./", /* Specify the output directory for generated declaration files. */
64 | // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */
65 | /* Interop Constraints */
66 | // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
67 | // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
68 | "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */
69 | // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
70 | "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
71 | /* Type Checking */
72 | "strict": true, /* Enable all strict type-checking options. */
73 | // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */
74 | // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */
75 | // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
76 | // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */
77 | // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
78 | // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */
79 | // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */
80 | // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
81 | // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */
82 | // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */
83 | // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
84 | // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
85 | // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
86 | // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */
87 | // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
88 | // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */
89 | // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
90 | // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
91 | /* Completeness */
92 | // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
93 | "skipLibCheck": true /* Skip type checking all .d.ts files. */
94 | }
95 | }
--------------------------------------------------------------------------------
/packages/preact/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | ### Dependencies
4 |
5 | * The following workspace dependencies were updated
6 | * dependencies
7 | * @impalajs/core bumped to 0.0.6
8 |
9 | ### Dependencies
10 |
11 | * The following workspace dependencies were updated
12 | * dependencies
13 | * @impalajs/core bumped to 0.0.7
14 |
15 | ### Dependencies
16 |
17 | * The following workspace dependencies were updated
18 | * dependencies
19 | * @impalajs/core bumped to 0.0.8
20 |
21 | ### Dependencies
22 |
23 | * The following workspace dependencies were updated
24 | * dependencies
25 | * @impalajs/core bumped to 0.0.9
26 |
27 | ### Dependencies
28 |
29 | * The following workspace dependencies were updated
30 | * dependencies
31 | * @impalajs/core bumped to 0.0.10
32 |
33 | ### Dependencies
34 |
35 | * The following workspace dependencies were updated
36 | * dependencies
37 | * @impalajs/core bumped to 0.0.11
38 |
39 | ### Dependencies
40 |
41 | * The following workspace dependencies were updated
42 | * dependencies
43 | * @impalajs/core bumped to 0.0.12
44 |
45 | ### Dependencies
46 |
47 | * The following workspace dependencies were updated
48 | * dependencies
49 | * @impalajs/core bumped to 0.0.13
50 |
51 | ### Dependencies
52 |
53 | * The following workspace dependencies were updated
54 | * dependencies
55 | * @impalajs/core bumped to 0.0.14
56 |
57 | ## 0.0.5 (2023-03-28)
58 |
59 |
60 | ### Features
61 |
62 | * add preact support ([#9](https://github.com/ascorbic/impala/issues/9)) ([ef71048](https://github.com/ascorbic/impala/commit/ef710486657819cbf6addaa1aaff671931b5ed4f))
63 |
64 | ## 0.0.5 (2023-03-28)
65 |
66 |
67 | ### Features
68 |
69 | * add dev server ([#2](https://github.com/ascorbic/impala/issues/2)) ([bab917a](https://github.com/ascorbic/impala/commit/bab917a28df70d9df691f7d1db61bf6e140b7acb))
70 | * **create-impala:** add create-impala cli ([d40b61c](https://github.com/ascorbic/impala/commit/d40b61c469223bc88d62fce156790ecaf2090e49))
71 |
72 |
73 | ### Bug Fixes
74 |
75 | * pass props to pages properly ([6b18945](https://github.com/ascorbic/impala/commit/6b189453d821ad85fdf828f5d270c754fecb0b26))
76 | * pass props to pages properly ([#3](https://github.com/ascorbic/impala/issues/3)) ([ee3bb82](https://github.com/ascorbic/impala/commit/ee3bb8279987dcdd0655ef02a53bad883ee3413a))
77 |
78 |
79 | ### Dependencies
80 |
81 | * The following workspace dependencies were updated
82 | * dependencies
83 | * @impalajs/core bumped to 0.0.5
84 |
--------------------------------------------------------------------------------
/packages/preact/README.md:
--------------------------------------------------------------------------------
1 | # Impala
2 |
3 |
4 |
5 | 
6 |
7 |
8 |
--------------------------------------------------------------------------------
/packages/preact/client.d.ts:
--------------------------------------------------------------------------------
1 | export * from "./dist/client";
2 |
--------------------------------------------------------------------------------
/packages/preact/head.d.ts:
--------------------------------------------------------------------------------
1 | export { Head } from "./dist/head";
2 |
--------------------------------------------------------------------------------
/packages/preact/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@impalajs/preact",
3 | "version": "0.0.14",
4 | "description": "",
5 | "scripts": {
6 | "build": "tsup src/index.ts src/client.tsx src/head.tsx --format cjs,esm --dts --clean"
7 | },
8 | "main": "./dist/index.js",
9 | "module": "./dist/index.mjs",
10 | "types": "./dist/index.d.ts",
11 | "exports": {
12 | ".": {
13 | "types": "./dist/index.d.ts",
14 | "require": "./dist/index.js",
15 | "import": "./dist/index.mjs"
16 | },
17 | "./client": {
18 | "types": "./dist/client.d.ts",
19 | "require": "./dist/client.js",
20 | "import": "./dist/client.mjs"
21 | },
22 | "./head": {
23 | "types": "./dist/head.d.ts",
24 | "require": "./dist/head.js",
25 | "import": "./dist/head.mjs"
26 | }
27 | },
28 | "keywords": [],
29 | "author": "",
30 | "license": "MIT",
31 | "dependencies": {
32 | "@impalajs/core": "workspace:*",
33 | "preact-render-to-string": "^6.0.1"
34 | },
35 | "devDependencies": {
36 | "preact": "^10.13.2",
37 | "tsup": "^6.7.0"
38 | },
39 | "peerDependencies": {
40 | "preact": "^10.0.0"
41 | },
42 | "publishConfig": {
43 | "access": "public"
44 | }
45 | }
--------------------------------------------------------------------------------
/packages/preact/src/client.tsx:
--------------------------------------------------------------------------------
1 | import type {
2 | ModuleImports,
3 | RouteModule as CoreRouteModule,
4 | } from "@impalajs/core";
5 | import { hydrate, ComponentType } from "preact";
6 |
7 | export type RouteModule = CoreRouteModule;
8 | export function clientBootstrap(modules: ModuleImports) {
9 | const context = (window as any).___CONTEXT;
10 |
11 | if (context && "chunk" in context) {
12 | const mod = modules[context.chunk];
13 | if (mod) {
14 | mod().then(({ default: Page }) => {
15 | hydrate(, document.getElementById("__impala")!);
16 | });
17 | } else {
18 | console.error(
19 | `[Impala] Could not hydrate page. Module not found: ${context?.chunk}`
20 | );
21 | }
22 | } else {
23 | console.log("[Impala] No context found. Skipping hydration.");
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/packages/preact/src/entry-server.tsx:
--------------------------------------------------------------------------------
1 | import { useContext } from "preact/hooks";
2 | import renderToString from "preact-render-to-string";
3 | import type { ComponentType } from "preact";
4 | import type { Context, RouteModule } from "@impalajs/core";
5 | import { HeadContext } from "./head-context";
6 |
7 | function HeadContent() {
8 | const headProvider = useContext(HeadContext);
9 | return <>{...headProvider.getHead()}>;
10 | }
11 |
12 | export async function render(
13 | context: Context,
14 | mod: () => Promise>>,
15 | bootstrapModules?: Array
16 | ) {
17 | const { default: Page } = await mod();
18 |
19 | const body = renderToString();
20 |
21 | const modules = bootstrapModules?.map(
22 | (m) => ``
23 | );
24 |
25 | const headContent = renderToString();
26 |
27 | return {
28 | body,
29 | head: [
30 | headContent,
31 | ``,
32 | modules,
33 | ].join(""),
34 | };
35 | }
36 |
--------------------------------------------------------------------------------
/packages/preact/src/head-context.tsx:
--------------------------------------------------------------------------------
1 | import { createContext, VNode } from "preact";
2 | import render from "preact-render-to-string";
3 |
4 | class HeadProvider {
5 | private head: VNode[] = [];
6 |
7 | private removeTag(tag: string) {
8 | this.head = this.head.filter((item) => item.type !== tag);
9 | }
10 |
11 | public addHead(head?: VNode | VNode[]) {
12 | if (!head) {
13 | return;
14 | }
15 | if (Array.isArray(head)) {
16 | // Can't have more than one title tag
17 | if (head.some((item) => item.type === "title")) {
18 | this.removeTag("title");
19 | }
20 | this.head.push(...head);
21 | return;
22 | }
23 |
24 | if (head.type === "title") {
25 | this.removeTag("title");
26 | }
27 | this.head.push(head);
28 | }
29 |
30 | public getHead() {
31 | return this.head;
32 | }
33 |
34 | public getAsString() {
35 | return render(<>{...this.head}>);
36 | }
37 | }
38 |
39 | const headProvider = new HeadProvider();
40 |
41 | export const HeadContext = createContext(headProvider);
42 |
--------------------------------------------------------------------------------
/packages/preact/src/head.tsx:
--------------------------------------------------------------------------------
1 | import { useContext } from "preact/hooks";
2 | import type { FunctionComponent, VNode } from "preact";
3 | import { HeadContext } from "./head-context";
4 |
5 | interface HeadProps {
6 | title?: string;
7 | children?: VNode | VNode[];
8 | }
9 |
10 | export const Head: FunctionComponent = ({ children, title }) => {
11 | const headProvider = useContext(HeadContext);
12 | headProvider.addHead(children);
13 | if (title) {
14 | headProvider.addHead({title});
15 | }
16 | return null;
17 | };
18 |
--------------------------------------------------------------------------------
/packages/preact/src/index.ts:
--------------------------------------------------------------------------------
1 | import type {
2 | DataModule,
3 | RouteModule as CoreRouteModule,
4 | } from "@impalajs/core";
5 | import type { ComponentChildren } from "preact";
6 | export type RouteModule = CoreRouteModule;
7 | export type { DataModule };
8 |
9 | export * from "./entry-server";
10 |
--------------------------------------------------------------------------------
/packages/preact/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ESNext",
4 | "useDefineForClassFields": true,
5 | "lib": [
6 | "DOM",
7 | "DOM.Iterable",
8 | "ESNext"
9 | ],
10 | "types": [],
11 | "allowJs": false,
12 | "skipLibCheck": true,
13 | "esModuleInterop": false,
14 | "allowSyntheticDefaultImports": true,
15 | "strict": true,
16 | "forceConsistentCasingInFileNames": true,
17 | "module": "ESNext",
18 | "moduleResolution": "Node",
19 | "resolveJsonModule": true,
20 | "isolatedModules": true,
21 | "noEmit": true,
22 | "jsx": "react-jsx",
23 | "jsxImportSource": "preact"
24 | },
25 | "include": [
26 | "src"
27 | ],
28 | "references": [
29 | {
30 | "path": "./tsconfig.node.json"
31 | }
32 | ]
33 | }
--------------------------------------------------------------------------------
/packages/react/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | ### Dependencies
4 |
5 | * The following workspace dependencies were updated
6 | * dependencies
7 | * @impalajs/core bumped to 0.0.6
8 |
9 | ### Dependencies
10 |
11 | * The following workspace dependencies were updated
12 | * dependencies
13 | * @impalajs/core bumped to 0.0.7
14 |
15 | ### Dependencies
16 |
17 | * The following workspace dependencies were updated
18 | * dependencies
19 | * @impalajs/core bumped to 0.0.8
20 |
21 | ### Dependencies
22 |
23 | * The following workspace dependencies were updated
24 | * dependencies
25 | * @impalajs/core bumped to 0.0.9
26 |
27 | ### Dependencies
28 |
29 | * The following workspace dependencies were updated
30 | * dependencies
31 | * @impalajs/core bumped to 0.0.10
32 |
33 | ### Dependencies
34 |
35 | * The following workspace dependencies were updated
36 | * dependencies
37 | * @impalajs/core bumped to 0.0.11
38 |
39 | ### Dependencies
40 |
41 | * The following workspace dependencies were updated
42 | * dependencies
43 | * @impalajs/core bumped to 0.0.12
44 |
45 | ### Dependencies
46 |
47 | * The following workspace dependencies were updated
48 | * dependencies
49 | * @impalajs/core bumped to 0.0.13
50 |
51 | ### Dependencies
52 |
53 | * The following workspace dependencies were updated
54 | * dependencies
55 | * @impalajs/core bumped to 0.0.14
56 |
57 | ## 0.0.5 (2023-03-28)
58 |
59 |
60 | ### Features
61 |
62 | * add dev server ([#2](https://github.com/ascorbic/impala/issues/2)) ([bab917a](https://github.com/ascorbic/impala/commit/bab917a28df70d9df691f7d1db61bf6e140b7acb))
63 | * **create-impala:** add create-impala cli ([d40b61c](https://github.com/ascorbic/impala/commit/d40b61c469223bc88d62fce156790ecaf2090e49))
64 |
65 |
66 | ### Bug Fixes
67 |
68 | * pass props to pages properly ([6b18945](https://github.com/ascorbic/impala/commit/6b189453d821ad85fdf828f5d270c754fecb0b26))
69 | * pass props to pages properly ([#3](https://github.com/ascorbic/impala/issues/3)) ([ee3bb82](https://github.com/ascorbic/impala/commit/ee3bb8279987dcdd0655ef02a53bad883ee3413a))
70 |
71 |
72 | ### Dependencies
73 |
74 | * The following workspace dependencies were updated
75 | * dependencies
76 | * @impalajs/core bumped to 0.0.5
77 |
--------------------------------------------------------------------------------
/packages/react/README.md:
--------------------------------------------------------------------------------
1 | # Impala
2 |
3 |
4 |
5 | 
6 |
7 |
8 |
--------------------------------------------------------------------------------
/packages/react/client.d.ts:
--------------------------------------------------------------------------------
1 | export * from "./dist/client";
2 |
--------------------------------------------------------------------------------
/packages/react/head.d.ts:
--------------------------------------------------------------------------------
1 | export { Head } from "./dist/head";
2 |
--------------------------------------------------------------------------------
/packages/react/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@impalajs/react",
3 | "version": "0.0.14",
4 | "description": "",
5 | "scripts": {
6 | "build": "tsup src/index.ts src/client.tsx src/head.tsx --format cjs,esm --dts --clean"
7 | },
8 | "main": "./dist/index.js",
9 | "module": "./dist/index.mjs",
10 | "types": "./dist/index.d.ts",
11 | "exports": {
12 | ".": {
13 | "types": "./dist/index.d.ts",
14 | "require": "./dist/index.js",
15 | "import": "./dist/index.mjs"
16 | },
17 | "./client": {
18 | "types": "./dist/client.d.ts",
19 | "require": "./dist/client.js",
20 | "import": "./dist/client.mjs"
21 | },
22 | "./head": {
23 | "types": "./dist/head.d.ts",
24 | "require": "./dist/head.js",
25 | "import": "./dist/head.mjs"
26 | }
27 | },
28 | "keywords": [],
29 | "author": "",
30 | "license": "MIT",
31 | "dependencies": {
32 | "@impalajs/core": "workspace:*"
33 | },
34 | "devDependencies": {
35 | "@types/node": "^18.15.7",
36 | "@types/react": "^18.0.28",
37 | "@types/react-dom": "^18.0.11",
38 | "react": "^18.2.0",
39 | "react-dom": "^18.2.0",
40 | "tsup": "^6.7.0"
41 | },
42 | "peerDependencies": {
43 | "react": ">=18",
44 | "react-dom": ">=18"
45 | },
46 | "publishConfig": {
47 | "access": "public"
48 | }
49 | }
--------------------------------------------------------------------------------
/packages/react/src/client.tsx:
--------------------------------------------------------------------------------
1 | import type {
2 | ModuleImports,
3 | RouteModule as CoreRouteModule,
4 | } from "@impalajs/core";
5 | import type { ElementType } from "react";
6 | import ReactDOM from "react-dom/client";
7 |
8 | export type RouteModule = CoreRouteModule;
9 | export function clientBootstrap(modules: ModuleImports) {
10 | const context = (window as any).___CONTEXT;
11 |
12 | if (context && "chunk" in context) {
13 | const mod = modules[context.chunk];
14 | if (mod) {
15 | mod().then(({ default: Page }) => {
16 | ReactDOM.hydrateRoot(
17 | document.getElementById("__impala")!,
18 |
19 | );
20 | });
21 | } else {
22 | console.error(
23 | `[Impala] Could not hydrate page. Module not found: ${context?.chunk}`
24 | );
25 | }
26 | } else {
27 | console.log("[Impala] No context found. Skipping hydration.");
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/packages/react/src/entry-server.tsx:
--------------------------------------------------------------------------------
1 | import { renderToPipeableStream, renderToStaticMarkup } from "react-dom/server";
2 | import { ElementType, useContext } from "react";
3 | import type { Context, RouteModule } from "@impalajs/core";
4 | import { Writable, WritableOptions } from "node:stream";
5 | import { HeadContext } from "./head-context";
6 |
7 | class StringResponse extends Writable {
8 | private buffer: string;
9 | private responseData: Promise;
10 | constructor(options?: WritableOptions) {
11 | super(options);
12 | this.buffer = "";
13 | this.responseData = new Promise((resolve, reject) => {
14 | this.on("finish", () => resolve(this.buffer));
15 | this.on("error", reject);
16 | });
17 | }
18 |
19 | _write(
20 | chunk: any,
21 | encoding: BufferEncoding,
22 | callback: (error?: Error | null) => void
23 | ): void {
24 | this.buffer += chunk;
25 | callback();
26 | }
27 |
28 | getData(): Promise {
29 | return this.responseData;
30 | }
31 | }
32 |
33 | function HeadContent() {
34 | const headProvider = useContext(HeadContext);
35 | return <>{...headProvider.getHead()}>;
36 | }
37 |
38 | export async function render(
39 | context: Context,
40 | mod: () => Promise>,
41 | bootstrapModules?: Array
42 | ) {
43 | const { default: Page } = await mod();
44 |
45 | const response = new StringResponse();
46 |
47 | const { pipe } = renderToPipeableStream(, {
48 | bootstrapModules,
49 | bootstrapScriptContent: `window.___CONTEXT=${JSON.stringify(context)};`,
50 | onAllReady() {
51 | pipe(response);
52 | },
53 | onError(error) {
54 | console.error(error);
55 | },
56 | });
57 |
58 | const body = await response.getData();
59 |
60 | const head = renderToStaticMarkup();
61 |
62 | return { body, head };
63 | }
64 |
--------------------------------------------------------------------------------
/packages/react/src/head-context.tsx:
--------------------------------------------------------------------------------
1 | import React, { createContext, ReactElement, ReactNode } from "react";
2 | import { renderToStaticMarkup } from "react-dom/server";
3 |
4 | class HeadProvider {
5 | private head: React.ReactElement[] = [];
6 |
7 | private removeTag(tag: string) {
8 | this.head = this.head.filter((item) => item.type !== tag);
9 | }
10 |
11 | public addHead(head?: ReactElement | ReactElement[]) {
12 | if (!head) {
13 | return;
14 | }
15 | if (Array.isArray(head)) {
16 | // Can't have more than one title tag
17 | if (head.some((item) => item.type === "title")) {
18 | this.removeTag("title");
19 | }
20 | this.head.push(...head);
21 | return;
22 | }
23 |
24 | if (head.type === "title") {
25 | this.removeTag("title");
26 | }
27 | this.head.push(head);
28 | }
29 |
30 | public getHead() {
31 | return this.head;
32 | }
33 |
34 | public getAsString() {
35 | return renderToStaticMarkup(<>{...this.head}>);
36 | }
37 | }
38 |
39 | const headProvider = new HeadProvider();
40 |
41 | export const HeadContext = createContext(headProvider);
42 |
--------------------------------------------------------------------------------
/packages/react/src/head.tsx:
--------------------------------------------------------------------------------
1 | import { useContext } from "react";
2 | import { HeadContext } from "./head-context";
3 |
4 | interface HeadProps {
5 | title?: string;
6 | children?: React.ReactElement | React.ReactElement[];
7 | }
8 |
9 | export const Head: React.FC = ({ children, title }) => {
10 | const headProvider = useContext(HeadContext);
11 | headProvider.addHead(children);
12 | if (title) {
13 | headProvider.addHead({title});
14 | }
15 | return null;
16 | };
17 |
--------------------------------------------------------------------------------
/packages/react/src/index.ts:
--------------------------------------------------------------------------------
1 | import type {
2 | DataModule,
3 | ModuleImports,
4 | RouteModule as CoreRouteModule,
5 | } from "@impalajs/core";
6 | import type { ElementType } from "react";
7 | export type RouteModule = CoreRouteModule;
8 | export type { DataModule };
9 |
10 | export * from "./entry-server";
11 |
--------------------------------------------------------------------------------
/packages/react/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ESNext",
4 | "useDefineForClassFields": true,
5 | "lib": [
6 | "DOM",
7 | "DOM.Iterable",
8 | "ESNext"
9 | ],
10 | "allowJs": false,
11 | "skipLibCheck": true,
12 | "esModuleInterop": false,
13 | "allowSyntheticDefaultImports": true,
14 | "strict": true,
15 | "forceConsistentCasingInFileNames": true,
16 | "module": "ESNext",
17 | "moduleResolution": "Node",
18 | "resolveJsonModule": true,
19 | "isolatedModules": true,
20 | "noEmit": true,
21 | "jsx": "react-jsx"
22 | },
23 | "include": [
24 | "src"
25 | ],
26 | "references": [
27 | {
28 | "path": "./tsconfig.node.json"
29 | }
30 | ]
31 | }
--------------------------------------------------------------------------------
/pnpm-workspace.yaml:
--------------------------------------------------------------------------------
1 | packages:
2 | - templates/*
3 | - packages/*
4 | - demo
5 | - scripts
6 |
--------------------------------------------------------------------------------
/release-please-config.json:
--------------------------------------------------------------------------------
1 | {
2 | "packages": {
3 | "packages/core": {},
4 | "packages/preact": {},
5 | "packages/react": {},
6 | "packages/create-impala": {}
7 | },
8 | "plugins": [
9 | "node-workspace"
10 | ],
11 | "bump-minor-pre-major": true,
12 | "bump-patch-for-minor-pre-major": true,
13 | "$schema": "https://raw.githubusercontent.com/googleapis/release-please/main/schemas/config.json"
14 | }
--------------------------------------------------------------------------------
/scripts/compile-template.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Mostly copied from Shopify/Hydrogen
3 | */
4 |
5 | import path from "path";
6 | import fs from "fs/promises";
7 | import prettier, { type Options as FormatOptions } from "prettier";
8 | import ts, { type CompilerOptions } from "typescript";
9 | import glob from "fast-glob";
10 | import { existsSync } from "fs";
11 |
12 | const escapeNewLines = (code: string) =>
13 | code.replace(/\n\n/g, "\n/* :newline: */");
14 | const restoreNewLines = (code: string) =>
15 | code.replace(/\/\* :newline: \*\//g, "\n");
16 |
17 | const DEFAULT_TS_CONFIG: Omit = {
18 | lib: ["DOM", "DOM.Iterable", "ES2022"],
19 | isolatedModules: true,
20 | esModuleInterop: true,
21 | resolveJsonModule: true,
22 | target: "ES2022",
23 | strict: true,
24 | allowJs: true,
25 | forceConsistentCasingInFileNames: true,
26 | skipLibCheck: true,
27 | };
28 |
29 | export function transpileFile(code: string, config = DEFAULT_TS_CONFIG) {
30 | // We need to escape new lines in the template because TypeScript
31 | // will remove them when compiling.
32 | const withArtificialNewLines = escapeNewLines(code);
33 |
34 | // We compile the template to JavaScript.
35 | const compiled = ts.transpileModule(withArtificialNewLines, {
36 | reportDiagnostics: false,
37 | compilerOptions: {
38 | ...config,
39 | // '1' tells TypeScript to preserve the JSX syntax.
40 | jsx: 1,
41 | removeComments: false,
42 | },
43 | });
44 |
45 | // Here we restore the new lines that were removed by TypeScript.
46 | return restoreNewLines(compiled.outputText);
47 | }
48 |
49 | const DEFAULT_PRETTIER_CONFIG: FormatOptions = {
50 | arrowParens: "always",
51 | singleQuote: true,
52 | bracketSpacing: false,
53 | trailingComma: "all",
54 | };
55 |
56 | export async function resolveFormatConfig(filePath = process.cwd()) {
57 | try {
58 | // Try to read a prettier config file from the project.
59 | return (await prettier.resolveConfig(filePath)) || DEFAULT_PRETTIER_CONFIG;
60 | } catch {
61 | return DEFAULT_PRETTIER_CONFIG;
62 | }
63 | }
64 |
65 | export function format(content: string, config: FormatOptions, filePath = "") {
66 | const ext = path.extname(filePath);
67 |
68 | const formattedContent = prettier.format(content, {
69 | // Specify the TypeScript parser for ts/tsx files. Otherwise
70 | // we need to use the babel parser because the default parser
71 | // Otherwise prettier will print a warning.
72 | parser: ext === ".tsx" || ext === ".ts" ? "typescript" : "babel",
73 | ...config,
74 | });
75 |
76 | return formattedContent;
77 | }
78 |
79 | const DEFAULT_JS_CONFIG: Omit = {
80 | allowJs: true,
81 | forceConsistentCasingInFileNames: true,
82 | strict: true,
83 | lib: ["DOM", "DOM.Iterable", "ES2022"],
84 | esModuleInterop: true,
85 | isolatedModules: true,
86 | jsx: "react-jsx",
87 | noEmit: true,
88 | resolveJsonModule: true,
89 | };
90 |
91 | // https://code.visualstudio.com/docs/languages/jsconfig#_jsconfig-options
92 | const JS_CONFIG_KEYS = [
93 | "noLib",
94 | "target",
95 | "module",
96 | "moduleResolution",
97 | "checkJs",
98 | "experimentalDecorators",
99 | "allowSyntheticDefaultImports",
100 | "baseUrl",
101 | "paths",
102 | ...Object.keys(DEFAULT_JS_CONFIG),
103 | ];
104 |
105 | export function convertConfigToJS(tsConfig: {
106 | include?: string[];
107 | compilerOptions?: CompilerOptions;
108 | }) {
109 | const jsConfig = {
110 | compilerOptions: { ...DEFAULT_JS_CONFIG },
111 | } as typeof tsConfig;
112 |
113 | if (tsConfig.include) {
114 | jsConfig.include = tsConfig.include
115 | .filter((s) => !s.endsWith(".d.ts"))
116 | .map((s) => s.replace(/\.ts(x?)$/, ".js$1"));
117 | }
118 |
119 | if (tsConfig.compilerOptions) {
120 | for (const key of JS_CONFIG_KEYS) {
121 | if (tsConfig.compilerOptions[key] !== undefined && key !== "allowJs") {
122 | jsConfig.compilerOptions![key] = tsConfig.compilerOptions[key];
123 | }
124 | }
125 | }
126 |
127 | return jsConfig;
128 | }
129 |
130 | export async function transpileProject(projectDir: string) {
131 | const entries = await glob("**/*.+(ts|tsx)", {
132 | absolute: true,
133 | cwd: projectDir,
134 | });
135 |
136 | const formatConfig = await resolveFormatConfig();
137 |
138 | for (const entry of entries) {
139 | if (entry.endsWith(".d.ts")) {
140 | await fs.rm(entry);
141 | continue;
142 | }
143 |
144 | const tsx = await fs.readFile(entry, "utf8");
145 | const mjs = format(transpileFile(tsx), formatConfig);
146 |
147 | await fs.rm(entry);
148 | await fs.writeFile(entry.replace(/\.ts(x?)$/, ".js$1"), mjs, "utf8");
149 | }
150 |
151 | for (const [src, target] of [
152 | ["tsconfig.json", "jsconfig.json"],
153 | ["tsconfig.node.json", "jsconfig.node.json"],
154 | ]) {
155 | // Transpile tsconfig.json to jsconfig.json
156 | try {
157 | const tsConfigPath = path.join(projectDir, src);
158 | const tsConfigWithComments = await fs.readFile(tsConfigPath, "utf8");
159 | const jsConfig = convertConfigToJS(
160 | JSON.parse(tsConfigWithComments.replace(/^\s*\/\/.*$/gm, ""))
161 | );
162 |
163 | await fs.rm(tsConfigPath);
164 | await fs.writeFile(
165 | path.join(projectDir, target),
166 | JSON.stringify(jsConfig, null, 2),
167 | "utf8"
168 | );
169 | } catch (error) {
170 | console.log(
171 | "Could not transpile tsconfig.json:\n" + (error as Error).stack
172 | );
173 | }
174 | }
175 |
176 | // Remove some TS dependencies
177 | try {
178 | const pkgJson = JSON.parse(
179 | await fs.readFile(path.join(projectDir, "package.json"), "utf8")
180 | );
181 |
182 | for (const key of Object.keys(pkgJson.devDependencies)) {
183 | if (key.startsWith("@types/")) {
184 | delete pkgJson.devDependencies[key];
185 | }
186 | }
187 |
188 | await fs.writeFile(
189 | path.join(projectDir, "package.json"),
190 | JSON.stringify(pkgJson, null, 2)
191 | );
192 | } catch (error) {
193 | console.log(
194 | "Could not remove TS dependencies from package.json:\n" +
195 | (error as Error).stack
196 | );
197 | }
198 | }
199 |
200 | async function generateJsTemplate(templateBasename: string) {
201 | const tsTemplate = `${templateBasename}-ts`;
202 | const jsTemplate = `${templateBasename}-js`;
203 | if (!existsSync(tsTemplate)) {
204 | throw new Error(`Template ${tsTemplate} does not exist`);
205 | }
206 | if (existsSync(jsTemplate)) {
207 | await fs.rm(jsTemplate, { recursive: true, force: true });
208 | }
209 | await fs.cp(tsTemplate, jsTemplate, { recursive: true });
210 | await fs.rm(path.join(jsTemplate, "node_modules"), {
211 | recursive: true,
212 | force: true,
213 | });
214 | await fs.rm(path.join(jsTemplate, "dist"), { recursive: true, force: true });
215 | await transpileProject(jsTemplate);
216 | }
217 |
218 | for (const framework of ["react", "preact"]) {
219 | const baseDir = path.join(__dirname, "..", "templates", framework);
220 | generateJsTemplate(baseDir);
221 | }
222 |
--------------------------------------------------------------------------------
/scripts/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "impala-scripts",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "private": true,
7 | "scripts": {
8 | "test": "echo \"Error: no test specified\" && exit 1"
9 | },
10 | "keywords": [],
11 | "author": "",
12 | "license": "MIT",
13 | "devDependencies": {
14 | "@types/node": "^18.15.7",
15 | "@types/prettier": "^2.7.2",
16 | "fast-glob": "^3.2.12",
17 | "prettier": "^2.8.7",
18 | "ts-node": "^10.9.1",
19 | "typescript": "^4.9.3"
20 | }
21 | }
--------------------------------------------------------------------------------
/scripts/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | /* Visit https://aka.ms/tsconfig to read more about this file */
4 | /* Projects */
5 | // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */
6 | // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
7 | // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */
8 | // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */
9 | // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
10 | // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
11 | /* Language and Environment */
12 | "target": "es2022", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
13 | // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
14 | // "jsx": "preserve", /* Specify what JSX code is generated. */
15 | // "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */
16 | // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
17 | // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */
18 | // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
19 | // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */
20 | // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */
21 | // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
22 | // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
23 | // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */
24 | /* Modules */
25 | "module": "NodeNext", /* Specify what module code is generated. */
26 | // "rootDir": "./", /* Specify the root folder within your source files. */
27 | "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */
28 | // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
29 | // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
30 | // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
31 | // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */
32 | // "types": [], /* Specify type package names to be included without being referenced in a source file. */
33 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
34 | // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */
35 | // "resolveJsonModule": true, /* Enable importing .json files. */
36 | // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */
37 | /* JavaScript Support */
38 | // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */
39 | // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
40 | // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */
41 | /* Emit */
42 | // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
43 | // "declarationMap": true, /* Create sourcemaps for d.ts files. */
44 | // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
45 | // "sourceMap": true, /* Create source map files for emitted JavaScript files. */
46 | // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */
47 | // "outDir": "./", /* Specify an output folder for all emitted files. */
48 | // "removeComments": true, /* Disable emitting comments. */
49 | // "noEmit": true, /* Disable emitting files from a compilation. */
50 | // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
51 | // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */
52 | // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
53 | // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
54 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
55 | // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
56 | // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
57 | // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
58 | // "newLine": "crlf", /* Set the newline character for emitting files. */
59 | // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */
60 | // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */
61 | // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
62 | // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */
63 | // "declarationDir": "./", /* Specify the output directory for generated declaration files. */
64 | // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */
65 | /* Interop Constraints */
66 | // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
67 | // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
68 | "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */
69 | // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
70 | "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
71 | /* Type Checking */
72 | "strict": true, /* Enable all strict type-checking options. */
73 | // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */
74 | // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */
75 | // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
76 | // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */
77 | // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
78 | // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */
79 | // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */
80 | // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
81 | // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */
82 | // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */
83 | // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
84 | // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
85 | // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
86 | // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */
87 | // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
88 | // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */
89 | // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
90 | // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
91 | /* Completeness */
92 | // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
93 | "skipLibCheck": true /* Skip type checking all .d.ts files. */
94 | }
95 | }
--------------------------------------------------------------------------------
/templates/preact-js/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | pnpm-debug.log*
8 | lerna-debug.log*
9 |
10 | node_modules
11 | dist
12 | dist-ssr
13 | *.local
14 |
15 | # Editor directories and files
16 | .vscode/*
17 | !.vscode/extensions.json
18 | .idea
19 | .DS_Store
20 | *.suo
21 | *.ntvs*
22 | *.njsproj
23 | *.sln
24 | *.sw?
25 |
--------------------------------------------------------------------------------
/templates/preact-js/README.md:
--------------------------------------------------------------------------------
1 | # Impala
2 |
3 |
4 |
5 |
6 |
7 | Thanks for trying the pre-alpha of Impala. To get started, run the dev server:
8 |
9 | ```bash
10 | npm run dev
11 | ```
12 |
13 | When you're ready to build for production, run:
14 |
15 | ```bash
16 | npm run build
17 | ```
18 |
19 | See [the GitHub repo](https://github.com/ascorbic/impala) for more help.
20 |
--------------------------------------------------------------------------------
/templates/preact-js/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/templates/preact-js/jsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "allowJs": true,
4 | "forceConsistentCasingInFileNames": true,
5 | "strict": true,
6 | "lib": [
7 | "DOM",
8 | "DOM.Iterable",
9 | "ESNext"
10 | ],
11 | "esModuleInterop": false,
12 | "isolatedModules": true,
13 | "jsx": "react-jsx",
14 | "noEmit": true,
15 | "resolveJsonModule": true,
16 | "target": "ESNext",
17 | "module": "ESNext",
18 | "moduleResolution": "Node",
19 | "allowSyntheticDefaultImports": true
20 | },
21 | "include": [
22 | "src"
23 | ]
24 | }
--------------------------------------------------------------------------------
/templates/preact-js/jsconfig.node.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "allowJs": true,
4 | "forceConsistentCasingInFileNames": true,
5 | "strict": true,
6 | "lib": [
7 | "DOM",
8 | "DOM.Iterable",
9 | "ES2022"
10 | ],
11 | "esModuleInterop": true,
12 | "isolatedModules": true,
13 | "jsx": "react-jsx",
14 | "noEmit": true,
15 | "resolveJsonModule": true,
16 | "module": "ESNext",
17 | "moduleResolution": "Node",
18 | "allowSyntheticDefaultImports": true
19 | },
20 | "include": [
21 | "vite.config.js"
22 | ]
23 | }
--------------------------------------------------------------------------------
/templates/preact-js/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@demo/impala-preact-js",
3 | "private": true,
4 | "version": "0.0.0",
5 | "type": "module",
6 | "scripts": {
7 | "dev": "impala dev",
8 | "build:server": "vite build --ssr",
9 | "build:client": "vite build",
10 | "build:prerender": "impala prerender",
11 | "build": "npm run build:client && npm run build:server && npm run build:prerender",
12 | "preview": "vite preview"
13 | },
14 | "dependencies": {
15 | "@impalajs/core": "latest",
16 | "@impalajs/preact": "latest",
17 | "preact": "^10.13.0"
18 | },
19 | "devDependencies": {
20 | "@babel/core": "^7.0.0-0",
21 | "@preact/preset-vite": "^2.5.0",
22 | "typescript": "^4.9.3",
23 | "vite": "^6.0.3"
24 | }
25 | }
--------------------------------------------------------------------------------
/templates/preact-js/src/App.css:
--------------------------------------------------------------------------------
1 | #__impala {
2 | max-width: 1280px;
3 | margin: 0 auto;
4 | padding: 2rem;
5 | text-align: center;
6 | }
7 |
8 | .logo {
9 | height: 6em;
10 | padding: 1.5em;
11 | will-change: filter;
12 | transition: filter 300ms;
13 | }
14 | .logo:hover {
15 | filter: drop-shadow(0 0 2em #646cffaa);
16 | }
17 | .logo.react:hover {
18 | filter: drop-shadow(0 0 2em #61dafbaa);
19 | }
20 |
21 | @keyframes logo-spin {
22 | from {
23 | transform: rotate(0deg);
24 | }
25 | to {
26 | transform: rotate(360deg);
27 | }
28 | }
29 |
30 | @media (prefers-reduced-motion: no-preference) {
31 | a:nth-of-type(2) .logo {
32 | animation: logo-spin infinite 20s linear;
33 | }
34 | }
35 |
36 | .card {
37 | padding: 2em;
38 | }
39 |
40 | .read-the-docs {
41 | color: #888;
42 | }
43 |
--------------------------------------------------------------------------------
/templates/preact-js/src/App.jsx:
--------------------------------------------------------------------------------
1 | import "./App.css";
2 | import { Head } from "@impalajs/preact/head";
3 |
4 | export const App = ({ children, title }) => {
5 | return (
6 | <>
7 |
8 | {title}
9 |
10 | {children}
11 | >
12 | );
13 | };
14 |
--------------------------------------------------------------------------------
/templates/preact-js/src/assets/impala.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ascorbic/impala/74a4ab04744e8a539a3a7ec1225753c36ac040df/templates/preact-js/src/assets/impala.png
--------------------------------------------------------------------------------
/templates/preact-js/src/entry-client.jsx:
--------------------------------------------------------------------------------
1 | import { clientBootstrap } from "@impalajs/preact/client";
2 |
3 | const modules = import.meta.glob("./routes/**/*.{tsx,jsx}");
4 |
5 | clientBootstrap(modules);
6 |
--------------------------------------------------------------------------------
/templates/preact-js/src/entry-server.jsx:
--------------------------------------------------------------------------------
1 | export { render } from "@impalajs/preact";
2 | export const routeModules = import.meta.glob("./routes/**/*.{tsx,jsx}");
3 | export const dataModules = import.meta.glob("./routes/**/*.data.{ts,js}");
4 |
--------------------------------------------------------------------------------
/templates/preact-js/src/routes/hello.data.js:
--------------------------------------------------------------------------------
1 | export function getRouteData() {
2 | return {
3 | msg: "hello world",
4 | };
5 | }
6 |
--------------------------------------------------------------------------------
/templates/preact-js/src/routes/hello.jsx:
--------------------------------------------------------------------------------
1 | import { App } from "../App";
2 |
3 | export default function Hello({ path, routeData }) {
4 | return (
5 |
6 |
7 | <>
8 | {routeData?.msg} {path}!
9 | >
10 |
11 |
12 | );
13 | }
14 |
--------------------------------------------------------------------------------
/templates/preact-js/src/routes/index.css:
--------------------------------------------------------------------------------
1 | :root {
2 | font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
3 | line-height: 1.5;
4 | font-weight: 400;
5 |
6 | color-scheme: light;
7 | color: #213547;
8 | background-color: #ffffff;
9 |
10 | font-synthesis: none;
11 | text-rendering: optimizeLegibility;
12 | -webkit-font-smoothing: antialiased;
13 | -moz-osx-font-smoothing: grayscale;
14 | -webkit-text-size-adjust: 100%;
15 | }
16 |
17 | a {
18 | font-weight: 500;
19 | color: #646cff;
20 | text-decoration: inherit;
21 | }
22 |
23 | a:hover {
24 | color: #747bff;
25 | }
26 |
27 | body {
28 | margin: 0;
29 | display: flex;
30 | place-items: center;
31 | min-width: 320px;
32 | min-height: 100vh;
33 | }
34 |
35 | h1 {
36 | font-size: 3.2em;
37 | line-height: 1.1;
38 | }
39 |
40 | button {
41 | border-radius: 8px;
42 | border: 1px solid #999;
43 | padding: 0.6em 1.2em;
44 | font-size: 1em;
45 | font-weight: 500;
46 | font-family: inherit;
47 | background-color: #f9f9f9;
48 | cursor: pointer;
49 | transition: border-color 0.25s;
50 | }
51 | button:hover {
52 | border-color: #646cff;
53 | }
54 | button:focus,
55 | button:focus-visible {
56 | outline: 4px auto -webkit-focus-ring-color;
57 | }
58 |
--------------------------------------------------------------------------------
/templates/preact-js/src/routes/index.jsx:
--------------------------------------------------------------------------------
1 | import { useState } from "preact/hooks";
2 | import { App } from "../App";
3 | import logo from "../assets/impala.png";
4 | import "./index.css";
5 |
6 | export default function Hello({ path }) {
7 | const [count, setCount] = useState(0);
8 |
9 | return (
10 |
11 |
12 |
13 |

14 |
15 |
Impala
16 |
17 |
20 |
21 | Edit src/routes/index.tsx
and save to test HMR
22 |
23 |
24 |
25 |
26 | );
27 | }
28 |
--------------------------------------------------------------------------------
/templates/preact-js/src/routes/world/[id].data.js:
--------------------------------------------------------------------------------
1 | // Use this function to define pages to build, and load data for each page.
2 | export async function getStaticPaths() {
3 | return {
4 | paths: [
5 | { params: { id: "1" }, data: { title: "One", description: "Page one" } },
6 | { params: { id: "2" }, data: { title: "Two", description: "Page two" } },
7 | {
8 | params: { id: "3" },
9 | data: { title: "Three", description: "Page three" },
10 | },
11 | ],
12 | };
13 | }
14 |
15 | // This data is shared by all pages in the route.
16 |
17 | export function getRouteData() {
18 | return {
19 | msg: "hello world",
20 | };
21 | }
22 |
--------------------------------------------------------------------------------
/templates/preact-js/src/routes/world/[id].jsx:
--------------------------------------------------------------------------------
1 | import { App } from "../../App";
2 | import { Head } from "@impalajs/preact/head";
3 |
4 | export default function Hello({ path, params, data }) {
5 | return (
6 |
7 |
8 | {/* You can also set a title tag in here if you prefer */}
9 |
10 |
11 |
12 | Hello {path} {params.id}!
13 |
14 |
15 | );
16 | }
17 |
--------------------------------------------------------------------------------
/templates/preact-js/vite.config.js:
--------------------------------------------------------------------------------
1 | import { defineConfig } from "vite";
2 | import preact from "@preact/preset-vite";
3 | import impala from "@impalajs/core/plugin";
4 |
5 | // https://vitejs.dev/config/
6 | export default defineConfig({
7 | plugins: [preact(), impala()],
8 | });
9 |
--------------------------------------------------------------------------------
/templates/preact-ts/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | pnpm-debug.log*
8 | lerna-debug.log*
9 |
10 | node_modules
11 | dist
12 | dist-ssr
13 | *.local
14 |
15 | # Editor directories and files
16 | .vscode/*
17 | !.vscode/extensions.json
18 | .idea
19 | .DS_Store
20 | *.suo
21 | *.ntvs*
22 | *.njsproj
23 | *.sln
24 | *.sw?
25 |
--------------------------------------------------------------------------------
/templates/preact-ts/README.md:
--------------------------------------------------------------------------------
1 | # Impala
2 |
3 |
4 |
5 |
6 |
7 | Thanks for trying the pre-alpha of Impala. To get started, run the dev server:
8 |
9 | ```bash
10 | npm run dev
11 | ```
12 |
13 | When you're ready to build for production, run:
14 |
15 | ```bash
16 | npm run build
17 | ```
18 |
19 | See [the GitHub repo](https://github.com/ascorbic/impala) for more help.
20 |
--------------------------------------------------------------------------------
/templates/preact-ts/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/templates/preact-ts/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@demo/impala-preact-ts",
3 | "private": true,
4 | "version": "0.0.0",
5 | "type": "module",
6 | "scripts": {
7 | "dev": "impala dev",
8 | "build:server": "vite build --ssr",
9 | "build:client": "vite build",
10 | "build:prerender": "impala prerender",
11 | "build": "npm run build:client && npm run build:server && npm run build:prerender",
12 | "preview": "vite preview"
13 | },
14 | "dependencies": {
15 | "@impalajs/core": "latest",
16 | "@impalajs/preact": "latest",
17 | "preact": "^10.13.0"
18 | },
19 | "devDependencies": {
20 | "@babel/core": "^7.0.0-0",
21 | "@preact/preset-vite": "^2.5.0",
22 | "typescript": "^4.9.3",
23 | "vite": "^6.0.3"
24 | }
25 | }
--------------------------------------------------------------------------------
/templates/preact-ts/src/App.css:
--------------------------------------------------------------------------------
1 | #__impala {
2 | max-width: 1280px;
3 | margin: 0 auto;
4 | padding: 2rem;
5 | text-align: center;
6 | }
7 |
8 | .logo {
9 | height: 6em;
10 | padding: 1.5em;
11 | will-change: filter;
12 | transition: filter 300ms;
13 | }
14 | .logo:hover {
15 | filter: drop-shadow(0 0 2em #646cffaa);
16 | }
17 | .logo.react:hover {
18 | filter: drop-shadow(0 0 2em #61dafbaa);
19 | }
20 |
21 | @keyframes logo-spin {
22 | from {
23 | transform: rotate(0deg);
24 | }
25 | to {
26 | transform: rotate(360deg);
27 | }
28 | }
29 |
30 | @media (prefers-reduced-motion: no-preference) {
31 | a:nth-of-type(2) .logo {
32 | animation: logo-spin infinite 20s linear;
33 | }
34 | }
35 |
36 | .card {
37 | padding: 2em;
38 | }
39 |
40 | .read-the-docs {
41 | color: #888;
42 | }
43 |
--------------------------------------------------------------------------------
/templates/preact-ts/src/App.tsx:
--------------------------------------------------------------------------------
1 | import "./App.css";
2 | import { Head } from "@impalajs/preact/head";
3 | import { FunctionComponent } from "preact";
4 |
5 | interface AppProps {
6 | title: string;
7 | }
8 |
9 | export const App: FunctionComponent = ({ children, title }) => {
10 | return (
11 | <>
12 |
13 | {title}
14 |
15 | {children}
16 | >
17 | );
18 | };
19 |
--------------------------------------------------------------------------------
/templates/preact-ts/src/assets/impala.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ascorbic/impala/74a4ab04744e8a539a3a7ec1225753c36ac040df/templates/preact-ts/src/assets/impala.png
--------------------------------------------------------------------------------
/templates/preact-ts/src/entry-client.tsx:
--------------------------------------------------------------------------------
1 | import { clientBootstrap, RouteModule } from "@impalajs/preact/client";
2 |
3 | const modules = import.meta.glob("./routes/**/*.{tsx,jsx}");
4 |
5 | clientBootstrap(modules);
6 |
--------------------------------------------------------------------------------
/templates/preact-ts/src/entry-server.tsx:
--------------------------------------------------------------------------------
1 | import type { RouteModule, DataModule } from "@impalajs/preact";
2 | export { render } from "@impalajs/preact";
3 | export const routeModules = import.meta.glob(
4 | "./routes/**/*.{tsx,jsx}"
5 | );
6 | export const dataModules = import.meta.glob(
7 | "./routes/**/*.data.{ts,js}"
8 | );
9 |
--------------------------------------------------------------------------------
/templates/preact-ts/src/routes/hello.data.ts:
--------------------------------------------------------------------------------
1 | export function getRouteData() {
2 | return {
3 | msg: "hello world",
4 | };
5 | }
6 |
--------------------------------------------------------------------------------
/templates/preact-ts/src/routes/hello.tsx:
--------------------------------------------------------------------------------
1 | import { App } from "../App";
2 | import type { StaticRouteProps } from "@impalajs/core";
3 |
4 | export default function Hello({
5 | path,
6 | routeData,
7 | }: StaticRouteProps) {
8 | return (
9 |
10 |
11 | <>
12 | {routeData?.msg} {path}!
13 | >
14 |
15 |
16 | );
17 | }
18 |
--------------------------------------------------------------------------------
/templates/preact-ts/src/routes/index.css:
--------------------------------------------------------------------------------
1 | :root {
2 | font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
3 | line-height: 1.5;
4 | font-weight: 400;
5 |
6 | color-scheme: light;
7 | color: #213547;
8 | background-color: #ffffff;
9 |
10 | font-synthesis: none;
11 | text-rendering: optimizeLegibility;
12 | -webkit-font-smoothing: antialiased;
13 | -moz-osx-font-smoothing: grayscale;
14 | -webkit-text-size-adjust: 100%;
15 | }
16 |
17 | a {
18 | font-weight: 500;
19 | color: #646cff;
20 | text-decoration: inherit;
21 | }
22 |
23 | a:hover {
24 | color: #747bff;
25 | }
26 |
27 | body {
28 | margin: 0;
29 | display: flex;
30 | place-items: center;
31 | min-width: 320px;
32 | min-height: 100vh;
33 | }
34 |
35 | h1 {
36 | font-size: 3.2em;
37 | line-height: 1.1;
38 | }
39 |
40 | button {
41 | border-radius: 8px;
42 | border: 1px solid #999;
43 | padding: 0.6em 1.2em;
44 | font-size: 1em;
45 | font-weight: 500;
46 | font-family: inherit;
47 | background-color: #f9f9f9;
48 | cursor: pointer;
49 | transition: border-color 0.25s;
50 | }
51 | button:hover {
52 | border-color: #646cff;
53 | }
54 | button:focus,
55 | button:focus-visible {
56 | outline: 4px auto -webkit-focus-ring-color;
57 | }
58 |
--------------------------------------------------------------------------------
/templates/preact-ts/src/routes/index.tsx:
--------------------------------------------------------------------------------
1 | import type { StaticRouteProps } from "@impalajs/core";
2 | import { useState } from "preact/hooks";
3 | import { App } from "../App";
4 | import logo from "../assets/impala.png";
5 | import "./index.css";
6 |
7 | export default function Hello({ path }: StaticRouteProps) {
8 | const [count, setCount] = useState(0);
9 |
10 | return (
11 |
12 |
13 |
14 |

15 |
16 |
Impala
17 |
18 |
21 |
22 | Edit src/routes/index.tsx
and save to test HMR
23 |
24 |
25 |
26 |
27 | );
28 | }
29 |
--------------------------------------------------------------------------------
/templates/preact-ts/src/routes/world/[id].data.ts:
--------------------------------------------------------------------------------
1 | // Use this function to define pages to build, and load data for each page.
2 | export async function getStaticPaths() {
3 | return {
4 | paths: [
5 | { params: { id: "1" }, data: { title: "One", description: "Page one" } },
6 | { params: { id: "2" }, data: { title: "Two", description: "Page two" } },
7 | {
8 | params: { id: "3" },
9 | data: { title: "Three", description: "Page three" },
10 | },
11 | ],
12 | };
13 | }
14 |
15 | // This data is shared by all pages in the route.
16 |
17 | export function getRouteData() {
18 | return {
19 | msg: "hello world",
20 | };
21 | }
22 |
--------------------------------------------------------------------------------
/templates/preact-ts/src/routes/world/[id].tsx:
--------------------------------------------------------------------------------
1 | import { DynamicRouteProps } from "@impalajs/core";
2 | import { App } from "../../App";
3 | import { Head } from "@impalajs/preact/head";
4 |
5 | export default function Hello({
6 | path,
7 | params,
8 | data,
9 | }: DynamicRouteProps) {
10 | return (
11 |
12 |
13 | {/* You can also set a title tag in here if you prefer */}
14 |
15 |
16 |
17 | Hello {path} {params.id}!
18 |
19 |
20 | );
21 | }
22 |
--------------------------------------------------------------------------------
/templates/preact-ts/src/vite-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/templates/preact-ts/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ESNext",
4 | "useDefineForClassFields": true,
5 | "lib": ["DOM", "DOM.Iterable", "ESNext"],
6 | "allowJs": false,
7 | "skipLibCheck": true,
8 | "esModuleInterop": false,
9 | "allowSyntheticDefaultImports": true,
10 | "strict": true,
11 | "forceConsistentCasingInFileNames": true,
12 | "module": "ESNext",
13 | "moduleResolution": "Node",
14 | "resolveJsonModule": true,
15 | "isolatedModules": true,
16 | "noEmit": true,
17 | "jsx": "react-jsx",
18 | "jsxImportSource": "preact"
19 | },
20 | "include": ["src"],
21 | "references": [{ "path": "./tsconfig.node.json" }]
22 | }
23 |
--------------------------------------------------------------------------------
/templates/preact-ts/tsconfig.node.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "composite": true,
4 | "module": "ESNext",
5 | "moduleResolution": "Node",
6 | "allowSyntheticDefaultImports": true
7 | },
8 | "include": ["vite.config.ts"]
9 | }
10 |
--------------------------------------------------------------------------------
/templates/preact-ts/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from "vite";
2 | import preact from "@preact/preset-vite";
3 | import impala from "@impalajs/core/plugin";
4 |
5 | // https://vitejs.dev/config/
6 | export default defineConfig({
7 | plugins: [preact(), impala()],
8 | });
9 |
--------------------------------------------------------------------------------
/templates/react-js/README.md:
--------------------------------------------------------------------------------
1 | # Impala
2 |
3 |
4 |
5 |
6 |
7 | Thanks for trying the pre-alpha of Impala. To get started, run the dev server:
8 |
9 | ```bash
10 | npm run dev
11 | ```
12 |
13 | When you're ready to build for production, run:
14 |
15 | ```bash
16 | npm run build
17 | ```
18 |
19 | See [the GitHub repo](https://github.com/ascorbic/impala) for more help.
20 |
--------------------------------------------------------------------------------
/templates/react-js/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/templates/react-js/jsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "allowJs": true,
4 | "forceConsistentCasingInFileNames": true,
5 | "strict": true,
6 | "lib": [
7 | "DOM",
8 | "DOM.Iterable",
9 | "ESNext"
10 | ],
11 | "esModuleInterop": false,
12 | "isolatedModules": true,
13 | "jsx": "react-jsx",
14 | "noEmit": true,
15 | "resolveJsonModule": true,
16 | "target": "ESNext",
17 | "module": "ESNext",
18 | "moduleResolution": "Node",
19 | "allowSyntheticDefaultImports": true
20 | },
21 | "include": [
22 | "src"
23 | ]
24 | }
--------------------------------------------------------------------------------
/templates/react-js/jsconfig.node.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "allowJs": true,
4 | "forceConsistentCasingInFileNames": true,
5 | "strict": true,
6 | "lib": [
7 | "DOM",
8 | "DOM.Iterable",
9 | "ES2022"
10 | ],
11 | "esModuleInterop": true,
12 | "isolatedModules": true,
13 | "jsx": "react-jsx",
14 | "noEmit": true,
15 | "resolveJsonModule": true,
16 | "module": "ESNext",
17 | "moduleResolution": "Node",
18 | "allowSyntheticDefaultImports": true
19 | },
20 | "include": [
21 | "vite.config.js",
22 | "vite-plugin"
23 | ]
24 | }
--------------------------------------------------------------------------------
/templates/react-js/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@demo/impala-react-js",
3 | "private": true,
4 | "version": "0.0.0",
5 | "type": "module",
6 | "scripts": {
7 | "dev": "impala dev",
8 | "build:server": "vite build --ssr",
9 | "build:client": "vite build",
10 | "build:prerender": "impala prerender",
11 | "build": "npm run build:client && npm run build:server && npm run build:prerender",
12 | "preview": "vite preview"
13 | },
14 | "dependencies": {
15 | "@impalajs/core": "latest",
16 | "@impalajs/react": "latest",
17 | "react": "^18.2.0",
18 | "react-dom": "^18.2.0"
19 | },
20 | "devDependencies": {
21 | "@vitejs/plugin-react": "^3.1.0",
22 | "typescript": "^4.9.3",
23 | "vite": "^6.0.3"
24 | }
25 | }
--------------------------------------------------------------------------------
/templates/react-js/src/App.css:
--------------------------------------------------------------------------------
1 | #__impala {
2 | max-width: 1280px;
3 | margin: 0 auto;
4 | padding: 2rem;
5 | text-align: center;
6 | }
7 |
8 | .logo {
9 | height: 6em;
10 | padding: 1.5em;
11 | will-change: filter;
12 | transition: filter 300ms;
13 | }
14 | .logo:hover {
15 | filter: drop-shadow(0 0 2em #646cffaa);
16 | }
17 | .logo.react:hover {
18 | filter: drop-shadow(0 0 2em #61dafbaa);
19 | }
20 |
21 | @keyframes logo-spin {
22 | from {
23 | transform: rotate(0deg);
24 | }
25 | to {
26 | transform: rotate(360deg);
27 | }
28 | }
29 |
30 | @media (prefers-reduced-motion: no-preference) {
31 | a:nth-of-type(2) .logo {
32 | animation: logo-spin infinite 20s linear;
33 | }
34 | }
35 |
36 | .card {
37 | padding: 2em;
38 | }
39 |
40 | .read-the-docs {
41 | color: #888;
42 | }
43 |
--------------------------------------------------------------------------------
/templates/react-js/src/App.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import "./App.css";
3 | import { Head } from "@impalajs/react/head";
4 |
5 | export const App = ({ children, title }) => {
6 | return (
7 | <>
8 |
9 | {title}
10 |
11 | {children}
12 | >
13 | );
14 | };
15 |
--------------------------------------------------------------------------------
/templates/react-js/src/assets/impala.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ascorbic/impala/74a4ab04744e8a539a3a7ec1225753c36ac040df/templates/react-js/src/assets/impala.png
--------------------------------------------------------------------------------
/templates/react-js/src/entry-client.jsx:
--------------------------------------------------------------------------------
1 | import { clientBootstrap } from "@impalajs/react/client";
2 |
3 | const modules = import.meta.glob("./routes/**/*.{tsx,jsx}");
4 |
5 | clientBootstrap(modules);
6 |
--------------------------------------------------------------------------------
/templates/react-js/src/entry-server.jsx:
--------------------------------------------------------------------------------
1 | export { render } from "@impalajs/react";
2 | export const routeModules = import.meta.glob("./routes/**/*.{tsx,jsx}");
3 | export const dataModules = import.meta.glob("./routes/**/*.data.{ts,js}");
4 |
--------------------------------------------------------------------------------
/templates/react-js/src/routes/hello.data.js:
--------------------------------------------------------------------------------
1 | export function getRouteData() {
2 | return {
3 | msg: "hello world",
4 | };
5 | }
6 |
--------------------------------------------------------------------------------
/templates/react-js/src/routes/hello.jsx:
--------------------------------------------------------------------------------
1 | import { App } from "../App";
2 |
3 | export default function Hello({ path, routeData }) {
4 | return (
5 |
6 |
7 | <>
8 | {routeData?.msg} {path}!
9 | >
10 |
11 |
12 | );
13 | }
14 |
--------------------------------------------------------------------------------
/templates/react-js/src/routes/index.css:
--------------------------------------------------------------------------------
1 | :root {
2 | font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
3 | line-height: 1.5;
4 | font-weight: 400;
5 |
6 | color-scheme: light;
7 | color: #213547;
8 | background-color: #ffffff;
9 |
10 | font-synthesis: none;
11 | text-rendering: optimizeLegibility;
12 | -webkit-font-smoothing: antialiased;
13 | -moz-osx-font-smoothing: grayscale;
14 | -webkit-text-size-adjust: 100%;
15 | }
16 |
17 | a {
18 | font-weight: 500;
19 | color: #646cff;
20 | text-decoration: inherit;
21 | }
22 |
23 | a:hover {
24 | color: #747bff;
25 | }
26 |
27 | body {
28 | margin: 0;
29 | display: flex;
30 | place-items: center;
31 | min-width: 320px;
32 | min-height: 100vh;
33 | }
34 |
35 | h1 {
36 | font-size: 3.2em;
37 | line-height: 1.1;
38 | }
39 |
40 | button {
41 | border-radius: 8px;
42 | border: 1px solid #999;
43 | padding: 0.6em 1.2em;
44 | font-size: 1em;
45 | font-weight: 500;
46 | font-family: inherit;
47 | background-color: #f9f9f9;
48 | cursor: pointer;
49 | transition: border-color 0.25s;
50 | }
51 | button:hover {
52 | border-color: #646cff;
53 | }
54 | button:focus,
55 | button:focus-visible {
56 | outline: 4px auto -webkit-focus-ring-color;
57 | }
58 |
--------------------------------------------------------------------------------
/templates/react-js/src/routes/index.jsx:
--------------------------------------------------------------------------------
1 | import { useState } from "react";
2 | import { App } from "../App";
3 | import logo from "../assets/impala.png";
4 | import "./index.css";
5 |
6 | export default function Hello({ path }) {
7 | const [count, setCount] = useState(0);
8 |
9 | return (
10 |
11 |
12 |
13 |

14 |
15 |
Impala
16 |
17 |
20 |
21 | Edit src/routes/index.tsx
and save to test HMR
22 |
23 |
24 |
25 |
26 | );
27 | }
28 |
--------------------------------------------------------------------------------
/templates/react-js/src/routes/world/[id].data.js:
--------------------------------------------------------------------------------
1 | // Use this function to define pages to build, and load data for each page.
2 | export async function getStaticPaths() {
3 | return {
4 | paths: [
5 | { params: { id: "1" }, data: { title: "One", description: "Page one" } },
6 | { params: { id: "2" }, data: { title: "Two", description: "Page two" } },
7 | {
8 | params: { id: "3" },
9 | data: { title: "Three", description: "Page three" },
10 | },
11 | ],
12 | };
13 | }
14 |
15 | // This data is shared by all pages in the route.
16 |
17 | export function getRouteData() {
18 | return {
19 | msg: "hello world",
20 | };
21 | }
22 |
--------------------------------------------------------------------------------
/templates/react-js/src/routes/world/[id].jsx:
--------------------------------------------------------------------------------
1 | import { App } from "../../App";
2 | import { Head } from "@impalajs/react/head";
3 |
4 | export default function Hello({ path, params, data }) {
5 | return (
6 |
7 |
8 | {/* You can also set a title tag in here if you prefer */}
9 |
10 |
11 |
12 | Hello {path} {params.id}!
13 |
14 |
15 | );
16 | }
17 |
--------------------------------------------------------------------------------
/templates/react-js/vite.config.js:
--------------------------------------------------------------------------------
1 | import { defineConfig } from "vite";
2 | import react from "@vitejs/plugin-react";
3 | import impala from "@impalajs/core/plugin";
4 |
5 | export default defineConfig({
6 | plugins: [react(), impala()],
7 | });
8 |
--------------------------------------------------------------------------------
/templates/react-ts/README.md:
--------------------------------------------------------------------------------
1 | # Impala
2 |
3 |
4 |
5 |
6 |
7 | Thanks for trying the pre-alpha of Impala. To get started, run the dev server:
8 |
9 | ```bash
10 | npm run dev
11 | ```
12 |
13 | When you're ready to build for production, run:
14 |
15 | ```bash
16 | npm run build
17 | ```
18 |
19 | See [the GitHub repo](https://github.com/ascorbic/impala) for more help.
20 |
--------------------------------------------------------------------------------
/templates/react-ts/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/templates/react-ts/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@demo/impala-react-ts",
3 | "private": true,
4 | "version": "0.0.0",
5 | "type": "module",
6 | "scripts": {
7 | "dev": "impala dev",
8 | "build:server": "vite build --ssr",
9 | "build:client": "vite build",
10 | "build:prerender": "impala prerender",
11 | "build": "npm run build:client && npm run build:server && npm run build:prerender",
12 | "preview": "vite preview"
13 | },
14 | "dependencies": {
15 | "@impalajs/core": "latest",
16 | "@impalajs/react": "latest",
17 | "react": "^18.2.0",
18 | "react-dom": "^18.2.0"
19 | },
20 | "devDependencies": {
21 | "@types/node": "^18.15.7",
22 | "@types/react": "^18.0.28",
23 | "@types/react-dom": "^18.0.11",
24 | "@vitejs/plugin-react": "^3.1.0",
25 | "typescript": "^4.9.3",
26 | "vite": "^6.0.3"
27 | }
28 | }
--------------------------------------------------------------------------------
/templates/react-ts/src/App.css:
--------------------------------------------------------------------------------
1 | #__impala {
2 | max-width: 1280px;
3 | margin: 0 auto;
4 | padding: 2rem;
5 | text-align: center;
6 | }
7 |
8 | .logo {
9 | height: 6em;
10 | padding: 1.5em;
11 | will-change: filter;
12 | transition: filter 300ms;
13 | }
14 | .logo:hover {
15 | filter: drop-shadow(0 0 2em #646cffaa);
16 | }
17 | .logo.react:hover {
18 | filter: drop-shadow(0 0 2em #61dafbaa);
19 | }
20 |
21 | @keyframes logo-spin {
22 | from {
23 | transform: rotate(0deg);
24 | }
25 | to {
26 | transform: rotate(360deg);
27 | }
28 | }
29 |
30 | @media (prefers-reduced-motion: no-preference) {
31 | a:nth-of-type(2) .logo {
32 | animation: logo-spin infinite 20s linear;
33 | }
34 | }
35 |
36 | .card {
37 | padding: 2em;
38 | }
39 |
40 | .read-the-docs {
41 | color: #888;
42 | }
43 |
--------------------------------------------------------------------------------
/templates/react-ts/src/App.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import "./App.css";
3 | import { Head } from "@impalajs/react/head";
4 |
5 | interface AppProps {
6 | title: string;
7 | }
8 |
9 | export const App: React.FC> = ({
10 | children,
11 | title,
12 | }) => {
13 | return (
14 | <>
15 |
16 | {title}
17 |
18 | {children}
19 | >
20 | );
21 | };
22 |
--------------------------------------------------------------------------------
/templates/react-ts/src/assets/impala.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ascorbic/impala/74a4ab04744e8a539a3a7ec1225753c36ac040df/templates/react-ts/src/assets/impala.png
--------------------------------------------------------------------------------
/templates/react-ts/src/entry-client.tsx:
--------------------------------------------------------------------------------
1 | import { clientBootstrap, RouteModule } from "@impalajs/react/client";
2 |
3 | const modules = import.meta.glob("./routes/**/*.{tsx,jsx}");
4 |
5 | clientBootstrap(modules);
6 |
--------------------------------------------------------------------------------
/templates/react-ts/src/entry-server.tsx:
--------------------------------------------------------------------------------
1 | import type { RouteModule, DataModule } from "@impalajs/react";
2 | export { render } from "@impalajs/react";
3 | export const routeModules = import.meta.glob(
4 | "./routes/**/*.{tsx,jsx}"
5 | );
6 | export const dataModules = import.meta.glob(
7 | "./routes/**/*.data.{ts,js}"
8 | );
9 |
--------------------------------------------------------------------------------
/templates/react-ts/src/routes/hello.data.ts:
--------------------------------------------------------------------------------
1 | export function getRouteData() {
2 | return {
3 | msg: "hello world",
4 | };
5 | }
6 |
--------------------------------------------------------------------------------
/templates/react-ts/src/routes/hello.tsx:
--------------------------------------------------------------------------------
1 | import { App } from "../App";
2 | import type { StaticRouteProps } from "@impalajs/core";
3 |
4 | export default function Hello({
5 | path,
6 | routeData,
7 | }: StaticRouteProps) {
8 | return (
9 |
10 |
11 | <>
12 | {routeData?.msg} {path}!
13 | >
14 |
15 |
16 | );
17 | }
18 |
--------------------------------------------------------------------------------
/templates/react-ts/src/routes/index.css:
--------------------------------------------------------------------------------
1 | :root {
2 | font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
3 | line-height: 1.5;
4 | font-weight: 400;
5 |
6 | color-scheme: light;
7 | color: #213547;
8 | background-color: #ffffff;
9 |
10 | font-synthesis: none;
11 | text-rendering: optimizeLegibility;
12 | -webkit-font-smoothing: antialiased;
13 | -moz-osx-font-smoothing: grayscale;
14 | -webkit-text-size-adjust: 100%;
15 | }
16 |
17 | a {
18 | font-weight: 500;
19 | color: #646cff;
20 | text-decoration: inherit;
21 | }
22 |
23 | a:hover {
24 | color: #747bff;
25 | }
26 |
27 | body {
28 | margin: 0;
29 | display: flex;
30 | place-items: center;
31 | min-width: 320px;
32 | min-height: 100vh;
33 | }
34 |
35 | h1 {
36 | font-size: 3.2em;
37 | line-height: 1.1;
38 | }
39 |
40 | button {
41 | border-radius: 8px;
42 | border: 1px solid #999;
43 | padding: 0.6em 1.2em;
44 | font-size: 1em;
45 | font-weight: 500;
46 | font-family: inherit;
47 | background-color: #f9f9f9;
48 | cursor: pointer;
49 | transition: border-color 0.25s;
50 | }
51 | button:hover {
52 | border-color: #646cff;
53 | }
54 | button:focus,
55 | button:focus-visible {
56 | outline: 4px auto -webkit-focus-ring-color;
57 | }
58 |
--------------------------------------------------------------------------------
/templates/react-ts/src/routes/index.tsx:
--------------------------------------------------------------------------------
1 | import type { StaticRouteProps } from "@impalajs/core";
2 | import { useState } from "react";
3 | import { App } from "../App";
4 | import logo from "../assets/impala.png";
5 | import "./index.css";
6 |
7 | export default function Hello({ path }: StaticRouteProps) {
8 | const [count, setCount] = useState(0);
9 |
10 | return (
11 |
12 |
13 |
14 |

15 |
16 |
Impala
17 |
18 |
21 |
22 | Edit src/routes/index.tsx
and save to test HMR
23 |
24 |
25 |
26 |
27 | );
28 | }
29 |
--------------------------------------------------------------------------------
/templates/react-ts/src/routes/world/[id].data.ts:
--------------------------------------------------------------------------------
1 | // Use this function to define pages to build, and load data for each page.
2 | export async function getStaticPaths() {
3 | return {
4 | paths: [
5 | { params: { id: "1" }, data: { title: "One", description: "Page one" } },
6 | { params: { id: "2" }, data: { title: "Two", description: "Page two" } },
7 | {
8 | params: { id: "3" },
9 | data: { title: "Three", description: "Page three" },
10 | },
11 | ],
12 | };
13 | }
14 |
15 | // This data is shared by all pages in the route.
16 |
17 | export function getRouteData() {
18 | return {
19 | msg: "hello world",
20 | };
21 | }
22 |
--------------------------------------------------------------------------------
/templates/react-ts/src/routes/world/[id].tsx:
--------------------------------------------------------------------------------
1 | import { DynamicRouteProps } from "@impalajs/core";
2 | import { App } from "../../App";
3 | import { Head } from "@impalajs/react/head";
4 |
5 | export default function Hello({
6 | path,
7 | params,
8 | data,
9 | }: DynamicRouteProps) {
10 | return (
11 |
12 |
13 | {/* You can also set a title tag in here if you prefer */}
14 |
15 |
16 |
17 | Hello {path} {params.id}!
18 |
19 |
20 | );
21 | }
22 |
--------------------------------------------------------------------------------
/templates/react-ts/src/vite-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/templates/react-ts/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ESNext",
4 | "useDefineForClassFields": true,
5 | "lib": [
6 | "DOM",
7 | "DOM.Iterable",
8 | "ESNext"
9 | ],
10 | "allowJs": false,
11 | "skipLibCheck": true,
12 | "esModuleInterop": false,
13 | "allowSyntheticDefaultImports": true,
14 | "strict": true,
15 | "forceConsistentCasingInFileNames": true,
16 | "module": "ESNext",
17 | "moduleResolution": "Node",
18 | "resolveJsonModule": true,
19 | "isolatedModules": true,
20 | "noEmit": true,
21 | "jsx": "react-jsx"
22 | },
23 | "include": [
24 | "src"
25 | ],
26 | "references": [
27 | {
28 | "path": "./tsconfig.node.json"
29 | }
30 | ]
31 | }
--------------------------------------------------------------------------------
/templates/react-ts/tsconfig.node.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "composite": true,
4 | "module": "ESNext",
5 | "moduleResolution": "Node",
6 | "allowSyntheticDefaultImports": true
7 | },
8 | "include": [
9 | "vite.config.ts",
10 | "vite-plugin"
11 | ]
12 | }
--------------------------------------------------------------------------------
/templates/react-ts/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from "vite";
2 | import react from "@vitejs/plugin-react";
3 | import impala from "@impalajs/core/plugin";
4 |
5 | export default defineConfig({
6 | plugins: [react(), impala()],
7 | });
8 |
--------------------------------------------------------------------------------