jqueryruby-on-railsmastodon

Adding jQuery to Rails 6 in Mastodon


I gave up trying to add jQuery to Mastodon 4.0.2 via Webpacker How do I add jQuery to Mastodon 4.02 (using Rails 6 and Webpacker 4)?

So now I want to simply add a link to a jQuery CDN in the Mastodon header file application.html.haml.

I add

= javascript_pack_tag "https://ajax.googleapis.com/ajax/libs/jquery/3.6.3/jquery.min.js"

to application.html.haml and then run

RAILS_ENV=production bundle exec rake tmp:cache:clear
RAILS_ENV=production bundle exec rails assets:generate_static_pages
RAILS_ENV=production bundle exec rails assets:precompile

and then exit from the root account and run

systemctl restart mastodon-*

but Mastodon crashes with the app's generic error screen.

Edit 2/1/23

Using

%script src: "https://ajax.googleapis.com/ajax/libs/jquery/3.6.3/jquery.min.js"

also crashes Mastodon.

Questions:

• How can I simply add a lnk to the jQuery CDN in the header of Mastodon?

Edit 2/7/23 alex's solution to edit the content_security_policy.rb to use a CDN and avoid CSP issues works

Edit 2/6/23 alex points out that the CDN will be blocked due to CSP headers (you could add an exception in Nginx, but that's for another question); the best solution is to use alex's Sprockets answer below.

• Where are the error logs that would show me more info on the crash?

Edit 2/6/23 The most relevant logs are viewed by running journalctl -f -u mastodon-web

which outputs:

Feb 06 15:12:28 ExampleMastodon bundle[9057]: [44fb4365-61e3-4bbb-b495-704b84c0ba06] ActionView::Template::Error (Inconsistent indentation: 1 tab used for indentation, but the rest of the document was indented using 2 spaces.)

Feb 06 15:12:28 ExampleMastodon bundle[9057]: [44fb4365-61e3-4bbb-b495-704b84c0ba06] 32: = javascript_pack_tag "locale_#{I18n.locale}", crossorigin: 'anonymous

Feb 06 15:12:28 ExampleMastodon bundle[9057]: [44fb4365-61e3-4bbb-b495-704b84c0ba06] 33: = javascript_include_tag "jquery"

Feb 06 15:12:28 ExampleMastodon bundle[9057]: [44fb4365-61e3-4bbb-b495-704b84c0ba06] 34: = csrf_meta_tags

Feb 06 15:12:28 ExampleMastodon bundle[9057]: [44fb4365-61e3-4bbb-b495-704b84c0ba06] 35: %meta{ name: 'style-nonce', content: request.content_security_policy_nonce }

Feb 06 15:12:28 ExampleMastodon bundle[9057]: [44fb4365-61e3-4bbb-b495-704b84c0ba06] 36:

Feb 06 15:12:28 ExampleMastodon bundle[9057]: [44fb4365-61e3-4bbb-b495-704b84c0ba06] 37: = stylesheet_link_tag '/inert.css', skip_pipeline: true, media: 'all', id: 'inert-style'

Feb 06 15:12:28 ExampleMastodon bundle[9057]: [44fb4365-61e3-4bbb-b495-704b84c0ba06] 38: = stylesheet_link_tag custom_css_path, skip_pipeline: true, host: root_url, media: 'all'

Feb 06 15:12:28 ExampleMastodon bundle[9057]: [44fb4365-61e3-4bbb-b495-704b84c0ba06]

Feb 06 15:12:28 ExampleMastodon bundle[9057]: [44fb4365-61e3-4bbb-b495-704b84c0ba06] app/views/layouts/application.html.haml:35

which shows the cause of the crash: "Inconsistent indentation"

Arrgg...

Fixed that and it works.


This is application.html.haml at Github: https://github.com/mastodon/mastodon/blob/a5a00d7f7adff5e0afbd23ac1e1b16120137509a/app/views/layouts/application.html.haml

!!! 5
%html{ lang: I18n.locale }
  %head
    %meta{ charset: 'utf-8' }/
    %meta{ name: 'viewport', content: 'width=device-width, initial-scale=1' }/

    - if cdn_host?
      %link{ rel: 'dns-prefetch', href: cdn_host }/
      %meta{ name: 'cdn-host', content: cdn_host }/

    - if storage_host?
      %link{ rel: 'dns-prefetch', href: storage_host }/

    %link{ rel: 'icon', href: '/favicon.ico', type: 'image/x-icon' }/

    - %w(16 32 48).each do |size|
      %link{ rel: 'icon', sizes: "#{size}x#{size}", href: asset_pack_path("media/icons/favicon-#{size}x#{size}.png"), type: 'image/png' }/

    - %w(57 60 72 76 114 120 144 152 167 180 1024).each do |size|
      %link{ rel: 'apple-touch-icon', sizes: "#{size}x#{size}", href: asset_pack_path("media/icons/apple-touch-icon-#{size}x#{size}.png") }/

    %link{ rel: 'mask-icon', href: asset_pack_path('media/images/logo-symbol-icon.svg'), color: '#6364FF' }/
    %link{ rel: 'manifest', href: manifest_path(format: :json) }/
    %meta{ name: 'theme-color', content: '#191b22' }/
    %meta{ name: 'apple-mobile-web-app-capable', content: 'yes' }/

    %title= content_for?(:page_title) ? safe_join([yield(:page_title).chomp.html_safe, title], ' - ') : title

    = stylesheet_pack_tag 'common', media: 'all', crossorigin: 'anonymous'
    = stylesheet_pack_tag current_theme, media: 'all', crossorigin: 'anonymous'
    = javascript_pack_tag 'common', crossorigin: 'anonymous'
    = javascript_pack_tag "locale_#{I18n.locale}", crossorigin: 'anonymous'
    = csrf_meta_tags
    %meta{ name: 'style-nonce', content: request.content_security_policy_nonce }

    = stylesheet_link_tag '/inert.css', skip_pipeline: true, media: 'all', id: 'inert-style'
    = stylesheet_link_tag custom_css_path, skip_pipeline: true, host: root_url, media: 'all'

    = yield :header_tags

  %body{ class: body_classes }
    = content_for?(:content) ? yield(:content) : yield

    .logo-resources{ 'tabindex' => '-1', 'inert' => true, 'aria-hidden' => true }
      = render_symbol :icon
      = render_symbol :wordmark

Solution

  • Managed to get mastodon running in vagrant in development mode, but it should be the same in production, you just have to precompile and restart.


    Webpack

    $ yarn add jquery
    Done in 999.72s # <= at least it worked on the first try
    

    Add it to plugins:
    https://github.com/mastodon/mastodon/blob/v4.0.2/config/webpack/shared.js#L69

    // config/webpack/shared.js
    
    plugins: [
      new webpack.ProvidePlugin({
        $: 'jquery',
        jQuery: 'jquery',
      }),
      ...
    

    https://webpack.js.org/plugins/provide-plugin/#usage-jquery

    That means you automatically have $ and jQuery in modules. A console.log($); in application.js gives me this:

    ƒ jQuery(selector, context) {                                 application.js?2e28:4 
          // The jQuery object is actually just the init constructor 'enhanced'
          // Need init if jQuery is called (just allow error to be thrown if not included)
          re…
    

    You can try to add jquery to window if you want:

    // app/javascript/application.js
    
    window.$ = window.jQuery = jQuery;
    //                         ^ this is automatically given by wepback.ProvidePlugin
    

    Now you have $ and jQuery outside of the modules:

    // app/views/layouts/application.html.haml
    
    :javascript
      console.log($) // works in development
    

    I think, it will fail in production, because of CSP policy, so no inline scripts.


    CDN

    # app/views/layouts/application.html.haml
    
    = javascript_include_tag "https://cdn.jsdelivr.net/npm/jquery@3.6.3/dist/jquery.min.js"
    
    or
    
    %script{ src: "https://cdn.jsdelivr.net/npm/jquery@3.6.3/dist/jquery.min.js" }
    

    Network tab shows the reason it's not working:

    jquery.min.js          (blocked:csp)        script
    

    Content security policy is configured not to accept scripts from outside hosts and nonce is only set for styles:

    https://github.com/mastodon/mastodon/blob/v4.0.2/config/initializers/content_security_policy.rb

    You're looking for script_src config, it's in a few places there, I think updating this section should be enough:

    if Rails.env.development?
      webpacker_urls = %w(ws http).map { |protocol| "#{protocol}#{Webpacker.dev_server.https? ? 's' : ''}://#{Webpacker.dev_server.host_with_port}" }
    
      p.connect_src :self, :data, :blob, assets_host, media_host, Rails.configuration.x.streaming_api_base_url, *webpacker_urls
    
      p.script_src  :self, :unsafe_inline, :unsafe_eval, assets_host, host_to_url("cdn.jsdelivr.net")
      # this one worked for me                                        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    
      p.child_src   :self, :blob, assets_host
      p.worker_src  :self, :blob, assets_host
    else
      p.connect_src :self, :data, :blob, assets_host, media_host, Rails.configuration.x.streaming_api_base_url
    
      p.script_src  :self, assets_host, "'wasm-unsafe-eval'", host_to_url("cdn.jsdelivr.net")
      # this one should work for you                          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    
      p.child_src   :self, :blob, assets_host
      p.worker_src  :self, :blob, assets_host
    end
    

    Home page is explore if you click on it in Network tab, you can see response headers:

    # just showing the relevant part
    Content-Security-Policy:
    # this is present only in development, that means no inline scripts in production
    #                   vvvvvvvvvvvvvvv
      script-src 'self' 'unsafe-inline' 'unsafe-eval' http://mastodon.local http://cdn.jsdelivr.net;
    # ^^^^^^^^^^                                                            ^^^^^^^^^^^^^^^^^^^^^^^
    

    https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP
    https://api.rubyonrails.org/classes/ActionDispatch/ContentSecurityPolicy.html


    Sprockets

    Download jquery to vendor/assets/javascripts/jquery.js and add it to be precompiled:

    # config/initializers/assets.rb
    
    Rails.application.config.assets.precompile += ["jquery.js"]
    

    Now you don't need to mess with CSP:

    # app/views/layouts/application.html.haml
    
    = javascript_include_tag "jquery"
    

    In production

    To double check, nginx.conf should be configured to serve assets from public/assets:
    https://github.com/mastodon/mastodon/blob/v4.0.2/dist/nginx.conf#L67

    Logger is set to STDOUT:
    https://github.com/mastodon/mastodon/blob/v4.0.2/config/environments/production.rb#L26

    Depending on the system and config values, finding the logs is the game of gofish. First thing it says journalctl -u mastodon-web to see the logs:

    root@mastodon:~$ journalctl -u mastodon-web
    No journal files were found.
    -- No entries --
    
    # nothing ever works the first time
    
    root@mastodon:~$ systemctl restart systemd-journald
    root@mastodon:~$ systemctl restart mastodon-web.service
    root@mastodon:~$ journalctl -f -u mastodon-web
    -- Logs begin at Mon 2023-02-06 11:01:52 UTC. --
    Feb 06 11:05:29 mastodon systemd[1]: Started mastodon-web.
    Feb 06 11:05:30 mastodon bundle[10473]: [10473] Puma starting in cluster mode...
    Feb 06 11:05:30 mastodon bundle[10473]: [10473] * Puma version: 5.6.5 (ruby 3.0.4-p208) ("Birdie's Version")
    # ...
    # refresh the page after puma boots all the way, you should get something here
    
    # in case that ^ didn't help, try this:
    tail -f /var/log/syslog
    

    I will need to see the error that you're getting to help any further.

    mastodon@mastodon:~/live$ RAILS_ENV=production bin/rails assets:precompile
    yarn install v1.22.19
    Done in 2.50s.
    INFO -- : Writing /home/mastodon/live/public/assets/jquery-6c97f936439a920f7055f372924fa39fb6acaec5a89af8467f0847ebdbe047ee.js
    INFO -- : Writing /home/mastodon/live/public/assets/jquery-6c97f936439a920f7055f372924fa39fb6acaec5a89af8467f0847ebdbe047ee.js.gz
    
    root@mastodon:~$ systemctl restart mastodon-web.service
    
    # journalctl -f -u mastodon-web
    
    method=GET path=/explore format=html controller=HomeController action=index status=200 duration=424.95 view=364.50 db=40.52
                                                                                ^^^^^^^^^^
    
    # tail -f /var/log/nginx/access.log
    
    "GET /assets/jquery-6c9...47ee.js HTTP/2.0" 200 31123
                 ^^^^^^                         ^^^