Reputation: 441
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?
Upvotes: 5
Views: 4654
Reputation: 161
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).
Upvotes: 0
Reputation: 3144
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:
Upvotes: 0
Reputation: 4549
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
Upvotes: 1