Adding Typescript to Vue + Rails app - Imports no longer work? - ruby-on-rails

I'm trying to add TypeScript to an existing VueJS + Rails app. I cloned this demo (https://github.com/gbarillot/rails-vue-demo-app) then followed the instructions from https://github.com/rails/webpacker
$ bundle exec rails webpacker:install:vue
$ bundle exec rails webpacker:install:typescript
I then modified config/webpack/loaders/typescript.js as described here.
Everything seems to compile, but when I go into my "home" view and change the script to typescript:
<script lang="ts">
import Layout from '../shared/layout';
export default {
components: {
Layout
}
}
</script>
I get the following error:
Failed to compile.
/Users/matt/projects/rails-vue-demo-app/app/javascript/packs/components/home/index.vue.ts
[tsl] ERROR in /Users/matt/projects/rails-vue-demo-app/app/javascript/packs/components/home/index.vue.ts(13,20)
TS2307: Cannot find module '../shared/layout'.
Why can I no longer find the layout file when typescript is enabled?

I remember running into a similar problem for presentational components. Try adding an empty export in shared/layout so typescript can pick it up:
<script lang="ts">
export default {}
</script>

Related

How to use tailwind css gem in a rails 7 engine?

How to use tailwind in a rails engine? According to the documentation supplying a css argument to the Rails generator should work
Rails 7.0.2.2 engine generated using
rails plugin new tailtest --mountable --full -d postgresql --css tailwind
This generates the engine with Postgresql but does nothing with tailwind at all, and following manual installation instructions fail too.
Running, as per documentation, bundle add tailwindcss-rails adds tailwind to the gemfile rather than the engines tailtest.gemspec
So after adding the dependency to the gemspec
spec.add_dependency "tailwindcss-rails", "~> 2.0"
and running bundle install does install the engine however the rest of the manual installation fails
then adding the require to lib/engine.rb
require "tailwindcss-rails"
module Tailtest
class Engine < ::Rails::Engine
isolate_namespace Tailtest
end
end
then running the install process fails
rails tailwindcss:install
Resolving dependencies...
rails aborted!
Don't know how to build task 'tailwindcss:install' (See the list of available tasks with `rails --tasks`)
Did you mean? app:tailwindcss:install
Obviously the app:tailwindcss:install command fails too.
So I am probably missing an initializer of some sort in the engine.rb file but no idea on what it should be.
It is the same idea as How to set up importmap-rails in Rails 7 engine?. We don't need to use the install task. Even if you're able to run it, it's not helpful in the engine (see the end of the answer for explanation).
Also rails plugin new doesn't have a --css option. To see available options: rails plugin new -h.
Update engine's gemspec file:
# my_engine/my_engine.gemspec
spec.add_dependency "tailwindcss-rails"
Update engine.rb:
# my_engine/lib/my_engine/engine.rb
module MyEngine
class Engine < ::Rails::Engine
isolate_namespace MyEngine
# NOTE: add engine manifest to precompile assets in production, if you don't have this yet.
initializer "my-engine.assets" do |app|
app.config.assets.precompile += %w[my_engine_manifest]
end
end
end
Update assets manifest:
# my_engine/app/assets/config/my_engine_manifest.js
//= link_tree ../builds/ .css
Update engine's layout:
# my_engine/app/views/layouts/my_engine/application.html.erb
<!DOCTYPE html>
<html>
<head>
<%#
NOTE: make sure this name doesn't clash with anything in the main app.
think of it as `require` and `$LOAD_PATH`,
but instead it is `stylesheet_link_tag` and `manifest.js`.
%>
<%= stylesheet_link_tag "my_engine", "data-turbo-track": "reload" %>
</head>
<body> <%= yield %> </body>
</html>
bundle show command will give us the path where the gem is installed, so we can copy a few files:
$ bundle show tailwindcss-rails
/home/alex/.rbenv/versions/3.1.2/lib/ruby/gems/3.1.0/gems/tailwindcss-rails-2.0.8-x86_64-linux
Copy tailwind.config.js file from tailwindcss-rails:
$ cp $(bundle show tailwindcss-rails)/lib/install/tailwind.config.js config/tailwind.config.js
Copy application.tailwind.css file into any directory to fit your setup:
$ cp $(bundle show tailwindcss-rails)/lib/install/application.tailwind.css app/assets/stylesheets/application.tailwind.css
Because tailwindcss-rails uses standalone executable, we don't need node or rails to compile the stylesheets. We just need to get to the executable itself.
Executable is located here https://github.com/rails/tailwindcss-rails/tree/v2.0.8/exe/. Instead of running the build task https://github.com/rails/tailwindcss-rails/blob/v2.0.8/lib/tasks/build.rake we can just call the executable directly.
$ $(bundle show tailwindcss-rails)/exe/tailwindcss -i app/assets/stylesheets/application.tailwind.css -o app/assets/builds/my_engine.css -c config/tailwind.config.js --minify
Use -w option to start watch mode.
$ $(bundle show tailwindcss-rails)/exe/tailwindcss -i app/assets/stylesheets/application.tailwind.css -o app/assets/builds/my_engine.css -c config/tailwind.config.js --minify -w
The output file should match the name in stylesheet_link_tag "my_engine".
Now that you have a plain my_engine.css file, do with it what you want. Use it in the layout, require it from the main app application.css. The usual rails asset pipeline rules apply.
If you want to put all that into a task, use Engine.root to get the paths.
# my_engine/lib/tasks/my_engine.rake
task :tailwind_engine_watch do
require "tailwindcss-rails"
# NOTE: tailwindcss-rails is an engine
system "#{Tailwindcss::Engine.root.join("exe/tailwindcss")} \
-i #{MyEngine::Engine.root.join("app/assets/stylesheets/application.tailwind.css")} \
-o #{MyEngine::Engine.root.join("app/assets/builds/my_engine.css")} \
-c #{MyEngine::Engine.root.join("config/tailwind.config.js")} \
--minify -w"
end
From the engine directory:
$ bin/rails app:tailwind_engine_watch
+ /home/alex/.rbenv/versions/3.1.2/lib/ruby/gems/3.1.0/gems/tailwindcss-rails-2.0.8-x86_64-linux/exe/x86_64-linux/tailwindcss -i /home/alex/code/stackoverflow/my_engine/app/assets/stylesheets/application.tailwind.css -o /home/alex/code/stackoverflow/my_engine/app/assets/builds/my_engine.css -c /home/alex/code/stackoverflow/my_engine/config/tailwind.config.js --minify -w
Rebuilding...
Done in 549ms.
Make your own install task if you have a lot of engines to set up:
desc "Install tailwindcss into our engine"
task :tailwind_engine_install do
require "tailwindcss-rails"
# NOTE: use default app template, which will fail to modify layout, manifest,
# and the last command that compiles the initial `tailwind.css`.
# It will also add `bin/dev` and `Procfile.dev` which we don't need.
# Basically, it's useless in the engine as it is.
template = Tailwindcss::Engine.root.join("lib/install/tailwindcss.rb")
# TODO: better to copy the template from
# https://github.com/rails/tailwindcss-rails/blob/v2.0.8/lib/install/tailwindcss.rb
# and customize it
# template = MyEngine::Engine.root("lib/install/tailwindcss.rb")
require "rails/generators"
require "rails/generators/rails/app/app_generator"
# NOTE: because the app template uses `Rails.root` it will run the install
# on our engine's dummy app. Just override `Rails.root` with our engine
# root to run install in the engine directory.
Rails.configuration.root = MyEngine::Engine.root
generator = Rails::Generators::AppGenerator.new [Rails.root], {}, { destination_root: Rails.root }
generator.apply template
end
Install task reference:
https://github.com/rails/rails/blob/v7.0.2.4/railties/lib/rails/tasks/framework.rake#L8
https://github.com/rails/tailwindcss-rails/blob/v2.0.8/lib/tasks/install.rake
Watch task reference:
https://github.com/rails/tailwindcss-rails/blob/v2.0.8/lib/tasks/build.rake#L10
Update How to merge two tailwinds.
Above setup assumes the engine is its own separate thing, like admin backend, it has its own routes, templates, and styles. If an engine functionality is meant to be mixed with the main app, like a view_component collection, then tailwind styles will override each other. In this case isolating engine styles with a prefix could work:
https://tailwindcss.com/docs/configuration#prefix
The reason that tailwind styles don't mix is because most of the selectors have the same specificity and the order is very important.
So here is an example. Main app with an engine, both using tailwind, both compile styles separately, tailwind configs are only watching one file from the engine and one from the main app, only using #tailwind utilities; directive:
Engine template, that we want to use in the main app, should work fine:
<!-- blep/app/views/blep/_partial.html.erb -->
<div class="bg-red-500 sm:bg-blue-500"> red never-blue </div>
But when rendered in the main app it never turns blue. Here is the demonstration set up:
<!-- app/views/home/index.html.erb -->
<%= stylesheet_link_tag "blep", "data-turbo-track": "reload" %>
<%= stylesheet_link_tag "tailwind", "data-turbo-track": "reload" %>
<!-- output generated css in the same order as above link tags -->
<% require "open-uri" %>
<b>Engine css</b>
<pre><%= URI.open(asset_url("blep")).read %></pre>
<b>Main app css</b>
<pre><%= URI.open(asset_url("tailwind")).read %></pre>
<div class="bg-red-500"> red </div> <!-- this generates another bg-red-500 -->
<br>
<%= render "blep/partial" %>
And it looks like this:
/* Engine css */
.bg-red-500 {
--tw-bg-opacity: 1;
background-color: rgb(239 68 68 / var(--tw-bg-opacity))
}
#media (min-width: 640px) {
.sm\:bg-blue-500 {
--tw-bg-opacity: 1;
background-color: rgb(59 130 246 / var(--tw-bg-opacity))
}
}
/* Main app css */
.bg-red-500 {
--tw-bg-opacity: 1;
background-color: rgb(239 68 68 / var(--tw-bg-opacity))
}
<div class="bg-red-500"> red </div>
<br>
<div class="bg-red-500 sm:bg-blue-500"> red never-blue </div>
^ you can hit run and click "full page". Main app bg-red-500 selector is last so it overrides engines sm:bg-blue-500 selector, media queries don't add to specificity score. It's the same reason you can't override, say, mt-1 with m-2, margin top comes later in the stylesheet. This is why #layer directives are important.
The only way around this is to watch the engine directory when running tailwind in the main app, so that styles are compiled together and in the correct order. Which means you don't really need tailwind in the engine:
module.exports = {
content: [
"./app/**/*",
"/just/type/the/path/to/engine/views",
"/or/see/updated/task/below",
],
}
Other ways I tried, like running 6 tailwind commands for each layer for main app and engine, so that I can put them in order, better but was still out of order a bit and duplicated. Or doing an #import and somehow letting postcss-import know where to look for engine styles (I don't know, I just symlinked it into node_modules to test), but this still required tailwind to watch engine files.
I did some more digging, tailwind cli has a --content option, which will override content from tailwind.config.js. We can use it to setup a new task:
namespace :tailwindcss do
desc "Build your Tailwind CSS + Engine"
task :watch do |_, args|
# NOTE: there have been some updates, there is a whole Commands class now
# lets copy paste and modify. (debug = no --minify)
command = Tailwindcss::Commands.watch_command(debug: true, poll: false)
# --content /path/to/app/**/*,/path/to/engine/**/*
command << "--content"
command << [
Rails.root.join("app/views/home/*"),
Blep::Engine.root.join("app/views/**/*.erb")
].join(",")
p command
system(*command)
end
# same for build, just call `compile_command`
# task :build do |_, args|
# command = Tailwindcss::Commands.compile_command(debug: false)
# ...
end
https://github.com/rails/tailwindcss-rails/blob/v2.0.21/lib/tasks/build.rake#L11
That answer by Alex is really good, i wish i had it when starting out. (But i didn't even have the question to google)
Just want to add two things:
1- a small simplification. I just made a script to run tailwind in the engine
#!/usr/bin/env sh
# Since tailwind does not install into the engine, this will
# watch and recompile during development
# tailwindcss executable must exist (by bundling tailwindcss-rails eg)
tailwindcss -i app/assets/stylesheets/my_engine.tailwind.css \
-o app/assets/stylesheets/my_engine/my_engine.css \
-c config/tailwind.config.js \
-w
2- For usage in an app, that obviously also uses tailwind, i was struggling, since the two generated css's were biting each other and i could not get both styles to work in one page. Always one or the other (app or engine) was not styled right. Until i got the app's tailwind to pick up the engines classes.
Like so:
Add to the app's tailwind.config.js: before the module
const execSync = require('child_process').execSync;
const output = execSync('bundle show my_engine', { encoding: 'utf-8' });
And then inside the content as last line
output.trim() + '/app/**/*.{erb,haml,html,rb}'
Then just include the apps generated tailwind css in the layout, like the installer will. Don't include the engines stylesheet in the layout, or add it to the asset

SilverStripe running tests using Behat throwing "No behat.yml found for module silverstripe/framework" error

I am working on a SilverStripe project. I am trying to write Behavioural Tests using Behat for my projects. But I am getting an error when I run the tests. Following is what I have done so far.
First I install the module using composer
composer require --dev silverstripe/behat-extension
I have the behat.yml file right under the project root folder with the following definition
default:
suites: []
extensions:
SilverStripe\BehatExtension\MinkExtension:
default_session: facebook_web_driver
javascript_session: facebook_web_driver
facebook_web_driver:
browser: chrome
wd_host: "http://127.0.0.1:9515"
browser_name: chrome
SilverStripe\BehatExtension\Extension:
bootstrap_file: vendor/silverstripe/cms/tests/behat/serve-bootstrap.php
screenshot_path: %paths.base%/artifacts/screenshots
retry_seconds: 4 # default is 2
Then I tried to run the tests executing the following command.
vendor/bin/behat #framework
Then I get the following error.
In ModuleSuiteLocator.php line 166:
No behat.yml found for module silverstripe/framework
behat [-s|--suite SUITE] [-f|--format FORMAT] [-o|--out OUT] [--format-settings FORMAT-SETTINGS] [--init] [--namespace NAMESPACE] [--lang LANG] [--name NAME] [--tags TAGS] [--role ROLE] [--story-syntax] [-d|--definitions DEFINITIONS] [--snippets-for [SNIPPETS-FOR]] [--snippets-type SNIPPETS-TYPE] [--append-snippets] [--no-snippets] [--strict] [--order ORDER] [--rerun] [--stop-on-failure] [--dry-run] [--] [<module> [<paths>]]
There is not behat.yml in the vendor/silverstripe/framework folder. Actually, it is supposed to come with the framework. But it is not there. How can I solve the error?
For all SilverStripe modules the tests and associated configuration are not shipped with distributable packages (e.g. tags).
If you want to use them, you'll need to install the "dev" version. E.g. 4.5.x-dev instead of ~4.5.0.

Select2 Css is not loading via yarn or assets precompile

I am running into a strange issue where everything is working as expected except select2. I am having Rails 6.0.0 app and installed select2 via yarn add select2.
Then i added it to application.js file. File looks like below.
#app/javascripts/packs/application.js
require("#rails/ujs").start()
require("turbolinks").start()
require("#rails/activestorage").start()
require("channels")
require('datatables.net')
require('datatables.net-dt')
require('datatables.net-bs4')
require('select2')
import "bootstrap"
import 'popper.js/dist/popper.js';
import $ from 'jquery';
global.$ = jQuery;
import "chart.js"
import 'select2'; // globally assign select2 fn to $ element
import 'select2/dist/css/select2.css'; // optional if you have css loader
document.addEventListener("turbolinks:load", () => {
setTimeout(function() {
$('.success, .alert').fadeOut();
}, 10000);
})
Right now it looks like this
select2 dropdown box
When I go to Source tab in chrome developer tools, it shows only select2/dist/js folder under node_modules. I have tried precompiling and clobbering the assets but not working. The whole this is running fine in my local, only causing issue in production server.
Edit:
My production.rb
https://gist.github.com/rajanhcah/3ee13925b0ae305a6fe2c64013ab6534
If you're including CSS in app/javascripts/packs/application.js, make sure that app/views/layouts/application.html.erb is also using the stylesheet_pack_tag (see the "Usage" section in the webpacker doc).
Otherwise, if you have stylesheet_link_tag, you can use the classical app/assets/stylesheets/application.css.
# app/assets/stylesheets/application.scss
#import 'select2/dist/css/select2';

Getting Cesium to work with Webpacker and Rails

I'm trying to follow these instructions to add cesium to my rails project with Webpack, but I can't figure out how to translate the instructions to work with the rails implementation of Webpack.
For example:
In webpack.config.js, we add the following above our configuration object:
// The path to the Cesium source code
const cesiumSource = 'node_modules/cesium/Source';
const cesiumWorkers = '../Build/Cesium/Workers';
I assume in a rails project we would do our file imports in app/javascript/packs/application.js like this:
import 'cesium/Source';
import 'cesium/Build/Cesium/Workers';
but that gives the error:
Failed to compile.
./app/javascript/packs/application.js
Module not found: Error: Can't resolve 'cesium/Build/Cesium/Workers' in '/Users/user/Developer/appName/app/javascript/packs'
# ./app/javascript/packs/application.js 15:0-37
# multi (webpack)-dev-server/client?http://localhost:3035 ./app/javascript/packs/application.js
I've double checked, and the path is correct.
The Cesium instructions also indicate that I need to add some configuration options, but I can't figure out where I would put those since the rails webpacker gem doesn't have a webpack.config.js file. Do I just add config to the same file as the imports?
You would rather add a new config file which resolve your libs custom paths.
You can get inspiration from Webpacker documentation here:
https://github.com/rails/webpacker/blob/master/docs/webpack.md#configuration
You could do this :
//config/webpack/cesium.js
module.exports = {
resolve: {
alias: {
cesiumSource: 'cesium/Source',
cesiumWorkers: 'cesium/Build/Cesium/Workers'
}
}
}
Then merge the config options either in environment.js (global) or in development|test|production.js if this is specific to a particular environment
// config/webpack/environment.js
const { environment } = require('#rails/webpacker')
const cesiumConfig = require('./cesium')
environment.config.merge(cesiumConfig)
Hope this help.
Regards

Expose Rails Env to Webpacker

I'm running Rails v5 with Webpacker v2. Everything's been smooth so far, but I've hit one hiccup: how to expose Rails helpers to my TypeScript.
I know Webpacker ships with rails-erb-loader, so I was expecting that I'd be able to add .erb to a TypeScript file, and then import that file elsewhere:
// app/javascript/utils/rails.ts.erb
export const env = "<%= Rails.env %>"
export function isEnv(envName: string) {
return env == envName
}
// app/javascript/packs/application.ts
import { env } from "../utils/rails"
But Webpack can't find the "rails" file even if I modify the typescript loader to include ERB files:
module.exports = {
test: /.ts(\.erb)?$/,
loader: 'ts-loader'
}
All I see is:
error TS2307: Cannot find module '../utils/rails'.
What's the best way to go about exposing Rails helpers and variables to my JavaScript?
Rails env comes from your environment variable. This means you configure it by setting (in bash for example) the variable in this way:
export RAILS_ENV=production
As a consequence, you don't need to deal with Rails at all.
// app/javascript/utils/rails.ts.erb
export const env = process.env.RAILS_ENV || "development"
export function isEnv(envName: string) {
return env == envName
}
This comes with a great advantage: you don't have to load the whole rails app just to compile your javascript. Rails can become really slow on first load if your app grows.
Since you won't have access to process.env on the frontend (browser), you also need a way to make it exist. In webpack, this is done through the DefinePlugin:
Update your webpack configuration to use the plugin (in plugins section): new webpack.DefinePlugin({ "process.env": { RAILS_ENV: process.env.RAILS_ENV } }) and you will get a process.env.RAILS_ENV available in the client
Rails Env can be accessed through:
process.env.RAILS_ENV

Resources