I'm trying to install toastr by using webpacker in Rails - ruby-on-rails

I'm trying to install toastr by using webpacker in Rails
Now, I'm using toastr by gem 'toastr-rails'
But replace it by using yarn add toastr
How I do setting app/javascript/packs/application.js?
This is application.js in my application
app/japascript/packs/application.js
import "jquery"
global.$ = require("jquery")
// JS-----
// install by yarn
import 'modaal/dist/js/modaal'
import 'flatpickr/dist/flatpickr'
require("jquery-ui-bundle")
// CSS ------
// install by yarn
import 'flatpickr/dist/flatpickr.min.css';
import 'font-awesome/css/font-awesome.min.css';
import 'jquery/dist/jquery';
import 'stylesheets/application';
import 'javascripts/application';
require.context('../images', true, /\.(png|jpg|jpeg|svg)$/);
$("#login-button").click(function(event){
event.preventDefault();
$('form').fadeOut(500);
$('.wrapper').addClass('form-success');
});
console.log('Hello World from Webpacker')
// Support component names relative to this directory:
var componentRequireContext = require.context("components", true)
var ReactRailsUJS = require("react_ujs")
ReactRailsUJS.useContext(componentRequireContext)
config/initializers/asset.rb
Do I have to add something in the asset.rb?
# Be sure to restart your server when you modify this file.
# Version of your assets, change this if you want to expire all your
assets.
Rails.application.config.assets.version = '1.0'
# Add additional assets to the asset load path.
# Rails.application.config.assets.paths << Emoji.images_path
# Add Yarn node_modules folder to the asset load path.
Rails.application.config.assets.paths <<
Rails.root.join('node_modules')
# Precompile additional assets.
# application.js, application.css, and all non-JS/CSS in the app/assets
# folder are already added.
# Rails.application.config.assets.precompile += %w( admin.js admin.css
)

to use toastr in rails app with webpacker:
install toastr with yarn: yarn add toastr
load lib in your application.js:
import toastr from 'toastr';
toastr.options = {
"closeButton": true
.... add options here ...
};
global.toastr = toastr;
create helper method (for example in your application_helper.rb file):
def custom_bootstrap_flash
flash_messages = []
flash.each do |type, message|
type = 'success' if type == 'notice'
type = 'error' if type == 'alert'
text = "toastr.#{type}('#{message}');"
flash_messages << text.html_safe if message
end
flash_messages = flash_messages.join('\n')
"<script>$(document).ready(function() { #{ flash_messages } });</script>".html_safe
end
use it on the bottom of your layout file (app/views/layouts/application.html.erb):
<%= custom_bootstrap_flash %>

This advice assumes you have a node_modules folder at the root of your rails application.
If you installed toastr with the command...
yarn add toastr
Then yarn will automatically generate a node_modules folder in your application root. This folder gets created the first time you add a dependency with yarn add.
Its possible that you already have this node_modules folder and you just cant see it. This is because by default rails adds this folder to .gitignore which makes it invisible in some file systems.
You can check to see if you have a node_modules folder by temporarily removing node_modules line inside your .gitignore file then refreshing your file tree.
You should be able to see the node_modules folder at this time. Add node_modules back to your .gitignore after you've confirmed that this folder exists in your project.
Once this is done then open your config/initializers/assets.rb file and add the following lines
Rails.application.config.assets.paths << Rails.root.join('node_modules')
Rails.application.config.assets.precompile += ['node_modules/toastr/build/toastr.min.js']
Rails.application.config.assets.precompile += ['node_modules/toastr/build/toastr.min.css']
Then add the following require statement to your application.js file
//= require toastr/build/toastr.min
And assuming your using .scss extensions on your css files then you should add the following to your application.scss file
#import 'toastr/build/toastr.min';
This works in my environment.
Note that toastr requires jquery be loaded before it is loaded.
As such you should make sure that you include the jquery script before your <%= javascript_include_tag 'application' %> tag inside your application.html.erb layout.

Related

Rails 7: Compiling assets with different folder structure

I am having a problem with my rails app. Today I upgraded from Rails 6 to Rails 7. In Rails 7 webpacker is kinda removed, so I am now using ESBuild. In my project I have TypeScript and SASS files. To compile these assets I am running the following script:
import esbuild from "esbuild";
import { sassPlugin } from "esbuild-sass-plugin";
import postcss from 'postcss';
import autoprefixer from 'autoprefixer';
// Generate CSS/JS Builds
esbuild
.build({
entryPoints: ['app/assets/scss/app.scss', 'app/assets/ts/app.ts'],
outdir: "dist",
bundle: true,
metafile: true,
plugins: [
sassPlugin({
async transform(source) {
const { css } = await postcss([autoprefixer]).process(source);
return css;
},
}),
],
})
.then(() => console.log("⚡ Build complete! ⚡"))
.catch(() => process.exit(1));
In the app/assets/config/manifest.js I have the following content:
// = link_tree ../images
// = link_tree ../scss .css
// = link_tree ../ts .js
// = link_tree ../builds
But I am using a different folder structure that is probably causing the issues. This is my folder structure for .scss and .ts files:
app/
└── assets/
├── scss/
│ ├── math/
│ │ └── calculate.scss
│ └── app.scss
└── ts/
├── math/
│ └── calculate.ts
└── app.ts
As you can see, under assets I have created the folders scss and ts, which makes more sense to me than putting the typescript in the javascript folder first. The problem is that when I use the include tag:
<%= javascript_include_tag 'math/calculate', 'data-turbolinks-track': 'reload' %>
The asset cannot be found, I think it is caused by the fact that it is still looking in the assets/javascript folder.
I can see in the public/assets that all my ts files are now .js files and all the .scss files are .css files, so ESBuild does his job, but the problem is in the including part I think. I get this error:
The asset "math/calculate.js" is not present in the asset pipeline.
Can someone help me fix this, I hope I can keep my folder structure like it is now!?
First, I have to mention that rails comes with tools to build javascript and css, we'll be doing our own set up, but these are still useful as a reference:
https://github.com/rails/jsbundling-rails
https://github.com/rails/cssbundling-rails
In Rails 7 js, css, and any other local assets are served by sprockets. For sprockets to find your assets they have to be in Rails.application.config.assets.paths; any directory under app/assets/ will be automatically be part of asset paths:
>> Rails.application.config.assets.paths
=> ["/home/alex/code/SO/ts/app/assets/builds",
"/home/alex/code/SO/ts/app/assets/config",
"/home/alex/code/SO/ts/app/assets/scss",
"/home/alex/code/SO/ts/app/assets/ts",
...
Most of the time an app would serve application.js and application.css. Any assets that go into javascript_include_tag, stylesheet_link_tag or anything else that needs to be served in the browser, also have to be declared for precompilation. For js and css these are the entrypoints, where you #import other files to be part of a bundle:
# app/assets/config/manifest.js
//= link_tree ../builds
app/assets/builds directory is in asset paths and every file in that directory will be precompiled in production. Because sprockets works with js and css, there has to be an additional build step to compile ts => js and scss => css.
The asset source files can be anywhere and organized however you like, rails doesn't care about them, as long as compiled assets end up in app/assets/builds.
CSS:
// esbuild.css.js
import esbuild from "esbuild";
import { sassPlugin } from "esbuild-sass-plugin";
import postcss from "postcss";
import autoprefixer from "autoprefixer";
const watch = process.argv.includes("--watch");
esbuild
.build({
// declare you entrypoint, where, I hope, you're importing all other styles.
entryPoints: ["app/assets/scss/app.scss"],
// spit out plain css into `builds`, so that sprockets can serve them.
outdir: "app/assets/builds",
// you'll want this running with --watch flag
watch: watch,
publicPath: "assets",
bundle: true,
metafile: true,
plugins: [
sassPlugin({
async transform(source) {
const { css } = await postcss([autoprefixer]).process(source);
return css;
},
}),
],
})
.then(() => console.log("⚡ CSS build complete! ⚡"))
.catch(() => process.exit(1));
JS:
// esbuild.js
import esbuild from "esbuild";
import glob from "glob";
const watch = process.argv.includes("--watch");
esbuild
.build({
// you say you want every file compiled separately, best I could come up:
entryPoints: glob.sync("app/assets/ts/**/*.ts"),
// spit out plain js into `builds`
outdir: "app/assets/builds",
watch: watch,
publicPath: "assets",
bundle: true,
metafile: true,
})
.then(() => console.log("⚡ JS build complete! ⚡"))
.catch(() => process.exit(1));
Rails compiles js and css separately, and so can we. This also avoids nested build/ts/ and build/scss/ directories.
In case you don't have this:
# bin/dev
#!/usr/bin/env sh
if ! gem list foreman -i --silent; then
echo "Installing foreman..."
gem install foreman
fi
exec foreman start -f Procfile.dev "$#"
# Procfile.dev
web: bin/rails server
_js: yarn build --watch
css: yarn build:css --watch
Add build scripts:
// package.json
{
...
"scripts": {
"build": "node esbuild.js",
"build:css": "node esbuild.css.js"
}
}
I think this should be everything:
app/assets
├── builds # <= this is the only directory "servable" by sprockets
├── config
│ └── manifest.js # //= link_tree ../builds
├── scss
│ ├── app.scss # #import "./math/calculate.scss"
│ └── math
│ └── calculate.scss
└── ts
├── app.ts # no imports here? ¯\_(ツ)_/¯
└── math
└── calculate.ts # console.log("do the calc")
You can run bin/dev to start the server and start compiling scss and ts:
$ bin/dev
or run build scripts manually:
$ yarn build && yarn build:css
⚡ JS build complete! ⚡
⚡ CSS build complete! ⚡
And it compiles into builds directory:
app/assets/builds
├── app.css
├── app.js
└── math
└── calculate.js
These ^ are the assets you can use in your layout:
<%= stylesheet_link_tag "app", "data-turbo-track": "reload" %>
<%= javascript_include_tag "app", "math/calculate", "data-turbo-track": "reload", defer: true %>
Now, when you ask for math/calculate, sprockets will find it in Rails.application.config.assets.paths and it will check if it is declared for precompilation:
# NOTE: if it is, you get a digested url to that file
>> helper.asset_path("math/calculate.js")
=> "/assets/math/calculate-11273ac5ce5f76704d22644f4b03b94908a318451578f2d10a85847c0f7f2998.js"
# everything as expected here
>> puts URI.open(helper.asset_url("math/calculate.js", host: "http://localhost:5555")).read
(() => {
// app/assets/ts/math/calculate.ts
console.log("do the calc");
})();
Hook your custom build scripts into a few rails tasks, like assets:precompile so that everything gets built automatically when deploying:
https://github.com/rails/jsbundling-rails/blob/v1.1.1/lib/tasks/jsbundling/build.rake
# lib/tasks/build.rake
namespace :ts_scss do
desc "Build your TS & SCSS bundle"
task :build do
# put your build commands here VVVVVVVVVVVVVVVVVVVVVVVVVVVV
unless system "yarn install && yarn build && yarn build:css"
raise "Command build failed, ensure yarn is installed"
end
end
end
if Rake::Task.task_defined?("assets:precompile")
Rake::Task["assets:precompile"].enhance(["ts_scss:build"])
end
if Rake::Task.task_defined?("test:prepare")
Rake::Task["test:prepare"].enhance(["ts_scss:build"])
elsif Rake::Task.task_defined?("spec:prepare")
Rake::Task["spec:prepare"].enhance(["ts_scss:build"])
elsif Rake::Task.task_defined?("db:test:prepare")
Rake::Task["db:test:prepare"].enhance(["ts_scss:build"])
end
OMG! Do not precompile assets in development! I don't know where people got that idea. You'll be serving assets from public/assets and they will not update automatically. If you did, just undo it:
bin/rails assets:clobber
Long story short:
app/assets/ts/app.ts # compile to `js` with esbuild
V # output into builds
app/assets/builds # is in `Rails.application.config.assets.paths`
V # is in `manifest.js` (//=link_tree ../builds)
javascript_include_tag("app")
V
asset_path("app.js") # served by `sprockets`
V # or by something else in production (thats why we precompile)
Browser!

How to precompile a pdf asset in Rails?

In Rails 3.2.11 app I'm trying to publish my app to Heroku.
In the assets folder I have a pdf subfolder with some pdf files inside.
In my production.rb file I have added the following:
config.assets.precompile += %w[*.png *.jpg *.jpeg *.gif *.pdf]
config.assets.precompile += ["*.js"]
config.assets.precompile += ["*.css"]
config.assets.precompile += ['pdf/*']
config.assets.precompile += %w( ricerca_wg.pdf )
If I check the pdf assets paths on my console I get:
Rails.application.config.assets.paths
# [
# "/Users/Augusto/Sites/wisegrowth/app/assets/images",
# "/Users/Augusto/Sites/wisegrowth/app/assets/javascripts",
# "/Users/Augusto/Sites/wisegrowth/app/assets/pdf",
# "/Users/Augusto/Sites/wisegrowth/app/assets/stylesheets",
# "/Users/Augusto/Sites/wisegrowth/vendor/assets/javascripts",
# "/Users/Augusto/Sites/wisegrowth/vendor/assets/stylesheets",
# "/Users/Augusto/.rvm/gems/ruby-1.9.3-p551/gems/jquery-rails-2.3.0/vendor/assets/javascripts",
# "/Users/Augusto/.rvm/gems/ruby-1.9.3-p551/gems/coffee-rails-3.2.2/lib/assets/javascripts",
# "/Users/Augusto/.rvm/gems/ruby-1.9.3-p551/gems/formtastic-2.1.1/app/assets/stylesheets"
# ]
But when I run
rake assets:precompile RAILS_ENV=production
everything is precompiled BUT the pdf files and in my production app on Heroku I get the following error:
ActionView::Template::Error (ricerca_wg.pdf isn't precompiled):
I don't think a pdf must be "precompiled".
if you just want to access the pdf from your app without using another service like S3, you can just put that pdf folder on your public folder of the rails app, and they will be available on the app as an static file.
www.domain.com/pdf/ricerca_wg.pdf
just be sure that the public/pdf folder isn't in the gitignore and it must work.
I believe the ricerca_wg.pdf is under /Users/Augusto/Sites/wisegrowth/app/assets/pdf/? If not, simply
remove config.assets.precompile += %w( ricerca_wg.pdf )
move ricerca_wg.pdf under /Users/Augusto/Sites/wisegrowth/app/assets/pdf/ - it should be precompiled along with other pdf files from this directory

Can you configure Rails to precompile ALL files under a directory

Yes, there are a million questions asking how to register a directory for the assets pipeline. But my question is slightly different...
I'm building a basic theme system and the file structure will look like this:
app/assets/skins/
some_theme/
style.css.scss
fonts/
font1.woff
font2.woff
images/
whocares.png
another_theme/
...
As you can see, the theme-specific assets are bundled in their own directories. When I new theme gets added, I don't want it to require any tinkering with the configurations.
Can I configure the asset pipeline to search/precompile ALL the files under app/assets/skins/?
I think I want something like this in application.rb...
config.assets.paths << Rails.root.join('app', 'assets', 'skins', '**')
...but this isn't how it works. How does it work?
To include all your scripts: add to your app/assets/javascripts/application.js:
//= require_tree ../skins
To include all your styles: add to your app/assets/stylesheets/application.css:
*= require_tree ../skins
Or the equivalent syntax for sass (better than using comments):
#import '../skins/**/*'
To include all other assets add to config/initializers/assets.rb:
Dir.glob( Rails.root.join( 'app', 'assets', 'skins', '**' ) ).each do |path|
Rails.application.config.assets.paths << path
end
And to access for example app/assets/skins/a_theme/skin.png you can use view helpers like:
<%= image_tag 'skin.png' %>
And in your sass files helpers like:
background-image: image-url('skin.png')
The same for fonts assets.
UPDATE: just to clear a point: with Dir.glob if 2 images have the same name in different paths only the first in paths list will be used

How to precompile assets and avoid "Error: file isn't precompiled" on specific pages?

When I push my code to Heroku I get the following message:
Precompiling assets failed, enabling runtime asset compilation
Runtime compilation can cause issues, so I added the following line to config/application.rb:
config.assets.initialize_on_precompile = false
(As suggested at Error pushing to heroku - aborting my rake assets:precompile and on Heroku Help )
This allows the precompilation to work. However, some of my pages include other javascript files. For example, I include a file from 'vendor/javascripts' within certain pages by putting a tag on the specific page:
<%= javascript_include_tag "src/ace.js" %>
When I visit such a page, it causes the following error (when precompiling works):
ActionView::Template::Error: src/ace.js isn't precompiled
How can I fix it so such pages do not throw errors?
You'll need to add the file to the assets.precompile.
Add the following to your config/environments/production.rb to compile all your css/js files:
config.assets.precompile = ['*.js', '*.css']
You can add your ace.js and other required files in a javascript file for instance "custom.js"
Then you can use your regular code to include the file
Add the following in custom.js. ( Assumed ace.js resides in vendor/javascripts)
//= require ace
Add the file to compilation list by adding following in application.rb
config.assets.precompile += %w( custom.js )
You can now use your javascript_tag to include the source
<%= javascript_include_tag "custom" %>

How do I use config.assets.precompile for directories rather than single files?

How do I use config.assets.precompile in production to only include the files in 'lib/assets/javascripts', 'lib/assets/stylesheets', 'vendor/assets/javascripts' and 'vendor/assets/stylesheets'?
Basically something like:
config.assets.precompile += %w( pagespecific.js anotherpage.js )
But used to auto include files in specific directories that are not 'app/assets/javascripts' or 'app/assets/stylesheets'.
*edit: adding the solution I ended up using for page specific js
config.assets.precompile += ['pages/*.js']
You can simply write it like this:
config.assets.precompile += ['directory/*']
The point of compiling assets is to build one (or a small number of) files to minimize the number of HTTP requests from the browser.
If you're going to serve each file individually, then why not just disable precompile?
To use precompile as intended, build an entire directory into one file using Sprockets' require_directory:
//= require_directory ./awesome_js_app
...and list that file in your config.assets.precompile array.
By default, all CSS is built into application.css & JS into application.js. You can add more top-level files to compile with the precompile directive in config/environments/production.rb (and other envs if you wish.) For example:
config.assets.precompile += %w( public.css public.js )
Then the Sprockets //= require ... directives in those top-level files will determine the composition of final compiled file.
You can use these additional top-level files in your layouts to have different CSS & JS for certain views.
It's probably a bit better to include this as an asset path(as per your example solution it would be):
config.assets.paths << Rails.root.join('app', 'assets', 'javascripts', 'pages')
It also allows you to include paths not in the assets directory (for example with including the bootstrap-sass). These paths are then added to your assets folder in your public directory, and pushed to your asset location if using something like asset_sync.
I found this link, and think may be it helpful for you, please see
keithgaputis's answer. Rails config.assets.precompile setting to process all CSS and JS files in app/assets
I think you can do like following.
# In production.rb
config.assets.precompile << Proc.new { |path|
if path =~ /\.(css|js)\z/
full_path = Rails.application.assets.resolve(path).to_path
app_assets_path = Rails.root.join('app', 'assets').to_path
if full_path.starts_with? app_assets_path
puts "excluding asset: " + full_path
false
else
puts "including asset: " + full_path
true
end
else
false
end
}
As of Sprockets 3 you can use a manifest.js file to declare which files or directories are precompiled. See upgrade docs. So in your config you would add:
config.assets.precompile = %w(manifest.js)
Then in app/assets/config/manifest.js you can have
//= link_directory ../pages .js
Use link_tree if you want js files in nested sub-directories to be precompiled too.

Resources