mileszs / wicked_pdf

PDF generator (from HTML) plugin for Ruby on Rails
http://www.mileszs.com/wicked-pdf-plugin
MIT License
3.54k stars 646 forks source link

Webpack helpers does not work #860

Open EdouardPan opened 5 years ago

EdouardPan commented 5 years ago

Issue description

In wicked_pdf doc, it is said that I can use wicked_pdf_stylesheet_pack_tag and wicked_pdf_javascript_pack_tag to include my stylesheets and javascript from webpack but nothing works.

In a rails project with webpack, here is the code from the controller:

      format.pdf do
        render template: "pdf_reports/show", 
        layout: "wicked_layout",
        pdf: "report"
      end

Here is the code from the layout:

<!DOCTYPE html>
    <html>
        <head>
           <%= csrf_meta_tags %>
           <%= wicked_pdf_javascript_pack_tag 'application', 'data-turbolinks-track': 'reload' %>
           <%= wicked_pdf_stylesheet_pack_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %>
           <%= wicked_pdf_stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %>
      </head>
      <body>
        <%= yield %>
      </body>
</html>

Here is the code from the view pdf.erb:

<h1 class="text-red-base">Test pdf</h1>
<h2 class="test-wicked">mldgmdjgfd</h2>

Expected or desired behavior

It works with wicked_pdf_stylesheet_link_tag (test-wicked is applied from sprockets: text is blue) but not with wicked_pdf_stylesheet_pack_tag (h1 should be red but is not).

PS: I posted originally the question on stackoverflow but I think it makes more sens to address an issue.

System specifications

wicked_pdf gem version (output of cat Gemfile.lock | grep wicked_pdf): 1.4.0

wkhtmltopdf version (output of wkhtmltopdf --version): 0.12.4

whtmltopdf provider gem and version if one is used: wkhtmltopdf-binary (0.12.4)

platform/distribution and version (e.g. Windows 10 / Ubuntu 16.04 / Heroku cedar): Ubuntu 18.04

Thank you in advance!

unixmonkey commented 4 years ago

Cross-referencing your StackOverflow post here: https://stackoverflow.com/questions/58490299/how-to-include-css-stylesheet-into-wicked-pdf

There's no answer there. Did you ever figure this out or find a workaround?

EdouardPan commented 4 years ago

No, I did not find a workaround. If anyone has a clue, he is welcomed!

artm commented 4 years ago

https://stackoverflow.com/a/60541688/538534

Analysis

The webpack helpers make several assumptions that might not hold in every project.

They produce two different results depending on what running_in_develpment? returns. With webpacker 3.0.0 or newer this method delegates to Webpacker.dev_server.running?.

Without the dev server running the helpers will assume the assets were precompiled and will attempt to paste the contents of the asset into a <style> or <script> tag. This should work if assets are precompiled in production and are available in the filesystem of the environment where the application is running. This is most often true.

With the dev server running the webpack helpers will return a tag with an asset path to the pack asset (eventually using standard asset_path helper). The actual path depends on the rails config. In some cases the path will be incompatible with the html being rendered by wkhtmltopdf from a file://...:

Additional constraints

In our case we had some additional constraints:

Sample solution

Our solution has been to implement our own helpers that are more aware of our set up.

  1. In production the default behavior is compatible with our setup, so we delegate to the original, which will find the assets in the filesystem and include them in the generated HTML.

  2. In development, when generating the PDF, we pass the hostname/port of the webpack dev server to webpacker tag helpers (which will pass them down to asset_path). Dev server runs in a separate process and hence will repond even while the application is inside the request handler.

  3. In development, when returning intermediate HTML (as determined by our custom helper show_as_html?), don't pass host: to asset_pack_url, which will result in "normal" asset path like in the rest of the application, which will work in the browser.

module PdfHelper
  def pdf_stylesheet_pack_tag(source)
    if running_in_development?
      options = { media: "all" }
      wds = Webpacker.dev_server
      options[:host] = "#{wds.host}:#{wds.port}" unless show_as_html?
      stylesheet_pack_tag(source, options)
    else
      wicked_pdf_stylesheet_pack_tag(source)
    end
  end

  def pdf_javascript_pack_tag(source)
    if running_in_development?
      options = {}
      wds = Webpacker.dev_server
      options[:host] = "#{wds.host}:#{wds.port}" unless show_as_html?
      javascript_pack_tag(source, options)
    else
      wicked_pdf_javascript_pack_tag(source)
    end
  end
end

In the pdf layout (slim)

html
  head
    ...
    = pdf_stylesheet_pack_tag "pdf"
    = pdf_javascript_pack_tag "pdf"
    ...
basiszwo commented 4 years ago

I already commented in the stackoverflow thread but for completeness I'll restate my comment here:

The solution of @artm is working perfectly for me except for the fact that the method show_as_html? has to be defined.

I did this by just defining it in the PdfHelper itself as

def show_as_html?
  params[:debug].present?
end

This may not be the best solution but it basically works for the moment.

Another thing I came about is that using the custom helpers won't work when creating the pdf outside of the ActionController context. E.g. when doing

pdf_html = ActionController::Base.new.render_to_string(tamplate: "my_pdf_template.pdf", layout: "pdf")
pdf = WickedPdf.new.pdf_from_string(pdf_html)

I am running in undefined method pdf_stylesheet_pack_tag Errors. I went down the rabbit hole but it looks very nasty and I'd like to achieve a solution in the wicked_pdf upstream.

Any suggestions?

unixmonkey commented 4 years ago

The instantiation of a ActionController::Base.new doesn't have view helpers included by default. That was a hacky way to reach into Rails internals for Rails versions less than 5, that should no longer be necessary today.

If you really wanted to make what you have work, you'd have to do something more like this:

renderer = ActionController::Base.new
renderer.extend(ApplicationHelper) # add helper methods
renderer.extend(Rails.application.routes.url_helpers) # add route helpers for link_to and such
pdf_html = renderer.render_to_string(tamplate: "my_pdf_template.pdf", layout: "pdf")

Rails now has a version of render that can be called like this from a model, or anywhere:

pdf_html = ApplicationController.render(
  template: 'my_pdf_template',
  format: :pdf,
  layout: 'pdf',
  assigns: { users: @users }
)

Please let me know if that helps or not!

basiszwo commented 4 years ago

This helped a lot. Thank you!

mrjonesbot commented 3 years ago

Hi @basiszwo,

Thank you for the detailed explanation of the problem + solution.

A little over a year and a half later, I'm finding this solution is not completely resolving the css loading issue.

My pdf loads, but without any styles; any help would be greatly appreciated.

Here's my setup:

webacker 6.0.0.beta.7 rails 6.1.4 webpack 5.11.0

// app/javascript/packs/pdf.js

import "stylesheets/pdf.scss"
/* app/javascript/stylesheets/pdf.scss */

@import "tailwindcss/base"
<!-- app/views/layouts/pdf.html.erb -->

<!DOCTYPE html>
<html>
  <head>
    <meta content="text/html; charset=UTF-8" http-equiv="Content-Type"/>
    <%= pdf_stylesheet_pack_tag "pdf" %>

  </head>
  <body onload="number_pages">
    <div id="header">
      <!--= wicked_pdf_image_tag 'thumbnail.png', height: "30", width: "auto"-->
    </div>
    <div id="content">
      <%= yield %>
    </div>
  </body>
</html>

The original helpers included a method running_in_development?, but I found two issues:

  1. It doesn't exist in 2021
  2. When I define it, Webpacker.dev_server.running? mysteriously returns false, even though it's running in dev (using webpack-dev-server).

For the sake of progress I'm checking Rails, Rails.env.development?:

module ApplicationHelper
  def pdf_stylesheet_pack_tag(source)
    if Rails.env.development?
      options = { media: "all" }
      wds = Webpacker.dev_server
      options[:host] = "#{wds.host}:#{wds.port}" unless show_as_html?
      stylesheet_pack_tag(source, options)
    else
      wicked_pdf_stylesheet_pack_tag(source)
    end
  end

  def pdf_javascript_pack_tag(source)
    if Rails.env.development?
      options = {}
      wds = Webpacker.dev_server
      options[:host] = "#{wds.host}:#{wds.port}" unless show_as_html?
      javascript_pack_tag(source, options)
    else
      wicked_pdf_javascript_pack_tag(source)
    end
  end

  def show_as_html?
    params[:debug].present?
  end

  def running_in_development?
    Webpacker.dev_server.running?
  end
end

Output:

Screen Shot 2021-07-31 at 8 03 13 PM

Update

I realized my webpack was hosted on 8080, even though Webpacker outputs 3035. After hardcoding that value, the default tailwind font is now loading.

Screen Shot 2021-07-31 at 8 15 35 PM

Adding components and utilities now renders everything but color and grid.

// app/javascript/stylesheets/pdf.scss

@import "tailwindcss/base";
@import "tailwindcss/components";
@import "tailwindcss/utilities";
Screen Shot 2021-07-31 at 8 19 53 PM

Seems like we're almost there, but I'm at a loss as to why colors won't render.

costa commented 3 years ago

I'm serving assets/packs with Rails (behind a CDN in production) and I'm quite happy with my setup.

So, after encountering this (at all the latest components' versions at the time of writing) and not being satisfied with the suggested workarounds of the already convoluted functionality, I've come up with this: https://gist.github.com/costa/82dd49e370e0185b462e80c48a728bb4 (you'll need the sinatra gem) meaning that the regular _tag helpers can be used (e.g. javascript_pack_tag) and everything just works (including things like webpack-bundled fonts — within a simple containerised setup at least.

I hope this is useful and will inspire some rework of the gem — I believe a simple task like "pass input to wkhtmltopdf and return output" can be implemented in a much, much simpler way. However, unfortunately, I can't even recommend this too much since wkhtmltox is a mess in and of itself.

omontigny commented 2 years ago

Seems like we're almost there, but I'm at a loss as to why colors won't render.

Hello @mrjonesbot : I'm exactly in your situation (no color, no grid) if I use TailwindCSS. (with a specific Css file it's correct as we can see on the rest of the invoice) but I really need to use TailwindCSS !

Did you find a solution ?

Thanks a lot.

html pdf
unixmonkey commented 2 years ago

@omontigny If your stylesheet uses too-modern selectors from CSS 3.0, and probably 2.1, wkhtmltopdf likely will not be able to style them. wkhtmltopdf is equivalent to a very very old version of Chrome, and cannot do new CSS or JS stuff. For example, flexbox does not work, JS cannot use const, etc. I'm not saying this is what you are seeing, but it very well could be. It's possible you may need to write some old-school CSS to get things looking the way you want them.

mrjonesbot commented 2 years ago

@omontigny I switched to Grover, which takes a slightly different approach and depends on puppeteer.

I've found it to be great since I can use Tailwind without issues.

wl02599509 commented 8 months ago

@omontigny I switched to Grover, which takes a slightly different approach and depends on puppeteer.

I've found it to be great since I can use Tailwind without issues.

@mrjonesbot How did you use Tailwind in Grover? My pdf made by grover can not show the CSS style from tailwind, but the tailwind on html is working. What do I miss when I use grover?!

app/assets/stylesheets/application.tailwind.css:
@tailwind base;
@tailwind components;
@tailwind utilities;
app/views/layouts/pdf.pdf.erb:
<!DOCTYPE html>
<html lang="<%= I18n.locale %>">
  <head>
    <meta charset="UTF-8">
    <%= stylesheet_link_tag "application", "data-turbo-track": "reload" %>
  </head>
  <body>
    <div id="content">
      <%= yield %>
    </div>
  </body>
</html>
controller:
html = Students::MyController.new.render_to_string({
        template: 'my_template',
        layout: 'pdf',
        locals: { **locals_info }
      })
grover_options = { format: 'A4', scale: 0.9, wait_until: 'domcontentloaded' }
pdf = Grover.new(html, **grover_options).to_pdf
send_data pdf, filename: 'report.pdf', type: 'application/pdf', disposition: 'attachment'
mrjonesbot commented 8 months ago

@omontigny I switched to Grover, which takes a slightly different approach and depends on puppeteer. I've found it to be great since I can use Tailwind without issues.

@mrjonesbot How did you use Tailwind in Grover? My pdf made by grover can not show the CSS style from tailwind, but the tailwind on html is working. What do I miss when I use grover?!

app/assets/stylesheets/application.tailwind.css:
@tailwind base;
@tailwind components;
@tailwind utilities;
app/views/layouts/pdf.pdf.erb:
<!DOCTYPE html>
<html lang="<%= I18n.locale %>">
  <head>
    <meta charset="UTF-8">
    <%= stylesheet_link_tag "application", "data-turbo-track": "reload" %>
  </head>
  <body>
    <div id="content">
      <%= yield %>
    </div>
  </body>
</html>
controller:
html = Students::MyController.new.render_to_string({
        template: 'my_template',
        layout: 'pdf',
        locals: { **locals_info }
      })
grover_options = { format: 'A4', scale: 0.9, wait_until: 'domcontentloaded' }
pdf = Grover.new(html, **grover_options).to_pdf
send_data pdf, filename: 'report.pdf', type: 'application/pdf', disposition: 'attachment'

Try adding media: "all", as the second option to your stylesheet_link_tag call.

Screenshot 2024-03-06 at 10 05 32 AM