最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - React with Google Chromes Puppeteer - Stack Overflow

programmeradmin2浏览0评论

Trying to render a react ponent with chrome puppeteer running on my Node.js environment I’m having following problem:

  • logging element gives me in the headless chrome console: console.log(element) => <div id="test-wrapper"></div>
  • testWrapper in the terminal console.log(testWrapper) => {}

    puppeteer.launch().then(async browser => {
    
        const page = await browser.newPage();
    
        const testDocumentPath = path.resolve('./lib/ponents/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);
    });
    

So trying to do …

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

… obviously results in an error (node:90555) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): Invariant Violation: _registerComponent(...): Target container is not a DOM element.

I feel like even if I manage to get the DOM element I’m missing something here to inject a react application.

Trying to render a react ponent with chrome puppeteer running on my Node.js environment I’m having following problem:

  • logging element gives me in the headless chrome console: console.log(element) => <div id="test-wrapper"></div>
  • testWrapper in the terminal console.log(testWrapper) => {}

    puppeteer.launch().then(async browser => {
    
        const page = await browser.newPage();
    
        const testDocumentPath = path.resolve('./lib/ponents/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);
    });
    

So trying to do …

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

… obviously results in an error (node:90555) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): Invariant Violation: _registerComponent(...): Target container is not a DOM element.

I feel like even if I manage to get the DOM element I’m missing something here to inject a react application.

Share Improve this question asked Sep 5, 2017 at 15:54 floziaflozia 4,6481 gold badge12 silver badges8 bronze badges 2
  • What's present in the testDocument.html? Does it have all the prerequisites for rendering a react app? If so, you should render it in the evaluate function, not outside of it (where console.log(testWrapper)) is now. – tomahaug Commented Sep 18, 2017 at 14:27
  • maybe making minimalist repository to reproduce the error, will give an ease to examine the problem. – Adi Prasetyo Commented Oct 16, 2017 at 0:31
Add a ment  | 

1 Answer 1

Reset to default 4

.evaluate does not return a dom element. And, you are trying to modify the elements in different context. The page in the browser window and the context you have in your nodeJS is absolutely different.

Here is a different way to deal with React and Puppeteer. First, I have an entry file where I export the function to window.

By doing this, I can access it from the browsers context easily. Instead of window, you can actually export it and try expose-loader and so on. I'll use webpack to build it.

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;

On the webpack config,

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
  }
};

Now whenever I run webpack, it'll create a bundle.js file for me. Now let's have a puppeteer file,

const puppeteer = require('puppeteer');

(async () => {
  const browser = await puppeteer.launch({ headless: false });
  const page = await browser.newPage();
  await page.goto('https://github.');
  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();
})();

As you can see, I'm using the renderIt function that I exposed to window earlier. And when I run it, here is the result,

Sweet! Hello from react :)

Oh! And if it fails to execute script on the page due to CORS issue, you can inject it instead using the old injectFile function, until they fix their addScriptTag function, or remove deprecation from 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
发布评论

评论列表(0)

  1. 暂无评论