arcway 0.1.22 → 0.1.24

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.
@@ -0,0 +1,246 @@
1
+ import path from 'node:path';
2
+ import fs from 'node:fs/promises';
3
+ import { createRequire } from 'node:module';
4
+ import reactPlugin from '@vitejs/plugin-react';
5
+ import tailwindcss from '@tailwindcss/vite';
6
+ import { createServer as createViteServer } from 'vite';
7
+ import { patternToFileName } from './build-server.js';
8
+
9
+ function toPosixPath(value) {
10
+ return value.replace(/\\/g, '/');
11
+ }
12
+
13
+ function toViteModuleUrl(rootDir, filePath) {
14
+ if (!filePath) return null;
15
+ const abs = path.resolve(filePath);
16
+ const rel = path.relative(rootDir, abs);
17
+ if (!rel.startsWith('..') && !path.isAbsolute(rel)) {
18
+ return `/${toPosixPath(rel)}`;
19
+ }
20
+ return `/@fs/${toPosixPath(abs)}`;
21
+ }
22
+
23
+ function getViteEntryPath(outDir, pattern) {
24
+ return path.join(outDir, '.vite-entries', `${patternToFileName(pattern)}.tsx`);
25
+ }
26
+
27
+ function buildViteHydrationEntry({
28
+ componentPath,
29
+ layouts = [],
30
+ loadings = [],
31
+ pattern = '/',
32
+ globalStylesPath,
33
+ }) {
34
+ const imports = [
35
+ `import { hydrateRoot } from 'react-dom/client';`,
36
+ `import { ApiProvider, Router } from 'arcway/lib/client';`,
37
+ ];
38
+
39
+ if (globalStylesPath) {
40
+ imports.push(`import '${toPosixPath(path.resolve(globalStylesPath))}';`);
41
+ }
42
+
43
+ imports.push(`import Component from '${toPosixPath(path.resolve(componentPath))}';`);
44
+
45
+ const layoutNames = [];
46
+ for (let i = 0; i < layouts.length; i++) {
47
+ imports.push(`import Layout${i} from '${toPosixPath(path.resolve(layouts[i]))}';`);
48
+ layoutNames.push(`Layout${i}`);
49
+ }
50
+
51
+ const loadingNames = [];
52
+ for (let i = 0; i < loadings.length; i++) {
53
+ imports.push(`import Loading${i} from '${toPosixPath(path.resolve(loadings[i]))}';`);
54
+ loadingNames.push(`Loading${i}`);
55
+ }
56
+
57
+ const layoutsArray = layoutNames.length > 0 ? `[${layoutNames.join(', ')}]` : '[]';
58
+ const loadingsArray = loadingNames.length > 0 ? `[${loadingNames.join(', ')}]` : '[]';
59
+
60
+ return `${imports.join('\n')}
61
+
62
+ const container = document.getElementById('__app');
63
+ const propsEl = document.getElementById('__app_props');
64
+ const props = propsEl ? JSON.parse(propsEl.textContent || '{}') : {};
65
+ const ROOT_KEY = '__arcway_root__';
66
+ const rootOwner = window;
67
+ const element = <ApiProvider><Router initialPath={window.location.pathname} initialParams={props} initialPattern={${JSON.stringify(pattern)}} initialComponent={Component} initialLayouts={${layoutsArray}} initialLoadings={${loadingsArray}} /></ApiProvider>;
68
+
69
+ if (!container) {
70
+ throw new Error('Arcway Vite hydrate entry could not find #__app');
71
+ }
72
+
73
+ if (!rootOwner[ROOT_KEY]) {
74
+ rootOwner[ROOT_KEY] = hydrateRoot(container, element);
75
+ } else {
76
+ rootOwner[ROOT_KEY].render(element);
77
+ }
78
+ `;
79
+ }
80
+
81
+ async function syncViteHydrationEntries({ manifest, outDir }) {
82
+ const viteDir = path.join(outDir, '.vite-entries');
83
+ await fs.mkdir(viteDir, { recursive: true });
84
+
85
+ const expected = new Set();
86
+
87
+ for (const entry of manifest.entries) {
88
+ const entryPath = getViteEntryPath(outDir, entry.pattern);
89
+ const content = buildViteHydrationEntry({
90
+ componentPath: entry.srcPath,
91
+ layouts: entry.layoutDirs
92
+ .map((dirPath) => manifest.layouts.get(dirPath)?.srcPath)
93
+ .filter((value) => value !== undefined),
94
+ loadings: entry.loadingDirs
95
+ .map((dirPath) => manifest.loadings.get(dirPath)?.srcPath)
96
+ .filter((value) => value !== undefined),
97
+ pattern: entry.pattern,
98
+ globalStylesPath: manifest.stylesPath,
99
+ });
100
+ await fs.mkdir(path.dirname(entryPath), { recursive: true });
101
+ await fs.writeFile(entryPath, content);
102
+ expected.add(toPosixPath(path.relative(viteDir, entryPath)));
103
+ }
104
+
105
+ const existing = await listFiles(viteDir);
106
+ await Promise.all(
107
+ existing
108
+ .filter((name) => !expected.has(name))
109
+ .map((name) => fs.rm(path.join(viteDir, name), { force: true })),
110
+ );
111
+ }
112
+
113
+ async function listFiles(rootDir, currentDir = rootDir) {
114
+ const entries = await fs.readdir(currentDir, { withFileTypes: true }).catch(() => []);
115
+ const files = [];
116
+ for (const entry of entries) {
117
+ const abs = path.join(currentDir, entry.name);
118
+ if (entry.isDirectory()) {
119
+ const nested = await listFiles(rootDir, abs);
120
+ files.push(...nested);
121
+ continue;
122
+ }
123
+ files.push(toPosixPath(path.relative(rootDir, abs)));
124
+ }
125
+ return files;
126
+ }
127
+
128
+ function buildViteClientManifestJson(manifest, rootDir) {
129
+ const clientManifest = {
130
+ cssBundle: null,
131
+ routes: manifest.entries.map((entry) => {
132
+ const route = {
133
+ pattern: entry.pattern,
134
+ paramNames: entry.paramNames,
135
+ clientBundle: toViteModuleUrl(rootDir, entry.srcPath),
136
+ layoutBundles: entry.layoutDirs
137
+ .map((dirPath) => manifest.layouts.get(dirPath)?.srcPath)
138
+ .filter(Boolean)
139
+ .map((filePath) => toViteModuleUrl(rootDir, filePath)),
140
+ loadingBundles: entry.loadingDirs
141
+ .map((dirPath) => manifest.loadings.get(dirPath)?.srcPath)
142
+ .filter(Boolean)
143
+ .map((filePath) => toViteModuleUrl(rootDir, filePath)),
144
+ cssBundles: [],
145
+ };
146
+ if (entry.catchAllParam) route.catchAllParam = entry.catchAllParam;
147
+ return route;
148
+ }),
149
+ };
150
+ return JSON.stringify(clientManifest).replace(/</g, '\\u003c');
151
+ }
152
+
153
+ function buildViteRoute(route, { rootDir, outDir }) {
154
+ return {
155
+ ...route,
156
+ clientBundle: toViteModuleUrl(rootDir, getViteEntryPath(outDir, route.pattern)),
157
+ sharedChunks: ['/@vite/client'],
158
+ sharedCssChunks: [],
159
+ };
160
+ }
161
+
162
+ function resolveAppAliases(rootDir) {
163
+ const appRequire = createRequire(path.join(rootDir, 'package.json'));
164
+ const aliases = [];
165
+
166
+ try {
167
+ const reactRoot = path.dirname(appRequire.resolve('react/package.json'));
168
+ aliases.push({ find: /^react$/, replacement: toPosixPath(path.join(reactRoot, 'index.js')) });
169
+ aliases.push({ find: /^react\/jsx-runtime$/, replacement: toPosixPath(path.join(reactRoot, 'jsx-runtime.js')) });
170
+ aliases.push({ find: /^react\/jsx-dev-runtime$/, replacement: toPosixPath(path.join(reactRoot, 'jsx-dev-runtime.js')) });
171
+ } catch {}
172
+
173
+ try {
174
+ const reactDomRoot = path.dirname(appRequire.resolve('react-dom/package.json'));
175
+ aliases.push({ find: /^react-dom$/, replacement: toPosixPath(path.join(reactDomRoot, 'index.js')) });
176
+ aliases.push({ find: /^react-dom\/client$/, replacement: toPosixPath(path.join(reactDomRoot, 'client.js')) });
177
+ } catch {}
178
+
179
+ try {
180
+ aliases.push({ find: /^swr$/, replacement: toPosixPath(appRequire.resolve('swr')) });
181
+ } catch {}
182
+
183
+ return aliases;
184
+ }
185
+
186
+ async function createViteDevRouter({ rootDir, log, config }) {
187
+ const appAliases = resolveAppAliases(rootDir);
188
+ const vite = await createViteServer({
189
+ root: rootDir,
190
+ appType: 'custom',
191
+ server: {
192
+ middlewareMode: true,
193
+ },
194
+ resolve: {
195
+ alias: appAliases,
196
+ dedupe: ['react', 'react-dom', 'swr'],
197
+ },
198
+ optimizeDeps: {
199
+ include: ['react', 'react-dom', 'react/jsx-runtime', 'react/jsx-dev-runtime', 'swr'],
200
+ },
201
+ plugins: [reactPlugin(), tailwindcss()],
202
+ clearScreen: false,
203
+ });
204
+
205
+ const internalPrefixes = ['/@vite/', '/@id/', '/@react-refresh', '/@fs/', '/node_modules/.vite/'];
206
+ const sourceExt = /\.(?:[cm]?[jt]sx?|css|pcss|postcss|json|svg)$/i;
207
+
208
+ async function handle(req, res) {
209
+ const url = req.url ?? '/';
210
+ const pathname = url.split('?')[0];
211
+ const shouldDelegate =
212
+ internalPrefixes.some((prefix) => pathname.startsWith(prefix)) ||
213
+ sourceExt.test(pathname) ||
214
+ pathname.startsWith('/.build/pages/.vite-entries/');
215
+
216
+ if (!shouldDelegate) return false;
217
+
218
+ return new Promise((resolve, reject) => {
219
+ vite.middlewares(req, res, (err) => {
220
+ if (err) {
221
+ reject(err);
222
+ return;
223
+ }
224
+ resolve(res.writableEnded || res.headersSent);
225
+ });
226
+ });
227
+ }
228
+
229
+ return {
230
+ vite,
231
+ handle,
232
+ async close() {
233
+ await vite.close();
234
+ log?.info?.('Vite dev server closed');
235
+ },
236
+ };
237
+ }
238
+
239
+ export {
240
+ buildViteClientManifestJson,
241
+ buildViteRoute,
242
+ createViteDevRouter,
243
+ getViteEntryPath,
244
+ syncViteHydrationEntries,
245
+ toViteModuleUrl,
246
+ };
@@ -36,7 +36,9 @@ function createPagesWatcher(options) {
36
36
  }
37
37
  for (const e of events) {
38
38
  if (e.event !== 'change') continue;
39
- const result = lazyContext.invalidate(e.path);
39
+ const result = lazyContext.handleSourceChange
40
+ ? await lazyContext.handleSourceChange(e.path)
41
+ : lazyContext.invalidate(e.path);
40
42
  if (result.touched) {
41
43
  log.info(`Pages: invalidated ${e.relativePath} (${formatAffected(result.affected)})`);
42
44
  }
package/server/watcher.js CHANGED
@@ -1,8 +1,17 @@
1
1
  import { watch } from 'chokidar';
2
2
  import path from 'node:path';
3
3
  function startWatcher(options) {
4
- const dirs = options.dirs ?? ['api', 'lib', 'listeners', 'jobs'];
5
- const extensions = new Set(options.extensions ?? ['.js']);
4
+ const dirs = options.dirs ?? [
5
+ 'api',
6
+ 'lib',
7
+ 'listeners',
8
+ 'jobs',
9
+ 'server',
10
+ 'templates',
11
+ 'migrations',
12
+ 'seeds',
13
+ ];
14
+ const extensions = new Set(options.extensions ?? ['.js', '.jsx', '.ts', '.tsx', '.css']);
6
15
  const debounceMs = options.debounceMs ?? 300;
7
16
  const watchPaths = dirs.map((dir) => path.join(options.rootDir, dir));
8
17
  let debounceTimer = null;