Webpacker include pack directly from file in node_modules? - ruby-on-rails

I have a package with JS, CSS files 'mymodule' which I installed with yarn add module. So it has files like node_modules/mymodule/somefile.js.
I want to include js files from node_modules in Rails application:
# app/views/home/index.html.haml
= javascript_pack_tag 'mymodule/somefile.js (?? path to file??)'
I cannot create a pack in app/javascript/packs/mypack.js in main Rails application.
How to create a pack which is generated directly from files in node_modules so this pack can be included from view?
What to have in webpack.config.js?
const webpack = require("webpack");
module.exports = {
context: __dirname + "/app/javascript/packs",
entry: {
application: ["application.js", "application.css"],
mypack: [???]
},
output: {
path: __dirname + "/public/packs",
},
???
}

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!

I'm trying to install toastr by using webpacker in 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.

Electron Build Windows Folder Structure

Given an application made in electron. The folder structure would look something like:
App
- assets
-models
- exe files
index.html
main.js
When executing the build following the recommendation of the site by entering the following command:
electron-packager . --overwrite --asar=true --platform=win32 --arch=ia32 --icon=assets/icons/win/icon.ico --prune=true --out=release-builds --version-string.CompanyName=CE --version-string.FileDescription=CE --version-string.ProductName="Electron Tutorial App"
The electron v.1.7.9 creates the build correctly, however it inside the release-builds / resources folder the app.asar file, so all the content that was inside my models folder becomes inaccessible. Inside this folder were .exe files that should be run on demand.
The system then looks for the files in the following url: parth_do_projeto / resources / app.asar / assets / models /, that is, it considers that the app.assar is a folder, but after the app.asar build is a file.
Since there were .exe files inside the original folder, the app.asar can not absorb executables.
What would be the way I keep these .exe files? If you build the build without the --asar parameter, the program works correctly, enter, all my project folder / source code is exposed.
My question is what is the best way to generate the build, keeping the code closed and making use of .exe files?
The short answer to your question is to use the unpackDir option for the asar option inside of electron-packager. Here is a sample of what this might look like:
'use strict';
... ...
var packager = require('electron-packager');
var electronPackage = require('electron/package.json');
var pkg = require('./package.json');
// pull the electron version from the package.json file
var electronVersion = electronPackage.version;
... ...
var opts = {
name: pkg.name,
platform: 'win32',
arch: 'ia32', // ia32, x64 or all
dir: './', // source location of app
out: './edist/', // destination location for app os/native binaries
ignore: config.electronignore, // don't include these directories in the electron app build
icon: config.icon,
asar: {unpackDir: config.excludeFromASAR}, // compress project/modules into an asar blob excluding some things.
overwrite: true,
prune: true,
electronVersion: electronVersion , // Tell the packager what version of electron to build with
appCopyright: pkg.copyright, // copyright info
appVersion: pkg.version, // The version of the application we are building
win32metadata: { // Windows Only config data
CompanyName: pkg.authors,
ProductName: pkg.name,
FileDescription: pkg.description,
OriginalFilename: pkg.name + '.exe'
}
};
// Build the electron app
gulp.task('build:electron', function (cb) {
console.log('Launching task to package binaries for ' + opts.name + ' v' + opts['appVersion']);
packager(opts, function (err, appPath) {
console.log(' <- packagerDone() ' + err + ' ' + appPath);
console.log(' all done!');
cb();
});
});

How to create a PDF on Node.js using PDFMake and vfs_fonts?

It looks like this question has been asked quite a few times with older versions of PDFMake, but hasn't been updated with what appears to be the latest directory structure. Plus, copying fonts into a root "fonts" folder isn't great.
How in the world do I get a server side version of PDFMake ("pdfmake": "^0.1.31") running on Node.js with the included vfs_fonts.js file?
Install using npm on command line
npm install pdfmake fs --save
Boot up a Node.js app index.js with the following:
var fonts = {
Roboto: {
normal: 'fonts/Roboto-Regular.ttf',
bold: 'fonts/Roboto-Medium.ttf',
italics: 'fonts/Roboto-Italic.ttf',
bolditalics: 'fonts/Roboto-MediumItalic.ttf'
}
};
var PdfPrinter = require('pdfmake/src/printer');
var printer = new PdfPrinter(fonts);
var dd = {
content: [
'First paragraph',
'Another paragraph'
]
}
var pdfDoc = printer.createPdfKitDocument(dd);
pdfDoc.pipe(fs.createWriteStream('basics.pdf')).on('finish',function(){
//success
});
pdfDoc.end();
Hit run and bam:
/usr/local/bin/node index.js
fs.js:640
return binding.open(pathModule._makeLong(path), stringToFlags(flags), mode);
^
Error: ENOENT: no such file or directory, open 'fonts/Roboto-Regular.ttf'
at Error (native)
at Object.fs.openSync (fs.js:640:18)
The problem seems to lie with the location of the fonts/Roboto... files. Client-side, this is solved by including the vfs_fonts.js file. Server-side, I'm not sure. There are NO fonts folder or .ttf files included. The meteor framework example I've found doesn't seem applicable.
Any ideas? All the official examples reference a src/fonts folder. Not a good way to go for an npm install server module.
This is what I did to resolve this.
Downloaded "roboto-font": "0.1.0" module and assign path of that fonts in Roboto object and it worked fine.
let fonts = {
Roboto: {
normal: 'node_modules/roboto-font/fonts/Roboto/roboto-regular-webfont.ttf',
bold: 'node_modules/roboto-font/fonts/Roboto/roboto-bold-webfont.ttf',
italics: 'node_modules/roboto-font/fonts/Roboto/roboto-italic-webfont.ttf',
bolditalics: 'node_modules/roboto-font/fonts/Roboto/roboto-bolditalic-webfont.ttf'
}
};
let printer = new pdfMake(fonts);
let pdfDoc = printer.createPdfKitDocument(pdfData);
pdfDoc.pipe(fs.createWriteStream(reportName));
pdfDoc.end();
You need to download first the Roboto font here https://fonts.google.com/specimen/Roboto
and copy them inside you fonts folder.
Update your fonts object like this:
var fonts = {
Roboto: {
normal: path.join(__dirname, '..', 'your_public_folder', '/fonts/Roboto-Regular.ttf'),
bold: path.join(__dirname, '..', 'your_public_folder', '/fonts/Roboto-Medium.ttf'),
italics: path.join(__dirname, '..', 'your_public_folder', '/fonts/Roboto-Italic.ttf'),
bolditalics: path.join(__dirname, '..', 'your_public_folder', '/fonts/Roboto-MediumItalic.ttf')
}
}
In this example, replace the 'your_public_folder' with folder name where you have all your html, css and js files.

Having trouble setting up ngtemplate-loader and webpacker

I've tried setting up the webpack loader by adding this to the webpack/loaders directory
var path = require('path');
module.exports = {
test: /\.html$/,
loaders: [`ngtemplate?relativeTo=${path.join(__dirname, "/app")}`, "html"] //html?attrs[]=div:ng-include
}
and then requiring the appropriate template file from the .js code.
templateUrl: require('ngtemplate!html!./app/views/environment/index.html')
but I get the following error
Module not found: Error: Can't resolve 'html' in '/Users/joe/testrails5.1/app/javascript/app/scripts'
resolve 'html' in '/Users/joe/testrails5.1/app/javascript/app/scripts'
Parsed request is a module
Does this work for anyone else? I'm using angular 1.6 so do I need to use a different module?

Resources