Skip to content

Commit c0e959a

Browse files
committed
WIP: make webpack dev mode work
1 parent 40b8b80 commit c0e959a

1 file changed

Lines changed: 60 additions & 36 deletions

File tree

cmp/compiler/src/plugin/unplugin.ts

Lines changed: 60 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import {
1818
} from "../translation-server";
1919
import {
2020
cleanupExistingMetadata,
21-
getMetadataPath,
21+
getMetadataPath as rawGetMetadataPath,
2222
MetadataManager,
2323
} from "../metadata/manager";
2424
import { createLingoConfig } from "../utils/config-factory";
@@ -50,14 +50,24 @@ export const lingoUnplugin = createUnplugin<LingoPluginOptions>((options) => {
5050
// Won't work for webpack most likely. Use mode there to set correct environment in configs.
5151
const isDev = config.environment === "development";
5252
const startPort = config.dev.translationServerStartPort;
53-
const metadataFilePath = getMetadataPath(config);
53+
54+
// For webpack: store the actual mode and use it to compute the correct metadata path
55+
let webpackMode: "development" | "production" | undefined;
56+
// Should be dynamic, because webpack only tells us the mode inside the plugin, not inside the config.
57+
const getMetadataPath = () => {
58+
return rawGetMetadataPath(
59+
webpackMode ? { ...config, environment: webpackMode } : config,
60+
);
61+
};
5462

5563
return {
5664
name: PLUGIN_NAME,
5765
enforce: "pre", // Run before other plugins (especially before React plugin)
5866

5967
vite: {
6068
async buildStart() {
69+
const metadataFilePath = getMetadataPath();
70+
6171
cleanupExistingMetadata(metadataFilePath);
6272

6373
registerCleanupOnCurrentProcess({
@@ -86,12 +96,13 @@ export const lingoUnplugin = createUnplugin<LingoPluginOptions>((options) => {
8696
},
8797

8898
async buildEnd() {
99+
const metadataFilePath = getMetadataPath();
89100
if (!isDev) {
90101
try {
91102
await processBuildTranslations({
92103
config,
93104
publicOutputPath: "public/translations",
94-
metadataFilePath: metadataFilePath,
105+
metadataFilePath,
95106
});
96107
} catch (error) {
97108
logger.error("Build-time translation processing failed:", error);
@@ -104,13 +115,22 @@ export const lingoUnplugin = createUnplugin<LingoPluginOptions>((options) => {
104115
// if (config.isEmbeddedIntoNext) {
105116
// return;
106117
// }
107-
compiler.hooks.watchRun.tapPromise(PLUGIN_NAME, async () => {
118+
119+
webpackMode =
120+
compiler.options.mode === "development" ? "development" : "production";
121+
const metadataFilePath = getMetadataPath();
122+
// Yes, this is dirty play, but webpack runs only for this plugin, and this way we save people from using wrong config
123+
config.environment = webpackMode;
124+
125+
compiler.hooks.initialize.tap(PLUGIN_NAME, () => {
108126
cleanupExistingMetadata(metadataFilePath);
109127
registerCleanupOnCurrentProcess({
110128
cleanup: () => cleanupExistingMetadata(metadataFilePath),
111129
});
130+
});
112131

113-
if (compiler.options.mode === "development" && !translationServer) {
132+
compiler.hooks.watchRun.tapPromise(PLUGIN_NAME, async () => {
133+
if (webpackMode === "development" && !translationServer) {
114134
translationServer = await startTranslationServer({
115135
startPort,
116136
onError: (err) => {
@@ -121,7 +141,7 @@ export const lingoUnplugin = createUnplugin<LingoPluginOptions>((options) => {
121141
`Translation server started successfully on port: ${port}`,
122142
);
123143
},
124-
config: { ...config, environment: compiler.options.mode },
144+
config,
125145
});
126146
registerCleanupOnCurrentProcess({
127147
asyncCleanup: async () => {
@@ -132,12 +152,12 @@ export const lingoUnplugin = createUnplugin<LingoPluginOptions>((options) => {
132152
});
133153

134154
compiler.hooks.additionalPass.tapPromise(PLUGIN_NAME, async () => {
135-
if (compiler.options.mode === "production") {
155+
if (webpackMode === "production") {
136156
try {
137157
await processBuildTranslations({
138-
config: { ...config, environment: compiler.options.mode },
158+
config,
139159
publicOutputPath: "public/translations",
140-
metadataFilePath: metadataFilePath,
160+
metadataFilePath,
141161
});
142162
} catch (error) {
143163
logger.error("Build-time translation processing failed:", error);
@@ -146,7 +166,7 @@ export const lingoUnplugin = createUnplugin<LingoPluginOptions>((options) => {
146166
}
147167
});
148168

149-
// Duplicates process handlers, but won't hurt
169+
// Duplicates the cleanup process handlers does, but won't hurt since cleanup is idempotent.
150170
compiler.hooks.shutdown.tapPromise(PLUGIN_NAME, async () => {
151171
cleanupExistingMetadata(metadataFilePath);
152172
await translationServer?.stop();
@@ -189,43 +209,48 @@ export const lingoUnplugin = createUnplugin<LingoPluginOptions>((options) => {
189209
return null;
190210
},
191211

192-
load(id) {
193-
logger.warn(`ID: ${id}`);
194-
if (id === "\0virtual:lingo-dev-config") {
195-
const serverUrl =
196-
translationServer?.getUrl() || `http://127.0.0.1:${startPort}`;
197-
const cacheDir = getCacheDir(config);
198-
199-
return `export const serverUrl = ${JSON.stringify(serverUrl)};
212+
load: {
213+
filter: {
214+
id: /virtual:/,
215+
},
216+
handler(id: string) {
217+
logger.warn(`ID: ${id}`);
218+
if (id === "\0virtual:lingo-dev-config") {
219+
const serverUrl =
220+
translationServer?.getUrl() || `http://127.0.0.1:${startPort}`;
221+
const cacheDir = getCacheDir(config);
222+
223+
return `export const serverUrl = ${JSON.stringify(serverUrl)};
200224
export const cacheDir = ${JSON.stringify(cacheDir)};`;
201-
}
225+
}
202226

203-
// Server locale resolver - default implementation
204-
if (id === "\0virtual:locale-server") {
205-
// For Next.js, generate server-side locale resolver using cookies
206-
const implementation = generateServerLocaleCode(config);
207-
return `
227+
// Server locale resolver - default implementation
228+
if (id === "\0virtual:locale-server") {
229+
// For Next.js, generate server-side locale resolver using cookies
230+
const implementation = generateServerLocaleCode(config);
231+
return `
208232
export async function getServerLocale() {${implementation}
209233
}
210234
`;
211-
}
235+
}
212236

213-
// Client locale resolver - default implementation
214-
// Includes both getClientLocale() and persistLocale()
215-
if (id === "\0virtual:locale-client") {
216-
const { getClientLocale, persistLocale } =
217-
generateClientLocaleCode(config);
218-
return `
237+
// Client locale resolver - default implementation
238+
// Includes both getClientLocale() and persistLocale()
239+
if (id === "\0virtual:locale-client") {
240+
const { getClientLocale, persistLocale } =
241+
generateClientLocaleCode(config);
242+
return `
219243
export function getClientLocale() {
220244
${getClientLocale}
221245
}
222246
223247
export function persistLocale(locale) {
224248
${persistLocale}
225249
}`;
226-
}
250+
}
227251

228-
return null;
252+
return null;
253+
},
229254
},
230255

231256
transform: {
@@ -239,8 +264,8 @@ export function persistLocale(locale) {
239264
code: config.useDirective ? useI18nRegex : undefined,
240265
},
241266
handler: async (code, id) => {
267+
// TODO (AleksandrSl 13/12/2025): It's weird we don't have any this.getOptions() here
242268
try {
243-
// TODO (AleksandrSl 13/12/2025): How do I get Webpack mode here?
244269
// Transform the component
245270
const result = transformComponent({
246271
code,
@@ -255,8 +280,7 @@ export function persistLocale(locale) {
255280
logger.debug(`No transformation needed for ${id}`);
256281
return null;
257282
}
258-
259-
const metadataManager = new MetadataManager(metadataFilePath);
283+
const metadataManager = new MetadataManager(getMetadataPath());
260284

261285
// Update metadata with new entries (thread-safe)
262286
if (result.newEntries && result.newEntries.length > 0) {

0 commit comments

Comments
 (0)