Merge pull request #13032 from vector-im/t3chguy/app_load1

App load order tweaks for code splitting
This commit is contained in:
Michael Telatynski 2020-04-07 09:48:17 +01:00 committed by GitHub
commit 06bd9d1bf4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 217 additions and 159 deletions

View file

@ -96,6 +96,7 @@
"@babel/preset-typescript": "^7.7.4",
"@babel/register": "^7.7.4",
"@babel/runtime": "^7.7.6",
"@types/modernizr": "^3.5.3",
"@types/react": "16.9",
"@types/react-dom": "^16.9.4",
"autoprefixer": "^9.7.3",

View file

@ -14,9 +14,22 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import {ReactNode} from "react";
import "modernizr";
declare global {
interface Window {
Modernizr: ModernizrAPI & FeatureDetects;
Olm: {
init: () => Promise<void>;
};
mxSendRageshake: (text: string, withLogs?: boolean) => void;
matrixChat: ReactNode;
}
// workaround for https://github.com/microsoft/TypeScript/issues/30933
interface ObjectConstructor {
fromEntries?(xs: [string|number|symbol, any][]): object
}
}

View file

@ -23,7 +23,6 @@ import React from 'react';
// access via the console
global.React = React;
import ReactDOM from 'react-dom';
import * as sdk from 'matrix-react-sdk';
import PlatformPeg from 'matrix-react-sdk/src/PlatformPeg';
import * as VectorConferenceHandler from 'matrix-react-sdk/src/VectorConferenceHandler';
@ -45,41 +44,6 @@ import {loadConfig, preparePlatform, loadLanguage, loadOlm} from "./init";
let lastLocationHashSet = null;
function checkBrowserFeatures() {
if (!window.Modernizr) {
console.error("Cannot check features - Modernizr global is missing.");
return false;
}
// custom checks atop Modernizr because it doesn't have ES2018/ES2019 checks in it for some features we depend on,
// Modernizr requires rules to be lowercase with no punctuation:
// ES2018: http://www.ecma-international.org/ecma-262/9.0/#sec-promise.prototype.finally
window.Modernizr.addTest("promiseprototypefinally", () =>
window.Promise && window.Promise.prototype && typeof window.Promise.prototype.finally === "function");
// ES2019: http://www.ecma-international.org/ecma-262/10.0/#sec-object.fromentries
window.Modernizr.addTest("objectfromentries", () =>
window.Object && typeof window.Object.fromEntries === "function");
const featureList = Object.keys(window.Modernizr);
let featureComplete = true;
for (let i = 0; i < featureList.length; i++) {
if (window.Modernizr[featureList[i]] === undefined) {
console.error(
"Looked for feature '%s' but Modernizr has no results for this. " +
"Has it been configured correctly?", featureList[i],
);
return false;
}
if (window.Modernizr[featureList[i]] === false) {
console.error("Browser missing feature: '%s'", featureList[i]);
// toggle flag rather than return early so we log all missing features rather than just the first.
featureComplete = false;
}
}
return featureComplete;
}
// Parse the given window.location and return parameters that can be used when calling
// MatrixChat.showScreen(screen, params)
function getScreenFromLocation(location) {
@ -164,7 +128,7 @@ function onTokenLoginCompleted() {
window.location.href = formatted;
}
export async function loadApp() {
export async function loadApp(fragParams: {}, acceptBrowser: boolean) {
// XXX: the way we pass the path to the worker script from webpack via html in body's dataset is a hack
// but alternatives seem to require changing the interface to passing Workers to js-sdk
const vectorIndexeddbWorkerScript = document.body.dataset.vectorIndexeddbWorkerScript;
@ -192,26 +156,8 @@ export async function loadApp() {
// Load language after loading config.json so that settingsDefaults.language can be applied
await loadLanguage();
const fragparts = parseQsFromFragment(window.location);
const params = parseQs(window.location);
// don't try to redirect to the native apps if we're
// verifying a 3pid (but after we've loaded the config)
// or if the user is following a deep link
// (https://github.com/vector-im/riot-web/issues/7378)
const preventRedirect = fragparts.params.client_secret || fragparts.location.length > 0;
if (!preventRedirect) {
const isIos = /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream;
const isAndroid = /Android/.test(navigator.userAgent);
if (isIos || isAndroid) {
if (document.cookie.indexOf("riot_mobile_redirect_to_guide=false") === -1) {
window.location = "mobile_guide/";
return;
}
}
}
// as quickly as we possibly can, set a default theme...
await setTheme();
@ -235,45 +181,35 @@ export async function loadApp() {
);
const GenericErrorPage = sdk.getComponent("structures.GenericErrorPage");
window.matrixChat = ReactDOM.render(
<GenericErrorPage message={errorMessage} title={_t("Your Riot is misconfigured")} />,
document.getElementById('matrixchat'),
);
return;
return <GenericErrorPage message={errorMessage} title={_t("Your Riot is misconfigured")} />;
}
const validBrowser = checkBrowserFeatures();
const acceptInvalidBrowser = window.localStorage && window.localStorage.getItem('mx_accepts_unsupported_browser');
const urlWithoutQuery = window.location.protocol + '//' + window.location.host + window.location.pathname;
console.log("Vector starting at " + urlWithoutQuery);
if (configError) {
window.matrixChat = ReactDOM.render(<div className="error">
return <div className="error">
Unable to load config file: please refresh the page to try again.
</div>, document.getElementById('matrixchat'));
} else if (validBrowser || acceptInvalidBrowser) {
</div>;
} else if (acceptBrowser) {
platform.startUpdater();
try {
// Don't bother loading the app until the config is verified
verifyServerConfig().then((newConfig) => {
const config = await verifyServerConfig();
const MatrixChat = sdk.getComponent('structures.MatrixChat');
window.matrixChat = ReactDOM.render(
<MatrixChat
return <MatrixChat
onNewScreen={onNewScreen}
makeRegistrationUrl={makeRegistrationUrl}
ConferenceHandler={VectorConferenceHandler}
config={newConfig}
config={config}
realQueryParams={params}
startingFragmentQueryParams={fragparts.params}
enableGuest={!SdkConfig.get().disable_guests}
startingFragmentQueryParams={fragParams}
enableGuest={!config.disable_guests}
onTokenLoginCompleted={onTokenLoginCompleted}
initialScreenAfterLogin={getScreenFromLocation(window.location)}
defaultDeviceDisplayName={platform.getDefaultDeviceDisplayName()}
/>,
document.getElementById('matrixchat'),
);
}).catch(err => {
/>;
} catch (err) {
console.error(err);
let errorMessage = err.translatedMessage
@ -282,23 +218,17 @@ export async function loadApp() {
// Like the compatibility page, AWOOOOOGA at the user
const GenericErrorPage = sdk.getComponent("structures.GenericErrorPage");
window.matrixChat = ReactDOM.render(
<GenericErrorPage message={errorMessage} title={_t("Your Riot is misconfigured")} />,
document.getElementById('matrixchat'),
);
});
return <GenericErrorPage message={errorMessage} title={_t("Your Riot is misconfigured")} />;
}
} else {
console.error("Browser is missing required features.");
// take to a different landing page to AWOOOOOGA at the user
const CompatibilityPage = sdk.getComponent("structures.CompatibilityPage");
window.matrixChat = ReactDOM.render(
<CompatibilityPage onAccept={function() {
return <CompatibilityPage onAccept={function() {
if (window.localStorage) window.localStorage.setItem('mx_accepts_unsupported_browser', true);
console.log("User accepts the compatibility risks.");
loadApp();
}} />,
document.getElementById('matrixchat'),
);
}} />;
}
}
@ -384,7 +314,6 @@ async function verifyServerConfig() {
}
}
validatedConfig.isDefault = true;
// Just in case we ever have to debug this

View file

@ -1,47 +0,0 @@
/*
Copyright 2015, 2016 OpenMarket Ltd
Copyright 2017 Vector Creations Ltd
Copyright 2018, 2019 New Vector Ltd
Copyright 2019 Michael Telatynski <7t3chguy@gmail.com>
Copyright 2020 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Require common CSS here; this will make webpack process it into bundle.css.
// Our own CSS (which is themed) is imported via separate webpack entry points
// in webpack.config.js
require('gfm.css/gfm.css');
require('highlight.js/styles/github.css');
// These are things that can run before the skin loads - be careful not to reference the react-sdk though.
import './rageshakesetup';
import './modernizr';
// load service worker if available on this platform
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('sw.js');
}
// Ensure the skin is the very first thing to load for the react-sdk. We don't even want to reference
// the SDK until we have to in imports.
console.log("Loading skin...");
import * as sdk from 'matrix-react-sdk';
import * as skin from "../component-index";
sdk.loadSkin(skin);
console.log("Skin loaded!");
// Finally, load the app. All of the other react-sdk imports are in this file which causes the skinner to
// run on the components. We use `require` here to make sure webpack doesn't optimize this into an async
// import and thus running before the skin can load.
require("./app").loadApp();

122
src/vector/index.ts Normal file
View file

@ -0,0 +1,122 @@
/*
Copyright 2015, 2016 OpenMarket Ltd
Copyright 2017 Vector Creations Ltd
Copyright 2018, 2019 New Vector Ltd
Copyright 2019 Michael Telatynski <7t3chguy@gmail.com>
Copyright 2020 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Require common CSS here; this will make webpack process it into bundle.css.
// Our own CSS (which is themed) is imported via separate webpack entry points
// in webpack.config.js
require('gfm.css/gfm.css');
require('highlight.js/styles/github.css');
// These are things that can run before the skin loads - be careful not to reference the react-sdk though.
import {parseQsFromFragment} from "./url_utils";
import './modernizr';
// load service worker if available on this platform
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('sw.js');
}
async function settled(prom: Promise<any>) {
try {
await prom;
} catch (e) {
console.error(e);
}
}
function checkBrowserFeatures() {
if (!window.Modernizr) {
console.error("Cannot check features - Modernizr global is missing.");
return false;
}
// custom checks atop Modernizr because it doesn't have ES2018/ES2019 checks in it for some features we depend on,
// Modernizr requires rules to be lowercase with no punctuation:
// ES2018: http://www.ecma-international.org/ecma-262/9.0/#sec-promise.prototype.finally
window.Modernizr.addTest("promiseprototypefinally", () =>
window.Promise && window.Promise.prototype && typeof window.Promise.prototype.finally === "function");
// ES2019: http://www.ecma-international.org/ecma-262/10.0/#sec-object.fromentries
window.Modernizr.addTest("objectfromentries", () =>
window.Object && typeof window.Object.fromEntries === "function");
const featureList = Object.keys(window.Modernizr);
let featureComplete = true;
for (let i = 0; i < featureList.length; i++) {
if (window.Modernizr[featureList[i]] === undefined) {
console.error(
"Looked for feature '%s' but Modernizr has no results for this. " +
"Has it been configured correctly?", featureList[i],
);
return false;
}
if (window.Modernizr[featureList[i]] === false) {
console.error("Browser missing feature: '%s'", featureList[i]);
// toggle flag rather than return early so we log all missing features rather than just the first.
featureComplete = false;
}
}
return featureComplete;
}
// React depends on Map & Set which we check for using modernizr's es6collections
// if modernizr fails we may not have a functional react to show the error message.
// try in react but fallback to an `alert`
async function start() {
// load init.ts async so that its code is not executed immediately and we can catch any exceptions
const {rageshakePromise, loadSkin, loadApp} = await import(
/* webpackChunkName: "init" */
/* webpackPreload: true */
"./init");
await settled(rageshakePromise); // give rageshake a chance to load/fail
const fragparts = parseQsFromFragment(window.location);
// don't try to redirect to the native apps if we're
// verifying a 3pid (but after we've loaded the config)
// or if the user is following a deep link
// (https://github.com/vector-im/riot-web/issues/7378)
const preventRedirect = fragparts.params.client_secret || fragparts.location.length > 0;
if (!preventRedirect) {
const isIos = /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream;
const isAndroid = /Android/.test(navigator.userAgent);
if (isIos || isAndroid) {
if (document.cookie.indexOf("riot_mobile_redirect_to_guide=false") === -1) {
window.location.href = "mobile_guide/";
return;
}
}
}
await loadSkin();
let acceptBrowser = checkBrowserFeatures();
if (!acceptBrowser && window.localStorage) {
acceptBrowser = Boolean(window.localStorage.getItem("mx_accepts_unsupported_browser"));
}
// Finally, load the app. All of the other react-sdk imports are in this file which causes the skinner to
// run on the components. We use `require` here to make sure webpack doesn't optimize this into an async
// import and thus running before the skin can load.
await loadApp(fragparts.params, acceptBrowser);
}
start();

View file

@ -20,14 +20,19 @@ limitations under the License.
// @ts-ignore
import olmWasmPath from "olm/olm.wasm";
import Olm from 'olm';
import * as ReactDOM from "react-dom";
import * as languageHandler from 'matrix-react-sdk/src/languageHandler';
import * as languageHandler from "matrix-react-sdk/src/languageHandler";
import SettingsStore from "matrix-react-sdk/src/settings/SettingsStore";
import ElectronPlatform from "./platform/ElectronPlatform";
import WebPlatform from "./platform/WebPlatform";
import PlatformPeg from 'matrix-react-sdk/src/PlatformPeg';
import PlatformPeg from "matrix-react-sdk/src/PlatformPeg";
import SdkConfig from "matrix-react-sdk/src/SdkConfig";
import { initRageshake } from "./rageshakesetup";
export const rageshakePromise = initRageshake();
export function preparePlatform() {
if ((<any>window).ipcRenderer) {
@ -112,3 +117,33 @@ export async function loadLanguage() {
console.error("Unable to set language", e);
}
}
export async function loadSkin() {
// Ensure the skin is the very first thing to load for the react-sdk. We don't even want to reference
// the SDK until we have to in imports.
console.log("Loading skin...");
// load these async so that its code is not executed immediately and we can catch any exceptions
const [sdk, skin] = await Promise.all([
import(
/* webpackChunkName: "matrix-react-sdk" */
/* webpackPreload: true */
"matrix-react-sdk"),
import(
/* webpackChunkName: "riot-web-component-index" */
/* webpackPreload: true */
// @ts-ignore - this module is generated so may fail lint
"../component-index"),
]);
sdk.loadSkin(skin);
console.log("Skin loaded!");
}
export async function loadApp(fragParams: {}, acceptBrowser: boolean) {
// load app.js async so that its code is not executed immediately and we can catch any exceptions
const module = await import(
/* webpackChunkName: "riot-web-app" */
/* webpackPreload: true */
"./app");
window.matrixChat = ReactDOM.render(await module.loadApp(fragParams, acceptBrowser),
document.getElementById('matrixchat'));
}

View file

@ -30,8 +30,9 @@ import * as rageshake from "matrix-react-sdk/src/rageshake/rageshake";
import SdkConfig from "matrix-react-sdk/src/SdkConfig";
import sendBugReport from "matrix-react-sdk/src/rageshake/submit-rageshake";
function initRageshake() {
rageshake.init().then(() => {
export function initRageshake() {
const prom = rageshake.init();
prom.then(() => {
console.log("Initialised rageshake.");
console.log("To fix line numbers in Chrome: " +
"Meatball menu → Settings → Blackboxing → Add /rageshake\\.js$");
@ -46,10 +47,9 @@ function initRageshake() {
}, (err) => {
console.error("Failed to initialise rageshake: " + err);
});
return prom;
}
initRageshake();
window.mxSendRageshake = function(text: string, withLogs?: boolean) {
if (withLogs === undefined) withLogs = true;
if (!text || !text.trim()) {

View file

@ -32,7 +32,7 @@ export function parseQsFromFragment(location: Location) {
const result = {
location: decodeURIComponent(hashparts[0]),
params: {},
params: <qs.ParsedUrlQuery>{},
};
if (hashparts.length > 1) {

View file

@ -31,7 +31,7 @@ module.exports = (env, argv) => {
...development,
entry: {
"bundle": "./src/vector/index.js",
"bundle": "./src/vector/index.ts",
"indexeddb-worker": "./src/vector/indexeddb-worker.js",
"mobileguide": "./src/vector/mobile_guide/index.js",
"jitsi": "./src/vector/jitsi/index.ts",

View file

@ -1245,6 +1245,11 @@
resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d"
integrity sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==
"@types/modernizr@^3.5.3":
version "3.5.3"
resolved "https://registry.yarnpkg.com/@types/modernizr/-/modernizr-3.5.3.tgz#8ef99e6252191c1d88647809109dc29884ba6d7a"
integrity sha512-jhMOZSS0UGYTS9pqvt6q3wtT3uvOSve5piTEmTMx3zzTuBLvSIMxSIBIc3d5lajVD5h4xc41AMZD2M5orN3PxA==
"@types/node@*":
version "13.9.0"
resolved "https://registry.yarnpkg.com/@types/node/-/node-13.9.0.tgz#5b6ee7a77faacddd7de719017d0bc12f52f81589"