Is it possible to pass a React Component to puppeteer?

后端 未结 1 1086
抹茶落季
抹茶落季 2021-01-06 01:38

I have a React component with some componentDidMount logic:

export default class MyComponent {
    componentDidMount() {
        // some changes to DOM done          


        
相关标签:
1条回答
  • 2021-01-06 01:50

    I answered this question here. Let's try the same here.

    Install babel, webpack and puppeteer packages.

    {
      "name": "react-puppeteer",
      "version": "1.0.0",
      "main": "index.js",
      "license": "MIT",
      "scripts": {
        "compile": "webpack",
        "build": "webpack -p",
        "start": "webpack && node pup.js"
      },
      "devDependencies": {
        "babel-core": "^6.26.0",
        "babel-loader": "^7.1.2",
        "babel-preset-env": "^1.6.1",
        "babel-preset-es2015": "^6.24.1",
        "babel-preset-react": "^6.24.1",
        "react": "^16.2.0",
        "react-dom": "^16.2.0",
        "webpack": "^3.10.0",
        "webpack-dev-middleware": "^2.0.3"
      },
      "dependencies": {
        "puppeteer": "^0.13.0"
      }
    }
    

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

    Create entry file, On this file, instead of mounting the element directly, export it to window so that we can access it later.

    import React from 'react';
    import { render } from 'react-dom';
    
    class Hello extends React.Component {
      render() {
        return <h1>Hello, {this.props.name}</h1>;
      }
    }
    
    // accept a name for example and a domNode where to render
    function renderIt(name, domNode) {
      render(<Hello name={name} />, domNode);
    }
    
    window.renderIt = renderIt;
    

    When we run webpack, it's going to produce a bundle.js file. We can use it on puppeteer.

    They have deprecated the injectFile function on puppeteer, so we are going to resurrect it. Here is a sample repo for that, you can yarn add it.

    https://github.com/entrptaher/puppeteer-inject-file
    

    Now, lets create a puppeteer script.

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

    And when we run this, we get following result,

    If we added a componentDidMount() call, we could have done that too. But if we are trying to do more modification, we have to make the puppeteer script wait for that which have been discussed many times in other questions.

    Say we have a state now which will return something once component is loaded.

    class Hello extends React.Component {
      state = {
        jokes: null
      };
    
      componentDidMount() {
        const self = this;
        const jokesUrl = `http://api.icndb.com/jokes/random?firstName=John&amp;lastName=Doe`;
        fetch(jokesUrl)
          .then(data => data.json())
          .then(data => {
            self.setState({
              jokes: data.value.joke
            });
          });
      }
    
      render() {
        if(!!this.state.jokes){
          return <p id='quote'>{this.state.jokes}</p>
        }
        return <h1>Hello, {this.props.name}</h1>;
      }
    }
    

    On puppeteer, I can wait for the element like this,

      ...
      await injectFile(page, require.resolve('./bundle.js'));
      await page.evaluate(() => {
        renderIt("Someone", document.querySelector('div'));
      });
      await page.waitFor('p#quote');
      ...
    

    We might need babel-preset-stage-2 but I'll leave that to you. And here is the result,

    Figure the rest of the problem yourself :) ...

    0 讨论(0)
提交回复
热议问题