@fougere/vite 0.2.0-alpha.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Fougere contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,26 @@
1
+ import type { Plugin } from 'vite';
2
+ /**
3
+ * The entity names a build must not rename, read off the filesystem.
4
+ *
5
+ * `fronds/blog/entities/Post.ts` IS the declaration of an entity called `Post` —
6
+ * the scan already treats the file name that way. So the list needs no config and
7
+ * no annotation: it is derived from the same convention the framework already
8
+ * relies on, which is why this feint costs the developer nothing.
9
+ */
10
+ export declare function entityNamesIn(root: string): string[];
11
+ /** Packages a Fougere boot loads at runtime — they must not be bundled. */
12
+ export declare const RUNTIME_PACKAGES: string[];
13
+ export interface FougereViteOptions {
14
+ /** Extra packages the boot loads at runtime, added to the defaults. */
15
+ external?: string[];
16
+ /**
17
+ * Set false to keep the host's own minifier. Only do this if something else in
18
+ * the build already preserves class names — otherwise production designates a
19
+ * renamed class and the call is refused.
20
+ */
21
+ keepClassNames?: boolean;
22
+ /** Extra identifiers to reserve, for entities that do not live under `fronds/`. */
23
+ reserved?: string[];
24
+ }
25
+ export declare function fougere(options?: FougereViteOptions): Plugin;
26
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AA6CA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,MAAM,CAAC;AAEnC;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAepD;AAED,2EAA2E;AAC3E,eAAO,MAAM,gBAAgB,UAU5B,CAAC;AAEF,MAAM,WAAW,kBAAkB;IACjC,uEAAuE;IACvE,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB;;;;OAIG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,mFAAmF;IACnF,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;CACrB;AAED,wBAAgB,OAAO,CAAC,OAAO,GAAE,kBAAuB,GAAG,MAAM,CA2ChE"}
package/dist/index.js ADDED
@@ -0,0 +1,127 @@
1
+ /**
2
+ * `@fougere/vite` — the one place a Vite-built host is told what a Fougere app needs.
3
+ *
4
+ * ```ts
5
+ * // vite.config.ts
6
+ * import { fougere } from '@fougere/vite';
7
+ *
8
+ * export default defineConfig({ plugins: [fougere(), sveltekit()] });
9
+ * ```
10
+ *
11
+ * One plugin, three hosts: TanStack Start, React Router and SvelteKit all build with
12
+ * Vite, so none of them needs a package of its own. That was the hole in the first
13
+ * version of this fix — it lived in `@fougere/next`, and the three hosts that have
14
+ * no adapter package had nowhere to put it.
15
+ *
16
+ * ## What it states, and why each one
17
+ *
18
+ * **1. The entity's name survives minification.** Designation is class + verb, so
19
+ * `useQuery(Post, 'list')` reads `Post.name`, and that name travels — it is the
20
+ * JSON-RPC method (`post.list`) and the REST path.
21
+ *
22
+ * Getting there took measuring what actually happens, because the obvious setting
23
+ * is not enough. Rollup cannot keep `class Post extends entity({…})` as a hoisted
24
+ * declaration — its heritage clause is a CALL — so it emits `var Post = class
25
+ * extends entity({…})`. The class is ANONYMOUS; `Post.name` works only through
26
+ * JavaScript's name inference from the variable. So `keep_classnames` protects
27
+ * nothing (there is no class name to keep), in `compress` or in `mangle` — both
28
+ * measured, both `Ot`.
29
+ *
30
+ * What works is reserving the IDENTIFIER: `mangle.reserved`. The variable keeps its
31
+ * name, inference keeps working, and everything else is still mangled. Measured:
32
+ * `var Post=class extends…` in the production client bundle.
33
+ *
34
+ * And the list is not config — `entityNamesIn` reads it from `fronds/<frond>/entities/`,
35
+ * the same convention the scan already uses. The developer writes nothing.
36
+ *
37
+ * **2. The scan's dependencies stay out of the server bundle.** A boot reads frond
38
+ * sources off disk through jiti, so those packages are loaded at runtime instead.
39
+ * The three demos each copied this list into their own config; it belongs here.
40
+ *
41
+ * The rule this encodes is not "Vite is special", it is: **any bundler that carries
42
+ * a Fougere entity class must preserve its name.**
43
+ */
44
+ import { readdirSync, existsSync } from 'node:fs';
45
+ import { join } from 'node:path';
46
+ /**
47
+ * The entity names a build must not rename, read off the filesystem.
48
+ *
49
+ * `fronds/blog/entities/Post.ts` IS the declaration of an entity called `Post` —
50
+ * the scan already treats the file name that way. So the list needs no config and
51
+ * no annotation: it is derived from the same convention the framework already
52
+ * relies on, which is why this feint costs the developer nothing.
53
+ */
54
+ export function entityNamesIn(root) {
55
+ const frondsDir = join(root, 'fronds');
56
+ if (!existsSync(frondsDir))
57
+ return [];
58
+ const names = new Set();
59
+ for (const frond of readdirSync(frondsDir, { withFileTypes: true })) {
60
+ if (!frond.isDirectory())
61
+ continue;
62
+ const entities = join(frondsDir, frond.name, 'entities');
63
+ if (!existsSync(entities))
64
+ continue;
65
+ for (const file of readdirSync(entities)) {
66
+ const match = /^(.+)\.tsx?$/.exec(file);
67
+ if (match)
68
+ names.add(match[1]);
69
+ }
70
+ }
71
+ return [...names];
72
+ }
73
+ /** Packages a Fougere boot loads at runtime — they must not be bundled. */
74
+ export const RUNTIME_PACKAGES = [
75
+ '@fougere/app',
76
+ '@fougere/core',
77
+ '@fougere/schema',
78
+ '@fougere/schema-sql',
79
+ '@fougere/schema-graphql',
80
+ '@fougere/auth-better',
81
+ 'better-sqlite3',
82
+ 'jiti',
83
+ 'typescript',
84
+ ];
85
+ export function fougere(options = {}) {
86
+ const external = [...new Set([...RUNTIME_PACKAGES, ...(options.external ?? [])])];
87
+ return {
88
+ name: 'fougere',
89
+ /**
90
+ * `order: 'post'` and a MUTATION, not a returned patch — and both halves were
91
+ * measured.
92
+ *
93
+ * Returning `{ build: { minify: 'terser' } }` is the idiomatic form, and it did
94
+ * not work: Vite deep-merges those patches, and the framework plugins
95
+ * (`tanstackStart()`, `reactRouter()`, `sveltekit()`) set their own `build`
96
+ * afterwards, so all three demos still shipped `C=class extends…`. Running last
97
+ * and writing the value directly is what makes the setting hold whatever the
98
+ * host declares.
99
+ *
100
+ * The app keeps the last word anyway: `keepClassNames: false` turns this off.
101
+ */
102
+ config: {
103
+ order: 'post',
104
+ handler(config) {
105
+ config.ssr ??= {};
106
+ config.ssr.external = [...new Set([...(config.ssr.external ?? []), ...external])];
107
+ if (options.keepClassNames === false)
108
+ return;
109
+ const reserved = [...(options.reserved ?? []), ...entityNamesIn(config.root ?? process.cwd())];
110
+ if (reserved.length === 0)
111
+ return;
112
+ config.build ??= {};
113
+ // Vite's default minifier is esbuild, which has no per-name option; terser does.
114
+ config.build.minify = 'terser';
115
+ config.build.terserOptions = {
116
+ ...config.build.terserOptions,
117
+ keep_classnames: true,
118
+ mangle: {
119
+ ...(config.build.terserOptions?.mangle ?? {}),
120
+ reserved: [...new Set([...(config.build.terserOptions?.mangle?.reserved ?? []), ...reserved])],
121
+ },
122
+ };
123
+ },
124
+ },
125
+ };
126
+ }
127
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,49 @@
1
+ {
2
+ "name": "@fougere/vite",
3
+ "version": "0.2.0-alpha.0",
4
+ "description": "The Vite plugin: what any Vite-built host must know to run a Fougere app.",
5
+ "keywords": [
6
+ "fougere",
7
+ "vite",
8
+ "plugin",
9
+ "sveltekit",
10
+ "tanstack",
11
+ "react-router"
12
+ ],
13
+ "license": "MIT",
14
+ "repository": {
15
+ "type": "git",
16
+ "url": "git+https://github.com/chok/fougere.git",
17
+ "directory": "packages/app/vite"
18
+ },
19
+ "type": "module",
20
+ "main": "dist/index.js",
21
+ "types": "dist/index.d.ts",
22
+ "exports": {
23
+ ".": {
24
+ "types": "./dist/index.d.ts",
25
+ "import": "./dist/index.js"
26
+ }
27
+ },
28
+ "files": [
29
+ "dist"
30
+ ],
31
+ "dependencies": {
32
+ "terser": "^5.44.0"
33
+ },
34
+ "devDependencies": {
35
+ "vite": "^8.0.0",
36
+ "vitest": "^4.1.0"
37
+ },
38
+ "peerDependencies": {
39
+ "vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0"
40
+ },
41
+ "publishConfig": {
42
+ "access": "public"
43
+ },
44
+ "scripts": {
45
+ "build": "rm -rf dist && tsc",
46
+ "test": "vitest run",
47
+ "typecheck": "tsc --noEmit -p tsconfig.test.json"
48
+ }
49
+ }