From b834db0fd424e7ab02fcd5e509d855f2d97863bd Mon Sep 17 00:00:00 2001 From: Kristóf Marussy Date: Sat, 2 Oct 2021 02:11:31 +0200 Subject: perf(web): split off CodeMirror chunks Also optimizes statis asset caching. --- language-web/package.json | 5 +- language-web/src/main/css/index.scss | 29 ++++- .../solver/language/web/CacheControlFilter.java | 55 +++++++++ .../viatra/solver/language/web/ServerLauncher.java | 3 + language-web/src/main/js/App.tsx | 6 +- language-web/src/main/js/editor/Editor.tsx | 20 --- language-web/src/main/js/editor/EditorArea.tsx | 42 +++++++ language-web/src/main/js/editor/EditorStore.ts | 75 ++++++++++-- language-web/src/main/js/editor/editor.ts | 18 +++ language-web/src/main/js/theme/ThemeStore.ts | 4 +- language-web/webpack.config.js | 135 +++++++++++++-------- language-web/yarn.lock | 33 ++++- 12 files changed, 328 insertions(+), 97 deletions(-) create mode 100644 language-web/src/main/java/org/eclipse/viatra/solver/language/web/CacheControlFilter.java delete mode 100644 language-web/src/main/js/editor/Editor.tsx create mode 100644 language-web/src/main/js/editor/EditorArea.tsx create mode 100644 language-web/src/main/js/editor/editor.ts (limited to 'language-web') diff --git a/language-web/package.json b/language-web/package.json index ec54f7fd..ddb00f57 100644 --- a/language-web/package.json +++ b/language-web/package.json @@ -26,8 +26,8 @@ "@babel/preset-env": "^7.15.0", "@babel/preset-react": "^7.14.5", "@babel/preset-typescript": "^7.15.0", + "@babel/plugin-transform-runtime": "^7.15.0", "babel-loader": "^8.2.2", - "before-build-webpack": "^0.2.11", "css-loader": "^6.2.0", "eslint": "^7.32.0", "eslint-config-airbnb": "^18.2.1", @@ -38,7 +38,9 @@ "eslint-plugin-jsx-a11y": "^6.4.1", "html-webpack-plugin": "^5.3.2", "image-webpack-loader": "^7.0.1", + "magic-comments-loader": "^1.4.1", "mini-css-extract-plugin": "^2.2.0", + "@principalstudio/html-webpack-inject-preload": "^1.2.7", "sass": "^1.38.0", "sass-loader": "^12.1.0", "style-loader": "^3.2.1", @@ -56,6 +58,7 @@ "webpack-subresource-integrity": "^5.0.0-rc.1" }, "dependencies": { + "@babel/runtime": "^7.15.0", "@emotion/react": "^11.4.1", "@emotion/styled": "^11.3.0", "@fontsource/jetbrains-mono": "^4.5.0", diff --git a/language-web/src/main/css/index.scss b/language-web/src/main/css/index.scss index 9d6e0f6a..54f3a654 100644 --- a/language-web/src/main/css/index.scss +++ b/language-web/src/main/css/index.scss @@ -30,12 +30,27 @@ body { height: 100%; } -.CodeMirror, .CodeMirror-hints { +.problem-fallback-editor { + display: block; + height: 100%; + width: 100%; + resize: none; + border: none; + outline: none; + padding: 4px 4px 4px 16px; + white-space: pre; + overflow-wrap: normal; + overflow: auto; +} + +.CodeMirror, .CodeMirror-hints, .problem-fallback-editor { font-size: 16px; font-family: 'JetBrains MonoVariable', 'JetBrains Mono', monospace; font-feature-settings: 'liga', 'calt'; font-weight: 400; text-rendering: optimizeLegibility; + line-height: 1.35; + letter-spacing: 0; } @each $themeName, $theme in $themes { @@ -45,6 +60,16 @@ body { color: map.get($theme, 'foreground'); } + &.problem-fallback-editor { + background: map.get($theme, 'background'); + color: map.get($theme, 'foreground'); + caret-color: map.get($theme, 'cursor'); + + &::selection { + background: map.get($theme, 'selection'); + } + } + .CodeMirror-gutters { background: map.get($theme, 'background'); border: none; @@ -183,11 +208,13 @@ li.CodeMirror-hint-active { .xtext-marker_read { background: rgba(128, 203, 196, 0.2); + display: inline-block; } .xtext-marker_write { background: rgba(255, 229, 100, 0.2); + display: inline-block; } .problem-abstract { diff --git a/language-web/src/main/java/org/eclipse/viatra/solver/language/web/CacheControlFilter.java b/language-web/src/main/java/org/eclipse/viatra/solver/language/web/CacheControlFilter.java new file mode 100644 index 00000000..41b8e5bf --- /dev/null +++ b/language-web/src/main/java/org/eclipse/viatra/solver/language/web/CacheControlFilter.java @@ -0,0 +1,55 @@ +package org.eclipse.viatra.solver.language.web; + +import java.io.IOException; +import java.util.regex.Pattern; + +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +public class CacheControlFilter implements Filter { + + private static final String CACHE_CONTROL_HEADER = "Cache-Control"; + + private static final String EXPIRES_HEADER = "Expires"; + + private static final Pattern CACHE_URI_PATTERN = Pattern.compile(".*\\.(css|gif|js|map|png|svg|woff2)"); + + private static final long EXPIRY = 31536000; + + private static final String CACHE_CONTROL_CACHE_VALUE = "public, max-age: " + EXPIRY + ", immutable"; + + private static final String CACHE_CONTROL_NO_CACHE_VALUE = "no-cache, no-store, max-age: 0, must-revalidate"; + + @Override + public void init(FilterConfig filterConfig) throws ServletException { + // Nothing to initialize. + } + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) + throws IOException, ServletException { + if (request instanceof HttpServletRequest && response instanceof HttpServletResponse) { + var httpRequest = (HttpServletRequest) request; + var httpResponse = (HttpServletResponse) response; + if (CACHE_URI_PATTERN.matcher(httpRequest.getRequestURI()).matches()) { + httpResponse.setHeader(CACHE_CONTROL_HEADER, CACHE_CONTROL_CACHE_VALUE); + httpResponse.setDateHeader(EXPIRES_HEADER, System.currentTimeMillis() + EXPIRY * 1000L); + } else { + httpResponse.setHeader(CACHE_CONTROL_HEADER, CACHE_CONTROL_NO_CACHE_VALUE); + httpResponse.setDateHeader(EXPIRES_HEADER, 0); + } + } + chain.doFilter(request, response); + } + + @Override + public void destroy() { + // Nothing to dispose. + } +} diff --git a/language-web/src/main/java/org/eclipse/viatra/solver/language/web/ServerLauncher.java b/language-web/src/main/java/org/eclipse/viatra/solver/language/web/ServerLauncher.java index d92c7735..a6d58f95 100644 --- a/language-web/src/main/java/org/eclipse/viatra/solver/language/web/ServerLauncher.java +++ b/language-web/src/main/java/org/eclipse/viatra/solver/language/web/ServerLauncher.java @@ -8,8 +8,10 @@ import java.io.IOException; import java.net.InetSocketAddress; import java.net.URI; import java.net.URISyntaxException; +import java.util.EnumSet; import java.util.Set; +import javax.servlet.DispatcherType; import javax.servlet.SessionTrackingMode; import org.eclipse.jetty.server.Server; @@ -42,6 +44,7 @@ public class ServerLauncher { handler.setWelcomeFiles(new String[] { "index.html" }); addDefaultServlet(handler); } + handler.addFilter(CacheControlFilter.class, "/*", EnumSet.of(DispatcherType.REQUEST)); server.setHandler(handler); } diff --git a/language-web/src/main/js/App.tsx b/language-web/src/main/js/App.tsx index 17d4f339..5cd157fa 100644 --- a/language-web/src/main/js/App.tsx +++ b/language-web/src/main/js/App.tsx @@ -9,12 +9,12 @@ import MenuIcon from '@material-ui/icons/Menu'; import PlayArrowIcon from '@material-ui/icons/PlayArrow'; import { makeStyles } from './makeStyles'; -import { Editor } from './editor/Editor'; +import { EditorArea } from './editor/EditorArea'; import { EditorButtons } from './editor/EditorButtons'; const useStyles = makeStyles()((theme) => ({ container: { - maxHeight: '100vh', + height: '100vh', }, menuButton: { marginRight: theme.spacing(2), @@ -85,7 +85,7 @@ export const App = (): JSX.Element => { flexShrink={1} className={cx(classes.editorBox)} > - + ); diff --git a/language-web/src/main/js/editor/Editor.tsx b/language-web/src/main/js/editor/Editor.tsx deleted file mode 100644 index 9badb6a3..00000000 --- a/language-web/src/main/js/editor/Editor.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import { observer } from 'mobx-react-lite'; -import React from 'react'; -import { Controlled as CodeMirror } from 'react-codemirror2'; - -import { useRootStore } from '../RootStore'; - -export const Editor = observer(() => { - const { editorStore } = useRootStore(); - - return ( - editorStore.editorDidMount(editor)} - editorWillUnmount={() => editorStore.editorWillUnmount()} - onBeforeChange={(_editor, _data, value) => editorStore.updateValue(value)} - onChange={() => editorStore.reportChanged()} - /> - ); -}); diff --git a/language-web/src/main/js/editor/EditorArea.tsx b/language-web/src/main/js/editor/EditorArea.tsx new file mode 100644 index 00000000..f07a0ad8 --- /dev/null +++ b/language-web/src/main/js/editor/EditorArea.tsx @@ -0,0 +1,42 @@ +import { observer } from 'mobx-react-lite'; +import React, { useRef } from 'react'; + +import { useRootStore } from '../RootStore'; + +export const EditorArea = observer(() => { + const { editorStore } = useRootStore(); + const { CodeMirror } = editorStore.chunk || {}; + const fallbackTextarea = useRef(null); + + if (!CodeMirror) { + return ( + + ); + } + + const textarea = fallbackTextarea.current; + if (textarea) { + editorStore.setInitialSelection( + textarea.selectionStart, + textarea.selectionEnd, + document.activeElement === textarea, + ); + } + + return ( + editorStore.editorDidMount(editor)} + editorWillUnmount={() => editorStore.editorWillUnmount()} + onBeforeChange={(_editor, _data, value) => editorStore.updateValue(value)} + onChange={() => editorStore.reportChanged()} + /> + ); +}); diff --git a/language-web/src/main/js/editor/EditorStore.ts b/language-web/src/main/js/editor/EditorStore.ts index 5da45ac1..1ac2e79f 100644 --- a/language-web/src/main/js/editor/EditorStore.ts +++ b/language-web/src/main/js/editor/EditorStore.ts @@ -1,19 +1,14 @@ -import { Editor, EditorConfiguration } from 'codemirror'; -import 'codemirror/addon/selection/active-line'; +import type { Editor, EditorConfiguration } from 'codemirror'; import { createAtom, makeAutoObservable, observable, + runInAction, } from 'mobx'; -import 'mode-problem'; -import { - IXtextOptions, - IXtextServices, - createServices, - removeServices, -} from 'xtext/xtext-codemirror'; +import type { IXtextOptions, IXtextServices } from 'xtext/xtext-codemirror'; -import { ThemeStore } from '../theme/ThemeStore'; +import type { IEditorChunk } from './editor'; +import type { ThemeStore } from '../theme/ThemeStore'; const xtextLang = 'problem'; @@ -33,6 +28,8 @@ export class EditorStore { atom; + chunk?: IEditorChunk; + editor?: Editor; xtextServices?: IXtextServices; @@ -41,15 +38,56 @@ export class EditorStore { showLineNumbers = false; + initialSelection!: { start: number, end: number, focused: boolean }; + constructor(themeStore: ThemeStore) { this.themeStore = themeStore; this.atom = createAtom('EditorStore'); + this.resetInitialSelection(); makeAutoObservable(this, { themeStore: false, atom: false, + chunk: observable.ref, editor: observable.ref, xtextServices: observable.ref, + initialSelection: false, }); + import('./editor').then(({ editorChunk }) => { + runInAction(() => { + this.chunk = editorChunk; + }); + }).catch((error) => { + console.warn('Error while loading editor', error); + }); + } + + setInitialSelection(start: number, end: number, focused: boolean): void { + this.initialSelection = { start, end, focused }; + this.applyInitialSelectionToEditor(); + } + + private resetInitialSelection(): void { + this.initialSelection = { + start: 0, + end: 0, + focused: false, + }; + } + + private applyInitialSelectionToEditor(): void { + if (this.editor) { + const { start, end, focused } = this.initialSelection; + const doc = this.editor.getDoc(); + const startPos = doc.posFromIndex(start); + const endPos = doc.posFromIndex(end); + doc.setSelection(startPos, endPos, { + scroll: true, + }); + if (focused) { + this.editor.focus(); + } + this.resetInitialSelection(); + } } /** @@ -61,16 +99,23 @@ export class EditorStore { * @param newEditor The new CodeMirror instance */ editorDidMount(newEditor: Editor): void { + if (!this.chunk) { + throw new Error('Editor not loaded yet'); + } if (this.editor) { throw new Error('CoreMirror editor mounted before unmounting'); } this.editor = newEditor; - this.xtextServices = createServices(newEditor, xtextOptions); + this.xtextServices = this.chunk.createServices(newEditor, xtextOptions); + this.applyInitialSelectionToEditor(); } editorWillUnmount(): void { + if (!this.chunk) { + throw new Error('Editor not loaded yet'); + } if (this.editor) { - removeServices(this.editor); + this.chunk.removeServices(this.editor); } delete this.editor; delete this.xtextServices; @@ -93,10 +138,14 @@ export class EditorStore { this.atom.reportObserved(); } + get codeMirrorTheme(): string { + return `problem-${this.themeStore.className}`; + } + get codeMirrorOptions(): EditorConfiguration { return { ...codeMirrorGlobalOptions, - theme: this.themeStore.codeMirrorTheme, + theme: this.codeMirrorTheme, lineNumbers: this.showLineNumbers, }; } diff --git a/language-web/src/main/js/editor/editor.ts b/language-web/src/main/js/editor/editor.ts new file mode 100644 index 00000000..fbf8796b --- /dev/null +++ b/language-web/src/main/js/editor/editor.ts @@ -0,0 +1,18 @@ +import 'codemirror/addon/selection/active-line'; +import 'mode-problem'; +import { Controlled } from 'react-codemirror2'; +import { createServices, removeServices } from 'xtext/xtext-codemirror'; + +export interface IEditorChunk { + CodeMirror: typeof Controlled; + + createServices: typeof createServices; + + removeServices: typeof removeServices; +} + +export const editorChunk: IEditorChunk = { + CodeMirror: Controlled, + createServices, + removeServices, +}; diff --git a/language-web/src/main/js/theme/ThemeStore.ts b/language-web/src/main/js/theme/ThemeStore.ts index 0e4aeb23..2644a96a 100644 --- a/language-web/src/main/js/theme/ThemeStore.ts +++ b/language-web/src/main/js/theme/ThemeStore.ts @@ -51,7 +51,7 @@ export class ThemeStore { return responsiveFontSizes(materialUiTheme); } - get codeMirrorTheme(): string { - return `problem-${this.currentThemeData.className}`; + get className(): string { + return this.currentThemeData.className; } } diff --git a/language-web/webpack.config.js b/language-web/webpack.config.js index ae2f2386..1bd0edb2 100644 --- a/language-web/webpack.config.js +++ b/language-web/webpack.config.js @@ -1,8 +1,8 @@ const fs = require('fs'); const path = require('path'); -const WebpackBeforeBuildPlugin = require('before-build-webpack'); const HtmlWebpackPlugin = require('html-webpack-plugin'); +const HtmlWebpackInjectPreload = require('@principalstudio/html-webpack-inject-preload'); const MiniCssExtractPlugin = require('mini-css-extract-plugin'); const { SubresourceIntegrityPlugin } = require('webpack-subresource-integrity'); @@ -37,6 +37,15 @@ const babelPresets = [ ], '@babel/preset-react', ]; +const babelPlugins = [ + '@babel/plugin-transform-runtime', +] +const magicCommentsLoader = { + loader: 'magic-comments-loader', + options: { + webpackChunkName: true, + } +}; module.exports = { mode: devMode ? 'development' : 'production', @@ -44,8 +53,10 @@ module.exports = { output: { path: outputPath, publicPath: '/', - filename: devMode ? '[name].js' : '[contenthash].js', - chunkFilename: devMode ? '[id].js' : '[contenthash].js', + filename: devMode ? '[name].js' : '[name].[contenthash].js', + chunkFilename: devMode ? '[name].js' : '[name].[contenthash].js', + assetModuleFilename: devMode ? '[name].js' : '[name].[contenthash][ext]', + clean: true, crossOriginLoading: 'anonymous', }, module: { @@ -53,41 +64,53 @@ module.exports = { { test: /\.jsx?$/i, ...babelLoaderFilters, - loader: 'babel-loader', - options: { - presets: babelPresets, - plugins: [ - [ - '@babel/plugin-proposal-class-properties', - { - loose: false, + use: [ + { + loader: 'babel-loader', + options: { + presets: babelPresets, + plugins: [ + [ + '@babel/plugin-proposal-class-properties', + { + loose: false, + }, + ...babelPlugins, + ], + ], + assumptions: { + 'setPublicClassFields': false, }, - ], - ], - assumptions: { - 'setPublicClassFields': false, + }, }, - }, + magicCommentsLoader, + ], }, { test: /.tsx?$/i, ...babelLoaderFilters, - loader: 'babel-loader', - options: { - presets: [ - ...babelPresets, - [ - '@babel/preset-typescript', - { - isTSX: true, - allExtensions: true, - allowDeclareFields: true, - onlyRemoveTypeImports: true, - optimizeConstEnums: true, - }, - ] - ], - }, + use: [ + { + loader: 'babel-loader', + options: { + presets: [ + ...babelPresets, + [ + '@babel/preset-typescript', + { + isTSX: true, + allExtensions: true, + allowDeclareFields: true, + onlyRemoveTypeImports: true, + optimizeConstEnums: true, + }, + ] + ], + plugins: babelPlugins, + }, + }, + magicCommentsLoader, + ], }, { test: /\.scss$/i, @@ -133,8 +156,23 @@ module.exports = { }, devtool: devMode ? 'inline-source-map' : 'source-map', optimization: { + providedExports: !devMode, + sideEffects: devMode ? 'flag' : true, splitChunks: { chunks: 'all', + cacheGroups: { + defaultVendors: { + test: /[\\/]node_modules[\\/]/, + priority: -10, + reuseExistingChunk: true, + filename: devMode ? 'vendor.[id].js' : 'vendor.[contenthash].js', + }, + default: { + minChunks: 2, + priority: -20, + reuseExistingChunk: true, + }, + }, }, }, devServer: { @@ -157,8 +195,8 @@ module.exports = { }, plugins: [ new MiniCssExtractPlugin({ - filename: '[contenthash].css', - chunkFilename: '[contenthash].css', + filename: '[name].[contenthash].css', + chunkFilename: '[name].[contenthash].css', }), new SubresourceIntegrityPlugin(), new HtmlWebpackPlugin({ @@ -173,22 +211,17 @@ module.exports = { useShortDoctype: true, }, }), - new WebpackBeforeBuildPlugin((stats, callback) => { - // https://stackoverflow.com/a/40370750 - const newlyCreatedAssets = stats.compilation.assets; - const unlinked = []; - fs.readdir(outputPath, (err, files) => { - files.forEach(file => { - if (!newlyCreatedAssets[file]) { - fs.unlinkSync(path.resolve(outputPath, file)); - unlinked.push(file); - } - }); - if (unlinked.length > 0) { - console.log('Removed old assets: ', unlinked); - } - }); - callback(); - }, ['done']), + new HtmlWebpackInjectPreload({ + files: [ + { + match: /(roboto-latin-(400|500)-normal|jetbrains-mono-latin-variable).*\.woff2/, + attributes: { + as: 'font', + type: 'font/woff2', + crossorigin: 'anonymous', + }, + }, + ], + }), ], }; diff --git a/language-web/yarn.lock b/language-web/yarn.lock index 15cabc7c..3aac4633 100644 --- a/language-web/yarn.lock +++ b/language-web/yarn.lock @@ -742,6 +742,18 @@ dependencies: "@babel/helper-plugin-utils" "^7.14.5" +"@babel/plugin-transform-runtime@^7.15.0": + version "7.15.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.15.0.tgz#d3aa650d11678ca76ce294071fda53d7804183b3" + integrity sha512-sfHYkLGjhzWTq6xsuQ01oEsUYjkHRux9fW1iUA68dC7Qd8BS1Unq4aZ8itmQp95zUzIcyR2EbNMTzAicFj+guw== + dependencies: + "@babel/helper-module-imports" "^7.14.5" + "@babel/helper-plugin-utils" "^7.14.5" + babel-plugin-polyfill-corejs2 "^0.2.2" + babel-plugin-polyfill-corejs3 "^0.2.2" + babel-plugin-polyfill-regenerator "^0.2.2" + semver "^6.3.0" + "@babel/plugin-transform-shorthand-properties@^7.14.5": version "7.14.5" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.14.5.tgz#97f13855f1409338d8cadcbaca670ad79e091a58" @@ -921,7 +933,7 @@ core-js-pure "^3.16.0" regenerator-runtime "^0.13.4" -"@babel/runtime@^7.10.2", "@babel/runtime@^7.11.2", "@babel/runtime@^7.13.10", "@babel/runtime@^7.14.8", "@babel/runtime@^7.5.5", "@babel/runtime@^7.7.2", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7": +"@babel/runtime@^7.10.2", "@babel/runtime@^7.11.2", "@babel/runtime@^7.13.10", "@babel/runtime@^7.14.8", "@babel/runtime@^7.15.0", "@babel/runtime@^7.5.5", "@babel/runtime@^7.7.2", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7": version "7.15.4" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.15.4.tgz#fd17d16bfdf878e6dd02d19753a39fa8a8d9c84a" integrity sha512-99catp6bHCaxr4sJ/DbTGgHS4+Rs2RVd2g7iOap6SLGPDknRK9ztKNsE/Fg6QhSeh1FGE5f6gHGQmvvn3I3xhw== @@ -1227,6 +1239,11 @@ resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.10.1.tgz#728ecd95ab207aab8a9a4e421f0422db329232be" integrity sha512-HnUhk1Sy9IuKrxEMdIRCxpIqPw6BFsbYSEUO9p/hNw5sMld/+3OLMWQP80F8/db9qsv3qUjs7ZR5bS/R+iinXw== +"@principalstudio/html-webpack-inject-preload@^1.2.7": + version "1.2.7" + resolved "https://registry.yarnpkg.com/@principalstudio/html-webpack-inject-preload/-/html-webpack-inject-preload-1.2.7.tgz#0c1f0b32a34d814b36ce84111f89990441cc64e8" + integrity sha512-KJKkiKG63ugBjf8U0e9jUcI9CLPTFIsxXplEDE0oi3mPpxd90X9SJovo3W2l7yh/ARKIYXhQq8fSXUN7M29TzQ== + "@sindresorhus/is@^0.7.0": version "0.7.0" resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.7.0.tgz#9a06f4f137ee84d7df0460c1fdb1135ffa6c50fd" @@ -1927,11 +1944,6 @@ batch@0.6.1: resolved "https://registry.yarnpkg.com/batch/-/batch-0.6.1.tgz#dc34314f4e679318093fc760272525f94bf25c16" integrity sha1-3DQxT05nkxgJP8dgJyUl+UvyXBY= -before-build-webpack@^0.2.11: - version "0.2.11" - resolved "https://registry.yarnpkg.com/before-build-webpack/-/before-build-webpack-0.2.11.tgz#ce508c92e42dfb8d398bce2eba40d211b85439da" - integrity sha512-xigRuKoJmla3cO/BP76CDlmkXmQFrjlHv6oS16RxmbckYTfi5I3ZBp7MnoKv+C05DqDT1pSl+znLf7pv1Vv4ew== - big.js@^5.2.2: version "5.2.2" resolved "https://registry.yarnpkg.com/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328" @@ -5022,6 +5034,15 @@ lru-cache@^6.0.0: dependencies: yallist "^4.0.0" +magic-comments-loader@^1.4.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/magic-comments-loader/-/magic-comments-loader-1.4.1.tgz#d54c88f0b96418e19a7695c978960c3b8be5a142" + integrity sha512-5Kh0NkWO40o35sCNJ3NqlDBVop449giCaLJwBZmX32UJADKCcDBnI7MjmlBfjtbBhhnRlMFU5CebqWDfFYyg8Q== + dependencies: + loader-utils "^2.0.0" + micromatch "^4.0.4" + schema-utils "^3.1.1" + make-dir@^1.0.0, make-dir@^1.2.0: version "1.3.0" resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-1.3.0.tgz#79c1033b80515bd6d24ec9933e860ca75ee27f0c" -- cgit v1.2.3-70-g09d2