Grover

repository·main·Indexed 22 days ago

https://github.com/studiosity/grover

A Ruby gem that transforms HTML into PDFs, PNGs, or JPEGs using Google Puppeteer and Chromium. It supports rendering Rails view templates, executing JavaScript during rendering, and providing Rack middleware for automatic file generation via URL extensions. Features include remote Chromium connection via browser_ws_endpoint, vision deficiency emulation, and HTML pre-processing for relative paths.

Tokens
7.9K
Snippets
29
Records
38
Agent score
78%

What's inside grover

  1. Configure Grover via HTML meta tags

    main

    You can provide configuration options directly within the HTML content using <meta> tags.

    Rules for meta tags:

    • Use underscore case for option names.
    • Use a dash to separate sub-options (e.g., grover-margin-top).
    • Meta tag options will overwrite other provided options, including emulate_media and display_url.
    # Example: setting page ranges and top margin via meta tags
    Grover.new('<html><head><meta name="grover-page_ranges" content="1-3"><meta name="grover-margin-top" content="10px"></head></html>')
  2. How to add cover pages to PDFs

    main

    Since Puppeteer's header/footer configuration is global, you cannot easily change them for specific pages like covers. Grover provides two ways to handle cover pages:

    1. Using Middleware (Automatic Combination)

    If using the middleware, you can specify relative paths for cover pages. Grover will render the covers in isolation and then use the combine_pdf gem to merge them with the main content.

    Paths can be set via global configuration or via HTML <meta> tags in the response:

    <meta name="grover-back_cover_path" content="/back/cover/page?bar=baz" />

    Note: Requires the combine_pdf gem.

    2. Direct Execution (Manual Combination)

    If not using middleware, you must manually create multiple Grover instances and combine them using the combine_pdf gem.

    # Manual combination example
    require 'combine_pdf'
    
    def invoke(file_path)
      pdf = CombinePDF.parse(Grover.new(pdf_report_url).to_pdf)
      pdf >> CombinePDF.parse(Grover.new(pdf_front_cover_url).to_pdf)
      pdf << CombinePDF.parse(Grover.new(pdf_back_cover_url).to_pdf)
      pdf.save file_path
    end
  3. Handle relative paths in HTML

    main

    When passing inline HTML to Grover (instead of a URL), Chromium may fail to resolve relative paths (like images or CSS) because it lacks a base context. To fix this, you have two choices:

    1. Specify a display_url: Pass the display_url option to Grover.new so Chromium knows which host to use for resolution.
    2. Pre-process HTML: Use Grover::HTMLPreprocessor.process to convert relative paths to absolute paths before passing the HTML to Grover. This is useful if you want to avoid exposing a specific host via display_url (e.g., when behind a NAT gateway).

    Note: When using the pre-processor, ensure the base URL ends with a trailing slash.

    # Convert relative paths to absolute paths
    absolute_html = Grover::HTMLPreprocessor.process relative_html, 'http://my.server/', 'http'
    
    # Then pass to Grover
    pdf = Grover.new(absolute_html).to_pdf
  4. Connect to a remote Chromium instance

    main

    By default, Grover launches a local Chromium instance. To use a remote/external Chromium (e.g., running in Docker via Browserless), use the browser_ws_endpoint option.

    If you are only using remote Chromium, you can install puppeteer-core instead of the full puppeteer package to save space, as Grover will fallback to puppeteer-core if available.

    # Connect to a remote instance
    grover = Grover.new("https://mysite.com/path/to/thing", browser_ws_endpoint: "ws://localhost:3000/chrome")
    File.open("grover.png", "wb") { |f| f << grover.to_png }

    Install puppeteer-core for remote usage

    npm install puppeteer-core
  5. Install Grover and Puppeteer

    main

    To use Grover, add the gem to your Gemfile and ensure the puppeteer npm package is installed in your environment, as Grover relies on Google Puppeteer and Chromium to perform transformations.

    # In your Gemfile
    gem 'grover'
    # In your terminal
    npm install puppeteer
  6. Set up Grover Middleware

    main

    Grover includes middleware that allows you to generate a PDF, PNG, or JPEG view of any page on your site by appending .pdf, .png, or .jpeg/.jpg to the URL.

    For Non-Rails Rack apps: Add use Grover::Middleware to your config.ru.

    For Rails apps: Add config.middleware.use Grover::Middleware to your application.rb.

    Note on Images: By default, PNG and JPEG middleware are disabled to prevent breaking standard behaviors. If you enable them, you must also configure ignore_path or ignore_request to prevent the middleware from attempting to process static assets, which would result in 404 errors.

    # Non-Rails Rack apps (config.ru)
    require 'grover'
    use Grover::Middleware
    
    # Rails apps (application.rb)
    require 'grover'
    config.middleware.use Grover::Middleware
  7. Debug DevTools protocol traffic

    main

    To inspect the raw communication between Grover and the browser, set the DEBUG environment variable for Node.js to puppeteer:*. This will populate the Grover#debug_output method with the protocol traffic logs.

    Warning: Do not leave this enabled in production, as the captured output may contain sensitive information.

    Grover.configuration.node_env_vars = { 'DEBUG' => "puppeteer:*" }
    
    grover = Grover.new('Hello World')
    grover.to_pdf
    grover.debug_output
    # => ["2026-02-04T14:51:42.783Z puppeteer:browsers:launcher ...", ...]
  8. Enable browser debugging with headless and devtools options

    main

    You can debug HTML conversion issues by enabling browser visibility or devtools. These options can be set globally via Grover.configure, passed to the Grover.new initializer, or provided via meta tags.

    Important Limitations:

    • Setting headless: false is not compatible with PDF export.
    • Enabling devtools: true will cause the browser to halt, which may result in a navigation timeout.
    Grover.configure do |config|
      config.debug = {
        headless: false,  # Default true. When set to false, the Chromium browser will be displayed
        devtools: true    # Default false. When set to true, the browser devtools will be displayed.
      }
    end
  9. Deploy Grover to Heroku

    main

    To run Grover (which uses Puppeteer) on Heroku, follow these steps:

    1. Add Node.js buildpack: Puppeteer requires a Node environment.
      heroku buildpacks:add heroku/nodejs --index=1
    2. Add Puppeteer buildpack: Ensure it runs after Node but before Ruby.
      heroku buildpacks:add jontewks/puppeteer --index=2
    3. Enable No-Sandbox mode: Set the GROVER_NO_SANDBOX environment variable to true.
      heroku config:set GROVER_NO_SANDBOX=true
    4. Configure Puppeteer Cache: If using Puppeteer 19+, create a .puppeteerrc.cjs file in your project root to specify the cache directory:
      const {join} = require('path');
      module.exports = {
        cacheDirectory: join(__dirname, '.cache', 'puppeteer'),
      };
    // .puppeteerrc.cjs
    const {join} = require('path');
    
    /**
    * @type {import("puppeteer").Configuration}
    * */
    module.exports = {
      cacheDirectory: join(__dirname, '.cache', 'puppeteer'),
    };
  10. Enable local file URI and network access in Grover

    main

    By default, Grover restricts access to local files and the local network for security reasons.

    • allow_file_uris: When set to true, allows Grover.new to accept file:/// URIs to render HTML documents from the local file system. Use with extreme caution to avoid exposing sensitive system files.
    • allow_local_network_access: When set to true, allows Puppeteer to make web requests to localhost. This is necessary if your rendered pages need to access local assets or services.
    # config/initializers/grover.rb
    Grover.configure do |config|
      # WARNING: Use with extreme caution
      config.allow_file_uris = true
    
      # Allows Puppeteer to access localhost
      config.allow_local_network_access = true
    end
    
    # Usage with file URI
    grover = Grover.new('file:///some/local/file.html', format: 'A4')
    pdf = grover.to_pdf
  11. Set custom environment variables for Node

    main

    Use the node_env_vars configuration option to set custom environment variables for the spawned Node.js process. This is useful for environment-specific tweaks like disabling jemalloc.

    # config/initializers/grover.rb
    Grover.configure do |config|
      config.node_env_vars = { "LD_PRELOAD" => "" }
    end
  12. Configure Grover via meta tags in HTML

    main

    When providing inline HTML to Grover.new (instead of a URL), you can control Puppeteer behavior using <meta> tags. Grover looks for tags matching the pattern [meta_tag_prefix][option-name].

    By default, the prefix is determined by Grover.configuration.meta_tag_prefix. If a tag is found, the content attribute is used to set the option. Nested options are supported using hyphens in the tag name (e.g., prefix-option-suboption).

    Note: Meta tag extraction is skipped if the input @uri is a URL (starting with http://, https://, or file://).