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

Add Bun support #49241

Merged
merged 15 commits into from Sep 12, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
28 changes: 21 additions & 7 deletions actioncable/lib/rails/generators/channel/channel_generator.rb
Expand Up @@ -24,7 +24,7 @@ def create_channel_files

if using_importmap?
pin_javascript_dependencies
elsif using_node?
elsif using_js_runtime?
install_javascript_dependencies
end
end
Expand Down Expand Up @@ -57,22 +57,26 @@ def create_shared_channel_javascript_files
def create_channel_javascript_file
channel_js_path = File.join("app/javascript/channels", class_path, "#{file_name}_channel")
js_template "javascript/channel", channel_js_path
gsub_file "#{channel_js_path}.js", /\.\/consumer/, "channels/consumer" unless using_node?
gsub_file "#{channel_js_path}.js", /\.\/consumer/, "channels/consumer" unless using_js_runtime?
end

def import_channels_in_javascript_entrypoint
append_to_file "app/javascript/application.js",
using_node? ? %(import "./channels"\n) : %(import "channels"\n)
using_js_runtime? ? %(import "./channels"\n) : %(import "channels"\n)
end

def import_channel_in_javascript_entrypoint
append_to_file "app/javascript/channels/index.js",
using_node? ? %(import "./#{file_name}_channel"\n) : %(import "channels/#{file_name}_channel"\n)
using_js_runtime? ? %(import "./#{file_name}_channel"\n) : %(import "channels/#{file_name}_channel"\n)
end

def install_javascript_dependencies
say "Installing JavaScript dependencies", :green
run "yarn add @rails/actioncable"
if using_bun?
run "bun add @rails/actioncable"
elsif using_node?
run "yarn add @rails/actioncable"
end
end

def pin_javascript_dependencies
Expand All @@ -82,7 +86,6 @@ def pin_javascript_dependencies
RUBY
end


def file_name
@_file_name ||= super.sub(/_channel\z/i, "")
end
Expand All @@ -95,8 +98,19 @@ def using_javascript?
@using_javascript ||= options[:assets] && root.join("app/javascript").exist?
end

def using_js_runtime?
@using_js_runtime ||= root.join("package.json").exist?
end

def using_bun?
# Cannot assume bun.lockb has been generated yet so we look for
# a file known to be generated by the jsbundling-rails gem
@using_bun ||= using_js_runtime? && root.join("bun.config.js").exist?
end

def using_node?
@using_node ||= root.join("package.json").exist?
# Bun is the only runtime that _isn't_ node.
@using_node ||= using_js_runtime? && !root.join("bun.config.js").exist?
end

def using_importmap?
Expand Down
21 changes: 19 additions & 2 deletions actiontext/lib/generators/action_text/install/install_generator.rb
Expand Up @@ -9,8 +9,10 @@ class InstallGenerator < ::Rails::Generators::Base
source_root File.expand_path("templates", __dir__)

def install_javascript_dependencies
if Pathname(destination_root).join("package.json").exist?
say "Installing JavaScript dependencies", :green
say "Installing JavaScript dependencies", :green
if using_bun?
run "bun add @rails/actiontext trix"
elsif using_node?
run "yarn add @rails/actiontext trix"
end
end
Expand Down Expand Up @@ -66,6 +68,21 @@ def create_migrations
rails_command "railties:install:migrations FROM=active_storage,action_text", inline: true
end

def using_js_runtime?
@using_js_runtime ||= Pathname(destination_root).join("package.json").exist?
end

def using_bun?
# Cannot assume yarn.lock has been generated yet so we look for
# a file known to be generated by the jsbundling-rails gem
@using_bun ||= using_js_runtime? && Pathname(destination_root).join("bun.config.js").exist?
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Memoization only helps here if this returns true. Is there a big performance gain to memoizing @terracatta? If not we could remove it to simplify the code.

end

def using_node?
# Bun is the only runtime that _isn't_ node.
@using_node ||= using_js_runtime? && !Pathname(destination_root).join("bun.config.js").exist?
end

hook_for :test_framework
end
end
Expand Down
5 changes: 4 additions & 1 deletion actionview/app/javascript/README.md
Expand Up @@ -15,6 +15,9 @@ Note that the `data` attributes this library adds are a feature of HTML5. If you

## Installation

### Bun
bun add @rails/ujs

### npm

npm install @rails/ujs --save
Expand All @@ -37,7 +40,7 @@ In a conventional Rails application that uses the asset pipeline, require `rails

### ES2015+

If you're using the Webpacker gem or some other JavaScript bundler, add the following to your main JS file:
If you're using a JavaScript bundler, add the following to your main JS file:

```javascript
import Rails from "@rails/ujs"
Expand Down
4 changes: 2 additions & 2 deletions guides/source/asset_pipeline.md
Expand Up @@ -1050,9 +1050,9 @@ We are aware that there are no one-size-fits-it-all solutions for the various Ja

### jsbundling-rails

[`jsbundling-rails`](https://github.com/rails/jsbundling-rails) is a Node.js dependent alternative to the `importmap-rails` way of bundling JavaScript with [esbuild](https://esbuild.github.io/), [rollup.js](https://rollupjs.org/), or [Webpack](https://webpack.js.org/).
[`jsbundling-rails`](https://github.com/rails/jsbundling-rails) is a JavaScript run-time dependent alternative to the `importmap-rails` way of bundling JS with [Bun](https://bun.sh), [esbuild](https://esbuild.github.io/), [rollup.js](https://rollupjs.org/), or [Webpack](https://webpack.js.org/).

The gem provides `yarn build --watch` process to automatically generate output in development. For production, it automatically hooks `javascript:build` task into `assets:precompile` task to ensure that all your package dependencies have been installed and JavaScript has been built for all entry points.
The gem provides a build task in `package.json` to watch for changes and automatically generate output in development. For production, it automatically hooks `javascript:build` task into `assets:precompile` task to ensure that all your package dependencies have been installed and JavaScript has been built for all entry points.

**When to use instead of `importmap-rails`?** If your JavaScript code depends on transpiling so if you are using [Babel](https://babeljs.io/), [TypeScript](https://www.typescriptlang.org/) or React `JSX` format then `jsbundling-rails` is the correct way to go.

Expand Down
2 changes: 1 addition & 1 deletion guides/source/upgrading_ruby_on_rails.md
Expand Up @@ -44,7 +44,7 @@ Repeat this process until you reach your target Rails version.
To move between versions:

1. Change the Rails version number in the `Gemfile` and run `bundle update`.
2. Change the versions for Rails JavaScript packages in `package.json` and run `yarn install`, if running on Webpacker.
2. Change the versions for Rails JavaScript packages in `package.json` and run `bin/rails javascript:install` if running jsbundling-rails
3. Run the [Update task](#the-update-task).
4. Run your tests.

Expand Down
33 changes: 25 additions & 8 deletions guides/source/working_with_javascript_in_rails.md
Expand Up @@ -10,7 +10,7 @@ Rails.
After reading this guide, you will know:

* How to use Rails without the need for a Node.js, Yarn, or a JavaScript bundler.
* How to create a new Rails application using import maps, esbuild, rollup, or webpack to bundle
* How to create a new Rails application using import maps, bun, esbuild, rollup, or webpack to bundle
your JavaScript.
* What Turbo is, and how to use it.
* How to use the Turbo HTML helpers provided by Rails.
Expand Down Expand Up @@ -67,16 +67,16 @@ Adding NPM Packages with JavaScript Bundlers

Import maps are the default for new Rails applications, but if you prefer traditional JavaScript
bundling, you can create new Rails applications with your choice of
[esbuild](https://esbuild.github.io/), [webpack](https://webpack.js.org/), or
[rollup.js](https://rollupjs.org/guide/en/).
[Bun](https://bun.sh), [esbuild](https://esbuild.github.io/),
[webpack](https://webpack.js.org/), or [rollup.js](https://rollupjs.org/guide/en/).

To use a bundler instead of import maps in a new Rails application, pass the `—javascript` or `-j`
option to `rails new`:

```bash
$ rails new my_new_app --javascript=webpack
$ rails new my_new_app --javascript=bun
OR
$ rails new my_new_app -j webpack
$ rails new my_new_app -j bun
```

These bundling options each come with a simple configuration and integration with the asset
Expand All @@ -85,10 +85,27 @@ pipeline via the [jsbundling-rails](https://github.com/rails/jsbundling-rails) g
When using a bundling option, use `bin/dev` to start the Rails server and build JavaScript for
development.

### Installing Node.js and Yarn
### Installing a JavaScript Runtime

If you are using a JavaScript bundler in your Rails application, Node.js and Yarn must be
installed.
If you are using a esbuild, rollup.js or Webpack, to bundle your JavaScript in
your Rails application, Node.js and Yarn must be installed. If you are using
Bun, then you just need to install Bun as it is both a JavaScript runtime and a bundler.

#### Installing Bun

Find the installation instructions at the [Bun website](https://bun.sh) and
verify it’s installed correctly with the following command:

```bash
$ bun --version
```

The version of your Bun runtime should be printed out. If it says something
like `1.0.0`, Bun has been installed correctly.

#### Installing Node.js and Yarn

If you are using esbuild, rollup.js or Webpack you will need Node.js and Yarn.

Find the installation instructions at the [Node.js website](https://nodejs.org/en/download/) and
verify it’s installed correctly with the following command:
Expand Down
8 changes: 8 additions & 0 deletions railties/CHANGELOG.md
@@ -1,3 +1,11 @@
* `rails new --javascript` generator now supports Bun

```bash
rails new my_new_app --javascript=bun
```

*Jason Meller*

* bin/setup uses `bun` instead of `yarn` when generated an app with bun

Use `bun install` on `bin/setup` when using `bun`.
Expand Down
43 changes: 28 additions & 15 deletions railties/lib/rails/generators/app_base.rb
Expand Up @@ -16,6 +16,7 @@ class AppBase < Base # :nodoc:
include AppName

NODE_LTS_VERSION = "18.15.0"
BUN_VERSION = "1.0.1"

attr_accessor :rails_template
add_shebang_option!
Expand Down Expand Up @@ -466,15 +467,17 @@ def hotwire_gemfile_entry
[ turbo_rails_entry, stimulus_rails_entry ]
end

def using_node?
return if using_bun?

def using_js_runtime?
(options[:javascript] && !%w[importmap].include?(options[:javascript])) ||
(options[:css] && !%w[tailwind sass].include?(options[:css]))
end

def using_node?
using_js_runtime? && !%w[bun].include?(options[:javascript])
end

def using_bun?
options[:javascript] == "bun"
using_js_runtime? && %w[bun].include?(options[:javascript])
end

def node_version
Expand All @@ -493,6 +496,12 @@ def dockerfile_yarn_version
"latest"
end

def dockerfile_bun_version
using_bun? and "bun-v#{`bun --version`[/\d+\.\d+\.\d+/]}"
rescue
BUN_VERSION
end

def dockerfile_binfile_fixups
# binfiles may have OS specific paths to ruby. Normalize them.
shebangs = Dir["bin/*"].map { |file| IO.read(file).lines.first }.join
Expand Down Expand Up @@ -532,9 +541,13 @@ def dockerfile_build_packages
# ActiveStorage preview support
packages << "libvips" unless skip_active_storage?

packages << "curl" if using_js_runtime?

packages << "unzip" if using_bun?

# node support, including support for building native modules
if using_node?
packages += %w(curl node-gyp) # pkg-config already listed above
packages << "node-gyp" # pkg-config already listed above

# module build process depends on Python, and debian changed
# how python is installed with the bullseye release. Below
Expand Down Expand Up @@ -575,12 +588,12 @@ def dockerfile_deploy_packages
def css_gemfile_entry
return unless options[:css]

if using_node? || using_bun?
GemfileEntry.floats "cssbundling-rails", "Bundle and process CSS [https://github.com/rails/cssbundling-rails]"
elsif options[:css] == "tailwind"
if !using_js_runtime? && options[:css] == "tailwind"
GemfileEntry.floats "tailwindcss-rails", "Use Tailwind CSS [https://github.com/rails/tailwindcss-rails]"
elsif options[:css] == "sass"
elsif !using_js_runtime? && options[:css] == "sass"
GemfileEntry.floats "dartsass-rails", "Use Dart SASS [https://github.com/rails/dartsass-rails]"
else
GemfileEntry.floats "cssbundling-rails", "Bundle and process CSS [https://github.com/rails/cssbundling-rails]"
end
end

Expand Down Expand Up @@ -676,8 +689,8 @@ def run_javascript
return if options[:skip_javascript] || !bundle_install?

case options[:javascript]
when "importmap" then rails_command "importmap:install"
when "webpack", "esbuild", "rollup" then rails_command "javascript:install:#{options[:javascript]}"
when "importmap" then rails_command "importmap:install"
when "webpack", "bun", "esbuild", "rollup" then rails_command "javascript:install:#{options[:javascript]}"
end
end

Expand All @@ -690,12 +703,12 @@ def run_hotwire
def run_css
return if !options[:css] || !bundle_install?

if using_bun? || using_node?
rails_command "css:install:#{options[:css]}"
elsif options[:css] == "tailwind"
if !using_js_runtime? && options[:css] == "tailwind"
rails_command "tailwindcss:install"
elsif options[:css] == "sass"
elsif !using_js_runtime? && options[:css] == "sass"
rails_command "dartsass:install"
else
rails_command "css:install:#{options[:css]}"
end
end

Expand Down
2 changes: 1 addition & 1 deletion railties/lib/rails/generators/rails/app/app_generator.rb
Expand Up @@ -277,7 +277,7 @@ class AppGenerator < AppBase
class_option :version, type: :boolean, aliases: "-v", group: :rails, desc: "Show Rails version number and quit"
class_option :api, type: :boolean, desc: "Preconfigure smaller stack for API only apps"
class_option :minimal, type: :boolean, desc: "Preconfigure a minimal rails app"
class_option :javascript, type: :string, aliases: ["-j", "--js"], default: "importmap", desc: "Choose JavaScript approach [options: importmap (default), webpack, esbuild, rollup]"
class_option :javascript, type: :string, aliases: ["-j", "--js"], default: "importmap", desc: "Choose JavaScript approach [options: importmap (default), bun, webpack, esbuild, rollup]"
class_option :css, type: :string, aliases: "-c", desc: "Choose CSS processor [options: tailwind, bootstrap, bulma, postcss, sass] check https://github.com/rails/cssbundling-rails for more options"
class_option :skip_bundle, type: :boolean, aliases: "-B", default: nil, desc: "Don't run bundle install"
class_option :skip_decrypted_diffs, type: :boolean, default: nil, desc: "Don't configure git to show decrypted diffs of encrypted credentials"
Expand Down
16 changes: 16 additions & 0 deletions railties/lib/rails/generators/rails/app/templates/Dockerfile.tt
Expand Up @@ -32,6 +32,15 @@ RUN curl -sL https://github.com/nodenv/node-build/archive/master.tar.gz | tar xz
rm -rf /tmp/node-build-master

<% end -%>

<% if using_bun? -%>
ENV BUN_INSTALL=/usr/local/bun
ENV PATH=/usr/local/bun/bin:$PATH
ARG BUN_VERSION=<%= dockerfile_bun_version %>
RUN curl -fsSL https://bun.sh/install | bash -s -- "${BUN_VERSION}"
terracatta marked this conversation as resolved.
Show resolved Hide resolved
terracatta marked this conversation as resolved.
Show resolved Hide resolved

<% end -%>

# Install application gems
COPY Gemfile Gemfile.lock ./
RUN bundle install && \
Expand All @@ -44,6 +53,13 @@ RUN bundle install && \
COPY package.json yarn.lock ./
RUN yarn install --frozen-lockfile

<% end -%>

<% if using_bun? -%>
# Install node modules
COPY package.json bun.lockb ./
RUN bun install --frozen-lockfile

<% end -%>
# Copy application code
COPY . .
Expand Down
Expand Up @@ -19,10 +19,10 @@ FileUtils.chdir APP_ROOT do

# Install JavaScript dependencies
system("yarn check --check-files") || system!("yarn install")
<% elsif using_bun? %>
# Install JavaScript dependencies
<% elsif using_bun? -%>

system("bun install")
# Install JavaScript dependencies
system!("bun install")
<% end -%>
<% unless options.skip_active_record? -%>

Expand Down