@inlang/paraglide-js 2.18.1 → 2.19.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/README.md +35 -29
- package/dist/bundler-plugins/unplugin.d.ts.map +1 -1
- package/dist/bundler-plugins/unplugin.js +241 -133
- package/dist/bundler-plugins/unplugin.js.map +1 -1
- package/dist/bundler-plugins/unplugin.test.js +184 -1
- package/dist/bundler-plugins/unplugin.test.js.map +1 -1
- package/dist/cli/commands/compile/command.d.ts.map +1 -1
- package/dist/cli/commands/compile/command.js +8 -3
- package/dist/cli/commands/compile/command.js.map +1 -1
- package/dist/cli/commands/compile/command.test.d.ts +2 -0
- package/dist/cli/commands/compile/command.test.d.ts.map +1 -0
- package/dist/cli/commands/compile/command.test.js +63 -0
- package/dist/cli/commands/compile/command.test.js.map +1 -0
- package/dist/cli/commands/init/command.js +2 -2
- package/dist/cli/commands/init/command.js.map +1 -1
- package/dist/compiler/compile-annotation.d.ts +13 -0
- package/dist/compiler/compile-annotation.d.ts.map +1 -0
- package/dist/compiler/compile-annotation.js +136 -0
- package/dist/compiler/compile-annotation.js.map +1 -0
- package/dist/compiler/compile-local-variable.d.ts.map +1 -1
- package/dist/compiler/compile-local-variable.js +1 -108
- package/dist/compiler/compile-local-variable.js.map +1 -1
- package/dist/compiler/compile-message.js +4 -0
- package/dist/compiler/compile-message.js.map +1 -1
- package/dist/compiler/compile-message.test.js +81 -0
- package/dist/compiler/compile-message.test.js.map +1 -1
- package/dist/compiler/compile-pattern.d.ts +8 -0
- package/dist/compiler/compile-pattern.d.ts.map +1 -1
- package/dist/compiler/compile-pattern.js +38 -2
- package/dist/compiler/compile-pattern.js.map +1 -1
- package/dist/compiler/compile-pattern.test.js +109 -0
- package/dist/compiler/compile-pattern.test.js.map +1 -1
- package/dist/compiler/compile-project.js +1 -1
- package/dist/compiler/compile-project.test.js +128 -0
- package/dist/compiler/compile-project.test.js.map +1 -1
- package/dist/compiler/compiler-options.d.ts +1 -1
- package/dist/compiler/create-readme.js +4 -4
- package/dist/compiler/emit-ts-declarations.d.ts.map +1 -1
- package/dist/compiler/emit-ts-declarations.js +77 -1
- package/dist/compiler/emit-ts-declarations.js.map +1 -1
- package/dist/compiler/runtime/extract-locale-from-request.js +1 -1
- package/dist/compiler/runtime/extract-locale-from-request.js.map +1 -1
- package/dist/compiler/runtime/generate-static-localized-urls.d.ts +1 -1
- package/dist/compiler/runtime/generate-static-localized-urls.js +1 -1
- package/dist/compiler/runtime/get-locale.js +4 -4
- package/dist/compiler/runtime/get-locale.js.map +1 -1
- package/dist/compiler/runtime/localize-href.d.ts +2 -2
- package/dist/compiler/runtime/localize-href.js +2 -2
- package/dist/compiler/runtime/localize-url.d.ts +2 -2
- package/dist/compiler/runtime/localize-url.js +2 -2
- package/dist/compiler/runtime/set-locale.d.ts +1 -1
- package/dist/compiler/runtime/set-locale.js +1 -1
- package/dist/compiler/runtime/should-redirect.d.ts +1 -1
- package/dist/compiler/runtime/should-redirect.js +1 -1
- package/dist/compiler/runtime/strategy.d.ts +2 -2
- package/dist/compiler/runtime/strategy.js +2 -2
- package/dist/compiler/seed-previous-compilation.d.ts +8 -0
- package/dist/compiler/seed-previous-compilation.d.ts.map +1 -0
- package/dist/compiler/seed-previous-compilation.js +9 -0
- package/dist/compiler/seed-previous-compilation.js.map +1 -0
- package/dist/compiler/server/middleware.d.ts +1 -1
- package/dist/compiler/server/middleware.js +1 -1
- package/dist/services/env-variables/index.js +1 -1
- package/dist/services/file-watching/tracked-fs.d.ts.map +1 -1
- package/dist/services/file-watching/tracked-fs.js +3 -1
- package/dist/services/file-watching/tracked-fs.js.map +1 -1
- package/package.json +8 -5
package/README.md
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
</p>
|
|
12
12
|
|
|
13
13
|
<p align="center">
|
|
14
|
-
<a href="https://
|
|
14
|
+
<a href="https://paraglidejs.com"><strong>Documentation</strong></a> ·
|
|
15
15
|
<a href="#quick-start"><strong>Quick Start</strong></a> ·
|
|
16
16
|
<a href="https://github.com/opral/inlang-paraglide-js/issues"><strong>Report Bug</strong></a>
|
|
17
17
|
</p>
|
|
@@ -26,14 +26,14 @@
|
|
|
26
26
|
<a href="https://www.michelin.com/"><img src="https://github.com/opral/paraglide-js/blob/main/assets/used-by/michelin.svg?raw=true" alt="Michelin" height="18"></a>
|
|
27
27
|
<a href="https://www.idealista.com/"><img src="https://github.com/opral/paraglide-js/blob/main/assets/used-by/idealista.svg?raw=true" alt="idealista" height="18"></a>
|
|
28
28
|
<a href="https://www.architonic.com/"><img src="https://github.com/opral/paraglide-js/blob/main/assets/used-by/architonic.png?raw=true" alt="Architonic" height="18"></a>
|
|
29
|
-
<a href="https://
|
|
30
|
-
<a href="https://
|
|
29
|
+
<a href="https://lovable.dev/"><img src="https://github.com/opral/paraglide-js/blob/main/assets/used-by/lovable.svg?raw=true" alt="Lovable" height="18"></a>
|
|
30
|
+
<a href="https://www.klaviyo.com/"><img src="https://github.com/opral/paraglide-js/blob/main/assets/used-by/klaviyo.svg?raw=true" alt="Klaviyo" height="18"></a>
|
|
31
31
|
</p>
|
|
32
32
|
|
|
33
33
|
<p align="center">
|
|
34
34
|
<sub>Framework-authored and framework-tested</sub><br/><br/>
|
|
35
35
|
<a href="https://svelte.dev/docs/cli/paraglide"><img src="https://cdn.simpleicons.org/svelte/FF3E00" alt="Svelte" height="14" /> SvelteKit's official i18n integration</a><br/>
|
|
36
|
-
<a href="https://
|
|
36
|
+
<a href="https://paraglidejs.com/blog/tanstack-ci"><img src="https://tanstack.com/images/logos/logo-color-100.png" alt="TanStack" height="14" /> TanStack Router's e2e-tested i18n example</a>
|
|
37
37
|
</p>
|
|
38
38
|
|
|
39
39
|
## Code Preview
|
|
@@ -51,7 +51,7 @@ import { m } from "./paraglide/messages.js";
|
|
|
51
51
|
m.greeting({ name: "World" }); // "Hello World!" — fully typesafe
|
|
52
52
|
```
|
|
53
53
|
|
|
54
|
-
The compiler turns your messages into typed ESM functions. Vite, Rollup, and other modern bundlers can tree-shake unused translations before they reach the browser. Expect [**up to 70% smaller i18n bundle sizes**](https://
|
|
54
|
+
The compiler turns your messages into typed ESM functions. Vite, Rollup, and other modern bundlers can tree-shake unused translations before they reach the browser. Expect [**up to 70% smaller i18n bundle sizes**](https://paraglidejs.com/benchmark) compared to runtime i18n libraries (e.g. 47 KB vs 205 KB).
|
|
55
55
|
|
|
56
56
|
## Why Paraglide?
|
|
57
57
|
|
|
@@ -67,23 +67,23 @@ The compiler turns your messages into typed ESM functions. Vite, Rollup, and oth
|
|
|
67
67
|
## Get Started With Your Framework
|
|
68
68
|
|
|
69
69
|
<p>
|
|
70
|
-
<a href="https://
|
|
71
|
-
<a href="https://
|
|
70
|
+
<a href="https://paraglidejs.com/vite"><img src="https://cdn.simpleicons.org/react/61DAFB" alt="React" width="18" height="18" /> React</a> ·
|
|
71
|
+
<a href="https://paraglidejs.com/vite"><img src="https://cdn.simpleicons.org/vuedotjs/4FC08D" alt="Vue" width="18" height="18" /> Vue</a> ·
|
|
72
72
|
<a href="https://github.com/TanStack/router/tree/main/examples/react/start-i18n-paraglide"><img src="https://tanstack.com/images/logos/logo-color-100.png" alt="TanStack" width="18" height="18" /> TanStack Start</a> ·
|
|
73
|
-
<a href="https://
|
|
74
|
-
<a href="https://
|
|
75
|
-
<a href="https://
|
|
76
|
-
<a href="https://
|
|
73
|
+
<a href="https://paraglidejs.com/sveltekit"><img src="https://cdn.simpleicons.org/svelte/FF3E00" alt="Svelte" width="18" height="18" /> SvelteKit</a> ·
|
|
74
|
+
<a href="https://paraglidejs.com/react-router"><img src="https://cdn.simpleicons.org/reactrouter/CA4245" alt="React Router" width="18" height="18" /> React Router</a> ·
|
|
75
|
+
<a href="https://paraglidejs.com/astro"><img src="https://cdn.simpleicons.org/astro/FF5D01" alt="Astro" width="18" height="18" /> Astro</a> ·
|
|
76
|
+
<a href="https://paraglidejs.com/vanilla-js-ts"><img src="https://cdn.simpleicons.org/javascript/F7DF1E" alt="JavaScript" width="18" height="18" /> Vanilla JS/TS</a>
|
|
77
77
|
</p>
|
|
78
78
|
|
|
79
79
|
- **[TanStack Start example](https://github.com/TanStack/router/tree/main/examples/react/start-i18n-paraglide)** — SSR, localized routing, and TanStack Router integration.
|
|
80
|
-
- **[SvelteKit guide](https://
|
|
81
|
-
- **[React Router guide](https://
|
|
82
|
-
- **[Astro guide](https://
|
|
83
|
-
- **[Vite guide](https://
|
|
80
|
+
- **[SvelteKit guide](https://paraglidejs.com/sveltekit)** — SvelteKit's official i18n integration.
|
|
81
|
+
- **[React Router guide](https://paraglidejs.com/react-router)** — SSR and client routing.
|
|
82
|
+
- **[Astro guide](https://paraglidejs.com/astro)** — static and server-rendered sites.
|
|
83
|
+
- **[Vite guide](https://paraglidejs.com/vite)** — React, Vue, Solid, or vanilla JS/TS.
|
|
84
84
|
|
|
85
85
|
> [!TIP]
|
|
86
|
-
> <img src="https://vitejs.dev/logo.svg" alt="Vite" width="16" height="16" /> **Paraglide is ideal for Vite-based apps.** Setup is one plugin, messages compile to ESM, and Vite's tree-shaking eliminates unused translations automatically. [Get started →](https://
|
|
86
|
+
> <img src="https://vitejs.dev/logo.svg" alt="Vite" width="16" height="16" /> **Paraglide is ideal for Vite-based apps.** Setup is one plugin, messages compile to ESM, and Vite's tree-shaking eliminates unused translations automatically. [Get started →](https://paraglidejs.com/vite)
|
|
87
87
|
|
|
88
88
|
## SSR Ready
|
|
89
89
|
|
|
@@ -109,7 +109,7 @@ export function handle(request: Request) {
|
|
|
109
109
|
}
|
|
110
110
|
```
|
|
111
111
|
|
|
112
|
-
**[SSR Docs →](https://
|
|
112
|
+
**[SSR Docs →](https://paraglidejs.com/server-side-rendering)** · **[Middleware Docs →](https://paraglidejs.com/middleware)**
|
|
113
113
|
|
|
114
114
|
## Router Composition
|
|
115
115
|
|
|
@@ -129,7 +129,7 @@ localizeUrl("https://example.com/about", { locale: "de" }).href; // https://exam
|
|
|
129
129
|
|
|
130
130
|
For routers with rewrite hooks, call `deLocalizeUrl()` on incoming URLs and `localizeUrl()` on outgoing URLs. For file-based routers, keep your file routes canonical and localize at the routing boundary.
|
|
131
131
|
|
|
132
|
-
**[i18n Routing Docs →](https://
|
|
132
|
+
**[i18n Routing Docs →](https://paraglidejs.com/i18n-routing)**
|
|
133
133
|
|
|
134
134
|
### TanStack Start
|
|
135
135
|
|
|
@@ -177,7 +177,7 @@ getLocale(); // "en"
|
|
|
177
177
|
setLocale("de"); // switches to German
|
|
178
178
|
```
|
|
179
179
|
|
|
180
|
-
**[Full Getting Started Guide →](https://
|
|
180
|
+
**[Full Getting Started Guide →](https://paraglidejs.com)**
|
|
181
181
|
|
|
182
182
|
## Rich Text
|
|
183
183
|
|
|
@@ -202,7 +202,7 @@ export function ContactCta() {
|
|
|
202
202
|
|
|
203
203
|
The markup names come from your message and are type-checked, so translators control where links and emphasis appear while your React app controls how they render.
|
|
204
204
|
|
|
205
|
-
**[Markup Docs →](https://
|
|
205
|
+
**[Markup Docs →](https://paraglidejs.com/markup)** · **[React](https://www.npmjs.com/package/@inlang/paraglide-js-react)** · **[Svelte](https://www.npmjs.com/package/@inlang/paraglide-js-svelte)** · **[Vue](https://www.npmjs.com/package/@inlang/paraglide-js-vue)** · **[Solid](https://www.npmjs.com/package/@inlang/paraglide-js-solid)**
|
|
206
206
|
|
|
207
207
|
## How It Works
|
|
208
208
|
|
|
@@ -248,7 +248,7 @@ m.items_in_cart({ count: 5 }); // "5 items in cart"
|
|
|
248
248
|
|
|
249
249
|
Message format is **plugin-based** — use the default inlang format, or switch to i18next, JSON, or ICU MessageFormat via [plugins](https://inlang.com/c/plugins). If your team relies on ICU MessageFormat 1 syntax, use the [inlang-icu-messageformat-1 plugin](https://inlang.com/m/p7c8m1d2/plugin-inlang-icu-messageformat-1).
|
|
250
250
|
|
|
251
|
-
**[Formatting Docs →](https://
|
|
251
|
+
**[Formatting Docs →](https://paraglidejs.com/formatting)** · **[Pluralization & Variants Docs →](https://paraglidejs.com/variants)**
|
|
252
252
|
|
|
253
253
|
## Why Compiler-First?
|
|
254
254
|
|
|
@@ -256,7 +256,7 @@ Runtime i18n libraries like i18next resolve message keys from dictionaries while
|
|
|
256
256
|
|
|
257
257
|
That means Vite can tree-shake unused translations, TypeScript can autocomplete message keys and parameters, and your components call plain functions instead of resolving strings through a runtime lookup layer.
|
|
258
258
|
|
|
259
|
-
In the [Paraglide benchmark](https://
|
|
259
|
+
In the [Paraglide benchmark](https://paraglidejs.com/benchmark), typical scenarios shipped **47-144 KB with Paraglide** vs **205-422 KB with i18next**. With 5 locales, 100 used messages, and 200 total messages, Paraglide shipped **47 KB** while i18next shipped **205 KB**.
|
|
260
260
|
|
|
261
261
|
Tree-shaking also keeps Paraglide stable as your message catalog grows. In the benchmark, using 100 messages shipped **47 KB** with Paraglide whether the project had 200, 500, or 1,000 total messages. The i18next runtime bundle grew from **205 KB** to **414 KB**.
|
|
262
262
|
|
|
@@ -273,7 +273,7 @@ Tree-shaking also keeps Paraglide stable as your message catalog grows. In the b
|
|
|
273
273
|
| **Rich text** | ✅ Typed markup adapters | ✅ Rich-text components | Via framework wrappers |
|
|
274
274
|
| **ICU MessageFormat 1** | ✅ [Via plugin](https://inlang.com/m/p7c8m1d2/plugin-inlang-icu-messageformat-1) | ✅ | Via plugin |
|
|
275
275
|
|
|
276
|
-
**[Full Comparison →](https://
|
|
276
|
+
**[Full Comparison →](https://paraglidejs.com/comparison)**
|
|
277
277
|
|
|
278
278
|
## FAQ
|
|
279
279
|
|
|
@@ -307,6 +307,12 @@ Yes. Paraglide can compile existing i18next translation files through the [i18ne
|
|
|
307
307
|
>
|
|
308
308
|
> Daniel · [Why I Replaced i18next with Paraglide JS](https://dropanote.de/en/blog/20250726-why-i-replaced-i18next-with-paraglide-js/)
|
|
309
309
|
|
|
310
|
+
## Blog Posts
|
|
311
|
+
|
|
312
|
+
- [Why I Replaced i18next with Paraglide JS](https://dropanote.de/en/blog/20250726-why-i-replaced-i18next-with-paraglide-js/) — A developer's experience reducing bundle size from 40KB to 2KB
|
|
313
|
+
- [react-i18next Was Fine. Then I Found Paraglide.](https://brodin.dev/blog/paraglide-vs-react-i18n) — A developer's experience moving from react-i18next to Paraglide
|
|
314
|
+
- [Inlang / ParaglideJS blew my mind](https://dev.to/robertosnap/inlang-paraglidejs-blew-my-mind-3984) — A developer's first impressions using Paraglide JS with Remix
|
|
315
|
+
|
|
310
316
|
## Talks
|
|
311
317
|
|
|
312
318
|
- [Paraglide JS 1.0 announcement](https://www.youtube.com/watch?v=-YES3CCAG90)
|
|
@@ -327,12 +333,12 @@ Paraglide compiles messages from [inlang](https://github.com/opral/inlang), the
|
|
|
327
333
|
|
|
328
334
|
## Documentation
|
|
329
335
|
|
|
330
|
-
- [Getting Started](https://
|
|
331
|
-
- [Framework Guides](https://
|
|
332
|
-
- [Message Syntax & Pluralization](https://
|
|
333
|
-
- [Formatting (Number/Date/Relative Time)](https://
|
|
334
|
-
- [Routing & SSR](https://
|
|
335
|
-
- [API Reference](https://
|
|
336
|
+
- [Getting Started](https://paraglidejs.com)
|
|
337
|
+
- [Framework Guides](https://paraglidejs.com/react-router) (React Router, SvelteKit, Astro, etc.)
|
|
338
|
+
- [Message Syntax & Pluralization](https://paraglidejs.com/variants)
|
|
339
|
+
- [Formatting (Number/Date/Relative Time)](https://paraglidejs.com/formatting)
|
|
340
|
+
- [Routing & SSR](https://paraglidejs.com/server-side-rendering)
|
|
341
|
+
- [API Reference](https://paraglidejs.com)
|
|
336
342
|
|
|
337
343
|
## Contributing
|
|
338
344
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"unplugin.d.ts","sourceRoot":"","sources":["../../src/bundler-plugins/unplugin.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;
|
|
1
|
+
{"version":3,"file":"unplugin.d.ts","sourceRoot":"","sources":["../../src/bundler-plugins/unplugin.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAMhD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,iCAAiC,CAAC;AA8IvE,eAAO,MAAM,eAAe,EAAE,eAAe,CAAC,eAAe,CAiN5D,CAAC"}
|
|
@@ -1,176 +1,284 @@
|
|
|
1
1
|
import { compile } from "../compiler/compile.js";
|
|
2
|
-
import
|
|
2
|
+
import { relative } from "node:path";
|
|
3
|
+
import { createHash } from "node:crypto";
|
|
4
|
+
import nodeFs from "node:fs";
|
|
3
5
|
import { Logger } from "../services/logger/index.js";
|
|
4
6
|
import { createTrackedFs, getWatchTargets, isPathWithinDirectories, } from "../services/file-watching/tracked-fs.js";
|
|
5
7
|
import { nodeNormalizePath } from "../utilities/node-normalize-path.js";
|
|
6
|
-
import {
|
|
8
|
+
import { seedPreviousCompilationFromOutdir } from "../compiler/seed-previous-compilation.js";
|
|
7
9
|
const PLUGIN_NAME = "unplugin-paraglide-js";
|
|
8
10
|
const logger = new Logger();
|
|
9
11
|
/**
|
|
10
12
|
* Default isServer which differs per bundler.
|
|
11
13
|
*/
|
|
12
14
|
let isServer;
|
|
13
|
-
|
|
14
|
-
|
|
15
|
+
// Module-scoped so the warm state survives plugin re-instantiation within
|
|
16
|
+
// one process (e.g. a vite config reload), but recreated when a different
|
|
17
|
+
// fs is passed — the tracked wrapper, read set, and cached compilation are
|
|
18
|
+
// only valid for the filesystem they were produced from.
|
|
19
|
+
let pluginState;
|
|
20
|
+
function getPluginState(args) {
|
|
21
|
+
if (pluginState === undefined || pluginState.baseFs !== args.fs) {
|
|
22
|
+
const tracked = createTrackedFs({ fs: args.fs });
|
|
23
|
+
pluginState = {
|
|
24
|
+
baseFs: args.fs,
|
|
25
|
+
trackedFs: tracked.fs,
|
|
26
|
+
readFiles: tracked.readFiles,
|
|
27
|
+
clearReadFiles: tracked.clearReadFiles,
|
|
28
|
+
previousCompilation: undefined,
|
|
29
|
+
previousInputsDigest: undefined,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
return pluginState;
|
|
33
|
+
}
|
|
15
34
|
function withoutCleanOutdir(args) {
|
|
16
35
|
const { cleanOutdir, ...compileArgs } = args;
|
|
17
36
|
void cleanOutdir;
|
|
18
37
|
return compileArgs;
|
|
19
38
|
}
|
|
20
39
|
/**
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
* the
|
|
24
|
-
*
|
|
25
|
-
* concurrent readers (SSR/prerender, sibling Vite processes).
|
|
40
|
+
* Hashes the files (and directory listings) the last compilation read,
|
|
41
|
+
* together with the options that affect the output. Returns `undefined`
|
|
42
|
+
* when the digest can't be computed (no tracked reads yet, an unexpected
|
|
43
|
+
* read error, ...) — `undefined` never matches, so the caller compiles.
|
|
26
44
|
*
|
|
27
|
-
*
|
|
45
|
+
* Directory listings are included so that a message file *added* next to
|
|
46
|
+
* the tracked ones invalidates the digest, not only edits to known files.
|
|
47
|
+
* All components are length-prefixed so distinct input states can't
|
|
48
|
+
* produce the same hash stream.
|
|
49
|
+
*
|
|
50
|
+
* The digest is taken after the compile, by re-reading the inputs. A file
|
|
51
|
+
* edited *during* a compile can therefore be hashed at its new content
|
|
52
|
+
* while the output reflects the old one — accepted: in dev, watchChange
|
|
53
|
+
* recompiles on that edit, and a fresh build process always recompiles.
|
|
28
54
|
*/
|
|
29
|
-
async function
|
|
30
|
-
|
|
31
|
-
const resolvedFs = fs ?? (await import("node:fs"));
|
|
32
|
-
const outputHashes = await hashDirectory(absoluteOutdir, resolvedFs.promises);
|
|
33
|
-
if (!outputHashes)
|
|
55
|
+
async function computeInputsDigest(state, args, outputStructure) {
|
|
56
|
+
if (state.readFiles.size === 0) {
|
|
34
57
|
return undefined;
|
|
35
|
-
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
isServer,
|
|
57
|
-
...withoutCleanOutdir(args),
|
|
58
|
-
cleanOutdir: false,
|
|
59
|
-
});
|
|
60
|
-
logger.success(`Compilation complete (${outputStructure})`);
|
|
61
|
-
}
|
|
62
|
-
catch (error) {
|
|
63
|
-
logger.error("Failed to compile project:", error.message);
|
|
64
|
-
logger.info("Please check your translation files for syntax errors.");
|
|
65
|
-
if (isProduction)
|
|
66
|
-
throw error;
|
|
67
|
-
}
|
|
68
|
-
finally {
|
|
69
|
-
// in any case add the files to watch
|
|
70
|
-
const targets = getWatchTargets(readFiles, { outdir: args.outdir });
|
|
71
|
-
for (const filePath of targets.files) {
|
|
72
|
-
this.addWatchFile(filePath);
|
|
58
|
+
}
|
|
59
|
+
const targets = getWatchTargets(state.readFiles, { outdir: args.outdir });
|
|
60
|
+
if (targets.files.size === 0) {
|
|
61
|
+
return undefined;
|
|
62
|
+
}
|
|
63
|
+
const fsp = (args.fs ?? nodeFs).promises;
|
|
64
|
+
const hash = createHash("sha256");
|
|
65
|
+
try {
|
|
66
|
+
const { fs: _fs, ...serializableArgs } = args;
|
|
67
|
+
void _fs;
|
|
68
|
+
hash.update(JSON.stringify({ ...serializableArgs, outputStructure, isServer }));
|
|
69
|
+
for (const directoryPath of [...targets.directories].sort()) {
|
|
70
|
+
const entries = await fsp
|
|
71
|
+
.readdir(directoryPath)
|
|
72
|
+
// tracked reads include probed-but-absent paths (the SDK reads
|
|
73
|
+
// optional files and handles ENOENT itself) — a missing entry
|
|
74
|
+
// is valid input state, hash it as such
|
|
75
|
+
.catch(rethrowUnlessEnoent);
|
|
76
|
+
hash.update(`\0dir:${directoryPath.length}:${directoryPath}:`);
|
|
77
|
+
if (entries === undefined) {
|
|
78
|
+
hash.update("missing");
|
|
73
79
|
}
|
|
74
|
-
|
|
75
|
-
|
|
80
|
+
else {
|
|
81
|
+
for (const entry of [...entries].sort()) {
|
|
82
|
+
hash.update(`${entry.length}:${entry},`);
|
|
83
|
+
}
|
|
76
84
|
}
|
|
77
85
|
}
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
return;
|
|
84
|
-
}
|
|
85
|
-
const shouldCompile = targets.files.has(normalizedPath) ||
|
|
86
|
-
isPathWithinDirectories(normalizedPath, targets.directories);
|
|
87
|
-
if (shouldCompile === false) {
|
|
88
|
-
return;
|
|
89
|
-
}
|
|
90
|
-
const isProduction = process.env.NODE_ENV === "production";
|
|
91
|
-
// default to locale-modules for development to speed up the dev server
|
|
92
|
-
// https://github.com/opral/inlang-paraglide-js/issues/486
|
|
93
|
-
const outputStructure = args.outputStructure ??
|
|
94
|
-
(isProduction ? "message-modules" : "locale-modules");
|
|
95
|
-
const previouslyReadFiles = new Set(readFiles);
|
|
96
|
-
try {
|
|
97
|
-
logger.info(`Re-compiling inlang project... File "${relative(process.cwd(), path)}" has changed.`);
|
|
98
|
-
// Clear readFiles to track fresh file reads
|
|
99
|
-
clearReadFiles();
|
|
100
|
-
previousCompilation = await compile({
|
|
101
|
-
fs: trackedFs,
|
|
102
|
-
previousCompilation,
|
|
103
|
-
outputStructure,
|
|
104
|
-
isServer,
|
|
105
|
-
...withoutCleanOutdir(args),
|
|
106
|
-
cleanOutdir: false,
|
|
107
|
-
});
|
|
108
|
-
logger.success(`Re-compilation complete (${outputStructure})`);
|
|
109
|
-
// Add any new files to watch
|
|
110
|
-
const nextTargets = getWatchTargets(readFiles, { outdir: args.outdir });
|
|
111
|
-
for (const filePath of nextTargets.files) {
|
|
112
|
-
this.addWatchFile(filePath);
|
|
86
|
+
for (const filePath of [...targets.files].sort()) {
|
|
87
|
+
const content = await fsp.readFile(filePath).catch(rethrowUnlessEnoent);
|
|
88
|
+
hash.update(`\0file:${filePath.length}:${filePath}:`);
|
|
89
|
+
if (content === undefined) {
|
|
90
|
+
hash.update("missing");
|
|
113
91
|
}
|
|
114
|
-
|
|
115
|
-
|
|
92
|
+
else {
|
|
93
|
+
hash.update(`${content.length}:`);
|
|
94
|
+
hash.update(content);
|
|
116
95
|
}
|
|
117
96
|
}
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
},
|
|
138
|
-
},
|
|
139
|
-
},
|
|
140
|
-
webpack(compiler) {
|
|
141
|
-
compiler.options.resolve = {
|
|
142
|
-
...compiler.options.resolve,
|
|
143
|
-
fallback: {
|
|
144
|
-
...compiler.options.resolve?.fallback,
|
|
145
|
-
// https://stackoverflow.com/a/72989932
|
|
146
|
-
async_hooks: false,
|
|
147
|
-
},
|
|
148
|
-
};
|
|
149
|
-
compiler.hooks.beforeRun.tapPromise(PLUGIN_NAME, async () => {
|
|
97
|
+
}
|
|
98
|
+
catch {
|
|
99
|
+
return undefined;
|
|
100
|
+
}
|
|
101
|
+
return hash.digest("hex");
|
|
102
|
+
}
|
|
103
|
+
function rethrowUnlessEnoent(error) {
|
|
104
|
+
if (error?.code === "ENOENT") {
|
|
105
|
+
return undefined;
|
|
106
|
+
}
|
|
107
|
+
throw error;
|
|
108
|
+
}
|
|
109
|
+
export const unpluginFactory = (args) => {
|
|
110
|
+
const state = getPluginState(args);
|
|
111
|
+
const { trackedFs, readFiles, clearReadFiles } = state;
|
|
112
|
+
return {
|
|
113
|
+
name: PLUGIN_NAME,
|
|
114
|
+
enforce: "pre",
|
|
115
|
+
async buildStart() {
|
|
150
116
|
const isProduction = process.env.NODE_ENV === "production";
|
|
151
117
|
// default to locale-modules for development to speed up the dev server
|
|
152
118
|
// https://github.com/opral/inlang-paraglide-js/issues/486
|
|
153
119
|
const outputStructure = args.outputStructure ??
|
|
154
120
|
(isProduction ? "message-modules" : "locale-modules");
|
|
155
121
|
try {
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
previousCompilation
|
|
159
|
-
|
|
122
|
+
// `vite build` calls buildStart once per environment (client, ssr).
|
|
123
|
+
// Skip the expensive compile when the inputs haven't changed.
|
|
124
|
+
if (state.previousCompilation && state.previousInputsDigest) {
|
|
125
|
+
const currentDigest = await computeInputsDigest(state, args, outputStructure);
|
|
126
|
+
if (currentDigest === state.previousInputsDigest) {
|
|
127
|
+
logger.info(`Compilation skipped — inputs unchanged (${outputStructure})`);
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
// On a fresh process, seed previousCompilation from on-disk hashes
|
|
132
|
+
// so the first compile is a no-op when inputs are unchanged. Avoids
|
|
133
|
+
// racing concurrent readers that wiping outdir would interrupt.
|
|
134
|
+
const seededPrevious = state.previousCompilation ??
|
|
135
|
+
(await seedPreviousCompilationFromOutdir({
|
|
136
|
+
outdir: args.outdir,
|
|
137
|
+
fs: args.fs?.promises,
|
|
138
|
+
}));
|
|
139
|
+
state.previousCompilation = await compile({
|
|
160
140
|
previousCompilation: seededPrevious,
|
|
161
141
|
outputStructure,
|
|
142
|
+
isServer,
|
|
162
143
|
...withoutCleanOutdir(args),
|
|
163
144
|
cleanOutdir: false,
|
|
145
|
+
// after the args spread so a user-provided fs doesn't bypass
|
|
146
|
+
// the read tracking (trackedFs wraps args.fs when provided)
|
|
147
|
+
fs: trackedFs,
|
|
164
148
|
});
|
|
149
|
+
state.previousInputsDigest = await computeInputsDigest(state, args, outputStructure);
|
|
165
150
|
logger.success(`Compilation complete (${outputStructure})`);
|
|
166
151
|
}
|
|
167
152
|
catch (error) {
|
|
168
|
-
|
|
169
|
-
logger.
|
|
153
|
+
state.previousInputsDigest = undefined;
|
|
154
|
+
logger.error("Failed to compile project:", error.message);
|
|
155
|
+
logger.info("Please check your translation files for syntax errors.");
|
|
170
156
|
if (isProduction)
|
|
171
157
|
throw error;
|
|
172
158
|
}
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
});
|
|
159
|
+
finally {
|
|
160
|
+
// in any case add the files to watch
|
|
161
|
+
const targets = getWatchTargets(readFiles, { outdir: args.outdir });
|
|
162
|
+
for (const filePath of targets.files) {
|
|
163
|
+
this.addWatchFile(filePath);
|
|
164
|
+
}
|
|
165
|
+
for (const directoryPath of targets.directories) {
|
|
166
|
+
this.addWatchFile(directoryPath);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
},
|
|
170
|
+
async watchChange(path) {
|
|
171
|
+
const normalizedPath = nodeNormalizePath(path);
|
|
172
|
+
const targets = getWatchTargets(readFiles, { outdir: args.outdir });
|
|
173
|
+
if (targets.isIgnoredPath(normalizedPath)) {
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
const shouldCompile = targets.files.has(normalizedPath) ||
|
|
177
|
+
isPathWithinDirectories(normalizedPath, targets.directories);
|
|
178
|
+
if (shouldCompile === false) {
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
const isProduction = process.env.NODE_ENV === "production";
|
|
182
|
+
// default to locale-modules for development to speed up the dev server
|
|
183
|
+
// https://github.com/opral/inlang-paraglide-js/issues/486
|
|
184
|
+
const outputStructure = args.outputStructure ??
|
|
185
|
+
(isProduction ? "message-modules" : "locale-modules");
|
|
186
|
+
const previouslyReadFiles = new Set(readFiles);
|
|
187
|
+
try {
|
|
188
|
+
logger.info(`Re-compiling inlang project... File "${relative(process.cwd(), path)}" has changed.`);
|
|
189
|
+
// Clear readFiles to track fresh file reads
|
|
190
|
+
clearReadFiles();
|
|
191
|
+
state.previousCompilation = await compile({
|
|
192
|
+
previousCompilation: state.previousCompilation,
|
|
193
|
+
outputStructure,
|
|
194
|
+
isServer,
|
|
195
|
+
...withoutCleanOutdir(args),
|
|
196
|
+
cleanOutdir: false,
|
|
197
|
+
fs: trackedFs,
|
|
198
|
+
});
|
|
199
|
+
state.previousInputsDigest = await computeInputsDigest(state, args, outputStructure);
|
|
200
|
+
logger.success(`Re-compilation complete (${outputStructure})`);
|
|
201
|
+
// Add any new files to watch
|
|
202
|
+
const nextTargets = getWatchTargets(readFiles, { outdir: args.outdir });
|
|
203
|
+
for (const filePath of nextTargets.files) {
|
|
204
|
+
this.addWatchFile(filePath);
|
|
205
|
+
}
|
|
206
|
+
for (const directoryPath of nextTargets.directories) {
|
|
207
|
+
this.addWatchFile(directoryPath);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
catch (e) {
|
|
211
|
+
clearReadFiles();
|
|
212
|
+
for (const filePath of previouslyReadFiles) {
|
|
213
|
+
readFiles.add(filePath);
|
|
214
|
+
}
|
|
215
|
+
// Reset compilation result on error
|
|
216
|
+
state.previousCompilation = undefined;
|
|
217
|
+
state.previousInputsDigest = undefined;
|
|
218
|
+
logger.warn("Failed to re-compile project:", e.message);
|
|
219
|
+
}
|
|
220
|
+
},
|
|
221
|
+
vite: {
|
|
222
|
+
config: {
|
|
223
|
+
handler: () => {
|
|
224
|
+
isServer = "import.meta.env?.SSR ?? typeof window === 'undefined'";
|
|
225
|
+
},
|
|
226
|
+
},
|
|
227
|
+
configEnvironment: {
|
|
228
|
+
handler: () => {
|
|
229
|
+
isServer = "import.meta.env?.SSR ?? typeof window === 'undefined'";
|
|
230
|
+
},
|
|
231
|
+
},
|
|
232
|
+
},
|
|
233
|
+
webpack(compiler) {
|
|
234
|
+
compiler.options.resolve = {
|
|
235
|
+
...compiler.options.resolve,
|
|
236
|
+
fallback: {
|
|
237
|
+
...compiler.options.resolve?.fallback,
|
|
238
|
+
// https://stackoverflow.com/a/72989932
|
|
239
|
+
async_hooks: false,
|
|
240
|
+
},
|
|
241
|
+
};
|
|
242
|
+
compiler.hooks.beforeRun.tapPromise(PLUGIN_NAME, async () => {
|
|
243
|
+
const isProduction = process.env.NODE_ENV === "production";
|
|
244
|
+
// default to locale-modules for development to speed up the dev server
|
|
245
|
+
// https://github.com/opral/inlang-paraglide-js/issues/486
|
|
246
|
+
const outputStructure = args.outputStructure ??
|
|
247
|
+
(isProduction ? "message-modules" : "locale-modules");
|
|
248
|
+
try {
|
|
249
|
+
// Multi-compiler webpack setups (client + server) trigger
|
|
250
|
+
// beforeRun once per compiler — skip when inputs are unchanged.
|
|
251
|
+
if (state.previousCompilation && state.previousInputsDigest) {
|
|
252
|
+
const currentDigest = await computeInputsDigest(state, args, outputStructure);
|
|
253
|
+
if (currentDigest === state.previousInputsDigest) {
|
|
254
|
+
logger.info(`Compilation skipped — inputs unchanged (${outputStructure})`);
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
const seededPrevious = state.previousCompilation ??
|
|
259
|
+
(await seedPreviousCompilationFromOutdir({
|
|
260
|
+
outdir: args.outdir,
|
|
261
|
+
fs: args.fs?.promises,
|
|
262
|
+
}));
|
|
263
|
+
state.previousCompilation = await compile({
|
|
264
|
+
previousCompilation: seededPrevious,
|
|
265
|
+
outputStructure,
|
|
266
|
+
...withoutCleanOutdir(args),
|
|
267
|
+
cleanOutdir: false,
|
|
268
|
+
fs: trackedFs,
|
|
269
|
+
});
|
|
270
|
+
state.previousInputsDigest = await computeInputsDigest(state, args, outputStructure);
|
|
271
|
+
logger.success(`Compilation complete (${outputStructure})`);
|
|
272
|
+
}
|
|
273
|
+
catch (error) {
|
|
274
|
+
state.previousInputsDigest = undefined;
|
|
275
|
+
logger.warn("Failed to compile project:", error.message);
|
|
276
|
+
logger.warn("Please check your translation files for syntax errors.");
|
|
277
|
+
if (isProduction)
|
|
278
|
+
throw error;
|
|
279
|
+
}
|
|
280
|
+
});
|
|
281
|
+
},
|
|
282
|
+
};
|
|
283
|
+
};
|
|
176
284
|
//# sourceMappingURL=unplugin.js.map
|