From 9874a53206ffbaa6ac108bf6dc63fbe85cc370b2 Mon Sep 17 00:00:00 2001
From: Dan Brown <ssddanbrown@googlemail.com>
Date: Fri, 14 Apr 2023 18:08:57 +0100
Subject: [PATCH] Added cm6 strategy for splitting and dyn. loading langs

Split out legacy modes to their own dynamically imported bundle to
reduce main code bundle size.
---
 dev/build/esbuild.js               |   1 +
 package-lock.json                  |  33 ++++-
 package.json                       |   3 +
 resources/js/code/index.mjs        |   1 +
 resources/js/code/languages.js     | 186 +++++++++++++----------------
 resources/js/code/legacy-modes.mjs |  28 +++++
 resources/js/code/setups.js        |   2 -
 resources/js/code/views.js         |   4 +-
 8 files changed, 144 insertions(+), 114 deletions(-)
 create mode 100644 resources/js/code/legacy-modes.mjs

diff --git a/dev/build/esbuild.js b/dev/build/esbuild.js
index 2ff7ac1f4..c1e246955 100644
--- a/dev/build/esbuild.js
+++ b/dev/build/esbuild.js
@@ -12,6 +12,7 @@ const isProd = process.argv[2] === 'production';
 const entryPoints = {
     app: path.join(__dirname, '../../resources/js/app.js'),
     code: path.join(__dirname, '../../resources/js/code/index.mjs'),
+    'legacy-modes': path.join(__dirname, '../../resources/js/code/legacy-modes.mjs'),
 };
 
 // Locate our output directory
diff --git a/package-lock.json b/package-lock.json
index 241e5392f..1c75d341b 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -6,6 +6,9 @@
     "": {
       "dependencies": {
         "@codemirror/commands": "^6.0.1",
+        "@codemirror/lang-css": "^6.1.1",
+        "@codemirror/lang-javascript": "^6.1.6",
+        "@codemirror/lang-json": "^6.0.1",
         "@codemirror/lang-markdown": "^6.0.1",
         "@codemirror/lang-php": "^6.0.0",
         "@codemirror/language": "^6.2.1",
@@ -61,9 +64,9 @@
       }
     },
     "node_modules/@codemirror/lang-css": {
-      "version": "6.0.2",
-      "resolved": "https://registry.npmjs.org/@codemirror/lang-css/-/lang-css-6.0.2.tgz",
-      "integrity": "sha512-4V4zmUOl2Glx0GWw0HiO1oGD4zvMlIQ3zx5hXOE6ipCjhohig2bhWRAasrZylH9pRNTcl1VMa59Lsl8lZWlTzw==",
+      "version": "6.1.1",
+      "resolved": "https://registry.npmjs.org/@codemirror/lang-css/-/lang-css-6.1.1.tgz",
+      "integrity": "sha512-P6jdNEHyRcqqDgbvHYyC9Wxkek0rnG3a9aVSRi4a7WrjPbQtBTaOmvYpXmm13zZMAatO4Oqpac+0QZs7sy+LnQ==",
       "dependencies": {
         "@codemirror/autocomplete": "^6.0.0",
         "@codemirror/language": "^6.0.0",
@@ -88,9 +91,9 @@
       }
     },
     "node_modules/@codemirror/lang-javascript": {
-      "version": "6.1.4",
-      "resolved": "https://registry.npmjs.org/@codemirror/lang-javascript/-/lang-javascript-6.1.4.tgz",
-      "integrity": "sha512-OxLf7OfOZBTMRMi6BO/F72MNGmgOd9B0vetOLvHsDACFXayBzW8fm8aWnDM0yuy68wTK03MBf4HbjSBNRG5q7A==",
+      "version": "6.1.6",
+      "resolved": "https://registry.npmjs.org/@codemirror/lang-javascript/-/lang-javascript-6.1.6.tgz",
+      "integrity": "sha512-TTK28z+vJQY9GAefLTDDptI2LMqMfAiuTpt8s9SsNwocjVQ1v9yTzfReMf1hYhspQCdhfa7fdKnQJ78mKe/bHQ==",
       "dependencies": {
         "@codemirror/autocomplete": "^6.0.0",
         "@codemirror/language": "^6.6.0",
@@ -101,6 +104,15 @@
         "@lezer/javascript": "^1.0.0"
       }
     },
+    "node_modules/@codemirror/lang-json": {
+      "version": "6.0.1",
+      "resolved": "https://registry.npmjs.org/@codemirror/lang-json/-/lang-json-6.0.1.tgz",
+      "integrity": "sha512-+T1flHdgpqDDlJZ2Lkil/rLiRy684WMLc74xUnjJH48GQdfJo/pudlTRreZmKwzP8/tGdKf83wlbAdOCzlJOGQ==",
+      "dependencies": {
+        "@codemirror/language": "^6.0.0",
+        "@lezer/json": "^1.0.0"
+      }
+    },
     "node_modules/@codemirror/lang-markdown": {
       "version": "6.1.0",
       "resolved": "https://registry.npmjs.org/@codemirror/lang-markdown/-/lang-markdown-6.1.0.tgz",
@@ -599,6 +611,15 @@
         "@lezer/lr": "^1.3.0"
       }
     },
+    "node_modules/@lezer/json": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/@lezer/json/-/json-1.0.0.tgz",
+      "integrity": "sha512-zbAuUY09RBzCoCA3lJ1+ypKw5WSNvLqGMtasdW6HvVOqZoCpPr8eWrsGnOVWGKGn8Rh21FnrKRVlJXrGAVUqRw==",
+      "dependencies": {
+        "@lezer/highlight": "^1.0.0",
+        "@lezer/lr": "^1.0.0"
+      }
+    },
     "node_modules/@lezer/lr": {
       "version": "1.3.3",
       "resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.3.3.tgz",
diff --git a/package.json b/package.json
index 3579b2d25..349f1ae8d 100644
--- a/package.json
+++ b/package.json
@@ -25,6 +25,9 @@
   },
   "dependencies": {
     "@codemirror/commands": "^6.0.1",
+    "@codemirror/lang-css": "^6.1.1",
+    "@codemirror/lang-javascript": "^6.1.6",
+    "@codemirror/lang-json": "^6.0.1",
     "@codemirror/lang-markdown": "^6.0.1",
     "@codemirror/lang-php": "^6.0.0",
     "@codemirror/language": "^6.2.1",
diff --git a/resources/js/code/index.mjs b/resources/js/code/index.mjs
index dbed1c1e6..1a3429d58 100644
--- a/resources/js/code/index.mjs
+++ b/resources/js/code/index.mjs
@@ -200,6 +200,7 @@ export function markdownEditor(elem, onChange, domEventHandlers, keyBindings) {
 
     // Create editor view, hide original input
     const ev = createView(config);
+    setMode(ev, 'markdown', '');
     elem.style.display = 'none';
 
     return ev;
diff --git a/resources/js/code/languages.js b/resources/js/code/languages.js
index 4a3591624..21ce69e6d 100644
--- a/resources/js/code/languages.js
+++ b/resources/js/code/languages.js
@@ -1,119 +1,97 @@
 import {StreamLanguage} from "@codemirror/language"
 
-import {css} from '@codemirror/legacy-modes/mode/css';
-import {c, cpp, csharp, java, kotlin, scala, dart} from '@codemirror/legacy-modes/mode/clike';
-import {diff} from '@codemirror/legacy-modes/mode/diff';
-import {fortran} from '@codemirror/legacy-modes/mode/fortran';
-import {go} from '@codemirror/legacy-modes/mode/go';
-import {haskell} from '@codemirror/legacy-modes/mode/haskell';
-import {javascript, json, typescript} from '@codemirror/legacy-modes/mode/javascript';
-import {julia} from '@codemirror/legacy-modes/mode/julia';
-import {lua} from '@codemirror/legacy-modes/mode/lua';
+import {css} from '@codemirror/lang-css';
+import {json} from '@codemirror/lang-json';
+import {javascript} from '@codemirror/lang-javascript';
 import {markdown} from '@codemirror/lang-markdown';
-import {oCaml, fSharp, sml} from '@codemirror/legacy-modes/mode/mllike';
-import {nginx} from '@codemirror/legacy-modes/mode/nginx';
-import {octave} from '@codemirror/legacy-modes/mode/octave';
-import {perl} from '@codemirror/legacy-modes/mode/perl';
-import {pascal} from '@codemirror/legacy-modes/mode/pascal';
 import {php} from '@codemirror/lang-php';
-import {powerShell} from '@codemirror/legacy-modes/mode/powershell';
-import {properties} from '@codemirror/legacy-modes/mode/properties';
-import {python} from '@codemirror/legacy-modes/mode/python';
-import {ruby} from '@codemirror/legacy-modes/mode/ruby';
-import {rust} from '@codemirror/legacy-modes/mode/rust';
-import {scheme} from '@codemirror/legacy-modes/mode/scheme';
-import {shell} from '@codemirror/legacy-modes/mode/shell';
-import {smarty} from "@ssddanbrown/codemirror-lang-smarty";
-import {standardSQL, pgSQL, msSQL, mySQL, sqlite, plSQL} from '@codemirror/legacy-modes/mode/sql';
-import {stex} from '@codemirror/legacy-modes/mode/stex';
-import {swift} from "@codemirror/legacy-modes/mode/swift";
-import {toml} from '@codemirror/legacy-modes/mode/toml';
-import {twig} from "@ssddanbrown/codemirror-lang-twig";
-import {vb} from '@codemirror/legacy-modes/mode/vb';
-import {vbScript} from '@codemirror/legacy-modes/mode/vbscript';
-import {xml, html} from '@codemirror/legacy-modes/mode/xml';
-import {yaml} from '@codemirror/legacy-modes/mode/yaml';
+export {twig} from "@ssddanbrown/codemirror-lang-twig";
+
+const legacyLoad = async (mode) => {
+    const modes = await window.importVersioned('legacy-modes');
+    return StreamLanguage.define(modes[mode]);
+};
 
 
 // Mapping of possible languages or formats from user input to their codemirror modes.
 // Value can be a mode string or a function that will receive the code content & return the mode string.
 // The function option is used in the event the exact mode could be dynamic depending on the code.
 const modeMap = {
-    bash: () => StreamLanguage.define(shell),
-    c: () => StreamLanguage.define(c),
-    css: () => StreamLanguage.define(css),
-    'c++': () => StreamLanguage.define(cpp),
-    'c#': () => StreamLanguage.define(csharp),
-    csharp: () => StreamLanguage.define(csharp),
-    dart: () => StreamLanguage.define(dart),
-    diff: () => StreamLanguage.define(diff),
-    for: () => StreamLanguage.define(fortran),
-    fortran: () => StreamLanguage.define(fortran),
-    'f#': () => StreamLanguage.define(fSharp),
-    fsharp: () => StreamLanguage.define(fSharp),
-    go: () => StreamLanguage.define(go),
-    haskell: () => StreamLanguage.define(haskell),
-    hs: () => StreamLanguage.define(haskell),
-    html: () => StreamLanguage.define(html),
-    ini: () => StreamLanguage.define(properties),
-    java: () => StreamLanguage.define(java),
-    javascript: () => StreamLanguage.define(javascript),
-    json: () => StreamLanguage.define(json),
-    js: () => StreamLanguage.define(javascript),
-    jl: () => StreamLanguage.define(julia),
-    julia: () => StreamLanguage.define(julia),
-    kotlin: () => StreamLanguage.define(kotlin),
-    latex: () => StreamLanguage.define(stex),
-    lua: () => StreamLanguage.define(lua),
-    markdown: () => markdown(),
-    matlab: () => StreamLanguage.define(octave),
-    md: () => markdown(),
-    mdown: () => markdown(),
-    ml: () => StreamLanguage.define(sml),
-    mssql: () => StreamLanguage.define(msSQL),
-    mysql: () => StreamLanguage.define(mySQL),
-    nginx: () => StreamLanguage.define(nginx),
-    octave: () => StreamLanguage.define(octave),
-    pas: () => StreamLanguage.define(pascal),
-    pascal: () => StreamLanguage.define(pascal),
-    perl: () => StreamLanguage.define(perl),
-    pgsql: () => StreamLanguage.define(pgSQL),
-    php: (code) => {
+    bash: () => legacyLoad('shell'),
+    c: () => legacyLoad('c'),
+    css: async () => css(),
+    'c++': () => legacyLoad('cpp'),
+    'c#': () => legacyLoad('csharp'),
+    csharp: () => legacyLoad('csharp'),
+    dart: () => legacyLoad('dart'),
+    diff: () => legacyLoad('diff'),
+    for: () => legacyLoad('fortran'),
+    fortran: () => legacyLoad('fortran'),
+    'f#': () => legacyLoad('fSharp'),
+    fsharp: () => legacyLoad('fSharp'),
+    go: () => legacyLoad('go'),
+    haskell: () => legacyLoad('haskell'),
+    hs: () => legacyLoad('haskell'),
+    html: () => legacyLoad('html'),
+    ini: () => legacyLoad('properties'),
+    java: () => legacyLoad('java'),
+    javascript: async () => javascript(),
+    json: async () => json(),
+    js: async () => javascript(),
+    jl: () => legacyLoad('julia'),
+    julia: () => legacyLoad('julia'),
+    kotlin: () => legacyLoad('kotlin'),
+    latex: () => legacyLoad('stex'),
+    lua: () => legacyLoad('lua'),
+    markdown: async () => markdown(),
+    matlab: () => legacyLoad('octave'),
+    md: async () => markdown(),
+    mdown: async () => markdown(),
+    ml: () => legacyLoad('sml'),
+    mssql: () => legacyLoad('msSQL'),
+    mysql: () => legacyLoad('mySQL'),
+    nginx: () => legacyLoad('nginx'),
+    octave: () => legacyLoad('octave'),
+    pas: () => legacyLoad('pascal'),
+    pascal: () => legacyLoad('pascal'),
+    perl: () => legacyLoad('perl'),
+    pgsql: () => legacyLoad('pgSQL'),
+    php: async (code) => {
         const hasTags = code.includes('<?php');
         return php({plain: !hasTags});
     },
-    pl: () => StreamLanguage.define(perl),
-    'pl/sql': () => StreamLanguage.define(plSQL),
-    postgresql: () => StreamLanguage.define(pgSQL),
-    powershell: () => StreamLanguage.define(powerShell),
-    properties: () => StreamLanguage.define(properties),
-    ocaml: () => StreamLanguage.define(oCaml),
-    py: () => StreamLanguage.define(python),
-    python: () => StreamLanguage.define(python),
-    rb: () => StreamLanguage.define(ruby),
-    rs: () => StreamLanguage.define(rust),
-    ruby: () => StreamLanguage.define(ruby),
-    rust: () => StreamLanguage.define(rust),
-    scala: () => StreamLanguage.define(scala),
-    scheme: () => StreamLanguage.define(scheme),
-    shell: () => StreamLanguage.define(shell),
-    sh: () => StreamLanguage.define(shell),
-    smarty: () => StreamLanguage.define(smarty),
-    stext: () => StreamLanguage.define(stex),
-    swift: () => StreamLanguage.define(swift),
-    toml: () => StreamLanguage.define(toml),
-    ts: () => StreamLanguage.define(typescript),
-    twig: () => twig(),
-    typescript: () => StreamLanguage.define(typescript),
-    sql: () => StreamLanguage.define(standardSQL),
-    sqlite: () => StreamLanguage.define(sqlite),
-    vbs: () => StreamLanguage.define(vbScript),
-    vbscript: () => StreamLanguage.define(vbScript),
-    'vb.net': () => StreamLanguage.define(vb),
-    vbnet: () => StreamLanguage.define(vb),
-    xml: () => StreamLanguage.define(xml),
-    yaml: () => StreamLanguage.define(yaml),
-    yml: () => StreamLanguage.define(yaml),
+    pl: () => legacyLoad('perl'),
+    'pl/sql': () => legacyLoad('plSQL'),
+    postgresql: () => legacyLoad('pgSQL'),
+    powershell: () => legacyLoad('powerShell'),
+    properties: () => legacyLoad('properties'),
+    ocaml: () => legacyLoad('oCaml'),
+    py: () => legacyLoad('python'),
+    python: () => legacyLoad('python'),
+    rb: () => legacyLoad('ruby'),
+    rs: () => legacyLoad('rust'),
+    ruby: () => legacyLoad('ruby'),
+    rust: () => legacyLoad('rust'),
+    scala: () => legacyLoad('scala'),
+    scheme: () => legacyLoad('scheme'),
+    shell: () => legacyLoad('shell'),
+    sh: () => legacyLoad('shell'),
+    smarty: () => legacyLoad('smarty'),
+    stext: () => legacyLoad('stex'),
+    swift: () => legacyLoad('swift'),
+    toml: () => legacyLoad('toml'),
+    ts: async () => javascript({typescript: true}),
+    twig: async () => twig(),
+    typescript: async () => javascript({typescript: true}),
+    sql: () => legacyLoad('standardSQL'),
+    sqlite: () => legacyLoad('sqlite'),
+    vbs: () => legacyLoad('vbScript'),
+    vbscript: () => legacyLoad('vbScript'),
+    'vb.net': () => legacyLoad('vb'),
+    vbnet: () => legacyLoad('vb'),
+    xml: () => legacyLoad('xml'),
+    yaml: () => legacyLoad('yaml'),
+    yml: () => legacyLoad('yaml'),
 };
 
 /**
@@ -121,7 +99,7 @@ const modeMap = {
  * suggestion and content.
  * @param {String} langSuggestion
  * @param {String} content
- * @returns {StreamLanguage}
+ * @returns {Promise<StreamLanguage|LanguageSupport>}
  */
 export function getLanguageExtension(langSuggestion, content) {
     const suggestion = langSuggestion.trim().replace(/^\./g, '').toLowerCase();
diff --git a/resources/js/code/legacy-modes.mjs b/resources/js/code/legacy-modes.mjs
new file mode 100644
index 000000000..857ea6787
--- /dev/null
+++ b/resources/js/code/legacy-modes.mjs
@@ -0,0 +1,28 @@
+export {c, cpp, csharp, java, kotlin, scala, dart} from '@codemirror/legacy-modes/mode/clike';
+export {diff} from '@codemirror/legacy-modes/mode/diff';
+export {fortran} from '@codemirror/legacy-modes/mode/fortran';
+export {go} from '@codemirror/legacy-modes/mode/go';
+export {haskell} from '@codemirror/legacy-modes/mode/haskell';
+export {julia} from '@codemirror/legacy-modes/mode/julia';
+export {lua} from '@codemirror/legacy-modes/mode/lua';
+export {oCaml, fSharp, sml} from '@codemirror/legacy-modes/mode/mllike';
+export {nginx} from '@codemirror/legacy-modes/mode/nginx';
+export {octave} from '@codemirror/legacy-modes/mode/octave';
+export {perl} from '@codemirror/legacy-modes/mode/perl';
+export {pascal} from '@codemirror/legacy-modes/mode/pascal';
+export {powerShell} from '@codemirror/legacy-modes/mode/powershell';
+export {properties} from '@codemirror/legacy-modes/mode/properties';
+export {python} from '@codemirror/legacy-modes/mode/python';
+export {ruby} from '@codemirror/legacy-modes/mode/ruby';
+export {rust} from '@codemirror/legacy-modes/mode/rust';
+export {scheme} from '@codemirror/legacy-modes/mode/scheme';
+export {shell} from '@codemirror/legacy-modes/mode/shell';
+export {standardSQL, pgSQL, msSQL, mySQL, sqlite, plSQL} from '@codemirror/legacy-modes/mode/sql';
+export {stex} from '@codemirror/legacy-modes/mode/stex';
+export {swift} from "@codemirror/legacy-modes/mode/swift";
+export {toml} from '@codemirror/legacy-modes/mode/toml';
+export {vb} from '@codemirror/legacy-modes/mode/vb';
+export {vbScript} from '@codemirror/legacy-modes/mode/vbscript';
+export {xml, html} from '@codemirror/legacy-modes/mode/xml';
+export {yaml} from '@codemirror/legacy-modes/mode/yaml';
+export {smarty} from "@ssddanbrown/codemirror-lang-smarty";
\ No newline at end of file
diff --git a/resources/js/code/setups.js b/resources/js/code/setups.js
index b061bb3fe..cd56261d3 100644
--- a/resources/js/code/setups.js
+++ b/resources/js/code/setups.js
@@ -6,7 +6,6 @@ import {defaultKeymap, history, historyKeymap} from "@codemirror/commands"
 import {EditorState} from "@codemirror/state"
 
 import {defaultLight} from "./themes";
-import {getLanguageExtension} from "./languages";
 
 export function viewer() {
     return [
@@ -44,7 +43,6 @@ export function editor(language) {
             ...defaultKeymap,
             ...historyKeymap,
         ]),
-        getLanguageExtension(language, ''),
         EditorView.lineWrapping,
     ];
 }
\ No newline at end of file
diff --git a/resources/js/code/views.js b/resources/js/code/views.js
index 8202551b3..54f30fe6c 100644
--- a/resources/js/code/views.js
+++ b/resources/js/code/views.js
@@ -47,9 +47,9 @@ function getTheme(viewParentEl) {
  * @param {string} modeSuggestion
  * @param {string} content
  */
-export function updateViewLanguage(ev, modeSuggestion, content) {
+export async function updateViewLanguage(ev, modeSuggestion, content) {
     const compartment = viewLangCompartments.get(ev);
-    const language = getLanguageExtension(modeSuggestion, content);
+    const language = await getLanguageExtension(modeSuggestion, content);
 
     ev.dispatch({
         effects: compartment.reconfigure(language ? language : [])