How to get SCSS variables into react - node.js

I followed this post How to use SCSS variables into my React components or this one React and SCSS export a variable from scss to react to get scss variable in my react app but it does not work.
myvar.scss file:
$color: red;
:export {
color: $color;
}
.myclass {
background-color: $color;
}
App.js file:
import variables from './myvar.scss';
const App = () => {
console.log(variables);
return <div className="myclass">Hello</div>
}
export default App;
The div background is red, so myvar.scss is working. But variables is an empty object.
(react version : 17.0.1)
node_modules\react-scripts\config\webpack.config.js
module: {
strictExportPresence: true,
rules: [
{ parser: { requireEnsure: false } },
{
oneOf: [
...
{
test: sassRegex,
exclude: sassModuleRegex,
use: getStyleLoaders(
{
importLoaders: 3,
sourceMap: isEnvProduction
? shouldUseSourceMap
: isEnvDevelopment,
},
'sass-loader'
),
sideEffects: true,
},

UPDATE: The original answer claims that it is only supported by webpack, but this is no longer true. Many bundlers now support this via their own css processing pipeline.
Original Answer:
That's a webpack/css-loader feature and only works with webpack and css-loader (https://webpack.js.org/loaders/css-loader/#separating-interoperable-css-only-and-css-module-features)
Important: the :export syntax in your SCSS/CSS files will only work for those files that are treated as a module by css-loader, because this syntax is part of a css module proposal.
You can...
either use the default behavior of css-loader and trigger that behavior via filename: e.g. foostyle.module.scss
or you can configure css-loader to treat all files as modules e.g. loader: 'css-loader', options: { modules: true }
A blogpost with an example can be found here: https://til.hashrocket.com/posts/sxbrscjuqu-share-scss-variables-with-javascript
(Be aware that the blogpost doesn't mention the fact that css modules must be used.)
$white-color: #fcf5ed;
:export {
whitecolor: $white-color;
}
and
import variables from 'variables.module.scss';
console.log(variables.whitecolor)
your webpack.config.js will probably contain a chain of loaders with css-loader as last or second-to-last (process-chronologically speaking) and this should work.
module: {
rules: [
{
test: /\.scss$/,
use: [ 'style-loader', 'css-loader', 'sass-loader' ],
},

I would say try using like this
const App = () => {
const [parameters, setParameters] = useState({
fontFamily: ''
});
return (
<div style={setParameters(fontFamily && { fontType: 'font_name' })}>
{content}
</div>
);
};
Hope this works for you.

Related

How to wrap Vite build in IIFE and still have all the dependencies bundled into a single file?

I'm building a chrome extension using Vite as my build tool. The main problem is during minification and mangling there are a lot of global variables created. After injecting my script to the page they conflict with already defined variables on window object.
I imagine the perfect solution would be to have my entire script wrapped in IIFE. I tried using esbuild.format = 'iife'. The resulting build is in fact wrapped in IIFE, however all the imports are not inlined. Instead resulting script is like 15 lines long with a bunch of require statements, which obviously does not work in the browser.
This is my config file:
export default defineConfig({
plugins: [
vue(),
],
esbuild: {
format: 'iife',
},
build: {
emptyOutDir: false,
rollupOptions: {
input: resolve(__dirname, './src/web/index.ts'),
output: {
dir: resolve(__dirname, './dist'),
entryFileNames: 'web.js',
assetFileNames: 'style.css',
},
},
},
resolve: {
alias: {
'#': resolve(__dirname, './src'),
},
},
});
I'm currently using this hack so to say to wrap my build in IIFE (for this I removed the esbuild.format option).
Hey I am doing the exact same thing! And I also noticed the unminified variables and functions can clash with random code in a webpage.
From what I researched myself on this topic, you shouldn't change esbuild build options with Vite as that will prevent Rollup from transforming the output. Instead, you should use format: 'iife' in the rollupOptions of your vite.config. However, in my case (and yours I believe), I have to output multiple bundles since the extension code can't share modules amongst each other. Which will crash when you set the format to 'iife' due to:
Invalid value for option "output.inlineDynamicImports" - multiple inputs are not supported when "output.inlineDynamicImports" is true.
The only solution in my case seems to be to either use multiple vite.configs (I already have two) for each of my bundle with single input entry point and format as 'iife'. Or, as you did, just write the self-invoking function yourself with some hacky script. Seems though there aren't any perfect solutions as of now.
EDIT: Okay, got it working. This is my vite.config.ts (the project):
import { defineConfig } from 'vite'
import { svelte } from '#sveltejs/vite-plugin-svelte'
import tsconfigPaths from 'vite-tsconfig-paths'
import path from 'path'
/** #type {import('vite').UserConfig} */
export default defineConfig({
plugins: [svelte({}), tsconfigPaths()],
build: {
minify: false,
rollupOptions: {
output: {
chunkFileNames: '[name].js',
entryFileNames: '[name].js'
},
input: {
inject: path.resolve('./src/inject.ts'),
proxy: path.resolve('./src/proxy.ts'),
'pop-up': path.resolve('./pop-up.html')
},
plugins: [
{
name: 'wrap-in-iife',
generateBundle(outputOptions, bundle) {
Object.keys(bundle).forEach((fileName) => {
const file = bundle[fileName]
if (fileName.slice(-3) === '.js' && 'code' in file) {
file.code = `(() => {\n${file.code}})()`
}
})
}
}
]
}
}
})
Okay, I made it working with this config:
export default defineConfig({
plugins: [
vue(),
],
build: {
emptyOutDir: false,
rollupOptions: {
input: resolve(__dirname, './src/web/index.ts'),
output: {
format: 'iife',
dir: resolve(__dirname, './dist'),
entryFileNames: 'web.js',
assetFileNames: 'style.css',
},
},
},
resolve: {
alias: {
'#': resolve(__dirname, './src'),
},
},
});
They key part is format: 'iife' inside build.rollupOptions.output.

How to configure Webpack to not pull content of imported Sass files to a source map?

I have a Sass file that imports Bootstrap and Font Awesome.
They has been put on the of the file before my custom CSS class.
Here is the code:
/src/scss/site.scss
#import "~bootstrap/scss/bootstrap";
#import "~font-awesome/scss/font-awesome";
// Custom style sheet here
.my-custom-header{
color:#F00
}
There is a source map after Webpack build but it includes the content of Bootstrap and font awesome.
Here what is look like in Browser:
When I tried to inspect a custom class it point to correct line number of origin source code but incorrect for generated source map because it has content of Bootstrap in the top.
My question is:
Is it possible to configure a output source map to keep the import statement and the content is exact the same as actual source code.
Here what source map I'm expected.
site.css.map
#import "~bootstrap/scss/bootstrap";
#import "~font-awesome/scss/font-awesome";
// Custom style sheet here
.my-custom-header{
color:#F00
}
Here my Webpack configuration:
const path = require('path');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const siteFile = [__dirname, 'src', 'scss', 'site'];
const outputPath = [__dirname, 'public', 'css'];
module.exports = {
entry: {
site: path.resolve(...siteFile),
},
output: {
path: path.resolve(...outputPath),
},
resolve: {
// https://github.com/webpack/webpack-dev-server/issues/720#issuecomment-268470989
extensions: ['.scss']
},
devtool: 'source-map',
module: {
rules: [
{
test: /\.scss$/,
use: [
{
loader: MiniCssExtractPlugin.loader,
},
{
loader: 'css-loader', // Translates CSS into CommonJS modules
options: {
sourceMap: true
}
},
{
loader: 'postcss-loader', // Run post css actions
options: {
plugins: () => {
// post css plugins, can be exported to postcss.config.js
return [
require('precss'),
require('autoprefixer')
];
},
sourceMap: true
}
},
{
loader: 'resolve-url-loader',
},
{
loader: 'sass-loader', // Compiles Sass to CSS, using node-sass by default
options: {
sourceMap: true
}
}
],
exclude: /node_modules/
},
{
test: /\.(png|jpe?g|gif|svg|eot|ttf|woff2?)$/,
use: [
{
loader: 'file-loader',
options: {
name: '[name].[ext]',
outputPath: '.' //relative to output
}
}
]
}
]
},
plugins: [
new MiniCssExtractPlugin({
filename: './[name].css' // relative to output
}),
],
devServer: {
contentBase: path.join(__dirname, 'public'),
compress: false,
port: 8080,
}
};
Here is a link to a repository of the example code.
codesanook-examples-webpack
Thank you.
Have you tried SourceMapDevToolPlugin?
https://webpack.js.org/plugins/source-map-dev-tool-plugin/
You might need to create a vendor bundle for node modules stuff then you can ignore it from sourcemap.

Migrating RequireJS/AMD with Plugins to Webpack

I'm working on migrating a large RequireJS application to Webpack. The basic build with Webpack seems to work fine -- I've moved "paths" definitions to "alias" and I've setup loaders for my content and shims, like jQuery.
However, there's a remaining issue I'm not sure how to resolve. Basically the RequireJS app uses the "text-plugin" to include HTML templates, and Webpack is throwing "Module not found" errors for the HTML templates.
An example AMD module I want to bundle looks something like this:
AMD Module with Text Plugin
define([
'security',
'modals',
'text!../templates/contact_info.html'
], function(security, modals, contactInfoTemplate) {
return {
foo: function() { return "bar"; }
};
});
I thought I could use the raw-loader to load the template files. I aliased 'text' to be the 'raw-loader':
text: {
test: /\.html$/,
loader: "raw-loader"
},
However, I'm seeing the following error for all of my templates that are required like above:
Module not found: Error: Can't resolve 'text'
BREAKING CHANGE: It's no longer allowed to omit the '-loader' suffix when using loaders. You need to specify 'text-loader' instead of 'text'.
I tried replacing 'text!...' with 'text-loader!...', and I then see this error complaining that it can't load/find the HTML module!
Module not found: Error: Can't resolve '../templates/contact_info.html'
webpack.config.js, version 3.9.1
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const path = require('path');
const webpack = require('webpack');
let basePath = path.join(__dirname, '/');
module.exports = {
entry: {
'main': basePath + 'js/main.js',
},
context: __dirname,
output: {
path: __dirname + '/build',
filename: '[name].min.js',
libraryTarget: 'amd',
umdNamedDefine: true
},
module: {
rules: [
{
test: /(\.js)$/,
exclude: /(node_modules)/,
use: {
// babel-loader to convert ES6 code to ES5 + amdCleaning requirejs code into simple JS code, taking care of modules to load as desired
loader: 'babel-loader',
options: {
presets: ['#babel/preset-env'],
plugins: []
}
}
},
{
test: /\.css$/,
use: [
{ loader: "style-loader" },
{ loader: "css-loader" }
]
},
{ test: /\.jpg$/, use: [ "file-loader" ] },
{ test: /\.png$/, use: [ "url-loader?mimetype=image/png" ] },
{
test: /\.(html)$/,
use: {
loader: 'raw-loader',
options: {
minimize: true
}
}
}
]
},
resolve: {
modules: [
'js/**/*.js',
'node_modules',
path.resolve('./js')
],
extensions: ['.js'], // File types,
alias: {
text: {
test: /\.html$/,
loader: "raw-loader"
},
bridge: 'libs/bridge',
cache: 'libs/cache',
cards: 'libs/cards',
moment: 'libs/moment',
underscore: 'libs/underscore',
}
},
plugins: [
new CleanWebpackPlugin(['dist']),
new HtmlWebpackPlugin({
filename: 'index.html',
template: '../index.html'
}),
new webpack.ProvidePlugin({
$: 'jquery',
jQuery: 'jquery'
})
]
};
Anyone know how to get Webpack to play nicely with the RequireJS Text plugin?
Thanks!
Maybe try installing text-loader?
In order for something like 'text!../templates/contact_info.html' to "load" properly, since it is not JS, you need to install text-loader to get webpack to understand the syntax text!.
npm install text-loader --save-dev
humm...i just installed text-loaded and it seems we also have to change text! to text-loader!

Can't Load Browser Dependencies Webpack

I am trying to scaffold an Angular 6 app by hand (ie not using the CLI). I was doing OK until I ran into the following error when running webpack:
ERROR in window is not defined
Now from googling around it looks like I'm missing some polyfills since webpack uses node in order to generate it's output. I've reviewed the examples on Angular's site and added the polyfills.ts file to my application but I still can't get rid of the error.
Here is my webpack.confg.js:
const HtmlWebpackPlugin = require('html-webpack-plugin');
const ScriptExtPlugin = require('script-ext-html-webpack-plugin');
const { AngularCompilerPlugin } = require('#ngtools/webpack');
module.exports = function() {
return {
entry: {
index: "./src/client/client.ts",
polyfills: "./src/client/polyfills.ts"
},
output: {
path: __dirname + "/client-dist",
filename: "[name].client.js"
},
resolve: {
extensions: ['.ts', '.js']
},
module: {
rules: [
{
test: /\.ts$/,
loader: '#ngtools/webpack'
},
{
test: /\.html$/,
loader: 'html-loader',
},
{
test: /\.css$/,
loader: ["style-loader", "css-loader"]
}
]
},
plugins: [
new HtmlWebpackPlugin({
template: __dirname + '/src/client/index.html',
output: __dirname + '/client-dist',
inject: 'head'
}),
new ScriptExtPlugin({
defaultAttribute: 'defer'
}),
new AngularCompilerPlugin({
tsConfigPath: './tsconfig.json',
entryModule: './src/client/app/app.module#AppModule'
})
]
}
}
My polyfills.ts file:
import 'core-js/es6';
import 'core-js/es7/reflect';
import 'zone.js/dist/zone';
And my client.ts file (entry point of my application):
import './polyfills'
import { platformBrowserDynamic } from '#angular/platform-browser-dynamic';
import { AppModule } from './app/app.module';
platformBrowserDynamic().bootstrapModule(AppModule);
I'm sure I'm just doing something stupid but any help would be appreciated. Thanks!
EDIT 1:
After reading the article posted by #SureshKumarAriya I tried changing the following in my webpack.config:
new AngularCompilerPlugin({
tsConfigPath: './tsconfig.json',
entryModule: './src/client/app/app.module#AppModule',
skipCodeGeneration: true // This is new
})
And I get a different error: ERROR in Resolution of relative paths requires a containing file.
I'm guessing this means it can't resolve one of the typescript files I reference in client.ts? I'm not sure this has gotten me any closer but still interesting.
As always thanks for the help!
Inside output cofiguration. please add globalObject: "this".
output: {
// ...
globalObject: "this"
}
https://github.com/markdalgleish/static-site-generator-webpack-plugin/issues/130
Seems like your dependencies still rely on the window object.
Do validate
typeof window !== 'undefined'
Please refer to the following link.

CSS Loader seems to not to export anything

I have a nodejs + typescript + ReactJS + webpack + css loader application.
I got to get imports to CSS modules, i.e. my statements
import * as styleILove from './css/mycoolCSS.css';
Bundles properly and the output seems valid. Otherwise the application works fine.
The problem is, if I do console.log (styleILove) the object exists but there is nothing inside the object. According to CSS loader documentation I should be able to issue console.log(styleILove.myClassName) but there is nothing. In the console of the browser it does not exist and VS code highlight also complains.
Any ideas why that is failing?
My webpack config:
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin')
const config = {
entry: "./src/index.tsx",
resolve: {
extensions: ['.tsx', '.js', '.css']
},
output: {
filename: "bundle.js",
},
plugins: [
new HtmlWebpackPlugin({
title: 'asdfasdf',
}),
],
module: {
rules: [
{
test: /.tsx$/,
loader: "ts-loader" ,
},
{
test: /.css$/,
use: [ 'style-loader', 'css-loader' ],
}
]
}
}
module.exports = config;
My css:
.myClassName {
box-shadow: 1cm;
}
I usually do it like this:
const css = require("./css/mycoolCSS.css");
export const TestComponent: React.SFC = (): JSX.Element => (<div className={css["myClassName"]}/>);

Resources