@intlayer/swc 9.2.0 → 9.3.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 CHANGED
@@ -79,6 +79,74 @@ Three import modes are supported:
79
79
  | `dynamic` | `useDictionaryDynamic` | Dynamic `.mjs` import |
80
80
  | `fetch` | `useDictionaryDynamic` | Fetch `.mjs` import |
81
81
 
82
+ ### Field renaming (`build.minify`)
83
+
84
+ When the compiled dictionaries have been minified — every user-defined content field renamed to a short alphabetic alias — the plugin rewrites the matching source accesses so both sides keep agreeing:
85
+
86
+ **Before**
87
+
88
+ ```ts
89
+ const { title } = useIntlayer("about");
90
+ const content = useIntlayer("about");
91
+ content.section.subtitle;
92
+ ```
93
+
94
+ **After**
95
+
96
+ ```ts
97
+ const { d: title } = useIntlayer("about");
98
+ const content = useIntlayer("about");
99
+ content.b.a;
100
+ ```
101
+
102
+ Destructuring (including nested patterns, local aliases and defaults), member chains, optional chaining, static computed accesses (`content["title"]`), array indexes (`content.list[0].title`) and signal-style accessors (`content().title`) are all handled. Dynamic accesses (`content[key]`) stop the rewrite, leaving the rest of the chain untouched.
103
+
104
+ The rename tables come from the `fieldRenameMap` option. Deciding _which_ fields are unused and _what_ alias each gets requires reading every component source file and rewriting the dictionary JSON — file I/O and cross-file state a per-file Wasm transform cannot do — so that analysis runs in Node (in `@intlayer/babel`, driven by `withIntlayer`) and only its result is handed to this plugin.
105
+
106
+ ### Build reporting
107
+
108
+ The plugin transforms one file at a time with no cross-file state, so all it can report is a line per file. That is a tracing aid, not build output — the purge and minify summaries a build normally prints (which dictionaries were pruned, which fields were removed, what was partially minified) come from the Node-side pipeline and follow `log.mode` in `intlayer.config.*`, exactly as in a Vite build.
109
+
110
+ `logLevel` is therefore `"off"` unless you ask for tracing:
111
+
112
+ | Level | Output |
113
+ | --------- | --------------------------------------------------------------------------- |
114
+ | `"off"` | Nothing (default) |
115
+ | `"info"` | One line per transformed file: injected imports and renamed field count |
116
+ | `"debug"` | Everything above, plus skipped files and the emitted code of each transform |
117
+
118
+ With `next-intlayer`, set `INTLAYER_SWC_LOG_LEVEL=info` (or `debug`) to turn it on; `log.mode: "disabled"` silences it regardless.
119
+
120
+ ---
121
+
122
+ ## Next.js compatibility
123
+
124
+ An SWC Wasm plugin can only be loaded by a host that speaks its `swc_ecma_ast`
125
+ schema. **Next.js 16.1.0 is the minimum**: it is the first release built on
126
+ SWC's forward-compatible plugin ABI, where the AST travels as self-describing
127
+ CBOR instead of rkyv, so one binary keeps working on later releases. Earlier
128
+ releases require an exact schema match and reject the plugin.
129
+
130
+ | Next.js | `swc_ecma_ast` | Plugin loads |
131
+ | ------- | -------------- | -------------- |
132
+ | 14.2.x | 0.112.7 | ❌ rkyv ABI |
133
+ | 15.5.x | 14.0.0 | ❌ rkyv ABI |
134
+ | 16.0.x | 16.0.0 | ❌ rkyv ABI |
135
+ | 16.1.x | 19.0.0 | ✅ exact match |
136
+ | 16.2.x | 20.0.1 | ✅ |
137
+ | 16.3.x | 25.0.0 | ✅ |
138
+
139
+ You do not have to check this yourself: `withIntlayer` from
140
+ [`next-intlayer`](https://www.npmjs.com/package/next-intlayer) reads the Next.js
141
+ version from your project and simply does not register the plugin below 16.1.0.
142
+ Those builds succeed, they just run without the bundle optimisation instead of
143
+ failing with `failed to invoke plugin`.
144
+
145
+ This is also why the crate pins `swc_core` to the `54.x` line rather than the
146
+ latest release: `54.x` is the newest `swc_core` still on `swc_ecma_ast` 19, the
147
+ schema Next.js 16.1 ships. Moving the pin forward would raise the minimum
148
+ supported Next.js version with it.
149
+
82
150
  ---
83
151
 
84
152
  ## Usage: Next.js / SWC Wasm plugin (recommended)
@@ -112,6 +180,8 @@ const nextConfig: NextConfig = {
112
180
  replaceDictionaryEntry: false,
113
181
  filesList: [], // empty = transform all files
114
182
  dictionaryModeMap: {}, // per-key overrides, e.g. { "heavy-dict": "dynamic" }
183
+ fieldRenameMap: {}, // minified field aliases, e.g. { about: { title: { shortName: "a", children: {} } } }
184
+ logLevel: "off", // "off" | "info" | "debug"
115
185
  },
116
186
  ],
117
187
  ],
@@ -148,8 +218,7 @@ fn my_transform(program: Program, file_path: &str) -> Program {
148
218
  fetch_dictionaries_dir: "/project/.intlayer/fetch_dictionaries".into(),
149
219
  import_mode: Some("static".into()),
150
220
  replace_dictionary_entry: Some(false),
151
- files_list: vec![],
152
- dictionary_mode_map: None,
221
+ ..PluginConfig::default()
153
222
  };
154
223
  process_transform(program, config, file_path.into())
155
224
  }
@@ -166,22 +235,34 @@ cargo build-wasip1 --release
166
235
  cargo build --target wasm32-wasip1 --features plugin --release
167
236
  ```
168
237
 
238
+ Build through the alias, or at least through this crate's `.cargo/config.toml`:
239
+ it sets `--cfg=swc_ast_unknown` for `wasm32` targets, which is what opts the
240
+ binary into the forward-compatible ABI. Built without it, the plugin only loads
241
+ on hosts sharing its exact `swc_ecma_ast` version, and the first Next.js release
242
+ that adds an AST node breaks every build using it.
243
+
169
244
  ---
170
245
 
171
246
  ## Plugin configuration reference
172
247
 
173
248
  All fields correspond to the JSON object passed as the second element of each `swcPlugins` tuple.
174
249
 
175
- | Field | Type | Default | Description |
176
- | ------------------------ | ---------------------------------- | ---------- | --------------------------------------------------- |
177
- | `dictionariesDir` | `string` | required | Absolute path to compiled `.json` dictionaries |
178
- | `dictionariesEntryPath` | `string` | required | Absolute path to the generated entry `.mjs` file |
179
- | `dynamicDictionariesDir` | `string` | required | Absolute path for dynamic `.mjs` modules |
180
- | `fetchDictionariesDir` | `string` | required | Absolute path for fetch `.mjs` modules |
181
- | `importMode` | `"static" \| "dynamic" \| "fetch"` | `"static"` | Global import strategy |
182
- | `replaceDictionaryEntry` | `boolean` | `false` | Replace entry file with empty stubs |
183
- | `filesList` | `string[]` | `[]` | Allowlist of absolute file paths; empty = all files |
184
- | `dictionaryModeMap` | `Record<string, string>` | `{}` | Per-dictionary import mode overrides |
250
+ | Field | Type | Default | Description |
251
+ | ------------------------ | ---------------------------------- | ---------- | ---------------------------------------------------- |
252
+ | `dictionariesDir` | `string` | required | Absolute path to compiled `.json` dictionaries |
253
+ | `dictionariesEntryPath` | `string` | required | Absolute path to the generated entry `.mjs` file |
254
+ | `dynamicDictionariesDir` | `string` | required | Absolute path for dynamic `.mjs` modules |
255
+ | `fetchDictionariesDir` | `string` | required | Absolute path for fetch `.mjs` modules |
256
+ | `importMode` | `"static" \| "dynamic" \| "fetch"` | `"static"` | Global import strategy |
257
+ | `replaceDictionaryEntry` | `boolean` | `false` | Replace entry file with empty stubs |
258
+ | `filesList` | `string[]` | `[]` | Allowlist of absolute file paths; empty = all files |
259
+ | `dictionaryModeMap` | `Record<string, string>` | `{}` | Per-dictionary import mode overrides |
260
+ | `nestingDictionaryKeys` | `string[]` | `[]` | Keys imported through their `nested/` companion |
261
+ | `extraCallers` | `ExtraCallerConfig[]` | `[]` | Compat-adapter callers to rewrite like `useIntlayer` |
262
+ | `fieldRenameMap` | `Record<string, FieldRenameMap>` | `{}` | Minified field aliases, per dictionary key |
263
+ | `logLevel` | `"off" \| "info" \| "debug"` | `"off"` | Build-time reporting verbosity |
264
+
265
+ `FieldRenameMap` is a recursive object mapping each original field name to `{ shortName: string; children: FieldRenameMap }`.
185
266
 
186
267
  ---
187
268
 
@@ -189,10 +270,29 @@ All fields correspond to the JSON object passed as the second element of each `s
189
270
 
190
271
  The following symbols are exported by this crate:
191
272
 
192
- - **`PluginConfig`** – configuration struct (mirrors the JSON options above).
273
+ - **`PluginConfig`** – configuration struct (mirrors the JSON options above). Implements `Default`, so `..PluginConfig::default()` keeps call sites stable as options are added.
274
+ - **`ExtraCallerConfig` / `NamespaceOptionConfig`** – compat-adapter caller descriptors.
275
+ - **`FieldRenameMap` / `FieldRenameNode`** – the minified field alias tables.
276
+ - **`LogLevel`** – build-time reporting verbosity.
193
277
  - **`process_transform(program, cfg, filename) -> Program`** – core transform function; accepts and returns an SWC `Program` AST.
194
278
  - **`normalize_path(path: &str) -> String`** – normalises Windows-style backslash paths to forward slashes for cross-platform path diffing.
195
279
 
280
+ ### Crate layout
281
+
282
+ | Module | Responsibility |
283
+ | ------------------ | ------------------------------------------------------------ |
284
+ | `config` | Plugin option types and their wire format |
285
+ | `ast` | Small helpers for reading and building AST nodes |
286
+ | `paths` | Path normalisation and relative module specifiers |
287
+ | `packages` | Recognised package specifiers and generated-file conventions |
288
+ | `extra_caller` | Namespace resolution for compat-adapter callers |
289
+ | `field_rename` | Source-side content field renaming (`build.minify`) |
290
+ | `pre_pass` | Caller discovery and the file-level dynamic/static decision |
291
+ | `optimize` | Call-site and import-specifier rewriting |
292
+ | `imports` | Injection of the dictionary imports the rewrite created |
293
+ | `dictionary_entry` | Emptying of the generated dictionaries entry module |
294
+ | `logger` | Build-time reporting |
295
+
196
296
  ---
197
297
 
198
298
  ## Documentation
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@intlayer/swc",
3
- "version": "9.2.0",
3
+ "version": "9.3.0",
4
4
  "private": false,
5
5
  "description": "A SWC plugin for Intlayer that transforms declaration files and provides internationalization features during the build process according to the Intlayer configuration.",
6
6
  "keywords": [