Merge pull request #18318 from vector-im/palid/feature/css-hmr

Add CSS hot module reload
This commit is contained in:
Dariusz Niemczyk 2021-08-04 11:07:05 +02:00 committed by GitHub
commit 8b56a55906
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 241 additions and 49 deletions

14
.env.example Normal file
View File

@ -0,0 +1,14 @@
# 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.
# 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'
# 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'

1
.gitignore vendored
View File

@ -21,3 +21,4 @@ electron/pub
/webpack-stats.json
.vscode
.vscode/
.env

View File

@ -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 <https://app.element.io>.
The `develop` branch is continuously deployed to <https://develop.element.io>
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 <https://github.com/vector-im/element-web/releases>
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 <https://packages.riot.im/element-release-key.asc>.
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.
<https://github.com/vector-im/element-web/issues/1977> 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]:
@ -130,7 +130,7 @@ guide](https://classic.yarnpkg.com/en/docs/install) if you do not have it alread
* 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 <https://develop.element.io> - 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,14 +148,15 @@ 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 <https://element.io/get-started> 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 <https://github.com/vector-im/element-desktop>.
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)
```bash
@ -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] <s> [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 <http://127.0.0.1:8080/> 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:

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: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",
@ -105,6 +105,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#2306b3d4da4eba908b256014b979f1d3d43d2945",
@ -142,6 +143,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",

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

@ -0,0 +1,46 @@
/*
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");
/**
* 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);
}

View File

@ -43,7 +43,7 @@
if (match) {
var title = match[1].charAt(0).toUpperCase() + match[1].slice(1);
%>
<link rel="stylesheet" disabled="disabled" title="<%= title %>" href="<%= file %>">
<link rel="stylesheet" disabled="disabled" data-mx-theme="<%= title %>" title="<%= title %>" href="<%= file %>">
<% } else { %>
<link rel="stylesheet" href="<%= file %>">
<% }

View File

@ -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';

View File

@ -1,18 +1,34 @@
/* eslint-disable quote-props */
const dotenv = require('dotenv');
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');
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';
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() {
// We want to use `light` theme by default if it's not defined.
const theme = process.env.MATRIX_THEMES ?? 'dark';
const themes = theme.split(',').filter(x => x).map(x => x.trim()).filter(x => x);
return themes;
}
module.exports = (env, argv) => {
let nodeEnv = argv.mode;
@ -29,6 +45,8 @@ module.exports = (env, argv) => {
// application to productions standards
nodeEnv = "production";
}
const devMode = nodeEnv !== 'production';
const useCssHotReload = process.env.CSS_HOT_RELOAD === '1' && devMode;
const development = {};
if (argv.mode === "production") {
@ -45,9 +63,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,
node: {
// Mock out the NodeFS module: The opus decoder imports this wrongly.
fs: 'empty',
@ -59,14 +90,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",
...(useCssHotReload ? {} : cssThemes),
},
optimization: {
@ -150,6 +174,14 @@ module.exports = (env, argv) => {
/olm[\\/](javascript[\\/])?olm\.js$/,
],
rules: [
useCssHotReload && {
test: /devcss\.ts$/,
loader: 'string-replace-loader',
options: {
search: '"use theming";',
replace: getThemesImports(),
},
},
{
test: /\.worker\.ts$/,
loader: "worker-loader",
@ -230,7 +262,40 @@ 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.
*/
useCssHotReload ? {
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 parent = document.querySelector('head');
// 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);
},
},
} : MiniCssExtractPlugin.loader,
{
loader: 'css-loader',
options: {
@ -370,13 +435,19 @@ module.exports = (env, argv) => {
},
],
},
],
].filter(Boolean),
},
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: 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
}),
@ -437,7 +508,6 @@ module.exports = (env, argv) => {
files: [{ match: /.*Inter.*\.woff2$/ }],
}),
...additionalPlugins,
],
output: {
@ -457,17 +527,17 @@ module.exports = (env, argv) => {
// configuration for the webpack-dev-server
devServer: {
// serve unwebpacked assets from webapp.
contentBase: './webapp',
contentBase: [
'./webapp',
],
// 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: false,
// injectHot: false,
hotOnly: true,
inline: true,
},
};
};

View File

@ -1555,7 +1555,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==
@ -2020,7 +2020,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==
@ -4180,6 +4180,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"
@ -7349,6 +7354,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"
@ -10446,6 +10460,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"
@ -10968,6 +10991,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"
@ -11083,6 +11114,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"