Importing CSS with webpacker in Rails 6 - ruby-on-rails

I use webpacker with Rails and am installing taildwindcss right now. Their installation guide says to use an #import method if I'm using postcss-import. I must say I get confused whenever I have to import CSS to Rails with webpacker, so I have a few questions:
1) The #import method from the docs - is it a JavaScript or CSS import method?
2) If it's a CSS method, why do I have to paste it inside javascript folder (e.g. javascript/stylesheets? I tried to put it inside the application.css file and it doesn't work. I assume it is somehow related to the fact that it's using PostCSS and package was installed via yarn?
3) If the above is true, does it mean that I have to import every CSS package that way if it's installed via yarn?

You will likely want to actually want to import into the CSS and javascript!
The typically setup will have app/javascript/styles/application.css for example which will bootstrap your global css:
#import "tailwindcss/base";
#import "tailwindcss/components";
#import "components/buttons"; // custom components that map to path app/javascript/styles/components/buttons.css for example
#import "tailwindcss/utilities";
In your app/javascript/packs/application.js you will import this:
// other js
import('styles/application.css');
// other js
In your layout you will add <%= stylesheet_pack_tag 'application' %> to add the css from application.js and <%= javascript_pack_tag 'application' %> to add the javascript from application.js.
The reason for this setup is that webpack is going to process application.js and it will handle the CSS and JS separately. Think of javascript/pack/application.js more of a bootstrap/dependencies file than a running javascript file. It's saying here's a list of stuff I need to work. In this case, one of the things is app/javascript/styles/xyz.css, and by the way, use post-css to manage how it is processed.

Related

How to Import node_modules with Webpacker

I'm new to the whole JS/webpacker game and am failing to understand how to import and use a javascript package using webpacker.
I am trying to include and use the Animate On Scroll Library.
I have Webpacker installed and working (I know it's working because I am able to happily use StimulusJs).
Here's what my /javascript/packs/application.js file looks like:
import {
Application
} from "stimulus"
import {
definitionsFromContext
} from "stimulus/webpack-helpers"
import {
trix
} from "trix"
import AOS from "aos"
const application = Application.start()
const context = require.context("controllers", true, /.js$/)
application.load(definitionsFromContext(context))
AOS.init();
I have my javascript_pack_tag included on my application.html.erb as
<%= javascript_pack_tag 'application', 'data-turbolinks-track': 'reload', defer: true %>
I imported the required css files using my /assets/css/application.scss with #import 'aos/dist/aos'; so that shouldn't be the issue.
When I try and use the AOS package by doing something like <h1 class="text-center" data-aos="fade-in">This is a header.</h1> nothing happens. What am I missing?
Thanks.
Upon reading this again I guess it's more a JS issue you are encountering. I'll leave my points about the CSS below in case useful. See the fourth point about webpack-dev-server if you aren't using it already. Does your browser support defer? You could try moving the scripts to right before </body> to see. I'd probably just cut-to-the-chase and set a javascript breakpoint in AOS.init() and see what's happening.
A few points I've leaned recently to help better understand webpacker/css/sprockets...
First, the CSS in the assets folder is outside of webpack. It's sprockets. That's not a problem but often mixing the two presents challenges.
At the very least you'll need a stylesheet_link_tag 'application'... in addition to the pack tags.
Second, is AOS added via yarn or is it a gem? Gems with webpack can be a bit tricky. I and others I encountered gave up trying to use gem assets in webpack. Best to stick to yarn/npm modules for webpack. Otherwise, move all the assets into the sprockets pipeline (ie in the assets/ folder) and use that for this portion of your site. (it's ok to mix them, just keep them separate).
Third, if the AOS module is added via yarn add ... (ie it resides in the nodule_modules directory) then try replacing the CSS import to just
#import '~aos';
This works because node_modules is in the search path and if the plugin folder has a package.json manifest and it includes a "style" entry, it pulls the css file path from there.
Third, you can try moving the CSS to webpack. Try this...
Make an application.scss file in your components subfolder
Add to your packs/application.js file: import '../components/application.scss
Add a stylesheet_pack_tag 'application' .... to your layout
Put your css imports (from node modules) in your new application.scss
Fourth: Use bin/webpack-dev-server. This compiles webpack on the fly whenever any of your source files changes instead of on every page load (saves you a lot time). Since your CSS is now under webpack, it will give you errors if the import isn't right (though sprockets should do this too in your server logs).
Good luck! It gets easier and yarn/webpack is cool, better than the old ruby-gems-for-front-end-components, IMO

imported css files are not compiled into application.css file

I have a style.scss file where i have imported bunch of other files like
#import url(asset-path('bootstrap/css/bootstrap.min.scss'));
#import url(asset-path('ionicons/css/ionicons.min.scss'));
#import url(asset-path('slick-carousel/slick/slick.scss'));
#import url(asset-path('slick-carousel/slick/slick-theme.scss'));
#import url(asset-path('owl-carousel/assets/owl.carousel.min.scss'));
#import url(asset-path('owl-carousel/assets/owl.theme.default.scss'));
#import url(asset-path('owl-carousel/assets/carousel.min.scss'));
#import url(asset-path('bxslider/jquery.bxslider.min.scss'));
#import url(asset-path('magicsuggest/magicsuggest-min.scss'));
these files are located under vendor/ directory.
Looking at the network tab in production mode, the server makes request to each an every of those imported files from the scss files instead of compiling them under on file.
I am also using sass rails gem. Is there anything I am not understanding about the rails assets pipeline?
The confusion comes from the fact that SASS overrides the #import directive in a way.
In your case the pure CSS' #import directive is used since your are passing url(.., which as you noticed makes HTTP request for every file.
In order to use the SASS' version of #import (which will import and combine the contents), you need to pass the files in quotes:
...
#import 'slick-carousel/slick/slick.scss';
...
Here's a detailed explanation about SASS' #import
Your master css file is done through app/assets/stylesheets/application.css file.
For example :
*= require navbars.css.scss
*= require footer.css.scss
*= require cookiewarning.css.scss
*= require_self
The above code will include in the application.css master file all of the mentionned css files, whether they are located in app/assets app/lib or app\vendor.
You master file is called in views/layouts/application.html.erb
<%= stylesheet_link_tag 'application', media: 'all' %>
<%= yield(:headcss) %>
<%= stylesheet_link_tag params[:controller], media: 'all' %>
As you can see I have a separate file for the current controller. And also a yield tag for extra CSS I would like to add when needed.
Also one note about your files: SASS is a preprocessor of CSS. It is better to name your files whatever.css.scss than whatever.scss. I got some problems getting the SASS helpers work properly because of this: Sass rails seems to generate a different logical path than in manifest

How do I organize SCSS in rails?

I just bootstrapped a new rails project and I was trying to reference from an open sourced rails project on how they architect their app.
Link to the open sourced project
I noticed they have multiple layouts e.g. admin, application, home ..etc. And each may load in different stylesheets via stylesheet_link_tag.
For example in focus_home.html.erb:
<!-- Load styles -->
<%= stylesheet_link_tag 'focus_home' %>
<%= stylesheet_link_tag 'app/components/modal' %>
And in their app/assets/stylesheets directory, they have focus_home.scss
I try to follow their architecture where I have multiple css files and I call different stylesheets with different layout.
I created my home.scss to be used by home.html.slim
when I started my rails server and try to load the home page, the following error occurs
Asset filtered out and will not be served: add
`Rails.application.config.assets.precompile += %w( home.css )` to
`config/initializers/assets.rb` and restart your server
Basically it asked me to tell rails to precompile home.scss. However, when I browse through the open source project's code base. It doesn't seem to have this line of code. The precompilation just seem to happen like magic.
So I am wondering what I am missing ?
==============
Edit: Further explain the case
In their project they DO NOT have an application.css file like normal rails project.
/* * This is a manifest file that'll be compiled into application.css, which will include all the files * listed below. *
* Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets, * or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path. * * You're free to add application-wide styles to this file and they'll appear at the bottom of the * compiled file so the styles you add here take precedence over styles defined in any styles * defined in the other CSS/SCSS files in this directory. It is generally better to create a new * file per style scope. * *= require_tree . *= require_self */
Instead, in their application.scss, it goes like
#charset "utf-8";
#import "vars/base";
#import "constants";
// Libraries
#import 'bootstrap';
#import 'app/mixins/*';
#import 'basscss';
#import '_deprecated/basspluss';
#import '_deprecated/utility';
#import '_deprecated/nocss_vars';
#import '_deprecated/nocss';
#import '_deprecated/nocss_mq';
#import "app/main";
#import "base/*";
#import "utilities/*";
#import "app/components/*";
#import "components/*";
#import "app/slop";
#import 'libs/owl.carousel';
#import 'libs/owl.transitions';
#import 'libs/owl.theme';
#import 'c3/c3'
So I wonder how they actually do their precompilation??
You don't need to use Sprockets comments in your application.scss and must only use #import CSS rules.
From rails-sass documentation:
Sprockets provides some directives that are placed inside of comments
called require, require_tree, and require_self. DO NOT USE THEM IN
YOUR SASS/SCSS FILES. They are very primitive and do not work well
with Sass files. Instead, use Sass's native #import directive which
sass-rails has customized to integrate with the conventions of your
Rails projects.
Then, if you have any other .scss that needs to be precompiled, you will have to explicitly add them using the Rails.Application.config.assets.precompile directive, then the sprockets railtie will do the rest of the job!
To answer your original question, the reason why the open source project do not need to specify assets to precompile is because they are using config.assets.compile = true in the config/environment/production.rb file. This is obviously a very bad practice and I don't recommend you to switch this directive to true in a production environment... you will end up with a slow code making a lot of requests and there a slow page load.

rails engines and assets

I have a rails engine that has a widget in it. I'm rendering the widget using a layout: false property in render and to style the widget, I'm recycling CSS on my main app.
Now here's the tricky part, I only needed 3 stylesheets for my widget and 2 of them are in my main app, those are bootstrap.css and _ss_font.css. On development, it works fine but when I push it to production, I'm having an error on bootstrap.css, it seems that it's dependent on another file on my vendor assets. How do I include those assets?
Note: If I use =stylesheet_link_tag "application" my problem is partially resolved but I have to handle conflicting class names and it will include all assets on my application. I just want to load the necessary stylesheets to style my widget in the engine.
EDIT 1
As suggested I'm including a more descriptive explanation on this:
So this is the error I'm having.
ActionView::Template::Error (File to import not found or unreadable:
ss-variables. Load path:
/data/serviceseeking_au_staging/releases/20140604114806 (in
/data/serviceseeking_au_staging/releases/20140604114806/vendor/assets/stylesheets/bootstrap.scss)):
2: !!! 5
3: %html
4: %head
5: = stylesheet_link_tag "bootstrap", "_ss_font", "feedback/reviews-widget"
6:
7: %body
8: .reviews-container
lib/action_logger.rb:31:in `call'
This view is inside the engine. As you can see, this is my ideal goal, to only include 3 stylesheets. There's not much to see on the reviews-widget, it's just sass without any external file dependency so I will not show it. So looking at the error message, where is this "ss-variable"? Well, let me take you guys on a field trip first before we arrive there.
I first traced where bootstrap.scss is and found it sitting on the main app under the vendor/assets/stylesheets folder. It's contents are as follows:
#import "bootstrap/variables";
#import "bootstrap/mixins";
#import "bootstrap/scaffolding";
#import "bootstrap/grid";
#import "bootstrap/layouts";
etcetera...
So by tracing boostrap.scss and opening it, I saw that it's importing a partial named _variables.scss, so on with the tour and here's _variable.scss
#import "ss-variables";
// Grays
// -------------------------
$black: #000 !default;
$grayDarker: #222 !default;
(Note: I saw another boostrap.scss inside the /vendor/assets/stylesheets/bootstrap where _variables.ss is located) Aha! There's that little freak, "ss-variable"!! We're getting close on this wild partial chase. So I searched where ss-variables.scss is and found it on my lib/assets/stylesheets/ in the main app and it contains more scss variables.
So tracing them, I formulated this hypothesis that when trying to include bootstrap using stylesheet_link_tag on my engine, the engine doesn't see the dependent files that lies on the main app.
Bonus: I have a remarkable feeling that after this, the _ss_font css will be next. But that's for another story. Thanks.
From the looks of it, your problem might be caused by the asset_fingerprinting feature of the asset pipeline - basically appends all the production assets with an MD5 hash
--
Asset Fingerprinting
Essentially, if you're using the likes of Heroku (which requires your assets to be precompiled for them to be served), you will basically get a series of files in your public/assets folder like this:
|-public
|--assets
|---images
|---stylesheets
|---javascripts
The files contained in this folder, when precompiled, all have the fingerprinted filename. This means if you wanted to call one of these, you will need to use one of the ERB / SCSS preprocessors to access them:
#app/assets/stylesheets/application.css.scss
.style {
background: asset_url("background.png");
}
This will reference the file, regardless of whether it's been precompiled (& fingerprinted) or not. Although not exactly your issue, it is an important factor in what you're asking
--
Fix
I would suggest your issue is likely that you're referencing "static" CSS files, which really need to be dynamically referenced.
How do I include those assets?
You'll be best using #import like this:
#gem/app/assets/gem/widget.css.scss
#import "bootstrap.css" /* will probably need a relative path for this */
#import "other.css" /* again, will need a relative path */

How do I use Controller specific stylesheets in Rails 3.2.1?

Using Rails 3.2.1
I created a simple controller called Home using the command:
rails g controller Home index
And it created a new controller and view for me:
Notice how there are two stylesheets, one "Application" and one "Home". I can't find any documentation to support this assumption but I'm guessing you put styles that will only be applied to the "Home" views, in the Home.css.scss file, correct?
So as a test, I added in some global styles to Application.css.scss.erb and ran the application.
The styles applied as expected.
Next, I added in some rules to the Home.css.scss file and I visited a "Home/index" view, yet the style in that file wasn't attached, neither as a seperate CSS reference link, or even appended to the single Application.css.scss file. This is highly confusing to me, since the comments say:
// Place all the styles related to the Home controller here.
// They will automatically be included in application.css.
// You can use Sass (SCSS) here: http://sass-lang.com/
Why aren't the rules written in Home.css.scss applied to my website?
It can work this way and Marek is quite correct, the answer is in the guide.
In the introduction to section 2.1:
For example, if you generate a ProjectsController, Rails will also add a new file at app/assets/javascripts/projects.js.coffee and another at app/assets/stylesheets/projects.css.scss. You should put any JavaScript or CSS unique to a controller inside their respective asset files, as these files can then be loaded just for these controllers with lines such as <%= javascript_include_tag params[:controller] %> or <%= stylesheet_link_tag params[:controller] %>.
So to set your application up to load controller specific stylesheets:
First, disable the default loading of all stylesheets by removing any extra requires in the application.css manifest.
Typically you'll see an entry like this:
*= require_tree .
If you still want to load some common css files, you can move them to a subdirectory and do something like this:
*= require_tree ./common
Second, In your application's layout add the suggested stylesheet_link_tag eg
<%= stylesheet_link_tag "application", :media => "all" %>
<%= stylesheet_link_tag params[:controller] %>
In this example we first load the application css file, we then load any css file that matches the current controller name.
I've solved this problem with a simple solution. I add to body the controller name as a class, editing views/layouts/application.html.slim:
body class=controller.controller_name
Or views/layouts/application.html.erb:
<body class="<%= controller.controller_name%>">
And then in my css I just use body.controller_name as a namespace:
/* example for /users/ */
body.users {
color: #000;
}
body.users a {
text-decoration: none;
}
For small projects I think it's fine.
I don't think it works that way (Home.css being applied only to Home controller actions). The different files are just for separation, to make it clearer what are the CSS rules describing. You can read this guide about the asset pipeline. I'm guessing you altered the default application.css.scss and removed the line importing all CSS files from app/assets/stylesheets.
TL;DR:
Ignore the comment, it's not made by Sass. But put:
#import "*";
into your application.css.scss file, and it will automatically import all the controller scss files.
Full read:
Disclaimer: This is my current understanding of the asset pipeline flow with and without Sass.
I think this comment is written by the standard Rails Asset pipeline (sprockets), and not by Sass:
// Place all the styles related to the Home controller here.
// They will automatically be included in application.css.
// You can use Sass (SCSS) here: http://sass-lang.com/
The standard pipeline will handle scss files but doesn't presume an application.css.scss file. But if you create such a file with Sass, then Sass will compile it to the application.css file.
If you use the normal Rails asset pipeline, without Sass, then sprockets would load the css file into the application.css file automatically (if that file has the default *= require_tree . line in it).
When you use Sass, with an application.css.scss file, Sass will compile this file into a application.css file. (I assume it would overwrite or take precedence over any application.css file you already had).
To get your home.css.scss file (and other controller files) automatically included, put this line into your application.css.scss file:
#import "*";
For reference, see this question:
Is it possible to import a whole directory in sass using #import?

Resources