Skip to content

Commit d41e741

Browse files
authored
Merge pull request #181 from bcomnes/ts-experiment
Enable support for .ts files via node and esbuild type stripping
2 parents 9b76ae4 + 0ce7235 commit d41e741

File tree

19 files changed

+475
-47
lines changed

19 files changed

+475
-47
lines changed

.github/workflows/neocities.yml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ on:
66
- master
77

88
env:
9-
node-version: lts/*
109
FORCE_COLOR: 1
1110

1211
concurrency: # prevent concurrent deploys doing starnge things
@@ -23,7 +22,7 @@ jobs:
2322
- name: Use Node.js
2423
uses: actions/setup-node@v4
2524
with:
26-
node-version: ${{env.node-version}}
25+
node-version-file: package.json
2726
check-latest: true
2827
- run: npm i
2928
- run: npm run build

.github/workflows/release.yml

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,7 @@ on:
88
required: true
99

1010
env:
11-
node_version: lts/*
12-
FORCE_COLOR: 2
11+
FORCE_COLOR: 1
1312

1413
jobs:
1514
version_and_release:
@@ -19,10 +18,10 @@ jobs:
1918
with:
2019
# fetch full history so things like auto-changelog work properly
2120
fetch-depth: 0
22-
- name: Use Node.js ${{ env.node_version }}
21+
- name: Use Node.js
2322
uses: actions/setup-node@v4
2423
with:
25-
node-version: ${{ env.node_version }}
24+
node-version-file: package.json
2625
check-latest: true
2726
# setting a registry enables the NODE_AUTH_TOKEN env variable where we can set an npm token. REQUIRED
2827
registry-url: 'https://registry.npmjs.org'
@@ -37,4 +36,3 @@ jobs:
3736
newversion: ${{ github.event.inputs.newversion }}
3837
github_token: ${{ secrets.GITHUB_TOKEN }} # built in actions token. Passed tp gh-release if in use.
3938
npm_token: ${{ secrets.NPM_TOKEN }} # user set secret token generated at npm
40-

.github/workflows/tests.yml

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
---
21
name: tests
32

43
on: [pull_request, push]
@@ -13,7 +12,7 @@ jobs:
1312
strategy:
1413
matrix:
1514
os: [ubuntu-latest]
16-
node-version: [lts/*]
15+
node-version: [lts/*, '23']
1716

1817
steps:
1918
- uses: actions/checkout@v4
@@ -23,12 +22,30 @@ jobs:
2322
node-version: ${{ matrix.node-version }}
2423
check-latest: true
2524
- run: npm i
26-
- run: npm test
25+
- name: Run tests
26+
run: |
27+
node_major=$(node -p "process.versions.node.split('.')[0]")
28+
if [ "$node_major" -lt 23 ]; then
29+
NODE_OPTIONS="--experimental-strip-types" npm test
30+
else
31+
npm test
32+
fi
2733
- name: Coveralls
2834
uses: coverallsapp/github-action@v2
2935
with:
3036
github-token: ${{ secrets.GITHUB_TOKEN }}
3137
files: .tap/report/lcov.info
38+
parallel: true
39+
40+
coverage:
41+
needs: test
42+
runs-on: ubuntu-latest
43+
steps:
44+
- name: Close parallel build
45+
uses: coverallsapp/github-action@v2
46+
with:
47+
parallel-finished: true
48+
3249

3350
automerge:
3451
needs: test

README.md

Lines changed: 123 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ npm install top-bun
1616
- 🌎 [`top-bun` docs website](https://top-bun.org)
1717
- 💬 [Discord Chat](https://discord.gg/AVTsPRGeR9)
1818
- 📢 [v7 Announcement](https://bret.io/blog/2023/reintroducing-top-bun/)
19+
- 📘 [Full TypeScript Support](#typescript-support)
1920

2021
## Table of Contents
2122

@@ -73,13 +74,20 @@ src % tree
7374
│ ├── page.html # Raw html pages are also supported. They support handlebars template blocks.
7475
│ ├── page.vars.js # pages can define page variables in a page.vars.js.
7576
│ └── style.css
77+
├── js-page
78+
│ └── page.js # A page can also just be a plain javascript function that returns content
79+
├── ts-page
80+
│ ├── client.ts # client bundles can be written in typescript via type stripping
81+
│ ├── page.vars.ts # pages can define page variables in a page.vars.js.
82+
│ └── page.ts # Anywhere you can use js in top-bun, you can also use typescript files. They compile via speedy type stripping.
7683
├── feeds
7784
│ └── feeds.template.js # Templates let you generate any file you want from variables and page data.
7885
├── layouts # layouts can live anywhere. The inner content of your page is slotted into your layout.
7986
│ ├── blog.layout.js # pages specify which layout they want by setting a `layout` page variable.
8087
│ ├── blog.layout.css # layouts can define an additional layout style.
8188
│ ├── blog.layout.client.js # layouts can also define a layout client.
8289
│ ├── article.layout.js # layouts can extend other layouts, since they are just functions.
90+
│ ├── typescript.layout.ts # layouts can also be written in typescript
8391
│ └── root.layout.js # the default layout is called root.
8492
├── globals # global assets can live anywhere. Here they are in a folder called globals.
8593
│ ├── global.client.js # you can define a global js client that loads on every page.
@@ -588,7 +596,7 @@ These imports will include the `root.layout.js` layout assets into the `blog.lay
588596

589597
All static assets in the `src` directory are copied 1:1 to the `public` directory. Any file in the `src` directory that doesn't end in `.js`, `.css`, `.html`, or `.md` is copied to the `dest` directory.
590598

591-
### 📁 `--copy` directories
599+
### `--copy` directories
592600

593601
You can specify directories to copy into your `dest` directory using the `--copy` flag. Everything in those directories will be copied as-is into the destination, including js, css, html and markdown, preserving the internal directory structure. Conflicting files are not detected or reported and will cause undefined behavior.
594602

@@ -926,18 +934,6 @@ Template files receive a similar set of variables:
926934
- `pages`: An array of [`PageData`](https://github.com/bcomnes/top-bun/blob/master/lib/build-pages/page-data.js) instances for every page in the site build. Use this array to introspect pages to generate feeds and index pages.
927935
- `template`: An object of the template file data being rendered.
928936

929-
### Variable types
930-
931-
The following types are exported from `top-bun`:
932-
933-
```ts
934-
LayoutFunction<T>
935-
PostVarsFunction<T>
936-
PageFunction<T>
937-
TemplateFunction<T>
938-
TemplateAsyncIterator<T>
939-
```
940-
941937
Where `T` is your set of variables in the `vars` object.
942938

943939
### `postVars` post processing variables (Advanced) {#postVars}
@@ -992,6 +988,115 @@ This `postVars` renders some html from page introspection of the last 5 blog pos
992988
\{{{ vars.blogPostsHtml }}}
993989
```
994990

991+
992+
## TypeScript Support
993+
994+
`top-bun` now supports **TypeScript** via native type-stripping in Node.js.
995+
996+
- **Requires Node.js ≥23** *(built-in)* or **Node.js 22** with the `NODE_OPTIONS="--experimental-strip-types" top-bun` env variable.
997+
- Seamlessly mix `.ts`, `.mts`, `.cts` files alongside `.js`, `.mjs`, `.cjs`.
998+
- No explicit compilation step needed—Node.js handles type stripping at runtime.
999+
- Fully compatible with existing `top-bun` file naming conventions.
1000+
1001+
### Supported File Types
1002+
1003+
Anywhere you can use a `.js`, `.mjs` or `.cjs` file in top-bun, you can now use `.ts`, `.mts`, `.cts`.
1004+
When running in a Node.js context, [type-stripping](https://nodejs.org/api/typescript.html#type-stripping) is used.
1005+
When running in a web client context, [esbuild](https://esbuild.github.io/content-types/#typescript) type stripping is used.
1006+
Type stripping provides 0 type checking, so be sure to set up `tsc` and `tsconfig.json` so you can catch type errors while editing or in CI.
1007+
1008+
### Recommended `tsconfig.json`
1009+
1010+
Install [@voxpelli/tsconfig](https://ghub.io/@voxpelli/tsconfig) which provides type checking in `.js` and `.ts` files and preconfigured for `--no-emit` and extend with type stripping friendly rules:
1011+
1012+
```json
1013+
{
1014+
"extends": "@voxpelli/tsconfig/node20.json",
1015+
"compilerOptions": {
1016+
"skipLibCheck": true,
1017+
"erasableSyntaxOnly": true,
1018+
"allowImportingTsExtensions": true,
1019+
"rewriteRelativeImportExtensions": true,
1020+
"verbatimModuleSyntax": true
1021+
},
1022+
"include": [
1023+
"**/*",
1024+
],
1025+
"exclude": [
1026+
"**/*.js",
1027+
"node_modules",
1028+
"coverage",
1029+
".github"
1030+
]
1031+
}
1032+
```
1033+
1034+
### Using TypeScript with top-bun Types
1035+
1036+
You can use `top-bun`'s built-in types to strongly type your layout, page, and template functions. The following types are available:
1037+
1038+
```ts
1039+
import type {
1040+
LayoutFunction,
1041+
PostVarsFunction,
1042+
PageFunction,
1043+
TemplateFunction,
1044+
TemplateAsyncIterator
1045+
} from 'top-bun'
1046+
```
1047+
1048+
They are all generic and accept a variable template that you can develop and share between files.
1049+
1050+
### TypeScript Examples
1051+
1052+
#### Page Function
1053+
1054+
```typescript
1055+
// page.ts
1056+
import type { PageFunction } from 'top-bun'
1057+
1058+
export const vars = {
1059+
message: 'TypeScript pages are easy!'
1060+
}
1061+
1062+
const page: PageFunction<typeof vars> = async ({ vars }) => {
1063+
return `<h1>Hello from TypeScript!</h1><p>${vars.message}</p>`
1064+
}
1065+
1066+
export default page
1067+
```
1068+
1069+
#### Layout Function
1070+
1071+
```typescript
1072+
// root.layout.ts
1073+
import type { LayoutFunction } from 'top-bun'
1074+
import { html, render } from 'uhtml-isomorphic'
1075+
1076+
type Vars = {
1077+
siteName: string,
1078+
title?: string
1079+
}
1080+
1081+
const layout: LayoutFunction<Vars> = ({ vars, scripts, styles, children }) => {
1082+
return render(String, html`
1083+
<!DOCTYPE html>
1084+
<html>
1085+
<head>
1086+
<title>${vars.title ? `${vars.title} | ` : ''}${vars.siteName}</title>
1087+
${styles?.map(style => html`<link rel="stylesheet" href="${style}">`)}
1088+
${scripts?.map(script => html`<script type="module" src="${script}"></script>`)}
1089+
</head>
1090+
<body>
1091+
${children}
1092+
</body>
1093+
</html>
1094+
`)
1095+
}
1096+
1097+
export default layout
1098+
```
1099+
9951100
## Design Goals
9961101

9971102
- Convention over configuration. All configuration should be optional, and at most it should be minimal.
@@ -1013,8 +1118,8 @@ This `postVars` renders some html from page introspection of the last 5 blog pos
10131118
- Garbage in, garbage out. Don't over-correct bad input.
10141119
- Conventions + standards. Vanilla file types. No new file extensions. No weird syntax to learn. Language tools should just work because you aren't doing anything weird or out of band.
10151120
- Encourage directly runnable source files. Direct run is an incredible, undervalued feature more people should learn to use.
1016-
- Support typescript, via ts-in-js and type stripping features when available.
1017-
- Embrace the now. Limit support to pretend you are working in the future (technology predictions nearly always are wrong!)
1121+
- Support typescript, via ts-in-js and type stripping features. Leave type checking to tsc.
1122+
- Embrace the now. Limit support on features that let one pretend they are working with future ecosystem features e.g. pseudo esm (technology predictions nearly always are wrong!)
10181123

10191124
## FAQ
10201125

@@ -1077,7 +1182,9 @@ Some notable features are included below, see the [roadmap](https://github.com/u
10771182
- [x] Built in browsersync dev server
10781183
- [x] Real default layout style builds
10791184
- [x] Esbuild settings escape hatch
1080-
- [x] Copy folders
1185+
- [x] Copy folders
1186+
- [x] Full Typescript support via native type stripping
1187+
- [ ] JSX support in client bundles
10811188
- ...[See roadmap](https://github.com/users/bcomnes/projects/3/)
10821189

10831190
## History
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
{
2+
"name": "@top-bun/preact-example",
3+
"version": "0.0.0",
4+
"type": "module",
5+
"scripts": {
6+
"start": "npm run watch",
7+
"build": "npm run clean && top-bun",
8+
"clean": "rm -rf public && mkdir -p public",
9+
"watch": "npm run clean && tb --watch"
10+
},
11+
"author": "Bret Comnes <[email protected]> (https://bret.io/)",
12+
"license": "MIT",
13+
"dependencies": {
14+
"@preact/signals": "^2.0.0",
15+
"highlight.js": "^11.9.0",
16+
"htm": "^3.1.1",
17+
"mine.css": "^9.0.1",
18+
"preact": "^10.24.0",
19+
"preact-render-to-string": "^6.5.11",
20+
"top-bun": "../../."
21+
},
22+
"devDependencies": {
23+
"@voxpelli/tsconfig": "^15.0.0",
24+
"npm-run-all2": "^6.0.0",
25+
"typescript": "~5.8.2"
26+
}
27+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# Preact example
2+
3+
This is a preact example.
4+
5+
[Isomorphic Component Rendering](./isomorphic/)
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import { toggleTheme } from 'mine.css'
2+
// @ts-ignore
3+
window.toggleTheme = toggleTheme
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
@import 'mine.css/dist/mine.css';
2+
@import 'mine.css/dist/layout.css';
3+
@import 'highlight.js/styles/github-dark-dimmed.css';
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import { html, Component } from 'htm/preact'
2+
import { render } from 'preact'
3+
import { useCallback } from 'preact/hooks'
4+
import { useSignal, useComputed } from '@preact/signals'
5+
6+
const Header = ({ name }) => html`<h1>${name} List</h1>`
7+
8+
const Footer = props => {
9+
const count = useSignal(0)
10+
const double = useComputed(() => count.value * 2)
11+
12+
const handleClick = useCallback(() => {
13+
count.value++
14+
}, [count])
15+
16+
return html`<footer ...${props}>
17+
${count}
18+
${double}
19+
${props.children}
20+
<button onClick=${handleClick}>Click</button>
21+
</footer>`
22+
}
23+
24+
class App extends Component {
25+
addTodo () {
26+
const { todos = [] } = this.state
27+
this.setState({ todos: todos.concat(`Item ${todos.length}`) })
28+
}
29+
30+
render ({ page }, { todos = [] }) {
31+
return html`
32+
<div class="app">
33+
<${Header} name="ToDo's (${page})" />
34+
<ul>
35+
${todos.map(todo => html`
36+
<li key=${todo}>${todo}</li>
37+
`)}
38+
</ul>
39+
<button onClick=${() => this.addTodo()}>Add Todo</button>
40+
<${Footer}>footer content here<//>
41+
</div>
42+
`
43+
}
44+
}
45+
46+
export const page = () => html`
47+
<${App} page="Isomorphic"/>
48+
<${Footer}>footer content here<//>
49+
<${Footer}>footer content here<//>
50+
`
51+
52+
if (typeof window !== 'undefined') {
53+
const renderTarget = document.querySelector('.app-main')
54+
render(page(), renderTarget)
55+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import { page } from './client.ts'
2+
3+
export default () => {
4+
return page()
5+
}

0 commit comments

Comments
 (0)