@nodefony/frontend 10.0.0-alpha.1
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 +544 -0
- package/README.md +338 -0
- package/dist/_virtual/_@oxc-project_runtime@0.148.0/helpers/esm/decorate.js +9 -0
- package/dist/_virtual/_@oxc-project_runtime@0.148.0/helpers/esm/decorateMetadata.js +6 -0
- package/dist/index.js +63 -0
- package/dist/nodefony/command/frontend-build.js +68 -0
- package/dist/nodefony/command/frontend-dev.js +31 -0
- package/dist/nodefony/command/frontend-status.js +40 -0
- package/dist/nodefony/config/config.js +65 -0
- package/dist/nodefony/config/defineModuleConfig.js +34 -0
- package/dist/nodefony/interfaces/IFrontBuilder.js +1 -0
- package/dist/nodefony/interfaces/IFrontPreset.js +1 -0
- package/dist/nodefony/interfaces/IFrontendService.js +1 -0
- package/dist/nodefony/interfaces/IViteSupervisor.js +1 -0
- package/dist/nodefony/interfaces/index.js +1 -0
- package/dist/nodefony/service/FrontendService.js +708 -0
- package/dist/nodefony/service/ViteConfigGenerator.js +139 -0
- package/dist/nodefony/service/ViteProcessSupervisor.js +589 -0
- package/dist/nodefony/src/FrontendAdminApi.js +113 -0
- package/dist/nodefony/src/builders/ViteBuilder.js +75 -0
- package/dist/nodefony/src/errors/FrontendError.js +50 -0
- package/dist/nodefony/src/isolationGroups.js +116 -0
- package/dist/nodefony/src/presets/angular-vite.js +27 -0
- package/dist/nodefony/src/presets/react19-vite.js +26 -0
- package/dist/nodefony/src/presets/svelte5-vite.js +37 -0
- package/dist/nodefony/src/presets/vanilla-vite.js +17 -0
- package/dist/nodefony/src/presets/vue3-vite.js +23 -0
- package/dist/nodefony/src/remoteDev.js +157 -0
- package/dist/nodefony/src/template/TemplateHelper.js +255 -0
- package/dist/types/index.d.ts +51 -0
- package/dist/types/nodefony/command/frontend-build.d.ts +17 -0
- package/dist/types/nodefony/command/frontend-dev.d.ts +12 -0
- package/dist/types/nodefony/command/frontend-status.d.ts +14 -0
- package/dist/types/nodefony/config/config.d.ts +38 -0
- package/dist/types/nodefony/config/defineModuleConfig.d.ts +29 -0
- package/dist/types/nodefony/interfaces/IFrontBuilder.d.ts +69 -0
- package/dist/types/nodefony/interfaces/IFrontPreset.d.ts +26 -0
- package/dist/types/nodefony/interfaces/IFrontendService.d.ts +77 -0
- package/dist/types/nodefony/interfaces/IViteSupervisor.d.ts +56 -0
- package/dist/types/nodefony/interfaces/index.d.ts +4 -0
- package/dist/types/nodefony/service/FrontendService.d.ts +230 -0
- package/dist/types/nodefony/service/ViteConfigGenerator.d.ts +66 -0
- package/dist/types/nodefony/service/ViteProcessSupervisor.d.ts +214 -0
- package/dist/types/nodefony/src/FrontendAdminApi.d.ts +63 -0
- package/dist/types/nodefony/src/builders/ViteBuilder.d.ts +17 -0
- package/dist/types/nodefony/src/errors/FrontendError.d.ts +34 -0
- package/dist/types/nodefony/src/isolationGroups.d.ts +89 -0
- package/dist/types/nodefony/src/presets/angular-vite.d.ts +15 -0
- package/dist/types/nodefony/src/presets/react19-vite.d.ts +9 -0
- package/dist/types/nodefony/src/presets/svelte5-vite.d.ts +13 -0
- package/dist/types/nodefony/src/presets/vanilla-vite.d.ts +9 -0
- package/dist/types/nodefony/src/presets/vue3-vite.d.ts +11 -0
- package/dist/types/nodefony/src/remoteDev.d.ts +107 -0
- package/dist/types/nodefony/src/template/TemplateHelper.d.ts +99 -0
- package/docs/index.md +925 -0
- package/package.json +80 -0
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { FrontendPresetUnknownError } from "../errors/FrontendError.js";
|
|
2
|
+
import react19Preset from "../presets/react19-vite.js";
|
|
3
|
+
import vue3Preset from "../presets/vue3-vite.js";
|
|
4
|
+
import angularPreset from "../presets/angular-vite.js";
|
|
5
|
+
import vanillaPreset from "../presets/vanilla-vite.js";
|
|
6
|
+
import svelte5Preset from "../presets/svelte5-vite.js";
|
|
7
|
+
import path from "node:path";
|
|
8
|
+
//#region nodefony/src/builders/ViteBuilder.ts
|
|
9
|
+
/**
|
|
10
|
+
* Construit la config Vite finale à partir des entrées résolues et des presets.
|
|
11
|
+
*
|
|
12
|
+
* Le builder ne lance JAMAIS Vite — il fournit uniquement la config. Le
|
|
13
|
+
* démarrage est délégué au superviseur (child_process ou in-proc).
|
|
14
|
+
*/
|
|
15
|
+
var ViteBuilder = class {
|
|
16
|
+
presets = /* @__PURE__ */ new Map();
|
|
17
|
+
constructor() {
|
|
18
|
+
this.registerPreset(react19Preset);
|
|
19
|
+
this.registerPreset(vue3Preset);
|
|
20
|
+
this.registerPreset(angularPreset);
|
|
21
|
+
this.registerPreset(vanillaPreset);
|
|
22
|
+
this.registerPreset(svelte5Preset);
|
|
23
|
+
}
|
|
24
|
+
listPresets() {
|
|
25
|
+
return Array.from(this.presets.values());
|
|
26
|
+
}
|
|
27
|
+
getPreset(type) {
|
|
28
|
+
return this.presets.get(type);
|
|
29
|
+
}
|
|
30
|
+
registerPreset(preset) {
|
|
31
|
+
this.presets.set(preset.type, preset);
|
|
32
|
+
}
|
|
33
|
+
async buildViteConfig(entries, mode, assetBaseUrl = "") {
|
|
34
|
+
if (entries.length === 0) return { mode };
|
|
35
|
+
const usedPresets = /* @__PURE__ */ new Set();
|
|
36
|
+
for (const e of entries) usedPresets.add(e.type);
|
|
37
|
+
const plugins = [];
|
|
38
|
+
const optimizeInclude = [];
|
|
39
|
+
for (const type of usedPresets) {
|
|
40
|
+
const preset = this.presets.get(type);
|
|
41
|
+
if (!preset) throw new FrontendPresetUnknownError(type);
|
|
42
|
+
plugins.push(...await preset.buildPlugins());
|
|
43
|
+
optimizeInclude.push(...preset.optimizeDepsInclude);
|
|
44
|
+
}
|
|
45
|
+
const input = {};
|
|
46
|
+
for (const e of entries) input[e.entryName] = path.resolve(e.root, e.entryFile);
|
|
47
|
+
const root = entries[0].root;
|
|
48
|
+
const outDir = entries[0].outDir;
|
|
49
|
+
const base = mode === "production" ? assetBaseUrl + entries[0].publicPath : void 0;
|
|
50
|
+
const dedupe = [];
|
|
51
|
+
if (usedPresets.has("react19")) dedupe.push("react", "react-dom");
|
|
52
|
+
if (usedPresets.has("vue3")) dedupe.push("vue");
|
|
53
|
+
if (usedPresets.has("angular")) dedupe.push("@angular/core", "@angular/common", "@angular/platform-browser");
|
|
54
|
+
return {
|
|
55
|
+
mode,
|
|
56
|
+
...base ? { base } : {},
|
|
57
|
+
root,
|
|
58
|
+
plugins,
|
|
59
|
+
...dedupe.length ? { resolve: { dedupe } } : {},
|
|
60
|
+
optimizeDeps: { include: optimizeInclude },
|
|
61
|
+
build: {
|
|
62
|
+
outDir,
|
|
63
|
+
manifest: true,
|
|
64
|
+
rollupOptions: { input },
|
|
65
|
+
emptyOutDir: true
|
|
66
|
+
},
|
|
67
|
+
server: {
|
|
68
|
+
strictPort: false,
|
|
69
|
+
cors: true
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
//#endregion
|
|
75
|
+
export { ViteBuilder, ViteBuilder as default };
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
//#region nodefony/src/errors/FrontendError.ts
|
|
2
|
+
/**
|
|
3
|
+
* Erreur de base pour @nodefony/frontend.
|
|
4
|
+
*
|
|
5
|
+
* - `code` : identifiant machine, consommé par Vision et l'audit-logger.
|
|
6
|
+
* - `context` : payload structuré pour le PDU syslog.
|
|
7
|
+
*/
|
|
8
|
+
var FrontendError = class extends Error {
|
|
9
|
+
code;
|
|
10
|
+
context;
|
|
11
|
+
constructor(message, code, context) {
|
|
12
|
+
super(message);
|
|
13
|
+
this.code = code;
|
|
14
|
+
this.context = context;
|
|
15
|
+
this.name = "FrontendError";
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
/** Preset inconnu — module a déclaré `type: "foo"` non enregistré. */
|
|
19
|
+
var FrontendPresetUnknownError = class extends FrontendError {
|
|
20
|
+
constructor(type) {
|
|
21
|
+
super(`Unknown frontend preset: "${type}"`, "PRESET_UNKNOWN", { type });
|
|
22
|
+
this.name = "FrontendPresetUnknownError";
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
/** Echec de démarrage de Vite (spawn raté, port occupé, config invalide). */
|
|
26
|
+
var FrontendSupervisorStartError = class extends FrontendError {
|
|
27
|
+
constructor(reason, cause) {
|
|
28
|
+
super(`Vite supervisor failed to start: ${reason}`, "SUPERVISOR_START", { reason });
|
|
29
|
+
this.name = "FrontendSupervisorStartError";
|
|
30
|
+
if (cause instanceof Error) this.cause = cause;
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
/** Au moins un bundle a échoué au build production (Vite). */
|
|
34
|
+
var FrontendBuildError = class extends FrontendError {
|
|
35
|
+
failures;
|
|
36
|
+
constructor(failures) {
|
|
37
|
+
super(`Frontend production build failed for ${failures.length} bundle(s): ${failures.map((f) => f.entryName).join(", ")}`, "BUILD_FAILED", { failures });
|
|
38
|
+
this.failures = failures;
|
|
39
|
+
this.name = "FrontendBuildError";
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
/** Aucune entrée front trouvée alors qu'on tente de démarrer le dev server. */
|
|
43
|
+
var FrontendNoEntriesError = class extends FrontendError {
|
|
44
|
+
constructor() {
|
|
45
|
+
super(`No frontend entries declared. Add { frontend: { type, entry } } in your module config.`, "NO_ENTRIES");
|
|
46
|
+
this.name = "FrontendNoEntriesError";
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
//#endregion
|
|
50
|
+
export { FrontendBuildError, FrontendError, FrontendNoEntriesError, FrontendPresetUnknownError, FrontendSupervisorStartError };
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
//#region nodefony/src/isolationGroups.ts
|
|
2
|
+
/**
|
|
3
|
+
* Famille d'isolation Vite d'un type de preset frontend.
|
|
4
|
+
*
|
|
5
|
+
* Les presets d'une même famille **cohabitent dans une seule instance Vite** ;
|
|
6
|
+
* les familles distinctes tournent dans des **process Vite séparés** (multi-
|
|
7
|
+
* supervisor).
|
|
8
|
+
*
|
|
9
|
+
* **Le motif est toujours le même : un plugin qui transforme ce qui ne lui
|
|
10
|
+
* appartient pas.** Deux familles le paient aujourd'hui.
|
|
11
|
+
*
|
|
12
|
+
* Angular : son plugin (`@analogjs/vite-plugin-angular`) transforme **tout**
|
|
13
|
+
* fichier `.ts` du serveur de développement — y compris ceux des autres bundles
|
|
14
|
+
* (ex. stores MobX à décorateurs). Mélangé aux autres, il throw sur des fichiers
|
|
15
|
+
* hors de son tsconfig → erreurs de compilation + boucle de rechargement.
|
|
16
|
+
*
|
|
17
|
+
* Vue : le compilateur de composants monofichiers sert le bloc script sous un
|
|
18
|
+
* identifiant qui se TERMINE par `.ts`
|
|
19
|
+
* (`App.vue?vue&type=script&setup=true&lang.ts`). Le filtre de
|
|
20
|
+
* `@vitejs/plugin-react` y mord — il est élargi aux identifiants portant une
|
|
21
|
+
* query — et injecte son `$RefreshSig$()`, que rien ne définit sur une page sans
|
|
22
|
+
* préambule React : la vitrine Vue ne se montait plus du tout
|
|
23
|
+
* (`ReferenceError: $RefreshSig$ is not defined`).
|
|
24
|
+
*
|
|
25
|
+
* ⚠️ Il a longtemps été écrit ici que « React / Vue / vanilla ciblent des
|
|
26
|
+
* extensions disjointes ». C'était faux, et c'est ce qui a fait chercher
|
|
27
|
+
* ailleurs. Passer `exclude: [/\.vue/]` à `@vitejs/plugin-react` **ne suffit
|
|
28
|
+
* pas** : mesuré sur Vite 8.2.2, l'injection persiste jusqu'à `exclude: [/./]`
|
|
29
|
+
* inclus — l'option ne borne pas ce chemin. Seule l'isolation en process
|
|
30
|
+
* distinct l'arrête.
|
|
31
|
+
*
|
|
32
|
+
* Svelte, lui, cohabite sans dommage (constaté) : son bloc script n'est pas
|
|
33
|
+
* servi sous un identifiant qui finit par `.ts`.
|
|
34
|
+
*
|
|
35
|
+
* @param type - type de preset déclaré par le module (`react19`, `vue3`,
|
|
36
|
+
* `angular`, `vanilla`, …).
|
|
37
|
+
* @returns la clé de famille d'isolation (`"default"` pour tout ce qui peut
|
|
38
|
+
* partager une instance, une clé dédiée pour ce qui doit être isolé).
|
|
39
|
+
*/
|
|
40
|
+
function isolationGroup(type) {
|
|
41
|
+
switch (type) {
|
|
42
|
+
case "angular": return "angular";
|
|
43
|
+
case "vue3": return "vue";
|
|
44
|
+
default: return "default";
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Famille servie sur le port de base (`devPort`). Les autres familles prennent
|
|
49
|
+
* les blocs de ports suivants. Garder `default` ici garantit que l'instance
|
|
50
|
+
* principale (React/Vue/Studio) reste sur le port habituel (5173).
|
|
51
|
+
*/
|
|
52
|
+
const PRIMARY_FAMILY = "default";
|
|
53
|
+
/**
|
|
54
|
+
* Ordonne les familles de façon déterministe : la famille primaire d'abord
|
|
55
|
+
* (port de base), puis les autres triées alphabétiquement. L'ordre fixe le
|
|
56
|
+
* port attribué à chaque famille — il doit être stable entre deux démarrages.
|
|
57
|
+
*
|
|
58
|
+
* @param families - familles présentes (clés de regroupement des entries).
|
|
59
|
+
* @returns familles ordonnées, `PRIMARY_FAMILY` en tête s'il est présent.
|
|
60
|
+
*/
|
|
61
|
+
function orderFamilies(families) {
|
|
62
|
+
const others = families.filter((f) => f !== PRIMARY_FAMILY).sort();
|
|
63
|
+
return families.includes("default") ? [PRIMARY_FAMILY, ...others] : others;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Plan d'allocation des ports : un **bloc disjoint** par famille. Chaque famille
|
|
67
|
+
* réserve `portRetryAttempts + 1` ports consécutifs → le port-retry d'une
|
|
68
|
+
* instance (sur EADDRINUSE) ne peut jamais empiéter sur le bloc d'une autre
|
|
69
|
+
* famille. La famille primaire (index 0) garde `devPort` (le port habituel).
|
|
70
|
+
*
|
|
71
|
+
* @param devPort - port de base de la première famille (ex. 5173).
|
|
72
|
+
* @param families - familles présentes ; réordonnées en interne via `orderFamilies`.
|
|
73
|
+
* @param portRetryAttempts - tentatives de port-retry par instance (bloc = +1).
|
|
74
|
+
* @returns map ordonnée `famille → port de base de son bloc`.
|
|
75
|
+
*/
|
|
76
|
+
function familyPortPlan(devPort, families, portRetryAttempts) {
|
|
77
|
+
const block = portRetryAttempts + 1;
|
|
78
|
+
const plan = /* @__PURE__ */ new Map();
|
|
79
|
+
orderFamilies(families).forEach((family, index) => {
|
|
80
|
+
plan.set(family, devPort + index * block);
|
|
81
|
+
});
|
|
82
|
+
return plan;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Bloc de ports de chaque famille : tous les ports que SON instance Vite peut
|
|
86
|
+
* prendre, port-retry compris.
|
|
87
|
+
*
|
|
88
|
+
* Le plan seul ne dit que le port ESPÉRÉ ; sur `EADDRINUSE` le superviseur
|
|
89
|
+
* glisse dans son bloc. Un CSP construit sur le seul port espéré laisse la page
|
|
90
|
+
* sans rechargement à chaud dès que le port de base est pris — et il l'est dès
|
|
91
|
+
* qu'un second projet tourne sur la machine.
|
|
92
|
+
*
|
|
93
|
+
* Rendu par famille, et non à plat : le consommateur remplace le bloc d'une
|
|
94
|
+
* famille par son port RÉEL dès qu'il est connu. Déclarer les douze ports d'un
|
|
95
|
+
* poste à trois familles coûte un en-tête CSP de ~8 Ko sur chaque réponse
|
|
96
|
+
* (mesuré) — la plage est le prix d'une incertitude, elle ne doit pas survivre
|
|
97
|
+
* à sa levée.
|
|
98
|
+
*
|
|
99
|
+
* Dérivé du plan plutôt que recalculé depuis `devPort` : la règle des blocs
|
|
100
|
+
* disjoints n'est écrite qu'à un endroit, `familyPortPlan`.
|
|
101
|
+
*
|
|
102
|
+
* @param plan - `famille → port de base`, tel que rendu par `familyPortPlan`.
|
|
103
|
+
* @param portRetryAttempts - tentatives de port-retry par instance (bloc = +1).
|
|
104
|
+
* @returns `famille → ports du bloc`, croissants.
|
|
105
|
+
*/
|
|
106
|
+
function familyPortBlocks(plan, portRetryAttempts) {
|
|
107
|
+
const blocks = /* @__PURE__ */ new Map();
|
|
108
|
+
for (const [family, base] of plan) {
|
|
109
|
+
const ports = [];
|
|
110
|
+
for (let i = 0; i <= portRetryAttempts; i++) ports.push(base + i);
|
|
111
|
+
blocks.set(family, ports);
|
|
112
|
+
}
|
|
113
|
+
return blocks;
|
|
114
|
+
}
|
|
115
|
+
//#endregion
|
|
116
|
+
export { PRIMARY_FAMILY, familyPortBlocks, familyPortPlan, isolationGroup, orderFamilies };
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
//#region nodefony/src/presets/angular-vite.ts
|
|
2
|
+
/**
|
|
3
|
+
* Preset Angular (17+ standalone) + Vite via `@analogjs/vite-plugin-angular`.
|
|
4
|
+
*
|
|
5
|
+
* Charge le plugin paresseusement : aucune dépendance Angular tant qu'aucune
|
|
6
|
+
* entrée `angular` n'est déclarée. Comme React/Vue, aucun preamble HTML n'est
|
|
7
|
+
* requis — `bootstrapApplication(AppComponent)` dans le point d'entrée suffit.
|
|
8
|
+
*
|
|
9
|
+
* ⚠️ Contrairement à `.vue`/`.tsx`, le plugin Angular transforme les `.ts`
|
|
10
|
+
* (extension non dédiée). Le scoping se fait via le `tsconfig` de l'app Angular
|
|
11
|
+
* (passé par `ViteConfigGenerator`) dont le `include` ne couvre que le frontend
|
|
12
|
+
* Angular — les `main.ts` des autres bundles (Vue) restent hors programme.
|
|
13
|
+
*/
|
|
14
|
+
const angularPreset = {
|
|
15
|
+
type: "angular",
|
|
16
|
+
extensions: [".ts", ".html"],
|
|
17
|
+
optimizeDepsInclude: [
|
|
18
|
+
"@angular/core",
|
|
19
|
+
"@angular/common",
|
|
20
|
+
"@angular/platform-browser"
|
|
21
|
+
],
|
|
22
|
+
async buildPlugins() {
|
|
23
|
+
return [(await import("@analogjs/vite-plugin-angular")).default()];
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
//#endregion
|
|
27
|
+
export { angularPreset as default };
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
//#region nodefony/src/presets/react19-vite.ts
|
|
2
|
+
/**
|
|
3
|
+
* Preset React 19 + Vite.
|
|
4
|
+
*
|
|
5
|
+
* Charge `@vitejs/plugin-react` paresseusement : aucune dépendance
|
|
6
|
+
* sur le module si aucune entrée React n'est déclarée.
|
|
7
|
+
*/
|
|
8
|
+
const react19Preset = {
|
|
9
|
+
type: "react19",
|
|
10
|
+
extensions: [
|
|
11
|
+
".jsx",
|
|
12
|
+
".tsx",
|
|
13
|
+
".js",
|
|
14
|
+
".ts"
|
|
15
|
+
],
|
|
16
|
+
optimizeDepsInclude: [
|
|
17
|
+
"react",
|
|
18
|
+
"react-dom",
|
|
19
|
+
"react-dom/client"
|
|
20
|
+
],
|
|
21
|
+
async buildPlugins() {
|
|
22
|
+
return [(await import("@vitejs/plugin-react")).default({ jsxRuntime: "automatic" })];
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
//#endregion
|
|
26
|
+
export { react19Preset as default };
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
//#region nodefony/src/presets/svelte5-vite.ts
|
|
2
|
+
/**
|
|
3
|
+
* Preset Svelte 5 + Vite.
|
|
4
|
+
*
|
|
5
|
+
* Charge `@sveltejs/vite-plugin-svelte` paresseusement : aucune dépendance sur
|
|
6
|
+
* le module tant qu'aucune entrée Svelte n'est déclarée — c'est l'application
|
|
7
|
+
* qui porte `svelte` et le plugin en devDependencies. Comme Vue, Svelte n'exige
|
|
8
|
+
* aucun preamble HMR injecté côté serveur (`mount(App, { target })` suffit) ;
|
|
9
|
+
* il cohabite donc dans la famille d'isolation `default` (extensions `.svelte`
|
|
10
|
+
* disjointes de `.tsx`/`.vue`).
|
|
11
|
+
*/
|
|
12
|
+
const svelte5Preset = {
|
|
13
|
+
type: "svelte5",
|
|
14
|
+
extensions: [
|
|
15
|
+
".svelte",
|
|
16
|
+
".ts",
|
|
17
|
+
".js"
|
|
18
|
+
],
|
|
19
|
+
optimizeDepsInclude: ["svelte"],
|
|
20
|
+
async buildPlugins() {
|
|
21
|
+
const pkg = "@sveltejs/vite-plugin-svelte";
|
|
22
|
+
let mod;
|
|
23
|
+
try {
|
|
24
|
+
mod = await import(pkg);
|
|
25
|
+
} catch {
|
|
26
|
+
const [{ createRequire }, { pathToFileURL }, path] = await Promise.all([
|
|
27
|
+
import("node:module"),
|
|
28
|
+
import("node:url"),
|
|
29
|
+
import("node:path")
|
|
30
|
+
]);
|
|
31
|
+
mod = await import(pathToFileURL(createRequire(path.join(process.cwd(), "package.json")).resolve(pkg)).href);
|
|
32
|
+
}
|
|
33
|
+
return [mod.svelte()];
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
//#endregion
|
|
37
|
+
export { svelte5Preset as default };
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
//#region nodefony/src/presets/vanilla-vite.ts
|
|
2
|
+
/**
|
|
3
|
+
* Preset vanilla — TS/JS sans framework.
|
|
4
|
+
*
|
|
5
|
+
* Aucun plugin Vite, utile pour les modules qui n'ont besoin
|
|
6
|
+
* que de bundling ESM + HMR de base.
|
|
7
|
+
*/
|
|
8
|
+
const vanillaPreset = {
|
|
9
|
+
type: "vanilla",
|
|
10
|
+
extensions: [".ts", ".js"],
|
|
11
|
+
optimizeDepsInclude: [],
|
|
12
|
+
async buildPlugins() {
|
|
13
|
+
return [];
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
//#endregion
|
|
17
|
+
export { vanillaPreset as default };
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
//#region nodefony/src/presets/vue3-vite.ts
|
|
2
|
+
/**
|
|
3
|
+
* Preset Vue 3 + Vite.
|
|
4
|
+
*
|
|
5
|
+
* Charge `@vitejs/plugin-vue` paresseusement : aucune dépendance
|
|
6
|
+
* sur le module tant qu'aucune entrée Vue n'est déclarée. Contrairement
|
|
7
|
+
* à React, Vue ne nécessite aucun preamble HMR injecté côté serveur —
|
|
8
|
+
* `createApp(App).mount(...)` dans le point d'entrée suffit.
|
|
9
|
+
*/
|
|
10
|
+
const vue3Preset = {
|
|
11
|
+
type: "vue3",
|
|
12
|
+
extensions: [
|
|
13
|
+
".vue",
|
|
14
|
+
".ts",
|
|
15
|
+
".js"
|
|
16
|
+
],
|
|
17
|
+
optimizeDepsInclude: ["vue"],
|
|
18
|
+
async buildPlugins() {
|
|
19
|
+
return [(await import("@vitejs/plugin-vue")).default()];
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
//#endregion
|
|
23
|
+
export { vue3Preset as default };
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
//#region nodefony/src/remoteDev.ts
|
|
2
|
+
/**
|
|
3
|
+
* Dev déporté (P14.17) — calculs PURS d'origine publique du dev server Vite.
|
|
4
|
+
*
|
|
5
|
+
* Le problème résolu : `devHost` est une adresse d'ÉCOUTE ; l'origine que le
|
|
6
|
+
* NAVIGATEUR utilise peut être toute autre chose — un forwarder TLS (Codespaces,
|
|
7
|
+
* Gitpod), une passerelle de conteneur (`host.docker.internal`), un port remappé.
|
|
8
|
+
* Ce module dissocie les deux : il produit l'origine publique (assets, `base`
|
|
9
|
+
* Vite, WebSocket HMR) à partir d'un TEMPLATE (`{port}` substitué au port réel
|
|
10
|
+
* du spawn) — explicite (`frontend.publicOrigin`) ou détecté depuis
|
|
11
|
+
* l'environnement de la plateforme.
|
|
12
|
+
*
|
|
13
|
+
* Tout est pur et injectable (env en paramètre) : testable sans process, sans
|
|
14
|
+
* réseau, sur les trois plateformes.
|
|
15
|
+
*
|
|
16
|
+
* Formats VÉRIFIÉS (docs officielles + source Vite 8) :
|
|
17
|
+
* - Codespaces : `https://${CODESPACE_NAME}-${port}.${GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN}`
|
|
18
|
+
* (TLS terminé par le forwarder → WS HMR en `wss` sur 443).
|
|
19
|
+
* - Gitpod classic : `https://${port}-<hôte de GITPOD_WORKSPACE_URL>`.
|
|
20
|
+
* - Vite `server.allowedHosts` : IP et `localhost`/`*.localhost` TOUJOURS
|
|
21
|
+
* acceptés ; un préfixe `.` = le domaine ET tous ses sous-domaines.
|
|
22
|
+
* - VS Code Remote / dev containers / WSL2 : forwarding sur `localhost` →
|
|
23
|
+
* les défauts locaux suffisent, aucune détection requise.
|
|
24
|
+
*/
|
|
25
|
+
/** Placeholder substitué par le port réel du spawn dans un template d'origine. */
|
|
26
|
+
const PORT_PLACEHOLDER = "{port}";
|
|
27
|
+
/**
|
|
28
|
+
* Template d'origine publique : `scheme://hostTemplate[:portTemplate]`, sans
|
|
29
|
+
* chemin. `{port}` peut apparaître dans l'hôte (Codespaces/Gitpod : port encodé
|
|
30
|
+
* dans le sous-domaine) OU en position de port (`host:{port}`).
|
|
31
|
+
* Regex plutôt que `new URL()` : le parseur WHATWG refuse `{`/`}` dans un hôte.
|
|
32
|
+
*/
|
|
33
|
+
const ORIGIN_TEMPLATE_RE = /^(https?):\/\/([^/:\s]+)(?::(\d+|\{port\}))?$/;
|
|
34
|
+
/**
|
|
35
|
+
* Hôte utilisable par un NAVIGATEUR pour une adresse d'écoute donnée.
|
|
36
|
+
* `0.0.0.0`/`::` sont des adresses d'ÉCOUTE (toutes interfaces), pas des
|
|
37
|
+
* destinations : dérivées telles quelles dans une URL, elles donnent des
|
|
38
|
+
* `<script src="http://0.0.0.0:5173/…">` que les navigateurs refusent — et
|
|
39
|
+
* sous Windows, une CONNEXION vers `0.0.0.0` échoue aussi (health check).
|
|
40
|
+
*/
|
|
41
|
+
function browserReachableHost(listenHost) {
|
|
42
|
+
return listenHost === "0.0.0.0" || listenHost === "::" || listenHost === "[::]" || listenHost === "" ? "127.0.0.1" : listenHost;
|
|
43
|
+
}
|
|
44
|
+
/** Un template d'origine est-il syntaxiquement valide ? (autorité unique) */
|
|
45
|
+
function isValidOriginTemplate(template) {
|
|
46
|
+
return ORIGIN_TEMPLATE_RE.test(template);
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Origine RÉSOLUE (scheme + hôte + port), telle qu'elle sort du superviseur :
|
|
50
|
+
* `https://127.0.0.1:5173`, `https://x.app.github.dev` (port implicite),
|
|
51
|
+
* `http://[::1]:5173`. Pas de `{port}` ici — le template est déjà substitué.
|
|
52
|
+
*/
|
|
53
|
+
const RESOLVED_ORIGIN_RE = /^(https?:\/\/)(\[[^\]/]+\]|[^/:\s]+)(:\d+)?$/;
|
|
54
|
+
/**
|
|
55
|
+
* Rejoue une origine résolue sur un AUTRE nom d'hôte, en conservant le scheme
|
|
56
|
+
* et le port. Pure.
|
|
57
|
+
*
|
|
58
|
+
* C'est le cœur de la dérivation par requête : le scheme et le port sont ceux
|
|
59
|
+
* du serveur Vite (il écoute où il écoute), seul le NOM change — celui par
|
|
60
|
+
* lequel le client est arrivé. Une page servie sur `http://poste:5151` charge
|
|
61
|
+
* donc `https://poste:5173` si Vite est en TLS : le scheme ne se déduit JAMAIS
|
|
62
|
+
* de la page.
|
|
63
|
+
*
|
|
64
|
+
* `new URL()` est volontairement évité (une allocation par page rendue, pour
|
|
65
|
+
* une chaîne dont nous produisons nous-mêmes la grammaire).
|
|
66
|
+
*
|
|
67
|
+
* @param origin - origine résolue (`scheme://hôte[:port]`).
|
|
68
|
+
* @param hostname - nom d'hôte de remplacement, SANS port (`Context.domain`) ;
|
|
69
|
+
* une IPv6 est acceptée sous sa forme canonique entre crochets (`[::1]`).
|
|
70
|
+
* @returns l'origine réécrite, ou `null` si l'un des deux est inexploitable
|
|
71
|
+
* (l'appelant garde alors l'origine d'origine — jamais d'URL bancale émise).
|
|
72
|
+
*/
|
|
73
|
+
function originWithHostname(origin, hostname) {
|
|
74
|
+
const m = RESOLVED_ORIGIN_RE.exec(origin);
|
|
75
|
+
if (!m) return null;
|
|
76
|
+
if (!/^(\[[^\]/]+\]|[A-Za-z0-9._-]+)$/.test(hostname)) return null;
|
|
77
|
+
return `${m[1]}${hostname}${m[3] ?? ""}`;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Résout un template d'origine contre le port RÉEL du spawn. Pure.
|
|
81
|
+
*
|
|
82
|
+
* @returns origine + config HMR cliente, ou `null` si le template est invalide
|
|
83
|
+
* (l'appelant retombe sur la dérivation locale en l'ANNONÇANT — jamais en
|
|
84
|
+
* silence).
|
|
85
|
+
*/
|
|
86
|
+
function resolveOriginTemplate(template, port) {
|
|
87
|
+
const m = ORIGIN_TEMPLATE_RE.exec(template);
|
|
88
|
+
if (!m) return null;
|
|
89
|
+
const [, scheme, hostTemplate, portTemplate] = m;
|
|
90
|
+
const host = hostTemplate.replaceAll(PORT_PLACEHOLDER, String(port));
|
|
91
|
+
const explicitPort = portTemplate ? parseInt(portTemplate.replaceAll(PORT_PLACEHOLDER, String(port)), 10) : void 0;
|
|
92
|
+
const secure = scheme === "https";
|
|
93
|
+
return {
|
|
94
|
+
origin: `${scheme}://${host}${explicitPort !== void 0 ? `:${explicitPort}` : ""}`,
|
|
95
|
+
hmr: {
|
|
96
|
+
host,
|
|
97
|
+
clientPort: explicitPort ?? (secure ? 443 : 80),
|
|
98
|
+
protocol: secure ? "wss" : "ws"
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Motif `server.allowedHosts` couvrant TOUTES les origines qu'un template peut
|
|
104
|
+
* produire. `{port}` dans l'hôte → le sous-domaine varie avec le port : motif
|
|
105
|
+
* `.suffixe` (wildcard Vite = domaine + sous-domaines). Hôte fixe → verbatim.
|
|
106
|
+
*
|
|
107
|
+
* @returns le motif, ou `null` si template invalide ou hôte non exprimable
|
|
108
|
+
* (un `{port}` dans le DERNIER label n'a pas de suffixe à wildcarder).
|
|
109
|
+
*/
|
|
110
|
+
function allowedHostPatternForTemplate(template) {
|
|
111
|
+
const m = ORIGIN_TEMPLATE_RE.exec(template);
|
|
112
|
+
if (!m) return null;
|
|
113
|
+
const hostTemplate = m[2];
|
|
114
|
+
if (!hostTemplate.includes("{port}")) return hostTemplate;
|
|
115
|
+
const dot = hostTemplate.indexOf(".");
|
|
116
|
+
if (dot === -1 || hostTemplate.slice(dot + 1).includes("{port}")) return null;
|
|
117
|
+
return hostTemplate.slice(dot);
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Motif Vite `allowedHosts` équivalent d'un pattern `trustedHosts` http.
|
|
121
|
+
* `*.suffixe` (wildcard un-label de la barrière Host) → `.suffixe` (wildcard
|
|
122
|
+
* Vite). Un `*` ailleurs n'est pas exprimable chez Vite → `null` (l'hôte reste
|
|
123
|
+
* couvert par la barrière Nodefony ; Vite le refusera — motif à écrire en
|
|
124
|
+
* clair dans `trustedHosts` si le cas se présente).
|
|
125
|
+
*/
|
|
126
|
+
function viteAllowedHostFromPattern(pattern) {
|
|
127
|
+
if (!pattern.includes("*")) return pattern;
|
|
128
|
+
if (pattern.startsWith("*.") && !pattern.slice(2).includes("*")) return pattern.slice(1);
|
|
129
|
+
return null;
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Détecte un environnement de dev déporté depuis les variables de plateforme
|
|
133
|
+
* (variables qu'on ne possède pas — elles se lisent, ne se renomment pas).
|
|
134
|
+
* Ordre : Codespaces puis Gitpod (jamais les deux posées en pratique).
|
|
135
|
+
* Env local/VS Code Remote/WSL2 → `null` (les défauts locaux suffisent).
|
|
136
|
+
*/
|
|
137
|
+
function detectRemoteDev(env) {
|
|
138
|
+
const csName = env.CODESPACE_NAME;
|
|
139
|
+
const csDomain = env.GITHUB_CODESPACES_PORT_FORWARDING_DOMAIN;
|
|
140
|
+
if (csName && csDomain) return {
|
|
141
|
+
provider: "codespaces",
|
|
142
|
+
originTemplate: `https://${csName}-${PORT_PLACEHOLDER}.${csDomain}`
|
|
143
|
+
};
|
|
144
|
+
const gpUrl = env.GITPOD_WORKSPACE_URL;
|
|
145
|
+
if (gpUrl) try {
|
|
146
|
+
const host = new URL(gpUrl).hostname;
|
|
147
|
+
return {
|
|
148
|
+
provider: "gitpod",
|
|
149
|
+
originTemplate: `https://${PORT_PLACEHOLDER}-${host}`
|
|
150
|
+
};
|
|
151
|
+
} catch {
|
|
152
|
+
return null;
|
|
153
|
+
}
|
|
154
|
+
return null;
|
|
155
|
+
}
|
|
156
|
+
//#endregion
|
|
157
|
+
export { PORT_PLACEHOLDER, allowedHostPatternForTemplate, browserReachableHost, detectRemoteDev, isValidOriginTemplate, originWithHostname, resolveOriginTemplate, viteAllowedHostFromPattern };
|