Webpack externals failing to resolve in parent project - javascript

I have a library, this library is React component library so it depends on react and react-dom. It also depends on react-router-dom (and transitively react-router).
These are packages the parent project provides. I don't want them in the bundle. This library is generated using webpack so I have added them to the externals configuration in the libraries webpack configuration, which is below (Webpack v4.4)
const webpack = require("webpack");
const path = require("path");
const ExtractTextPlugin = require("extract-text-webpack-plugin");
const OptimizeCssAssetsPlugin = require("optimize-css-assets-webpack-plugin");
module.exports = {
entry: "./src/index.js",
output: {
filename: "bundle.js",
path: path.resolve(__dirname, "dist"),
libraryTarget: "commonjs",
},
externals: {
"react-router": {
"commonjs": "react-router",
"commonjs2": "eact-router",
"amd": "react-router",
"root": "react-router"
},
"react-router-dom": {
"commonjs": "react-router-dom",
"commonjs2": "react-router-dom",
"amd": "react-router-dom",
"root": "react-router-dom"
},
"react": {
"commonjs": "react",
"commonjs2": "react",
"amd": "react",
"root": "React"
},
"react-dom": {
"commonjs": "react-dom",
"commonjs2": "react-dom",
"amd": "react-dom",
"root": "ReactDOM"
}
},
// devtool: "cheap-eval-source-map",
module: {
rules: [
{
test: /\.js$/,
exclude: /(node_modules|bower_components)/,
use: {
loader: "babel-loader",
options: {
presets: ["#babel/preset-env", "#babel/preset-react"],
plugins: [
"#babel/plugin-transform-flow-strip-types",
"#babel/plugin-proposal-object-rest-spread",
"transform-class-properties",
],
},
},
},
{
test: /\.scss$/,
use: ExtractTextPlugin.extract({
fallback: "style-loader",
use: [
{ loader: "css-loader", options: { minify: true } },
"sass-loader",
],
}),
},
{
// Match woff2 in addition to patterns like .woff?v=1.1.1.
test: /\.(woff|woff2)(\?v=\d+\.\d+\.\d+)?$/,
use: [
{
loader: "url-loader",
options: {
// Limit at 50k. Above that it emits separate files
limit: 50000,
// url-loader sets mimetype if it's passed.
// Without this it derives it from the file extension
mimetype: "application/font-woff",
// Output below fonts directory
name: "./fonts/[name].[ext]",
},
},
],
},
{
test: /\.svg$/,
use: [
{
loader: "svg-inline-loader",
options: {
removeSVGTagAttrs: false,
},
},
],
},
{
test: /\.(png|jp(e*)g)$/,
use: [{
loader: 'url-loader',
options: {
limit: 8000, // Convert images < 8kb to base64 strings
name: 'images/[hash]-[name].[ext]'
}
}]
},
],
},
plugins: [
new ExtractTextPlugin("styles.css"),
new OptimizeCssAssetsPlugin({
assetNameRegExp: /\.optimize\.css$/g,
cssProcessor: require("cssnano"),
cssProcessorOptions: { discardComments: { removeAll: true } },
canPrint: true,
}),
new webpack.optimize.AggressiveMergingPlugin(),
],
};
However, when I include the output of the library in the parent project it complains during the build phase that
ERROR in ../moss/dist/bundle.js
Module not found: Error: Can't resolve 'react-router' in '/Users/ubermouse/code/moss/dist'
# ../moss/dist/bundle.js 19655:223-261
# ./matai/static_src/js/containers/App.js
# ./matai/static_src/js/containers/Root.js
# ./matai/static_src/js/index.js
# multi ./matai/static_src/js/utils/polyfills.js ./matai/static_src/js/index.js
I get the above error for both react-router and react-router-dom. I do not get it for react or react-dom.
What is going on here? Am I using externals wrong? Why does it work for react/react-dom? Are they just not required yet? Can externals not pull dependencies from the parents node_modules? I've been tearing my hair out over this for hours :(
One thing I have noticed, react-router/react-router-dom have a module property in their package.json so their ESModule gets loaaded. react/react-router does not. I don't know if this changes anything, but it is something.
Our parent projects webpack config for completeness
const webpack = require('webpack');
const path = require('path');
const config = require('./frontend.config');
/**
* Base Webpack config, defining how our code should compile.
*/
module.exports = {
entry: {
vendor: [
"moment",
"react",
"react-dom",
"react-intl",
"react-intl-redux",
"react-redux",
"react-leaflet",
"react-router",
"react-router-dom",
"react-router-redux",
"redux",
"redux-observable",
"rxjs",
],
matai: [
path.join(config.paths.js, 'utils', 'polyfills.js'),
path.join(config.paths.js, 'index.js'),
],
},
output: {
path: path.join(config.paths.build_js),
filename: '[name].js',
},
plugins: [
new webpack.optimize.CommonsChunkPlugin({
name: "vendor",
minChunks: Infinity,
// (with more entries, this ensures that no other module
// goes into the vendor chunk)
}),
],
module: {
rules: [
// Disable require.ensure as it's not a standard language feature.
{ parser: { requireEnsure: false } },
// First, run the linter.
// It's important to do this before Babel processes the JS.
{
test: /\.js$/,
enforce: 'pre',
use: ['eslint-loader'],
include: path.join(config.paths.js),
},
{
test: /\.js$/,
use: ['babel-loader'],
exclude: [/node_modules/],
},
],
},
stats: {
// Set the maximum number of modules to be shown
maxModules: 3,
// Add chunk information (setting this to `false` allows for a less verbose output)
chunks: false,
// Add the hash of the compilation
hash: false,
// `webpack --colors` equivalent
colors: true,
// Add information about the reasons why modules are included
reasons: false,
// Add webpack version information
version: false,
},
// Some libraries import Node modules but don't use them in the browser.
// Tell Webpack to provide empty mocks for them so importing them works.
node: {
fs: 'empty',
net: 'empty',
tls: 'empty',
},
};

Related

Webpack breaking change

I am trying to build a react app but each time I run npm start, I am greeted with this message
Module not found: Error: Can't resolve 'buffer' in '/Users/abdus/Documents/GitHub/keywords-tracker/node_modules/buffer-equal-constant-time'
BREAKING CHANGE: webpack < 5 used to include polyfills for node.js core modules by default.
This is no longer the case. Verify if you need this module and configure a polyfill for it.
It gives the same message for a few different modules. I have tried npm installing these modules but the error persists
this is my webpack set up that works. you should install all the packages that listed in fallback:
// const path = require("path");
const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const CopyWebpackPlugin = require("copy-webpack-plugin");
const webpack = require("webpack");
module.exports = {
mode: "development",
target: "web",
entry: ["regenerator-runtime/runtime", "./src/index.js"],
output: {
filename: "bundle.js",
path: path.join(__dirname, "dist"),
publicPath: "/",
},
resolve: {
extensions: [".js", ".css"],
alias: {
// add as many aliases as you like!
components: path.resolve(__dirname, "src/components"),
},
fallback: {
// path: require.resolve("path-browserify"),
fs: false,
assert: require.resolve("assert/"),
os: require.resolve("os-browserify/browser"),
constants: require.resolve("constants-browserify"),
stream: require.resolve("stream-browserify"),
crypto: require.resolve("crypto-browserify"),
http: require.resolve("stream-http"),
https: require.resolve("https-browserify"),
},
},
// devtool: "eval-cheap-source-map",
devtool: "eval",
module: {
rules: [
{ test: /\.(js|jsx)/, loader: "babel-loader", exclude: /node_modules/ },
{ test: /\.css$/, use: ["style-loader", "css-loader"] },
// {
// test: /\.m?js/,
// resolve: {
// fullySpecified: false
// }
// },
{
test: /\.(woff(2)?|ttf|eot|jpg|jpeg|png|gif)(\?v=\d+\.\d+\.\d+)?$/,
use: [
{
loader: "file-loader",
options: {
name: "[name].[contenthash].[ext]",
outputPath: "fonts/",
},
},
],
},
{
test: /\.svg$/,
use: [
{
loader: "svg-url-loader",
options: {
limit: 10000,
},
},
],
},
{
test: /\.json5$/i,
loader: "json5-loader",
type: "javascript/auto",
options: {
esModule: true,
},
},
],
},
devServer: {
contentBase: path.join(__dirname, "build"),
historyApiFallback: true,
overlay: true,
},
plugins: [
new HtmlWebpackPlugin({
title: "NFT",
template: "src/index.html",
}),
// new CopyWebpackPlugin({
// patterns: [{ from: "assets", to: "assets" }],
// }),
],
};
you can get this webpack5-Boilerplate
Since there are too many polyfills, instead of manually installing all, you can use node-polyfill-webpack-plugin package. instead of fallback property
const NodePolyfillPlugin = require("node-polyfill-webpack-plugin");
plugins: [
new HtmlWebpackPlugin({
title: "esBUild",
template: "src/index.html",
}),
// instead of fallback
new NodePolyfillPlugin(),
// new webpack.ProvidePlugin({
// process: "process/browser",
// Buffer: ["buffer", "Buffer"],
// React: "react",
}),
],
It seems like you are using a front-end react app and some dependency is internally using the buffer module which is only available in target: node under webpack. So you will need to add a polyfill for the same.
module.exports = {
resolve: {
fallback: {
buffer: require.resolve('buffer'),
}
},
}
You can check the docs here at webpack: https://webpack.js.org/configuration/resolve/#resolvefallback
From Webpack 5 onwards, webpack doesn't polyfill for browser-based applications.

Blank page with webpack after conversion to typescript

I have a project that I was using with plain JS, built with rekit. I got tired of using JSDoc to specify the types, and I wanted to use typescript instead. I converted the entire codebase to TS using ts-migrate, but I am having serious problems with webpack.
The project seems to compile just fine, no errors on webpack side, and If I turn on performance hints it tells me that the size of some assets is too big, just like before.
However, when I open the page on the browser, it still downloads the bundle, but it is not executed. I can't understand what is wrong, if it is the TS configuration, webpack configuration or just the dev server. Below there are the two relevant files, a common file for shared configs between dev and prod, and the dev file. The webpack dev server and everything was working before, it is just after the migration to TS that fails.
If you prefer to see the changes in context, here is the pull request where I am doing the migration
Here is the common file:
const extensions = ['.web.js', '.mjs', '.js', '.json', '.web.jsx', '.jsx', 'ts', 'tsx'];
const rules = [
{
test: /\.(ts|js)x?$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: ['#babel/preset-env', '#babel/preset-react', '#babel/preset-typescript'],
},
},
},
{
test: /\.(js|jsx)$/,
exclude: /node_modules/,
use: ['babel-loader'],
},
];
module.exports = {
extensions,
rules,
};
And here is the webpack dev config:
const autoprefixer = require('autoprefixer');
const path = require('path');
const webpack = require('webpack');
const TsconfigPathsPlugin = require('tsconfig-paths-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CaseSensitivePathsPlugin = require('case-sensitive-paths-webpack-plugin');
const ModuleScopePlugin = require('react-dev-utils/ModuleScopePlugin');
const getClientEnvironment = require('./env');
const paths = require('./paths');
const { extensions, rules } = require('./webpack.common');
const publicPath = '/';
const publicUrl = '';
const env = getClientEnvironment(publicUrl);
module.exports = {
mode: 'development',
devtool: 'cheap-module-source-map',
entry: [
require.resolve('react-dev-utils/webpackHotDevClient'),
paths.appIndexJs,
paths.appIndexStyle,
],
output: {
pathinfo: true,
filename: 'static/js/bundle.js',
chunkFilename: 'static/js/[name].chunk.js',
publicPath: publicPath,
devtoolModuleFilenameTemplate: info =>
path.resolve(info.absoluteResourcePath).replace(/\\/g, '/'),
},
resolve: {
modules: ['node_modules', paths.appNodeModules].concat(
process.env.NODE_PATH.split(path.delimiter).filter(Boolean),
),
extensions,
plugins: [
new ModuleScopePlugin(paths.appSrc, [paths.appPackageJson]),
new TsconfigPathsPlugin({ logLevel: 'info', extensions }),
],
},
module: {
strictExportPresence: true,
rules: [
...rules,
{
oneOf: [
{
test: [/\.bmp$/, /\.gif$/, /\.jpe?g$/, /\.png$/],
loader: require.resolve('url-loader'),
options: {
limit: 10000,
name: 'static/media/[name].[hash:8].[ext]',
},
},
{
test: /\.less$/,
loader: 'style-loader!css-loader?sourceMap!less-loader?sourceMap',
},
{
test: /\.css$/,
use: [
require.resolve('style-loader'),
{
loader: require.resolve('css-loader'),
options: {
importLoaders: 1,
},
},
{
loader: require.resolve('postcss-loader'),
options: {
ident: 'postcss',
plugins: () => [
require('postcss-flexbugs-fixes'),
autoprefixer({
browsers: ['>1%', 'last 4 versions', 'Firefox ESR', 'not ie < 9'],
flexbox: 'no-2009',
}),
],
},
},
],
},
{
loader: require.resolve('file-loader'),
exclude: [/\.(js|jsx|mjs)$/, /\.html$/, /\.json$/],
options: {
name: 'static/media/[name].[hash:8].[ext]',
},
},
],
},
],
},
plugins: [
new webpack.EnvironmentPlugin(env.raw),
new HtmlWebpackPlugin({
inject: true,
template: paths.appHtml,
env: env.raw,
}),
new webpack.HotModuleReplacementPlugin(),
new CaseSensitivePathsPlugin(),
new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/),
],
node: {
dgram: 'empty',
fs: 'empty',
net: 'empty',
tls: 'empty',
child_process: 'empty',
},
performance: {
hints: false,
},
};
As you can see on this screenshot, there is no error on the console and the bundle is correctly being downloaded:
If I run a production build, all it "bundles" are the style loader and the webpack runtime. Here is the result of the webpack bundle analyzer:

Webpack authored library ReferenceError: react is undefined

I've searched and searched, but can't find an answer. Losing hope.
Here's the issue:
I have a component library that's built on React 17. I've bundled it using webpack 4.
The bundle gets generated and there don't seem to be any issues with it, until I tag it and attempt to use it in my work app.
Then I get the following error:
ReferenceError: react is not defined
The app compiles just fine, no errors. It only crashes when trying to use it.
Here's my webpack config for the library:
/* eslint-disable #typescript-eslint/no-var-requires */
const path = require('path');
const flexBugFixes = require('postcss-flexbugs-fixes');
const presetEnv = require('postcss-preset-env');
const autoprefixer = require('autoprefixer');
const normalize = require('postcss-normalize');
const nodeExternals = require('webpack-node-externals');
// const TerserPlugin = require('terser-webpack-plugin');
module.exports = {
target: 'web',
mode: 'production',
entry: './src/index.tsx',
devtool: 'source-map',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'index.js',
library: 'RenezaComponents',
libraryTarget: 'umd'
},
module: {
rules: [
{
test: /\.tsx?$/,
use: [
{
loader: 'babel-loader',
options: {
presets: [
'#babel/preset-env',
[
'#babel/preset-react',
{
runtime: 'automatic'
}
]
]
}
},
'ts-loader'
],
exclude: /node_modules/
},
{
test: /\.css$/,
use: [
'style-loader',
'css-loader',
{
loader: 'postcss-loader',
options: {
postcssOptions: {
plugins: [
flexBugFixes,
presetEnv({
autoprefixer: {
flexbox: 'no-2009'
},
stage: 3
}),
autoprefixer,
normalize()
]
}
}
}
],
sideEffects: true
},
{
test: /\.s(a|c)ss$/,
use: [
'style-loader',
'css-loader',
{
loader: 'postcss-loader',
options: {
postcssOptions: {
plugins: [
flexBugFixes,
presetEnv({
autoprefixer: {
flexbox: 'no-2009'
},
stage: 3
}),
autoprefixer,
normalize()
]
}
}
},
'sass-loader'
]
},
{
test: /.(woff(2)?|ttf)/,
loader: 'file-loader'
},
{
test: /\.(png|svg|jpg|gif)$/,
loader: 'file-loader'
}
]
},
resolve: {
extensions: ['.tsx', '.ts', '.js']
},
externals: nodeExternals()
};
The library gets installed using npm from our work private repository.
Example:
import { Button } from 'reneza-components'; // package.json has this as the library name
I've tried mucking about with externals, but none of them work. It always complains about the same thing.
The only other solution right now that I can come up with is just bundling react with it so it'd shut up. But that's not optimal.
I'm out of ideas. Perhaps anyone here would be a bit more clever than me?
Edit: Even bundling it together makes it throw the same error. So I'm all out of ideas.

Disable resolving for CSS/SASS/SCSS/LESS URLs in Webpack 5

In SASS, I have a CSS rule with a URL that looks like:
.eSearchFAQsAccord-q {
&::after {
content: url("./images/caret.svg");
}
}
By default, Webpack will resolve this (though it chokes on my SVG). I want it to NOT resolve. I want the browser to load this image separately, from my static directory.
I'm not sure what to say about what I've tried so far. I've tried a lot of settings from around the web in my Webpack config that didn't work...
Here's my Webpack config, which I think is pretty out-of-the-box:
const CopyWebpackPlugin = require("copy-webpack-plugin");
module.exports = {
mode: "development",
entry: {
index: `./src/index.js`,
},
target: "web",
output: {
path: `${__dirname}/dist`,
filename: "[name].js",
},
plugins: [
new CopyWebpackPlugin({
patterns: [{ from: "static" }],
}),
],
devServer: {
hot: true,
inline: true,
host: "localhost",
port: 8080,
watchOptions: {
poll: true,
},
},
module: {
rules: [
{
test: /\.s[ac]ss$/i,
use: [
"style-loader",
{
loader: "css-loader",
options: {
sourceMap: true,
},
},
{
loader: "sass-loader",
options: {
sourceMap: true,
},
},
],
},
{
test: /\.js$/,
enforce: "pre",
use: ["source-map-loader"],
},
],
},
};
I'm sure my issue is because I don't have intimate knowledge of Webpack, but the documentation I don't think is particularly intuitive at a glance.
I think Webpack 5 css-loader resolves this url() calls by default, and you need to disable it manually:
loader: "css-loader",
options: {
// Add this option
url: false,
},
More in the docs
Maybe there are same options for your other loaders, but I'm pretty sure you just need to disable it for css-loader only

Updating webpack.dev.config from Webpack 1 to 4

For the open source site, ReForum, I'm trying to update the site to the latest components (ie: webpack 4, react 16, etc). I started with babel and it upgraded smoothly. Then moved to webpack. I spent more than 10 hours trying various configs until I finally got it to compile using the following:
/**
* module dependencies for webpack dev configuration
* Proper webpack dev setup:
* https://medium.freecodecamp.org/how-to-develop-react-js-apps-fast-using-webpack-4-3d772db957e4
*/
const path = require('path');
const webpack = require('webpack');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const autoprefixer = require('autoprefixer');
// define paths
const nodeModulesPath = path.resolve(__dirname, '../node_modules');
const buildPath = path.resolve(__dirname, '../public', 'build');
const mainAppPath = path.resolve(__dirname, '../frontend', 'App', 'index.js');
const sharedStylesPath = path.resolve(__dirname, '../frontend', 'SharedStyles');
const componentsPath = path.resolve(__dirname, '../frontend', 'Components');
const containersPath = path.resolve(__dirname, '../frontend', 'Containers');
const viewsPath = path.resolve(__dirname, '../frontend', 'Views');
/**
* webpack development configuration
*/
module.exports = {
mode: 'development',
target: 'web',
devtool: 'inline-source-map',
entry: [
'webpack-hot-middleware/client',
mainAppPath,
],
output: {
filename: 'bundle.js',
path: buildPath,
publicPath: '/build/',
},
plugins: [
new MiniCssExtractPlugin({
// Options similar to the same options in webpackOptions.output
// both options are optional
filename: '[name].css',
chunkFilename: '[id].css',
}),
new webpack.HotModuleReplacementPlugin(),
],
module: {
rules: [
{
test: /\.js$/,
use: [ 'babel-loader' ],
exclude: [nodeModulesPath],
},
{
test: /\.(sa|sc|c)ss$/,
use: [
'style-loader',
'css-loader',
// 'postcss-loader',
{ loader: 'postcss-loader',
options: {
sourceMap: true,
plugins() {
return [autoprefixer('last 5 version')];
},
// plugins: () => [require('autoprefixer')({
// 'browsers': ['> 1%', 'last 5 versions'],
// })],
},
},
'sass-loader',
],
},
{ test: /\.(png|jpg)$/, use: ['url-loader?limit=8192'] },
{ test: /\.svg$/, use: ['url-loader?limit=10000&mimetype=image/svg+xml'] },
],
},
resolve : {
// automatically resolve file extensions (ie import File from '../path/file')
extensions: ['.js', '.css'],
// alias to call specified folders
alias: {
SharedStyles: sharedStylesPath,
Components: componentsPath,
Containers: containersPath,
Views: viewsPath,
},
},
};
Original Webpack 1 dev config
However, the React elements classname disappear, preventing the styles from being applied. It should be like:
But instead is:
Also, the head now have multiple <style>s .
Please help me get classnames to reappear and fix the multiple head style elements.
FYI, the only way I was able to get postcss-loader to run is by turning it into an object. It would fail with errors like "Error: No PostCSS Config found in ... "
Update 1:
Tried #noppa and #Alex Ilyaev suggestions the following but it didn't work.
{
test: /\.(sa|sc|c)ss$/,
use: [
'style-loader',
// 'css-loader',
{
loader: 'css-loader',
options: {
modules: true,
loaders: true,
importLoaders: 1,
localIndentName:'[name]__[local]___[hash:base64:5]',
},
},
// 'postcss-loader',
{ loader: 'postcss-loader',
options: {
sourceMap: 'inline',
options: {
ident: 'postcss',
plugins: (loader) => [
require('autoprefixer')(),
],
},
},
},
],
},
If you're using Webpack 4, start with no config file - that's right - no config file. WP4 includes sane defaults so most of your non webpack related issues will surface right there.
Regarding the multiple styles block, you need to switch to mini-css-extract-plugin (doesn't support HMR) or extract-css-chunks-webpack-plugin (supports HMR).
Also, note that during dev mode, you'll see multiple style blocks (for HMR). But production build should not have multiple style blocks.

Categories