glashjs 0.13.4 → 0.14.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/package.json +3 -2
- package/src/create.mjs +73 -4
- package/src/i18n.mjs +185 -0
- package/src/server/jsx.mjs +11 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "glashjs",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.14.0",
|
|
4
4
|
"description": "glashjs — The Postgres-native full-stack framework for builders who want to ship without DevOps. Framework, hosting, database, auth, and deploy in one GlashDB-native runtime.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -21,7 +21,8 @@
|
|
|
21
21
|
"./image": "./src/components/image.mjs",
|
|
22
22
|
"./video": "./src/components/video.mjs",
|
|
23
23
|
"./link": "./src/components/link.mjs",
|
|
24
|
-
"./package.json": "./package.json"
|
|
24
|
+
"./package.json": "./package.json",
|
|
25
|
+
"./i18n": "./src/i18n.mjs"
|
|
25
26
|
},
|
|
26
27
|
"files": [
|
|
27
28
|
"bin",
|
package/src/create.mjs
CHANGED
|
@@ -42,6 +42,8 @@ async function collectAnswers(options, rl) {
|
|
|
42
42
|
const auth = boolAnswer(options.auth, await ask(rl, 'Add glashAuth routes?', 'yes'));
|
|
43
43
|
const sqlRunner = boolAnswer(options.sqlRunner, await ask(rl, 'Add SQL runner support?', 'yes'));
|
|
44
44
|
const aiPrompts = boolAnswer(options.aiPrompts, await ask(rl, 'Add AI deployment prompts?', 'yes'));
|
|
45
|
+
const translate = boolAnswer(options.translate, await ask(rl, 'Add built-in IP auto-translation?', 'yes'));
|
|
46
|
+
const animation = boolAnswer(options.animation, await ask(rl, 'Add motion + 3D (motion, three.js)?', 'no'));
|
|
45
47
|
const install = boolAnswer(options.install, await ask(rl, 'Install dependencies now?', 'yes'));
|
|
46
48
|
const packageManager = normalizeChoice(options.packageManager || await ask(rl, 'Package manager (npm/pnpm/yarn/bun)', detectPackageManager()), PM_CHOICES, 'npm');
|
|
47
49
|
const git = boolAnswer(options.git, await ask(rl, 'Initialize git?', 'yes'));
|
|
@@ -54,6 +56,8 @@ async function collectAnswers(options, rl) {
|
|
|
54
56
|
auth,
|
|
55
57
|
sqlRunner,
|
|
56
58
|
aiPrompts,
|
|
59
|
+
translate,
|
|
60
|
+
animation,
|
|
57
61
|
install,
|
|
58
62
|
packageManager,
|
|
59
63
|
git,
|
|
@@ -85,6 +89,8 @@ function projectFiles(answers) {
|
|
|
85
89
|
pg: '^8.16.3',
|
|
86
90
|
preact: '^10.29.2',
|
|
87
91
|
'preact-render-to-string': '^6.7.0',
|
|
92
|
+
// Motion (animation) + three.js (3D), bundled client-side via esbuild.
|
|
93
|
+
...(answers.animation ? { motion: '^11.15.0', three: '^0.171.0' } : {}),
|
|
88
94
|
};
|
|
89
95
|
const devDependencies = answers.css === 'tailwind'
|
|
90
96
|
? { tailwindcss: '^4.1.0', '@tailwindcss/cli': '^4.1.0' }
|
|
@@ -105,7 +111,7 @@ function projectFiles(answers) {
|
|
|
105
111
|
'.env.local': envLocal(answers),
|
|
106
112
|
'glash.config.mjs': configFile(answers, hasCss),
|
|
107
113
|
'README.md': projectReadme(answers),
|
|
108
|
-
'routes/_layout.jsx': layoutRoute(),
|
|
114
|
+
'routes/_layout.jsx': layoutRoute(answers),
|
|
109
115
|
'routes/index.jsx': indexRoute(answers),
|
|
110
116
|
'routes/api/health.mjs': healthRoute(),
|
|
111
117
|
'db/schema.sql': schemaSql(answers),
|
|
@@ -113,6 +119,8 @@ function projectFiles(answers) {
|
|
|
113
119
|
...(answers.css === 'tailwind' ? { 'styles/input.css': tailwindInput() } : {}),
|
|
114
120
|
...(answers.auth ? authRoutes() : {}),
|
|
115
121
|
...(answers.sqlRunner ? sqlRunnerRoutes() : {}),
|
|
122
|
+
...(answers.translate ? { 'routes/api/_glash/geo.mjs': geoRoute() } : {}),
|
|
123
|
+
...(answers.animation ? { 'routes/demo/motion.jsx': motionDemoRoute() } : {}),
|
|
116
124
|
...(answers.aiPrompts ? { '.glash/prompts/deploy.md': aiDeployPrompt(answers) } : {}),
|
|
117
125
|
'public/favicon.svg': faviconSvg(),
|
|
118
126
|
};
|
|
@@ -140,10 +148,19 @@ export default defineConfig({
|
|
|
140
148
|
`;
|
|
141
149
|
}
|
|
142
150
|
|
|
143
|
-
function layoutRoute() {
|
|
144
|
-
|
|
151
|
+
function layoutRoute(answers = {}) {
|
|
152
|
+
const t = answers.translate;
|
|
153
|
+
const imports = [`import { Link } from 'glashjs/link';`];
|
|
154
|
+
if (t) {
|
|
155
|
+
imports.push(`import { useEffect } from 'preact/hooks';`);
|
|
156
|
+
imports.push(`import { glashAutoTranslate } from 'glashjs/i18n';`);
|
|
157
|
+
}
|
|
158
|
+
return `${imports.join('\n')}
|
|
145
159
|
|
|
146
|
-
export default function RootLayout({ children }) {
|
|
160
|
+
export default function RootLayout({ children }) {${t ? `
|
|
161
|
+
// Built-in IP auto-translation: detect the visitor's country and offer to
|
|
162
|
+
// translate the page into their native language. Reads /api/_glash/geo.
|
|
163
|
+
useEffect(() => { glashAutoTranslate(); }, []);` : ''}
|
|
147
164
|
return (
|
|
148
165
|
<div className="shell">
|
|
149
166
|
<header className="nav">
|
|
@@ -160,6 +177,58 @@ export default function RootLayout({ children }) {
|
|
|
160
177
|
`;
|
|
161
178
|
}
|
|
162
179
|
|
|
180
|
+
function geoRoute() {
|
|
181
|
+
return `// IP -> country -> native language, read from the edge geo header
|
|
182
|
+
// (CF-IPCountry / X-Glash-Country on glashDB hosting). The client i18n runtime
|
|
183
|
+
// calls this to decide whether to offer auto-translation.
|
|
184
|
+
import { geoRouteHandler } from 'glashjs/i18n';
|
|
185
|
+
|
|
186
|
+
export const GET = geoRouteHandler;
|
|
187
|
+
`;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function motionDemoRoute() {
|
|
191
|
+
return `import { useEffect, useRef } from 'preact/hooks';
|
|
192
|
+
import { animate } from 'motion';
|
|
193
|
+
import * as THREE from 'three';
|
|
194
|
+
|
|
195
|
+
export const metadata = { title: 'Motion + 3D' };
|
|
196
|
+
|
|
197
|
+
export default function MotionDemo() {
|
|
198
|
+
const card = useRef(null);
|
|
199
|
+
const canvas = useRef(null);
|
|
200
|
+
|
|
201
|
+
useEffect(() => {
|
|
202
|
+
if (card.current) {
|
|
203
|
+
animate(card.current, { rotate: [0, 8, -8, 0], scale: [1, 1.06, 1] }, { duration: 2.4, repeat: Infinity });
|
|
204
|
+
}
|
|
205
|
+
const el = canvas.current;
|
|
206
|
+
if (!el) return;
|
|
207
|
+
const renderer = new THREE.WebGLRenderer({ canvas: el, antialias: true, alpha: true });
|
|
208
|
+
renderer.setSize(240, 240, false);
|
|
209
|
+
const scene = new THREE.Scene();
|
|
210
|
+
const camera = new THREE.PerspectiveCamera(60, 1, 0.1, 100);
|
|
211
|
+
camera.position.z = 3;
|
|
212
|
+
const cube = new THREE.Mesh(new THREE.BoxGeometry(), new THREE.MeshNormalMaterial());
|
|
213
|
+
scene.add(cube);
|
|
214
|
+
let raf;
|
|
215
|
+
const loop = () => { cube.rotation.x += 0.01; cube.rotation.y += 0.013; renderer.render(scene, camera); raf = requestAnimationFrame(loop); };
|
|
216
|
+
loop();
|
|
217
|
+
return () => cancelAnimationFrame(raf);
|
|
218
|
+
}, []);
|
|
219
|
+
|
|
220
|
+
return (
|
|
221
|
+
<main className="hero">
|
|
222
|
+
<h1>Motion + 3D</h1>
|
|
223
|
+
<p className="lede">motion drives the card animation; three.js renders the cube.</p>
|
|
224
|
+
<div ref={card} style={{ width: '120px', height: '120px', borderRadius: '16px', background: '#e8eaed' }} />
|
|
225
|
+
<canvas ref={canvas} width={240} height={240} />
|
|
226
|
+
</main>
|
|
227
|
+
);
|
|
228
|
+
}
|
|
229
|
+
`;
|
|
230
|
+
}
|
|
231
|
+
|
|
163
232
|
function indexRoute(answers) {
|
|
164
233
|
return `import { useState } from 'preact/hooks';
|
|
165
234
|
import { callServerFunction } from 'glashjs/server-functions';
|
package/src/i18n.mjs
ADDED
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
// glashjs/i18n — built-in IP-based auto-translation.
|
|
2
|
+
//
|
|
3
|
+
// Server:
|
|
4
|
+
// detectCountry(req) -> 'US' | null (reads edge/CDN geo headers)
|
|
5
|
+
// languageForCountry('FR') -> { code: 'fr', name: 'Français' } | null
|
|
6
|
+
//
|
|
7
|
+
// Client:
|
|
8
|
+
// glashAutoTranslate(opts) -> looks up the visitor's country (via a glashjs
|
|
9
|
+
// geo API route, falling back to a public IP service), maps it to a language,
|
|
10
|
+
// and — if that differs from the page language — shows a small prompt to
|
|
11
|
+
// translate the page into the visitor's native language. No API key: it uses
|
|
12
|
+
// the Google website-translate widget (cookie-driven, in-place).
|
|
13
|
+
//
|
|
14
|
+
// Wire it up in a client effect:
|
|
15
|
+
// import { glashAutoTranslate } from 'glashjs/i18n';
|
|
16
|
+
// useEffect(() => glashAutoTranslate(), []);
|
|
17
|
+
// and scaffold `routes/api/_glash/geo.mjs` (see geoRouteHandler) so detection
|
|
18
|
+
// works on glashDB hosting, where the edge sets the country header.
|
|
19
|
+
|
|
20
|
+
// country code -> [BCP-47 language, native name]
|
|
21
|
+
export const COUNTRY_LANGUAGE = {
|
|
22
|
+
US: ['en', 'English'], GB: ['en', 'English'], AU: ['en', 'English'], CA: ['en', 'English'],
|
|
23
|
+
IE: ['en', 'English'], NZ: ['en', 'English'], NG: ['en', 'English'], GH: ['en', 'English'],
|
|
24
|
+
KE: ['en', 'English'], ZA: ['en', 'English'], IN: ['hi', 'हिन्दी'], PK: ['ur', 'اردو'],
|
|
25
|
+
FR: ['fr', 'Français'], BE: ['fr', 'Français'], LU: ['fr', 'Français'], CI: ['fr', 'Français'],
|
|
26
|
+
SN: ['fr', 'Français'], CM: ['fr', 'Français'], ES: ['es', 'Español'], MX: ['es', 'Español'],
|
|
27
|
+
AR: ['es', 'Español'], CO: ['es', 'Español'], CL: ['es', 'Español'], PE: ['es', 'Español'],
|
|
28
|
+
VE: ['es', 'Español'], DE: ['de', 'Deutsch'], AT: ['de', 'Deutsch'], CH: ['de', 'Deutsch'],
|
|
29
|
+
IT: ['it', 'Italiano'], PT: ['pt', 'Português'], BR: ['pt', 'Português'], AO: ['pt', 'Português'],
|
|
30
|
+
NL: ['nl', 'Nederlands'], RU: ['ru', 'Русский'], UA: ['uk', 'Українська'], PL: ['pl', 'Polski'],
|
|
31
|
+
CZ: ['cs', 'Čeština'], SK: ['sk', 'Slovenčina'], RO: ['ro', 'Română'], HU: ['hu', 'Magyar'],
|
|
32
|
+
GR: ['el', 'Ελληνικά'], TR: ['tr', 'Türkçe'], SE: ['sv', 'Svenska'], NO: ['no', 'Norsk'],
|
|
33
|
+
DK: ['da', 'Dansk'], FI: ['fi', 'Suomi'], IS: ['is', 'Íslenska'], CN: ['zh-CN', '中文'],
|
|
34
|
+
TW: ['zh-TW', '繁體中文'], HK: ['zh-TW', '繁體中文'], JP: ['ja', '日本語'], KR: ['ko', '한국어'],
|
|
35
|
+
TH: ['th', 'ไทย'], VN: ['vi', 'Tiếng Việt'], ID: ['id', 'Bahasa Indonesia'], MY: ['ms', 'Bahasa Melayu'],
|
|
36
|
+
PH: ['tl', 'Filipino'], SA: ['ar', 'العربية'], AE: ['ar', 'العربية'], EG: ['ar', 'العربية'],
|
|
37
|
+
MA: ['ar', 'العربية'], DZ: ['ar', 'العربية'], IQ: ['ar', 'العربية'], IL: ['he', 'עברית'],
|
|
38
|
+
IR: ['fa', 'فارسی'], BD: ['bn', 'বাংলা'], ET: ['am', 'አማርኛ'],
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export function detectCountry(req) {
|
|
42
|
+
const h = (req && req.headers) || {};
|
|
43
|
+
const get = (k) => (typeof h.get === 'function' ? h.get(k) : (h[k] ?? h[k.toLowerCase()]));
|
|
44
|
+
const cc = String(
|
|
45
|
+
get('cf-ipcountry') || get('x-glash-country') || get('x-vercel-ip-country') || get('x-country-code') || '',
|
|
46
|
+
).split(',')[0].trim().toUpperCase();
|
|
47
|
+
return /^[A-Z]{2}$/.test(cc) && cc !== 'XX' ? cc : null;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function languageForCountry(country) {
|
|
51
|
+
const entry = COUNTRY_LANGUAGE[String(country || '').toUpperCase()];
|
|
52
|
+
return entry ? { code: entry[0], name: entry[1] } : null;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** Drop-in handler for `routes/api/_glash/geo.mjs` (export const GET = geoRouteHandler). */
|
|
56
|
+
export function geoRouteHandler(ctx) {
|
|
57
|
+
const country = detectCountry({ headers: (ctx && ctx.headers) || {} });
|
|
58
|
+
const language = country ? languageForCountry(country) : null;
|
|
59
|
+
return { country, language };
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// ---- client ---------------------------------------------------------------
|
|
63
|
+
|
|
64
|
+
function baseLang(code) { return String(code || '').toLowerCase().split('-')[0]; }
|
|
65
|
+
|
|
66
|
+
async function lookupLanguage(geoEndpoint) {
|
|
67
|
+
// 1) glashjs geo route (works on glashDB hosting via the edge country header).
|
|
68
|
+
try {
|
|
69
|
+
const r = await fetch(geoEndpoint, { headers: { accept: 'application/json' } });
|
|
70
|
+
if (r.ok) {
|
|
71
|
+
const d = await r.json();
|
|
72
|
+
if (d && d.language && d.language.code) return d.language;
|
|
73
|
+
if (d && d.country) { const l = languageForCountry(d.country); if (l) return l; }
|
|
74
|
+
}
|
|
75
|
+
} catch { /* fall through */ }
|
|
76
|
+
// 2) Public IP geolocation fallback (no key, CORS-enabled).
|
|
77
|
+
try {
|
|
78
|
+
const r = await fetch('https://ipapi.co/json/');
|
|
79
|
+
if (r.ok) {
|
|
80
|
+
const d = await r.json();
|
|
81
|
+
const l = d && d.country_code ? languageForCountry(d.country_code) : null;
|
|
82
|
+
if (l) return l;
|
|
83
|
+
}
|
|
84
|
+
} catch { /* fall through */ }
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function setGoogTransCookie(from, to) {
|
|
89
|
+
const val = `/${from}/${to}`;
|
|
90
|
+
const host = location.hostname;
|
|
91
|
+
document.cookie = `googtrans=${val};path=/`;
|
|
92
|
+
document.cookie = `googtrans=${val};path=/;domain=${host}`;
|
|
93
|
+
if (host.split('.').length > 1) document.cookie = `googtrans=${val};path=/;domain=.${host}`;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function loadGoogleWidget(pageLanguage) {
|
|
97
|
+
if (window.__glashGTLoaded) return;
|
|
98
|
+
window.__glashGTLoaded = true;
|
|
99
|
+
if (!document.getElementById('glash-gt')) {
|
|
100
|
+
const el = document.createElement('div');
|
|
101
|
+
el.id = 'glash-gt';
|
|
102
|
+
el.style.display = 'none';
|
|
103
|
+
document.body.appendChild(el);
|
|
104
|
+
}
|
|
105
|
+
window.googleTranslateElementInit = function () {
|
|
106
|
+
try { new window.google.translate.TranslateElement({ pageLanguage, autoDisplay: false }, 'glash-gt'); } catch { /* ignore */ }
|
|
107
|
+
};
|
|
108
|
+
const s = document.createElement('script');
|
|
109
|
+
s.src = 'https://translate.google.com/translate_a/element.js?cb=googleTranslateElementInit';
|
|
110
|
+
document.head.appendChild(s);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function showPrompt(language, onAccept, onDismiss) {
|
|
114
|
+
if (document.getElementById('glash-translate-prompt')) return;
|
|
115
|
+
const box = document.createElement('div');
|
|
116
|
+
box.id = 'glash-translate-prompt';
|
|
117
|
+
box.setAttribute('role', 'dialog');
|
|
118
|
+
box.style.cssText = [
|
|
119
|
+
'position:fixed', 'z-index:2147483646', 'left:50%', 'bottom:20px', 'transform:translateX(-50%)',
|
|
120
|
+
'max-width:min(92vw,460px)', 'display:flex', 'gap:12px', 'align-items:center',
|
|
121
|
+
'padding:12px 14px', 'border-radius:12px', 'font:14px/1.4 system-ui,sans-serif',
|
|
122
|
+
'background:#0b0d12', 'color:#e8eaed', 'border:1px solid rgba(255,255,255,0.12)',
|
|
123
|
+
'box-shadow:0 8px 30px rgba(0,0,0,0.45)',
|
|
124
|
+
].join(';');
|
|
125
|
+
const text = document.createElement('span');
|
|
126
|
+
text.style.cssText = 'flex:1';
|
|
127
|
+
text.textContent = `View this site in ${language.name}?`;
|
|
128
|
+
const yes = document.createElement('button');
|
|
129
|
+
yes.textContent = 'Translate';
|
|
130
|
+
yes.style.cssText = 'cursor:pointer;border:0;border-radius:8px;padding:7px 12px;font:inherit;background:#e8eaed;color:#0b0d12;font-weight:600';
|
|
131
|
+
const no = document.createElement('button');
|
|
132
|
+
no.textContent = 'No thanks';
|
|
133
|
+
no.style.cssText = 'cursor:pointer;border:0;border-radius:8px;padding:7px 10px;font:inherit;background:transparent;color:#9aa0a6';
|
|
134
|
+
yes.addEventListener('click', () => { box.remove(); onAccept(); });
|
|
135
|
+
no.addEventListener('click', () => { box.remove(); onDismiss(); });
|
|
136
|
+
box.append(text, yes, no);
|
|
137
|
+
document.body.appendChild(box);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Detect the visitor's country by IP, map it to their native language, and (if it
|
|
142
|
+
* differs from the page language) prompt to auto-translate the page in place.
|
|
143
|
+
*
|
|
144
|
+
* Options:
|
|
145
|
+
* pageLanguage language the site is authored in (default: <html lang> or 'en')
|
|
146
|
+
* geoEndpoint glashjs geo route (default: '/api/_glash/geo')
|
|
147
|
+
* auto translate immediately without prompting (default: false)
|
|
148
|
+
* storageKey localStorage key remembering the choice
|
|
149
|
+
*/
|
|
150
|
+
export function glashAutoTranslate(opts = {}) {
|
|
151
|
+
if (typeof window === 'undefined' || typeof document === 'undefined') return;
|
|
152
|
+
const {
|
|
153
|
+
pageLanguage = baseLang(document.documentElement.lang) || 'en',
|
|
154
|
+
geoEndpoint = '/api/_glash/geo',
|
|
155
|
+
auto = false,
|
|
156
|
+
storageKey = 'glash:autotranslate',
|
|
157
|
+
} = opts;
|
|
158
|
+
|
|
159
|
+
// If a previous choice already translated the page, re-apply the widget so the
|
|
160
|
+
// translation persists across navigations, then stop.
|
|
161
|
+
if (/(^|;\s*)googtrans=\//.test(document.cookie)) { loadGoogleWidget(pageLanguage); return; }
|
|
162
|
+
let saved = null;
|
|
163
|
+
try { saved = localStorage.getItem(storageKey); } catch { /* ignore */ }
|
|
164
|
+
if (saved === 'dismissed') return;
|
|
165
|
+
|
|
166
|
+
const translateTo = (code) => {
|
|
167
|
+
setGoogTransCookie(pageLanguage, code);
|
|
168
|
+
loadGoogleWidget(pageLanguage);
|
|
169
|
+
setTimeout(() => location.reload(), 60);
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
if (saved && saved !== pageLanguage) { translateTo(saved); return; }
|
|
173
|
+
|
|
174
|
+
lookupLanguage(geoEndpoint).then((language) => {
|
|
175
|
+
if (!language || baseLang(language.code) === pageLanguage) return;
|
|
176
|
+
if (auto) { try { localStorage.setItem(storageKey, language.code); } catch {} translateTo(language.code); return; }
|
|
177
|
+
showPrompt(
|
|
178
|
+
language,
|
|
179
|
+
() => { try { localStorage.setItem(storageKey, language.code); } catch {} translateTo(language.code); },
|
|
180
|
+
() => { try { localStorage.setItem(storageKey, 'dismissed'); } catch {} },
|
|
181
|
+
);
|
|
182
|
+
}).catch(() => {});
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
export default { detectCountry, languageForCountry, geoRouteHandler, glashAutoTranslate, COUNTRY_LANGUAGE };
|
package/src/server/jsx.mjs
CHANGED
|
@@ -54,6 +54,15 @@ const REACT_ALIAS = {
|
|
|
54
54
|
'next/headers': path.join(pkgRoot, 'next/headers.mjs'),
|
|
55
55
|
};
|
|
56
56
|
|
|
57
|
+
// CJS deps bundled into an ESM server module (e.g. lucide-react -> require('react')
|
|
58
|
+
// -> aliased preact/compat, which is external) emit a dynamic require that ESM
|
|
59
|
+
// can't run ("Dynamic require of X is not supported"). Defining `require` via
|
|
60
|
+
// createRequire makes esbuild's __require shim use the real Node require, so those
|
|
61
|
+
// runtime requires resolve from the app's node_modules.
|
|
62
|
+
const NODE_ESM_REQUIRE_BANNER = {
|
|
63
|
+
js: "import { createRequire as __glashCreateRequire } from 'node:module';\nconst require = __glashCreateRequire(import.meta.url);",
|
|
64
|
+
};
|
|
65
|
+
|
|
57
66
|
export function isComponentRoute(file) {
|
|
58
67
|
return /\.(jsx|tsx)$/.test(file);
|
|
59
68
|
}
|
|
@@ -76,7 +85,7 @@ export async function compileModule(file, root, dev) {
|
|
|
76
85
|
entryPoints: [file], bundle: true, platform: 'node', format: 'esm',
|
|
77
86
|
jsx: 'automatic', jsxImportSource: 'preact',
|
|
78
87
|
external: ['preact', 'preact/*', 'preact-render-to-string'],
|
|
79
|
-
alias: REACT_ALIAS, outfile: out, logLevel: 'silent',
|
|
88
|
+
alias: REACT_ALIAS, banner: NODE_ESM_REQUIRE_BANNER, outfile: out, logLevel: 'silent',
|
|
80
89
|
});
|
|
81
90
|
return import(pathToFileURL(out).href + (dev ? `?t=${Date.now()}` : ''));
|
|
82
91
|
}
|
|
@@ -158,7 +167,7 @@ export async function loadComponentRoute(pageFile, layouts, root, dev, force = f
|
|
|
158
167
|
stdin: { contents: serverEntry(pageFile, layouts), resolveDir: path.dirname(pageFile), loader: 'js', sourcefile: 'glash-server-entry.js' },
|
|
159
168
|
bundle: true, platform: 'node', format: 'esm', jsx: 'automatic', jsxImportSource: 'preact',
|
|
160
169
|
external: ['preact', 'preact/*', 'preact-render-to-string'],
|
|
161
|
-
alias: REACT_ALIAS,
|
|
170
|
+
alias: REACT_ALIAS, banner: NODE_ESM_REQUIRE_BANNER,
|
|
162
171
|
outfile: out, logLevel: 'silent',
|
|
163
172
|
});
|
|
164
173
|
const mod = await import(pathToFileURL(out).href + (dev ? `?t=${Date.now()}` : ''));
|