Ensure updated locales are auto-reloaded in electron renderer dev mode (#4066)

* * Ensure updated locales are auto-reloaded in electron renderer dev mode

* * Add watch external file plugin in dev mode only

* * Make changes from code review
This commit is contained in:
PikachuEXE 2023-10-26 05:36:35 +08:00 committed by GitHub
parent 2c562d60ca
commit 342444f433
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 123 additions and 40 deletions

View File

@ -1,4 +1,4 @@
const { existsSync, readFileSync } = require('fs')
const { existsSync, readFileSync, statSync } = require('fs')
const { brotliCompress, constants } = require('zlib')
const { promisify } = require('util')
const { load: loadYaml } = require('js-yaml')
@ -8,6 +8,7 @@ const brotliCompressAsync = promisify(brotliCompress)
class ProcessLocalesPlugin {
constructor(options = {}) {
this.compress = !!options.compress
this.isIncrementalBuild = false
if (typeof options.inputDir !== 'string') {
throw new Error('ProcessLocalesPlugin: no input directory `inputDir` specified.')
@ -21,10 +22,11 @@ class ProcessLocalesPlugin {
}
this.outputDir = options.outputDir
this.locales = []
this.locales = {}
this.localeNames = []
this.activeLocales = []
this.cache = []
this.cache = {}
this.loadLocales()
}
@ -37,66 +39,98 @@ class ProcessLocalesPlugin {
compilation.hooks.additionalAssets.tapPromise('process-locales-plugin', async (_assets) => {
// While running in the webpack dev server, this hook gets called for every incrememental build.
// While running in the webpack dev server, this hook gets called for every incremental build.
// For incremental builds we can return the already processed versions, which saves time
// and makes webpack treat them as cached
if (IS_DEV_SERVER && this.cache.length > 0) {
for (const { filename, source } of this.cache) {
compilation.emitAsset(filename, source, { minimized: true })
}
const promises = []
// Prevents `loadLocales` called twice on first time (e.g. release build)
if (this.isIncrementalBuild) {
this.loadLocales(true)
} else {
const promises = []
this.isIncrementalBuild = true
}
for (const { locale, data } of this.locales) {
promises.push(new Promise(async (resolve) => {
if (Object.prototype.hasOwnProperty.call(data, 'Locale Name')) {
delete data['Locale Name']
Object.values(this.locales).forEach((localeEntry) => {
const { locale, data, mtimeMs } = localeEntry
promises.push(new Promise(async (resolve) => {
if (IS_DEV_SERVER) {
const cacheEntry = this.cache[locale]
if (cacheEntry != null) {
const { filename, source, mtimeMs: cachedMtimeMs } = cacheEntry
if (cachedMtimeMs === mtimeMs) {
compilation.emitAsset(filename, source, { minimized: true })
resolve()
return
}
}
}
this.removeEmptyValues(data)
if (Object.prototype.hasOwnProperty.call(data, 'Locale Name')) {
delete data['Locale Name']
}
let filename = `${this.outputDir}/${locale}.json`
let output = JSON.stringify(data)
this.removeEmptyValues(data)
if (this.compress) {
filename += '.br'
output = await this.compressLocale(output)
}
let filename = `${this.outputDir}/${locale}.json`
let output = JSON.stringify(data)
let source = new RawSource(output)
if (this.compress) {
filename += '.br'
output = await this.compressLocale(output)
}
if (IS_DEV_SERVER) {
source = new CachedSource(source)
this.cache.push({ filename, source })
}
let source = new RawSource(output)
compilation.emitAsset(filename, source, { minimized: true })
if (IS_DEV_SERVER) {
source = new CachedSource(source)
this.cache[locale] = { filename, source, mtimeMs }
}
resolve()
}))
}
compilation.emitAsset(filename, source, { minimized: true })
await Promise.all(promises)
resolve()
}))
if (IS_DEV_SERVER) {
// we don't need the unmodified sources anymore, as we use the cache `this.cache`
// so we can clear this to free some memory
delete this.locales
delete localeEntry.data
}
}
})
await Promise.all(promises)
})
})
}
loadLocales() {
const activeLocales = JSON.parse(readFileSync(`${this.inputDir}/activeLocales.json`))
loadLocales(loadModifiedFilesOnly = false) {
if (this.activeLocales.length === 0) {
this.activeLocales = JSON.parse(readFileSync(`${this.inputDir}/activeLocales.json`))
}
for (const locale of activeLocales) {
const contents = readFileSync(`${this.inputDir}/${locale}.yaml`, 'utf-8')
for (const locale of this.activeLocales) {
const filePath = `${this.inputDir}/${locale}.yaml`
// Cannot use `mtime` since values never equal
const mtimeMsFromStats = statSync(filePath).mtimeMs
if (loadModifiedFilesOnly) {
// Skip reading files where mtime (modified time) same as last read
// (stored in mtime)
const existingMtime = this.locales[locale]?.mtimeMs
if (existingMtime != null && existingMtime === mtimeMsFromStats) {
continue
}
}
const contents = readFileSync(filePath, 'utf-8')
const data = loadYaml(contents)
this.locales[locale] = { locale, data, mtimeMs: mtimeMsFromStats }
this.localeNames.push(data['Locale Name'] ?? locale)
this.locales.push({ locale, data })
const localeName = data['Locale Name'] ?? locale
if (!loadModifiedFilesOnly) {
this.localeNames.push(localeName)
}
}
}

View File

@ -1,10 +1,12 @@
const path = require('path')
const { readFileSync } = require('fs')
const webpack = require('webpack')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const VueLoaderPlugin = require('vue-loader/lib/plugin')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin')
const ProcessLocalesPlugin = require('./ProcessLocalesPlugin')
const WatchExternalFilesPlugin = require('webpack-watch-external-files-plugin')
const isDevMode = process.env.NODE_ENV === 'development'
@ -128,7 +130,7 @@ const config = {
new MiniCssExtractPlugin({
filename: isDevMode ? '[name].css' : '[name].[contenthash].css',
chunkFilename: isDevMode ? '[id].css' : '[id].[contenthash].css',
})
}),
],
resolve: {
alias: {
@ -146,4 +148,16 @@ const config = {
target: 'electron-renderer',
}
if (isDevMode) {
const activeLocales = JSON.parse(readFileSync(path.join(__dirname, '../static/locales/activeLocales.json')))
config.plugins.push(
new WatchExternalFilesPlugin({
files: [
`./static/locales/{${activeLocales.join(',')}}.yaml`,
],
}),
)
}
module.exports = config

View File

@ -127,6 +127,7 @@
"webpack": "^5.89.0",
"webpack-cli": "^5.1.4",
"webpack-dev-server": "^4.15.1",
"webpack-watch-external-files-plugin": "^2.0.0",
"yaml-eslint-parser": "^1.2.2"
}
}

View File

@ -4491,6 +4491,17 @@ glob-to-regexp@^0.4.1:
resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz#c75297087c851b9a578bd217dd59a92f59fe546e"
integrity sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==
glob@8.1.0:
version "8.1.0"
resolved "https://registry.yarnpkg.com/glob/-/glob-8.1.0.tgz#d388f656593ef708ee3e34640fdfb99a9fd1c33e"
integrity sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==
dependencies:
fs.realpath "^1.0.0"
inflight "^1.0.4"
inherits "2"
minimatch "^5.0.1"
once "^1.3.0"
glob@^10.3.7:
version "10.3.10"
resolved "https://registry.yarnpkg.com/glob/-/glob-10.3.10.tgz#0351ebb809fd187fe421ab96af83d3a70715df4b"
@ -6476,6 +6487,14 @@ path-type@^4.0.0:
resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b"
integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==
path@0.12.7:
version "0.12.7"
resolved "https://registry.yarnpkg.com/path/-/path-0.12.7.tgz#d4dc2a506c4ce2197eb481ebfcd5b36c0140b10f"
integrity sha512-aXXC6s+1w7otVF9UletFkFcDsJeO7lSZBPUQhtb5O0xJe8LtYhj/GxldoL09bBj9+ZmE2hNoHqQSFMN5fikh4Q==
dependencies:
process "^0.11.1"
util "^0.10.3"
pend@~1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50"
@ -6858,7 +6877,7 @@ process-nextick-args@~2.0.0:
resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2"
integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==
process@^0.11.10:
process@^0.11.1, process@^0.11.10:
version "0.11.10"
resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182"
integrity sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==
@ -8431,6 +8450,13 @@ util-deprecate@^1.0.1, util-deprecate@^1.0.2, util-deprecate@~1.0.1:
resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=
util@^0.10.3:
version "0.10.4"
resolved "https://registry.yarnpkg.com/util/-/util-0.10.4.tgz#3aa0125bfe668a4672de58857d3ace27ecb76901"
integrity sha512-0Pm9hTQ3se5ll1XihRic3FDIku70C+iHUdT/W926rSgHV5QgXsYbKZN8MSC3tJtSkhuROzvsQjAaFENRXr+19A==
dependencies:
inherits "2.0.3"
util@^0.12.4:
version "0.12.4"
resolved "https://registry.yarnpkg.com/util/-/util-0.12.4.tgz#66121a31420df8f01ca0c464be15dfa1d1850253"
@ -8750,6 +8776,14 @@ webpack-sources@^3.2.3:
resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-3.2.3.tgz#2d4daab8451fd4b240cc27055ff6a0c2ccea0cde"
integrity sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==
webpack-watch-external-files-plugin@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/webpack-watch-external-files-plugin/-/webpack-watch-external-files-plugin-2.0.0.tgz#4c4e08c59b092c1d705e94d7a380eaa827a76d9c"
integrity sha512-dPUCEcgBjJHWyD4b0HcHD6h3mkPxVKmsfUSks5CvCbrF7HHLvIl/Wq4KSkUMMiNymloZKKkBjYpQxhubAEFM+Q==
dependencies:
glob "8.1.0"
path "0.12.7"
webpack@^5.89.0:
version "5.89.0"
resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.89.0.tgz#56b8bf9a34356e93a6625770006490bf3a7f32dc"