Code splitting not happening via React lazy import ,and Webpack - javascript

//webpack dev
const common = require("./webpack.common");
const merge = require("webpack-merge");
const globImporter = require('node-sass-glob-importer');
module.exports = merge(common, {
mode: "development",
module: {
rules: [
{
test: /\.(sa|sc|c)ss$/,
use: [
{ loader: 'style-loader' },
{ loader: 'css-loader' },
{
loader: 'postcss-loader',
options: {
plugins: () => [require('autoprefixer')({
'overrideBrowserslist': ['> 1%', 'last 2 versions']
})],
}
},
{
loader: 'sass-loader', options: {
sassOptions: {
importer: globImporter()
}
}
}]
},
]
},
devServer: {
// contentBase: path.join(__dirname, 'dist'),
historyApiFallback: true,
port: 8081
}
});
//webpack prod
const common = require("./webpack.common");
const path = require("path");
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const merge = require("webpack-merge");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
module.exports = merge(common, {
mode: "production",
output: {
filename: "main.[contenthash].js",
path: path.resolve(__dirname, "dist")
},
module: {
rules: [
{
test: /\.(sa|sc|c)ss$/,
use: [
{ loader: MiniCssExtractPlugin.loader },
{ loader: 'css-loader' },
{
loader: 'postcss-loader',
options: {
plugins: () => [require('autoprefixer')({
'overrideBrowserslist': ['> 1%', 'last 2 versions']
})],
}
},
{
loader: 'sass-loader', options: {
sassOptions: {
importer: globImporter()
}
}
}]
}
]
},
plugins: [new MiniCssExtractPlugin({
filename: "./src/css/[name].[contentHash].css"
},
), new CleanWebpackPlugin()]
})
// webpack common
const HTMLWebpackPlugin = require('html-webpack-plugin');
module.exports={
entry:"./src/index.tsx",
resolve: {
// Add '.ts' and '.tsx' as resolvable extensions.
extensions: [".ts", ".tsx",".js", ".jsx"]
},
module:{
rules:[
{
test: /\.ts(x?)$/,
exclude: /node_modules/,
use: [
{
loader: "ts-loader"
}
]
},
{
test:/\html$/,
use:["html-loader"]
},
{
test:/\.(svg|png|jpe?g|gif)$/i,
use:{
loader:"file-loader",
options:{
name:"[name].[hash].[ext]",
outputPath:"images"
}
}
}
]
},
// externals: {
// "react": "React",
// "react-dom": "ReactDOM"
// },
plugins:[new HTMLWebpackPlugin({
template:"./src/index.html"
})]
}
Code splitting not happening via React lazy import ,and Webpack
I want to do code splitting with React Suspense and Lazy imports , I don't know what is missing because separate chunk is not getting created for my dynamically import component
Please anyone help I am using webpack 4 and React version 16.9
Getting below message warning console
WARNING in entrypoint size limit: The following entrypoint(s) combined asset size exceeds the recommended limit (244 KiB).
This can impact web performance.
Entrypoints:
main (533 KiB)
./src/css/main.000e3971ce67d214e0d7.css
main.5877aa0d0c3e45fb034f.js
WARNING in webpack performance recommendations:
You can limit the size of your bundles by using import() or require.ensure to lazy load some parts of your application.
For more info visit https://webpack.js.org/guides/code-splitting/

In tsconfig.json set "module": "esnext"

Related

'Cannot find module' when using dynamic import

I've deleted CRA and configured webpack/babel on my own. Now I have problems with dynamic imports.
This works fine:
import("./" + "CloudIcon" + ".svg")
.then(file => {
console.log(file);
})
This doesn't work:
const name = 'CloudIcon';
import("./" + name + ".svg")
.then(file => {
console.log(file);
})
I've tried to export files with different types. It didn't work.
Have tried to use Webpack Magic Comments, but didn't help either.
I suppose, that something is wrong with my webpack/babel settings, but what?
babel.config.js:
const plugins = [
"#babel/syntax-dynamic-import",
["#babel/plugin-transform-runtime"],
"#babel/transform-async-to-generator",
"#babel/plugin-proposal-class-properties"
];
if (process.env.NODE_ENV === 'development') {
plugins.push('react-refresh/babel');
}
module.exports = {
presets: [[
"#babel/preset-env", {
"debug": false,
"modules": false,
"useBuiltIns": false
}],
['#babel/preset-react', {throwIfNamespace: false}],
'#babel/preset-typescript'
],
plugins,
};
webpack.config.js:
require('dotenv').config();
const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const ReactRefreshWebpackPlugin = require('#pmmmwh/react-refresh-webpack-plugin');
const webpack = require('webpack');
const reactAppVars = (() => {
const obj = {};
for (let key in process.env) {
if (key.startsWith('REACT_APP_')) obj[key] = process.env[key];
}
return obj;
})();
const target = process.env['NODE_ENV'] === 'production' ? 'browserslist' : 'web';
const plugins = [
new webpack.EnvironmentPlugin({'NODE_ENV': process.env['NODE_ENV'], 'PUBLIC_URL': '', ...reactAppVars}),
new HtmlWebpackPlugin({template: path.resolve(__dirname, '../public/index.html')}),
new MiniCssExtractPlugin({filename: '[name].[contenthash].css'}),
new webpack.ProvidePlugin({process: 'process/browser'}),
new webpack.ProvidePlugin({"React": "react"}),
];
if (process.env['SERVE']) plugins.push(new ReactRefreshWebpackPlugin());
const proxy = {
//Proxy settings
}
module.exports = {
entry: "./src/index.js",
output: {
filename: "main.js",
path: path.resolve(__dirname, "../build"),
assetModuleFilename: '[path][name].[ext]'
},
plugins,
devtool: 'source-map',
devServer: {
static: {
directory: path.resolve(__dirname, "../public"),
},
proxy,
port: 9999,
hot: true,
},
module: {
rules: [
{ test: /\.(html)$/, use: ['html-loader'] },
{
test: /\.(s[ac]|c)ss$/i,
use: [
MiniCssExtractPlugin.loader,
'css-loader',
'postcss-loader',
'sass-loader'
]
},
{
test: /\.less$/i,
use: [
MiniCssExtractPlugin.loader,
'css-loader',
'postcss-loader',
{
loader: 'less-loader',
options: {
lessOptions: {
javascriptEnabled: true
}
}
}
]
},
{
test: /\.(png|jpe?g|gif|webp|ico)$/i,
type: process.env['NODE_ENV'] === 'production' ? 'asset' : 'asset/resource'
},
{
test: /\.svg$/i,
issuer: /\.[jt]sx?$/,
use: ['#svgr/webpack', {
loader: 'file-loader',
options: {
name: '[path][name].[ext]'
}
}],
},
{
test: /\.(woff2?|eot|ttf|otf)$/i,
type: process.env['NODE_ENV'] === 'production' ? 'asset' : 'asset/resource'
},
{
test: /\.jsx?$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
cacheDirectory: true,
}
}
},
{
test: /\.([cm]?ts|tsx)$/,
use: {
loader: "babel-loader",
options: {
presets: [
"#babel/preset-env",
"#babel/preset-react",
"#babel/preset-typescript",
]
}
}
},
{
test: /\.md$/,
loader: "raw-loader"
},
],
},
resolve: {
'roots': [path.resolve('./src')],
'extensions': ['', '.js', '.jsx', '.ts', '.tsx'],
extensionAlias: {
".js": [".js", ".ts"],
".cjs": [".cjs", ".cts"],
".mjs": [".mjs", ".mts"]
},
fallback: {
'process/browser': require.resolve('process/browser')
}
},
mode: process.env['NODE_ENV'],
target
}
What is basically happen that when you have defined import path, webpack will include that code in already existing chunk.
When you have a dynamic import (which translated to Regex) it will take all files that correspond to this Regex and place them in a different chunk.
It hard to determine from the details if you serve this additional chunks and the browser can reach them. Network and server logs is a good start.
https://webpack.js.org/guides/code-splitting/#prefetchingpreloading-modules
The problem was the extensions array in webpack
'extensions': ['', '.js', '.jsx', '.ts', '.tsx']
I changed it to this
'extensions': ['.js', '.jsx', '.ts', '.tsx', '...']
And now everything works fine.

split style[hash].css into dynamically loaded chunks

I am trying to split code of styles into smaller chunks due to the size of generated output. I am developing using react so i solved javascript loading with React.lazy().
Lets say that there is one big application with 50 different views you can open. Not all views are available to all users.
What i did in my router is:
...
const view = React.lazy(() => import("./views/view"));
...
...
<view />
...
What this did is divided entire application to 50 separate js files that are dynamically loaded which is good.
It however did not divide styles the same way. So lets look into one of the views:
import React, { Component } from "react";
import "../../scss/view.scss";
class view extends Component {
...
}
export default view;
So each view has its own scss files dedicated for this view, which makes them virtually standalone. The problem is that webpack is still producing only one big style[hash].css file.
Webpack config:
const HtmlWebPackPlugin = require("html-webpack-plugin");
const ExtractTextPlugin = require("extract-text-webpack-plugin");
const htmlPlugin = new HtmlWebPackPlugin({
template: "./src/index.html",
filename: "index.html"
});
const extractTextPlugin = new ExtractTextPlugin({
filename: "css/style.[hash:8].css",
allChunks: true
});
module.exports = {
output: {
filename: "js/main.[hash:8].js"
},
module: {
rules: [
{
test: /\.(js|jsx)$/,
exclude: /node_modules/,
use: "babel-loader"
},
{
test: /\.(png|jpg|gif|ico|svg|eot|ttf|woff|woff2)$/,
loader: "url-loader",
options: {
limit: 25000,
outputPath: "/assets/",
name: "[name].[hash:8].[ext]"
}
},
{
test: /\.(scss|sass|css)$/i,
use: ExtractTextPlugin.extract({
fallback: "style-loader",
use: [
{ loader: "css-loader" },
{ loader: "postcss-loader", options: { sourceMap: true } },
{ loader: "sass-loader", options: { sourceMap: true } }
]
})
}
]
},
resolve: {
extensions: [".js", ".jsx", ".json"]
},
plugins: [htmlPlugin, extractTextPlugin],
devServer: {
historyApiFallback: {
rewrites: [{ from: /^\/$/, to: "/index.html" }]
}
}
};
Is there any way to make styles also produced and loaded dynamically respective to their js file, so for example if i open view 10 it loads 10.main[hash].js and 10.style[hash].css ?
Answer to this problem is to replace deprecated extract-text-webpack-plugin package that is actually missing this functionality (splitting styles into respective chunks).
Instead mini-css-extract-plugin should be used and it automatically works.
updated working webpack config file:
const HtmlWebPackPlugin = require("html-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const htmlPlugin = new HtmlWebPackPlugin({
template: "./src/index.html",
filename: "index.html",
});
const miniCssExtractPlugin = new MiniCssExtractPlugin({
filename: "css/style.[hash:8].css",
chunkFilename: "css/[id].style.[hash:8].css",
});
module.exports = {
output: {
filename: "js/main.[hash:8].js",
},
module: {
rules: [
{
test: /\.(js|jsx)$/,
exclude: /node_modules/,
use: "babel-loader",
},
{
test: /\.(png|jpg|jpeg|gif|ico|svg|eot|ttf|woff|woff2)$/,
loader: "url-loader",
options: {
limit: 25000,
outputPath: "/assets/",
name: "[name].[hash:8].[ext]",
},
},
{
test: /\.(scss|sass|css)$/i,
use: [
MiniCssExtractPlugin.loader,
"css-loader",
"postcss-loader",
"sass-loader",
],
},
],
},
resolve: {
extensions: [".js", ".jsx", ".json"],
},
plugins: [htmlPlugin, miniCssExtractPlugin],
devServer: {
historyApiFallback: {
rewrites: [{ from: /^\/$/, to: "/index.html" }],
},
},
};

Webpack - postcss-url and file-loader

I'm building for myself a simple webpack configuration, to handle SCSS, ES6+, images/fonts copy etc.. for theme developing on Shopify.
I've hit a roadblock, when trying to handle fonts/images.
The file-loader handles them well, but for Shopify, when there is url() function in the CSS, the path must be like {{- 'file-name.ext' | asset_url -}}. The CSS bundle itself, must be like bundle.css.liquid. That is good, everything works as expected as for copying the assets. Here is the webpack config:
const path = require('path');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const sass = require('sass');
module.exports = {
entry: './src/js/index.js',
output: {
path: path.resolve(__dirname, '../shopify/assets/'),
filename: 'bundle.js'
},
module: {
rules: [
{
test: /\.js$/,
exclude: /(node_modules)/,
use: {
loader: 'babel-loader',
options: {
presets: [
[
'#babel/preset-env',
{
targets: {
browsers: ['last 3 version']
}
}
]
]
}
}
},
{
test: /\.(sa|sc|c)ss$/,
use: [
{
loader: MiniCssExtractPlugin.loader
},
{
loader: 'css-loader'
},
{
loader: 'postcss-loader',
options: {
config: {
path: './config'
}
}
},
{
loader: 'sass-loader',
options: {
implementation: sass
}
}
]
},
{
test: /\.(png|jpe?g|gif|svg|woff2?|ttf|otf|eot)$/i,
use: [
{
loader: 'file-loader',
options: {
name: '[name].[ext]'
}
}
]
}
]
},
plugins: [
new MiniCssExtractPlugin({
filename: 'bundle.css.liquid'
})
],
mode: 'production',
watch: true
};
Now, when i tried to do some work for the url(), it works as expected too, but now it doesn't copy the files in the desired folder. It compiles bundle.js and bundle.css.liquid as expected, but this time no assets are copied. I need the assets requested in the url() and the url change in the compiled bundles. Here is the postcss config:
const fs = require('fs');
const postcss = require('postcss');
const autoprefixer = require('autoprefixer');
const cssnano = require('cssnano');
const postcssurl = require('postcss-url');
const chalk = require('chalk');
const urlOptions = {
url: (asset, dir) => {
const file = asset.url.split('/')[2];
const processedUrl = `{{- ${file} | asset_url -}}`;
return processedUrl;
}
};
module.exports = {
plugins: [autoprefixer, cssnano, postcssurl(urlOptions)]
};
I appreciate any advices. Thank you in advance!
your test is kind of wierd
you can check the full example here
{
test: /\.(css|scss|sass)$/,
use: [
{ loader: 'style-loader' },
{ loader: 'css-loader' },
{
loader: 'postcss-loader',
options: {
ident: "postcss",
plugins: () => [
require("postcss-flexbugs-fixes"),
require("postcss-preset-env")({
autoprefixer: {
flexbox: "no-2009",
},
stage: 3,
}),
postcssNormalize(),
],
sourceMap: true,
}
},
{ loader: 'sass-loader' },
]
}

Optimize bundle.js file size

Below is the webpack config code and it's size is around 6.2 MB. In production mode it looks more time load the signin page url and from second time onwards it looks good , the problem with first time and need suggestion to reduce the bundle.js file size
webpack.base.js
module.exports = {
//Running babel to every file
module: {
rules: [
{
test: /\.js?$/,
loader: 'babel-loader',
exclude: /node_modules/,
options: {
presets: [
'react',
'stage-0',
['env', { targets: { browsers: ['last 2 versions'] } }]
]
}
}, {
test: /\.css$/,
loader: "style-loader!css-loader"
},
{
test: /\.less$/,
use:[
{
loader: "style-loader"
},
{
loader: 'css-loader'
},
{
loader: "less-loader"
}
]
},
{
test: /\.(jpe?g|png|gif|woff|woff2|eot|ttf|svg|otf)(\?[a-z0-9=.]+)?$/,
loader: 'url-loader?limit=100000'
}
]
}//end module
}
webpack.client.js:
const config = {
entry: './src/client/client.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'public')
}
};
module.exports = merge(baseConfig, config)
webpack.server.js:
const server_config = {
//letting webapck know that this bundle is created for node server.
target: 'node',
entry: './src/server/server.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'build')
},
externals: [webpackNodeExternals()],
node:{
__dirname:false
}
};
module.exports = merge(baseConfig, server_config);
Here is how I optimize webpack. You can view my full config here
First you need to run webpack production mode when you want to build in production
webpack -p --mode=production
Below is minial config
const path = require("path");
const webpack = require("webpack");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const CompressionPlugin = require("compression-webpack-plugin");
const UglifyJsPlugin = require("uglifyjs-webpack-plugin");
const WebpackShellPlugin = require('webpack-shell-plugin');
const OptimizeCSSAssetsPlugin = require("optimize-css-assets-webpack-plugin");
module.exports = {
optimization: {
minimizer: [
new UglifyJsPlugin({ // minify js file
cache: true,
parallel: true,
sourceMap: false,
extractComments: 'all',
uglifyOptions: {
compress: true,
output: null
}
}),
new OptimizeCSSAssetsPlugin({ // minify css
cssProcessorOptions: {
safe: true,
discardComments: {
removeAll: true,
},
},
})
]
},
plugins: [
new CompressionPlugin({ // gzip js and css
test: /\.(js|css)/
}),
new UglifyJsPlugin(), // uglify js
new WebpackShellPlugin({
onBuildStart: ['echo "Starting postcss command"'],
onBuildEnd: ['postcss --dir wwwroot/dist wwwroot/dist/*.css'] // uglify css using postcss
})
],
module: {
rules: [{
test: /\.scss$/,
use: [
'style-loader',
MiniCssExtractPlugin.loader,
{
loader: "css-loader",
options: {
minimize: true,
sourceMap: true
}
},
{
loader: "sass-loader"
}
]
},
{
test: /\.(js|jsx)$/,
exclude: /node_modules/,
loader: ["babel-loader", "eslint-loader"]
},
{
test: /\.(jpe?g|png|gif)$/i,
loader: "file-loader"
},
{
test: /\.(woff|ttf|otf|eot|woff2|svg)$/i,
loader: "file-loader"
}
]
}
};

Adding a csv file to a webpack build

I have placed a csv file in my assets folder for my react app, however, that file is not getting picked up and added to my dist build via webpack (the images are still added as assets to the build but the csv file is not). You can see my webpack build below. So how do I add a csv file to my dist build via webpack (the goal is for users of my app to be able to download this file)? Thanks!
webpack.dev.js
const merge = require('webpack-merge');
const common = require('./webpack.common.js');
const config = merge(common, {
mode: 'development',
devtool: 'inline-source-map',
devServer: {
contentBase: './dist',
historyApiFallback: true,
hot: true,
proxy: {
'/api': {
target: 'http://localhost:5001',
secure: false,
},
},
allowedHosts: [
'localhost',
'fatpandadev'
],
public: 'fatpandadev:8080'
},
});
module.exports = config;
webpack.common.js
const path = require('path');
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const DIST_DIR = path.resolve(__dirname, "dist");
const SRC_DIR = path.resolve(__dirname, "src");
const config = {
entry: [
"babel-polyfill",
`${SRC_DIR}/app/index.js`,
`${SRC_DIR}/app/assets/stylesheets/application.scss`,
`${SRC_DIR}/app/components/index.scss`,
"font-awesome/scss/font-awesome.scss",
"react-datepicker/dist/react-datepicker.css",
"rc-time-picker/assets/index.css",
"react-circular-progressbar/dist/styles.css",
"#trendmicro/react-toggle-switch/dist/react-toggle-switch.css",
],
output: {
path: `${DIST_DIR}/app/`,
filename: "bundle.js",
publicPath: "/app/"
},
module: {
rules: [
{
enforce: "pre",
test: /\.js$/,
exclude: /node_modules/,
loader: "eslint-loader",
options: {
failOnWarning: false,
failOnError: true
}
},
{
test: /\.js$/,
include: SRC_DIR,
loader: 'babel-loader',
query: {
presets: ['react', 'stage-2']
}
},
{
test: /\.css$/,
use: [
MiniCssExtractPlugin.loader,
'css-loader'
]
},
{
test: /\.scss$/,
use: [
MiniCssExtractPlugin.loader,
'css-loader',
'sass-loader'
]
},
{
test: /\.(jpe?g|png|gif|svg)$/i,
loaders: ['file-loader?context=src/images&name=images/[path][name].[ext]', {
loader: 'image-webpack-loader',
query: {
mozjpeg: {
progressive: true,
},
gifsicle: {
interlaced: false,
},
optipng: {
optimizationLevel: 7,
},
pngquant: {
quality: '75-90',
speed: 3,
},
},
}],
exclude: path.resolve(__dirname, "node_modules"),
include: __dirname,
},
{
test: /\.woff2?(\?v=[0-9]\.[0-9]\.[0-9])?$/,
// loader: "url?limit=10000"
use: "url-loader"
},
{
test: /\.(ttf|eot|svg)(\?[\s\S]+)?$/,
use: 'file-loader'
},
{
test: /\.(txt|csv)$/,
use: [
{
loader: 'file-loader',
options: {}
}
]
},
]
},
plugins: [
new MiniCssExtractPlugin({
filename: "application.css"
})
]
};
module.exports = config;
(this answer is only referenced on the server side)
In addition to #PlayMa256,
On Server side(Nodejs runtime), you may need emitFile: true
{
test: /\.(txt|csv|mmdb)$/,
use: [
{
loader: 'file-loader',
options: {
name: "[path][name].[ext]",
emitFile: true,
},
},
],
},
Refer to this PR: https://github.com/webpack-contrib/file-loader/pull/135
In my opinion, file-loader way seem to better than copy-webpack-plugin way.
You can test like below:
import csvPath from './assets/data.csv'
console.log(csvPath) // assets/data.csv
Tested version:
$ cat node_modules/webpack/package.json | jq .version
"4.29.5"
$ cat node_modules/file-loader/package.json | jq .version
"3.0.1"
You might want to check the CopyWebpackPlugin if you have no need to process/parse the files, but only to copy them to your dist folder.
Copy Webpack Plugin
Copies individual files or entire directories to the build directory
Install
npm i -D copy-webpack-plugin
Usage
webpack.config.js
const CopyWebpackPlugin = require('copy-webpack-plugin')
const config = {
plugins: [
new CopyWebpackPlugin([ ...patterns ], options)
]
}
Patterns
A simple pattern looks like this
{ from: 'source', to: 'dest' }
{
test: /\.(txt|csv)$/,
use: [
{
loader: 'file-loader',
options: {}
}
]
}
You should import you csv file as you import your images too.
import your csv files using raw-loader,
{
test: /\.csv$/i,
use: 'raw-loader',
}

Categories

Resources