I am working on a micro frontend using Webpack.
And I have a problem where all local my SVGs and PNGs are not being loaded by Webpack5 react app. I keep getting 404 when doing that.
Can anyone point me out what I am doing wrong?
Here is the folder structuring
/public
/src
/components
navbar.tsx
/assets
Logo.svg
webpack.config.js
Here is my Webpack config. I am including the loader for assets, as indicated in the Webpack documentation
Webpack.config.js
const HtmlWebPackPlugin = require("html-webpack-plugin");
const ModuleFederationPlugin = require("webpack/lib/container/ModuleFederationPlugin");
const deps = require("./package.json").dependencies;
module.exports = {
output: {
publicPath: "http://localhost:3000/",
},
resolve: {
extensions: [".vue", ".tsx", ".ts", ".jsx", ".js", ".json"],
},
devServer: {
port: 3000,
historyApiFallback: true,
},
module: {
rules: [
{
test: /\.m?js/,
type: "javascript/auto",
resolve: {
fullySpecified: false,
},
},
{
test: /\.(css|s[ac]ss)$/i,
use: ["style-loader", "css-loader", "postcss-loader"],
},
{
test: /\.(ts|tsx|js|jsx)$/,
exclude: /node_modules/,
use: { loader: "babel-loader" },
},
{
test: /\.(png|svg|jpg|jpeg|gif)$/i,
type: 'asset/resource',
},
],
},
plugins: [
new ModuleFederationPlugin({
name: "App1",
filename: "remoteEntry.js",
remotes: {},
exposes: {},
shared: {
...deps,
react: {
singleton: true,
requiredVersion: deps.react,
},
"react-dom": {
singleton: true,
requiredVersion: deps["react-dom"],
},
},
}),
new HtmlWebPackPlugin({
template: "./src/index.html",
}),
],
};
And here is one of the images imported in the Navbar.
Navbar.tsx
import React from "react";
export default function Navbar() {
return (
<img src="./assets/Logo.svg" alt="Logo" />
)
}
I'm not an expert in react but with the app rendered in App.js maybe your url img src should be something like src='./components/assets/ because it would start from the src folder ? (i know if it's an import it works as expected but here it's a src ..)
Feel free to delete i didn't have enough karma to comment
Related
I have a React TypeScript project that uses webpack 5.
I am trying to get a runtime-config.js file that I can change out in production by importing it as decribed in a similar Vuejs Docker issue.
I wanted to use File Loader but found that it's been deprecated in webpack 5
Here's what I want to achieve:
have ./runtime-config.js non bundled so that I can refer to it on the window object
easily reference that file in React (TypeScript) so that this dynamic configuration can be read.
Any help is appreciated.
Below is my webpack configuration:
// webpack.config.js
import path from 'path'
import { Configuration as WebpackConfiguration, DefinePlugin, NormalModuleReplacementPlugin } from 'webpack'
import { Configuration as WebpackDevServerConfiguration } from 'webpack-dev-server';
import HtmlWebpackPlugin from 'html-webpack-plugin'
import ForkTsCheckerWebpackPlugin from 'fork-ts-checker-webpack-plugin'
import ESLintPlugin from 'eslint-webpack-plugin'
import tailwindcss from 'tailwindcss'
import autoprefixer from 'autoprefixer'
const CopyPlugin = require('copy-webpack-plugin');
interface Configuration extends WebpackConfiguration {
devServer?: WebpackDevServerConfiguration;
}
const config: Configuration = {
mode: 'development',
target: 'web',
devServer: {
static: path.join(__dirname, 'build'),
historyApiFallback: true,
port: 4000,
open: true,
liveReload: true,
},
output: {
publicPath: '/',
path: path.join(__dirname, 'dist'),
filename: '[name].bundle.js',
},
entry: {
main: './src/index.tsx',
'pdf.worker': path.join(__dirname, '../node_modules/pdfjs-dist/build/pdf.worker.js'),
},
module: {
rules: [
{
test: /\.(ts|js)x?$/i,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: [
'#babel/preset-env',
'#babel/preset-react',
'#babel/preset-typescript',
],
},
},
},
{
test: /\.(sa|sc|c)ss$/i,
use: [
'style-loader',
'css-loader',
'sass-loader',
{
loader: 'postcss-loader', // postcss loader needed for tailwindcss
options: {
postcssOptions: {
ident: 'postcss',
plugins: [tailwindcss, autoprefixer],
},
},
},
],
},
{
test: /\.(woff|woff2|eot|ttf|otf)$/,
loader: 'file-loader',
options: {
outputPath: '../fonts',
},
},
{
test: /runtime-config.js$/,
use: [
{
loader: 'file-loader',
options: {
name: 'runtime-config.js',
},
},
],
},
],
},
resolve: {
extensions: ['.tsx', '.ts', '.js'],
},
plugins: [
new HtmlWebpackPlugin({
template: 'public/index.html',
}),
new NormalModuleReplacementPlugin(
/^pdfjs-dist$/,
(resource) => {
// eslint-disable-next-line no-param-reassign
resource.request = path.join(__dirname, '../node_modules/pdfjs-dist/webpack.js')
},
),
new CopyPlugin({
patterns: [
// relative path is from src
{ from: 'public/images', to: 'images' },
],
}),
// Add type checking on dev run
new ForkTsCheckerWebpackPlugin({
async: false,
}),
// Environment Variable for Build Number - Done in NPM scripts
new DefinePlugin({
VERSION_NUMBER: JSON.stringify(process.env.VERSION_NUMBER || 'development'),
}),
// Add lint checking on dev run
new ESLintPlugin({
extensions: ['js', 'jsx', 'ts', 'tsx'],
}),
],
devtool: 'inline-source-map',
};
export default config
webpack works well with import syntax.
import image1 from '../public/assets/image1.png'
image1.png is bundled successfully.
But I want to pass the image src using props like below.
const paths = ['../public/assets/image1.png','../public/assets/image2.png']
const Parent = () => {
return (
<div>
<Child srcPath={path[0]} />
</div>
);
};
const Child = ({srcPath}) => {
return (
<img src={srcPath} />
)
}
webpack not bundle png files.
How do I let webpack bundle without import syntax?
My webpack version is 5.24.3 and this is my webpack.config.js
// webpack.config.js
module.exports = {
mode: "development",
entry: { index: path.resolve(__dirname, "src", "app.tsx") },
output: {
path: path.resolve(__dirname, "build"),
assetModuleFilename: "images/[hash][ext][query]",
},
resolve: { extensions: [".ts", ".tsx", ".js"] },
module: {
rules: [
{ test: /\.tsx?$/, loader: "ts-loader" },
{ test: /\.(png|svg|jpg|jpeg|gif)$/i, type: "asset/resource" },
],
},
plugins: [
new HtmlWebpackPlugin({
template: "public/index.html",
}),
],
devServer: {
contentBase: path.join(__dirname, "build"),
host: "localhost",
port: port,
historyApiFallback: true,
hot: true,
},
};
I am trying to split a big monolith React app into micro-frontends using webpack module federation.
I already have the remote module deployed and working great when running the dev server locally, everything works perfectly fine.
But after running yarn build and deploying the consumer app, it fails with the following error:
Uncaught (in promise) ChunkLoadError: Loading chunk 935 failed.
When commenting out the lazy loaded remote module, everything works fine.
Anyone have any idea how to get webpack to build correctly with a remotely loaded module?
This is my consumer component:
import WorkOrderTrackingErrorBoundary from './work_order_tracking_error_boundary'
const WorkOrderTracking = lazy(() => import('dMove/WorkOrderTracking'))
const WorkOrderTrackingPage = (props) => {
const spinner = (
<SpinnerContainer>
<Spinner type="bars" color="#3e145e" width="48px"/>
</SpinnerContainer>
)
return (
<Suspense fallback={spinner}>
<WorkOrderTrackingErrorBoundary>
<WorkOrderTracking/>
</WorkOrderTrackingErrorBoundary>
</Suspense>
);
};
export default WorkOrderTrackingPage;
and this is my webpack.prod.config.js file:
const HtmlWebpackPlugin = require('html-webpack-plugin');
const webpack = require('webpack');
const StringReplacePlugin = require('string-replace-webpack-plugin');
const NodePolyfillPlugin = require("node-polyfill-webpack-plugin")
const ModuleFederationPlugin = require("webpack/lib/container/ModuleFederationPlugin");
const paths = require('./paths');
const path = require('path');
module.exports = {
bail: true,
mode: 'production',
devtool: 'source-map',
module: {
rules: [
{
oneOf: [
{
test: /\.(js|jsx)$/,
exclude: paths.appNodeModules,
use: {
loader: 'babel-loader'
}
},
{
test: /\.html$/,
use: [
{
loader: 'html-loader'
}
]
},
{
test: [
/\.bmp$/,
/\.gif$/,
/\.jpe?g$/,
/\.png$/,
/\.svg$/
],
loader: require.resolve('url-loader'),
options: {
limit: 10000,
name: 'static/media/[name].[hash:8].[ext]'
}
},
{
exclude: [paths.appResources, paths.appNodeModules],
test: /\.css$/,
use: [
require.resolve('style-loader'),
{
loader: require.resolve('css-loader'),
options: {
importLoaders: 1,
modules: true,
localIdentName: '[name]__[local]___[hash:base64:5]'
}
}
]
},
{
include: [paths.appResources, paths.appNodeModules],
test: /\.css$/,
use: [
{
loader: 'style-loader' // creates style nodes from JS strings
},
{
loader: 'css-loader' // translates CSS into CommonJS
}
]
},
{
exclude: [/\.(js|jsx|mjs)$/, /\.html$/, /\.json$/],
loader: require.resolve('file-loader'),
options: {
name: 'static/media/[name].[hash:8].[ext]'
}
}
]
}
]
},
plugins: [
new ModuleFederationPlugin({
name: "dView", // name of this project
filename: "remoteEntry.js", // entry file to this project, remoteEntry.js is the conventional name
exposes: {
// components exposed out for other projects to consume
},
remotes: {
// projects to consume
dMove: "dMove#https://dmove-dev.3dsignals.io/remoteEntry.js"
},
shared: {
// shared modules between projects
react: {singleton: true},
"react-dom": {singleton: true}
}
}),
new webpack.DefinePlugin({
"GET_API_TOKEN_URL": JSON.stringify("/security/getAPIToken"),
"GET_REFRESH_TOKEN_URL": JSON.stringify("/security/refreshToken"),
"LOGIN_WITH_FOREIGN_IDM": JSON.stringify("/security/foreignLogin"),
"MAPBOX_API_TOKEN": JSON.stringify("pk.eyJ1IjoiM2RzZGV2b3BzIiwiYSI6ImNrazB2ZHJ2bTBsOTMydnJ1cG40dXc2NjYifQ.pGAk7cQ-ekRJY9KSSrW3lg")
}),
new StringReplacePlugin(),
new HtmlWebpackPlugin({
inject: true,
template: paths.appHtml,
minify: {
removeComments: true,
collapseWhitespace: true,
removeRedundantAttributes: true,
useShortDoctype: true,
removeEmptyAttributes: true,
removeStyleLinkTypeAttributes: true,
keepClosingSlash: true,
minifyJS: true,
minifyCSS: true,
minifyURLs: true
}
}),
new NodePolyfillPlugin()
],
output: {
filename: 'static/js/bundle.js',
chunkFilename: 'static/js/[name].[contenthash].chunk.js',
publicPath: '/',
path: paths.appBuild,
devtoolModuleFilenameTemplate: info =>
path.resolve(info.absoluteResourcePath).replace(/\\/g, '/')
},
entry: [
'#babel/polyfill',
paths.appIndexJs
],
resolve: {
modules: [
'node_modules',
paths.appNodeModules,
paths.appSrc,
paths.appResources
],
extensions: [
'.web.js',
'.mjs',
'.js',
'.json',
'.web.jsx',
'.jsx',
'tsx',
'ts'
]
}
};
ChunkLoadError: Loading chunk 946 failed. (missing:
I was also facing the missing - Uncaught (in promise) ChunkLoadError: Loading chunk 935 failed when access the chunks of another remote entry js and other chunks in development server but it's working fine local environment.
This chunk file was loading but content type showing as text/html instead of application/javascript when file loads in network tab response header.
You can check the response header - content type as application/javascript..If no then try to check nginx web server config for MIME types.
For me, it worked well in UAT but SIT was showing this error.
Hopes this will help to understand the reason of chunk files missing error.
In your webpack.prod.config.js file, eagerly load all of your shared libaries -
const HtmlWebpackPlugin = require('html-webpack-plugin');
const webpack = require('webpack');
const StringReplacePlugin = require('string-replace-webpack-plugin');
const NodePolyfillPlugin = require("node-polyfill-webpack-plugin")
const ModuleFederationPlugin = require("webpack/lib/container/ModuleFederationPlugin");
const paths = require('./paths');
const path = require('path');
//DO THIS
const packageJson = require("../package.json");
const eagerlyLoadedDependencyObject = Object.keys(
packageJson.dependencies
).reduce((obj, val) => {
obj[val] = {
eager: true,
};
return obj;
}, {});
module.exports = {
bail: true,
mode: 'production',
devtool: 'source-map',
module: {
rules: [
{
oneOf: [
{
test: /\.(js|jsx)$/,
exclude: paths.appNodeModules,
use: {
loader: 'babel-loader'
}
},
{
test: /\.html$/,
use: [
{
loader: 'html-loader'
}
]
},
{
test: [
/\.bmp$/,
/\.gif$/,
/\.jpe?g$/,
/\.png$/,
/\.svg$/
],
loader: require.resolve('url-loader'),
options: {
limit: 10000,
name: 'static/media/[name].[hash:8].[ext]'
}
},
{
exclude: [paths.appResources, paths.appNodeModules],
test: /\.css$/,
use: [
require.resolve('style-loader'),
{
loader: require.resolve('css-loader'),
options: {
importLoaders: 1,
modules: true,
localIdentName: '[name]__[local]___[hash:base64:5]'
}
}
]
},
{
include: [paths.appResources, paths.appNodeModules],
test: /\.css$/,
use: [
{
loader: 'style-loader' // creates style nodes from JS strings
},
{
loader: 'css-loader' // translates CSS into CommonJS
}
]
},
{
exclude: [/\.(js|jsx|mjs)$/, /\.html$/, /\.json$/],
loader: require.resolve('file-loader'),
options: {
name: 'static/media/[name].[hash:8].[ext]'
}
}
]
}
]
},
plugins: [
new ModuleFederationPlugin({
name: "dView", // name of this project
filename: "remoteEntry.js", // entry file to this project, remoteEntry.js is the conventional name
exposes: {
// components exposed out for other projects to consume
},
remotes: {
// projects to consume
dMove: "dMove#https://dmove-dev.3dsignals.io/remoteEntry.js"
},
shared: packageJson.dependencies,
}),
new webpack.DefinePlugin({
"GET_API_TOKEN_URL": JSON.stringify("/security/getAPIToken"),
"GET_REFRESH_TOKEN_URL": JSON.stringify("/security/refreshToken"),
"LOGIN_WITH_FOREIGN_IDM": JSON.stringify("/security/foreignLogin"),
"MAPBOX_API_TOKEN": JSON.stringify("pk.eyJ1IjoiM2RzZGV2b3BzIiwiYSI6ImNrazB2ZHJ2bTBsOTMydnJ1cG40dXc2NjYifQ.pGAk7cQ-ekRJY9KSSrW3lg")
}),
new StringReplacePlugin(),
new HtmlWebpackPlugin({
inject: true,
template: paths.appHtml,
minify: {
removeComments: true,
collapseWhitespace: true,
removeRedundantAttributes: true,
useShortDoctype: true,
removeEmptyAttributes: true,
removeStyleLinkTypeAttributes: true,
keepClosingSlash: true,
minifyJS: true,
minifyCSS: true,
minifyURLs: true
}
}),
new NodePolyfillPlugin()
],
output: {
filename: 'static/js/bundle.js',
chunkFilename: 'static/js/[name].[contenthash].chunk.js',
publicPath: '/',
path: paths.appBuild,
devtoolModuleFilenameTemplate: info =>
path.resolve(info.absoluteResourcePath).replace(/\\/g, '/')
},
entry: [
'#babel/polyfill',
paths.appIndexJs
],
resolve: {
modules: [
'node_modules',
paths.appNodeModules,
paths.appSrc,
paths.appResources
],
extensions: [
'.web.js',
'.mjs',
'.js',
'.json',
'.web.jsx',
'.jsx',
'tsx',
'ts'
]
}
};
eagerlyLoadedDependencyObject changes your dependencies from -
{
"react": "17.0.2",
"react-dom": "17.0.2",
"react-redux": "7.2.6",
"react-router-dom": "5.3.4"
}
to this -
{
"react":
{
"eager": true
},
"react-dom":
{
"eager": true
},
"react-redux":
{
"eager": true
},
"react-router-dom":
{
"eager": true
}
}
I'm trying to create a library of reusable react components for our internal use.
Webpack is bundling the output - which should be a single file. But it's actually putting out the bundle.js that I'd expect plus a file called [some_hash].worker.js.
I'm not sure how to force webpack to include that "worker" file with the single bundle that I'm asking it for.
The webpack.config:
const path = require('path');
const webpack = require('webpack');
const DIST_PATH = path.resolve(__dirname, "../dist");
const SRC_PATH = path.resolve(__dirname, "../src");
const APP_PATH = path.resolve(__dirname, "../src/index.js");
const BASE_PATH = path.resolve(__dirname);
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
module.exports = ({ appPath = APP_PATH, distPath = DIST_PATH }) => ({
context: BASE_PATH,
devServer: {
contentBase: distPath,
compress: true,
port: 9000,
historyApiFallback: true
},
resolve: {
modules: ['node_modules', SRC_PATH],
alias: {
'react': path.resolve(__dirname, '../node_modules/react'),
'react-dom': path.resolve(__dirname, '../node_modules/react-dom'),
}
},
externals: {
// Don't bundle react or react-dom
react: {
commonjs: "react",
commonjs2: "react",
amd: "React",
root: "React"
},
"react-dom": {
commonjs: "react-dom",
commonjs2: "react-dom",
amd: "ReactDOM",
root: "ReactDOM"
}
},
entry: {
bundle: appPath,
},
output: {
path: distPath,
filename: 'index.js',
publicPath: '/dist/',
library: 'internal-components',
libraryTarget: 'umd',
umdNamedDefine: true
},
module: {
rules: [
{
test: /\.jsx$/,
use: {
loader: 'babel-loader',
options: {
presets: ['#babel/preset-env', '#babel/preset-react'],
plugins: [
'#babel/plugin-proposal-object-rest-spread',
'#babel/plugin-syntax-dynamic-import',
[ '#babel/plugin-proposal-decorators', { 'legacy': true } ],
[ '#babel/plugin-proposal-class-properties', { 'loose': true } ]
]
}
}
},
{
test: /\.js$/,
exclude: /(node_modules|build)/,
use: {
loader: 'babel-loader',
options: {
presets: ['#babel/preset-env'],
plugins: [
'#babel/plugin-proposal-object-rest-spread',
'#babel/plugin-syntax-dynamic-import',
['#babel/plugin-proposal-decorators', {'legacy': true}],
["#babel/plugin-proposal-class-properties", {'loose': true}]
]
}
}
},
...
]
},
plugins: [
new CleanWebpackPlugin(),
new webpack.optimize.LimitChunkCountPlugin({
maxChunks: 1,
})
]
});
You might try using the worker-loader plugin with inline to handle the bundling -
rules: [
...
{
test: /\.worker\.js$/,
use: {
loader: 'worker-loader',
options: { inline: true, fallback: false }
}
}
]
That said, there are several open issues on Github surrounding using the worker as a blob, so YMMV
Actually if you are using webpack 3 and above, chunking of the bundle is automatically done for you. In the SplitChunks Plugin documentation here it is actually stated how this behaves.
So because of this you might need to scan your code and check for this conditions. Also it's good to know if you are asynchrously importing the some module? That might signal webpack to make it into a separate chunk.
I am new to webpack 2 and it's lazy loading, so far I have created project without lazy loading and code splitting, but now want to split my code into chunks and use System imports with React Router. I have created React Router part according to this article
this webpack 2 config file is below.
let webpack = require('webpack');
let path = require('path');
let ExtractTextPlugin = require('extract-text-webpack-plugin');
var devFlagPlugin = new webpack.DefinePlugin({
__DEV__: JSON.stringify(JSON.parse(process.env.DEBUG || 'false')),
'process.env': {
NODE_ENV: JSON.stringify(process.env.NODE_ENV || 'development'),
}
});
let extractSCSS = new ExtractTextPlugin('[name].css')
module.exports = {
context: __dirname,
entry: {
bundle: './src/app/app-client.jsx',
styles: './src/app/sass/main.scss',
vendor: [
'react', 'react-dom'
]
},
output: {
filename: '[name].js',
chunkFilename: 'chunk.[id].[chunkhash:8].js',
path: './src/build',
},
resolve: {
extensions: ['.js', '.jsx']
},
devtool: 'cheap-module-source-map',
module : {
rules : [
{
test: /\.scss$/,
loader: extractSCSS.extract(['css-loader','sass-loader'])
},
{
test: /\.jsx?$/,
exclude: [/node_modules/, /libs/],
use: {
loader: "babel-loader",
query: {
presets: ['es2015', 'react', 'stage-2' ],
plugins: [ "transform-runtime" ]
}
}
},
{
test: /\.woff2?$|\.ttf$|\.eot$|\.svg$|\.png|\.jpe?g|\.gif$/,
use: {
loader:'file-loader'
}
}
]
},
plugins: [
extractSCSS,
devFlagPlugin,
new webpack.optimize.CommonsChunkPlugin({
name: 'bundle',
children: true,
async: true,
minChunks: 2
}),
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor',
children: true,
async: true,
minChunks: 2
})
]
}
but webpack creates only two files, vendor and bundle, also size of bundle hasn't reduced after I separated React and React DOM.
this is my routes.
import App from './App.jsx';
function errorLoading(err) {
console.error('Dynamic page loading failed', err);
}
function loadRoute(cb) {
return (module) => cb(null, module.default);
}
export default {
component: App,
childRoutes: [
{
path: 'stock/info/:symbol(/:tab)',
getComponent(location, cb) {
System.import('./StockPage')
.then(loadRoute(cb))
.catch(errorLoading);
}
},
{
path: '*',
getComponent(location, cb) {
System.import('./NoMatch')
.then(loadRoute(cb))
.catch(errorLoading);
}
}
]
};
my application runs, but lazy loading won't work of course, because I have no chunks of my modules within System.import.
Please help me to create right webpack config for performance of my application!
Thanks in advance and sorry if something is nonsense, since I am new to webpack.
Webpack2 switched from System.import() to import() to match the current proposed javascript feature. Which is in stage3 right now.
So you should be able to change your webpack config to include STAGE-3
{
test: /\.jsx?$/,
exclude: [/node_modules/, /libs/],
use: {
loader: "babel-loader",
query: {
presets: ['es2015', 'react', 'stage-2', 'stage-3' ],
plugins: [ "transform-runtime" ]
}
}
},
Or the dynamic-import plugin
{
test: /\.jsx?$/,
exclude: [/node_modules/, /libs/],
use: {
loader: "babel-loader",
query: {
presets: ['es2015', 'react', 'stage-2' ],
plugins: [ "transform-runtime", "syntax-dynamic-import"]
}
}
},
Then change your routes
export default {
component: App,
childRoutes: [
{
path: 'stock/info/:symbol(/:tab)',
getComponent(location, cb) {
import('./StockPage')
.then(loadRoute(cb))
.catch(errorLoading);
}
},
{
path: '*',
getComponent(location, cb) {
import('./NoMatch')
.then(loadRoute(cb))
.catch(errorLoading);
}
}
]
};
See the webpack2 help page here for full docs on using import for code splitting and lazy loading.
https://webpack.js.org/guides/migrating/#code-splitting-with-es2015
https://github.com/tc39/proposal-dynamic-import
To enable Webpack2 tree shaking which only requires making one change to your babel setup.
presets: ['es2015', 'react', 'stage-2' ],
becomes
presets: [['es2015', { modules: false }], 'react', 'stage-2' ],
This is the article that I found out about treeshaking from: https://medium.freecodecamp.com/tree-shaking-es6-modules-in-webpack-2-1add6672f31b#.lv3ldgfhs