diff --git a/curriculum/package.json b/curriculum/package.json index a0e4ff08295..10b24cdb6a9 100644 --- a/curriculum/package.json +++ b/curriculum/package.json @@ -49,6 +49,7 @@ "@types/js-yaml": "4.0.5", "@types/polka": "^0.5.7", "@types/string-similarity": "^4.0.2", + "@typescript/vfs-1.6.1": "npm:@typescript/vfs@1.6.1", "@vitest/ui": "^3.2.4", "eslint": "^9.39.1", "glob": "8.1.0", @@ -64,6 +65,7 @@ "puppeteer": "22.12.1", "sirv": "^3.0.2", "string-similarity": "4.0.4", + "typescript-5.9.2": "npm:typescript@5.9.2", "vitest": "^3.2.4" } } diff --git a/curriculum/src/test/test-challenges.js b/curriculum/src/test/test-challenges.js index 1b3d908a15b..1ef99d777f1 100644 --- a/curriculum/src/test/test-challenges.js +++ b/curriculum/src/test/test-challenges.js @@ -1,11 +1,8 @@ -import { describe, it, beforeAll, expect } from 'vitest'; +import { describe, it, beforeAll, expect, vi } from 'vitest'; + import jsdom from 'jsdom'; import lodash from 'lodash'; -import { - buildChallenge, - runnerTypes -} from '../../../client/src/templates/Challenges/utils/build'; import { challengeTypes, hasNoSolution @@ -28,6 +25,32 @@ import { sortChallenges } from './utils/sort-challenges.js'; const { flatten, isEmpty, cloneDeep } = lodash; +vi.mock( + '../../../client/src/templates/Challenges/utils/typescript-worker-handler', + async importOriginal => { + const actual = await importOriginal(); + + // ts and tsvfs must match the versions used in the typescript-worker. + const tsvfs = await import('@typescript/vfs-1.6.1'); + const ts = await import('typescript-5.9.2'); + // use the same TS compiler as the client + const tsCompiler = await import( + '../../../tools/client-plugins/browser-scripts/modules/typescript-compiler' + ); + const compiler = new tsCompiler.Compiler(ts, tsvfs); + await compiler.setup({ useNodeModules: true }); + return { + ...actual, + checkTSServiceIsReady: () => Promise.resolve(true), + compileTypeScriptCode: code => { + const { result, error } = compiler.compile(code, 'index.tsx'); + if (error) throw error; + return result; + } + }; + } +); + const dom = new jsdom.JSDOM(''); global.document = dom.window.document; global.DOMParser = dom.window.DOMParser; @@ -86,13 +109,13 @@ export async function defineTestsForBlock(testFilter) { const challengeData = { meta, challenges, lang }; - describe('Check challenges', () => { + describe('Check challenges', async () => { beforeAll(async () => { page = await newPageContext(); global.Worker = createPseudoWorker(page); }); - populateTestsForLang(challengeData, () => page); + await populateTestsForLang(challengeData, () => page); }); } @@ -123,7 +146,13 @@ export async function getChallenges(lang, filters) { return sortChallenges(challenges); } -function populateTestsForLang({ lang, challenges, meta }) { +async function populateTestsForLang({ lang, challenges, meta }) { + // We have to dynamically import this because otherwise it will not be mocked. + // Presumably this is because we import from_this file in the generated block + // test files and that happens before the mock is applied. + const { buildChallenge } = await import( + '../../../client/src/templates/Challenges/utils/build' + ); const validateChallenge = challengeSchemaValidator(); describe(`Language: ${lang}`, function () { @@ -341,6 +370,10 @@ async function createTestRunner( buildChallenge, solutionFromNext ) { + const { runnerTypes } = await import( + '../../../client/src/templates/Challenges/utils/build' + ); + const challengeFiles = replaceChallengeFilesContentsWithSolutions( challenge.challengeFiles, solutionFiles diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4129a8ba248..cf673e56729 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -121,7 +121,7 @@ importers: version: 1.3.1 '@prisma/client': specifier: 6.16.2 - version: 6.16.2(prisma@6.16.2(typescript@5.8.2))(typescript@5.8.2) + version: 6.16.2(prisma@6.16.2(typescript@5.9.3))(typescript@5.9.3) '@sentry/node': specifier: 9.1.0 version: 9.1.0 @@ -230,10 +230,10 @@ importers: version: 26.1.0 msw: specifier: ^2.7.0 - version: 2.8.7(@types/node@20.12.8)(typescript@5.8.2) + version: 2.8.7(@types/node@20.12.8)(typescript@5.9.3) prisma: specifier: 6.16.2 - version: 6.16.2(typescript@5.8.2) + version: 6.16.2(typescript@5.9.3) supertest: specifier: 6.3.3 version: 6.3.3 @@ -242,7 +242,7 @@ importers: version: 4.19.1 vitest: specifier: ^3.2.4 - version: 3.2.4(@types/debug@4.1.12)(@types/node@20.12.8)(@vitest/ui@3.2.4)(jiti@2.6.1)(jsdom@26.1.0)(msw@2.8.7(@types/node@20.12.8)(typescript@5.8.2))(terser@5.28.1)(tsx@4.19.1)(yaml@2.8.1) + version: 3.2.4(@types/debug@4.1.12)(@types/node@20.12.8)(@vitest/ui@3.2.4)(jiti@2.6.1)(jsdom@26.1.0)(msw@2.8.7(@types/node@20.12.8)(typescript@5.9.3))(terser@5.28.1)(tsx@4.19.1)(yaml@2.8.1) client: dependencies: @@ -703,6 +703,9 @@ importers: '@types/string-similarity': specifier: ^4.0.2 version: 4.0.2 + '@typescript/vfs-1.6.1': + specifier: npm:@typescript/vfs@1.6.1 + version: '@typescript/vfs@1.6.1(typescript@5.9.3)' '@vitest/ui': specifier: ^3.2.4 version: 3.2.4(vitest@3.2.4) @@ -741,16 +744,19 @@ importers: version: 0.5.2 puppeteer: specifier: 22.12.1 - version: 22.12.1(typescript@5.8.2) + version: 22.12.1(typescript@5.9.3) sirv: specifier: ^3.0.2 version: 3.0.2 string-similarity: specifier: 4.0.4 version: 4.0.4 + typescript-5.9.2: + specifier: npm:typescript@5.9.2 + version: typescript@5.9.2 vitest: specifier: ^3.2.4 - version: 3.2.4(@types/debug@4.1.12)(@types/node@20.12.8)(@vitest/ui@3.2.4)(jiti@2.6.1)(jsdom@16.7.0)(msw@2.8.7(@types/node@20.12.8)(typescript@5.8.2))(terser@5.28.1)(tsx@4.19.1)(yaml@2.8.1) + version: 3.2.4(@types/debug@4.1.12)(@types/node@20.12.8)(@vitest/ui@3.2.4)(jiti@2.6.1)(jsdom@16.7.0)(msw@2.8.7(@types/node@20.12.8)(typescript@5.9.3))(terser@5.28.1)(tsx@4.19.1)(yaml@2.8.1) e2e: devDependencies: @@ -834,7 +840,7 @@ importers: version: 9.39.1(jiti@2.6.1) vitest: specifier: ^3.2.4 - version: 3.2.4(@types/debug@4.1.12)(@types/node@20.12.8)(@vitest/ui@3.2.4)(jiti@2.6.1)(jsdom@26.1.0)(msw@2.8.7(@types/node@20.12.8)(typescript@5.8.2))(terser@5.28.1)(tsx@4.19.1)(yaml@2.8.1) + version: 3.2.4(@types/debug@4.1.12)(@types/node@20.12.8)(@vitest/ui@3.2.4)(jiti@2.6.1)(jsdom@26.1.0)(msw@2.8.7(@types/node@20.12.8)(typescript@5.9.3))(terser@5.28.1)(tsx@4.19.1)(yaml@2.8.1) tools/challenge-editor/api: dependencies: @@ -1062,13 +1068,13 @@ importers: version: 3.0.4 vitest: specifier: ^3.2.4 - version: 3.2.4(@types/debug@4.1.12)(@types/node@20.12.8)(@vitest/ui@3.2.4)(jiti@2.6.1)(jsdom@26.1.0)(msw@2.8.7(@types/node@20.12.8)(typescript@5.8.2))(terser@5.28.1)(tsx@4.19.1)(yaml@2.8.1) + version: 3.2.4(@types/debug@4.1.12)(@types/node@20.12.8)(@vitest/ui@3.2.4)(jiti@2.6.1)(jsdom@26.1.0)(msw@2.8.7(@types/node@20.12.8)(typescript@5.9.3))(terser@5.28.1)(tsx@4.19.1)(yaml@2.8.1) tools/client-plugins/browser-scripts: dependencies: '@freecodecamp/curriculum-helpers': specifier: ^7.1.0 - version: 7.1.0(debug@4.3.4)(typescript@5.7.3) + version: 7.1.0(debug@4.3.4)(typescript@5.9.2) xterm: specifier: ^5.2.1 version: 5.3.0 @@ -1092,8 +1098,8 @@ importers: specifier: ^8.0.1 version: 8.0.1(webpack-cli@4.10.0) '@typescript/vfs': - specifier: ^1.6.0 - version: 1.6.1(typescript@5.7.3) + specifier: 1.6.1 + version: 1.6.1(typescript@5.9.2) babel-loader: specifier: 8.3.0 version: 8.3.0(@babel/core@7.28.5)(webpack@5.90.3) @@ -1112,6 +1118,9 @@ importers: sass.js: specifier: 0.11.1 version: 0.11.1 + typescript: + specifier: 5.9.2 + version: 5.9.2 util: specifier: 0.12.5 version: 0.12.5 @@ -1177,7 +1186,7 @@ importers: version: 9.39.1(jiti@2.6.1) vitest: specifier: ^3.2.4 - version: 3.2.4(@types/debug@4.1.12)(@types/node@20.12.8)(@vitest/ui@3.2.4)(jiti@2.6.1)(jsdom@26.1.0)(msw@2.8.7(@types/node@20.12.8)(typescript@5.8.2))(terser@5.28.1)(tsx@4.19.1)(yaml@2.8.1) + version: 3.2.4(@types/debug@4.1.12)(@types/node@20.12.8)(@vitest/ui@3.2.4)(jiti@2.6.1)(jsdom@26.1.0)(msw@2.8.7(@types/node@20.12.8)(typescript@5.9.3))(terser@5.28.1)(tsx@4.19.1)(yaml@2.8.1) tools/scripts/seed: devDependencies: @@ -13091,6 +13100,16 @@ packages: engines: {node: '>=14.17'} hasBin: true + typescript@5.9.2: + resolution: {integrity: sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==} + engines: {node: '>=14.17'} + hasBin: true + + typescript@5.9.3: + resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} + engines: {node: '>=14.17'} + hasBin: true + uc.micro@2.0.0: resolution: {integrity: sha512-DffL94LsNOccVn4hyfRe5rdKa273swqeA5DJpMOeFmEn1wCDc7nAbbB0gXlgBCL7TNzeTv6G7XVWzan7iJtfig==} @@ -17098,7 +17117,7 @@ snapshots: prop-types: 15.8.1 react: 17.0.2 - '@freecodecamp/curriculum-helpers@7.1.0(debug@4.3.4)(typescript@5.7.3)': + '@freecodecamp/curriculum-helpers@7.1.0(debug@4.3.4)(typescript@5.9.2)': dependencies: '@sinonjs/fake-timers': 14.0.0 '@types/jquery': 3.5.32 @@ -17111,10 +17130,10 @@ snapshots: http-server: 14.1.1(debug@4.3.4) jquery: 3.7.1 process: 0.11.10 - puppeteer: 24.10.0(typescript@5.7.3) + puppeteer: 24.10.0(typescript@5.9.2) pyodide: 0.23.3 util: 0.12.5 - vitest-environment-puppeteer: 11.0.3(debug@4.3.4)(typescript@5.7.3) + vitest-environment-puppeteer: 11.0.3(debug@4.3.4)(typescript@5.9.2) transitivePeerDependencies: - bare-buffer - bufferutil @@ -17902,10 +17921,10 @@ snapshots: '@polka/url@1.0.0-next.29': {} - '@prisma/client@6.16.2(prisma@6.16.2(typescript@5.8.2))(typescript@5.8.2)': + '@prisma/client@6.16.2(prisma@6.16.2(typescript@5.9.3))(typescript@5.9.3)': optionalDependencies: - prisma: 6.16.2(typescript@5.8.2) - typescript: 5.8.2 + prisma: 6.16.2(typescript@5.9.3) + typescript: 5.9.3 '@prisma/config@6.16.2': dependencies: @@ -19609,10 +19628,17 @@ snapshots: '@typescript-eslint/types': 8.47.0 eslint-visitor-keys: 4.2.1 - '@typescript/vfs@1.6.1(typescript@5.7.3)': + '@typescript/vfs@1.6.1(typescript@5.9.2)': dependencies: debug: 4.3.4(supports-color@8.1.1) - typescript: 5.7.3 + typescript: 5.9.2 + transitivePeerDependencies: + - supports-color + + '@typescript/vfs@1.6.1(typescript@5.9.3)': + dependencies: + debug: 4.3.4(supports-color@8.1.1) + typescript: 5.9.3 transitivePeerDependencies: - supports-color @@ -19728,13 +19754,13 @@ snapshots: msw: 2.8.7(@types/node@20.12.8)(typescript@5.7.3) vite: 7.1.3(@types/node@20.12.8)(jiti@2.6.1)(terser@5.28.1)(tsx@4.19.1)(yaml@2.8.1) - '@vitest/mocker@3.2.4(msw@2.8.7(@types/node@20.12.8)(typescript@5.8.2))(vite@7.1.3(@types/node@20.12.8)(jiti@2.6.1)(terser@5.28.1)(tsx@4.19.1)(yaml@2.8.1))': + '@vitest/mocker@3.2.4(msw@2.8.7(@types/node@20.12.8)(typescript@5.9.3))(vite@7.1.3(@types/node@20.12.8)(jiti@2.6.1)(terser@5.28.1)(tsx@4.19.1)(yaml@2.8.1))': dependencies: '@vitest/spy': 3.2.4 estree-walker: 3.0.3 magic-string: 0.30.17 optionalDependencies: - msw: 2.8.7(@types/node@20.12.8)(typescript@5.8.2) + msw: 2.8.7(@types/node@20.12.8)(typescript@5.9.3) vite: 7.1.3(@types/node@20.12.8)(jiti@2.6.1)(terser@5.28.1)(tsx@4.19.1)(yaml@2.8.1) '@vitest/pretty-format@3.2.4': @@ -19766,7 +19792,7 @@ snapshots: sirv: 3.0.2 tinyglobby: 0.2.14 tinyrainbow: 2.0.0 - vitest: 3.2.4(@types/debug@4.1.12)(@types/node@20.12.8)(@vitest/ui@3.2.4)(jiti@2.6.1)(jsdom@26.1.0)(msw@2.8.7(@types/node@20.12.8)(typescript@5.7.3))(terser@5.28.1)(tsx@4.19.1)(yaml@2.8.1) + vitest: 3.2.4(@types/debug@4.1.12)(@types/node@20.12.8)(@vitest/ui@3.2.4)(jiti@2.6.1)(jsdom@26.1.0)(msw@2.8.7(@types/node@20.12.8)(typescript@5.9.3))(terser@5.28.1)(tsx@4.19.1)(yaml@2.8.1) '@vitest/utils@3.2.4': dependencies: @@ -21380,14 +21406,23 @@ snapshots: optionalDependencies: typescript: 5.7.3 - cosmiconfig@9.0.0(typescript@5.8.2): + cosmiconfig@9.0.0(typescript@5.9.2): dependencies: env-paths: 2.2.1 import-fresh: 3.3.0 js-yaml: 4.1.0 parse-json: 5.2.0 optionalDependencies: - typescript: 5.8.2 + typescript: 5.9.2 + + cosmiconfig@9.0.0(typescript@5.9.3): + dependencies: + env-paths: 2.2.1 + import-fresh: 3.3.0 + js-yaml: 4.1.0 + parse-json: 5.2.0 + optionalDependencies: + typescript: 5.9.3 create-ecdh@4.0.4: dependencies: @@ -22365,7 +22400,7 @@ snapshots: confusing-browser-globals: 1.0.11 eslint: 7.32.0 eslint-plugin-flowtype: 5.10.0(eslint@7.32.0) - eslint-plugin-import: 2.31.0(@typescript-eslint/parser@4.33.0(eslint@7.32.0)(typescript@5.2.2))(eslint@9.39.1(jiti@2.6.1)) + eslint-plugin-import: 2.31.0(@typescript-eslint/parser@4.33.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.2.2))(eslint@9.39.1(jiti@2.6.1)) eslint-plugin-jsx-a11y: 6.10.2(eslint@9.39.1(jiti@2.6.1)) eslint-plugin-react: 7.37.4(eslint@9.39.1(jiti@2.6.1)) eslint-plugin-react-hooks: 4.6.0(eslint@9.39.1(jiti@2.6.1)) @@ -22395,7 +22430,7 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-module-utils@2.12.0(@typescript-eslint/parser@4.33.0(eslint@7.32.0)(typescript@5.2.2))(eslint-import-resolver-node@0.3.9)(eslint@9.39.1(jiti@2.6.1)): + eslint-module-utils@2.12.0(@typescript-eslint/parser@4.33.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.2.2))(eslint-import-resolver-node@0.3.9)(eslint@9.39.1(jiti@2.6.1)): dependencies: debug: 3.2.7 optionalDependencies: @@ -22449,7 +22484,7 @@ snapshots: - typescript - utf-8-validate - eslint-plugin-import@2.31.0(@typescript-eslint/parser@4.33.0(eslint@7.32.0)(typescript@5.2.2))(eslint@9.39.1(jiti@2.6.1)): + eslint-plugin-import@2.31.0(@typescript-eslint/parser@4.33.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.2.2))(eslint@9.39.1(jiti@2.6.1)): dependencies: '@rtsao/scc': 1.1.0 array-includes: 3.1.8 @@ -22460,7 +22495,7 @@ snapshots: doctrine: 2.1.0 eslint: 9.39.1(jiti@2.6.1) eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.0(@typescript-eslint/parser@4.33.0(eslint@7.32.0)(typescript@5.2.2))(eslint-import-resolver-node@0.3.9)(eslint@9.39.1(jiti@2.6.1)) + eslint-module-utils: 2.12.0(@typescript-eslint/parser@4.33.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.2.2))(eslint-import-resolver-node@0.3.9)(eslint@9.39.1(jiti@2.6.1)) hasown: 2.0.2 is-core-module: 2.16.1 is-glob: 4.0.3 @@ -23683,7 +23718,7 @@ snapshots: eslint-config-react-app: 6.0.0(@typescript-eslint/eslint-plugin@4.33.0(@typescript-eslint/parser@4.33.0(eslint@7.32.0)(typescript@5.2.2))(eslint@7.32.0)(typescript@5.2.2))(@typescript-eslint/parser@4.33.0(eslint@7.32.0)(typescript@5.2.2))(babel-eslint@10.1.0(eslint@9.39.1(jiti@2.6.1)))(eslint-plugin-flowtype@5.10.0(eslint@7.32.0))(eslint-plugin-import@2.31.0(@typescript-eslint/parser@4.33.0(eslint@7.32.0)(typescript@5.2.2))(eslint@7.32.0))(eslint-plugin-jsx-a11y@6.10.2(eslint@7.32.0))(eslint-plugin-react-hooks@4.6.0(eslint@7.32.0))(eslint-plugin-react@7.37.4(eslint@7.32.0))(eslint@7.32.0)(typescript@5.2.2) eslint-plugin-flowtype: 5.10.0(eslint@7.32.0) eslint-plugin-graphql: 4.0.0(@types/node@20.12.8)(graphql@15.8.0)(typescript@5.2.2) - eslint-plugin-import: 2.31.0(@typescript-eslint/parser@4.33.0(eslint@7.32.0)(typescript@5.2.2))(eslint@9.39.1(jiti@2.6.1)) + eslint-plugin-import: 2.31.0(@typescript-eslint/parser@4.33.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.2.2))(eslint@9.39.1(jiti@2.6.1)) eslint-plugin-jsx-a11y: 6.10.2(eslint@9.39.1(jiti@2.6.1)) eslint-plugin-react: 7.37.4(eslint@9.39.1(jiti@2.6.1)) eslint-plugin-react-hooks: 4.6.0(eslint@9.39.1(jiti@2.6.1)) @@ -26479,7 +26514,7 @@ snapshots: - '@types/node' optional: true - msw@2.8.7(@types/node@20.12.8)(typescript@5.8.2): + msw@2.8.7(@types/node@20.12.8)(typescript@5.9.3): dependencies: '@bundled-es-modules/cookie': 2.0.1 '@bundled-es-modules/statuses': 1.0.1 @@ -26500,7 +26535,7 @@ snapshots: type-fest: 4.37.0 yargs: 17.7.2 optionalDependencies: - typescript: 5.8.2 + typescript: 5.9.3 transitivePeerDependencies: - '@types/node' @@ -27449,12 +27484,12 @@ snapshots: ansi-styles: 5.2.0 react-is: 18.2.0 - prisma@6.16.2(typescript@5.8.2): + prisma@6.16.2(typescript@5.9.3): dependencies: '@prisma/config': 6.16.2 '@prisma/engines': 6.16.2 optionalDependencies: - typescript: 5.8.2 + typescript: 5.9.3 transitivePeerDependencies: - magicast @@ -27592,10 +27627,10 @@ snapshots: - supports-color - utf-8-validate - puppeteer@22.12.1(typescript@5.8.2): + puppeteer@22.12.1(typescript@5.9.3): dependencies: '@puppeteer/browsers': 2.2.3 - cosmiconfig: 9.0.0(typescript@5.8.2) + cosmiconfig: 9.0.0(typescript@5.9.3) devtools-protocol: 0.0.1299070 puppeteer-core: 22.12.1 transitivePeerDependencies: @@ -27605,11 +27640,11 @@ snapshots: - typescript - utf-8-validate - puppeteer@24.10.0(typescript@5.7.3): + puppeteer@24.10.0(typescript@5.9.2): dependencies: '@puppeteer/browsers': 2.10.5 chromium-bidi: 5.1.0(devtools-protocol@0.0.1452169) - cosmiconfig: 9.0.0(typescript@5.7.3) + cosmiconfig: 9.0.0(typescript@5.9.2) devtools-protocol: 0.0.1452169 puppeteer-core: 24.10.0 typed-query-selector: 2.12.0 @@ -29806,6 +29841,10 @@ snapshots: typescript@5.8.2: {} + typescript@5.9.2: {} + + typescript@5.9.3: {} + uc.micro@2.0.0: {} umd@3.0.3: {} @@ -30283,21 +30322,21 @@ snapshots: transitivePeerDependencies: - debug - vitest-environment-puppeteer@11.0.3(debug@4.3.4)(typescript@5.7.3): + vitest-environment-puppeteer@11.0.3(debug@4.3.4)(typescript@5.9.2): dependencies: chalk: 4.1.2 - cosmiconfig: 9.0.0(typescript@5.7.3) + cosmiconfig: 9.0.0(typescript@5.9.2) deepmerge: 4.3.1 vitest-dev-server: 11.0.3(debug@4.3.4) transitivePeerDependencies: - debug - typescript - vitest@3.2.4(@types/debug@4.1.12)(@types/node@20.12.8)(@vitest/ui@3.2.4)(jiti@2.6.1)(jsdom@16.7.0)(msw@2.8.7(@types/node@20.12.8)(typescript@5.8.2))(terser@5.28.1)(tsx@4.19.1)(yaml@2.8.1): + vitest@3.2.4(@types/debug@4.1.12)(@types/node@20.12.8)(@vitest/ui@3.2.4)(jiti@2.6.1)(jsdom@16.7.0)(msw@2.8.7(@types/node@20.12.8)(typescript@5.9.3))(terser@5.28.1)(tsx@4.19.1)(yaml@2.8.1): dependencies: '@types/chai': 5.2.2 '@vitest/expect': 3.2.4 - '@vitest/mocker': 3.2.4(msw@2.8.7(@types/node@20.12.8)(typescript@5.8.2))(vite@7.1.3(@types/node@20.12.8)(jiti@2.6.1)(terser@5.28.1)(tsx@4.19.1)(yaml@2.8.1)) + '@vitest/mocker': 3.2.4(msw@2.8.7(@types/node@20.12.8)(typescript@5.9.3))(vite@7.1.3(@types/node@20.12.8)(jiti@2.6.1)(terser@5.28.1)(tsx@4.19.1)(yaml@2.8.1)) '@vitest/pretty-format': 3.2.4 '@vitest/runner': 3.2.4 '@vitest/snapshot': 3.2.4 @@ -30425,11 +30464,11 @@ snapshots: - tsx - yaml - vitest@3.2.4(@types/debug@4.1.12)(@types/node@20.12.8)(@vitest/ui@3.2.4)(jiti@2.6.1)(jsdom@26.1.0)(msw@2.8.7(@types/node@20.12.8)(typescript@5.8.2))(terser@5.28.1)(tsx@4.19.1)(yaml@2.8.1): + vitest@3.2.4(@types/debug@4.1.12)(@types/node@20.12.8)(@vitest/ui@3.2.4)(jiti@2.6.1)(jsdom@26.1.0)(msw@2.8.7(@types/node@20.12.8)(typescript@5.9.3))(terser@5.28.1)(tsx@4.19.1)(yaml@2.8.1): dependencies: '@types/chai': 5.2.2 '@vitest/expect': 3.2.4 - '@vitest/mocker': 3.2.4(msw@2.8.7(@types/node@20.12.8)(typescript@5.8.2))(vite@7.1.3(@types/node@20.12.8)(jiti@2.6.1)(terser@5.28.1)(tsx@4.19.1)(yaml@2.8.1)) + '@vitest/mocker': 3.2.4(msw@2.8.7(@types/node@20.12.8)(typescript@5.9.3))(vite@7.1.3(@types/node@20.12.8)(jiti@2.6.1)(terser@5.28.1)(tsx@4.19.1)(yaml@2.8.1)) '@vitest/pretty-format': 3.2.4 '@vitest/runner': 3.2.4 '@vitest/snapshot': 3.2.4 diff --git a/tools/client-plugins/browser-scripts/modules/typescript-compiler.ts b/tools/client-plugins/browser-scripts/modules/typescript-compiler.ts index ca46cf4cc91..801da2b003e 100644 --- a/tools/client-plugins/browser-scripts/modules/typescript-compiler.ts +++ b/tools/client-plugins/browser-scripts/modules/typescript-compiler.ts @@ -11,15 +11,12 @@ export class Compiler { tsvfs: TSVFS; tsEnv?: VirtualTypeScriptEnvironment; compilerHost?: CompilerHost; - constructor( - ts: typeof import('typescript'), - tsvfs: typeof import('@typescript/vfs') - ) { + constructor(ts: TS, tsvfs: TSVFS) { this.ts = ts; this.tsvfs = tsvfs; } - async setup() { + async setup(opts?: { useNodeModules: boolean }) { const ts = this.ts; const tsvfs = this.tsvfs; @@ -33,17 +30,21 @@ export class Compiler { jsx: ts.JsxEmit.Preserve, // Babel will handle JSX, allowUmdGlobalAccess: true // Necessary because React is loaded via a UMD script. }; - const fsMap = await tsvfs.createDefaultMapFromCDN( - compilerOptions, - ts.version, - false, // TODO: cache this. It needs a store that's available to workers and implements https://github.com/microsoft/TypeScript-Website/blob/ac68b8b8e4a621113c4ee45c4051002fd55ede24/packages/typescript-vfs/src/index.ts#L11 - ts - ); + + const fsMap = opts?.useNodeModules + ? tsvfs.createDefaultMapFromNodeModules(compilerOptions, ts) + : await tsvfs.createDefaultMapFromCDN( + compilerOptions, + ts.version, + false, // TODO: cache this. It needs a store that's available to workers and implements https://github.com/microsoft/TypeScript-Website/blob/ac68b8b8e4a621113c4ee45c4051002fd55ede24/packages/typescript-vfs/src/index.ts#L11 + ts + ); // This can be any path, but doing this means import React from 'react' works, if we ever need it. const reactTypesPath = `/node_modules/@types/react/index.d.ts`; // It may be necessary to get all the types (global.d.ts etc) + fsMap.set(reactTypesPath, reactTypes['react-18'] || ''); const system = tsvfs.createSystem(fsMap); @@ -65,10 +66,14 @@ export class Compiler { ).compilerHost; } - compile(code: string, fileName: string) { + compile(rawCode: string, fileName: string) { if (!this.tsEnv || !this.compilerHost) { throw Error('TypeScript environment not set up'); } + // If we try to update or create an empty file, the environment will become + // permanently unable to interact with that file. The workaround is to create + // a file with a single newline character. + const code = rawCode || '\n'; // TODO: If creating the file fresh each time is too slow, we can try checking // if the file exists and updating it if it does. this.tsEnv.createFile(fileName, code); diff --git a/tools/client-plugins/browser-scripts/package.json b/tools/client-plugins/browser-scripts/package.json index 9c06e97e0f9..56172129ec2 100644 --- a/tools/client-plugins/browser-scripts/package.json +++ b/tools/client-plugins/browser-scripts/package.json @@ -32,13 +32,14 @@ "@babel/preset-typescript": "7.23.3", "@freecodecamp/eslint-config": "workspace:*", "@types/copy-webpack-plugin": "^8.0.1", - "@typescript/vfs": "^1.6.0", + "@typescript/vfs": "1.6.1", "babel-loader": "8.3.0", "copy-webpack-plugin": "9.1.0", "eslint": "^9.39.1", "process": "0.11.10", "pyodide": "^0.23.3", "sass.js": "0.11.1", + "typescript": "5.9.2", "util": "0.12.5", "webpack": "5.90.3", "webpack-cli": "4.10.0" diff --git a/tools/client-plugins/browser-scripts/typescript-worker.ts b/tools/client-plugins/browser-scripts/typescript-worker.ts index d2d13d7610e..255d116c306 100644 --- a/tools/client-plugins/browser-scripts/typescript-worker.ts +++ b/tools/client-plugins/browser-scripts/typescript-worker.ts @@ -92,11 +92,7 @@ async function handleCheckIsReadyRequest(port: MessagePort) { } function handleCompileRequest(data: TSCompileEvent['data'], port: MessagePort) { - // If we try to update or create an empty file, the environment will become - // permanently unable to interact with that file. The workaround is to create - // a file with a single newline character. - const code = (data.code || '').slice() || '\n'; - const { result, error } = compiler.compile(code, 'index.tsx'); + const { result, error } = compiler.compile(data.code, 'index.tsx'); const message: TSCompiledMessage = { type: 'compiled', value: result,