Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(webpack + vite): fix dependency watching in loader #1671

Merged
merged 3 commits into from
May 24, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 12 additions & 12 deletions packages/cli/src/api/catalog/getCatalogDependentFiles.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ describe("getCatalogDependentFiles", () => {

it("Should return list template + fallbacks + sourceLocale", async () => {
mockFs({
"src/locales": {
"/src/locales": {
"messages.pot": "bla",
"en.po": "bla",
"pl.po": "bla",
Expand All @@ -41,7 +41,7 @@ describe("getCatalogDependentFiles", () => {
const catalog = new Catalog(
{
name: null,
path: "src/locales/{locale}",
path: "/src/locales/{locale}",
include: ["src/"],
exclude: [],
format,
Expand All @@ -54,16 +54,16 @@ describe("getCatalogDependentFiles", () => {

expect(actual).toMatchInlineSnapshot(`
[
src/locales/messages.pot,
src/locales/pt-BR.po,
src/locales/en.po,
/src/locales/messages.pot,
/src/locales/pt-BR.po,
/src/locales/en.po,
]
`)
})

it("Should not return itself", async () => {
mockFs({
"src/locales": {
"/src/locales": {
"messages.pot": "bla",
"en.po": "bla",
"pl.po": "bla",
Expand All @@ -88,7 +88,7 @@ describe("getCatalogDependentFiles", () => {
const catalog = new Catalog(
{
name: null,
path: "src/locales/{locale}",
path: "/src/locales/{locale}",
include: ["src/"],
exclude: [],
format,
Expand All @@ -101,14 +101,14 @@ describe("getCatalogDependentFiles", () => {

expect(actual).toMatchInlineSnapshot(`
[
src/locales/messages.pot,
/src/locales/messages.pot,
]
`)
})

it("Should not return non-existing files", async () => {
mockFs({
"src/locales": {
"/src/locales": {
// "messages.pot": "bla",
"en.po": "bla",
"pl.po": "bla",
Expand All @@ -133,7 +133,7 @@ describe("getCatalogDependentFiles", () => {
const catalog = new Catalog(
{
name: null,
path: "src/locales/{locale}",
path: "/src/locales/{locale}",
include: ["src/"],
exclude: [],
format,
Expand All @@ -146,8 +146,8 @@ describe("getCatalogDependentFiles", () => {

expect(actual).toMatchInlineSnapshot(`
[
src/locales/pt-BR.po,
src/locales/en.po,
/src/locales/pt-BR.po,
/src/locales/en.po,
]
`)
})
Expand Down
6 changes: 2 additions & 4 deletions packages/cli/src/api/catalog/getCatalogDependentFiles.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { Catalog } from "../catalog"
import { getFallbackListForLocale } from "./getFallbackListForLocale"
import path from "pathe"
import fs from "node:fs/promises"

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

for (const file of files) {
const filePath = path.join(catalog.config.rootDir, file)
if (await fileExists(filePath)) {
out.push(filePath)
if (await fileExists(file)) {
out.push(file)
}
}

Expand Down
1 change: 1 addition & 0 deletions packages/loader/test/__snapshots__/loader.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ exports[`lingui-loader should compile catalog in json format 1`] = `

exports[`lingui-loader should compile catalog in po format 1`] = `
{
ED2Xk0: String from template,
mVmaLu: [
My name is ,
[
Expand Down
69 changes: 58 additions & 11 deletions packages/loader/test/compiler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,53 @@ import webpack from "webpack"
import { mkdtempSync } from "fs"
import os from "os"

export default async (
entryPoint: string,
options?: any
): Promise<webpack.StatsCompilation> => {
const compiler = webpack({
export type BuildResult = {
loadBundle(): Promise<any>
stats: webpack.StatsCompilation
}

export async function build(entryPoint: string): Promise<BuildResult> {
const compiler = getCompiler(entryPoint)

return new Promise((resolve, reject) => {
compiler.run((err, stats) => {
if (err) reject(err)
if (stats.hasErrors()) reject(stats.toJson().errors)

const jsonStats = stats.toJson()
resolve({
loadBundle: () => import(path.join(jsonStats.outputPath, "bundle.js")),
stats: jsonStats,
})
})
})
}

export function watch(entryPoint: string) {
const compiler = getCompiler(entryPoint)

let deferred = createDeferred<webpack.StatsCompilation>()

const watching = compiler.watch({}, async (err, stats) => {
err ? deferred.reject(err) : deferred.resolve(stats.toJson())
deferred = createDeferred<any>()
})

return {
build: async (): Promise<BuildResult> => {
const stats = await deferred.promise

return {
loadBundle: () => import(path.join(stats.outputPath, "bundle.js")),
stats,
}
},
stop: () => new Promise((resolve) => watching.close(resolve)),
}
}

export function getCompiler(entryPoint: string) {
return webpack({
mode: "development",
target: "node",
entry: entryPoint,
Expand All @@ -22,13 +64,18 @@ export default async (
libraryTarget: "commonjs",
},
})
}

return new Promise((resolve, reject) => {
compiler.run((err, stats) => {
if (err) reject(err)
if (stats.hasErrors()) reject(stats.toJson().errors)
function createDeferred<T>() {
let deferred: {
resolve: (r: T) => void
reject: (err: any) => void
promise: Promise<T>
}

resolve(stats.toJson())
})
const promise = new Promise<T>((resolve, reject) => {
deferred = { resolve, reject, promise: undefined }
})

return { ...deferred, promise }
}
94 changes: 85 additions & 9 deletions packages/loader/test/loader.test.ts
Original file line number Diff line number Diff line change
@@ -1,28 +1,104 @@
import path from "path"
import compiler from "./compiler"
import fs from "node:fs/promises"
import { build, watch } from "./compiler"
import { mkdtempSync } from "fs"
import os from "os"

const skipOnWindows = os.platform() === "win32" ? it.skip : it

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

const stats = await compiler(
path.join(__dirname, "po-format/entrypoint.js")
)
const built = await build(path.join(__dirname, "po-format/entrypoint.js"))

const data = await import(path.join(stats.outputPath, "bundle.js"))
expect(stats.errors).toEqual([])
const data = await built.loadBundle()
expect(built.stats.errors).toEqual([])
expect((await data.load()).messages).toMatchSnapshot()
})

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

const stats = await compiler(
const built = await build(
path.join(__dirname, "./json-format/entrypoint.js")
)

const data = await import(path.join(stats.outputPath, "bundle.js"))
expect(stats.errors).toEqual([])
expect(built.stats.errors).toEqual([])

const data = await built.loadBundle()
expect((await data.load()).messages).toMatchSnapshot()
})

skipOnWindows(
"should trigger webpack recompile on catalog dependency change",
async () => {
const fixtureTempPath = await copyFixture(
path.join(__dirname, "po-format")
)

const watching = watch(path.join(fixtureTempPath, "/entrypoint.js"))

const res = await watching.build()

expect((await res.loadBundle().then((m) => m.load())).messages)
.toMatchInlineSnapshot(`
{
ED2Xk0: String from template,
mVmaLu: [
My name is ,
[
name,
],
],
mY42CM: Hello World,
}
`)

// change the dependency
await fs.writeFile(
path.join(fixtureTempPath, "/locale/messages.pot"),
`msgid "Hello World"
msgstr ""

msgid "My name is {name}"
msgstr ""

msgid "String from template changes!"
msgstr ""
`
)

const stats2 = await watching.build()
jest.resetModules()

expect((await stats2.loadBundle().then((m) => m.load())).messages)
.toMatchInlineSnapshot(`
{
mVmaLu: [
My name is ,
[
name,
],
],
mY42CM: Hello World,
wg2uwk: String from template changes!,
}
`)

await watching.stop()
}
)
})

async function copyFixture(srcPath: string) {
const fixtureTempPath = mkdtempSync(
path.join(os.tmpdir(), `lingui-test-fixture-${process.pid}`)
)

await fs.cp(srcPath, fixtureTempPath, {
recursive: true,
})

return fixtureTempPath
}
2 changes: 1 addition & 1 deletion packages/loader/test/po-format/entrypoint.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
export async function load() {
return import("@lingui/loader?option=foo!./locale/en.po")
return import("@lingui/loader!./locale/en.po")
}
8 changes: 8 additions & 0 deletions packages/loader/test/po-format/locale/messages.pot
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
msgid "Hello World"
msgstr ""

msgid "My name is {name}"
msgstr ""

msgid "String from template"
msgstr ""