From 821586527f864933e1b4febe1b595f96a829b62d Mon Sep 17 00:00:00 2001 From: Antoine du Hamel Date: Sun, 31 Jan 2021 23:10:11 +0100 Subject: [PATCH] tools,doc: add support for several flavors of JS code snippets MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Enable code example using both modern ESM syntax and legacy CJS syntax. It adds a toggle on the web interface to let users switch from one JavaScript flavor to the other. PR-URL: https://github.com/nodejs/node/pull/37162 Reviewed-By: James M Snell Reviewed-By: Rich Trott Reviewed-By: Michaƫl Zasso --- doc/api/wasi.md | 21 ++++++++ doc/api_assets/js-flavor-cjs.svg | 5 ++ doc/api_assets/js-flavor-esm.svg | 5 ++ doc/api_assets/style.css | 47 ++++++++++++++++++ test/doctool/test-doctool-html.js | 10 +++- .../document_with_cjs_and_esm_code_snippet.md | 11 +++++ .../document_with_esm_and_cjs_code_snippet.md | 11 +++++ tools/doc/html.js | 49 +++++++++++++++---- 8 files changed, 149 insertions(+), 10 deletions(-) create mode 100644 doc/api_assets/js-flavor-cjs.svg create mode 100644 doc/api_assets/js-flavor-esm.svg create mode 100644 test/fixtures/document_with_cjs_and_esm_code_snippet.md create mode 100644 test/fixtures/document_with_esm_and_cjs_code_snippet.md diff --git a/doc/api/wasi.md b/doc/api/wasi.md index e72eac2091003b..6fcb80f6670567 100644 --- a/doc/api/wasi.md +++ b/doc/api/wasi.md @@ -29,6 +29,27 @@ const instance = await WebAssembly.instantiate(wasm, importObject); wasi.start(instance); ``` +```cjs +'use strict'; +const fs = require('fs'); +const { WASI } = require('wasi'); +const wasi = new WASI({ + args: process.argv, + env: process.env, + preopens: { + '/sandbox': '/some/real/path/that/wasm/can/access' + } +}); +const importObject = { wasi_snapshot_preview1: wasi.wasiImport }; + +(async () => { + const wasm = await WebAssembly.compile(fs.readFileSync('./demo.wasm')); + const instance = await WebAssembly.instantiate(wasm, importObject); + + wasi.start(instance); +})(); +``` + To run the above example, create a new WebAssembly text format file named `demo.wat`: diff --git a/doc/api_assets/js-flavor-cjs.svg b/doc/api_assets/js-flavor-cjs.svg new file mode 100644 index 00000000000000..800c1516423059 --- /dev/null +++ b/doc/api_assets/js-flavor-cjs.svg @@ -0,0 +1,5 @@ + +CJSESM diff --git a/doc/api_assets/js-flavor-esm.svg b/doc/api_assets/js-flavor-esm.svg new file mode 100644 index 00000000000000..bcea3ba100698a --- /dev/null +++ b/doc/api_assets/js-flavor-esm.svg @@ -0,0 +1,5 @@ + +CJSESM diff --git a/doc/api_assets/style.css b/doc/api_assets/style.css index 0488cb42a77fb6..95f5877f0b5894 100644 --- a/doc/api_assets/style.css +++ b/doc/api_assets/style.css @@ -782,6 +782,33 @@ kbd { display: block; } +.js-flavor-selector { + appearance: none; + float: right; + background-image: url(./js-flavor-cjs.svg); + background-size: contain; + background-repeat: no-repeat; + width: 142px; + height: 20px; +} +.js-flavor-selector:checked { + background-image: url(./js-flavor-esm.svg); +} +.js-flavor-selector:not(:checked) ~ .esm, +.js-flavor-selector:checked ~ .cjs { + display: none; +} +.dark-mode .js-flavor-selector { + filter: invert(1); +} +@supports (aspect-ratio: 1 / 1) { + .js-flavor-selector { + height: 1.5em; + width: auto; + aspect-ratio: 2719 / 384; + } +} + @media print { html { height: auto; @@ -832,4 +859,24 @@ kbd { #apicontent { overflow: hidden; } + .js-flavor-selector { + display: none; + } + .js-flavor-selector + * { + margin-bottom: 2rem; + padding-bottom: 2rem; + border-bottom: 1px solid var(--color-text-primary); + } + .js-flavor-selector ~ * { + display: block !important; + background-position: top right; + background-size: 142px 20px; + background-repeat: no-repeat; + } + .js-flavor-selector ~ .cjs { + background-image: url(./js-flavor-cjs.svg); + } + .js-flavor-selector ~ .mjs { + background-image: url(./js-flavor-esm.svg); + } } diff --git a/test/doctool/test-doctool-html.js b/test/doctool/test-doctool-html.js index 2fa4766c837a27..dd651a66c1e94f 100644 --- a/test/doctool/test-doctool-html.js +++ b/test/doctool/test-doctool-html.js @@ -129,7 +129,15 @@ const testData = [ { file: fixtures.path('document_with_special_heading.md'), html: 'Sample markdown with special heading |', - } + }, + { + file: fixtures.path('document_with_esm_and_cjs_code_snippet.md'), + html: '<input class="js-flavor-selector" type="checkbox" checked', + }, + { + file: fixtures.path('document_with_cjs_and_esm_code_snippet.md'), + html: '<input class="js-flavor-selector" type="checkbox" aria-label', + }, ]; const spaces = /\s/g; diff --git a/test/fixtures/document_with_cjs_and_esm_code_snippet.md b/test/fixtures/document_with_cjs_and_esm_code_snippet.md new file mode 100644 index 00000000000000..aae5ea28353a7d --- /dev/null +++ b/test/fixtures/document_with_cjs_and_esm_code_snippet.md @@ -0,0 +1,11 @@ +# Usage and Example + +CJS snippet is first, it should be the one displayed by default. + +```cjs +require('path'); +``` + +```mjs +import 'node:url'; +``` \ No newline at end of file diff --git a/test/fixtures/document_with_esm_and_cjs_code_snippet.md b/test/fixtures/document_with_esm_and_cjs_code_snippet.md new file mode 100644 index 00000000000000..a7e14383762979 --- /dev/null +++ b/test/fixtures/document_with_esm_and_cjs_code_snippet.md @@ -0,0 +1,11 @@ +# Usage and Example + +ESM snippet is first, it should be the one displayed by default. + +```mjs +import 'node:url'; +``` + +```cjs +require('path'); +``` \ No newline at end of file diff --git a/tools/doc/html.js b/tools/doc/html.js index c54a9d1ad1e791..671cb4adf62065 100644 --- a/tools/doc/html.js +++ b/tools/doc/html.js @@ -196,6 +196,8 @@ function linkJsTypeDocs(text) { return parts.join('`'); } +const isJSFlavorSnippet = (node) => node.lang === 'cjs' || node.lang === 'mjs'; + // Preprocess headers, stability blockquotes, and YAML blocks. function preprocessElements({ filename }) { return (tree) => { @@ -203,7 +205,7 @@ function preprocessElements({ filename }) { let headingIndex = -1; let heading = null; - visit(tree, null, (node, index) => { + visit(tree, null, (node, index, parent) => { if (node.type === 'heading') { headingIndex = index; heading = node; @@ -213,15 +215,44 @@ function preprocessElements({ filename }) { `No language set in ${filename}, ` + `line ${node.position.start.line}`); } - const language = (node.lang || '').split(' ')[0]; - const highlighted = getLanguage(language) ? - highlight(language, node.value).value : - node.value; + const className = isJSFlavorSnippet(node) ? + `language-js ${node.lang}` : + `language-${node.lang}`; + const highlighted = + `<code class='${className}'>` + + (getLanguage(node.lang || '') ? + highlight(node.lang, node.value) : node).value + + '</code>'; node.type = 'html'; - node.value = '<pre>' + - `<code class = 'language-${node.lang}'>` + - highlighted + - '</code></pre>'; + + if (isJSFlavorSnippet(node)) { + const previousNode = parent.children[index - 1] || {}; + const nextNode = parent.children[index + 1] || {}; + + if (!isJSFlavorSnippet(previousNode) && + isJSFlavorSnippet(nextNode) && + nextNode.lang !== node.lang) { + // Saving the highlight code as value to be added in the next node. + node.value = highlighted; + } else if (isJSFlavorSnippet(previousNode)) { + node.value = '<pre>' + + '<input class="js-flavor-selector" type="checkbox"' + + // If CJS comes in second, ESM should display by default. + (node.lang === 'cjs' ? ' checked' : '') + + ' aria-label="Show modern ES modules syntax">' + + previousNode.value + + highlighted + + '</pre>'; + node.lang = null; + previousNode.value = ''; + previousNode.lang = null; + } else { + // Isolated JS snippet, no need to add the checkbox. + node.value = `<pre>${highlighted}</pre>`; + } + } else { + node.value = `<pre>${highlighted}</pre>`; + } } else if (node.type === 'html' && common.isYAMLBlock(node.value)) { node.value = parseYAML(node.value);