Use Webpack HMR with a hoisted Lerna React Project - reactjs

Is it possible to use Webpack Hot Module Replacement (HMR) with a hoisted Lerna React project?
This is because each Lerna React package (see below as an example) is built independently and when a webpack-dev-server is launched on the main project(p3 for instance), it can only see its own changes (I mean p3 changes only) and NOT its dependencies (p1 or p2) changes sitting in other packages of its monorepo
some_lerna_project
/node_modules
/packages
/p1
/src
package.json
/p2
/src
package.json
/p3
/src
package.json
webpack.confing.js
lenra.json
If yes, would you please provide a sample config?

You need these packages be part of transpiled files.
// webpack.config.js
const path = require('path');
const PATH_DELIMITER = "[\\\\/]"; // match 2 antislashes or one slash
const safePath = (module) => module.split(/[\\\/]/g).join(PATH_DELIMITER);
const generateIncludes = (modules) => {
return [
new RegExp(`(${modules.map(safePath).join("|")})$`),
new RegExp(
`(${modules.map(safePath).join("|")})${PATH_DELIMITER}(?!.*node_modules)`
),
];
};
const transpileModules = ["#my-scope/p1", "#my-scope/p2"]; // using scoped packages
module.exports = {
mode: "development",
entry: {
app: "./src/index.js",
print: "./src/print.js",
},
module: {
rules: [
{
test: /\.m?js$/,
exclude: /(node_modules)/,
use: {
loader: "babel-loader",
options: {
presets: ["#babel/preset-env"],
},
},
},
{
test: /\.m?js$/,
include: generateIncludes(transpileModules),
use: {
loader: "babel-loader",
options: {
// use your preferred babel config
presets: ["#babel/preset-env"],
},
},
},
],
},
resolve: {
symlinks: false, // Avoid Webpack to resolve transpiled modules path to their real path
},
output: {
filename: "[name].bundle.js",
path: path.resolve(__dirname, "dist"),
},
};
This code is based on next-transpile-modules which enables Next.js projects to transpile mono-repo packages.

Related

mini-css-extract plugin with postcss throws this.getOptions is not a function

I'm trying to set up a tailwind css for my personal project. It's a react SSR application. I'm having an issue with postcss setup under the webpack configuration. It throws the same error on every *.css file (even on the empty ones).
It looks like it can't resolve the configuration file or default options? Tried different configurations, but no effect. Initially, I thought that it could be something with my css files, but they all valid and compile if I remove postcss plugin
webpack config
const path = require('path');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const ESLintPlugin = require('eslint-webpack-plugin');
const paths = require('./paths');
module.exports = {
entry: {
index: path.resolve(paths.projectSrc, 'index.js'),
},
resolve: {
alias: {
'#src': paths.projectSrc,
},
},
module: {
rules: [
{
test: /.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
},
},
{
test: /\.html$/,
use: [
{
loader: 'html-loader',
options: { minimize: true },
},
],
exclude: /node_modules/,
},
{
exclude: /node_modules/,
test: /\.css$/,
use: [
{
loader: MiniCssExtractPlugin.loader,
options: {
publicPath: path.resolve(__dirname, './client-build/css/'),
},
},
{
loader: 'css-loader',
options: { importLoaders: 1 },
},
{
loader: 'postcss-loader',
options: {
postcssOptions: {
config: path.resolve(__dirname, 'postcss.config.js'),
},
},
},
],
},
{
test: /\.(woff2?|ttf|otf|eot|png|jpg|svg|gif)$/,
exclude: /node_modules/,
loader: 'file-loader',
options: {
name: './assets/[name].[ext]',
},
},
],
},
plugins: [
new ESLintPlugin(),
new HtmlWebpackPlugin({
template: path.resolve(paths.public, 'index.html'),
filename: 'index.html',
}),
new MiniCssExtractPlugin({
filename: '[name].bundle.css',
chunkFilename: '[id].css',
}),
new CopyWebpackPlugin({
patterns: [{ from: path.resolve(paths.public, 'assets'), to: 'assets' }],
}),
],
devtool: 'inline-source-map',
};
postcss.config.js
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
};
console output
This is caused by a breaking change in v5.0.0 of postcss-loader where support for version 4 of webpack was dropped.
README which states:
Changelog
All notable changes to this project will be documented in this file. See standard-version for commit guidelines.
5.0.0 (2021-02-02)
⚠ BREAKING CHANGES
minimum supported webpack version is 5
How to fix it
You will need to downgrade postcss-loader to v4.2.
Side Note for Angular Projects
In case this helps other readers: You may not just see this in React projects. I found this issue after upgrading an Angular 8 project to Angular 11.
While I can't help with React projects, this Angular hint may also be of use anyways.
If you are on an Angular project and you've already tried to fix this issue by upgrading to v5 of webpack and then ran into dependency issues with libraries using v4 of webpack - You will want to follow these steps.
Downgrade postcss-loader to v4.2 as mentioned above.
Remove webpack v5 from package.json.
Delete package.lock.json.
Delete node_modules directory.
Run npm install.
Run ng serve or ng build.
Tell the boss you fixed it.
After this, you should be good to go with tailwindcss and Angular.

Why does %PUBLIC_URL% not get replaced in Webpack bundled index.html?

I am trying to deploy the front-end of my web application, but when I deploy to my hosting platform (currently AWS Amplify/S3) - no content displays on the website
I created the application using create-react-app, so the project structure follows their standard (public, src folders etc.).
When I run the application locally it works fine.
From the console errors, and looking at the index.html page that is generated by webpack, it looks like it is not replacing the %PUBLIC_URL% field that should be replaced with the public directory on build.
Please can someone explain how to fix this issue?
I have included my webpack.config file below and the full repo can be found here
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const config = {
entry: ['react-hot-loader/patch', './src/index.js'],
output: {
path: path.resolve(__dirname, 'build'),
filename: 'bundle.js',
},
module: {
rules: [
{
test: /\.(js|jsx)$/,
use: 'babel-loader',
exclude: /node_modules/,
},
{
test: /\.css$/,
use: ['style-loader', 'css-loader'],
},
{
test: /\.svg$/,
use: 'file-loader',
},
{
test: /\.png$/,
use: [
{
loader: 'url-loader',
options: {
mimetype: 'image/png',
},
},
],
},
{
test: /\.(ttf|eot|woff|woff2)$/,
use: 'file-loader',
},
],
},
resolve: {
extensions: ['.js', '.jsx'],
alias: {
'react-dom': '#hot-loader/react-dom',
},
},
devServer: {
contentBase: './build',
},
plugins: [
new HtmlWebpackPlugin({
template: path.resolve('./public/index.html'),
}),
],
}
module.exports = config
Turns out this was an issue with me trying to add a custom Webpack configuration to a create-react-app project.
I've removed the Webpack config now, and instead used the dist files created when using the create-react-app build process.

React Module parse failed: Unexpected character '#'

I am getting an error when trying to import the following in my react component:
import FontIconPicker from '#fonticonpicker/react-fonticonpicker';
import '#fonticonpicker/react-fonticonpicker/dist/fonticonpicker.base-theme.react.css';
I'm using this module: https://fonticonpicker.github.io/react-fonticonpicker/
I get this error:
./node_modules/#fonticonpicker/react-fonticonpicker/dist/fonticonpicker.base-theme.react.css
Module parse failed: Unexpected character '#' (18:0) You may need an
appropriate loader to handle this file type. | * | / |
#font-face{font-family:fontIconPicker;src:url(assets/fontIconPicker.ttf)
format("truetype"),url(assets/fontIconPicker.woff)
format("woff"),url(assets/fontIconPicker.svg#fontIconPicker)
format("svg");font-weight:400;font-style:normal}[class="
fipicon-"],[class^=fipicon-]{font-family:fontIconPicker!important;speak:none;font-style .......
The error can be reproduced with my code from github: https://github.com/gregbia/my-app
Use npm install, and npm start and the error will show.
My webpack looks like this:
/**
* Webpack Configuration
*
* Working of a Webpack can be very simple or complex. This is an intenally simple
* build configuration.
*
* Webpack basics — If you are new the Webpack here's all you need to know:
* 1. Webpack is a module bundler. It bundles different JS modules together.
* 2. It needs and entry point and an ouput to process file(s) and bundle them.
* 3. By default it only understands common JavaScript but you can make it
* understand other formats by way of adding a Webpack loader.
* 4. In the file below you will find an entry point, an ouput, and a babel-loader
* that tests all .js files excluding the ones in node_modules to process the
* ESNext and make it compatible with older browsers i.e. it converts the
* ESNext (new standards of JavaScript) into old JavaScript through a loader
* by Babel.
*
* TODO: Instructions.
*
* #since 1.0.0
*/
const paths = require( './paths' );
const autoprefixer = require( 'autoprefixer' );
const ExtractTextPlugin = require( 'extract-text-webpack-plugin' );
// Extract style.css for both editor and frontend styles.
const blocksCSSPlugin = new ExtractTextPlugin( {
filename: './dist/blocks.style.build.css',
} );
// Extract editor.css for editor styles.
const editBlocksCSSPlugin = new ExtractTextPlugin( {
filename: './dist/blocks.editor.build.css',
} );
// Configuration for the ExtractTextPlugin — DRY rule.
const extractConfig = {
use: [
// "postcss" loader applies autoprefixer to our CSS.
{ loader: 'raw-loader' },
{
loader: 'postcss-loader',
options: {
ident: 'postcss',
plugins: [
autoprefixer( {
browsers: [
'>1%',
'last 4 versions',
'Firefox ESR',
'not ie < 9', // React doesn't support IE8 anyway
],
flexbox: 'no-2009',
} ),
],
},
},
// "sass" loader converts SCSS to CSS.
{
loader: 'sass-loader',
options: {
// Add common CSS file for variables and mixins.
data: '#import "./src/common.scss";\n',
outputStyle: 'nested',
},
},
],
};
// Export configuration.
module.exports = {
entry: {
'./dist/blocks.build': paths.pluginBlocksJs, // 'name' : 'path/file.ext'.
},
output: {
// Add /* filename */ comments to generated require()s in the output.
pathinfo: true,
// The dist folder.
path: paths.pluginDist,
filename: '[name].js', // [name] = './dist/blocks.build' as defined above.
},
// You may want 'eval' instead if you prefer to see the compiled output in DevTools.
devtool: 'cheap-eval-source-map',
module: {
rules: [
{
test: /\.(js|jsx|mjs)$/,
exclude: /(node_modules|bower_components)/,
use: {
loader: 'babel-loader',
options: {
// #remove-on-eject-begin
babelrc: false,
presets: [ require.resolve( 'babel-preset-cgb' ) ],
// #remove-on-eject-end
// This is a feature of `babel-loader` for webpack (not Babel itself).
// It enables caching results in ./node_modules/.cache/babel-loader/
// directory for faster rebuilds.
cacheDirectory: true,
},
},
},
{
test: /style\.s?css$/,
exclude: /(node_modules|bower_components)/,
use: blocksCSSPlugin.extract( extractConfig ),
},
{
test: /editor\.s?css$/,
exclude: /(node_modules|bower_components)/,
use: editBlocksCSSPlugin.extract( extractConfig ),
},
],
},
// Add plugins.
plugins: [ blocksCSSPlugin, editBlocksCSSPlugin ],
stats: 'minimal',
// stats: 'errors-only',
// Add externals.
externals: {
react: 'React',
'react-dom': 'ReactDOM',
ga: 'ga', // Old Google Analytics.
gtag: 'gtag', // New Google Analytics.
jquery: 'jQuery', // import $ from 'jquery' // Use the WordPress version.
},
};
Actually, I'm so surprised that you used SCSS webpack configs beside the PostCSS because with a little configuration you can pre-process your CSSes and then post-process them to a compressed version by SCSS syntax. I set a config you in this link. I know it is not your main problem but I think your project configuration is not optimized.
The above link of webpack config will help you to make your configuration better and you can see the webpack configs of fonts. any way...
For your exact error, you should fix your font configuration on webpack just like below:
{
test: /\.(woff|woff2|eot|ttf|svg)$/,
exclude: /node_modules/,
loader: 'file-loader',
options: {
limit: 1024,
name: '[name].[ext]',
publicPath: 'dist/assets/',
outputPath: 'dist/assets/'
}
},
Update after work on the repo:
I write the webpack.config.dev.js file like below:
const paths = require('./paths');
const externals = require('./externals');
const autoprefixer = require('autoprefixer');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
// Extract style.css for both editor and frontend styles.
const blocksCSSPlugin = new ExtractTextPlugin({
filename: './dist/blocks.style.build.css',
});
// Extract editor.css for editor styles.
const editBlocksCSSPlugin = new ExtractTextPlugin({
filename: './dist/blocks.editor.build.css',
});
// Configuration for the ExtractTextPlugin — DRY rule.
const extractConfig = {
fallback: 'style-loader',
use: [
// "postcss" loader applies autoprefixer to our CSS.
{loader: 'css-loader'},
{
loader: 'postcss-loader',
options: {
ident: 'postcss',
plugins: [
autoprefixer({
browsers: [
'>1%',
'last 4 versions',
'Firefox ESR',
'not ie < 9', // React doesn't support IE8 anyway
],
flexbox: 'no-2009',
}),
],
},
},
// "sass" loader converts SCSS to CSS.
{
loader: 'sass-loader',
options: {
// Add common CSS file for variables and mixins.
data: '#import "./src/common.scss";\n',
outputStyle: 'nested',
},
},
],
};
// Export configuration.
module.exports = {
entry: {
'./dist/blocks.build': paths.pluginBlocksJs, // 'name' : 'path/file.ext'.
},
output: {
// Add /* filename */ comments to generated require()s in the output.
pathinfo: true,
// The dist folder.
path: paths.pluginDist,
filename: '[name].js', // [name] = './dist/blocks.build' as defined above.
},
// You may want 'eval' instead if you prefer to see the compiled output in DevTools.
devtool: 'cheap-eval-source-map',
module: {
rules: [
{
test: /\.(js|jsx|mjs)$/,
exclude: /(node_modules|bower_components)/,
use: {
loader: 'babel-loader',
options: {
// #remove-on-eject-begin
babelrc: false,
presets: [require.resolve('babel-preset-cgb')],
// #remove-on-eject-end
// This is a feature of `babel-loader` for webpack (not Babel itself).
// It enables caching results in ./node_modules/.cache/babel-loader/
// directory for faster rebuilds.
cacheDirectory: true,
},
},
},
{
test: /style\.s?css$/,
exclude: /(node_modules|bower_components)/,
use: blocksCSSPlugin.extract(extractConfig),
},
{
test: /editor\.s?css$/,
exclude: /(node_modules|bower_components)/,
use: editBlocksCSSPlugin.extract(extractConfig),
},
{
test: /\.css$/,
include: /(node_modules\/#fonticonpicker\/react-fonticonpicker\/dist)/,
loaders: ['style-loader', 'css-loader']
},
{
test: /\.(woff|woff2|eot|ttf|svg)$/,
include: /(node_modules\/#fonticonpicker\/react-fonticonpicker\/dist)/,
loader: 'file-loader',
options: {
limit: 1024,
name: '[name].[ext]',
publicPath: 'dist/assets/',
outputPath: 'dist/assets/'
}
}
],
},
// Add plugins.
plugins: [blocksCSSPlugin, editBlocksCSSPlugin],
stats: 'minimal',
// stats: 'errors-only',
// Add externals.
externals: externals,
};
And also install css-loader and file-loader.
npm install file-loader css-loader
Hint: it seems fonts need to have an outputPath in webpack configuration.
The problem is webpack is not loading your fonts #font-face in node_modules. You are excluding the loading of css from node_modules. But your #fonticonpicker/react-fonticonpicker/dist/fonticonpicker.base-theme.react.css is in node_modules.
Change this snippet in your webpack config
{
test: /style\.s?css$/,
exclude: /(node_modules|bower_components)/,
use: blocksCSSPlugin.extract( extractConfig ),
},
{
test: /editor\.s?css$/,
exclude: /(node_modules|bower_components)/,
use: editBlocksCSSPlugin.extract( extractConfig ),
},
to
{
test: /style\.s?css$/,
use: blocksCSSPlugin.extract( extractConfig ),
},
{
test: /editor\.s?css$/,
use: editBlocksCSSPlugin.extract( extractConfig ),
},
{ test: /(\.css$)/, // you need to load all css imported from node_modules
loaders: ['style-loader', 'css-loader', 'postcss-loader']
}
Seems like you're missing css-loader for .css stored in node_modules. That is why you are facing this issue. Run npm i -D css-loader and add this rule to your node_modules > cgb-scrips > config > webpack.config.<env>.js file:
module: {
rules: [
// ...
{
test: /\.css$/,
use: [
{ loader: 'raw-loader' },
{ loader: 'css-loader' },
]
},
// ...
],
},
Alternatively, to skip editing webpack.config.js you could simply import your files like so:
import 'raw-loader!css-loader!#fonticonpicker/react-fonticonpicker/dist/fonticonpicker.base-theme.react.css';
import 'raw-loader!css-loader!#fonticonpicker/react-fonticonpicker/dist/fonticonpicker.material-theme.react.css';
Your loader config in webpack doesn't match the CSS file Route.
import '#fonticonpicker/react-fonticonpicker/dist/fonticonpicker.base-theme.react.css';
It is neither style.css or editor.css. Hence you get an error. Also you are ignoring node_modules in your webpack loader config but you import the css from node_modules.
Adding
{
test: /react\.s?css$/,
use: [{
loader: 'css-loader',
options: {
modules: true
}
}],
},
should work

Webpack: Can I get a source map for after babel, before minification?

I have a fairly basic webpack setup that runs babel and out comes my minified js with a source map.
Now when I run my source map in chrome I get the js before babel and before minification. However I would often like to have my source map after babel but before minification. Is this possible?
TL;DR I want source map to post-babel pre-minifcation. Possible?
For completeness
I run babel-loader 8 with webpack 4
Here is a screenshot from chrome showing the problem. As you can see the Dropzone tag indicates this is jsx (and so before babel)
Secondly here is my webpack config (not that it actually matters for my question).
const path = require('path');
module.exports = {
context: path.join(__dirname, 'Scripts', 'react'),
entry: {
client: './client'
},
output: {
path: path.join(__dirname, 'Scripts', 'app'),
filename: '[name].bundle.min.js'
},
module: {
rules: [
{
test: /\.jsx?$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
plugins: [require('#babel/plugin-proposal-object-rest-spread')],
presets: ["#babel/es2015", "#babel/react", "#babel/stage-0"]
}
}
}
]
},
resolve: {
extensions: ['.js', '.jsx']
},
externals: {
// Use external version of React (from CDN for client-side, or
// bundled with ReactJS.NET for server-side)
react: 'React'
},
devtool: 'source-map'
};
Running webpack with -d gives a second set of source maps in chrome that does the trick.

SCSS files not importing in create-react-app

I'm using the React cli create-react-app and trying to load in a .scss file from a UI library http://react-conventions.herokuapp.com/. I've already run npm run eject and added the following to loaders in the config.dev I also downloaded the sass-loader already.
var path = require('path');
var autoprefixer = require('autoprefixer');
var webpack = require('webpack');
var findCacheDir = require('find-cache-dir');
var HtmlWebpackPlugin = require('html-webpack-plugin');
var CaseSensitivePathsPlugin = require('case-sensitive-paths-webpack-plugin');
var InterpolateHtmlPlugin = require('react-dev-utils/InterpolateHtmlPlugin');
var WatchMissingNodeModulesPlugin = require('react-dev- utils/WatchMissingNodeModulesPlugin');
var getClientEnvironment = require('./env');
var paths = require('./paths');
var publicPath = '/';
var env = getClientEnvironment(publicUrl);
module.exports = {
entry: [
require.resolve('react-dev-utils/webpackHotDevClient'),
require.resolve('./polyfills'),
paths.appIndexJs
],
output: {
path: paths.appBuild,
pathinfo: true,
filename: 'static/js/bundle.js',
publicPath: publicPath
},
resolve: {
fallback: paths.nodePaths,
extensions: ['.js', '.json', '.jsx', ''],
alias: {
'react-native': 'react-native-web'
}
},
module: {
// First, run the linter.
// It's important to do this before Babel processes the JS.
preLoaders: [
{
test: /\.(js|jsx)$/,
loader: 'eslint',
include: paths.appSrc,
}
],
loaders: [
// Process JS with Babel.
{
test: /\.(js|jsx)$/,
include: paths.appSrc,
loader: 'babel',
query: {
// This is a feature of `babel-loader` for webpack (not Babel itself).
// It enables caching results in ./node_modules/.cache/react-scripts/
// directory for faster rebuilds. We use findCacheDir() because of:
// https://github.com/facebookincubator/create-react-app/issues/483
cacheDirectory: findCacheDir({
name: 'react-scripts'
})
}
},
// "postcss" loader applies autoprefixer to our CSS.
// "css" loader resolves paths in CSS and adds assets as dependencies.
// "style" loader turns CSS into JS modules that inject <style> tags.
// In production, we use a plugin to extract that CSS to a file, but
// in development "style" loader enables hot editing of CSS.
{
test: /\.css$/,
loader: 'style!css?importLoaders=1!postcss'
},
{
test: /\.scss$/,
loaders: ["style", "css", "sass"],
exclude: /node_modules(?!\/react-conventions)/
},
{
test: /\.sass$/,
loaders: ["style", "css", "sass"]
},
// JSON is not enabled by default in Webpack but both Node and Browserify
// allow it implicitly so we also enable it.
{
test: /\.json$/,
loader: 'json'
},
// "file" loader makes sure those assets get served by WebpackDevServer.
// When you `import` an asset, you get its (virtual) filename.
// In production, they would get copied to the `build` folder.
{
test: /\.(ico|jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2)(\?.*)?$/,
loader: 'file',
query: {
name: 'static/media/[name].[hash:8].[ext]'
}
},
// "url" loader works just like "file" loader but it also embeds
// assets smaller than specified size as data URLs to avoid requests.
{
test: /\.(mp4|webm|wav|mp3|m4a|aac|oga)(\?.*)?$/,
loader: 'url',
query: {
limit: 10000,
name: 'static/media/[name].[hash:8].[ext]'
}
}
]
},
// We use PostCSS for autoprefixing only.
postcss: function() {
return [
autoprefixer({
browsers: [
'>1%',
'last 4 versions',
'Firefox ESR',
'not ie < 9', // React doesn't support IE8 anyway
]
}),
];
},
plugins: [
new InterpolateHtmlPlugin({
PUBLIC_URL: publicUrl
}),
// Generates an `index.html` file with the <script> injected.
new HtmlWebpackPlugin({
inject: true,
template: paths.appHtml,
}),
new webpack.DefinePlugin(env),
// This is necessary to emit hot updates (currently CSS only):
new webpack.HotModuleReplacementPlugin(),
new CaseSensitivePathsPlugin(),
new WatchMissingNodeModulesPlugin(paths.appNodeModules)
],
node: {
fs: 'empty',
net: 'empty',
tls: 'empty'
}
};
I try to import in my component using import Input from 'react-conventions/lib/Input'; But I'm still getting an error
Failed to compile.
Error in ./~/react-conventions/lib/components/Input/style.scss
Module parse failed: /Users/user/Desktop/App/node_modules/react- conventions/lib/components/Input/style.scss Unexpected character '#' (1:0)
You may need an appropriate loader to handle this file type.
SyntaxError: Unexpected character '#' (1:0)
# ./~/react-conventions/lib/components/Input/Input.js 19:13-36
Could this be a problem with the library? Doubtful but I'm just not seeing what I'm missing.
try this (exclude with a negative lookahead):
{
test: /\.scss$/,
loaders: ["style", "css", "sass"],
exclude: /node_modules(?!\/react-conventions)/
}
Try adding this to your package.json scripts
"scripts": {
"build-css": "node-sass src/ -o src/",
"watch-css": "npm run build-css && node-sass src/ -o src/ --watch --recursive"
and install also node-sass since the scripts depend on it.
This will work assuming you haven't eject your project yet.

Resources