This PR aims to vastly improve our TS types and how we ship them.
Our previous attempt at shipping TypeScript was unfortunately flawed for
many reasons when compared to the @types/puppeteer package:
* It only worked if you needed the default export. If you wanted to
import a type that Puppeteer uses, you'd have to do `import type X
from 'puppeteer/lib/...'`. This is not something we want to encourage
because that means our internal file structure becomes almost public
API.
* It gave absolutely no help to CommonJS users in JS files because it
would warn people they needed to do `const pptr =
require('puppeteer').default, which is not correct.
* I found a bug in the `evaluate` types which mean't you couldn't
override the types to provide more info, and TS would insist the types
were all `unknown`.
The goal of this PR is to support:
1. In a `ts` file, `import puppeteer from 'puppeteer'`
1. In a `ts` file, `import type {ElementHandle} from 'puppeteer'`
1. In a `ts` file, referencing a type as `puppeteer.ElementHandle`
1. In a `ts` file, you can get good type inference when running
`foo.evaluate(x => x.clientHeight)`.
1. In a `js` file using CJS, you can do `const puppeteer =
require('puppeteer')` and get good type help from VSCode.
To test this I created a new empty repository with two test files in,
one `.ts` file with this in:
https://gist.github.com/jackfranklin/22ba2f390f97c7312cd70025a2096fc8,
and a `js` file with this in:
https://gist.github.com/jackfranklin/06bed136fdb22419cb7a8a9a4d4ef32f.
These files included enough code to check that the types were behaving
as I expected.
The fix for our types was to make use of API Extractor, which we already
use for our docs, to "rollup" all the disparate type files that TS
generates into one large `types.d.ts` which contains all the various
types that we define, such as:
```ts
export declare class ElementHandle {...}
export type EvaluateFn ...
```
If we then update our `package.json` `types` field to point to that file
in `lib/types.d.ts`, this then allows a developer to write:
```
import type {ElementHandle} from 'puppeteer'
```
And get the correct type definitions. However, what the `types.d.ts`
file doesn't do out of the box is declare the default export, so
importing Puppeteer's default export to call a method such as `launch`
on it will get you an error.
That's where the `script/add-default-export-to-types.ts` comes in. It
appends the following to the auto-generated `types.d.ts` file:
```ts
declare const puppeteer: PuppeteerNode;
export = puppeteer;
```
This tells TypeScript what the default export is, and by using the
`export =` syntax, we make sure TS understands both in a TS ESM
environment and in a JS CJS environment.
Now the `build` step, which is run by GitHub Actions when we release,
will generate the `.d.ts` file and then extend it with the default
export code.
To ensure that I was generating a valid package, I created a new
repository locally with the two code samples linked in Gists above. I
then ran:
```
npm init -y
npm install --save-dev typescript
npx tsc --init
```
Which gives me a base to test from. In Puppeteer, I ran `npm pack`,
which packs the module into a tar that's almost identical to what would
be published, so I can be confident that the .d.ts files in there are
what would be published.
I then installed it:
```
npm install --save-dev ../../puppeteer/puppeteer-7.0.1-post.tgz
```
And then reloaded VSCode in my dummy project. By deliberately making
typos and hovering over the code, I could confirm that all the goals
listed above were met, and this seems like a vast improvement on our
types.
* chore: vendor Mitt into src/common/third-party
As discussed in #6203 we need to vendor our common dependencies in so
that when we ship an ESM build all imports point to file paths and do
not rely on Node resolution (e.g. a browser does not understand `import
mitt from 'mitt'`).
* chore(docs): reduce warnings when generating docs
This is a bunch of small miscellaneous fixes that reduce the amount of
warnings logged when generating our new docs. The long term goal is to
get this list down to 0 warnings, but I'll do it in multiple PRs.
* satisfy doclint
* chore(agnostic): ship CJS and ESM builds
For our work to enable Puppeteer in other environments (e.g. a browser)
we need to ship an ESM build. This commit changes our config to ship to
`lib/cjs` and `lib/esm` accordingly. The majority of our code stays the
same, with one small fix for the CJS build to ensure that we ship a
version that lets you `require('puppeteer')` rather than have to
`require('puppeteer').default`. We do this with the `cjs-entry.js` which
is what the `main` field in our `package.json` points to.
We also swap to `read-pkg-up` to find the `package.json` file. This is
because the folder structure of `lib/` does not match `src/` now we ship
to `cjs` and `esm`, so you cannot rely on exact paths. This module works
up from the file to find the nearest `package.json` so it will always
find Puppeteer's `package.json`.
Note that we *do not* point any users to the ESM build. We happen to
ship those files so people who know about them can get at them but it's
not expected (nor will we actively support) that people will rely on
them. The CommonJS build is considered our main build.
We may make breaking changes to the structure of the ESM build which we
will do without requiring new major versions. For example the ESM build
currently ships all files that the CJS build does, but given we are
working on the ESM build being able to run in the browser this may
change over time.
Long term once the Node versions catch up we can ditch CJS and ship
exclusively ESM but we are not there yet.