与 Google Chromes Puppeteer react

IT技术 javascript node.js reactjs google-chrome-headless puppeteer
2021-05-22 19:22:31

尝试 使用在我的 Node.js 环境中运行的chrome puppeteer渲染一个 react 组件我遇到了以下问题:

  • 日志记录element让我进入无头 chrome控制台:console.log(element)=><div id="test-wrapper"></div>
  • testWrapper在终端console.log(testWrapper)=>{}

    puppeteer.launch().then(async browser => {
    
        const page = await browser.newPage();
    
        const testDocumentPath = path.resolve('./lib/components/util/testDocument.html');
        await page.goto(`file://${testDocumentPath}`);
    
        const testWrapper = await page.evaluate((selector) => {
            const element = document.querySelector(selector);
            console.log(element);
    
            return element;
        }, '#test-wrapper');
    
        console.log(testWrapper);
    });
    

所以试图做...

ReactDOM.render(
    <div>{':)'}</div>,
    testWrapper
);

……显然会导致错误 (node:90555) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): Invariant Violation: _registerComponent(...): Target container is not a DOM element.

我觉得即使我设法获得 DOM 元素,我也缺少一些东西来注入react应用程序。

1个回答

.evaluate不返回 dom 元素。而且,您正在尝试修改不同上下文中的元素。浏览器窗口中的页面和您在 nodeJS 中的上下文完全不同。

这是处理 React 和 Puppeteer 的不同方式。首先,我有一个入口文件,我将函数导出到窗口。

通过这样做,我可以轻松地从浏览器上下文访问它。您可以实际导出它并尝试公开加载器等,而不是窗口。我将使用 webpack 来构建它。

import React from 'react';
import { render } from 'react-dom';

function Hello() {
  return <h1>Hello from React</h1>;
}

function renderIt(domNode) {
  render(<Hello />, domNode);
}

window.renderIt = renderIt;

在 webpack 配置中,

const webpack = require('webpack');

const loaders = [
  {
    test: /\.jsx?$/,
    exclude: /node_modules/,
    loader: 'babel-loader',
    query: {
      presets: ['babel-preset-es2015', 'babel-preset-react'],
      plugins: []
    }
  }
];

module.exports = {
  entry: './entry.js',
  output: {
    path: __dirname,
    filename: 'bundle.js',
    libraryTarget: 'umd'
  },
  module: {
    loaders: loaders
  }
};

现在每当我运行 webpack 时,它都会为我创建一个 bundle.js 文件。现在让我们有一个 puppeteer 文件,

const puppeteer = require('puppeteer');

(async () => {
  const browser = await puppeteer.launch({ headless: false });
  const page = await browser.newPage();
  await page.goto('https://github.com');
  await page.addScriptTag({ path: require.resolve('./bundle.js') });
  await page.evaluate(() => {
    renderIt(document.querySelector('div.jumbotron.jumbotron-codelines > div > div > div > h1'));
  });
  await page.screenshot({ path: 'example.png' });
  await browser.close();
})();

如您所见,我正在使用之前向 window 公开的 renderIt 函数。当我运行它时,这是结果,

在此处输入图片说明

甜的!你好来自react:)

哦!如果由于 CORS 问题而无法在页面上执行脚本,您可以使用旧的 injectFile 函数来注入它,直到他们修复其 addScriptTag 函数,或从 injectFile 中删除弃用。

/**
 * injects file to puppeteer page context
 * @param  {Object} page     context where to execute the script
 * @param  {String} filePath path of specific script
 * @return {Promise}         Injects content to page context
 */
const fs = require('fs');

async function injectFile(page, filePath) {
  let contents = await new Promise((resolve, reject) => {
    fs.readFile(filePath, 'utf8', (err, data) => {
      if (err) return reject(err);
      resolve(data);
    });
  });
  contents += `//# sourceURL=` + filePath.replace(/\n/g, '');
  return page.mainFrame().evaluate(contents);
}

// usage: await injectFile(page, require.resolve('FILE PATH'));
// export it if you want to keep things seperate