I have a React project that is bundled by Webpack and served up by react_on_rails. In this project I use the Select
component from react-select. Everything works as expected when using Webpack 3. After upgrading to Webpack 4, everything also works as expected in development
mode. However, when I build in production
mode, the Select
component from react-select
does not have any styling applied to it.
(I don't have enough reputation points to post images so I am going to provide links to the images.)
Here is what the selector looks like when built in development
mode.
And here is what the selector looks like when built in production
mode.
The reason the styles are not applied is that react-select
uses Emotion css-in-js and the css gets injected into the head in stylesheets.
Here is an example in the head when in development
mode.
screenshot of stylesheets in the head
These style
tags are all absent in the head
when in production
mode.
I have narrowed it down to the fact that it seems to be caused by the webpack minification step. If I add
optimization: {
minimize: false
}
to my webpack.config.js
, then the styles are present when in production
mode.
Here's my webpack.config.js
without the optimization
added:
const webpack = require('webpack');
const pathLib = require('path');
const devBuild = process.env.NODE_ENV !== 'production';
const config = {
entry: [
'es5-shim/es5-shim',
'es5-shim/es5-sham',
'babel-polyfill',
'./app/bundles/analytic',
'./app/bundles/Pulse/startup/registration',
],
output: {
filename: 'webpack-bundle.js',
path: pathLib.resolve(__dirname, '../app/assets/webpack'),
},
devtool: "source-map",
resolve: {
extensions: [".ts", ".tsx", '.js', '.jsx'],
},
plugins: [
new webpack.EnvironmentPlugin({ NODE_ENV: 'development' }),
],
module: {
rules: [
{
test: /travel-info-type.ts/,
use: [{
loader: 'expose-loader',
options: 'TravelInfoType'
}]
},
{
test: /heatmap-getter.ts/,
use: [{
loader: 'expose-loader',
options: 'HeatmapGetter'
}]
},
{
test: /data-hub.ts/,
use: [{
loader: 'expose-loader',
options: 'DataHub'
}]
},
{
test: /exported-functions.js/,
use: [{
loader: 'expose-loader',
options: 'ExportedFunctions'
}]
},
{
test: /analyticsTracker.ts/,
use: [{
loader: 'expose-loader',
options: 'analyticsTracker'
}]
},
{
test: /railsAnalytics.js/,
use: [{
loader: 'expose-loader',
options: 'railsAnalytics'
}]
},
{
test: require.resolve('react'),
use: {
loader: 'imports-loader',
options: {
shim: 'es5-shim/es5-shim',
sham: 'es5-shim/es5-sham',
}
},
},
{
test: /\.(woff|woff2|eot|ttf|svg|gif|png)$/,
use: [{
loader: 'url-loader'
}],
},
{
test: /\.jsx?$/,
use: 'babel-loader',
exclude: /node_modules/,
},
// All files with a '.ts' or '.tsx' extension will be handled by 'ts-loader'.
{ test: /\.tsx?$/, loader: "ts-loader" },
// All output '.js' files will have any sourcemaps re-processed by 'source-map-loader'.
{ enforce: "pre", test: /\.js$/, loader: "source-map-loader" },
// Extract css files
{
test: /\.css$/,
use: ['style-loader', 'css-loader'],
},
{
test: /\.scss$/,
use: [ "style-loader", "css-loader", "sass-loader" ],
},
],
},
};
module.exports = config;
if (devBuild) {
console.log('Webpack dev build for Rails'); // eslint-disable-line no-console
module.exports.devtool = 'eval-source-map';
} else {
console.log('Webpack production build for Rails'); // eslint-disable-line no-console
}
And here is my package.json
{
"name": "myProject",
"version": "0.0.1",
"private": true,
"scripts": {
"build:test": "webpack --config webpack.config.js",
"build:production": "NODE_ENV=production webpack --mode=production --config webpack.config.js",
"build:development": "webpack --mode=development -w --config webpack.config.js",
"test": "jest",
"test:watch": "yarn test --watch",
"storybook": "start-storybook -p 6006",
"build-storybook": "build-storybook -o ../public/storybook"
},
"cacheDirectories": [
"node_modules",
"client/node_modules"
],
"dependencies": {
"actioncable": "^5.2.0",
"color-convert": "^1.9.0",
"es5-shim": "^4.5.9",
"expose-loader": "^0.7.3",
"imports-loader": "^0.7.1",
"js-cookie": "^2.2.0",
"moment": "^2.18.1",
"prop-types": "^15.5.7",
"rc-slider": "^8.6.7",
"react": "^16.7.0",
"react-dom": "^16.7.0",
"react-on-rails": "6.9.3",
"react-onclickoutside": "^5.11.1",
"react-redux": "^6.0.0",
"react-router-dom": "^4.1.1",
"react-select": "^2.3.0",
"react-table": "^6.0.5",
"react-toggle-switch": "^2.1.3",
"react-tooltip": "^3.6.1",
"redux": "^4.0.1",
"redux-batched-actions": "^0.2.0",
"redux-thunk": "^2.3.0",
"rxjs": "5.5.2"
},
"devDependencies": {
"@storybook/addon-knobs": "^3.4.11",
"@storybook/addons": "^3.4.11",
"@storybook/react": "^3.4.11",
"@types/actioncable": "^0.0.2",
"@types/bugsnag": "^2.5.28",
"@types/google-maps": "^3.2.0",
"@types/googlemaps": "^3.26.11",
"@types/highcharts": "^4.2.55",
"@types/jest": "23.3.10",
"@types/jquery": "^2.0.45",
"@types/js-cookie": "^2.2.0",
"@types/lodash": "^4.14.118",
"@types/moment": "^2.13.0",
"@types/rc-slider": "^8.6.3",
"@types/react": "^16.8.1",
"@types/react-dates": "^16.0.5",
"@types/react-dom": "16.0.11",
"@types/react-redux": "^7.0.1",
"@types/react-router": "^4.0.26",
"@types/react-router-dom": "^4.2.7",
"@types/react-select": "^2.0.11",
"@types/react-tooltip": "^3.3.5",
"ts-loader": "^5.3.3",
"babel-cli": "^6.23.0",
"babel-core": "^6.23.1",
"babel-loader": "^7.1.5",
"babel-polyfill": "^6.23.0",
"babel-preset-es2015": "^6.22.0",
"babel-preset-react": "^6.23.0",
"babel-preset-stage-2": "^6.22.0",
"babel-runtime": "^6.23.0",
"css-loader": "^0.28.0",
"enzyme": "^3.8.0",
"enzyme-adapter-react-16": "^1.9.0",
"highcharts": "^6.0.3",
"jest": "23.3.0",
"jquery": "^3.2.1",
"jsdom": "^10.0.0",
"node-sass": "^4.9.3",
"react-test-renderer": "^16.7.0",
"redux-mock-store": "^1.2.3",
"sass-loader": "^7.1.0",
"sinon": "^2.4.1",
"source-map-loader": "^0.2.1",
"storybook-addon-jsx": "^5.4.0",
"style-loader": "^0.16.1",
"ts-jest": "23.10.5",
"typescript": "^3.0.1",
"url-loader": "^1.1.2",
"webpack": "^4.29.5",
"webpack-cli": "^3.2.3"
}
}
And here is the component that is using the Select
component:
import * as React from 'react'
import Select from 'react-select'
import { MultiSelectOption } from '../interfaces/SelectionUI'
class MultipleSelectPicker extends React.PureComponent<MultipleSelectPickerProps> {
onChange = (allSelected: MultiSelectOption[]) => {
const {
onAdd,
onRemove,
values,
} = this.props
if (values.length < allSelected.length) {
const addedOption = allSelected.find(selected => !values.includes(selected))
onAdd(addedOption)
}
else if (values.length > allSelected.length) {
const removedOption = values.find(value => !allSelected.includes(value))
onRemove(removedOption)
}
}
render() {
const {
name,
values,
options,
placeholder,
} = this.props
return (
<Select
name={name}
value={values}
className={`${name} selectpicker`}
options={options}
onChange={this.onChange}
isMulti
placeholder={placeholder}
/>
)
}
}
export interface MultipleSelectPickerProps {
name: string,
options: MultiSelectOption[],
values: MultiSelectOption[],
placeholder?: string,
onAdd: (addedOption: MultiSelectOption) => void,
onRemove: (removedOption: MultiSelectOption) => void,
}
export default MultipleSelectPicker
Anyone have an idea about why the Webpack 4 minimization would keep the react-select
Emotion stylesheets from getting injected and how to fix that?