-
Notifications
You must be signed in to change notification settings - Fork 2
Upgrading from Sprockets to jsbundling‐rails
The asset pipeline continues to evolve. There's a somewhat-helpful blog post here that talks about it. It looks like the new Rails 7 does allow for the use of Sprockets, but Sprockets has trouble handling modern JavaScript functionality, including the ES-6 format.
We needed to update how Javascript is handled on Treatment Database to add modern functionality like Stimulus and adding a WYSIWYG text editor. Initially, I had planned to update just to Webpacker from Sprockets, but Webpacker has a security issue that is fixed by upgrading from Webpacker to webpack with jsbundling-rails. Therefore, I decided it would be better to skip the migration to Webpacker and go straight to jsbundling-rails from Sprockets.
Quick note: "Webpack" is NOT the same thing as "Webpacker". It's really easy to get these two confused, and both ChatGPT and Google Searches will often assume you meant "Webpacker" when you type "Webpack" to look things up. Be on the lookout for this so you don't end up wandering down the wrong path. We are only using Webpack.
jsbundling-rails uses Webpack behind the scenes to handle files. The Webpack documentation is surprisingly readable and can be found at https://webpack.js.org/concepts/.
This wiki page will talk about what was involved in the migration and how the new jsbundling-rails set-up is laid out, what the different components are, and why I made the choices I did. The following sections are meant to be reviewed in order, but you may need to jump around and refer back to them if you're attempting to upgrade a different app.
The following is based off what I did in the following PR: https://github.com/uclibs/treatment_database/pull/497
-
You'll need a recent version of Ruby. At the time of this PR, the most recent version of Ruby was 3.3.3, which has some security fixes on previous versions.
-
In order to run
yarn install
, we need to be on an updated version of Node. The current server version 12.22.12 is not recent enough to handle the changes. Before any of this would work, we need to get our server to run different versions of Node, and to have a .nvmrc file. This is discussed in the Node upgrade documentation
Installing Webpack and JSBundling Rails
The next step in this migration is to install and configure Webpack since jsbundling-rails utilizes Webpack under the hood to manage JavaScript bundling.
- If you have the gem
gem 'sprockets-rails'
in your Gemfile, keep it - it's needed for deployments still. - Add
gem 'jsbundling-rails'
to your Gemfile and runbundle install
. - Run
yarn add webpack webpack-cli
to add the Webpack and webpack-cli dependencies to your package.json file. - Run
rails javascript:install:webpack
to install and configure Webpack.
Updating the package.json file
The package.json file is a manifest for your JavaScript dependencies and project metadata, similar to how a Gemfile works for Ruby gems, specifying which packages to install and how to configure scripts and tools for a project.
It needs to have all the dependencies that will handle loading and running the front end of the app. Here's how ours ended up, after including all the dependencies for handling JavaScript, css, and images:
{
"name": "treatment_database",
"private": true,
"dependencies": {
"@rails/activestorage": "^7.1.3-2",
"@rails/ujs": "^7.1.3-2",
"babel-loader": "^9.1.3",
"bootstrap": "^4.3.1",
"jquery": "^3.5.1",
"mini-css-extract-plugin": "^2.9.0",
"popper.js": "^1.16.1",
"turbolinks": "^5.2.0",
"webpack": "^5.91.0",
"webpack-cli": "^5.1.4",
"webpack-manifest-plugin": "^5.0.0",
"@babel/core": "^7.24.5",
"@babel/preset-env": "^7.24.5",
"css-loader": "^7.1.1",
"file-loader": "^6.2.0",
"sass": "^1.77.0",
"sass-loader": "^14.2.1",
"style-loader": "^4.0.0"
},
"scripts": {
"build": "webpack --mode production --config webpack.config.js"
}
}
For any missing dependency that you want to add, run yarn add package-name@version
and that will add it to the "dependencies" section. It's fine to copy/paste these dependencies into your package.json file. If you do this, you will need to run yarn install
after changing the package.json file. That's similar to manually adding a gem to your Gemfile and then running bundle install
.
When a dependency is added, either by yarn add
or by adding manually to package.json and then running yarn install
, it will create or update a yarn.lock file. You don't edit anything in the yarn.lock file. This is the equivalent of the Gemfile.lock file, but for your front-end (yarn-managed) dependencies.
If you think you've messed something up or if things aren't running right, it's fine to delete the yarn.lock file and run yarn install
again to get a fresh installation of the dependencies.
In the sample package.json above, take a look at the section:
"scripts": {
"build": "webpack --mode production --config webpack.config.js"
}
This sets up the yarn build
command for you to run from the terminal. It says that when you run yarn build
it should use production mode for webpack, and that the config file for webpack is "webpack.config.js".
Creating a Webpack Config File
Think of Webpack like a pipeline, where your JavaScript, CSS, and image files enter in their raw, development-friendly format. Through this pipeline, they are transformed, optimized, and output as fewer, optimized files that are easier and faster for browsers to load. The configuration file webpack.config.js tells Webpack how to conduct this process in terms of:
- Where to start and end the process.
- How to handle different file types.
- How to name the output files for efficient caching.
- How to keep track of everything via a manifest.
Webpack assumes everything is in production mode. It has a development mode that could be slightly faster for development, but I found with our small Treatment Database that trying to manage both a production mode and a development mode added needless complexity, and running Webpack in production mode all the time worked fine.
It could be different with a larger codebase. There, you might see noticeable performance improvements for development mode.
The webpack.config.js file looks intimidating to someone not familiar with that format. Let's take a look at it. Here's how ours ended up:
const path = require('path');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const { WebpackManifestPlugin } = require('webpack-manifest-plugin');
module.exports = (env, argv) => {
const isProduction = argv.mode === 'production';
const publicPath = isProduction ? '/treatment_database/build/' : '/build/';
return {
devtool: 'source-map',
entry: {
application: './app/javascript/application.js'
},
output: {
path: path.resolve(__dirname, 'public', 'build'),
publicPath: publicPath,
filename: 'javascripts/[name]-[contenthash].js'
},
module: {
rules: [
{
test: /\.css$/,
use: [MiniCssExtractPlugin.loader, 'css-loader']
},
{
test: /\.scss$/,
use: [MiniCssExtractPlugin.loader, 'css-loader', 'sass-loader']
},
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env']
}
}
},
{
test: /\.(png|jpe?g|svg)$/i,
use: [
{
loader: 'file-loader',
options: {
outputPath: 'assets/images/',
publicPath: '/build/assets/images',
name: '[name]-[hash].[ext]',
},
},
],
},
]
},
plugins: [
new MiniCssExtractPlugin({
filename: 'stylesheets/[name]-[contenthash].css',
}),
new WebpackManifestPlugin({
publicPath: '/build/',
writeToFileEmit: true,
generate: (seed, files) => {
return files.reduce((manifest, file) => {
const name = path.basename(file.name, path.extname(file.name));
const ext = path.extname(file.name);
manifest[name + ext] = file.path.replace(/^.*\/build\//, '');
return manifest;
}, seed);
}
})
],
};
}
Let's break it down into chunks. Here's the main points:
-
entry: Specifies the starting point of your application. Webpack will begin its process here, in our case with ./app/javascript/application.js.
-
output: Defines where the bundled files will be placed. In this setup:
- path: Where to output the files on the disk (under public/build in your project directory).
- publicPath: The base path for all assets within the application. We configured ours with two different versions to handle Rails production mode and development mode.
- filename: Pattern for naming the bundled JavaScript files, where [name] is replaced by the entry name and [contenthash] helps in cache busting by appending a unique hash generated from the file content.
module.rules: these are the instructions for how to process different types of files. When you see a list of things after the "use" line, it uses the last item first, then the next to last item, etc. So [MiniCssExtractPlugin.loader, 'css-loader'] gets executed first with 'css-loader' and then with the MiniCssExtractPlugin.loader. This is relevant if you're troubleshooting why something won't compile.
- CSS: Uses MiniCssExtractPlugin.loader and css-loader to process .css files. The plugin extracts CSS into separate files.
- Sass/SCSS: Similar to CSS but includes sass-loader to handle Sass files.
- JavaScript: Uses babel-loader to transpile modern JavaScript to backward-compatible versions using Babel, particularly focusing on @babel/preset-env for handling modern JavaScript syntax.
- Images: Uses file-loader for processing image files like PNG, JPEG, and SVG, placing them in a specific directory and modifying the file names to include a hash.
The end result of these rules is that we've told Webpack how to handle css, scss, javascript, and image files. These should not need to be handled through Sprockets anymore, and any reference to them in the old Sprockets setup (app/assets/javascripts/*) can be removed.
-
MiniCssExtractPlugin: Extracts CSS into separate files named according to the [name]-[contenthash].css pattern for caching purposes.
-
WebpackManifestPlugin: Generates a manifest file, useful for managing assets. It maps the original file names to the output filenames, which can include a hash. This plugin is especially handy for integrating with Rails' asset management.
Creating a New application.js File
A new application.js file was created to serve as the main entry point for all JavaScript. This file integrates various libraries and modules. It needs to be located at `app/javascript/application.js`. After we run `yarn build` there will be a copy of the compiled code in the `public/build/javascripts` directory, but we only modify the one at app/javascript.
The main point of the application.js file is to load the javascript and the front-end libraries. This is how ours looks right now:
// Import Rails libraries
import Rails from '@rails/ujs';
import * as ActiveStorage from '@rails/activestorage';
import Turbolinks from 'turbolinks';
Rails.start();
ActiveStorage.start();
Turbolinks.start();
// Import jQuery and other libraries
import jQuery from 'jquery';
window.jQuery = jQuery;
window.$ = jQuery;
// Import Popper
import Popper from 'popper.js';
window.Popper = Popper;
// Import Bootstrap JavaScript
import 'bootstrap';
// Import custom CSS
import './stylesheets/application.scss';
// Import all the images in the images file:
function importAll(r) {
r.keys().forEach(r);
}
importAll(require.context('./images/', false, /\.(png|jpe?g|svg)$/));
The order of the imports and starting matters. First we start rails, then active_storage, then turbolinks. Bootstrap also needs to be loaded before the stylesheets. If you're getting errors that something isn't available when you're sure you loaded it, it's worth your time to check the order that you are importing and loading your dependencies.
Some Notes About Bootstrap
Our app is currently set up to use Bootstrap 4. We will need to upgrade to 5 soon, and some of this may not apply to Bootstrap 5. Bootstrap 4 needs to be imported in several places...
- Bootstrap uses JavaScript for things like opening and closing modals and drop-down lists. If we don't import it like a javascript dependency, we will lose all functionality. In application.js:
// Import Bootstrap JavaScript
import 'bootstrap';
- We need to load the node modules. In package.json:
"dependencies": {
"bootstrap": "^4.3.1"
},
- We import it from our node modules and need to use a relative path for the import. In app/javascript/stylesheets/application.scss:
@import "~bootstrap/scss/bootstrap";
Initially, I hadn't wanted to load our styling and images through Webpack. Unfortunately, the JavaScript portion of Bootstrap broke with our migration to Webpack, and I was forced to load it in the dual method described above. It does not work with Sprockets anymore in our setup.
Updating .circleci/config.yml
To accommodate the changes in JavaScript management, the .circleci/config.yml file was updated to include steps for installing the Node.js and yarn dependencies and building assets using Webpack. See Updating Node with NVM on the Server for more information about using Node with CircleCI.
Changes: Because of the timing of this change, we were forced to update to Ruby from 3.3.0 to 3.3.3 for security updates. While this didn't change much for running it locally, we did have to make several changes to the .circleci/config.yml file on top of the changes needed to compile the code properly. Here are the changes made:
- Update the Ruby version to 3.3.1
jobs:
build:
docker:
# specify the version you desire here
- image: cimg/ruby:3.3.3
- Manually install node (using the .nvmrc file) and yarn. (See Updating Node with NVM on the Server)
steps:
- run:
name: Install NVM and Node.js
command: |
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash
echo 'export NVM_DIR="$HOME/.nvm"' >> $BASH_ENV
echo '[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" --install' >> $BASH_ENV
echo '[ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion"' >> $BASH_ENV
source $BASH_ENV
nvm install $(cat .nvmrc)
nvm use $(cat .nvmrc)
echo "Node Version Manager (NVM) installed successfully."
echo "NVM version: $(nvm --version)"
echo "Node.js version: $(node --version)"
- run:
name: Install Yarn
command: |
source $BASH_ENV
nvm use
npm install -g yarn
echo "Yarn installed successfully."
echo "Yarn version: $(yarn --version)"
- Before the tests run, do a
yarn install
andyarn build
.
- run:
name: Install Yarn Dependencies
command: |
echo "Installing Yarn dependencies..."
source $BASH_ENV
nvm use
yarn install
- run:
name: Run Yarn Build
command: |
echo "Running Yarn build..."
source $BASH_ENV
nvm use
yarn build
Each task of the CircleCI build is run as if it's in a new, separate terminal or shell. That means that you can set the node version in one place and it won't remain set unless you set it again in your newest task. That's why we keep repeating source $BASH_ENV
and nvm use
.
Updating the SCSS files
Transitioning to jsbundling-rails and Webpack required updates to how SCSS files are managed:
- Moved the stylesheets from app/assets/stylesheets to app/javascript/stylesheets.
- We used to use a generic
*.*
import previously for the files now located at app/assets/stylesheets/partials. That type of import is no longer supported, so I made an "index" file at app/javascript/stylesheets/partials/_index.scss and imported that to aplication.scss directly after the new format bootstrap import:
# application.scss
@import "~bootstrap/scss/bootstrap";
@import "partials/index";
- Configured Webpack to compile SCSS files into CSS, including setting up loaders such as sass-loader and css-loader. See the "rules" part in the section above on creating a webpack config file.
Updating the Image files
Image files are now being served through jsbundling-rails and webpack as well.
- Moved all image assets from app/assets/images to app/javascript/images.
- Configured webpack to serve these files. See the "rules" part in the section above on the webpack config file.
Building the Front End & the Manifest
Before we can launch the program with rails server
, we need to build the front end. This is done through the console command yarn build
. Once that has been successfully done, we can run rails server
.
When assets are compiled with yarn build
, a compiled version of the code is saved in public/build/
.
- The images are now copied over to
public/build/assets/images/
. - The stylesheets are compiled at
public/build/stylesheets/
. - The application.js file is now at
public/build/javascripts/
.
The file names, instead of being something like application.js
, will now be hashed like this: application-4585d1d8c24d86edd3f6.js
. This is referred to as application-[hash].js
. The hash is regenerated every time we run yarn build
, which makes it tricky to try to link to the files. To solve this problem, yarn build
will also create a manifest.
It looks like this:
{
"application.css": "stylesheets/application-79e9558c20b88067884b.css",
"application.js": "javascripts/application-4585d1d8c24d86edd3f6.js",
"delete.svg": "assets/images/delete-5e9b43c1abd7ceb1ad6f82587130173e.svg",
"delete.png": "assets/images/delete-72ccf5711928f0c4d59f5d285c0855c6.png",
"application-css.map": "stylesheets/application-79e9558c20b88067884b.css.map",
"application-js.map": "javascripts/application-4585d1d8c24d86edd3f6.js.map"
}
The manifest is used to help map out the locations of files. When we go to look for application.css
, we use the manifest to locate the most recent hashed version at stylesheets/application-79e9558c20b88067884b.css
. To make this process easier, we created several helper methods. (See the next section.)
Creating Helper Functions
There are three new helper functions at app/helpers/application_helper.rb
: one to generate the path to application.js, one to generate the path to the stylesheet address, and one to dynamically get the addresses for images.
# Creates and returns the path for an image being handled by webpack
# Use like: <%= image_tag webpack_image_path('delete.png'), class: 'delete-icon', alt: 'Delete' %>
# Exact image_name to be passed in can be determined from the manifest.json file in the public/build directory
def webpack_image_path(image_name)
# Determine the base path based on the environment
base_path = Rails.env.production? ? '/treatment_database/build' : '/build'
manifest_path = Rails.public_path.join('build/manifest.json')
begin
manifest = JSON.parse(File.read(manifest_path))
"#{base_path}/#{manifest[image_name]}"
rescue StandardError
"#{base_path}/#{image_name}" # Fallback if there's an error reading the manifest or the key is not found
end
end
# Creates and returns the path for a stylesheet file being handled by webpack
def webpack_stylesheet_path
# Determine the base path based on the environment
base_path = Rails.env.production? ? '/treatment_database/build' : '/build'
manifest_path = Rails.public_path.join('build/manifest.json')
manifest = JSON.parse(File.read(manifest_path))
"#{base_path}/#{manifest['application.css']}"
rescue StandardError
"#{base_path}/stylesheets/application.css" # Fallback if manifest is missing or key is not found
end
# Creates and returns the path for the javascript file being handled by webpack
def webpack_javascript_path
# Determine the base path based on the environment
base_path = Rails.env.production? ? '/treatment_database/build' : '/build'
manifest_path = Rails.public_path.join('build/manifest.json')
manifest = JSON.parse(File.read(manifest_path))
"#{base_path}/#{manifest['application.js']}"
rescue StandardError
"#{base_path}/javascripts/application.js" # Fallback if manifest is missing or key is not found
end
end
We used to have these tag links in our application.html.erb
file:
<%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %>
<%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %>
There are some new tags supported by webpack, but I couldn't get them to work. Instead, this solution is what works:
<link rel="stylesheet" href="<%= webpack_stylesheet_path %>" media="all" data-turbolinks-track="reload">
<script src="<%= webpack_javascript_path %>" type="module" data-turbo-track="reload"></script>
The image files are now linked in this fashion:
<%= image_tag webpack_image_path('delete.png'), class: 'delete-icon', alt: 'Delete' %>
Updating .gitignore
The build
folder should not be pushed up to Github. The following was added to .gitignore:
# Ignore everything in public/build
/public/build/*
Using Rake Tasks in Capistrano
Rake is a Ruby-based build language, similar to make in UNIX. It allows you to write tasks in Ruby, which can be executed from the command line. Capistrano uses Rake tasks extensively to handle the automation of deployment processes. These tasks can include steps such as setting up directories on the server, updating the codebase from a version control system, restarting services, and much more.
To write a custom Rake task for use with Capistrano, you typically define tasks in a file within the lib/capistrano/tasks
directory of your project. Here’s a simple example of a custom task:
# lib/capistrano/tasks/my_custom_task.rake
namespace :deploy do
desc 'My custom task description'
task :my_custom_task do
on roles(:all) do
execute :echo, 'Hello from Capistrano'
end
end
end
-
namespace works kind of like a module, where you are going to describe 1 or more tasks within. When you are calling your custom task in a hook, this is the first part of the call. Use the
:namespace
format. -
desc short description of the task's purpose. It is a string. This is shown if you list all tasks with the command
cap --tasks
. -
task the name of the task, which you call in order to execute all code within this task's block. Use
:task_name
format.
More about roles in Capistrano
Roles: In Capistrano, roles are used to define different responsibilities or types of services your servers provide in your deployment environment. Common roles include `:web` for servers that handle web traffic, `:app` for application servers, and `:db` for databases. These roles help target specific commands to only relevant servers, enhancing efficiency and security.On roles(:all) do
Usage: By using on roles(:all), you instruct Capistrano to execute the following commands on every server involved in the deployment, regardless of its specific role. This is particularly useful for tasks that need to be universally applied, such as setting environment variables, deploying code, or performing system-wide updates.
Capistrano executes each command as a separate shell script. This means every line in a Capistrano task that uses the execute
method runs in a new shell session on the remote server. This isolation helps prevent errors from one command affecting others but requires careful management of environment settings and paths.
Hooks in Capistrano allow you to execute tasks at specific points in the deployment process. For example, you can run a custom task before or after updating the code, restarting the application, or performing cleanup tasks. Here’s how you might add a hook to run a custom task before the assets are precompiled:
# config/deploy.rb
before 'deploy:compile_assets', 'deploy:my_custom_task'
This setup ensures that your custom task runs before Capistrano starts the asset compilation process.
This is discussed extensively in Updating Node with NVM on the Server. Please refer to that documentation for details.
The yarn:build Rake Task
The yarn build command prepares your web application for production by running scripts defined in package.json. These scripts typically compile, bundle, and minify JavaScript and CSS files to improve performance. The specific tools and tasks used, like Webpack or Babel, depend on your project's configuration.
After running, yarn build places the optimized files in a build or dist directory. These files are then moved to the public folder, ready to be served. This process is crucial for reducing load times and ensuring your application performs well for users.
namespace :yarn do
desc 'Build yarn packages'
task :build do
on roles(:all) do
within release_path do
execute :echo, 'Sourcing NVM and running yarn build'
execute "source ~/.nvm/nvm.sh && nvm use $(cat #{release_path}/.nvmrc) && cd #{release_path} && RAILS_ENV=production yarn build"
end
end
end
end
This yarn task makes sure to source the NVM and set the Node version in the same execute statement to maintain the correct environment settings throughout the command execution. It is called like this in deploy.rb:
after 'deploy:updated', 'yarn:build'
Cleaning Up Old Assets
The deploy process was not removing the multiple hashed files created in public/assets and public/build with each deploy, and they were just piling up. The deployment will create a public, compressed version of the stylesheets and JavaScript files and create or use the public/build and public/assets directories. Nothing is needed to be kept from deployment to deployment. To prevent the public directory from becoming cluttered with outdated files, I created a task to clear old assets:
namespace :deploy do
desc 'Remove old assets'
task :clear_assets do
on roles(:web) do
execute :rm, '-rf', release_path.join('public/assets/*')
execute :rm, '-rf', release_path.join('public/build/*')
end
end
end
This task is called after deploy:confirmation, ensuring that it runs only after deployment parameters have been confirmed:
after 'deploy:confirmation', 'deploy:clear_assets'
Setting up Our Capistrano Deploy
Each environment (production, deploy, test) has its own config file that has specific instructions for setting up that particular environment's deploy. The files are at: `config/deploy/qa.rb`, etc. These files include some hooks for custom rake tasks already. I didn't modify anything about them.
The file at config/deploy.rb
applies to each environment. It currently contains the custom hooks previously created, as well as some general set-up that applies to all environments. This file is where I put the new hooks to the new custom methods and where I modified the deploy:compile_assets
task with my NVMIntegration module. The changes look like this:
# frozen_string_literal: true
.
.
.
# Other setup previously present including rake tasks
.
.
.
after 'git:create_release', 'nvm:load'
after 'nvm:load', 'nvm:setup'
before 'deploy:starting', 'deploy:confirmation'
after 'deploy:confirmation', 'deploy:clear_assets'
after 'deploy:updated', 'yarn:build'
I moved Sean's confirmation task to lib/capistrano/tasks/deploy.rake
because I had other tasks that belonged in the "deploy" namespace. I made sure it was still called before 'deploy:starting'
- only the location of the file changed.
All custom rake tasks are at lib/capistrano/tasks/
. This is the default location where Rails expects to find these tasks, and we had already set up Capistrano to look there for tasks in our Capfile
with the line
# Load custom tasks from `lib/capistrano/tasks` if you have any defined
Dir.glob('lib/capistrano/tasks/*.rake').each { |r| import r }`
For review, here are the tasks we currently have:
deploy.rake
# frozen_string_literal: true
namespace :deploy do
desc 'Confirmation before deploy'
task :confirmation do
stage = fetch(:stage).upcase
branch = fetch(:branch)
puts <<-WARN
========================================================================
*** Deploying to branch `#{branch}` to #{stage} server ***
WARNING: You're about to perform actions on #{stage} server(s)
Please confirm that all your intentions are kind and friendly
========================================================================
WARN
ask :value, "Sure you want to continue deploying `#{branch}` on #{stage}? (Y or Yes)"
unless fetch(:value).match?(/\A(?i:yes|y)\z/)
puts "\nNo confirmation - deploy cancelled!"
exit
end
end
desc 'Remove old assets'
task :clear_assets do
on roles(:web) do
execute :rm, '-rf', release_path.join('public/assets/*')
execute :rm, '-rf', release_path.join('public/build/*')
end
end
end
nvm.rake
# frozen_string_literal: true
namespace :nvm do
task :load do
on roles(:all) do
within release_path do
execute :echo, 'Sourcing NVM, installing Node version, and setting Node version'
execute "source ~/.nvm/nvm.sh && nvm install $(cat #{release_path}/.nvmrc) && nvm use $(cat #{release_path}/.nvmrc)"
end
end
end
end
nvm_integration.rake
(Discussed more thoroughly at https://github.com/uclibs/treatment_database/wiki/Updating-Node-with-NVM-on-the-Server)
# frozen_string_literal: true
namespace :nvm do
task :setup do
on roles(:web) do
SSHKit.config.command_map.prefix[:rake] ||= []
begin
execute :echo, 'Checking .nvmrc presence...'
if test("[ -f #{release_path}/.nvmrc ]")
execute :echo, 'Sourcing NVM and setting Node version...'
SSHKit.config.command_map.prefix[:rake].unshift("source ~/.nvm/nvm.sh && nvm use $(cat #{release_path}/.nvmrc) &&")
else
error "No .nvmrc file found in #{release_path}"
exit 1
end
rescue SSHKit::Command::Failed => e
error "NVM setup failed: #{e.message}"
raise
end
end
end
end
yarn.rake
# frozen_string_literal: true
namespace :yarn do
task :build do
on roles(:all) do
within release_path do
execute :echo, 'Sourcing NVM and running yarn build'
execute "source ~/.nvm/nvm.sh && nvm use $(cat #{release_path}/.nvmrc) && cd #{release_path} && RAILS_ENV=production yarn build"
end
end
end
end
Thanks for taking the time to read through this rather lengthy explanation. I hope I answered all your questions about how we migrated to jsbundling-rails. If anything needs clarification or expanding please let me know! -Janell