react-rails icon indicating copy to clipboard operation
react-rails copied to clipboard

Document Support for Esbuild

Open IsmailM opened this issue 3 years ago • 31 comments

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 avatar Nov 15 '21 11:11 IsmailM

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

igortice avatar Nov 25 '21 00:11 igortice

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";

elektronaut avatar Dec 02 '21 14:12 elektronaut

Not working

akhilgkrishnan avatar Dec 13 '21 07:12 akhilgkrishnan

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.

multiplegeorges avatar Dec 28 '21 04:12 multiplegeorges

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

cionescu avatar Dec 31 '21 09:12 cionescu

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.

net1957 avatar Jan 14 '22 21:01 net1957

@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 🥇

navidemad avatar Jan 17 '22 13:01 navidemad

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

dyeje avatar Jan 18 '22 02:01 dyeje

@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

net1957 avatar Jan 18 '22 08:01 net1957

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: https://github.com/rails/jsbundling-rails/pull/79.

justin808 avatar Feb 01 '22 22:02 justin808

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);

louishuyng avatar Feb 24 '22 17:02 louishuyng

Hi everybody, Shakapacker supports esbuild:

https://github.com/shakacode/shakapacker/blob/master/docs/using_esbuild_loader.md

justin808 avatar Feb 27 '22 23:02 justin808

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 "#<:programerror: typeerror: cannot read properties of undefined>" 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?

christiannaths avatar Mar 24 '22 21:03 christiannaths

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.

Petercopter avatar Jun 02 '22 16:06 Petercopter

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?

guyas avatar Jun 27 '22 14:06 guyas

@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));

dyeje avatar Jun 27 '22 17:06 dyeje

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>

guyas avatar Jun 28 '22 11:06 guyas

@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!

mrpineapples avatar Dec 26 '22 04:12 mrpineapples

@mrpineapples Thanks!

@ahangarha we need this in the docs.

justin808 avatar Dec 26 '22 08:12 justin808

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?

justin808 avatar Dec 26 '22 08:12 justin808

@justin808 Just for posterity it doesn't actually need to be a default export, the code I used which was in this example https://github.com/reactjs/react-rails/issues/1149#issuecomment-1003318505 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;

mrpineapples avatar Dec 26 '22 08:12 mrpineapples

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]
}

satoko2pac avatar Feb 21 '23 18:02 satoko2pac

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 avatar Sep 13 '23 01:09 imjared

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

SeanRoberts avatar Oct 07 '23 20:10 SeanRoberts

@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.

imjared avatar Oct 09 '23 16:10 imjared

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

SeanRoberts avatar Oct 09 '23 16:10 SeanRoberts

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

imjared avatar Oct 09 '23 16:10 imjared

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

ahangarha avatar Oct 10 '23 12:10 ahangarha

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.

MrNagoo avatar Nov 08 '23 17:11 MrNagoo

Any chance that somebody could summarize for the project docs?

justin808 avatar Nov 09 '23 02:11 justin808