Make CSS Hot-reload work in a hacky way

pull/18318/head
Dariusz Niemczyk 2021-08-01 16:28:38 +02:00 committed by Dariusz Niemczyk
parent 4b12b711ee
commit b677edbc58
No known key found for this signature in database
GPG Key ID: 0AD2F70C94CA5B03
5 changed files with 174 additions and 28 deletions

View File

@ -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": "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: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: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": "yarn lint:types && yarn lint:js && yarn lint:style",
"lint:js": "eslint --max-warnings 0 src", "lint:js": "eslint --max-warnings 0 src",
"lint:js-fix": "eslint --fix src", "lint:js-fix": "eslint --fix src",
@ -141,6 +141,8 @@
"rimraf": "^3.0.2", "rimraf": "^3.0.2",
"shell-escape": "^0.2.0", "shell-escape": "^0.2.0",
"simple-proxy-agent": "^1.1.0", "simple-proxy-agent": "^1.1.0",
"string-replace-loader": "2",
"style-loader": "2",
"stylelint": "^13.9.0", "stylelint": "^13.9.0",
"stylelint-config-standard": "^20.0.0", "stylelint-config-standard": "^20.0.0",
"stylelint-scss": "^3.18.0", "stylelint-scss": "^3.18.0",

33
src/vector/devcss.ts Normal file
View File

@ -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";
}

View File

@ -25,6 +25,14 @@ require('gfm.css/gfm.css');
require('highlight.js/styles/github.css'); require('highlight.js/styles/github.css');
require('katex/dist/katex.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. // 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 { parseQsFromFragment } from "./url_utils";
import './modernizr'; import './modernizr';

View File

@ -1,6 +1,7 @@
/* eslint-disable quote-props */ /* eslint-disable quote-props */
const path = require('path'); const path = require('path');
const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin'); const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin'); const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const TerserPlugin = require('terser-webpack-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; let ogImageUrl = process.env.RIOT_OG_IMAGE_URL;
if (!ogImageUrl) ogImageUrl = 'https://app.element.io/themes/element/img/logos/opengraph.png'; if (!ogImageUrl) ogImageUrl = 'https://app.element.io/themes/element/img/logos/opengraph.png';
const additionalPlugins = [ const cssThemes = {
// This is where you can put your customisation replacements. // 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) => { module.exports = (env, argv) => {
let nodeEnv = argv.mode; let nodeEnv = argv.mode;
@ -29,6 +56,7 @@ module.exports = (env, argv) => {
// application to productions standards // application to productions standards
nodeEnv = "production"; nodeEnv = "production";
} }
const devMode = nodeEnv !== 'production';
const development = {}; const development = {};
if (argv.mode === "production") { if (argv.mode === "production") {
@ -48,6 +76,13 @@ module.exports = (env, argv) => {
return { return {
...development, ...development,
watch: true,
watchOptions: {
aggregateTimeout: 200,
poll: 1000,
ignored: [/node_modules([\\]+|\/)+(?!matrix-react-sdk|matrix-js-sdk)/],
},
node: { node: {
// Mock out the NodeFS module: The opus decoder imports this wrongly. // Mock out the NodeFS module: The opus decoder imports this wrongly.
fs: 'empty', fs: 'empty',
@ -59,14 +94,7 @@ module.exports = (env, argv) => {
"jitsi": "./src/vector/jitsi/index.ts", "jitsi": "./src/vector/jitsi/index.ts",
"usercontent": "./node_modules/matrix-react-sdk/src/usercontent/index.js", "usercontent": "./node_modules/matrix-react-sdk/src/usercontent/index.js",
"recorder-worklet": "./node_modules/matrix-react-sdk/src/audio/RecorderWorklet.ts", "recorder-worklet": "./node_modules/matrix-react-sdk/src/audio/RecorderWorklet.ts",
...(devMode ? {} : 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",
}, },
optimization: { optimization: {
@ -89,7 +117,7 @@ module.exports = (env, argv) => {
// This fixes duplicate files showing up in chrome with sourcemaps enabled. // This fixes duplicate files showing up in chrome with sourcemaps enabled.
// See https://github.com/webpack/webpack/issues/7128 for more info. // 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 // Minification is normally enabled by default for webpack in production mode, but
// we use a CSS optimizer too and need to manage it ourselves. // we use a CSS optimizer too and need to manage it ourselves.
@ -150,6 +178,14 @@ module.exports = (env, argv) => {
/olm[\\/](javascript[\\/])?olm\.js$/, /olm[\\/](javascript[\\/])?olm\.js$/,
], ],
rules: [ rules: [
devMode && {
test: /devcss\.ts$/,
loader: 'string-replace-loader',
options: {
search: '"use theming";',
replace: getThemesImports(),
},
},
{ {
test: /\.worker\.ts$/, test: /\.worker\.ts$/,
loader: "worker-loader", loader: "worker-loader",
@ -181,7 +217,7 @@ module.exports = (env, argv) => {
{ {
test: /\.css$/, test: /\.css$/,
use: [ use: [
MiniCssExtractPlugin.loader, devMode ? 'style-loader' : MiniCssExtractPlugin.loader,
{ {
loader: 'css-loader', loader: 'css-loader',
options: { options: {
@ -219,7 +255,7 @@ module.exports = (env, argv) => {
// It's important that this plugin is last otherwise we end // It's important that this plugin is last otherwise we end
// up with broken CSS. // 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", parser: "postcss-scss",
"local-plugins": true, "local-plugins": true,
@ -230,7 +266,31 @@ module.exports = (env, argv) => {
{ {
test: /\.scss$/, test: /\.scss$/,
use: [ 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', loader: 'css-loader',
options: { options: {
@ -257,7 +317,7 @@ module.exports = (env, argv) => {
// It's important that this plugin is last otherwise we end // It's important that this plugin is last otherwise we end
// up with broken CSS. // 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", parser: "postcss-scss",
"local-plugins": true, "local-plugins": true,
@ -374,9 +434,15 @@ module.exports = (env, argv) => {
}, },
plugins: [ 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. // This exports our CSS using the splitChunks and loaders above.
new MiniCssExtractPlugin({ 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 ignoreOrder: false, // Enable to remove warnings about conflicting order
}), }),
@ -437,7 +503,6 @@ module.exports = (env, argv) => {
files: [{ match: /.*Inter.*\.woff2$/ }], files: [{ match: /.*Inter.*\.woff2$/ }],
}), }),
...additionalPlugins,
], ],
output: { output: {
@ -457,17 +522,21 @@ module.exports = (env, argv) => {
// configuration for the webpack-dev-server // configuration for the webpack-dev-server
devServer: { devServer: {
// serve unwebpacked assets from webapp. // 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. // Only output errors, warnings, or new compilations.
// This hides the massive list of modules. // This hides the massive list of modules.
stats: 'minimal', stats: 'minimal',
hot: true,
// hot module replacement doesn't work (I think we'd need react-hot-reload?) injectHot: true,
// so webpack-dev-server reloads the page on every update which is quite hotOnly: true,
// tedious in Riot since that can take a while. inline: true,
hot: false,
inline: false,
}, },
}; };
}; };

View File

@ -1454,7 +1454,7 @@
dependencies: dependencies:
"@types/istanbul-lib-report" "*" "@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" version "7.0.8"
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.8.tgz#edf1bf1dbf4e04413ca8e5b17b3b7d7d54b59818" resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.8.tgz#edf1bf1dbf4e04413ca8e5b17b3b7d7d54b59818"
integrity sha512-YSBPTLTVm2e2OoQIDYx8HaeWJ5tTToLH67kXR7zYNGupXMEHa2++G8k+DczX2cFVgalypqtyZIcU19AFcmOpmg== 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" resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.5.2.tgz#31f29da5ab6e00d1c2d329acf7b5929614d5014d"
integrity sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ== 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" version "6.12.6"
resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4"
integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== 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" emojis-list "^3.0.0"
json5 "^1.0.1" 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: locate-path@^3.0.0:
version "3.0.0" version "3.0.0"
resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-3.0.0.tgz#dbec3b3ab759758071b58fe59fc41871af21400e" 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 "^6.12.4"
ajv-keywords "^3.5.2" 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: select-hose@^2.0.0:
version "2.0.0" version "2.0.0"
resolved "https://registry.yarnpkg.com/select-hose/-/select-hose-2.0.0.tgz#625d8658f865af43ec962bfc376a37359a4994ca" 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" char-regex "^1.0.2"
strip-ansi "^6.0.0" 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: string-width@^3.0.0, string-width@^3.1.0:
version "3.1.0" version "3.1.0"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-3.1.0.tgz#22767be21b62af1081574306f69ac51b62203961" 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" resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006"
integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== 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: style-search@^0.1.0:
version "0.1.0" version "0.1.0"
resolved "https://registry.yarnpkg.com/style-search/-/style-search-0.1.0.tgz#7958c793e47e32e07d2b5cafe5c0bf8e12e77902" resolved "https://registry.yarnpkg.com/style-search/-/style-search-0.1.0.tgz#7958c793e47e32e07d2b5cafe5c0bf8e12e77902"