diff --git a/apps/docs/__tests__/ContentHighlighting_.test.res b/apps/docs/__tests__/ContentHighlighting_.test.res new file mode 100644 index 000000000..a86f1c881 --- /dev/null +++ b/apps/docs/__tests__/ContentHighlighting_.test.res @@ -0,0 +1,102 @@ +open Vitest + +type registeredLanguage +type highlightOptions = {language: string} +type highlightResult = {value: string} + +@module("highlight.js/lib/core") @scope("default") +external createHighlighter: unit => HighlightLanguages.highlighter = "newInstance" + +@send +external getLanguage: (HighlightLanguages.highlighter, string) => option = + "getLanguage" + +@send +external highlight: (HighlightLanguages.highlighter, string, highlightOptions) => highlightResult = + "highlight" + +let aliases = [ + "rescript", + "res", + "resi", + "javascript", + "js", + "jsx", + "mjs", + "cjs", + "css", + "ts", + "tsx", + "mts", + "cts", + "typescript", + "sh", + "bash", + "zsh", + "toml", + "json", + "jsonc", + "text", + "txt", + "html", + "xhtml", + "rss", + "atom", + "xjb", + "xsd", + "xsl", + "plist", + "wsf", + "svg", + "diff", + "patch", + "yaml", + "yml", +] + +test("content registration preserves every established language name and alias", async () => { + let highlighter = createHighlighter() + highlighter->ContentHighlighting.register + + let missingAliases = + aliases->Array.filter(alias => highlighter->getLanguage(alias)->Option.isNone) + expect(missingAliases)->toEqual([]) +}) + +test("content registration produces highlighted ReScript and escaped plain text", async () => { + let highlighter = createHighlighter() + highlighter->ContentHighlighting.register + + let rescript = highlighter->highlight("let count = 1", {language: "res"}) + let text = highlighter->highlight("", {language: "text"}) + + expect(rescript.value->String.includes("let"))->toBe(true) + expect(rescript.value->String.includes("1"))->toBe(true) + expect(text.value)->toBe("<button>Example & code</button>") +}) + +test("repeated content initialization leaves registered grammars unchanged", async () => { + let highlighter = createHighlighter() + highlighter->ContentHighlighting.register + let original = highlighter->getLanguage("javascript") + let before = highlighter->highlight("const count: number = 1;", {language: "ts"}) + + highlighter->ContentHighlighting.register + + let after = highlighter->highlight("const count: number = 1;", {language: "ts"}) + expect(highlighter->getLanguage("javascript"))->toBe(original) + expect(highlighter->getLanguage("js"))->toBe(original) + expect(after.value)->toBe(before.value) +}) + +test("content registration highlights YAML installation examples and their alias", async () => { + let highlighter = createHighlighter() + highlighter->ContentHighlighting.register + let code = "packages:\n - apps/*" + + let yaml = highlighter->highlight(code, {language: "yaml"}) + let alias = highlighter->highlight(code, {language: "yml"}) + + expect(yaml.value->String.includes("packages:"))->toBe(true) + expect(alias.value)->toBe(yaml.value) +}) diff --git a/apps/docs/__tests__/HighlightLanguages_.test.res b/apps/docs/__tests__/HighlightLanguages_.test.res new file mode 100644 index 000000000..6b1179bde --- /dev/null +++ b/apps/docs/__tests__/HighlightLanguages_.test.res @@ -0,0 +1,51 @@ +open Vitest + +type registeredLanguage +type highlightOptions = {language: string} +type highlightResult = {value: string} + +@module("highlight.js/lib/core") @scope("default") +external createHighlighter: unit => HighlightLanguages.highlighter = "newInstance" + +@module("highlight.js/lib/languages/javascript") +external javascript: HighlightLanguages.definition = "default" + +@module("highlight.js/lib/languages/plaintext") +external plaintext: HighlightLanguages.definition = "default" + +@send +external getLanguage: (HighlightLanguages.highlighter, string) => option = + "getLanguage" + +@send +external highlight: (HighlightLanguages.highlighter, string, highlightOptions) => highlightResult = + "highlight" + +test("registering a missing language makes its grammar and aliases available", async () => { + let highlighter = createHighlighter() + + expect(highlighter->getLanguage("javascript"))->toEqual(None) + expect(highlighter->getLanguage("js"))->toEqual(None) + highlighter->HighlightLanguages.ensureRegistered("javascript", javascript) + + let result = highlighter->highlight("const answer = 42;", {language: "js"}) + expect(result.value)->toBe( + "const answer = 42;", + ) +}) + +test("registering an existing name or alias does not replace its grammar", async () => { + let highlighter = createHighlighter() + highlighter->HighlightLanguages.ensureRegistered("javascript", javascript) + let original = highlighter->getLanguage("javascript") + + highlighter->HighlightLanguages.ensureRegistered("javascript", plaintext) + highlighter->HighlightLanguages.ensureRegistered("js", plaintext) + + expect(highlighter->getLanguage("javascript"))->toBe(original) + expect(highlighter->getLanguage("js"))->toBe(original) + let result = highlighter->highlight("const answer = 42;", {language: "js"}) + expect(result.value)->toBe( + "const answer = 42;", + ) +}) diff --git a/apps/docs/__tests__/JavaScriptHighlighting_.test.res b/apps/docs/__tests__/JavaScriptHighlighting_.test.res new file mode 100644 index 000000000..78e5b3454 --- /dev/null +++ b/apps/docs/__tests__/JavaScriptHighlighting_.test.res @@ -0,0 +1,35 @@ +open Vitest + +type registeredLanguage +type highlightOptions = {language: string} +type highlightResult = {value: string} + +@module("highlight.js/lib/core") @scope("default") +external createHighlighter: unit => HighlightLanguages.highlighter = "newInstance" + +@send +external listLanguages: HighlightLanguages.highlighter => array = "listLanguages" + +@send +external getLanguage: (HighlightLanguages.highlighter, string) => option = + "getLanguage" + +@send +external highlight: (HighlightLanguages.highlighter, string, highlightOptions) => highlightResult = + "highlight" + +test("playground registration loads only JavaScript and its working aliases", async () => { + let highlighter = createHighlighter() + highlighter->JavaScriptHighlighting.register + + let missingAliases = + ["javascript", "js", "jsx", "mjs", "cjs"]->Array.filter(alias => + highlighter->getLanguage(alias)->Option.isNone + ) + expect(highlighter->listLanguages)->toEqual(["javascript"]) + expect(missingAliases)->toEqual([]) + let result = highlighter->highlight("const answer = 42;", {language: "js"}) + expect(result.value)->toBe( + "const answer = 42;", + ) +}) diff --git a/apps/docs/app/DocsRoot.res b/apps/docs/app/DocsRoot.res index 5baf7427e..f45def69d 100644 --- a/apps/docs/app/DocsRoot.res +++ b/apps/docs/app/DocsRoot.res @@ -4,35 +4,6 @@ external mainCss: string = "default" @module("../styles/_hljs.css?url") external hljsCss: string = "default" -%%raw(` - import hljs from 'highlight.js/lib/core'; - import bash from 'highlight.js/lib/languages/bash'; - import css from 'highlight.js/lib/languages/css'; - import diff from 'highlight.js/lib/languages/diff'; - import javascript from 'highlight.js/lib/languages/javascript'; - import typescript from 'highlight.js/lib/languages/typescript'; - import json from 'highlight.js/lib/languages/json'; - import text from 'highlight.js/lib/languages/plaintext'; - import html from 'highlight.js/lib/languages/xml'; - import yaml from 'highlight.js/lib/languages/yaml'; - import toml from 'highlight.js/lib/languages/ini'; - import rescript from 'highlightjs-rescript'; - - hljs.registerLanguage('rescript', rescript) - hljs.registerLanguage('javascript', javascript) - hljs.registerLanguage('css', css) - hljs.registerLanguage('ts', typescript) - hljs.registerLanguage('sh', bash) - hljs.registerLanguage('bash', bash) - hljs.registerLanguage('toml', toml) - hljs.registerLanguage('json', json) - hljs.registerLanguage('text', text) - hljs.registerLanguage('html', html) - hljs.registerLanguage('diff', diff) - hljs.registerLanguage('typescript', typescript) - hljs.registerLanguage('yaml', yaml) -`) - open ReactRouter @react.component diff --git a/apps/docs/app/DocsRoot.resi b/apps/docs/app/DocsRoot.resi index 1ddeb3433..98c027984 100644 --- a/apps/docs/app/DocsRoot.resi +++ b/apps/docs/app/DocsRoot.resi @@ -1,4 +1,4 @@ -/** Shared shell styles and the stable Cypress bootstrap slot. -Feature-specific styles load with their components. */ +/** Shared shell, token styles, and the stable Cypress bootstrap slot. +Runtime grammars belong to content routes and the playground. */ @react.component let default: unit => Jsx.element diff --git a/apps/docs/app/DocsRoutes.res b/apps/docs/app/DocsRoutes.res index 2992c859f..326a19350 100644 --- a/apps/docs/app/DocsRoutes.res +++ b/apps/docs/app/DocsRoutes.res @@ -74,30 +74,39 @@ let syntaxLookupDetailRoutes = let default = [ index("./routes/LandingPageRoute.jsx"), - route("packages", "./routes/PackagesRoute.jsx"), route("try", "./routes/TryRoute.jsx"), - route("brand", "./routes/BrandRoute.jsx"), - route("blog", "./routes/BlogRoute.jsx", ~options={id: "blog-index"}), - route("blog/archived", "./routes/BlogRoute.jsx", ~options={id: "blog-archived"}), - ...blogArticleRoutes, - ...communityRoutes, - route("docs", "./routes/DocsOverview.jsx", ~options={id: "docs-overview"}), layout( - "./layouts/DocsLayoutRoute.jsx", + "./layouts/ContentLayoutRoute.jsx", [ - route("docs/manual/api", "./routes/ApiOverviewRoute.jsx", ~options={id: "api-overview"}), - route("docs/manual/api/stdlib", "./routes/ApiRoute.jsx", ~options={id: "api-stdlib"}), - route("docs/manual/api/introduction", "./routes/ApiRoute.jsx", ~options={id: "api-intro"}), - route("docs/manual/api/belt", "./routes/ApiRoute.jsx", ~options={id: "api-belt"}), - route("docs/manual/api/dom", "./routes/ApiRoute.jsx", ~options={id: "api-dom"}), - ...stdlibRoutes, - ...beltRoutes, - ...domRoutes, - ...docsManualRoutes, - ...docsGuidesRoutes, - ...docsReactRoutes, - route("syntax-lookup", "./routes/SyntaxLookupRoute.jsx", ~options={id: "syntax-lookup"}), - ...syntaxLookupDetailRoutes, + route("packages", "./routes/PackagesRoute.jsx"), + route("brand", "./routes/BrandRoute.jsx"), + route("blog", "./routes/BlogRoute.jsx", ~options={id: "blog-index"}), + route("blog/archived", "./routes/BlogRoute.jsx", ~options={id: "blog-archived"}), + ...blogArticleRoutes, + ...communityRoutes, + route("docs", "./routes/DocsOverview.jsx", ~options={id: "docs-overview"}), + layout( + "./layouts/DocsLayoutRoute.jsx", + [ + route("docs/manual/api", "./routes/ApiOverviewRoute.jsx", ~options={id: "api-overview"}), + route("docs/manual/api/stdlib", "./routes/ApiRoute.jsx", ~options={id: "api-stdlib"}), + route( + "docs/manual/api/introduction", + "./routes/ApiRoute.jsx", + ~options={id: "api-intro"}, + ), + route("docs/manual/api/belt", "./routes/ApiRoute.jsx", ~options={id: "api-belt"}), + route("docs/manual/api/dom", "./routes/ApiRoute.jsx", ~options={id: "api-dom"}), + ...stdlibRoutes, + ...beltRoutes, + ...domRoutes, + ...docsManualRoutes, + ...docsGuidesRoutes, + ...docsReactRoutes, + route("syntax-lookup", "./routes/SyntaxLookupRoute.jsx", ~options={id: "syntax-lookup"}), + ...syntaxLookupDetailRoutes, + ], + ), ], ), route("*", "./routes/NotFoundRoute.jsx"), diff --git a/apps/docs/app/DocsRoutes.resi b/apps/docs/app/DocsRoutes.resi index 51f6d36ee..c60e74bfd 100644 --- a/apps/docs/app/DocsRoutes.resi +++ b/apps/docs/app/DocsRoutes.resi @@ -10,4 +10,5 @@ let docsReactRoutes: array let docsGuidesRoutes: array let communityRoutes: array let syntaxLookupDetailRoutes: array +/** Keep home, the lazy playground, and not-found outside the content highlighting boundary. */ let default: array diff --git a/apps/docs/app/layouts/ContentLayoutRoute.res b/apps/docs/app/layouts/ContentLayoutRoute.res new file mode 100644 index 000000000..5d0015195 --- /dev/null +++ b/apps/docs/app/layouts/ContentLayoutRoute.res @@ -0,0 +1,5 @@ +// Route-module initialization runs before any content children render. +let () = ContentHighlighting.register(HighlightLanguages.defaultInstance) + +@react.component +let default = () => diff --git a/apps/docs/app/layouts/ContentLayoutRoute.resi b/apps/docs/app/layouts/ContentLayoutRoute.resi new file mode 100644 index 000000000..a5c43f841 --- /dev/null +++ b/apps/docs/app/layouts/ContentLayoutRoute.resi @@ -0,0 +1,3 @@ +/** Own runtime highlighting for content routes without adding a DOM wrapper. */ +@react.component +let default: unit => React.element diff --git a/apps/docs/e2e/Playground.cy.res b/apps/docs/e2e/Playground.cy.res index 0cc0d4a8d..1b77dc10e 100644 --- a/apps/docs/e2e/Playground.cy.res +++ b/apps/docs/e2e/Playground.cy.res @@ -63,6 +63,13 @@ describe("Playground", () => { waitForHydration() }) + it("should highlight JavaScript after a direct playground load", () => { + visit("/try") + get(".cm-editor")->shouldBeVisible->ignore + get("pre.whitespace-pre-wrap")->shouldContainText("react/jsx-runtime")->ignore + get("pre code.lang-js span[class^='hljs-']")->should("exist")->ignore + }) + it("should compile and run imported code in the playground", () => { // Navigate to the playground from the homepage clickNavLink(~testId="navbar-primary-left-content", ~text="Playground") diff --git a/apps/docs/e2e/bindings/Cypress.res b/apps/docs/e2e/bindings/Cypress.res index c104ed58c..ff6e045fb 100644 --- a/apps/docs/e2e/bindings/Cypress.res +++ b/apps/docs/e2e/bindings/Cypress.res @@ -80,6 +80,8 @@ external shouldCssProperty: (chain, @as("have.css") _, string) => chai @send external shouldInt: (chain<'a>, string, int) => chain<'a> = "should" @send external shouldValue: (chain, @as("have.value") _, string) => chain = "should" +@send +external shouldText: (chain, @as("have.text") _, string) => chain = "should" @send external shouldEqual: (chain<'a>, @as("equal") _, 'a) => chain<'a> = "should" @send external shouldDeepEqual: (chain<'a>, @as("deep.equal") _, 'a) => chain<'a> = "should" @send external shouldMatch: (chain<'a>, @as("match") _, RegExp.t) => chain<'a> = "should" @@ -95,6 +97,7 @@ external shouldProperty: (chain, @as("have.prop") _, string, string) = @send external propertyInt: (chain<'a>, string) => chain = "its" @send external shouldSatisfy: (chain<'a>, 'a => unit) => chain<'a> = "should" @send external click: chain => chain = "click" +@send external first: chain => chain = "first" @send external typeText: (chain, string) => chain = "type" @send external containsChildRegex: (chain, string, RegExp.t) => chain = "contains" diff --git a/apps/docs/e2e/homepage/HomepageHighlighting.cy.res b/apps/docs/e2e/homepage/HomepageHighlighting.cy.res index fa6a82b36..e16db0687 100644 --- a/apps/docs/e2e/homepage/HomepageHighlighting.cy.res +++ b/apps/docs/e2e/homepage/HomepageHighlighting.cy.res @@ -1,7 +1,7 @@ open Cypress open HomepageHelpers -it("initial homepage scripts do not include example preparation or compression", () => { +it("initial homepage scripts exclude example preparation and the syntax runtime", () => { homepageDocument(document => { let scripts = document->initialScriptUrls expect(scripts->Array.length)->greaterThan(0) @@ -12,6 +12,7 @@ it("initial homepage scripts do not include example preparation or compression", response => { expect(response.status, ~message=asset)->equal(200) expect(response.body, ~message=asset)->notInclude("compressToEncodedURIComponent") + expect(response.body, ~message=asset)->notInclude("registerLanguage") expect(response.body, ~message=asset)->notInclude("function Playground$Button(props)") }, ) @@ -20,6 +21,36 @@ it("initial homepage scripts do not include example preparation or compression", }) }) +it("content routes highlight JSON on direct loads and cold homepage navigation", () => { + let path = "/docs/manual/build-configuration" + request(`${path}/`) + ->then(response => { + expect(response.status)->equal(200) + let document = parser()->parseHtml(response.body) + let text = + document + ->querySelector("code.lang-json .hljs-attr") + ->Null.toOption + ->Option.flatMap(element => element->textContent->Null.toOption) + expect(text)->equal(Some(`"sources"`)) + }) + ->ignore + visit(`${path}/`) + get("code.lang-json .hljs-attr")->first->shouldText(`"sources"`)->ignore + visit("/") + cyLocation("pathname")->shouldEqual("/")->ignore + containsIn("h1", headline)->should("be.visible")->ignore + containsInRegex("a", /^Docs$/)->click->ignore + containsInRegex("h1", /^ReScript$/)->should("be.visible")->ignore + get("aside:visible") + ->containsChildRegex("a", /^Configuration$/) + ->scrollIntoView + ->click + ->ignore + cyLocation("pathname")->shouldEqual(path)->ignore + get("code.lang-json .hljs-attr")->first->shouldText(`"sources"`)->ignore +}) + it("prepared examples survive hydration and navigation back from documentation", () => { homepageDocument(document => { let examples = ["res", "js"]->Array.map( diff --git a/apps/docs/src/bindings/HighlightJsBindings.res b/apps/docs/src/bindings/HighlightJsBindings.res index cbfe747c0..f793d247f 100644 --- a/apps/docs/src/bindings/HighlightJsBindings.res +++ b/apps/docs/src/bindings/HighlightJsBindings.res @@ -1,19 +1,49 @@ -type t -type language type highlightOptions = {language: string} type highlightResult = {value: string} @module("highlight.js/lib/core") @scope("default") -external make: unit => t = "newInstance" +external make: unit => HighlightLanguages.highlighter = "newInstance" + +@module("highlight.js/lib/languages/bash") +external bash: HighlightLanguages.definition = "default" + +@module("highlight.js/lib/languages/css") +external css: HighlightLanguages.definition = "default" + +@module("highlight.js/lib/languages/diff") +external diff: HighlightLanguages.definition = "default" @module("highlight.js/lib/languages/javascript") -external javascript: language = "default" +external javascript: HighlightLanguages.definition = "default" + +@module("highlight.js/lib/languages/typescript") +external typescript: HighlightLanguages.definition = "default" + +@module("highlight.js/lib/languages/json") +external json: HighlightLanguages.definition = "default" + +@module("highlight.js/lib/languages/plaintext") +external text: HighlightLanguages.definition = "default" + +@module("highlight.js/lib/languages/xml") +external html: HighlightLanguages.definition = "default" + +@module("highlight.js/lib/languages/yaml") +external yaml: HighlightLanguages.definition = "default" + +@module("highlight.js/lib/languages/ini") +external toml: HighlightLanguages.definition = "default" @module("highlightjs-rescript") -external rescript: language = "default" +external rescript: HighlightLanguages.definition = "default" @send -external registerLanguage: (t, string, language) => unit = "registerLanguage" +external registerLanguage: ( + HighlightLanguages.highlighter, + string, + HighlightLanguages.definition, +) => unit = "registerLanguage" @send -external highlight: (t, string, highlightOptions) => highlightResult = "highlight" +external highlight: (HighlightLanguages.highlighter, string, highlightOptions) => highlightResult = + "highlight" diff --git a/apps/docs/src/common/ContentHighlighting.res b/apps/docs/src/common/ContentHighlighting.res new file mode 100644 index 000000000..faae2e2c3 --- /dev/null +++ b/apps/docs/src/common/ContentHighlighting.res @@ -0,0 +1,15 @@ +let register = highlighter => { + highlighter->HighlightLanguages.ensureRegistered("rescript", HighlightJsBindings.rescript) + highlighter->HighlightLanguages.ensureRegistered("javascript", HighlightJsBindings.javascript) + highlighter->HighlightLanguages.ensureRegistered("css", HighlightJsBindings.css) + highlighter->HighlightLanguages.ensureRegistered("ts", HighlightJsBindings.typescript) + highlighter->HighlightLanguages.ensureRegistered("sh", HighlightJsBindings.bash) + highlighter->HighlightLanguages.ensureRegistered("bash", HighlightJsBindings.bash) + highlighter->HighlightLanguages.ensureRegistered("toml", HighlightJsBindings.toml) + highlighter->HighlightLanguages.ensureRegistered("json", HighlightJsBindings.json) + highlighter->HighlightLanguages.ensureRegistered("text", HighlightJsBindings.text) + highlighter->HighlightLanguages.ensureRegistered("html", HighlightJsBindings.html) + highlighter->HighlightLanguages.ensureRegistered("diff", HighlightJsBindings.diff) + highlighter->HighlightLanguages.ensureRegistered("typescript", HighlightJsBindings.typescript) + highlighter->HighlightLanguages.ensureRegistered("yaml", HighlightJsBindings.yaml) +} diff --git a/apps/docs/src/common/ContentHighlighting.resi b/apps/docs/src/common/ContentHighlighting.resi new file mode 100644 index 000000000..db03a979d --- /dev/null +++ b/apps/docs/src/common/ContentHighlighting.resi @@ -0,0 +1,2 @@ +/** Register the content routes' grammars and their established aliases. */ +let register: HighlightLanguages.highlighter => unit diff --git a/apps/docs/vitest.config.mjs b/apps/docs/vitest.config.mjs index c39aeb9fa..0d24589ec 100644 --- a/apps/docs/vitest.config.mjs +++ b/apps/docs/vitest.config.mjs @@ -27,6 +27,7 @@ const setupDeps = [ "highlight.js/lib/languages/plaintext", "highlight.js/lib/languages/typescript", "highlight.js/lib/languages/xml", + "highlight.js/lib/languages/yaml", "highlightjs-rescript", ]; diff --git a/packages/playground/src/Playground.res b/packages/playground/src/Playground.res index 37f210e68..70038bc9e 100644 --- a/packages/playground/src/Playground.res +++ b/packages/playground/src/Playground.res @@ -1,6 +1,9 @@ open CompilerManagerHook module Api = RescriptCompilerApi +// The lazy playground must highlight direct visits without loading a content route first. +let () = JavaScriptHighlighting.register(HighlightLanguages.defaultInstance) + type layout = Column | Row type tab = JavaScript | Output | Problems | Settings diff --git a/packages/shared/src/HighlightLanguages.res b/packages/shared/src/HighlightLanguages.res new file mode 100644 index 000000000..60250770b --- /dev/null +++ b/packages/shared/src/HighlightLanguages.res @@ -0,0 +1,18 @@ +type highlighter +type definition +type registeredLanguage + +@module("highlight.js/lib/core") +external defaultInstance: highlighter = "default" + +@send +external getLanguage: (highlighter, string) => option = "getLanguage" + +@send +external registerLanguage: (highlighter, string, definition) => unit = "registerLanguage" + +let ensureRegistered = (highlighter, name, definition) => + switch highlighter->getLanguage(name) { + | Some(_) => () + | None => highlighter->registerLanguage(name, definition) + } diff --git a/packages/shared/src/HighlightLanguages.resi b/packages/shared/src/HighlightLanguages.resi new file mode 100644 index 000000000..13ebcccba --- /dev/null +++ b/packages/shared/src/HighlightLanguages.resi @@ -0,0 +1,7 @@ +type highlighter +type definition + +let defaultInstance: highlighter + +/** Preserve an existing language or alias when route modules initialize more than once. */ +let ensureRegistered: (highlighter, string, definition) => unit diff --git a/packages/shared/src/JavaScriptHighlighting.res b/packages/shared/src/JavaScriptHighlighting.res new file mode 100644 index 000000000..7bfa6de35 --- /dev/null +++ b/packages/shared/src/JavaScriptHighlighting.res @@ -0,0 +1,5 @@ +@module("highlight.js/lib/languages/javascript") +external javascript: HighlightLanguages.definition = "default" + +let register = highlighter => + highlighter->HighlightLanguages.ensureRegistered("javascript", javascript) diff --git a/packages/shared/src/JavaScriptHighlighting.resi b/packages/shared/src/JavaScriptHighlighting.resi new file mode 100644 index 000000000..b69289ac6 --- /dev/null +++ b/packages/shared/src/JavaScriptHighlighting.resi @@ -0,0 +1,2 @@ +/** Register only the playground's JavaScript grammar and its aliases. */ +let register: HighlightLanguages.highlighter => unit