Reputation: 2637
My webpack configuration mostly works, but production builds have no css styling applied.
If I run webpack using this node script webpack --mode development
then open the ./dist/index.html
file in my browser, the website works as expected and the content is styled correctly.
If I run webpack using this node script webpack --mode production
then open the ./dist/index.html
file in my browser, the website content comes up but with no styling at all applied to the content.
The webpack dev server works perfectly with styled content, live update etc.
I modified my webpack.config.js
to comment out the production specific settings, so that production and development builds use the exact same webpack config. I am having a hard time explaining why the development build works but the production build does not even with identical config. Any ideas on how to debug the issue?
My webpack.config.js
looks like this:
const path = require('path');
const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = (env, { mode }) => {
var config = {
entry: {
app: './src/app.js',
react: 'react',
},
devtool: 'none',
devServer: {
contentBase: path.join(__dirname, 'dist'),
compress: true,
hot: true,
port: 9000,
},
resolve: {
extensions: ['.js', '.jsx', '.css', '.scss', '.sass'],
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'index.[hash].js',
chunkFilename: '[name].[contenthash].js',
},
optimization: {
runtimeChunk: 'single',
splitChunks: {
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
chunks: 'all',
},
},
},
},
plugins: [
new HtmlWebpackPlugin({
minify: false,
template: require('html-webpack-template'),
inject: true,
title: 'Octopedia',
favicon: './public/images/favicon/favicon.ico',
appMountId: 'app',
meta: [{ name: 'viewport', content: 'width=device-width, initial-scale=1.0' }],
links: [{ href: '/site.webmanifest', rel: 'manifest' }],
appMountHtmlSnippet: '<div class="app-spinner"></div>',
headHtmlSnippet:
'<style>div.app-spinner{position:fixed;top:50%;left:50%;border:16px solid #f3f3f3;border-top:16px solid #3498db;border-radius:50%;width:120px;height:120px;margin:-60px 0 0 -60px;z-index:1;animation:spin 2s linear infinite;}@keyframes spin{0%{transform:rotate(0deg);}100%{transform:rotate(360deg);}}</style >',
}),
new CopyWebpackPlugin({
patterns: [{ from: 'public' }],
}),
],
module: {
rules: [
{
test: /\.(js|jsx)$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
cacheDirectory: true,
cacheCompression: false,
presets: ['@babel/preset-env', '@babel/preset-react'],
},
},
},
],
},
};
// if (mode === 'development') {
config.mode = 'development';
config.devtool = 'eval-source-map';
config.plugins.push(new webpack.HotModuleReplacementPlugin());
config.module.rules.push({
test: /\.(sass|scss|css)/,
exclude: /node_modules/,
use: ['style-loader', 'css-loader', 'sass-loader'],
});
// } else {
// config.mode = 'production';
// config.devtool = 'none';
// config.plugins.push(new MiniCssExtractPlugin({ filename: 'index.css' }));
// config.module.rules.push({
// test: /\.(sass|scss|css)/,
// exclude: /node_modules/,
// use: [MiniCssExtractPlugin.loader, 'css-loader', 'sass-loader'],
// });
// }
return config;
};
The output from webpack in development mode looks like this:
Asset Size Chunks Chunk Names
app.6335bee170373d18f82e.js 39.8 KiB app [emitted] [immutable] app
favicon.ico 15 KiB [emitted]
images/.DS_Store 6 KiB [emitted]
images/favicon/.DS_Store 6 KiB [emitted]
images/favicon/android-chrome-192x192.png 8.1 KiB [emitted]
images/favicon/android-chrome-512x512.png 23.3 KiB [emitted]
images/favicon/apple-touch-icon.png 7.25 KiB [emitted]
images/favicon/favicon-16x16.png 534 bytes [emitted]
images/favicon/favicon-32x32.png 1.06 KiB [emitted]
images/favicon/favicon.ico 15 KiB [emitted]
index.87da9b99395c61fa6b02.js 33.4 KiB runtime [emitted] [immutable] runtime
index.html 1.04 KiB [emitted]
react.a0fc75f932f5dd9cdda1.js 133 bytes react [emitted] [immutable] react
site.webmanifest 447 bytes [emitted]
vendors.404f5d4ed29b35b04b91.js 2.41 MiB vendors [emitted] [immutable] vendors
Entrypoint app = index.87da9b99395c61fa6b02.js vendors.404f5d4ed29b35b04b91.js app.6335bee170373d18f82e.js
Entrypoint react = index.87da9b99395c61fa6b02.js vendors.404f5d4ed29b35b04b91.js react.a0fc75f932f5dd9cdda1.js
The output from webpack in production mode loolks like this:
Asset Size Chunks Chunk Names
app.170943a8008bc467199f.js 2.77 KiB 2 [emitted] [immutable] app
favicon.ico 15 KiB [emitted]
images/.DS_Store 6 KiB [emitted]
images/favicon/.DS_Store 6 KiB [emitted]
images/favicon/android-chrome-192x192.png 8.1 KiB [emitted]
images/favicon/android-chrome-512x512.png 23.3 KiB [emitted]
images/favicon/apple-touch-icon.png 7.25 KiB [emitted]
images/favicon/favicon-16x16.png 534 bytes [emitted]
images/favicon/favicon-32x32.png 1.06 KiB [emitted]
images/favicon/favicon.ico 15 KiB [emitted]
index.a162f7e38dd204127c0f.js 9.03 KiB 0 [emitted] [immutable] runtime
index.html 1.04 KiB [emitted]
react.f87e0cf2654ac4f74576.js 71 bytes 3 [emitted] [immutable] react
site.webmanifest 447 bytes [emitted]
vendors.dd9d4a26464d8d880c74.js 352 KiB 1 [emitted] [immutable] [big] vendors
Upvotes: 2
Views: 7819
Reputation: 2637
After several days of banging my head against a documentation brick wall, I finally tracked down the problem. I consider this to be a bug in webpack but others may disagree.
The issue comes about because in 'production' mode webpack performs optimizations that remove unreferenced code. This is a good thing of course, but it does not distinguish between javascript and other types of files, which to me is a bug.
The end result is that because none of my Javascript references anything exported from the css (how could it, css doesn't have ES6 import/export mechanism) the webpack tree shaker discards all of the css from the production build.
The solution is very simple but very hard to find. In your package.json
file you need to add:
"sideEffects": ["*.css", "*.scss", "*.sass"],
Which tells webpack that it cannot prune these modules from the tree, because just loading the module is enough to make the module provide functionality without anything in the module being referenced by the application.
It has taken three days of experimenting and reading docs and stackoverflow articles to track down this simple 1 line change. I hope this answer can save someone else from spending the same time.
Upvotes: 1
Reputation: 19762
Since you're using the same config, you'll need to condtionally add MiniCssExtractPlugin:
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const { NODE_ENV } = process.env;
const inDevelopment = NODE_ENV === "development";
module.exports = {
...options,
module: {
rules: [
...rules,
{
test: /\.(scss|css)/,
exclude: /node_modules/,
use: [
inDevelopment ? "style-loader" : MiniCssExtractPlugin.loader,
'css-loader',
'sass-loader'
],
},
],
},
plugins: [
...plugins,
!inDevelopment && new MiniCssExtractPlugin({ filename: "bundle.min.css" }),
].filter(Boolean)
}
In short, if you're in development it'll use style-loader
. When you're in production, you'll use MiniCssExtractPlugin.loader
with the MiniCssExtractPlugin
plugin. The above assumes you'll want to bundle into a single css file. If you want to code-split it, then you'll need to add optimization.splitChunks and update the plugin for chunking.
Upvotes: 1