diff --git a/api-extractor.json b/api-extractor.json index 76afa3db00369..1242d4a5de6fd 100644 --- a/api-extractor.json +++ b/api-extractor.json @@ -1,6 +1,6 @@ { "$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json", - "mainEntryPointFilePath": "/lib/api-docs-entry.d.ts", + "mainEntryPointFilePath": "/lib/cjs/api-docs-entry.d.ts", "bundledPackages": [ ], "apiReport": { diff --git a/cjs-entry.js b/cjs-entry.js new file mode 100644 index 0000000000000..424ffadf1a14e --- /dev/null +++ b/cjs-entry.js @@ -0,0 +1,29 @@ +/** + * Copyright 2020 Google Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * We use `export default puppeteer` in `src/index.ts` to expose the library But + * TypeScript in CJS mode compiles that to `exports.default = `. This means that + * our CJS Node users would have to use `require('puppeteer').default` which + * isn't very nice. + * + * So instead we expose this file as our entry point. This requires the compiled + * Puppeteer output and re-exports the `default` export via `module.exports.` + * This means that we can publish to CJS and ESM whilst maintaining the expected + * import behaviour for CJS and ESM users. + */ +const puppeteerExport = require('./lib/cjs/index'); +module.exports = puppeteerExport.default; diff --git a/package.json b/package.json index 7164a480ee17f..565425af2ee39 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "puppeteer", "version": "4.0.0-post", "description": "A high-level API to control headless Chrome over the DevTools Protocol", - "main": "lib/index.js", + "main": "./cjs-entry.js", "repository": "github:puppeteer/puppeteer", "engines": { "node": ">=10.18.1" @@ -28,7 +28,9 @@ "lint": "npm run eslint && npm run tsc && npm run doc", "doc": "node utils/doclint/cli.js", "clean-lib": "rm -rf lib", - "tsc": "npm run clean-lib && tsc --version && tsc -p . && cp src/protocol.d.ts lib/", + "tsc": "npm run clean-lib && tsc --version && npm run tsc-cjs && npm run tsc-esm", + "tsc-cjs": "tsc -p . && cp src/protocol.d.ts lib/cjs", + "tsc-esm": "tsc --build tsconfig-esm.json && cp src/protocol.d.ts lib/esm", "apply-next-version": "node utils/apply_next_version.js", "update-protocol-d-ts": "node utils/protocol-types-generator update", "compare-protocol-d-ts": "node utils/protocol-types-generator compare", @@ -41,7 +43,8 @@ "lib/", "index.js", "install.js", - "typescript-if-required.js" + "typescript-if-required.js", + "cjs-entry.js" ], "author": "The Chromium Authors", "license": "Apache-2.0", @@ -53,6 +56,7 @@ "mitt": "^2.0.1", "progress": "^2.0.1", "proxy-from-env": "^1.0.0", + "read-pkg-up": "^7.0.1", "rimraf": "^3.0.2", "tar-fs": "^2.0.0", "unbzip2-stream": "^1.3.3", diff --git a/scripts/test-install.sh b/scripts/test-install.sh index 1fdeeeb7369ee..2782d2acff224 100755 --- a/scripts/test-install.sh +++ b/scripts/test-install.sh @@ -4,7 +4,8 @@ set -e # Pack the module into a tarball npm pack tarball="$(realpath puppeteer-*.tgz)" -cd "$(mktemp -d)" +TMPDIR="$(mktemp -d)" +cd $TMPDIR # Check we can install from the tarball. # This emulates installing from npm and ensures that: # 1. we publish the right files in the `files` list from package.json @@ -12,6 +13,7 @@ cd "$(mktemp -d)" # 3. Requiring Puppeteer from Node works. npm install --loglevel silent "${tarball}" node --eval="require('puppeteer')" +ls $TMPDIR/node_modules/puppeteer/.local-chromium/ # Again for Firefox TMPDIR="$(mktemp -d)" diff --git a/src/common/Puppeteer.ts b/src/common/Puppeteer.ts index 5996298cc604e..9cb299a20d76c 100644 --- a/src/common/Puppeteer.ts +++ b/src/common/Puppeteer.ts @@ -23,6 +23,7 @@ import { ProductLauncher } from '../node/Launcher'; import { BrowserFetcher, BrowserFetcherOptions } from '../node/BrowserFetcher'; import { puppeteerErrors, PuppeteerErrors } from './Errors'; import { ConnectionTransport } from './ConnectionTransport'; +import readPkgUp from 'read-pkg-up'; import { devicesMap } from './DeviceDescriptors'; import { DevicesMap } from './DeviceDescriptors'; @@ -172,9 +173,7 @@ export class Puppeteer { this._lazyLauncher.product !== this._productName || this._changedProduct ) { - // @ts-ignore - // eslint-disable-next-line @typescript-eslint/no-var-requires - const packageJson = require('../../package.json'); + const { packageJson } = readPkgUp.sync({ cwd: __dirname }); switch (this._productName) { case 'firefox': this._preferredRevision = packageJson.puppeteer.firefox_revision; diff --git a/src/index.ts b/src/index.ts index 6adf9e0b36bf9..c395a7690ae14 100644 --- a/src/index.ts +++ b/src/index.ts @@ -14,21 +14,20 @@ * limitations under the License. */ -import { initializePuppeteer } from './initialize'; +import { initializePuppeteer, InitOptions } from './initialize'; import * as path from 'path'; +import readPkgUp from 'read-pkg-up'; + +const packageJsonResult = readPkgUp.sync({ + cwd: __dirname, +}); +const packageJson = packageJsonResult.packageJson as unknown; + +const rootDir = path.dirname(packageJsonResult.path); const puppeteer = initializePuppeteer({ - packageJson: require(path.join(__dirname, '..', 'package.json')), - rootDirectory: path.join(__dirname, '..'), + packageJson: packageJson as InitOptions['packageJson'], + rootDirectory: rootDir, }); -/* - * Has to be CJS here rather than ESM such that the output file ends with - * module.exports = puppeteer. - * - * If this was export default puppeteer the output would be: - * exports.default = puppeteer - * And therefore consuming via require('puppeteer') would break / require the user - * to access require('puppeteer').default; - */ -export = puppeteer; +export default puppeteer; diff --git a/src/initialize.ts b/src/initialize.ts index 73f0c624c38b0..4cf16ecf4b798 100644 --- a/src/initialize.ts +++ b/src/initialize.ts @@ -22,7 +22,7 @@ const api = require('./api'); import { helper } from './common/helper'; import { Puppeteer } from './common/Puppeteer'; -interface InitOptions { +export interface InitOptions { packageJson: { puppeteer: { chromium_revision: string; diff --git a/tsconfig-esm.json b/tsconfig-esm.json new file mode 100644 index 0000000000000..06d8e25d98bab --- /dev/null +++ b/tsconfig-esm.json @@ -0,0 +1,7 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "./lib/esm", + "module": "ES2015", + }, +} diff --git a/tsconfig.json b/tsconfig.json index 8b25d93823ed2..5a62ace19cb13 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -2,7 +2,7 @@ "compilerOptions": { "allowJs": true, "checkJs": true, - "outDir": "./lib", + "outDir": "./lib/cjs", "target": "ESNext", "moduleResolution": "node", "module": "CommonJS", diff --git a/utils/doclint/cli.js b/utils/doclint/cli.js index 51387b5a494d9..ef551ac080f84 100755 --- a/utils/doclint/cli.js +++ b/utils/doclint/cli.js @@ -71,8 +71,9 @@ async function run() { const jsSources = [ ...(await Source.readdir(path.join(PROJECT_DIR, 'lib'))), - ...(await Source.readdir(path.join(PROJECT_DIR, 'lib', 'common'))), - ...(await Source.readdir(path.join(PROJECT_DIR, 'lib', 'node'))), + ...(await Source.readdir(path.join(PROJECT_DIR, 'lib', 'cjs'))), + ...(await Source.readdir(path.join(PROJECT_DIR, 'lib', 'cjs', 'common'))), + ...(await Source.readdir(path.join(PROJECT_DIR, 'lib', 'cjs', 'node'))), ]; const allSrcCode = [...jsSources, ...tsSourcesNoDefinitions]; messages.push(...(await checkPublicAPI(page, mdSources, allSrcCode)));