How do you extract css imports from submodules with webpack? - javascript

I'm trying to create a React application with multiple entries using webpack and extract-text-webpack-plugin.
My config file looks like this,
const commonsChunkPlugin = require('webpack/lib/optimize/CommonsChunkPlugin');
const extractTextPlugin = require('extract-text-webpack-plugin');
let config = {
entry: {
app: './client/app.entry.js',
signIn: './client/sign-in.entry.js',
},
output: {
path: './server/public',
filename: '[name].js'
},
module: {
loaders: [
{
test: /\.(js|jsx)$/,
exclude: /node_modules/,
loader: 'babel-loader',
query: {
presets: ['react', 'es2015']
}
},
{
test: /\.css$/,
loader: extractTextPlugin.extract('style-loader', 'css-loader?modules&importLoaders=1&localIdentName=[name]__[local]___[hash:base64:5]')
}
]
},
resolve: {
modulesDirectories: ['node_modules', 'client'],
extensions: ['', '.js']
},
plugins: [
new commonsChunkPlugin('common', 'common.js'),
new extractTextPlugin('styles.css', { allChunks: true })
]
};
module.exports = config;
My problem is that extract-text-webpack-plugin only includes imported css files from the entry chunks, and not from submodules of the entry chunks.
So if app.entry.js has
import "./app-style.css";
import "./sub-module"; // This module has import "./sub-style.css";
then the styles from app-style.css gets bundled but not the styles from sub-style.css.
I haven't had this issue before when there's only been one entry file, so I'm wondering if having multiple entries requires another setup?
Something to also take into consideration is the use of CSSModules by the way the css-loader is used, which also could be a factor.
Any ideas?

I'm trying to solve similar problem, and i think it will be nice idea to document the solution and thoughts for those who has the same questions.
TextExtract plugin can work with chunks that have to be configured with commonchunks plugin, enable chunks support:
// Configuration of the extract plugin with chunks and naming
new ExtractTextPlugin("[name].css", { allChunks: true })
It's all ) Next thing is just configuration of the chunks (webpack is very flexible tool, everyone configure it for own needs. For an instance i'll show how i configure "vendour.css" and "application.css" build configuration based on "imports")
// Vendour chunks definition for vendor css
entry: {
vendor : ['./css/vendour.sass']
Example of entrypoint file.js
import "./css/vendor.sass"
import "./css/application.sass"
After build, webpack will create vendor.css (where you export vendour things with #import "~vendormodules/sass/alla") and application.css files.
Thanks,

Related

mini-css-extract-plugin does not load css if it is not declared via dynamic import

I have very basic webpack + mini-css-extract-plugin project (you can found it here).
Here is webpack.config.js:
const path = require("path");
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = {
resolve: {
modules: [path.resolve(process.cwd(), 'node_modules')]
},
module: {
rules: [
// file loader allows to copy file to the build folder and form proper url
// usually images are used from css files, see css loader below
{
test: /\.png$/,
exclude: /node_modules/,
use: [
{
loader: 'file-loader',
options: {
name: "_assets/[name].[ext]"
}
}
]
},
// css files are processed to copy any dependent resources like images
// then they copied to the build folder and inserted via link tag
{
test: /\.css$/i,
sideEffects: true,
exclude: /node_modules/,
// for tests we use simplified raw-loader for css files
use: [
{
loader: MiniCssExtractPlugin.loader,
options: {
// public path has changed so url(...) inside css files use relative pathes
// like: url(../_assets/image.png) instead of absolute urls
publicPath: '../',
}
},
'css-loader'
]
}
]
},
plugins: [
// plugin helps to properly process css files which are imported from the source code
new MiniCssExtractPlugin({
// Options similar to the same options in webpackOptions.output
// both options are optional
filename: '_assets/[name].css',
chunkFilename: '_assets/[id].css'
})
],
entry: {
'test': "./src/test"
},
output: {
path: path.resolve(process.cwd(), `public`),
publicPath: '',
filename: '[name].js',
chunkFilename: '_chunks/chunk.[name].js'
}
};
main entry file test.js:
import './css/testCss.css';
console.log('Loaded');
When i run webpack build i got the following output structure:
/
|-test.js
|-_assets/
| |-test.css
When i include this js bundle into html i would expect that test.js bundle will load test.css file dynamically but this is not the case - js bundle works ok, but css file is not loaded at all.
It is only loaded when i modify source of the test.js like so:
import('./css/testCss.css'); // <--------- NOTE: dynamic import here
console.log('Loaded');
in this case after webpack build i got the following output:
/
|-test.js
|-_assets/
| |-0.css
|-_chunks/
| |-chunk.0.js
and when i load this js bundle in html - it loads both chink.0.js and 0.css
MAIN QUESTION: Is dynamic import the only correct way to include css into my js files via mini-css-extract-plugin?
Because in documentation they say yo use normal static import like import "./test.css"
my environement info:
node version: v14.12.0
webpack version: 4.44.1 (also tested on 5.2.0)
mini-css-extract-plugin version 1.1.2

Add style to bundled code using webpack

GitHub: https://github.com/Chirag161198/react-boilerplate 1
Here is the react boilerplate I’m trying to make from scratch.
I have bundled html with the react code but I’m not able to add styles (CSS).
I have heard about ExtractTextPlugin but not able to configure it.
Please suggest some way to add styles to it.
Thank you in advance.
You need to use style-loader and css-loader in your webpack.config.js
First, install these two packages via npm:
npm install style-loader, css-loader --dev
Then, create a styles.css in your src folder and append the following styles into the file (just for demo purpose, so you know it's working correctly):
body {
background-color: #ff4444;
}
Don't forget to import the css file in your src/index.js:
import React from 'react';
import ReactDOM from 'react-dom';
import App from './components/App.js';
import './styles.css'; // <- import the css file here, so webpack will handle it when bundling
ReactDOM.render(<App />, document.getElementById('app'));
And use style-loader and css-loader in your webpack.config.js:
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: './src/index.js',
output: {
path: path.join(__dirname, 'dist'),
filename: 'bundle.js',
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: { loader: 'babel-loader' },
},
{
test: /\.css$/,
use: ['style-loader', 'css-loader'],
},
],
},
plugins: [
new HtmlWebpackPlugin({
template: './src/index.html',
}),
],
};
If you don't see the correct output, you might need to restart the webpack dev server again. I have cloned your repo and made the changes like I mentioned above, it works.
As for ExtractTextPlugin, you will need this when bundling for a production build, you can learn more from their Repo
Hope it helps!
Hi Chirag ExtractTextPlugin works great but when it comes to caching and bundle hashing. Css bundle becomes 0 bytes. So they introduced MiniCssExtractPlugin which has tackled this issue. It is really important to cache static files as your app size increase by time.
import plugin first:
var MiniCssExtractPlugin = require("mini-css-extract-plugin");
add these in your webpack config:
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader'
}
},
{
test: /\.scss$/,
use: [ 'style-loader', MiniCssExtractPlugin.loader, 'css-loader', 'sass-loader']
}
]
}
plugins: [
new MiniCssExtractPlugin({
filename: 'style.css',
}),
new HtmlWebpackPlugin({
template: './src/index.html',
}),
Let me know you the issue still persists.
first you need to load style-loader and css-loader.Then you will add the following code in "rules" in webpack.config.js file.
{ test: /\.css$/,use: ['style-loader', 'css-loader']}
then import the "style.css" file location into the "index.js" file and for example:
import "./style.css";
When you package, "css" codes will be added in "bundle.js".

CSS images not copied to output folder in Webpack

I am using Webpack to bundle a number of js/css files in a site. I am bundling bootstrap.css and chosen.css as part of my bundles. In order to create the bundles, I have a main.js that I am using as an entry point to import all the other files that I will need. I am using file-loader to process font and image files and move them to the appropriate directories. I am using the ExtractTextPlugin with the css-loader and resolve-url-loader to create a separate css bundle from my js bundle.
My main.js is:
import 'bootstrap/dist/css/bootstrap.css';
import 'chosen-js/chosen.css';
import './datetimehelper.js';
import './deletelink.js';
import './dropdown.js';
My webpack.config.js is:
var ExtractTextPlugin = require('extract-text-webpack-plugin');
module.exports = {
entry: './src/js/main.js',
output: {
filename: 'wwwroot/js/bundle.js'
},
module: {
rules: [
{
test: /\.js$/,
exclude: /(node_modules|bower_components)/,
use: {
loader: 'babel-loader',
options: {
presets: ['#babel/preset-env']
}
}
},
{
test: /\.(png|jpg|gif)$/,
use: [
{
loader: 'file-loader',
options: {
name: 'images/[name].[ext]',
outputPath: 'wwwroot/'
}
}
]
},
{
test: /\.(eot|svg|ttf|woff|woff2)$/,
use: [
{
loader: 'file-loader',
options: {
name: 'fonts/[name].[ext]',
outputPath: 'wwwroot/'
}
}
]
},
{
test: /\.css$/,
use: ExtractTextPlugin.extract({
use: ['css-loader?url=false', 'resolve-url-loader'],
publicPath: '../'
})
}
]
},
plugins: [
new ExtractTextPlugin({
filename: 'wwwroot/css/bundle.css'
})
]
};
With the above configuration, the font references in bootstrap.css are picked up, moved to the appropriate directory and the urls are fixed in the css bundle that is emitted. However, the images that are referenced in chosen.css are not being picked up. Can anyone tell me what I need to do to make the images work correctly? I've tried replacing file-loader with url-loader and no change. I've also tried importing the images in my main.js and they were moved, but the urls in the css bundle were not rewritten correctly.
Having path configured in output makes life a lot easier. That would serve as the base output folder and all other loaders/plugins can work relative to that. May be the files were copied but not to your intended directory. Please do take a look at WebpackBootstrap repo. The config copies as well as converts image paths properly.
I finally figured it out. In the rules, I had:
{
test: /\.css$/,
use: ExtractTextPlugin.extract('css-loader', 'resolve-url-loader')
}
Instead, it should be:
{
test: /\.css$/,
loader: ExtratTextPlugin.extract('css-loader', 'resolve-url-loader')
}
Not sure what the difference is between use and loader because I'm fairly new to Webpack, but in this case it makes all the difference.

react-dom blowing out webpack bundle size MASSIVELY

This has got to be one of the strangest issues with webpack i have ever come across...
Check out this bundle breakdown:
react 116.01KB - fair enough
react-dom 533.24KB - seriously WTF
I thought it may be a corruption in my dependencies but nuking node_modules and reinstalling doesn't have any effect. I guess it's something to do with the way webpack is bundling it but i'm lost for ideas. The way i'm handing .js imports is pretty stock standard.
// webpack.config.js
const path = require('path');
// const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const Dashboard = require('webpack-dashboard');
const DashboardPlugin = require('webpack-dashboard/plugin');
const dashboard = new Dashboard();
module.exports = {
context: path.join(__dirname, 'src'),
entry: {
bundle: './index.js',
},
output: {
filename: 'bundle.js',
path: path.join(__dirname, 'build'),
},
module: {
rules: [
{
test: /\.html$/,
use: 'file-loader?name=[name].[ext]',
},
{
test: /.scss$/,
use: ExtractTextPlugin.extract({
fallback: 'style-loader',
use: [
'css-loader',
'postcss-loader',
],
}),
},
{
test: /\.js$/,
exclude: /node_modules/,
use: 'babel-loader',
},
],
},
plugins: [
// new BundleAnalyzerPlugin(),
new ExtractTextPlugin('styles.css'),
new DashboardPlugin(dashboard.setData),
],
devServer: {
quiet: true,
},
};
// .babelrc
{
"presets": [
"react",
"es2015"
],
"plugins": ["transform-object-rest-spread"]
}
http://elijahmanor.com/react-file-size/
In v15.4.0 the file size of react-dom grew from 1.17kB to 619.05kB. Which means my webpack setup isn't doing anything wrong bundling files. The reason why this module grew so large is because code was transferred from the react module.
I had to change my webpack.config.js, from
devtool: 'inline-source-map'
to
devtool: 'source-map'
Now it generates a much smaller .js + a separate .js.map file, for each of the chunks.
Notice the JS size is even less than react-dom.production.min.js in node_modules:
If you look into the corresponding folders under the node_modules folder, and note the file sizes, you'll see that there's nothing to be surprised about:
That is, the size of the bundle grows noticeably because the size of react-dom.js is large.
Add this following commands at plugins to minify your imports:
new webpack.optimize.OccurrenceOrderPlugin(),
new webpack.DefinePlugin(GLOBALS),
new webpack.optimize.UglifyJsPlugin(),
You should create a file or option to production bundle to use this plugins

Compiling Sass with Webpack (and local scope class names)

I've spent hours attempting to get my Webpack config to compile Sass; it's kinda ridiculous. During my research I found dozens of Github issues, Stackoverflow posts, and blogs talking about how to use Sass with Webpack, and they all do it differently. Also, there are so many people with problems. I just think Webpack needs to be better documented. Ugh.
I figured out how to compile Sass and have Webpack serve it in memory from /static, but I want the class names to be locally scoped. Isn't that one of the benefits of modular CSS with React components?
Example of locally scoped: .foo__container___uZbLx {...}
So, this is my Webpack config file:
const webpack = require('webpack');
const path = require('path');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
module.exports = {
devtool: 'source-map',
entry: {
bundle: './src/js/app'
},
output: {
path: __dirname,
filename: '[name].js',
publicPath: '/static'
},
plugins: [
new webpack.optimize.OccurrenceOrderPlugin(),
new ExtractTextPlugin('[name].css', {allChunks: true})
],
module: {
loaders: [
{
test: /\.js$/,
exclude: /node_modules/,
include: path.join(__dirname, 'src'),
loader: 'babel'
},
{
test: /\.scss$/,
exclude: /node_modules/,
include: path.join(__dirname, 'src'),
loader: ExtractTextPlugin.extract('style', 'css?sourceMap!sass')
}
]
}
};
I managed to get it to work for vanilla CSS:
{
test: /\.css$/,
exclude: /node_modules/,
include: path.join(__dirname, 'src'),
loader: ExtractTextPlugin.extract('style', 'css?modules&importLoaders=1&localIdentName=[name]__[local]___[hash:base64:5]')
}
I don't really understand the parameter-like syntax with all the ? marks, and I don't know what to search for to find documentation pertaining to that.
This is what my React component looks like; just incase you want to see how I am importing the style:
import React, { Component } from 'react';
import s from './foo.css';
class Foo extends Component {
render() {
return (
<div className={s.container}>
<h1 className="title">Welcome!</h1>
<p className="body">This is a dummy component for demonstration purposes.</p>
</div>
);
}
}
export default Foo;
Also, I have three unrelated questions:
What's the point of output.path property if Webpack merely serves the file from memory by means of /static?
What's the point of webpack-dev-server if what I am doing here is adequate? From my understanding, webpack-dev-server is just for hot module replacement stuff, right? Just automatic refreshing?
Are my exclude and include properties redundant? From my understanding, excluding node_modules decreases the compilation time making it work quicker; less files to process.
I got it to work with this:
loader: ExtractTextPlugin.extract('style', 'css?modules&localIdentName=[name]__[local]___[hash:base64:5]!sass')
All I had to do was put !sass at the end of the query. I wish this stuff was better documented; can't find adequate docs anywhere...

Categories