Enhanced ESBuild Error Handling for Rails

Public
Improves ESBuild error visibility in development by logging errors to a file and displaying them in the browser when ESBuild fails. Includes a custom esbuild.config.mjs and an EsbuildErrorRendering concern. 🚀
Used 3 times
Created by
A Alemaño

Usage
This template enhances Esbuild error handling in development mode by logging errors to a file and displaying them in the browser.

1. Update your package.json build script

Replace the existing build command inside package.json with:
"build": "node esbuild.config.mjs"

If you use a custom Esbuild setup, adjust the command accordingly.

2. Review and adapt esbuild.config.mjs

Modify the following settings inside esbuild.config.mjs based on your project’s structure:
• entryPoints: Adjust the paths if your JavaScript files are located elsewhere.
• publicPath: Update if your asset serving path differs.
• outdir: Change if your build output is stored in a different directory.

Once set up, run:
yarn build

Or start watching for changes with:
yarn build --watch

🚀 Your Rails app will now catch Esbuild errors and display them directly in the browser!

For further information: https://www.dotruby.com/articles/enhancing-esbuild-error-handling-in-a-rails-app

Run this command in your Rails app directory in the terminal:

rails app:template LOCATION="https://railsbytes.com/script/XnJsOq"
Template Source

Review the code before running this template on your machine.

# Add `esbuild_error_*.txt` to .gitignore
append_to_file ".gitignore", "\nesbuild_error_*.txt\n"

# Create the EsbuildErrorRendering concern
create_file "app/controllers/concerns/esbuild_error_rendering.rb", <<~RUBY
  module EsbuildErrorRendering
    ESBUILD_ERROR = Rails.root.join("esbuild_error_\#{Rails.env}.txt")

    def self.included(base)
      base.before_action :render_esbuild_error, if: :render_esbuild_error?
    end

    private

    def render_esbuild_error
      heading, errors = ESBUILD_ERROR.read.split("\\n", 2)

      render html: <<~HTML.html_safe, layout: false
        <html>
          <head></head>
          <body>
            <h1>\#{ERB::Util.html_escape(heading)}</h1>
            <pre>\#{ERB::Util.html_escape(errors)}</pre>
          </body>
        </html>
      HTML
    end

    def render_esbuild_error?
      ESBUILD_ERROR.file? && !ESBUILD_ERROR.zero?
    end
  end
RUBY

# Include the concern in ApplicationController
insert_into_file "app/controllers/application_controller.rb",
                 "\n  include EsbuildErrorRendering if Rails.env.development?",
                 after: "include Pagy::Backend"

# Create the esbuild config file
create_file "esbuild.config.mjs", <<~JAVASCRIPT
  import esbuild from 'esbuild'
  import fs from 'fs'

  const watch = process.argv.includes('--watch')
  const errorLogFile = `esbuild_error_\${process.env.RAILS_ENV}.txt`

  const onEndPlugin = {
    name: 'onEnd',
    setup (build) {
      build.onEnd((result) => {
        if (result.errors.length > 0) {
          console.log(result.errors)
          const errors = result.errors.map(error => `\${error.location.file}:\${error.location.line}\\n\${error.text}`).join('\\n\\n')
          fs.writeFileSync(
            errorLogFile,
            `esbuild ended with \${result.errors.length} error\${result.errors.length !== 1 ? 's' : ''}\\n\${errors}`
          )
        } else if (fs.existsSync(errorLogFile)) fs.truncate(errorLogFile, 0, () => {})
      })
    }
  }

  await esbuild
    .context({
      entryPoints: ['app/javascript/**/*.*'],
      bundle: true,
      sourcemap: true,
      format: 'esm',
      outdir: 'app/assets/builds',
      publicPath: '/assets',
      logLevel: 'info',
      plugins: [onEndPlugin]
    })
    .then(context => {
      if (watch) {
        context.watch()
      } else {
        context.rebuild().then(result => {
          context.dispose()
        })
      }
    }).catch(() => process.exit(1))
JAVASCRIPT

say "Esbuild error handling setup complete!", :green
Comments

Sign up or Login to leave a comment.