Rails 6.1 webpacker reduce unused JavaScript - ruby-on-rails

I have a rails 6.1 app using webpacker and tailwind.
My page load speed test shows a bad loading speed and suggests to "Reduce unused JavaScript". Sidenote: it also suggests to "Reduce unused CSS". So purge seems not to work. But on the page where I perform the page load speed test, the chartkick/chart.js is not even used. So it seems like it is loading every JS library, despite it is not even used on a page.
Here is my packs/application.js file:
// This file is automatically compiled by Webpack, along with any other files
// present in this directory. You're encouraged to place your actual application logic in
// a relevant structure within app/javascript and only use these pack files to reference
// that code so it'll be compiled.
import Rails from "#rails/ujs"
import Turbolinks from "turbolinks"
import * as ActiveStorage from "#rails/activestorage"
import "channels"
import "chartkick/chart.js"
import ahoy from "ahoy.js"
import { initMapbox } from '../packs/init_mapbox'
require("stylesheets/application.scss")
require("../stylesheets/components/pagination.scss")
require("../stylesheets/components/vacancy_description.scss")
Rails.start()
Turbolinks.start()
ActiveStorage.start()
document.addEventListener('turbolinks:load', () => {
initMapbox();
})
Here is my tailwind.config.js file:
const colors = require('tailwindcss/colors')
module.exports = {
purge: {
enabled: process.env.NODE_ENV === "production"
content: ['./app/views/**/*.html.erb', './app/javascript/**/*.js',],
},
darkMode: false, // or 'media' or 'class'
theme: {
colors: {
transparent: 'transparent',
current: 'currentColor',
black: colors.black,
white: colors.white,
gray: colors.trueGray,
indigo: colors.indigo,
red: colors.rose,
yellow: colors.amber,
green: colors.emerald
},
extend: {},
},
variants: {
extend: {},
},
plugins: [],
}
Hope someone can help!

Related

How to use quasar framework in vuepress 2?

I'm now using vuepress2 with quasar 2.7.1 like this:
import { Quasar } from 'quasar';
export default defineClientAppEnhance(({ app, router, siteData }) => {
app.use(Quasar);
}
#import url(https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900|Material+Icons|Material+Icons+Outlined);
#import 'quasar/src/css/variables.sass';
#import 'quasar/src/css/core/colors.sass';
.quasar-comp {
#import 'quasar/src/css/index.sass';
}
/* I wrap the custome component in class .quasar-comp
so that the style from quasar won't conflict with style from vuepress. */
but there are 2 issues:
The style from quasar cannot works on some components, like q-btn-dropdown or q-menu.
It works well on dev mode(npm run docs:dev), but failed to build(npm run docs:build).
✔ Compiling with vite - done
✖ Rendering pages - failed
TypeError: Cannot convert undefined or null to object
at Function.assign (<anonymous>)
at installQuasar (/Users/lxm/Documents/neo/leaneo-docs/node_modules/quasar/dist/quasar.cjs.prod.js:6:15228)
at Object.install (/Users/lxm/Documents/neo/leaneo-docs/node_modules/quasar/dist/quasar.cjs.prod.js:6:479348)
at Object.use (/Users/lxm/Documents/neo/leaneo-docs/node_modules/#vue/runtime-core/dist/runtime-core.cjs.prod.js:3393:28)
at /Users/lxm/Documents/neo/leaneo-docs/docs/.vuepress/dist/.server/app.js:3745:7
at createVueApp (/Users/lxm/Documents/neo/leaneo-docs/docs/.vuepress/dist/.server/app.js:4177:11)
at async /Users/lxm/Documents/neo/leaneo-docs/node_modules/vuepress-vite/node_modules/#vuepress/bundler-vite/lib/build/build.js:49:52
at async /Users/lxm/Documents/neo/leaneo-docs/node_modules/#vuepress/utils/lib/withSpinner.js:12:24
at async build (/Users/lxm/Documents/neo/leaneo-docs/node_modules/vuepress-vite/node_modules/#vuepress/bundler-vite/lib/build/build.js:34:5)
at async /Users/lxm/Documents/neo/leaneo-docs/node_modules/#vuepress/cli/lib/commands/build/createBuild.js:51:5
Is there a better way to make quasar and vuepress works together?
Here is the implementation in Vuepress 2 (beta) and Quasar with Typescript.
First, install Quasar into Vuepress with the new syntax and import all styles:
client.ts:
import { defineClientConfig } from "#vuepress/client";
import { Quasar } from "quasar";
import 'quasar/src/css/index.sass';
export default defineClientConfig({
enhance({app, router, siteData}) {
app.use(Quasar);
},
setup() {},
rootComponents: [],
});
Then, ignore deprecation errors from sass if you're using vite.
config.ts
import { viteBundler } from '#vuepress/bundler-vite'
import { defineUserConfig } from '#vuepress/cli'
export default defineUserConfig({
// your config
...
bundler: viteBundler({
viteOptions: {
css: {
preprocessorOptions: {
scss: {
sassOptions: {
// ignore sass deprecation errors
quietDeps: true
}
}
}
}
},
}),
});
Hope it helps :)
Quasar needs some globales to be defined (ie if it's SSR). My following solution works ONYL on client-side so be sure to wrap your custom-quasar component in <ClientOnly> !
// .vuepress/config.ts
bundler: viteBundler({
viteOptions: {
define: {
__QUASAR_VERSION__: `'dev'`,
__QUASAR_SSR__: false,
__QUASAR_SSR_SERVER__: false,
__QUASAR_SSR_CLIENT__: false,
__QUASAR_SSR_PWA__: false
}
}
}),
// .vuepress/client.ts
import { defineClientConfig } from '#vuepress/client'
import Quasar from "quasar/src/install-quasar.js";
// optionally import your styles here
// import 'quasar/src/css/index.sass';
export default defineClientConfig({
enhance({ app, router, siteData }) {
app.use(Quasar);
},
setup() {
},
rootComponents: [],
});
<!-- README.md -->
<ClientOnly>
<MyComponent />
</ClientOnly>

Using <style lang="scss"> in vue component gives error

I am trying to use vue js in rails.
Everything works, except when I tried to use <style> inside .vue component
The exact error is:
./app/javascript/layouts/dashboard.vue?vue&type=style&index=0&lang=scss& (./node_modules/css-loader/dist/cjs.js!./node_modules/vue-loader/lib/loaders/stylePostLoader.js!./node_modules/sass-loader/dist/cjs.js??ref--1-2!./node_modules/style-loader/dist!./node_modules/css-loader/dist/cjs.js??ref--5-1!./node_modules/postcss-loader/src??ref--5-2!./node_modules/sass-loader/dist/cjs.js??ref--5-3!./node_modules/vue-loader/lib??vue-loader-options!./app/javascript/layouts/dashboard.vue?vue&type=style&index=0&lang=scss&)
Module build failed (from ./node_modules/sass-loader/dist/cjs.js):
SassError: Expected newline.
My environment.js file
const { environment } = require('#rails/webpacker')
const { VueLoaderPlugin } = require('vue-loader')
const vueLoader = require('./loaders/vueLoader')
const vuetifyLoader = require('./loaders/vuetifyLoader')
environment.plugins.prepend('VueLoaderPlugin', new VueLoaderPlugin())
environment.loaders.prepend('vue', vueLoader)
environment.loaders.prepend('vuetify', vuetifyLoader)
const resolver = {
resolve: {
alias: {
'vue$': 'vue/dist/vue.esm.js'
}
}
}
environment.config.merge(resolver)
module.exports = environment
VuetifyLoader.js file
module.exports = {
test: /\.s(c|a)ss$/,
use: [
'vue-style-loader',
'css-loader',
{
loader: 'sass-loader',
// Requires sass-loader#^7.0.0
options: {
implementation: require('sass'),
fiber: require('fibers'),
indentedSyntax: true // optional
},
// Requires sass-loader#^8.0.0
options: {
implementation: require('sass'),
sassOptions: {
fiber: require('fibers'),
indentedSyntax: true // optional
},
},
},
],
}
install these two plugins.
npm install --save node-sass
npm install --save sass-loader
So, the problem was with fiber and indentedSyntax. After removing those two, everything works as expected. I was getting lots of error related to scss like
like
expected new line
in sass files inside node_modules. I don't know, why vuetify recommends to use fiber in sass loader.

Rails 6 + Webpacker 4 + PostCSS & #font-face + Local fonts

I'm trying to import fonts in my rails 6 app for hours now.
It's a fresh app using Rails 6, Webpacker 4 and PostCSS.
Everything is loaded (with no error) through webpack (css, js, images). Compilation is correct. Image are properly displayed (using css background:url).
OTF/EOT/WOFF fonts : Compiled properly and fonts inside my #font-face are loaded and salted by Webpack. The font are not rendered properly in view (I got the default browser font instead).
I think I tried everything I know. I Switched file-loader for url-loader and back, with no success. Changed the folders hierachy, tried different fonts files, absolute urls (resolve-url-loader). Nothing seems to work.
Anyone would be kind enough point me to the right direction or to share a working config of local fonts loading with webpacker on rails 6 and PostCSS ?
Thank you in advance for your help.
Here is my config :
javascript > fonts
raleway_thin-webfont.woff
javascript > packs > application.js
require.context('../fonts/', true, /\.(eot|ttf|woff|woff2|otf)$/i);
import "./application.pcss";
import "js";
require("#rails/ujs").start();
require("turbolinks").start();
require("#rails/activestorage").start();
require("channels");
javascript > packs > application.pcss
#font-face {
font-family: 'Raleway';
src: url('../fonts/raleway_thin-webfont.eot'), format('eot');
}
#font-face {
font-family: 'Amaranth';
src: url('../fonts/Amaranth-Regular.otf'), format('otf');
}
#test-div { font-family: 'Raleway'; }
/postcss.config.js
module.exports = {
plugins: [
require('postcss-import'),
require('postcss-flexbugs-fixes'),
require('postcss-preset-env')({
autoprefixer: {
flexbox: 'no-2009'
},
stage: 3
})
]
};
/config > webpack > environment.js
const webpack = require("webpack");
const { environment } = require("#rails/webpacker");
const { VueLoaderPlugin } = require("vue-loader");
const vue = require("./loaders/vue");
// const url = require('./loaders/url');
environment.plugins.append(
"Provide",
new webpack.ProvidePlugin({
jQuery: "jquery",
$: "jquery",
Tether: "tether",
tether: "tether",
Popper: ["popper.js", "default"]
})
);
["css", "moduleCss"].forEach(loaderName => {
const loader = environment.loaders.get(loaderName);
loader.test = /\.(p?css)$/i;
environment.loaders.insert(loaderName, loader);
});
environment.plugins.prepend("VueLoaderPlugin", new VueLoaderPlugin());
environment.loaders.prepend("vue", vue);
// environment.loaders.prepend("url", url);
// avoid using both file and url loaders
// environment.loaders.get("file").test = /\.(tiff|ico|svg)$/i;
module.exports = environment;
/config > webpack > loaders > url (not in use)
module.exports = {
test: [/\.eot$/, /\.otf$/, /\.ttf$/, /\.woff$/, /\.woff2$/],
use: [
{
loader: "url-loader",
options: {
limit: 10000,
name: "[name]-[hash].[ext]"
}
}
]
};

Load fonts from node_modules in react-rails application with webpack

I have a react-rails application set up with webpacker.
I am trying to load font-awesome-pro with it's fonts from node_modules.
I assume this is a trivial task but I can't seem to find any good documentation on how to do this.
This is what I have so far:
package.json dependencies:
"dependencies": {
"#rails/webpacker": "3.5",
"babel-preset-react": "^6.24.1",
"bootstrap": "^4.1.3",
"prop-types": "^15.6.2",
"react": "^16.5.2",
"react-dom": "^16.5.2",
"react-slick": "^0.23.1",
"react_ujs": "^2.4.4",
"slick-carousel": "^1.8.1",
"tachyons-z-index": "^1.0.9"
},
"devDependencies": {
"#fortawesome/fontawesome-pro": "^5.2.0",
"babel-plugin-transform-class-properties": "^6.24.1",
"babel-preset-env": "^1.7.0",
"file-loader": "^2.0.0",
"path": "^0.12.7",
"webpack-dev-server": "2.11.2"
}
file.js:
var path = require('path');
module.exports = {
test: /\.(woff(2)?|eot|otf|ttf|svg)(\?v=[0-9]\.[0-9]\.[0-9])?$/,
exclude: path.resolve(__dirname, '../../app/assets'),
use: {
loader: 'file-loader',
options: {
outputPath: 'fonts/',
useRelativePath: false
}
}
}
environment.js
const { environment } = require('#rails/webpacker')
const file = require('./file')
environment.loaders.prepend('file', file)
module.exports = environment
application.scss:
#import '#fortawesome/fontawesome-pro/scss/fontawesome.scss';
application.rb:
config.assets.paths << Rails.root.join('node_modules')
What am I missing? From what I can gather, webpack should be looking at the node_modules directory, finding font files based on the webpack test and putting the assets into the output directory: fonts/.
FontAwesome with webfonts:
For me with the free version the example below is working well. I don't know the pro version, but if I'm not mistaken, you just have to rename fontawesome-free to fontawesome-pro in the paths.
application.scss:
$fa-font-path: "~#fortawesome/fontawesome-free/webfonts";
#import "~#fortawesome/fontawesome-free/scss/fontawesome.scss";
#import "~#fortawesome/fontawesome-free/scss/solid.scss";
#import "~#fortawesome/fontawesome-free/scss/regular.scss";
#import "~#fortawesome/fontawesome-free/scss/brands.scss";
In SCSS ~ (tilde import) means that look for the nearest node_modules directory. Not all SASS compilers supports it, but node-sass does, and this is the common for Webpack.
This way in your html you only have to use your application.css. There's no need to include any other FontAwesome css files.
Your font loader config seems OK (tested, worked). With that Webpack should resolve the font files and then copy them to your desired output as you wanted. This needs that your css-loader be configured with url: true but I that is the default.
A minimal/usual config for the loaders in your Webpack config file:
module: {
rules: [
{
test: /\.s?css$/,
use: [
MiniCssExtractPlugin.loader, // optional (the most common way to export css)
"css-loader", // its url option must be true, but that is the default
"sass-loader"
]
},
{
// find these extensions in our css, copy the files to the outputPath,
// and rewrite the url() in our css to point them to the new (copied) location
test: /\.(woff(2)?|eot|otf|ttf|svg)(\?v=[0-9]\.[0-9]\.[0-9])?$/,
use: {
loader: 'file-loader',
options: {
outputPath: 'fonts/'
}
}
}
]
},
Loading only the needed fonts (the new way with JS and SVGs)
Again, I will demonstrate it with the free version because I don't have the pro version.
This way your generated bundle will only contain those icons what you need, resulting in a much smaller size which means faster page loads. (I'm using this in my projects)
The needed packages:
#fortawesome/fontawesome-svg-core
#fortawesome/free-brands-svg-icons
#fortawesome/free-regular-svg-icons
#fortawesome/free-solid-svg-icons
Include this in your scss file:
#import "~#fortawesome/fontawesome-svg-core/styles";
Create a new file, name it fontawesome.js:
import { library, dom, config } from '#fortawesome/fontawesome-svg-core';
config.autoAddCss = false;
config.keepOriginalSource = false;
config.autoReplaceSvg = true;
config.observeMutations = true;
// this is the 100% working way (deep imports)
import { faUser } from '#fortawesome/free-solid-svg-icons/faUser';
import { faHome } from '#fortawesome/free-solid-svg-icons/faHome';
import { faFacebook } from '#fortawesome/free-brands-svg-icons/faFacebook';
import { faYoutube } from '#fortawesome/free-brands-svg-icons/faYoutube';
// this is the treeshaking way (better, but read about it below)
import { faUser, faHome } from '#fortawesome/free-solid-svg-icons';
import { faFacebook, faYoutube } from '#fortawesome/free-brands-svg-icons';
library.add(faUser, faHome, faFacebook, faYoutube);
dom.watch();
.. and then require it somewhere in your js:
require('./fontawesome');
That's all. If you want to read more on this, start with understanding SVG JavaScript Core, have a look on its configuration and read the documantation of treeshaking.

Module parse failed: Unexpected character '#' when using React on Rails with Antd.

I'm using React on Rails and have been trying to use Antd UI framework.
I have successully imported components from 'antd' like buttons and Datepickers
import { Button } from 'antd';
but they are not styled at all. Looking at my server I have the following error...
ERROR in ./node_modules/antd/lib/button/style/index.less
Module parse failed: Unexpected character '#' (1:0)
You may need an appropriate loader to handle this file type.
| #import "../../style/themes/default";
| #import "../../style/mixins/index";
| #import "./mixin";
# ./node_modules/antd/lib/button/style/index.js 5:0-23
# ./app/javascript/packs/application.js
ERROR in ./node_modules/antd/lib/style/index.less
Module parse failed: Unexpected character '#' (1:0)
You may need an appropriate loader to handle this file type.
| #import "./themes/default";
| #import "./core/index";
|
I have tried a few different ways to access the styles like directly references the specific stylesheet in my jsx file
//app/javascript/bundles/Page/components/Page.jsx
import React, { Component } from "react";
// import { Button, DatePicker } from 'antd'
import { Button } from 'antd';
// import Button from 'antd/lib/button';
// import 'antd/lib/button/style/css';
export default class Page extends Component {
render() {
return (
<div >
<Button type="primary">Primary</Button>
<Button>Default</Button>
<Button type="dashed">Dashed</Button>
<Button type="danger">Danger</Button>
</div>
);
}
}
Looking at the Antd docs, I have followed the Importing on demand technique and added
"import", {
"libraryName": "antd",
"style": "css",
}
]
to my .babelrc file
https://ant.design/docs/react/getting-started#Import-on-Demand
I have also tried to install the less and less-loader as I'm pretty sure it has something to do with a css file containing an '#' which indicates to me that it is a less or sass file.
The only successful way I've been able to load the styles is by putting
#import 'antd/dist/antd.css';
in my app/assets/stylesheets/page.scss.
While this option works, it does not allow for the ability to import on demand and feels like the incorrect way to import the css as it uses the rails asset pipeline instead of webpack ( via webpacker)
For me the issue was that I needed to have differently configured .less loaders in webpack for both the .less files found in antd's modules & the .less files I had written locally in my project.
For Antd I have this (Note that it's excluding /src/):
{
test: /\.(less)$/,
exclude: [
/\.(css)$/,
/src/
],
use: [
MiniCssExtractPlugin.loader,
{
loader: "css-loader"
},
{
loader: "less-loader",
options: {
javascriptEnabled: true,
modifyVars: get_theme()
}
}
]
},
And for my own .less files I have this:
{
test: /\.(less)$/,
exclude: [
/\.(css)$/,
/node_modules/
],
use: [{
loader: 'style-loader' // creates style nodes from JS strings
}, {
loader: 'css-loader' // translates CSS into CommonJS
}, {
loader: "less-loader",
options: {
javascriptEnabled: true
}
}]
},
Versions of dependencies for this that I was using:
"css-loader": "^3.3.2",
"less": "3.10.3",
"less-loader": "^5.0.0",
"mini-css-extract-plugin": "^0.8.0",
"style-loader": "^1.2.1",
"antd": "4.4.2",
"webpack": "^4.43.0",

Resources