I try to lazy load routes using react-router-dom but it doesn't work. Webpack should automatically split chunks on import() but it doesn't, I always end up with one main.hash.js file instead of multiple chunks.
Is there something I'm missing ?
App Component:
import * as React from 'react';
import { Route, BrowserRouter, Link } from 'react-router-dom';
const Todos = React.lazy(() => import('routes/Todos'))
class App extends React.Component<{}, {}> {
render() {
return (
<>
<BrowserRouter>
<React.Suspense fallback={<div>loading...</div>}>
<Route exact path="/" render={() => <Link to="/todos">Todos</Link>} />
<Route exact path="/todos" component={Todos} />
</React.Suspense>
</BrowserRouter>
</>
)
}
}
export default App;
Here is the webpack config in case it may be related to some plugins or missing config on this side:
webpack common config:
const webpack = require('webpack');
const HtmlWebPackPlugin = require('html-webpack-plugin');
// clean folder (dist in this case)
const CleanWebpackPlugin = require('clean-webpack-plugin');
// copy files
const CopyPlugin = require('copy-webpack-plugin');
const path = require('path');
module.exports = {
entry: path.resolve(__dirname, 'src', 'index.tsx'),
resolve: {
extensions: ['.js', '.ts', '.tsx', '.scss'],
alias: {
'src': path.resolve(__dirname, 'src/'),
'components': path.resolve(__dirname, 'src/components/'),
'routes': path.resolve(__dirname, 'src/routes/'),
}
},
plugins: [
new CleanWebpackPlugin(['dist'], {}),
new HtmlWebPackPlugin({
template: "./src/index.html",
filename: "index.html"
}),
new CopyPlugin([
{ from: 'assets', to: 'assets' },
]),
]
};
webpack prod config:
const merge = require('webpack-merge');
const common = require('./webpack.common.js');
const path = require('path');
// split css per js file
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
// optimize js
const TerserPlugin = require('terser-webpack-plugin');
// optimize css
const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin');
// service-worker
const Workbox = require('workbox-webpack-plugin');
module.exports = merge(common, {
mode: 'production',
module: {
rules: [
{
test: /\.ts|\.tsx$/,
loader: "ts-loader",
include: path.resolve(__dirname, 'src')
},
{
test: /\.scss$/,
loader: MiniCssExtractPlugin.loader
},
{
test: /\.scss$/,
loader: 'css-loader',
options: {
modules: true,
localIdentName: '[hash:base64:5]'
}
},
{
test: /\.scss$/,
loader: 'postcss-loader',
},
{
test: /\.scss$/,
loader: 'sass-loader',
}
],
},
plugins: [
new MiniCssExtractPlugin({
filename: '[name].[hash].css',
chunkFilename: '[id].[hash].css',
}),
new OptimizeCssAssetsPlugin({}),
new Workbox.GenerateSW({
clientsClaim: true,
skipWaiting: true,
exclude: [/\.(?:png|jpg|jpeg|svg)$/],
runtimeCaching: [
{
urlPattern: /https?:\/\/.+/,
handler: 'StaleWhileRevalidate',
options: {
cacheableResponse: {
statuses: [0, 200]
}
}
}, {
urlPattern: /\.(?:png|jpg|jpeg|svg)$/,
handler: 'CacheFirst',
}],
})
],
optimization: {
minimizer: [new TerserPlugin()],
},
output: {
filename: '[name].[contenthash].js',
chunkFilename: '[name].[contenthash].js',
path: path.resolve(__dirname, 'dist')
}
});
From: https://github.com/webpack/webpack/issues/5703#issuecomment-357512412
compilerOptions.module has to be set to esnext in order for webpack to split dynamic imports.
Related
I'm using webpack in React JS. and Here is my error,
ERROR in Error: /Users/prakash/Desktop/projects/limitscale/goal.ly/goaly-react/src:58758
var theme = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches
^
ReferenceError: window is not defined
src:58758 Object../node_modules/react-axe/dist/index.js
/Users/prakash/Desktop/projects/limitscale/goal.ly/goaly-react/src:58758:13
src:113496 webpack_require
/Users/prakash/Desktop/projects/limitscale/goal.ly/goaly-react/src:113496:42
src:38954 Module../node_modules/html-webpack-plugin/lib/loader.js!./src/index.js
/Users/prakash/Desktop/projects/limitscale/goal.ly/goaly-react/src:38954:13
src:113496 webpack_require
/Users/prakash/Desktop/projects/limitscale/goal.ly/goaly-react/src:113496:42
src:113646
/Users/prakash/Desktop/projects/limitscale/goal.ly/goaly-react/src:113646:18
src:113647
/Users/prakash/Desktop/projects/limitscale/goal.ly/goaly-react/src:113647:12
index.js:321 HtmlWebpackPlugin.evaluateCompilationResult
[goaly-react]/[html-webpack-plugin]/index.js:321:28
index.js:237
[goaly-react]/[html-webpack-plugin]/index.js:237:22
task_queues.js:93 processTicksAndRejections
internal/process/task_queues.js:93:5
and my webpack config file,
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const CopyWebpackPlugin = require('copy-webpack-plugin')
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const ROOT_DIRECTORY = path.join(__dirname, '..')
const SRC_DIRECTORY = path.join(ROOT_DIRECTORY, 'src')
const devMode = process.env.NODE_ENV !== 'production';
const plugins = [];
plugins.push(new HtmlWebpackPlugin({
template: path.join(SRC_DIRECTORY, '')
})
);
plugins.push( new CopyWebpackPlugin({
patterns: [
{ from: path.join(SRC_DIRECTORY, 'assets'), to: path.join(ROOT_DIRECTORY, 'build') }
],
}));
plugins.push(new MiniCssExtractPlugin())
const config = {
entry: [path.resolve(__dirname, '../src/index.js')],
output: {
globalObject: "this",
path: path.resolve(__dirname, '../build'),
// filename: 'bundle.js',
filename: '[name].[hash:8].js',
sourceMapFilename: '[name].[hash:8].map',
chunkFilename: '[id].[hash:8].js',
publicPath: '/',
},
mode: 'development',
resolve: {
modules: [path.resolve('node_modules'), 'node_modules'],
extensions: [".jsx", ".js"]
},
performance: {
hints: false
},
plugins: plugins ,
module: {
rules: [
{
test: /\.(js|jsx)$/,
exclude: /node_modules/,
use: {
loader: "babel-loader"
}
},
{
test: /\.html$/,
use: [
{
loader: "html-loader"
}
]
},
{
test: /\.(s*)css$/,
use: [
MiniCssExtractPlugin.loader,
'css-loader',
'sass-loader'
]
},
{
test: /\.(jpe?g|gif|bmp|mp3|mp4|ogg|wav|eot|ttf|woff|woff2|png|svg)$/,
use: ['url-loader?limit=10000']
}
]
}
}
module.exports = config
What should I do to avoid this error?
Facing this issue for a while. Couldn't able to figure out. Is there any error in my code?.New to webpack.
I'm using React lazy loading in order to dynamically load my components and some antd icons.
It is working perfectly in development environment. But I have some ChunkLoadError on my page when it is trying to load components. However, it is working fine when I remove all lazy loading.
Here code example:
const ProtectedRoute = lazy(() => import('./ProtectedRoute'));
const Login = lazy(() => import('../examples/AnimatedLoginForm'));
const Home = lazy(() => import('./Home'));
const Account = lazy(() => import('./Account'));
const Router = () => {
return (
<BrowserRouter>
<MainLayout>
<Suspense fallback={<Spinner />}>
<Switch>
<Route exact path="/login" component={Login} />
<ProtectedRoute exact path="/" component={Test} />
<ProtectedRoute path="/form" component={Home} />
<ProtectedRoute path="/settings" component={Settings} />
<ProtectedRoute path="/account" component={Account} />
</Switch>
</Suspense>
</MainLayout>
</BrowserRouter>
);
};
And there my webpack.config.js:
const path = require('path');
const webpack = require('webpack');
const dotenv = require('dotenv');
const fs = require('fs');
const AntdDayjsWebpackPlugin = require('antd-dayjs-webpack-plugin');
module.exports = ({ ENVIRONMENT }) => {
// Get the root path (assuming your webpack config is in the root of your project!)
const currentPath = path.join(__dirname);
// Create the fallback path (the production .env)
const basePath = `${currentPath}/.env`;
// We're concatenating the environment name to our filename to specify the correct env file!
const envPath = `${basePath}.${ENVIRONMENT}`;
// Check if the file exists, otherwise fall back to the production .env
const finalPath = fs.existsSync(envPath) ? envPath : basePath;
// Set the path parameter in the dotenv config
const fileEnv = dotenv.config({ path: finalPath }).parsed;
// Reduce it to a nice object, the same as before (but with the variables from the file)
const envKeys = fileEnv
? Object.keys(fileEnv).reduce((prev, next) => {
const state = prev;
state[`process.env.${next}`] = JSON.stringify(fileEnv[next]);
return state;
}, {})
: {};
return {
entry: { app: './src/index.jsx' },
module: {
rules: [
{
test: /\.(js|jsx)$/,
exclude: /(node_modules|bower_components)/,
loader: 'babel-loader',
options: {
presets: ['#babel/preset-env'],
plugins: [
[
'import',
{ libraryName: 'antd', libraryDirectory: 'es', style: true }
]
]
}
},
{
// CSS imports
test: /\.css$/,
use: ['style-loader', 'css-loader']
},
{
test: /\.less$/,
use: [
{ loader: 'style-loader' },
{ loader: 'css-loader' },
{
loader: 'less-loader',
options: {
modifyVars: {},
javascriptEnabled: true
}
}
]
},
{
test: /\.svg(\?v=\d+\.\d+\.\d+)?$/,
use: [
{
loader: 'babel-loader'
},
{
loader: '#svgr/webpack',
options: {
babel: false,
icon: true
}
}
]
},
{
// Images imports
test: /\.(png|jpg|gif)$/,
use: ['file-loader']
},
{
// Fonts imports
test: /\.(woff|woff2|eot|ttf|otf)$/,
use: ['file-loader']
}
]
},
resolve: { extensions: ['*', '.js', '.jsx'] },
output: {
path: path.resolve(__dirname, 'dist'),
publicPath: '/dist/',
filename: '[name].bundle.js'
},
devServer: {
contentBase: path.join(__dirname, 'public/'),
port: 3000,
publicPath: 'http://localhost:3000/dist/',
historyApiFallback: true,
hotOnly: true
},
plugins: [
new webpack.HotModuleReplacementPlugin(),
new webpack.DefinePlugin(envKeys),
new AntdDayjsWebpackPlugin()
]
};
};
And my .babelrc
{
"presets": [
"#babel/env",
"#babel/preset-react"
],
"plugins": [
"syntax-dynamic-import"
]
}
I tried many many things, nothing worked, please help me.
Export svg from root file index.ts
import * as upArrow from "./up-arrow.svg";
import * as downArrow from "./down-arrow.svg";
export { upArrow, downArrow };
Try to use in my component
import * as React from "react";
import { Icon } from "components";
import { upArrow, downArrow } from "common/assets";
const CollapseIcon = ({ condition }) => (
<Icon alternative={upArrow} asset={downArrow} condition={condition} />
);
export default CollapseIcon;
If it's important, I use values asset and alternative in src attribute like that
export default styled.img.attrs<IconProps>({
src: ({ condition, alternative, asset }: IconProps) =>
condition ? alternative : asset,
alt: ({ alt }: IconProps) => alt
})`
vertical-align: middle;
`
But get html-element with double quotes. If delete it, element view correctly. Where am I wrong?
I try to declare non-code assets accrding docs, but never change
declare module "*.svg" {
const content: string;
export default content;
}
declare module "*.png";
declare module "*.jpg";
My Webpack config:
"use strict";
const path = require("path");
const webpack = require("webpack");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const CaseSensitivePathsPlugin = require("case-sensitive-paths-webpack-plugin");
const InterpolateHtmlPlugin = require("react-dev-utils/InterpolateHtmlPlugin");
const WatchMissingNodeModulesPlugin = require("react-dev-utils/WatchMissingNodeModulesPlugin");
const ModuleScopePlugin = require("react-dev-utils/ModuleScopePlugin");
const getClientEnvironment = require("./env");
const paths = require("./paths");
const publicPath = "/";
const publicUrl = "";
const env = getClientEnvironment(publicUrl);
module.exports = {
devtool: "cheap-module-source-map",
entry: [
require.resolve("./polyfills"),
require.resolve("react-dev-utils/webpackHotDevClient"),
paths.appIndexJs
],
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: [paths.appSrc, paths.appNodeModules].concat(
process.env.NODE_PATH.split(path.delimiter).filter(Boolean)
),
extensions: [".ts", ".tsx", ".js", ".jsx"],
alias: {
"react-native": "react-native-web"
},
plugins: [
new ModuleScopePlugin(paths.appSrc, [paths.appPackageJson])
]
},
module: {
strictExportPresence: true,
rules: [
{
enforce: "pre",
test: /\.js$/,
loader: "source-map-loader"
},
{
oneOf: [
{
test: [/\.bmp$/, /\.gif$/, /\.jpe?g$/, /\.png$/],
loader: require.resolve("url-loader"),
options: {
limit: 10000,
name: "static/media/[name].[hash:8].[ext]"
}
},
{
test: /\.(ts|tsx|js|jsx)?$/,
loader: "awesome-typescript-loader",
options: {
getCustomTransformers: require("../config/transformers.js")
}
},
{
exclude: [/\.(js|jsx|mjs)$/, /\.html$/, /\.json$/],
loader: require.resolve("file-loader"),
options: {
name: "static/media/[name].[hash:8].[ext]"
}
}
]
}
]
},
plugins: [
new InterpolateHtmlPlugin(env.raw),
new HtmlWebpackPlugin({
inject: true,
template: paths.appHtml
}),
new webpack.NamedModulesPlugin(),
new webpack.DefinePlugin(env.stringified),
new webpack.HotModuleReplacementPlugin(),
new CaseSensitivePathsPlugin(),
new WatchMissingNodeModulesPlugin(paths.appNodeModules),
new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/)
],
node: {
dgram: "empty",
fs: "empty",
net: "empty",
tls: "empty",
child_process: "empty"
},
performance: {
hints: false
},
externals: {
react: "React",
"react-dom": "ReactDOM"
},
devtool: "source-map"
};
UPD. As I see, browser receive my string as link
Add .svg extension to extenstions list for url-loader like that
...
rules: [
...
{
test: [/\.bmp$/, /\.gif$/, /\.jpe?g$/, /\.png$/, /\.svg$/],
loader: require.resolve("url-loader"),
options: {
limit: 10000,
name: "static/media/[name].[hash:8].[ext]"
}
},
...
I need to import default Kendo-ui template styles from this site https://www.telerik.com/kendo-react-ui/components/styling/.
When it trying to load styles, it throws NodeInvocationException: Prerendering failed because of error: Error: Module parse failed: "project folder"\node_modules\#progress\kendo-theme-default\dist\all.css Unexpected token (1:0)
You may need an appropriate loader to handle this file type.
Here is my layout.ts file where I want to import style template
import * as React from 'react';
import { NavMenu } from './NavMenu';
import "#progress/kendo-theme-default/dist/all.css";
export class Layout extends React.Component<{}, {}> {
public render() {
return <div className='container-fluid'>
<div className='row'>
<div className='col-sm-3'>
<NavMenu />
</div>
<div className='col-sm-9'>
{ this.props.children }
</div>
</div>
</div>;
}
}
webpack.config looks like this, its default configuration from auto generated react-redux asp.net project
const path = require('path');
const webpack = require('webpack');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const CheckerPlugin = require('awesome-typescript-loader').CheckerPlugin;
const merge = require('webpack-merge');
module.exports = (env) => {
const isDevBuild = !(env && env.prod);
// Configuration in common to both client-side and server-side bundles
const sharedConfig = () => ({
stats: { modules: false },
resolve: { extensions: ['.js', '.jsx', '.ts', '.tsx'] },
output: {
filename: '[name].js',
publicPath: 'dist/' // Webpack dev middleware, if enabled, handles requests for this URL prefix
},
module: {
rules: [
{ test: /\.tsx?$/, include: /ClientApp/, use: 'awesome-typescript-loader?silent=true' },
{ test: /\.(png|jpg|jpeg|gif|svg)$/, use: 'url-loader?limit=25000' }
]
},
plugins: [new CheckerPlugin()]
});
// Configuration for client-side bundle suitable for running in browsers
const clientBundleOutputDir = './wwwroot/dist';
const clientBundleConfig = merge(sharedConfig(), {
entry: { 'main-client': './ClientApp/boot-client.tsx' },
module: {
rules: [
{ test: /\.css$/, use: ExtractTextPlugin.extract({ use: isDevBuild ? 'css-loader' : 'css-loader?minimize' }) }
]
},
output: { path: path.join(__dirname, clientBundleOutputDir) },
plugins: [
new ExtractTextPlugin('site.css'),
new webpack.DllReferencePlugin({
context: __dirname,
manifest: require('./wwwroot/dist/vendor-manifest.json')
})
].concat(isDevBuild ? [
// Plugins that apply in development builds only
new webpack.SourceMapDevToolPlugin({
filename: '[file].map', // Remove this line if you prefer inline source maps
moduleFilenameTemplate: path.relative(clientBundleOutputDir, '[resourcePath]') // Point sourcemap entries to the original file locations on disk
})
] : [
// Plugins that apply in production builds only
new webpack.optimize.UglifyJsPlugin()
])
});
// Configuration for server-side (prerendering) bundle suitable for running in Node
const serverBundleConfig = merge(sharedConfig(), {
resolve: { mainFields: ['main'] },
entry: { 'main-server': './ClientApp/boot-server.tsx' },
plugins: [
new webpack.DllReferencePlugin({
context: __dirname,
manifest: require('./ClientApp/dist/vendor-manifest.json'),
sourceType: 'commonjs2',
name: './vendor'
})
],
output: {
libraryTarget: 'commonjs',
path: path.join(__dirname, './ClientApp/dist')
},
target: 'node',
devtool: 'inline-source-map'
});
return [clientBundleConfig, serverBundleConfig];
};
In order for the external .css files to work ( like kendo-theme-default coming from node_modules ), a postcss-loader is required.
im working on a project that was made in react with hashrouter, i want to change to browserouter but the project already has a webpack config and im kind of new to it, i know i should make webpack to take all calls to index (since im getting Cannot get on all routes) but i cant find any info on this kind of setup:
This is the current webpack config
const path = require('path');
const webpack = require('webpack');
const CaseSensitivePathsPlugin = require('case-sensitive-paths-webpack-plugin');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const FriendlyErrorsWebpackPlugin = require('friendly-errors-webpack-plugin');
module.exports = {
devtool: 'cheap-module-source-map',
entry: {
app: [
'webpack-hot-middleware/client',
'react-hot-loader/patch',
'./src/app'
]
},
resolve: {
modules: ['node_modules'],
extensions: ['.js', '.jsx', '.scss'],
alias: {
'react-native': 'react-native-web'
}
},
output: {
path: path.join(__dirname, 'public/assets'),
publicPath: '/assets/',
filename: '[name].bundle.js'
},
module: {
rules: [{
test: /\.jsx?$/,
exclude: /node_modules/,
use: [{
loader: 'react-hot-loader/webpack'
}, {
loader: 'babel-loader', options: {cacheDirectory: '.babel-cache'}
}]
}, {
// Most react-native libraries include uncompiled ES6 JS.
test: /\.js$/,
include: [
/node_modules\/react-native-/,
/node_modules\/react-router-native/,
/node_modules\/#indec/
],
loader: 'babel-loader',
query: {
presets: ['react-app'],
cacheDirectory: '.babel-cache'
}
}, {
test: /\.scss$/,
loader: [
'css-hot-loader'
].concat(
ExtractTextPlugin.extract({
fallback: 'style-loader',
use: ['css-loader', 'sass-loader']
})
)
}, {
test: /\.css$/,
use: ['style-loader', 'css-loader']
}, {
exclude: [
/\.html$/,
/\.(js|jsx)$/,
/\.json$/,
/\.s?css$/,
/\.(jpg|png)/
],
loader: 'url-loader',
options: {name: '[name].[ext]', limit: 10000}
}, {
test: /\.(jpg|png)(\?v=[0-9]\.[0-9]\.[0-9])?$/,
loader: 'file-loader',
options: {name: '[name].[ext]'}
}]
},
plugins: [
new webpack.DefinePlugin({
VERSION: JSON.stringify(require('./package.json').version),
NODE_ENV: JSON.stringify(process.env.NODE_ENV),
ENDPOINT: JSON.stringify(require('./config.json').endpoint)
}),
new webpack.optimize.CommonsChunkPlugin('vendor'),
new webpack.HotModuleReplacementPlugin(),
new CaseSensitivePathsPlugin(),
new FriendlyErrorsWebpackPlugin(),
new ExtractTextPlugin('[name].bundle.css')
],
node: {
fs: 'empty',
net: 'empty',
tls: 'empty'
}
};
app gets initiated on this index.js file
const path = require('path');
const app = require('connect')();
const config = require('./config.json');
const winston = require('winston');
const PORT = process.env.PORT || config.server.port;
process.env.NODE_ENV = config.mode;
app.use(require('morgan')(config.server.morgan));
app.use(require('compression')());
app.use(require('serve-static')(path.join(__dirname, config.server.static)));
if (config.mode === 'development') {
const config = require('./webpack.config');
const compiler = require('webpack')(config);
app.use(require('webpack-dev-middleware')(compiler, {
publicPath: config.output.publicPath,
}));
app.use(require('webpack-hot-middleware')(compiler));
}
require('http').createServer(app).listen(
PORT,
() => winston.info('Server started at port %s', config.server.port)
);
and my app js
import React from 'react';
import {BrowserRouter, Route, Switch} from 'react-router-dom';
import Aux from 'react-aux';
import Home from '../Home';
import Admin from '../Admin';
import SignIn from '../SignIn';
import Header from './Header';
import Footer from './Footer';
const App = () => (
<BrowserRouter>
<Aux>
<Header/>
<main>
<Switch>
<Route path="/" component={Home}/>
<Route path="/admin" component={Admin}/>
<Route path="/signIn" component={SignIn}/>
</Switch>
</main>
</Aux>
</BrowserRouter>
);
export default App;
I end up finding a easy solution, just created a express app and handled all 404 to index, didn't find how to do it with connect.
const path = require('path');
const express = require('express');
const app = express();
const config = require('./config.json');
const winston = require('winston');
const PORT = process.env.PORT || config.server.port;
process.env.NODE_ENV = config.mode;
app.use(require('morgan')(config.server.morgan));
app.use(require('compression')());
app.use(require('serve-static')(path.join(__dirname, config.server.static)));
if (config.mode === 'development') {
const config = require('./webpack.config');
const compiler = require('webpack')(config);
app.use(require('webpack-dev-middleware')(compiler, {
publicPath: config.output.publicPath,
}));
app.use(require('webpack-hot-middleware')(compiler));
}
app.get('/*', function(req, res) {
res.sendFile(path.join(__dirname, './public/index.html'), function(err) {
if (err) {
res.status(500).send(err);
}
});
});
app.use((req, res, next) => {
const err = new Error('Not Found');
err.status = 404;
next(err);
});
require('http').createServer(app).listen(
PORT,
() => winston.info('Server started at port %s', config.server.port)
);
You can use historyApiFallback: true in your config to do this.
Docs on that are located here