feat(puppeteer): export esm modules in package.json (#7964)

* feat(puppeteer): export esm modules in package.json

Signed-off-by: Randolf Jung <jrandolf@chromium.org>

Co-authored-by: Alex Rudenko <OrKoN@users.noreply.github.com>
Co-authored-by: Randolf Jung <jrandolf@chromium.org>
This commit is contained in:
jrandolf 2022-02-09 08:47:27 +01:00 committed by GitHub
parent a858cf7021
commit 523b487e88
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 108 additions and 14 deletions

2
.gitignore vendored
View File

@ -19,7 +19,7 @@ yarn.lock
test/coverage.json test/coverage.json
temp/ temp/
new-docs/ new-docs/
puppeteer.tgz puppeteer*.tgz
docs-api-json/ docs-api-json/
docs-dist/ docs-dist/
website/docs website/docs

16
compat/README.md Normal file
View File

@ -0,0 +1,16 @@
# Compatibility layer
This directory provides an additional compatibility layer between ES modules and CommonJS.
## Why?
Both `./cjs/compat.ts` and `./esm/compat.ts` are written as ES modules, but `./cjs/compat.ts` can additionally use NodeJS CommonJS globals such as `__dirname` and `require` while these are disabled in ES module mode. For more information, see [Differences between ES modules and CommonJS](https://nodejs.org/api/esm.html#differences-between-es-modules-and-commonjs).
## Adding exports
In order to add exports, two things need to be done:
- The exports must be declared in `src/compat.ts`.
- The exports must be realized in `./cjs/compat.ts` and `./esm/compat.ts`.
In the event `compat.ts` becomes too large, you can place declarations in another file. Just make sure `./cjs`, `./esm`, and `src` have the same structure.

3
compat/cjs/compat.ts Normal file
View File

@ -0,0 +1,3 @@
import { dirname } from 'path';
export const puppeteerDirname = dirname(dirname(dirname(__dirname)));

11
compat/cjs/tsconfig.json Normal file
View File

@ -0,0 +1,11 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"composite": true,
"outDir": "../../lib/cjs/puppeteer",
"module": "CommonJS"
},
"references": [
{ "path": "../../vendor/tsconfig.cjs.json"}
]
}

6
compat/esm/compat.ts Normal file
View File

@ -0,0 +1,6 @@
import { dirname } from 'path';
import { fileURLToPath } from 'url';
export const puppeteerDirname = dirname(
dirname(dirname(dirname(fileURLToPath(import.meta.url))))
);

9
compat/esm/tsconfig.json Normal file
View File

@ -0,0 +1,9 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"composite": true,
"outDir": "../../lib/esm/puppeteer",
"module": "esnext"
},
"references": [{ "path": "../../vendor/tsconfig.esm.json" }]
}

View File

@ -2,7 +2,12 @@
"name": "puppeteer", "name": "puppeteer",
"version": "13.2.0-post", "version": "13.2.0-post",
"description": "A high-level API to control headless Chrome over the DevTools Protocol", "description": "A high-level API to control headless Chrome over the DevTools Protocol",
"type": "commonjs",
"main": "./cjs-entry.js", "main": "./cjs-entry.js",
"exports": {
"import": "./lib/esm/puppeteer/node.js",
"require": "./cjs-entry.js"
},
"types": "lib/types.d.ts", "types": "lib/types.d.ts",
"repository": "github:puppeteer/puppeteer", "repository": "github:puppeteer/puppeteer",
"engines": { "engines": {
@ -29,15 +34,18 @@
"lint": "npm run eslint && npm run build && npm run doc && npm run markdownlint", "lint": "npm run eslint && npm run build && npm run doc && npm run markdownlint",
"doc": "node utils/doclint/cli.js", "doc": "node utils/doclint/cli.js",
"clean-lib": "rimraf lib", "clean-lib": "rimraf lib",
"build": "npm run tsc && npm run generate-d-ts", "build": "npm run tsc && npm run generate-d-ts && npm run generate-pkg-json",
"tsc": "npm run clean-lib && tsc --version && npm run tsc-cjs && npm run tsc-esm", "tsc": "npm run clean-lib && tsc --version && (npm run tsc-cjs & npm run tsc-esm) && (npm run tsc-compat-cjs & npm run tsc-compat-esm)",
"tsc-cjs": "tsc -b src/tsconfig.cjs.json", "tsc-cjs": "tsc -b src/tsconfig.cjs.json",
"tsc-esm": "tsc -b src/tsconfig.esm.json", "tsc-esm": "tsc -b src/tsconfig.esm.json",
"tsc-compat-cjs": "tsc -b compat/cjs/tsconfig.json",
"tsc-compat-esm": "tsc -b compat/esm/tsconfig.json",
"apply-next-version": "node utils/apply_next_version.js", "apply-next-version": "node utils/apply_next_version.js",
"test-install": "scripts/test-install.sh", "test-install": "scripts/test-install.sh",
"clean-docs": "rimraf website/docs && rimraf docs-api-json", "clean-docs": "rimraf website/docs && rimraf docs-api-json",
"generate-d-ts": "npm run clean-docs && api-extractor run --local --verbose", "generate-d-ts": "npm run clean-docs && api-extractor run --local --verbose",
"generate-docs": "npm run generate-d-ts && api-documenter markdown -i docs-api-json -o website/docs && node utils/remove-tag.js", "generate-docs": "npm run generate-d-ts && api-documenter markdown -i docs-api-json -o website/docs && node utils/remove-tag.js",
"generate-pkg-json": "echo '{\"type\": \"module\"}' > lib/esm/package.json",
"ensure-correct-devtools-protocol-revision": "ts-node -s scripts/ensure-correct-devtools-protocol-package", "ensure-correct-devtools-protocol-revision": "ts-node -s scripts/ensure-correct-devtools-protocol-package",
"ensure-pinned-deps": "ts-node -s scripts/ensure-pinned-deps", "ensure-pinned-deps": "ts-node -s scripts/ensure-pinned-deps",
"test-types-file": "ts-node -s scripts/test-ts-definition-files.ts", "test-types-file": "ts-node -s scripts/test-ts-definition-files.ts",
@ -50,6 +58,7 @@
"lib/**/*.d.ts.map", "lib/**/*.d.ts.map",
"lib/**/*.js", "lib/**/*.js",
"lib/**/*.js.map", "lib/**/*.js.map",
"lib/**/package.json",
"install.js", "install.js",
"typescript-if-required.js", "typescript-if-required.js",
"cjs-entry.js", "cjs-entry.js",

View File

@ -16,6 +16,26 @@ npm install --loglevel silent "${tarball}"
node --eval="require('puppeteer')" node --eval="require('puppeteer')"
ls $TMPDIR/node_modules/puppeteer/.local-chromium/ ls $TMPDIR/node_modules/puppeteer/.local-chromium/
# Testing ES module features
TMPDIR="$(mktemp -d)"
cd $TMPDIR
echo '{"type":"module"}' >>$TMPDIR/package.json
npm install --loglevel silent "${tarball}"
node --input-type="module" --eval="import puppeteer from 'puppeteer'"
ls $TMPDIR/node_modules/puppeteer/.local-chromium/
node --input-type="module" --eval="
import puppeteer from 'puppeteer';
(async () => {
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.goto('http://example.com');
await page.screenshot({ path: 'example.png' });
await browser.close();
})();
"
# Again for Firefox # Again for Firefox
TMPDIR="$(mktemp -d)" TMPDIR="$(mktemp -d)"
cd $TMPDIR cd $TMPDIR
@ -39,3 +59,9 @@ cd $TMPDIR
npm install --loglevel silent "${tarball}" npm install --loglevel silent "${tarball}"
node --eval="require('puppeteer-core')" node --eval="require('puppeteer-core')"
# Testing ES module features
TMPDIR="$(mktemp -d)"
cd $TMPDIR
echo '{"type":"module"}' >>$TMPDIR/package.json
npm install --loglevel silent "${tarball}"
node --input-type="module" --eval="import puppeteer from 'puppeteer-core'"

View File

@ -55,7 +55,9 @@ import { isNode } from '../environment.js';
export const debug = (prefix: string): ((...args: unknown[]) => void) => { export const debug = (prefix: string): ((...args: unknown[]) => void) => {
if (isNode) { if (isNode) {
// eslint-disable-next-line @typescript-eslint/no-var-requires // eslint-disable-next-line @typescript-eslint/no-var-requires
return require('debug')(prefix); return async (...logArgs: unknown[]) => {
(await import('debug')).default(prefix)(logArgs);
};
} }
return (...logArgs: unknown[]): void => { return (...logArgs: unknown[]): void => {

3
src/compat.ts Normal file
View File

@ -0,0 +1,3 @@
declare const puppeteerDirname: string;
export { puppeteerDirname };

View File

@ -18,9 +18,10 @@ import { PuppeteerNode } from './node/Puppeteer.js';
import { PUPPETEER_REVISIONS } from './revisions.js'; import { PUPPETEER_REVISIONS } from './revisions.js';
import pkgDir from 'pkg-dir'; import pkgDir from 'pkg-dir';
import { Product } from './common/Product.js'; import { Product } from './common/Product.js';
import { puppeteerDirname } from './compat.js';
export const initializePuppeteerNode = (packageName: string): PuppeteerNode => { export const initializePuppeteerNode = (packageName: string): PuppeteerNode => {
const puppeteerRootDirectory = pkgDir.sync(__dirname); const puppeteerRootDirectory = pkgDir.sync(puppeteerDirname);
let preferredRevision = PUPPETEER_REVISIONS.chromium; let preferredRevision = PUPPETEER_REVISIONS.chromium;
const isPuppeteerCore = packageName === 'puppeteer-core'; const isPuppeteerCore = packageName === 'puppeteer-core';

View File

@ -34,6 +34,8 @@ import createHttpsProxyAgent, {
} from 'https-proxy-agent'; } from 'https-proxy-agent';
import { getProxyForUrl } from 'proxy-from-env'; import { getProxyForUrl } from 'proxy-from-env';
import { assert } from '../common/assert.js'; import { assert } from '../common/assert.js';
import tar from 'tar-fs';
import bzip from 'unbzip2-stream';
const debugFetcher = debug('puppeteer:fetcher'); const debugFetcher = debug('puppeteer:fetcher');
@ -499,10 +501,6 @@ function install(archivePath: string, folderPath: string): Promise<unknown> {
* @internal * @internal
*/ */
function extractTar(tarPath: string, folderPath: string): Promise<unknown> { function extractTar(tarPath: string, folderPath: string): Promise<unknown> {
// eslint-disable-next-line @typescript-eslint/no-var-requires
const tar = require('tar-fs');
// eslint-disable-next-line @typescript-eslint/no-var-requires
const bzip = require('unbzip2-stream');
return new Promise((fulfill, reject) => { return new Promise((fulfill, reject) => {
const tarStream = tar.extract(folderPath); const tarStream = tar.extract(folderPath);
tarStream.on('error', reject); tarStream.on('error', reject);

View File

@ -15,18 +15,26 @@
*/ */
import { ConnectionTransport } from '../common/ConnectionTransport.js'; import { ConnectionTransport } from '../common/ConnectionTransport.js';
import NodeWebSocket from 'ws'; import NodeWebSocket from 'ws';
import { readFileSync } from 'fs';
import { join } from 'path';
import { puppeteerDirname } from '../compat.js';
// We parse rather than import for ES module compatibility.
const version = JSON.parse(
readFileSync(join(puppeteerDirname, 'package.json'), {
encoding: 'utf8',
})
).version;
export class NodeWebSocketTransport implements ConnectionTransport { export class NodeWebSocketTransport implements ConnectionTransport {
static create(url: string): Promise<NodeWebSocketTransport> { static create(url: string): Promise<NodeWebSocketTransport> {
// eslint-disable-next-line @typescript-eslint/no-var-requires
const pkg = require('../../../../package.json');
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const ws = new NodeWebSocket(url, [], { const ws = new NodeWebSocket(url, [], {
followRedirects: true, followRedirects: true,
perMessageDeflate: false, perMessageDeflate: false,
maxPayload: 256 * 1024 * 1024, // 256Mb maxPayload: 256 * 1024 * 1024, // 256Mb
headers: { headers: {
'User-Agent': `Puppeteer ${pkg.version}`, 'User-Agent': `Puppeteer ${version}`,
}, },
}); });

View File

@ -12,5 +12,5 @@
*/ */
"module": "esnext" "module": "esnext"
}, },
"include": ["src"] "include": ["src"],
} }

View File

@ -21,7 +21,9 @@ const path = require('path');
const packagePath = path.join(__dirname, '..', 'package.json'); const packagePath = path.join(__dirname, '..', 'package.json');
const json = require(packagePath); const json = require(packagePath);
json.name = 'puppeteer-core';
delete json.scripts.install; delete json.scripts.install;
json.name = 'puppeteer-core';
json.main = './cjs-entry-core.js'; json.main = './cjs-entry-core.js';
json.exports.import = './lib/esm/puppeteer/node-puppeteer-core.js';
fs.writeFileSync(packagePath, JSON.stringify(json, null, ' ')); fs.writeFileSync(packagePath, JSON.stringify(json, null, ' '));