diff --git a/CHANGELOG.md b/CHANGELOG.md index 55704933..6d34d7d2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,7 +17,15 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - +## Unreleased + +### Added + +- Fetch all `@types` packages listed in the project's `package.json` file and + include them for TypeScript compilation. This allows type-checking packages + that do not ship their own types but do have a DefinitelyTyped package + available. Note: This does not automatically download the `@types` package for + a package. It must be manually listed in `package.json`. ## [0.17.0] - 2022-11-11 diff --git a/README.md b/README.md index 4875d3e8..f2dd7871 100644 --- a/README.md +++ b/README.md @@ -361,6 +361,10 @@ statement should use the `.js` extension (the same as you would do when running import './my-other-module.js'; ``` +You may also include any Definitely Typed (`@types`) packages for type checking +during compilation by listing it as a dependency in the project's +[`package.json` file](#packagejson). + ## Hiding & folding If a region of code in a Playground project file is surrounded by diff --git a/src/test/types-fetcher_test.ts b/src/test/types-fetcher_test.ts index 45b63445..24d42e9c 100644 --- a/src/test/types-fetcher_test.ts +++ b/src/test/types-fetcher_test.ts @@ -630,6 +630,52 @@ suite('types fetcher', () => { }); }); + test('@types package', async () => { + const sourceTexts: string[] = []; + const packageJson: PackageJson = { + dependencies: { + '@types/a': '1.0.0', + }, + }; + const cdnData: CdnData = { + '@types/a': { + versions: { + '1.0.0': { + files: { + 'index.d.ts': { + content: 'declare module a { export const a: 1; }', + }, + }, + }, + }, + }, + }; + const expectedDependencyGraph: ExpectedDependencyGraph = { + root: { + '@types/a': '1.0.0', + }, + deps: {}, + }; + const expectedLayout: NodeModulesDirectory = { + '@types/a': { + version: '1.0.0', + nodeModules: {}, + }, + }; + const expectedFiles = new Map([ + ['@types/a/index.d.ts', 'declare module a { export const a: 1; }'], + ['@types/a/package.json', '{}'], + ]); + await checkTypesFetcher({ + sourceTexts, + packageJson, + cdnData, + expectedFiles, + expectedDependencyGraph, + expectedLayout, + }); + }); + test('declare module', async () => { // Declaring a module should not count as an import, but anything imported // from within the declare module block should. diff --git a/src/typescript-worker/types-fetcher.ts b/src/typescript-worker/types-fetcher.ts index 5aeebbe2..390881fe 100644 --- a/src/typescript-worker/types-fetcher.ts +++ b/src/typescript-worker/types-fetcher.ts @@ -94,6 +94,7 @@ export class TypesFetcher { fetcher._handleBareAndRelativeSpecifiers(source, root) ), ...tsLibs.map((lib) => fetcher._addTypeScriptStandardLib(lib)), + fetcher._fetchTypesPackages(), ]); const layout = new NodeModulesLayoutMaker().layout( fetcher._rootDependencies, @@ -123,6 +124,27 @@ export class TypesFetcher { this._rootPackageJson = rootPackageJson; } + private async _fetchTypesPackages(): Promise { + if ( + this._rootPackageJson === undefined || + this._rootPackageJson.dependencies === undefined + ) { + return; + } + + const typesPackages = Object.keys( + this._rootPackageJson.dependencies + ).filter((k) => k.startsWith('@types/')); + + if (typesPackages.length === 0) { + return; + } + + await Promise.allSettled( + typesPackages.map((k) => this._handleBareSpecifier(k, root)) + ); + } + private async _addTypeScriptStandardLib(lib: string): Promise { return this._handleBareSpecifier( `typescript/lib/lib.${lib.toLowerCase()}.js`, diff --git a/web-test-runner.config.js b/web-test-runner.config.js index 5c35f579..5ebeb5d2 100644 --- a/web-test-runner.config.js +++ b/web-test-runner.config.js @@ -45,8 +45,9 @@ export default { // only one or the other can be installed at once (see our "postinstall" NPM // script). See // https://modern-web.dev/docs/test-runner/browser-launchers/puppeteer/. - puppeteerLauncher({launchOptions: {product: 'firefox'}}), + puppeteerLauncher({launchOptions: {product: 'firefox'}, concurrency: 1}), ], + concurrentBrowsers: Number(process.env.CONCURRENT_BROWSERS) || 2, // default 2 browserStartTimeout: 30000, // default 30000 testsStartTimeout: 20000, // default 10000 testsFinishTimeout: 90000, // default 20000