I'm trying to set up a very basic css configuration for my react project. I'm using webpack and style loaders, like so:
// webpack.config.js
const {resolve} = require("path");
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const port = process.env.PORT || 3000;
module.exports = {
entry: "./src/js/index.js",
output: {
filename: "bundle.[hash].js"
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: "babel-loader"
}
}, {
test: /\.scss$/,
use: [
MiniCssExtractPlugin.loader, {
loader: "css-loader",
options: {
modules: true,
camelCase: true,
sourceMap: true
}
}, {
loader: "sass-loader",
options: {
sourceMap: true,
precision: 8,
data: "$ENV: " + "PRODUCTION" + ";"
}
}
]
}
]
},
devServer: {
host: 'localhost',
port: port
},
devtool: 'inline-source-map',
plugins: [
new HtmlWebpackPlugin({
template: resolve("public", "index.html"),
favicon: resolve("public", "favicon.ico")
}),
new MiniCssExtractPlugin({
filename: "[name].css",
chunkFilename: "[id].css"
})
]
};
My problem is that changes in my css file aren't reflected in the html that my react components return.
So if I have a component like:
import React from 'react'
require('../../styles/style.scss')
const App = () => (<div className="root">
<div id='banner1' className='banner'>
<h1>foo</h1>
<h2>bar</h2>
</div>
</div>)
export default App
... and an scss file like:
#banner1 {
height: 100vh;
background: blue;
width: 100%;
}
h1 {
font-size: 30px;
color: white;
}
... my styles will show up initially, but any changes while the server is still running won't be reflected if I refresh the page. It will only reflect the changes in my stylesheet when I restart the server.
My suspicion is that the mini-css-extract-plugin package is minifying the css and packing it into a bundle that react doesn't see in the development environment whenever it's changed, and it doesn't get rebundled.
If I'm right, my conflict is that this is the ubiquitous way I've read in tutorials to set up your webpack configuration, and there is literally zero mention of this side effect being present in a dev environment. Is there an alternate configuration I should be specifying for a dev environment? Is there something I'm missing?
Try using classname={styles.banner} in case of scss.
Related
Simple webpack setup, when I use import to load my scss it is completely missing from the bundle. The line where the import should be is simply missing. When I use require instead, it works.
optimization: {usedExports: true} is not the problem, I tried with and without
mini-css-extract-plugin also did not work.
when I put a typo in the scss it complains, so it is parsed but simply not bundled in the end?
index.js
require("./scss/style.scss");
//import "./scss/style.scss" <--- not working
import { createApp } from 'vue';
import App from './components/App.vue';
const el = document.getElementById('app');
createApp(App).mount(el);
webpack config
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const { VueLoaderPlugin } = require('vue-loader');
const { DefinePlugin } = require('webpack');
const dist = path.resolve(__dirname, "dist");
module.exports = env => {
const mode = env.production == true ? "production" : "development";
const devtool = mode == "production" ? false : "inline-source-map";
return {
mode: mode,
entry: './web/index.js',
output: {
filename: 'bundle.js',
path: dist
},
optimization: {
usedExports: true,
},
devServer: {
static: {
directory: dist
},
port: 8888
},
module: {
rules: [{
test: /\.(sa|sc|c)ss$/,
use: [
'style-loader',
'css-loader',
'sass-loader',
],
}, {
test: /\.(ttf|eot|woff|woff2|svg)$/,
use: {
loader: 'file-loader',
options: {
name: '[name].[ext]',
outputPath: 'fonts/'
},
},
}, {
test: /\.vue$/,
loader: 'vue-loader'
}]
},
plugins: [
new CleanWebpackPlugin(),
new DefinePlugin({
__VUE_OPTIONS_API__: false,
__VUE_PROD_DEVTOOLS__: false,
}),
new HtmlWebpackPlugin({
template: path.resolve("./web/index.html")
}),
new VueLoaderPlugin()
],
resolve: {
extensions: ['.js'],
alias: {
"#": path.resolve(__dirname, 'web')
}
},
devtool
};
};
I found the problem but I don't understand why webpack drops it.
Quote from https://webpack.js.org/guides/tree-shaking/
Note that any imported file is subject to tree shaking. This means if you use something like css-loader in your project and import a CSS file, it needs to be added to the side effect list so it will not be unintentionally dropped in production mode:
In my package.json I put
"sideEffects": false,
to be able to use treeshaking.
But I had to disable it in the loader rule
{
test: /\.(sa|sc|c)ss$/,
use: ['style-loader','css-loader','sass-loader'],
sideEffects: true <----
}
For 2 days I have been trying to compile the js and css file to a separate file because now everything is together. Does anyone have any idea how this can be solved?
I would be very grateful for your help.
There is my code webpack.config.js
const path = require('path');
const webpack = require('webpack');
const BrowserSyncPlugin = require('browser-sync-webpack-plugin');
module.exports = {
entry: './src/index.js',
output: {
path: path.resolve(__dirname, 'src/dist'),
filename: 'bundle.js'
},
module: {
rules: [
{
test: /\.m?js$/,
exclude: /(node_modules|bower_components)/,
use: {
loader: 'babel-loader',
options: {
presets: ['#babel/preset-env']
}
}
},
{
test: /\.scss$/,
use: [
"style-loader", // creates style nodes from JS strings
{
loader: "css-loader",
options: {
url: false
}
},
"sass-loader" // compiles Sass to CSS, using Node Sass by default
]
},
]
},
plugins: [
new BrowserSyncPlugin({
// browse to http://localhost:3000/ during development,
// ./public directory is being served
host: 'localhost',
port: 3000,
files: ['./src/*.html'],
server: { baseDir: ['src'] }
}),
new webpack.ProvidePlugin({
$: 'jquery',
jQuery: 'jquery'
})
]
};
I think MiniCssExtractPlugin is what you are looking for.
It takes the output of css-loader and create .css bundles. It takes care of downloading them in the browser (by pushing a section of code in webpack runtime code), and also yeah, it minifies the .css :).
Simple usage:
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = {
plugins: [new MiniCssExtractPlugin()],
module: {
rules: [
{
test: /\.css$/i,
use: [MiniCssExtractPlugin.loader, 'css-loader'],
},
],
},
};
Yes you are right. Style-loader creates javascript snippets that later in runtime creates .css rules and push them to the browser global css scope.
I have set my global.css file which I import in index.js
--root {
--main-color: red;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
index.js
import "./global.css";
import App from "./App.svelte";
const app = new App({
target: document.body
});
My webpack setup
const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const ExtractTextPlugin = require("extract-text-webpack-plugin");
module.exports = {
entry: "./src/index.js",
output: {
filename: "bundle.js",
path: path.resolve(__dirname, "dist")
},
module: {
rules: [
{
test: /\.(html|svelte)$/,
exclude: /node_modules/,
use: {
loader: "svelte-loader",
options: {
emitCss: true,
hotReload: true
}
}
},
{
test: /\.css$/,
use: ExtractTextPlugin.extract({
fallback: { loader: "style-loader", options: { sourceMap: true } },
use: [
{ loader: "css-loader", options: { sourceMap: true } },
{
loader: "postcss-loader",
options: {
sourceMap: true,
ident: "postcss",
plugins: loader => [
require("postcss-import")({}),
require("postcss-preset-env")(),
require("cssnano")()
]
}
}
]
})
}
]
},
plugins: [new HtmlWebpackPlugin(), new ExtractTextPlugin("styles.css")]
};
Works perfect for setting up global css for the entire app. But I am trying to use the --main-color in my svelte components. Is there a way to inject them down to all the components' css ?
Since I import global.css first, it should work as it emits a file with --root{} first then rest of the component styles.
You can place global styles under /routes/index.svelte file, like the example below:
<style>
:global(:root){
--header-color: purple
}
</style>
And simply use it anywhere like normally how you use CSS variables like so:
h1 {
color: var(--header-color);
}
I was busy with this, trying different webpack settings etc., seeing that the output css should work, I just could not find why it did not work. I wrote the post before trying for one last time, which wasted another hour. I finally found the error.
Instead of using :root{} I have mistyped it --root{}. I have posted it anyways, in case someone is stuck with the same mistake.
I am using MiniCssExtractPlugin in my typescript and webpack project.
My webpack config for the MiniCssExtractPlugin looks like
const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
module.exports = {
entry: './src/index.tsx',
mode: "development",
output: {
path: path.resolve(__dirname, "build"),
filename: 'bundle.js'
},
module: {
rules: [
{
test: /\.tsx?$/,
loader: "awesome-typescript-loader"
},
{
enforce: "pre",
test: /\.js$/,
loader: "source-map-loader"
},
{
test: /\.scss$/,
use: [
MiniCssExtractPlugin.loader,
{
loader: "css-loader",
options: {
modules: true,
sourceMap: true,
importLoader: 2
}
},
"sass-loader"
]
}
]
},
plugins: [
new HtmlWebpackPlugin({
template: "./index.html"
}),
new MiniCssExtractPlugin({
filename: "foo.css",
chunkFilename: "[id].css"
})],
devtool: "source-map",
resolve: {
extensions: [".js", ".ts", ".tsx"]
}
}
Now the scss file in my project has this fragment
h1 {
border-bottom: 3px solid #880055;
display: inline;
}
.container {
font-size: 1.3rem;
}
.is-completed {
text-decoration: line-through;
color: #00ff00;
}
when my application is run using npm start I can see that the heading H1 has a underline of the color 880055. So this means that my scss file was read correctly.
If I go into chrome developer tools and go into network tab and look for CSS. I can see a foo.css being downloaded. If I look into the content of foo.css
It doesn't have my "is-completed" class. instead I see something like
h1 {
border-bottom: 3px solid #880055;
display: inline; }
.pxcHIyOVHeytUeG27u4TO {
font-size: 1.3rem; }
._1Z5_KVJNKd1X2P3HKM63j {
text-decoration: line-through;
color: #00ff00; }
So element classes like h1 are good, but everything else is garbled. What's going on?
When you set modules: true in your CSS config you are telling the css-loader to use CSS-Modules to scope your class names to a particular file.
You can use the localIndentName query paramater in the css-loader options to specify what you want your generated class (identifier) to look like in development and/or in prod. See example below for what solved this for me.
module.exports = {
module: {
rules: [
{
test: /\.css$/i,
loader: 'css-loader',
options: {
modules: {
localIdentName: '[name]-[local]--[hash:base64:5]',
},
},
},
],
},
};
If I were to use the configuration in the example above and the name of the component that I was rendering was called HelloWorld and a class used in that component was .container, if I were to run my app (dev or prod) and inspect the element in the devtools the class on my HelloWorld component appear as follows:
<div class="HelloWorld-container--16ABh"> Hello World </div>
You can play around with what you set as your localIdentName and how many characters of the hash you show.
See the documentation for the localIdentName query param here: https://github.com/webpack-contrib/css-loader#localidentname
I'm trying to setup css modules with postcss + cssnext. It all seems to be working fine, except that the composes keyword is simply not working. The rule vanishes when the bundle is compiled.
Here's my webpack config file:
'use strict'
const path = require('path')
const webpack = require('webpack')
const HtmlPlugin = require('html-webpack-plugin')
module.exports = {
devtool: 'inline-source-map',
entry: [
'react-hot-loader/patch',
'webpack-dev-server/client?http://localhost:3000',
'webpack/hot/only-dev-server',
path.join(__dirname, 'src', 'index')
],
output: {
path: path.join(__dirname, 'dist'),
filename: '[name]-[hash].js',
publicPath: ''
},
plugins: [
// new DashboardPlugin(),
new webpack.HotModuleReplacementPlugin(),
new webpack.NamedModulesPlugin(),
new HtmlPlugin({
title: 'Github App',
template: path.join(__dirname, 'src', 'html', 'template-dev.html')
})
],
module: {
rules: [{
test: /\.js$/,
exclude: /node_modules/,
include: /src/,
use: 'babel-loader'
}, {
test: /\.css$/,
exclude: /node_modules/,
include: /src/,
use: ['style-loader', {
loader: 'css-loader',
options: {
modules: true,
importLoaders: 1,
localIdentName: '[local]--[hash:base64:5]'
}
}, {
loader: 'postcss-loader',
options: {
ident: 'postcss'
}
}]
}]
},
resolve: {
alias: {
Src: path.join(__dirname, 'src'),
Components: path.join(__dirname, 'src', 'components')
}
}
}
I'm using style-loader for this dev environment so I can use hot reloading. The css file is being imported like this: import './app.css'
app.css:
:global{
.app {
float: left;
padding: 10px;
width: 100%;
}
}
.className {
color: green;
background: red;
}
.otherClassName{
composes: className;
color: yellow;
}
this results in:
My postcss.config.js file:
module.exports = {
plugins: {
'postcss-import': {},
'postcss-cssnext': {
browsers: ['last 2 versions', '> 5%']
},
'postcss-nested': {}
}
}
Am I missing something to get composes to work?
Looks like this is fine:
The implementation of webpack's css_loader is to add both classes when exporting the styles (see https://github.com/webpack-contrib/css-loader#composing)
This also makes more sense, since it will ultimately render out less CSS code.
Try importing the styles and apply them to an HTML node and you will see it should receive both classes.
In your example it would have done something like:
exports.locals = {
className: 'className-2yqlI',
otherClassName: 'className-2yqlI otherClassName-1qAvb'
}
So when you do:
import styles from '../app.css'
// ...
<div className={styles.otherClassName} />
The div gets both classes.