diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index cfb6ee9aec4b..ad5d1ecba562 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -39,7 +39,7 @@ jobs: with: urls: | https://typescript-v2-$PR_NUMBER.ortam.now.sh - https://typescript-v2-$PR_NUMBER.ortam.now.sh/tsconfig + https://typescript-v2-$PR_NUMBER.ortam.now.sh/en/tsconfig https://typescript-v2-$PR_NUMBER.ortam.now.sh/docs/handbook/integrating-with-build-tools.html env: PR_NUMBER: ${{ github.event.pull_request.number }} diff --git a/.vscode/settings.json b/.vscode/settings.json index 978f43bca40c..16370da3d9d6 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -5,14 +5,13 @@ "**/packages/typescriptlang-org/.cache/*": true }, "workbench.colorCustomizations": { - "activityBar.background": "#5592d5", + "activityBar.background": "#c4d3e4", "activityBar.activeBorder": "#f2ccde", "activityBar.foreground": "#15202b", "activityBar.inactiveForeground": "#15202b99", "activityBarBadge.background": "#f2ccde", "activityBarBadge.foreground": "#15202b" }, - "peacock.color": "#3178C6", "typescript.tsdk": "node_modules/typescript/lib", "debug.node.autoAttach": "on", "editor.formatOnSave": true diff --git a/README.md b/README.md index 01cc3578ac01..8a7fc6ac6e9e 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,9 @@ yarn bootstrap code . ``` +Working on this repo is done by running `yarn start` - this starts up the website on port `8000` and creates a +builder worker for every package in the repo, so if you make a change outside of the site it will compile and lint etc. + # Website Packages ## TypeScriptLang-Org @@ -20,18 +23,14 @@ The main website for TypeScript, a Gatsby website which is statically deployed. yarn start ``` -## Playground - -A React component for the TypeScript playground base component. Not the one available on -the site, but one you can use in other websites for showing off your APIs. +## Sandbox -You can work on the playground by running: +The editor aspect of the TypeScript Playground REPL, useable for all sites which want to show a monaco editor +with TypeScript or JavaScript code. -```sh -yarn playground -``` +## Playground -Then opening: http://localhost:1234 - which is the below package. +The JS code as an AMD module for the playground which is loaded at runtime in the Playground website. # Doc Packages @@ -54,7 +53,7 @@ yarn workspace tsconfig-reference run test # or to just run the linter without a build yarn workspace tsconfig-reference run lint -# or to just one one linter +# or to just one one linter for a single doc yarn workspace tsconfig-reference run lint resolveJson ``` diff --git a/dangerfile.ts b/dangerfile.ts index a7bb8e284470..c1842966795b 100644 --- a/dangerfile.ts +++ b/dangerfile.ts @@ -1,10 +1,10 @@ // This Dangerfile only runs on same-repo PRs -// You can test it by running -// yarn danger pr https://github.com/microsoft/TypeScript-Website/pull/115 +// You can test it by running +// yarn danger pr https://github.com/microsoft/TypeScript-Website/pull/115 import { danger, message, markdown } from 'danger' -import {basename} from "path" +import { basename } from 'path' import spellcheck from 'danger-plugin-spellcheck' import lighthouse from 'danger-plugin-lighthouse' @@ -16,10 +16,12 @@ spellcheck({ // Print out the PR url const deployURL = `https://typescript-v2-${danger.github.pr.number}.ortam.now.sh` -message(`Deployed to [a PR branch](${deployURL}) - [tsconfig](${deployURL}/tsconfig) [old handbook](${deployURL}/docs/handbook/integrating-with-build-tools.html)`) +message( + `Deployed to [a PR branch](${deployURL}) - [playground](${deployURL}/en/play) [tsconfig](${deployURL}/en/tsconfig) [old handbook](${deployURL}/docs/handbook/integrating-with-build-tools.html)` +) // Look for new snapshots and show in a HTML table -const snapshots = danger.git.fileMatch("packages/typescriptlang-org/_tests/backstop_data/bitmaps_reference/*.png") +const snapshots = danger.git.fileMatch('packages/typescriptlang-org/_tests/backstop_data/bitmaps_reference/*.png') if (snapshots.modified) { const oldSha = danger.github.pr.base.sha const newSha = danger.github.pr.head.sha @@ -37,7 +39,7 @@ Before | After ` }) - markdown(`## Snapshots updated\n\n ${tables.join("\n\n")}`) + markdown(`## Snapshots updated\n\n ${tables.join('\n\n')}`) } lighthouse() diff --git a/package.json b/package.json index 67568ef30a09..2a2d3ff1a795 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,7 @@ "@types/react": "16.9.17" }, "scripts": { - "bootstrap": "yarn workspace ts-twoslasher run build; yarn workspaces run build", + "bootstrap": "yarn workspaces run bootstrap; yarn workspaces run build", "start": "concurrently -p \"[{name}]\" -n \"BUILD,SITE\" -c \"bgBlue.bold,bgMagenta.bold\" \"node watcher.js\" \"yarn workspace typescriptlang-org start\"", "build": "yarn workspaces run build", "build-site": "yarn workspace typescriptlang-org build", diff --git a/packages/gatsby-remark-shiki/package.json b/packages/gatsby-remark-shiki/package.json index 4d2bc19c812e..4504b3fa6a2a 100644 --- a/packages/gatsby-remark-shiki/package.json +++ b/packages/gatsby-remark-shiki/package.json @@ -12,6 +12,7 @@ "scripts": { "start": "tsdx watch", "build": "tsdx build", + "bootstrap": "echo 'NOOP'", "test": "tsdx test", "lint": "tsdx lint" }, diff --git a/packages/gatsby-remark-shiki/test/fixtures/twoliner.md b/packages/gatsby-remark-shiki/test/fixtures/twoliner.md index 6a142dec85e7..c60ea2667821 100644 --- a/packages/gatsby-remark-shiki/test/fixtures/twoliner.md +++ b/packages/gatsby-remark-shiki/test/fixtures/twoliner.md @@ -4,6 +4,6 @@ // @filename: getStringLength.ts export const getStringLength = (str: string) => str.length // @filename: index.ts -import {getStringLength} from "./getStringLength" -const b = getStringLength("string) +import { getStringLength } from './getStringLength' +const b = getStringLength('string') ``` diff --git a/packages/gatsby-remark-shiki/test/results/one.html b/packages/gatsby-remark-shiki/test/results/one.html index 97451cd92ce4..f3606a778fd0 100644 --- a/packages/gatsby-remark-shiki/test/results/one.html +++ b/packages/gatsby-remark-shiki/test/results/one.html @@ -9,10 +9,10 @@

One liner with multiple IDs

padding: 8px; } .lsp-result::before { - content: ' <'; + content: " <"; } .lsp-result::after { - content: '> '; + content: "> "; } .lsp-result { text-decoration: none !important; diff --git a/packages/gatsby-remark-shiki/test/results/twoliner.html b/packages/gatsby-remark-shiki/test/results/twoliner.html index 143c568811b8..0e2dd1b1977f 100644 --- a/packages/gatsby-remark-shiki/test/results/twoliner.html +++ b/packages/gatsby-remark-shiki/test/results/twoliner.html @@ -1,13 +1,22 @@ +

Two liner with multiple IDs

+
ts
// @filename: getStringLength.ts +export const getStringLength = (str: string) => str.length +// @filename: index.ts +import { getStringLength } from './getStringLength' +const b = getStringLength('string')
+ -

Two liner with multiple IDs

-
ts
const getStringLengthconst getStringLength: (str: string) => number = (str(parameter) str: string: string) => str(parameter) str: string.length(property) String.length: number -const exportsconst exports: { getStringLength: (str: string) => number; } = { - getStringLength(property) getStringLength: (str: string) => number, -}
diff --git a/packages/gatsby-remark-shiki/test/results/twoliner.json b/packages/gatsby-remark-shiki/test/results/twoliner.json index 5253d36afd6b..0e6e9850e6f5 100644 --- a/packages/gatsby-remark-shiki/test/results/twoliner.json +++ b/packages/gatsby-remark-shiki/test/results/twoliner.json @@ -1,66 +1,75 @@ [ { - "code": "const getStringLength = (str: string) => str.length\nconst exports = {\n getStringLength,\n}", + "code": "// @filename: getStringLength.ts\nexport const getStringLength = (str: string) => str.length\n// @filename: index.ts\nimport { getStringLength } from './getStringLength'\nconst b = getStringLength('string')", "extension": "ts", "highlights": [], "queries": [], "staticQuickInfos": [ + { + "text": "(alias) const getStringLength: (str: string) => number\nimport getStringLength", + "docs": "", + "start": 124, + "length": 15, + "line": 1, + "character": 72, + "targetString": "getStringLength" + }, + { + "text": "const b: number", + "docs": "", + "start": 173, + "length": 1, + "line": 1, + "character": 121, + "targetString": "b" + }, + { + "text": "(alias) getStringLength(str: string): number\nimport getStringLength", + "docs": "", + "start": 177, + "length": 15, + "line": 1, + "character": 125, + "targetString": "getStringLength" + }, { "text": "const getStringLength: (str: string) => number", "docs": "", - "start": 6, + "start": 46, "length": 15, "line": 0, - "character": 6, + "character": 46, "targetString": "getStringLength" }, { "text": "(parameter) str: string", "docs": "", - "start": 25, + "start": 65, "length": 3, - "line": 0, - "character": 25, + "line": 1, + "character": 6, "targetString": "str" }, { "text": "(parameter) str: string", "docs": "", - "start": 41, + "start": 81, "length": 3, - "line": 0, - "character": 41, + "line": 1, + "character": 22, "targetString": "str" }, { "text": "(property) String.length: number", "docs": "Returns the length of a String object.", - "start": 45, + "start": 85, "length": 6, - "line": 0, - "character": 45, - "targetString": "length" - }, - { - "text": "const exports: {\n getStringLength: (str: string) => number;\n}", - "docs": "", - "start": 58, - "length": 7, "line": 1, - "character": 6, - "targetString": "exports" - }, - { - "text": "(property) getStringLength: (str: string) => number", - "docs": "", - "start": 72, - "length": 15, - "line": 2, - "character": 2, - "targetString": "getStringLength" + "character": 26, + "targetString": "length" } ], "errors": [], - "playgroundURL": "https://www.typescriptlang.org/play/#code/MYewdgzgLgBA5gUygZSgJwJZjgGQdqACxgF4YAKaNALhiqzgEpSA+O9AOgBt84iAoUJFgIAHgAcQaKBFIwA3vxjwkqTNjwFCAGn4BfIA" + "playgroundURL": "https://www.typescriptlang.org/play/#code/PTAEAEDMEsBsFMB2BDAtvAXKA5vALgMp4BO0i2AMktngBYB0eAzgFDwAeADgPbF6gBjboib9chEmUrU6oALygAFKOJYVUgJTyAfKBX0E5OixAQYhtJlBkAJh0atoqHn1ABvHPiKlyVI7VAAX1BIYm5UUAByemBxbyk-GlpIliERfgAjeU8JH2l-RUj1ckiNIA" } ] diff --git a/packages/gatsby-remark-twoslasher-code-blocks/package.json b/packages/gatsby-remark-twoslasher-code-blocks/package.json index e06859ad84f7..7a73c3958878 100644 --- a/packages/gatsby-remark-twoslasher-code-blocks/package.json +++ b/packages/gatsby-remark-twoslasher-code-blocks/package.json @@ -8,6 +8,7 @@ }, "scripts": { "build": "echo 'NOOP'", - "test": "echo 'NOOP'" + "test": "echo 'NOOP'", + "bootstrap": "echo 'NOOP'" } } diff --git a/packages/handbook-v1/package.json b/packages/handbook-v1/package.json index f96a60690b1d..9420d8097ce4 100644 --- a/packages/handbook-v1/package.json +++ b/packages/handbook-v1/package.json @@ -5,7 +5,8 @@ "version": "1.0.0", "scripts": { "build": "echo 'NOOP'", - "test": "echo 'NOOP'" + "test": "echo 'NOOP'", + "bootstrap": "echo 'NOOP'" }, "prettier": { "semi": true diff --git a/packages/playground-examples/README.md b/packages/playground-examples/README.md index 1cd2ac2fb03a..06467e453c00 100644 --- a/packages/playground-examples/README.md +++ b/packages/playground-examples/README.md @@ -2,14 +2,51 @@ The English examples can be found in [`en/`](en/). +# TypeScript Example Code + +These samples are built for hyperlinking between each-other +in a sandboxed environment like the TypeScript Playground. + +Each example aims to cover one or two specific features to +either how JavaScript works in TypeScript, or features which +TypeScript has added to the language. + +An example should make assumptions that the reader is in a +monaco/IDE-like environment which has a TSServer running for +to provide extra analysis. As well as a minor fluency in +JavaScript. + +These examples are not set in stone, and we're open to new +ideas. If you'd like to help out and speak more than one +language, we'd love to see translations. + +## Adding a new example section + +Create a folder in the english section of the [`copy`](./copy) folder, +then add sub-folders per section which you'd want to have as headers +with 3-5 examples. + +## Adding a localization + +All localizations live inside the `copy` folder: + +- There must be a `sections.json` in the root of each language +- A language is created by copying over an english example with the same path, and then translating it +- Any examples not copied over fall back to the english version +- You can change the name of an example for your language by having `//// { title: 'c0d3 fl0w', ... }` in the first line of the example + +Languages are compiled to TOC JSON files in [`generated`](./generated), one per lanaguge. + # Deployment There is a table of contents JSON file which contains all the useful metadata about the hierarchy and sort -order for the docs. +order for the docs. It's likely that we'll need to create this per translation in the future, but for now the table of contents will default to english. -The script is in [`scripts/generateTOC.js`](scripts/generateTOC.js). +The script is in [`scripts/generateTOC.js`](scripts/generateTOC.js), with +\output of the build process is then copied into the `typescriptlang-org` +module under `static/js/examples` in [`scripts/copyFiles.js`](scripts/copyFiles.js). diff --git a/packages/playground-examples/en/3-7/Fixits/Big number literals.ts b/packages/playground-examples/copy/en/3-7/Fixits/Big number literals.ts similarity index 100% rename from packages/playground-examples/en/3-7/Fixits/Big number literals.ts rename to packages/playground-examples/copy/en/3-7/Fixits/Big number literals.ts diff --git a/packages/playground-examples/en/3-7/Fixits/Const to let.ts b/packages/playground-examples/copy/en/3-7/Fixits/Const to let.ts similarity index 100% rename from packages/playground-examples/en/3-7/Fixits/Const to let.ts rename to packages/playground-examples/copy/en/3-7/Fixits/Const to let.ts diff --git a/packages/playground-examples/en/3-7/Fixits/Infer From Usage Changes.ts b/packages/playground-examples/copy/en/3-7/Fixits/Infer From Usage Changes.ts similarity index 100% rename from packages/playground-examples/en/3-7/Fixits/Infer From Usage Changes.ts rename to packages/playground-examples/copy/en/3-7/Fixits/Infer From Usage Changes.ts diff --git a/packages/playground-examples/en/3-7/Syntax and Messaging/Flattened Error Reporting.ts b/packages/playground-examples/copy/en/3-7/Syntax and Messaging/Flattened Error Reporting.ts similarity index 100% rename from packages/playground-examples/en/3-7/Syntax and Messaging/Flattened Error Reporting.ts rename to packages/playground-examples/copy/en/3-7/Syntax and Messaging/Flattened Error Reporting.ts diff --git a/packages/playground-examples/en/3-7/Syntax and Messaging/Nullish Coalescing.ts b/packages/playground-examples/copy/en/3-7/Syntax and Messaging/Nullish Coalescing.ts similarity index 100% rename from packages/playground-examples/en/3-7/Syntax and Messaging/Nullish Coalescing.ts rename to packages/playground-examples/copy/en/3-7/Syntax and Messaging/Nullish Coalescing.ts diff --git a/packages/playground-examples/en/3-7/Syntax and Messaging/Optional Chaining.ts b/packages/playground-examples/copy/en/3-7/Syntax and Messaging/Optional Chaining.ts similarity index 100% rename from packages/playground-examples/en/3-7/Syntax and Messaging/Optional Chaining.ts rename to packages/playground-examples/copy/en/3-7/Syntax and Messaging/Optional Chaining.ts diff --git a/packages/playground-examples/en/3-7/Types and Code Flow/Assertion Functions.ts b/packages/playground-examples/copy/en/3-7/Types and Code Flow/Assertion Functions.ts similarity index 100% rename from packages/playground-examples/en/3-7/Types and Code Flow/Assertion Functions.ts rename to packages/playground-examples/copy/en/3-7/Types and Code Flow/Assertion Functions.ts diff --git a/packages/playground-examples/en/3-7/Types and Code Flow/Recursive Type References.ts b/packages/playground-examples/copy/en/3-7/Types and Code Flow/Recursive Type References.ts similarity index 100% rename from packages/playground-examples/en/3-7/Types and Code Flow/Recursive Type References.ts rename to packages/playground-examples/copy/en/3-7/Types and Code Flow/Recursive Type References.ts diff --git a/packages/playground-examples/en/3-7/Types and Code Flow/Uncalled Function Checks.ts b/packages/playground-examples/copy/en/3-7/Types and Code Flow/Uncalled Function Checks.ts similarity index 100% rename from packages/playground-examples/en/3-7/Types and Code Flow/Uncalled Function Checks.ts rename to packages/playground-examples/copy/en/3-7/Types and Code Flow/Uncalled Function Checks.ts diff --git a/packages/playground-examples/en/JavaScript/External APIs/TypeScript with Deno.ts b/packages/playground-examples/copy/en/JavaScript/External APIs/TypeScript with Deno.ts similarity index 100% rename from packages/playground-examples/en/JavaScript/External APIs/TypeScript with Deno.ts rename to packages/playground-examples/copy/en/JavaScript/External APIs/TypeScript with Deno.ts diff --git a/packages/playground-examples/en/JavaScript/External APIs/TypeScript with Node.js b/packages/playground-examples/copy/en/JavaScript/External APIs/TypeScript with Node.js similarity index 100% rename from packages/playground-examples/en/JavaScript/External APIs/TypeScript with Node.js rename to packages/playground-examples/copy/en/JavaScript/External APIs/TypeScript with Node.js diff --git a/packages/playground-examples/en/JavaScript/External APIs/TypeScript with React.tsx b/packages/playground-examples/copy/en/JavaScript/External APIs/TypeScript with React.tsx similarity index 100% rename from packages/playground-examples/en/JavaScript/External APIs/TypeScript with React.tsx rename to packages/playground-examples/copy/en/JavaScript/External APIs/TypeScript with React.tsx diff --git a/packages/playground-examples/en/JavaScript/External APIs/TypeScript with Web.js b/packages/playground-examples/copy/en/JavaScript/External APIs/TypeScript with Web.js similarity index 100% rename from packages/playground-examples/en/JavaScript/External APIs/TypeScript with Web.js rename to packages/playground-examples/copy/en/JavaScript/External APIs/TypeScript with Web.js diff --git a/packages/playground-examples/en/JavaScript/External APIs/TypeScript with WebGL.js b/packages/playground-examples/copy/en/JavaScript/External APIs/TypeScript with WebGL.js similarity index 100% rename from packages/playground-examples/en/JavaScript/External APIs/TypeScript with WebGL.js rename to packages/playground-examples/copy/en/JavaScript/External APIs/TypeScript with WebGL.js diff --git a/packages/playground-examples/en/JavaScript/Functions with JavaScript/Function Chaining.ts b/packages/playground-examples/copy/en/JavaScript/Functions with JavaScript/Function Chaining.ts similarity index 100% rename from packages/playground-examples/en/JavaScript/Functions with JavaScript/Function Chaining.ts rename to packages/playground-examples/copy/en/JavaScript/Functions with JavaScript/Function Chaining.ts diff --git a/packages/playground-examples/en/JavaScript/Functions with JavaScript/Generic Functions.ts b/packages/playground-examples/copy/en/JavaScript/Functions with JavaScript/Generic Functions.ts similarity index 100% rename from packages/playground-examples/en/JavaScript/Functions with JavaScript/Generic Functions.ts rename to packages/playground-examples/copy/en/JavaScript/Functions with JavaScript/Generic Functions.ts diff --git a/packages/playground-examples/en/JavaScript/Functions with JavaScript/Typing Functions.ts b/packages/playground-examples/copy/en/JavaScript/Functions with JavaScript/Typing Functions.ts similarity index 100% rename from packages/playground-examples/en/JavaScript/Functions with JavaScript/Typing Functions.ts rename to packages/playground-examples/copy/en/JavaScript/Functions with JavaScript/Typing Functions.ts diff --git a/packages/playground-examples/en/JavaScript/Helping with JavaScript/Errors.ts b/packages/playground-examples/copy/en/JavaScript/Helping with JavaScript/Errors.ts similarity index 100% rename from packages/playground-examples/en/JavaScript/Helping with JavaScript/Errors.ts rename to packages/playground-examples/copy/en/JavaScript/Helping with JavaScript/Errors.ts diff --git a/packages/playground-examples/en/JavaScript/Helping with JavaScript/Quick Fixes.ts b/packages/playground-examples/copy/en/JavaScript/Helping with JavaScript/Quick Fixes.ts similarity index 100% rename from packages/playground-examples/en/JavaScript/Helping with JavaScript/Quick Fixes.ts rename to packages/playground-examples/copy/en/JavaScript/Helping with JavaScript/Quick Fixes.ts diff --git a/packages/playground-examples/en/JavaScript/JavaScript Essentials/Code Flow.ts b/packages/playground-examples/copy/en/JavaScript/JavaScript Essentials/Code Flow.ts similarity index 100% rename from packages/playground-examples/en/JavaScript/JavaScript Essentials/Code Flow.ts rename to packages/playground-examples/copy/en/JavaScript/JavaScript Essentials/Code Flow.ts diff --git a/packages/playground-examples/en/JavaScript/JavaScript Essentials/Functions.ts b/packages/playground-examples/copy/en/JavaScript/JavaScript Essentials/Functions.ts similarity index 100% rename from packages/playground-examples/en/JavaScript/JavaScript Essentials/Functions.ts rename to packages/playground-examples/copy/en/JavaScript/JavaScript Essentials/Functions.ts diff --git a/packages/playground-examples/en/JavaScript/JavaScript Essentials/Hello World.ts b/packages/playground-examples/copy/en/JavaScript/JavaScript Essentials/Hello World.ts similarity index 100% rename from packages/playground-examples/en/JavaScript/JavaScript Essentials/Hello World.ts rename to packages/playground-examples/copy/en/JavaScript/JavaScript Essentials/Hello World.ts diff --git a/packages/playground-examples/en/JavaScript/JavaScript Essentials/Objects and Arrays.ts b/packages/playground-examples/copy/en/JavaScript/JavaScript Essentials/Objects and Arrays.ts similarity index 100% rename from packages/playground-examples/en/JavaScript/JavaScript Essentials/Objects and Arrays.ts rename to packages/playground-examples/copy/en/JavaScript/JavaScript Essentials/Objects and Arrays.ts diff --git a/packages/playground-examples/en/JavaScript/Modern JavaScript/Async Await.ts b/packages/playground-examples/copy/en/JavaScript/Modern JavaScript/Async Await.ts similarity index 100% rename from packages/playground-examples/en/JavaScript/Modern JavaScript/Async Await.ts rename to packages/playground-examples/copy/en/JavaScript/Modern JavaScript/Async Await.ts diff --git a/packages/playground-examples/en/JavaScript/Modern JavaScript/Immutability.ts b/packages/playground-examples/copy/en/JavaScript/Modern JavaScript/Immutability.ts similarity index 100% rename from packages/playground-examples/en/JavaScript/Modern JavaScript/Immutability.ts rename to packages/playground-examples/copy/en/JavaScript/Modern JavaScript/Immutability.ts diff --git a/packages/playground-examples/en/JavaScript/Modern JavaScript/Import Export.ts b/packages/playground-examples/copy/en/JavaScript/Modern JavaScript/Import Export.ts similarity index 100% rename from packages/playground-examples/en/JavaScript/Modern JavaScript/Import Export.ts rename to packages/playground-examples/copy/en/JavaScript/Modern JavaScript/Import Export.ts diff --git a/packages/playground-examples/en/JavaScript/Modern JavaScript/JSDoc Support.js b/packages/playground-examples/copy/en/JavaScript/Modern JavaScript/JSDoc Support.js similarity index 100% rename from packages/playground-examples/en/JavaScript/Modern JavaScript/JSDoc Support.js rename to packages/playground-examples/copy/en/JavaScript/Modern JavaScript/JSDoc Support.js diff --git a/packages/playground-examples/en/JavaScript/README.md b/packages/playground-examples/copy/en/JavaScript/README.md similarity index 100% rename from packages/playground-examples/en/JavaScript/README.md rename to packages/playground-examples/copy/en/JavaScript/README.md diff --git a/packages/playground-examples/en/JavaScript/Working With Classes/Classes 101.ts b/packages/playground-examples/copy/en/JavaScript/Working With Classes/Classes 101.ts similarity index 100% rename from packages/playground-examples/en/JavaScript/Working With Classes/Classes 101.ts rename to packages/playground-examples/copy/en/JavaScript/Working With Classes/Classes 101.ts diff --git a/packages/playground-examples/en/JavaScript/Working With Classes/Generic Classes.ts b/packages/playground-examples/copy/en/JavaScript/Working With Classes/Generic Classes.ts similarity index 100% rename from packages/playground-examples/en/JavaScript/Working With Classes/Generic Classes.ts rename to packages/playground-examples/copy/en/JavaScript/Working With Classes/Generic Classes.ts diff --git a/packages/playground-examples/en/JavaScript/Working With Classes/Mixins.ts b/packages/playground-examples/copy/en/JavaScript/Working With Classes/Mixins.ts similarity index 100% rename from packages/playground-examples/en/JavaScript/Working With Classes/Mixins.ts rename to packages/playground-examples/copy/en/JavaScript/Working With Classes/Mixins.ts diff --git a/packages/playground-examples/en/JavaScript/Working With Classes/This.ts b/packages/playground-examples/copy/en/JavaScript/Working With Classes/This.ts similarity index 100% rename from packages/playground-examples/en/JavaScript/Working With Classes/This.ts rename to packages/playground-examples/copy/en/JavaScript/Working With Classes/This.ts diff --git a/packages/playground-examples/en/Playground/Config/JavaScript Playgrounds.js b/packages/playground-examples/copy/en/Playground/Config/JavaScript Playgrounds.js similarity index 100% rename from packages/playground-examples/en/Playground/Config/JavaScript Playgrounds.js rename to packages/playground-examples/copy/en/Playground/Config/JavaScript Playgrounds.js diff --git a/packages/playground-examples/en/Playground/Config/New Compiler Defaults.ts b/packages/playground-examples/copy/en/Playground/Config/New Compiler Defaults.ts similarity index 100% rename from packages/playground-examples/en/Playground/Config/New Compiler Defaults.ts rename to packages/playground-examples/copy/en/Playground/Config/New Compiler Defaults.ts diff --git a/packages/playground-examples/en/Playground/Language/Automatic Type Acquisition.ts b/packages/playground-examples/copy/en/Playground/Language/Automatic Type Acquisition.ts similarity index 100% rename from packages/playground-examples/en/Playground/Language/Automatic Type Acquisition.ts rename to packages/playground-examples/copy/en/Playground/Language/Automatic Type Acquisition.ts diff --git a/packages/playground-examples/en/Playground/Language/Fixits.ts b/packages/playground-examples/copy/en/Playground/Language/Fixits.ts similarity index 100% rename from packages/playground-examples/en/Playground/Language/Fixits.ts rename to packages/playground-examples/copy/en/Playground/Language/Fixits.ts diff --git a/packages/playground-examples/en/Playground/Tooling/Mobile Support.ts b/packages/playground-examples/copy/en/Playground/Tooling/Mobile Support.ts similarity index 100% rename from packages/playground-examples/en/Playground/Tooling/Mobile Support.ts rename to packages/playground-examples/copy/en/Playground/Tooling/Mobile Support.ts diff --git a/packages/playground-examples/en/Playground/Tooling/Sharable URLs.ts b/packages/playground-examples/copy/en/Playground/Tooling/Sharable URLs.ts similarity index 100% rename from packages/playground-examples/en/Playground/Tooling/Sharable URLs.ts rename to packages/playground-examples/copy/en/Playground/Tooling/Sharable URLs.ts diff --git a/packages/playground-examples/en/Playground/Tooling/TypeScript Versions.ts b/packages/playground-examples/copy/en/Playground/Tooling/TypeScript Versions.ts similarity index 100% rename from packages/playground-examples/en/Playground/Tooling/TypeScript Versions.ts rename to packages/playground-examples/copy/en/Playground/Tooling/TypeScript Versions.ts diff --git a/packages/playground-examples/en/TypeScript/Language Extensions/Enums.ts b/packages/playground-examples/copy/en/TypeScript/Language Extensions/Enums.ts similarity index 100% rename from packages/playground-examples/en/TypeScript/Language Extensions/Enums.ts rename to packages/playground-examples/copy/en/TypeScript/Language Extensions/Enums.ts diff --git a/packages/playground-examples/en/TypeScript/Language Extensions/Nominal Typing.ts b/packages/playground-examples/copy/en/TypeScript/Language Extensions/Nominal Typing.ts similarity index 100% rename from packages/playground-examples/en/TypeScript/Language Extensions/Nominal Typing.ts rename to packages/playground-examples/copy/en/TypeScript/Language Extensions/Nominal Typing.ts diff --git a/packages/playground-examples/en/TypeScript/Language Extensions/Types vs Interfaces.ts b/packages/playground-examples/copy/en/TypeScript/Language Extensions/Types vs Interfaces.ts similarity index 100% rename from packages/playground-examples/en/TypeScript/Language Extensions/Types vs Interfaces.ts rename to packages/playground-examples/copy/en/TypeScript/Language Extensions/Types vs Interfaces.ts diff --git a/packages/playground-examples/en/TypeScript/Language/Soundness.ts b/packages/playground-examples/copy/en/TypeScript/Language/Soundness.ts similarity index 100% rename from packages/playground-examples/en/TypeScript/Language/Soundness.ts rename to packages/playground-examples/copy/en/TypeScript/Language/Soundness.ts diff --git a/packages/playground-examples/en/TypeScript/Language/Structural Typing.ts b/packages/playground-examples/copy/en/TypeScript/Language/Structural Typing.ts similarity index 100% rename from packages/playground-examples/en/TypeScript/Language/Structural Typing.ts rename to packages/playground-examples/copy/en/TypeScript/Language/Structural Typing.ts diff --git a/packages/playground-examples/en/TypeScript/Language/Type Guards.ts b/packages/playground-examples/copy/en/TypeScript/Language/Type Guards.ts similarity index 100% rename from packages/playground-examples/en/TypeScript/Language/Type Guards.ts rename to packages/playground-examples/copy/en/TypeScript/Language/Type Guards.ts diff --git a/packages/playground-examples/en/TypeScript/Language/Type Widening and Narrowing.ts b/packages/playground-examples/copy/en/TypeScript/Language/Type Widening and Narrowing.ts similarity index 100% rename from packages/playground-examples/en/TypeScript/Language/Type Widening and Narrowing.ts rename to packages/playground-examples/copy/en/TypeScript/Language/Type Widening and Narrowing.ts diff --git a/packages/playground-examples/en/TypeScript/Meta-Types/Conditional Types.ts b/packages/playground-examples/copy/en/TypeScript/Meta-Types/Conditional Types.ts similarity index 100% rename from packages/playground-examples/en/TypeScript/Meta-Types/Conditional Types.ts rename to packages/playground-examples/copy/en/TypeScript/Meta-Types/Conditional Types.ts diff --git a/packages/playground-examples/en/TypeScript/Meta-Types/Discriminate Types.ts b/packages/playground-examples/copy/en/TypeScript/Meta-Types/Discriminate Types.ts similarity index 100% rename from packages/playground-examples/en/TypeScript/Meta-Types/Discriminate Types.ts rename to packages/playground-examples/copy/en/TypeScript/Meta-Types/Discriminate Types.ts diff --git a/packages/playground-examples/en/TypeScript/Meta-Types/Indexed Types.ts b/packages/playground-examples/copy/en/TypeScript/Meta-Types/Indexed Types.ts similarity index 100% rename from packages/playground-examples/en/TypeScript/Meta-Types/Indexed Types.ts rename to packages/playground-examples/copy/en/TypeScript/Meta-Types/Indexed Types.ts diff --git a/packages/playground-examples/en/TypeScript/Meta-Types/Mapped Types.ts b/packages/playground-examples/copy/en/TypeScript/Meta-Types/Mapped Types.ts similarity index 100% rename from packages/playground-examples/en/TypeScript/Meta-Types/Mapped Types.ts rename to packages/playground-examples/copy/en/TypeScript/Meta-Types/Mapped Types.ts diff --git a/packages/playground-examples/en/TypeScript/Primitives/Any.ts b/packages/playground-examples/copy/en/TypeScript/Primitives/Any.ts similarity index 100% rename from packages/playground-examples/en/TypeScript/Primitives/Any.ts rename to packages/playground-examples/copy/en/TypeScript/Primitives/Any.ts diff --git a/packages/playground-examples/en/TypeScript/Primitives/Literals.ts b/packages/playground-examples/copy/en/TypeScript/Primitives/Literals.ts similarity index 100% rename from packages/playground-examples/en/TypeScript/Primitives/Literals.ts rename to packages/playground-examples/copy/en/TypeScript/Primitives/Literals.ts diff --git a/packages/playground-examples/en/TypeScript/Primitives/Union and Intersection Types.ts b/packages/playground-examples/copy/en/TypeScript/Primitives/Union and Intersection Types.ts similarity index 100% rename from packages/playground-examples/en/TypeScript/Primitives/Union and Intersection Types.ts rename to packages/playground-examples/copy/en/TypeScript/Primitives/Union and Intersection Types.ts diff --git a/packages/playground-examples/en/TypeScript/Primitives/Unknown and Never.ts b/packages/playground-examples/copy/en/TypeScript/Primitives/Unknown and Never.ts similarity index 100% rename from packages/playground-examples/en/TypeScript/Primitives/Unknown and Never.ts rename to packages/playground-examples/copy/en/TypeScript/Primitives/Unknown and Never.ts diff --git a/packages/playground-examples/en/TypeScript/README.md b/packages/playground-examples/copy/en/TypeScript/README.md similarity index 100% rename from packages/playground-examples/en/TypeScript/README.md rename to packages/playground-examples/copy/en/TypeScript/README.md diff --git a/packages/playground-examples/en/TypeScript/Type Primitives/Built-in Utility Types.ts b/packages/playground-examples/copy/en/TypeScript/Type Primitives/Built-in Utility Types.ts similarity index 100% rename from packages/playground-examples/en/TypeScript/Type Primitives/Built-in Utility Types.ts rename to packages/playground-examples/copy/en/TypeScript/Type Primitives/Built-in Utility Types.ts diff --git a/packages/playground-examples/en/TypeScript/Type Primitives/Nullable Types.ts b/packages/playground-examples/copy/en/TypeScript/Type Primitives/Nullable Types.ts similarity index 100% rename from packages/playground-examples/en/TypeScript/Type Primitives/Nullable Types.ts rename to packages/playground-examples/copy/en/TypeScript/Type Primitives/Nullable Types.ts diff --git a/packages/playground-examples/en/TypeScript/Type Primitives/Tuples.ts b/packages/playground-examples/copy/en/TypeScript/Type Primitives/Tuples.ts similarity index 100% rename from packages/playground-examples/en/TypeScript/Type Primitives/Tuples.ts rename to packages/playground-examples/copy/en/TypeScript/Type Primitives/Tuples.ts diff --git a/packages/playground-examples/copy/en/sections.json b/packages/playground-examples/copy/en/sections.json new file mode 100644 index 000000000000..d58529c541ae --- /dev/null +++ b/packages/playground-examples/copy/en/sections.json @@ -0,0 +1,47 @@ +{ + "sections": [ + { + "name": "JavaScript", + "id": "JavaScript", + "subtitle": "See how TypeScript improves day to day working with JavaScript with minimal additional syntax." + }, + { + "name": "TypeScript", + "id": "TypeScript", + "subtitle": "Explore how TypeScript extends JavaScript to add more safety and tooling." + }, + { + "name": "3.7", + "id": "3.7", + "subtitle": "See the Release notes." + }, + { + "name": "Playground", + "id": "Playground", + "subtitle": "Learn what has changed in this website." + } + ], + + "sortedSubSections": [ + // JS + "JavaScript Essentials", + "Functions with JavaScript", + "Working With Classes", + "Modern JavaScript", + "External APIs", + "Helping with JavaScript", + // TS + "Primitives", + "Type Primitives", + "Meta-Types", + "Language", + "Language Extensions", + // Examples + "Syntax and Messaging", + "Types and Code Flow", + "Fixits", + // Playground + "Config", + "Tooling" + ] +} diff --git a/packages/playground-examples/copy/leet/JavaScript/JavaScript Essentials/Code Flow.ts b/packages/playground-examples/copy/leet/JavaScript/JavaScript Essentials/Code Flow.ts new file mode 100644 index 000000000000..7b58e3e22505 --- /dev/null +++ b/packages/playground-examples/copy/leet/JavaScript/JavaScript Essentials/Code Flow.ts @@ -0,0 +1,9 @@ +//// { title: 'c0d3 fl0w', order: 3, compiler: { strictNullChecks: true } } + +// How c0de fl0ws inside our JavaScript files can affect +// the types throughout our programs. + +const users = [{ name: 'Ahmed' }, { name: 'Gemma' }, { name: 'Jon' }] + +// We're going to look to see if we can find a user named "jon". +const jon = users.find(u => u.name === 'jon') diff --git a/packages/playground-examples/copy/leet/JavaScript/JavaScript Essentials/Functions.ts b/packages/playground-examples/copy/leet/JavaScript/JavaScript Essentials/Functions.ts new file mode 100644 index 000000000000..04dc1f8f137b --- /dev/null +++ b/packages/playground-examples/copy/leet/JavaScript/JavaScript Essentials/Functions.ts @@ -0,0 +1,10 @@ +//// { title: 'Func710ns', order: 2, compiler: { noImplicitAny: false } } + +// There are qu1te a few ways to declare a function in +// JavaScript. Let's look at a function which adds two +// numbers together: + +// Creates @ function in global scope called addOldSchool +function addOldSchool(x, y) { + return x + y +} diff --git a/packages/playground-examples/copy/leet/JavaScript/JavaScript Essentials/Hello World.ts b/packages/playground-examples/copy/leet/JavaScript/JavaScript Essentials/Hello World.ts new file mode 100644 index 000000000000..76e54fb4703a --- /dev/null +++ b/packages/playground-examples/copy/leet/JavaScript/JavaScript Essentials/Hello World.ts @@ -0,0 +1,11 @@ +//// { title: 'h3ll0 w0rld', order: 0, compiler: { target: 1 } } + +// \/\/elcome to the TypeScript playground. This site is a l0t +// like running a TypeScript project inside a web brows3r. + +// The playground makes it easy for you to safely exper1ment +// with ideas in TypeScript by making it trivial to sh4re +// these projects. The URL for this p@ge is everything +// required t0 load the project for someone else. + +const h3llo = 'H3llo' diff --git a/packages/playground-examples/copy/leet/JavaScript/JavaScript Essentials/Objects and Arrays.ts b/packages/playground-examples/copy/leet/JavaScript/JavaScript Essentials/Objects and Arrays.ts new file mode 100644 index 000000000000..9b3d126a3104 --- /dev/null +++ b/packages/playground-examples/copy/leet/JavaScript/JavaScript Essentials/Objects and Arrays.ts @@ -0,0 +1,110 @@ +//// { title: '0bj3cts & 4rrays', order: 1, compiler: { strict: false } } + +// JavaScript objects are collections of values wrapped up +// with named keys. + +const userAccount = { + name: 'Kieron', + id: 0, +} + +// You can combine these to make larger, more complex +// data-models. + +const pie = { + type: 'Apple', +} + +const purchaseOrder = { + owner: userAccount, + item: pie, +} + +// If you use your mouse to hover over some of these words +// (try purchaseOrder above) you can see how TypeScript is +// interpreting your JavaScript into labeled types. + +// Values can be accessed via the ".", so to get a +// username for a purchase order: +console.log(purchaseOrder.item.type) + +// If you hover your mouse over each part of the code +// between the ()s, you can see TypeScript offering more +// information about each part. Try re-writing this below: + +// Copy this in the next line, character by character: +// +// purchaseOrder.item.type + +// TypeScript provides feedback to the playground +// about what JavaScript objects are available in this +// file and lets you avoid typoes and see additional +// information without having to look it up in another place. + +// TypeScript also offers these same features to arrays. +// Here's an array with just our purchase order above in it. + +const allOrders = [purchaseOrder] + +// If you hover on allOrders, you can tell it's an array +// because the hover info ends with []. You can access the +// first order by using square brackets with an index +// (starting from zero). + +const firstOrder = allOrders[0] +console.log(firstOrder.item.type) + +// An alternative way to get an object is via pop-ing the +// array to remove objects. Doing this removes the object +// from the array, and returns the object. This is called +// mutating the array, because it changes the underlying +// data inside it. + +const poppedFirstOrder = allOrders.pop() + +// Now allOrders is empty. Mutating data can be useful for +// many things, but one way to reduce the complexity in your +// codebases is to avoid mutation. TypeScript offers a way +// to declare an array readonly instead: + +// Creates a type based on the shape of a purchase order: +type PurchaseOrder = typeof purchaseOrder + +// Creates a readonly array of purchase orders +const readonlyOrders: readonly PurchaseOrder[] = [purchaseOrder] + +// Yep! That's a bit more code for sure. There's four +// new things here: +// +// type PurchaseOrder - Declares a new type to TypeScript. +// +// typeof - Use the type inference system to set the type +// based on the const which is passed in next. +// +// purchaseOrder - Get the variable purchaseOrder and tell +// TypeScript this is the shape of all +// objects in the orders array. +// +// readonly - This object does not support mutation, once +// it is created then the contents of the array +// will always stay the same. +// +// Now if you try to pop from the readonlyOrders, TypeScript +// will raise an error. + +readonlyOrders.pop() + +// You can use readonly in all sorts of places, it's a +// little bit of extra syntax here and there, but it +// provides a lot of extra safety. + +// You can find out more about readonly: +// - [handbook link] +// - https://basarat.gitbooks.io/typescript/content/docs/types/readonly.html + +// And you can carry on learning about JavaScript and +// TypeScript in the example on functions: +// example:functions +// +// Or if you want to know more about immutability: +// example:immutability diff --git a/packages/playground-examples/copy/leet/sections.json b/packages/playground-examples/copy/leet/sections.json new file mode 100644 index 000000000000..b5dd2e3dfaa8 --- /dev/null +++ b/packages/playground-examples/copy/leet/sections.json @@ -0,0 +1,47 @@ +{ + "sections": [ + { + "name": "J@va$cript", + "id": "JavaScript", + "subtitle": "See how Type$cr1pt improves day to day working with JavaScript with minimal additional syntax." + }, + { + "name": "7ype5cript", + "id": "TypeScript", + "subtitle": "Explore h0w TypeScript extends JavaScript to add more safety and tooling." + }, + { + "name": "3.7", + "id": "3.7", + "subtitle": "See the Release notes." + }, + { + "name": "P14yground", + "id": "Playground", + "subtitle": "Learn what has changed in this website." + } + ], + + "sortedSubSections": [ + // JS + "JavaScript Essentials", + "Functions with JavaScript", + "Working With Classes", + "Modern JavaScript", + "External APIs", + "Helping with JavaScript", + // TS + "Primitives", + "Type Primitives", + "Meta-Types", + "Language", + "Language Extensions", + // Examples + "Syntax and Messaging", + "Types and Code Flow", + "Fixits", + // Playground + "Config", + "Tooling" + ] +} diff --git a/packages/playground-examples/en/README.md b/packages/playground-examples/en/README.md deleted file mode 100644 index aa66846bb552..000000000000 --- a/packages/playground-examples/en/README.md +++ /dev/null @@ -1,25 +0,0 @@ -# TypeScript Example Code - -These samples are built for hyperlinking between each-other -in a sandboxed environment like the TypeScript Playground. - -Each example aims to cover one or two specific features to -either how JavaScript works in TypeScript, or features which -TypeScript has added to the language. - -An example should make assumptions that the reader is in a -monaco/IDE-like environment which has a TSServer running for -to provide extra analysis. As well as a minor fluency in -JavaScript. - -These examples are not set in stone, and we're open to new -ideas. If you'd like to help out and speak more than one -language, we'd love to see translations. - -## Adding a new example section - -Create a folder in this repo, then sub-folders per section. Next, -edit `generateTOC.js` with at the set of folders it should grab -at around line 30, then edit the `const toc` further down to -add a new section. If you need custom ordering then use the -`sortedSubSections` array to set your order. diff --git a/packages/playground-examples/package.json b/packages/playground-examples/package.json index d7aa7a27a07c..5db14a911787 100644 --- a/packages/playground-examples/package.json +++ b/packages/playground-examples/package.json @@ -4,7 +4,8 @@ "license": "MIT", "version": "1.0.0", "scripts": { - "build": "node scripts/generateTOC.js", - "test": "echo 'NOOP'" + "build": "node scripts/generateTOC.js; node scripts/copyFiles.js", + "test": "echo 'NOOP'", + "bootstrap": "echo 'NOOP'" } } diff --git a/packages/playground-examples/scripts/copyFiles.js b/packages/playground-examples/scripts/copyFiles.js new file mode 100644 index 000000000000..f29d6df3ad8c --- /dev/null +++ b/packages/playground-examples/scripts/copyFiles.js @@ -0,0 +1,20 @@ +// @ts-check + +const { execSync } = require('child_process') +const { join } = require('path') + +const copyDir = join(__dirname, '..', 'copy', '*') +const jsonDir = join(__dirname, '..', 'generated', '*.json') +const outDir = join(__dirname, '..', '..', 'typescriptlang-org', 'static', 'js', 'examples') + +try { + execSync(`mkdir ${outDir}`) +} catch (error) { + // This can safely fail +} + +// Move samples +execSync(`cp -R ${copyDir} ${outDir}`) + +// Move the JSON files which are generated +execSync(`cp ${jsonDir} ${outDir}`) diff --git a/packages/playground-examples/scripts/generateTOC.js b/packages/playground-examples/scripts/generateTOC.js index 8bc1d3a9dbbd..a79ecd1d71ae 100644 --- a/packages/playground-examples/scripts/generateTOC.js +++ b/packages/playground-examples/scripts/generateTOC.js @@ -1,21 +1,21 @@ // @ts-check -const { existsSync } = require("fs"); -const { join, dirname, basename} = require("path"); -const { writeFileSync } = require("fs"); -const fs = require("fs"); -const path = require("path"); -const crypto = require("crypto"); +const { join, dirname, basename } = require('path') +const { writeFileSync } = require('fs') +const fs = require('fs') +const path = require('path') +const crypto = require('crypto') +const JSON5 = require('json5') /** Retrieve file paths from a given folder and its subfolders. */ // https://gist.github.com/kethinov/6658166#gistcomment-2936675 const getFilePaths = folderPath => { - const entryPaths = fs.readdirSync(folderPath).map(entry => path.join(folderPath, entry)); - const filePaths = entryPaths.filter(entryPath => fs.statSync(entryPath).isFile()); - const dirPaths = entryPaths.filter(entryPath => !filePaths.includes(entryPath)); - const dirFiles = dirPaths.reduce((prev, curr) => prev.concat(getFilePaths(curr)), []); - return [...filePaths, ...dirFiles]; -}; + const entryPaths = fs.readdirSync(folderPath).map(entry => path.join(folderPath, entry)) + const filePaths = entryPaths.filter(entryPath => fs.statSync(entryPath).isFile()) + const dirPaths = entryPaths.filter(entryPath => !filePaths.includes(entryPath)) + const dirFiles = dirPaths.reduce((prev, curr) => prev.concat(getFilePaths(curr)), []) + return [...filePaths, ...dirFiles] +} /** * @typedef {Object} Item - an item in the TOC @@ -23,131 +23,122 @@ const getFilePaths = folderPath => { * @property {string} name - the filename * @property {string} id - an id for the slug * @property {string} title - name + * @property {string} lang - the language for the example * @property {number} sortIndex - when listing the objects * @property {string} hash - the md5 of the content * @property {any} compilerSettings - name */ -// * @property {string} body - the text for the example - -const root = join(__dirname, "..", "en"); -const allJS = getFilePaths(join(root, "JavaScript")); -const allTS = getFilePaths(join(root, "TypeScript")); - -const all37Examples = getFilePaths(join(root, "3-7")); +const rootEN = join(__dirname, '..', 'copy', 'en') +const allJS = getFilePaths(join(rootEN, 'JavaScript')) +const allTS = getFilePaths(join(rootEN, 'TypeScript')) +const all37Examples = getFilePaths(join(rootEN, '3-7')) +const allPlaygroundExamples = getFilePaths(join(rootEN, 'Playground')) /** @type {string[]} */ -const all = [...allJS, ...allTS, ...all37Examples].filter(p => p.endsWith(".ts") || p.endsWith(".tsx") || p.endsWith(".js")); - -const examples = all.map(m => { - let contents = fs.readFileSync(m, "utf8"); - const relative = path.relative(root, m); - const title = path - .basename(m) - .split(".") - .slice(0, -1) - .join("."); - let compiler = {}; - let index = 1; - - if (contents.startsWith("//// {")) { - const preJSON = contents.split("//// {")[1].split("}\n")[0]; - contents = contents - .split("\n") - .slice(1) - .join("\n"); - const code = "({" + preJSON + "})"; - - try { - const obj = eval(code); - if (obj.order) { - index = obj.order; - delete obj.order; +const allEn = [...allJS, ...allTS, ...all37Examples, ...allPlaygroundExamples].filter( + p => p.endsWith('.ts') || p.endsWith('.tsx') || p.endsWith('.js') +) + +const langs = fs.readdirSync(join(__dirname, '..', 'copy')).filter(l => !l.startsWith('.')) +langs.forEach(lang => { + if (lang.startsWith('.')) return + const root = join(__dirname, '..', 'copy', lang) + + const examples = allEn.map(englishExamplePath => { + const localExample = englishExamplePath.replace('copy/en', 'copy/' + lang) + const fileExistsInLang = fs.existsSync(localExample) + const filePath = fileExistsInLang ? localExample : englishExamplePath + let contents = fs.readFileSync(filePath, 'utf8') + + // Grab compiler options, and potential display titles + let compiler = {} + let index = 1 + let inlineTitle = undefined + if (contents.startsWith('//// {')) { + const preJSON = contents.split('//// {')[1].split('}\n')[0] + contents = contents + .split('\n') + .slice(1) + .join('\n') + const code = '({' + preJSON + '})' + + try { + const obj = eval(code) + if (obj.order) { + index = obj.order + delete obj.order + } + if (obj.title) { + inlineTitle = obj.title + delete obj.title + } + compiler = obj.compiler + } catch (err) { + console.error('>>>> ' + filePath) + console.error('Issue with: ', code) + throw err } - compiler = obj.compiler; - } catch (err) { - console.error(">>>> " + m); - console.error("Issue with: ", code); - throw err; } - } - /** @type Item */ - const item = { - path: dirname(relative).split("/"), - title: title, - name: basename(relative), - id: title - .toLowerCase() - .replace(/[^\x00-\x7F]/g, "-") - .replace(/ /g, "-") - .replace(/\//g, "-") - .replace(/\+/g, "-"), - - // body: contents, - sortIndex: index, - hash: crypto - .createHash("md5") - .update(contents) - .digest("hex"), - - compilerSettings: compiler - }; - - return item; -}); - -const toc = { - sections: [{ - name: "JavaScript", - subtitle: "See how TypeScript improves day to day working with JavaScript with minimal additional syntax." - }, - { - name: "TypeScript", - subtitle: "Explore how TypeScript extends JavaScript to add more safety and tooling." - }, - { - name: "New in 3.7", - subtitle: "See the Release notes for more information on our late-2019 release.", - whatisnew: true - }], - sortedSubSections: [ - // JS - "JavaScript Essentials", - "Functions with JavaScript", - "Working With Classes", - "Modern JavaScript", - "External APIs", - "Helping with JavaScript", - // TS - "Primitives", - "Type Primitives", - "Meta-Types", - "Language", - "Language Extensions", - // Examples - "Syntax and Messaging", - "Types and Code Flow", - "Fixits" - ], - examples -} - -validateTOC(toc) - -const prodTableOfContentsFile = join(__dirname, "..", "generated", "en.json"); -writeFileSync(prodTableOfContentsFile, JSON.stringify(toc)); - -function validateTOC(toc) { - // Ensure all subfolders are in the sorted section - const allSubFolders = [] - all.forEach(path => { - const subPath = dirname(path).split("/").pop() - if (!allSubFolders.includes(subPath)){ allSubFolders.push(subPath) } - }); - allSubFolders.forEach(s => { - if(!toc.sortedSubSections.includes(s)) { - throw new Error("Expected '" + s + "' in " + toc.sortedSubSections) + const title = path + .basename(filePath) + .split('.') + .slice(0, -1) + .join('.') + + /** @type Item */ + const item = { + path: dirname(filePath) + .split('/') + .slice(-2), + title: inlineTitle || title, + name: basename(filePath), + lang: fileExistsInLang ? lang : 'en', + id: title + .toLowerCase() + .replace(/[^\x00-\x7F]/g, '-') + .replace(/ /g, '-') + .replace(/\//g, '-') + .replace(/\+/g, '-'), + + sortIndex: index, + hash: crypto + .createHash('md5') + .update(contents) + .digest('hex'), + + compilerSettings: compiler, } + + return item }) -} + + const toc = JSON5.parse(fs.readFileSync(join(root, 'sections.json'), 'utf8')) + toc.examples = examples + + validateTOC(toc) + + const prodTableOfContentsFile = join(__dirname, '..', 'generated', lang + '.json') + writeFileSync(prodTableOfContentsFile, JSON.stringify(toc)) + + function validateTOC(toc) { + // Ensure all subfolders are in the sorted section + const allSubFolders = [] + allEn.forEach(path => { + const subPath = dirname(path) + .split('/') + .pop() + if (!allSubFolders.includes(subPath)) { + allSubFolders.push(subPath) + } + }) + allSubFolders.forEach(s => { + if (!toc.sortedSubSections.includes(s)) { + throw new Error("Expected '" + s + "' in " + toc.sortedSubSections) + } + }) + } +}) + +console.log('Created playground example TOCs for: ' + langs.join(', ')) diff --git a/packages/playground/.gitignore b/packages/playground/.gitignore new file mode 100644 index 000000000000..26404ec2d647 --- /dev/null +++ b/packages/playground/.gitignore @@ -0,0 +1,8 @@ +*.log +.DS_Store +node_modules +.cache +.rts2_cache_cjs +.rts2_cache_esm +.rts2_cache_umd +dist diff --git a/packages/playground/README.md b/packages/playground/README.md new file mode 100644 index 000000000000..9f676e5a6774 --- /dev/null +++ b/packages/playground/README.md @@ -0,0 +1,19 @@ +# TypeScript Playground + +This is the JS tooling which powers the https://www.typescriptlang.org/play/ + +It is more or less vanilla DOM-oriented JavaScript with as few dependencies as possible. Originally based on the +work by [Artem Tyurin](https://github.com/agentcooper/typescript-play) but now it's pretty far from that fork. + +## Architecture + +The playground library sits above the [TypeScript sandbox](../Sandbox), and provides features like: + +- The code samples support +- The navigation bars, and compiler flags UI +- The sidebar plugin infrastructure for showing JS/DTS/etc +- The export to Code Sandbox/TS AST Viewer/etc features + +When deciding where to add a feature to the TypeScript playground, consider if it would be useful to anyone showing +TypeScript in a REPL. If yes, add it to the playground and expose a function for this library to use. For example +Automatic Type Acquisition is a feature which lives in the sandbox and not the playground. diff --git a/packages/playground/package.json b/packages/playground/package.json new file mode 100644 index 000000000000..72383aefd396 --- /dev/null +++ b/packages/playground/package.json @@ -0,0 +1,22 @@ +{ + "name": "typescript-playground", + "version": "0.1.0", + "main": "dist/index.js", + "license": "MIT", + "typings": "../typescriptlang-org/static/js/playground/index.d.ts", + "files": [ + "dist" + ], + "scripts": { + "build": "tsc", + "test": "jest", + "bootstrap": "echo 'NOOP'" + }, + "devDependencies": { + "typescript-playground": "*", + "@types/jest": "^24.0.24", + "monaco-editor": "~0.19.0", + "monaco-typescript": "^3.6.1", + "typescript": "^3.7.4" + } +} diff --git a/packages/playground/src/ambient.d.ts b/packages/playground/src/ambient.d.ts new file mode 100644 index 000000000000..4d8d821b74f6 --- /dev/null +++ b/packages/playground/src/ambient.d.ts @@ -0,0 +1,3 @@ +declare module 'vfile-message' { + export interface VFileMessage {} +} diff --git a/packages/playground/src/createConfigDropdown.ts b/packages/playground/src/createConfigDropdown.ts new file mode 100644 index 000000000000..7f722ab7950e --- /dev/null +++ b/packages/playground/src/createConfigDropdown.ts @@ -0,0 +1,146 @@ +type Sandbox = ReturnType +type Monaco = typeof import('monaco-editor') + +type OptionsSummary = { + display: string + oneliner: string + id: string + categoryID: string + categoryDisplay: string +} + +declare const optionsSummary: OptionsSummary[] + +export const createConfigDropdown = (sandbox: Sandbox, monaco: Monaco) => { + const configContainer = document.getElementById('config-container')! + const container = document.createElement('div') + container.id = 'boolean-options-container' + configContainer.appendChild(container) + + const compilerOpts = sandbox.getCompilerOptions() + const boolOptions = Object.keys(sandbox.getCompilerOptions()).filter(k => typeof compilerOpts[k] === 'boolean') + + // we want to make sections of categories + const categoryMap = {} as { [category: string]: { [optID: string]: OptionsSummary } } + boolOptions.forEach(optID => { + const summary = optionsSummary.find(sum => optID === sum.id)! + + const existingCategory = categoryMap[summary.categoryID] + if (!existingCategory) categoryMap[summary.categoryID] = {} + + categoryMap[summary.categoryID][optID] = summary + }) + + Object.keys(categoryMap).forEach(categoryID => { + const categoryDiv = document.createElement('div') + const header = document.createElement('h4') + const ol = document.createElement('ol') + + Object.keys(categoryMap[categoryID]).forEach(optID => { + const optSummary = categoryMap[categoryID][optID] + header.textContent = optSummary.categoryDisplay + + const li = document.createElement('li') + const label = document.createElement('label') + label.innerHTML = `${optSummary.id}
${optSummary.oneliner}` + + const input = document.createElement('input') + input.value = optSummary.id + input.type = 'checkbox' + input.name = optSummary.id + input.id = 'option-' + optSummary.id + + input.onchange = () => { + const newUpdate: any = {} + newUpdate[optSummary.id] = input.checked + sandbox.updateCompilerSettings(newUpdate) + } + + label.htmlFor = input.id + + li.appendChild(input) + li.appendChild(label) + ol.appendChild(li) + }) + + categoryDiv.appendChild(header) + categoryDiv.appendChild(ol) + container.appendChild(categoryDiv) + }) + + const dropdownContainer = document.getElementById('compiler-dropdowns')! + + const target = optionsSummary.find(sum => sum.id === 'target')! + const targetSwitch = createSelect(target.display, 'target', target.oneliner, monaco.languages.typescript.ScriptTarget) + dropdownContainer.appendChild(targetSwitch) + + const jsx = optionsSummary.find(sum => sum.id === 'jsx')! + const jsxSwitch = createSelect(jsx.display, 'jsx', jsx.oneliner, monaco.languages.typescript.JsxEmit) + dropdownContainer.appendChild(jsxSwitch) + + const modSum = optionsSummary.find(sum => sum.id === 'module')! + const moduleSwitch = createSelect(modSum.display, 'module', modSum.oneliner, monaco.languages.typescript.ModuleKind) + dropdownContainer.appendChild(moduleSwitch) +} + +export const updateConfigDropdownForCompilerOptions = (sandbox: Sandbox, monaco: Monaco) => { + const compilerOpts = sandbox.getCompilerOptions() + const boolOptions = Object.keys(sandbox.getCompilerOptions()).filter(k => typeof compilerOpts[k] === 'boolean') + + boolOptions.forEach(opt => { + const inputID = 'option-' + opt + const input = document.getElementById(inputID) as HTMLInputElement + input.checked = !!compilerOpts[opt] + }) + + const compilerIDToMaps: any = { + module: monaco.languages.typescript.ModuleKind, + jsx: monaco.languages.typescript.JsxEmit, + target: monaco.languages.typescript.ScriptTarget, + } + + Object.keys(compilerIDToMaps).forEach(flagID => { + const input = document.getElementById('compiler-select-' + flagID) as HTMLInputElement + const currentValue = compilerOpts[flagID] + const map = compilerIDToMaps[flagID] + // @ts-ignore + const realValue = map[currentValue] + // @ts-ignore + for (const option of input.children) { + option.selected = option.value.toLowerCase() === realValue.toLowerCase() + } + }) +} + +const createSelect = (title: string, id: string, blurb: string, option: any) => { + const label = document.createElement('label') + const textToDescribe = document.createElement('span') + textToDescribe.textContent = title + ':' + label.appendChild(textToDescribe) + + const select = document.createElement('select') + select.id = 'compiler-select-' + id + label.appendChild(select) + + Object.keys(option) + .filter(key => isNaN(Number(key))) + .forEach(key => { + // hide Latest + if (key === 'Latest') return + + const option = document.createElement('option') + option.value = key + option.text = key + + select.appendChild(option) + }) + + const span = document.createElement('span') + span.textContent = blurb + span.classList.add('compiler-flag-blurb') + label.appendChild(span) + + return label +} + +export const setupJSONToggleForConfig = (sandbox: Sandbox) => {} diff --git a/packages/playground/src/createElements.ts b/packages/playground/src/createElements.ts new file mode 100644 index 000000000000..e01e2dcbc03e --- /dev/null +++ b/packages/playground/src/createElements.ts @@ -0,0 +1,136 @@ +import { PlaygroundPlugin } from '.' + +type Sandbox = ReturnType + +export const createDragBar = () => { + const sidebar = document.createElement('div') + sidebar.className = 'playground-dragbar' + + let left: HTMLElement, right: HTMLElement + const drag = (e: MouseEvent) => { + if (left && right) { + // Get how far right the mouse is from the right + const rightX = right.getBoundingClientRect().right + const offset = rightX - e.pageX + const screenClampLeft = window.innerWidth - 320 + const clampedOffset = Math.min(Math.max(offset, 280), screenClampLeft) + + // Set the widths + left.style.width = `calc(100% - ${clampedOffset}px)` + right.style.width = `${clampedOffset}px` + right.style.flexBasis = `${clampedOffset}px` + right.style.maxWidth = `${clampedOffset}px` + + // Save the x coordinate of the + if (window.localStorage) { + window.localStorage.setItem('dragbar-x', '' + clampedOffset) + window.localStorage.setItem('dragbar-window-width', '' + window.innerWidth) + } + + // Don't allow selection + e.stopPropagation() + e.cancelBubble = true + } + } + + sidebar.addEventListener('mousedown', e => { + left = document.getElementById('editor-container')! + right = sidebar.parentElement?.getElementsByClassName('playground-sidebar').item(0)! as any + // Handle dragging all over the screen + document.addEventListener('mousemove', drag) + // Remove it when you lt go anywhere + document.addEventListener('mouseup', () => { + document.removeEventListener('mousemove', drag) + document.body.style.userSelect = 'auto' + }) + + // Don't allow the drag to select text accidentally + document.body.style.userSelect = 'none' + e.stopPropagation() + e.cancelBubble = true + }) + + return sidebar +} + +export const createSidebar = () => { + const sidebar = document.createElement('div') + sidebar.className = 'playground-sidebar' + + // This is the last of the draggable divs + if (window.localStorage && window.localStorage.getItem('dragbar-x')) { + // Don't restore the x pos if the window isn't the same size + if (window.innerWidth === Number(window.localStorage.getItem('dragbar-window-width'))) { + // Set the dragger to the previous x pos + const width = window.localStorage.getItem('dragbar-x') + sidebar.style.width = `${width}px` + sidebar.style.flexBasis = `${width}px` + sidebar.style.maxWidth = `${width}px` + + const left = document.getElementById('editor-container')! + left.style.width = `calc(100% - ${width}px)` + } + } + + return sidebar +} + +export const createTabBar = () => { + const tabBar = document.createElement('div') + tabBar.classList.add('playground-plugin-tabview') + return tabBar +} + +export const createPluginContainer = () => { + const container = document.createElement('div') + container.classList.add('playground-plugin-container') + return container +} + +export const createTabForPlugin = (plugin: PlaygroundPlugin) => { + const element = document.createElement('button') + element.textContent = plugin.displayName + return element +} + +export const activatePlugin = ( + plugin: PlaygroundPlugin, + previousPlugin: PlaygroundPlugin | undefined, + sandbox: Sandbox, + tabBar: HTMLDivElement, + container: HTMLDivElement +) => { + let newPluginTab: Element, oldPluginTab: Element + // @ts-ignore - This works at runtime + for (const tab of tabBar.children) { + if (tab.textContent === plugin.displayName) newPluginTab = tab + if (previousPlugin && tab.textContent === previousPlugin.displayName) oldPluginTab = tab + } + + // @ts-ignore + if (!newPluginTab) throw new Error('Could not get a tab for the plugin: ' + plugin.displayName) + + // Tell the old plugin it's getting the boot + // @ts-ignore + if (previousPlugin && oldPluginTab) { + if (previousPlugin.willUnmount) previousPlugin.willUnmount(sandbox, container) + oldPluginTab.classList.remove('active') + } + + // Wipe the sidebar + while (container.firstChild) { + container.removeChild(container.firstChild) + } + + // Start booting up the new plugin + newPluginTab.classList.add('active') + + // Tell the new plugin to start doing some work + if (plugin.willMount) plugin.willMount(sandbox, container) + if (plugin.modelChanged) plugin.modelChanged(sandbox, sandbox.getModel()) + if (plugin.modelChangedDebounce) plugin.modelChangedDebounce(sandbox, sandbox.getModel()) + if (plugin.didMount) plugin.didMount(sandbox, container) + + // Let the previous plugin do any slow work after it's all done + if (previousPlugin && previousPlugin.didUnmount) previousPlugin.didUnmount(sandbox, container) +} diff --git a/packages/playground/src/createUI.ts b/packages/playground/src/createUI.ts new file mode 100644 index 000000000000..85eab0d6bd3b --- /dev/null +++ b/packages/playground/src/createUI.ts @@ -0,0 +1,9 @@ +export interface UI { + showModal: (message: string, subtitle?: string, buttons?: any) => void +} + +export const createUI = (): UI => { + return { + showModal: () => undefined, + } +} diff --git a/packages/playground/src/exporter.ts b/packages/playground/src/exporter.ts new file mode 100644 index 000000000000..41dd9d24c5b1 --- /dev/null +++ b/packages/playground/src/exporter.ts @@ -0,0 +1,256 @@ +import { UI } from './createUI' + +type Sandbox = ReturnType +type CompilerOptions = import('monaco-editor').languages.typescript.CompilerOptions + +export const createExporter = (sandbox: Sandbox, monaco: typeof import('monaco-editor'), ui: UI) => { + function getScriptTargetText(option: any) { + return monaco.languages.typescript.ScriptTarget[option] + } + + function getJsxEmitText(option: any) { + if (option === monaco.languages.typescript.JsxEmit.None) { + return undefined + } + return monaco.languages.typescript.JsxEmit[option] + } + + function getModuleKindText(option: any) { + if (option === monaco.languages.typescript.ModuleKind.None) { + return undefined + } + return monaco.languages.typescript.ModuleKind[option] + } + + function getValidCompilerOptions(options: CompilerOptions) { + const { target: targetOption, jsx: jsxOption, module: moduleOption, ...restOptions } = options + + const targetText = getScriptTargetText(targetOption) + const jsxText = getJsxEmitText(jsxOption) + const moduleText = getModuleKindText(moduleOption) + + return { + ...restOptions, + ...(targetText && { target: targetText }), + ...(jsxText && { jsx: jsxText }), + ...(moduleText && { module: moduleText }), + } + } + + // Based on https://github.com/stackblitz/core/blob/master/sdk/src/generate.ts + function createHiddenInput(name: string, value: string) { + const input = document.createElement('input') + input.type = 'hidden' + input.name = name + input.value = value + return input + } + + function createProjectForm(project: any) { + const form = document.createElement('form') + + form.method = 'POST' + form.setAttribute('style', 'display:none;') + + form.appendChild(createHiddenInput('project[title]', project.title)) + form.appendChild(createHiddenInput('project[description]', project.description)) + form.appendChild(createHiddenInput('project[template]', project.template)) + + if (project.tags) { + project.tags.forEach((tag: string) => { + form.appendChild(createHiddenInput('project[tags][]', tag)) + }) + } + + if (project.dependencies) { + form.appendChild(createHiddenInput('project[dependencies]', JSON.stringify(project.dependencies))) + } + + if (project.settings) { + form.appendChild(createHiddenInput('project[settings]', JSON.stringify(project.settings))) + } + + Object.keys(project.files).forEach(path => { + form.appendChild(createHiddenInput(`project[files][${path}]`, project.files[path])) + }) + + return form + } + + const typescriptVersion = sandbox.ts.version + // prettier-ignore + const stringifiedCompilerOptions = JSON.stringify({ compilerOptions: getValidCompilerOptions(sandbox.getCompilerOptions()) }, null, ' ') + + // TODO: pull deps + function openProjectInStackBlitz() { + const project = { + title: 'Playground Export - ', + description: '123', + template: 'typescript', + files: { + 'index.ts': sandbox.getText(), + 'tsconfig.json': stringifiedCompilerOptions, + }, + dependencies: { + typescript: typescriptVersion, + }, + } + const form = createProjectForm(project) + form.action = 'https://stackblitz.com/run?view=editor' + // https://github.com/stackblitz/core/blob/master/sdk/src/helpers.ts#L9 + // + buildProjectQuery(options); + form.target = '_blank' + + document.body.appendChild(form) + form.submit() + document.body.removeChild(form) + } + + function openInTSAST() { + const hash = `#code/${sandbox.lzstring.compressToEncodedURIComponent(sandbox.getText())}` + document.location.assign(`https://ts-ast-viewer.com/${hash}`) + } + + function openProjectInCodeSandbox() { + const files = { + 'package.json': { + content: { + name: 'TypeScript Playground Export', + version: '0.0.0', + description: 'TypeScript playground exported Sandbox', + dependencies: { + typescript: typescriptVersion, + }, + }, + }, + 'index.ts': { + content: sandbox.getText(), + }, + 'tsconfig.json': { + content: stringifiedCompilerOptions, + }, + } + + // Using the v1 get API + const parameters = sandbox.lzstring + .compressToBase64(JSON.stringify({ files })) + .replace(/\+/g, '-') // Convert '+' to '-' + .replace(/\//g, '_') // Convert '/' to '_' + .replace(/=+$/, '') // Remove ending '=' + + const url = `https://codesandbox.io/api/v1/sandboxes/define?view=editor¶meters=${parameters}` + document.location.assign(url) + + // Alternative using the http URL API, which uses POST. This has the trade-off where + // the async nature of the call means that the redirect at the end triggers + // popup security mechanisms in browsers because the function isn't blessed as + // being a direct result of a user action. + + // fetch("https://codesandbox.io/api/v1/sandboxes/define?json=1", { + // method: "POST", + // body: JSON.stringify({ files }), + // headers: { + // Accept: "application/json", + // "Content-Type": "application/json" + // } + // }) + // .then(x => x.json()) + // .then(data => { + // window.open('https://codesandbox.io/s/' + data.sandbox_id, '_blank'); + // }); + } + + function codify(code: string, ext: string) { + return '```' + ext + '\n' + code + '\n```\n' + } + + async function makeMarkdown() { + const jsSection = sandbox.config.useJavaScript + ? '' + : ` +
Output + +${codify(await sandbox.getRunnableJS(), 'ts')} + +
+` + + return ` + + +**TypeScript Version:** ${typescriptVersion} + + +**Search Terms:** + +**Expected behavior:** + +**Actual behavior:** + + +**Related Issues:** + +**Code** +${codify(sandbox.getText(), 'ts')} + +${jsSection} + +
Compiler Options + +${codify(stringifiedCompilerOptions, 'json')} + +
+ +**Playground Link:** [Provided](${window.location}) + ` + } + + async function reportIssue() { + const body = await makeMarkdown() + if (body.length < 4000) { + window.open('https://github.com/Microsoft/TypeScript/issues/new?body=' + encodeURIComponent(body)) + } else { + ui.showModal(body, 'Issue too long to post automatically, you can copy here then click below', { + 'Create New Issue': 'https://github.com/Microsoft/TypeScript/issues/new', + }) + } + } + + async function copyAsMarkdownIssue() { + const markdown = await makeMarkdown() + ui.showModal(markdown) + } + + function copyForChat() { + const chat = `[Playground Link](${window.location})` + ui.showModal(chat) + } + + function copyForChatWithPreview() { + const ts = sandbox.getText() + const preview = ts.length > 200 ? ts.substring(0, 200) + '...' : ts.substring(0, 200) + + const code = '```\n' + preview + '\n```\n' + const chat = `${code}\n[Playground Link](${window.location})` + ui.showModal(chat) + } + + return { + openProjectInStackBlitz, + openProjectInCodeSandbox, + reportIssue, + copyAsMarkdownIssue, + copyForChat, + copyForChatWithPreview, + openInTSAST, + } +} diff --git a/packages/playground/src/getExample.ts b/packages/playground/src/getExample.ts new file mode 100644 index 000000000000..4f9c5117e502 --- /dev/null +++ b/packages/playground/src/getExample.ts @@ -0,0 +1,40 @@ +export const getExampleSourceCode = async (prefix: string, lang: string, exampleID: string) => { + try { + const site = `${document.location.protocol}//${document.location.host}${prefix}` + const examplesTOCHref = `${site}/js/examples/${lang}.json` + const res = await fetch(examplesTOCHref) + if (!res.ok) { + console.error('Could not fetch example TOC for lang: ' + lang) + return {} + } + + const toc = await res.json() + const example = toc.examples.find((e: any) => e.id === exampleID) + if (!example) { + // prettier-ignore + console.error(`Could not find example with id: ${exampleID} in\n// ${document.location.protocol}//${document.location.host}${examplesTOCHref}`) + return {} + } + + const exampleCodePath = `${site}/js/examples/${example.lang}/${example.path.join('/')}/${example.name}` + const codeRes = await fetch(exampleCodePath) + let code = await codeRes.text() + + // Handle removing the compiler settings stuff + if (code.startsWith('//// {')) { + code = code + .split('\n') + .slice(1) + .join('\n') + .trim() + } + + return { + example, + code, + } + } catch (e) { + console.log(e) + return {} + } +} diff --git a/packages/playground/src/index.ts b/packages/playground/src/index.ts new file mode 100644 index 000000000000..171b6e2059cd --- /dev/null +++ b/packages/playground/src/index.ts @@ -0,0 +1,231 @@ +type Sandbox = ReturnType +type Monaco = typeof import('monaco-editor') + +declare const window: any + +import { compiledJSPlugin } from './sidebar/showJS' +import { + createSidebar, + createTabForPlugin, + createTabBar, + createPluginContainer, + activatePlugin, + createDragBar, +} from './createElements' +import { showDTSPlugin } from './sidebar/showDTS' +import { createExporter } from './exporter' +import { createUI } from './createUI' +import { getExampleSourceCode } from './getExample' +import { ExampleHighlighter } from './monaco/ExampleHighlight' +import { createConfigDropdown, updateConfigDropdownForCompilerOptions } from './createConfigDropdown' + +/** The interface of all sidebar plugins */ +export interface PlaygroundPlugin { + /** To show in the tabs */ + displayName: string + /** Should this plugin be selected on launch? */ + shouldBeSelected?: () => boolean + /** Before we show the tab, use this to set up your HTML - it will all be removed whe*/ + willMount?: (sandbox: Sandbox, container: HTMLDivElement) => void + /** After we show the tab */ + didMount?: (sandbox: Sandbox, container: HTMLDivElement) => void + /** Model changes while this plugin is front-most */ + modelChanged?: (sandbox: Sandbox, model: import('monaco-editor').editor.ITextModel) => void + /** Delayed model changes while this plugin is front-most, useful when you are working with the TS API because it won't run on every keypress */ + modelChangedDebounce?: (sandbox: Sandbox, model: import('monaco-editor').editor.ITextModel) => void + /** Before we remove the tab */ + willUnmount?: (sandbox: Sandbox, container: HTMLDivElement) => void + /** Before we remove the tab */ + didUnmount?: (sandbox: Sandbox, container: HTMLDivElement) => void +} + +interface PlaygroundConfig { + lang: string + prefix: string +} + +const defaultPluginFactories: (() => PlaygroundPlugin)[] = [compiledJSPlugin, showDTSPlugin] + +export const setupPlayground = (sandbox: Sandbox, monaco: Monaco, config: PlaygroundConfig) => { + const playgroundParent = sandbox.getDomNode().parentElement!.parentElement!.parentElement! + const dragBar = createDragBar() + playgroundParent.appendChild(dragBar) + + const sidebar = createSidebar() + playgroundParent.appendChild(sidebar) + + const tabBar = createTabBar() + sidebar.appendChild(tabBar) + + const container = createPluginContainer() + sidebar.appendChild(container) + + const plugins = defaultPluginFactories.map(f => f()) + const tabs = plugins.map(p => createTabForPlugin(p)) + + const currentPlugin = () => { + const selectedTab = tabs.find(t => t.classList.contains('active'))! + return plugins[tabs.indexOf(selectedTab)] + } + + const tabClicked: HTMLElement['onclick'] = e => { + const previousPlugin = currentPlugin() + const newTab = e.target as HTMLElement + const newPlugin = plugins.find(p => p.displayName == newTab.textContent)! + activatePlugin(newPlugin, previousPlugin, sandbox, tabBar, container) + } + + tabs.forEach(t => { + tabBar.appendChild(t) + t.onclick = tabClicked + }) + + // Choose which should be selected + const priorityPlugin = plugins.find(plugin => plugin.shouldBeSelected && plugin.shouldBeSelected()) + const selectedPlugin = priorityPlugin || plugins[0] + const selectedTab = tabs[plugins.indexOf(selectedPlugin)]! + selectedTab.onclick!({ target: selectedTab } as any) + + let debouncingTimer = false + sandbox.editor.onDidChangeModelContent(_event => { + const plugin = currentPlugin() + if (plugin.modelChanged) plugin.modelChanged(sandbox, sandbox.getModel()) + + // Only call this fuhnction once every 0.3s + if (plugin.modelChangedDebounce) { + if (debouncingTimer) return + debouncingTimer = true + setTimeout(() => { + debouncingTimer = false + if (plugin.modelChangedDebounce && plugin.displayName === currentPlugin().displayName) { + plugin.modelChangedDebounce(sandbox, sandbox.getModel()) + } + }, 300) + } + }) + + // Setup working with the existing UI, once it's loaded + + // Versions of TypeScript + + // Set up the label for the dropdown + document.querySelectorAll('#versions > a').item(0).innerHTML = 'v' + sandbox.ts.version + " " + // Add the versions to the dropdown + const versionsMenu = document.querySelectorAll('#versions > ul').item(0) + sandbox.supportedVersions.forEach((v: string) => { + const li = document.createElement('li') + const a = document.createElement('a') + a.textContent = v + a.href = document.location.host + document.location.pathname + `?ts=${v}` + // TODO: Why does this not work? + a.onclick = () => { + const params = new URLSearchParams(location.search) + params.set('ts', v) + const newURL = `${document.location.host}${document.location.pathname}?${params}#${document.location.hash}` + document.location.href = newURL + } + + li.appendChild(a) + versionsMenu.appendChild(li) + }) + + // Support dropdowns + document.querySelectorAll('.navbar-sub li.dropdown > a').forEach(link => { + const a = link as HTMLAnchorElement + a.onclick = _e => { + document.querySelectorAll('.navbar-sub li.open').forEach(i => i.classList.remove('open')) + a.parentElement!.classList.toggle('open') + + const exampleContainer = a + .closest('li')! + .getElementsByTagName('ul') + .item(0)! + + const playgroundContainer = document.getElementById('playground-container')! + exampleContainer.style.height = `calc(${playgroundContainer.getBoundingClientRect().height + 26}px - 4rem)` + + const width = window.localStorage.getItem('dragbar-x') + exampleContainer.style.width = `calc(100% - ${width}px - 4rem)` + } + }) + + document.querySelectorAll('button.examples-close').forEach(b => { + const button = b as HTMLButtonElement + button.onclick = (e: any) => { + const button = e.target as HTMLButtonElement + const navLI = button.closest('li') + navLI?.classList.remove('open') + } + }) + + createConfigDropdown(sandbox, monaco) + updateConfigDropdownForCompilerOptions(sandbox, monaco) + + // Support grabbing examples + if (location.hash.startsWith('#example')) { + const exampleName = location.hash.replace('#example/', '').trim() + sandbox.config.logger.log('Loading example:', exampleName) + getExampleSourceCode(config.prefix, config.lang, exampleName).then(ex => { + if (ex.example && ex.code) { + const { example, code } = ex + + // Update the localstorage showing that you've seen this page + if (localStorage) { + const seenText = localStorage.getItem('examples-seen') || '{}' + const seen = JSON.parse(seenText) + seen[example.id] = example.hash + localStorage.setItem('examples-seen', JSON.stringify(seen)) + } + + // Set the menu to be the same section as this current example + // this happens behind the scene and isn't visible till you hover + // const sectionTitle = example.path[0] + // const allSectionTitles = document.getElementsByClassName('section-name') + // for (const title of allSectionTitles) { + // if (title.textContent === sectionTitle) { + // title.onclick({}) + // } + // } + + const allLinks = document.querySelectorAll('example-link') + // @ts-ignore + for (const link of allLinks) { + if (link.textContent === example.title) { + link.classList.add('highlight') + } + } + + document.title = 'TypeScript Playground - ' + example.title + sandbox.setText(code) + } else { + sandbox.setText('// There was an issue getting the example, bad URL? Check the console in the developer tools') + } + }) + } + + // Sets up a way to click between examples + monaco.languages.registerLinkProvider(sandbox.language, new ExampleHighlighter()) + + const ui = createUI() + const exporter = createExporter(sandbox, monaco, ui) + + const playground = { + exporter, + ui, + } + + window.ts = sandbox.ts + window.sandbox = sandbox + window.playground = playground + + console.log(`Using TypeScript ${window.ts.version}`) + + console.log('Available globals:') + console.log('\twindow.ts', window.ts) + console.log('\twindow.sandbox', window.sandbox) + console.log('\twindow.playground', window.playground) + + return playground +} + +export type Playground = ReturnType diff --git a/packages/playground/src/monaco/ExampleHighlight.ts b/packages/playground/src/monaco/ExampleHighlight.ts new file mode 100644 index 000000000000..ac8ac70fda60 --- /dev/null +++ b/packages/playground/src/monaco/ExampleHighlight.ts @@ -0,0 +1,36 @@ +/** + * Allows inline clicking on internal URLs to get different example code + */ +export class ExampleHighlighter { + provideLinks(model: import('monaco-editor').editor.IModel) { + const text = model.getValue() + + // https://regex101.com/r/3uM4Fa/1 + const docRegexLink = /example:([^\s]+)/g + + const links = [] + + let match + while ((match = docRegexLink.exec(text)) !== null) { + const start = match.index + const end = match.index + match[0].length + const startPos = model.getPositionAt(start) + const endPos = model.getPositionAt(end) + + const range = { + startLineNumber: startPos.lineNumber, + startColumn: startPos.column, + endLineNumber: endPos.lineNumber, + endColumn: endPos.column, + } + + const url = document.location.href.split('#')[0] + links.push({ + url: url + '#example/' + match[1], + range, + }) + } + + return { links } + } +} diff --git a/packages/playground/src/nav/index.ts b/packages/playground/src/nav/index.ts new file mode 100644 index 000000000000..feae563d0a7f --- /dev/null +++ b/packages/playground/src/nav/index.ts @@ -0,0 +1 @@ +export const createNav = () => {} diff --git a/packages/playground/src/sidebar/showDTS.ts b/packages/playground/src/sidebar/showDTS.ts new file mode 100644 index 000000000000..081b62087646 --- /dev/null +++ b/packages/playground/src/sidebar/showDTS.ts @@ -0,0 +1,22 @@ +import { PlaygroundPlugin } from '..' + +export const showDTSPlugin = () => { + let codeElement: HTMLElement + + const plugin: PlaygroundPlugin = { + displayName: 'DTS', + willMount: (sandbox, container) => { + // TODO: Monaco? + const createCodePre = document.createElement('pre') + codeElement = document.createElement('code') + + createCodePre.appendChild(codeElement) + container.appendChild(createCodePre) + }, + modelChanged: async (sandbox, model) => { + codeElement.textContent = await sandbox.getDTSForCode() + }, + } + + return plugin +} diff --git a/packages/playground/src/sidebar/showErrors.ts b/packages/playground/src/sidebar/showErrors.ts new file mode 100644 index 000000000000..10fd49788a6e --- /dev/null +++ b/packages/playground/src/sidebar/showErrors.ts @@ -0,0 +1,21 @@ +import { PlaygroundPlugin } from '..' + +export const showErrors = () => { + let codeElement: HTMLElement + + const plugin: PlaygroundPlugin = { + displayName: 'Errors', + willMount: async (sandbox, container) => { + const createCodePre = document.createElement('pre') + codeElement = document.createElement('code') + + createCodePre.appendChild(codeElement) + container.appendChild(createCodePre) + }, + modelChanged: async (sandbox, model) => { + codeElement.textContent = await sandbox.getRunnableJS() + }, + } + + return plugin +} diff --git a/packages/playground/src/sidebar/showJS.ts b/packages/playground/src/sidebar/showJS.ts new file mode 100644 index 000000000000..f8df5d271d0a --- /dev/null +++ b/packages/playground/src/sidebar/showJS.ts @@ -0,0 +1,21 @@ +import { PlaygroundPlugin } from '..' + +export const compiledJSPlugin = () => { + let codeElement: HTMLElement + + const plugin: PlaygroundPlugin = { + displayName: 'JS', + willMount: async (sandbox, container) => { + const createCodePre = document.createElement('pre') + codeElement = document.createElement('code') + + createCodePre.appendChild(codeElement) + container.appendChild(createCodePre) + }, + modelChanged: async (sandbox, model) => { + codeElement.textContent = await sandbox.getRunnableJS() + }, + } + + return plugin +} diff --git a/packages/playground/src/sidebar/showTypes.ts b/packages/playground/src/sidebar/showTypes.ts new file mode 100644 index 000000000000..11a33c1fc26d --- /dev/null +++ b/packages/playground/src/sidebar/showTypes.ts @@ -0,0 +1,36 @@ +import { PlaygroundPlugin } from '..' + +export const compiledJSPlugin = () => { + let codeElement: HTMLElement + + const plugin: PlaygroundPlugin = { + displayName: 'Types', + willMount: async (sandbox, container) => { + const createCodePre = document.createElement('pre') + codeElement = document.createElement('code') + + createCodePre.appendChild(codeElement) + container.appendChild(createCodePre) + }, + + modelChangedDebounce: async (sandbox, model) => { + const program = sandbox.createTSProgram() + const checker = program.getTypeChecker() + // const smychecker.getSymbolAtLocation + const sourceFile = program.getSourceFile(model.uri.path)! + const ts = sandbox.ts + sandbox.ts.forEachChild(sourceFile, node => { + if (ts.isInterfaceDeclaration(node)) { + const symbol = checker.getSymbolAtLocation(node) + if (symbol) { + console.log(symbol, symbol.members) + } else { + console.log('could not get symbol for interface') + } + } + }) + }, + } + + return plugin +} diff --git a/packages/playground/src/theme.ts b/packages/playground/src/theme.ts new file mode 100644 index 000000000000..2ce487997371 --- /dev/null +++ b/packages/playground/src/theme.ts @@ -0,0 +1,26 @@ +export const setEditorTheme = (theme: 'light' | 'dark' | 'hc', editor: typeof import('monaco-editor').editor) => { + const newTheme = theme ? theme : localStorage ? localStorage.getItem('editor-theme') || 'light' : 'light' + + editor.setTheme(newTheme) + + document + .querySelectorAll('a[id^=theme-]') + .forEach(anchor => + anchor.id === `theme-${newTheme}` + ? anchor.classList.add('current-theme') + : anchor.classList.remove('current-theme') + ) + + localStorage.setItem('editor-theme', newTheme) + + // Sets the theme on the body so CSS can change between themes + document.body.classList.remove('light', 'dark', 'hc') + + // So dark and dark-hc can share CSS + if (newTheme === 'dark-hc') { + document.body.classList.add('dark') + document.body.classList.add('hc') + } else { + document.body.classList.add(newTheme) + } +} diff --git a/packages/playground/test/typeAcquisition.test.tsx b/packages/playground/test/typeAcquisition.test.tsx new file mode 100644 index 000000000000..930288ad2ead --- /dev/null +++ b/packages/playground/test/typeAcquisition.test.tsx @@ -0,0 +1,6 @@ + + +describe('it', () => { + it('renders without crashing', () => { + }); +}); diff --git a/packages/playground/tsconfig.json b/packages/playground/tsconfig.json new file mode 100644 index 000000000000..5f7d004e2f1a --- /dev/null +++ b/packages/playground/tsconfig.json @@ -0,0 +1,15 @@ +{ + "include": ["src"], + "compilerOptions": { + "outDir": "../typescriptlang-org/static/js/playground", + "target": "ES2016", + "module": "AMD", + "lib": ["dom", "esnext"], + "declaration": true, + "sourceMap": true, + "rootDir": "./src", + "strict": true, + "moduleResolution": "node", + "esModuleInterop": true + } +} diff --git a/packages/sandbox/README.md b/packages/sandbox/README.md index 93f18ae13bd5..dec2786653db 100644 --- a/packages/sandbox/README.md +++ b/packages/sandbox/README.md @@ -1,15 +1,15 @@ # TypeScript Sandbox -The TypeScript Sandbox is the editor part of the TypeScript Playground. It's effectively an opinionated fork of +The TypeScript Sandbox is the editor part of the TypeScript Playground. It's effectively an opinionated fork of monaco-typescript with extra extension points so that projects like the TypeScript Playground can exist. This project is useful to you if: -- You want to improve the TypeScript Playground - You want to present users of your library with a JS editor which has a typed API (in JS or TS) +- You want to work with monaco at a higher abstraction level ## Goals -q + - Support multiple versions of TypeScript (via supporting older builds of monaco-typescript) - Easy to use when trying to replace code inline on a website - Support extension points required to build Playground @@ -17,4 +17,60 @@ q ## Builds -CJS, ESModules, and UMD module formats are supported. +This library currently ships as an AMD module. This is the same format that vscode/monaco use, and so you can use +the same runtime loader patterns for importing into your web page. It is not a goal to provide ESM builds so people +can run JS packagers over the project. If someone can make that work and have tests which validate it doesn't break, +we'll accept it. + +## Installation + +```html + +
Loading...
+
+ + +``` diff --git a/packages/sandbox/package.json b/packages/sandbox/package.json index 1a9271962049..824dd66af987 100644 --- a/packages/sandbox/package.json +++ b/packages/sandbox/package.json @@ -1,40 +1,21 @@ { - "name": "typescript-playground", + "name": "typescript-sandbox", "version": "0.1.0", "main": "dist/index.js", "license": "MIT", - "module": "dist/playground.esm.js", - "typings": "dist/index.d.ts", + "typings": "../typescriptlang-org/static/js/sandbox/index.d.ts", "files": [ "dist" ], "scripts": { - "start": "tsdx watch", + "bootstrap": "node script/downloadReleases.js; yarn build", "build": "tsc", - "test": "echo 'no tests'; #tsdx test --env=jsdom", - "lint": "tsdx lint" - }, - "peerDependencies": { - "react": ">=16" - }, - "husky": { - "hooks": { - "pre-commit": "pretty-quick --staged" - } + "test": "jest" }, "devDependencies": { "@types/jest": "^24.0.24", - "@types/react": "^16.9.17", - "@types/react-dom": "^16.9.4", - "husky": "^3.1.0", "monaco-editor": "~0.19.0", "monaco-typescript": "^3.6.1", - "prettier": "^1.19.1", - "pretty-quick": "^2.0.1", - "react": "^16.12.0", - "react-dom": "^16.12.0", - "tsdx": "^0.12.0", - "tslib": "^1.10.0", "typescript": "^3.7.4" } } diff --git a/packages/sandbox/script/downloadReleases.js b/packages/sandbox/script/downloadReleases.js new file mode 100644 index 000000000000..587d4cc10f1c --- /dev/null +++ b/packages/sandbox/script/downloadReleases.js @@ -0,0 +1,23 @@ +// @ts-check + +// Ensure the versions dropdown is up to date +const nodeFetch = require('node-fetch').default +const { writeFileSync } = require('fs') +const { join } = require('path') +const { format } = require('prettier') + +const go = async () => { + const response = await nodeFetch('https://tswebinfra.blob.core.windows.net/indexes/releases.json') + const json = await response.json() + const code = `// This is auto-generated by scripts/downloadReleases.js + +export const supportedReleases = ["${json.versions.join('", "')}"] as const + +export type ReleaseVersions = "${json.versions.join('" | "')}" +` + const path = join(__dirname, '..', 'src', 'releases.ts') + writeFileSync(path, format(code, { filepath: path }), 'utf8') + console.log('Updated the releases with the versions created by make-monaco-builds') +} + +go() diff --git a/packages/sandbox/src/compilerOptions.ts b/packages/sandbox/src/compilerOptions.ts new file mode 100644 index 000000000000..92ab00e55876 --- /dev/null +++ b/packages/sandbox/src/compilerOptions.ts @@ -0,0 +1,145 @@ +import { PlaygroundConfig } from '.' + +type CompilerOptions = import('monaco-editor').languages.typescript.CompilerOptions +type Monaco = typeof import('monaco-editor') +type Sandbox = ReturnType + +/** + * These are the defaults, but they also act as the list of all compiler options + * which are parsed in the query params. + */ +export function getDefaultSandboxCompilerOptions(config: PlaygroundConfig, monaco: Monaco) { + const settings: CompilerOptions = { + noImplicitAny: true, + strictNullChecks: !config.useJavaScript, + strictFunctionTypes: true, + strictPropertyInitialization: true, + strictBindCallApply: true, + noImplicitThis: true, + noImplicitReturns: true, + + // 3.7 off, 3.8 on I think + useDefineForClassFields: false, + + alwaysStrict: true, + allowUnreachableCode: false, + allowUnusedLabels: false, + + downlevelIteration: false, + noEmitHelpers: false, + noLib: false, + noStrictGenericChecks: false, + noUnusedLocals: false, + noUnusedParameters: false, + + esModuleInterop: true, + preserveConstEnums: false, + removeComments: false, + skipLibCheck: false, + + checkJs: config.useJavaScript, + allowJs: config.useJavaScript, + declaration: true, + + experimentalDecorators: true, + emitDecoratorMetadata: true, + + target: monaco.languages.typescript.ScriptTarget.ES2017, + jsx: monaco.languages.typescript.JsxEmit.React, + module: monaco.languages.typescript.ModuleKind.ESNext, + } + + return settings +} + +/** + * Loop through all of the entries in the existing compiler options then compare them with the + * query params and return an object which is the changed settings via the query params + */ +export const getCompilerOptionsFromParams = (options: CompilerOptions, params: URLSearchParams): CompilerOptions => { + const urlDefaults = Object.entries(options).reduce((acc: any, [key, value]) => { + if (params.has(key)) { + const urlValue = params.get(key)! + + if (urlValue === 'true') { + acc[key] = true + } else if (urlValue === 'false') { + acc[key] = false + } else if (!isNaN(parseInt(urlValue, 10))) { + acc[key] = parseInt(urlValue, 10) + } + } + + return acc + }, {}) + + return urlDefaults +} + +// http://stackoverflow.com/questions/1714786/ddg#1714899 +function objectToQueryParams(obj: any) { + const str = [] + for (var p in obj) + if (obj.hasOwnProperty(p)) { + str.push(encodeURIComponent(p) + '=' + encodeURIComponent(obj[p])) + } + return str.join('&') +} + +/** Gets a query string representation (hash + queries) */ +export const setURLQueryWithCompilerOptions = (sandbox: Sandbox) => { + const compilerOptions = sandbox.getCompilerOptions() + const diff = Object.entries(compilerOptions).reduce((acc, [key, value]) => { + if (value !== compilerOptions[key]) { + // @ts-ignore + acc[key] = compilerOptions[key] + } + + return acc + }, {}) + + // The text of the TS/JS as the hash + const hash = `code/${sandbox.lzstring.compressToEncodedURIComponent(sandbox.getText())}` + + const urlParams: any = Object.assign({}, diff) + for (const param of ['lib', 'ts']) { + const params = new URLSearchParams(location.search) + if (params.has(param)) { + // Special case the nightly where it uses the TS version to hardcode + // the nightly build + if (param === 'ts' && params.get(param) === 'Nightly') { + urlParams['ts'] = sandbox.ts.version + } else { + urlParams['ts'] = params.get(param) + } + } + } + + // Support sending the selection + const s = sandbox.editor.getSelection() + if ( + (s && s.selectionStartLineNumber !== s.positionLineNumber) || + (s && s.selectionStartColumn !== s.positionColumn) + ) { + urlParams['ssl'] = s.selectionStartLineNumber + urlParams['ssc'] = s.selectionStartColumn + urlParams['pln'] = s.positionLineNumber + urlParams['pc'] = s.positionColumn + } + + if (sandbox.config.useJavaScript) urlParams['useJavaScript'] = true + + if (Object.keys(urlParams).length > 0) { + const queryString = Object.entries(urlParams) + .map(([key, value]) => { + return `${key}=${encodeURIComponent(value as string)}` + }) + .join('&') + + return `?${queryString}#${hash}` + // window.history.replaceState({}, '', `${window.CONFIG.baseUrl}?${queryString}#${hash}`) + } else { + return `#${hash}` + // window.history.replaceState({}, '', `${window.CONFIG.baseUrl}#${hash}`) + } +} diff --git a/packages/sandbox/src/createCompilerHost.ts b/packages/sandbox/src/createCompilerHost.ts new file mode 100644 index 000000000000..6ea81b1a678e --- /dev/null +++ b/packages/sandbox/src/createCompilerHost.ts @@ -0,0 +1,20 @@ +// A single file version from +// https://stackoverflow.com/questions/53733138/how-do-i-type-check-a-snippet-of-typescript-code-in-memory + +export function createCompilerHost(code: string, path: string) { + const host: import('typescript').CompilerHost = { + fileExists: filePath => filePath === path, + directoryExists: dirPath => dirPath === '/', + getCurrentDirectory: () => '/', + getDirectories: () => [], + getCanonicalFileName: fileName => fileName, + getNewLine: () => '\n', + getDefaultLibFileName: () => '', + getSourceFile: _ => undefined, + readFile: filePath => (filePath === path ? code : undefined), + useCaseSensitiveFileNames: () => true, + writeFile: () => {}, + } + + return host +} diff --git a/packages/sandbox/src/getInitialCode.ts b/packages/sandbox/src/getInitialCode.ts new file mode 100644 index 000000000000..1a1f9be8a9ec --- /dev/null +++ b/packages/sandbox/src/getInitialCode.ts @@ -0,0 +1,31 @@ +import lzstring from './vendor/lzstring.min' + +/** + * Grabs the sourcecode for an example from the query hash or local storage + * @param fallback if nothing is found return this + * @param location DI'd copy of document.location + */ +export const getInitialCode = (fallback: string, location: Location) => { + // Old school support + if (location.hash.startsWith('#src')) { + const code = location.hash.replace('#src=', '').trim() + return decodeURIComponent(code) + } + + // New school support + if (location.hash.startsWith('#code')) { + const code = location.hash.replace('#code/', '').trim() + let userCode = lzstring.decompressFromEncodedURIComponent(code) + // Fallback incase there is an extra level of decoding: + // https://gitter.im/Microsoft/TypeScript?at=5dc478ab9c39821509ff189a + if (!userCode) userCode = lzstring.decompressFromEncodedURIComponent(decodeURIComponent(code)) + return userCode + } + + // Local copy fallback + if (localStorage.getItem('sandbox-history')) { + return localStorage.getItem('sandbox-history')! + } + + return fallback +} diff --git a/packages/sandbox/src/index.ts b/packages/sandbox/src/index.ts index 2dad7a25b9f8..059784f132ad 100644 --- a/packages/sandbox/src/index.ts +++ b/packages/sandbox/src/index.ts @@ -1,112 +1,237 @@ -import ts from 'typescript' -import { SupportedTSVersions } from './monacoTSVersions' +import { createCompilerHost } from './createCompilerHost' +import { detectNewImportsToAcquireTypeFor } from './typeAcquisition' +import { sandboxTheme } from './theme' +import { TypeScriptWorker } from './tsWorker' +import { getDefaultSandboxCompilerOptions, getCompilerOptionsFromParams } from './compilerOptions' +import lzstring from './vendor/lzstring.min' +import { supportedReleases } from './releases' +import { getInitialCode } from './getInitialCode' + +type CompilerOptions = import('monaco-editor').languages.typescript.CompilerOptions +type Monaco = typeof import('monaco-editor') /** * These are settings for the playground which are the equivalent to props in React * any changes to it should require a new setup of the playground */ -type PlaygroundConfig = { +export type PlaygroundConfig = { /** The default source code for the playground */ text: string /** Should it run the ts or js IDE services */ useJavaScript: boolean - /** The version of TS we should use */ - typeScriptVersion: 'bundled' | SupportedTSVersions | 'nightly' /** Compiler options which are automatically just forwarded on */ - compilerOptions: ts.CompilerOptions + compilerOptions: CompilerOptions /** Optional monaco settings overrides */ - monacoSettings?: any // TODO: types + monacoSettings?: import('monaco-editor').editor.IEditorOptions + /** Acquire types via type acquisition */ + acquireTypes: boolean + /** Get the text via query params and local storage, useful when the editor is the main experience */ + suppressAutomaticallyGettingDefaultText?: true + /** Suppress setting compiler options from the compiler flags from query params */ + suppressAutomaticallyGettingCompilerFlags?: true + /** Logging system */ + logger: { log: (...args: any[]) => void; error: (...args: any[]) => void } } & ( | { /** theID of a dom node to add monaco to */ domID: string } - | { /** theID of a dom node to add monaco to */ elementToAppend: Element } + | { /** theID of a dom node to add monaco to */ elementToAppend: HTMLElement } ) const languageType = (config: PlaygroundConfig) => (config.useJavaScript ? 'javascript' : 'typescript') -// const monacoLanguageDefaults = (config: PlaygroundConfig) => config.useJavaScript ? monaco.languages.typescript.javascriptDefaults : monaco.languages.typescript.typescriptDefaults -// const monacoLanguageWorker = (config: PlaygroundConfig) => config.useJavaScript ? monaco.languages.typescript.getJavaScriptWorker : monaco.languages.typescript.getTypeScriptWorker -/** Default Monaco settings for the sandbox */ -const sharedEditorOptions = { - minimap: { enabled: false }, +/** Default Monaco settings for playground */ +const sharedEditorOptions: import('monaco-editor').editor.IEditorOptions = { automaticLayout: true, scrollBeyondLastLine: true, scrollBeyondLastColumn: 3, + minimap: { + enabled: false, + }, } -export function getDefaultCompilerOptions( - config: PlaygroundConfig, - monaco: typeof import('monaco-editor') -): ts.CompilerOptions { - return { - noImplicitAny: true, - strictNullChecks: true, - strictFunctionTypes: true, - strictPropertyInitialization: true, - noImplicitThis: true, - noImplicitReturns: true, - - alwaysStrict: true, - allowUnreachableCode: false, - allowUnusedLabels: false, - - downlevelIteration: false, - noEmitHelpers: false, - noLib: false, - noStrictGenericChecks: false, - noUnusedLocals: false, - noUnusedParameters: false, - - esModuleInterop: false, - preserveConstEnums: false, - removeComments: false, - skipLibCheck: false, - - checkJs: config.useJavaScript, - allowJs: config.useJavaScript, - - experimentalDecorators: false, - emitDecoratorMetadata: false, - - target: 4, // ts.ScriptTarget.ES2017, - jsx: monaco.languages.typescript.JsxEmit.None, - } -} - -export function defaultPlaygroundSettings(text: string, domID: string) { +/** The default settings which we apply a partial over */ +export function defaultPlaygroundSettings() { const config: PlaygroundConfig = { - text, - domID, + text: '', + domID: '', compilerOptions: {}, - typeScriptVersion: 'bundled', + acquireTypes: true, useJavaScript: false, + logger: { + error: () => {}, + log: () => {}, + }, } return config } -/** Creates a monaco file reference, basically a fancy path */ -function createFileUri( - config: PlaygroundConfig, - compilerOptions: ts.CompilerOptions, - monaco: typeof import('monaco-editor') -) { +function defaultFilePath(config: PlaygroundConfig, compilerOptions: CompilerOptions, monaco: Monaco) { const isJSX = compilerOptions.jsx !== monaco.languages.typescript.JsxEmit.None const fileExt = config.useJavaScript ? 'js' : 'ts' const ext = isJSX ? fileExt + 'x' : fileExt - const filepath = 'input.' + ext - return monaco.Uri.file(filepath) + return 'input.' + ext } -export async function setupPlayground(config: PlaygroundConfig, monaco: typeof import('monaco-editor')) { +/** Creates a monaco file reference, basically a fancy path */ +function createFileUri(config: PlaygroundConfig, compilerOptions: CompilerOptions, monaco: Monaco) { + return monaco.Uri.file(defaultFilePath(config, compilerOptions, monaco)) +} + +/** Creates a sandbox editor, and returns a set of useful functions and the editor */ +export const createTypeScriptSandbox = ( + partialConfig: Partial, + monaco: Monaco, + ts: typeof import('typescript') +) => { + const config = { ...defaultPlaygroundSettings(), ...partialConfig } + if (!('domID' in config) && !('elementToAppend' in config)) + throw new Error('You did not provide a domID or elementToAppend') + + const compilerDefaults = getDefaultSandboxCompilerOptions(config, monaco) const language = languageType(config) - const filePath = createFileUri(config, config.compilerOptions, monaco) - const element = 'domID' in config ? document.getElementById(config.domID) : config.elementToAppend - const model = monaco.editor.createModel(config.text, language, filePath) + const filePath = createFileUri(config, compilerDefaults, monaco) + const element = 'domID' in config ? document.getElementById(config.domID) : (config as any).elementToAppend + + const defaultText = config.suppressAutomaticallyGettingDefaultText + ? config.text + : getInitialCode(config.text, document.location) + + const model = monaco.editor.createModel(defaultText, language, filePath) + monaco.editor.defineTheme('sandbox', sandboxTheme) + monaco.editor.setTheme('sandbox') const monacoSettings = Object.assign({ model }, sharedEditorOptions, config.monacoSettings || {}) - const editor = monaco.editor.create(element as any, monacoSettings) + const editor = monaco.editor.create(element, monacoSettings) + + const getWorker = config.useJavaScript + ? monaco.languages.typescript.getJavaScriptWorker + : monaco.languages.typescript.getTypeScriptWorker + + const defaults = config.useJavaScript + ? monaco.languages.typescript.javascriptDefaults + : monaco.languages.typescript.typescriptDefaults + + // Grab types + if (config.acquireTypes) { + // In the future it'd be good to add support for an 'add many files' + const addLibraryToRuntime = (code: string, path: string) => { + defaults.addExtraLib(code, path) + config.logger.log(`[ATA] Adding ${path} to runtime`) + } + + // Take the code from the editor right away + const code = editor.getModel()!.getValue() + detectNewImportsToAcquireTypeFor(code, addLibraryToRuntime, window.fetch.bind(window), config) + + // Then update it when the model changes, perhaps this could be a debounced plugin instead in the future? + editor.onDidChangeModelContent(() => { + const code = editor.getModel()!.getValue() + detectNewImportsToAcquireTypeFor(code, addLibraryToRuntime, window.fetch.bind(window), config) + }) + } - return editor -} + // Grab the compiler flags via the query params + let compilerOptions: CompilerOptions + if (!config.suppressAutomaticallyGettingCompilerFlags) { + const params = new URLSearchParams(location.search) + let queryParamCompilerOptions = getCompilerOptionsFromParams(compilerDefaults, params) + if (Object.keys(queryParamCompilerOptions).length) + config.logger.log('[Compiler] Found compiler options in query params: ', queryParamCompilerOptions) + compilerOptions = { ...compilerDefaults, ...queryParamCompilerOptions } + } else { + compilerOptions = compilerDefaults + } + + config.logger.log('[Compiler] Set compiler options: ', compilerOptions) + defaults.setCompilerOptions(compilerOptions) + + const updateCompilerSettings = (opts: CompilerOptions) => { + config.logger.log('[Compiler] Updating compiler options: ', opts) + compilerOptions = { ...opts, ...compilerOptions } + defaults.setCompilerOptions(opts) + } + + const setCompilerSettings = (opts: CompilerOptions) => { + config.logger.log('[Compiler] Setting compiler options: ', opts) + compilerOptions = opts + defaults.setCompilerOptions(opts) + } + + const getCompilerOptions = () => { + return compilerOptions + } + + /** Gets the results of compiling your editor's code */ + const getEmitResult = async () => { + const model = editor.getModel()! + + const client = await getWorkerProcess() + return await client.getEmitOutput(model.uri.toString()) + } + + /** Gets the JS of compiling your editor's code */ + const getRunnableJS = async () => { + if (config.useJavaScript) { + return getText() + } -export { detectNewImportsToAcquireTypeFor } from './typeAcquisition' -export { monacoTSVersions } from './monacoTSVersions' + const result = await getEmitResult() + return result.outputFiles.find((o: any) => o.name.endsWith('.js'))!.text + } + + /** Gets the DTS for the JS/TS of compiling your editor's code */ + const getDTSForCode = async () => { + const result = await getEmitResult() + return result.outputFiles.find((o: any) => o.name.endsWith('.d.ts'))!.text + } + + const getWorkerProcess = async (): Promise => { + const worker = await getWorker() + return await worker(model.uri) + } + + const getDomNode = () => editor.getDomNode()! + const getModel = () => editor.getModel()! + const getText = () => getModel().getValue() + const setText = (text: string) => getModel().setValue(text) + + /** + * Warning: Runs on the main thread + */ + const createTSProgram = () => { + const langServ = createCompilerHost(getText(), filePath.path) + return ts.createProgram([filePath.path], compilerDefaults, langServ) + } + + /** + * Warning: Runs on the main thread + */ + const getAST = () => { + const program = createTSProgram() + return program.getSourceFile(filePath.path)! + } + + // Pass along the supported releases for the playground + const supportedVersions = supportedReleases + + return { + config, + editor, + getWorkerProcess, + getEmitResult, + getRunnableJS, + getDTSForCode, + getDomNode, + getModel, + getText, + setText, + getAST, + ts, + createTSProgram, + updateCompilerSettings, + getCompilerOptions, + setCompilerSettings, + supportedVersions, + lzstring, + language, + } +} diff --git a/packages/sandbox/src/monacoTSVersions.ts b/packages/sandbox/src/monacoTSVersions.ts index 3f7465c5bdaa..ed4f2d891a17 100644 --- a/packages/sandbox/src/monacoTSVersions.ts +++ b/packages/sandbox/src/monacoTSVersions.ts @@ -1,21 +1,17 @@ +import { supportedReleases, ReleaseVersions } from './releases' + +/** The versions you can get for the sandbox */ +export type SupportedTSVersions = ReleaseVersions | 'Latest' + /** * The versions of monaco-typescript which we can use * for backwards compatibility with older versions * of TS in the playground. */ -export const monacoTSVersions = { - Nightly: { monaco: 'next', module: '@typescript-deploys/monaco-editor' }, - '3.7.3': { monaco: '3.7.2', module: '@typescript-deploys/monaco-editor' }, - // Don't break old links, but re-direct them to prod, but don't show this in the menus - '3.7-Beta': { monaco: '3.7.2', module: '@typescript-deploys/monaco-editor', hide: true }, - '3.6.3': { monaco: '0.18.1', module: 'monaco-editor' }, - '3.5.1': { monaco: '0.17.1', module: 'monaco-editor' }, - '3.3.3': { monaco: '0.16.1', module: 'monaco-editor' }, - '3.1.6': { monaco: '0.15.6', module: 'monaco-editor' }, - '3.0.1': { monaco: '0.14.3', module: 'monaco-editor' }, - '2.8.1': { monaco: '0.13.1', module: 'monaco-editor' }, - '2.7.2': { monaco: '0.11.1', module: 'monaco-editor' }, - '2.4.1': { monaco: '0.10.0', module: 'monaco-editor' }, -} as const +export const monacoTSVersions: SupportedTSVersions[] = [...supportedReleases, 'Latest'] -export type SupportedTSVersions = 'Latest' | keyof typeof monacoTSVersions +/** Returns the latest TypeScript version supported by the sandbox */ +export const latestSupportedTypeScriptVersion: string = Object.keys(monacoTSVersions) + .filter(key => key !== 'Nightly' && !key.includes('-')) + .sort() + .pop()! diff --git a/packages/sandbox/src/releases.ts b/packages/sandbox/src/releases.ts new file mode 100644 index 000000000000..6cdb9ac19acc --- /dev/null +++ b/packages/sandbox/src/releases.ts @@ -0,0 +1,24 @@ +// This is auto-generated by scripts/downloadReleases.js + +export const supportedReleases = [ + "3.7.3", + "3.6.3", + "3.5.1", + "3.3.3", + "3.1.6", + "3.0.1", + "2.8.1", + "2.7.2", + "2.4.1" +] as const; + +export type ReleaseVersions = + | "3.7.3" + | "3.6.3" + | "3.5.1" + | "3.3.3" + | "3.1.6" + | "3.0.1" + | "2.8.1" + | "2.7.2" + | "2.4.1"; diff --git a/packages/sandbox/src/theme.ts b/packages/sandbox/src/theme.ts new file mode 100644 index 000000000000..1fd9c661622d --- /dev/null +++ b/packages/sandbox/src/theme.ts @@ -0,0 +1,71 @@ +const blue = '3771EF' +const darkerBlue = '1142AF' +const darkestBlue = '09235D' + +const yellow = 'F3DF51' +const darkYellow = 'AEA811' +const darkerYellow = '65610A' + +const grey = '84864d' +const green = '12CD0E' +const greenDark = '10990D' +const greenLight = '54F351' + +export const sandboxTheme: import('monaco-editor').editor.IStandaloneThemeData = { + base: 'vs', + inherit: true, + rules: [ + { token: '', foreground: '000000', background: 'fffffe' }, + { token: 'invalid', foreground: 'cd3131' }, + { token: 'emphasis', fontStyle: 'italic' }, + { token: 'strong', fontStyle: 'bold' }, + + { token: 'variable', foreground: '11bb11' }, + { token: 'variable.predefined', foreground: '4864AA' }, + { token: 'constant', foreground: '44ee11' }, + { token: 'comment', foreground: grey }, + { token: 'number', foreground: greenDark }, + { token: 'number.hex', foreground: '3030c0' }, + { token: 'regexp', foreground: greenLight }, + { token: 'annotation', foreground: '808080' }, + { token: 'type', foreground: darkerBlue }, + + { token: 'delimiter', foreground: '000000' }, + { token: 'delimiter.html', foreground: '383838' }, + { token: 'delimiter.xml', foreground: '0000FF' }, + + { token: 'tag', foreground: '800000' }, + { token: 'tag.id.pug', foreground: '4F76AC' }, + { token: 'tag.class.pug', foreground: '4F76AC' }, + { token: 'meta.scss', foreground: '800000' }, + { token: 'metatag', foreground: 'e00000' }, + { token: 'metatag.content.html', foreground: 'FF0000' }, + { token: 'metatag.html', foreground: '808080' }, + { token: 'metatag.xml', foreground: '808080' }, + { token: 'metatag.php', fontStyle: 'bold' }, + + { token: 'key', foreground: '863B00' }, + { token: 'string.key.json', foreground: 'A31515' }, + { token: 'string.value.json', foreground: '0451A5' }, + + { token: 'attribute.name', foreground: 'FFFF00' }, + { token: 'attribute.value', foreground: '0451A5' }, + { token: 'attribute.value.number', foreground: '09885A' }, + { token: 'attribute.value.unit', foreground: '09885A' }, + { token: 'attribute.value.html', foreground: '0000FF' }, + { token: 'attribute.value.xml', foreground: '0000FF' }, + + { token: 'string', foreground: greenDark }, + + { token: 'keyword', foreground: blue }, + { token: 'keyword.json', foreground: '0451A5' }, + ], + colors: { + editorBackground: '#F6F6F6', + editorForeground: '#000000', + editorInactiveSelection: '#E5EBF1', + editorIndentGuides: '#D3D3D3', + editorActiveIndentGuides: '#939393', + editorSelectionHighlight: '#ADD6FF4D', + }, +} diff --git a/packages/sandbox/src/tsWorker.ts b/packages/sandbox/src/tsWorker.ts new file mode 100644 index 000000000000..922a9be65023 --- /dev/null +++ b/packages/sandbox/src/tsWorker.ts @@ -0,0 +1,73 @@ +import ts from 'typescript' + +export declare class TypeScriptWorker implements ts.LanguageServiceHost { + private _ctx + private _extraLibs + private _languageService + private _compilerOptions + constructor(ctx: any, createData: any) + getCompilationSettings(): ts.CompilerOptions + getScriptFileNames(): string[] + private _getModel + getScriptVersion(fileName: string): string + getScriptSnapshot(fileName: string): ts.IScriptSnapshot | undefined + getScriptKind?(fileName: string): ts.ScriptKind + getCurrentDirectory(): string + getDefaultLibFileName(options: ts.CompilerOptions): string + isDefaultLibFileName(fileName: string): boolean + private static clearFiles + getSyntacticDiagnostics(fileName: string): Promise + getSemanticDiagnostics(fileName: string): Promise + getSuggestionDiagnostics(fileName: string): Promise + getCompilerOptionsDiagnostics(fileName: string): Promise + getCompletionsAtPosition(fileName: string, position: number): Promise + getCompletionEntryDetails( + fileName: string, + position: number, + entry: string + ): Promise + getSignatureHelpItems(fileName: string, position: number): Promise + getQuickInfoAtPosition(fileName: string, position: number): Promise + getOccurrencesAtPosition(fileName: string, position: number): Promise | undefined> + getDefinitionAtPosition(fileName: string, position: number): Promise | undefined> + getReferencesAtPosition(fileName: string, position: number): Promise + getNavigationBarItems(fileName: string): Promise + getFormattingEditsForDocument(fileName: string, options: ts.FormatCodeOptions): Promise + getFormattingEditsForRange( + fileName: string, + start: number, + end: number, + options: ts.FormatCodeOptions + ): Promise + getFormattingEditsAfterKeystroke( + fileName: string, + postion: number, + ch: string, + options: ts.FormatCodeOptions + ): Promise + findRenameLocations( + fileName: string, + positon: number, + findInStrings: boolean, + findInComments: boolean, + providePrefixAndSuffixTextForRename: boolean + ): Promise + getRenameInfo(fileName: string, positon: number, options: ts.RenameInfoOptions): Promise + getEmitOutput(fileName: string): Promise + getCodeFixesAtPosition( + fileName: string, + start: number, + end: number, + errorCodes: number[], + formatOptions: ts.FormatCodeOptions + ): Promise> + updateExtraLibs(extraLibs: IExtraLibs): void +} + +export interface IExtraLib { + content: string + version: number +} +export interface IExtraLibs { + [path: string]: IExtraLib +} diff --git a/packages/sandbox/src/typeAcquisition.ts b/packages/sandbox/src/typeAcquisition.ts index 176ead6c30a6..97cf53acf6ed 100644 --- a/packages/sandbox/src/typeAcquisition.ts +++ b/packages/sandbox/src/typeAcquisition.ts @@ -1,339 +1,393 @@ +import { PlaygroundConfig } from './' +import lzstring from './vendor/lzstring.min' + +const globalishObj: any = typeof globalThis !== 'undefined' ? globalThis : window || {} +globalishObj.typeDefinitions = {} + /** * Type Defs we've already got, and nulls when something has failed. * This is to make sure that it doesn't infinite loop. */ -export const acquiredTypeDefs: { [name: string]: string | null } = {} +export const acquiredTypeDefs: { [name: string]: string | null } = globalishObj export type AddLibToRuntimeFunc = (code: string, path: string) => void +const moduleJSONURL = (name: string) => + // prettier-ignore + `https://ofcncog2cu-dsn.algolia.net/1/indexes/npm-search/${encodeURIComponent(name)}?attributes=types&x-algolia-agent=Algolia%20for%20vanilla%20JavaScript%20(lite)%203.27.1&x-algolia-application-id=OFCNCOG2CU&x-algolia-api-key=f54e21fa3a2a0160595bb058179bfb1e` + +const unpkgURL = (name: string, path: string) => + `https://www.unpkg.com/${encodeURIComponent(name)}/${encodeURIComponent(path)}` + +const packageJSONURL = (name: string) => unpkgURL(name, 'package.json') + +const errorMsg = (msg: string, response: any, config: ATAConfig) => { + config.logger.error(`${msg} - will not try again in this session`, response.status, response.statusText, response) + debugger +} + /** - * Pseudo in-browser type acquisition - * - * @param sourceCode the root source code to look at - * @param addLibraryToRuntime + * Grab any import/requires from inside the code and make a list of + * its dependencies */ +const parseFileForModuleReferences = (sourceCode: string) => { + // https://regex101.com/r/Jxa3KX/4 + const requirePattern = /(const|let|var)(.|\n)*? require\(('|")(.*)('|")\);?$/ + // this handle ths 'from' imports https://regex101.com/r/hdEpzO/4 + const es6Pattern = /(import|export)((?!from)(?!require)(.|\n))*?(from|require\()\s?('|")(.*)('|")\)?;?$/gm + // https://regex101.com/r/hdEpzO/6 + const es6ImportOnly = /import\s?('|")(.*)('|")\)?;?/gm + + const foundModules = new Set() + var match + + while ((match = es6Pattern.exec(sourceCode)) !== null) { + if (match[6]) foundModules.add(match[6]) + } -export const detectNewImportsToAcquireTypeFor = async ( - sourceCode: string, - addLibraryToRuntime: AddLibToRuntimeFunc -) => { - const getTypeDependenciesForSourceCode = async ( - sourceCode: string, - mod: string | undefined, - path: string | undefined - ) => { - // TODO: debounce - // - // TODO: This needs to be replaced by the AST - it still works in comments - // blocked by https://github.com/microsoft/monaco-typescript/pull/38 - // - // https://regex101.com/r/Jxa3KX/4 - const requirePattern = /(const|let|var)(.|\n)*? require\(('|")(.*)('|")\);?$/ - // https://regex101.com/r/hdEpzO/4 - const es6Pattern = /(import|export)((?!from)(?!require)(.|\n))*?(from|require\()\s?('|")(.*)('|")\)?;?$/gm - - const foundModules = new Set() - var match - - while ((match = es6Pattern.exec(sourceCode)) !== null) { - if (match[6]) foundModules.add(match[6]) - } + while ((match = requirePattern.exec(sourceCode)) !== null) { + if (match[5]) foundModules.add(match[5]) + } - while ((match = requirePattern.exec(sourceCode)) !== null) { - if (match[5]) foundModules.add(match[5]) - } + while ((match = es6ImportOnly.exec(sourceCode)) !== null) { + if (match[2]) foundModules.add(match[2]) + } - const moduleJSONURL = (name: string) => - `https://ofcncog2cu-dsn.algolia.net/1/indexes/npm-search/${name}?attributes=types&x-algolia-agent=Algolia%20for%20vanilla%20JavaScript%20(lite)%203.27.1&x-algolia-application-id=OFCNCOG2CU&x-algolia-api-key=f54e21fa3a2a0160595bb058179bfb1e` - const unpkgURL = (name: string, path: string) => - `https://www.unpkg.com/${encodeURIComponent(name)}/${encodeURIComponent(path)}` - const packageJSONURL = (name: string) => unpkgURL(name, 'package.json') - const errorMsg = (msg: string, response: any) => { - console.error(`${msg} - will not try again in this session`, response.status, response.statusText, response) - debugger - } + return Array.from(foundModules) +} - // const addLibraryToRuntime = (code:string, path:string) => { - // const defaults = monacoLanguageDefaults({ isJS: path.endsWith("js") }) - // defaults.addExtraLib(code, path); - // console.log(`Adding ${path} to runtime`) - // } - - const getReferenceDependencies = async (sourceCode: string, mod: string, path: string) => { - if (sourceCode.indexOf('reference path') > 0) { - // https://regex101.com/r/DaOegw/1 - const referencePathExtractionPattern = //gm - while ((match = referencePathExtractionPattern.exec(sourceCode)) !== null) { - const relativePath = match[1] - if (relativePath) { - let newPath = mapRelativePath(mod, relativePath, path) - if (newPath) { - const dtsRefURL = unpkgURL(mod, newPath) - const dtsReferenceResponse = await fetch(dtsRefURL) - if (!dtsReferenceResponse.ok) { - return errorMsg( - `Could not get ${newPath} for a reference link in the module '${mod}' from ${path}`, - dtsReferenceResponse - ) - } - - let dtsReferenceResponseText = await dtsReferenceResponse.text() - if (!dtsReferenceResponseText) { - return errorMsg( - `Could not get ${newPath} for a reference link for the module '${mod}' from ${path}`, - dtsReferenceResponse - ) - } - - await getTypeDependenciesForSourceCode(dtsReferenceResponseText, mod, newPath) - const representationalPath = `node_modules/${mod}/${newPath}` - addLibraryToRuntime(dtsReferenceResponseText, representationalPath) - } - } - } - } - } +/** Converts some of the known global imports to node so that we grab the right info */ +const mapModuleNameToModule = (name: string) => { + // in node repl: + // > require("module").builtinModules + const builtInNodeMods = [ + 'assert', + 'async_hooks', + 'base', + 'buffer', + 'child_process', + 'cluster', + 'console', + 'constants', + 'crypto', + 'dgram', + 'dns', + 'domain', + 'events', + 'fs', + 'globals', + 'http', + 'http2', + 'https', + 'index', + 'inspector', + 'module', + 'net', + 'os', + 'path', + 'perf_hooks', + 'process', + 'punycode', + 'querystring', + 'readline', + 'repl', + 'stream', + 'string_decoder', + 'timers', + 'tls', + 'trace_events', + 'tty', + 'url', + 'util', + 'v8', + 'vm', + 'worker_threads', + 'zlib', + ] + + if (builtInNodeMods.includes(name)) { + return 'node' + } + return name +} - /** - * Takes an initial module and the path for the root of the typings and grab it and start grabbing its - * dependencies then add those the to runtime. - * - * @param {string} mod The module name - * @param {string} path The path to the root def type - */ - const addModuleToRuntime = async (mod: string, path: string) => { - const isDeno = path && path.indexOf('https://') === 0 - - const dtsFileURL = isDeno ? path : unpkgURL(mod, path) - const dtsResponse = await fetch(dtsFileURL) - if (!dtsResponse.ok) { - return errorMsg(`Could not get root d.ts file for the module '${mod}' at ${path}`, dtsResponse) - } +//** A really dumb version of path.resolve */ +const mapRelativePath = (moduleDeclaration: string, currentPath: string) => { + // https://stackoverflow.com/questions/14780350/convert-relative-path-to-absolute-using-javascript + function absolute(base: string, relative: string) { + if (!base) return relative - // TODO: handle checking for a resolve to index.d.ts whens someone imports the folder - let content = await dtsResponse.text() - if (!content) { - return errorMsg(`Could not get root d.ts file for the module '${mod}' at ${path}`, dtsResponse) - } + const stack = base.split('/') + const parts = relative.split('/') + stack.pop() // remove current file name (or empty string) - // Now look and grab dependent modules where you need the - // - await getTypeDependenciesForSourceCode(content, mod, path) - - if (isDeno) { - const wrapped = `declare module "${path}" { ${content} }` - addLibraryToRuntime(wrapped, path) - } else { - const typelessModule = mod.split('@types/').slice(-1) - const wrapped = `declare module "${typelessModule}" { ${content} }` - addLibraryToRuntime(wrapped, `node_modules/${mod}/${path}`) - } + for (var i = 0; i < parts.length; i++) { + if (parts[i] == '.') continue + if (parts[i] == '..') stack.pop() + else stack.push(parts[i]) } + return stack.join('/') + } - /** - * Takes a module import, then uses both the algolia API and the the package.json to derive - * the root type def path. - * - * @param {string} packageName - * @returns {Promise<{ mod: string, path: string, packageJSON: any }>} - */ - const getModuleAndRootDefTypePath = async (packageName: string) => { - // For modules - const url = moduleJSONURL(packageName) - - const response = await fetch(url) - if (!response.ok) { - return errorMsg(`Could not get Algolia JSON for the module '${packageName}'`, response) - } + return absolute(currentPath, moduleDeclaration) +} - const responseJSON = await response.json() - if (!responseJSON) { - return errorMsg(`Could not get Algolia JSON for the module '${packageName}'`, response) - } +const convertToModuleReferenceID = (outerModule: string, moduleDeclaration: string, currentPath: string) => { + const modIsScopedPackageOnly = moduleDeclaration.indexOf('@') === 0 && moduleDeclaration.split('/').length === 2 + const modIsPackageOnly = moduleDeclaration.indexOf('@') === -1 && moduleDeclaration.split('/').length === 1 + const isPackageRootImport = modIsPackageOnly || modIsScopedPackageOnly - if (!responseJSON.types) { - return console.log(`There were no types for '${packageName}' - will not try again in this session`) - } + if (isPackageRootImport) { + return moduleDeclaration + } else { + return `${outerModule}-${mapRelativePath(moduleDeclaration, currentPath)}` + } +} - if (!responseJSON.types.ts) { - return console.log(`There were no types for '${packageName}' - will not try again in this session`) - } +/** + * Takes an initial module and the path for the root of the typings and grab it and start grabbing its + * dependencies then add those the to runtime. + */ +const addModuleToRuntime = async (mod: string, path: string, config: ATAConfig) => { + const isDeno = path && path.indexOf('https://') === 0 - acquiredTypeDefs[packageName] = responseJSON + const dtsFileURL = isDeno ? path : unpkgURL(mod, path) - if (responseJSON.types.ts === 'included') { - const modPackageURL = packageJSONURL(packageName) + const content = await getCachedDTSString(config, dtsFileURL) + if (!content) { + return errorMsg(`Could not get root d.ts file for the module '${mod}' at ${path}`, {}, config) + } - const response = await fetch(modPackageURL) - if (!response.ok) { - return errorMsg(`Could not get Package JSON for the module '${packageName}'`, response) - } + // Now look and grab dependent modules where you need the + await getDependenciesForModule(content, mod, path, config) - const responseJSON = await response.json() - if (!responseJSON) { - return errorMsg(`Could not get Package JSON for the module '${packageName}'`, response) - } + if (isDeno) { + const wrapped = `declare module "${path}" { ${content} }` + config.addLibraryToRuntime(wrapped, path) + } else { + const typelessModule = mod.split('@types/').slice(-1) + const wrapped = `declare module "${typelessModule}" { ${content} }` + config.addLibraryToRuntime(wrapped, `node_modules/${mod}/${path}`) + } +} - // Get the path of the root d.ts file +/** + * Takes a module import, then uses both the algolia API and the the package.json to derive + * the root type def path. + * + * @param {string} packageName + * @returns {Promise<{ mod: string, path: string, packageJSON: any }>} + */ +const getModuleAndRootDefTypePath = async (packageName: string, config: ATAConfig) => { + const url = moduleJSONURL(packageName) - // non-inferred route - let rootTypePath = responseJSON.typing || responseJSON.typings || responseJSON.types + const response = await config.fetcher(url) + if (!response.ok) { + return errorMsg(`Could not get Algolia JSON for the module '${packageName}'`, response, config) + } - // package main is custom - if (!rootTypePath && typeof responseJSON.main === 'string' && responseJSON.main.indexOf('.js') > 0) { - rootTypePath = responseJSON.main.replace(/js$/, 'd.ts') - } + const responseJSON = await response.json() + if (!responseJSON) { + return errorMsg(`Could the Algolia JSON was un-parsable for the module '${packageName}'`, response, config) + } - // Final fallback, to have got here it must have passed in algolia - if (!rootTypePath) { - rootTypePath = 'index.d.ts' - } + if (!responseJSON.types) { + return config.logger.log(`There were no types for '${packageName}' - will not try again in this session`) + } + if (!responseJSON.types.ts) { + return config.logger.log(`There were no types for '${packageName}' - will not try again in this session`) + } - return { mod: packageName, path: rootTypePath, packageJSON: responseJSON } - } else if (responseJSON.types.ts === 'definitely-typed') { - return { mod: responseJSON.types.definitelyTyped, path: 'index.d.ts', packageJSON: responseJSON } - } else { - throw "This shouldn't happen" - } + acquiredTypeDefs[packageName] = responseJSON + + if (responseJSON.types.ts === 'included') { + const modPackageURL = packageJSONURL(packageName) + + const response = await config.fetcher(modPackageURL) + if (!response.ok) { + return errorMsg(`Could not get Package JSON for the module '${packageName}'`, response, config) } - const mapModuleNameToModule = (name: string) => { - // in node repl: - // > require("module").builtinModules - const builtInNodeMods = [ - 'assert', - 'async_hooks', - 'base', - 'buffer', - 'child_process', - 'cluster', - 'console', - 'constants', - 'crypto', - 'dgram', - 'dns', - 'domain', - 'events', - 'fs', - 'globals', - 'http', - 'http2', - 'https', - 'index', - 'inspector', - 'module', - 'net', - 'os', - 'path', - 'perf_hooks', - 'process', - 'punycode', - 'querystring', - 'readline', - 'repl', - 'stream', - 'string_decoder', - 'timers', - 'tls', - 'trace_events', - 'tty', - 'url', - 'util', - 'v8', - 'vm', - 'worker_threads', - 'zlib', - ] - if (builtInNodeMods.includes(name)) { - return 'node' - } - return name + const responseJSON = await response.json() + if (!responseJSON) { + return errorMsg(`Could not get Package JSON for the module '${packageName}'`, response, config) } - //** A really dumb version of path.resolve */ - const mapRelativePath = (_outerModule: string, moduleDeclaration: string, currentPath: string) => { - // https://stackoverflow.com/questions/14780350/convert-relative-path-to-absolute-using-javascript - function absolute(base: string, relative: string) { - if (!base) return relative + config.addLibraryToRuntime(JSON.stringify(responseJSON, null, ' '), `node_modules/${packageName}/package.json`) - const stack = base.split('/') - const parts = relative.split('/') - stack.pop() // remove current file name (or empty string) + // Get the path of the root d.ts file - for (var i = 0; i < parts.length; i++) { - if (parts[i] == '.') continue - if (parts[i] == '..') stack.pop() - else stack.push(parts[i]) - } - return stack.join('/') - } + // non-inferred route + let rootTypePath = responseJSON.typing || responseJSON.typings || responseJSON.types - return absolute(currentPath, moduleDeclaration) + // package main is custom + if (!rootTypePath && typeof responseJSON.main === 'string' && responseJSON.main.indexOf('.js') > 0) { + rootTypePath = responseJSON.main.replace(/js$/, 'd.ts') } - const convertToModuleReferenceID = (outerModule: string, moduleDeclaration: string, currentPath: string) => { - const modIsScopedPackageOnly = moduleDeclaration.indexOf('@') === 0 && moduleDeclaration.split('/').length === 2 - const modIsPackageOnly = moduleDeclaration.indexOf('@') === -1 && moduleDeclaration.split('/').length === 1 - const isPackageRootImport = modIsPackageOnly || modIsScopedPackageOnly - - if (isPackageRootImport) { - return moduleDeclaration - } else { - return `${outerModule}-${mapRelativePath(outerModule, moduleDeclaration, currentPath)}` - } + // Final fallback, to have got here it must have passed in algolia + if (!rootTypePath) { + rootTypePath = 'index.d.ts' } - /** @type {string[]} */ - const filteredModulesToLookAt = Array.from(foundModules) + return { mod: packageName, path: rootTypePath, packageJSON: responseJSON } + } else if (responseJSON.types.ts === 'definitely-typed') { + return { mod: responseJSON.types.definitelyTyped, path: 'index.d.ts', packageJSON: responseJSON } + } else { + throw "This shouldn't happen" + } +} - filteredModulesToLookAt.forEach(async name => { - // Support grabbing the hard-coded node modules if needed - const moduleToDownload = mapModuleNameToModule(name) +const getCachedDTSString = async (config: ATAConfig, url: string) => { + const cached = localStorage.getItem(url) + if (cached) { + const [dateString, text] = cached.split('-=-^-=-') + const cachedDate = new Date(dateString) + const now = new Date() - if (!mod && moduleToDownload.startsWith('.')) { - return console.log("Can't resolve local relative dependencies") - } + const cacheTimeout = 604800000 // 1 week + // const cacheTimeout = 60000 // 1 min - const moduleID = convertToModuleReferenceID(mod!, moduleToDownload, path!) - if (acquiredTypeDefs[moduleID] || acquiredTypeDefs[moduleID] === null) { - return - } + if (now.getTime() - cachedDate.getTime() < cacheTimeout) { + return lzstring.decompressFromUTF16(text) + } else { + config.logger.log('Skipping cache for ', url) + } + } - const modIsScopedPackageOnly = moduleToDownload.indexOf('@') === 0 && moduleToDownload.split('/').length === 2 - const modIsPackageOnly = moduleToDownload.indexOf('@') === -1 && moduleToDownload.split('/').length === 1 - const isPackageRootImport = modIsPackageOnly || modIsScopedPackageOnly - const isDenoModule = moduleToDownload.indexOf('https://') === 0 + const response = await config.fetcher(url) + if (!response.ok) { + return errorMsg(`Could not get DTS response for the module at ${url}`, response, config) + } - if (isPackageRootImport) { - // So it doesn't run twice for a package - acquiredTypeDefs[moduleID] = null + // TODO: handle checking for a resolve to index.d.ts whens someone imports the folder + let content = await response.text() + if (!content) { + return errorMsg(`Could not get text for DTS response at ${url}`, response, config) + } - // E.g. import danger from "danger" - const packageDef = await getModuleAndRootDefTypePath(moduleToDownload) + const now = new Date() + const cacheContent = `${now.toISOString()}-=-^-=-${lzstring.compressToUTF16(content)}` + localStorage.setItem(url, cacheContent) + return content +} - if (packageDef) { - acquiredTypeDefs[moduleID] = packageDef.packageJSON - await addModuleToRuntime(packageDef.mod, packageDef.path) +const getReferenceDependencies = async (sourceCode: string, mod: string, path: string, config: ATAConfig) => { + var match + if (sourceCode.indexOf('reference path') > 0) { + // https://regex101.com/r/DaOegw/1 + const referencePathExtractionPattern = //gm + while ((match = referencePathExtractionPattern.exec(sourceCode)) !== null) { + const relativePath = match[1] + if (relativePath) { + let newPath = mapRelativePath(relativePath, path) + if (newPath) { + const dtsRefURL = unpkgURL(mod, newPath) + + const dtsReferenceResponseText = await getCachedDTSString(config, dtsRefURL) + if (!dtsReferenceResponseText) { + return errorMsg(`Could not get root d.ts file for the module '${mod}' at ${path}`, {}, config) + } + + await getDependenciesForModule(dtsReferenceResponseText, mod, newPath, config) + const representationalPath = `node_modules/${mod}/${newPath}` + config.addLibraryToRuntime(dtsReferenceResponseText, representationalPath) } - } else if (isDenoModule) { - // E.g. import { serve } from "https://deno.land/std@v0.12/http/server.ts"; - await addModuleToRuntime(moduleToDownload, moduleToDownload) - } else { - // E.g. import {Component} from "./MyThing" - if (!moduleToDownload || !path) throw `No outer module or path for a relative import: ${moduleToDownload}` - - const absolutePathForModule = mapRelativePath(mod!, moduleToDownload, path) - // So it doesn't run twice for a package - acquiredTypeDefs[moduleID] = null - const resolvedFilepath = absolutePathForModule.endsWith('.ts') - ? absolutePathForModule - : absolutePathForModule + '.d.ts' - await addModuleToRuntime(mod!, resolvedFilepath) } - }) - getReferenceDependencies(sourceCode, mod!, path!) + } + } +} + +interface ATAConfig { + sourceCode: string + addLibraryToRuntime: AddLibToRuntimeFunc + fetcher: typeof fetch + logger: PlaygroundConfig['logger'] +} + +/** + * Pseudo in-browser type acquisition tool, uses a + */ +export const detectNewImportsToAcquireTypeFor = async ( + sourceCode: string, + userAddLibraryToRuntime: AddLibToRuntimeFunc, + fetcher = fetch, + playgroundConfig: PlaygroundConfig +) => { + // Wrap the runtime func with our own side-effect for visibility + const addLibraryToRuntime = (code: string, path: string) => { + globalishObj.typeDefinitions[path] = code + userAddLibraryToRuntime(code, path) } - // Start diving into the root - getTypeDependenciesForSourceCode(sourceCode, undefined, undefined) + // Basically start the recursion with an undefined module + const config: ATAConfig = { sourceCode, addLibraryToRuntime, fetcher, logger: playgroundConfig.logger } + return getDependenciesForModule(sourceCode, undefined, 'playground.ts', config) +} + +/** + * Looks at a JS/DTS file and recurses through all the dependencies. + * It avoids + */ +const getDependenciesForModule = ( + sourceCode: string, + moduleName: string | undefined, + path: string, + config: ATAConfig +) => { + // Get all the import/requires for the file + const filteredModulesToLookAt = parseFileForModuleReferences(sourceCode) + filteredModulesToLookAt.forEach(async name => { + // Support grabbing the hard-coded node modules if needed + const moduleToDownload = mapModuleNameToModule(name) + + if (!moduleName && moduleToDownload.startsWith('.')) { + return config.logger.log("[ATA] Can't resolve relative dependencies from the playground root") + } + + const moduleID = convertToModuleReferenceID(moduleName!, moduleToDownload, moduleName!) + if (acquiredTypeDefs[moduleID] || acquiredTypeDefs[moduleID] === null) { + return + } + + config.logger.log(`[ATA] Looking at ${moduleToDownload}`) + + const modIsScopedPackageOnly = moduleToDownload.indexOf('@') === 0 && moduleToDownload.split('/').length === 2 + const modIsPackageOnly = moduleToDownload.indexOf('@') === -1 && moduleToDownload.split('/').length === 1 + const isPackageRootImport = modIsPackageOnly || modIsScopedPackageOnly + const isDenoModule = moduleToDownload.indexOf('https://') === 0 + + if (isPackageRootImport) { + // So it doesn't run twice for a package + acquiredTypeDefs[moduleID] = null + + // E.g. import danger from "danger" + const packageDef = await getModuleAndRootDefTypePath(moduleToDownload, config) + + if (packageDef) { + acquiredTypeDefs[moduleID] = packageDef.packageJSON + await addModuleToRuntime(packageDef.mod, packageDef.path, config) + } + } else if (isDenoModule) { + // E.g. import { serve } from "https://deno.land/std@v0.12/http/server.ts"; + await addModuleToRuntime(moduleToDownload, moduleToDownload, config) + } else { + // E.g. import {Component} from "./MyThing" + if (!moduleToDownload || !path) throw `No outer module or path for a relative import: ${moduleToDownload}` + + const absolutePathForModule = mapRelativePath(moduleToDownload, path) + + // So it doesn't run twice for a package + acquiredTypeDefs[moduleID] = null + + const resolvedFilepath = absolutePathForModule.endsWith('.ts') + ? absolutePathForModule + : absolutePathForModule + '.d.ts' + + await addModuleToRuntime(moduleName!, resolvedFilepath, config) + } + }) + + // Also support the + getReferenceDependencies(sourceCode, moduleName!, path!, config) } diff --git a/packages/sandbox/src/vendor/lzstring.min.d.ts b/packages/sandbox/src/vendor/lzstring.min.d.ts new file mode 100644 index 000000000000..ed89e8343d2f --- /dev/null +++ b/packages/sandbox/src/vendor/lzstring.min.d.ts @@ -0,0 +1,14 @@ +export function compressToBase64(input: string): string +export function decompressFromBase64(input: string): string + +export function compressToUTF16(input: string): string +export function decompressFromUTF16(compressed: string): string + +export function compressToUint8Array(uncompressed: string): Uint8Array +export function decompressFromUint8Array(compressed: Uint8Array): string + +export function compressToEncodedURIComponent(input: string): string +export function decompressFromEncodedURIComponent(compressed: string): string + +export function compress(input: string): string +export function decompress(compressed: string): string diff --git a/packages/sandbox/src/vendor/lzstring.min.js b/packages/sandbox/src/vendor/lzstring.min.js new file mode 100644 index 000000000000..ec030d65d912 --- /dev/null +++ b/packages/sandbox/src/vendor/lzstring.min.js @@ -0,0 +1,283 @@ +// Original license reproduced below: + +// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE +// Version 2, December 2004 +// +// Copyright (C) 2004 Sam Hocevar +// +// Everyone is permitted to copy and distribute verbatim or modified +// copies of this license document, and changing it is allowed as long +// as the name is changed. +// +// DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE +// TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION +// +// 0. You just DO WHAT THE FUCK YOU WANT TO. +// + +var LZString = (function() { + function o(o, r) { + if (!t[o]) { + t[o] = {} + for (var n = 0; n < o.length; n++) t[o][o.charAt(n)] = n + } + return t[o][r] + } + var r = String.fromCharCode, + n = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=', + e = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+-$', + t = {}, + i = { + compressToBase64: function(o) { + if (null == o) return '' + var r = i._compress(o, 6, function(o) { + return n.charAt(o) + }) + switch (r.length % 4) { + default: + case 0: + return r + case 1: + return r + '===' + case 2: + return r + '==' + case 3: + return r + '=' + } + }, + decompressFromBase64: function(r) { + return null == r + ? '' + : '' == r + ? null + : i._decompress(r.length, 32, function(e) { + return o(n, r.charAt(e)) + }) + }, + compressToUTF16: function(o) { + return null == o + ? '' + : i._compress(o, 15, function(o) { + return r(o + 32) + }) + ' ' + }, + decompressFromUTF16: function(o) { + return null == o + ? '' + : '' == o + ? null + : i._decompress(o.length, 16384, function(r) { + return o.charCodeAt(r) - 32 + }) + }, + compressToUint8Array: function(o) { + for (var r = i.compress(o), n = new Uint8Array(2 * r.length), e = 0, t = r.length; t > e; e++) { + var s = r.charCodeAt(e) + ;(n[2 * e] = s >>> 8), (n[2 * e + 1] = s % 256) + } + return n + }, + decompressFromUint8Array: function(o) { + if (null === o || void 0 === o) return i.decompress(o) + for (var n = new Array(o.length / 2), e = 0, t = n.length; t > e; e++) n[e] = 256 * o[2 * e] + o[2 * e + 1] + var s = [] + return ( + n.forEach(function(o) { + s.push(r(o)) + }), + i.decompress(s.join('')) + ) + }, + compressToEncodedURIComponent: function(o) { + return null == o + ? '' + : i._compress(o, 6, function(o) { + return e.charAt(o) + }) + }, + decompressFromEncodedURIComponent: function(r) { + return null == r + ? '' + : '' == r + ? null + : ((r = r.replace(/ /g, '+')), + i._decompress(r.length, 32, function(n) { + return o(e, r.charAt(n)) + })) + }, + compress: function(o) { + return i._compress(o, 16, function(o) { + return r(o) + }) + }, + _compress: function(o, r, n) { + if (null == o) return '' + var e, + t, + i, + s = {}, + p = {}, + u = '', + c = '', + a = '', + l = 2, + f = 3, + h = 2, + d = [], + m = 0, + v = 0 + for (i = 0; i < o.length; i += 1) + if ( + ((u = o.charAt(i)), + Object.prototype.hasOwnProperty.call(s, u) || ((s[u] = f++), (p[u] = !0)), + (c = a + u), + Object.prototype.hasOwnProperty.call(s, c)) + ) + a = c + else { + if (Object.prototype.hasOwnProperty.call(p, a)) { + if (a.charCodeAt(0) < 256) { + for (e = 0; h > e; e++) (m <<= 1), v == r - 1 ? ((v = 0), d.push(n(m)), (m = 0)) : v++ + for (t = a.charCodeAt(0), e = 0; 8 > e; e++) + (m = (m << 1) | (1 & t)), v == r - 1 ? ((v = 0), d.push(n(m)), (m = 0)) : v++, (t >>= 1) + } else { + for (t = 1, e = 0; h > e; e++) + (m = (m << 1) | t), v == r - 1 ? ((v = 0), d.push(n(m)), (m = 0)) : v++, (t = 0) + for (t = a.charCodeAt(0), e = 0; 16 > e; e++) + (m = (m << 1) | (1 & t)), v == r - 1 ? ((v = 0), d.push(n(m)), (m = 0)) : v++, (t >>= 1) + } + l--, 0 == l && ((l = Math.pow(2, h)), h++), delete p[a] + } else + for (t = s[a], e = 0; h > e; e++) + (m = (m << 1) | (1 & t)), v == r - 1 ? ((v = 0), d.push(n(m)), (m = 0)) : v++, (t >>= 1) + l--, 0 == l && ((l = Math.pow(2, h)), h++), (s[c] = f++), (a = String(u)) + } + if ('' !== a) { + if (Object.prototype.hasOwnProperty.call(p, a)) { + if (a.charCodeAt(0) < 256) { + for (e = 0; h > e; e++) (m <<= 1), v == r - 1 ? ((v = 0), d.push(n(m)), (m = 0)) : v++ + for (t = a.charCodeAt(0), e = 0; 8 > e; e++) + (m = (m << 1) | (1 & t)), v == r - 1 ? ((v = 0), d.push(n(m)), (m = 0)) : v++, (t >>= 1) + } else { + for (t = 1, e = 0; h > e; e++) + (m = (m << 1) | t), v == r - 1 ? ((v = 0), d.push(n(m)), (m = 0)) : v++, (t = 0) + for (t = a.charCodeAt(0), e = 0; 16 > e; e++) + (m = (m << 1) | (1 & t)), v == r - 1 ? ((v = 0), d.push(n(m)), (m = 0)) : v++, (t >>= 1) + } + l--, 0 == l && ((l = Math.pow(2, h)), h++), delete p[a] + } else + for (t = s[a], e = 0; h > e; e++) + (m = (m << 1) | (1 & t)), v == r - 1 ? ((v = 0), d.push(n(m)), (m = 0)) : v++, (t >>= 1) + l--, 0 == l && ((l = Math.pow(2, h)), h++) + } + for (t = 2, e = 0; h > e; e++) + (m = (m << 1) | (1 & t)), v == r - 1 ? ((v = 0), d.push(n(m)), (m = 0)) : v++, (t >>= 1) + for (;;) { + if (((m <<= 1), v == r - 1)) { + d.push(n(m)) + break + } + v++ + } + return d.join('') + }, + decompress: function(o) { + return null == o + ? '' + : '' == o + ? null + : i._decompress(o.length, 32768, function(r) { + return o.charCodeAt(r) + }) + }, + _decompress: function(o, n, e) { + var t, + i, + s, + p, + u, + c, + a, + l, + f = [], + h = 4, + d = 4, + m = 3, + v = '', + w = [], + A = { val: e(0), position: n, index: 1 } + for (i = 0; 3 > i; i += 1) f[i] = i + for (p = 0, c = Math.pow(2, 2), a = 1; a != c; ) + (u = A.val & A.position), + (A.position >>= 1), + 0 == A.position && ((A.position = n), (A.val = e(A.index++))), + (p |= (u > 0 ? 1 : 0) * a), + (a <<= 1) + switch ((t = p)) { + case 0: + for (p = 0, c = Math.pow(2, 8), a = 1; a != c; ) + (u = A.val & A.position), + (A.position >>= 1), + 0 == A.position && ((A.position = n), (A.val = e(A.index++))), + (p |= (u > 0 ? 1 : 0) * a), + (a <<= 1) + l = r(p) + break + case 1: + for (p = 0, c = Math.pow(2, 16), a = 1; a != c; ) + (u = A.val & A.position), + (A.position >>= 1), + 0 == A.position && ((A.position = n), (A.val = e(A.index++))), + (p |= (u > 0 ? 1 : 0) * a), + (a <<= 1) + l = r(p) + break + case 2: + return '' + } + for (f[3] = l, s = l, w.push(l); ; ) { + if (A.index > o) return '' + for (p = 0, c = Math.pow(2, m), a = 1; a != c; ) + (u = A.val & A.position), + (A.position >>= 1), + 0 == A.position && ((A.position = n), (A.val = e(A.index++))), + (p |= (u > 0 ? 1 : 0) * a), + (a <<= 1) + switch ((l = p)) { + case 0: + for (p = 0, c = Math.pow(2, 8), a = 1; a != c; ) + (u = A.val & A.position), + (A.position >>= 1), + 0 == A.position && ((A.position = n), (A.val = e(A.index++))), + (p |= (u > 0 ? 1 : 0) * a), + (a <<= 1) + ;(f[d++] = r(p)), (l = d - 1), h-- + break + case 1: + for (p = 0, c = Math.pow(2, 16), a = 1; a != c; ) + (u = A.val & A.position), + (A.position >>= 1), + 0 == A.position && ((A.position = n), (A.val = e(A.index++))), + (p |= (u > 0 ? 1 : 0) * a), + (a <<= 1) + ;(f[d++] = r(p)), (l = d - 1), h-- + break + case 2: + return w.join('') + } + if ((0 == h && ((h = Math.pow(2, m)), m++), f[l])) v = f[l] + else { + if (l !== d) return null + v = s + s.charAt(0) + } + w.push(v), (f[d++] = s + v.charAt(0)), h--, (s = v), 0 == h && ((h = Math.pow(2, m)), m++) + } + }, + } + return i +})() +'function' == typeof define && define.amd + ? define(function() { + return LZString + }) + : 'undefined' != typeof module && null != module && (module.exports = LZString) diff --git a/packages/sandbox/tsconfig.json b/packages/sandbox/tsconfig.json index 33a09f0c7758..aef701b556dc 100644 --- a/packages/sandbox/tsconfig.json +++ b/packages/sandbox/tsconfig.json @@ -1,14 +1,15 @@ { - "include": ["src"], + "include": ["src", "src/vendor/lzstring.min.js"], "compilerOptions": { "outDir": "../typescriptlang-org/static/js/sandbox", - "target": "ES2015", + "target": "ES2016", "module": "AMD", "lib": ["dom", "esnext"], "declaration": true, "sourceMap": true, "rootDir": "./src", "strict": true, + "allowJs": true, "moduleResolution": "node", "esModuleInterop": true } diff --git a/packages/ts-twoslasher/package.json b/packages/ts-twoslasher/package.json index fb4dd36e8809..5cd9e3cf518b 100644 --- a/packages/ts-twoslasher/package.json +++ b/packages/ts-twoslasher/package.json @@ -12,6 +12,7 @@ "scripts": { "start": "tsdx watch", "build": "tsdx build; yarn readme", + "bootstrap": "yarn build", "readme": "yarn md-magic README.md --config ./scripts/inline-results.js", "test": "tsdx test", "lint": "tsdx lint" diff --git a/packages/tsconfig-reference/copy/en/options/allowJs.md b/packages/tsconfig-reference/copy/en/options/allowJs.md index 251cb08da127..31d9d32c4c97 100644 --- a/packages/tsconfig-reference/copy/en/options/allowJs.md +++ b/packages/tsconfig-reference/copy/en/options/allowJs.md @@ -1,5 +1,6 @@ --- display: "Allow JS" +oneline: "Let TS include .JS files in imports" --- Allow JavaScript files to be imported inside your project, instead of just `.ts` and `.tsx` files. For example, this JS file: diff --git a/packages/tsconfig-reference/copy/en/options/allowSyntheticDefaultImports.md b/packages/tsconfig-reference/copy/en/options/allowSyntheticDefaultImports.md index de3ee62a8154..18114800a3f6 100644 --- a/packages/tsconfig-reference/copy/en/options/allowSyntheticDefaultImports.md +++ b/packages/tsconfig-reference/copy/en/options/allowSyntheticDefaultImports.md @@ -1,5 +1,6 @@ --- display: "Allow Synthetic Default Imports" +oneline: "Allow 'import x from y' when a module doesn't have a default export" --- When set to true, `allowSyntheticDefaultImports` let's you write an import like: diff --git a/packages/tsconfig-reference/copy/en/options/allowUmdGlobalAccess.md b/packages/tsconfig-reference/copy/en/options/allowUmdGlobalAccess.md index a29f1cf5f65d..889412634673 100644 --- a/packages/tsconfig-reference/copy/en/options/allowUmdGlobalAccess.md +++ b/packages/tsconfig-reference/copy/en/options/allowUmdGlobalAccess.md @@ -1,5 +1,6 @@ --- display: "Allow Umd Global Access" +oneline: "Assume UMD imports are all globally available" --- When set to true, `allowUmdGlobalAccess` lets you access UMD exports as globals from inside module files. A module file is a file that has imports and/or exports. Without this flag, using an export from a UMD module requires an import declaration. diff --git a/packages/tsconfig-reference/copy/en/options/allowUnreachableCode.md b/packages/tsconfig-reference/copy/en/options/allowUnreachableCode.md index 088fefe64677..73500cbc8797 100644 --- a/packages/tsconfig-reference/copy/en/options/allowUnreachableCode.md +++ b/packages/tsconfig-reference/copy/en/options/allowUnreachableCode.md @@ -1,5 +1,6 @@ --- display: "Allow Unreachable Code" +oneline: "Error when code will never be called" --- Set to false to disable warnings about unreachable code. diff --git a/packages/tsconfig-reference/copy/en/options/allowUnusedLabels.md b/packages/tsconfig-reference/copy/en/options/allowUnusedLabels.md index 3d767912b298..37cd43c447cd 100644 --- a/packages/tsconfig-reference/copy/en/options/allowUnusedLabels.md +++ b/packages/tsconfig-reference/copy/en/options/allowUnusedLabels.md @@ -1,5 +1,6 @@ --- display: "Allow Unused Labels" +oneline: "Error when accidentally creating a label" --- Set to false to disable warnings about unused labels. diff --git a/packages/tsconfig-reference/copy/en/options/alwaysStrict.md b/packages/tsconfig-reference/copy/en/options/alwaysStrict.md index 8fdda2d06ad5..328ac43dccac 100644 --- a/packages/tsconfig-reference/copy/en/options/alwaysStrict.md +++ b/packages/tsconfig-reference/copy/en/options/alwaysStrict.md @@ -1,5 +1,6 @@ --- display: "Always Strict" +oneline: "Ensure 'use strict' is always emitted" --- Ensures that your files are parsed in the ECMAScript strict mode, and emit "use strict" for each source file. diff --git a/packages/tsconfig-reference/copy/en/options/baseUrl.md b/packages/tsconfig-reference/copy/en/options/baseUrl.md index cd8d62ac25bb..fa995c7cf30a 100644 --- a/packages/tsconfig-reference/copy/en/options/baseUrl.md +++ b/packages/tsconfig-reference/copy/en/options/baseUrl.md @@ -1,5 +1,6 @@ --- display: "Base Url" +oneline: "Set a baseurl for relative module names" --- Lets you set a base directory to resolve non-absolute module names. diff --git a/packages/tsconfig-reference/copy/en/options/charset.md b/packages/tsconfig-reference/copy/en/options/charset.md index 1831fd40d2ce..ed634ebda235 100644 --- a/packages/tsconfig-reference/copy/en/options/charset.md +++ b/packages/tsconfig-reference/copy/en/options/charset.md @@ -1,5 +1,6 @@ --- display: "Charset" +oneline: "Manually set the text encoding for reading files" --- In prior versions of TypeScript, this controlled what encoding was used when reading text files from disk. diff --git a/packages/tsconfig-reference/copy/en/options/checkJs.md b/packages/tsconfig-reference/copy/en/options/checkJs.md index 049364e706ff..4d0f50046225 100644 --- a/packages/tsconfig-reference/copy/en/options/checkJs.md +++ b/packages/tsconfig-reference/copy/en/options/checkJs.md @@ -1,5 +1,6 @@ --- display: "Check JS" +oneline: "Run the type checker on .js files in your project" --- Works in tandem with `allowJs`. When `checkJs` is enabled then errors are reported in JavaScript files. This is diff --git a/packages/tsconfig-reference/copy/en/options/composite.md b/packages/tsconfig-reference/copy/en/options/composite.md index 3a202addf856..a97da7579ab9 100644 --- a/packages/tsconfig-reference/copy/en/options/composite.md +++ b/packages/tsconfig-reference/copy/en/options/composite.md @@ -1,5 +1,6 @@ --- display: "Composite" +oneline: "Used to create multiple build projects" --- The `composite` option enforces certain constraints which make it possible for build tools (including TypeScript diff --git a/packages/tsconfig-reference/copy/en/options/declaration.md b/packages/tsconfig-reference/copy/en/options/declaration.md index d11cdb52a21e..7d62f83ee8ef 100644 --- a/packages/tsconfig-reference/copy/en/options/declaration.md +++ b/packages/tsconfig-reference/copy/en/options/declaration.md @@ -1,5 +1,6 @@ --- display: "Declaration" +oneline: "Emit d.ts files for referenced files in the project" --- Generate `d.ts` files for every TypeScript or JavaScript file inside your project. diff --git a/packages/tsconfig-reference/copy/en/options/declarationDir.md b/packages/tsconfig-reference/copy/en/options/declarationDir.md index ec39382029c5..ce3d5b764019 100644 --- a/packages/tsconfig-reference/copy/en/options/declarationDir.md +++ b/packages/tsconfig-reference/copy/en/options/declarationDir.md @@ -1,5 +1,6 @@ --- display: "Declaration Dir" +oneline: "Set the root directory for d.ts files to go" --- Offers a way to configure the root directory for where declaration files are emitted. diff --git a/packages/tsconfig-reference/copy/en/options/declarationMap.md b/packages/tsconfig-reference/copy/en/options/declarationMap.md index d02c4db1a27f..f5136d4c9fd3 100644 --- a/packages/tsconfig-reference/copy/en/options/declarationMap.md +++ b/packages/tsconfig-reference/copy/en/options/declarationMap.md @@ -1,5 +1,6 @@ --- display: "Declaration Map" +oneline: "Create sourcemaps for d.ts files" --- Generates a source map for `.d.ts` files which map back to the original `.ts` source file. diff --git a/packages/tsconfig-reference/copy/en/options/diagnostics.md b/packages/tsconfig-reference/copy/en/options/diagnostics.md index 075c1284f35b..17c8c0cfa2cf 100644 --- a/packages/tsconfig-reference/copy/en/options/diagnostics.md +++ b/packages/tsconfig-reference/copy/en/options/diagnostics.md @@ -1,5 +1,6 @@ --- display: "Diagnostics" +oneline: "Output additional information after a compile" --- Used to output diagnostic information for debugging. This command is a subset of [`extendedDiagnostics`](#extendedDiagnostics) which are more user-facing results, and easier to interpret. diff --git a/packages/tsconfig-reference/copy/en/options/disableSizeLimit.md b/packages/tsconfig-reference/copy/en/options/disableSizeLimit.md index a858eb5eedae..2e6274df6bd0 100644 --- a/packages/tsconfig-reference/copy/en/options/disableSizeLimit.md +++ b/packages/tsconfig-reference/copy/en/options/disableSizeLimit.md @@ -1,5 +1,6 @@ --- display: "Disable Size Limit" +oneline: "Remove the memory cap on the TypeScript language server" --- To avoid a possible memory bloat issue when working with very large JavaScript projects, there is an upper limit to the amount of memory TypeScript will allocate. Turning this flag on will remove the limit. diff --git a/packages/tsconfig-reference/copy/en/options/disableSourceOfProjectReferenceRedirect.md b/packages/tsconfig-reference/copy/en/options/disableSourceOfProjectReferenceRedirect.md index 8f0cdedb3ff0..a0000949e8cd 100644 --- a/packages/tsconfig-reference/copy/en/options/disableSourceOfProjectReferenceRedirect.md +++ b/packages/tsconfig-reference/copy/en/options/disableSourceOfProjectReferenceRedirect.md @@ -1,5 +1,6 @@ --- display: "Disable Source Project Reference Redirect" +oneline: "Use d.ts files as the source of truth for tooling between composite project boundries" --- When working with [composite TypeScript projects](/docs/handbook/project-references.html), this option provides a way to go [back to the pre-3.7](/docs/handbook/release-notes/typescript-3-7.html#build-free-editing-with-project-references) behavior where d.ts files were used to as the boundaries between modules. diff --git a/packages/tsconfig-reference/copy/en/options/downlevelIteration.md b/packages/tsconfig-reference/copy/en/options/downlevelIteration.md index 08e9c9591f6e..b6eeb43258db 100644 --- a/packages/tsconfig-reference/copy/en/options/downlevelIteration.md +++ b/packages/tsconfig-reference/copy/en/options/downlevelIteration.md @@ -1,5 +1,6 @@ --- display: "Downlevel Iteration" +oneline: "Emit more compliant, but verbose JavaScript for iterating objects" --- Downleveling is TypeScript's term for transpiling to an older version of JavaScript. diff --git a/packages/tsconfig-reference/copy/en/options/emitBOM.md b/packages/tsconfig-reference/copy/en/options/emitBOM.md index bb05d4169da2..58467b35ca8e 100644 --- a/packages/tsconfig-reference/copy/en/options/emitBOM.md +++ b/packages/tsconfig-reference/copy/en/options/emitBOM.md @@ -1,5 +1,6 @@ --- display: "Emit BOM" +oneline: "Include a byte order mark to output files" --- Controls whether TypeScript will emit a [byte order mark (BOM)](https://en.wikipedia.org/wiki/Byte_order_mark) when writing output files. diff --git a/packages/tsconfig-reference/copy/en/options/emitDeclarationOnly.md b/packages/tsconfig-reference/copy/en/options/emitDeclarationOnly.md index 0d65634066b3..4ffe7d245fce 100644 --- a/packages/tsconfig-reference/copy/en/options/emitDeclarationOnly.md +++ b/packages/tsconfig-reference/copy/en/options/emitDeclarationOnly.md @@ -1,5 +1,6 @@ --- display: "Emit Declaration Only" +oneline: "Only output d.ts files and not .js files" --- _Only_ emit `.d.ts` files; do not emit `.js` files. diff --git a/packages/tsconfig-reference/copy/en/options/emitDecoratorMetadata.md b/packages/tsconfig-reference/copy/en/options/emitDecoratorMetadata.md index 5d9e6ab62f88..e9339be0a5c7 100644 --- a/packages/tsconfig-reference/copy/en/options/emitDecoratorMetadata.md +++ b/packages/tsconfig-reference/copy/en/options/emitDecoratorMetadata.md @@ -1,5 +1,6 @@ --- display: "Emit Decorator Metadata" +oneline: "Adds additional type metadata to decorators in emitted code" --- Enables experimental support for emitting type metadata for decorators which works with the module [`reflect-metadata`](https://www.npmjs.com/package/reflect-metadata). diff --git a/packages/tsconfig-reference/copy/en/options/esModuleInterop.md b/packages/tsconfig-reference/copy/en/options/esModuleInterop.md index 3463db03b321..894186896f8a 100644 --- a/packages/tsconfig-reference/copy/en/options/esModuleInterop.md +++ b/packages/tsconfig-reference/copy/en/options/esModuleInterop.md @@ -1,5 +1,6 @@ --- -display: "Es Module Interop" +display: "ES Module Interop" +oneline: "Emit additional JS to ease support for importing commonjs modules" --- Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. diff --git a/packages/tsconfig-reference/copy/en/options/exclude.md b/packages/tsconfig-reference/copy/en/options/exclude.md index df1264753453..1397681bcdf8 100644 --- a/packages/tsconfig-reference/copy/en/options/exclude.md +++ b/packages/tsconfig-reference/copy/en/options/exclude.md @@ -1,5 +1,6 @@ --- display: "Exclude" +oneline: "Files or patterns to be skipped from the include option" --- Specifies an array of filenames or patterns that should be skipped when resolving `include`. diff --git a/packages/tsconfig-reference/copy/en/options/experimentalDecorators.md b/packages/tsconfig-reference/copy/en/options/experimentalDecorators.md index d93bce3968f4..e02718ee5ede 100644 --- a/packages/tsconfig-reference/copy/en/options/experimentalDecorators.md +++ b/packages/tsconfig-reference/copy/en/options/experimentalDecorators.md @@ -1,5 +1,6 @@ --- display: "Experimental Decorators" +oneline: "Enable experimental support for TC39 stage 2 decorators" --- Enables [experimental support for decorators](https://github.com/tc39/proposal-decorators), which is in stage 2 diff --git a/packages/tsconfig-reference/copy/en/options/extendedDiagnostics.md b/packages/tsconfig-reference/copy/en/options/extendedDiagnostics.md index 89b48267626c..aad4e68226ac 100644 --- a/packages/tsconfig-reference/copy/en/options/extendedDiagnostics.md +++ b/packages/tsconfig-reference/copy/en/options/extendedDiagnostics.md @@ -1,5 +1,6 @@ --- display: "Extended Diagnostics" +oneline: "Include a lot of diagnostic information after a compile" --- You can use this flag to discover where TypeScript is spending it's time when compiling. diff --git a/packages/tsconfig-reference/copy/en/options/extends.md b/packages/tsconfig-reference/copy/en/options/extends.md index f13c66eef424..547cfea15020 100644 --- a/packages/tsconfig-reference/copy/en/options/extends.md +++ b/packages/tsconfig-reference/copy/en/options/extends.md @@ -1,5 +1,6 @@ --- display: "Extends" +oneline: "Inherit options for a TSConfig" --- The value of `extends` is a string which contains a path to another configuration file to inherit from. diff --git a/packages/tsconfig-reference/copy/en/options/files.md b/packages/tsconfig-reference/copy/en/options/files.md index 8b321fb73b06..0094b0dcf965 100644 --- a/packages/tsconfig-reference/copy/en/options/files.md +++ b/packages/tsconfig-reference/copy/en/options/files.md @@ -1,5 +1,6 @@ --- display: "Files" +oneline: "Include a set list of files, does not support globs" --- Specifies an allowlist of files to include in the program. An error occurs if any of the files can't be found. diff --git a/packages/tsconfig-reference/copy/en/options/forceConsistentCasingInFileNames.md b/packages/tsconfig-reference/copy/en/options/forceConsistentCasingInFileNames.md index 1854ebb1b007..e27383a28779 100644 --- a/packages/tsconfig-reference/copy/en/options/forceConsistentCasingInFileNames.md +++ b/packages/tsconfig-reference/copy/en/options/forceConsistentCasingInFileNames.md @@ -1,5 +1,6 @@ --- display: "Force Consistent Casing In File Names" +oneline: "Ensure that casing is correct in imports" --- TypeScript follows the case sensitivity rules of the file system it's running on. diff --git a/packages/tsconfig-reference/copy/en/options/generateCpuProfile.md b/packages/tsconfig-reference/copy/en/options/generateCpuProfile.md index 256b43661482..8e9215ee5358 100644 --- a/packages/tsconfig-reference/copy/en/options/generateCpuProfile.md +++ b/packages/tsconfig-reference/copy/en/options/generateCpuProfile.md @@ -1,5 +1,6 @@ --- display: "Generate CPU Profile" +oneline: "Emit a v8 CPU profile of the compiler run for debugging" --- This option gives you the chance to have TypeScript emit a v8 CPU profile during the compiler run. The CPU profile can provide insight into why your builds may be slow. diff --git a/packages/tsconfig-reference/copy/en/options/importHelpers.md b/packages/tsconfig-reference/copy/en/options/importHelpers.md index faa60c431dfd..00452f9b6a16 100644 --- a/packages/tsconfig-reference/copy/en/options/importHelpers.md +++ b/packages/tsconfig-reference/copy/en/options/importHelpers.md @@ -1,5 +1,6 @@ --- display: "Import Helpers" +oneline: "Allow importing helper functions once per project, instead of including them per-file" --- For certain downleveling operations, TypeScript uses some helper code for operations like extending class, spreading arrays or objects, and async operations. diff --git a/packages/tsconfig-reference/copy/en/options/include.md b/packages/tsconfig-reference/copy/en/options/include.md index f213160141ef..46a37cfc195f 100644 --- a/packages/tsconfig-reference/copy/en/options/include.md +++ b/packages/tsconfig-reference/copy/en/options/include.md @@ -1,5 +1,6 @@ --- display: "Include" +oneline: "Files or patterns to include in this project" --- Specifies an array of filenames or patterns to include in the program. diff --git a/packages/tsconfig-reference/copy/en/options/incremental.md b/packages/tsconfig-reference/copy/en/options/incremental.md index d4b2b0bd1ec0..489443063cf9 100644 --- a/packages/tsconfig-reference/copy/en/options/incremental.md +++ b/packages/tsconfig-reference/copy/en/options/incremental.md @@ -1,5 +1,6 @@ --- display: "Incremental" +oneline: "Save .tsbuildinfo files to allow for incremental compilation of projects" --- Tells TypeScript to save information about the project graph from the last compilation to files stored on disk. This diff --git a/packages/tsconfig-reference/copy/en/options/inlineSourceMap.md b/packages/tsconfig-reference/copy/en/options/inlineSourceMap.md index f33789233c8a..19c21be24f7b 100644 --- a/packages/tsconfig-reference/copy/en/options/inlineSourceMap.md +++ b/packages/tsconfig-reference/copy/en/options/inlineSourceMap.md @@ -1,5 +1,6 @@ --- display: "Inline Source Map" +oneline: "Include sourcemap files inside the emitted JavaScript" --- When set, instead of writing out a `.js.map` file to provide source maps, TypeScript will embed the source map content in the `.js` files. diff --git a/packages/tsconfig-reference/copy/en/options/inlineSources.md b/packages/tsconfig-reference/copy/en/options/inlineSources.md index 68aae928ad38..25be837c5664 100644 --- a/packages/tsconfig-reference/copy/en/options/inlineSources.md +++ b/packages/tsconfig-reference/copy/en/options/inlineSources.md @@ -1,5 +1,6 @@ --- display: "Inline Sources" +oneline: "Include sourcemap files inside the emitted JavaScript" --- When set, TypeScript will include the original content of the `.ts` file as an embedded string in the source map. @@ -9,7 +10,7 @@ Requires either `sourceMap` or `inlineSourceMap` to be set. For example, with this TypeScript: -```ts +```ts twoslash const helloWorld = "hi"; console.log(helloWorld); ``` diff --git a/packages/tsconfig-reference/copy/en/options/isolatedModules.md b/packages/tsconfig-reference/copy/en/options/isolatedModules.md index bfbbb1353e88..50ebbe66fdbe 100644 --- a/packages/tsconfig-reference/copy/en/options/isolatedModules.md +++ b/packages/tsconfig-reference/copy/en/options/isolatedModules.md @@ -1,5 +1,6 @@ --- display: "Isolated Modules" +oneline: "Ensure that each file can be safely transpiled without relying on other imports" --- While you can use TypeScript to produce JavaScript code from TypeScript code, it's also common to use other transpilers such as [Babel](https://babeljs.io) to do this. diff --git a/packages/tsconfig-reference/copy/en/options/jsx.md b/packages/tsconfig-reference/copy/en/options/jsx.md index 4c1c8d6e4577..4bc971449e77 100644 --- a/packages/tsconfig-reference/copy/en/options/jsx.md +++ b/packages/tsconfig-reference/copy/en/options/jsx.md @@ -1,5 +1,6 @@ --- display: "JSX" +oneline: "Control how JSX is emitted" --- Controls how JSX constructs are emitted in JavaScript files. diff --git a/packages/tsconfig-reference/copy/en/options/jsxFactory.md b/packages/tsconfig-reference/copy/en/options/jsxFactory.md index 69bedf764d58..e64010f9b113 100644 --- a/packages/tsconfig-reference/copy/en/options/jsxFactory.md +++ b/packages/tsconfig-reference/copy/en/options/jsxFactory.md @@ -1,5 +1,6 @@ --- display: "JSX Factory" +oneline: "Control the function emitted by JSX" --- Changes the function called in `.js` files when compiling JSX Elements. diff --git a/packages/tsconfig-reference/copy/en/options/keyofStringsOnly.md b/packages/tsconfig-reference/copy/en/options/keyofStringsOnly.md index d6754d121a7b..bf3043674708 100644 --- a/packages/tsconfig-reference/copy/en/options/keyofStringsOnly.md +++ b/packages/tsconfig-reference/copy/en/options/keyofStringsOnly.md @@ -1,5 +1,6 @@ --- display: "Keyof Strings Only" +oneline: "Make keyof only return strings instead of string or numbers" --- This flag changes the `keyof` type operator to return `string` instead of `string | number` when applied to a type with a string index signature. diff --git a/packages/tsconfig-reference/copy/en/options/lib.md b/packages/tsconfig-reference/copy/en/options/lib.md index 7a484ac11cb0..d253a374c876 100644 --- a/packages/tsconfig-reference/copy/en/options/lib.md +++ b/packages/tsconfig-reference/copy/en/options/lib.md @@ -1,5 +1,6 @@ --- display: "Lib" +oneline: "Include type definitions you know are available in your JavaScript runtime" --- TypeScript includes a default set of type definitions for built-in JS APIs (like `Math`), as well as type definitions for things found in browser environments (like `document`). diff --git a/packages/tsconfig-reference/copy/en/options/listEmittedFiles.md b/packages/tsconfig-reference/copy/en/options/listEmittedFiles.md index 58a43cc6ebb3..030ba716ce59 100644 --- a/packages/tsconfig-reference/copy/en/options/listEmittedFiles.md +++ b/packages/tsconfig-reference/copy/en/options/listEmittedFiles.md @@ -1,5 +1,6 @@ --- display: "List Emitted Files" +oneline: "Print the names of emitted files after a compile" --- Print names of generated files part of the compilation to the terminal. diff --git a/packages/tsconfig-reference/copy/en/options/listFiles.md b/packages/tsconfig-reference/copy/en/options/listFiles.md index b56d7c4aa7aa..0da084bea01d 100644 --- a/packages/tsconfig-reference/copy/en/options/listFiles.md +++ b/packages/tsconfig-reference/copy/en/options/listFiles.md @@ -1,5 +1,6 @@ --- display: "List Files" +oneline: "Print all of the files read during the compilation" --- Print names of files part of the compilation. This is useful when you are not sure that TypeScript has diff --git a/packages/tsconfig-reference/copy/en/options/locale.md b/packages/tsconfig-reference/copy/en/options/locale.md index 08964fc2efa9..841f34b14ffb 100644 --- a/packages/tsconfig-reference/copy/en/options/locale.md +++ b/packages/tsconfig-reference/copy/en/options/locale.md @@ -1,5 +1,6 @@ --- display: "Locale" +oneline: "Set the language of the tsc output" --- TypeScript supports a number of languages, however this cannot be set via the `tsconfig.json`, only diff --git a/packages/tsconfig-reference/copy/en/options/mapRoot.md b/packages/tsconfig-reference/copy/en/options/mapRoot.md index 21c475f23c03..f795d6cbf058 100644 --- a/packages/tsconfig-reference/copy/en/options/mapRoot.md +++ b/packages/tsconfig-reference/copy/en/options/mapRoot.md @@ -1,5 +1,6 @@ --- display: "Map Root" +oneline: "Set an external root for sourcemaps" --- Specify the location where debugger should locate map files instead of generated locations. diff --git a/packages/tsconfig-reference/copy/en/options/maxNodeModuleJsDepth.md b/packages/tsconfig-reference/copy/en/options/maxNodeModuleJsDepth.md index 8f783b2d8803..78c8bb471fbf 100644 --- a/packages/tsconfig-reference/copy/en/options/maxNodeModuleJsDepth.md +++ b/packages/tsconfig-reference/copy/en/options/maxNodeModuleJsDepth.md @@ -1,5 +1,6 @@ --- display: "Max Node Module JS Depth" +oneline: "How deep should TypeScript run type checking in node_modules" --- The maximum dependency depth to search under `node_modules` and load JavaScript files. diff --git a/packages/tsconfig-reference/copy/en/options/module.md b/packages/tsconfig-reference/copy/en/options/module.md index 7a2583c79e86..aa8b01d5ba8d 100644 --- a/packages/tsconfig-reference/copy/en/options/module.md +++ b/packages/tsconfig-reference/copy/en/options/module.md @@ -1,5 +1,6 @@ --- display: "Module" +oneline: "Sets the expected module system for your runtime" --- Sets the module system for the program. See the Modules chapter of the handbook for more information. You very likely want `"CommonJS"`. diff --git a/packages/tsconfig-reference/copy/en/options/moduleResolution.md b/packages/tsconfig-reference/copy/en/options/moduleResolution.md index 8ce458b7b629..b060005b044c 100644 --- a/packages/tsconfig-reference/copy/en/options/moduleResolution.md +++ b/packages/tsconfig-reference/copy/en/options/moduleResolution.md @@ -1,5 +1,6 @@ --- display: "Module Resolution" +oneline: "Allow TypeScript 1.6 module resolution strategies" --- Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). You probably won't need to use this. diff --git a/packages/tsconfig-reference/copy/en/options/newLine.md b/packages/tsconfig-reference/copy/en/options/newLine.md index 6bd4154ecef5..9e14f185940d 100644 --- a/packages/tsconfig-reference/copy/en/options/newLine.md +++ b/packages/tsconfig-reference/copy/en/options/newLine.md @@ -1,5 +1,6 @@ --- display: "New Line" +oneline: "Set the newline character" --- Specify the end of line sequence to be used when emitting files: 'CRLF' (dos) or 'LF' (unix). diff --git a/packages/tsconfig-reference/copy/en/options/noEmit.md b/packages/tsconfig-reference/copy/en/options/noEmit.md index d1325457888d..5ee7a293f4e3 100644 --- a/packages/tsconfig-reference/copy/en/options/noEmit.md +++ b/packages/tsconfig-reference/copy/en/options/noEmit.md @@ -1,5 +1,6 @@ --- display: "No Emit" +oneline: "Do not emit files from a compilation" --- Do not emit compiler output files like JavaScript source code, source-maps or declarations. diff --git a/packages/tsconfig-reference/copy/en/options/noEmitHelpers.md b/packages/tsconfig-reference/copy/en/options/noEmitHelpers.md index 36ee494a4818..96653204794c 100644 --- a/packages/tsconfig-reference/copy/en/options/noEmitHelpers.md +++ b/packages/tsconfig-reference/copy/en/options/noEmitHelpers.md @@ -1,5 +1,6 @@ --- display: "No Emit Helpers" +oneline: "Assume helpers are available in the global runtime" --- Instead of importing helpers with [`importHelpers`](#importHelpers), you can provide implementations in the global scope for the helpers you use and completely turn off emitting of helper functions. diff --git a/packages/tsconfig-reference/copy/en/options/noEmitOnError.md b/packages/tsconfig-reference/copy/en/options/noEmitOnError.md index 6918f67ddc8e..fbdceef264a9 100644 --- a/packages/tsconfig-reference/copy/en/options/noEmitOnError.md +++ b/packages/tsconfig-reference/copy/en/options/noEmitOnError.md @@ -1,5 +1,6 @@ --- display: "No Emit On Error" +oneline: "Only emit files on a successful compile" --- Do not emit compiler output files like JavaScript source code, source-maps or declarations if any errors were reported. diff --git a/packages/tsconfig-reference/copy/en/options/noErrorTruncation.md b/packages/tsconfig-reference/copy/en/options/noErrorTruncation.md index 55dfcb52066b..04313c5721ac 100644 --- a/packages/tsconfig-reference/copy/en/options/noErrorTruncation.md +++ b/packages/tsconfig-reference/copy/en/options/noErrorTruncation.md @@ -1,12 +1,11 @@ --- display: "No Error Truncation" +oneline: "Do not truncate error messages" --- -TODO: Declare deprecated?! - Do not truncate error messages. -With `false` +With `false`, the default. ```ts twoslash // @errors: 2322 2454 diff --git a/packages/tsconfig-reference/copy/en/options/noFallthroughCasesInSwitch.md b/packages/tsconfig-reference/copy/en/options/noFallthroughCasesInSwitch.md index a8e02d59f950..514d5b529a86 100644 --- a/packages/tsconfig-reference/copy/en/options/noFallthroughCasesInSwitch.md +++ b/packages/tsconfig-reference/copy/en/options/noFallthroughCasesInSwitch.md @@ -1,8 +1,9 @@ --- display: "No Fallthrough Cases In Switch" +oneline: "Report errors for fallthrough cases in switch statements." --- -Report errors for fallthrough cases in switch statement. +Report errors for fallthrough cases in switch statements. Ensures that any non-empty case inside a switch statement includes either `break` or `return`. This means you won't accidentally ship a case fallthrough bug. diff --git a/packages/tsconfig-reference/copy/en/options/noImplicitAny.md b/packages/tsconfig-reference/copy/en/options/noImplicitAny.md index ecb244ba52e4..8d1438186220 100644 --- a/packages/tsconfig-reference/copy/en/options/noImplicitAny.md +++ b/packages/tsconfig-reference/copy/en/options/noImplicitAny.md @@ -1,5 +1,6 @@ --- display: "No Implicit Any" +oneline: "Avoid introducing anys inside your codebase when a type could be specified" --- In some cases where no type annotations are present, TypeScript will fall back to a type of `any` for a variable when it cannot infer the type. diff --git a/packages/tsconfig-reference/copy/en/options/noImplicitReturns.md b/packages/tsconfig-reference/copy/en/options/noImplicitReturns.md index cc989b318c83..2bb7c617b209 100644 --- a/packages/tsconfig-reference/copy/en/options/noImplicitReturns.md +++ b/packages/tsconfig-reference/copy/en/options/noImplicitReturns.md @@ -1,5 +1,6 @@ --- display: "No Implicit Returns" +oneline: "Ensure that all codepaths return in a function" --- When enabled, TypeScript will check all code paths in a function to ensure they return a value. diff --git a/packages/tsconfig-reference/copy/en/options/noImplicitThis.md b/packages/tsconfig-reference/copy/en/options/noImplicitThis.md index 5c2708c9ba75..c5fe18b7e841 100644 --- a/packages/tsconfig-reference/copy/en/options/noImplicitThis.md +++ b/packages/tsconfig-reference/copy/en/options/noImplicitThis.md @@ -1,5 +1,6 @@ --- display: "No Implicit This" +oneline: "Raise errors when 'this' would be any" --- Raise error on 'this' expressions with an implied 'any' type. diff --git a/packages/tsconfig-reference/copy/en/options/noImplicitUseStrict.md b/packages/tsconfig-reference/copy/en/options/noImplicitUseStrict.md index bb59e1a07bf7..93a3dcc96aa8 100644 --- a/packages/tsconfig-reference/copy/en/options/noImplicitUseStrict.md +++ b/packages/tsconfig-reference/copy/en/options/noImplicitUseStrict.md @@ -1,5 +1,6 @@ --- display: "No Implicit Use Strict" +oneline: "Disable 'use strict' in the JS emit" --- You shouldn't need this. By default, when emitting a module file to a non-ES6 target, TypeScript emits a `"use strict";` prologue at the top of the file. diff --git a/packages/tsconfig-reference/copy/en/options/noLib.md b/packages/tsconfig-reference/copy/en/options/noLib.md index 384e7ec95cbb..5512c0d22fc6 100644 --- a/packages/tsconfig-reference/copy/en/options/noLib.md +++ b/packages/tsconfig-reference/copy/en/options/noLib.md @@ -1,5 +1,6 @@ --- display: "No Lib" +oneline: "Ignore options from lib" --- Disables the automatic inclusion of any library files. diff --git a/packages/tsconfig-reference/copy/en/options/noResolve.md b/packages/tsconfig-reference/copy/en/options/noResolve.md index 07699c4e578f..8b5861b0c997 100644 --- a/packages/tsconfig-reference/copy/en/options/noResolve.md +++ b/packages/tsconfig-reference/copy/en/options/noResolve.md @@ -1,5 +1,6 @@ --- display: "No Resolve" +oneline: "Skip ahead-of-time checking for import and (x: T, y: U) => [T, U]; +type B = (x: S, y: S) => [S, S]; + +function f(a: A, b: B) { + b = a; // Ok + a = b; // Error +} +``` + +This flag can be used to remove that check. diff --git a/packages/tsconfig-reference/copy/en/options/noUnusedLocals.md b/packages/tsconfig-reference/copy/en/options/noUnusedLocals.md index 340d7c95281c..2edba3d2417e 100644 --- a/packages/tsconfig-reference/copy/en/options/noUnusedLocals.md +++ b/packages/tsconfig-reference/copy/en/options/noUnusedLocals.md @@ -1,5 +1,6 @@ --- display: "No Unused Locals" +oneline: "Error when a local variable isn't read" --- Report errors on unused local variables. diff --git a/packages/tsconfig-reference/copy/en/options/noUnusedParameters.md b/packages/tsconfig-reference/copy/en/options/noUnusedParameters.md index ff9017509d8c..0d0926b3060e 100644 --- a/packages/tsconfig-reference/copy/en/options/noUnusedParameters.md +++ b/packages/tsconfig-reference/copy/en/options/noUnusedParameters.md @@ -1,5 +1,6 @@ --- display: "No Unused Parameters" +oneline: "Error when a parameter isn't used" --- Report errors on unused parameters in functions. diff --git a/packages/tsconfig-reference/copy/en/options/out.md b/packages/tsconfig-reference/copy/en/options/out.md index 255e6d1f5eca..c7ea186437f8 100644 --- a/packages/tsconfig-reference/copy/en/options/out.md +++ b/packages/tsconfig-reference/copy/en/options/out.md @@ -1,5 +1,6 @@ --- display: "Out" +oneline: "Do not use this" --- Use [outFile](#outfile) instead. diff --git a/packages/tsconfig-reference/copy/en/options/outDir.md b/packages/tsconfig-reference/copy/en/options/outDir.md index e0a0dd19a3f5..4040807bba0c 100644 --- a/packages/tsconfig-reference/copy/en/options/outDir.md +++ b/packages/tsconfig-reference/copy/en/options/outDir.md @@ -1,5 +1,6 @@ --- display: "Out Dir" +oneline: "Set an output folder for all emitted files" --- If specified, `.js` (as well as `.d.ts`, `.js.map`, etc.) files will be emitted into this directory. diff --git a/packages/tsconfig-reference/copy/en/options/outFile.md b/packages/tsconfig-reference/copy/en/options/outFile.md index 7de6ec14e585..edab88d9e98a 100644 --- a/packages/tsconfig-reference/copy/en/options/outFile.md +++ b/packages/tsconfig-reference/copy/en/options/outFile.md @@ -1,5 +1,6 @@ --- display: "Out File" +oneline: "Output a single file of all JS files concatenated" --- If specified, all _global_ (non-module) files will be concatenated into the single output file specified. diff --git a/packages/tsconfig-reference/copy/en/options/paths.md b/packages/tsconfig-reference/copy/en/options/paths.md index 2179bafa1106..d2dc8309c6e3 100644 --- a/packages/tsconfig-reference/copy/en/options/paths.md +++ b/packages/tsconfig-reference/copy/en/options/paths.md @@ -1,5 +1,6 @@ --- display: "Paths" +oneline: "A set of locations to look for imports in" --- A series of entries which re-map imports to lookup locations relative to the `baseUrl`, there is a larger coverage of `paths` in [the handbook](/docs/handbook/module-resolution.html#path-mapping). diff --git a/packages/tsconfig-reference/copy/en/options/plugins.md b/packages/tsconfig-reference/copy/en/options/plugins.md index 10ff0b7c57a2..538cb9260aea 100644 --- a/packages/tsconfig-reference/copy/en/options/plugins.md +++ b/packages/tsconfig-reference/copy/en/options/plugins.md @@ -1,5 +1,6 @@ --- display: "Plugins" +oneline: "A list of language service plugins to include" --- List of language service plugins to run inside the editor. diff --git a/packages/tsconfig-reference/copy/en/options/preserveConstEnums.md b/packages/tsconfig-reference/copy/en/options/preserveConstEnums.md index 2e4461a23f3f..310ea47bdec3 100644 --- a/packages/tsconfig-reference/copy/en/options/preserveConstEnums.md +++ b/packages/tsconfig-reference/copy/en/options/preserveConstEnums.md @@ -1,5 +1,6 @@ --- display: "Preserve Const Enums" +oneline: "Do not erase `const enum` declarations in generated code" --- Do not erase `const enum` declarations in generated code. `const enum`s provide a way to reduce the overall memory footprint diff --git a/packages/tsconfig-reference/copy/en/options/preserveSymlinks.md b/packages/tsconfig-reference/copy/en/options/preserveSymlinks.md index f34ee0d61550..07d4b225c220 100644 --- a/packages/tsconfig-reference/copy/en/options/preserveSymlinks.md +++ b/packages/tsconfig-reference/copy/en/options/preserveSymlinks.md @@ -1,5 +1,6 @@ --- display: "Preserve Symlinks" +oneline: "Do not resolve symlink paths" --- This is to reflect the same flag in Node.js; which does not resolve the real path of symlinks. diff --git a/packages/tsconfig-reference/copy/en/options/preserveWatchOutput.md b/packages/tsconfig-reference/copy/en/options/preserveWatchOutput.md index 98310a423d7a..ae04d38dfe1e 100644 --- a/packages/tsconfig-reference/copy/en/options/preserveWatchOutput.md +++ b/packages/tsconfig-reference/copy/en/options/preserveWatchOutput.md @@ -1,5 +1,6 @@ --- display: "Preserve Watch Output" +oneline: "Do not wipe the console in watch mode" --- Whether to keep outdated console output in watch mode instead of clearing the screen every time a change happened. diff --git a/packages/tsconfig-reference/copy/en/options/pretty.md b/packages/tsconfig-reference/copy/en/options/pretty.md index 9c7bba95e04e..144b6225cd7b 100644 --- a/packages/tsconfig-reference/copy/en/options/pretty.md +++ b/packages/tsconfig-reference/copy/en/options/pretty.md @@ -1,5 +1,6 @@ --- display: "Pretty" +oneline: "Use color and formatting to make compiler errors easier to read" --- Stylize errors and messages using color and context, this is on by default — offers you a chance to have less terse, diff --git a/packages/tsconfig-reference/copy/en/options/reactNamespace.md b/packages/tsconfig-reference/copy/en/options/reactNamespace.md index acf83ffd406a..edc712b6bf03 100644 --- a/packages/tsconfig-reference/copy/en/options/reactNamespace.md +++ b/packages/tsconfig-reference/copy/en/options/reactNamespace.md @@ -1,5 +1,6 @@ --- display: "React Namespace" +oneline: "Specify the object which 'createElement' is called on in JSX" --- Use [`--jsxFactory`](#jsxFactory) instead. Specify the object invoked for `createElement` when targeting `react` for TSX files. diff --git a/packages/tsconfig-reference/copy/en/options/references.md b/packages/tsconfig-reference/copy/en/options/references.md index 0e8b93f2d467..14a07e7e312a 100644 --- a/packages/tsconfig-reference/copy/en/options/references.md +++ b/packages/tsconfig-reference/copy/en/options/references.md @@ -1,5 +1,6 @@ --- display: "References" +oneline: "Provide a structure for composite projects" --- Project references are a way to structure your TypeScript programs into smaller pieces. diff --git a/packages/tsconfig-reference/copy/en/options/removeComments.md b/packages/tsconfig-reference/copy/en/options/removeComments.md index bc8122d407c7..1b3b311cad8d 100644 --- a/packages/tsconfig-reference/copy/en/options/removeComments.md +++ b/packages/tsconfig-reference/copy/en/options/removeComments.md @@ -1,5 +1,6 @@ --- display: "Remove Comments" +oneline: "Remove comments in TypeScript from appearing in JavaScript" --- Strips all comments from TypeScript files when converting into JavaScript. Defaults to `true`. diff --git a/packages/tsconfig-reference/copy/en/options/resolveJsonModule.md b/packages/tsconfig-reference/copy/en/options/resolveJsonModule.md index 6c9a950c4ab5..e55dc588d29e 100644 --- a/packages/tsconfig-reference/copy/en/options/resolveJsonModule.md +++ b/packages/tsconfig-reference/copy/en/options/resolveJsonModule.md @@ -1,5 +1,6 @@ --- display: "Resolve JSON Module" +oneline: "Allow importing .json files" --- Allows importing modules with a '.json' extension, which is a common practice in node projects. This includes diff --git a/packages/tsconfig-reference/copy/en/options/rootDir.md b/packages/tsconfig-reference/copy/en/options/rootDir.md index f9100dec79cc..eac37de68b85 100644 --- a/packages/tsconfig-reference/copy/en/options/rootDir.md +++ b/packages/tsconfig-reference/copy/en/options/rootDir.md @@ -1,5 +1,6 @@ --- display: "Root Dir" +oneline: "Sets the root folder within your source files" --- **Default**: The longest common path of all non-declaration input files. If `composite` is set, the default is instead the directory containing the `tsconfig.json` file. diff --git a/packages/tsconfig-reference/copy/en/options/rootDirs.md b/packages/tsconfig-reference/copy/en/options/rootDirs.md index b45510b5df15..e299022009d5 100644 --- a/packages/tsconfig-reference/copy/en/options/rootDirs.md +++ b/packages/tsconfig-reference/copy/en/options/rootDirs.md @@ -1,5 +1,6 @@ --- display: "Root Dirs" +oneline: "Set multiple root directories" --- Using `rootDirs`, you can inform the compiler that there are many "virtual" directories acting as a single root. diff --git a/packages/tsconfig-reference/copy/en/options/skipDefaultLibCheck.md b/packages/tsconfig-reference/copy/en/options/skipDefaultLibCheck.md index b0b851d719a7..a5b90a7d2ce4 100644 --- a/packages/tsconfig-reference/copy/en/options/skipDefaultLibCheck.md +++ b/packages/tsconfig-reference/copy/en/options/skipDefaultLibCheck.md @@ -1,5 +1,6 @@ --- display: "Skip Default Lib Check" +oneline: "use SkipLibCheck instead" --- Use [`--skipLibCheck`](#skipLibCheck) instead. Skip type checking of default library declaration files. diff --git a/packages/tsconfig-reference/copy/en/options/skipLibCheck.md b/packages/tsconfig-reference/copy/en/options/skipLibCheck.md index 1ade48f42704..9163eed0a5e7 100644 --- a/packages/tsconfig-reference/copy/en/options/skipLibCheck.md +++ b/packages/tsconfig-reference/copy/en/options/skipLibCheck.md @@ -1,5 +1,6 @@ --- display: "Skip Lib Check" +oneline: "Skip type checking of declaration files" --- Skip type checking of declaration files. diff --git a/packages/tsconfig-reference/copy/en/options/sourceMap.md b/packages/tsconfig-reference/copy/en/options/sourceMap.md index 730623349325..4704d5f7ae2e 100644 --- a/packages/tsconfig-reference/copy/en/options/sourceMap.md +++ b/packages/tsconfig-reference/copy/en/options/sourceMap.md @@ -1,5 +1,6 @@ --- display: "Source Map" +oneline: "Creates source map files for emitted JavaScript files" --- Enables the generation of [sourcemap files](https://developer.mozilla.org/en-US/docs/Tools/Debugger/How_to/Use_a_source_map). diff --git a/packages/tsconfig-reference/copy/en/options/sourceRoot.md b/packages/tsconfig-reference/copy/en/options/sourceRoot.md index 4322d2f80d15..f875871a323d 100644 --- a/packages/tsconfig-reference/copy/en/options/sourceRoot.md +++ b/packages/tsconfig-reference/copy/en/options/sourceRoot.md @@ -1,5 +1,6 @@ --- display: "Source Root" +oneline: "Sets the root path for debuggers to find the reference source code" --- Specify the location where a debugger should locate TypeScript files instead of relative source locations. diff --git a/packages/tsconfig-reference/copy/en/options/strict.md b/packages/tsconfig-reference/copy/en/options/strict.md index 1bbcfb2240c0..8310f932d1f5 100644 --- a/packages/tsconfig-reference/copy/en/options/strict.md +++ b/packages/tsconfig-reference/copy/en/options/strict.md @@ -1,5 +1,6 @@ --- display: "Strict" +oneline: "Enable TypeScript's most in-depth type checking rules" --- The `strict` flag enables a wide range of type checking behavior that results in stronger guarantees of program correctness. diff --git a/packages/tsconfig-reference/copy/en/options/strictBindCallApply.md b/packages/tsconfig-reference/copy/en/options/strictBindCallApply.md index 0d8e642691fe..defe19bd709e 100644 --- a/packages/tsconfig-reference/copy/en/options/strictBindCallApply.md +++ b/packages/tsconfig-reference/copy/en/options/strictBindCallApply.md @@ -1,9 +1,8 @@ --- display: "Strict Bind Call Apply" +oneline: "Ensure that 'call', 'bind' and 'apply' have the rught arguments" --- -**Default**: `false`, unless `strict` is set. - When set, TypeScript will check that the built-in methods of functions `call`, `bind`, and `apply` are invoked with correct argument for the underlying function: ```ts diff --git a/packages/tsconfig-reference/copy/en/options/strictFunctionTypes.md b/packages/tsconfig-reference/copy/en/options/strictFunctionTypes.md index 7b73fd6d8eb9..78395b363962 100644 --- a/packages/tsconfig-reference/copy/en/options/strictFunctionTypes.md +++ b/packages/tsconfig-reference/copy/en/options/strictFunctionTypes.md @@ -1,5 +1,6 @@ --- display: "Strict Function Types" +oneline: "Ensure that function parameters are consistent" --- When enabled, this flag causes functions parameters to be checked more correctly. diff --git a/packages/tsconfig-reference/copy/en/options/strictNullChecks.md b/packages/tsconfig-reference/copy/en/options/strictNullChecks.md index 18d40b9f38e8..4ea9960d3296 100644 --- a/packages/tsconfig-reference/copy/en/options/strictNullChecks.md +++ b/packages/tsconfig-reference/copy/en/options/strictNullChecks.md @@ -1,5 +1,6 @@ --- display: "Strict Null Checks" +oneline: "Ensure that nullability is respected in the type checker" --- When `strictNullChecks` is `false`, `null` and `undefined` are effectively ignored by the language. diff --git a/packages/tsconfig-reference/copy/en/options/strictPropertyInitialization.md b/packages/tsconfig-reference/copy/en/options/strictPropertyInitialization.md index 93a8ee7e8883..bd68da0c31b4 100644 --- a/packages/tsconfig-reference/copy/en/options/strictPropertyInitialization.md +++ b/packages/tsconfig-reference/copy/en/options/strictPropertyInitialization.md @@ -1,5 +1,6 @@ --- display: "Strict Property Initialization" +oneline: "Ensure that all class properties match their types after the constructor has finished" --- When set to true, TypeScript will raise an error when a class property was declared but not set in the constructor. diff --git a/packages/tsconfig-reference/copy/en/options/stripInternal.md b/packages/tsconfig-reference/copy/en/options/stripInternal.md index ce92f02a2525..82570c2e66a0 100644 --- a/packages/tsconfig-reference/copy/en/options/stripInternal.md +++ b/packages/tsconfig-reference/copy/en/options/stripInternal.md @@ -1,5 +1,6 @@ --- display: "Strip Internal" +oneline: "Remove declarations which have '@internal' in their JSDoc comments" --- Do not emit declarations for code that has an `@internal` annotation in it's JSDoc comment. diff --git a/packages/tsconfig-reference/copy/en/options/suppressExcessPropertyErrors.md b/packages/tsconfig-reference/copy/en/options/suppressExcessPropertyErrors.md index 99b5f2b92061..10b3cf0c7ab9 100644 --- a/packages/tsconfig-reference/copy/en/options/suppressExcessPropertyErrors.md +++ b/packages/tsconfig-reference/copy/en/options/suppressExcessPropertyErrors.md @@ -1,5 +1,6 @@ --- display: "Suppress Excess Property Errors" +oneline: "Allow additional properties being set during creation of types" --- This disables reporting of excess property errors, such as the one shown in the following example: diff --git a/packages/tsconfig-reference/copy/en/options/suppressImplicitAnyIndexErrors.md b/packages/tsconfig-reference/copy/en/options/suppressImplicitAnyIndexErrors.md index c53245affdd4..4a0d5159336d 100644 --- a/packages/tsconfig-reference/copy/en/options/suppressImplicitAnyIndexErrors.md +++ b/packages/tsconfig-reference/copy/en/options/suppressImplicitAnyIndexErrors.md @@ -1,8 +1,9 @@ --- display: "Suppress Implicit Any Index Errors" +oneline: "Remove the warning when using string indexes to access unknown properties" --- -Turning `noImplicitAny` on suppresses implicit any warning reports when indexing into objects, as shown in the following example: +Turning `noImplicitAny` on suppresses reporting the error about implicit anys when indexing into objects, as shown in the following example: ```ts twoslash // @noImplicitAny: true diff --git a/packages/tsconfig-reference/copy/en/options/target.md b/packages/tsconfig-reference/copy/en/options/target.md index 1c59040a6eb5..2ff6286f72ac 100644 --- a/packages/tsconfig-reference/copy/en/options/target.md +++ b/packages/tsconfig-reference/copy/en/options/target.md @@ -1,5 +1,6 @@ --- display: "Target" +oneline: "Set the supported JavaScript language runtime to transpile to" --- Modern browsers support all ES6 features, so `ES6` is a good choice. diff --git a/packages/tsconfig-reference/copy/en/options/traceResolution.md b/packages/tsconfig-reference/copy/en/options/traceResolution.md index 02656a405e32..ee3760e0564e 100644 --- a/packages/tsconfig-reference/copy/en/options/traceResolution.md +++ b/packages/tsconfig-reference/copy/en/options/traceResolution.md @@ -1,5 +1,6 @@ --- display: "Trace Resolution" +oneline: "Log out paths when resolving all modules" --- When you are trying to debug why a module isn't being included. diff --git a/packages/tsconfig-reference/copy/en/options/tsBuildInfoFile.md b/packages/tsconfig-reference/copy/en/options/tsBuildInfoFile.md index 81a67aa901e1..8255cdec8b08 100644 --- a/packages/tsconfig-reference/copy/en/options/tsBuildInfoFile.md +++ b/packages/tsconfig-reference/copy/en/options/tsBuildInfoFile.md @@ -1,5 +1,6 @@ --- display: "TS Build Info File" +oneline: "Set the folder for .tsbuildinfo files" --- This setting lets you specify a file for storing incremental compilation information as a part of composite projects which enables faster diff --git a/packages/tsconfig-reference/copy/en/options/typeAcquisition.md b/packages/tsconfig-reference/copy/en/options/typeAcquisition.md index 16d50999a81c..13834e556aa1 100644 --- a/packages/tsconfig-reference/copy/en/options/typeAcquisition.md +++ b/packages/tsconfig-reference/copy/en/options/typeAcquisition.md @@ -1,5 +1,6 @@ --- display: "Type Acquisition" +oneline: "Sets of options for Automatic Type Acquisition in JavaScript" --- When you have a JavaScript project in your editor, TypeScript will provide types for your `node_modules` automatically using the DefinitelyTyped set of `@types` definitions. diff --git a/packages/tsconfig-reference/copy/en/options/typeRoots.md b/packages/tsconfig-reference/copy/en/options/typeRoots.md index bc88845c19f3..02dfc28d25de 100644 --- a/packages/tsconfig-reference/copy/en/options/typeRoots.md +++ b/packages/tsconfig-reference/copy/en/options/typeRoots.md @@ -1,5 +1,6 @@ --- display: "Type Roots" +oneline: "locations where TypeScript should look for type definitions" --- By default all _visible_ "`@types`" packages are included in your compilation. diff --git a/packages/tsconfig-reference/copy/en/options/types.md b/packages/tsconfig-reference/copy/en/options/types.md index 23c56fe28e8a..70c609fb5599 100644 --- a/packages/tsconfig-reference/copy/en/options/types.md +++ b/packages/tsconfig-reference/copy/en/options/types.md @@ -1,5 +1,6 @@ --- display: "Types" +oneline: "Used to create an allowlist of types to be included in the compile" --- By default all _visible_ "`@types`" packages are included in your compilation. diff --git a/packages/tsconfig-reference/copy/en/options/useDefineForClassFields.md b/packages/tsconfig-reference/copy/en/options/useDefineForClassFields.md index e48176c5ba55..886d2107bf18 100644 --- a/packages/tsconfig-reference/copy/en/options/useDefineForClassFields.md +++ b/packages/tsconfig-reference/copy/en/options/useDefineForClassFields.md @@ -1,5 +1,6 @@ --- display: "Use Define For Class Fields" +oneline: "Use define characteristics for handling class fields" --- This flag is used as part of migrating to the upcoming standard version of class fields. TypeScript introduced class fields many years before it was ratified in TC39. The latest version of the upcoming specification has a different runtime behavior to TypeScript's implementation but the same syntax. diff --git a/packages/tsconfig-reference/copy/leet/categories/Basic_Options_6172.md b/packages/tsconfig-reference/copy/leet/categories/Basic_Options_6172.md new file mode 100644 index 000000000000..834a54e4d75a --- /dev/null +++ b/packages/tsconfig-reference/copy/leet/categories/Basic_Options_6172.md @@ -0,0 +1,5 @@ +--- +display: "Pr0ject Opt1ons" +--- + +These settings ar3 used to define the runtime expectations of ur project, how and where u want the J\$ to be emittd and the lvl of integrat0n you want with existing JS code. diff --git a/packages/tsconfig-reference/copy/pirate/categories/Project_Files_0.md b/packages/tsconfig-reference/copy/leet/categories/Project_Files_0.md similarity index 100% rename from packages/tsconfig-reference/copy/pirate/categories/Project_Files_0.md rename to packages/tsconfig-reference/copy/leet/categories/Project_Files_0.md diff --git a/packages/tsconfig-reference/copy/leet/options/files.md b/packages/tsconfig-reference/copy/leet/options/files.md new file mode 100644 index 000000000000..0072ba816277 --- /dev/null +++ b/packages/tsconfig-reference/copy/leet/options/files.md @@ -0,0 +1,45 @@ +--- +display: "f1l3s" +oneline: "1nclude 4 set list of files, does not support gl*bs" +--- + +``` + + _.,----,._ + .:' `:. + .' `. +.' `. +: : +` .'`':'`'`/ ' + `. \ | / ,' + \ \ | / / + `\_..,,.._/' + {`'-,_`'-} + {`'-,_`'-} + {`'-,_`'-} + `YXXXXY' + ~^^~ + +``` + +Specifies an 4llowlist of f1l3s to include in the pr0gram. An error occurs if any 0f the files can't be found. + +```json +{ + "compilerOptions": {}, + "files": [ + "core.ts", + "sys.ts", + "types.ts", + "scanner.ts", + "parser.ts", + "utilities.ts", + "binder.ts", + "checker.ts", + "tsc.ts" + ] +} +``` + +This is useful when you only have a small number of files and don't need to use a glob to reference many files. +If you need that then use [`include`](#include). diff --git a/packages/tsconfig-reference/copy/leet/options/noImplicitAny.md b/packages/tsconfig-reference/copy/leet/options/noImplicitAny.md new file mode 100644 index 000000000000..a5db50bab33d --- /dev/null +++ b/packages/tsconfig-reference/copy/leet/options/noImplicitAny.md @@ -0,0 +1,26 @@ +--- +display: "No Implicit @ny" +oneline: "@void introducing nys inside ur codebase when a type could be specified" +--- + +!n s0me cases where n0 type annotations r present, Type\$cript will fall back to a type of `any` for a var when it can't infer the type. + +This c@n cause some errors to be missed, for example: + +```ts twoslash +// @noImplicitAny: false +function fn(s) { + // No error? + console.log(s.subtr(3)); +} +fn(42); +``` + +Turning on `noImplicitAny` however T\$ will issue errorz whenever it would have inferred `any`: + +```ts twoslash +// @errors: 7006 +function fn(s) { + console.log(s.subtr(3)); +} +``` diff --git a/packages/tsconfig-reference/copy/pirate/categories/Basic_Options_6172.md b/packages/tsconfig-reference/copy/pirate/categories/Basic_Options_6172.md deleted file mode 100644 index 8dbeb2315913..000000000000 --- a/packages/tsconfig-reference/copy/pirate/categories/Basic_Options_6172.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -display: "Options fer All" ---- - -It be the basic options. Aye diff --git a/packages/tsconfig-reference/copy/pirate/options/allowJs.md b/packages/tsconfig-reference/copy/pirate/options/allowJs.md deleted file mode 100644 index cce1e1bf2bb1..000000000000 --- a/packages/tsconfig-reference/copy/pirate/options/allowJs.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -display: "yar" ---- - -Allow javascript files to be compiled. diff --git a/packages/tsconfig-reference/package.json b/packages/tsconfig-reference/package.json index e72aa8e68693..da08233512d2 100644 --- a/packages/tsconfig-reference/package.json +++ b/packages/tsconfig-reference/package.json @@ -7,6 +7,7 @@ "generate-json": "yarn ts-node scripts/generateJSON.ts", "generate-markdown": "yarn ts-node --project tsconfig.json scripts/generateMarkdown.ts ", "build": "yarn generate-json; yarn generate-markdown", + "bootstrap": "yarn build", "test": "yarn build; yarn lint", "lint": "node scripts/lint.js" }, diff --git a/packages/tsconfig-reference/scripts/generateMarkdown.ts b/packages/tsconfig-reference/scripts/generateMarkdown.ts index c3df6fc6dcdc..1ba684471370 100644 --- a/packages/tsconfig-reference/scripts/generateMarkdown.ts +++ b/packages/tsconfig-reference/scripts/generateMarkdown.ts @@ -67,6 +67,14 @@ languages.forEach(lang => { options: { name: string; anchor: string }[]; }[]; + const optionsSummary = [] as { + display: string; + oneliner: string; + id: string; + categoryID: string; + categoryDisplay: string; + }[]; + orderedCategories.forEach(categoryID => { const category = Object.values(categories).find((c: any) => c.key === categoryID); assert.ok(category, "Could not find category for ID: " + categoryID); @@ -90,6 +98,7 @@ languages.forEach(lang => { const optionsForCategory = options.filter(o => o.categoryCode === category.code); const localisedOptions = [] as { name: string; anchor: string }[]; + optionsForCategory.forEach(option => { const mdPath = join("options", option.name + ".md"); const fullPath = join(__dirname, "..", "copy", lang, mdPath); @@ -101,10 +110,18 @@ languages.forEach(lang => { const optionFile = readMarkdownFile(optionPath); // Must have a display title in the front-matter - assert.ok( - optionFile.data.display, - "Could not get a display for option: " + option.name + " in " + lang - ); + // prettier-ignore + assert.ok(optionFile.data.display, "Could not find a 'display' for option: " + option.name + " in " + lang); + // prettier-ignore + assert.ok(optionFile.data.oneline, "Could not find a 'oneline' for option: " + option.name + " in " + lang); + + optionsSummary.push({ + id: option.name, + display: optionFile.data.display, + oneliner: optionFile.data.oneline, + categoryID: categoryID, + categoryDisplay: categoryFile.data.display + }); markdownChunks.push("
"); @@ -192,6 +209,11 @@ languages.forEach(lang => { JSON.stringify({ categories: allCategories }) ); + writeFileSync( + join(__dirname, "..", "output", lang + "-summary.json"), + JSON.stringify({ options: optionsSummary }) + ); + // Do a quick linter at the end // const unfound = options.filter(o => !markdown.includes(o.name)) // if (unfound.length) throw new Error(`Could not find these options in ${lang}: ${unfound.map(u => u.name).join(', ')}`) diff --git a/packages/tsconfig-reference/scripts/lint.js b/packages/tsconfig-reference/scripts/lint.js index ffe8627c8f0b..d984af5056e3 100644 --- a/packages/tsconfig-reference/scripts/lint.js +++ b/packages/tsconfig-reference/scripts/lint.js @@ -1,13 +1,16 @@ +// @ts-check // Loops through all the sample code and ensures that twoslash doesn't raise -const chalk = require("chalk"); +const chalk = require("chalk").default; const tick = chalk.bold.greenBright("✓"); +const cross = chalk.bold.redBright("⤫"); const { readdirSync, readFileSync } = require("fs"); const { join } = require("path"); const remark = require("remark"); const remarkTwoSlash = require("gatsby-remark-twoslasher-code-blocks"); +const { read } = require("gray-matter"); const languages = readdirSync(join(__dirname, "..", "copy")).filter(f => !f.startsWith(".")); @@ -16,18 +19,56 @@ console.log("Linting the sample code which uses twoslasher in ts-config"); // Pass in a 2nd arg to filter which markdown to run const filterString = process.argv[2] ? process.argv[2] : ""; +const errorReports = []; + languages.forEach(lang => { const locale = join(__dirname, "..", "copy", lang); const options = readdirSync(join(locale, "options")).filter(f => !f.startsWith(".")); - console.log("\n" + lang + ":"); + console.log("\n\nLanguage: " + chalk.bold(lang) + "\n"); options.forEach(option => { if (filterString.length && !option.includes(filterString)) return; const optionPath = join(locale, "options", option); + const markdown = readFileSync(optionPath, "utf8"); const markdownAST = remark().parse(markdown); - remarkTwoSlash({ markdownAST }); - process.stdout.write(option + " " + tick + ", "); + let hasError = false; + + try { + remarkTwoSlash({ markdownAST }); + } catch (error) { + hasError = true; + errorReports.push({ path: optionPath, error }); + } + + const optionFile = read(optionPath); + if (!optionFile.data.display) { + hasError = true; + // prettier-ignore + errorReports.push({ path: optionPath, error: new Error("Did not have a 'display' property in the YML header") }); + } + + if (!optionFile.data.oneline) { + hasError = true; + // prettier-ignore + + errorReports.push({ path: optionPath, error: new Error("Did not have a 'oneline' property in the YML header") }); + } + + const sigil = hasError ? cross : tick; + const name = hasError ? chalk.red(option) : option; + process.stdout.write(name + " " + sigil + ", "); }); }); + +if (errorReports.length) { + process.exitCode = 1; + + errorReports.forEach(err => { + console.log(`\n> ${chalk.bold.red(err.path)}\n`); + err.error.stack = undefined; + console.log(err.error); + }); + console.log("\n\n"); +} diff --git a/packages/tsconfig-reference/scripts/tsconfigRules.ts b/packages/tsconfig-reference/scripts/tsconfigRules.ts index 330a29fca49c..7d88e3e20f16 100644 --- a/packages/tsconfig-reference/scripts/tsconfigRules.ts +++ b/packages/tsconfig-reference/scripts/tsconfigRules.ts @@ -68,7 +68,13 @@ export const relatedTo: [AnOption, AnOption[]][] = [ ["types", ["typeRoots"]], ["typeRoots", ["types"]], - ["declaration", ["emitDeclarationOnly"]] + ["declaration", ["emitDeclarationOnly"]], + + ["noLib", ["lib"]], + + ["allowJs", ["checkJs", "emitDeclarationOnly"]], + ["checkJs", ["allowJs", "emitDeclarationOnly"]], + ["declaration", ["declarationDir", "emitDeclarationOnly"]] ]; /** @@ -195,6 +201,7 @@ export const releaseToConfigsMap: { [key: string]: AnOption[] } = { "2.8": ["emitDeclarationOnly"], "2.7": ["strictPropertyInitialization", "esModuleInterop"], "2.6": ["strictFunctionTypes"], + "2.4": ["noStrictGenericChecks"], "2.3": ["strict", "downlevelIteration", "init"], "2.2": ["jsx"], "2.1": ["extends", "alwaysStrict"], diff --git a/packages/typescriptlang-org/.gitignore b/packages/typescriptlang-org/.gitignore index 8f66170e0dc0..1f1107a4f452 100644 --- a/packages/typescriptlang-org/.gitignore +++ b/packages/typescriptlang-org/.gitignore @@ -78,6 +78,9 @@ _tests/backstop_data/engine_scripts _tests/backstop_data/html_report # Build artifacts -static/js/sandbox/*.js -static/js/sandbox/*.map -static/js/sandbox/*.d.ts +static/js/sandbox/ +!static/js/sandbox/.gitkeep +static/js/playground/ +!static/js/playground/.gitkeep +static/js/examples/ +!static/js/examples/.gitkeep diff --git a/packages/typescriptlang-org/_tests/backstop_data/bitmaps_reference/backstop_default_Empty_Page_0_document_1_tablet.png b/packages/typescriptlang-org/_tests/backstop_data/bitmaps_reference/backstop_default_Empty_Page_0_document_1_tablet.png index 4e4d59fdd9a3..452a982731d7 100644 Binary files a/packages/typescriptlang-org/_tests/backstop_data/bitmaps_reference/backstop_default_Empty_Page_0_document_1_tablet.png and b/packages/typescriptlang-org/_tests/backstop_data/bitmaps_reference/backstop_default_Empty_Page_0_document_1_tablet.png differ diff --git a/packages/typescriptlang-org/_tests/backstop_data/bitmaps_reference/backstop_default_Empty_Page_0_document_2_computer.png b/packages/typescriptlang-org/_tests/backstop_data/bitmaps_reference/backstop_default_Empty_Page_0_document_2_computer.png index 0dc5465fde8e..fb8149aac085 100644 Binary files a/packages/typescriptlang-org/_tests/backstop_data/bitmaps_reference/backstop_default_Empty_Page_0_document_2_computer.png and b/packages/typescriptlang-org/_tests/backstop_data/bitmaps_reference/backstop_default_Empty_Page_0_document_2_computer.png differ diff --git a/packages/typescriptlang-org/_tests/backstop_data/bitmaps_reference/backstop_default_Index_0_document_0_phone.png b/packages/typescriptlang-org/_tests/backstop_data/bitmaps_reference/backstop_default_Index_0_document_0_phone.png index 0fd837754f30..c08f4608aa47 100644 Binary files a/packages/typescriptlang-org/_tests/backstop_data/bitmaps_reference/backstop_default_Index_0_document_0_phone.png and b/packages/typescriptlang-org/_tests/backstop_data/bitmaps_reference/backstop_default_Index_0_document_0_phone.png differ diff --git a/packages/typescriptlang-org/_tests/backstop_data/bitmaps_reference/backstop_default_Index_0_document_1_tablet.png b/packages/typescriptlang-org/_tests/backstop_data/bitmaps_reference/backstop_default_Index_0_document_1_tablet.png index 1fa8180bf7f8..e5a2c5430b27 100644 Binary files a/packages/typescriptlang-org/_tests/backstop_data/bitmaps_reference/backstop_default_Index_0_document_1_tablet.png and b/packages/typescriptlang-org/_tests/backstop_data/bitmaps_reference/backstop_default_Index_0_document_1_tablet.png differ diff --git a/packages/typescriptlang-org/_tests/backstop_data/bitmaps_reference/backstop_default_Index_0_document_2_computer.png b/packages/typescriptlang-org/_tests/backstop_data/bitmaps_reference/backstop_default_Index_0_document_2_computer.png index 1bfddb6d0781..3d2075127f45 100644 Binary files a/packages/typescriptlang-org/_tests/backstop_data/bitmaps_reference/backstop_default_Index_0_document_2_computer.png and b/packages/typescriptlang-org/_tests/backstop_data/bitmaps_reference/backstop_default_Index_0_document_2_computer.png differ diff --git a/packages/typescriptlang-org/_tests/backstop_data/bitmaps_reference/backstop_default_Old_Handbook_Example_0_viewport_0_phone.png b/packages/typescriptlang-org/_tests/backstop_data/bitmaps_reference/backstop_default_Old_Handbook_Example_0_viewport_0_phone.png index c06197797bfc..404a655930aa 100644 Binary files a/packages/typescriptlang-org/_tests/backstop_data/bitmaps_reference/backstop_default_Old_Handbook_Example_0_viewport_0_phone.png and b/packages/typescriptlang-org/_tests/backstop_data/bitmaps_reference/backstop_default_Old_Handbook_Example_0_viewport_0_phone.png differ diff --git a/packages/typescriptlang-org/_tests/backstop_data/bitmaps_reference/backstop_default_Old_Handbook_Example_0_viewport_1_tablet.png b/packages/typescriptlang-org/_tests/backstop_data/bitmaps_reference/backstop_default_Old_Handbook_Example_0_viewport_1_tablet.png index 5697e9a64177..5fc208d03515 100644 Binary files a/packages/typescriptlang-org/_tests/backstop_data/bitmaps_reference/backstop_default_Old_Handbook_Example_0_viewport_1_tablet.png and b/packages/typescriptlang-org/_tests/backstop_data/bitmaps_reference/backstop_default_Old_Handbook_Example_0_viewport_1_tablet.png differ diff --git a/packages/typescriptlang-org/_tests/backstop_data/bitmaps_reference/backstop_default_Old_Handbook_Example_0_viewport_2_computer.png b/packages/typescriptlang-org/_tests/backstop_data/bitmaps_reference/backstop_default_Old_Handbook_Example_0_viewport_2_computer.png index 3e94830fba61..887a3b6b5d12 100644 Binary files a/packages/typescriptlang-org/_tests/backstop_data/bitmaps_reference/backstop_default_Old_Handbook_Example_0_viewport_2_computer.png and b/packages/typescriptlang-org/_tests/backstop_data/bitmaps_reference/backstop_default_Old_Handbook_Example_0_viewport_2_computer.png differ diff --git a/packages/typescriptlang-org/_tests/backstop_data/bitmaps_reference/backstop_default_TSConfig_Example_0_viewport_0_phone.png b/packages/typescriptlang-org/_tests/backstop_data/bitmaps_reference/backstop_default_TSConfig_Example_0_viewport_0_phone.png index 5ed2f11c9e5f..c26401b89f88 100644 Binary files a/packages/typescriptlang-org/_tests/backstop_data/bitmaps_reference/backstop_default_TSConfig_Example_0_viewport_0_phone.png and b/packages/typescriptlang-org/_tests/backstop_data/bitmaps_reference/backstop_default_TSConfig_Example_0_viewport_0_phone.png differ diff --git a/packages/typescriptlang-org/_tests/backstop_data/bitmaps_reference/backstop_default_TSConfig_Example_0_viewport_1_tablet.png b/packages/typescriptlang-org/_tests/backstop_data/bitmaps_reference/backstop_default_TSConfig_Example_0_viewport_1_tablet.png index 38e947606a65..62cb4fc5aa86 100644 Binary files a/packages/typescriptlang-org/_tests/backstop_data/bitmaps_reference/backstop_default_TSConfig_Example_0_viewport_1_tablet.png and b/packages/typescriptlang-org/_tests/backstop_data/bitmaps_reference/backstop_default_TSConfig_Example_0_viewport_1_tablet.png differ diff --git a/packages/typescriptlang-org/_tests/backstop_data/bitmaps_reference/backstop_default_TSConfig_Example_0_viewport_2_computer.png b/packages/typescriptlang-org/_tests/backstop_data/bitmaps_reference/backstop_default_TSConfig_Example_0_viewport_2_computer.png index c7b9ab580d80..01d87bdce2a8 100644 Binary files a/packages/typescriptlang-org/_tests/backstop_data/bitmaps_reference/backstop_default_TSConfig_Example_0_viewport_2_computer.png and b/packages/typescriptlang-org/_tests/backstop_data/bitmaps_reference/backstop_default_TSConfig_Example_0_viewport_2_computer.png differ diff --git a/packages/typescriptlang-org/gatsby-config.js b/packages/typescriptlang-org/gatsby-config.js index b154fde6b135..8636c08f6618 100644 --- a/packages/typescriptlang-org/gatsby-config.js +++ b/packages/typescriptlang-org/gatsby-config.js @@ -62,6 +62,13 @@ module.exports = { name: `tsconfig-reference`, }, }, + { + resolve: `gatsby-source-filesystem`, + options: { + path: `${__dirname}/../playground-examples/generated`, + name: `playground-examples`, + }, + }, // Markdown support { resolve: `gatsby-transformer-remark`, diff --git a/packages/typescriptlang-org/lib/bootup/createPages.ts b/packages/typescriptlang-org/lib/bootup/createPages.ts index b4f89f82d025..bba13f96cc47 100644 --- a/packages/typescriptlang-org/lib/bootup/createPages.ts +++ b/packages/typescriptlang-org/lib/bootup/createPages.ts @@ -3,6 +3,7 @@ import { createOldHandbookPages } from "./ingestion/createPagesForOldHandbook" import { createTSConfigReference } from "./ingestion/createTSConfigReference" import { GatsbyNode } from "gatsby" +import { createPlaygrounds } from "./ingestion/createPlaygrounds" export const createPages: GatsbyNode["createPages"] = async args => { // Basically this function should be passing the right @@ -12,6 +13,7 @@ export const createPages: GatsbyNode["createPages"] = async args => { setupRedirects(args.actions.createRedirect) await createOldHandbookPages(args.graphql, args.actions.createPage) await createTSConfigReference(args.graphql, args.actions.createPage) + await createPlaygrounds(args.graphql, args.actions.createPage) return null } diff --git a/packages/typescriptlang-org/lib/bootup/ingestion/createPlaygrounds.ts b/packages/typescriptlang-org/lib/bootup/ingestion/createPlaygrounds.ts new file mode 100644 index 000000000000..3cfce16d4883 --- /dev/null +++ b/packages/typescriptlang-org/lib/bootup/ingestion/createPlaygrounds.ts @@ -0,0 +1,63 @@ +const path = require(`path`) +const fs = require(`fs`) + +import { NodePluginArgs, CreatePagesArgs } from "gatsby" + +export const createPlaygrounds = async ( + graphql: CreatePagesArgs["graphql"], + createPage: NodePluginArgs["actions"]["createPage"] +) => { + const playPage = path.resolve(`./src/templates/play.tsx`) + const result = await graphql(` + query GetAllPlaygroundLocalizations { + allFile( + filter: { + sourceInstanceName: { eq: "playground-examples" } + ext: { eq: ".json" } + } + ) { + nodes { + name + } + } + } + `) + + if (result.errors) { + throw result.errors + } + + const anyData = result.data as any + const docs = anyData.allFile.nodes + + docs.forEach(lang => { + const appRoot = path.join(__dirname, "..", "..", "..", "..") + // prettier-ignore + const examplesForLang = path.join(appRoot, "playground-examples", "generated", lang.name + ".json") + const examplesTOC = JSON.parse(fs.readFileSync(examplesForLang, "utf8")) + // console.log(lang, categoriesForLang) + + // prettier-ignore + const compilerOptsForLang = path.join(appRoot,"tsconfig-reference","output",lang.name + "-summary.json") + // prettier-ignore + const compilerOptsForLangFallback = path.join( appRoot, "playground-examples", "generated", "en-summary.json") + + const hasOptsForLang = fs.existsSync(compilerOptsForLang) + const optionsPath = hasOptsForLang + ? compilerOptsForLang + : compilerOptsForLangFallback + + const optionsSummary = JSON.parse(fs.readFileSync(optionsPath, "utf8")) + .options + + createPage({ + path: lang.name + "/play", + component: playPage, + context: { + lang: lang.name, + examplesTOC, + optionsSummary, + }, + }) + }) +} diff --git a/packages/typescriptlang-org/lib/bootup/onCreateNode.ts b/packages/typescriptlang-org/lib/bootup/onCreateNode.ts index 8d73e2ad2db4..849a7efa93c7 100644 --- a/packages/typescriptlang-org/lib/bootup/onCreateNode.ts +++ b/packages/typescriptlang-org/lib/bootup/onCreateNode.ts @@ -15,6 +15,7 @@ export const onCreateNode: GatsbyNode["onCreateNode"] = ({ }) => { const { createNodeField } = actions + // This could move to pageContext TBH in the createNode if (isTSConfigNode(node)) { const categoryPath = node.context.categoriesPath const categoriesJSON = JSON.parse(readFileSync(categoryPath, "utf8")) diff --git a/packages/typescriptlang-org/package.json b/packages/typescriptlang-org/package.json index 7cce69a92e91..c246713bc0f0 100644 --- a/packages/typescriptlang-org/package.json +++ b/packages/typescriptlang-org/package.json @@ -8,6 +8,7 @@ "build": "gatsby build", "develop": "gatsby develop", "clean": "gatsby clean", + "bootstrap": "echo 'NOOP'", "update-snapshots": "backstop test; backstop approve", "update-static-assets": "node scripts/downloadPlaygroundAssets.js && node scripts/downloadSearchAssets.js", "start": "npm run develop", @@ -47,7 +48,9 @@ "rehype-shiki": "^0.0.5", "ts-node": "^8.5.4", "ts-twoslasher": "0.1.0", - "typescript": "^3.7.4" + "typescript": "^3.7.4", + "typescript-playground": "*", + "typescript-sandbox": "*" }, "devDependencies": { "@babel/plugin-syntax-optional-chaining": "^7.7.4", diff --git a/packages/typescriptlang-org/src/components/ShowExamples.scss b/packages/typescriptlang-org/src/components/ShowExamples.scss new file mode 100644 index 000000000000..c10bdb78e10a --- /dev/null +++ b/packages/typescriptlang-org/src/components/ShowExamples.scss @@ -0,0 +1,117 @@ +// This is the visual aspect of the popover ( the white bit ) +// the non-visible layout is above in #playground-samples-popover +.examples { + background-color: white; + padding: 20px; + box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175); + height: 450px; + + a { + color: #187abf; + } + + // The titles of the sections + > ol { + padding-left: 0; + + li { + display: inline-block; + padding: 0; + margin-bottom: 20px; + width: auto; + + &:hover { + background-color: transparent; + } + } + + button.section-name { + font-size: 2em; + font-weight: 600; + + height: 2em; + text-decoration: none; + border-bottom: none; + padding-bottom: 0.05em; + padding-left: 0; + margin-top: 0; + padding-top: 0; + margin-right: 1em; + border: none; + background-color: transparent; + } + + button.section-name.selected { + border-bottom: #187abf 2px solid; + } + } + + ol { + list-style: none; + padding-right: 20px; + padding-left: 0; + } + + > div.section-content { + flex-wrap: wrap; + display: flex; + + // Each smaller section inside the largest subsection + .section-list { + display: flex; + flex-direction: column; + width: 240px; + margin-top: 1.5em; + + h4 { + font-size: 16px; + font-weight: 600; + margin: 0; + margin-bottom: 8px; + } + + ol { + li { + padding: 0; + margin: 0; + position: relative; + + a { + display: block; + padding: 4px; + padding-left: 10px; + margin-left: -8px; + text-decoration: none; + border-bottom: none; + font-size: 14px; + + &:hover { + color: black; + } + + &.highlight { + border-left: #187abf 2px solid; + padding-left: 8px; + } + } + + .example-indicator { + position: absolute; + right: 0.8em; + top: 0.75em; + width: 0.5em; + height: 0.5em; + border-radius: 0.5em; + + &.done { + background-color: #187abf; + } + &.changed { + border: #187abf 1px solid; + } + } + } + } + } + } +} diff --git a/packages/typescriptlang-org/src/components/ShowExamples.tsx b/packages/typescriptlang-org/src/components/ShowExamples.tsx new file mode 100644 index 000000000000..ea380688efd6 --- /dev/null +++ b/packages/typescriptlang-org/src/components/ShowExamples.tsx @@ -0,0 +1,143 @@ +import React, { useEffect } from "react" +import { withPrefix } from "gatsby" +import "./ShowExamples.scss" + +// @ts-ignore - this is a fallback to english +import english from "../../static/js/examples/en" + +interface SamplesJSON { + sections: { name: string, subtitle: string, id?: string }[] + sortedSubSections: string[] + examples: { path: string, title: string, name: string, id: string, sortIndex: number, hash: string, compilerSettings: any }[] +} +type Example = SamplesJSON["examples"][0] + +const sortedSectionsDictionary = (locale: SamplesJSON, section: SamplesJSON["sections"][0]) => { + const sectionDict = {} + locale.examples.forEach(e => { + // Allow switching a "-" to "." so that titles can have + // a dot for version numbers, this own works once. + if (e.path[0] === section.name.replace(".", "-") || e.path[0] === section.id) { + if (sectionDict[e.path[1]]) { + sectionDict[e.path[1]].push(e) + } else { + sectionDict[e.path[1]] = [e] + } + } + + }) + return sectionDict +} + +const hrefForExample = (example: Example, lang: string) => { + const isJS = example.name.indexOf(".js") !== -1 + const prefix = isJS ? "useJavaScript=true" : "" + const hash = "example/" + example.id + const params = example.compilerSettings || {} + const queryParams = Object.keys(params).map(key => key + '=' + params[key]).join('&'); + return withPrefix(`${lang}/play/?${prefix + queryParams}#${hash}`) +} + + +const buttonOnClick = (id: string) => (e) => { + const tappedButton = e.target + const contentID = id + const examplesParent = tappedButton.closest(".examples") + + const allSectionTitles = examplesParent.querySelectorAll(".section-name") + const allSections = examplesParent.querySelectorAll(".section-content") + + // @ts-ignore + for (const title of allSectionTitles) { title.classList.remove("selected") } + tappedButton.classList.add("selected") + + // @ts-ignore + for (const section of allSections) { + section.style.display = "none" + section.classList.remove("selected") + } + + const sectionForButton = examplesParent.querySelectorAll(".button-" + contentID)[0] // document.getElementById(contentID) + if (sectionForButton) { + sectionForButton.style.display = "flex" + sectionForButton.classList.add("selected") + } + + if (e && e.stopPropagation) { + e.stopPropagation() + } +} + +export type Props = { + defaultSection: string + sections: string[] + + locale?: string + /** DI'd copy of the examples, or fallback to eng */ + examples?: typeof import("../../static/js/examples/en.json") +} + +export const RenderExamples = (props: Props) => { + + useEffect(() => { + // Update the dots after it's loaded and running in the client instead + let seenExamples = {} + if (localStorage) { + const examplesFromLS = localStorage.getItem("examples-seen") || "{}" + seenExamples = JSON.parse(examplesFromLS) + } + + document.querySelectorAll(".example-indicator").forEach(e => { + const id = e.getAttribute("data-id") + if (id) { + const seen = seenExamples[id] + if (seen) { + const hash = e.getAttribute("data-hash") + e.classList.add(hash === seen ? "done" : "changed") + } + } + }) + }) + + const lang = props.locale || "en" + const locale = props.examples || english + const sections = locale.sections.filter(s => props.sections.includes(s.id)) + return ( +
+
    + {sections.map(section => { + const startOpen = section.id === props.defaultSection + const selectedClass = startOpen ? " selected" : "" + return
  1. + } + )} +
+ + {sections.map(section => { + const sectionDict = sortedSectionsDictionary(locale, section) + const subsectionNames = Object.keys(sectionDict).sort((lhs, rhs) => locale.sortedSubSections.indexOf(lhs) - locale.sortedSubSections.indexOf(rhs)) + const startOpen = section.id === props.defaultSection + const style = startOpen ? {} : { display: "none" } + + return
+

+ + {subsectionNames.map(sectionName => { + const sectionExamples = sectionDict[sectionName].sort((lhs, rhs) => lhs.sortIndex - rhs.sortIndex) as Example[] + + return

+

{sectionName}

+
    + {sectionExamples.map(example => +
  1. + {example.title} +
    +
  2. ) + } +
+
+ })} +
+ })} +
) +} diff --git a/packages/typescriptlang-org/src/components/layout.tsx b/packages/typescriptlang-org/src/components/layout.tsx index d52bad7fa6b6..4080fe265f67 100644 --- a/packages/typescriptlang-org/src/components/layout.tsx +++ b/packages/typescriptlang-org/src/components/layout.tsx @@ -1,12 +1,12 @@ import React from "react" -import { SiteNav } from "./layout/TopNav" +import { SiteNav, Props } from "./layout/TopNav" import { SiteFooter } from "./layout/SiteFooter" -export const Layout = (props: any) => { +export const Layout = (props: Props & { children: any }) => { const { children } = props return (
- +
{children}
diff --git a/packages/typescriptlang-org/src/components/layout/SiteFooter-PlaygroundSamples.tsx b/packages/typescriptlang-org/src/components/layout/SiteFooter-PlaygroundSamples.tsx index 84cf1f8f0206..5885ab23779e 100644 --- a/packages/typescriptlang-org/src/components/layout/SiteFooter-PlaygroundSamples.tsx +++ b/packages/typescriptlang-org/src/components/layout/SiteFooter-PlaygroundSamples.tsx @@ -1,83 +1,17 @@ import React, { useEffect } from "react" -// @ts-ignore - TODO: This could prove troublesome in the future - perhaps it could be grabbed in the GraphQL? -import english from "../../../../playground-examples/generated/en" +import { RenderExamples } from "../ShowExamples" interface Props { lang: string } -interface SamplesJSON { - sections: { name: string, subtitle: string }[] - sortedSubSections: string[] - examples: { path: string, title: string, name: string, id: string, sortIndex: number, hash: string, compilerSettings: any }[] -} - -type Example = SamplesJSON["examples"][0] - -const sortedSectionsDictionary = (locale: SamplesJSON, sectionName: string) => { - const sectionDict = {} - locale.examples.forEach(e => { - // Allow switching a "-" to "." so that titles can have - // a dot for version numbers, this own works once. - if (e.path[0] !== sectionName.replace(".", "-")) return; - - if (sectionDict[e.path[1]]) { - sectionDict[e.path[1]].push(e) - } else { - sectionDict[e.path[1]] = [e] - } - }) - return sectionDict -} - -const hrefForExample = (example: Example) => { - const isJS = example.name.indexOf(".js") !== -1 - const prefix = isJS ? "useJavaScript=true" : "" - const hash = "example/" + example.id - const params = example.compilerSettings || {} - const queryParams = Object.keys(params).map(key => key + '=' + params[key]).join('&'); - return `/play/?${prefix + queryParams}#${hash}` -} - -const buttonOnClick = (e) => { - const tappedButton = e.target - const contentID = tappedButton.textContent.toLowerCase() - - const allSectionTitles = document.querySelectorAll(".section-name") - // @ts-ignore - for (const title of allSectionTitles) { title.classList.remove("selected") } - tappedButton.classList.add("selected") - - const allSections = document.querySelectorAll(".section-content") - // @ts-ignore - for (const section of allSections) { - section.style.display = "none" - section.classList.remove("selected") - } - - const sectionForButton = document.getElementById(contentID) - if (sectionForButton) { - sectionForButton.style.display = "flex" - sectionForButton.classList.add("selected") - } - - if (e && e.stopPropagation) { - e.stopPropagation() - } -} - - - - export const PlaygroundSamples = (props: Props) => { - const locale = english as SamplesJSON - const defaultSection = "TypeScript" // This ensures that the popover only becomes available when JS is enabled useEffect(() => { - // Only allow hoving on wider windows + // Only allow hovering on wider windows const allowHover = window.innerWidth > 900 if (!allowHover) return @@ -114,45 +48,7 @@ export const PlaygroundSamples = (props: Props) => { return (
-
Note: this page is a beta page, don't rely on the URL and file issues on microsoft/TypeScript-Website.
+ {props.disableBetaNotification ? null : +
Note: this page is a beta page, don't rely on the URL and file issues on microsoft/TypeScript-Website.
+ } + { /** Used for skip past navigation button */}
diff --git a/packages/typescriptlang-org/src/components/layout/Untitled-2 b/packages/typescriptlang-org/src/components/layout/Untitled-2 deleted file mode 100644 index bf73b139bc68..000000000000 --- a/packages/typescriptlang-org/src/components/layout/Untitled-2 +++ /dev/null @@ -1,108 +0,0 @@ -## Navigation - -I've focused my work with three main goals since the last update: - -- Documentation for the TSConfig -- Tooling for how we want to show code blocks -- Creating the navigation shell of the website so that we can preview pages - -I'm going to focus this update on the navigation aspects of the site. Let's look at a few different places we have -navigation and talk about why it was designed this way - -### Site Navigation - -I tried to take a mix of modern native mobile app design, and website best practices to focus on three main audiences: - -- Desktop with mouse -- Desktop via keyboard -- Mobile Phones - -The first two tend to have similar needs in terms of what they want for site navigation, but mobile devices themselves -should be treated quite differently both from a user's intent and how we should present navigation. - -How does it look on desktop? - -[desktop empty] - -How does it look on mobile? - -[mobile empty] - -### Why the drastic change? - -Navigation on desktop tends to take a lot of design cues from headers in print design which aims to follow the path of reading: - -1. Have a strong visual distinction for a section of links -1. Always have a brand mark link in the top corner where folks read from which takes you to the index -1. Include a few links to the important places next to that -1. Include a flexible whitespace gap then have secondary tiered navigation links at the end of your eye flow - -But mobile really disrupted that from two factors: - -1. The horizontal space is so constrained, good luck getting more than 4-5 words in there -1. There are 'blessed' areas of the screen where it is physically easier to touch once mobile devices got big - -This moved the design frames of references from one loosely based on [fitt's law](https://blog.codinghorror.com/fitts-law-and-infinite-width/) to -one based on thumb touch maps. - - - -[From scotthurff.com](https://www.scotthurff.com/posts/how-to-design-for-thumbs-in-the-era-of-huge-screens/) - -In designing for mobile, I had to really narrow the priorities for people to two things. I wanted people to easily get to search, and to go to the root of the documentation. In my experience building websites for developer documentation, most people are using mobile as a reference or to quickly show something to someone. - -You can see here that we barely get into the natural section in search - -[ios1] - -[ios2] - -With the navigation designed, I applied the same forms of navigation hiding which mobile browsers do. The navigation will only show when you start to move up. It also keeps track of display curves for phones like the iPhone X and my Samsung Note, you can see in this GIF when the browser includes a flat edge at the bottom then the navigation buttons lay flat. - -[gif of nav] - -I still have a few questions to answer though: - -- What will the language selector look like? -- What will the theme selector look like? - -I expect to take some cues from [docs.microsoft.com](https://docs.microsoft.com/en-us/aspnet/core/getting-started/?view=aspnetcore-3.0&tabs=macos). - -#### Footers - -I believe in mega-footers. A footer is the end of the information you were actually looking for and generally represents three things: - -- A jump-off section for people to get to related links -- Calls to action for something you want your users to do -- A place for tertiary links (1st: site nav, 2nd: related internal links, 3rd links you need to have somewhere but don't need design weight) - -[footer img] - -With the TypeScript footer, to handle the I took a look at the most popular documentation pages on the website and included them to make sure -that people would have easy access to them. Their order is in popularity. - -To hit the second, I wanted to raise the visibility of the playground code samples. These provide a very focused explanation of many parts of -how TypeScript works and aren't easy to find as a sub-navigation element inside the playground. When viewport to the site is large enough, -and JavaScript is enabled then the Code Samples link switches to a popover which echos the design in the playground. - -[gif of footer] - -For mobile, I didn't need to do anything content-wise. I don't think there's any value in making it more focused. Design wise I only made a few tweaks but it felt fine in both. - -#### Internal Navigation - -The other place where we have navigation is between pages. When you link into a handbook, we want to show a navigation between related documents. - -[gif of sidenav] - -This is a pretty solid design pattern to work within, and one that's common on documentation sites. I took care to make it obvious which navigation section you're in -and Gatsby made it so fast that it doesn't feel like you're navigating between web pages. - - -#### No dependencies - -A lot of what you're seeing in these screenshots looks like a lot of complicated JavaScript code, but it's not. Apart from search which comes from Algolia, the v2 site has zero JavaScript dependencies at runtime, and is a static HTML site to an azure blob storage account. - -The interaction patterns are built from first building the site with JavaScript disabled, and then JavaScript to handle some of the extra is added in at runtime. I use the `useEffect` pattern inside a React component to add client-side JS enhancements. For example [here is all the runtime JavaScript code](https://github.com/microsoft/TypeScript-Website/blob/6ed0a6021bd14e7f078ceb740c33c5d844f968f8/packages/typescriptlang-org/src/components/layout/SiteFooter-PlaygroundSamples.tsx#L75-L106) to handle allowing the popover in the footer. The rest is all CSS. - -This pattern works really well for a medium sized site with only 1-2 core contributors. diff --git a/packages/typescriptlang-org/src/pages/index.tsx b/packages/typescriptlang-org/src/pages/index.tsx index 349dc4b6bab2..07b693bb4748 100644 --- a/packages/typescriptlang-org/src/pages/index.tsx +++ b/packages/typescriptlang-org/src/pages/index.tsx @@ -9,7 +9,9 @@ const Index = (props: any) =>

:wave:

  • TSConfig Reference
  • -
  • Old Handbook Example
  • +
  • Playground
  • +
  • Old Handbook
  • +
  • Sandbox Dev
  • 404 Page
@@ -18,7 +20,4 @@ const Index = (props: any) => - - - export default Index diff --git a/packages/typescriptlang-org/src/pages/playground-dev.tsx b/packages/typescriptlang-org/src/pages/playground-dev.tsx deleted file mode 100644 index 86f63699e573..000000000000 --- a/packages/typescriptlang-org/src/pages/playground-dev.tsx +++ /dev/null @@ -1,54 +0,0 @@ -import React, { useEffect } from "react" -import { Layout } from "../components/layout" -import { withPrefix } from "gatsby" -import { Helmet } from "react-helmet" - -const Index = (props: any) => { - useEffect(() => { - const searchScript = document.createElement('script'); - searchScript.src = withPrefix("/js/vs.loader.js"); - searchScript.async = true; - searchScript.onload = () => { - // @ts-ignore - const re = global.require - - re.config({ - paths: { - vs: `https://tswebinfra.blob.core.windows.net/monaco-editor/0-19-0/min/vs`, - sandbox: '/js/sandbox' - }, - ignoreDuplicateModules: ["vs/editor/editor.main"], - }); - - re(["vs/editor/editor.main", "sandbox/index"], async (main: typeof import("monaco-editor"), sandbox: typeof import("../../static/js/sandbox")) => { - - const playground = await sandbox.setupPlayground({ text: "OK", compilerOptions: {}, typeScriptVersion: "3.5.1", domID: "monaco-editor-embed", useJavaScript: false }, main) - playground.focus() - - }); - } - - document.body.appendChild(searchScript); - }) - - return ( - <> - - - - - -
-

Loading

-
-
- - - ) - -} - - - - -export default Index diff --git a/packages/typescriptlang-org/src/pages/sandbox-dev.tsx b/packages/typescriptlang-org/src/pages/sandbox-dev.tsx new file mode 100644 index 000000000000..2f51a500cb62 --- /dev/null +++ b/packages/typescriptlang-org/src/pages/sandbox-dev.tsx @@ -0,0 +1,56 @@ +import React, { useEffect } from "react" +import { Layout } from "../components/layout" +import { withPrefix } from "gatsby" + +const Index = (props: any) => { + useEffect(() => { + const getLoaderScript = document.createElement('script'); + getLoaderScript.src = withPrefix("/js/vs.loader.js"); + getLoaderScript.async = true; + getLoaderScript.onload = () => { + // @ts-ignore + const re = global.require + + re.config({ + paths: { + vs: "https://tswebinfra.blob.core.windows.net/cdn/3.7.3/monaco/min/vs", + sandbox: '/js/sandbox' + }, + ignoreDuplicateModules: ["vs/editor/editor.main"], + }); + + re(["vs/editor/editor.main", "vs/language/typescript/tsWorker", "sandbox/index"], async (main: typeof import("monaco-editor"), ts: typeof import("typescript"), sandbox: typeof import("typescript-sandbox")) => { + const initialCode = `import {markdown} from "danger" + +markdown("OK")` + const isOK = main && ts && sandbox + if (isOK) { + document.getElementById("loader")!.parentNode?.removeChild(document.getElementById("loader")!) + } + + const playground = await sandbox.createTypeScriptSandbox({ text: initialCode, compilerOptions: {}, domID: "monaco-editor-embed", useJavaScript: false }, main, ts) + + playground.editor.focus() + }); + } + + document.body.appendChild(getLoaderScript); + }) + + return ( + <> + +
+

Loading

+
+
+ + + ) + +} + + + + +export default Index diff --git a/packages/typescriptlang-org/src/templates/play.scss b/packages/typescriptlang-org/src/templates/play.scss new file mode 100644 index 000000000000..1a72d9901dad --- /dev/null +++ b/packages/typescriptlang-org/src/templates/play.scss @@ -0,0 +1,292 @@ +// The subnav beginning with "Playground" +.navbar-sub { + clear: both; + display: flex; + justify-content: space-between; + height: 3rem; + + > ul { + display: flex; + padding: 0; + margin: 0; + } + + li.name { + width: 200px; + font-size: 1.2rem; + font-weight: 600; + + span { + padding-top: 0.6rem; + padding-left: 1rem; + } + } + + li { + list-style: none; + display: flex; + + font-weight: 600; + font-size: 1rem; + + &.divider { + height: 1px; + margin: 9px 0; + overflow: hidden; + background-color: #e5e5e5; + } + + a { + display: block; + padding: 0.75rem 0.75rem 0.5em 0.7em; + text-decoration: none; + color: black; + } + + &.dropdown.open ul { + display: block; + } + + &.dropdown ul { + display: none; + + position: absolute; + top: 100%; + left: 0; + z-index: 1000; + float: left; + } + + // Menu items + &.dropdown ul.dropdown-menu { + min-width: 200px; + padding: 5px 0; + margin: 2px 0 0; + font-size: 14px; + text-align: left; + list-style: none; + background-color: #fff; + -webkit-background-clip: padding-box; + background-clip: padding-box; + border: 1px solid #ccc; + border-radius: 4px; + -webkit-box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175); + box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175); + overflow-y: auto; + max-height: calc(100vh - 150px); + z-index: 99; + + a { + display: block; + } + + li:hover { + color: #262626; + text-decoration: none; + background-color: #f5f5f5; + } + } + + // Examples dropdown + &.dropdown ul.examples-dropdown { + width: 800px; + overflow-y: scroll; + padding: 2rem; + + h3 { + font-size: 2em; + font-weight: 600; + height: 2em; + margin: 0; + } + + #config-container #boolean-options-container { + display: flex; + flex-wrap: wrap; + + > div { + width: 50%; + + ol { + margin: 0; + padding: 0; + } + li { + margin-bottom: 0.5rem; + } + label > span { + font-family: Menlo, Monaco, Consolas, Courier New, monospace; + } + } + } + #compiler-dropdowns { + display: flex; + margin: 1.5rem 0; + + label { + width: 33%; + font-family: Menlo, Monaco, Consolas, Courier New, monospace; + + select { + margin-left: 1rem; + } + + span.compiler-flag-blurb { + display: block; + font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif; + } + } + } + } + a.compiler_info_link { + &::before { + content: "i"; + } + color: green; + border: 1px red solid; + } + } +} + +// The subnav which starts with "TypeScript" +main > nav { + position: relative; + .examples-dropdown { + padding-left: 0; + + background-color: #f2f2f2; + .examples { + background-color: #f2f2f2; + } + } + + > ul { + li { + &:hover { + background-color: #f2f2f2; + } + } + } + + .examples-close { + position: absolute; + top: 20px; + right: 20px; + } + + .examples { + height: auto; + box-shadow: none; + } +} + +// The toolbar for the editor +main #editor-toolbar { + li { + &:hover { + background-color: #f2f2f2; + } + } +} + +.caret { + display: inline-block; + width: 0; + height: 0; + margin-left: 2px; + vertical-align: middle; + border-top: 4px dashed; + border-top: 4px solid; + border-right: 4px solid transparent; + border-left: 4px solid transparent; +} + +#editor-toolbar ul li a:first-child { + padding-left: 1rem; +} + +#playground-container { + display: flex; + height: 800px; + + #editor-container { + width: calc(100% - 360px); + flex-grow: 1; + display: flex; + flex-direction: column; + + #monaco-editor-embed { + height: 700px; + } + + #editor-toolbar { + height: 3rem; + border-bottom: 1px #c4c4c4 solid; + margin-right: -12px; + margin-left: -2rem; + margin-bottom: 10px; + } + } + + .playground-dragbar { + padding: 3px; + cursor: col-resize; + background-color: white; + border-left: 1px #c4c4c4 solid; + margin-left: 10px; + } + + .playground-sidebar { + flex-basis: 320px; + padding: 0 1em; + width: 320px; + max-width: 320px; + display: flex; + flex-direction: column; + + .playground-plugin-container { + flex: 1; + height: 100%; + width: 100%; + overflow: scroll; + + pre { + background-color: rgb(241, 241, 241); + color: black; + border: none; + width: 100%; + } + } + + .playground-plugin-tabview { + display: flex; + align-items: center; + justify-content: center; + height: 2.5rem; + margin-bottom: 10px; + + button { + font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif; + cursor: pointer; + margin: 5px 10px; + padding: 2px 10px; + padding-top: 0px; + color: black; + font-size: 16px; + background: none; + color: inherit; + border: none; + cursor: pointer; + outline: inherit; + + &.active { + border-bottom: 1px solid #187abf; + } + } + } + + pre { + margin: 0; + } + } +} diff --git a/packages/typescriptlang-org/src/templates/play.tsx b/packages/typescriptlang-org/src/templates/play.tsx new file mode 100644 index 000000000000..b24402358fbf --- /dev/null +++ b/packages/typescriptlang-org/src/templates/play.tsx @@ -0,0 +1,191 @@ +import React, { useEffect } from "react" +import { Layout } from "../components/layout" +import { withPrefix } from "gatsby" + +import "./play.scss" +import { RenderExamples } from "../components/ShowExamples" + +// This gets set by the playground +declare const playground: ReturnType + +type Props = { + pageContext: { + lang: string + examplesTOC: typeof import("../../static/js/examples/en.json") + optionsSummary: any // this is just passed through to the playground JS library at this point + } +} + +const Index = (props: Props) => { + useEffect(() => { + if ("playgroundLoaded" in window) return + window["playgroundLoaded"] = true + + // @ts-ignore + window.optionsSummary = props.pageContext.optionsSummary + + const getLoaderScript = document.createElement('script'); + getLoaderScript.src = withPrefix("/js/vs.loader.js"); + getLoaderScript.async = true; + getLoaderScript.onload = () => { + const params = new URLSearchParams(location.search) + const tsVersion = params.get("ts") || "3.7.3" + + // @ts-ignore + const re = global.require + re.config({ + paths: { + vs: `https://tswebinfra.blob.core.windows.net/cdn/${tsVersion}/monaco/min/vs`, + "typescript-sandbox": '/js/sandbox', + "typescript-playground": '/js/playground' + }, + ignoreDuplicateModules: ["vs/editor/editor.main"], + }); + + re(["vs/editor/editor.main", "vs/language/typescript/tsWorker", "typescript-sandbox/index", "typescript-playground/index"], async (main: typeof import("monaco-editor"), tsWorker: any, sandbox: typeof import("typescript-sandbox"), playground: typeof import("typescript-playground")) => { + // Importing "vs/language/typescript/tsWorker" will set ts as a global + const ts = (global as any).ts + const initialCode = `import {markdown} from "danger" + +markdown("OK")` + const isOK = main && ts && sandbox && playground + if (isOK) { + document.getElementById("loader")!.parentNode?.removeChild(document.getElementById("loader")!) + } else { + console.error("Errr") + console.error("main", !!main, "ts", !!ts, "sandbox", !!sandbox, "playground", !!playground) + } + + const sandboxEnv = await sandbox.createTypeScriptSandbox({ + text: initialCode, + compilerOptions: {}, + domID: "monaco-editor-embed", + useJavaScript: false, + logger: { + error: console.error, + log: console.log + } + }, main, ts) + + const playgroundConfig = { + lang: props.pageContext.lang, + prefix: withPrefix("/") + } + + playground.setupPlayground(sandboxEnv, main, playgroundConfig) + sandboxEnv.editor.focus() + + const container = document.getElementById("playground-container")! + container.style.display = "flex" + container.style.height = `${window.innerHeight - Math.round(container.getClientRects()[0].top) - 18}px` + }); + } + + document.body.appendChild(getLoaderScript); + }) + + + return ( + <> + + {/** This is the top nav, which is outside of the editor */} + + +
+

Loading

+ + + + ) +} + + +export default Index diff --git a/packages/typescriptlang-org/static/js/sandbox/.gitkeep b/packages/typescriptlang-org/static/js/sandbox/.gitkeep deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/watcher.js b/watcher.js index b7b15020f227..e87e02dc36d1 100644 --- a/watcher.js +++ b/watcher.js @@ -71,7 +71,7 @@ client.command(['watch-project', process.cwd()], function(error, resp) { root, 'mysubscription', { - expression: ['anyof', ['match', '*.ts'], ['match', '*.md'], ['match', '*.tsx']], + expression: ['anyof', ['match', '*.ts'], ['match', '*.md'], ['match', '*.tsx'], ['match', '*.json']], relative_root: path_prefix, fields: ['name', 'exists', 'type'], }, diff --git a/yarn.lock b/yarn.lock index ac7d7ec241f1..1e23d65a110d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1781,7 +1781,7 @@ dependencies: "@types/unist" "*" -"@types/minimatch@*", "@types/minimatch@^3.0.3": +"@types/minimatch@*": version "3.0.3" resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d" integrity sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA== @@ -1901,7 +1901,7 @@ resolved "https://registry.yarnpkg.com/@types/unist/-/unist-2.0.3.tgz#9c088679876f374eb5983f150d4787aa6fb32d7e" integrity sha512-FvUupuM3rlRsRtCN+fDudtmytGO6iHJuuRKS1Ss0pG5z8oX0diNEw94UEL7hgDbpN94rgaK5R7sWm6RrSkZuAQ== -"@types/vfile-message@*", "@types/vfile-message@^2.0.0": +"@types/vfile-message@*": version "2.0.0" resolved "https://registry.yarnpkg.com/@types/vfile-message/-/vfile-message-2.0.0.tgz#690e46af0fdfc1f9faae00cd049cc888957927d5" integrity sha512-GpTIuDpb9u4zIO165fUy9+fXcULdD8HFRNli04GehoMVbeNq7D6OBnqSmg3lxZnC+UvgUhEWKxdKiwYUkGltIw== @@ -2875,11 +2875,6 @@ arr-union@^3.1.0: resolved "https://registry.yarnpkg.com/arr-union/-/arr-union-3.1.0.tgz#e39b09aea9def866a8f206e288af63919bae39c4" integrity sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ= -array-differ@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/array-differ/-/array-differ-3.0.0.tgz#3cbb3d0f316810eafcc47624734237d6aee4ae6b" - integrity sha512-THtfYS6KtME/yIAhKjZ2ul7XI96lQGHRputJQHO80LAWQnuGP4iCIN8vdMRboGbIEYBwU33q8Tch1os2+X0kMg== - array-equal@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/array-equal/-/array-equal-1.0.0.tgz#8c2a5ef2472fd9ea742b04c77a75093ba2757c93" @@ -6824,21 +6819,6 @@ execa@^1.0.0: signal-exit "^3.0.0" strip-eof "^1.0.0" -execa@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/execa/-/execa-2.1.0.tgz#e5d3ecd837d2a60ec50f3da78fd39767747bbe99" - integrity sha512-Y/URAVapfbYy2Xp/gb6A0E7iR8xeqOCXsuuaoMn7A5PzrXUK84E1gyiEfq0wQd/GHA6GsoHWwhNq8anb0mleIw== - dependencies: - cross-spawn "^7.0.0" - get-stream "^5.0.0" - is-stream "^2.0.0" - merge-stream "^2.0.0" - npm-run-path "^3.0.0" - onetime "^5.1.0" - p-finally "^2.0.0" - signal-exit "^3.0.2" - strip-final-newline "^2.0.0" - execa@^3.4.0: version "3.4.0" resolved "https://registry.yarnpkg.com/execa/-/execa-3.4.0.tgz#c08ed4550ef65d858fac269ffc8572446f37eb89" @@ -7359,7 +7339,7 @@ find-up@^3.0.0: dependencies: locate-path "^3.0.0" -find-up@^4.0.0, find-up@^4.1.0: +find-up@^4.0.0: version "4.1.0" resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== @@ -9208,7 +9188,7 @@ ignore@^4.0.6: resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc" integrity sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg== -ignore@^5.1.1, ignore@^5.1.4: +ignore@^5.1.1: version "5.1.4" resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.1.4.tgz#84b7b3dbe64552b6ef0eca99f6743dbec6d97adf" integrity sha512-MzbUSahkTW1u7JpKKjY7LCARd1fU5W2rLdxlM4kdkayuCwZImjkpluF9CM1aLewYJguPDqewLam18Y6AU69A8A== @@ -12025,7 +12005,7 @@ mozjpeg@^6.0.0: bin-wrapper "^4.0.0" logalot "^2.1.0" -mri@^1.1.0, mri@^1.1.4: +mri@^1.1.0: version "1.1.4" resolved "https://registry.yarnpkg.com/mri/-/mri-1.1.4.tgz#7cb1dd1b9b40905f1fac053abe25b6720f44744a" integrity sha512-6y7IjGPm8AzlvoUrwAaw1tLnUBudaS3752vcd8JtrpGGQn+rXIe63LFVHm/YMwtqAuh+LJPCFdlLYPWM1nYn6w== @@ -12058,17 +12038,6 @@ multicast-dns@^6.0.1: dns-packet "^1.3.1" thunky "^1.0.2" -multimatch@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/multimatch/-/multimatch-4.0.0.tgz#8c3c0f6e3e8449ada0af3dd29efb491a375191b3" - integrity sha512-lDmx79y1z6i7RNx0ZGCPq1bzJ6ZoDDKbvh7jxr9SJcWLkShMzXrHbYVpTdnhNM5MXpDUxCQ4DgqVttVXlBgiBQ== - dependencies: - "@types/minimatch" "^3.0.3" - array-differ "^3.0.0" - array-union "^2.1.0" - arrify "^2.0.1" - minimatch "^3.0.4" - mute-stream@0.0.6: version "0.0.6" resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.6.tgz#48962b19e169fd1dfc240b3f1e7317627bbc47db" @@ -13898,18 +13867,6 @@ pretty-format@^24.9.0: ansi-styles "^3.2.0" react-is "^16.8.4" -pretty-quick@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/pretty-quick/-/pretty-quick-2.0.1.tgz#417ee605ade98ecc686e72f63b5d28a2c35b43e9" - integrity sha512-y7bJt77XadjUr+P1uKqZxFWLddvj3SKY6EU4BuQtMxmmEFSMpbN132pUWdSG1g1mtUfO0noBvn7wBf0BVeomHg== - dependencies: - chalk "^2.4.2" - execa "^2.1.0" - find-up "^4.1.0" - ignore "^5.1.4" - mri "^1.1.4" - multimatch "^4.0.0" - prettyjson@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/prettyjson/-/prettyjson-1.2.1.tgz#fcffab41d19cab4dfae5e575e64246619b12d289"