Skip to content

Commit f06cdf5

Browse files
fix(webpack + vite): fix dependency watching in loader (#1671)
1 parent 507b33d commit f06cdf5

File tree

7 files changed

+167
-37
lines changed

7 files changed

+167
-37
lines changed

packages/cli/src/api/catalog/getCatalogDependentFiles.test.ts

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ describe("getCatalogDependentFiles", () => {
1616

1717
it("Should return list template + fallbacks + sourceLocale", async () => {
1818
mockFs({
19-
"src/locales": {
19+
"/src/locales": {
2020
"messages.pot": "bla",
2121
"en.po": "bla",
2222
"pl.po": "bla",
@@ -41,7 +41,7 @@ describe("getCatalogDependentFiles", () => {
4141
const catalog = new Catalog(
4242
{
4343
name: null,
44-
path: "src/locales/{locale}",
44+
path: "/src/locales/{locale}",
4545
include: ["src/"],
4646
exclude: [],
4747
format,
@@ -54,16 +54,16 @@ describe("getCatalogDependentFiles", () => {
5454

5555
expect(actual).toMatchInlineSnapshot(`
5656
[
57-
src/locales/messages.pot,
58-
src/locales/pt-BR.po,
59-
src/locales/en.po,
57+
/src/locales/messages.pot,
58+
/src/locales/pt-BR.po,
59+
/src/locales/en.po,
6060
]
6161
`)
6262
})
6363

6464
it("Should not return itself", async () => {
6565
mockFs({
66-
"src/locales": {
66+
"/src/locales": {
6767
"messages.pot": "bla",
6868
"en.po": "bla",
6969
"pl.po": "bla",
@@ -88,7 +88,7 @@ describe("getCatalogDependentFiles", () => {
8888
const catalog = new Catalog(
8989
{
9090
name: null,
91-
path: "src/locales/{locale}",
91+
path: "/src/locales/{locale}",
9292
include: ["src/"],
9393
exclude: [],
9494
format,
@@ -101,14 +101,14 @@ describe("getCatalogDependentFiles", () => {
101101

102102
expect(actual).toMatchInlineSnapshot(`
103103
[
104-
src/locales/messages.pot,
104+
/src/locales/messages.pot,
105105
]
106106
`)
107107
})
108108

109109
it("Should not return non-existing files", async () => {
110110
mockFs({
111-
"src/locales": {
111+
"/src/locales": {
112112
// "messages.pot": "bla",
113113
"en.po": "bla",
114114
"pl.po": "bla",
@@ -133,7 +133,7 @@ describe("getCatalogDependentFiles", () => {
133133
const catalog = new Catalog(
134134
{
135135
name: null,
136-
path: "src/locales/{locale}",
136+
path: "/src/locales/{locale}",
137137
include: ["src/"],
138138
exclude: [],
139139
format,
@@ -146,8 +146,8 @@ describe("getCatalogDependentFiles", () => {
146146

147147
expect(actual).toMatchInlineSnapshot(`
148148
[
149-
src/locales/pt-BR.po,
150-
src/locales/en.po,
149+
/src/locales/pt-BR.po,
150+
/src/locales/en.po,
151151
]
152152
`)
153153
})

packages/cli/src/api/catalog/getCatalogDependentFiles.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import { Catalog } from "../catalog"
22
import { getFallbackListForLocale } from "./getFallbackListForLocale"
3-
import path from "pathe"
43
import fs from "node:fs/promises"
54

65
const fileExists = async (path: string) =>
@@ -27,9 +26,8 @@ export async function getCatalogDependentFiles(
2726
const out: string[] = []
2827

2928
for (const file of files) {
30-
const filePath = path.join(catalog.config.rootDir, file)
31-
if (await fileExists(filePath)) {
32-
out.push(filePath)
29+
if (await fileExists(file)) {
30+
out.push(file)
3331
}
3432
}
3533

packages/loader/test/__snapshots__/loader.test.ts.snap

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ exports[`lingui-loader should compile catalog in json format 1`] = `
1414

1515
exports[`lingui-loader should compile catalog in po format 1`] = `
1616
{
17+
ED2Xk0: String from template,
1718
mVmaLu: [
1819
My name is ,
1920
[

packages/loader/test/compiler.ts

Lines changed: 58 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,53 @@ import webpack from "webpack"
33
import { mkdtempSync } from "fs"
44
import os from "os"
55

6-
export default async (
7-
entryPoint: string,
8-
options?: any
9-
): Promise<webpack.StatsCompilation> => {
10-
const compiler = webpack({
6+
export type BuildResult = {
7+
loadBundle(): Promise<any>
8+
stats: webpack.StatsCompilation
9+
}
10+
11+
export async function build(entryPoint: string): Promise<BuildResult> {
12+
const compiler = getCompiler(entryPoint)
13+
14+
return new Promise((resolve, reject) => {
15+
compiler.run((err, stats) => {
16+
if (err) reject(err)
17+
if (stats.hasErrors()) reject(stats.toJson().errors)
18+
19+
const jsonStats = stats.toJson()
20+
resolve({
21+
loadBundle: () => import(path.join(jsonStats.outputPath, "bundle.js")),
22+
stats: jsonStats,
23+
})
24+
})
25+
})
26+
}
27+
28+
export function watch(entryPoint: string) {
29+
const compiler = getCompiler(entryPoint)
30+
31+
let deferred = createDeferred<webpack.StatsCompilation>()
32+
33+
const watching = compiler.watch({}, async (err, stats) => {
34+
err ? deferred.reject(err) : deferred.resolve(stats.toJson())
35+
deferred = createDeferred<any>()
36+
})
37+
38+
return {
39+
build: async (): Promise<BuildResult> => {
40+
const stats = await deferred.promise
41+
42+
return {
43+
loadBundle: () => import(path.join(stats.outputPath, "bundle.js")),
44+
stats,
45+
}
46+
},
47+
stop: () => new Promise((resolve) => watching.close(resolve)),
48+
}
49+
}
50+
51+
export function getCompiler(entryPoint: string) {
52+
return webpack({
1153
mode: "development",
1254
target: "node",
1355
entry: entryPoint,
@@ -22,13 +64,18 @@ export default async (
2264
libraryTarget: "commonjs",
2365
},
2466
})
67+
}
2568

26-
return new Promise((resolve, reject) => {
27-
compiler.run((err, stats) => {
28-
if (err) reject(err)
29-
if (stats.hasErrors()) reject(stats.toJson().errors)
69+
function createDeferred<T>() {
70+
let deferred: {
71+
resolve: (r: T) => void
72+
reject: (err: any) => void
73+
promise: Promise<T>
74+
}
3075

31-
resolve(stats.toJson())
32-
})
76+
const promise = new Promise<T>((resolve, reject) => {
77+
deferred = { resolve, reject, promise: undefined }
3378
})
79+
80+
return { ...deferred, promise }
3481
}

packages/loader/test/loader.test.ts

Lines changed: 85 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,104 @@
11
import path from "path"
2-
import compiler from "./compiler"
2+
import fs from "node:fs/promises"
3+
import { build, watch } from "./compiler"
4+
import { mkdtempSync } from "fs"
5+
import os from "os"
6+
7+
const skipOnWindows = os.platform() === "win32" ? it.skip : it
38

49
describe("lingui-loader", () => {
510
it("should compile catalog in po format", async () => {
611
expect.assertions(2)
712

8-
const stats = await compiler(
9-
path.join(__dirname, "po-format/entrypoint.js")
10-
)
13+
const built = await build(path.join(__dirname, "po-format/entrypoint.js"))
1114

12-
const data = await import(path.join(stats.outputPath, "bundle.js"))
13-
expect(stats.errors).toEqual([])
15+
const data = await built.loadBundle()
16+
expect(built.stats.errors).toEqual([])
1417
expect((await data.load()).messages).toMatchSnapshot()
1518
})
1619

1720
it("should compile catalog in json format", async () => {
1821
expect.assertions(2)
1922

20-
const stats = await compiler(
23+
const built = await build(
2124
path.join(__dirname, "./json-format/entrypoint.js")
2225
)
2326

24-
const data = await import(path.join(stats.outputPath, "bundle.js"))
25-
expect(stats.errors).toEqual([])
27+
expect(built.stats.errors).toEqual([])
28+
29+
const data = await built.loadBundle()
2630
expect((await data.load()).messages).toMatchSnapshot()
2731
})
32+
33+
skipOnWindows(
34+
"should trigger webpack recompile on catalog dependency change",
35+
async () => {
36+
const fixtureTempPath = await copyFixture(
37+
path.join(__dirname, "po-format")
38+
)
39+
40+
const watching = watch(path.join(fixtureTempPath, "/entrypoint.js"))
41+
42+
const res = await watching.build()
43+
44+
expect((await res.loadBundle().then((m) => m.load())).messages)
45+
.toMatchInlineSnapshot(`
46+
{
47+
ED2Xk0: String from template,
48+
mVmaLu: [
49+
My name is ,
50+
[
51+
name,
52+
],
53+
],
54+
mY42CM: Hello World,
55+
}
56+
`)
57+
58+
// change the dependency
59+
await fs.writeFile(
60+
path.join(fixtureTempPath, "/locale/messages.pot"),
61+
`msgid "Hello World"
62+
msgstr ""
63+
64+
msgid "My name is {name}"
65+
msgstr ""
66+
67+
msgid "String from template changes!"
68+
msgstr ""
69+
`
70+
)
71+
72+
const stats2 = await watching.build()
73+
jest.resetModules()
74+
75+
expect((await stats2.loadBundle().then((m) => m.load())).messages)
76+
.toMatchInlineSnapshot(`
77+
{
78+
mVmaLu: [
79+
My name is ,
80+
[
81+
name,
82+
],
83+
],
84+
mY42CM: Hello World,
85+
wg2uwk: String from template changes!,
86+
}
87+
`)
88+
89+
await watching.stop()
90+
}
91+
)
2892
})
93+
94+
async function copyFixture(srcPath: string) {
95+
const fixtureTempPath = mkdtempSync(
96+
path.join(os.tmpdir(), `lingui-test-fixture-${process.pid}`)
97+
)
98+
99+
await fs.cp(srcPath, fixtureTempPath, {
100+
recursive: true,
101+
})
102+
103+
return fixtureTempPath
104+
}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
export async function load() {
2-
return import("@lingui/loader?option=foo!./locale/en.po")
2+
return import("@lingui/loader!./locale/en.po")
33
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
msgid "Hello World"
2+
msgstr ""
3+
4+
msgid "My name is {name}"
5+
msgstr ""
6+
7+
msgid "String from template"
8+
msgstr ""

0 commit comments

Comments
 (0)