I've recently upgrade my webpack version from 3 to 4.x. Since then, I've noticed that I'm having some style collisions under certain circumstances.
For instance, I have two different components named PlanCard
that are in two different directories within an application. For the sake of brevity, let's say the directory structure resembles something like:
- app
+ - Onboarding
+ - OnboardingMembership
+ - PlanCard
+ PlanCard.jsx
+ PlanCard.module.scss
+ - Settings
+ - ClientSelect
+ - PlanCard
+ PlanCard.jsx
+ PlanCard.module.scss
However, I'm seeing that styles from the OnboardingMembership/PlanCard
are getting applied to the ClientSelect/PlanCard
when it's used within the application. For instance, on a page within the ClientSelect
, which is importing the ClientSelect/PlanCard
, I see the following in the Element inspector:
Another peculiar thing I've noticed when searching for PlanCard
in the css files coming bundled with the application is the following:
What stands out to me is the hash. To my knowledge, these values should be different and I suspect that's where this clash is coming from.
To be complete, I'll include some of the relevant webpack files. Bear with me, as it's a fairly large application so the webpack files are broken up across several files:
// webpack.dev.js
/* eslint-env node */
const merge = require('webpack-merge')
const common = require('./webpack.common')
const plugins = require('./webpack/plugins')
const outputs = require('./webpack/outputs')
const modules = require('./webpack/modules')
module.exports = merge(common, {
mode: 'development',
devtool: 'cheap-module-source-map',
output: outputs.client,
module: modules.client,
plugins: [...plugins.client, ...plugins.clientDev],
})
// webpack.common.js
/* eslint-env node */
const { ROOT_PATH, path } = require('./webpack/constants')
const entries = require('./webpack/entries')
const resolves = require('./webpack/resolves')
module.exports = {
context: path.join(ROOT_PATH, 'frontend'),
optimization: {
splitChunks: {
name: 'common',
minChunks: 3,
},
},
entry: entries.client,
resolve: resolves.client,
}
// webpack/plugins.js
'use strict'
/* eslint-env node */
const {
extractCSSModules,
extractCSS,
extractCSSModulesProd,
extractCSSProd,
extractCreatorSCSS,
} = require('./constants')
const ManifestPlugin = require('webpack-manifest-plugin')
const plugins = {}
plugins.client = [
new ManifestPlugin({
generate: generateManifest,
}),
]
plugins.clientDev = [
extractCSS,
extractCSSModules,
]
plugins.clientProd = [
extractCSSProd,
extractCSSModulesProd,
]
module.exports = plugins
// webpack/outputs.js
'use strict'
/* eslint-env node */
const { path, ROOT_PATH, outputTemplates } = require('./constants')
const outputs = {}
outputs.client = {
filename: outputTemplates.development.js,
chunkFilename: outputTemplates.development.jsChunk,
path: path.join(ROOT_PATH, '/public/build'),
publicPath: '/build/',
}
outputs.clientProd = {
filename: outputTemplates.production.js,
chunkFilename: outputTemplates.production.jsChunk,
path: path.join(ROOT_PATH, '/public/build'),
publicPath: '/build/',
}
outputs.testUtils = {
filename: '[name].bundle.js',
path: path.join(ROOT_PATH, 'frontend_build/testUtils'),
}
module.exports = outputs
// webpack/module.js
'use strict'
/* eslint-env node */
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const { path, ROOT_PATH } = require('./constants')
const modules = {}
const isDevelopment = process.env.NODE_ENV === 'development'
modules.client = {
rules: [
{
test: /\.(js|jsx)$/,
use: 'babel-loader',
exclude: /node_modules/,
},
{
test: /\.module\.(css|scss)$/,
use: [
isDevelopment ? 'style-loader' : MiniCssExtractPlugin.loader,
{
loader: 'css-loader',
query: {
modules: true,
namedExport: true,
sourceMap: isDevelopment,
camelCase: true,
importLoaders: true,
localIdentName: '[name]__[local]__[hash:base64:5]',
},
},
{
loader: 'postcss-loader',
options: {
plugins: () => [
require('autoprefixer')({
browsers: ['> 1%', 'last 2 versions'],
}),
],
},
},
{ loader: 'sass-loader' },
],
include: path.join(ROOT_PATH, 'frontend'),
exclude: path.join(ROOT_PATH, 'frontend', 'css'),
},
{
test: /\.scss$/,
use: [
isDevelopment ? 'style-loader' : MiniCssExtractPlugin.loader,
{ loader: 'css-loader' },
{
loader: 'postcss-loader',
options: {
plugins: () => [
require('autoprefixer')({
browsers: ['> 1%', 'last 2 versions'],
}),
],
},
},
{ loader: 'sass-loader' },
],
include: path.join(ROOT_PATH, 'frontend', 'css'),
},
{
test: /\.scss$/,
include: '/node_modules/foundation/scss',
use: [
isDevelopment ? 'style-loader' : MiniCssExtractPlugin.loader,
{
loader: 'css-loader',
query: {
namedExport: true,
importLoaders: true,
},
},
{
loader: 'postcss-loader',
options: {
plugins: () => [
require('autoprefixer')({
browsers: ['> 1%', 'last 2 versions'],
}),
],
},
},
{ loader: 'sass-loader' },
],
},
{
test: /\.(ttf|eot|svg|woff(2)?)(\?[a-z0-9]+)?$/,
use: [
{
loader: 'file-loader',
options: {
name: '[name].[hash].[ext]',
},
},
],
},
],
}
modules.clientProd = {
rules: [
{
test: /\.(js|jsx)$/,
use: 'babel-loader',
exclude: /node_modules/,
},
{
test: /\.module\.(css|scss)$/,
use: [
isDevelopment ? 'style-loader' : MiniCssExtractPlugin.loader,
{
loader: 'css-loader',
options: { modules: true, exportOnlyLocals: true },
},
{
loader: 'postcss-loader',
options: {
plugins: () => [
require('autoprefixer')({
browsers: ['> 1%', 'last 2 versions'],
}),
],
},
},
{ loader: 'sass-loader' },
],
include: path.join(ROOT_PATH, 'frontend'),
exclude: path.join(ROOT_PATH, 'frontend', 'css'),
},
{
test: /\.scss$/,
use: [
isDevelopment ? 'style-loader' : MiniCssExtractPlugin.loader,
{ loader: 'css-loader' },
{
loader: 'postcss-loader',
options: {
plugins: () => [
require('autoprefixer')({
browsers: ['> 1%', 'last 2 versions'],
}),
],
},
},
{ loader: 'sass-loader' },
],
include: path.join(ROOT_PATH, 'frontend', 'css'),
},
{
test: /\.scss$/,
include: '/node_modules/foundation/scss',
use: [
{
loader: MiniCssExtractPlugin.loader,
},
{ loader: 'style-loader' },
{
loader: 'css-loader',
query: {
namedExport: true,
importLoaders: true,
},
},
{
loader: 'postcss-loader',
options: {
plugins: () => [
require('autoprefixer')({
browsers: ['> 1%', 'last 2 versions'],
}),
],
},
},
{ loader: 'sass-loader' },
],
},
{
test: /\.(ttf|eot|svg|woff(2)?)(\?[a-z0-9]+)?$/,
use: [
{
loader: 'file-loader',
options: {
name: '[name].[hash].[ext]',
},
},
],
},
],
}
module.exports = modules
// webpack/constants.js
'use strict'
/* eslint-env node */
const webpack = require('webpack')
const path = require('path')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const ROOT_PATH = path.resolve(__dirname, '../../')
const outputTemplates = {
production: {
scss: '[name].scss',
css: '[name].[hash].css',
cssModule: '[name].modules.[hash].css',
js: '[name].[hash].js',
jsChunk: '[name].chunk.[chunkhash].js',
},
development: {
scss: '[name].scss',
css: '[name].css',
cssModule: '[name].modules.css',
js: '[name].js',
jsChunk: '[name].chunk.js',
},
}
const extractSCSS = new MiniCssExtractPlugin({
filename: outputTemplates.development.scss,
allChunks: true,
})
const extractCSSModules = new MiniCssExtractPlugin({
filename: outputTemplates.development.cssModule,
allChunks: true,
})
const extractCSS = new MiniCssExtractPlugin({
filename: outputTemplates.development.css,
allChunks: true,
})
const extractCSSModulesProd = new MiniCssExtractPlugin({
filename: outputTemplates.production.cssModule,
allChunks: true,
})
const extractCSSProd = new MiniCssExtractPlugin({
filename: outputTemplates.production.css,
allChunks: true,
})
const extractCreatorSCSS = new MiniCssExtractPlugin({
filename: 'creator.bundle.css',
allChunks: true,
})
module.exports = {
webpack,
path,
ROOT_PATH,
extractCSSModules,
extractCSS,
extractSCSS,
extractCSSModulesProd,
extractCSSProd,
extractCreatorSCSS,
outputTemplates,
}
If you're still with me, I really appreciate you giving me your time. My specific questions are:
- What exactly is causing this naming collision (or not properly generating a new hash for a different component with the same name)?
- What about my configuration should change?
- Although I use webpack everyday, I've ever really dug into it enough to understand the internals or best practices. If there's anything else you spot that can be done better, please feel free to call it out in a comment.