feat: support node-like environments (#8490)

This commit is contained in:
jrandolf 2022-06-09 13:03:44 +02:00 committed by GitHub
parent 353358a996
commit f64ec2051b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 118 additions and 102 deletions

View File

@ -14,18 +14,18 @@
* limitations under the License. * limitations under the License.
*/ */
import { ConnectionTransport } from './ConnectionTransport.js'; import { debugError } from '../common/helper.js';
import { isNode } from '../environment.js';
import { assert } from './assert.js';
import { import {
Browser, Browser,
TargetFilterCallback,
IsPageTargetCallback, IsPageTargetCallback,
TargetFilterCallback,
} from './Browser.js'; } from './Browser.js';
import { assert } from './assert.js';
import { debugError } from '../common/helper.js';
import { Connection } from './Connection.js'; import { Connection } from './Connection.js';
import { Viewport } from './PuppeteerViewport.js'; import { ConnectionTransport } from './ConnectionTransport.js';
import { isNode } from '../environment.js';
import { getFetch } from './fetch.js'; import { getFetch } from './fetch.js';
import { Viewport } from './PuppeteerViewport.js';
/** /**
* Generic browser options that can be passed when launching any browser or when * Generic browser options that can be passed when launching any browser or when

View File

@ -14,30 +14,29 @@
* limitations under the License. * limitations under the License.
*/ */
import { Protocol } from 'devtools-protocol';
import { assert } from './assert.js'; import { assert } from './assert.js';
import { helper, debugError } from './helper.js'; import { CDPSession } from './Connection.js';
import { TimeoutError } from './Errors.js';
import {
EvaluateFn,
EvaluateFnReturnType,
EvaluateHandleFn,
SerializableOrJSHandle,
UnwrapPromiseLike,
WrapElementHandle,
} from './EvalTypes.js';
import { ExecutionContext } from './ExecutionContext.js';
import { Frame, FrameManager } from './FrameManager.js';
import { debugError, helper } from './helper.js';
import { MouseButton } from './Input.js';
import { ElementHandle, JSHandle } from './JSHandle.js';
import { import {
LifecycleWatcher, LifecycleWatcher,
PuppeteerLifeCycleEvent, PuppeteerLifeCycleEvent,
} from './LifecycleWatcher.js'; } from './LifecycleWatcher.js';
import { TimeoutError } from './Errors.js';
import { JSHandle, ElementHandle } from './JSHandle.js';
import { ExecutionContext } from './ExecutionContext.js';
import { TimeoutSettings } from './TimeoutSettings.js';
import { MouseButton } from './Input.js';
import { FrameManager, Frame } from './FrameManager.js';
import { getQueryHandlerAndSelector } from './QueryHandler.js'; import { getQueryHandlerAndSelector } from './QueryHandler.js';
import { import { TimeoutSettings } from './TimeoutSettings.js';
SerializableOrJSHandle,
EvaluateHandleFn,
WrapElementHandle,
EvaluateFn,
EvaluateFnReturnType,
UnwrapPromiseLike,
} from './EvalTypes.js';
import { isNode } from '../environment.js';
import { Protocol } from 'devtools-protocol';
import { CDPSession } from './Connection.js';
// predicateQueryHandler and checkWaitForOptions are declared here so that // predicateQueryHandler and checkWaitForOptions are declared here so that
// TypeScript knows about them when used in the predicate function below. // TypeScript knows about them when used in the predicate function below.
@ -330,13 +329,18 @@ export class DOMWorld {
} }
if (path !== null) { if (path !== null) {
if (!isNode) { let fs;
throw new Error( try {
'Cannot pass a filepath to addScriptTag in the browser environment.' fs = (await import('fs')).promises;
); } catch (error) {
if (error instanceof TypeError) {
throw new Error(
'Can only pass a filepath to addScriptTag in a Node-like environment.'
);
}
throw error;
} }
const fs = await import('fs'); let contents = await fs.readFile(path, 'utf8');
let contents = await fs.promises.readFile(path, 'utf8');
contents += '//# sourceURL=' + path.replace(/\n/g, ''); contents += '//# sourceURL=' + path.replace(/\n/g, '');
const context = await this.executionContext(); const context = await this.executionContext();
const handle = await context.evaluateHandle( const handle = await context.evaluateHandle(
@ -437,13 +441,19 @@ export class DOMWorld {
} }
if (path !== null) { if (path !== null) {
if (!isNode) { let fs: typeof import('fs').promises;
throw new Error( try {
'Cannot pass a filepath to addStyleTag in the browser environment.' fs = (await import('fs')).promises;
); } catch (error) {
if (error instanceof TypeError) {
throw new Error(
'Cannot pass a filepath to addStyleTag in the browser environment.'
);
}
throw error;
} }
const fs = await import('fs');
let contents = await fs.promises.readFile(path, 'utf8'); let contents = await fs.readFile(path, 'utf8');
contents += '/*# sourceURL=' + path.replace(/\n/g, '') + '*/'; contents += '/*# sourceURL=' + path.replace(/\n/g, '') + '*/';
const context = await this.executionContext(); const context = await this.executionContext();
const handle = await context.evaluateHandle(addStyleContent, contents); const handle = await context.evaluateHandle(addStyleContent, contents);

View File

@ -14,25 +14,24 @@
* limitations under the License. * limitations under the License.
*/ */
import { assert } from './assert.js';
import { helper, debugError } from './helper.js';
import { ExecutionContext } from './ExecutionContext.js';
import { Page, ScreenshotOptions } from './Page.js';
import { CDPSession } from './Connection.js';
import { KeyInput } from './USKeyboardLayout.js';
import { FrameManager, Frame } from './FrameManager.js';
import { getQueryHandlerAndSelector } from './QueryHandler.js';
import { Protocol } from 'devtools-protocol'; import { Protocol } from 'devtools-protocol';
import { assert } from './assert.js';
import { CDPSession } from './Connection.js';
import { import {
EvaluateFn, EvaluateFn,
SerializableOrJSHandle,
EvaluateFnReturnType, EvaluateFnReturnType,
EvaluateHandleFn, EvaluateHandleFn,
WrapElementHandle, SerializableOrJSHandle,
UnwrapPromiseLike, UnwrapPromiseLike,
WrapElementHandle,
} from './EvalTypes.js'; } from './EvalTypes.js';
import { isNode } from '../environment.js'; import { ExecutionContext } from './ExecutionContext.js';
import { Frame, FrameManager } from './FrameManager.js';
import { debugError, helper } from './helper.js';
import { MouseButton } from './Input.js'; import { MouseButton } from './Input.js';
import { Page, ScreenshotOptions } from './Page.js';
import { getQueryHandlerAndSelector } from './QueryHandler.js';
import { KeyInput } from './USKeyboardLayout.js';
/** /**
* @public * @public
@ -822,17 +821,18 @@ export class ElementHandle<
'Multiple file uploads only work with <input type=file multiple>' 'Multiple file uploads only work with <input type=file multiple>'
); );
if (!isNode) {
throw new Error(
`JSHandle#uploadFile can only be used in Node environments.`
);
}
/*
This import is only needed for `uploadFile`, so keep it scoped here to
avoid paying the cost unnecessarily.
*/
const path = await import('path');
// Locate all files and confirm that they exist. // Locate all files and confirm that they exist.
let path: typeof import('path');
try {
path = await import('path');
} catch (error) {
if (error instanceof TypeError) {
throw new Error(
`JSHandle#uploadFile can only be used in Node-like environments.`
);
}
throw error;
}
const files = filePaths.map((filePath) => { const files = filePaths.map((filePath) => {
if (path.isAbsolute(filePath)) { if (path.isAbsolute(filePath)) {
return filePath; return filePath;

View File

@ -16,7 +16,6 @@
import { Protocol } from 'devtools-protocol'; import { Protocol } from 'devtools-protocol';
import type { Readable } from 'stream'; import type { Readable } from 'stream';
import { isNode } from '../environment.js';
import { Accessibility } from './Accessibility.js'; import { Accessibility } from './Accessibility.js';
import { assert, assertNever } from './assert.js'; import { assert, assertNever } from './assert.js';
import { Browser, BrowserContext } from './Browser.js'; import { Browser, BrowserContext } from './Browser.js';
@ -2825,13 +2824,17 @@ export class Page extends EventEmitter {
: Buffer.from(result.data, 'base64'); : Buffer.from(result.data, 'base64');
if (options.path) { if (options.path) {
if (!isNode) { try {
throw new Error( const fs = (await import('fs')).promises;
'Screenshots can only be written to a file path in a Node environment.' await fs.writeFile(options.path, buffer);
); } catch (error) {
if (error instanceof TypeError) {
throw new Error(
'Screenshots can only be written to a file path in a Node-like environment.'
);
}
throw error;
} }
const fs = (await import('fs')).promises;
await fs.writeFile(options.path, buffer);
} }
return buffer; return buffer;

View File

@ -14,9 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
import { isNode } from '../environment.js';
/* Use the global version if we're in the browser, else load the node-fetch module. */ /* Use the global version if we're in the browser, else load the node-fetch module. */
export const getFetch = async (): Promise<typeof fetch> => { export const getFetch = async (): Promise<typeof fetch> => {
return isNode ? (await import('cross-fetch')).fetch : globalThis.fetch; return globalThis.fetch || (await import('cross-fetch')).fetch;
}; };

View File

@ -14,15 +14,14 @@
* limitations under the License. * limitations under the License.
*/ */
import type { Readable } from 'stream';
import { TimeoutError } from './Errors.js';
import { debug } from './Debug.js';
import { CDPSession } from './Connection.js';
import { Protocol } from 'devtools-protocol'; import { Protocol } from 'devtools-protocol';
import { CommonEventEmitter } from './EventEmitter.js'; import type { Readable } from 'stream';
import { assert } from './assert.js';
import { isNode } from '../environment.js'; import { isNode } from '../environment.js';
import { assert } from './assert.js';
import { CDPSession } from './Connection.js';
import { debug } from './Debug.js';
import { TimeoutError } from './Errors.js';
import { CommonEventEmitter } from './EventEmitter.js';
export const debugError = debug('puppeteer:error'); export const debugError = debug('puppeteer:error');
@ -318,31 +317,34 @@ async function getReadableAsBuffer(
readable: Readable, readable: Readable,
path?: string path?: string
): Promise<Buffer | null> { ): Promise<Buffer | null> {
if (!isNode && path) {
throw new Error('Cannot write to a path outside of Node.js environment.');
}
const fs = isNode ? (await import('fs')).promises : null;
let fileHandle: import('fs').promises.FileHandle | undefined;
if (path && fs) {
fileHandle = await fs.open(path, 'w');
}
const buffers = []; const buffers = [];
for await (const chunk of readable) { if (path) {
buffers.push(chunk); let fs: typeof import('fs').promises;
if (fileHandle && fs) { try {
await fs.writeFile(fileHandle, chunk); fs = (await import('fs')).promises;
} catch (error) {
if (error instanceof TypeError) {
throw new Error(
'Cannot write to a path outside of a Node-like environment.'
);
}
throw error;
}
const fileHandle = await fs.open(path, 'w+');
for await (const chunk of readable) {
buffers.push(chunk);
await fileHandle.writeFile(chunk);
}
await fileHandle.close();
} else {
for await (const chunk of readable) {
buffers.push(chunk);
} }
} }
if (path && fileHandle) await fileHandle.close();
let resultBuffer = null;
try { try {
resultBuffer = Buffer.concat(buffers); return Buffer.concat(buffers);
} finally { } catch (error) {
return resultBuffer; return null;
} }
} }
@ -350,8 +352,8 @@ async function getReadableFromProtocolStream(
client: CDPSession, client: CDPSession,
handle: string handle: string
): Promise<Readable> { ): Promise<Readable> {
// TODO: // TODO: Once Node 18 becomes the lowest supported version, we can migrate to
// This restriction can be lifted once https://github.com/nodejs/node/pull/39062 has landed // ReadableStream.
if (!isNode) { if (!isNode) {
throw new Error('Cannot create a stream outside of Node.js environment.'); throw new Error('Cannot create a stream outside of Node.js environment.');
} }

View File

@ -14,8 +14,8 @@
* limitations under the License. * limitations under the License.
*/ */
import { initializePuppeteerNode } from './initialize-node.js';
import { isNode } from './environment.js'; import { isNode } from './environment.js';
import { initializePuppeteerNode } from './initialize-node.js';
if (!isNode) { if (!isNode) {
throw new Error('Cannot run puppeteer-core outside of Node.js'); throw new Error('Cannot run puppeteer-core outside of Node.js');

View File

@ -14,10 +14,12 @@
* limitations under the License. * limitations under the License.
*/ */
import { initializePuppeteerNode } from './initialize-node.js';
import { isNode } from './environment.js'; import { isNode } from './environment.js';
import { initializePuppeteerNode } from './initialize-node.js';
if (!isNode) { if (!isNode) {
throw new Error('Trying to run Puppeteer-Node in a web environment.'); throw new Error('Trying to run Puppeteer-Node in a web environment.');
} }
export default initializePuppeteerNode('puppeteer');
const puppeteer = initializePuppeteerNode('puppeteer');
export default puppeteer;

View File

@ -14,11 +14,12 @@
* limitations under the License. * limitations under the License.
*/ */
import { initializePuppeteerWeb } from './initialize-web.js';
import { isNode } from './environment.js'; import { isNode } from './environment.js';
import { initializePuppeteerWeb } from './initialize-web.js';
if (isNode) { if (isNode) {
throw new Error('Trying to run Puppeteer-Web in a Node environment'); throw new Error('Trying to run Puppeteer-Web in a Node environment');
} }
export default initializePuppeteerWeb('puppeteer'); const puppeteer = initializePuppeteerWeb('puppeteer');
export default puppeteer;