@inlang/paraglide-js 2.28.0 → 2.29.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/dist/bundler-plugins/unplugin.d.ts +4 -1
- package/dist/bundler-plugins/unplugin.js +55 -12
- package/dist/bundler-plugins/unplugin.js.map +1 -1
- package/dist/bundler-plugins/unplugin.test.js +156 -0
- package/dist/bundler-plugins/unplugin.test.js.map +1 -1
- package/dist/bundler-plugins/vite-error-response.test.d.ts +1 -0
- package/dist/bundler-plugins/vite-error-response.test.js +171 -0
- package/dist/bundler-plugins/vite-error-response.test.js.map +1 -0
- package/dist/bundler-plugins/vite.d.ts +8 -0
- package/dist/bundler-plugins/vite.js +14 -5
- package/dist/bundler-plugins/vite.js.map +1 -1
- package/dist/compiler/compile-bundle.js +58 -12
- package/dist/compiler/compile-bundle.js.map +1 -1
- package/dist/compiler/compile-bundle.test.js +208 -26
- package/dist/compiler/compile-bundle.test.js.map +1 -1
- package/dist/compiler/compile-lifecycle.test.js +83 -0
- package/dist/compiler/compile-lifecycle.test.js.map +1 -1
- package/dist/compiler/compile-message.d.ts +9 -0
- package/dist/compiler/compile-message.js +29 -12
- package/dist/compiler/compile-message.js.map +1 -1
- package/dist/compiler/compile-project.test.js +157 -0
- package/dist/compiler/compile-project.test.js.map +1 -1
- package/dist/compiler/compile.js +12 -0
- package/dist/compiler/compile.js.map +1 -1
- package/dist/compiler/runtime/localize-url.d.ts +8 -4
- package/dist/compiler/runtime/localize-url.js +12 -8
- package/dist/compiler/runtime/localize-url.js.map +1 -1
- package/dist/compiler/runtime/localize-url.test.js +45 -0
- package/dist/compiler/runtime/localize-url.test.js.map +1 -1
- package/dist/compiler/runtime/type.test.js +41 -0
- package/dist/compiler/runtime/type.test.js.map +1 -1
- package/dist/services/env-variables/index.js +1 -1
- package/dist/services/env-variables/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import { afterEach, expect, test, vi } from "vitest";
|
|
2
|
+
import fs from "node:fs/promises";
|
|
3
|
+
import nodeFs from "node:fs";
|
|
4
|
+
import { tmpdir } from "node:os";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
import { createServer } from "vite";
|
|
7
|
+
import { loadProjectInMemory, newProject, saveProjectToDirectory, } from "@inlang/sdk";
|
|
8
|
+
import { paraglideVitePlugin } from "./vite.js";
|
|
9
|
+
const cleanups = [];
|
|
10
|
+
const initialNodeEnv = process.env.NODE_ENV;
|
|
11
|
+
afterEach(async () => {
|
|
12
|
+
for (const cleanup of cleanups.splice(0).reverse())
|
|
13
|
+
await cleanup();
|
|
14
|
+
vi.restoreAllMocks();
|
|
15
|
+
if (initialNodeEnv === undefined)
|
|
16
|
+
delete process.env.NODE_ENV;
|
|
17
|
+
else
|
|
18
|
+
process.env.NODE_ENV = initialNodeEnv;
|
|
19
|
+
});
|
|
20
|
+
async function createFixture(options) {
|
|
21
|
+
process.env.NODE_ENV = "development";
|
|
22
|
+
const root = nodeFs.realpathSync(await fs.mkdtemp(path.join(tmpdir(), "paraglide-error-response-")));
|
|
23
|
+
cleanups.push(() => fs.rm(root, { recursive: true, force: true }));
|
|
24
|
+
const projectDir = path.join(root, "project.inlang");
|
|
25
|
+
const project = await loadProjectInMemory({
|
|
26
|
+
blob: await newProject({ settings: { baseLocale: "en", locales: ["en"] } }),
|
|
27
|
+
});
|
|
28
|
+
try {
|
|
29
|
+
await saveProjectToDirectory({ project, path: projectDir, fs });
|
|
30
|
+
}
|
|
31
|
+
finally {
|
|
32
|
+
await project.close();
|
|
33
|
+
}
|
|
34
|
+
const configFile = path.join(projectDir, "paraglide.config.js");
|
|
35
|
+
await fs.writeFile(configFile, "export default {};");
|
|
36
|
+
await fs.writeFile(path.join(root, "index.html"), "<p>Translation preview</p>");
|
|
37
|
+
const file = options.source === "settings"
|
|
38
|
+
? path.join(projectDir, "settings.json")
|
|
39
|
+
: configFile;
|
|
40
|
+
const original = await fs.readFile(file, "utf8");
|
|
41
|
+
const invalid = options.source === "settings" ? "{" : "export default { outdir: 42 };";
|
|
42
|
+
if (options.isInitiallyInvalid)
|
|
43
|
+
await fs.writeFile(file, invalid);
|
|
44
|
+
const server = await createServer({
|
|
45
|
+
root,
|
|
46
|
+
configFile: false,
|
|
47
|
+
logLevel: "silent",
|
|
48
|
+
plugins: [
|
|
49
|
+
paraglideVitePlugin({
|
|
50
|
+
project: projectDir,
|
|
51
|
+
outdir: path.join(root, "paraglide"),
|
|
52
|
+
devErrorResponse: options.devErrorResponse,
|
|
53
|
+
experimentalPerLocaleBuild: options.experimentalPerLocaleBuild,
|
|
54
|
+
}),
|
|
55
|
+
],
|
|
56
|
+
server: { host: "127.0.0.1", port: 0, watch: null },
|
|
57
|
+
optimizeDeps: { noDiscovery: true, include: [] },
|
|
58
|
+
});
|
|
59
|
+
cleanups.push(() => server.close());
|
|
60
|
+
await server.listen();
|
|
61
|
+
const url = server.resolvedUrls.local[0];
|
|
62
|
+
return { server, file, original, invalid, url };
|
|
63
|
+
}
|
|
64
|
+
async function notifyChange(server, file) {
|
|
65
|
+
await server.pluginContainer.watchChange(file, { event: "update" });
|
|
66
|
+
}
|
|
67
|
+
test.each(["settings", "config"])("serves HTTP 500 for invalid %s on startup and recovers without restarting", async (source) => {
|
|
68
|
+
const fixture = await createFixture({
|
|
69
|
+
source,
|
|
70
|
+
devErrorResponse: true,
|
|
71
|
+
isInitiallyInvalid: true,
|
|
72
|
+
});
|
|
73
|
+
const response = await fetch(fixture.url);
|
|
74
|
+
expect(response.status).toBe(500);
|
|
75
|
+
expect(response.headers.get("content-type")).toContain("text/plain");
|
|
76
|
+
expect(response.headers.get("cache-control")).toBe("no-store");
|
|
77
|
+
expect(await response.text()).not.toContain("Translation preview");
|
|
78
|
+
await fs.writeFile(fixture.file, fixture.original);
|
|
79
|
+
await notifyChange(fixture.server, fixture.file);
|
|
80
|
+
const recovered = await fetch(fixture.url);
|
|
81
|
+
expect(recovered.status).toBe(200);
|
|
82
|
+
expect(await recovered.text()).toContain("Translation preview");
|
|
83
|
+
});
|
|
84
|
+
test.each(["settings", "config"])("blocks stale responses after invalid %s and restores them after recovery", async (source) => {
|
|
85
|
+
const fixture = await createFixture({ source, devErrorResponse: true });
|
|
86
|
+
const before = await fetch(fixture.url);
|
|
87
|
+
expect(before.status).toBe(200);
|
|
88
|
+
await before.body?.cancel();
|
|
89
|
+
await fs.writeFile(fixture.file, fixture.invalid);
|
|
90
|
+
await notifyChange(fixture.server, fixture.file);
|
|
91
|
+
const failed = await fetch(fixture.url);
|
|
92
|
+
expect(failed.status).toBe(500);
|
|
93
|
+
expect(await failed.text()).not.toContain("Translation preview");
|
|
94
|
+
await fs.writeFile(fixture.file, fixture.original);
|
|
95
|
+
await notifyChange(fixture.server, fixture.file);
|
|
96
|
+
const recovered = await fetch(fixture.url);
|
|
97
|
+
expect(recovered.status).toBe(200);
|
|
98
|
+
await recovered.body?.cancel();
|
|
99
|
+
});
|
|
100
|
+
test.each(["settings", "config"])("keeps the default HTTP behavior after invalid %s", async (source) => {
|
|
101
|
+
const fixture = await createFixture({ source });
|
|
102
|
+
await fs.writeFile(fixture.file, fixture.invalid);
|
|
103
|
+
await notifyChange(fixture.server, fixture.file);
|
|
104
|
+
const response = await fetch(fixture.url);
|
|
105
|
+
expect(response.status).toBe(200);
|
|
106
|
+
expect(await response.text()).toContain("Translation preview");
|
|
107
|
+
});
|
|
108
|
+
test("preserves error responses with experimental per-locale builds enabled", async () => {
|
|
109
|
+
const fixture = await createFixture({
|
|
110
|
+
source: "config",
|
|
111
|
+
devErrorResponse: true,
|
|
112
|
+
experimentalPerLocaleBuild: true,
|
|
113
|
+
});
|
|
114
|
+
await fs.writeFile(fixture.file, fixture.invalid);
|
|
115
|
+
await notifyChange(fixture.server, fixture.file);
|
|
116
|
+
const response = await fetch(fixture.url);
|
|
117
|
+
expect(response.status).toBe(500);
|
|
118
|
+
await response.body?.cancel();
|
|
119
|
+
await fs.writeFile(fixture.file, fixture.original);
|
|
120
|
+
await notifyChange(fixture.server, fixture.file);
|
|
121
|
+
const recovered = await fetch(fixture.url);
|
|
122
|
+
expect(recovered.status).toBe(200);
|
|
123
|
+
await recovered.body?.cancel();
|
|
124
|
+
});
|
|
125
|
+
test("blocks generated modules while preserving errors and recovery for connected clients", async () => {
|
|
126
|
+
const fixture = await createFixture({
|
|
127
|
+
source: "settings",
|
|
128
|
+
devErrorResponse: true,
|
|
129
|
+
});
|
|
130
|
+
const moduleUrl = new URL("paraglide/runtime.js", fixture.url);
|
|
131
|
+
const before = await fetch(moduleUrl);
|
|
132
|
+
expect(before.status).toBe(200);
|
|
133
|
+
expect(await before.text()).toContain("baseLocale");
|
|
134
|
+
const socketUrl = new URL(fixture.url);
|
|
135
|
+
socketUrl.protocol = "ws:";
|
|
136
|
+
socketUrl.searchParams.set("token", fixture.server.config.webSocketToken);
|
|
137
|
+
const socket = new WebSocket(socketUrl, "vite-hmr");
|
|
138
|
+
cleanups.push(() => {
|
|
139
|
+
if (socket.readyState === WebSocket.CLOSED)
|
|
140
|
+
return Promise.resolve();
|
|
141
|
+
return new Promise((resolve) => {
|
|
142
|
+
socket.addEventListener("close", () => resolve(), { once: true });
|
|
143
|
+
socket.close();
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
const messages = [];
|
|
147
|
+
socket.addEventListener("message", (event) => {
|
|
148
|
+
messages.push(JSON.parse(String(event.data)));
|
|
149
|
+
});
|
|
150
|
+
await vi.waitFor(() => {
|
|
151
|
+
expect(messages).toContainEqual({ type: "connected" });
|
|
152
|
+
});
|
|
153
|
+
await fs.writeFile(fixture.file, fixture.invalid);
|
|
154
|
+
await notifyChange(fixture.server, fixture.file);
|
|
155
|
+
await vi.waitFor(() => {
|
|
156
|
+
expect(messages.some((message) => message.type === "error")).toBe(true);
|
|
157
|
+
});
|
|
158
|
+
const failed = await fetch(moduleUrl);
|
|
159
|
+
expect(failed.status).toBe(500);
|
|
160
|
+
const diagnostic = messages.find((message) => message.type === "error");
|
|
161
|
+
expect(await failed.text()).toBe(diagnostic?.err.message);
|
|
162
|
+
await fs.writeFile(fixture.file, fixture.original);
|
|
163
|
+
await notifyChange(fixture.server, fixture.file);
|
|
164
|
+
await vi.waitFor(() => {
|
|
165
|
+
expect(messages).toContainEqual({ type: "full-reload" });
|
|
166
|
+
});
|
|
167
|
+
const recovered = await fetch(moduleUrl);
|
|
168
|
+
expect(recovered.status).toBe(200);
|
|
169
|
+
expect(await recovered.text()).toContain("baseLocale");
|
|
170
|
+
});
|
|
171
|
+
//# sourceMappingURL=vite-error-response.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"vite-error-response.test.js","sourceRoot":"","sources":["../../src/bundler-plugins/vite-error-response.test.ts"],"names":[],"mappings":"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","sourcesContent":["import { afterEach, expect, test, vi } from \"vitest\";\nimport fs from \"node:fs/promises\";\nimport nodeFs from \"node:fs\";\nimport { tmpdir } from \"node:os\";\nimport path from \"node:path\";\nimport { createServer, type HotPayload, type ViteDevServer } from \"vite\";\nimport {\n\tloadProjectInMemory,\n\tnewProject,\n\tsaveProjectToDirectory,\n} from \"@inlang/sdk\";\nimport { paraglideVitePlugin } from \"./vite.js\";\n\ntype FixtureOptions = {\n\tdevErrorResponse?: boolean;\n\texperimentalPerLocaleBuild?: boolean;\n\tsource: \"settings\" | \"config\";\n\tisInitiallyInvalid?: boolean;\n};\n\nconst cleanups: Array<() => Promise<void>> = [];\nconst initialNodeEnv = process.env.NODE_ENV;\n\nafterEach(async () => {\n\tfor (const cleanup of cleanups.splice(0).reverse()) await cleanup();\n\tvi.restoreAllMocks();\n\tif (initialNodeEnv === undefined) delete process.env.NODE_ENV;\n\telse process.env.NODE_ENV = initialNodeEnv;\n});\n\nasync function createFixture(options: FixtureOptions) {\n\tprocess.env.NODE_ENV = \"development\";\n\tconst root = nodeFs.realpathSync(\n\t\tawait fs.mkdtemp(path.join(tmpdir(), \"paraglide-error-response-\"))\n\t);\n\tcleanups.push(() => fs.rm(root, { recursive: true, force: true }));\n\tconst projectDir = path.join(root, \"project.inlang\");\n\tconst project = await loadProjectInMemory({\n\t\tblob: await newProject({ settings: { baseLocale: \"en\", locales: [\"en\"] } }),\n\t});\n\ttry {\n\t\tawait saveProjectToDirectory({ project, path: projectDir, fs });\n\t} finally {\n\t\tawait project.close();\n\t}\n\tconst configFile = path.join(projectDir, \"paraglide.config.js\");\n\tawait fs.writeFile(configFile, \"export default {};\");\n\tawait fs.writeFile(\n\t\tpath.join(root, \"index.html\"),\n\t\t\"<p>Translation preview</p>\"\n\t);\n\tconst file =\n\t\toptions.source === \"settings\"\n\t\t\t? path.join(projectDir, \"settings.json\")\n\t\t\t: configFile;\n\tconst original = await fs.readFile(file, \"utf8\");\n\tconst invalid =\n\t\toptions.source === \"settings\" ? \"{\" : \"export default { outdir: 42 };\";\n\tif (options.isInitiallyInvalid) await fs.writeFile(file, invalid);\n\tconst server = await createServer({\n\t\troot,\n\t\tconfigFile: false,\n\t\tlogLevel: \"silent\",\n\t\tplugins: [\n\t\t\tparaglideVitePlugin({\n\t\t\t\tproject: projectDir,\n\t\t\t\toutdir: path.join(root, \"paraglide\"),\n\t\t\t\tdevErrorResponse: options.devErrorResponse,\n\t\t\t\texperimentalPerLocaleBuild: options.experimentalPerLocaleBuild,\n\t\t\t}),\n\t\t],\n\t\tserver: { host: \"127.0.0.1\", port: 0, watch: null },\n\t\toptimizeDeps: { noDiscovery: true, include: [] },\n\t});\n\tcleanups.push(() => server.close());\n\tawait server.listen();\n\tconst url = server.resolvedUrls!.local[0]!;\n\treturn { server, file, original, invalid, url };\n}\n\nasync function notifyChange(server: ViteDevServer, file: string) {\n\tawait server.pluginContainer.watchChange(file, { event: \"update\" });\n}\n\ntest.each([\"settings\", \"config\"] as const)(\n\t\"serves HTTP 500 for invalid %s on startup and recovers without restarting\",\n\tasync (source) => {\n\t\tconst fixture = await createFixture({\n\t\t\tsource,\n\t\t\tdevErrorResponse: true,\n\t\t\tisInitiallyInvalid: true,\n\t\t});\n\t\tconst response = await fetch(fixture.url);\n\t\texpect(response.status).toBe(500);\n\t\texpect(response.headers.get(\"content-type\")).toContain(\"text/plain\");\n\t\texpect(response.headers.get(\"cache-control\")).toBe(\"no-store\");\n\t\texpect(await response.text()).not.toContain(\"Translation preview\");\n\t\tawait fs.writeFile(fixture.file, fixture.original);\n\t\tawait notifyChange(fixture.server, fixture.file);\n\t\tconst recovered = await fetch(fixture.url);\n\t\texpect(recovered.status).toBe(200);\n\t\texpect(await recovered.text()).toContain(\"Translation preview\");\n\t}\n);\n\ntest.each([\"settings\", \"config\"] as const)(\n\t\"blocks stale responses after invalid %s and restores them after recovery\",\n\tasync (source) => {\n\t\tconst fixture = await createFixture({ source, devErrorResponse: true });\n\t\tconst before = await fetch(fixture.url);\n\t\texpect(before.status).toBe(200);\n\t\tawait before.body?.cancel();\n\t\tawait fs.writeFile(fixture.file, fixture.invalid);\n\t\tawait notifyChange(fixture.server, fixture.file);\n\t\tconst failed = await fetch(fixture.url);\n\t\texpect(failed.status).toBe(500);\n\t\texpect(await failed.text()).not.toContain(\"Translation preview\");\n\t\tawait fs.writeFile(fixture.file, fixture.original);\n\t\tawait notifyChange(fixture.server, fixture.file);\n\t\tconst recovered = await fetch(fixture.url);\n\t\texpect(recovered.status).toBe(200);\n\t\tawait recovered.body?.cancel();\n\t}\n);\n\ntest.each([\"settings\", \"config\"] as const)(\n\t\"keeps the default HTTP behavior after invalid %s\",\n\tasync (source) => {\n\t\tconst fixture = await createFixture({ source });\n\t\tawait fs.writeFile(fixture.file, fixture.invalid);\n\t\tawait notifyChange(fixture.server, fixture.file);\n\t\tconst response = await fetch(fixture.url);\n\t\texpect(response.status).toBe(200);\n\t\texpect(await response.text()).toContain(\"Translation preview\");\n\t}\n);\n\ntest(\"preserves error responses with experimental per-locale builds enabled\", async () => {\n\tconst fixture = await createFixture({\n\t\tsource: \"config\",\n\t\tdevErrorResponse: true,\n\t\texperimentalPerLocaleBuild: true,\n\t});\n\tawait fs.writeFile(fixture.file, fixture.invalid);\n\tawait notifyChange(fixture.server, fixture.file);\n\tconst response = await fetch(fixture.url);\n\texpect(response.status).toBe(500);\n\tawait response.body?.cancel();\n\tawait fs.writeFile(fixture.file, fixture.original);\n\tawait notifyChange(fixture.server, fixture.file);\n\tconst recovered = await fetch(fixture.url);\n\texpect(recovered.status).toBe(200);\n\tawait recovered.body?.cancel();\n});\n\ntest(\"blocks generated modules while preserving errors and recovery for connected clients\", async () => {\n\tconst fixture = await createFixture({\n\t\tsource: \"settings\",\n\t\tdevErrorResponse: true,\n\t});\n\tconst moduleUrl = new URL(\"paraglide/runtime.js\", fixture.url);\n\tconst before = await fetch(moduleUrl);\n\texpect(before.status).toBe(200);\n\texpect(await before.text()).toContain(\"baseLocale\");\n\tconst socketUrl = new URL(fixture.url);\n\tsocketUrl.protocol = \"ws:\";\n\tsocketUrl.searchParams.set(\"token\", fixture.server.config.webSocketToken);\n\tconst socket = new WebSocket(socketUrl, \"vite-hmr\");\n\tcleanups.push(() => {\n\t\tif (socket.readyState === WebSocket.CLOSED) return Promise.resolve();\n\t\treturn new Promise<void>((resolve) => {\n\t\t\tsocket.addEventListener(\"close\", () => resolve(), { once: true });\n\t\t\tsocket.close();\n\t\t});\n\t});\n\tconst messages: HotPayload[] = [];\n\tsocket.addEventListener(\"message\", (event) => {\n\t\tmessages.push(JSON.parse(String(event.data)) as HotPayload);\n\t});\n\tawait vi.waitFor(() => {\n\t\texpect(messages).toContainEqual({ type: \"connected\" });\n\t});\n\tawait fs.writeFile(fixture.file, fixture.invalid);\n\tawait notifyChange(fixture.server, fixture.file);\n\tawait vi.waitFor(() => {\n\t\texpect(messages.some((message) => message.type === \"error\")).toBe(true);\n\t});\n\tconst failed = await fetch(moduleUrl);\n\texpect(failed.status).toBe(500);\n\tconst diagnostic = messages.find((message) => message.type === \"error\");\n\texpect(await failed.text()).toBe(diagnostic?.err.message);\n\tawait fs.writeFile(fixture.file, fixture.original);\n\tawait notifyChange(fixture.server, fixture.file);\n\tawait vi.waitFor(() => {\n\t\texpect(messages).toContainEqual({ type: \"full-reload\" });\n\t});\n\tconst recovered = await fetch(moduleUrl);\n\texpect(recovered.status).toBe(200);\n\texpect(await recovered.text()).toContain(\"baseLocale\");\n});\n"]}
|
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
import type { Plugin } from "vite";
|
|
2
2
|
import type { ParaglidePluginOptions } from "./unplugin.js";
|
|
3
3
|
export type ParaglideVitePluginOptions = ParaglidePluginOptions & {
|
|
4
|
+
/**
|
|
5
|
+
* Respond with HTTP 500 while the development compiler or its config is
|
|
6
|
+
* invalid, instead of serving the last successful output. Error overlays
|
|
7
|
+
* remain enabled. Set this on the Vite plugin, not in paraglide.config.
|
|
8
|
+
*
|
|
9
|
+
* @default false
|
|
10
|
+
*/
|
|
11
|
+
devErrorResponse?: boolean;
|
|
4
12
|
/**
|
|
5
13
|
* Emit one compiler-specialized client asset tree per project locale.
|
|
6
14
|
*
|
|
@@ -1,16 +1,25 @@
|
|
|
1
1
|
import { createVitePlugin } from "unplugin";
|
|
2
2
|
import { createPerLocaleBuildPlugins } from "./per-locale-build/index.js";
|
|
3
3
|
import { unpluginFactory } from "./unplugin.js";
|
|
4
|
-
const createCompilerVitePlugin = createVitePlugin(unpluginFactory);
|
|
5
|
-
const createPerLocaleCompilerVitePlugin = createVitePlugin((options, meta) => unpluginFactory(options, meta, {
|
|
4
|
+
const createCompilerVitePlugin = createVitePlugin(({ devErrorResponse, ...options }, meta) => unpluginFactory(options, meta, { devErrorResponse }));
|
|
5
|
+
const createPerLocaleCompilerVitePlugin = createVitePlugin(({ devErrorResponse, ...options }, meta) => unpluginFactory(options, meta, {
|
|
6
|
+
outputStructure: "locale-modules",
|
|
7
|
+
devErrorResponse,
|
|
8
|
+
}));
|
|
6
9
|
export function paraglideVitePlugin(options) {
|
|
7
|
-
const { experimentalPerLocaleBuild, ...compilerOptions } = options;
|
|
10
|
+
const { experimentalPerLocaleBuild, devErrorResponse, ...compilerOptions } = options;
|
|
8
11
|
if (!experimentalPerLocaleBuild) {
|
|
9
|
-
return createCompilerVitePlugin(
|
|
12
|
+
return createCompilerVitePlugin({
|
|
13
|
+
...compilerOptions,
|
|
14
|
+
devErrorResponse,
|
|
15
|
+
});
|
|
10
16
|
}
|
|
11
17
|
return createPerLocaleBuildPlugins({
|
|
12
18
|
compilerOptions,
|
|
13
|
-
createCompilerPlugin: (options) => createPerLocaleCompilerVitePlugin(
|
|
19
|
+
createCompilerPlugin: (options) => createPerLocaleCompilerVitePlugin({
|
|
20
|
+
...options,
|
|
21
|
+
devErrorResponse,
|
|
22
|
+
}),
|
|
14
23
|
});
|
|
15
24
|
}
|
|
16
25
|
//# sourceMappingURL=vite.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"vite.js","sourceRoot":"","sources":["../../src/bundler-plugins/vite.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAC;AAG5C,OAAO,EAAE,2BAA2B,EAAE,MAAM,6BAA6B,CAAC;AAC1E,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"vite.js","sourceRoot":"","sources":["../../src/bundler-plugins/vite.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAC;AAG5C,OAAO,EAAE,2BAA2B,EAAE,MAAM,6BAA6B,CAAC;AAC1E,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AA8BhD,MAAM,wBAAwB,GAAG,gBAAgB,CAChD,CAAC,EAAE,gBAAgB,EAAE,GAAG,OAAO,EAAE,EAAE,IAAI,EAAE,EAAE,CAC1C,eAAe,CAAC,OAAO,EAAE,IAAI,EAAE,EAAE,gBAAgB,EAAE,CAAC,CACrD,CAAC;AACF,MAAM,iCAAiC,GACtC,gBAAgB,CACf,CAAC,EAAE,gBAAgB,EAAE,GAAG,OAAO,EAAE,EAAE,IAAI,EAAE,EAAE,CAC1C,eAAe,CAAC,OAAO,EAAE,IAAI,EAAE;IAC9B,eAAe,EAAE,gBAAgB;IACjC,gBAAgB;CAChB,CAAC,CACH,CAAC;AAEH,MAAM,UAAU,mBAAmB,CAClC,OAAmC;IAEnC,MAAM,EAAE,0BAA0B,EAAE,gBAAgB,EAAE,GAAG,eAAe,EAAE,GACzE,OAAO,CAAC;IACT,IAAI,CAAC,0BAA0B,EAAE,CAAC;QACjC,OAAO,wBAAwB,CAAC;YAC/B,GAAG,eAAe;YAClB,gBAAgB;SAChB,CAAW,CAAC;IACd,CAAC;IAED,OAAO,2BAA2B,CAAC;QAClC,eAAe;QACf,oBAAoB,EAAE,CAAC,OAAO,EAAE,EAAE,CACjC,iCAAiC,CAAC;YACjC,GAAG,OAAO;YACV,gBAAgB;SAChB,CAAW;KACb,CAAC,CAAC;AACJ,CAAC","sourcesContent":["import { createVitePlugin } from \"unplugin\";\nimport type { Plugin } from \"vite\";\nimport type { ParaglidePluginOptions } from \"./unplugin.js\";\nimport { createPerLocaleBuildPlugins } from \"./per-locale-build/index.js\";\nimport { unpluginFactory } from \"./unplugin.js\";\n\nexport type ParaglideVitePluginOptions = ParaglidePluginOptions & {\n\t/**\n\t * Respond with HTTP 500 while the development compiler or its config is\n\t * invalid, instead of serving the last successful output. Error overlays\n\t * remain enabled. Set this on the Vite plugin, not in paraglide.config.\n\t *\n\t * @default false\n\t */\n\tdevErrorResponse?: boolean;\n\t/**\n\t * Emit one compiler-specialized client asset tree per project locale.\n\t *\n\t * Requires Vite 8+. Builds one untouched Rolldown client environment per\n\t * locale and emits `paraglide-vite-locales.json`. Locale changes need to use\n\t * full-document navigation. Framework SSR graph selection requires a public\n\t * framework integration API.\n\t *\n\t * @experimental\n\t * @default false\n\t */\n\texperimentalPerLocaleBuild?: boolean;\n};\n\ntype CompilerVitePluginOptions = Omit<\n\tParaglideVitePluginOptions,\n\t\"experimentalPerLocaleBuild\"\n>;\n\nconst createCompilerVitePlugin = createVitePlugin<CompilerVitePluginOptions>(\n\t({ devErrorResponse, ...options }, meta) =>\n\t\tunpluginFactory(options, meta, { devErrorResponse })\n);\nconst createPerLocaleCompilerVitePlugin =\n\tcreateVitePlugin<CompilerVitePluginOptions>(\n\t\t({ devErrorResponse, ...options }, meta) =>\n\t\t\tunpluginFactory(options, meta, {\n\t\t\t\toutputStructure: \"locale-modules\",\n\t\t\t\tdevErrorResponse,\n\t\t\t})\n\t);\n\nexport function paraglideVitePlugin(\n\toptions: ParaglideVitePluginOptions\n): Plugin | Plugin[] {\n\tconst { experimentalPerLocaleBuild, devErrorResponse, ...compilerOptions } =\n\t\toptions;\n\tif (!experimentalPerLocaleBuild) {\n\t\treturn createCompilerVitePlugin({\n\t\t\t...compilerOptions,\n\t\t\tdevErrorResponse,\n\t\t}) as Plugin;\n\t}\n\n\treturn createPerLocaleBuildPlugins({\n\t\tcompilerOptions,\n\t\tcreateCompilerPlugin: (options) =>\n\t\t\tcreatePerLocaleCompilerVitePlugin({\n\t\t\t\t...options,\n\t\t\t\tdevErrorResponse,\n\t\t\t}) as Plugin,\n\t});\n}\n"]}
|
|
@@ -22,22 +22,38 @@ export const compileBundle = (args) => {
|
|
|
22
22
|
// set the pattern for the language tag
|
|
23
23
|
compiledMessages[message.locale] = compiledMessage;
|
|
24
24
|
}
|
|
25
|
-
const
|
|
26
|
-
args.experimentalStaticLocale
|
|
27
|
-
|
|
28
|
-
|
|
25
|
+
const inlineMessages = args.inlineMessages === true &&
|
|
26
|
+
args.experimentalStaticLocale !== perLocaleBuildStaticLocaleExpression;
|
|
27
|
+
const inlinePlainTextByLocale = inlineMessages
|
|
28
|
+
? findPlainTextMessages(args.bundle)
|
|
29
|
+
: undefined;
|
|
30
|
+
const inlineCandidates = inlineMessages
|
|
31
|
+
? findInlineBodies(compiledMessages, inlinePlainTextByLocale ?? {})
|
|
32
|
+
: {};
|
|
29
33
|
const unreferencedMessageLocales = findUnreferencedMessageLocales({
|
|
30
|
-
|
|
34
|
+
inlinedLocales: [
|
|
35
|
+
...Object.keys(inlinePlainTextByLocale ?? {}),
|
|
36
|
+
...Object.keys(inlineCandidates),
|
|
37
|
+
],
|
|
31
38
|
compiledMessages,
|
|
32
39
|
fallbackMap: args.fallbackMap,
|
|
33
40
|
hasMarkup,
|
|
34
41
|
});
|
|
42
|
+
// A fallback target stays a message function that the missing-locale alias
|
|
43
|
+
// calls, so its dispatcher branch calls it too instead of repeating the body.
|
|
44
|
+
const inlineBodiesByLocale = Object.create(null);
|
|
45
|
+
for (const locale of unreferencedMessageLocales) {
|
|
46
|
+
const body = inlineCandidates[locale];
|
|
47
|
+
if (body)
|
|
48
|
+
inlineBodiesByLocale[locale] = body;
|
|
49
|
+
}
|
|
35
50
|
return {
|
|
36
51
|
bundle: compileBundleFunction({
|
|
37
52
|
bundle: args.bundle,
|
|
38
53
|
availableLocales: Object.keys(args.fallbackMap),
|
|
39
54
|
messageReferenceExpression: args.messageReferenceExpression,
|
|
40
55
|
inlinePlainTextByLocale,
|
|
56
|
+
inlineBodiesByLocale,
|
|
41
57
|
settings: args.settings,
|
|
42
58
|
matchTypes,
|
|
43
59
|
hasMarkup,
|
|
@@ -74,6 +90,20 @@ function findPlainTextMessages(bundle) {
|
|
|
74
90
|
}
|
|
75
91
|
return result;
|
|
76
92
|
}
|
|
93
|
+
/**
|
|
94
|
+
* Finds the messages with inputs whose function body the dispatcher can run
|
|
95
|
+
* in place of calling the message function. Markup messages keep their `.parts`
|
|
96
|
+
* function, and messages that are plain text are returned as a string instead.
|
|
97
|
+
*/
|
|
98
|
+
function findInlineBodies(compiledMessages, inlinePlainTextByLocale) {
|
|
99
|
+
const result = Object.create(null);
|
|
100
|
+
for (const [locale, message] of Object.entries(compiledMessages)) {
|
|
101
|
+
if (message.inline && inlinePlainTextByLocale[locale] === undefined) {
|
|
102
|
+
result[locale] = message.inline;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return result;
|
|
106
|
+
}
|
|
77
107
|
function findUnreferencedMessageLocales(args) {
|
|
78
108
|
if (args.hasMarkup)
|
|
79
109
|
return [];
|
|
@@ -94,7 +124,7 @@ function findUnreferencedMessageLocales(args) {
|
|
|
94
124
|
fallbackLocale = args.fallbackMap[fallbackLocale];
|
|
95
125
|
}
|
|
96
126
|
}
|
|
97
|
-
return
|
|
127
|
+
return args.inlinedLocales.filter((locale) => !fallbackTargets.has(locale));
|
|
98
128
|
}
|
|
99
129
|
const compileBundleFunction = (args) => {
|
|
100
130
|
const inputs = args.bundle.declarations.filter((decl) => decl.type === "input-variable");
|
|
@@ -109,7 +139,15 @@ const compileBundleFunction = (args) => {
|
|
|
109
139
|
baseLocale,
|
|
110
140
|
]
|
|
111
141
|
: args.availableLocales;
|
|
112
|
-
|
|
142
|
+
// Inlined bodies read their inputs as `i`.
|
|
143
|
+
const inputsName = Object.keys(args.inlineBodiesByLocale ?? {}).length
|
|
144
|
+
? "i"
|
|
145
|
+
: "inputs";
|
|
146
|
+
const anyBranchReadsInputs = dispatchLocales.some((locale) => {
|
|
147
|
+
if (args.inlinePlainTextByLocale?.[locale] !== undefined)
|
|
148
|
+
return false;
|
|
149
|
+
return args.inlineBodiesByLocale?.[locale]?.readsInput ?? true;
|
|
150
|
+
});
|
|
113
151
|
const inputType = args.inputTypeAliasName;
|
|
114
152
|
const localesUnion = args.availableLocales.length === 0
|
|
115
153
|
? "never"
|
|
@@ -132,7 +170,15 @@ const compileBundleFunction = (args) => {
|
|
|
132
170
|
if (inlineText !== undefined) {
|
|
133
171
|
return `${prefix}${condition}return /** @type {LocalizedString} */ (${JSON.stringify(inlineText)})`;
|
|
134
172
|
}
|
|
135
|
-
|
|
173
|
+
const inlineBody = args.inlineBodiesByLocale?.[locale];
|
|
174
|
+
if (inlineBody) {
|
|
175
|
+
const [onlyStatement] = inlineBody.statements;
|
|
176
|
+
return inlineBody.statements.length === 1 &&
|
|
177
|
+
onlyStatement?.startsWith("return ")
|
|
178
|
+
? `${prefix}${condition}${onlyStatement}`
|
|
179
|
+
: `${prefix}${condition}{\n${continuationIndent}\t${inlineBody.statements.join(`\n${continuationIndent}\t`)}\n${continuationIndent}}`;
|
|
180
|
+
}
|
|
181
|
+
return `${prefix}${condition}return ${messageRef}(${inputsName})`;
|
|
136
182
|
}
|
|
137
183
|
// A locale's message has .parts() only if its own variants have
|
|
138
184
|
// markup. Fallbacks and messages without markup are typed without
|
|
@@ -159,7 +205,7 @@ ${englishMatchTableDoc}${jsDocBundleFunctionTypes({
|
|
|
159
205
|
if (!args.experimentalMiddlewareLocaleSplitting) {
|
|
160
206
|
return "";
|
|
161
207
|
}
|
|
162
|
-
return `\n${indent}if (experimentalMiddlewareLocaleSplitting && isServer === false) {\n${indent}\treturn /** @type {any} */ (globalThis).__paraglide.ssr.${safeBundleId}(
|
|
208
|
+
return `\n${indent}if (experimentalMiddlewareLocaleSplitting && isServer === false) {\n${indent}\treturn /** @type {any} */ (globalThis).__paraglide.ssr.${safeBundleId}(${inputsName})\n${indent}}`;
|
|
163
209
|
};
|
|
164
210
|
const clientPartsMiddlewareGuard = (indent) => {
|
|
165
211
|
if (!args.experimentalMiddlewareLocaleSplitting) {
|
|
@@ -187,13 +233,13 @@ ${englishMatchTableDoc}${jsDocBundleFunctionTypes({
|
|
|
187
233
|
}
|
|
188
234
|
return `${indent}const locale = ${localeExpression}${overrideWarning}${maybeTrackMessageCall(indent)}`;
|
|
189
235
|
};
|
|
190
|
-
const keepUnusedInputsParameter = (indent) =>
|
|
191
|
-
? `\n${indent}void
|
|
236
|
+
const keepUnusedInputsParameter = (indent) => !anyBranchReadsInputs && !args.experimentalMiddlewareLocaleSplitting
|
|
237
|
+
? `\n${indent}void ${inputsName};`
|
|
192
238
|
: "";
|
|
193
239
|
let code = "";
|
|
194
240
|
if (!args.hasMarkup) {
|
|
195
241
|
code = `${commonJsDoc}
|
|
196
|
-
${isSafeBundleId ? "export " : ""}const ${safeBundleId} = /** @type {(${bundleFunctionType}) & ${messageMetadataType}} */ ((
|
|
242
|
+
${isSafeBundleId ? "export " : ""}const ${safeBundleId} = /** @type {(${bundleFunctionType}) & ${messageMetadataType}} */ ((${inputsName}${hasInputs ? "" : " = {}"}, options = {}) => {${clientMiddlewareGuard("\t")}${keepUnusedInputsParameter("\t")}
|
|
197
243
|
${localeResolutionStatement("\t")}
|
|
198
244
|
${compileLocaleReturnStatements("string", "\t")}${!isFullyTranslated
|
|
199
245
|
? `\n return /** @type {LocalizedString} */ ("${args.bundle.id}")`
|