How to use css with webpack in a rails app? - ruby-on-rails

Following the github docs, I tried the following to include scss assets in my app:
app/javascript/styles/app.scss:
#import 'https://fonts.googleapis.com/css?family=Roboto:300,400,500';
body {
margin: 0;
}
app/javascript/packs/application.js:
/* eslint no-console:0 */
// 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.
//
// To reference this file, add <%= javascript_pack_tag 'application' %> to the appropriate
// layout file, like app/views/layouts/application.html.erb
// Support component names relative to this directory:
var componentRequireContext = require.context("components", true)
var ReactRailsUJS = require("react_ujs")
ReactRailsUJS.useContext(componentRequireContext)
import React from 'react';
import '../styles/app.scss'
My webpack.config.js is the default webpack installs on rails. It is a combination of these 2 directories:
https://github.com/rails/webpacker/tree/master/lib/install/config/loaders/core
https://github.com/rails/webpacker/tree/master/lib/install/config/webpack
I can find the CSS config in config/loaders/sass.js:
const ExtractTextPlugin = require('extract-text-webpack-plugin')
const { env } = require('../configuration.js')
module.exports = {
test: /\.(scss|sass|css)$/i,
use: ExtractTextPlugin.extract({
fallback: 'style-loader',
use: [
{ loader: 'css-loader', options: { minimize: env.NODE_ENV === 'production' } },
{ loader: 'postcss-loader', options: { sourceMap: true } },
'resolve-url-loader',
{ loader: 'sass-loader', options: { sourceMap: true } }
]
})
}
On doing this, I do not get any error but the stylesheet isn't imported. Serving the same file from the asset pipeline works fine.

Related

webpack Failed to load resource using outputPath in assets/resource

I'm not very familiar with webpack.
My goal was to put all the assets inside my HTML in a specific folder.
For that, I set a new option under the rule that deals with the `type: assets/resource:
{
test: /\.(eot|svg|ttf|woff|woff2|png|jpg|gif)$/i,
type: 'asset/resource',
generator: {
outputPath: 'assets/' // this is the new option setted
}
}
It does actually work. webpack creates the folder after compiling and brings those files inside it. The problem is that the HTML file compiled doesn't understand that the assets files are inside assets/ folder.
How can I fix it?
Here is my webpack.config.js
const path = require('path')
const { merge } = require('webpack-merge')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin')
const ImageMinimizerPlugin = require('image-minimizer-webpack-plugin')
const stylesHandler = MiniCssExtractPlugin.loader
const base = {
entry: {
bundle: [
'./js/main.js',
'./css/style.scss'
]
},
output: {
path: path.resolve(__dirname, 'docs')
},
plugins: [
new HtmlWebpackPlugin({
template: 'index.html',
scriptLoading: 'module',
inject: 'body'
}),
new MiniCssExtractPlugin()
],
module: {
rules: [
{
test: /\.s[ac]ss$/i,
use: [stylesHandler, 'css-loader', 'postcss-loader', 'sass-loader'],
},
{
test: /\.(eot|svg|ttf|woff|woff2|png|jpg|gif)$/i,
type: 'asset/resource',
generator: {
outputPath: 'assets/' // this is the new option setted
}
},
{
test: /\.html$/i,
use: ['html-loader']
}
]
}
}
const dev = {
devServer: {
open: false,
host: 'localhost',
watchFiles: ['./index.html']
}
}
const prod = {
output: {
clean: true
}
}
module.exports = (env, args) => {
switch (args.mode) {
case 'development':
return merge(base, dev)
case 'production':
return merge(base, prod)
default:
throw new Error('No matching configuration was found!')
}
}
So I discover it by myself, and it was kinda obvious.
Well, when webpack compiles your index.html file, it won't understand if you just give a new path for your assets final destiny.
For example, like I did:
{
test: /\.(eot|svg|ttf|woff|woff2|png|jpg|gif)$/i,
type: 'asset/resource',
generator: {
outputPath: 'assets/' // this is the new option set
}
}
In order this to work you need to specify the publicPath:
{
test: /\.(eot|svg|ttf|woff|woff2|png|jpg|gif)$/i,
type: 'asset/resource',
generator: {
outputPath: 'assets/' // this is the new option set
publicPath: 'assets/'
}
}
You're telling webpack:
Put all the assets inside outputPath.
Hey HTML, when you look for the assets, please include the publicPath before looking for it.

Base url with multi page apps using vite

For my multi page app i cannot have html assets imports pointing to the root as i need to upload my project inside a sub-folder.
Using -
module.exports = defineConfig({
base: "./",
});
resolves this issue for root pages but cause a wrong import for nested pages.
Example
- assets
- index.html
- nested
- nested.html
Imports for index.html will point to ./assets which is correct.
Imports for nested.html will also point to ./assets which is incorrect. It needs to point to ../assets instead.
You can set an alias to point to the assets folder :
alias: {
"#": resolve(__dirname, './assets'),
},
Based on your tree sample, with this following example of multipage vite.config.js, you can import assets
from components with: import logo from '#/logo.png' (assuming there is a logo.png file in folder assets)
import vue from "#vitejs/plugin-vue";
import { join, parse, resolve } from "path";
export default {
base: '',
root: './',
plugins: [vue()],
alias: {
"#": resolve(__dirname, './assets'), // will resolve to `/assets/`
},
build: {
rollupOptions: {
input: entryPoints(
"index.html",
"nested/nested.html",
"foo/index.html",
"foo/bar/index.html",
),
},
},
};
function entryPoints(...paths) {
const entries = paths.map(parse).map(entry => {
const { dir, base, name, ext } = entry;
const key = join(dir, name);
const path = resolve(__dirname, dir, base);
return [key, path];
});
const config = Object.fromEntries(entries);
return config;
}
Example code of the nested FooBar component :
<template>
<img alt="Vue logo" :src="logo" />
<Nav/>
<h1>Foo Bar</h1>
</template>
<script setup>
import Nav from "~/components/Nav.vue";
import logo from '#/logo.png'
import "#/style/style.scss"
</script>

Module parse failed: Unexpected character '�' (1:0) You may need an appropriate loader to handle this file type when added jpeg or mp3

I am working on a rails app with vue on front end. I am getting this error when try to add any image or mp3 file to my project. Please help me resolve this issue. Below is my environment.js file.
I am using webpack version
#rails/webpacker": "^3.2.0"
Below is the home.vue file which is causing the issue. When I try to add this mp3 file I get that error.
home.vue
<template>
<div>
<v-layout row wrap>
<v-flex xs12 sm4 md4 class="hidden-xs-only">
<p>
<!-- <a :href="require('images/company-overview.mp3')" target="_blank" title="Read Article">
<img src="../../images/company-logo.png"/>
</a> -->
</p>
</v-flex>
</v-layout>
</div>
</div>
</template>
<script>
export default {
};
</script>
environment.js
const { environment } = require('#rails/webpacker')
const coffee = require('./loaders/coffee');
const vue = require('./loaders/vue')
environment.loaders.append('coffee', coffee);
environment.loaders.append('vue', vue)
environment.loaders.append('jshint', {
test: /\.js$/, // include .js files
enforce: "pre", // preload the jshint loader
exclude: /node_modules/, // exclude any and all files in the node_modules folder
use: [{
loader: "jshint-loader"
}]
});
environment.loaders.append('signature_pad', {
test: /\.js?$/,
include: [/node_modules\/signature_pad/],
use: [{
loader: 'babel-loader',
options: {
cacheDirectory: true,
presets: [['env', { 'modules': false, 'targets': { 'node': 4 } }]]
}
}],
});
environment.loaders.append('sass', {
test: /\.(sass|scss)$/,
use: [
// Creates `style` nodes from JS strings
'style-loader',
// Translates CSS into CommonJS
'css-loader',
// Compiles Sass to CSS
{
loader: 'sass-loader',
options: {
// Prefer `dart-sass`
implementation: require('sass'),
},
},
],
});
const resolver = {
resolve: {
alias: {
'vue$': 'vue/dist/vue.js'
}
}
};
environment.config.merge(resolver);
module.exports = environment;

Webpacker, babel, uglifyjs-webpack-plugin - not transforming arrow functions, but only in Vue files

Running webpacker 3.5.5 (both the gem and package). This is mostly working, but in IE11 the app is broken because arrow functions do not appear to be transformed. However, inspecting the minified code it seems like the only place arrow functions aren't transformed are inside my vue components.
I think this is because my babel class properties plugin is not applying to my Vue loader somehow, but I haven't been able to come up with a solution.
Here's my .babelrc
{
"presets": [
[
"env",
{
"modules": false,
"targets": {
"browsers": [
"> 1%",
"IE 11"
],
"uglify": true
},
"useBuiltIns": true
}
]
],
"plugins": [
"syntax-dynamic-import",
"transform-object-rest-spread",
[
"transform-class-properties",
{
"spec": true
}
]
],
"env": {
"test": {
"presets": ["es2015"]
}
}
}
And here's the entirety of my environment.js file that modifies the webpack environment that webpacker ships with (vue loader is at the bottom).
const { environment } = require('#rails/webpacker');
environment.loaders.append('expose', {
test: require.resolve('jquery'),
use: [{
loader: 'expose-loader',
options: 'jQuery'
}]
});
const webpack = require('webpack');
// append some global plugins
environment.plugins.append('Provide', new webpack.ProvidePlugin({
$: 'jquery',
jQuery: 'jquery',
axios: 'axios',
moment: 'moment-timezone',
_: 'lodash'
}));
// Necesary configuration for vue-loader v15
const VueLoaderPlugin = require('vue-loader/lib/plugin');
environment.plugins.append(
'VueLoaderPlugin',
new VueLoaderPlugin()
);
environment.loaders.append('vue', {
test: /\.vue$/,
loader: 'vue-loader'
});
module.exports = environment;
Edit for more info: Here is the entry point to my pack called 'wrestling'
import 'babel-polyfill';
import 'wrestling';
Then in wrestling.js...
import './styles/wrestling'
import Rails from 'rails-ujs'
Rails.start();
import wrestlingSetup from './wrestlingSetup'
wrestlingSetup();
WrestlingSetup contains the actual references to the vue files. I've cut down the file to show what a single vue import looks like within the file. All the rest are essentially the same.
import Vue from 'vue/dist/vue.esm'
// Redacted a bunch of imports, but they all look like this oen
import WrestlerCreate from './vue/wrestler_create.vue'
export default function() {
document.addEventListener('DOMContentLoaded', () => {
axiosSetup();
const app = new Vue({
el: '#app',
components: {
// Other vue components here that I've removed for simplicity
WrestlerCreate,
}
})
});
}
Here's an actual example of the Vue component
<template>
<div role="form">
<!-- other form elements -->
</div>
</template>
<script>
export default {
name: 'wrestler-create',
props: [
],
// This does not get transformed by babel
data() {
return {
loading: false,
error: false,
errorMessage: "Error, please try again later or contact support.",
first_name: '',
last_name: '',
weight_class: '',
academic_class: ''
}
},
methods: {
// removed for simplicity
}
}
</script>
For clarify sake:
Please use function() for data. I find function() gives me less trouble than arrow functions.
export default {
data: function() {
return {
message: "Hello something!",
secondMessage: "Hello world!"
};
}
}
If you really wish to use arrow function, you can write:
export default {
data: () => {
return {
message: "Hello something!",
secondMessage: "Hello world!"
};
}
}

How can I set that grunt compile all of scss to css in a folder?

I want that all scss files here:'sites/all/themes/uj/sass/*.scss'
be compiled here: sites/all/themes/uj/css/*.css'
The * doesn't work. Why?
And what if I have other scss files in an other folder?
Or my all scss files should be in the same folder?
This is my gruntfile.js:
module.exports = function(grunt) {
// Project configuration.
grunt.initConfig({
pkg: grunt.file.readJSON('package.json'),
connect: {
uses_defaults: {}
},
sass: {
dev: {
options: { sourceMap: true },
files: { 'sites/all/themes/uj/css/*.css' : 'sites/all/themes/uj/sass/*.scss' }
}
},
watch: {
css: {
files: 'sites/all/themes/uj/**/*.scss',
tasks: [ 'sass:dev' ],
options: { livereload: true }
}
}
});
// Load Grunt plugins
// grunt.loadNpmTasks('');
// Default task(s).
grunt.registerTask('default', []);
// Load Grunt plugins
grunt.loadNpmTasks('grunt-contrib-connect');
grunt.loadNpmTasks('grunt-sass');
grunt.loadNpmTasks('grunt-contrib-watch');
grunt.loadNpmTasks('grunt-sass-globbing');
grunt.loadNpmTasks('grunt-contrib-sass');
grunt.loadNpmTasks('grunt-contrib-cssmin');
};

Resources