How to ensure that hot CSS loads before JS in webpack-dev-server? - webpack-dev-server

I'm using webpack-dev-server to hot load all of my assets including CSS. Currently though, my CSS loads after the JavaScript which causes my application issues in some places that depend on layout existing.
How can I ensure that the CSS loads before the JavaScript executes?
I'm thinking there must be a way to do this from module, perhaps a a callback I could hook in to? Or maybe by configuring the style-loader to have priority?
This is my index.js:
import './styles/main.scss';
import './scripts/main.js';
if (module.hot) {
module.hot.accept();
}
and this is my styles loader:
{
test: /\.(scss)$/,
loader: 'style!css?&sourceMap!postcss!resolve-url!sass?sourceMap'
},
A similar question has been asked at this style-loader Github Issue: https://github.com/webpack/style-loader/issues/121

I was having the exacly same issue, in production the css are extracted so it always work, however in development because of the style-loader "sometimes executing after the main bundle" i had issues where the main bundle would calculate the size of some nodes in the DOM which was set by the css... so it could result to wrong sizes as the main css still havent loaded... i fixed this issue by having the option singleton:true.
example:
{
test: /\.s?css$/,
loader: ExtractTextPlugin.extract({
fallback: [
{
loader: 'style-loader',
options: { singleton: true }
}
],
use: [
{
loader: 'css-loader',
options: {
importLoaders: 1,
minimize: !isProduction,
sourceMap: !isProduction
}
},
{ loader: 'postcss-loader', options: { sourceMap: !isProduction, ...postcss } },
{ loader: 'resolve-url-loader', options: { sourceMap: !isProduction } },
{ loader: 'sass-loader', options: { sourceMap: true } }
]
}
)
}

Looks like there's no event, callback or any way to detect that the style has been loaded. After long hours of searching in vain, I had to do something really dirty:
function checkCSS() {
const repeat = requestAnimationFrame(checkCSS);
// CSS loaded?
if(getComputedStyle(document.body).boxSizing === 'border-box') {
routes.loadEvents() // Init JS
cancelAnimationFrame(repeat) // Cancel next frame
}
}
if (process.env.NODE_ENV !== 'production') {
checkCSS()
} else {
$(document).ready(() => {
routes.loadEvents()
})
}
Because I have * { box-sizing: border-box; } in my styles and that I'm pretty sure native CSS styles won't never look like this, I can be ~99% sure that my own CSS is loaded.
I died a little writing this. Hopefully we'll find a better way!

Related

How to add classnames from less with webpack and react?

I have a webpack configuration that uses less-loader, css-loader, and style-loader. When I import less file into my component file, the css is visible in chrome's devTools, but the classname is not.
I have google'd for a couple of hours and can't seem to find anything that can explain this. I know I am suppose to use this.props.className, but I am not sure how the className gets propagated. I have also tried using static strings for the className.
Here is my webpack config:
module: {
rules: [
...
{
test: /\.less$/,
use: [
'style-loader',
'css-loader',
'less-loader'
]
}
]
},
Here is my component:
import React from 'react';
import {AppBar} from '#material-ui/core';
import '../styles/layout.less';
class Home extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<AppBar className='header'></AppBar>
);
}
}
export default Home;
I expect the className to be header, but it doesn't show any class names i provide. It only happens with Material-UI components.
Sorry for late answer, recently had similar problem with 'antd' library and theme modification using less files. I had to split my webpack configuration into two parties (for node_modules and for resources) as follow:
{
test: /\.less$/,
include: /node_modules/,
use: [
{
loader: 'style-loader' // creates style nodes from JS strings
},
{
loader: 'css-loader', // translates CSS into CommonJs
},
{
loader: "less-loader", // compiles Less to CSS
options: {
javascriptEnabled: true
}
}
],
},
{
test: /\.less$/,
exclude: /node_modules/,
use: [
{
loader: 'style-loader' // creates style nodes from JS strings
},
{
loader: 'css-loader', // translates CSS into CommonJs
options: {
modules: true
}
},
{
loader: "less-loader", // compiles Less to CSS
options: {
javascriptEnabled: true
}
}
],
}
Hope that helps.

webpack: SASS loader fails "Module build failed (from ./node_modules/sass-loader/lib/loader.js)"

I've been trying to use sass-loader on webpack v4, but it fails to load scss files in a React component with TypeScript.
Here is a simplified snippet of the code.
//index.tsx
import * as React from 'react';
import './styles.scss';
const Navigation = () => {
return (<div className="app-bar"></div>)
}
//styles.scss
.app-bar {
background-color: #2196f3;
}
The following code is from my webpack config.
module: {
rules: [
//loaders for jsx, tsx etc
{
test: /\.(css|scss)$/,
use: [
{ loader: 'style-loader' },
{
loader: 'css-loader',
options: {
modules: true
}
},
{ loader: 'sass-loader' }
]
}]
},
plugins: [
new MiniCssExtractPlugin({
filename: "[name].css",
chunkFilename: "[id].css"
}),
new CleanWebpackPlugin(),
]
I followed the official doc's example, but it fails to load the styles.scss.
Just in case, I re-installed style-loader, css-loader, sass-loader (and node-sass), but it didn't solve the error.
The error message is ...
Module build failed (from ./node_modules/sass-loader/lib/loader.js):
I'm running webpack via Laravel Mix, but don't know if Laravel has anything to do with it.
What caused this issue? Any advice will be appreciated.
You dont need to put css in the test section because the sass-loader and css-loader will take care for you and it will transform your scss to css file
Below is my config
{
test: /\.scss$/,
use: [
//'style-loader' was the culprit, so I just needed to remove it
MiniCssExtractPlugin.loader,
{
loader: "css-loader",
options: {
minimize: true,
sourceMap: true
}
},
{
loader: "sass-loader"
}
]
I seem to remember having the same issues with webpack. I switched from SASS to Styled Components, it's a css-in-js library. I was wary at first but it's great.
https://www.styled-components.com/
It allows you to change CSS styles programmatically using React props. For example if I want to change the opacity of a menu when a button is clicked I can do it like this:
opacity: ${props => (props.menuOpen ? 1 : 0)};
That’s just one benefit, check the docs to see others. I find using React with styled-components is a great way to work. You have your JS, CSS and HTML all being generated in one place.

Unable to skip 3rd party library CSS from CSS-Module transformation

I am trying CSS Modules for the first time with React and Webpack and I came across at least three ways to achieve it:
css-loader
react-css-modules
babel-plugin-react-css-modules
I went with babel-plugin-react-css-modules in order to balance code simplicity and performance and everything seems to be working fine except for one thing: my 3rd party libraries (Bootstrap and Font Awesome) are also included in CSS Modules transformation.
<NavLink to="/about" styleName="navigation-button"></NavLink>
The above assigns a properly transformed className to the NavLink. However, a span inside needs to refer to global styles in order to render an icon.
<span className="fa fa-info" />
The above span is not assigned a transformed className which is expected, but my bundled stylesheet does not have these CSS classes as they are being transformed into something else, to simulate local scope.
Below is the content in my .babelrc file to activate babel-plugin-react-css-modules:
{
"presets": ["env", "react"],
"plugins": [
["react-css-modules", {
"generateScopedName": "[name]__[local]___[hash:base64:5]",
"filetypes": {
".less": {
"syntax": "postcss-less"
}
}
}]
]
}
In my Webpack configuration, below is the section to configure css-loader for transforms:
{
test: /\.(less|css)$/,
exclude: /node_modules/,
use: extractCSS.extract({
fallback: 'style-loader',
use: [
{
loader: 'css-loader',
options: {
minimize: true,
modules: true,
sourceMap: true,
localIdentName: '[name]__[local]___[hash:base64:5]'
}
},
{
loader: 'less-loader'
}
]
})
}
As far as I have read, the above rule should exclude the library stylesheets and I also tried adding another rule specifically for the excluded stylesheets, however that did not seem to work, as I guess as those stylesheets were still transformed with the original rule.
In order to import CSS from the two libraries, I have the below two lines in my parent stylesheet that declares some global styles:
#import '../../../node_modules/bootstrap/dist/css/bootstrap.min.css';
#import '../../../node_modules/font-awesome/css/font-awesome.min.css';
I find these two approaches below might be helpful:
https://stackoverflow.com/a/52294675
https://github.com/css-modules/css-modules/pull/65#issuecomment-412050034
In short, there seems to be no options to ignore/exclude certain paths from being modularized by the css-modules webpack plugin so far. Ideally it should be supported by the plugin, but here're some approaches you can try out:
use two webpack rules to utilise the webpack rule exclusion/inclusion:
module.exports = {
rules: [
{
test: /\.css$/,
exclude: /node_modules/,
use: [
'style-loader',
{
loader: 'css-loader',
options: {
modules: true,
localIdentName: '[path]__[local]___[hash:base64:5]',
},
},
],
},
{
test: /\.css$/,
include: /node_modules/,
use: ['style-loader', 'css-loader']
}
]
}
...or, inject into webpack's getLocalIdent from the second answer above to manually exclude certain paths.
const getLocalIdent = require('css-loader/lib/getLocalIdent');
{
loader: 'css-loader',
options: {
modules: true,
localIdentName: '[path][name]__[local]--[hash:base64:5]',
getLocalIdent: (loaderContext, localIdentName, localName, options) => {
return loaderContext.resourcePath.includes('semantic-ui-css') ?
localName :
getLocalIdent(loaderContext, localIdentName, localName, options);
}
}
}
For me using :global worked :
.my-component {
:global {
.external-ui-component {
padding: 16px;
// Some other styling adjustments here
}
...
}
}
Ps: for doing it with webpack config, please see another answer.
source
Updated solution from playing771
{
loader: 'css-loader',
options: {
modules: {
auto: (resourcePath) => !resourcePath.includes('node_modules'),
localIdentName: '[name]__[local]__[hash:base64:5]',
},
},
},

Storybook Global Scss variables

CLI Tool: Storybook
Framework: Vue/ Nuxt
Issue: I'm trying to pull in global SCSS variables to Storybook Stories so they run the components the same way as they do in Nuxt, I've tried the custom webpack config with sass-resources-loader but had no luck, just wanted to check if anyone else has already solved this problem
It seems to be an issue with Storybook handling multiple rules.
I solved it by a work around.
You can read the blog i wrote for detailed explaination here.
Below is my webpack config - main.js :
webpackFinal: async (config, { configType }) => {
config.module.rules.map((rule) => {
if (rule.oneOf) {
rule.oneOf = rule.oneOf.slice().map((subRule) => {
if (subRule.test instanceof RegExp && subRule.test.test('.scss')) {
return {
...subRule,
use: [
...subRule.use,
{
loader: require.resolve('sass-resources-loader'),
options: {
resources: [
path.resolve(__dirname, '../src/styles/_common.scss')
]
}
}
],
}
}
return subRule;
});
}
return rule;
});
return config;
},
Hope this helps someone!
I encountered the issue where global SASS variables were causing Storybook for Vue to fail.
For me, creating a webpack.config.js file in the .storybook folder with the below configuration solved my problem:
module.exports = (storybookBaseConfig, configType, defaultConfig) => {
defaultConfig.module.rules.push(
{
resourceQuery: /module/,
use: [
{
loader: 'vue-style-loader',
options: {
sourceMap: false,
shadowMode: false
}
},
{
loader: 'css-loader',
options: {
sourceMap: false,
importLoaders: 2,
modules: true,
localIdentName: '[name]_[local]_[hash:base64:5]'
}
},
{
loader: 'postcss-loader',
options: {
sourceMap: false
}
},
{
loader: 'sass-loader',
options: {
sourceMap: false,
indentedSyntax: true,
data: '#import "#/sass/_variables.scss";'
}
}
]
}
);
return defaultConfig;
};
Note the line data: '#import "#/sass/_variables.scss";' needs to match the file path for the SASS file with variables in your project.
This section of config was retrieved from Vue CLI 3 by running vue inspect > output.js and then copying the config for the rule test: /\.sass$/.
You need to add the scss rule in your .storybook/webpack.config.js for storybook to parse scss.
const path = require('path');
const scss = {
test: /\.scss$/,
use: [
'vue-style-loader',
'css-loader',
'sass-loader'
],
};
module.exports = (storybookBaseConfig, configType, defaultConfig) => {
defaultConfig.module.rules.push(scss);
return defaultConfig;
};
You may also need to install the appropriate loaders:
yarn add -D vue-style-loader sass-loader css-loader
For anybody who can actually get Storybook to read SCSS files but can't get it to read the global variables file, do this in your custom webpack config:
module: {
rules: [
// Apply loader
{
test: /\.scss$/,
loaders: [
'style-loader',
'css-loader',
{
loader: 'sass-loader',
options: {
prependData: '#import "path/to/global.scss";',
},
},
],
},
],
}
If your components do not get styles applied when run in the Storybook component explorer UI, just import SASS styles in your main Storybook config/storybook/config.js (in previous versions was by default at storybook/config.js) like so:
// Import Styles
import '../../src/assets/styles/index.scss';
Usually you'd have your styles and plugins imported in your src/main.js / src/main.ts but you also need to do this in Storybook config, as when running Storybook it's not running the whole Vue app but just those individual components.

Import Library Stylesheets With Babel-Plugin-React-Css-Modules

I am trying to include another library's css for their component in my own application. For reference, I am trying to use this data table library: https://github.com/filipdanic/spicy-datatable.
In the docs, it states Out of the box, spicy-datatable is bare-bones. Include this CSS starter file in your project to get the look from the demo. Edit it to suit your needs.
I tried to import the style sheet at the top of the component that I am building like this: import * as spicy from 'spicy-datatable/src/sample-styles.css'; in my own component file. It was not styled. I tried putting the raw code into my index.scss file in my assets/styles folder - did not work. I tried putting it in my own styles file ./component.scss - did not work.
I have them currently set up like:
import * as styles from './component.scss';
import * as spicy from 'spicy-datatable/src/sample-styles.css';
and am getting an error:
Module parse failed: Unexpected token (4:0)
You may need an appropriate loader to handle this file type.
webpack.config.js
const dirNode = 'node_modules';
const dirApp = path.join(__dirname, 'client');
const dirAssets = path.join(__dirname, 'assets');
/**
* Webpack Configuration
*/
module.exports = {
entry: {
vendor: ['lodash'],
bundle: path.join(dirApp, 'index')
},
resolve: {
modules: [dirNode, dirApp, dirAssets]
},
plugins: [],
module: {
rules: [
// BABEL
{
test: /\.js$/,
loader: 'babel-loader',
exclude: /(node_modules)/,
options: {
compact: true
}
},
// CSS / SASS
{
test: /\.(scss)$/,
use: [
'style-loader',
{
loader: 'css-loader',
options: {
importLoaders: 1,
modules: true,
localIdentName: '[path]___[name]__[local]___[hash:base64:5]'
}
},
'sass-loader'
]
},
// IMAGES
{
test: /\.(jpe?g|png|gif)$/,
loader: 'file-loader',
options: {
name: '[path][name].[ext]'
}
}
]
}
};
.babelrc
"plugins": [
[
"react-css-modules",
{
"filetypes": {
".scss": {
"syntax": "postcss-scss"
}
},
"webpackHotModuleReloading": true
}
]
I'm not sure if I need to add something to specifically handle .css files, this is my first time working with CSS Modules. I thought react-css-modules did that so I'm not quite sure why the CSS file isn't loading correctly.
Edit:
Edited my webpack around to include CSS:
{
test: /\.(css)$/,
use: [
'style-loader',
{
loader: 'css-loader',
options: {
modules: true,
localIdentName: '[path]___[name]__[local]___[hash:base64:5]'
}
}
]
},
Error is gone, but styles still do not appear.
Could you try changing below:
import * as spicy from 'spicy-datatable/src/sample-styles.css';
to
import from 'spicy-datatable/src/sample-styles.css';
If you are using CSS-Modules, try below:
import spicy from 'spicy-datatable/src/sample-styles.css';
and then use the style on JSX element like below:
<h1 className={classes.<className in CSS here>}>
I setup a codesandbox with the spicy-datatable library and imported the styles and looks like it applied. The styles are in "Hello.css" file and it is imported in "index.js".
https://codesandbox.io/s/4j31xj3689
If library doesn't use css-modules (uses className attribute instead of styleName) we need to disable modules for imported css, so the class names will remain unchanged. This can be done in 2 ways:
Modify your Webpack config
module: {
rules: [
{
test: /\.(css)$/,
use: [
'style-loader',
{
loader: 'css-loader',
options: {
modules: false
}
}
]
},
...
]
}
Import library css directly into your scss stylesheet (thanks to this answer pointing out how to perform proper .css import). Make sure to exclude .css file extension from import line. :global directive will prevent css-modules to modify class names for all styles within this directive.
:global {
#import "~library-module-name/.../CssFileWithoutExtension";
}

Resources