|
1 | 1 | import type { Plugin, ViteDevServer } from "vite"; |
2 | | -import type { ResolvedFreshViteConfig } from "../utils.ts"; |
3 | | -import { crawlFsItem } from "fresh/internal-dev"; |
| 2 | +import { pathWithRoot, type ResolvedFreshViteConfig } from "../utils.ts"; |
| 3 | +import { crawlFsItem, specToName } from "fresh/internal-dev"; |
| 4 | +import * as path from "@std/path"; |
4 | 5 |
|
5 | | -export function clientSnapshot(options: ResolvedFreshViteConfig): Plugin { |
| 6 | +export function clientSnapshot(options: ResolvedFreshViteConfig): Plugin[] { |
6 | 7 | const modName = "fresh:client-snapshot"; |
7 | 8 |
|
8 | 9 | const islands = new Set<string>(); |
9 | 10 | let server: ViteDevServer | undefined; |
10 | 11 | let isDev = false; |
11 | 12 |
|
12 | | - return { |
13 | | - name: "fresh:client-snapshot", |
14 | | - applyToEnvironment(env) { |
15 | | - return env.name === "client"; |
16 | | - }, |
17 | | - config(_, env) { |
18 | | - isDev = env.command === "serve"; |
19 | | - |
20 | | - return { |
21 | | - environments: { |
22 | | - client: { |
23 | | - build: { |
24 | | - rollupOptions: { |
25 | | - input: { |
26 | | - "client-snapshot": "fresh:client-snapshot", |
| 13 | + const entryToIsland = new Map<string, string>(); |
| 14 | + |
| 15 | + return [ |
| 16 | + { |
| 17 | + name: "fresh:client-snapshot", |
| 18 | + applyToEnvironment(env) { |
| 19 | + return env.name === "client"; |
| 20 | + }, |
| 21 | + |
| 22 | + async config(cfg, env) { |
| 23 | + isDev = env.command === "serve"; |
| 24 | + |
| 25 | + const cwd = Deno.cwd(); |
| 26 | + |
| 27 | + const result = await crawlFsItem({ |
| 28 | + islandDir: pathWithRoot(options.islandsDir, cfg.root ?? cwd), |
| 29 | + routeDir: pathWithRoot(options.routeDir, cfg.root ?? cwd), |
| 30 | + ignore: options.ignore, |
| 31 | + }); |
| 32 | + |
| 33 | + const input: Record<string, string> = {}; |
| 34 | + |
| 35 | + if (isDev) { |
| 36 | + input["client-snapshot"] = "fresh:client-snapshot"; |
| 37 | + } |
| 38 | + |
| 39 | + for (let i = 0; i < result.islands.length; i++) { |
| 40 | + const filePath = result.islands[i]; |
| 41 | + islands.add(filePath); |
| 42 | + |
| 43 | + if (!isDev) { |
| 44 | + const specName = specToName(filePath); |
| 45 | + const name = options.namer.getUniqueName(specName); |
| 46 | + |
| 47 | + entryToIsland.set(name, filePath); |
| 48 | + input[`fresh-island::${name}`] = `fresh-client-island::${name}`; |
| 49 | + } |
| 50 | + } |
| 51 | + |
| 52 | + return { |
| 53 | + environments: { |
| 54 | + client: { |
| 55 | + build: { |
| 56 | + rollupOptions: { |
| 57 | + input, |
27 | 58 | }, |
28 | 59 | }, |
29 | 60 | }, |
30 | 61 | }, |
31 | | - }, |
32 | | - }; |
33 | | - }, |
34 | | - configResolved() { |
35 | | - options.islandSpecifiers.forEach((_name, spec) => { |
36 | | - islands.add(spec); |
37 | | - }); |
38 | | - }, |
39 | | - async buildStart() { |
40 | | - const result = await crawlFsItem({ |
41 | | - islandDir: options.islandsDir, |
42 | | - routeDir: options.routeDir, |
43 | | - ignore: options.ignore, |
44 | | - }); |
45 | | - |
46 | | - for (let i = 0; i < result.islands.length; i++) { |
47 | | - const filePath = result.islands[i]; |
48 | | - islands.add(filePath); |
49 | | - } |
50 | | - }, |
51 | | - configureServer(devServer) { |
52 | | - server = devServer; |
53 | | - }, |
54 | | - resolveId: { |
55 | | - filter: { |
56 | | - id: /fresh:client-snapshot/, |
| 62 | + }; |
57 | 63 | }, |
58 | | - handler(id) { |
59 | | - if (id === modName) { |
60 | | - return `\0${modName}`; |
| 64 | + configResolved(cfg) { |
| 65 | + for (const [name, spec] of entryToIsland.entries()) { |
| 66 | + const full = pathWithRoot(spec, cfg.root); |
| 67 | + entryToIsland.set(name, full); |
61 | 68 | } |
62 | 69 | }, |
63 | | - }, |
64 | | - load: { |
65 | | - filter: { |
66 | | - id: /\0fresh:client-snapshot/, |
| 70 | + options(opts) { |
| 71 | + options.islandSpecifiers.forEach((_name, spec) => { |
| 72 | + islands.add(spec); |
| 73 | + |
| 74 | + if (!isDev) { |
| 75 | + const specName = specToName(spec); |
| 76 | + const name = options.namer.getUniqueName(specName); |
| 77 | + entryToIsland.set(name, spec); |
| 78 | + |
| 79 | + // deno-lint-ignore no-explicit-any |
| 80 | + (opts.input as any)[`fresh-island::${name}`] = |
| 81 | + `fresh-client-island::${name}`; |
| 82 | + } |
| 83 | + }); |
67 | 84 | }, |
68 | | - handler() { |
69 | | - const imports = Array.from(islands.keys()).map((file, i) => { |
70 | | - return `export const mod_${i} = await import(${ |
71 | | - JSON.stringify(file) |
72 | | - });`; |
73 | | - }).join("\n"); |
74 | | - |
75 | | - if (isDev && server !== undefined) { |
76 | | - const mod = server.environments.ssr.moduleGraph.getModuleById( |
77 | | - "\0fresh:server-snapshot", |
78 | | - ); |
79 | | - if (mod) { |
80 | | - server.environments.ssr.moduleGraph.invalidateModule(mod); |
| 85 | + configureServer(devServer) { |
| 86 | + server = devServer; |
| 87 | + |
| 88 | + server.watcher.on("add", (filePath) => { |
| 89 | + if (!isIslandPath(options, filePath)) return; |
| 90 | + |
| 91 | + islands.add(filePath); |
| 92 | + |
| 93 | + invalidateSnapshots(server!); |
| 94 | + }); |
| 95 | + server.watcher.on("unlink", (filePath) => { |
| 96 | + if (!isIslandPath(options, filePath)) return; |
| 97 | + |
| 98 | + islands.delete(filePath); |
| 99 | + |
| 100 | + invalidateSnapshots(server!); |
| 101 | + }); |
| 102 | + }, |
| 103 | + resolveId: { |
| 104 | + filter: { |
| 105 | + id: /fresh:client-snapshot/, |
| 106 | + }, |
| 107 | + handler(id) { |
| 108 | + if (id === modName) { |
| 109 | + return `\0${modName}`; |
| 110 | + } |
| 111 | + }, |
| 112 | + }, |
| 113 | + load: { |
| 114 | + filter: { |
| 115 | + id: /\0fresh:client-snapshot/, |
| 116 | + }, |
| 117 | + handler() { |
| 118 | + const imports = Array.from(islands.keys()).map((file, i) => { |
| 119 | + return `export const mod_${i} = await import(${ |
| 120 | + JSON.stringify(file) |
| 121 | + });`; |
| 122 | + }).join("\n"); |
| 123 | + |
| 124 | + if (isDev && server !== undefined) { |
| 125 | + const mod = server.environments.ssr.moduleGraph.getModuleById( |
| 126 | + "\0fresh:server-snapshot", |
| 127 | + ); |
| 128 | + if (mod) { |
| 129 | + server.environments.ssr.moduleGraph.invalidateModule(mod); |
| 130 | + } |
81 | 131 | } |
82 | | - } |
83 | 132 |
|
84 | | - return `${imports} |
| 133 | + return `${imports} |
85 | 134 | if (import.meta.hot) { |
86 | 135 | import.meta.hot.accept(() => { |
87 | 136 | console.log("accepting client-snapshot") |
88 | 137 | }); |
89 | 138 | } |
90 | 139 | `; |
| 140 | + }, |
| 141 | + }, |
| 142 | + }, |
| 143 | + { |
| 144 | + name: "fresh:client-island", |
| 145 | + resolveId: { |
| 146 | + filter: { |
| 147 | + id: /^fresh-client-island::/, |
| 148 | + }, |
| 149 | + handler(id) { |
| 150 | + const name = id.slice("fresh-client-island::".length); |
| 151 | + const full = entryToIsland.get(name); |
| 152 | + return full; |
| 153 | + }, |
91 | 154 | }, |
92 | 155 | }, |
93 | | - }; |
| 156 | + ]; |
| 157 | +} |
| 158 | + |
| 159 | +function isIslandPath( |
| 160 | + options: ResolvedFreshViteConfig, |
| 161 | + filePath: string, |
| 162 | +): boolean { |
| 163 | + const relIsland = path.relative(options.islandsDir, filePath); |
| 164 | + if (!relIsland.startsWith("..")) return true; |
| 165 | + |
| 166 | + const relRoutes = path.relative(options.routeDir, filePath); |
| 167 | + |
| 168 | + if (!relIsland.startsWith("..") && relRoutes.includes("(_islands)")) { |
| 169 | + return true; |
| 170 | + } |
| 171 | + return false; |
| 172 | +} |
| 173 | + |
| 174 | +function invalidateSnapshots(server: ViteDevServer) { |
| 175 | + const client = server.environments.client.moduleGraph.getModuleById( |
| 176 | + "\0fresh:client-snapshot", |
| 177 | + ); |
| 178 | + if (client !== undefined) { |
| 179 | + server.environments.client.moduleGraph.invalidateModule(client); |
| 180 | + } |
| 181 | + |
| 182 | + const ssr = server.environments.ssr.moduleGraph.getModuleById( |
| 183 | + "\0fresh:server-snapshot", |
| 184 | + ); |
| 185 | + if (ssr !== undefined) { |
| 186 | + server.environments.ssr.moduleGraph.invalidateModule(ssr); |
| 187 | + } |
94 | 188 | } |
0 commit comments