Skip to content

Commit a900ea6

Browse files
committed
Add early variable resolution in build websites to reduce flickering
Especially for Stable and Release builds most interpolated data, that are prominently shown at the top of the main page, can be derived from the drop-folder's name, which is contained in the page's URL/location. Using the identifier implied by the drop-folder allows to render the page with these most prominently visible variables already resolved initially and not only on the second pass, when the buildproperties.json is fetched. Thus this reduces flickering.
1 parent f36adf8 commit a900ea6

File tree

2 files changed

+89
-17
lines changed

2 files changed

+89
-17
lines changed

sites/eclipse/build/index.html

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -110,18 +110,20 @@ <h3 id="swt">SWT Binary and Source
110110
<script>
111111
loadPageData('buildproperties.json', data => {
112112
data.buildTypeName = getBuildTypeName(data)
113-
data.updatesP2RepositoryComposite = `https://download.eclipse.org/eclipse/updates/${data.releaseShort}`
114-
const buildID = data.identifier
115-
const buildType = buildID.charAt(0)
116-
if (buildType == 'I' || buildType == 'Y') {
117-
data.updatesP2RepositoryComposite += `-${buildType}-builds`
118-
}
119-
if (buildType == 'S') {
120-
data.updatesP2RepositoryComposite += '-I-builds'
121-
const timestamp = buildID.substring(buildID.lastIndexOf('-') + 1, buildID.length)
122-
data.updatesP2Repository = `${data.updatesP2RepositoryComposite}/I${timestamp.substring(0, 8)}-${timestamp.substring(8, 12)}`
123-
} else {
124-
data.updatesP2Repository = `${data.updatesP2RepositoryComposite}/${buildID}`
113+
if (data.releaseShort) {
114+
data.updatesP2RepositoryComposite = `https://download.eclipse.org/eclipse/updates/${data.releaseShort}`
115+
const buildID = data.identifier
116+
const buildType = buildID.charAt(0)
117+
if (buildType == 'I' || buildType == 'Y') {
118+
data.updatesP2RepositoryComposite += `-${buildType}-builds`
119+
}
120+
if (buildType == 'S') {
121+
data.updatesP2RepositoryComposite += '-I-builds'
122+
const timestamp = buildID.substring(buildID.lastIndexOf('-') + 1, buildID.length)
123+
data.updatesP2Repository = `${data.updatesP2RepositoryComposite}/I${timestamp.substring(0, 8)}-${timestamp.substring(8, 12)}`
124+
} else {
125+
data.updatesP2Repository = `${data.updatesP2RepositoryComposite}/${buildID}`
126+
}
125127
}
126128
return data
127129
})

sites/eclipse/page.js

Lines changed: 75 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,46 @@ function getCPUArchLabel(name) {
104104
}
105105
}
106106

107+
function getPreliminaryPageData() {
108+
const identifier = getIdentifierFromSiteLocation()
109+
if (identifier) {
110+
let data = { identifier: identifier }
111+
if (identifier.startsWith('I') || identifier.startsWith('Y')) {
112+
data.label = identifier
113+
} else if (identifier.startsWith('S-') || identifier.startsWith('R-')) {
114+
data.label = identifier.substring(2, identifier.indexOf('-', 2))
115+
const versionEndIndex = Math.max(data.label.indexOf('M'), data.label.indexOf('RC'))
116+
const version = versionEndIndex < 0 ? data.label : data.label.substring(0, versionEndIndex)
117+
data.release = version.split('.').length === 2 ? (version + '.0') : version
118+
data.releaseShort = data.release.substring(0, data.release.lastIndexOf('.'))
119+
} else {
120+
throw new Error(`Unexpected identifier: ${identifier}`)
121+
}
122+
if (!identifier.startsWith('Y')) { // Y-build's adapt their 'kind' to the targeted java version, which is therefore not constant
123+
data.kind = 'Integration'
124+
}
125+
if (_dataGenerator) {
126+
data = _dataGenerator(data)
127+
}
128+
Object.keys(data).forEach(k => data[k] == undefined && delete data[k]);
129+
return data
130+
}
131+
return null
132+
}
133+
134+
function getIdentifierFromSiteLocation() {
135+
if (window.location.hostname === 'download.eclipse.org') {
136+
const pathname = window.location.pathname
137+
for (const prefix of ['/eclipse/downloads/drops4/', '/equinox/drops/']) {
138+
if (pathname.startsWith(prefix)) {
139+
const idEndingSlash = pathname.indexOf('/', prefix.length)
140+
return pathname.substring(prefix.length, idEndingSlash > -1 ? idEndingSlash : pathname.length)
141+
}
142+
}
143+
}
144+
return null
145+
}
146+
107147
const BUILD_DATE_FORMAT = new Intl.DateTimeFormat('en-GB', {
108148
timeZone: 'UTC',
109149
year: 'numeric',
@@ -173,8 +213,10 @@ function fetchAllJSON(urls) {
173213
}
174214

175215
let _pageData = null
216+
let _dataGenerator = null
176217

177218
function loadPageData(dataPath, dataGenerator = null) {
219+
_dataGenerator = dataGenerator
178220
_pageData = fetch(dataPath).then(res => res.json())
179221
if (dataGenerator) {
180222
_pageData = _pageData.then(dataGenerator)
@@ -200,12 +242,20 @@ function generate() {
200242
}
201243

202244
const generatedBody = generateBody();
245+
// To reduce flickering, early resolve variables that can be derived from the build-drop's folder name
246+
const preliminaryData = _pageData ? getPreliminaryPageData() : null
247+
if (preliminaryData) {
248+
resolveDataReferences(generatedBody, preliminaryData, true)
249+
}
203250
document.body.replaceChildren(generatedBody);
204251

205252
generateTOCItems(document.body) // assume no headers (for the TOC) are generated dynamically
206253

207254
if (_pageData) {
208255
_pageData.then(data => {
256+
if (preliminaryData) {
257+
verifyDataConsistency(preliminaryData, data)
258+
}
209259
const mainElement = document.body.querySelector('main')
210260
const contentMain = mainElement.querySelector('main') // This is the main element of the calling html file
211261
resolveDataReferences(document, data)
@@ -234,27 +284,47 @@ function generateTOCItems(mainElement) {
234284

235285
const dataReferencePattern = /\${(?<path>[\w\-\.]+)}/g
236286

237-
function resolveDataReferences(contextElement, contextData) {
287+
function resolveDataReferences(contextElement, contextData, lenient = false) {
238288
const dataElements = Array.from(contextElement.getElementsByClassName('data-ref'))
239289
for (const element of dataElements) {
240-
element.classList.remove('data-ref') // Prevent multiple processing in subsequent passes with different context (therefore a copy is created from the list)
241-
element.outerHTML = element.outerHTML.replaceAll(dataReferencePattern, (_match, pathGroup, _offset, _string) => {
242-
return getValue(contextData, pathGroup)
290+
const resolved = element.outerHTML.replaceAll(dataReferencePattern, (match, pathGroup, _offset, _string) => {
291+
return getValue(contextData, pathGroup, lenient ? match : undefined)
243292
})
293+
element.outerHTML = resolved
294+
dataReferencePattern.lastIndex = 0 // reset lastIndex as RegExp.prototype.test() is stateful. See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/test
295+
if (!lenient || !dataReferencePattern.test(resolved)) {
296+
// Prevent multiple processing in subsequent passes with different context (if no variables are contained anymore)
297+
element.classList.remove('data-ref')
298+
}
244299
}
245300
}
246301

247-
function getValue(data, path) {
302+
function getValue(data, path, lenienceDefaultValue = undefined) {
248303
let value = data
249304
for (const key of path.split('.')) {
250305
if (!value.hasOwnProperty(key)) {
306+
if (lenienceDefaultValue) {
307+
return lenienceDefaultValue // just skip absent variables
308+
}
251309
throw new Error(`Key '${key}' not found in ${JSON.stringify(value)}`)
252310
}
253311
value = value[key]
254312
}
255313
return value;
256314
}
257315

316+
function verifyDataConsistency(preliminaryData, data) {
317+
for (const key in preliminaryData) {
318+
if (data[key] !== preliminaryData[key]) {
319+
const msg = `Prelininary value of key '${key}' differes from loaded data.
320+
preliminary - ${preliminaryData[key]},
321+
loaded data - ${data[key]}`
322+
logException(msg, msg)
323+
throw new Error(msg)
324+
}
325+
}
326+
}
327+
258328
function logException(message, loggedObject) {
259329
document.body.prepend(toElement(`<p>Failed to generate content: <b style="color: FireBrick">${message}</b></p>`));
260330
console.log(loggedObject);

0 commit comments

Comments
 (0)