katabatic 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +46 -0
- package/package.json +23 -0
- package/src/build.js +37 -0
- package/src/index.js +155 -0
- package/src/serve.js +14 -0
- package/src/utils/argv.js +12 -0
- package/src/utils/files.js +14 -0
- package/src/utils/misc.js +7 -0
package/README.md
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
# Katabatic
|
|
4
|
+
|
|
5
|
+
A simple and efficient way to develop Web Components !
|
|
6
|
+
|
|
7
|
+
Katabatic is a compiler that transforms HTML modules into JavaScript.
|
|
8
|
+
Its goal is to provide a modern, framework-free web development experience by relying solely on standard APIs.
|
|
9
|
+
|
|
10
|
+
- :sparkles: No API ! Write plain HTML, CSS and JS without having to learn yet an other framework.
|
|
11
|
+
- :heart: Web Components without the complexity
|
|
12
|
+
- :zap: Reactive with signals
|
|
13
|
+
|
|
14
|
+
```html
|
|
15
|
+
<script type="module">
|
|
16
|
+
export class Counter extends HTMLElement {
|
|
17
|
+
count = 0
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
customElements.define('my-counter', Counter);
|
|
21
|
+
</script>
|
|
22
|
+
|
|
23
|
+
<template>
|
|
24
|
+
<div>
|
|
25
|
+
<button onclick="{count++}">counter is {count}</button>
|
|
26
|
+
</div>
|
|
27
|
+
<style>
|
|
28
|
+
button {
|
|
29
|
+
padding: 0.5rem;
|
|
30
|
+
text-transform: uppercase;
|
|
31
|
+
}
|
|
32
|
+
</style>
|
|
33
|
+
</template>
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Getting started
|
|
37
|
+
|
|
38
|
+
```
|
|
39
|
+
npm i katabatic -D
|
|
40
|
+
npm i @katabatic/runtime
|
|
41
|
+
|
|
42
|
+
npx katabatic
|
|
43
|
+
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
See the [examples](examples)
|
package/package.json
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "katabatic",
|
|
3
|
+
"license": "MIT",
|
|
4
|
+
"version": "1.0.0",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/katabatic-js/katabatic.git",
|
|
9
|
+
"directory": "packages/katabatic"
|
|
10
|
+
},
|
|
11
|
+
"keywords": [
|
|
12
|
+
"katabatic",
|
|
13
|
+
"web-components",
|
|
14
|
+
"signals"
|
|
15
|
+
],
|
|
16
|
+
"bin": {
|
|
17
|
+
"katabatic": "src/index.js"
|
|
18
|
+
},
|
|
19
|
+
"dependencies": {
|
|
20
|
+
"@katabatic/compiler": "^1.0.0",
|
|
21
|
+
"@web/dev-server": "^0.4.6"
|
|
22
|
+
}
|
|
23
|
+
}
|
package/src/build.js
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import path from 'path'
|
|
2
|
+
import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'fs'
|
|
3
|
+
import { compile } from '@katabatic/compiler'
|
|
4
|
+
import { createRouter, compileHtmlIndex } from '@katabatic/compiler/router'
|
|
5
|
+
|
|
6
|
+
export function buildModule({ src, out, ...context }) {
|
|
7
|
+
const source = readFileSync(src.filePath, { encoding: 'utf8' })
|
|
8
|
+
const { code, ...module } = compile(source, context)
|
|
9
|
+
|
|
10
|
+
if (!existsSync(out.parentPath)) {
|
|
11
|
+
mkdirSync(out.parentPath, { recursive: true })
|
|
12
|
+
}
|
|
13
|
+
writeFileSync(out.filePath, code)
|
|
14
|
+
|
|
15
|
+
return module
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function buildIndex({ src, out, ...context }) {
|
|
19
|
+
const source = readFileSync(src.filePath, { encoding: 'utf8' })
|
|
20
|
+
const { code, ...index } = compileHtmlIndex(source, context)
|
|
21
|
+
|
|
22
|
+
if (!existsSync(out.parentPath)) {
|
|
23
|
+
mkdirSync(out.parentPath, { recursive: true })
|
|
24
|
+
}
|
|
25
|
+
writeFileSync(out.filePath, code)
|
|
26
|
+
|
|
27
|
+
return index
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function buildRouter(outDirPath, registry) {
|
|
31
|
+
const result = createRouter(registry)
|
|
32
|
+
|
|
33
|
+
if (!existsSync(outDirPath)) {
|
|
34
|
+
mkdirSync(outDirPath, { recursive: true })
|
|
35
|
+
}
|
|
36
|
+
writeFileSync(path.join(outDirPath, 'router.js'), result.code)
|
|
37
|
+
}
|
package/src/index.js
ADDED
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
#! /usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import path from 'path'
|
|
4
|
+
import { watch as watchDir, existsSync } from 'fs'
|
|
5
|
+
import { startDevServer } from '@web/dev-server'
|
|
6
|
+
import { walkDir } from './utils/files.js'
|
|
7
|
+
import * as argv from './utils/argv.js'
|
|
8
|
+
import { serve } from './serve.js'
|
|
9
|
+
import { buildIndex, buildModule, buildRouter } from './build.js'
|
|
10
|
+
import { hash } from './utils/misc.js'
|
|
11
|
+
|
|
12
|
+
const command = argv.command()
|
|
13
|
+
|
|
14
|
+
const rootPath = path.resolve(argv.option('root')) ?? process.cwd()
|
|
15
|
+
const srcDirPath = path.join(rootPath, argv.option('srcDir') ?? '.')
|
|
16
|
+
const outDirPath = path.join(rootPath, argv.option('outDir') ?? '.')
|
|
17
|
+
const routesDirPath = path.join(srcDirPath, argv.option('routesDir') ?? './routes')
|
|
18
|
+
const hasRoutes = existsSync(routesDirPath)
|
|
19
|
+
|
|
20
|
+
const rewriteRelativeImportExtensions = argv.option('rewriteRelativeImportExtensions') ?? false
|
|
21
|
+
|
|
22
|
+
switch (command) {
|
|
23
|
+
case 'build':
|
|
24
|
+
build()
|
|
25
|
+
break
|
|
26
|
+
default:
|
|
27
|
+
watch(build())
|
|
28
|
+
start()
|
|
29
|
+
break
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function build() {
|
|
33
|
+
const registry = {}
|
|
34
|
+
|
|
35
|
+
walkDir(srcDirPath, {}, (entry) => {
|
|
36
|
+
if (entry.name === 'index.html') {
|
|
37
|
+
if (!hasRoutes) return
|
|
38
|
+
|
|
39
|
+
const srcFilePath = path.join(entry.parentPath, entry.name)
|
|
40
|
+
const index = buildIndex(resolve(srcFilePath))
|
|
41
|
+
registry[index.ref] = index
|
|
42
|
+
return
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (entry.name.endsWith('.html') || entry.name.endsWith('.ktb')) {
|
|
46
|
+
const srcFilePath = path.join(entry.parentPath, entry.name)
|
|
47
|
+
const module = buildModule(resolve(srcFilePath, true))
|
|
48
|
+
registry[module.ref] = module
|
|
49
|
+
return
|
|
50
|
+
}
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
if (hasRoutes) {
|
|
54
|
+
buildRouter(outDirPath, registry)
|
|
55
|
+
}
|
|
56
|
+
return registry
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function watch(registry) {
|
|
60
|
+
watchDir(srcDirPath, { recursive: true }, (event, fileName) => {
|
|
61
|
+
if (fileName.endsWith('index.html')) {
|
|
62
|
+
if (!hasRoutes) return
|
|
63
|
+
|
|
64
|
+
const srcFilePath = path.join(srcDirPath, fileName)
|
|
65
|
+
const index = buildIndex(resolve(srcFilePath))
|
|
66
|
+
registry[index.ref] = index
|
|
67
|
+
return
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (fileName.endsWith('.html') || fileName.endsWith('.ktb')) {
|
|
71
|
+
const srcFilePath = path.join(srcDirPath, fileName)
|
|
72
|
+
const module = buildModule(resolve(srcFilePath, true))
|
|
73
|
+
registry[module.ref] = module
|
|
74
|
+
return
|
|
75
|
+
}
|
|
76
|
+
})
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function start() {
|
|
80
|
+
const plugins = []
|
|
81
|
+
|
|
82
|
+
if (hasRoutes) {
|
|
83
|
+
plugins.push(serve(outDirPath))
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
startDevServer({
|
|
87
|
+
config: {
|
|
88
|
+
rootDir: rootPath,
|
|
89
|
+
port: 3000,
|
|
90
|
+
watch: true,
|
|
91
|
+
open: true,
|
|
92
|
+
nodeResolve: true,
|
|
93
|
+
plugins
|
|
94
|
+
},
|
|
95
|
+
readCliArgs: false,
|
|
96
|
+
readFileConfig: false
|
|
97
|
+
})
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function resolve(srcFilePath, isModule) {
|
|
101
|
+
const srcFileName = path.basename(srcFilePath)
|
|
102
|
+
const srcParentPath = path.dirname(srcFilePath)
|
|
103
|
+
|
|
104
|
+
const parentPath = path.join(outDirPath, path.relative(srcDirPath, srcParentPath))
|
|
105
|
+
const fileName = srcFileName.replace(/\.html|\.ktb$/, '.js')
|
|
106
|
+
const filePath = path.join(parentPath, fileName)
|
|
107
|
+
|
|
108
|
+
const ref = path.relative(srcDirPath, srcFilePath)
|
|
109
|
+
let routerImport = `./${path.relative(outDirPath, filePath)}`
|
|
110
|
+
|
|
111
|
+
if (isModule) {
|
|
112
|
+
const name = path.basename(fileName, '.js')
|
|
113
|
+
const moduleHash = hash(srcFilePath)
|
|
114
|
+
|
|
115
|
+
let route = path.relative(routesDirPath, srcParentPath)
|
|
116
|
+
route = route.startsWith('..') ? undefined : `/${route}`
|
|
117
|
+
routerImport = !!route ? routerImport : undefined
|
|
118
|
+
const isPage = !!route && !!srcFileName.match(/^page(\.html|\.ktb)$/)
|
|
119
|
+
const isLayout = !!route && !!srcFileName.match(/^layout(\.html|\.ktb)$/)
|
|
120
|
+
|
|
121
|
+
return {
|
|
122
|
+
ref,
|
|
123
|
+
name,
|
|
124
|
+
customElementName: `${name}-${moduleHash}`,
|
|
125
|
+
route,
|
|
126
|
+
isPage,
|
|
127
|
+
isLayout,
|
|
128
|
+
routerImport,
|
|
129
|
+
hash: moduleHash,
|
|
130
|
+
rewriteRelativeImportExtensions,
|
|
131
|
+
src: {
|
|
132
|
+
filePath: srcFilePath
|
|
133
|
+
},
|
|
134
|
+
out: {
|
|
135
|
+
parentPath,
|
|
136
|
+
fileName,
|
|
137
|
+
filePath
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return {
|
|
143
|
+
ref,
|
|
144
|
+
isIndex: true,
|
|
145
|
+
routerImport,
|
|
146
|
+
src: {
|
|
147
|
+
filePath: srcFilePath
|
|
148
|
+
},
|
|
149
|
+
out: {
|
|
150
|
+
parentPath,
|
|
151
|
+
fileName,
|
|
152
|
+
filePath
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
package/src/serve.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import path from 'path'
|
|
2
|
+
|
|
3
|
+
export function serve(outDirPath) {
|
|
4
|
+
let router
|
|
5
|
+
|
|
6
|
+
return {
|
|
7
|
+
name: 'serve',
|
|
8
|
+
async serve({ request }) {
|
|
9
|
+
router ??= await import(path.join(outDirPath, 'router.js'))
|
|
10
|
+
console.log(request.url)
|
|
11
|
+
return router.route(request.url)
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export function command() {
|
|
2
|
+
return process.argv[2]
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
export function option(name) {
|
|
6
|
+
const index = process.argv.indexOf(`-${name}`)
|
|
7
|
+
|
|
8
|
+
if (index >= 0) {
|
|
9
|
+
const value = process.argv[index + 1]
|
|
10
|
+
return value && value[0] !== '-' ? value : undefined
|
|
11
|
+
}
|
|
12
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { readdirSync } from 'fs'
|
|
2
|
+
import path from 'path'
|
|
3
|
+
|
|
4
|
+
export function walkDir(dirPath, state, handle) {
|
|
5
|
+
const entries = readdirSync(dirPath, { withFileTypes: true })
|
|
6
|
+
|
|
7
|
+
for (const entry of entries) {
|
|
8
|
+
if (entry.isDirectory() && entry.name !== 'node_modules') {
|
|
9
|
+
walkDir(path.join(entry.parentPath, entry.name), state, handle)
|
|
10
|
+
} else if (entry.isFile()) {
|
|
11
|
+
handle(entry, state)
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
}
|