From 4b12b711eeded3e6f0270a1d7de7d3cfb1d01a0c Mon Sep 17 00:00:00 2001 From: Dariusz Niemczyk Date: Sun, 1 Aug 2021 16:17:58 +0200 Subject: [PATCH 1/8] Add data-mx-theme to theme css for hot-reload --- src/vector/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vector/index.html b/src/vector/index.html index 1a242a80ed..29b1f13221 100644 --- a/src/vector/index.html +++ b/src/vector/index.html @@ -43,7 +43,7 @@ if (match) { var title = match[1].charAt(0).toUpperCase() + match[1].slice(1); %> - + <% } else { %> <% } From b677edbc58d54dee274f0b193b2a931554451f33 Mon Sep 17 00:00:00 2001 From: Dariusz Niemczyk Date: Sun, 1 Aug 2021 16:28:38 +0200 Subject: [PATCH 2/8] Make CSS Hot-reload work in a hacky way --- package.json | 4 +- src/vector/devcss.ts | 33 ++++++++++++ src/vector/index.ts | 8 +++ webpack.config.js | 119 ++++++++++++++++++++++++++++++++++--------- yarn.lock | 38 +++++++++++++- 5 files changed, 174 insertions(+), 28 deletions(-) create mode 100644 src/vector/devcss.ts diff --git a/package.json b/package.json index eb6c4136cf..fcf2b1fa53 100644 --- a/package.json +++ b/package.json @@ -46,7 +46,7 @@ "start": "concurrently --kill-others-on-fail --prefix \"{time} [{name}]\" -n reskindex,reskindex-react,res,element-js \"yarn reskindex:watch\" \"yarn reskindex:watch-react\" \"yarn start:res\" \"yarn start:js\"", "start:https": "concurrently --kill-others-on-fail --prefix \"{time} [{name}]\" -n reskindex,reskindex-react,res,element-js \"yarn reskindex:watch\" \"yarn reskindex:watch-react\" \"yarn start:res\" \"yarn start:js --https\"", "start:res": "yarn build:jitsi && node scripts/copy-res.js -w", - "start:js": "webpack-dev-server --host=0.0.0.0 --output-filename=bundles/_dev_/[name].js --output-chunk-filename=bundles/_dev_/[name].js -w --progress --mode development --disable-host-check", + "start:js": "webpack-dev-server --host=0.0.0.0 --output-filename=bundles/_dev_/[name].js --output-chunk-filename=bundles/_dev_/[name].js -w --progress --mode development --disable-host-check --hot", "lint": "yarn lint:types && yarn lint:js && yarn lint:style", "lint:js": "eslint --max-warnings 0 src", "lint:js-fix": "eslint --fix src", @@ -141,6 +141,8 @@ "rimraf": "^3.0.2", "shell-escape": "^0.2.0", "simple-proxy-agent": "^1.1.0", + "string-replace-loader": "2", + "style-loader": "2", "stylelint": "^13.9.0", "stylelint-config-standard": "^20.0.0", "stylelint-scss": "^3.18.0", diff --git a/src/vector/devcss.ts b/src/vector/devcss.ts new file mode 100644 index 0000000000..1937ca949d --- /dev/null +++ b/src/vector/devcss.ts @@ -0,0 +1,33 @@ +/* +Copyright 2021 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. +*/ + +/** + * This code will be autoremoved on production builds. + * The purpose of this code is that the webpack's `string-replace-loader` + * pretty much search for this string in this specific file and replaces it + * like a macro before any previous compilations, which allows us to inject + * some css requires statements that are specific to the themes we have turned + * on by ourselves. Without that very specific workaround, webpack would just + * import all the CSSes, which would make the whole thing useless, as on my + * machine with i9 the recompilation for all themes turned ou would take way + * over 30s, which is definitely too high for nice css reloads speed. + * + * For more details, see webpack.config.js:184 (string-replace-loader) + */ +if (process.env.NODE_ENV === 'development') { + "use theming"; +} + diff --git a/src/vector/index.ts b/src/vector/index.ts index 8817167069..fe424c47f2 100644 --- a/src/vector/index.ts +++ b/src/vector/index.ts @@ -25,6 +25,14 @@ require('gfm.css/gfm.css'); require('highlight.js/styles/github.css'); require('katex/dist/katex.css'); +/** + * This require is necessary only for purposes of CSS hot reload, as otherwise + * webpack has some incredibly problems figuring out which css files should be + * hot reloaded, even with proper hints for the loader. + * + * On production build it's going to be an empty module, so don't worry about that. + */ +require('./devcss'); // 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'; diff --git a/webpack.config.js b/webpack.config.js index bb1d5d9ff5..03ac4a8eff 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -1,6 +1,7 @@ /* eslint-disable quote-props */ const path = require('path'); +const webpack = require('webpack'); const HtmlWebpackPlugin = require('html-webpack-plugin'); const MiniCssExtractPlugin = require('mini-css-extract-plugin'); const TerserPlugin = require('terser-webpack-plugin'); @@ -10,9 +11,35 @@ const HtmlWebpackInjectPreload = require('@principalstudio/html-webpack-inject-p let ogImageUrl = process.env.RIOT_OG_IMAGE_URL; if (!ogImageUrl) ogImageUrl = 'https://app.element.io/themes/element/img/logos/opengraph.png'; -const additionalPlugins = [ - // This is where you can put your customisation replacements. -]; +const cssThemes = { + // CSS themes + "theme-legacy": "./node_modules/matrix-react-sdk/res/themes/legacy-light/css/legacy-light.scss", + "theme-legacy-dark": "./node_modules/matrix-react-sdk/res/themes/legacy-dark/css/legacy-dark.scss", + "theme-light": "./node_modules/matrix-react-sdk/res/themes/light/css/light.scss", + "theme-dark": "./node_modules/matrix-react-sdk/res/themes/dark/css/dark.scss", + "theme-light-custom": "./node_modules/matrix-react-sdk/res/themes/light-custom/css/light-custom.scss", + "theme-dark-custom": "./node_modules/matrix-react-sdk/res/themes/dark-custom/css/dark-custom.scss", +}; + +function getActiveThemes() { + const theme = process.env.MATRIX_THEMES ?? 'light,dark'; + const themes = theme.split(',').filter(x => x).map(x => x.trim()).filter(x => x); + return themes; +} + +const ACTIVE_THEMES = getActiveThemes(); +function getThemesImports() { + const imports = ACTIVE_THEMES.map((t, index) => { + const themeImportPath = cssThemes[`theme-${t}`].replace('./node_modules/', ''); + return themeImportPath; + }); + const s = JSON.stringify(ACTIVE_THEMES); + return ` + window.MX_insertedThemeStylesCounter = 0 + window.MX_DEV_ACTIVE_THEMES = (${s}); + ${imports.map(i => `import("${i}")`).join('\n')}; + `; +} module.exports = (env, argv) => { let nodeEnv = argv.mode; @@ -29,6 +56,7 @@ module.exports = (env, argv) => { // application to productions standards nodeEnv = "production"; } + const devMode = nodeEnv !== 'production'; const development = {}; if (argv.mode === "production") { @@ -48,6 +76,13 @@ module.exports = (env, argv) => { return { ...development, + watch: true, + watchOptions: { + aggregateTimeout: 200, + poll: 1000, + ignored: [/node_modules([\\]+|\/)+(?!matrix-react-sdk|matrix-js-sdk)/], + }, + node: { // Mock out the NodeFS module: The opus decoder imports this wrongly. fs: 'empty', @@ -59,14 +94,7 @@ module.exports = (env, argv) => { "jitsi": "./src/vector/jitsi/index.ts", "usercontent": "./node_modules/matrix-react-sdk/src/usercontent/index.js", "recorder-worklet": "./node_modules/matrix-react-sdk/src/audio/RecorderWorklet.ts", - - // CSS themes - "theme-legacy": "./node_modules/matrix-react-sdk/res/themes/legacy-light/css/legacy-light.scss", - "theme-legacy-dark": "./node_modules/matrix-react-sdk/res/themes/legacy-dark/css/legacy-dark.scss", - "theme-light": "./node_modules/matrix-react-sdk/res/themes/light/css/light.scss", - "theme-dark": "./node_modules/matrix-react-sdk/res/themes/dark/css/dark.scss", - "theme-light-custom": "./node_modules/matrix-react-sdk/res/themes/light-custom/css/light-custom.scss", - "theme-dark-custom": "./node_modules/matrix-react-sdk/res/themes/dark-custom/css/dark-custom.scss", + ...(devMode ? {} : cssThemes), }, optimization: { @@ -89,7 +117,7 @@ module.exports = (env, argv) => { // This fixes duplicate files showing up in chrome with sourcemaps enabled. // See https://github.com/webpack/webpack/issues/7128 for more info. - namedModules: false, + namedModules: true, // Minification is normally enabled by default for webpack in production mode, but // we use a CSS optimizer too and need to manage it ourselves. @@ -150,6 +178,14 @@ module.exports = (env, argv) => { /olm[\\/](javascript[\\/])?olm\.js$/, ], rules: [ + devMode && { + test: /devcss\.ts$/, + loader: 'string-replace-loader', + options: { + search: '"use theming";', + replace: getThemesImports(), + }, + }, { test: /\.worker\.ts$/, loader: "worker-loader", @@ -181,7 +217,7 @@ module.exports = (env, argv) => { { test: /\.css$/, use: [ - MiniCssExtractPlugin.loader, + devMode ? 'style-loader' : MiniCssExtractPlugin.loader, { loader: 'css-loader', options: { @@ -219,7 +255,7 @@ module.exports = (env, argv) => { // It's important that this plugin is last otherwise we end // up with broken CSS. - require('postcss-preset-env')({stage: 3, browsers: 'last 2 versions'}), + require('postcss-preset-env')({ stage: 3, browsers: 'last 2 versions' }), ], parser: "postcss-scss", "local-plugins": true, @@ -230,7 +266,31 @@ module.exports = (env, argv) => { { test: /\.scss$/, use: [ - MiniCssExtractPlugin.loader, + /** + * This code is hopeful that no .scss outside of our themes will be directly imported in any + * of the JS/TS files. + * Should be MUCH better with webpack 5, but we're stuck to this solution for now. + */ + devMode ? { + loader: 'style-loader', options: { + attributes: { + 'data-mx-theme': 'replace_me', + }, + // Properly disable all other instances of themes + insert: function insertBeforeAt(element) { + const isMatrixTheme = element.attributes['data-mx-theme'].value === 'replace_me'; + const parent = document.querySelector('head'); + + element.disabled = true; + if (isMatrixTheme) { + element.attributes['data-mx-theme'].value = window.MX_DEV_ACTIVE_THEMES[window.MX_insertedThemeStylesCounter]; + window.MX_insertedThemeStylesCounter++; + } + + parent.appendChild(element); + }, + }, + } : MiniCssExtractPlugin.loader, { loader: 'css-loader', options: { @@ -257,7 +317,7 @@ module.exports = (env, argv) => { // It's important that this plugin is last otherwise we end // up with broken CSS. - require('postcss-preset-env')({stage: 3, browsers: 'last 2 versions'}), + require('postcss-preset-env')({ stage: 3, browsers: 'last 2 versions' }), ], parser: "postcss-scss", "local-plugins": true, @@ -374,9 +434,15 @@ module.exports = (env, argv) => { }, plugins: [ + new webpack.EnvironmentPlugin({ + NODE_ENV: 'development', // use 'development' unless process.env.NODE_ENV is defined + DEBUG: false, + }), + // This exports our CSS using the splitChunks and loaders above. new MiniCssExtractPlugin({ - filename: 'bundles/[hash]/[name].css', + filename: "bundles/[hash]/[name].css", + chunkFilename: "bundles/[hash]/[name].css", ignoreOrder: false, // Enable to remove warnings about conflicting order }), @@ -437,7 +503,6 @@ module.exports = (env, argv) => { files: [{ match: /.*Inter.*\.woff2$/ }], }), - ...additionalPlugins, ], output: { @@ -457,17 +522,21 @@ module.exports = (env, argv) => { // configuration for the webpack-dev-server devServer: { // serve unwebpacked assets from webapp. - contentBase: './webapp', + contentBase: [ + './src/', + './webapp', + './bundles/**', + './node_modules/matrix-react-sdk/**', + './node_modules/matrix-js-sdk/**', + ], // Only output errors, warnings, or new compilations. // This hides the massive list of modules. stats: 'minimal', - - // hot module replacement doesn't work (I think we'd need react-hot-reload?) - // so webpack-dev-server reloads the page on every update which is quite - // tedious in Riot since that can take a while. - hot: false, - inline: false, + hot: true, + injectHot: true, + hotOnly: true, + inline: true, }, }; }; diff --git a/yarn.lock b/yarn.lock index 39fd6d5ba8..74e40d7f90 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1454,7 +1454,7 @@ dependencies: "@types/istanbul-lib-report" "*" -"@types/json-schema@^7.0.5", "@types/json-schema@^7.0.7": +"@types/json-schema@^7.0.5", "@types/json-schema@^7.0.7", "@types/json-schema@^7.0.8": version "7.0.8" resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.8.tgz#edf1bf1dbf4e04413ca8e5b17b3b7d7d54b59818" integrity sha512-YSBPTLTVm2e2OoQIDYx8HaeWJ5tTToLH67kXR7zYNGupXMEHa2++G8k+DczX2cFVgalypqtyZIcU19AFcmOpmg== @@ -1919,7 +1919,7 @@ ajv-keywords@^3.1.0, ajv-keywords@^3.4.1, ajv-keywords@^3.5.2: resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.5.2.tgz#31f29da5ab6e00d1c2d329acf7b5929614d5014d" integrity sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ== -ajv@^6.1.0, ajv@^6.10.0, ajv@^6.10.2, ajv@^6.12.3, ajv@^6.12.4: +ajv@^6.1.0, ajv@^6.10.0, ajv@^6.10.2, ajv@^6.12.3, ajv@^6.12.4, ajv@^6.12.5: version "6.12.6" resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== @@ -7130,6 +7130,15 @@ loader-utils@^1.0.0, loader-utils@^1.1.0, loader-utils@^1.2.3, loader-utils@^1.4 emojis-list "^3.0.0" json5 "^1.0.1" +loader-utils@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-2.0.0.tgz#e4cace5b816d425a166b5f097e10cd12b36064b0" + integrity sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ== + dependencies: + big.js "^5.2.2" + emojis-list "^3.0.0" + json5 "^2.1.2" + locate-path@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-3.0.0.tgz#dbec3b3ab759758071b58fe59fc41871af21400e" @@ -10196,6 +10205,15 @@ schema-utils@^2.5.0, schema-utils@^2.6.5, schema-utils@^2.6.6, schema-utils@^2.7 ajv "^6.12.4" ajv-keywords "^3.5.2" +schema-utils@^3.0.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-3.1.1.tgz#bc74c4b6b6995c1d88f76a8b77bea7219e0c8281" + integrity sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw== + dependencies: + "@types/json-schema" "^7.0.8" + ajv "^6.12.5" + ajv-keywords "^3.5.2" + select-hose@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/select-hose/-/select-hose-2.0.0.tgz#625d8658f865af43ec962bfc376a37359a4994ca" @@ -10718,6 +10736,14 @@ string-length@^4.0.1: char-regex "^1.0.2" strip-ansi "^6.0.0" +string-replace-loader@2: + version "2.3.0" + resolved "https://registry.yarnpkg.com/string-replace-loader/-/string-replace-loader-2.3.0.tgz#7f29be7d73c94dd92eccd5c5a15651181d7ecd3d" + integrity sha512-HYBIHStViMKLZC/Lehxy42OuwsBaPzX/LjcF5mkJlE2SnHXmW6SW6eiHABTXnY8ZCm/REbdJ8qnA0ptmIzN0Ng== + dependencies: + loader-utils "^1.2.3" + schema-utils "^2.6.5" + string-width@^3.0.0, string-width@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/string-width/-/string-width-3.1.0.tgz#22767be21b62af1081574306f69ac51b62203961" @@ -10833,6 +10859,14 @@ strip-json-comments@^3.1.0, strip-json-comments@^3.1.1: resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== +style-loader@2: + version "2.0.0" + resolved "https://registry.yarnpkg.com/style-loader/-/style-loader-2.0.0.tgz#9669602fd4690740eaaec137799a03addbbc393c" + integrity sha512-Z0gYUJmzZ6ZdRUqpg1r8GsaFKypE+3xAzuFeMuoHgjc9KZv3wMyCRjQIWEbhoFSq7+7yoHXySDJyyWQaPajeiQ== + dependencies: + loader-utils "^2.0.0" + schema-utils "^3.0.0" + style-search@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/style-search/-/style-search-0.1.0.tgz#7958c793e47e32e07d2b5cafe5c0bf8e12e77902" From a9f3f873a5d24a6544e2d975d077aded204b6bf6 Mon Sep 17 00:00:00 2001 From: Dariusz Niemczyk Date: Sun, 1 Aug 2021 17:05:33 +0200 Subject: [PATCH 3/8] Add a .env config and explanation how to use HMR --- .env.example | 13 ++++++++ .gitignore | 1 + README.md | 51 +++++++++++++++++++------------ package.json | 1 + webpack.config.js | 77 +++++++++++++++++++++++++++++------------------ yarn.lock | 5 +++ 6 files changed, 99 insertions(+), 49 deletions(-) create mode 100644 .env.example diff --git a/.env.example b/.env.example new file mode 100644 index 0000000000..52f2a023a2 --- /dev/null +++ b/.env.example @@ -0,0 +1,13 @@ +# If you want to have proper hot-reload css experience, define one and set this on. +CSS_HOT_RELOAD=1 +# Define which one theme you want to load for hot-reload purposes. +# You can load multiple themes at once, but switching between is quite buggy with the hot-reload turned on. +# To use a single theme just uncomment a line with the theme you want to use. +MATRIX_THEMES='light' +#MATRIX_THEMES='dark' +#MATRIX_THEMES='light-legacy' +#MATRIX_THEMES='dark-legacy' +#MATRIX_THEMES='light-custom' +#MATRIX_THEMES='dark-custom' +# If you want to use multiple themes, define the combinations manually like below: +#MATRIX_THEMES='light,dark' diff --git a/.gitignore b/.gitignore index bc6255165b..52152e1a11 100644 --- a/.gitignore +++ b/.gitignore @@ -21,3 +21,4 @@ electron/pub /webpack-stats.json .vscode .vscode/ +.env diff --git a/README.md b/README.md index 8bf6ecd8cf..76acfcaf85 100644 --- a/README.md +++ b/README.md @@ -30,14 +30,14 @@ and [element-ios](https://github.com/vector-im/element-ios). Getting Started =============== -The easiest way to test Element is to just use the hosted copy at https://app.element.io. -The `develop` branch is continuously deployed to https://develop.element.io +The easiest way to test Element is to just use the hosted copy at . +The `develop` branch is continuously deployed to for those who like living dangerously. To host your own copy of Element, the quickest bet is to use a pre-built released version of Element: -1. Download the latest version from https://github.com/vector-im/element-web/releases +1. Download the latest version from 1. Untar the tarball on your web server 1. Move (or symlink) the `element-x.x.x` directory to an appropriate name 1. Configure the correct caching headers in your webserver (see below) @@ -46,7 +46,7 @@ released version of Element: 1. Enter the URL into your browser and log into Element! Releases are signed using gpg and the OpenPGP standard, and can be checked against the public key located -at https://packages.riot.im/element-release-key.asc. +at . Note that for the security of your chats will need to serve Element over HTTPS. Major browsers also do not allow you to use VoIP/video @@ -72,7 +72,7 @@ access to Element (or other apps) due to sharing the same domain. We have put some coarse mitigations into place to try to protect against this situation, but it's still not good practice to do it in the first place. See -https://github.com/vector-im/element-web/issues/1977 for more details. + for more details. Configuration best practices ---------------------------- @@ -80,15 +80,15 @@ Configuration best practices Unless you have special requirements, you will want to add the following to your web server configuration when hosting Element Web: -- The `X-Frame-Options: SAMEORIGIN` header, to prevent Element Web from being +* The `X-Frame-Options: SAMEORIGIN` header, to prevent Element Web from being framed and protect from [clickjacking][owasp-clickjacking]. -- The `frame-ancestors 'none'` directive to your `Content-Security-Policy` +* The `frame-ancestors 'none'` directive to your `Content-Security-Policy` header, as the modern replacement for `X-Frame-Options` (though both should be included since not all browsers support it yet, see [this][owasp-clickjacking-csp]). -- The `X-Content-Type-Options: nosniff` header, to [disable MIME +* The `X-Content-Type-Options: nosniff` header, to [disable MIME sniffing][mime-sniffing]. -- The `X-XSS-Protection: 1; mode=block;` header, for basic XSS protection in +* The `X-XSS-Protection: 1; mode=block;` header, for basic XSS protection in legacy browsers. [mime-sniffing]: @@ -127,10 +127,10 @@ guide](https://classic.yarnpkg.com/en/docs/install) if you do not have it alread 1. Clone the repo: `git clone https://github.com/vector-im/element-web.git`. 1. Switch to the element-web directory: `cd element-web`. 1. Install the prerequisites: `yarn install`. - * If you're using the `develop` branch, then it is recommended to set up a + * If you're using the `develop` branch, then it is recommended to set up a proper development environment (see [Setting up a dev environment](#setting-up-a-dev-environment) below). Alternatively, you - can use https://develop.element.io - the continuous integration release of + can use - the continuous integration release of the develop branch. 1. Configure the app by copying `config.sample.json` to `config.json` and modifying it. See the [configuration docs](docs/config.md) for details. @@ -148,15 +148,16 @@ Running as a Desktop app ======================== Element can also be run as a desktop app, wrapped in Electron. You can download a -pre-built version from https://element.io/get-started or, if you prefer, +pre-built version from or, if you prefer, build it yourself. -To build it yourself, follow the instructions at https://github.com/vector-im/element-desktop. +To build it yourself, follow the instructions at . Many thanks to @aviraldg for the initial work on the Electron integration. Other options for running as a desktop app: - * @asdf:matrix.org points out that you can use nativefier and it just works(tm) + +* @asdf:matrix.org points out that you can use nativefier and it just works(tm) ```bash yarn global add nativefier @@ -171,6 +172,7 @@ Running from Docker The Docker image can be used to serve element-web as a web server. The easiest way to use it is to use the prebuilt image: + ```bash docker run -p 80:80 vectorim/element-web ``` @@ -178,11 +180,13 @@ docker run -p 80:80 vectorim/element-web To supply your own custom `config.json`, map a volume to `/app/config.json`. For example, if your custom config was located at `/etc/element-web/config.json` then your Docker command would be: + ```bash docker run -p 80:80 -v /etc/element-web/config.json:/app/config.json vectorim/element-web ``` To build the image yourself: + ```bash git clone https://github.com/vector-im/element-web.git element-web cd element-web @@ -192,6 +196,7 @@ docker build . If you're building a custom branch, or want to use the develop branch, check out the appropriate element-web branch and then run: + ```bash docker build -t \ --build-arg USE_CUSTOM_SDKS=true \ @@ -224,6 +229,7 @@ Caching requirements ==================== Element requires the following URLs not to be cached, when/if you are serving Element from your own webserver: + ``` /config.*.json /i18n @@ -261,6 +267,10 @@ internet. So please don't depend on resources (JS libs, CSS, images, fonts) hosted by external CDNs or servers but instead please package all dependencies into Element itself. +CSS hot-reload is currently an opt-in development feature, and if you want to have +it working properly on your environment, create a `.env` file in this repository +with proper environmental, see `.env.example` for documentation and example. + Setting up a dev environment ============================ @@ -302,14 +312,15 @@ yarn reskindex yarn start ``` - Wait a few seconds for the initial build to finish; you should see something like: + ``` [element-js] [webpack.Progress] 100% [element-js] [element-js] ℹ 「wdm」: 1840 modules [element-js] ℹ 「wdm」: Compiled successfully. ``` + Remember, the command will not terminate since it runs the web server and rebuilds source files when they change. This development server also disables caching, so do NOT use it in production. @@ -317,7 +328,7 @@ Wait a few seconds for the initial build to finish; you should see something lik Configure the app by copying `config.sample.json` to `config.json` and modifying it. See the [configuration docs](docs/config.md) for details. -Open http://127.0.0.1:8080/ in your browser to see your newly built Element. +Open in your browser to see your newly built Element. **Note**: The build script uses inotify by default on Linux to monitor directories for changes. If the inotify limits are too low your build will fail silently or with @@ -393,14 +404,14 @@ Triaging issues We strive to completely cover all applicable issues with these core labels: 1. __Type__ — Every issue is assigned a type: - - __[T-Defect](https://github.com/vector-im/element-web/labels/T-Defect):__ + * __[T-Defect](https://github.com/vector-im/element-web/labels/T-Defect):__ Bugs, crashes, hangs, vulnerabilities, or other reported problems - - __[T-Enhancement](https://github.com/vector-im/element-web/labels/T-Enhancement):__ + * __[T-Enhancement](https://github.com/vector-im/element-web/labels/T-Enhancement):__ New features, changes in functionality, performance boosts, user-facing improvements - - __[T-Task](https://github.com/vector-im/element-web/labels/T-Task):__ + * __[T-Task](https://github.com/vector-im/element-web/labels/T-Task):__ Refactoring, enabling or disabling functionality, other engineering tasks - - __[T-Other](https://github.com/vector-im/element-web/labels/T-Other):__ + * __[T-Other](https://github.com/vector-im/element-web/labels/T-Other):__ Questions, user support, anything else 2. __Severity__ — All issues labeled `T-Defect` are also assigned a severity: diff --git a/package.json b/package.json index fcf2b1fa53..1b2c87f331 100644 --- a/package.json +++ b/package.json @@ -104,6 +104,7 @@ "concurrently": "^5.3.0", "cpx": "^1.5.0", "css-loader": "^3.6.0", + "dotenv": "^10.0.0", "eslint": "7.18.0", "eslint-config-google": "^0.14.0", "eslint-plugin-matrix-org": "github:matrix-org/eslint-plugin-matrix-org#main", diff --git a/webpack.config.js b/webpack.config.js index 03ac4a8eff..035cfdb977 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -1,5 +1,6 @@ /* eslint-disable quote-props */ +const dotenv = require('dotenv'); const path = require('path'); const webpack = require('webpack'); const HtmlWebpackPlugin = require('html-webpack-plugin'); @@ -21,24 +22,29 @@ const cssThemes = { "theme-dark-custom": "./node_modules/matrix-react-sdk/res/themes/dark-custom/css/dark-custom.scss", }; -function getActiveThemes() { - const theme = process.env.MATRIX_THEMES ?? 'light,dark'; - const themes = theme.split(',').filter(x => x).map(x => x.trim()).filter(x => x); - return themes; +let dotenvConfig = { parsed: {} }; +try { + dotenvConfig = dotenv.config(); +} catch (err) { + dotenvConfig = { + parsed: { + CSS_HOT_RELOAD: 0, + MATRIX_THEMES: 'light', + }, + }; } -const ACTIVE_THEMES = getActiveThemes(); -function getThemesImports() { - const imports = ACTIVE_THEMES.map((t, index) => { - const themeImportPath = cssThemes[`theme-${t}`].replace('./node_modules/', ''); - return themeImportPath; - }); - const s = JSON.stringify(ACTIVE_THEMES); - return ` - window.MX_insertedThemeStylesCounter = 0 - window.MX_DEV_ACTIVE_THEMES = (${s}); - ${imports.map(i => `import("${i}")`).join('\n')}; - `; +const CSS_HOT_RELOAD = process.env.CSS_HOT_RELOAD ?? dotenvConfig.CSS_HOT_RELOAD; +const MATRIX_THEMES = process.env.MATRIX_THEMES ?? dotenvConfig.MATRIX_THEMES; + +function getActiveThemes() { + // We want to use `light` theme by default if it's not defined. + const theme = MATRIX_THEMES; + const themes = theme.split(',').filter(x => x).map(x => x.trim()).filter(x => x); + if (themes.length > 1) { + throw new Error('Please see `.env.example` for proper hotreload&themes configuation.'); + } + return themes; } module.exports = (env, argv) => { @@ -57,6 +63,7 @@ module.exports = (env, argv) => { nodeEnv = "production"; } const devMode = nodeEnv !== 'production'; + const useCssHotReload = CSS_HOT_RELOAD === '1' && devMode; const development = {}; if (argv.mode === "production") { @@ -73,16 +80,22 @@ module.exports = (env, argv) => { const reactSdkSrcDir = path.resolve(require.resolve("matrix-react-sdk/package.json"), '..', 'src'); const jsSdkSrcDir = path.resolve(require.resolve("matrix-js-sdk/package.json"), '..', 'src'); + const ACTIVE_THEMES = getActiveThemes(); + function getThemesImports() { + const imports = ACTIVE_THEMES.map((t, index) => { + const themeImportPath = cssThemes[`theme-${ t }`].replace('./node_modules/', ''); + return themeImportPath; + }); + const s = JSON.stringify(ACTIVE_THEMES); + return ` + window.MX_insertedThemeStylesCounter = 0 + window.MX_DEV_ACTIVE_THEMES = (${ s }); + ${ imports.map(i => `import("${ i }")`).join('\n') }; + `; + } + return { ...development, - - watch: true, - watchOptions: { - aggregateTimeout: 200, - poll: 1000, - ignored: [/node_modules([\\]+|\/)+(?!matrix-react-sdk|matrix-js-sdk)/], - }, - node: { // Mock out the NodeFS module: The opus decoder imports this wrongly. fs: 'empty', @@ -94,7 +107,7 @@ module.exports = (env, argv) => { "jitsi": "./src/vector/jitsi/index.ts", "usercontent": "./node_modules/matrix-react-sdk/src/usercontent/index.js", "recorder-worklet": "./node_modules/matrix-react-sdk/src/audio/RecorderWorklet.ts", - ...(devMode ? {} : cssThemes), + ...(useCssHotReload ? {} : cssThemes), }, optimization: { @@ -178,7 +191,7 @@ module.exports = (env, argv) => { /olm[\\/](javascript[\\/])?olm\.js$/, ], rules: [ - devMode && { + useCssHotReload && { test: /devcss\.ts$/, loader: 'string-replace-loader', options: { @@ -217,7 +230,7 @@ module.exports = (env, argv) => { { test: /\.css$/, use: [ - devMode ? 'style-loader' : MiniCssExtractPlugin.loader, + MiniCssExtractPlugin.loader, { loader: 'css-loader', options: { @@ -271,8 +284,14 @@ module.exports = (env, argv) => { * of the JS/TS files. * Should be MUCH better with webpack 5, but we're stuck to this solution for now. */ - devMode ? { + useCssHotReload ? { loader: 'style-loader', options: { + /** + * If we refactor the `theme.js` in `matrix-react-sdk` a little bit, + * we could try using `lazyStyleTag` here to add and remove styles on demand, + * that would nicely resolve issues of race conditions for themes, + * at least for development purposes. + */ attributes: { 'data-mx-theme': 'replace_me', }, @@ -430,7 +449,7 @@ module.exports = (env, argv) => { }, ], }, - ], + ].filter(Boolean), }, plugins: [ diff --git a/yarn.lock b/yarn.lock index 74e40d7f90..495862952c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4024,6 +4024,11 @@ dot-prop@^5.2.0: dependencies: is-obj "^2.0.0" +dotenv@^10.0.0: + version "10.0.0" + resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-10.0.0.tgz#3d4227b8fb95f81096cdd2b66653fb2c7085ba81" + integrity sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q== + duplexer@^0.1.1: version "0.1.2" resolved "https://registry.yarnpkg.com/duplexer/-/duplexer-0.1.2.tgz#3abe43aef3835f8ae077d136ddce0f276b0400e6" From da4a49fd3159b202c52a0b10e8ba75a09b21c69c Mon Sep 17 00:00:00 2001 From: Dariusz Niemczyk Date: Sun, 1 Aug 2021 17:13:29 +0200 Subject: [PATCH 4/8] Add a default theme for CI --- webpack.config.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webpack.config.js b/webpack.config.js index 035cfdb977..7aab5d1387 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -39,7 +39,7 @@ const MATRIX_THEMES = process.env.MATRIX_THEMES ?? dotenvConfig.MATRIX_THEMES; function getActiveThemes() { // We want to use `light` theme by default if it's not defined. - const theme = MATRIX_THEMES; + const theme = MATRIX_THEMES ?? 'light'; const themes = theme.split(',').filter(x => x).map(x => x.trim()).filter(x => x); if (themes.length > 1) { throw new Error('Please see `.env.example` for proper hotreload&themes configuation.'); From 88df5c33a9e85674d39c1621c130d8122ce3f9a4 Mon Sep 17 00:00:00 2001 From: Dariusz Niemczyk <3636685+Palid@users.noreply.github.com> Date: Mon, 2 Aug 2021 11:09:10 +0200 Subject: [PATCH 5/8] Update webpack.config.js Co-authored-by: Germain --- webpack.config.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webpack.config.js b/webpack.config.js index 7aab5d1387..0701b0f437 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -42,7 +42,7 @@ function getActiveThemes() { const theme = MATRIX_THEMES ?? 'light'; const themes = theme.split(',').filter(x => x).map(x => x.trim()).filter(x => x); if (themes.length > 1) { - throw new Error('Please see `.env.example` for proper hotreload&themes configuation.'); + throw new Error('Please see `.env.example` for proper hot reload & themes configuration.'); } return themes; } From 10fb3859faab523dad0f9cce23fa7110fd943569 Mon Sep 17 00:00:00 2001 From: Dariusz Niemczyk <“palid@users.noreply.github.com”> Date: Mon, 2 Aug 2021 22:27:13 +0200 Subject: [PATCH 6/8] Add better docs and iframe support --- .env.example | 3 +- webpack.config.js | 74 +++++++++++++++++++---------------------------- 2 files changed, 32 insertions(+), 45 deletions(-) diff --git a/.env.example b/.env.example index 52f2a023a2..65d7581ad0 100644 --- a/.env.example +++ b/.env.example @@ -1,7 +1,6 @@ # If you want to have proper hot-reload css experience, define one and set this on. CSS_HOT_RELOAD=1 # Define which one theme you want to load for hot-reload purposes. -# You can load multiple themes at once, but switching between is quite buggy with the hot-reload turned on. # To use a single theme just uncomment a line with the theme you want to use. MATRIX_THEMES='light' #MATRIX_THEMES='dark' @@ -9,5 +8,7 @@ MATRIX_THEMES='light' #MATRIX_THEMES='dark-legacy' #MATRIX_THEMES='light-custom' #MATRIX_THEMES='dark-custom' +# You can load multiple themes at once, but switching between them may require full page reload. +# It will also multiple compliation times by the number of turned on themes. # If you want to use multiple themes, define the combinations manually like below: #MATRIX_THEMES='light,dark' diff --git a/webpack.config.js b/webpack.config.js index 0701b0f437..8df042d14c 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -9,6 +9,7 @@ const TerserPlugin = require('terser-webpack-plugin'); const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin'); const HtmlWebpackInjectPreload = require('@principalstudio/html-webpack-inject-preload'); +dotenv.config(); let ogImageUrl = process.env.RIOT_OG_IMAGE_URL; if (!ogImageUrl) ogImageUrl = 'https://app.element.io/themes/element/img/logos/opengraph.png'; @@ -22,28 +23,10 @@ const cssThemes = { "theme-dark-custom": "./node_modules/matrix-react-sdk/res/themes/dark-custom/css/dark-custom.scss", }; -let dotenvConfig = { parsed: {} }; -try { - dotenvConfig = dotenv.config(); -} catch (err) { - dotenvConfig = { - parsed: { - CSS_HOT_RELOAD: 0, - MATRIX_THEMES: 'light', - }, - }; -} - -const CSS_HOT_RELOAD = process.env.CSS_HOT_RELOAD ?? dotenvConfig.CSS_HOT_RELOAD; -const MATRIX_THEMES = process.env.MATRIX_THEMES ?? dotenvConfig.MATRIX_THEMES; - function getActiveThemes() { // We want to use `light` theme by default if it's not defined. - const theme = MATRIX_THEMES ?? 'light'; + const theme = process.env.MATRIX_THEMES ?? 'dark'; const themes = theme.split(',').filter(x => x).map(x => x.trim()).filter(x => x); - if (themes.length > 1) { - throw new Error('Please see `.env.example` for proper hot reload & themes configuration.'); - } return themes; } @@ -63,7 +46,7 @@ module.exports = (env, argv) => { nodeEnv = "production"; } const devMode = nodeEnv !== 'production'; - const useCssHotReload = CSS_HOT_RELOAD === '1' && devMode; + const useCssHotReload = process.env.CSS_HOT_RELOAD === '1' && devMode; const development = {}; if (argv.mode === "production") { @@ -88,10 +71,10 @@ module.exports = (env, argv) => { }); const s = JSON.stringify(ACTIVE_THEMES); return ` - window.MX_insertedThemeStylesCounter = 0 - window.MX_DEV_ACTIVE_THEMES = (${ s }); - ${ imports.map(i => `import("${ i }")`).join('\n') }; - `; + window.MX_insertedThemeStylesCounter = 0; + window.MX_DEV_ACTIVE_THEMES = (${ s }); + ${ imports.map(i => `import("${ i }")`).join('\n') }; + `; } return { @@ -285,27 +268,30 @@ module.exports = (env, argv) => { * Should be MUCH better with webpack 5, but we're stuck to this solution for now. */ useCssHotReload ? { - loader: 'style-loader', options: { - /** - * If we refactor the `theme.js` in `matrix-react-sdk` a little bit, - * we could try using `lazyStyleTag` here to add and remove styles on demand, - * that would nicely resolve issues of race conditions for themes, - * at least for development purposes. - */ - attributes: { - 'data-mx-theme': 'replace_me', - }, - // Properly disable all other instances of themes + loader: 'style-loader', + /** + * If we refactor the `theme.js` in `matrix-react-sdk` a little bit, + * we could try using `lazyStyleTag` here to add and remove styles on demand, + * that would nicely resolve issues of race conditions for themes, + * at least for development purposes. + */ + options: { + insert: function insertBeforeAt(element) { - const isMatrixTheme = element.attributes['data-mx-theme'].value === 'replace_me'; const parent = document.querySelector('head'); - - element.disabled = true; - if (isMatrixTheme) { - element.attributes['data-mx-theme'].value = window.MX_DEV_ACTIVE_THEMES[window.MX_insertedThemeStylesCounter]; - window.MX_insertedThemeStylesCounter++; + // We're in iframe + if (!window.MX_DEV_ACTIVE_THEMES) { + parent.appendChild(element); + return; } - + // Properly disable all other instances of themes + element.disabled = true; + element.onload = () => { + element.disabled = true; + }; + const theme = window.MX_DEV_ACTIVE_THEMES[window.MX_insertedThemeStylesCounter]; + element.setAttribute('data-mx-theme', theme); + window.MX_insertedThemeStylesCounter++; parent.appendChild(element); }, }, @@ -460,8 +446,8 @@ module.exports = (env, argv) => { // This exports our CSS using the splitChunks and loaders above. new MiniCssExtractPlugin({ - filename: "bundles/[hash]/[name].css", - chunkFilename: "bundles/[hash]/[name].css", + filename: useCssHotReload ? "bundles/[name].css" : "bundles/[hash]/[name].css", + chunkFilename: useCssHotReload ? "bundles/[name].css" : "bundles/[hash]/[name].css", ignoreOrder: false, // Enable to remove warnings about conflicting order }), From cc1a1e751b893143ac61a74c18c84a1b46cdc9fc Mon Sep 17 00:00:00 2001 From: Dariusz Niemczyk Date: Tue, 3 Aug 2021 18:37:48 +0200 Subject: [PATCH 7/8] Add some additional HMR cleanup for dev --- src/vector/devcss.ts | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/vector/devcss.ts b/src/vector/devcss.ts index 1937ca949d..75ce2b2af2 100644 --- a/src/vector/devcss.ts +++ b/src/vector/devcss.ts @@ -28,6 +28,19 @@ limitations under the License. * For more details, see webpack.config.js:184 (string-replace-loader) */ if (process.env.NODE_ENV === 'development') { - "use theming"; + ("use theming"); + /** + * Clean up old hot-module script injections as they hog up memory + * and anything other than the newest one is really not needed at all. + * We don't need to do it more frequently than every half a minute or so, + * but it's done to delay full page reload due to app slowness. + */ + setInterval(() => { + const elements = Array.from(document.querySelectorAll("script[src*=hot-update]")); + if (elements.length > 1) { + const oldInjects = elements.slice(0, elements.length - 1); + oldInjects.forEach(e => e.remove()); + } + }, 1000); } From 06d536afbc44f434e41465071a39e39aff940c09 Mon Sep 17 00:00:00 2001 From: Dariusz Niemczyk Date: Tue, 3 Aug 2021 18:38:00 +0200 Subject: [PATCH 8/8] Revert nameModules in webpackConfig --- webpack.config.js | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/webpack.config.js b/webpack.config.js index 8df042d14c..b2cb981ef6 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -113,7 +113,7 @@ module.exports = (env, argv) => { // This fixes duplicate files showing up in chrome with sourcemaps enabled. // See https://github.com/webpack/webpack/issues/7128 for more info. - namedModules: true, + namedModules: false, // Minification is normally enabled by default for webpack in production mode, but // we use a CSS optimizer too and need to manage it ourselves. @@ -528,18 +528,14 @@ module.exports = (env, argv) => { devServer: { // serve unwebpacked assets from webapp. contentBase: [ - './src/', './webapp', - './bundles/**', - './node_modules/matrix-react-sdk/**', - './node_modules/matrix-js-sdk/**', ], // Only output errors, warnings, or new compilations. // This hides the massive list of modules. stats: 'minimal', - hot: true, - injectHot: true, + // hot: false, + // injectHot: false, hotOnly: true, inline: true, },