Skip to content

Commit ad04e8b

Browse files
authored
Merge pull request element-hq#8710 from vector-im/bwindels/moarcachebustin
Cache busting for icons & language files
2 parents 5d2d8cc + 6a9c053 commit ad04e8b

File tree

4 files changed

+93
-56
lines changed

4 files changed

+93
-56
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,7 @@
125125
"karma-spec-reporter": "0.0.31",
126126
"karma-summary-reporter": "^1.5.1",
127127
"karma-webpack": "4.0.0-beta.0",
128+
"loader-utils": "^1.2.3",
128129
"matrix-mock-request": "^1.2.2",
129130
"matrix-react-test-utils": "^0.2.0",
130131
"minimist": "^1.2.0",

scripts/copy-res.js

Lines changed: 62 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
#!/usr/bin/env node
22

3+
const loaderUtils = require("loader-utils");
4+
35
// copies the resources into the webapp directory.
46
//
57

@@ -61,12 +63,6 @@ const COPY_LIST = [
6163
["./config.json", "webapp", { directwatch: 1 }],
6264
];
6365

64-
INCLUDE_LANGS.forEach(function(l) {
65-
COPY_LIST.push([
66-
l.value, "webapp/i18n/", { lang: 1 },
67-
]);
68-
});
69-
7066
const parseArgs = require('minimist');
7167
const Cpx = require('cpx');
7268
const chokidar = require('chokidar');
@@ -77,8 +73,8 @@ const argv = parseArgs(
7773
process.argv.slice(2), {}
7874
);
7975

80-
var watch = argv.w;
81-
var verbose = argv.v;
76+
const watch = argv.w;
77+
const verbose = argv.v;
8278

8379
function errCheck(err) {
8480
if (err) {
@@ -136,39 +132,11 @@ function next(i, err) {
136132
.on('change', copy)
137133
.on('ready', cb)
138134
.on('error', errCheck);
139-
} else if (opts.lang) {
140-
const reactSdkFile = 'node_modules/matrix-react-sdk/src/i18n/strings/' + source + '.json';
141-
const riotWebFile = 'src/i18n/strings/' + source + '.json';
142-
143-
// XXX: Use a debounce because for some reason if we read the language
144-
// file immediately after the FS event is received, the file contents
145-
// appears empty. Possibly https://github.com/nodejs/node/issues/6112
146-
let makeLangDebouncer;
147-
const makeLang = () => {
148-
if (makeLangDebouncer) {
149-
clearTimeout(makeLangDebouncer);
150-
}
151-
makeLangDebouncer = setTimeout(() => {
152-
genLangFile(source, dest);
153-
}, 500);
154-
};
155-
156-
[reactSdkFile, riotWebFile].forEach(function(f) {
157-
chokidar.watch(f)
158-
.on('add', makeLang)
159-
.on('change', makeLang)
160-
//.on('ready', cb) We'd have to do this when both files are ready
161-
.on('error', errCheck);
162-
});
163-
next(i + 1, err);
164135
} else {
165136
cpx.on('watch-ready', cb);
166137
cpx.on("watch-error", cb);
167138
cpx.watch();
168139
}
169-
} else if (opts.lang) {
170-
genLangFile(source, dest);
171-
next(i + 1, err);
172140
} else {
173141
cpx.copy(cb);
174142
}
@@ -195,21 +163,28 @@ function genLangFile(lang, dest) {
195163

196164
translations = weblateToCounterpart(translations);
197165

198-
fs.writeFileSync(dest + lang + '.json', JSON.stringify(translations, null, 4));
166+
const json = JSON.stringify(translations, null, 4);
167+
const jsonBuffer = Buffer.from(json);
168+
const digest = loaderUtils.getHashDigest(jsonBuffer, null, null, 7);
169+
const filename = `${lang}.${digest}.json`;
170+
171+
fs.writeFileSync(dest + filename, json);
199172
if (verbose) {
200-
console.log("Generated language file: " + lang);
173+
console.log("Generated language file: " + filename);
201174
}
175+
176+
return filename;
202177
}
203178

204-
function genLangList() {
179+
function genLangList(langFileMap) {
205180
const languages = {};
206181
INCLUDE_LANGS.forEach(function(lang) {
207182
const normalizedLanguage = lang.value.toLowerCase().replace("_", "-");
208183
const languageParts = normalizedLanguage.split('-');
209184
if (languageParts.length == 2 && languageParts[0] == languageParts[1]) {
210-
languages[languageParts[0]] = {'fileName': lang.value + '.json', 'label': lang.label};
185+
languages[languageParts[0]] = {'fileName': langFileMap[lang.value], 'label': lang.label};
211186
} else {
212-
languages[normalizedLanguage] = {'fileName': lang.value + '.json', 'label': lang.label};
187+
languages[normalizedLanguage] = {'fileName': langFileMap[lang.value], 'label': lang.label};
213188
}
214189
});
215190
fs.writeFile('webapp/i18n/languages.json', JSON.stringify(languages, null, 4), function(err) {
@@ -257,5 +232,50 @@ function weblateToCounterpart(inTrs) {
257232
return outTrs;
258233
}
259234

260-
genLangList();
235+
/**
236+
watch the input files for a given language,
237+
regenerate the file, adding its content-hashed filename to langFileMap
238+
and regenerating languages.json with the new filename
239+
*/
240+
function watchLanguage(lang, dest, langFileMap) {
241+
const reactSdkFile = 'node_modules/matrix-react-sdk/src/i18n/strings/' + lang + '.json';
242+
const riotWebFile = 'src/i18n/strings/' + lang + '.json';
243+
244+
// XXX: Use a debounce because for some reason if we read the language
245+
// file immediately after the FS event is received, the file contents
246+
// appears empty. Possibly https://github.com/nodejs/node/issues/6112
247+
let makeLangDebouncer;
248+
const makeLang = () => {
249+
if (makeLangDebouncer) {
250+
clearTimeout(makeLangDebouncer);
251+
}
252+
makeLangDebouncer = setTimeout(() => {
253+
const filename = genLangFile(lang, dest);
254+
langFileMap[lang]=filename;
255+
genLangList(langFileMap);
256+
}, 500);
257+
};
258+
259+
[reactSdkFile, riotWebFile].forEach(function(f) {
260+
chokidar.watch(f)
261+
.on('add', makeLang)
262+
.on('change', makeLang)
263+
.on('error', errCheck);
264+
});
265+
}
266+
267+
// language resources
268+
const I18N_DEST = "webapp/i18n/";
269+
const I18N_FILENAME_MAP = INCLUDE_LANGS.reduce((m, l) => {
270+
const filename = genLangFile(l.value, I18N_DEST);
271+
m[l.value] = filename;
272+
return m;
273+
}, {});
274+
genLangList(I18N_FILENAME_MAP);
275+
276+
if (watch) {
277+
INCLUDE_LANGS.forEach(l => watchLanguage(l.value, I18N_DEST, I18N_FILENAME_MAP));
278+
}
279+
280+
// non-language resources
261281
next(0);

src/vector/index.html

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,22 +3,22 @@
33
<head>
44
<meta charset="utf-8">
55
<title>Riot</title>
6-
<link rel="apple-touch-icon" sizes="57x57" href="vector-icons/apple-touch-icon-57x57.png">
7-
<link rel="apple-touch-icon" sizes="60x60" href="vector-icons/apple-touch-icon-60x60.png">
8-
<link rel="apple-touch-icon" sizes="72x72" href="vector-icons/apple-touch-icon-72x72.png">
9-
<link rel="apple-touch-icon" sizes="76x76" href="vector-icons/apple-touch-icon-76x76.png">
10-
<link rel="apple-touch-icon" sizes="114x114" href="vector-icons/apple-touch-icon-114x114.png">
11-
<link rel="apple-touch-icon" sizes="120x120" href="vector-icons/apple-touch-icon-120x120.png">
12-
<link rel="apple-touch-icon" sizes="144x144" href="vector-icons/apple-touch-icon-144x144.png">
13-
<link rel="apple-touch-icon" sizes="152x152" href="vector-icons/apple-touch-icon-152x152.png">
14-
<link rel="apple-touch-icon" sizes="180x180" href="vector-icons/apple-touch-icon-180x180.png">
6+
<link rel="apple-touch-icon" sizes="57x57" href="<%= require('../../res/vector-icons/apple-touch-icon-57x57.png') %>">
7+
<link rel="apple-touch-icon" sizes="60x60" href="<%= require('../../res/vector-icons/apple-touch-icon-60x60.png') %>">
8+
<link rel="apple-touch-icon" sizes="72x72" href="<%= require('../../res/vector-icons/apple-touch-icon-72x72.png') %>">
9+
<link rel="apple-touch-icon" sizes="76x76" href="<%= require('../../res/vector-icons/apple-touch-icon-76x76.png') %>">
10+
<link rel="apple-touch-icon" sizes="114x114" href="<%= require('../../res/vector-icons/apple-touch-icon-114x114.png') %>">
11+
<link rel="apple-touch-icon" sizes="120x120" href="<%= require('../../res/vector-icons/apple-touch-icon-120x120.png') %>">
12+
<link rel="apple-touch-icon" sizes="144x144" href="<%= require('../../res/vector-icons/apple-touch-icon-144x144.png') %>">
13+
<link rel="apple-touch-icon" sizes="152x152" href="<%= require('../../res/vector-icons/apple-touch-icon-152x152.png') %>">
14+
<link rel="apple-touch-icon" sizes="180x180" href="<%= require('../../res/vector-icons/apple-touch-icon-180x180.png') %>">
1515
<link rel="manifest" href="manifest.json">
16-
<link rel="shortcut icon" href="vector-icons/favicon.ico">
16+
<link rel="shortcut icon" href="<%= require('../../res/vector-icons/favicon.ico') %>">
1717
<meta name="apple-mobile-web-app-title" content="Riot">
1818
<meta name="application-name" content="Riot">
1919
<meta name="msapplication-TileColor" content="#da532c">
20-
<meta name="msapplication-TileImage" content="vector-icons/mstile-144x144.png">
21-
<meta name="msapplication-config" content="vector-icons/browserconfig.xml">
20+
<meta name="msapplication-TileImage" content="<%= require('../../res/vector-icons/mstile-144x144.png') %>">
21+
<meta name="msapplication-config" content="<%= require('../../res/vector-icons/browserconfig.xml') %>">
2222
<meta name="theme-color" content="#ffffff">
2323
<meta property="og:image" content="<%= htmlWebpackPlugin.options.vars.og_image_url %>" />
2424
<% for (var i=0; i < htmlWebpackPlugin.files.css.length; i++) {

webpack.config.js

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,12 @@ const HtmlWebpackPlugin = require('html-webpack-plugin');
66
let og_image_url = process.env.RIOT_OG_IMAGE_URL;
77
if (!og_image_url) og_image_url = 'https://riot.im/app/themes/riot/img/logos/riot-im-logo-black-text.png';
88

9+
// relative to languageHandler.js in matrix-react-sdk
10+
let RIOT_LANGUAGES_FILE = process.env.RIOT_LANGUAGES_FILE;
11+
if (!RIOT_LANGUAGES_FILE) {
12+
RIOT_LANGUAGES_FILE = "../../riot-web/webapp/i18n/languages.json";
13+
}
14+
915
module.exports = {
1016
entry: {
1117
// Load babel-polyfill first to avoid issues where some imports (namely react)
@@ -61,7 +67,17 @@ module.exports = {
6167
}),
6268
},
6369
{
64-
test: /\.(gif|png|svg|ttf)$/,
70+
// cache-bust languages.json file placed in
71+
// riot-web/webapp/i18n during build by copy-res.js
72+
test: /\.*languages.json$/,
73+
type: "javascript/auto",
74+
loader: 'file-loader',
75+
options: {
76+
name: 'i18n/[name].[hash:7].[ext]',
77+
},
78+
},
79+
{
80+
test: /\.(gif|png|svg|ttf|xml|ico)$/,
6581
// Use a content-based hash in the name so that we can set a long cache
6682
// lifetime for assets while still delivering changes quickly.
6783
oneOf: [
@@ -148,8 +164,8 @@ module.exports = {
148164
'process.env': {
149165
NODE_ENV: JSON.stringify(process.env.NODE_ENV),
150166
},
167+
'LANGUAGES_FILE': JSON.stringify(RIOT_LANGUAGES_FILE),
151168
}),
152-
153169
new ExtractTextPlugin("bundles/[hash]/[name].css", {
154170
allChunks: true,
155171
}),

0 commit comments

Comments
 (0)