React.js app.js file size - javascript

I created pretty simple react application containing 7 pages and 13 components.
I am using gulp to compile it, browserify for dependencies, all files are minimized.
My build'ed app.js file has 1.1 MB. I think it is quite big.
What can I do to reduce its size ?
Are there any good practices to achieve smallest size ?
EDIT:
My source code without dependencies is 91 KB.

Using webpack-uglify and disabling source maps can greatly improve the output to a reasonable size (~140kbs for a hello world application)
A couple of steps:
Setting devtool in webpack config to cheap-source-map or cheap-module-source-map so the source maps are not bundled with the output:
{
eval: 'cheap-source-map'
}
Activate uglify plugin or call webpack with -p argument
plugins: [
new webpack.optimize.UglifyJsPlugin({
compress: {
warnings: false
}
})
]
Defining node environment for production causes webpack to remove test helpers and optimize the ouput size:
plugins: [
new webpack.DefinePlugin({
'process.env': {
'NODE_ENV': JSON.stringify('production')
},
})
]
Note: these steps should be only used for production builds as they increase the build time.
Resource:
https://medium.com/modus-create-front-end-development/optimizing-webpack-production-build-for-react-es6-apps-a637e5692aea#.bug2p64de

It seems to me that you just have a lot of dependencies. In modern JS development it's pretty easy to go overboard and include every library under the sun. People tend to forget that if they include library X, they also include its dependencies.
The only thing I can recommend is to go through each one and asses, how useful it actually is. If you're just using a small part of a big library, try to replace it with something smaller, or maybe roll out your own solution.
Maybe you can find replacements here: microjs

Here is my plugin code that reduced the js from about 3MB to 350KB:
plugins: debug ? [] : [
new webpack.DefinePlugin({
'process.env': {
'NODE_ENV': '"production"'
}
}),
new webpack.optimize.DedupePlugin(),
new webpack.optimize.OccurenceOrderPlugin(),
new webpack.optimize.UglifyJsPlugin({
mangle: true,
sourcemap: false,
compress: {
warnings: false,
}
}),
]

Have a look at browserify-shim. That library helps you to outsource your dependencies with CDNs. So you have less dependencies in your compiled file.

If you don't manage to get your bundle size to where you need it to be, code-splitting is also an option.
The idea is that you don't send the code for your entire app at once - you send what is needed to load the first page, and then lazy-load other app code as it's needed.

Related

Module not found: Error: You attempted to import babel-preset which falls outside of the project src/ directory

I'm developing an application created using create-react-app
But then I needed to use mediainfojs library, this library requires wasm files, and based on what I understood I couldn't add it using create-react-app, I had to eject it.
After ejecting it, I went to mediainfo information on how to add the wasm on the webpack
They use the CopyPlugin, but then when I tried to do that it complained about the versions of my webpack (4) and the CopyPlugin.... so, I decided to migrate to webpack 5
That is when the pain starts... after follow their migration tutorial and do a bunch of modifications on my webpack.config I got to the following error while runing yarn build:
Module not found: Error: You attempted to import /MyWorkspace/project/node_modules/babel-preset-react-app/node_modules/#babel/runtime/helpers/esm/asyncToGenerator which falls outside of the project src/ directory. Relative imports outside of src/ are not supported.
The only place calling this babel-preset-react-app are in the configuation
Here:
{
test: /\.(js|mjs|jsx|ts|tsx)$/,
include: paths.appSrc,
loader: require.resolve("babel-loader"),
options: {
customize: require.resolve(
"babel-preset-react-app/webpack-overrides"
),
And here:
{
test: /\.(js|mjs)$/,
exclude: /#babel(?:\/|\\{1,2})runtime/,
loader: require.resolve("babel-loader"),
options: {
babelrc: false,
configFile: false,
compact: false,
presets: [
[
require.resolve("babel-preset-react-app/dependencies"),
{ helpers: true },
],
],
cacheDirectory: true,
cacheCompression: isEnvProduction,
// If an error happens in a package, it's possible to be
// because it was compiled. Thus, we don't want the browser
// debugger to show the original code. Instead, the code
// being evaluated would be much more helpful.
sourceMaps: false,
},
},
I have looked into similar issues reported here, but mostly of them seem to be related to either static files being dynamically imported or imports referencing ".." dir after the project directory
The full webpack config file is here
I'm probably missing something very silly, I'd be glad if someone can point it out.
I had a similar challenge and I was able to fix this by adding these definitions at the top of my webpack.config file
const babelRuntimeEntry = require.resolve('babel-preset-react-app');
const babelRuntimeEntryHelpers = require.resolve(
'#babel/runtime/helpers/esm/assertThisInitialized',
{ paths: [babelRuntimeEntry] }
);
const babelRuntimeRegenerator = require.resolve('#babel/runtime/regenerator', {
paths: [babelRuntimeEntry]
});
Then where you have the ModuleScopePlugin in the resolve.plugins
update it to be
new ModuleScopePlugin(paths.appSrc, [
paths.appPackageJson,
babelRuntimeEntry,
babelRuntimeEntryHelpers,
babelRuntimeRegenerator])
I'm also attempting to upgrade an ejected CRA project to Webpack 5. I was able to move forward using babel-preset-react-app-webpack-5, only to encounter the next CRA-related issue.
Be sure to replace calls like require.resolve("babel-preset-react-app/dependencies") with require.resolve("babel-preset-react-app-webpack-5/dependencies").
Also, be aware the package does not appear to be production-ready, but my own project is still in early development.
I had this issue with a few other babel packages after trying to upgrade an ejected CRA app to webpack v5. I tried many different approaches some of which worked in dev but not in prod and vice versa. I found this comment in the storybook github and it was the only thing that that seemed to work in all scenarios for me.
It's kinda annoying, but by simply moving the offending packages from devDependencies in my package.json to dependencies, it seems to fix the issue. I could spend more time trying to figure out why that fixes it, but I'll leave that to someone with more free time. :)

How to debug a Typescript library compiled with Rollup in a main project

I've been spending some time trying to figure out the follow scenario:
I got two projects, one is a library of components and the other one the main project which consumes the library. When I develop a new component I do as usual following a TDD process and at the end I do some case scenarios on storybook that I expect the component will behave under certain circumstances, however, one thing is the desirable behaviour and the other how will be the functionality in a real integration environment. For this case I've been trying to build the source maps for the library in order to debug it locally when its running on the main project.
The library is developed with typescript so I'm compiling it with rollup using the follow configuration:
rollup.config.js
import peerDepsExternal from "rollup-plugin-peer-deps-external";
import resolve from "#rollup/plugin-node-resolve";
import commonjs from "#rollup/plugin-commonjs";
import typescript from "rollup-plugin-typescript2";
import postcss from "rollup-plugin-postcss";
import image from "#rollup/plugin-image";
import babel from "rollup-plugin-babel";
const cssUrl = require("postcss-url");
const packageJson = require("./package.json");
const GLOBALS = {
react: "React",
"react-dom": "ReactDOM",
loadash: "lodash"
};
const isDev = process.env.NODE_ENV === "development";
export default {
input: "src/index.ts",
external: Object.keys(GLOBALS),
output: [
{
file: packageJson.main,
format: "cjs",
sourcemap: true,
// globals: global variable names of external dependencies
globals: GLOBALS
},
{
file: packageJson.module,
format: "esm",
sourcemap: true,
// globals: global variable names of external dependencies
globals: GLOBALS
}
],
plugins: [
peerDepsExternal(),
resolve(),
commonjs(),
babel({
exclude: "node_modules/**",
plugins: ["external-helpers"]
}),
typescript({ useTsconfigDeclarationDir: true }),
postcss({
modules: false,
extract: true,
sourceMap: true,
minimize: isDev ? false : true,
plugins: [cssUrl({ url: "inline" })]
}),
image()
]
};
When I execute npm run build it generates both compiled bundles for ESM and CommonJS and I can debug directly through the **index.esm.js" file directly but as a compiled version of the code is generated, it's harder to debug it setting the breakpoints and doing the inspection with a version of the code ugly and minified so I was wondering if I can take advantage of source maps files for this matter. For the root project it works like a charm but when it comes to use it within a generated code for the library I was not able to make it.
This is the dist folder generated:
The index.esm.js file has all the code compiled for every single component and at the bottom the follow line:
//# sourceMappingURL=index.esm.js.map
Pointing out that the sourcemapping corresponds to the index.esm.js.map. Within this file there are an array of mapping source files pointing to the many components inside the library, all of them prefixing with ../src/elements/<component_name> so as long as the files are set up on the relative map of index.esm.js.map that is node_modules/library-components/src/elements/... the browser should be able to map correctly these files but this is not the case since every time I launch the app this is what I see on the sources tab:
So neither src folder with the mapping source files or any source files corresponding with the library are available on the sources tab but I can see that the browser is able to detect the source map file for the index.esm.js that is pointing to the existing index.esm.js.map that we described but that's not what I really want to but the source maps mapping to every each component as I stated before. So my question is, it's possible to compile with rollup generating a bundle with the source maps for every component in a legible way in order to debug the components more easily and be the browser able to detect them instead of using the existing index.esm.js?
I was trying to perform several workflows with no success like running the tsc compiler that generates the source maps for every component but the problem is that in the process I need to generate also the bundles for the assets that rollup do using postcss and images for that purpose, otherwise I cannot launch the perform since the main app is not able to map correctly the asset files.
I don't know if at the end I explain myself very well in this topic but I think it's a pretty common scenario for those who works with several project instead of a monorepo and need continuously test and seek for potential errors in an integration env for the components inside of a third-party library.
Anyone that can throw some light on this topic?
Thanks in advance!

how can i optimize main.js size?

I have a Vue.JS application. I'm creating it using webpack. I'm getting large main.js size after application build. (9 Mb).
I have any packages. But I'm getting large main.js size after creating empty application, too. How can I solve the issue?
You can use uglifyjs-webpack-plugin, compression-webpack-plugin to optimize your bundle size.
Here is my full code
Example:
optimization: {
minimizer: [
new UglifyJsPlugin({
cache: true,
parallel: true,
sourceMap: false,
extractComments: 'all',
uglifyOptions: {
compress: true,
output: null
}
}),
new OptimizeCSSAssetsPlugin({
cssProcessorOptions: {
safe: true,
discardComments: {
removeAll: true,
},
},
})
]
},
plugins: [
new CompressionPlugin({
test: /\.(js|css)/
}),
new UglifyJsPlugin()
],
So UglifyJsPlugin help you to minify and uglify js code. CompressionPlugin help you compress your js file into gzip extension.
But I would recommend you use webpack-bundle-analyzer to check the bundle size affect final build
There are several ways of optimising your main file and application performance. All of them works, but some might be not suitable for your case.
Code splitting and lazy load components.
In my case I solved problem by using code splitting and lazy loading components.
It turned main.js into very small one and other files are loaded when becomes visible.
Check this article about code splitting and this one about lazy load in Vue.js. It is very common approach in libraries like React or Vue.js.
Optimising libraries. No much to add, check if is possible to pull function instead of whole library (like in lodash) or use native implementation instead of external one. Unused npms also does not makes your main file lighter. Even if now you don't have much packages optimising them in future will make a difference.
Minifying and uglyfing. Most common and most important practice not only because of performance reasons but also because of security. Those two webpack plugins are the most popular webpack plugins at all:
UglifyJS - for JS
MiniCSS - for CSS
Webpack optimisation. Webpack knows thousands of tricks how to make your bundle smaller, but from time to time it might be your false friend. Remember to define how to resolve some dependencies, for example by pointing to proper node_module or library. It can looks like, where alias for lodash saved me a lot of kBs.
resolve: {
modules: ['app', 'node_modules'],
extensions: [
'.js',
],
mainFields: [
'browser',
],
alias: {
'lodash-es': 'lodash'
}
}
Code responsibly.
The best way to understand your problem is to analyse main.js, you can achieve it by adding Bundle Analyzer Plugin to your webpack config into plugins. It should looks like:
new BundleAnalyzerPlugin({
reportFilename: 'report.html',
analyzerMode: 'static'
})
Look into docs if you want to adjust it somehow. Interactive report is awesome.

Best way to bundle multiple third-party javascript libraries into one with webpack

I was following the webpack tutorial and as much as I understood it, to bundle everything into one module it requires the scripts to use require('./xyz')
Instead until now, I used to write everything in separate scripts and load all the scripts in HTML with multiple script tags. I don't think changing every script is possible now. So is there any way to bundle everything into one module and use it?
PS: something like this : similar SO
Another thing I wanted to ask, as title says, How to bundle third party libraries like angularjs, jquery, bootstrap, ui-router and so on? (with no common connection with each other)? I tried giving an array as entry to the webpack and it produced a large 4MB JS plus it didn't even work. What is the better way to do it?
You can create an explicit vendor chunk with webpack using CommonsChunkPlugin (if you’re using webpack < 4)
entry: {
vendor: [
'angular',
'jquery',
// etc.
],
app: './index.js'
},
plugins: [
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor',
minChunks: Infinity,
})
]
You can also use DllPlugin for this, and there’s an example.

Minify css from webpack's ExtractTextPlugin and style-loader

In this tracer repo: https://github.com/pconerly/libsass-spritesmith-webpack-tracer
And this line:
https://github.com/pconerly/libsass-spritesmith-webpack-tracer/blob/master/webpack.config.js#L82
I'm loading .scss, and extracting them into plaintext. I'd also like to minify them--- how do I do that? style-loader doesn't seem to have an option for it. Should I be using another plugin like css-loader instead?
So this will come automatically through the CSS loader unless you've explicitly disabled it. Since you're asking the question I'm assuming this means you have. The UglifyJsPlugin won't minify the CSS by itself if you're extracting and not minifying.
For my needs I needed to extract the CSS and then provide both a minified and non-minified version. So I ran into the same problem where I could have it minified or non-minified but not both.
I was able to get this to work using the optimize-css-assets plugin for Webpack. It will allow you to minify the CSS you've extracted using ExtractTextPlugin and you can set a RegEx rule similar to the UglifyJsPlugin settings.
By default this plugin uses the css-nano module for it's compression, although you can swap out to your preferred module if you wish.
Here's a basic configuration:
plugins: [
new ExtractTextPlugin('[name].css'),
new webpack.optimize.UglifyJsPlugin({
compress: { warnings: false },
include: /\.min\.js$/
}),
new OptimizeCssAssetsPlugin({
assetNameRegExp: /\.min\.css$/,
cssProcessorOptions: { discardComments: { removeAll: true } }
})
]
I would suggest looking at postcss and postcss-loader. That way once you have it set up you can do lots of cool stuff to you CSS/SCSS without having to spend days fighting webpack first.
Adding an entry for the UglifyJsPlugin works for me.
plugins: [
new ExtractTextPlugin("[name].css"),
new webpack.optimize.UglifyJsPlugin({
compressor: { warnings: false }
})
]

Categories