Skip to content

Commit 85d3493

Browse files
fix: 🐛 use proper webpack hook
Refactors the webpack plugin to use the proper hook, only extracting keys from processed files. Also avoids triggering compilation twice in watch mode.
1 parent 8f17495 commit 85d3493

File tree

4 files changed

+103
-91
lines changed

4 files changed

+103
-91
lines changed

src/keys-builder/typescript/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ export function extractTSKeys(config: Config): ExtractionResult {
2626

2727
const translocoImport = /@(jsverse|ngneat)\/transloco/;
2828
const translocoKeysManagerImport = /@(jsverse|ngneat)\/transloco-keys-manager/;
29-
function TSExtractor(config: ExtractorConfig): ScopeMap {
29+
export function TSExtractor(config: ExtractorConfig): ScopeMap {
3030
const { file, scopes, defaultValue, scopeToKeys } = config;
3131
const content = readFile(file);
3232
const extractors = [];

src/utils/file.utils.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { readFileSync, writeFileSync } from 'fs';
1+
import { readFileSync, statSync, utimesSync, writeFileSync } from 'fs';
22

33
import { stringify } from './object.utils';
44

@@ -21,6 +21,16 @@ export function readFile(
2121
return content;
2222
}
2323

24-
export function writeFile(fileName: string, content: object) {
24+
export function writeFile(
25+
fileName: string,
26+
content: object,
27+
preserve: boolean = false,
28+
) {
29+
const stats = preserve
30+
? statSync(fileName, { throwIfNoEntry: false })
31+
: undefined;
2532
writeFileSync(fileName, stringify(content), { encoding: 'utf-8' });
33+
if (stats) {
34+
utimesSync(fileName, stats.atime, stats.mtime);
35+
}
2636
}

src/webpack-plugin/generate-keys.ts

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { sync as globSync } from 'glob';
44
import { basename } from 'node:path';
55

66
import { ScopeMap, Config } from '../types';
7-
import { readFile, writeFile } from '../utils/file.utils';
7+
import { readFile } from '../utils/file.utils';
88
import { mergeDeep } from '../utils/object.utils';
99

1010
type Params = {
@@ -54,13 +54,18 @@ export function generateKeys({ translationPath, scopeToKeys, config }: Params) {
5454
}
5555
}
5656

57-
for (let { files, keys } of result) {
57+
return result.flatMap(({ files, keys }) => {
5858
if (config.unflat) {
5959
keys = unflatten(keys);
6060
}
61-
for (const filePath of files) {
62-
const translation = readFile(filePath, { parse: true });
63-
writeFile(filePath, mergeDeep({}, keys, translation));
64-
}
65-
}
61+
return files.map((filePath) => {
62+
let translation: Record<string, any>;
63+
try {
64+
translation = readFile(filePath, { parse: true });
65+
} catch {
66+
translation = {};
67+
}
68+
return { filePath, content: mergeDeep({}, keys, translation) };
69+
});
70+
});
6671
}

src/webpack-plugin/webpack-plugin.ts

Lines changed: 78 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,14 @@
1-
import { buildTranslationFiles } from '../keys-builder';
2-
import { buildTranslationFile } from '../keys-builder/build-translation-file';
3-
import { extractTemplateKeys } from '../keys-builder/template';
4-
import { extractTSKeys } from '../keys-builder/typescript';
1+
import { relative, sep } from 'node:path';
2+
import { templateExtractor } from '../keys-builder/template';
3+
import { TSExtractor } from '../keys-builder/typescript';
54
import { Config, ScopeMap, FileType } from '../types';
5+
import { writeFile } from '../utils/file.utils';
66
import { initExtraction } from '../utils/init-extraction';
77
import { mergeDeep } from '../utils/object.utils';
8-
import { buildScopeFilePaths } from '../utils/path.utils';
98
import { resolveConfig } from '../utils/resolve-config';
10-
import { updateScopesMap } from '../utils/update-scopes-map';
119

1210
import { generateKeys } from './generate-keys';
1311

14-
let init = true;
15-
1612
export class TranslocoExtractKeysWebpackPlugin {
1713
config: Config;
1814

@@ -21,82 +17,83 @@ export class TranslocoExtractKeysWebpackPlugin {
2117
}
2218

2319
apply(compiler: any) {
24-
compiler.hooks.watchRun.tapPromise(
20+
compiler.hooks.thisCompilation.tap(
2521
'TranslocoExtractKeysPlugin',
26-
async (comp: any) => {
27-
if (init) {
28-
init = false;
29-
return buildTranslationFiles(this.config);
30-
}
31-
32-
const keysExtractions: Record<FileType, string[]> = {
33-
html: [],
34-
ts: [],
35-
};
36-
const files =
37-
comp.modifiedFiles ||
38-
Object.keys(comp.watchFileSystem.watcher.mtimes);
39-
40-
for (const file of files) {
41-
const fileType = resolveFileType(file);
42-
43-
if (fileType) {
44-
keysExtractions[fileType].push(file);
45-
}
46-
}
47-
48-
if (keysExtractions.html.length || keysExtractions.ts.length) {
49-
let htmlResult = initExtraction();
50-
let tsResult = initExtraction();
51-
if (keysExtractions.ts.length) {
52-
// Maybe someone added a TRANSLOCO_SCOPE
53-
const newScopes = updateScopesMap({ files: keysExtractions.ts });
54-
55-
const paths = buildScopeFilePaths({
56-
aliasToScope: newScopes,
57-
langs: this.config.langs,
58-
output: this.config.output,
59-
fileFormat: this.config.fileFormat,
60-
});
61-
62-
paths.forEach(({ path }) =>
63-
buildTranslationFile({
64-
path,
65-
fileFormat: this.config.fileFormat,
66-
}),
22+
(comp: any) => {
23+
comp.hooks.processAssets.tap(
24+
{
25+
name: 'TranslocoExtractKeysPlugin',
26+
stage: compiler.webpack.Compilation.PROCESS_ASSETS_STAGE_ADDITIONAL,
27+
},
28+
() => {
29+
let htmlResult = initExtraction();
30+
let tsResult = initExtraction();
31+
const files = compiler.modifiedFiles || comp.fileDependencies;
32+
33+
for (const file of files) {
34+
if (
35+
!this.config.input.some((input) => file.startsWith(input + sep))
36+
) {
37+
continue;
38+
}
39+
40+
const fileType = resolveFileType(file);
41+
42+
switch (fileType) {
43+
case 'html': {
44+
htmlResult.scopeToKeys = templateExtractor({
45+
defaultValue: this.config.defaultValue,
46+
file,
47+
scopes: this.config.scopes,
48+
scopeToKeys: htmlResult.scopeToKeys,
49+
});
50+
}
51+
case 'ts': {
52+
tsResult.scopeToKeys = TSExtractor({
53+
defaultValue: this.config.defaultValue,
54+
file,
55+
scopes: this.config.scopes,
56+
scopeToKeys: tsResult.scopeToKeys,
57+
});
58+
}
59+
}
60+
}
61+
62+
const scopeToKeys = mergeDeep(
63+
{},
64+
htmlResult.scopeToKeys,
65+
tsResult.scopeToKeys,
66+
) as ScopeMap;
67+
const hasTranslateKeys = Object.keys(scopeToKeys).some(
68+
(key) => Object.keys(scopeToKeys[key]).length > 0,
6769
);
68-
tsResult = extractTSKeys({
69-
...this.config,
70-
files: keysExtractions.ts,
71-
});
72-
}
73-
74-
if (keysExtractions.html.length) {
75-
htmlResult = extractTemplateKeys({
76-
...this.config,
77-
files: keysExtractions.html,
78-
});
79-
}
80-
81-
const scopeToKeys = mergeDeep(
82-
{},
83-
htmlResult.scopeToKeys,
84-
tsResult.scopeToKeys,
85-
) as ScopeMap;
86-
const hasTranslateKeys = Object.keys(scopeToKeys).some(
87-
(key) => Object.keys(scopeToKeys[key]).length > 0,
88-
);
89-
90-
if (hasTranslateKeys) {
91-
generateKeys({
92-
config: this.config,
93-
translationPath: this.config.translationsPath,
94-
scopeToKeys,
95-
});
96-
}
97-
}
9870

99-
return Promise.resolve();
71+
if (hasTranslateKeys) {
72+
const files = generateKeys({
73+
config: this.config,
74+
translationPath: this.config.translationsPath,
75+
scopeToKeys,
76+
});
77+
78+
for (const { filePath, content } of files) {
79+
writeFile(filePath, content, true);
80+
81+
for (const [name, info] of comp.assetsInfo) {
82+
if (
83+
info.sourceFilename &&
84+
relative(info.sourceFilename, filePath) === ''
85+
) {
86+
comp.updateAsset(
87+
name,
88+
new compiler.webpack.sources.RawSource(content),
89+
);
90+
break;
91+
}
92+
}
93+
}
94+
}
95+
},
96+
);
10097
},
10198
);
10299
}

0 commit comments

Comments
 (0)