This commit is contained in:
pfych 2025-04-05 17:05:06 +11:00
commit 84a1d08651
14 changed files with 3607 additions and 0 deletions

29
.eslintrc.js Normal file
View File

@ -0,0 +1,29 @@
module.exports = {
env: {
browser: true,
es6: true,
node: true,
},
extends: ['prettier'],
parser: '@typescript-eslint/parser',
parserOptions: {
ecmaFeatures: {
jsx: true,
},
ecmaVersion: 2018,
sourceType: 'module',
project: './tsconfig.json',
},
plugins: [
'@typescript-eslint/eslint-plugin',
'prettier',
],
rules: {
camelcase: 'error',
'@typescript-eslint/return-await': 'off',
'@typescript-eslint/camelcase': 'off',
'no-param-reassign': ['error', { props: false }],
'no-underscore-dangle': ['error', { allow: ['_id'] }],
},
reportUnusedDisableDirectives: true,
};

5
.gitignore vendored Normal file
View File

@ -0,0 +1,5 @@
.out/
.tmp/
node_modules/
.idea/
.vscode/

38
package.json Normal file
View File

@ -0,0 +1,38 @@
{
"name": "ssg",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"compile": "esbuild ssg/build.tsx --bundle --outfile=.tmp/ssg/build.js --jsx-import-source=@kitajs/html --minify --sourcemap --platform=node --external:esbuild",
"buld": "npm run compile && node .tmp/ssg/build.js"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"@kitajs/html": "^4.2.7",
"@kitajs/ts-html-plugin": "^4.1.1",
"@types/node": "^22.14.0",
"esbuild": "^0.25.2",
"esbuild-sass-plugin": "^3.3.1",
"glob": "^11.0.1",
"prettier": "^3.5.3",
"typescript": "^5.8.3",
"eslint": "8.56.0",
"eslint-config-prettier": "9.1.0",
"eslint-plugin-import": "2.29.1",
"eslint-plugin-prettier": "5.1.3",
"@typescript-eslint/typescript-estree": "8.22.0",
"@typescript-eslint/eslint-plugin": "7.0.2",
"@typescript-eslint/parser": "7.0.2"
},
"prettier": {
"semi": true,
"singleQuote": true,
"tabWidth": 2,
"trailingComma": "all",
"arrowParens": "always",
"printWidth": 80
}
}

3375
pnpm-lock.yaml Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,3 @@
.body {
color: blue;
}

View File

@ -0,0 +1,5 @@
import styles from './body.module.scss';
export default function Body(props: { children: any }) {
return <p className={styles.body}>{props.children}</p>;
}

View File

@ -0,0 +1,3 @@
.heading {
color: red;
}

View File

@ -0,0 +1,5 @@
import styles from './heading.module.scss';
export default function Heading(props: { children: any }) {
return <h1 className={styles.heading}>{props.children}</h1>;
}

3
src/pages/404.tsx Normal file
View File

@ -0,0 +1,3 @@
export default function NotFound() {
return <div>Not Found</div>;
}

11
src/pages/index.tsx Normal file
View File

@ -0,0 +1,11 @@
import Heading from '../components/heading/heading';
import Body from '../components/body/body';
export default function Index() {
return (
<div>
<Heading>foo</Heading>
<Body>Hello World!</Body>
</div>
);
}

51
ssg/build.tsx Normal file
View File

@ -0,0 +1,51 @@
import { glob } from 'glob';
import { build } from 'esbuild';
import { mkdirSync, writeFileSync, rmSync } from 'node:fs';
import { sassPlugin } from 'esbuild-sass-plugin';
import Root from './root';
import { getPaths, getStylePath, outDir } from './utils';
mkdirSync(outDir, { recursive: true });
void (async () => {
const pages = await glob('src/pages/**/*.tsx');
await Promise.all(
pages.map(async (page) => {
const { pagePath, tmpJsPath, outputHtmlPath } = getPaths(page);
await build({
entryPoints: [pagePath],
bundle: true,
outfile: tmpJsPath,
jsxImportSource: '@kitajs/html',
minify: true,
platform: 'node',
external: ['esbuild'],
plugins: [
sassPlugin({
filter: /\.module\.scss$/,
type: 'local-css',
}),
sassPlugin({
filter: /\.scss$/,
type: 'css',
}),
],
});
const { default: Page } = await import(tmpJsPath);
const html = Page.default();
let stylePaths: string[] = getStylePath(tmpJsPath, outputHtmlPath);
writeFileSync(
outputHtmlPath,
<Root title="My App" stylePaths={stylePaths} scriptPaths={[]}>
{html}
</Root>,
);
}),
);
rmSync('.tmp', { recursive: true });
})();

25
ssg/root.tsx Normal file
View File

@ -0,0 +1,25 @@
interface Props {
children: HTMLElement;
title: string;
scriptPaths: string[];
stylePaths: string[];
}
export default function Root(props: Props) {
const { children, title, scriptPaths, stylePaths } = props;
return (
<html lang="en">
<head>
<title>{title}</title>
{stylePaths.map((path) => (
<link rel="stylesheet" href={path} />
))}
{scriptPaths.map((path) => (
<script src={path} />
))}
</head>
<body>{children}</body>
</html>
);
}

31
ssg/utils.ts Normal file
View File

@ -0,0 +1,31 @@
import { cpSync, existsSync } from 'node:fs';
import { resolve } from 'path';
export const outDir = resolve('.out');
export const getStylePath = (tmpJsPath: string, outputHtmlPath: string) => {
let stylePaths: string[] = [];
if (existsSync(tmpJsPath.replace('.js', '.css'))) {
cpSync(
tmpJsPath.replace('.js', '.css'),
outputHtmlPath.replace('.html', '.css'),
);
stylePaths.push(
outputHtmlPath.replace('.html', '.css').replace(outDir, '.'),
);
}
return stylePaths;
};
export const getPaths = (rawPath: string) => {
const pagePath = resolve(rawPath);
const tmpJsPath = pagePath.replace('src', '.tmp').replace('.tsx', '.js');
const outputHtmlPath = pagePath
.replace('src/pages', '.out')
.replace('.tsx', '.html');
return { pagePath, tmpJsPath, outputHtmlPath };
};

23
tsconfig.json Normal file
View File

@ -0,0 +1,23 @@
{
"compilerOptions": {
"target": "es6",
"lib": ["dom", "dom.iterable", "es2021"],
"jsx": "react-jsx",
"jsxImportSource": "@kitajs/html",
"plugins": [{ "name": "@kitajs/ts-html-plugin" }],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noEmit": true,
"incremental": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"outDir": "./out",
},
"include": ["**/*.tsx", "**/*.ts"],
"exclude": ["node_modules"]
}