@fougere/vite 0.9.2-alpha.0 → 0.10.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.
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,MAAM,CAAC;AAEnC,OAAO,EAAE,KAAK,WAAW,EAAuB,MAAM,eAAe,CAAC;AAEtE,yEAAyE;AACzE,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,WAAW,GAAE,WAAiC,GAAG,MAAM,EAAE,CAepG;AAED,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAErD,MAAM,WAAW,kBAAkB;IACjC,uEAAuE;IACvE,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,iDAAiD;IACjD,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,mFAAmF;IACnF,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;CACrB;AAED,wBAAgB,OAAO,CAAC,OAAO,GAAE,kBAAuB,GAAG,MAAM,CA+BhE"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,MAAM,CAAC;AAEnC,OAAO,EAAE,KAAK,WAAW,EAAuB,MAAM,eAAe,CAAC;AAEtE,yEAAyE;AACzE,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,WAAW,GAAE,WAAiC,GAAG,MAAM,EAAE,CAepG;AAED,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAErD,MAAM,WAAW,kBAAkB;IACjC,uEAAuE;IACvE,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,iDAAiD;IACjD,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,mFAAmF;IACnF,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;CACrB;AAiDD,wBAAgB,OAAO,CAAC,OAAO,GAAE,kBAAuB,GAAG,MAAM,CAyDhE"}
package/dist/index.js CHANGED
@@ -1,6 +1,5 @@
1
- /** `@fougere/vite` — the one place a Vite-built host is told what a Fougere app needs. */
2
- import { readdirSync, existsSync } from 'node:fs';
3
- import { join } from 'node:path';
1
+ import { readdirSync, existsSync, mkdirSync, writeFileSync } from 'node:fs';
2
+ import { join, dirname } from 'node:path';
4
3
  import { RUNTIME_PACKAGES } from '@fougere/compiler';
5
4
  import { DEFAULT_CONVENTIONS } from '@fougere/core';
6
5
  /** The entity names a build must not rename, read off the filesystem. */
@@ -24,16 +23,86 @@ export function entityNamesIn(root, conventions = DEFAULT_CONVENTIONS) {
24
23
  return [...names];
25
24
  }
26
25
  export { RUNTIME_PACKAGES } from '@fougere/compiler';
26
+ /**
27
+ * Where the facade types land — the same place `fougere build` puts them, so a host that runs the
28
+ * command and one that only starts a dev server read one file and not two.
29
+ */
30
+ const FACADE = '.fougere/facade.generated.ts';
31
+ const NAMES = '.fougere/names.generated.d.ts';
32
+ /**
33
+ * The facades this app serves, written when the dev server comes up — one export per address,
34
+ * carrying the handler that answers there as a type.
35
+ *
36
+ * TypeScript records nothing about what a function throws, so a client cannot know which
37
+ * refusals one operation answers without being told. Nuxt writes its own beside the two modules it
38
+ * already generates; every other host reaches this plugin, which is what makes the narrowing
39
+ * something you get from starting the app rather than from remembering a command.
40
+ *
41
+ * A scan that fails is not a failure of the dev server: the types fall back to every code there
42
+ * is, which is what a client had before this existed.
43
+ */
44
+ async function writeDoors(root) {
45
+ try {
46
+ const { scanProject, emitFacade, emitNames, frondAliases } = await import('@fougere/compiler');
47
+ const { setModuleLoader } = await import('@fougere/core/node');
48
+ const { resolveConventions } = await import('@fougere/core');
49
+ const { createJiti } = await import('jiti');
50
+ // The scan LOADS a frond's own sources, and `@fronds/user/entities/User.js` inside a handler
51
+ // has to resolve there for the same reason it does in a page. Without it `scanProject`
52
+ // throws `Cannot find module …/Post.js`, the catch below swallows it, and the module is
53
+ // silently never written — which is what happened until this line existed.
54
+ const jiti = createJiti(import.meta.url, {
55
+ interopDefault: true,
56
+ alias: await frondAliases(root, resolveConventions()),
57
+ });
58
+ setModuleLoader((filePath) => jiti.import(filePath));
59
+ const { loadConfig } = await import('@fougere/core/node');
60
+ const out = join(root, FACADE);
61
+ mkdirSync(dirname(out), { recursive: true });
62
+ const scan = await scanProject(root);
63
+ writeFileSync(out, emitFacade(scan, { outFile: out }));
64
+ // What a config may NAME, from the same scan: the sources are the config's own keys, which
65
+ // no scan can find, and a project without one states none.
66
+ const config = await loadConfig(root).catch(() => ({}));
67
+ writeFileSync(join(root, NAMES), emitNames(scan, { sources: Object.keys(config.sources ?? {}) }));
68
+ }
69
+ catch { /* a host with no fronds, or a scan that could not run */ }
70
+ }
27
71
  export function fougere(options = {}) {
28
72
  const external = [...new Set([...RUNTIME_PACKAGES, ...(options.external ?? [])])];
73
+ // Where `config` said the project is, so `buildStart` writes beside the alias it set.
74
+ let root = process.cwd();
29
75
  return {
30
76
  name: 'fougere',
77
+ configureServer(server) {
78
+ void writeDoors(server.config?.root ?? root);
79
+ },
80
+ /**
81
+ * AWAITED, and that is the whole point: the bundler resolves `@fronds/facade` right after
82
+ * this hook, and a fire-and-forget write loses the race — `Could not load
83
+ * .fougere/facade.generated.ts`, measured on `demos/react-router-blog`.
84
+ */
85
+ async buildStart() {
86
+ await writeDoors(root);
87
+ },
31
88
  /** `order. */
32
89
  config: {
33
90
  order: 'post',
34
91
  handler(config) {
35
92
  config.ssr ??= {};
36
93
  config.ssr.external = [...new Set([...(config.ssr.external ?? []), ...external])];
94
+ // A page IMPORTS its facade, so the alias sits beside the file that declares them: a
95
+ // project that never generated it fails to resolve rather than losing its types in
96
+ // silence.
97
+ root = config.root ?? root;
98
+ config.resolve ??= {};
99
+ // Vite admits BOTH shapes and a host picks one: SvelteKit states an array of
100
+ // `{ find, replacement }`, and spreading an object over it made rolldown refuse the
101
+ // whole plugin — `StringExpected … on BindingViteAliasPluginAlias.replacement`.
102
+ const facade = join(root, FACADE);
103
+ config.resolve.alias = Array.isArray(config.resolve.alias)
104
+ ? [...config.resolve.alias, { find: '@fronds/facade', replacement: facade }]
105
+ : { ...config.resolve.alias, '@fronds/facade': facade };
37
106
  if (options.keepClassNames === false)
38
107
  return;
39
108
  const reserved = [...(options.reserved ?? []), ...entityNamesIn(config.root ?? process.cwd())];
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fougere/vite",
3
- "version": "0.9.2-alpha.0",
3
+ "version": "0.10.0-alpha.0",
4
4
  "description": "The Vite plugin: what any Vite-built host must know to run a Fougere app.",
5
5
  "keywords": [
6
6
  "fougere",
@@ -30,9 +30,10 @@
30
30
  "src"
31
31
  ],
32
32
  "dependencies": {
33
+ "jiti": "^2.7.0",
33
34
  "terser": "^5.44.0",
34
- "@fougere/core": "0.9.2-alpha.0",
35
- "@fougere/compiler": "0.9.2-alpha.0"
35
+ "@fougere/compiler": "0.10.0-alpha.0",
36
+ "@fougere/core": "0.10.0-alpha.0"
36
37
  },
37
38
  "devDependencies": {
38
39
  "vite": "^8.0.0",
package/src/index.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  /** `@fougere/vite` — the one place a Vite-built host is told what a Fougere app needs. */
2
- import { readdirSync, existsSync } from 'node:fs';
3
- import { join } from 'node:path';
2
+ import type { FougereConfig } from '@fougere/core';
3
+ import { readdirSync, existsSync, mkdirSync, writeFileSync } from 'node:fs';
4
+ import { join, dirname } from 'node:path';
4
5
  import type { Plugin } from 'vite';
5
6
  import { RUNTIME_PACKAGES } from '@fougere/compiler';
6
7
  import { type Conventions, DEFAULT_CONVENTIONS } from '@fougere/core';
@@ -34,11 +35,71 @@ export interface FougereViteOptions {
34
35
  reserved?: string[];
35
36
  }
36
37
 
38
+ /**
39
+ * Where the facade types land — the same place `fougere build` puts them, so a host that runs the
40
+ * command and one that only starts a dev server read one file and not two.
41
+ */
42
+ const FACADE = '.fougere/facade.generated.ts';
43
+ const NAMES = '.fougere/names.generated.d.ts';
44
+
45
+ /**
46
+ * The facades this app serves, written when the dev server comes up — one export per address,
47
+ * carrying the handler that answers there as a type.
48
+ *
49
+ * TypeScript records nothing about what a function throws, so a client cannot know which
50
+ * refusals one operation answers without being told. Nuxt writes its own beside the two modules it
51
+ * already generates; every other host reaches this plugin, which is what makes the narrowing
52
+ * something you get from starting the app rather than from remembering a command.
53
+ *
54
+ * A scan that fails is not a failure of the dev server: the types fall back to every code there
55
+ * is, which is what a client had before this existed.
56
+ */
57
+ async function writeDoors(root: string): Promise<void> {
58
+ try {
59
+ const { scanProject, emitFacade, emitNames, frondAliases } = await import('@fougere/compiler');
60
+ const { setModuleLoader } = await import('@fougere/core/node');
61
+ const { resolveConventions } = await import('@fougere/core');
62
+ const { createJiti } = await import('jiti');
63
+
64
+ // The scan LOADS a frond's own sources, and `@fronds/user/entities/User.js` inside a handler
65
+ // has to resolve there for the same reason it does in a page. Without it `scanProject`
66
+ // throws `Cannot find module …/Post.js`, the catch below swallows it, and the module is
67
+ // silently never written — which is what happened until this line existed.
68
+ const jiti = createJiti(import.meta.url, {
69
+ interopDefault: true,
70
+ alias: await frondAliases(root, resolveConventions()),
71
+ });
72
+ setModuleLoader((filePath: string) => jiti.import(filePath) as Promise<Record<string, unknown>>);
73
+ const { loadConfig } = await import('@fougere/core/node');
74
+ const out = join(root, FACADE);
75
+ mkdirSync(dirname(out), { recursive: true });
76
+ const scan = await scanProject(root);
77
+ writeFileSync(out, emitFacade(scan, { outFile: out }));
78
+ // What a config may NAME, from the same scan: the sources are the config's own keys, which
79
+ // no scan can find, and a project without one states none.
80
+ const config = await loadConfig(root).catch(() => ({}) as FougereConfig);
81
+ writeFileSync(join(root, NAMES), emitNames(scan, { sources: Object.keys(config.sources ?? {}) }));
82
+ } catch { /* a host with no fronds, or a scan that could not run */ }
83
+ }
84
+
37
85
  export function fougere(options: FougereViteOptions = {}): Plugin {
38
86
  const external = [...new Set([...RUNTIME_PACKAGES, ...(options.external ?? [])])];
87
+ // Where `config` said the project is, so `buildStart` writes beside the alias it set.
88
+ let root = process.cwd();
39
89
 
40
90
  return {
41
91
  name: 'fougere',
92
+ configureServer(server: { config?: { root?: string } }) {
93
+ void writeDoors(server.config?.root ?? root);
94
+ },
95
+ /**
96
+ * AWAITED, and that is the whole point: the bundler resolves `@fronds/facade` right after
97
+ * this hook, and a fire-and-forget write loses the race — `Could not load
98
+ * .fougere/facade.generated.ts`, measured on `demos/react-router-blog`.
99
+ */
100
+ async buildStart() {
101
+ await writeDoors(root);
102
+ },
42
103
  /** `order. */
43
104
  config: {
44
105
  order: 'post',
@@ -46,6 +107,19 @@ export function fougere(options: FougereViteOptions = {}): Plugin {
46
107
  config.ssr ??= {};
47
108
  config.ssr.external = [...new Set([...(config.ssr.external ?? []), ...external])];
48
109
 
110
+ // A page IMPORTS its facade, so the alias sits beside the file that declares them: a
111
+ // project that never generated it fails to resolve rather than losing its types in
112
+ // silence.
113
+ root = config.root ?? root;
114
+ config.resolve ??= {};
115
+ // Vite admits BOTH shapes and a host picks one: SvelteKit states an array of
116
+ // `{ find, replacement }`, and spreading an object over it made rolldown refuse the
117
+ // whole plugin — `StringExpected … on BindingViteAliasPluginAlias.replacement`.
118
+ const facade = join(root, FACADE);
119
+ config.resolve.alias = Array.isArray(config.resolve.alias)
120
+ ? [...config.resolve.alias, { find: '@fronds/facade', replacement: facade }]
121
+ : { ...config.resolve.alias, '@fronds/facade': facade };
122
+
49
123
  if (options.keepClassNames === false) return;
50
124
 
51
125
  const reserved = [...(options.reserved ?? []), ...entityNamesIn(config.root ?? process.cwd())];