Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Document Support for Esbuild #1149

Open
IsmailM opened this issue Nov 15, 2021 · 31 comments
Open

Document Support for Esbuild #1149

IsmailM opened this issue Nov 15, 2021 · 31 comments
Assignees

Comments

@IsmailM
Copy link

IsmailM commented Nov 15, 2021

Rails 7 seems to be moving away from webpack(er), with multiple options to bundle JS with the new js-bundling gem (e.g. with ESbuild etc.).

As such, in order to future proof the project, it might make sense to add support/documentation for JS bundling solutions other than webpack.

Notably, require.context() is webpack specific. I have managed to work around this by providing a mapping manually with ESbuild:

var ReactRailsUJS = require('react_ujs');
ReactRailsUJS.useContext({
  Table: require('./components/Table'),
});

As such, simply removing require.context() meant that I was able to successfully use react-rails with ESbuild.

This took quite some time to work out - as such, it would be useful to document this (or a better solution) somewhere.

@IsmailM IsmailM changed the title Support for Esbuild Document Support for Esbuild Nov 15, 2021
@igortice
Copy link

Rails 7 seems to be moving away from webpack(er), with multiple options to bundle JS with the new js-bundling gem (e.g. with ESbuild etc.).

As such, in order to future proof the project, it might make sense to add support/documentation for JS bundling solutions other than webpack.

Notably, require.context() is webpack specific. I have managed to work around this by providing a mapping manually with ESbuild:

var ReactRailsUJS = require('react_ujs');
ReactRailsUJS.useContext({
  Table: require('./components/Table'),
});

As such, simply removing require.context() meant that I was able to successfully use react-rails with ESbuild.

This took quite some time to work out - as such, it would be useful to document this (or a better solution) somewhere.

for me not work

@elektronaut
Copy link

Going by the README, it seems like providing your own getConstructor function is the cleanest solution. Here's how I solved it:

const ReactRailsUJS = require("react_ujs");
import * as Components from "./components";
ReactRailsUJS.getConstructor = (className) => Components[className];

With ./components.js being a manifest of all the components that can be mounted:

export { default as Component1 } from "./components/Component1";
export { default as Component2 } from "./components/Component2";
export { default as Component3 } from "./components/Component3";

@akhilgkrishnan
Copy link

Not working

@multiplegeorges
Copy link

Here's what I got working:

  • Install the esbuild-plugin-import-glob plugin
  • Create a esbuild.config.js file in the root of your project.
  • Put the following in the your esbuild.config.js:
const path = require('path')
const ImportGlobPlugin = require('esbuild-plugin-import-glob').default;
const esbuild = require("esbuild")

esbuild.build({
  entryPoints: ["application.js"],
  bundle: true,
  outdir: path.join(process.cwd(), "app/assets/builds"),
  absWorkingDir: path.join(process.cwd(), "app/javascript"),
  watch: true,
  minify: false,
  plugins: [
    ImportGlobPlugin()
  ],
}).catch(() => process.exit(1))
  • In package.json change your build command to: "build": "node esbuild.config.js".
  • Now in your app/javascripts/application.js add this config for react-rails:
// NOTE: I am using Typescript and tsx files here. Change for your setup.
import * as Components from "./components/**/*.tsx"

let componentsContext = {}
Components.filenames.forEach((fileName, i) => {
  let cleanName = fileName.replace("./components/", "").replace(".tsx", "")
  componentsContext[cleanName] = Components.default[i].default
})

const ReactRailsUJS = require("react_ujs")
console.log(ReactRailsUJS)

ReactRailsUJS.getConstructor = (name) => {
  return componentsContext[name]
}
ReactRailsUJS.handleEvent('turbo:load', ReactRailsUJS.handleMount, false);
ReactRailsUJS.handleEvent('turbo:frame-load', ReactRailsUJS.handleMount, false);
ReactRailsUJS.handleEvent('turbo:before-render', ReactRailsUJS.handleUnmount, false);

The key is globbing your files through the plugin, building up your own context object, and then providing your own custom getConstructor function so that ReacRailsUJS can find the correct component when the name is provided.

@cionescu
Copy link

I bet there are nicer approaches, but one that worked for me (heavily inspired by @multiplegeorges) with a fresh rails7 & esbuild app is:

app/javascript/components/index.js

import components from "./**/*.js"

let componentsContext = {}
components.forEach((component) => {
  componentsContext[component.name.replace(".js", "")] = component.module.default
})

const ReactRailsUJS = require("react_ujs")

ReactRailsUJS.getConstructor = (name) => {
  return componentsContext[name]
}
ReactRailsUJS.handleEvent('turbo:load', ReactRailsUJS.handleMount, false);
ReactRailsUJS.handleEvent('turbo:frame-load', ReactRailsUJS.handleMount, false);
ReactRailsUJS.handleEvent('turbo:before-render', ReactRailsUJS.handleUnmount, false);

@multiplegeorges's approach was throwing a warning for me: Import "filenames" will always be undefined because the file "components/index.js" has no exports.

working example: https://github.com/cionescu/rails-7-new-esbuild/blob/e50cdf3bd790ba26ffcf5bff7f0596aac4d1173c/app/javascript/components/index.js

@net1957
Copy link

net1957 commented Jan 14, 2022

I'm using the related project webpacker-react and tried it on a test project on rails 7 with esbuild (jsbundling-rails).

apart adding lodash in package.json it work out the box.

see https://github.com/renchap/webpacker-react

Perhaps it could help to modify this project.

@navidemad
Copy link

@net1957 So you are running webpacker and esbuild at the same time ?
It will be awesome if someone can edit the README.md with how to support esbuild 🥇

@dyeje
Copy link

dyeje commented Jan 18, 2022

I made a guide based on @cionescu's repo. I will try to make a PR with updated generators and README.

@net1957
Copy link

net1957 commented Jan 18, 2022

@navidemad No, I dropped webpacker in favor of esbuild, but webpacker-react don't depend on webpacker. The name is a little misleading.

this gem and webpacker-react resolve the same problem in Rails with the same interface in controllers and views

@justin808
Copy link
Collaborator

I'm maintaining shakapacker, the successor to rails/webpacker, which includes everything that was going into webpacker v6.

What's the advantage of moving away from webpacker? I just updated the comparison: rails/jsbundling-rails#79.

@louishuyng
Copy link

this one working fine with me

import components from './react/**/**.tsx';

let componentsContext = {};
components.forEach(component => {
  const name = Object.keys(component)[0];
  componentsContext[name] = component[name];
});

ReactRailsUJS.getConstructor = name => {
  return componentsContext[name];
};
ReactRailsUJS.handleEvent('turbo:load', ReactRailsUJS.handleMount, false);
ReactRailsUJS.handleEvent('turbo:frame-load', ReactRailsUJS.handleMount, false);
ReactRailsUJS.handleEvent('turbo:before-render', ReactRailsUJS.handleUnmount, false);

@justin808
Copy link
Collaborator

@christiannaths
Copy link

Big thanks to @multiplegeorges and @cionescu for the workaround, I managed to get components loaded and working after cloning @cionescu's example, however it seems to choke on prerender: <%= react_component 'Clock', {foo: 'bar'}, {prerender: true} %>

Encountered error "#<ExecJS::ProgramError: TypeError: Cannot read properties of undefined (reading 'serverRender')>" when prerendering Clock with {"foo":"bar"}
eval (eval at <anonymous> ((execjs):36:8), <anonymous>:6:45)
Click to expand and see the full backtrace ``` Encountered error "#" when prerendering Clock with {"foo":"bar"} eval (eval at ((execjs):36:8), :6:45) eval (eval at ((execjs):36:8), :18:13) (execjs):36:8 (execjs):54:14 (execjs):1:40 Object. ((execjs):1:58) Module._compile (node:internal/modules/cjs/loader:1103:14) Object.Module._extensions..js (node:internal/modules/cjs/loader:1157:10) Module.load (node:internal/modules/cjs/loader:981:32) Function.Module._load (node:internal/modules/cjs/loader:822:12) /usr/local/bundle/gems/execjs-2.8.1/lib/execjs/external_runtime.rb:39:in `exec' /usr/local/bundle/gems/execjs-2.8.1/lib/execjs/external_runtime.rb:21:in `eval' /usr/local/bundle/gems/react-rails-2.6.1/lib/react/server_rendering/exec_js_renderer.rb:39:in `render_from_parts' /usr/local/bundle/gems/react-rails-2.6.1/lib/react/server_rendering/exec_js_renderer.rb:20:in `render' /usr/local/bundle/gems/react-rails-2.6.1/lib/react/server_rendering/bundle_renderer.rb:40:in `render' /usr/local/bundle/gems/react-rails-2.6.1/lib/react/server_rendering.rb:27:in `block in render' /usr/local/bundle/gems/connection_pool-2.2.5/lib/connection_pool.rb:63:in `block (2 levels) in with' /usr/local/bundle/gems/connection_pool-2.2.5/lib/connection_pool.rb:62:in `handle_interrupt' /usr/local/bundle/gems/connection_pool-2.2.5/lib/connection_pool.rb:62:in `block in with' /usr/local/bundle/gems/connection_pool-2.2.5/lib/connection_pool.rb:59:in `handle_interrupt' /usr/local/bundle/gems/connection_pool-2.2.5/lib/connection_pool.rb:59:in `with' /usr/local/bundle/gems/react-rails-2.6.1/lib/react/server_rendering.rb:26:in `render' /usr/local/bundle/gems/react-rails-2.6.1/lib/react/rails/component_mount.rb:74:in `prerender_component' /usr/local/bundle/gems/react-rails-2.6.1/lib/react/rails/component_mount.rb:38:in `block in react_component' /usr/local/bundle/gems/actionview-7.0.2.3/lib/action_view/helpers/capture_helper.rb:45:in `block in capture' /usr/local/bundle/gems/actionview-7.0.2.3/lib/action_view/helpers/capture_helper.rb:209:in `with_output_buffer' /usr/local/bundle/gems/actionview-7.0.2.3/lib/action_view/helpers/capture_helper.rb:45:in `capture' /usr/local/bundle/gems/actionview-7.0.2.3/lib/action_view/helpers/tag_helper.rb:338:in `content_tag' /usr/local/bundle/gems/react-rails-2.6.1/lib/react/rails/component_mount.rb:57:in `react_component' /usr/local/bundle/gems/react-rails-2.6.1/lib/react/rails/view_helper.rb:21:in `react_component' /workspace/app/views/main/index.html.erb:6:in `_app_views_main_index_html_erb___4033424673020032686_24280' /usr/local/bundle/gems/actionview-7.0.2.3/lib/action_view/base.rb:244:in `public_send' /usr/local/bundle/gems/actionview-7.0.2.3/lib/action_view/base.rb:244:in `_run' /usr/local/bundle/gems/actionview-7.0.2.3/lib/action_view/template.rb:157:in `block in render' /usr/local/bundle/gems/activesupport-7.0.2.3/lib/active_support/notifications.rb:208:in `instrument' /usr/local/bundle/gems/actionview-7.0.2.3/lib/action_view/template.rb:361:in `instrument_render_template' /usr/local/bundle/gems/actionview-7.0.2.3/lib/action_view/template.rb:155:in `render' /usr/local/bundle/gems/actionview-7.0.2.3/lib/action_view/renderer/template_renderer.rb:65:in `block (2 levels) in render_template' /usr/local/bundle/gems/activesupport-7.0.2.3/lib/active_support/notifications.rb:206:in `block in instrument' /usr/local/bundle/gems/activesupport-7.0.2.3/lib/active_support/notifications/instrumenter.rb:24:in `instrument' /usr/local/bundle/gems/activesupport-7.0.2.3/lib/active_support/notifications.rb:206:in `instrument' /usr/local/bundle/gems/actionview-7.0.2.3/lib/action_view/renderer/template_renderer.rb:60:in `block in render_template' /usr/local/bundle/gems/actionview-7.0.2.3/lib/action_view/renderer/template_renderer.rb:75:in `block in render_with_layout' /usr/local/bundle/gems/activesupport-7.0.2.3/lib/active_support/notifications.rb:206:in `block in instrument' /usr/local/bundle/gems/activesupport-7.0.2.3/lib/active_support/notifications/instrumenter.rb:24:in `instrument' /usr/local/bundle/gems/activesupport-7.0.2.3/lib/active_support/notifications.rb:206:in `instrument' /usr/local/bundle/gems/actionview-7.0.2.3/lib/action_view/renderer/template_renderer.rb:74:in `render_with_layout' /usr/local/bundle/gems/actionview-7.0.2.3/lib/action_view/renderer/template_renderer.rb:59:in `render_template' /usr/local/bundle/gems/actionview-7.0.2.3/lib/action_view/renderer/template_renderer.rb:11:in `render' /usr/local/bundle/gems/actionview-7.0.2.3/lib/action_view/renderer/renderer.rb:61:in `render_template_to_object' /usr/local/bundle/gems/actionview-7.0.2.3/lib/action_view/renderer/renderer.rb:29:in `render_to_object' /usr/local/bundle/gems/actionview-7.0.2.3/lib/action_view/rendering.rb:117:in `block in _render_template' /usr/local/bundle/gems/actionview-7.0.2.3/lib/action_view/base.rb:270:in `in_rendering_context' /usr/local/bundle/gems/actionview-7.0.2.3/lib/action_view/rendering.rb:116:in `_render_template' /usr/local/bundle/gems/actionpack-7.0.2.3/lib/action_controller/metal/streaming.rb:216:in `_render_template' /usr/local/bundle/gems/actionview-7.0.2.3/lib/action_view/rendering.rb:103:in `render_to_body' /usr/local/bundle/gems/actionpack-7.0.2.3/lib/action_controller/metal/rendering.rb:46:in `render_to_body' /usr/local/bundle/gems/actionpack-7.0.2.3/lib/action_controller/metal/renderers.rb:142:in `render_to_body' /usr/local/bundle/gems/actionpack-7.0.2.3/lib/abstract_controller/rendering.rb:25:in `render' /usr/local/bundle/gems/actionpack-7.0.2.3/lib/action_controller/metal/rendering.rb:30:in `render' /usr/local/bundle/gems/actionpack-7.0.2.3/lib/action_controller/metal/instrumentation.rb:22:in `block (2 levels) in render' /usr/local/lib/ruby/3.1.0/benchmark.rb:311:in `realtime' /usr/local/bundle/gems/activesupport-7.0.2.3/lib/active_support/core_ext/benchmark.rb:14:in `ms' /usr/local/bundle/gems/actionpack-7.0.2.3/lib/action_controller/metal/instrumentation.rb:22:in `block in render' /usr/local/bundle/gems/actionpack-7.0.2.3/lib/action_controller/metal/instrumentation.rb:91:in `cleanup_view_runtime' /usr/local/bundle/gems/activerecord-7.0.2.3/lib/active_record/railties/controller_runtime.rb:34:in `cleanup_view_runtime' /usr/local/bundle/gems/actionpack-7.0.2.3/lib/action_controller/metal/instrumentation.rb:21:in `render' /usr/local/bundle/gems/actionpack-7.0.2.3/lib/action_controller/metal/implicit_render.rb:35:in `default_render' /usr/local/bundle/gems/actionpack-7.0.2.3/lib/action_controller/metal/basic_implicit_render.rb:6:in `block in send_action' :90:in `tap' /usr/local/bundle/gems/actionpack-7.0.2.3/lib/action_controller/metal/basic_implicit_render.rb:6:in `send_action' /usr/local/bundle/gems/actionpack-7.0.2.3/lib/abstract_controller/base.rb:214:in `process_action' /usr/local/bundle/gems/actionpack-7.0.2.3/lib/action_controller/metal/rendering.rb:53:in `process_action' /usr/local/bundle/gems/actionpack-7.0.2.3/lib/abstract_controller/callbacks.rb:234:in `block in process_action' /usr/local/bundle/gems/activesupport-7.0.2.3/lib/active_support/callbacks.rb:118:in `block in run_callbacks' /usr/local/bundle/gems/react-rails-2.6.1/lib/react/rails/controller_lifecycle.rb:31:in `use_react_component_helper' /usr/local/bundle/gems/activesupport-7.0.2.3/lib/active_support/callbacks.rb:127:in `block in run_callbacks' /usr/local/bundle/gems/actiontext-7.0.2.3/lib/action_text/rendering.rb:20:in `with_renderer' /usr/local/bundle/gems/actiontext-7.0.2.3/lib/action_text/engine.rb:69:in `block (4 levels) in ' /usr/local/bundle/gems/activesupport-7.0.2.3/lib/active_support/callbacks.rb:127:in `instance_exec' /usr/local/bundle/gems/activesupport-7.0.2.3/lib/active_support/callbacks.rb:127:in `block in run_callbacks' /usr/local/bundle/gems/activesupport-7.0.2.3/lib/active_support/callbacks.rb:138:in `run_callbacks' /usr/local/bundle/gems/actionpack-7.0.2.3/lib/abstract_controller/callbacks.rb:233:in `process_action' /usr/local/bundle/gems/actionpack-7.0.2.3/lib/action_controller/metal/rescue.rb:22:in `process_action' /usr/local/bundle/gems/actionpack-7.0.2.3/lib/action_controller/metal/instrumentation.rb:67:in `block in process_action' /usr/local/bundle/gems/activesupport-7.0.2.3/lib/active_support/notifications.rb:206:in `block in instrument' /usr/local/bundle/gems/activesupport-7.0.2.3/lib/active_support/notifications/instrumenter.rb:24:in `instrument' /usr/local/bundle/gems/activesupport-7.0.2.3/lib/active_support/notifications.rb:206:in `instrument' /usr/local/bundle/gems/actionpack-7.0.2.3/lib/action_controller/metal/instrumentation.rb:66:in `process_action' /usr/local/bundle/gems/actionpack-7.0.2.3/lib/action_controller/metal/params_wrapper.rb:259:in `process_action' /usr/local/bundle/gems/activerecord-7.0.2.3/lib/active_record/railties/controller_runtime.rb:27:in `process_action' /usr/local/bundle/gems/actionpack-7.0.2.3/lib/abstract_controller/base.rb:151:in `process' /usr/local/bundle/gems/actionview-7.0.2.3/lib/action_view/rendering.rb:39:in `process' /usr/local/bundle/gems/actionpack-7.0.2.3/lib/action_controller/metal.rb:188:in `dispatch' /usr/local/bundle/gems/actionpack-7.0.2.3/lib/action_controller/metal.rb:251:in `dispatch' /usr/local/bundle/gems/actionpack-7.0.2.3/lib/action_dispatch/routing/route_set.rb:49:in `dispatch' /usr/local/bundle/gems/actionpack-7.0.2.3/lib/action_dispatch/routing/route_set.rb:32:in `serve' /usr/local/bundle/gems/actionpack-7.0.2.3/lib/action_dispatch/journey/router.rb:50:in `block in serve' /usr/local/bundle/gems/actionpack-7.0.2.3/lib/action_dispatch/journey/router.rb:32:in `each' /usr/local/bundle/gems/actionpack-7.0.2.3/lib/action_dispatch/journey/router.rb:32:in `serve' /usr/local/bundle/gems/actionpack-7.0.2.3/lib/action_dispatch/routing/route_set.rb:850:in `call' /usr/local/bundle/gems/warden-1.2.9/lib/warden/manager.rb:36:in `block in call' /usr/local/bundle/gems/warden-1.2.9/lib/warden/manager.rb:34:in `catch' /usr/local/bundle/gems/warden-1.2.9/lib/warden/manager.rb:34:in `call' /usr/local/bundle/gems/rack-2.2.3/lib/rack/tempfile_reaper.rb:15:in `call' /usr/local/bundle/gems/rack-2.2.3/lib/rack/etag.rb:27:in `call' /usr/local/bundle/gems/rack-2.2.3/lib/rack/conditional_get.rb:27:in `call' /usr/local/bundle/gems/rack-2.2.3/lib/rack/head.rb:12:in `call' /usr/local/bundle/gems/actionpack-7.0.2.3/lib/action_dispatch/http/permissions_policy.rb:22:in `call' /usr/local/bundle/gems/actionpack-7.0.2.3/lib/action_dispatch/http/content_security_policy.rb:18:in `call' /usr/local/bundle/gems/rack-2.2.3/lib/rack/session/abstract/id.rb:266:in `context' /usr/local/bundle/gems/rack-2.2.3/lib/rack/session/abstract/id.rb:260:in `call' /usr/local/bundle/gems/actionpack-7.0.2.3/lib/action_dispatch/middleware/cookies.rb:693:in `call' /usr/local/bundle/gems/activerecord-7.0.2.3/lib/active_record/migration.rb:603:in `call' /usr/local/bundle/gems/actionpack-7.0.2.3/lib/action_dispatch/middleware/callbacks.rb:27:in `block in call' /usr/local/bundle/gems/activesupport-7.0.2.3/lib/active_support/callbacks.rb:99:in `run_callbacks' /usr/local/bundle/gems/actionpack-7.0.2.3/lib/action_dispatch/middleware/callbacks.rb:26:in `call' /usr/local/bundle/gems/actionpack-7.0.2.3/lib/action_dispatch/middleware/executor.rb:14:in `call' /usr/local/bundle/gems/actionpack-7.0.2.3/lib/action_dispatch/middleware/actionable_exceptions.rb:17:in `call' /usr/local/bundle/gems/actionpack-7.0.2.3/lib/action_dispatch/middleware/debug_exceptions.rb:28:in `call' /usr/local/bundle/gems/web-console-4.2.0/lib/web_console/middleware.rb:132:in `call_app' /usr/local/bundle/gems/web-console-4.2.0/lib/web_console/middleware.rb:19:in `block in call' /usr/local/bundle/gems/web-console-4.2.0/lib/web_console/middleware.rb:17:in `catch' /usr/local/bundle/gems/web-console-4.2.0/lib/web_console/middleware.rb:17:in `call' /usr/local/bundle/gems/actionpack-7.0.2.3/lib/action_dispatch/middleware/show_exceptions.rb:26:in `call' /usr/local/bundle/gems/railties-7.0.2.3/lib/rails/rack/logger.rb:36:in `call_app' /usr/local/bundle/gems/railties-7.0.2.3/lib/rails/rack/logger.rb:25:in `block in call' /usr/local/bundle/gems/activesupport-7.0.2.3/lib/active_support/tagged_logging.rb:99:in `block in tagged' /usr/local/bundle/gems/activesupport-7.0.2.3/lib/active_support/tagged_logging.rb:37:in `tagged' /usr/local/bundle/gems/activesupport-7.0.2.3/lib/active_support/tagged_logging.rb:99:in `tagged' /usr/local/bundle/gems/railties-7.0.2.3/lib/rails/rack/logger.rb:25:in `call' /usr/local/bundle/gems/sprockets-rails-3.4.2/lib/sprockets/rails/quiet_assets.rb:13:in `call' /usr/local/bundle/gems/actionpack-7.0.2.3/lib/action_dispatch/middleware/remote_ip.rb:93:in `call' /usr/local/bundle/gems/actionpack-7.0.2.3/lib/action_dispatch/middleware/request_id.rb:26:in `call' /usr/local/bundle/gems/rack-2.2.3/lib/rack/method_override.rb:24:in `call' /usr/local/bundle/gems/rack-2.2.3/lib/rack/runtime.rb:22:in `call' /usr/local/bundle/gems/activesupport-7.0.2.3/lib/active_support/cache/strategy/local_cache_middleware.rb:29:in `call' /usr/local/bundle/gems/actionpack-7.0.2.3/lib/action_dispatch/middleware/executor.rb:14:in `call' /usr/local/bundle/gems/actionpack-7.0.2.3/lib/action_dispatch/middleware/static.rb:23:in `call' /usr/local/bundle/gems/rack-2.2.3/lib/rack/sendfile.rb:110:in `call' /usr/local/bundle/gems/actionpack-7.0.2.3/lib/action_dispatch/middleware/host_authorization.rb:137:in `call' /usr/local/bundle/gems/railties-7.0.2.3/lib/rails/engine.rb:530:in `call' /usr/local/bundle/gems/puma-5.6.2/lib/puma/configuration.rb:252:in `call' /usr/local/bundle/gems/puma-5.6.2/lib/puma/request.rb:77:in `block in handle_request' /usr/local/bundle/gems/puma-5.6.2/lib/puma/thread_pool.rb:340:in `with_force_shutdown' /usr/local/bundle/gems/puma-5.6.2/lib/puma/request.rb:76:in `handle_request' /usr/local/bundle/gems/puma-5.6.2/lib/puma/server.rb:441:in `process_client' /usr/local/bundle/gems/puma-5.6.2/lib/puma/thread_pool.rb:147:in `block in spawn_thread' ```

I did notice one or two other issues here suggesting that the react-server js file from the gem needs to be imported before the react_ujs file, which does solve a similar issue when using sprockets, but not with esbuild 😕

Anyone have any ideas?

@Petercopter
Copy link

I struggled to get the import globbing to work with
https://github.com/thomaschaaf/esbuild-plugin-import-glob

I ended up installing
https://github.com/excid3/esbuild-rails

Then I did this in my esbuild.config.js:

const path = require('path')
const rails = require('esbuild-rails')

require('esbuild')
  .build({
    absWorkingDir: path.join(process.cwd(), 'app/javascript'),
    bundle: true,
    entryPoints: ['application.js'],
    minify: true,
    outdir: path.join(process.cwd(), 'app/assets/builds'),
    plugins: [rails()],
    watch: process.argv.includes('--watch')
  })
  .catch(() => process.exit(1))

And that seems to work so far.

@guyas
Copy link

guyas commented Jun 27, 2022

I'm trying to follow @dyeje 's instructions, but I can't find any file named esbuild.config.js.

ran the following commands with node v16.15.1:

rails new esb_app -j esbuild //this is a rails v7.0.3 app
cd esb_app
//(added 'react-rails' to the Gemfile)
bundle install
npm i esbuild-plugin-import-glob@^0.1.1
npm i react@^17.0.2
npm i react-dom@^17.0.2
npm i react_ujs@^2.6.1

Where is this file supposed to be?

@dyeje
Copy link

dyeje commented Jun 27, 2022

@guyas make it in the root of your project. Here's an example file from one of my projects:

const path = require("path");
const rails = require("esbuild-rails");
const ImportGlobPlugin = require("esbuild-plugin-import-glob").default;

require("esbuild")
  .build({
    entryPoints: ["application.js"],
    bundle: true,
    outdir: path.join(process.cwd(), "app/assets/builds"),
    absWorkingDir: path.join(process.cwd(), "app/javascript"),
    watch: process.argv.includes("--watch"),
    plugins: [rails(), ImportGlobPlugin()],
    loader: { ".js": "jsx" },
  })
  .catch(() => process.exit(1));

@guyas
Copy link

guyas commented Jun 28, 2022

I've created such file and went on with your guide, yet the component is not rendered as intended. I still get only

<div data-react-class="HelloWorld" data-react-props="{"html_options":{"prerender":true}}" data-react-cache-id="HelloWorld-0"></div>

@mrpineapples
Copy link

@guyas Idk if you were running into the same issue as I was but I was using named exports for my react components, after I switched to default exports my components were able to render fine!

@justin808
Copy link
Collaborator

@mrpineapples Thanks!

@ahangarha we need this in the docs.

@justin808
Copy link
Collaborator

Note, given that https://github.com/shakacode/shakapacker#esbuild-loader-configuration supports ESBuild, is there any reason to add complexity to also support jsbundling-rails?

@mrpineapples
Copy link

@justin808 Just for posterity it doesn't actually need to be a default export, the code I used which was in this example #1149 (comment) uses

componentsContext[component.name.replace(".js", "")] = component.module.default

which can be modified to support named and/or default exports by doing something like this

  const componentName = component.name.replace(".jsx", "");
  // We prefer named exports but fall back to default
  componentsContext[componentName] = component.module[componentName] || component.module.default;

@ahangarha ahangarha self-assigned this Dec 26, 2022
@satoko2pac
Copy link

In build options I set minify: true in production build, then I also needed to set keepNames: true Doc
Otherwise minification renames component's name and cannot find component error occurred!

require('esbuild').build({
  ...
  bundle: true,
  outdir: 'app/assets/builds',
  publicPath: 'assets',
  minify: true,
  // add this
  keepNames: true
  ...
}
import reactComponents from "./react/**/**.tsx"

let componentsContext = {}
reactComponents.forEach((component) => {
  // maybe component.default.name is renamed without keepNames: true
  componentsContext[component.default.name] = component.default
})

const ReactRailsUJS = require("react_ujs")
ReactRailsUJS.getConstructor = (name) => {
  return componentsContext[name]
}

@imjared
Copy link

imjared commented Sep 13, 2023

Working on a bit of legacy code and unfortunately with the solutions here, we've found ourselves with a mega-bundle of all our components in application.js that clocks in at nearly 5mb. Has anyone found a way to combo esbuild + react-rails and get reliable code-splitting? Even ideas for an approach would be appreciated.

@SeanRoberts
Copy link

SeanRoberts commented Oct 7, 2023

Working on a bit of legacy code and unfortunately with the solutions here, we've found ourselves with a mega-bundle of all our components in application.js that clocks in at nearly 5mb. Has anyone found a way to combo esbuild + react-rails and get reliable code-splitting? Even ideas for an approach would be appreciated.

@imjared Did you get anywhere with this? One naive thought would be to manually manage different bundles for the different pages that you need but that sounds like a pretty big pain

@imjared
Copy link

imjared commented Oct 9, 2023

@SeanRoberts - unfortunately not. i did briefly consider the different bundle/different page approach but with all the prop passing and whatnot that we got out of the box in react-rails, i wasn't too sure how it'd work out.

@SeanRoberts
Copy link

@imjared What did you end up choosing to do? Just deliver a large bundle?

@imjared
Copy link

imjared commented Oct 9, 2023

@SeanRoberts for now, yeah. we're considering looking into shakapacker but time is a limited resource.

@ahangarha
Copy link
Collaborator

Migration to Shakapacker shouldn't be challenging.
But you may also consider outsourcing it to our developers in Shakacode.

@MrNagoo
Copy link

MrNagoo commented Nov 8, 2023

Moved to jsbundling-rails with esbuild. removed scss from webpacker and let dartsass-rails handle it. then removed webpacker. React-rails was the last to get working and the first comment was the ticket! I didn't even create an esbuild.config... although I could. here's my script

"build": "esbuild app/javascript/*.* --bundle --sourcemap --loader:.png=file --loader:.svg=file --loader:.js=jsx --outdir=app/assets/builds --public-path=assets --define:global=window --define:process='{}' --define:module='{}' --define:process.env.NODE_ENV='\"production\"' --platform=browser"

Things that didn't work...

jsbundling-rails && cssbundling-rails (without unbundling sass)
vite-rails
shakapacker

These solutions would probably work with a simpler app (or more time configuring them) but I'm using an old app with millions of users and almost a decade of all kinds of developers

now i'm set up to switch to cssbundling-rails and propshaft using tailwind. Sky's the limit.

@justin808
Copy link
Collaborator

Any chance that somebody could summarize for the project docs?

@adifsgaid
Copy link

adifsgaid commented Jul 3, 2024

Ping @justin808

Issue with Dynamic Importing of Components in Rails 7 with ESBuild

Context

Rails 7 has been gradually moving away from Webpack, offering various JavaScript bundling options, including ESBuild. This shift prompted an exploration of non-Webpack solutions for integrating React with Rails using the react-rails gem.

Problem

The typical method for loading components dynamically (require.context()) is specific to Webpack, and adapting this to ESBuild required a different approach.

Solution

I followed a similar workaround to what @multiplegeorges suggested, but adjusted for my case. Here's the effective setup:

Component Registration and Importing

Instead of relying on require.context(), I used ESBuild's import globbing to dynamically import and register React components.

// app/javascript/application.js
import * as Components from "./components/**/*.{js,ts,tsx,jsx}";
const componentsContext = {};

Components.default.forEach((component) => {
  let cleanName = component.filename
    .replace("./components/", "")
    .replace(/\.\w+$/, ""); // Strips the file extension
  componentsContext[cleanName] = component.module.default;
});

const ReactRailsUJS = require("react_ujs");
ReactRailsUJS.getConstructor = (name) => componentsContext[name];

Configuration for ESBuild
Here's an example of how I configured ESBuild to bundle the JavaScript, including the dynamic imports:

// esbuild.config.mjs
import * as esbuild from "esbuild";
import path from "path";
import rails from "esbuild-rails";
import chokidar from "chokidar";
import http from "http";
import { setTimeout } from "timers/promises";
import ImportGlobPlugin from "esbuild-plugin-import-glob";

esbuild.build({
  entryPoints: ["application.js"],
  bundle: true,
  outdir: "app/assets/builds",
  absWorkingDir: path.join(process.cwd(), "app/javascript"),
  plugins: [ImportGlobPlugin()],
  format: "esm",
  minify: process.env.RAILS_ENV === "production",
  sourcemap: process.env.RAILS_ENV !== "production",
  splitting: true,
}).catch(() => process.exit(1));

And my components:

// javascript/components/Post.jsx
import React from 'react';

export default function Post({ title }) {
  return (
    <h1 className="text-2xl font-bold">Hey, <span className="underline">{title}</span></h1>
  );
}

I hope it helps 😊

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests