Multiple Tailwind CSS classes having multiple Webpack entry points - css

Problem statement
So I have a React project setup with webpack and tailwind CSS.
In my webpack config I have multiple entry point in order to generate different CSS and JS for each entry point.
The problem arises when I use the tailwind classes in my React components.
Let's suppose if I use a tailwind class bg-red-600 only in Component1(or entry point 1).
So after building the files through webpack the bg-red-600 will be present in all the entry point's generated CSS files(keep in mind I have just used this class in first entry point component only).
What it should be doing is only have bg-red-600 class in first component CSS file instead it is preset in all the CSS files even though I have not used it in any other place other than first component.
Hope I was able to made my point.
Thanks.
Webpack's entry points:
entry: {
app1: path.resolve(
__dirname,
'src/Component1'
),
app2: path.resolve(
__dirname,
'src/Component2'
),
},

Here is my solution:
/config folder with custom tailwind-xxx.config file for each entry js
eg. /config/tailwind-ConfirmButton.config.js:
module.exports = {
content: [
'./src/common/ConfirmButton/ConfirmButton.jsx',
],
// plugins: [require('#tailwindcss/forms')],
}
webpack.config.js
const postcssOpts = { postcssOptions: env => {
// here is the point
const component = env._module.context.slice(env._module.context.lastIndexOf('/') + 1)
return {
plugins: [
['tailwindcss', {
config: `./config/tailwind-${component}.config.js`,
}],
autoprefixer
]
}
}
}
...
entry: {
confirm: path.resolve(__dirname, './src/widgets/confirmButton.js'),
},
target: ['web', 'es5'], // <=== can be omitted as default is 'web'
output: {
filename: '[name]/tag.js',
path: path.resolve(__dirname, 'dist/exp'),
publicPath: './',
},
...
{
test: /\.css$/,
use: [
{ loader: 'style-loader' },
{ loader: 'css-loader' },
{
loader: 'postcss-loader',
options: postcssOpts,
},
],
},
entry widget js
eg. /src/widgets/customButton.js:
...
render(
<ConfirmButton
expId={expId}
content={content}
confirmBtn={confirmBtn}
cancelBtn={cancelBtn}
field={field}
/>,
container
)
finally run weppack --mode=production

Related

How to generate javascript file, which can be loaded via script tag by a non nextjs web page

In my current project we are migrating an application to nextjs. Now I would like to inject a react component into a legacy page through including a script, which is using code from the new nextjs application. Her a simplified version of the script:
function injectCode() {
const container = document.getElementById("container")
const reactRoot = createRoot(container)
reactRoot.render(<ExistingComponent />)
}
document.addEventListener('load', injectCode)
How can I generate a js file, that can be included into a webpage not rendered by nextjs? Best would be to generate the file with next build, but directly running webpack would be ok as well.
Until now I tried to write my own webpack config and just run webpack directly. It would be ok if I run webpack once and check the generated file into git. Therefore the generated file is put into the public folder.
This is not the only webpack config I tried, but after too many iterations solving one problem and getting the next I gave up thinking there is probably an easier solution.
module.exports = {
entry: './script.tsx',
module: {
rules: [
{
test: /\.(ts|tsx)?$/,
use: 'ts-loader',
exclude: /node_modules/,
},
{
test: /\.(ts|tsx)?$/,
include: path.resolve(__dirname, 'lib'),
use: [
{
loader: 'ts-loader',
},
],
},
{
test: /\.(ts|tsx)?$/,
include: path.resolve(__dirname, 'components'),
use: [
{
loader: 'ts-loader',
},
],
},
{
test: /\.(ts|tsx)?$/,
include: path.resolve(__dirname, 'single-header-footer'),
use: [
{
loader: 'ts-loader',
},
],
},
{
test: /\.(js)?$/,
include: path.resolve(__dirname, 'node-modules'),
use: [
{
loader: 'ts-loader',
},
],
},
],
},
resolve: {
extensions: ['.tsx', '.ts', '.js'],
},
output: {
path: path.resolve(__dirname, 'public'),
filename: 'next-js-component.js',
},
}
I am probably missing a lot of settings which are required.
The second thing I tried was extending the webpack configuration within the next.config.js file:
...
webpack: (config, { buildId, dev, isServer, defaultLoaders, nextRuntime, webpack }) => {
const originalEntry = config.entry
config.entry = async () => {
const entries = await originalEntry()
entries['next-js-component'] = ['script.tsx']
return entries
}
return config
},
...
This creates a chunk file .next/static/chunks/next-js-component-dc0ae8d2d7a87ec9.js. But I don't know how to use this file. Probably I need to add some other code into the page which loads the created chunk and additional chunks.
The header and footer of the legacy pages are already rendered by the next.js app and are included into the page via server side include. So I might be able to use something like the HtmlWebpackPlugin to inject the correct script tag into the header, which is then included into the legacy page.

Compile CSS and JS in difference files / WEBPACK

For 2 days I have been trying to compile the js and css file to a separate file because now everything is together. Does anyone have any idea how this can be solved?
I would be very grateful for your help.
There is my code webpack.config.js
const path = require('path');
const webpack = require('webpack');
const BrowserSyncPlugin = require('browser-sync-webpack-plugin');
module.exports = {
entry: './src/index.js',
output: {
path: path.resolve(__dirname, 'src/dist'),
filename: 'bundle.js'
},
module: {
rules: [
{
test: /\.m?js$/,
exclude: /(node_modules|bower_components)/,
use: {
loader: 'babel-loader',
options: {
presets: ['#babel/preset-env']
}
}
},
{
test: /\.scss$/,
use: [
"style-loader", // creates style nodes from JS strings
{
loader: "css-loader",
options: {
url: false
}
},
"sass-loader" // compiles Sass to CSS, using Node Sass by default
]
},
]
},
plugins: [
new BrowserSyncPlugin({
// browse to http://localhost:3000/ during development,
// ./public directory is being served
host: 'localhost',
port: 3000,
files: ['./src/*.html'],
server: { baseDir: ['src'] }
}),
new webpack.ProvidePlugin({
$: 'jquery',
jQuery: 'jquery'
})
]
};
I think MiniCssExtractPlugin is what you are looking for.
It takes the output of css-loader and create .css bundles. It takes care of downloading them in the browser (by pushing a section of code in webpack runtime code), and also yeah, it minifies the .css :).
Simple usage:
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = {
plugins: [new MiniCssExtractPlugin()],
module: {
rules: [
{
test: /\.css$/i,
use: [MiniCssExtractPlugin.loader, 'css-loader'],
},
],
},
};
Yes you are right. Style-loader creates javascript snippets that later in runtime creates .css rules and push them to the browser global css scope.

Webpack 4 - Style-loader/url not working

I'm having my webpack set up and it's running all fine, but in development it is serving my compiled scss stylesheets inline instead of using an URL.
module: {
rules: [
{
test: /\.scss$/,
use: [
{ loader: "style-loader"},
{ loader: "css-loader" },
{ loader: 'postcss-loader',
options: {
plugins: () => [require('autoprefixer')]
}
},
{ loader: "sass-loader" }
]
}
]
}
So I grabbed the docs and read up on how to use a single CSS file instead. I updated my webpack config to the following and since all loaders are running in reverse order this should be working;
module: {
rules: [
{
test: /\.scss$/,
use: [
{ loader: "style-loader/url"},
{ loader: "file-loader" },
{ loader: "css-loader" },
{ loader: 'postcss-loader',
options: {
plugins: () => [require('autoprefixer')]
}
},
{ loader: "sass-loader" }
]
}
]
}
It results in no errors, and inserts the following stylesheet into my header;
<link rel="stylesheet" type="text/css" href="6bbafb3b6c677b38556511efc7391506.scss">
As you can see it's creating an scss file, whereas I was expecting a .css file. I tried moving the file-loader around but that didn't work either and resulted in several crashes. Any idea how to turn this into a working css file?
I can't use mini-css-extract in my dev env since I'm using HMR. I already got this working on my prod env.
Update: When removing css-loader it compiles and shows my css applied to the page. But when I inspect the elements everything is on line 1 and the file it refers to can not be found
I'm importing my css like this in index.js by the way;
import '../css/styles.scss';
You can install extract-text-webpack-plugin for webpack 4 using:
npm i -D extract-text-webpack-plugin#next
The you can define the following constants:
// Configuring PostCSS loader
const postcssLoader = {
loader: 'postcss-loader',
options: {
ident: 'postcss',
plugins: [
// Write future-proof CSS and forget old preprocessor specific syntax.
// It transforms CSS specs into more compatible CSS so you don’t need to wait for browser support.
require('postcss-preset-env')()
]
}
};
// Configuring CSS loader
const cssloader = {
loader: 'css-loader',
options: {
importLoaders: 1
}
};
Then in your SASS loader section, you can use the following:
ExtractTextPlugin.extract({
use: [cssloader, postcssLoader, 'sass-loader']
})
Then in you plugins section, you need to use the following:
new ExtractTextPlugin({
filename: 'css/[name].css'
)
Now suppose that your entry section is like below:
entry: {
app: 'index.js'
}
The generated CSS will be named as app.css and placed inside the css folder.
Another useful plugins for handling these type of post creating operations are:
HtmlWebpackPlugin and HtmlWebpackIncludeAssetsPlugin
Working with these plugins along with extract-text-webpack-plugin gives you a lot of flexibility.
I had a similar issue with webpack, after searching for a long time i found the soluton of combining a few plugins:
This is my result config: (as a bonus it preserves your sass sourcemaps;))
watch: true,
mode: 'development',
devtool: 'source-map',
plugins: [
new MiniCssExtractPlugin({
filename: "[name].css", //make sure you use this format to prevent .scss extention in the hot reload file
chunkFilename: "[id].css"
})
],
module: {
rules: [
{
test: /\.scss$/,
use: [
'css-hot-loader', //5. this will hot load all the extracted css.
MiniCssExtractPlugin.loader, //4 this will extract all css
{
loader: "css-loader", //3. this is where the fun starts
options: {
sourceMap: true
}
},
{
loader: "postcss-loader", //2. add post css
options: {
sourceMap: true
}
},
{
loader: "sass-loader", //1 . you can ingore the globImporter
options: {
importer: globImporter(),
includePaths: ["node_modules"],
sourceMap: true
}
}
]
},
]
}

Webpack can't fix CSS override issue and bundle <style> elements in <head>

In my app, lets say I have two JS pages A and B, and each import a different stylesheet (import '../style/<A or B.css>').
Both stylesheets have identical classnames but but different properties.
I run yarn run dev ==> dev: webpack-dev-server --inline --hot ==> webpack -p
This is what my html <head> looks like
https://imgur.com/a/1JVb5
page A stylesheet is loaded first, then page B css style is loaded after
When I go to Page B, the css is correct
When I go to Page A, the css is mixed up and some class styles are overriden by page B.css.
My project structure is like
public/
bundle.js
index.html
src/
components/
pages/
style/
App.js
index.js
package.json
webpack.config.js
my webpack.config.js is
const path = require('path');
var config = {
entry: path.resolve(__dirname, 'src', 'index.js'),
output: {
path: path.resolve(__dirname, 'public'),
filename: 'bundle.js'
},
devServer: {
contentBase: path.resolve(__dirname, 'src'),
publicPath: path.resolve(__dirname, 'public')
},
module: {
rules: [
{
test: /\.js$/,
exclude: /(node_modules|bower_components)/,
use: [
{ loader: 'babel-loader',
options: { presets: ['react','env'] } }
]
},
{
test: /\.css$/,
use: [
{ loader: "style-loader?singleton",
options:
{ singleton: true }
},
{ loader: "css-loader" }
]
}
]
}
};
module.exports = config;
I want Webpack to merge the multiple elements and fix the css override issue
In Webpack, I have tried style-loader?singleton and { singleton: true } but it didnt work.
EDIT 1: looking into extract-text-webpack-plugin
EDIT 2:
import movieStyle from '../style/MovieDetail.css'
...
return (
<div id="CellDetail_right" className={ movieStyle['cell-detail-right'] }>...</div>
)
Ok, I added options: { modules: true } and it didnt work. My classNames are hyphenated and after compiling the browser renders the components WITHOUT any style or classes.
Div on browser looks like <div id="CellDetail_right">...<div>
One solution is to enable local scoped css to avoid styles bleeding/overrides.
Update your css-loader options to include modules: true
{
test: /\.css$/,
use: [
{
loader: "style-loader",
options: { singleton: true }
},
{
loader: "css-loader",
options: {
modules: true,
camelCase: 'dashes',
localIdentName: '[path][name]__[local]'
}
}
]
}
Then, using in your components as:
import styles from '../style/MovieDetail.css';
function MyComponent() {
return (
<div className={styles.container}>
<div className={styles.cellDetailRight}>Some Content</div>
</div>
);
}
This ensures that despite you have more .container rules defined in other css files, this particular rule becomes to something like ._-path-to-component__container.
Using camelCase: 'dashes' in options transform your hyphenated rules.
dashes in class names will be camelized
You can also review my webpack-demo project which includes configs to handle your scenario.
Check the webpack configurations
Read more on css-loader options

How does one deploy a ReactJs component that imports its own stylesheets?

I'm developing a large React component that I want to import into another React app. (I could have done it all as one big app, but I decided to separate this component into a its own project so that I could focus on the component separately.) It uses its own stylesheets, which it pulls in with "require('path/to/stylesheet.[s]css')".
For development, I serve it with webpack-dev-server which takes care of loading and pre-processing those stylesheets. My problem is that I don't know how to deploy the component so that another app can include it without breaking when the browser encounters those calls to require(path/to/stylesheet).
I've found lots of examples where projects use webpack-dev-server for development, but invoke babel (or other pre-processors) directly in order to deploy components to a dist/ directory, so this seems to be a common practice. But what to do about those invocations of "require('path/to/stylesheet.[s]css')"? Without webpack and its loaders to rely on, they all fail.
Ideally, I'd like to be able to use webpack for both development and production. Then I could deploy my component as a complete bundle and the css would be included in the code. I tried this earlier but it didn't work. It also has the disadvantage of making it hard for the main app to over-ride the styles in the component.
I suppose another way might be to set up my pipeline so that a main.scss includes all the other stylesheets and let Sass output a single style.css file that the consuming app has to include separately. Less elegant, but it makes it easier to override these styles.
I'm still mastering React and its eco-system, so I'd love to know what the best practice is for this in case anyone knows.
You need to run webpack and bundle all of your JS/CSS so that it can be consumed in your other project. If you use the correct loaders, the CSS is bundled directly into your JS bundle. Or, you can use a different loader to have webpack generate you a nice style.css file that bundles all your require(/path/to/css) into one file.
There are three or four basic things you need to do (Assuming webpack#3.*):
Use the "externals" in your webpack config file to exclude libraries in your package.json dependencies list.
Use babel-loader to transpile your javascript (ES2015 is supported by most browsers).
Use 'style-loader', 'css-loader', and 'sass-loader' to bundle your css/scss into your webpack js bundle. (This assumes you aren't using server-side react rendering. That's a bit trickier. You would need to use an isomorphic style loader instead.)
Optionally, use the extract-text-webpack-plugin to pull your css out into a separate file. (Commented out in the example below)
Be sure to make your bundle.js file the entry point in your package.json
Here is a good example of what you are trying to do: (This example works for server-side rendering as well as browser rendering. The BABEL_ENV lets us only require css files on the browser.)
const webpack = require('webpack');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const env = process.env.NODE_ENV;
console.log('Environment: ', env)
module.exports = {
devtool: 'source-map',
entry: env == 'production' ? './src/components/index.js' : './src/index.js',
output: {
path: env !== 'production' ? require('path').resolve('./dev'): require('path').resolve('./dist'),
filename: 'bundle.js',
publicPath: '/',
library: 'reactPorto',
libraryTarget: 'umd'
},
externals: env == 'production' ? [
'jquery',
'react',
'react-dom',
'react-bootstrap',
/^react-bootstrap\/.+$/,
'classnames',
'dom-helpers',
'react-owl-carousel',
'react-owl-carousel2',
'uncontrollable',
'warning',
'keycode',
'font-awesome'
] : [],
devServer: {
inline: true,
contentBase: './dev',
staticOptions: { index: 'test.html' },
historyApiFallback: {
rewrites:[{ from: /./, to: 'test.html' }],
},
hot: true,
},
plugins: [
// new ExtractTextPlugin({
// filename: 'style.css',
// allChunks: true
// }),
new webpack.DefinePlugin({
'process.env': {
'NODE_ENV': JSON.stringify(env || 'development'),
'BABEL_ENV': JSON.stringify(env || 'development')
}
}),
new webpack.optimize.OccurrenceOrderPlugin(),
...(env != 'production' ? [new webpack.HotModuleReplacementPlugin()] : []),
...(env == 'production' ? [new webpack.optimize.UglifyJsPlugin({ sourceMap: true, compress: { warnings: false } })] : []),
new webpack.NoEmitOnErrorsPlugin()
],
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: "babel-loader",
query: {
presets: [
'es2015',
'react',
'stage-2',
...(env != 'production' ? ['react-hmre'] : [])
],
plugins: []
}
}
},
{
test : /(\.css|\.scss)/,
// exclude: /node_modules/,
// use : ExtractTextPlugin.extract({
// use: [
// 'isomorphic-style-loader',
// {
// loader: 'css-loader',
// options: {
// importLoaders: 1
// }
// },
// 'sass-loader'
// ]
// }),
use: ['iso-morphic-style-loader', 'css-loader', 'sass-loader']
},
{test: /\.svg(\?v=\d+\.\d+\.\d+)?$/, loader: 'file-loader?mimetype=image/svg+xml'},
{test: /\.woff(\?v=\d+\.\d+\.\d+)?$/, loader: "file-loader?mimetype=application/font-woff"},
{test: /\.woff2(\?v=\d+\.\d+\.\d+)?$/, loader: "file-loader?mimetype=application/font-woff"},
{test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/, loader: "file-loader?mimetype=application/octet-stream"},
{test: /\.eot(\?v=\d+\.\d+\.\d+)?$/, loader: "file-loader"},
{test: /\.(png|jpg|jpeg)/, loader: 'file-loader' }
]
}
}

Resources