@octanejs/vite-plugin 0.1.5 → 0.1.9

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.
@@ -1,157 +0,0 @@
1
- // Production SSR build — end-to-end over the fixture app (tests/_fixtures/app):
2
- // `vite build` must produce BOTH bundles (dist/client assets + the
3
- // self-contained dist/server/entry.js), and the server bundle's handler must
4
- // render a route with hydratable output whose body region and #__octane_data
5
- // payload BYTE-MATCH dev SSR for the same request — that is the contract that
6
- // lets hydrateRoot adopt production responses exactly like dev ones.
7
- //
8
- // The fixture has no installed node_modules (it is not a workspace package);
9
- // the setup symlinks the workspace's octane / @octanejs/vite-plugin / vite in,
10
- // which is exactly what a pnpm install would produce.
11
- import { describe, it, expect, beforeAll, afterAll } from 'vitest';
12
- import fs from 'node:fs';
13
- import path from 'node:path';
14
- import { pathToFileURL, fileURLToPath } from 'node:url';
15
- import { build, createServer, type ViteDevServer } from 'vite';
16
-
17
- const fixtureRoot = fileURLToPath(new URL('./_fixtures/app', import.meta.url));
18
- const packageRoot = fileURLToPath(new URL('../', import.meta.url));
19
- const repoRoot = path.resolve(packageRoot, '../..');
20
- const distDir = path.join(fixtureRoot, 'dist');
21
-
22
- function linkPackage(name: string, target: string) {
23
- const dest = path.join(fixtureRoot, 'node_modules', name);
24
- fs.mkdirSync(path.dirname(dest), { recursive: true });
25
- fs.rmSync(dest, { recursive: true, force: true });
26
- fs.symlinkSync(target, dest, 'dir');
27
- }
28
-
29
- /** The rendered body region: everything streamed into `<div id="root">`. */
30
- function bodyRegionOf(html: string): string {
31
- const open = '<div id="root">';
32
- const start = html.indexOf(open);
33
- const end = html.lastIndexOf('</div>');
34
- expect(start).toBeGreaterThan(-1);
35
- expect(end).toBeGreaterThan(start);
36
- return html.slice(start + open.length, end);
37
- }
38
-
39
- function dataScriptOf(html: string): string {
40
- const match = html.match(/<script id="__octane_data" type="application\/json">(.*?)<\/script>/s);
41
- expect(match).not.toBeNull();
42
- return match![1];
43
- }
44
-
45
- let devServer: ViteDevServer | null = null;
46
- let devOrigin = '';
47
-
48
- beforeAll(async () => {
49
- linkPackage('octane', path.join(repoRoot, 'packages/octane'));
50
- linkPackage('@octanejs/vite-plugin', packageRoot);
51
- linkPackage('vite', path.join(packageRoot, 'node_modules/vite'));
52
-
53
- fs.rmSync(distDir, { recursive: true, force: true });
54
-
55
- // The production build: client bundle, then (closeBundle) the server bundle.
56
- await build({ root: fixtureRoot, logLevel: 'silent' });
57
-
58
- // A dev server on a random port — the byte-compat oracle.
59
- devServer = await createServer({
60
- root: fixtureRoot,
61
- logLevel: 'silent',
62
- server: { port: 0 },
63
- });
64
- await devServer.listen();
65
- const address = devServer.httpServer?.address();
66
- if (!address || typeof address !== 'object') throw new Error('dev server has no address');
67
- devOrigin = `http://localhost:${address.port}`;
68
- }, 180_000);
69
-
70
- afterAll(async () => {
71
- await devServer?.close();
72
- fs.rmSync(distDir, { recursive: true, force: true });
73
- fs.rmSync(path.join(fixtureRoot, 'node_modules'), { recursive: true, force: true });
74
- });
75
-
76
- describe('production SSR build', () => {
77
- it('emits both bundles, moves the template to dist/server, and strips build metadata', () => {
78
- expect(fs.existsSync(path.join(distDir, 'server/entry.js'))).toBe(true);
79
- expect(fs.existsSync(path.join(distDir, 'server/index.html'))).toBe(true);
80
- // The template must NOT stay in the static dir (it would shadow SSR at '/'
81
- // on filesystem-first hosts) and the manifest must not ship.
82
- expect(fs.existsSync(path.join(distDir, 'client/index.html'))).toBe(false);
83
- expect(fs.existsSync(path.join(distDir, 'client/.vite'))).toBe(false);
84
- // The client build produced hashed assets, including the hydrate entry
85
- // referenced by the moved template.
86
- const template = fs.readFileSync(path.join(distDir, 'server/index.html'), 'utf-8');
87
- const scriptSrc = template.match(/<script type="module"[^>]*src="(\/assets\/[^"]+)"/)?.[1];
88
- expect(scriptSrc).toBeTruthy();
89
- expect(fs.existsSync(path.join(distDir, 'client', scriptSrc!))).toBe(true);
90
- // The SSR placeholders survived the client build untouched.
91
- expect(template).toContain('<!--ssr-head-->');
92
- expect(template).toContain('<!--ssr-body-->');
93
- });
94
-
95
- it('the server bundle is self-contained (imports only node builtins)', () => {
96
- const entry = fs.readFileSync(path.join(distDir, 'server/entry.js'), 'utf-8');
97
- const specifiers = [...entry.matchAll(/^import[^'"]*['"]([^'"]+)['"]/gm)].map((m) => m[1]);
98
- expect(specifiers.length).toBeGreaterThan(0);
99
- for (const spec of specifiers) {
100
- expect(spec.startsWith('node:'), `unexpected external import: ${spec}`).toBe(true);
101
- }
102
- });
103
-
104
- it('renders a route through the built handler, byte-matching dev SSR', async () => {
105
- const { handler } = await import(pathToFileURL(path.join(distDir, 'server/entry.js')).href);
106
-
107
- for (const url of ['/', '/pages/hello']) {
108
- const prodResponse = await handler(new Request(`http://localhost${url}`));
109
- expect(prodResponse.status).toBe(200);
110
- expect(prodResponse.headers.get('content-type')).toBe('text/html; charset=utf-8');
111
- const prodHtml = await prodResponse.text();
112
-
113
- const devResponse = await fetch(`${devOrigin}${url}`);
114
- expect(devResponse.status).toBe(200);
115
- const devHtml = await devResponse.text();
116
-
117
- // The hydratable body region and the hydration payload are the
118
- // byte-compat contract between dev and production.
119
- expect(bodyRegionOf(prodHtml)).toBe(bodyRegionOf(devHtml));
120
- expect(dataScriptOf(prodHtml)).toBe(dataScriptOf(devHtml));
121
-
122
- // Sanity: it actually rendered the page.
123
- expect(prodHtml).toContain('fixture-nav');
124
- expect(prodHtml).toContain(url === '/' ? 'Fixture page home' : 'Fixture page hello');
125
- expect(prodHtml).toContain(`<p class="url">${url}</p>`);
126
- }
127
- });
128
-
129
- it('returns 404 for unmatched routes (no catch-all in the fixture)', async () => {
130
- const { handler } = await import(pathToFileURL(path.join(distDir, 'server/entry.js')).href);
131
- const response = await handler(new Request('http://localhost/nope/nothing'));
132
- expect(response.status).toBe(404);
133
- });
134
-
135
- it('nodeHandler bridges the same handler for Node-style serverless wrappers', async () => {
136
- const { nodeHandler } = await import(pathToFileURL(path.join(distDir, 'server/entry.js')).href);
137
- const chunks: Buffer[] = [];
138
- const headers: Record<string, unknown> = {};
139
- const res = {
140
- statusCode: 0,
141
- headersSent: false,
142
- setHeader(key: string, value: unknown) {
143
- headers[key.toLowerCase()] = value;
144
- },
145
- write(chunk: Uint8Array) {
146
- chunks.push(Buffer.from(chunk));
147
- },
148
- end(chunk?: Uint8Array) {
149
- if (chunk) chunks.push(Buffer.from(chunk));
150
- },
151
- };
152
- await nodeHandler({ method: 'GET', url: '/pages/node', headers: { host: 'localhost' } }, res);
153
- expect(res.statusCode).toBe(200);
154
- expect(headers['content-type']).toBe('text/html; charset=utf-8');
155
- expect(Buffer.concat(chunks).toString('utf-8')).toContain('Fixture page node');
156
- });
157
- });
@@ -1,18 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "module": "nodenext",
4
- "moduleResolution": "nodenext",
5
- "target": "es2022",
6
- "lib": ["esnext", "dom", "dom.iterable"],
7
- "noEmit": true,
8
- "strict": true,
9
- "skipLibCheck": true,
10
- "resolveJsonModule": true,
11
- "allowSyntheticDefaultImports": true,
12
- "types": ["node"],
13
- "allowJs": true,
14
- "checkJs": false
15
- },
16
- "include": ["src/**/*", "types/**/*", "tests/**/*.ts"],
17
- "exclude": ["node_modules", "dist", "tests/_fixtures"]
18
- }