diff --git a/.npmrc b/.npmrc
new file mode 100644
index 0000000..84c929e
--- /dev/null
+++ b/.npmrc
@@ -0,0 +1,2 @@
+# Needed by pnpm to work with "@preact/preset-vite"
+shamefully-hoist=true
diff --git a/Makefile b/Makefile
index fc3daf3..2b308bc 100644
--- a/Makefile
+++ b/Makefile
@@ -6,5 +6,5 @@ test:
.PHONY: build
build:
go run -v ./cmd/build
- pnpm run build --emptyOutDir
+ pnpm run build
go build -o ./out/bin/server ./cmd/server
diff --git a/cmd/build/main.go b/cmd/build/main.go
index 7742d8f..60b45cd 100644
--- a/cmd/build/main.go
+++ b/cmd/build/main.go
@@ -29,11 +29,6 @@ func main() {
log.Fatal(err)
}
- dev, err := sl.Use(l, dev.Slot)
- if err != nil {
- log.Fatal(err)
- }
-
f, err := os.Create("out/routes.json")
if err != nil {
log.Fatal(err)
@@ -41,7 +36,7 @@ func main() {
enc := json.NewEncoder(f)
enc.SetIndent("", " ")
- if err := enc.Encode(dev.HtmlRouteBindings); err != nil {
+ if err := enc.Encode(dev.UseRoutesMetadata(l)); err != nil {
log.Fatal(err)
}
diff --git a/frontend/pages/articles/article.html b/frontend/pages/articles/article.html
new file mode 100644
index 0000000..6280483
--- /dev/null
+++ b/frontend/pages/articles/article.html
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+ {{ .Title }} • Articoli • PHC
+
+
+
+
+
+ Articolo "{{ .Example }}"
+ Lorem ipsum dolor sit amet, consectetur adipisicing elit. Neque, quasi...
+
+
diff --git a/frontend/pages/articles/index.html b/frontend/pages/articles/index.html
new file mode 100644
index 0000000..d533431
--- /dev/null
+++ b/frontend/pages/articles/index.html
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+ Articoli • PHC
+
+
+
+
+ Articoli
+ {{ .Example }}
+
+
diff --git a/frontend/pages/articles/typography.scss b/frontend/pages/articles/typography.scss
new file mode 100644
index 0000000..d224431
--- /dev/null
+++ b/frontend/pages/articles/typography.scss
@@ -0,0 +1,3 @@
+h1 {
+ color: red;
+}
diff --git a/frontend/styles/main.scss b/frontend/styles/main.scss
new file mode 100644
index 0000000..4cd982c
--- /dev/null
+++ b/frontend/styles/main.scss
@@ -0,0 +1,5 @@
+*,
+*::before,
+*::after {
+ box-sizing: border-box;
+}
diff --git a/meta/routes.js b/meta/routes.js
new file mode 100644
index 0000000..89f69fe
--- /dev/null
+++ b/meta/routes.js
@@ -0,0 +1,18 @@
+import fetch from 'node-fetch'
+import { readFile } from 'fs/promises'
+
+export async function getBuildRoutesMetadata(file) {
+ console.log('Loading routes from disk...')
+
+ const routesRaw = await readFile(file, 'utf8')
+ return JSON.parse(routesRaw)
+}
+
+export async function getDevRoutesMetadata(url) {
+ console.log('Loading routes from go server...')
+
+ const routesReq = await fetch(url)
+ const routes = await routesReq.json()
+
+ return routes
+}
diff --git a/package.json b/package.json
index a92aade..872ff4a 100644
--- a/package.json
+++ b/package.json
@@ -3,12 +3,14 @@
"version": "1.0.0",
"type": "module",
"scripts": {
- "dev": "vite --clearScreen false",
- "build": "vite build"
+ "dev": "node server.js",
+ "build": "vite build --emptyOutDir"
},
"devDependencies": {
"@preact/preset-vite": "^2.5.0",
"axios": "^1.2.6",
+ "express": "^4.18.2",
+ "morgan": "^1.10.0",
"node-fetch": "^3.3.0",
"sass": "^1.57.1",
"vite": "^4.0.4"
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 5bd7478..3116d62 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -3,6 +3,8 @@ lockfileVersion: 5.4
specifiers:
'@preact/preset-vite': ^2.5.0
axios: ^1.2.6
+ express: ^4.18.2
+ morgan: ^1.10.0
node-fetch: ^3.3.0
preact: ^10.11.3
sass: ^1.57.1
@@ -14,6 +16,8 @@ dependencies:
devDependencies:
'@preact/preset-vite': 2.5.0_preact@10.11.3+vite@4.0.4
axios: 1.2.6
+ express: 4.18.2
+ morgan: 1.10.0
node-fetch: 3.3.0
sass: 1.57.1
vite: 4.0.4_sass@1.57.1
@@ -562,6 +566,14 @@ packages:
picomatch: 2.3.1
dev: true
+ /accepts/1.3.8:
+ resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==}
+ engines: {node: '>= 0.6'}
+ dependencies:
+ mime-types: 2.1.35
+ negotiator: 0.6.3
+ dev: true
+
/ansi-styles/3.2.1:
resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==}
engines: {node: '>=4'}
@@ -577,6 +589,10 @@ packages:
picomatch: 2.3.1
dev: true
+ /array-flatten/1.1.1:
+ resolution: {integrity: sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==}
+ dev: true
+
/asynckit/0.4.0:
resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==}
dev: true
@@ -597,11 +613,38 @@ packages:
'@babel/core': ^7.12.10
dev: true
+ /basic-auth/2.0.1:
+ resolution: {integrity: sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==}
+ engines: {node: '>= 0.8'}
+ dependencies:
+ safe-buffer: 5.1.2
+ dev: true
+
/binary-extensions/2.2.0:
resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==}
engines: {node: '>=8'}
dev: true
+ /body-parser/1.20.1:
+ resolution: {integrity: sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==}
+ engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16}
+ dependencies:
+ bytes: 3.1.2
+ content-type: 1.0.5
+ debug: 2.6.9
+ depd: 2.0.0
+ destroy: 1.2.0
+ http-errors: 2.0.0
+ iconv-lite: 0.4.24
+ on-finished: 2.4.1
+ qs: 6.11.0
+ raw-body: 2.5.1
+ type-is: 1.6.18
+ unpipe: 1.0.0
+ transitivePeerDependencies:
+ - supports-color
+ dev: true
+
/braces/3.0.2:
resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==}
engines: {node: '>=8'}
@@ -620,6 +663,18 @@ packages:
update-browserslist-db: 1.0.10_browserslist@4.21.5
dev: true
+ /bytes/3.1.2:
+ resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==}
+ engines: {node: '>= 0.8'}
+ dev: true
+
+ /call-bind/1.0.2:
+ resolution: {integrity: sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==}
+ dependencies:
+ function-bind: 1.1.1
+ get-intrinsic: 1.2.0
+ dev: true
+
/caniuse-lite/1.0.30001449:
resolution: {integrity: sha512-CPB+UL9XMT/Av+pJxCKGhdx+yg1hzplvFJQlJ2n68PyQGMz9L/E2zCyLdOL8uasbouTUgnPl+y0tccI/se+BEw==}
dev: true
@@ -665,15 +720,47 @@ packages:
delayed-stream: 1.0.0
dev: true
+ /content-disposition/0.5.4:
+ resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==}
+ engines: {node: '>= 0.6'}
+ dependencies:
+ safe-buffer: 5.2.1
+ dev: true
+
+ /content-type/1.0.5:
+ resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==}
+ engines: {node: '>= 0.6'}
+ dev: true
+
/convert-source-map/1.9.0:
resolution: {integrity: sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==}
dev: true
+ /cookie-signature/1.0.6:
+ resolution: {integrity: sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==}
+ dev: true
+
+ /cookie/0.5.0:
+ resolution: {integrity: sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==}
+ engines: {node: '>= 0.6'}
+ dev: true
+
/data-uri-to-buffer/4.0.1:
resolution: {integrity: sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==}
engines: {node: '>= 12'}
dev: true
+ /debug/2.6.9:
+ resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==}
+ peerDependencies:
+ supports-color: '*'
+ peerDependenciesMeta:
+ supports-color:
+ optional: true
+ dependencies:
+ ms: 2.0.0
+ dev: true
+
/debug/4.3.4:
resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==}
engines: {node: '>=6.0'}
@@ -691,10 +778,29 @@ packages:
engines: {node: '>=0.4.0'}
dev: true
+ /depd/2.0.0:
+ resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==}
+ engines: {node: '>= 0.8'}
+ dev: true
+
+ /destroy/1.2.0:
+ resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==}
+ engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16}
+ dev: true
+
+ /ee-first/1.1.1:
+ resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==}
+ dev: true
+
/electron-to-chromium/1.4.284:
resolution: {integrity: sha512-M8WEXFuKXMYMVr45fo8mq0wUrrJHheiKZf6BArTKk9ZBYCKJEOU5H8cdWgDT+qCVZf7Na4lVUaZsA+h6uA9+PA==}
dev: true
+ /encodeurl/1.0.2:
+ resolution: {integrity: sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==}
+ engines: {node: '>= 0.8'}
+ dev: true
+
/esbuild/0.16.17:
resolution: {integrity: sha512-G8LEkV0XzDMNwXKgM0Jwu3nY3lSTwSGY6XbxM9cr9+s0T/qSV1q1JVPBGzm3dcjhCic9+emZDmMffkwgPeOeLg==}
engines: {node: '>=12'}
@@ -730,6 +836,10 @@ packages:
engines: {node: '>=6'}
dev: true
+ /escape-html/1.0.3:
+ resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==}
+ dev: true
+
/escape-string-regexp/1.0.5:
resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==}
engines: {node: '>=0.8.0'}
@@ -739,6 +849,50 @@ packages:
resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==}
dev: true
+ /etag/1.8.1:
+ resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==}
+ engines: {node: '>= 0.6'}
+ dev: true
+
+ /express/4.18.2:
+ resolution: {integrity: sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==}
+ engines: {node: '>= 0.10.0'}
+ dependencies:
+ accepts: 1.3.8
+ array-flatten: 1.1.1
+ body-parser: 1.20.1
+ content-disposition: 0.5.4
+ content-type: 1.0.5
+ cookie: 0.5.0
+ cookie-signature: 1.0.6
+ debug: 2.6.9
+ depd: 2.0.0
+ encodeurl: 1.0.2
+ escape-html: 1.0.3
+ etag: 1.8.1
+ finalhandler: 1.2.0
+ fresh: 0.5.2
+ http-errors: 2.0.0
+ merge-descriptors: 1.0.1
+ methods: 1.1.2
+ on-finished: 2.4.1
+ parseurl: 1.3.3
+ path-to-regexp: 0.1.7
+ proxy-addr: 2.0.7
+ qs: 6.11.0
+ range-parser: 1.2.1
+ safe-buffer: 5.2.1
+ send: 0.18.0
+ serve-static: 1.15.0
+ setprototypeof: 1.2.0
+ statuses: 2.0.1
+ type-is: 1.6.18
+ utils-merge: 1.0.1
+ vary: 1.1.2
+ transitivePeerDependencies:
+ - supports-color
+ dev: true
+
/fetch-blob/3.2.0:
resolution: {integrity: sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==}
engines: {node: ^12.20 || >= 14.13}
@@ -754,6 +908,21 @@ packages:
to-regex-range: 5.0.1
dev: true
+ /finalhandler/1.2.0:
+ resolution: {integrity: sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==}
+ engines: {node: '>= 0.8'}
+ dependencies:
+ debug: 2.6.9
+ encodeurl: 1.0.2
+ escape-html: 1.0.3
+ on-finished: 2.4.1
+ parseurl: 1.3.3
+ statuses: 2.0.1
+ unpipe: 1.0.0
+ transitivePeerDependencies:
+ - supports-color
+ dev: true
+
/follow-redirects/1.15.2:
resolution: {integrity: sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==}
engines: {node: '>=4.0'}
@@ -780,6 +949,16 @@ packages:
fetch-blob: 3.2.0
dev: true
+ /forwarded/0.2.0:
+ resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==}
+ engines: {node: '>= 0.6'}
+ dev: true
+
+ /fresh/0.5.2:
+ resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==}
+ engines: {node: '>= 0.6'}
+ dev: true
+
/fsevents/2.3.2:
resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==}
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
@@ -797,6 +976,14 @@ packages:
engines: {node: '>=6.9.0'}
dev: true
+ /get-intrinsic/1.2.0:
+ resolution: {integrity: sha512-L049y6nFOuom5wGyRc3/gdTLO94dySVKRACj1RmJZBQXlbTMhtNIgkWkUHq+jYmZvKf14EW1EoJnnjbmoHij0Q==}
+ dependencies:
+ function-bind: 1.1.1
+ has: 1.0.3
+ has-symbols: 1.0.3
+ dev: true
+
/glob-parent/5.1.2:
resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==}
engines: {node: '>= 6'}
@@ -814,6 +1001,11 @@ packages:
engines: {node: '>=4'}
dev: true
+ /has-symbols/1.0.3:
+ resolution: {integrity: sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==}
+ engines: {node: '>= 0.4'}
+ dev: true
+
/has/1.0.3:
resolution: {integrity: sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==}
engines: {node: '>= 0.4.0'}
@@ -821,10 +1013,37 @@ packages:
function-bind: 1.1.1
dev: true
+ /http-errors/2.0.0:
+ resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==}
+ engines: {node: '>= 0.8'}
+ dependencies:
+ depd: 2.0.0
+ inherits: 2.0.4
+ setprototypeof: 1.2.0
+ statuses: 2.0.1
+ toidentifier: 1.0.1
+ dev: true
+
+ /iconv-lite/0.4.24:
+ resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==}
+ engines: {node: '>=0.10.0'}
+ dependencies:
+ safer-buffer: 2.1.2
+ dev: true
+
/immutable/4.2.2:
resolution: {integrity: sha512-fTMKDwtbvO5tldky9QZ2fMX7slR0mYpY5nbnFWYp0fOzDhHqhgIw9KoYgxLWsoNTS9ZHGauHj18DTyEw6BK3Og==}
dev: true
+ /inherits/2.0.4:
+ resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==}
+ dev: true
+
+ /ipaddr.js/1.9.1:
+ resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==}
+ engines: {node: '>= 0.10'}
+ dev: true
+
/is-binary-path/2.1.0:
resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==}
engines: {node: '>=8'}
@@ -881,6 +1100,20 @@ packages:
yallist: 3.1.1
dev: true
+ /media-typer/0.3.0:
+ resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==}
+ engines: {node: '>= 0.6'}
+ dev: true
+
+ /merge-descriptors/1.0.1:
+ resolution: {integrity: sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==}
+ dev: true
+
+ /methods/1.1.2:
+ resolution: {integrity: sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==}
+ engines: {node: '>= 0.6'}
+ dev: true
+
/mime-db/1.52.0:
resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==}
engines: {node: '>= 0.6'}
@@ -893,16 +1126,48 @@ packages:
mime-db: 1.52.0
dev: true
+ /mime/1.6.0:
+ resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==}
+ engines: {node: '>=4'}
+ hasBin: true
+ dev: true
+
+ /morgan/1.10.0:
+ resolution: {integrity: sha512-AbegBVI4sh6El+1gNwvD5YIck7nSA36weD7xvIxG4in80j/UoK8AEGaWnnz8v1GxonMCltmlNs5ZKbGvl9b1XQ==}
+ engines: {node: '>= 0.8.0'}
+ dependencies:
+ basic-auth: 2.0.1
+ debug: 2.6.9
+ depd: 2.0.0
+ on-finished: 2.3.0
+ on-headers: 1.0.2
+ transitivePeerDependencies:
+ - supports-color
+ dev: true
+
+ /ms/2.0.0:
+ resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==}
+ dev: true
+
/ms/2.1.2:
resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==}
dev: true
+ /ms/2.1.3:
+ resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
+ dev: true
+
/nanoid/3.3.4:
resolution: {integrity: sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==}
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
hasBin: true
dev: true
+ /negotiator/0.6.3:
+ resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==}
+ engines: {node: '>= 0.6'}
+ dev: true
+
/node-domexception/1.0.0:
resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==}
engines: {node: '>=10.5.0'}
@@ -926,10 +1191,42 @@ packages:
engines: {node: '>=0.10.0'}
dev: true
+ /object-inspect/1.12.3:
+ resolution: {integrity: sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==}
+ dev: true
+
+ /on-finished/2.3.0:
+ resolution: {integrity: sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==}
+ engines: {node: '>= 0.8'}
+ dependencies:
+ ee-first: 1.1.1
+ dev: true
+
+ /on-finished/2.4.1:
+ resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==}
+ engines: {node: '>= 0.8'}
+ dependencies:
+ ee-first: 1.1.1
+ dev: true
+
+ /on-headers/1.0.2:
+ resolution: {integrity: sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==}
+ engines: {node: '>= 0.8'}
+ dev: true
+
+ /parseurl/1.3.3:
+ resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==}
+ engines: {node: '>= 0.8'}
+ dev: true
+
/path-parse/1.0.7:
resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==}
dev: true
+ /path-to-regexp/0.1.7:
+ resolution: {integrity: sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==}
+ dev: true
+
/picocolors/1.0.0:
resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==}
dev: true
@@ -951,10 +1248,40 @@ packages:
/preact/10.11.3:
resolution: {integrity: sha512-eY93IVpod/zG3uMF22Unl8h9KkrcKIRs2EGar8hwLZZDU1lkjph303V9HZBwufh2s736U6VXuhD109LYqPoffg==}
+ /proxy-addr/2.0.7:
+ resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==}
+ engines: {node: '>= 0.10'}
+ dependencies:
+ forwarded: 0.2.0
+ ipaddr.js: 1.9.1
+ dev: true
+
/proxy-from-env/1.1.0:
resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==}
dev: true
+ /qs/6.11.0:
+ resolution: {integrity: sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==}
+ engines: {node: '>=0.6'}
+ dependencies:
+ side-channel: 1.0.4
+ dev: true
+
+ /range-parser/1.2.1:
+ resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==}
+ engines: {node: '>= 0.6'}
+ dev: true
+
+ /raw-body/2.5.1:
+ resolution: {integrity: sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==}
+ engines: {node: '>= 0.8'}
+ dependencies:
+ bytes: 3.1.2
+ http-errors: 2.0.0
+ iconv-lite: 0.4.24
+ unpipe: 1.0.0
+ dev: true
+
/readdirp/3.6.0:
resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==}
engines: {node: '>=8.10.0'}
@@ -979,6 +1306,18 @@ packages:
fsevents: 2.3.2
dev: true
+ /safe-buffer/5.1.2:
+ resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==}
+ dev: true
+
+ /safe-buffer/5.2.1:
+ resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==}
+ dev: true
+
+ /safer-buffer/2.1.2:
+ resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==}
+ dev: true
+
/sass/1.57.1:
resolution: {integrity: sha512-O2+LwLS79op7GI0xZ8fqzF7X2m/m8WFfI02dHOdsK5R2ECeS5F62zrwg/relM1rjSLy7Vd/DiMNIvPrQGsA0jw==}
engines: {node: '>=12.0.0'}
@@ -994,11 +1333,61 @@ packages:
hasBin: true
dev: true
+ /send/0.18.0:
+ resolution: {integrity: sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==}
+ engines: {node: '>= 0.8.0'}
+ dependencies:
+ debug: 2.6.9
+ depd: 2.0.0
+ destroy: 1.2.0
+ encodeurl: 1.0.2
+ escape-html: 1.0.3
+ etag: 1.8.1
+ fresh: 0.5.2
+ http-errors: 2.0.0
+ mime: 1.6.0
+ ms: 2.1.3
+ on-finished: 2.4.1
+ range-parser: 1.2.1
+ statuses: 2.0.1
+ transitivePeerDependencies:
+ - supports-color
+ dev: true
+
+ /serve-static/1.15.0:
+ resolution: {integrity: sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==}
+ engines: {node: '>= 0.8.0'}
+ dependencies:
+ encodeurl: 1.0.2
+ escape-html: 1.0.3
+ parseurl: 1.3.3
+ send: 0.18.0
+ transitivePeerDependencies:
+ - supports-color
+ dev: true
+
+ /setprototypeof/1.2.0:
+ resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==}
+ dev: true
+
+ /side-channel/1.0.4:
+ resolution: {integrity: sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==}
+ dependencies:
+ call-bind: 1.0.2
+ get-intrinsic: 1.2.0
+ object-inspect: 1.12.3
+ dev: true
+
/source-map-js/1.0.2:
resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==}
engines: {node: '>=0.10.0'}
dev: true
+ /statuses/2.0.1:
+ resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==}
+ engines: {node: '>= 0.8'}
+ dev: true
+
/supports-color/5.5.0:
resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==}
engines: {node: '>=4'}
@@ -1023,6 +1412,24 @@ packages:
is-number: 7.0.0
dev: true
+ /toidentifier/1.0.1:
+ resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==}
+ engines: {node: '>=0.6'}
+ dev: true
+
+ /type-is/1.6.18:
+ resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==}
+ engines: {node: '>= 0.6'}
+ dependencies:
+ media-typer: 0.3.0
+ mime-types: 2.1.35
+ dev: true
+
+ /unpipe/1.0.0:
+ resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==}
+ engines: {node: '>= 0.8'}
+ dev: true
+
/update-browserslist-db/1.0.10_browserslist@4.21.5:
resolution: {integrity: sha512-OztqDenkfFkbSG+tRxBeAnCVPckDBcvibKd35yDONx6OU8N7sqgwc7rCbkJ/WcYtVRZ4ba68d6byhC21GFh7sQ==}
hasBin: true
@@ -1034,6 +1441,16 @@ packages:
picocolors: 1.0.0
dev: true
+ /utils-merge/1.0.1:
+ resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==}
+ engines: {node: '>= 0.4.0'}
+ dev: true
+
+ /vary/1.1.2:
+ resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==}
+ engines: {node: '>= 0.8'}
+ dev: true
+
/vite/4.0.4_sass@1.57.1:
resolution: {integrity: sha512-xevPU7M8FU0i/80DMR+YhgrzR5KS2ORy1B4xcX/cXLsvnUWvfHuqMmVU6N0YiJ4JWGRJJsLCgjEzKjG9/GKoSw==}
engines: {node: ^14.18.0 || >=16.0.0}
diff --git a/server.js b/server.js
new file mode 100644
index 0000000..7cb2b93
--- /dev/null
+++ b/server.js
@@ -0,0 +1,76 @@
+import express from 'express'
+import { createServer as createViteServer } from 'vite'
+import { getDevRoutesMetadata } from './meta/routes.js'
+
+import fetch from 'node-fetch'
+
+import { readFile } from 'fs/promises'
+import { dirname, resolve } from 'path'
+
+import morgan from 'morgan'
+
+import { fileURLToPath } from 'url'
+const __dirname = fileURLToPath(new URL('.', import.meta.url))
+
+async function main() {
+ const routes = await getDevRoutesMetadata('http://127.0.0.1:4000/api/development/routes')
+ console.dir(routes)
+
+ const app = express()
+
+ app.use(morgan(':method :url :status :response-time ms - :res[content-length]'))
+
+ const vite = await createViteServer({
+ server: { middlewareMode: true },
+ appType: 'custom',
+ })
+
+ app.use(vite.middlewares)
+
+ Object.entries(routes.static).forEach(([route, file]) => {
+ app.get(route, async (_req, res) => {
+ let htmlPage = await readFile(resolve(__dirname, './frontend/', file), 'utf8')
+ htmlPage = htmlPage.replace(/\.\//g, '/' + dirname(file) + '/')
+
+ const html = await vite.transformIndexHtml(file, htmlPage)
+ console.dir(file)
+
+ res.writeHead(200, { 'Content-Type': 'text/html' }).end(html)
+ })
+ })
+
+ Object.entries(routes.dynamic).forEach(([route, file]) => {
+ app.get(route, async (req, res) => {
+ let htmlPage = await readFile(resolve(__dirname, './frontend/', file), 'utf8')
+ htmlPage = htmlPage.replace(/\.\//g, '/' + dirname(file) + '/')
+
+ const html = await vite.transformIndexHtml(file, htmlPage)
+
+ console.log('req.url = ', req.url)
+ console.log('req.originalUrl = ', req.originalUrl)
+
+ const templateHtmlReq = await fetch('http://127.0.0.1:4000/api/development/render', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({
+ route,
+ page: html,
+ request: {
+ params: req.params,
+ query: req.query,
+ },
+ }),
+ })
+
+ const renderedHtml = await templateHtmlReq.json()
+
+ res.writeHead(200, { 'Content-Type': 'text/html' }).end(renderedHtml)
+ })
+ })
+
+ app.listen(3000, () => {
+ console.log(`Listening on port 3000...`)
+ })
+}
+
+main()
diff --git a/services/server/articles/articles.go b/services/server/articles/articles.go
new file mode 100644
index 0000000..5705453
--- /dev/null
+++ b/services/server/articles/articles.go
@@ -0,0 +1,57 @@
+package articles
+
+import (
+ "html/template"
+ "phc/website/services/server/dev"
+ "phc/website/services/server/router"
+ "phc/website/sl"
+)
+
+func Configure(l *sl.ServiceLocator) error {
+ router.UseRouteTemplatedPage(l, "/articles",
+ "pages/articles/index.html",
+ func(w dev.ResponseWriter, r dev.Request) error {
+ tmpl := template.New("")
+
+ tmpl, err := tmpl.Parse(string(r.Page()))
+ if err != nil {
+ return err
+ }
+
+ ctx := map[string]any{
+ "Example": "Bla bla",
+ }
+
+ if err := tmpl.Execute(w, ctx); err != nil {
+ return err
+ }
+
+ return nil
+ },
+ )
+
+ router.UseRouteTemplatedPage(l, "/articles/:slug",
+ "pages/articles/article.html",
+ func(w dev.ResponseWriter, r dev.Request) error {
+ tmpl := template.New("")
+
+ tmpl, err := tmpl.Parse(string(r.Page()))
+ if err != nil {
+ return err
+ }
+
+ ctx := map[string]any{
+ "Title": r.Param("slug"),
+ "Example": "Bla bla " + r.Param("slug"),
+ }
+
+ if err := tmpl.Execute(w, ctx); err != nil {
+ return err
+ }
+
+ return nil
+ },
+ )
+
+ return nil
+}
diff --git a/services/server/dev/dev.go b/services/server/dev/dev.go
index fe7d3b7..9a67362 100644
--- a/services/server/dev/dev.go
+++ b/services/server/dev/dev.go
@@ -1,49 +1,157 @@
package dev
import (
+ "bytes"
+ "fmt"
+ "io"
"log"
"path"
"phc/website/services/server/routes"
"phc/website/sl"
+ "github.com/alecthomas/repr"
"github.com/gofiber/fiber/v2"
)
-var Slot = sl.NewSlot[*Dev]()
+// slot represents a private "write only" service
+var slot = sl.NewSlot[*devService]()
-type Dev struct {
- HtmlRouteBindings map[string]string
+// InjectInto a [*sl.ServiceLocator] an instance of the dev service
+func InjectInto(l *sl.ServiceLocator) {
+ sl.InjectLazy(l, slot, Configure)
}
-func New(l *sl.ServiceLocator) (*Dev, error) {
- dev := &Dev{
+func UseRoutesMetadata(l *sl.ServiceLocator) map[string]any {
+ dev, err := sl.Use(l, slot)
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ return map[string]any{
+ "static": dev.staticRoutes,
+ "dynamic": dev.dynamicRoutes,
+ }
+}
+
+type Request interface {
+ Page() []byte
+ Param(key string) string
+ Query(key string) string
+}
+
+type ResponseWriter interface {
+ io.Writer
+}
+
+// devServerRequest is used when handling request from the dev server where params and queries are parsed by express
+type devServerRequest struct {
+ page []byte
+ params map[string]string
+ query map[string]string
+}
+
+func (r devServerRequest) Page() []byte {
+ return r.page
+}
+
+func (r devServerRequest) Param(key string) string {
+ return r.params[key]
+}
+
+func (r devServerRequest) Query(key string) string {
+ return r.query[key]
+}
+
+// Handler is a custom routes handler
+type Handler func(ResponseWriter, Request) error
+
+type devService struct {
+ staticRoutes map[string]string
+ dynamicRoutes map[string]string
+
+ dynamicRoutesHandlers map[string]Handler
+}
+
+func Configure(l *sl.ServiceLocator) (*devService, error) {
+ d := &devService{
+ map[string]string{},
map[string]string{},
+ map[string]Handler{},
}
- router, err := sl.Use(l, routes.Api)
+ api, err := sl.Use(l, routes.Api)
if err != nil {
return nil, err
}
- router.Get("/dev/routes", func(c *fiber.Ctx) error {
- return c.JSON(dev.HtmlRouteBindings)
+ api.Get("/development/routes", func(c *fiber.Ctx) error {
+ return c.JSON(map[string]any{
+ "static": d.staticRoutes,
+ "dynamic": d.dynamicRoutes,
+ })
})
- return dev, nil
+ api.Post("/development/render", func(c *fiber.Ctx) error {
+ var data struct {
+ Route string `json:"route"`
+ Page string `json:"page"`
+ Request struct {
+ ParamsMap map[string]string `json:"params"`
+ QueryMap map[string]string `json:"query"`
+ } `json:"request"`
+ }
+
+ if err := c.BodyParser(&data); err != nil {
+ return err
+ }
+
+ repr.Print(data)
+
+ handler, ok := d.dynamicRoutesHandlers[data.Route]
+ if !ok {
+ return fmt.Errorf(`no handler for "%s"`, data.Route)
+ }
+
+ var buf bytes.Buffer
+ if err := handler(&buf, devServerRequest{
+ []byte(data.Page),
+ data.Request.ParamsMap,
+ data.Request.QueryMap,
+ }); err != nil {
+ return err
+ }
+
+ return c.JSON(buf.String())
+ })
+
+ return d, nil
}
-// UseVitePage this hook will link the provided "mountPoint" to the "frontendHtml" page
-func UseVitePage(l *sl.ServiceLocator, mountPoint, frontendHtml string) func(c *fiber.Ctx) error {
- log.Printf(`registering vite route %q for %q`, frontendHtml, mountPoint)
+// RegisterRoute will register the provided "mountPoint" to the "frontendHtml" page
+func RegisterRoute(l *sl.ServiceLocator, mountPoint, frontendFile string) {
+ log.Printf(`registering vite route %q for %q`, frontendFile, mountPoint)
- dev, err := sl.Use(l, Slot)
+ dev, err := sl.Use(l, slot)
if err != nil {
log.Fatal(err)
}
- dev.HtmlRouteBindings[mountPoint] = frontendHtml
+ dev.staticRoutes[mountPoint] = frontendFile
+ log.Print(dev)
+}
- return func(c *fiber.Ctx) error {
- return c.SendFile(path.Join("./out/frontend/", frontendHtml))
+func RegisterDynamicRoute(l *sl.ServiceLocator, mountPoint, frontendFile string, handler Handler) {
+ log.Printf(`registering vite route %q for %q`, frontendFile, mountPoint)
+
+ dev, err := sl.Use(l, slot)
+ if err != nil {
+ log.Fatal(err)
}
+
+ dev.dynamicRoutes[mountPoint] = frontendFile
+ dev.dynamicRoutesHandlers[mountPoint] = handler
+}
+
+func GetArtifactPath(frontendFile string) string {
+ return path.Join("./out/frontend/", frontendFile)
}
diff --git a/services/server/lista-utenti/lista-utenti.go b/services/server/listaUtenti/lista-utenti.go
similarity index 62%
rename from services/server/lista-utenti/lista-utenti.go
rename to services/server/listaUtenti/lista-utenti.go
index aa0caa4..28d1b82 100644
--- a/services/server/lista-utenti/lista-utenti.go
+++ b/services/server/listaUtenti/lista-utenti.go
@@ -1,8 +1,8 @@
-package lista_utenti
+package listaUtenti
import (
"phc/website/services/database"
- "phc/website/services/server/dev"
+ "phc/website/services/server/router"
"phc/website/services/server/routes"
"phc/website/sl"
@@ -10,21 +10,19 @@ import (
)
func Configure(l *sl.ServiceLocator) error {
+ router.UseRoutePage(l, "/utenti", "pages/lista-utenti/index.html")
+
db, err := sl.Use(l, database.Slot)
if err != nil {
return err
}
- r, err := sl.Use(l, routes.Root)
+ api, err := sl.Use(l, routes.Api)
if err != nil {
return err
}
- r.Get("/utenti",
- dev.UseVitePage(l, "/utenti", "pages/lista-utenti/index.html"),
- )
-
- r.Get("/api/lista-utenti", func(c *fiber.Ctx) error {
+ api.Get("/lista-utenti", func(c *fiber.Ctx) error {
users, err := db.ReadUsers()
if err != nil {
return err
diff --git a/services/server/lista-utenti/lista-utenti_test.go b/services/server/listaUtenti/lista-utenti_test.go
similarity index 98%
rename from services/server/lista-utenti/lista-utenti_test.go
rename to services/server/listaUtenti/lista-utenti_test.go
index 022e8d1..de48e01 100644
--- a/services/server/lista-utenti/lista-utenti_test.go
+++ b/services/server/listaUtenti/lista-utenti_test.go
@@ -1,4 +1,4 @@
-package lista_utenti_test
+package listaUtenti_test
import (
"context"
diff --git a/services/server/router/router.go b/services/server/router/router.go
new file mode 100644
index 0000000..bcde8d3
--- /dev/null
+++ b/services/server/router/router.go
@@ -0,0 +1,72 @@
+package router
+
+import (
+ "bytes"
+ "log"
+ "os"
+ "phc/website/services/server/dev"
+ "phc/website/services/server/routes"
+ "phc/website/sl"
+
+ "github.com/gofiber/fiber/v2"
+)
+
+// assert type of [ServerRequest] is [dev.Request]
+var _ dev.Request = ServerRequest{}
+
+// ServerRequest is used when the request is directly for the Go server
+type ServerRequest struct {
+ page []byte
+ fiberContext *fiber.Ctx
+}
+
+func (r ServerRequest) Page() []byte {
+ return r.page
+}
+
+func (ctx ServerRequest) Param(key string) string {
+ return ctx.fiberContext.Params(key)
+}
+
+func (ctx ServerRequest) Query(key string) string {
+ return ctx.fiberContext.Query(key)
+}
+
+func UseRoutePage(l *sl.ServiceLocator, route, frontendFile string) {
+ root, err := sl.Use(l, routes.Root)
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ dev.RegisterRoute(l, route, frontendFile)
+
+ root.Get(route, func(c *fiber.Ctx) error {
+ return c.SendFile(dev.GetArtifactPath(frontendFile))
+ })
+}
+
+func UseRouteTemplatedPage(l *sl.ServiceLocator, route, frontendFile string, handler dev.Handler) {
+ r, err := sl.Use(l, routes.Root)
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ dev.RegisterDynamicRoute(l, route, frontendFile, handler)
+
+ r.Get(route, func(c *fiber.Ctx) error {
+ rawPage, err := os.ReadFile(dev.GetArtifactPath(frontendFile))
+ if err != nil {
+ return err
+ }
+
+ var buf bytes.Buffer
+ if err := handler(&buf, ServerRequest{
+ rawPage,
+ c,
+ }); err != nil {
+ return err
+ }
+
+ return c.Type(".html").Send(buf.Bytes())
+ })
+}
diff --git a/services/server/routes/routes.go b/services/server/routes/routes.go
index 1eb795c..742db29 100644
--- a/services/server/routes/routes.go
+++ b/services/server/routes/routes.go
@@ -7,5 +7,4 @@ import (
)
var Root = sl.NewSlot[fiber.Router]()
-
var Api = sl.NewSlot[fiber.Router]()
diff --git a/services/server/server.go b/services/server/server.go
index 8557e78..c130ace 100644
--- a/services/server/server.go
+++ b/services/server/server.go
@@ -1,8 +1,9 @@
package server
import (
+ "phc/website/services/server/articles"
"phc/website/services/server/dev"
- lista_utenti "phc/website/services/server/lista-utenti"
+ "phc/website/services/server/listaUtenti"
"phc/website/services/server/routes"
"phc/website/sl"
@@ -13,17 +14,17 @@ type Server struct{ Router *fiber.App }
func Configure(l *sl.ServiceLocator) (*Server, error) {
r := fiber.New(fiber.Config{})
+ r.Static("/assets", "./out/frontend/assets")
- sl.InjectValue(l, routes.Root, r.Group("/"))
+ dev.InjectInto(l)
+
+ sl.InjectValue(l, routes.Root, fiber.Router(r))
sl.InjectValue(l, routes.Api, r.Group("/api"))
- devServerInterop, err := dev.New(l)
- if err != nil {
+ if err := listaUtenti.Configure(l); err != nil {
return nil, err
}
- sl.InjectValue(l, dev.Slot, devServerInterop)
-
- if err := lista_utenti.Configure(l); err != nil {
+ if err := articles.Configure(l); err != nil {
return nil, err
}
diff --git a/sl/sl.go b/sl/sl.go
index 13f21c0..ec7ef98 100644
--- a/sl/sl.go
+++ b/sl/sl.go
@@ -28,6 +28,7 @@ func (s *slot) checkInitialized(l *ServiceLocator) error {
log.Printf(`initialized lazy value of type %T for slot of type %s`, v, s.typ)
+ s.created = true
s.value = v
}
@@ -47,7 +48,12 @@ func New() *ServiceLocator {
func InjectValue[T any](l *ServiceLocator, slotKey SlotKey[T], value T) T {
log.Printf(`injected value of type %T for slot of type %s`, value, getTypeName[T]())
- l.providers[slotKey] = &slot{nil, true, value, getTypeName[T]()}
+ l.providers[slotKey] = &slot{
+ nil,
+ true,
+ value,
+ getTypeName[T](),
+ }
return value
}
diff --git a/vite.config.js b/vite.config.js
index c31fffb..58e66d8 100644
--- a/vite.config.js
+++ b/vite.config.js
@@ -1,68 +1,58 @@
import { defineConfig } from 'vite'
-import fetch from 'node-fetch'
-
-import { readFile } from 'fs/promises'
-import { dirname, resolve } from 'path'
+import { dirname, join, resolve } from 'path'
import preactPlugin from '@preact/preset-vite'
+import { getBuildRoutesMetadata } from './meta/routes.js'
-const retriveGoRoutes = {
- async build() {
- console.log('Loading routes from disk...')
-
- const routesRaw = await readFile('out/routes.json', 'utf8')
- return JSON.parse(routesRaw)
- },
- async serve() {
- console.log('Loading routes from go server...')
+import crypto from 'crypto'
- const routesReq = await fetch('http://127.0.0.1:4000/api/dev/routes')
- const routes = await routesReq.json()
+/** @type {import('vite').UserConfig} */
+const sharedConfig = {
+ root: './frontend',
+ plugins: [preactPlugin()],
+}
- return routes
- },
+function routesToRollupInput([route, file]) {
+ const chunkName =
+ file
+ .replaceAll('.html', '')
+ .replaceAll('index', '')
+ .replace(/^\/|\/$/, '')
+ .replaceAll('/', '-') +
+ '-' +
+ crypto.createHash('md5').update(route).update(file).digest('hex').slice(0, 8)
+
+ return [chunkName, join(__dirname, 'frontend', file)]
}
export default defineConfig(async config => {
- let routes = await retriveGoRoutes[config.command]()
- console.dir(routes)
-
- return {
- root: 'frontend',
- build: {
- outDir: '../out/frontend',
- rollupOptions: {
- input: {
- 'main': resolve(__dirname, 'frontend/pages/index.html'),
- 'lista-utenti': resolve(__dirname, 'frontend/pages/lista-utenti/index.html'),
+ if (config.command === 'build') {
+ const routes = await getBuildRoutesMetadata('out/routes.json')
+ const input = Object.fromEntries(
+ [...Object.entries(routes.static), ...Object.entries(routes.dynamic).map(([route, { htmlFile }]) => [route, htmlFile])].map(
+ routesToRollupInput
+ )
+ )
+
+ console.dir(input)
+
+ return {
+ ...sharedConfig,
+ build: {
+ outDir: '../out/frontend',
+ rollupOptions: {
+ input,
},
},
- },
- server: {
- port: 3000,
- proxy: {
- '/api': 'http://localhost:4000/',
- },
- },
- plugins: [
- preactPlugin(),
- {
- name: 'custom-router',
- configureServer(server) {
- Object.entries(routes).forEach(([route, file]) => {
- server.middlewares.use(route, async (req, res, next) => {
- let htmlPage = await readFile(resolve(__dirname, './frontend/', file), 'utf8')
- htmlPage = htmlPage.replace(/\.\//g, '/' + dirname(file) + '/')
-
- const url = file
- const html = await server.transformIndexHtml(url, htmlPage)
-
- console.log(url)
-
- res.writeHead(200, { 'Content-Type': 'text/html' }).end(html)
- })
- })
+ }
+ } else {
+ return {
+ ...sharedConfig,
+ server: {
+ port: 3000,
+ proxy: {
+ '/api': 'http://localhost:4000/',
},
},
- ],
+ }
}
})