Skip to content

Commit 06bd9d1

Browse files
authored
Merge pull request element-hq#13032 from vector-im/t3chguy/app_load1
App load order tweaks for code splitting
2 parents cb39edb + 1b9112b commit 06bd9d1

File tree

10 files changed

+217
-159
lines changed

10 files changed

+217
-159
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@
9696
"@babel/preset-typescript": "^7.7.4",
9797
"@babel/register": "^7.7.4",
9898
"@babel/runtime": "^7.7.6",
99+
"@types/modernizr": "^3.5.3",
99100
"@types/react": "16.9",
100101
"@types/react-dom": "^16.9.4",
101102
"autoprefixer": "^9.7.3",

src/@types/global.d.ts

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,22 @@ See the License for the specific language governing permissions and
1414
limitations under the License.
1515
*/
1616

17-
interface Window {
18-
Olm: {
19-
init: () => Promise<void>;
20-
};
21-
mxSendRageshake: (text: string, withLogs?: boolean) => void;
17+
import {ReactNode} from "react";
18+
import "modernizr";
19+
20+
declare global {
21+
interface Window {
22+
Modernizr: ModernizrAPI & FeatureDetects;
23+
Olm: {
24+
init: () => Promise<void>;
25+
};
26+
27+
mxSendRageshake: (text: string, withLogs?: boolean) => void;
28+
matrixChat: ReactNode;
29+
}
30+
31+
// workaround for https://github.com/microsoft/TypeScript/issues/30933
32+
interface ObjectConstructor {
33+
fromEntries?(xs: [string|number|symbol, any][]): object
34+
}
2235
}

src/vector/app.js

Lines changed: 28 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ import React from 'react';
2323
// access via the console
2424
global.React = React;
2525

26-
import ReactDOM from 'react-dom';
2726
import * as sdk from 'matrix-react-sdk';
2827
import PlatformPeg from 'matrix-react-sdk/src/PlatformPeg';
2928
import * as VectorConferenceHandler from 'matrix-react-sdk/src/VectorConferenceHandler';
@@ -45,41 +44,6 @@ import {loadConfig, preparePlatform, loadLanguage, loadOlm} from "./init";
4544

4645
let lastLocationHashSet = null;
4746

48-
function checkBrowserFeatures() {
49-
if (!window.Modernizr) {
50-
console.error("Cannot check features - Modernizr global is missing.");
51-
return false;
52-
}
53-
54-
// custom checks atop Modernizr because it doesn't have ES2018/ES2019 checks in it for some features we depend on,
55-
// Modernizr requires rules to be lowercase with no punctuation:
56-
// ES2018: http://www.ecma-international.org/ecma-262/9.0/#sec-promise.prototype.finally
57-
window.Modernizr.addTest("promiseprototypefinally", () =>
58-
window.Promise && window.Promise.prototype && typeof window.Promise.prototype.finally === "function");
59-
// ES2019: http://www.ecma-international.org/ecma-262/10.0/#sec-object.fromentries
60-
window.Modernizr.addTest("objectfromentries", () =>
61-
window.Object && typeof window.Object.fromEntries === "function");
62-
63-
const featureList = Object.keys(window.Modernizr);
64-
65-
let featureComplete = true;
66-
for (let i = 0; i < featureList.length; i++) {
67-
if (window.Modernizr[featureList[i]] === undefined) {
68-
console.error(
69-
"Looked for feature '%s' but Modernizr has no results for this. " +
70-
"Has it been configured correctly?", featureList[i],
71-
);
72-
return false;
73-
}
74-
if (window.Modernizr[featureList[i]] === false) {
75-
console.error("Browser missing feature: '%s'", featureList[i]);
76-
// toggle flag rather than return early so we log all missing features rather than just the first.
77-
featureComplete = false;
78-
}
79-
}
80-
return featureComplete;
81-
}
82-
8347
// Parse the given window.location and return parameters that can be used when calling
8448
// MatrixChat.showScreen(screen, params)
8549
function getScreenFromLocation(location) {
@@ -164,7 +128,7 @@ function onTokenLoginCompleted() {
164128
window.location.href = formatted;
165129
}
166130

167-
export async function loadApp() {
131+
export async function loadApp(fragParams: {}, acceptBrowser: boolean) {
168132
// XXX: the way we pass the path to the worker script from webpack via html in body's dataset is a hack
169133
// but alternatives seem to require changing the interface to passing Workers to js-sdk
170134
const vectorIndexeddbWorkerScript = document.body.dataset.vectorIndexeddbWorkerScript;
@@ -192,26 +156,8 @@ export async function loadApp() {
192156
// Load language after loading config.json so that settingsDefaults.language can be applied
193157
await loadLanguage();
194158

195-
const fragparts = parseQsFromFragment(window.location);
196159
const params = parseQs(window.location);
197160

198-
// don't try to redirect to the native apps if we're
199-
// verifying a 3pid (but after we've loaded the config)
200-
// or if the user is following a deep link
201-
// (https://github.com/vector-im/riot-web/issues/7378)
202-
const preventRedirect = fragparts.params.client_secret || fragparts.location.length > 0;
203-
204-
if (!preventRedirect) {
205-
const isIos = /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream;
206-
const isAndroid = /Android/.test(navigator.userAgent);
207-
if (isIos || isAndroid) {
208-
if (document.cookie.indexOf("riot_mobile_redirect_to_guide=false") === -1) {
209-
window.location = "mobile_guide/";
210-
return;
211-
}
212-
}
213-
}
214-
215161
// as quickly as we possibly can, set a default theme...
216162
await setTheme();
217163

@@ -235,45 +181,35 @@ export async function loadApp() {
235181
);
236182

237183
const GenericErrorPage = sdk.getComponent("structures.GenericErrorPage");
238-
window.matrixChat = ReactDOM.render(
239-
<GenericErrorPage message={errorMessage} title={_t("Your Riot is misconfigured")} />,
240-
document.getElementById('matrixchat'),
241-
);
242-
return;
184+
return <GenericErrorPage message={errorMessage} title={_t("Your Riot is misconfigured")} />;
243185
}
244186

245-
const validBrowser = checkBrowserFeatures();
246-
247-
const acceptInvalidBrowser = window.localStorage && window.localStorage.getItem('mx_accepts_unsupported_browser');
248-
249187
const urlWithoutQuery = window.location.protocol + '//' + window.location.host + window.location.pathname;
250188
console.log("Vector starting at " + urlWithoutQuery);
251189
if (configError) {
252-
window.matrixChat = ReactDOM.render(<div className="error">
190+
return <div className="error">
253191
Unable to load config file: please refresh the page to try again.
254-
</div>, document.getElementById('matrixchat'));
255-
} else if (validBrowser || acceptInvalidBrowser) {
192+
</div>;
193+
} else if (acceptBrowser) {
256194
platform.startUpdater();
257195

258-
// Don't bother loading the app until the config is verified
259-
verifyServerConfig().then((newConfig) => {
196+
try {
197+
// Don't bother loading the app until the config is verified
198+
const config = await verifyServerConfig();
260199
const MatrixChat = sdk.getComponent('structures.MatrixChat');
261-
window.matrixChat = ReactDOM.render(
262-
<MatrixChat
263-
onNewScreen={onNewScreen}
264-
makeRegistrationUrl={makeRegistrationUrl}
265-
ConferenceHandler={VectorConferenceHandler}
266-
config={newConfig}
267-
realQueryParams={params}
268-
startingFragmentQueryParams={fragparts.params}
269-
enableGuest={!SdkConfig.get().disable_guests}
270-
onTokenLoginCompleted={onTokenLoginCompleted}
271-
initialScreenAfterLogin={getScreenFromLocation(window.location)}
272-
defaultDeviceDisplayName={platform.getDefaultDeviceDisplayName()}
273-
/>,
274-
document.getElementById('matrixchat'),
275-
);
276-
}).catch(err => {
200+
return <MatrixChat
201+
onNewScreen={onNewScreen}
202+
makeRegistrationUrl={makeRegistrationUrl}
203+
ConferenceHandler={VectorConferenceHandler}
204+
config={config}
205+
realQueryParams={params}
206+
startingFragmentQueryParams={fragParams}
207+
enableGuest={!config.disable_guests}
208+
onTokenLoginCompleted={onTokenLoginCompleted}
209+
initialScreenAfterLogin={getScreenFromLocation(window.location)}
210+
defaultDeviceDisplayName={platform.getDefaultDeviceDisplayName()}
211+
/>;
212+
} catch (err) {
277213
console.error(err);
278214

279215
let errorMessage = err.translatedMessage
@@ -282,23 +218,17 @@ export async function loadApp() {
282218

283219
// Like the compatibility page, AWOOOOOGA at the user
284220
const GenericErrorPage = sdk.getComponent("structures.GenericErrorPage");
285-
window.matrixChat = ReactDOM.render(
286-
<GenericErrorPage message={errorMessage} title={_t("Your Riot is misconfigured")} />,
287-
document.getElementById('matrixchat'),
288-
);
289-
});
221+
return <GenericErrorPage message={errorMessage} title={_t("Your Riot is misconfigured")} />;
222+
}
290223
} else {
291224
console.error("Browser is missing required features.");
292225
// take to a different landing page to AWOOOOOGA at the user
293226
const CompatibilityPage = sdk.getComponent("structures.CompatibilityPage");
294-
window.matrixChat = ReactDOM.render(
295-
<CompatibilityPage onAccept={function() {
296-
if (window.localStorage) window.localStorage.setItem('mx_accepts_unsupported_browser', true);
297-
console.log("User accepts the compatibility risks.");
298-
loadApp();
299-
}} />,
300-
document.getElementById('matrixchat'),
301-
);
227+
return <CompatibilityPage onAccept={function() {
228+
if (window.localStorage) window.localStorage.setItem('mx_accepts_unsupported_browser', true);
229+
console.log("User accepts the compatibility risks.");
230+
loadApp();
231+
}} />;
302232
}
303233
}
304234

@@ -384,7 +314,6 @@ async function verifyServerConfig() {
384314
}
385315
}
386316

387-
388317
validatedConfig.isDefault = true;
389318

390319
// Just in case we ever have to debug this

src/vector/index.js

Lines changed: 0 additions & 47 deletions
This file was deleted.

src/vector/index.ts

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
/*
2+
Copyright 2015, 2016 OpenMarket Ltd
3+
Copyright 2017 Vector Creations Ltd
4+
Copyright 2018, 2019 New Vector Ltd
5+
Copyright 2019 Michael Telatynski <7t3chguy@gmail.com>
6+
Copyright 2020 The Matrix.org Foundation C.I.C.
7+
8+
Licensed under the Apache License, Version 2.0 (the "License");
9+
you may not use this file except in compliance with the License.
10+
You may obtain a copy of the License at
11+
12+
http://www.apache.org/licenses/LICENSE-2.0
13+
14+
Unless required by applicable law or agreed to in writing, software
15+
distributed under the License is distributed on an "AS IS" BASIS,
16+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17+
See the License for the specific language governing permissions and
18+
limitations under the License.
19+
*/
20+
21+
// Require common CSS here; this will make webpack process it into bundle.css.
22+
// Our own CSS (which is themed) is imported via separate webpack entry points
23+
// in webpack.config.js
24+
require('gfm.css/gfm.css');
25+
require('highlight.js/styles/github.css');
26+
27+
// These are things that can run before the skin loads - be careful not to reference the react-sdk though.
28+
import {parseQsFromFragment} from "./url_utils";
29+
import './modernizr';
30+
31+
// load service worker if available on this platform
32+
if ('serviceWorker' in navigator) {
33+
navigator.serviceWorker.register('sw.js');
34+
}
35+
36+
async function settled(prom: Promise<any>) {
37+
try {
38+
await prom;
39+
} catch (e) {
40+
console.error(e);
41+
}
42+
}
43+
44+
function checkBrowserFeatures() {
45+
if (!window.Modernizr) {
46+
console.error("Cannot check features - Modernizr global is missing.");
47+
return false;
48+
}
49+
50+
// custom checks atop Modernizr because it doesn't have ES2018/ES2019 checks in it for some features we depend on,
51+
// Modernizr requires rules to be lowercase with no punctuation:
52+
// ES2018: http://www.ecma-international.org/ecma-262/9.0/#sec-promise.prototype.finally
53+
window.Modernizr.addTest("promiseprototypefinally", () =>
54+
window.Promise && window.Promise.prototype && typeof window.Promise.prototype.finally === "function");
55+
// ES2019: http://www.ecma-international.org/ecma-262/10.0/#sec-object.fromentries
56+
window.Modernizr.addTest("objectfromentries", () =>
57+
window.Object && typeof window.Object.fromEntries === "function");
58+
59+
const featureList = Object.keys(window.Modernizr);
60+
61+
let featureComplete = true;
62+
for (let i = 0; i < featureList.length; i++) {
63+
if (window.Modernizr[featureList[i]] === undefined) {
64+
console.error(
65+
"Looked for feature '%s' but Modernizr has no results for this. " +
66+
"Has it been configured correctly?", featureList[i],
67+
);
68+
return false;
69+
}
70+
if (window.Modernizr[featureList[i]] === false) {
71+
console.error("Browser missing feature: '%s'", featureList[i]);
72+
// toggle flag rather than return early so we log all missing features rather than just the first.
73+
featureComplete = false;
74+
}
75+
}
76+
return featureComplete;
77+
}
78+
79+
// React depends on Map & Set which we check for using modernizr's es6collections
80+
// if modernizr fails we may not have a functional react to show the error message.
81+
// try in react but fallback to an `alert`
82+
async function start() {
83+
// load init.ts async so that its code is not executed immediately and we can catch any exceptions
84+
const {rageshakePromise, loadSkin, loadApp} = await import(
85+
/* webpackChunkName: "init" */
86+
/* webpackPreload: true */
87+
"./init");
88+
89+
await settled(rageshakePromise); // give rageshake a chance to load/fail
90+
91+
const fragparts = parseQsFromFragment(window.location);
92+
93+
// don't try to redirect to the native apps if we're
94+
// verifying a 3pid (but after we've loaded the config)
95+
// or if the user is following a deep link
96+
// (https://github.com/vector-im/riot-web/issues/7378)
97+
const preventRedirect = fragparts.params.client_secret || fragparts.location.length > 0;
98+
99+
if (!preventRedirect) {
100+
const isIos = /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream;
101+
const isAndroid = /Android/.test(navigator.userAgent);
102+
if (isIos || isAndroid) {
103+
if (document.cookie.indexOf("riot_mobile_redirect_to_guide=false") === -1) {
104+
window.location.href = "mobile_guide/";
105+
return;
106+
}
107+
}
108+
}
109+
110+
await loadSkin();
111+
112+
let acceptBrowser = checkBrowserFeatures();
113+
if (!acceptBrowser && window.localStorage) {
114+
acceptBrowser = Boolean(window.localStorage.getItem("mx_accepts_unsupported_browser"));
115+
}
116+
117+
// Finally, load the app. All of the other react-sdk imports are in this file which causes the skinner to
118+
// run on the components. We use `require` here to make sure webpack doesn't optimize this into an async
119+
// import and thus running before the skin can load.
120+
await loadApp(fragparts.params, acceptBrowser);
121+
}
122+
start();

0 commit comments

Comments
 (0)