diff options
author | Kristóf Marussy <marussy@mit.bme.hu> | 2021-10-02 02:11:31 +0200 |
---|---|---|
committer | Kristóf Marussy <marussy@mit.bme.hu> | 2021-10-02 02:11:31 +0200 |
commit | b834db0fd424e7ab02fcd5e509d855f2d97863bd (patch) | |
tree | b56ce9b8f752d8ca98e1d9082c63542e5dd993c1 | |
parent | feat: skeleton for language to store mapping (diff) | |
download | refinery-b834db0fd424e7ab02fcd5e509d855f2d97863bd.tar.gz refinery-b834db0fd424e7ab02fcd5e509d855f2d97863bd.tar.zst refinery-b834db0fd424e7ab02fcd5e509d855f2d97863bd.zip |
perf(web): split off CodeMirror chunks
Also optimizes statis asset caching.
-rw-r--r-- | language-web/package.json | 5 | ||||
-rw-r--r-- | language-web/src/main/css/index.scss | 29 | ||||
-rw-r--r-- | language-web/src/main/java/org/eclipse/viatra/solver/language/web/CacheControlFilter.java | 55 | ||||
-rw-r--r-- | language-web/src/main/java/org/eclipse/viatra/solver/language/web/ServerLauncher.java | 3 | ||||
-rw-r--r-- | language-web/src/main/js/App.tsx | 6 | ||||
-rw-r--r-- | language-web/src/main/js/editor/Editor.tsx | 20 | ||||
-rw-r--r-- | language-web/src/main/js/editor/EditorArea.tsx | 42 | ||||
-rw-r--r-- | language-web/src/main/js/editor/EditorStore.ts | 75 | ||||
-rw-r--r-- | language-web/src/main/js/editor/editor.ts | 18 | ||||
-rw-r--r-- | language-web/src/main/js/theme/ThemeStore.ts | 4 | ||||
-rw-r--r-- | language-web/webpack.config.js | 135 | ||||
-rw-r--r-- | language-web/yarn.lock | 33 |
12 files changed, 328 insertions, 97 deletions
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 @@ | |||
26 | "@babel/preset-env": "^7.15.0", | 26 | "@babel/preset-env": "^7.15.0", |
27 | "@babel/preset-react": "^7.14.5", | 27 | "@babel/preset-react": "^7.14.5", |
28 | "@babel/preset-typescript": "^7.15.0", | 28 | "@babel/preset-typescript": "^7.15.0", |
29 | "@babel/plugin-transform-runtime": "^7.15.0", | ||
29 | "babel-loader": "^8.2.2", | 30 | "babel-loader": "^8.2.2", |
30 | "before-build-webpack": "^0.2.11", | ||
31 | "css-loader": "^6.2.0", | 31 | "css-loader": "^6.2.0", |
32 | "eslint": "^7.32.0", | 32 | "eslint": "^7.32.0", |
33 | "eslint-config-airbnb": "^18.2.1", | 33 | "eslint-config-airbnb": "^18.2.1", |
@@ -38,7 +38,9 @@ | |||
38 | "eslint-plugin-jsx-a11y": "^6.4.1", | 38 | "eslint-plugin-jsx-a11y": "^6.4.1", |
39 | "html-webpack-plugin": "^5.3.2", | 39 | "html-webpack-plugin": "^5.3.2", |
40 | "image-webpack-loader": "^7.0.1", | 40 | "image-webpack-loader": "^7.0.1", |
41 | "magic-comments-loader": "^1.4.1", | ||
41 | "mini-css-extract-plugin": "^2.2.0", | 42 | "mini-css-extract-plugin": "^2.2.0", |
43 | "@principalstudio/html-webpack-inject-preload": "^1.2.7", | ||
42 | "sass": "^1.38.0", | 44 | "sass": "^1.38.0", |
43 | "sass-loader": "^12.1.0", | 45 | "sass-loader": "^12.1.0", |
44 | "style-loader": "^3.2.1", | 46 | "style-loader": "^3.2.1", |
@@ -56,6 +58,7 @@ | |||
56 | "webpack-subresource-integrity": "^5.0.0-rc.1" | 58 | "webpack-subresource-integrity": "^5.0.0-rc.1" |
57 | }, | 59 | }, |
58 | "dependencies": { | 60 | "dependencies": { |
61 | "@babel/runtime": "^7.15.0", | ||
59 | "@emotion/react": "^11.4.1", | 62 | "@emotion/react": "^11.4.1", |
60 | "@emotion/styled": "^11.3.0", | 63 | "@emotion/styled": "^11.3.0", |
61 | "@fontsource/jetbrains-mono": "^4.5.0", | 64 | "@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 { | |||
30 | height: 100%; | 30 | height: 100%; |
31 | } | 31 | } |
32 | 32 | ||
33 | .CodeMirror, .CodeMirror-hints { | 33 | .problem-fallback-editor { |
34 | display: block; | ||
35 | height: 100%; | ||
36 | width: 100%; | ||
37 | resize: none; | ||
38 | border: none; | ||
39 | outline: none; | ||
40 | padding: 4px 4px 4px 16px; | ||
41 | white-space: pre; | ||
42 | overflow-wrap: normal; | ||
43 | overflow: auto; | ||
44 | } | ||
45 | |||
46 | .CodeMirror, .CodeMirror-hints, .problem-fallback-editor { | ||
34 | font-size: 16px; | 47 | font-size: 16px; |
35 | font-family: 'JetBrains MonoVariable', 'JetBrains Mono', monospace; | 48 | font-family: 'JetBrains MonoVariable', 'JetBrains Mono', monospace; |
36 | font-feature-settings: 'liga', 'calt'; | 49 | font-feature-settings: 'liga', 'calt'; |
37 | font-weight: 400; | 50 | font-weight: 400; |
38 | text-rendering: optimizeLegibility; | 51 | text-rendering: optimizeLegibility; |
52 | line-height: 1.35; | ||
53 | letter-spacing: 0; | ||
39 | } | 54 | } |
40 | 55 | ||
41 | @each $themeName, $theme in $themes { | 56 | @each $themeName, $theme in $themes { |
@@ -45,6 +60,16 @@ body { | |||
45 | color: map.get($theme, 'foreground'); | 60 | color: map.get($theme, 'foreground'); |
46 | } | 61 | } |
47 | 62 | ||
63 | &.problem-fallback-editor { | ||
64 | background: map.get($theme, 'background'); | ||
65 | color: map.get($theme, 'foreground'); | ||
66 | caret-color: map.get($theme, 'cursor'); | ||
67 | |||
68 | &::selection { | ||
69 | background: map.get($theme, 'selection'); | ||
70 | } | ||
71 | } | ||
72 | |||
48 | .CodeMirror-gutters { | 73 | .CodeMirror-gutters { |
49 | background: map.get($theme, 'background'); | 74 | background: map.get($theme, 'background'); |
50 | border: none; | 75 | border: none; |
@@ -183,11 +208,13 @@ li.CodeMirror-hint-active { | |||
183 | 208 | ||
184 | .xtext-marker_read { | 209 | .xtext-marker_read { |
185 | background: rgba(128, 203, 196, 0.2); | 210 | background: rgba(128, 203, 196, 0.2); |
211 | display: inline-block; | ||
186 | } | 212 | } |
187 | 213 | ||
188 | 214 | ||
189 | .xtext-marker_write { | 215 | .xtext-marker_write { |
190 | background: rgba(255, 229, 100, 0.2); | 216 | background: rgba(255, 229, 100, 0.2); |
217 | display: inline-block; | ||
191 | } | 218 | } |
192 | 219 | ||
193 | .problem-abstract { | 220 | .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 @@ | |||
1 | package org.eclipse.viatra.solver.language.web; | ||
2 | |||
3 | import java.io.IOException; | ||
4 | import java.util.regex.Pattern; | ||
5 | |||
6 | import javax.servlet.Filter; | ||
7 | import javax.servlet.FilterChain; | ||
8 | import javax.servlet.FilterConfig; | ||
9 | import javax.servlet.ServletException; | ||
10 | import javax.servlet.ServletRequest; | ||
11 | import javax.servlet.ServletResponse; | ||
12 | import javax.servlet.http.HttpServletRequest; | ||
13 | import javax.servlet.http.HttpServletResponse; | ||
14 | |||
15 | public class CacheControlFilter implements Filter { | ||
16 | |||
17 | private static final String CACHE_CONTROL_HEADER = "Cache-Control"; | ||
18 | |||
19 | private static final String EXPIRES_HEADER = "Expires"; | ||
20 | |||
21 | private static final Pattern CACHE_URI_PATTERN = Pattern.compile(".*\\.(css|gif|js|map|png|svg|woff2)"); | ||
22 | |||
23 | private static final long EXPIRY = 31536000; | ||
24 | |||
25 | private static final String CACHE_CONTROL_CACHE_VALUE = "public, max-age: " + EXPIRY + ", immutable"; | ||
26 | |||
27 | private static final String CACHE_CONTROL_NO_CACHE_VALUE = "no-cache, no-store, max-age: 0, must-revalidate"; | ||
28 | |||
29 | @Override | ||
30 | public void init(FilterConfig filterConfig) throws ServletException { | ||
31 | // Nothing to initialize. | ||
32 | } | ||
33 | |||
34 | @Override | ||
35 | public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) | ||
36 | throws IOException, ServletException { | ||
37 | if (request instanceof HttpServletRequest && response instanceof HttpServletResponse) { | ||
38 | var httpRequest = (HttpServletRequest) request; | ||
39 | var httpResponse = (HttpServletResponse) response; | ||
40 | if (CACHE_URI_PATTERN.matcher(httpRequest.getRequestURI()).matches()) { | ||
41 | httpResponse.setHeader(CACHE_CONTROL_HEADER, CACHE_CONTROL_CACHE_VALUE); | ||
42 | httpResponse.setDateHeader(EXPIRES_HEADER, System.currentTimeMillis() + EXPIRY * 1000L); | ||
43 | } else { | ||
44 | httpResponse.setHeader(CACHE_CONTROL_HEADER, CACHE_CONTROL_NO_CACHE_VALUE); | ||
45 | httpResponse.setDateHeader(EXPIRES_HEADER, 0); | ||
46 | } | ||
47 | } | ||
48 | chain.doFilter(request, response); | ||
49 | } | ||
50 | |||
51 | @Override | ||
52 | public void destroy() { | ||
53 | // Nothing to dispose. | ||
54 | } | ||
55 | } | ||
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; | |||
8 | import java.net.InetSocketAddress; | 8 | import java.net.InetSocketAddress; |
9 | import java.net.URI; | 9 | import java.net.URI; |
10 | import java.net.URISyntaxException; | 10 | import java.net.URISyntaxException; |
11 | import java.util.EnumSet; | ||
11 | import java.util.Set; | 12 | import java.util.Set; |
12 | 13 | ||
14 | import javax.servlet.DispatcherType; | ||
13 | import javax.servlet.SessionTrackingMode; | 15 | import javax.servlet.SessionTrackingMode; |
14 | 16 | ||
15 | import org.eclipse.jetty.server.Server; | 17 | import org.eclipse.jetty.server.Server; |
@@ -42,6 +44,7 @@ public class ServerLauncher { | |||
42 | handler.setWelcomeFiles(new String[] { "index.html" }); | 44 | handler.setWelcomeFiles(new String[] { "index.html" }); |
43 | addDefaultServlet(handler); | 45 | addDefaultServlet(handler); |
44 | } | 46 | } |
47 | handler.addFilter(CacheControlFilter.class, "/*", EnumSet.of(DispatcherType.REQUEST)); | ||
45 | server.setHandler(handler); | 48 | server.setHandler(handler); |
46 | } | 49 | } |
47 | 50 | ||
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'; | |||
9 | import PlayArrowIcon from '@material-ui/icons/PlayArrow'; | 9 | import PlayArrowIcon from '@material-ui/icons/PlayArrow'; |
10 | 10 | ||
11 | import { makeStyles } from './makeStyles'; | 11 | import { makeStyles } from './makeStyles'; |
12 | import { Editor } from './editor/Editor'; | 12 | import { EditorArea } from './editor/EditorArea'; |
13 | import { EditorButtons } from './editor/EditorButtons'; | 13 | import { EditorButtons } from './editor/EditorButtons'; |
14 | 14 | ||
15 | const useStyles = makeStyles()((theme) => ({ | 15 | const useStyles = makeStyles()((theme) => ({ |
16 | container: { | 16 | container: { |
17 | maxHeight: '100vh', | 17 | height: '100vh', |
18 | }, | 18 | }, |
19 | menuButton: { | 19 | menuButton: { |
20 | marginRight: theme.spacing(2), | 20 | marginRight: theme.spacing(2), |
@@ -85,7 +85,7 @@ export const App = (): JSX.Element => { | |||
85 | flexShrink={1} | 85 | flexShrink={1} |
86 | className={cx(classes.editorBox)} | 86 | className={cx(classes.editorBox)} |
87 | > | 87 | > |
88 | <Editor /> | 88 | <EditorArea /> |
89 | </Box> | 89 | </Box> |
90 | </Box> | 90 | </Box> |
91 | ); | 91 | ); |
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 @@ | |||
1 | import { observer } from 'mobx-react-lite'; | ||
2 | import React from 'react'; | ||
3 | import { Controlled as CodeMirror } from 'react-codemirror2'; | ||
4 | |||
5 | import { useRootStore } from '../RootStore'; | ||
6 | |||
7 | export const Editor = observer(() => { | ||
8 | const { editorStore } = useRootStore(); | ||
9 | |||
10 | return ( | ||
11 | <CodeMirror | ||
12 | value={editorStore.value} | ||
13 | options={editorStore.codeMirrorOptions} | ||
14 | editorDidMount={(editor) => editorStore.editorDidMount(editor)} | ||
15 | editorWillUnmount={() => editorStore.editorWillUnmount()} | ||
16 | onBeforeChange={(_editor, _data, value) => editorStore.updateValue(value)} | ||
17 | onChange={() => editorStore.reportChanged()} | ||
18 | /> | ||
19 | ); | ||
20 | }); | ||
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 @@ | |||
1 | import { observer } from 'mobx-react-lite'; | ||
2 | import React, { useRef } from 'react'; | ||
3 | |||
4 | import { useRootStore } from '../RootStore'; | ||
5 | |||
6 | export const EditorArea = observer(() => { | ||
7 | const { editorStore } = useRootStore(); | ||
8 | const { CodeMirror } = editorStore.chunk || {}; | ||
9 | const fallbackTextarea = useRef<HTMLTextAreaElement>(null); | ||
10 | |||
11 | if (!CodeMirror) { | ||
12 | return ( | ||
13 | <textarea | ||
14 | value={editorStore.value} | ||
15 | onChange={(e) => editorStore.updateValue(e.target.value)} | ||
16 | ref={fallbackTextarea} | ||
17 | className={`problem-fallback-editor cm-s-${editorStore.codeMirrorTheme}`} | ||
18 | > | ||
19 | </textarea> | ||
20 | ); | ||
21 | } | ||
22 | |||
23 | const textarea = fallbackTextarea.current; | ||
24 | if (textarea) { | ||
25 | editorStore.setInitialSelection( | ||
26 | textarea.selectionStart, | ||
27 | textarea.selectionEnd, | ||
28 | document.activeElement === textarea, | ||
29 | ); | ||
30 | } | ||
31 | |||
32 | return ( | ||
33 | <CodeMirror | ||
34 | value={editorStore.value} | ||
35 | options={editorStore.codeMirrorOptions} | ||
36 | editorDidMount={(editor) => editorStore.editorDidMount(editor)} | ||
37 | editorWillUnmount={() => editorStore.editorWillUnmount()} | ||
38 | onBeforeChange={(_editor, _data, value) => editorStore.updateValue(value)} | ||
39 | onChange={() => editorStore.reportChanged()} | ||
40 | /> | ||
41 | ); | ||
42 | }); | ||
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 @@ | |||
1 | import { Editor, EditorConfiguration } from 'codemirror'; | 1 | import type { Editor, EditorConfiguration } from 'codemirror'; |
2 | import 'codemirror/addon/selection/active-line'; | ||
3 | import { | 2 | import { |
4 | createAtom, | 3 | createAtom, |
5 | makeAutoObservable, | 4 | makeAutoObservable, |
6 | observable, | 5 | observable, |
6 | runInAction, | ||
7 | } from 'mobx'; | 7 | } from 'mobx'; |
8 | import 'mode-problem'; | 8 | import type { IXtextOptions, IXtextServices } from 'xtext/xtext-codemirror'; |
9 | import { | ||
10 | IXtextOptions, | ||
11 | IXtextServices, | ||
12 | createServices, | ||
13 | removeServices, | ||
14 | } from 'xtext/xtext-codemirror'; | ||
15 | 9 | ||
16 | import { ThemeStore } from '../theme/ThemeStore'; | 10 | import type { IEditorChunk } from './editor'; |
11 | import type { ThemeStore } from '../theme/ThemeStore'; | ||
17 | 12 | ||
18 | const xtextLang = 'problem'; | 13 | const xtextLang = 'problem'; |
19 | 14 | ||
@@ -33,6 +28,8 @@ export class EditorStore { | |||
33 | 28 | ||
34 | atom; | 29 | atom; |
35 | 30 | ||
31 | chunk?: IEditorChunk; | ||
32 | |||
36 | editor?: Editor; | 33 | editor?: Editor; |
37 | 34 | ||
38 | xtextServices?: IXtextServices; | 35 | xtextServices?: IXtextServices; |
@@ -41,15 +38,56 @@ export class EditorStore { | |||
41 | 38 | ||
42 | showLineNumbers = false; | 39 | showLineNumbers = false; |
43 | 40 | ||
41 | initialSelection!: { start: number, end: number, focused: boolean }; | ||
42 | |||
44 | constructor(themeStore: ThemeStore) { | 43 | constructor(themeStore: ThemeStore) { |
45 | this.themeStore = themeStore; | 44 | this.themeStore = themeStore; |
46 | this.atom = createAtom('EditorStore'); | 45 | this.atom = createAtom('EditorStore'); |
46 | this.resetInitialSelection(); | ||
47 | makeAutoObservable(this, { | 47 | makeAutoObservable(this, { |
48 | themeStore: false, | 48 | themeStore: false, |
49 | atom: false, | 49 | atom: false, |
50 | chunk: observable.ref, | ||
50 | editor: observable.ref, | 51 | editor: observable.ref, |
51 | xtextServices: observable.ref, | 52 | xtextServices: observable.ref, |
53 | initialSelection: false, | ||
52 | }); | 54 | }); |
55 | import('./editor').then(({ editorChunk }) => { | ||
56 | runInAction(() => { | ||
57 | this.chunk = editorChunk; | ||
58 | }); | ||
59 | }).catch((error) => { | ||
60 | console.warn('Error while loading editor', error); | ||
61 | }); | ||
62 | } | ||
63 | |||
64 | setInitialSelection(start: number, end: number, focused: boolean): void { | ||
65 | this.initialSelection = { start, end, focused }; | ||
66 | this.applyInitialSelectionToEditor(); | ||
67 | } | ||
68 | |||
69 | private resetInitialSelection(): void { | ||
70 | this.initialSelection = { | ||
71 | start: 0, | ||
72 | end: 0, | ||
73 | focused: false, | ||
74 | }; | ||
75 | } | ||
76 | |||
77 | private applyInitialSelectionToEditor(): void { | ||
78 | if (this.editor) { | ||
79 | const { start, end, focused } = this.initialSelection; | ||
80 | const doc = this.editor.getDoc(); | ||
81 | const startPos = doc.posFromIndex(start); | ||
82 | const endPos = doc.posFromIndex(end); | ||
83 | doc.setSelection(startPos, endPos, { | ||
84 | scroll: true, | ||
85 | }); | ||
86 | if (focused) { | ||
87 | this.editor.focus(); | ||
88 | } | ||
89 | this.resetInitialSelection(); | ||
90 | } | ||
53 | } | 91 | } |
54 | 92 | ||
55 | /** | 93 | /** |
@@ -61,16 +99,23 @@ export class EditorStore { | |||
61 | * @param newEditor The new CodeMirror instance | 99 | * @param newEditor The new CodeMirror instance |
62 | */ | 100 | */ |
63 | editorDidMount(newEditor: Editor): void { | 101 | editorDidMount(newEditor: Editor): void { |
102 | if (!this.chunk) { | ||
103 | throw new Error('Editor not loaded yet'); | ||
104 | } | ||
64 | if (this.editor) { | 105 | if (this.editor) { |
65 | throw new Error('CoreMirror editor mounted before unmounting'); | 106 | throw new Error('CoreMirror editor mounted before unmounting'); |
66 | } | 107 | } |
67 | this.editor = newEditor; | 108 | this.editor = newEditor; |
68 | this.xtextServices = createServices(newEditor, xtextOptions); | 109 | this.xtextServices = this.chunk.createServices(newEditor, xtextOptions); |
110 | this.applyInitialSelectionToEditor(); | ||
69 | } | 111 | } |
70 | 112 | ||
71 | editorWillUnmount(): void { | 113 | editorWillUnmount(): void { |
114 | if (!this.chunk) { | ||
115 | throw new Error('Editor not loaded yet'); | ||
116 | } | ||
72 | if (this.editor) { | 117 | if (this.editor) { |
73 | removeServices(this.editor); | 118 | this.chunk.removeServices(this.editor); |
74 | } | 119 | } |
75 | delete this.editor; | 120 | delete this.editor; |
76 | delete this.xtextServices; | 121 | delete this.xtextServices; |
@@ -93,10 +138,14 @@ export class EditorStore { | |||
93 | this.atom.reportObserved(); | 138 | this.atom.reportObserved(); |
94 | } | 139 | } |
95 | 140 | ||
141 | get codeMirrorTheme(): string { | ||
142 | return `problem-${this.themeStore.className}`; | ||
143 | } | ||
144 | |||
96 | get codeMirrorOptions(): EditorConfiguration { | 145 | get codeMirrorOptions(): EditorConfiguration { |
97 | return { | 146 | return { |
98 | ...codeMirrorGlobalOptions, | 147 | ...codeMirrorGlobalOptions, |
99 | theme: this.themeStore.codeMirrorTheme, | 148 | theme: this.codeMirrorTheme, |
100 | lineNumbers: this.showLineNumbers, | 149 | lineNumbers: this.showLineNumbers, |
101 | }; | 150 | }; |
102 | } | 151 | } |
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 @@ | |||
1 | import 'codemirror/addon/selection/active-line'; | ||
2 | import 'mode-problem'; | ||
3 | import { Controlled } from 'react-codemirror2'; | ||
4 | import { createServices, removeServices } from 'xtext/xtext-codemirror'; | ||
5 | |||
6 | export interface IEditorChunk { | ||
7 | CodeMirror: typeof Controlled; | ||
8 | |||
9 | createServices: typeof createServices; | ||
10 | |||
11 | removeServices: typeof removeServices; | ||
12 | } | ||
13 | |||
14 | export const editorChunk: IEditorChunk = { | ||
15 | CodeMirror: Controlled, | ||
16 | createServices, | ||
17 | removeServices, | ||
18 | }; | ||
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 { | |||
51 | return responsiveFontSizes(materialUiTheme); | 51 | return responsiveFontSizes(materialUiTheme); |
52 | } | 52 | } |
53 | 53 | ||
54 | get codeMirrorTheme(): string { | 54 | get className(): string { |
55 | return `problem-${this.currentThemeData.className}`; | 55 | return this.currentThemeData.className; |
56 | } | 56 | } |
57 | } | 57 | } |
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 @@ | |||
1 | const fs = require('fs'); | 1 | const fs = require('fs'); |
2 | const path = require('path'); | 2 | const path = require('path'); |
3 | 3 | ||
4 | const WebpackBeforeBuildPlugin = require('before-build-webpack'); | ||
5 | const HtmlWebpackPlugin = require('html-webpack-plugin'); | 4 | const HtmlWebpackPlugin = require('html-webpack-plugin'); |
5 | const HtmlWebpackInjectPreload = require('@principalstudio/html-webpack-inject-preload'); | ||
6 | const MiniCssExtractPlugin = require('mini-css-extract-plugin'); | 6 | const MiniCssExtractPlugin = require('mini-css-extract-plugin'); |
7 | const { SubresourceIntegrityPlugin } = require('webpack-subresource-integrity'); | 7 | const { SubresourceIntegrityPlugin } = require('webpack-subresource-integrity'); |
8 | 8 | ||
@@ -37,6 +37,15 @@ const babelPresets = [ | |||
37 | ], | 37 | ], |
38 | '@babel/preset-react', | 38 | '@babel/preset-react', |
39 | ]; | 39 | ]; |
40 | const babelPlugins = [ | ||
41 | '@babel/plugin-transform-runtime', | ||
42 | ] | ||
43 | const magicCommentsLoader = { | ||
44 | loader: 'magic-comments-loader', | ||
45 | options: { | ||
46 | webpackChunkName: true, | ||
47 | } | ||
48 | }; | ||
40 | 49 | ||
41 | module.exports = { | 50 | module.exports = { |
42 | mode: devMode ? 'development' : 'production', | 51 | mode: devMode ? 'development' : 'production', |
@@ -44,8 +53,10 @@ module.exports = { | |||
44 | output: { | 53 | output: { |
45 | path: outputPath, | 54 | path: outputPath, |
46 | publicPath: '/', | 55 | publicPath: '/', |
47 | filename: devMode ? '[name].js' : '[contenthash].js', | 56 | filename: devMode ? '[name].js' : '[name].[contenthash].js', |
48 | chunkFilename: devMode ? '[id].js' : '[contenthash].js', | 57 | chunkFilename: devMode ? '[name].js' : '[name].[contenthash].js', |
58 | assetModuleFilename: devMode ? '[name].js' : '[name].[contenthash][ext]', | ||
59 | clean: true, | ||
49 | crossOriginLoading: 'anonymous', | 60 | crossOriginLoading: 'anonymous', |
50 | }, | 61 | }, |
51 | module: { | 62 | module: { |
@@ -53,41 +64,53 @@ module.exports = { | |||
53 | { | 64 | { |
54 | test: /\.jsx?$/i, | 65 | test: /\.jsx?$/i, |
55 | ...babelLoaderFilters, | 66 | ...babelLoaderFilters, |
56 | loader: 'babel-loader', | 67 | use: [ |
57 | options: { | 68 | { |
58 | presets: babelPresets, | 69 | loader: 'babel-loader', |
59 | plugins: [ | 70 | options: { |
60 | [ | 71 | presets: babelPresets, |
61 | '@babel/plugin-proposal-class-properties', | 72 | plugins: [ |
62 | { | 73 | [ |
63 | loose: false, | 74 | '@babel/plugin-proposal-class-properties', |
75 | { | ||
76 | loose: false, | ||
77 | }, | ||
78 | ...babelPlugins, | ||
79 | ], | ||
80 | ], | ||
81 | assumptions: { | ||
82 | 'setPublicClassFields': false, | ||
64 | }, | 83 | }, |
65 | ], | 84 | }, |
66 | ], | ||
67 | assumptions: { | ||
68 | 'setPublicClassFields': false, | ||
69 | }, | 85 | }, |
70 | }, | 86 | magicCommentsLoader, |
87 | ], | ||
71 | }, | 88 | }, |
72 | { | 89 | { |
73 | test: /.tsx?$/i, | 90 | test: /.tsx?$/i, |
74 | ...babelLoaderFilters, | 91 | ...babelLoaderFilters, |
75 | loader: 'babel-loader', | 92 | use: [ |
76 | options: { | 93 | { |
77 | presets: [ | 94 | loader: 'babel-loader', |
78 | ...babelPresets, | 95 | options: { |
79 | [ | 96 | presets: [ |
80 | '@babel/preset-typescript', | 97 | ...babelPresets, |
81 | { | 98 | [ |
82 | isTSX: true, | 99 | '@babel/preset-typescript', |
83 | allExtensions: true, | 100 | { |
84 | allowDeclareFields: true, | 101 | isTSX: true, |
85 | onlyRemoveTypeImports: true, | 102 | allExtensions: true, |
86 | optimizeConstEnums: true, | 103 | allowDeclareFields: true, |
87 | }, | 104 | onlyRemoveTypeImports: true, |
88 | ] | 105 | optimizeConstEnums: true, |
89 | ], | 106 | }, |
90 | }, | 107 | ] |
108 | ], | ||
109 | plugins: babelPlugins, | ||
110 | }, | ||
111 | }, | ||
112 | magicCommentsLoader, | ||
113 | ], | ||
91 | }, | 114 | }, |
92 | { | 115 | { |
93 | test: /\.scss$/i, | 116 | test: /\.scss$/i, |
@@ -133,8 +156,23 @@ module.exports = { | |||
133 | }, | 156 | }, |
134 | devtool: devMode ? 'inline-source-map' : 'source-map', | 157 | devtool: devMode ? 'inline-source-map' : 'source-map', |
135 | optimization: { | 158 | optimization: { |
159 | providedExports: !devMode, | ||
160 | sideEffects: devMode ? 'flag' : true, | ||
136 | splitChunks: { | 161 | splitChunks: { |
137 | chunks: 'all', | 162 | chunks: 'all', |
163 | cacheGroups: { | ||
164 | defaultVendors: { | ||
165 | test: /[\\/]node_modules[\\/]/, | ||
166 | priority: -10, | ||
167 | reuseExistingChunk: true, | ||
168 | filename: devMode ? 'vendor.[id].js' : 'vendor.[contenthash].js', | ||
169 | }, | ||
170 | default: { | ||
171 | minChunks: 2, | ||
172 | priority: -20, | ||
173 | reuseExistingChunk: true, | ||
174 | }, | ||
175 | }, | ||
138 | }, | 176 | }, |
139 | }, | 177 | }, |
140 | devServer: { | 178 | devServer: { |
@@ -157,8 +195,8 @@ module.exports = { | |||
157 | }, | 195 | }, |
158 | plugins: [ | 196 | plugins: [ |
159 | new MiniCssExtractPlugin({ | 197 | new MiniCssExtractPlugin({ |
160 | filename: '[contenthash].css', | 198 | filename: '[name].[contenthash].css', |
161 | chunkFilename: '[contenthash].css', | 199 | chunkFilename: '[name].[contenthash].css', |
162 | }), | 200 | }), |
163 | new SubresourceIntegrityPlugin(), | 201 | new SubresourceIntegrityPlugin(), |
164 | new HtmlWebpackPlugin({ | 202 | new HtmlWebpackPlugin({ |
@@ -173,22 +211,17 @@ module.exports = { | |||
173 | useShortDoctype: true, | 211 | useShortDoctype: true, |
174 | }, | 212 | }, |
175 | }), | 213 | }), |
176 | new WebpackBeforeBuildPlugin((stats, callback) => { | 214 | new HtmlWebpackInjectPreload({ |
177 | // https://stackoverflow.com/a/40370750 | 215 | files: [ |
178 | const newlyCreatedAssets = stats.compilation.assets; | 216 | { |
179 | const unlinked = []; | 217 | match: /(roboto-latin-(400|500)-normal|jetbrains-mono-latin-variable).*\.woff2/, |
180 | fs.readdir(outputPath, (err, files) => { | 218 | attributes: { |
181 | files.forEach(file => { | 219 | as: 'font', |
182 | if (!newlyCreatedAssets[file]) { | 220 | type: 'font/woff2', |
183 | fs.unlinkSync(path.resolve(outputPath, file)); | 221 | crossorigin: 'anonymous', |
184 | unlinked.push(file); | 222 | }, |
185 | } | 223 | }, |
186 | }); | 224 | ], |
187 | if (unlinked.length > 0) { | 225 | }), |
188 | console.log('Removed old assets: ', unlinked); | ||
189 | } | ||
190 | }); | ||
191 | callback(); | ||
192 | }, ['done']), | ||
193 | ], | 226 | ], |
194 | }; | 227 | }; |
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 @@ | |||
742 | dependencies: | 742 | dependencies: |
743 | "@babel/helper-plugin-utils" "^7.14.5" | 743 | "@babel/helper-plugin-utils" "^7.14.5" |
744 | 744 | ||
745 | "@babel/plugin-transform-runtime@^7.15.0": | ||
746 | version "7.15.0" | ||
747 | resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.15.0.tgz#d3aa650d11678ca76ce294071fda53d7804183b3" | ||
748 | integrity sha512-sfHYkLGjhzWTq6xsuQ01oEsUYjkHRux9fW1iUA68dC7Qd8BS1Unq4aZ8itmQp95zUzIcyR2EbNMTzAicFj+guw== | ||
749 | dependencies: | ||
750 | "@babel/helper-module-imports" "^7.14.5" | ||
751 | "@babel/helper-plugin-utils" "^7.14.5" | ||
752 | babel-plugin-polyfill-corejs2 "^0.2.2" | ||
753 | babel-plugin-polyfill-corejs3 "^0.2.2" | ||
754 | babel-plugin-polyfill-regenerator "^0.2.2" | ||
755 | semver "^6.3.0" | ||
756 | |||
745 | "@babel/plugin-transform-shorthand-properties@^7.14.5": | 757 | "@babel/plugin-transform-shorthand-properties@^7.14.5": |
746 | version "7.14.5" | 758 | version "7.14.5" |
747 | resolved "https://registry.yarnpkg.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.14.5.tgz#97f13855f1409338d8cadcbaca670ad79e091a58" | 759 | resolved "https://registry.yarnpkg.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.14.5.tgz#97f13855f1409338d8cadcbaca670ad79e091a58" |
@@ -921,7 +933,7 @@ | |||
921 | core-js-pure "^3.16.0" | 933 | core-js-pure "^3.16.0" |
922 | regenerator-runtime "^0.13.4" | 934 | regenerator-runtime "^0.13.4" |
923 | 935 | ||
924 | "@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": | 936 | "@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": |
925 | version "7.15.4" | 937 | version "7.15.4" |
926 | resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.15.4.tgz#fd17d16bfdf878e6dd02d19753a39fa8a8d9c84a" | 938 | resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.15.4.tgz#fd17d16bfdf878e6dd02d19753a39fa8a8d9c84a" |
927 | integrity sha512-99catp6bHCaxr4sJ/DbTGgHS4+Rs2RVd2g7iOap6SLGPDknRK9ztKNsE/Fg6QhSeh1FGE5f6gHGQmvvn3I3xhw== | 939 | integrity sha512-99catp6bHCaxr4sJ/DbTGgHS4+Rs2RVd2g7iOap6SLGPDknRK9ztKNsE/Fg6QhSeh1FGE5f6gHGQmvvn3I3xhw== |
@@ -1227,6 +1239,11 @@ | |||
1227 | resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.10.1.tgz#728ecd95ab207aab8a9a4e421f0422db329232be" | 1239 | resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.10.1.tgz#728ecd95ab207aab8a9a4e421f0422db329232be" |
1228 | integrity sha512-HnUhk1Sy9IuKrxEMdIRCxpIqPw6BFsbYSEUO9p/hNw5sMld/+3OLMWQP80F8/db9qsv3qUjs7ZR5bS/R+iinXw== | 1240 | integrity sha512-HnUhk1Sy9IuKrxEMdIRCxpIqPw6BFsbYSEUO9p/hNw5sMld/+3OLMWQP80F8/db9qsv3qUjs7ZR5bS/R+iinXw== |
1229 | 1241 | ||
1242 | "@principalstudio/html-webpack-inject-preload@^1.2.7": | ||
1243 | version "1.2.7" | ||
1244 | resolved "https://registry.yarnpkg.com/@principalstudio/html-webpack-inject-preload/-/html-webpack-inject-preload-1.2.7.tgz#0c1f0b32a34d814b36ce84111f89990441cc64e8" | ||
1245 | integrity sha512-KJKkiKG63ugBjf8U0e9jUcI9CLPTFIsxXplEDE0oi3mPpxd90X9SJovo3W2l7yh/ARKIYXhQq8fSXUN7M29TzQ== | ||
1246 | |||
1230 | "@sindresorhus/is@^0.7.0": | 1247 | "@sindresorhus/is@^0.7.0": |
1231 | version "0.7.0" | 1248 | version "0.7.0" |
1232 | resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.7.0.tgz#9a06f4f137ee84d7df0460c1fdb1135ffa6c50fd" | 1249 | resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.7.0.tgz#9a06f4f137ee84d7df0460c1fdb1135ffa6c50fd" |
@@ -1927,11 +1944,6 @@ batch@0.6.1: | |||
1927 | resolved "https://registry.yarnpkg.com/batch/-/batch-0.6.1.tgz#dc34314f4e679318093fc760272525f94bf25c16" | 1944 | resolved "https://registry.yarnpkg.com/batch/-/batch-0.6.1.tgz#dc34314f4e679318093fc760272525f94bf25c16" |
1928 | integrity sha1-3DQxT05nkxgJP8dgJyUl+UvyXBY= | 1945 | integrity sha1-3DQxT05nkxgJP8dgJyUl+UvyXBY= |
1929 | 1946 | ||
1930 | before-build-webpack@^0.2.11: | ||
1931 | version "0.2.11" | ||
1932 | resolved "https://registry.yarnpkg.com/before-build-webpack/-/before-build-webpack-0.2.11.tgz#ce508c92e42dfb8d398bce2eba40d211b85439da" | ||
1933 | integrity sha512-xigRuKoJmla3cO/BP76CDlmkXmQFrjlHv6oS16RxmbckYTfi5I3ZBp7MnoKv+C05DqDT1pSl+znLf7pv1Vv4ew== | ||
1934 | |||
1935 | big.js@^5.2.2: | 1947 | big.js@^5.2.2: |
1936 | version "5.2.2" | 1948 | version "5.2.2" |
1937 | resolved "https://registry.yarnpkg.com/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328" | 1949 | resolved "https://registry.yarnpkg.com/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328" |
@@ -5022,6 +5034,15 @@ lru-cache@^6.0.0: | |||
5022 | dependencies: | 5034 | dependencies: |
5023 | yallist "^4.0.0" | 5035 | yallist "^4.0.0" |
5024 | 5036 | ||
5037 | magic-comments-loader@^1.4.1: | ||
5038 | version "1.4.1" | ||
5039 | resolved "https://registry.yarnpkg.com/magic-comments-loader/-/magic-comments-loader-1.4.1.tgz#d54c88f0b96418e19a7695c978960c3b8be5a142" | ||
5040 | integrity sha512-5Kh0NkWO40o35sCNJ3NqlDBVop449giCaLJwBZmX32UJADKCcDBnI7MjmlBfjtbBhhnRlMFU5CebqWDfFYyg8Q== | ||
5041 | dependencies: | ||
5042 | loader-utils "^2.0.0" | ||
5043 | micromatch "^4.0.4" | ||
5044 | schema-utils "^3.1.1" | ||
5045 | |||
5025 | make-dir@^1.0.0, make-dir@^1.2.0: | 5046 | make-dir@^1.0.0, make-dir@^1.2.0: |
5026 | version "1.3.0" | 5047 | version "1.3.0" |
5027 | resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-1.3.0.tgz#79c1033b80515bd6d24ec9933e860ca75ee27f0c" | 5048 | resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-1.3.0.tgz#79c1033b80515bd6d24ec9933e860ca75ee27f0c" |