Webpack config for SSR SCSS - reactjs

I have a React-TypeScript SSR app where I used SCSS files for my styling. I need to write a rule in Webpack to load the SCSS and I haven't been able to do it.
I found various solutions online, all of which are extremely complex and use things like mini-css-extract-plugin. I couldn't get any of them to work.
I currently have two webpack config files, one for the client (web) and one for the server (node), both of which load the SCSS as such:
{
test: /\.scss$/,
use: ["css-loader", "sass-loader"]
}
I also encountered another issue in that I can't use style-loader as it throws an error about the window object. Does anyone have a working example (simple preferably) of loading SCSS in Webpack?

You are on right track with 2 web config file you can use
https://gist.github.com/mburakerman/629783c16acf5e5f03de60528d3139af
But don't set any other config file like babel.rc .yaml etc or other definition in project.json
try this
test: /\.scss$/,
use: [
MiniCssExtractPlugin.loader,
'css-loader',
'postcss-loader',
'sass-loader'
]
//..
plugins: [
new MiniCssExtractPlugin({
filename: 'assets/css/bundle-[contenthash].css',
chunkFilename: 'assets/css/bundle-[contenthash].css'
})
],
Look full example https://github.com/dewelloper/pzone/blob/master/webpack.config.store.js

A boilerplate for server-side rendering using react, webpack, Sass
(for both css-modules and pure sass)
webpack.config.js
const path = require('path');
const isDevelopment = true;
module.exports = [
{
name: 'client',
target: 'web',
entry: './client.jsx',
output: {
path: path.join(__dirname, 'static'),
filename: 'client.js',
publicPath: '/static/',
},
resolve: {
extensions: ['.js', '.jsx']
},
devtool: 'source-map',
module: {
rules: [
{
test: /\.(js|jsx)$/,
exclude: /(node_modules\/)/,
use: [
{
loader: 'babel-loader',
}
]
},
{
test: /\.scss$/,
use: [
{
loader: 'style-loader',
},
{
loader: "css-loader",
options: {
modules: {
localIdentName: "[name]__[local]___[hash:base64:5]",
},
sourceMap: isDevelopment,
}
},
{
loader: 'sass-loader'
}
]
}
],
},
},
{
name: 'server',
target: 'node',
entry: './server.jsx',
output: {
path: path.join(__dirname, 'static'),
filename: 'server.js',
libraryTarget: 'commonjs2',
publicPath: '/static/',
},
devtool: 'source-map',
resolve: {
extensions: ['.js', '.jsx']
},
module: {
rules: [
{
test: /\.(js|jsx)$/,
exclude: /(node_modules\/)/,
use: [
{
loader: 'babel-loader',
}
]
},
{
test: /\.scss$/,
use: [
{
loader: 'isomorphic-style-loader',
},
{
loader: "css-loader",
options: {
modules: {
localIdentName: "[name]__[local]___[hash:base64:5]",
},
sourceMap: isDevelopment,
}
},
{
loader: 'sass-loader'
}
]
}
],
},
}
];
dev dependencies:
npm i -D #babel/cli #babel/preset-es2015 #babel/core #babel/plugin-proposal-class-properties #babel/preset-env #babel/preset-react babel-core babel-loader babel-plugin-lodash babel-plugin-react-transform babel-preset-env babel-preset-es2015 babel-preset-react babel-preset-stage-0 css-loader express isomorphic-style-loader node-sass sass-loader style-loader webpack webpack-dev-middleware webpack-hot-middleware webpack-hot-server-middleware
and dependencies:
npm i react react-dom react-helmet react-router-dom
sever.jsx:
import React from 'react';
import ReactDOMServer from 'react-dom/server';
import { StaticRouter } from 'react-router-dom';
import {Helmet} from "react-helmet";
import Template from './template';
import App from './App';
export default function serverRenderer({ clientStats, serverStats }) {
return (req, res, next) => {
const context = {};
const markup = ReactDOMServer.renderToString(
<StaticRouter location={ req.url } context={ context }>
<App />
</StaticRouter>
);
const helmet = Helmet.renderStatic();
res.status(200).send(Template({
markup: markup,
helmet: helmet,
}));
};
}
App.jsx:
import React, { Component } from 'react';
import { Switch, Route } from 'react-router-dom';
import Menu from './Menu'
import Helmet from "react-helmet";
import homepageStyles from './homepage.scss';
class Homepage extends Component {
render() {
return (
<div className={ homepageStyles.component }>
<Helmet
title="Welcome to our Homepage"
/>
<Menu />
<h1>Homepage</h1>
</div>
);
}
}
class About extends Component {
render() {
return (
<div>
<h1>About</h1>
</div>
);
}
}
class Contact extends Component {
render() {
return (
<div>
<h1>Contact</h1>
</div>
);
}
}
export default class App extends Component {
constructor(props) {
super(props);
}
render() {
return (
<div>
<Helmet
htmlAttributes={{lang: "en", amp: undefined}} // amp takes no value
titleTemplate="%s | React App"
titleAttributes={{itemprop: "name", lang: "en"}}
meta={[
{name: "description", content: "Server side rendering example"},
{name: "viewport", content: "width=device-width, initial-scale=1"},
]}
/>
<Switch>
<Route exact path='/' component={ Homepage } />
<Route path="/about" component={ About } />
<Route path="/contact" component={ Contact } />
</Switch>
</div>
);
}
}
template.jsx
export default ({ markup, helmet }) => {
return `<!doctype html>
<html ${helmet.htmlAttributes.toString()}>
<head>
${helmet.title.toString()}
${helmet.meta.toString()}
${helmet.link.toString()}
</head>
<body ${helmet.bodyAttributes.toString()}>
<div id="root">${markup}</div>
<div>Heeeeeeeeeeeeeeeeeeeelmet</div>
<script src="/static/client.js" async></script>
</body>
</html>`;
};
menu.jsx:
import { Link } from 'react-router-dom';
import React, { Component } from 'react';
import './menu.scss';
class Menu extends Component {
render() {
return (
<div>
<ul>
<li>
<Link to={'/'}>Homepage</Link>
</li>
<li>
<Link to={'/about'}>About</Link>
</li>
<li>
<Link to={'/contact'}>Contact</Link>
</li>
</ul>
</div>
);
}
}
export default Menu;
.babelrc:
{
"presets": [
"#babel/react",
"#babel/preset-env"
],
"plugins": [
"#babel/proposal-class-properties"
]
}
homepage.sccs
.component {
color: blue;
}
menu.scss:
li {
background-color: yellow;
}
I used this article:
https://blog.digitalkwarts.com/server-side-rendering-with-reactjs-react-router-v4-react-helmet-and-css-modules/

Webpack Community now details an approach for SSR sass compiled via webpack in mini-css-extract-plugin/#recommend
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const devMode = process.env.NODE_ENV !== "production";
module.exports = {
module: {
rules: [
{
test: /\.(sa|sc|c)ss$/,
use: [
devMode ? "style-loader" : MiniCssExtractPlugin.loader,
"css-loader",
"postcss-loader",
"sass-loader",
],
},
],
},
plugins: [].concat(devMode ? [] : [new MiniCssExtractPlugin()]),
};
Note that style-loader should not be used in an SSR app or in a webpack production build because injects CSS into the DOM. MiniCSSExtractPlugin is recommended for SSR production builds and should not be used with style-loader (window will not be defined in webpack's node-based prod build).

Related

Can't render an img with react and webpack

Hi guys I'm new to Webpack so I'm having some problems when trying to add the src of an img tag because I'm getting an error, I already tried some solutions that I saw in other similar questions like adding the url-loader but I still can't get it to work
I'm getting this error in my code
ERROR in ./client/src/img/logo.png 1:0
[0] Module parse failed: Unexpected character '�' (1:0)
[0] You may need an appropriate loader to handle this file type, currently no loaders are
configured to process this file. See https://webpack.js.org/concepts#loaders
[0] (Source code omitted for this binary file)
[0] # ./client/src/pages/Homepage.js 108:9-35
[0] # ./client/src/App.js 3:0-40 9:38-46
[0] # ./client/src/index.js 3:0-24 4:50-53
My Webpack.config.js code
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: {
main: path.join(__dirname, 'client/src/index.js')
},
output: {
path: path.join(__dirname, 'build'),
filename: 'bundle.js'
},
plugins: [new HtmlWebpackPlugin({
title: 'Ig Scraper',
template: path.join(__dirname, 'client/templates/index.ejs'),
filename: 'index.html'
})],
module: {
rules: [
{
test: /\.(js|jsx)$/,
exclude: /(node_modules|express)/,
use: {
loader: 'babel-loader',
options: {
presets: [
"#babel/preset-env",
"#babel/preset-react"
]
},
}
},
{
test: /\.(html)$/,
use: {
loader: 'html-loader',
options: {
attrs: [':data-src']
}
}
},
// {
// test: /\.(png|jpg)$/,
// include: path.join(__dirname, '/client/img'),
// loader: 'file-loader'
// },
{
test: /\.css$/i,
use: ['style-loader', 'css-loader'],
},
{
test: /\.(jpe?g|png|gif|woff|woff2|eot|ttf|svg)(\?[a-z0-9=.]+)?$/,
use: [
{
loader: 'url-loader?limit=100000'
}
]
}
]
},
devServer: {},
resolve: {
extensions: ["*", ".js", ".jsx"]
},
resolveLoader: {
extensions: ["babel-loader"]
},
devtool: 'source-map',
mode: 'development',
resolve: {
fallback: {
fs: false
}
}
};
My Homepage.js code
import React, { useState } from "react";
import axios from "axios";
import '../assets/Homepage.css'
// import logo from '../img/instagramLogo.png'
const Homepage = () => {
return (
<>
<div className="container">
<div className="homepage">
<div className="homepage__igAssets">
<img src={require('../img/logo.png')} alt="" className="ig__logo" />
{/* <img src="./assets/instagram.png" alt="" className="ig__text" /> */}
</div>
<h1 className="homepage__title">¡Let's scrape your instagram account! </h1>
<div className="homepage__elements">
<input className="homepage__input" placeholder="Username..." value= {username} onChange={onChange} />
<button className="homepage__button" onClick={onClick}>Get instagram followers!</button>
</div>
{renderData()}
</div>
</div>
</>
);
};
export default Homepage;
My files organization
Most likely, this error comes from Homepage.js, on the line:
<img src={require('../img/logo.png')} ... />
Require is not meant for images, but for js modules (learn more here).
Erratum: Above is about require in nodejs, it may not be the way to go, but require seems to work fine with webpack.
The way to use images with react would be:
// First, import the image file
import image from './path-to-image.png';
// Later, use it as source in the render
<img src={image} ... />
Your webpack.config.js looks fine (although i'm no expert at such things).
Add: Here is an other question highly related that might help you

isomorphic-style-loader doesn't work as it supposed to

Hey I am doing this simple react + SSR project that incorporates the isomorphic-style loader. I followed the step-by-step guide to implement it as detailed here https://www.npmjs.com/package/isomorphic-style-loader but it just doesn't work. The style I made is not showing. Can anyone guide me in fixing this issue?
Here is my webpack config
var path = require('path');
var webpack = require('webpack');
var nodeExternals = require('webpack-node-externals');
var browserConfig = {
entry: './src/browser/index.js',
output: {
path: path.resolve(__dirname, 'public'),
filename: 'bundle.js',
publicPath: '/',
},
module: {
rules: [
{ test: /\.(js)$/, use: 'babel-loader' },
{
test: /\.css$/,
use: [
'isomorphic-style-loader',
{
loader: 'css-loader',
options: {
importLoaders: 1,
},
},
'postcss-loader',
],
},
],
},
mode: 'production',
plugins: [
new webpack.DefinePlugin({
__isBrowser__: 'true',
}),
],
};
var serverConfig = {
entry: './src/server/index.js',
target: 'node',
externals: [nodeExternals()],
output: {
path: __dirname,
filename: 'server.js',
publicPath: '/',
},
mode: 'production',
module: {
rules: [
{ test: /\.(js)$/, use: 'babel-loader' },
{
test: /\.css$/,
use: [
'isomorphic-style-loader',
{
loader: 'css-loader',
options: {
importLoaders: 1,
},
},
'postcss-loader',
],
},
],
},
plugins: [
new webpack.DefinePlugin({
__isBrowser__: 'false',
}),
],
};
module.exports = [browserConfig, serverConfig];
here is my index.js (server)
import express from 'express';
import cors from 'cors';
import React from 'react';
import { renderToString } from 'react-dom/server';
import { StaticRouter, matchPath } from 'react-router-dom';
import serialize from 'serialize-javascript';
import StyleContext from 'isomorphic-style-loader/StyleContext';
import App from '../shared/App';
import routes from '../shared/routes';
const app = express();
app.use(cors());
app.use(express.static('public'));
app.get('*', (req, res, next) => {
const css = new Set(); // CSS for all rendered React components
const insertCss = (...styles) =>
styles.forEach((style) => css.add(style._getCss()));
const activeRoute = routes.find((route) => matchPath(req.url, route)) || {};
const promise = activeRoute.fetchInitialData
? activeRoute.fetchInitialData(req.path)
: Promise.resolve();
promise
.then((data) => {
const context = { data };
const markup = renderToString(
<StyleContext.Provider value={{ insertCss }}>
<StaticRouter location={req.url} context={context}>
<App />
</StaticRouter>
</StyleContext.Provider>
);
res.send(`
<!DOCTYPE html>
<html>
<head>
<title>SSR with RR</title>
<script src="/bundle.js" defer></script>
<script>window.__INITIAL_DATA__ = ${serialize(data)}</script>
<style type="text/css">${[...css].join('')}</style>
</head>
<body>
<div id="app">${markup}</div>
</body>
</html>
`);
})
.catch(next);
});
app.listen(3000, () => {
console.log(`Server is listening on port: 3000`);
});
here is my index.js (browser)
import React from 'react';
import { hydrate } from 'react-dom';
import App from '../shared/App';
import { BrowserRouter } from 'react-router-dom';
import StyleContext from 'isomorphic-style-loader/StyleContext';
const insertCss = (...styles) => {
const removeCss = styles.map((style) => style._insertCss());
return () => removeCss.forEach((dispose) => dispose());
};
hydrate(
<StyleContext.Provider value={{ insertCss }}>
<BrowserRouter>
<App />
</BrowserRouter>
</StyleContext.Provider>,
document.getElementById('app')
);
and here is a component inside the App.js which uses the css styling that does not work.
import React from 'react';
import { NavLink } from 'react-router-dom';
import style from './css/style.css';
import withStyles from 'isomorphic-style-loader/withStyles';
function Navbar() {
const languages = [
{
name: 'All',
param: 'all',
},
{
name: 'JavaScript',
param: 'javascript',
},
{
name: 'Ruby',
param: 'ruby',
},
{
name: 'Python',
param: 'python',
},
{
name: 'Java',
param: 'java',
},
];
return (
<ul className='navbar'>
{languages.map(({ name, param }) => (
<li key={param}>
<NavLink
activeStyle={{ fontWeight: 'bold' }}
to={`/popular/${param}`}
>
{name}
</NavLink>
</li>
))}
</ul>
);
}
export default withStyles(style)(Navbar);
I faced the same problem. Problem is related with css-loader. By default, css-loader generates JS modules that use the ES modules syntax. isomorphic-style-loader needs a CommonJS modules syntax.
Try this:
{
loader: 'css-loader',
options: {
importLoaders: 1,
esModule: false,
},
}

LESS style not applied to react component in react+webpack application

In a react + web pack application, I'm trying to style my react component using LESS, but the style does not get applied, although I get no errors, so I wouldn't know where to look. Of course, my devDependencies includes less, less-loader, CSS-loader and style-loader.
webpack.config.js
const path = require("path");
const webpack = require("webpack");
module.exports = {
entry: './src/index.js',
module: {
rules: [
{
test: /\.(js|jsx)$/,
exclude: /node_modules/,
use: [
"babel-loader",
"eslint-loader"
]
},
{
test: /\.(c|le)ss$/,
use: [
"style-loader",
"css-loader",
"less-loader",
]
}
]
},
resolve: {
extensions: [".js", ".jsx"],
alias: {
"#components": path.resolve(__dirname, "src/components"),
"#containers": path.resolve(__dirname, "src/containers")
}
},
output: {
path: __dirname + '/dist',
filename: 'bundle.js'
},
plugins: [
new webpack.HotModuleReplacementPlugin()
],
devServer: {
contentBase: './dist',
hot: true
}
};
components/App/App.jsx
import React from "react";
import Body from "#components/Body/Body.jsx";
import Footer from "#components/Footer/Footer.jsx";
import styles from "./App.less";
class App extends React.Component {
render() {
return <div className={styles.root}>
<h1> test </h1>
<Body />
<Footer />
</div>;
}
}
export default App;
components/App/App.less
.root {
width: 100%;
height: 100%;
background-color: coral;
h1 {
margin-top: 200px;
color: red;
}
}
I expected to see the style applied, but it's not.
Try setting "root" as string-value to className.
The way your webpack has been configured, the content of the LESS-files will not be exported as css-rules but only collected to be rendered into a style-tag.
You need to import the less-file, so webpack knows which files to consider, but you neither can access its rules, or its styles. So to make it work, you simply set the CSS-class names so that the compiled CSS-rules match.
I had to enable CSS Modules in the Webpack config:
{
test: /\.(c|le)ss$/,
use: [
"style-loader",
{
loader: 'css-loader',
options: {
modules: true,
localIdentName: "[path][name]__[local]--[hash:base64:5]",
},
},
"less-loader"
]
},

Module not found: Error: Can't resolve './component/Hello'

I´m trying to import a simple Component but the webpack seems to cant find it. The route is good and the "resolve" in the webpack config is great too, therefore I cant understand where is the issue.
Give it at look please.
By the way, its a Sails/React environment.
ERROR in ./assets/src/component/Hello.jsx 6:12
Module parse failed: Unexpected token (6:12)
You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file.
Hello.jsx:
import React from 'react'
class Hello extends React.Component {
render() {
return(
<div> // Err supposed to be here (line6)
Hello World!
</div>
)
}
}
export default Hello;
index.js:
import React from 'react';
import ReactDOM from 'react-dom';
import Hello from './component/Hello'
const App = () => {
return (
<div>
Simple Sails-React stater
<Hello/>
</div>
);
};
ReactDOM.render(<App />, document.getElementById('root'));
.babelrc:
{
"presets": ["#babel/env", "#babel/react"]
}
webpack config file:
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: {
entry: './assets/src/index.js'
},
output: {
path: __dirname + '/.tmp/public',
filename: 'bundle.js'
},
module: {
rules: [
{
use: 'babel-loader',
test: /\.js$/,
exclude: /node_modules/
},
{
use: ['style-loader', 'css-loader'],
test: /\.css$/
}
]
},
resolve: {
extensions: ['*', '.js', '.jsx']
},
plugins: [
new HtmlWebpackPlugin({
template: 'assets/src/index.html'
})
]
};
the structure is like this:
-src
--component
----Hello.jsx
--index.js
--index.html
Could you try change extension to Hello.js or change bable-loader test to
test: /\.(js|jsx)$/,

Webpack2-React. Imported classNames do not target the component class after bundling

If the elements are refernced directly by tag refernce (, ...) the styles are applied, otherwise not. If we explore the DOM in the chrome console it visible that the styles where loaded:
Component layout
import React, { Component } from 'react';
import { Link } from 'react-router';
import './style.css';
const parent = 'mutualism-header';
const Header = () => (
<div className={parent}>
<div className={`${parent}__wrapper`}>
<Link to='#' className={`${parent}__button`}>ABOUT</Link>
<Link to='#' className={`${parent}__button`}>CONTACT</Link>
<div className={`${parent}__share-block`}>
<a className={`${parent}_button share-button`} href='#'>SHARE</a>
<div className={`${parent}__close-button`}>
<a className='close-line'> </a>
</div>
</div>
</div>
</div>
)
export default Header;
Webpack Config
const path = require('path'),
webpack = require('webpack'),
HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: [
'react-hot-loader/patch',
'webpack-dev-server/client?http://localhost:8080',
'webpack/hot/only-dev-server',
'./index.js'
],
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'build'),
publicPath: '/'
},
context: path.resolve(__dirname, 'logic'),
devtool: 'inline-source-map',
devServer: {
hot: true,
contentBase: path.resolve(__dirname, 'build'),
publicPath: '/'
},
module: {
rules: [
{
test: /\.js$/,
use: [
'babel-loader',
],
exclude: /node_modules/
},
{
test: /\.css$/,
use: [
'style-loader',
'css-loader?modules',
'postcss-loader',
],
},
],
},
plugins: [
new webpack.HotModuleReplacementPlugin(),
new webpack.NamedModulesPlugin(),
new HtmlWebpackPlugin({
template: './index.template.html'
})<script> tag
],
};
PostCSS Config
module.exports = {
plugins: {
'postcss-import': {},
'postcss-cssnext': {
browsers: ['last 2 versions', '> 5%'],
},
},
};
The problem is that your components will have a class like this: mutualism-header__wrapper but the styles contains hashes that have been created by the css-loader.
The style.css that you are importing is being parsed by the loader. It will then export an object with the classes as properties. The properties will be converted to unique identifiers to avoid collisions.
Here's an example:
import React, { Component } from 'react';
import { Link } from 'react-router';
import styles from './style.css';
const Header = () => (
<div className={parent}>
<div className={styles.wrapper`}>
<Link to='#' className={styles.button}>ABOUT</Link>
<Link to='#' className={styles.button}>CONTACT</Link>
...
</div>
</div>
)
export default Header;
See how css-loader works.

Resources