How to minimize the size of webpack's bundle? - reactjs

I'm writing a web app using react and webpack as my module bundler.
My jsx code is really light so far, the size of the entire folder is 25 kb.
My bundle.js created from webpack is 2.2 mb though. After running the optimization with the -p flag, it reduces the bundle to 700kb, which is still extremely big.
I have looked into the react.min.js file and its size is 130kb.
Is it possible that the webpack produces such big files or am I doing something wrong?
webpack.config.js
var path = require('path');
var webpack = require('webpack');
module.exports = {
entry: './public/components/main.jsx',
output: {
path: __dirname + "/public",
filename: 'bundle.js'
},
module: {
loaders: [{
test: /.jsx?$/,
loader: 'babel-loader',
exclude: /node_modules/,
query: {
presets: ['es2015', 'react']
}
}, {
test: /\.css$/,
loader: "style!css"
}]
}
};
EDIT
package.json:
{
"name": "XChange",
"version": "0.0.0",
"private": true,
"scripts": {
"start": "node ./bin/www"
},
"main": "./bin/www",
"devDependencies": {
"body-parser": "~1.13.2",
"cookie-parser": "~1.3.5",
"debug": "~2.2.0",
"express": "~4.13.1",
"jade": "~1.11.0",
"morgan": "~1.6.1",
"serve-favicon": "~2.3.0",
"react-dom": "~0.14.3",
"react": "~0.14.3",
"webpack": "~1.12.9",
"babel-loader": "~6.2.0",
"babel-core": "~6.2.1",
"babel-preset-react": "~6.1.18",
"babel-preset-es2015": "~6.1.18",
"react-bootstrap": "~0.28.1",
"material-ui": "~0.14.0-rc1",
"history": "~1.13.1",
"react-router": "~1.0.2",
"style-loader": "~0.13.0",
"css-loader": "~0.18.0"
},
"dependencies": {
"express-validator": "~2.18.0",
"mongoose": "~4.2.9",
"kerberos": "~0.0.17",
"bcrypt": "~0.8.5"
}
}

According to your comments you are using material-ui and react-bootstrap. Those dependencies are bundled by webpack along with your react and react-dom packages. Any time you require or import a package it is bundled as part of your bundle file.
And here it comes my guess. You are probably importing the react-bootstrap and material-ui components using the library way:
import { Button } from 'react-bootstrap';
import { FlatButton } from 'material-ui';
This is nice and handy but it does not only bundles Button and FlatButton (and their dependencies) but the whole libraries.
One way to alleviate it is to try to only import or require what is needed, lets say the component way. Using the same example:
import Button from 'react-bootstrap/lib/Button';
import FlatButton from 'material-ui/lib/flat-button';
This will only bundle Button, FlatButton and their respective dependencies. But not the whole library. So I would try to get rid of all your library imports and use the component way instead.
If you are not using lot of components then it should reduce considerably the size of your bundled file.
As further explanation:
When you are using the library way you are importing all these react-bootstrap and all these material-ui components, regardless which ones you are actually using.

01/2017 EDIT - I've since learned a little more about different Webpack Plugins, and wanted to update this. It turns out that UglifyJS has a littany of config options that don't seem to be very mainstream, but can have a dramatic effect on your bundle size. This is my current config w/ some annotations (docs on site are great):
new webpack.optimize.UglifyJsPlugin({
comments: false, // remove comments
compress: {
unused: true,
dead_code: true, // big one--strip code that will never execute
warnings: false, // good for prod apps so users can't peek behind curtain
drop_debugger: true,
conditionals: true,
evaluate: true,
drop_console: true, // strips console statements
sequences: true,
booleans: true,
}
})
I once encountered an obscure problem with uglify-ication of escaped unicode chars, so be mindful if you employ these transformations that edge-case things like that are possible.
You can read more about the specific options webpack supports in the webpack docs w/ some follow-on links to further reading.
(sidenote: I think your package.json is mixed-up... at least a few of those dev-dependencies are dependencies in every package.json I've seen (e.g., the react-starter-kit)
If you're preparing for production, there are a few more steps you should take to get your file size down. Here's a snip of my webpack.config.js:
plugins: [
new webpack.optimize.UglifyJsPlugin(),
new webpack.optimize.DedupePlugin(),
new webpack.DefinePlugin({
'process.env': {
'NODE_ENV': JSON.stringify('production')
}
})
],
1) minifies/uglifies your code
2) replaces duplicate code to minimize file-size
3) tells webpack to omit some things it uses for node environment builds
Finally, if you use a source map (which you probably should), you'll want to add the appropriate line. Sentry wrote a nice blog post about this.
In my build, i use devtool: 'source-map' for production

UPDATED 05/18 : update UglifyJsPlugin setting for better minification
I use below configuration for the minification in production code.
plugins: [
new webpack.DefinePlugin({
'process.env': {
// This has effect on the react lib size
'NODE_ENV': JSON.stringify('production'),
}
}),
new ExtractTextPlugin("bundle.css", {allChunks: false}),
new webpack.optimize.AggressiveMergingPlugin(),
new webpack.optimize.OccurrenceOrderPlugin(),
new webpack.optimize.DedupePlugin(),
new webpack.optimize.UglifyJsPlugin({
mangle: true,
compress: {
warnings: false, // Suppress uglification warnings
pure_getters: true,
unsafe: true,
unsafe_comps: true,
screw_ie8: true,
conditionals: true,
unused: true,
comparisons: true,
sequences: true,
dead_code: true,
evaluate: true,
if_return: true,
join_vars: true
},
output: {
comments: false,
},
exclude: [/\.min\.js$/gi] // skip pre-minified libs
}),
new webpack.IgnorePlugin(/^\.\/locale$/, [/moment$/]),
new CompressionPlugin({
asset: "[path].gz[query]",
algorithm: "gzip",
test: /\.js$|\.css$|\.html$/,
threshold: 10240,
minRatio: 0
})
],

Have you looked at how you're scripts are being sent over the wire... I had some very simple react components that were up around 300kb each, and that was after the webpack optimizations.
After they were gzipped they came down to 38kb. Still sizable - but that's what we get for using tomorrows features today.
If you're using node/express to serve static resources, including your javascript - look at compression (https://github.com/expressjs/compression).
I'd also suggest looking at the node best practices guide for production https://expressjs.com/en/advanced/best-practice-performance.html
If you're not serving files through node, then apache (or other webserver) will have options for compressing text based files.

I find it useful to mention the source-map-explorer utility that helps in knowing what exactly is in your bundle.js file. It can help you identify if there are any unnecessary things in bundle js.
you can install the source-map-explorer from npm and use it like
source-map-explorer yourBundle.js
Besides this, as mentioned by #kimmiju , check if your server is using some compression.
You can also try to asynchronously load routes (lazy loading in webpack) , so that your entire bundlejs file is not sent in one go , instead it is sent in chunks when user navigates to those routes.

Related

unable to use my component library based on Material UI with a nextjs application (both typescript)

On one side I have a component library using react, mui and emotion as peerDep.
package.json
"peerDependencies": {
"#emotion/react": "^11.10.5",
"#emotion/styled": "^11.10.5",
"#mui/icons-material": "^5.10.15",
"#mui/material": "^5.10.15",
"react": "18.2.0",
"react-dom": "18.2.0"
}
rollup config (note the regexp for #emotion and #mui)
external: [
'react',
'react-dom',
/#emotion\/.*/,
/#mui\/.*/,
],
output: [
{
file: 'dist/index.cjs.min.js',
format: 'cjs',
sourcemap: true,
plugins: [terser()],
},
{
file: 'dist/index.esm.min.js',
format: 'esm',
sourcemap: true,
plugins: [terser()],
},
],
plugins: [
resolve({ browser: true }),
...
typescript({ tsconfig: './tsconfig.json' }),
],
},
Lib is published with npm.
On the other side, I have an application (nextjs and storybook, webpack5)
Nothing special on the config on this side.
When I run storybook, it works fine.
But when I run dev, I get this error:
Warning: React.createElement: type is invalid -- expected a string (for built-in components) or a class/function (for composite components) but got: object.
at Bidon (/home/smallet/Dev/sandbox/spike-nextjs/node_modules/#msio/common-frontend/dist/index.cjs.min.js:629:18)
at div
at /home/smallet/Dev/sandbox/spike-nextjs/node_modules/#emotion/react/dist/emotion-element-b63ca7c6.cjs.dev.js:51:25
at Stack (/home/smallet/Dev/sandbox/spike-nextjs/node_modules/#mui/material/node/Stack/Stack.js:111:49)
at Home (webpack-internal:///./pages/index.tsx:32:81)
at Layout (webpack-internal:///./components/Layout/Layout.tsx:14:19)
at InnerThemeProvider (/home/smallet/Dev/sandbox/spike-nextjs/node_modules/#mui/system/ThemeProvider/ThemeProvider.js:19:39)
at ThemeProvider (/home/smallet/Dev/sandbox/spike-nextjs/node_modules/#mui/private-theming/node/ThemeProvider/ThemeProvider.js:39:5)
at ThemeProvider (/home/smallet/Dev/sandbox/spike-nextjs/node_modules/#mui/system/ThemeProvider/ThemeProvider.js:38:5)
at App (webpack-internal:///./pages/_app.tsx:27:16)
at StyleRegistry (/home/smallet/Dev/sandbox/spike-nextjs/node_modules/styled-jsx/dist/index/index.js:449:36)
at PathnameContextProviderAdapter (/home/smallet/Dev/sandbox/spike-nextjs/node_modules/next/dist/shared/lib/router/adapters.js:60:11)
at AppContainer (/home/smallet/Dev/sandbox/spike-nextjs/node_modules/next/dist/server/render.js:289:29)
at AppContainerWithIsomorphicFiberStructure (/home/smallet/Dev/sandbox/spike-nextjs/node_modules/next/dist/server/render.js:325:57)
at div
at Body (/home/smallet/Dev/sandbox/spike-nextjs/node_modules/next/dist/server/render.js:612:21)
I tried to change the tsconfig file but didn't change anything
I tried to put only #mui and not #emotion as a peer dependency, but it didn't work.
First for the bundle size it is better to have those libs as peer dependency,
but moreover, I have a ThemeProvider initialized with a custom theme and I want it to be shared between lib and app.
I spent a lot of time looking for something wrong in the Theme, but in fact, the error was here because it is the first component imported from my lib.
I added a dummy component with just a div => the app works fine.
But as soon as I add an import on #mui lib, like :
import Paper from '#mui/material/Paper';
I get the crash.
It works with storybook.
I also created a simple react app and it works, so it doesn't seem to be on the side on the library.
I suppose it comes from some config on Nextjs side.

TS Config nested alias for absolute path not working

I'm trying to set up path aliases in my tsconfig.json for a React app bundled with Vite. Here is the relevant part of my tsconfig.json:
{
"compilerOptions": {
"baseUrl": ".",
...
"paths": {
"*": ["src/*", "node_modules/*"],
"components/*": ["src/components/*"],
"containers/*": ["src/containers/*"],
"pages/*": ["src/constants/*"],
"store/*": ["src/store/*"],
"types/*": ["src/types/*"],
"NestedFolder/*": [
"src/components/NestedFolder/*"
],
}
},
"include": ["src/**/*", "*"]
}
The only issue is with the NestedFolder. When I import this way, everything works:
import { ComponentName } from "components/NestedFolder/types";
However, the nested alias fails:
import { ComponentName } from "NestedFolder/types";
// error
EslintPluginImportResolveError: typescript with invalid interface loaded as resolver
Occurred while linting .../src/components/NestedFolder/canvas/index.ts:1
Rule: "import/namespace"
// error on hover in VS Code
Unable to resolve path to module 'NestedFolder/types'.eslintimport/no-unresolved
I would like to do nested components because I have several folders that are nested 3-4 levels and it would be nice to have a cleaner view of my imports. Is there a way to do this?
You need to install the vite-tsconfig-paths plugin to set up path aliases using TypeScript and Vite.
If nothing changes and you are using VSCode make sure to restart the TypeScript server by pressing Ctrl+Shift+P or Cmd+Shift+P, typing restart, and then selecting the command: TypeScript: Restart TS server
The accepted answer did not work for me. I found that I had to install the following packages:
npm i eslint-plugin-import eslint-import-resolver-alias eslint-import-resolver-typescript
And then add the following configurations, with the important ingredient being strongly-defined alias paths:
const path = require('path');
module.exports = {
root: true, // important to ensure nested eslint scoping in monorepos
plugins: ['#typescript-eslint', 'import'],
extends: [
'airbnb-typescript-prettier',
'plugin:import/typescript'
],
parser: '#typescript-eslint/parser',
parserOptions: {
project: path.join(__dirname, './tsconfig.json'),
tsconfigRootDir: './src',
},
settings: {
"import/parsers": { // add this definition
"#typescript-eslint/parser": [".ts", ".tsx"],
},
'import/resolver': {
alias: {
map: [
// define each alias here
['components', path.join(__dirname, './src/components')],
],
extensions: ['.ts', '.tsx', '.js', '.jsx', '.json']
},
typescript: {
project: path.join(__dirname, './tsconfig.json'),
},
},
},
}
I think this could be improved on by harmonizing the aliases between the .eslintrc and vite.config so aliases only need to be defined once, using a tactic like the one defined here: https://stackoverflow.com/a/68908814/14198287
if vite-tsconfig-paths is not working for you. Make sure you didn't install v4.0.0. That version has a bug.
v4.0.1 fix it.
Install with the following:
npm install vite-tsconfig-paths#latest
Should install v4.0.1 at least.
I think this could be improved on by harmonizing the aliases between the .eslintrc and vite.config so aliases only need to be defined once, using a tactic like the one defined here: https://stackoverflow.com/a/68908814/14198287

How to resolve "Cannot use import statement outside a module" in jest

I have a React application (not using Create React App) built using TypeScript, Jest, Webpack, and Babel. When trying to run yarn jest, I get the following error:
I have tried removing all packages and re-adding them. It does not resolve this. I have looked at similar questions and documentation and I am still misunderstanding something. I went so far as to follow another guide for setting up this environment from scratch and still received this issue with my code.
Dependencies include...
"dependencies": {
"#babel/plugin-transform-runtime": "^7.6.2",
"#babel/polyfill": "^7.6.0",
"babel-jest": "^24.9.0",
"react": "^16.8.6",
"react-dom": "^16.8.6",
"react-test-renderer": "^16.11.0",
"source-map-loader": "^0.2.4"
},
"devDependencies": {
"#babel/core": "^7.6.0",
"#babel/preset-env": "^7.6.0",
"#babel/preset-react": "^7.0.0",
"#types/enzyme": "^3.9.2",
"#types/enzyme-adapter-react-16": "^1.0.5",
"#types/jest": "^24.0.13",
The component's import lines...
import * as React from "react";
import { BrowserRouter as Router, Route, Switch } from "react-router-dom";
import HomePage from "./components/pages";
import {
Footer,
Header,
Navigation,
} from "./components/shared";
The test file....
import * as React from "react";
import * as renderer from "react-test-renderer";
import App from "../App";
it("Renders the Footer correctly", () => {
const tree = renderer
.create(<App />)
.toJSON();
expect(tree).toMatchSnapshot();
});
I expected to be able to use named imports in my components without my tests blowing up. It appears to fix the issue if I only use default imports through my solution, but I would prefer to not go that route.
Also using Babel, Typescript and Jest. Had the same failure, driving me crazy for hours.
Ended up creating a new babel.config.js file specifically for the tests. Had a large .babelrc that wasn't getting picked up by jest no matter what i did to it. Main app still uses the .babelrc as this overrides babel.config.js files.
Install jest, ts-jest and babel-jest:
npm i jest ts-jest babel-jest
babel.config.js (only used by jest)
module.exports = {presets: ['#babel/preset-env']}
jest.config.js
module.exports = {
preset: 'ts-jest',
transform: {
'^.+\\.(ts|tsx)?$': 'ts-jest',
"^.+\\.(js|jsx)$": "babel-jest",
}
};
package.json
"scripts": {
"test": "jest"
Use Babel to transpile those JS Modules and you'll be able to write your tests with es6.
Install Babel/preset-env
npm i -D #babel/preset-env
Create a babel configuration file with the preset
//babel.config.js
module.exports = {presets: ['#babel/preset-env']}
I solved this by migrating the .babelrc file to babel.config.js! Shocker.
For future references,
I solved the problem by using below jest config, after reading Logan Shoemaker's answer.
module.exports = {
verbose: true,
setupFilesAfterEnv: ["<rootDir>src/setupTests.ts"],
moduleFileExtensions: ["js", "jsx", "ts", "tsx"],
moduleDirectories: ["node_modules", "src"],
moduleNameMapper: {
"\\.(css|less|scss)$": "identity-obj-proxy"
},
transform: {
'^.+\\.(ts|tsx)?$': 'ts-jest',
"^.+\\.(js|jsx)$": "babel-jest",
"\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": "<rootDir>/__mocks__/file.js",
}
};
try this thing if you are using babel 6
Adding #babel/plugin-transform-modules-commonjs in the plugin section of babel.config.js
or
For my case import issue was due to the react file drop by adding that to transformIgnorePatterns
"transformIgnorePatterns": ["/node_modules/(?!react-file-drop)"]
I fixed it by simply appending the pattern after the run statement in package.json runner
{
"scripts": {
...
"test": "react-scripts test --transformIgnorePatterns 'node_modules/(?!my-library-dir)/'"
...
Then, just run npm test
Solution: my named imports were coming from index.js files and I believe ts-jest needed them as index.ts files (I'm using Typescript). If anyone else runs into this error, couldn't hurt to check if you derped your file extensions.
I wasted a lot of time on this, unfortunately, but I learned a lot about webpack configurations and Babel.
Add your test script in package.json with Node experimental feature: --experimental-vm-modules
In this way you won't require babel or other dependencies.
Examples:
"test": "NODE_OPTIONS='--experimental-vm-modules --experimental-specifier-resolution=node' jest"
If you get this error: zsh: command not found: jest, try with node passing jest.js like this:
"test": "NODE_OPTIONS='--experimental-vm-modules --experimental-specifier-resolution=node --trace-warnings' node node_modules/jest/bin/jest.js --detectOpenHandles"
I'm surprised that none of the answers does not give an elegant solution:
jest.config.js
module.exports = {
...,
globals: {
"ts-jest": {
isolatedModules: true,
},
},
};
This compiles each file separately therefore avoiding the no exports issue.
Create .babelrc on the main directory and add this code and install these packages
#babel/core, #babel/preset-env and #babel/preset-react
{
"presets": [
[
"#babel/preset-env",
{
"modules": "commonjs"
}
],
"#babel/preset-react"
]
}
Matching file extensions:
I importing a file named Todo.jsx in the root as ./src/Todo/. Whenever I changed it to Todo.js the problem went away.
Disclaimer: I'm not sure what the requirement is for having your file extension as jsx vs js for your components. It did not effect me at all, but I could imagine it could mess with intellisense or snippets.
For me renaming file to babel.config.js worked.
Here is my config file an NX project using next with Typescript along with Twin-macro
// .babelrc.js >> babel.config.js
module.exports = {
presets: [
[
"#nrwl/react/babel",
{
"runtime": "automatic",
"targets": {
"browsers": [">0.25%", "not dead"]
},
"preset-react": {
runtime: "automatic",
importSource: "#emotion/react",
},
}
],
'#babel/preset-env',
'#emotion/babel-preset-css-prop',
'#babel/preset-typescript'
],
plugins: ['#emotion', 'macros', '#babel/plugin-transform-runtime', 'react-docgen'],
}
Also, please note even updating package.json works,
https://kulshekhar.github.io/ts-jest/docs/getting-started/presets/#basic-usage
// package.json
"jest": {
// Replace `ts-jest` with the preset you want to use
// from the above list
"preset": "ts-jest"
}
I encountered the same problem with Typescript, Jest, and VueJS/VueCli 3. The normal build has no problem. only happens for Jest. I struggled for hours by searching. But no answer actually works. In my case, I have a dependency on my own typescript package which I specific "target": "es6" in the tsconfig.json. That's the root cause. So the solution is simply to change the dependent's (Not the same project) back to es5 tsconfig.json:
{
"compilerOptions": {
"target": "es5",
...
},
...
}
Personnaly I followed #ajwl setup but discovered that jsdom-worker inside setupFiles: section of jest.config.js was triggering that same error. Once removed, my tests were passing.
P.S. my babel.config.js is a bit different, since I have a Vuejs (v2) SPA (bundled with Vitejs):
module.exports = {
plugins: ['#babel/plugin-transform-modules-commonjs'],
presets: [['#babel/preset-env', { targets: { node: 'current' } }]]
}
The problem is likely that jest doesn't support esmodules natively. Which can cause problems if youre typescript target is es6 or greater.
If you are testing the built typescript output, you could simply add a module=commonjs flag while transpiling. That way, your code can run with es6 or greater and still work with Jest.
"scripts": {
"test": tsc --module commonjs && jest {your-output-folder}/
}
What's great about this is that I didn't have to add any additional babel dependencies or special jest runners :)
I solved it by changing my tsconfig.json to a compatible native output
"module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */
It is not ideal in every scenario but you might be okay with this.
All I had to do, was simply updating the package #babel/preset-env in the dev dependencies to the latest version
// package.json
"#babel/preset-env": "^7.18.6"
None of the answers helped me, what did help me was making sure my NODE_ENV was set to test, since babel config is per NODE_ENV using the wrong NODE_ENV by accident that is not configured in babel config will mean you wont be using babel and the typescript files will not be transformed.
It took me couple of hours to figure this one out so i hope it will save someone else the time it took me.
Don't know why and how but how I solved the problem was really interesting.
Just add __mocks__ folder in your src folder and create an empty file inside __mocks__ named axios.js
I discovered that this error might be triggered when you try to load a dependency that is made for the browser and, thus, cannot work with jest (node).
I had a lot of trouble solving this issue for #zip.js/zip.js lib. But I could do it like that:
Here is my jest.config.js. Adapt it to your need. The trick here is the moduleNameMapper that will make all imports to zip.js point to the file __mocks__/#zip.js/zip.js I created in my root folder.
export default {
preset: 'ts-jest',
testEnvironment: 'node',
moduleNameMapper: {
'#zip.js/zip.js': '<rootDir>/__mocks__/#zip.js/zip.js',
},
}
And here is what I have in <rootDir>/__mocks__/#zip.js/zip.js file:
module.exports = {}
Too late for this answer :)
After trying all the possible solutions, this worked for me:
The solution, that works for me:
create a file named jest/mocks/#react-native-firebase/crashlytics.js
export default () => ({ log: jest.fn(), recordError: jest.fn(), });
create a file named jest/jestSetupFile.js
import mockFirebaseCrashlytics from './mocks/#react-native-firebase/crashlytics';
jest.mock('#react-native-firebase/crashlytics', () => mockFirebaseCrashlytics);
in package.json add
"jest": { "setupFiles": ["./jest/jestSetupFile.js"] },
I needed to do a couple things to get this to work for me
Rename my .babelrc to babel.config.js and make a little change:
// .babelrc
{
"presets": [
[
"#babel/preset-env",
{
"corejs": "3.26",
"useBuiltIns": "usage"
}
],
"#babel/preset-react"
],
...
}
// babel.config.js - This still works fine with webpack
module.exports = {
"presets": [
[
"#babel/preset-env",
{
"corejs": "3.26",
"useBuiltIns": "usage"
}
],
"#babel/preset-react"
],
...
}
Add the following to my jest config file:
{
...
"transformIgnorePatterns": [
"node_modules/(?!(react-leaflet-custom-control)/)"
],
...
}
Where react-leaflet-custom-control was the package causing issues for me.
If you're using TypeScript, and you have a tsconfig.json file, try removing "module": "esnext" if you're using it
Running npm ci fixed this problem for me.

Webpack 4: Production mode fails for React Library

I'm trying to build my react library however it fails on mode: production. When I import my library to another application I get the following message:
Uncaught TypeError: Cannot set property props of #<TWithProps> which has only a getter.
Followed by:
The above error occurred in the <_class3> component
The problem is that it does seem to bundle up my library, however when importing the bundled libary, I get the 2 errors above. Additionally This does not happen in development mode.
I tried following many guides, however they all lead to the same result. My first assumption is that it's likely due to my minimizer plugin (I've tried both UglifyPlugin and TerserPlugin) however that is not the case. I've also read on webpack's documentation that it should use the minimizing plugin if given one. However it doesn't seem like it?
This is my webpack
module.exports = {
mode: 'production',
entry: {
index: [
'babel-polyfill',
'./src/index.js',
],
},
output: {
path: srcBuild,
filename: '[name].js',
chunkFilename: '[name].[chunkhash].js',
libraryTarget: 'commonjs',
libraryExport: 'default',
},
optimization: {
noEmitOnErrors: true,
minimizer: [
new TerserPlugin({
cache: true,
parallel: true,
sourceMap: true,
terserOptions: {
mangle: false,
compress: {
reduce_funcs: false,
reduce_vars: false,
keep_classnames: true,
keep_fnames: true,
keep_fargs: true,
pure_getters: true,
},
},
}),
],
}
I'm expecting my library to run just fine as it does in mode: development.
May I ask how are you consuming your library from the other application? You have libraryTarget: 'commonjs' in your config. If you don't know how your clients will consume your library, the suggested option is to set the export to umd by setting libraryTarget: 'umd'.
This should allow you to use ES6 import or just require it, webpack or the other app's bundler will take care of resolving them.
Solved it by adding this plugin
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify('production'),
}),

Webpack+SemanticUI+React: process is not defined

I have found numerous posts about the Webpack error:
Uncaught ReferenceError: process is not defined
most of which suggest adding a plugin to the webpack.config.js:
plugins: [
// ...
new webpack.DefinePlugin({
'process.env': {
NODE_ENV: JSON.stringify(process.env.NODE_ENV || 'development')
}
}),
// ...
]
however this does not seem to do the trick in my case.
To make things easy, I have created a repo with the bare minimum to setup SemanticUI-React with Webpack, which should be straightforward to navigate. My config in webpack.config.js is heavily inspired from this recent tutorial which seems to have a lot of positive comments.
To reproduce the error, just clone the repo on your machine (I use yarn, but this should work with npm too):
git clone https://github.com/sheljohn/minimal-semantic-react
cd minimal-semantic-react/
yarn install
yarn run serve
which opens at localhost:3000, and the error can be seen in the developer console.
As far as I understand, it seems that when React loads, it is looking to determine whether production or development mode is set, using the variable process.env.NODE_ENV, which is undefined in the browser.
This might be related to the target field in the Webpack config (set to web by default); but since React is loaded from CDN, prior to the bundle, I guess it doesn't know about what WebPack is doing, which makes me perplex as to why adding a plugin in the config would change anything...
Hence my question: is it possible to use semantic-ui-react by declaring the big libs (React, ReactDOM, semantic) as externals? Everything works fine if I bundle them, but the bundle ends up around 4MB, which is quite big.
Additional Details
Error as seen in Chrome (OSX High Sierra, v66.0.3359.181, dev console):
react.development.js:14 Uncaught ReferenceError: process is not defined
at react.development.js:14
(anonymous) # react.development.js:14
and code excerpt at line 14:
if (process.env.NODE_ENV !== "production") {
File webpack.config.js
const path = require("path");
const webpack = require("webpack");
const publicFolder = path.resolve(__dirname, "public");
module.exports = {
entry: "./src/index.jsx",
target: "web",
output: {
path: publicFolder,
filename: "bundle.js"
},
devServer: {
contentBase: publicFolder,
port: 3000
},
externals: {
'jquery': 'jQuery',
'lodash': '_',
'react': 'React',
'react-dom': 'ReactDOM',
'semantic-ui-react': 'semantic-ui-react'
},
module: {
rules: [
{
test: /\.(js|jsx)$/,
exclude: /(node_modules|bower_components)/,
use: {
loader: 'babel-loader'
}
}
]
},
plugins: [
new webpack.DefinePlugin({
'process.env': {
NODE_ENV: JSON.stringify(process.env.NODE_ENV || 'development')
}
}),
new webpack.HotModuleReplacementPlugin()
]
};
File .babelrc
{
"presets": ["env", "react"]
}
I think I finally solved this:
Mistake #1: I was using cjs versions of the React libs from cdnjs, when I should have been using umd instead. Although UMD style is ugly, it seems to work fine within browsers, whereas CommonJS uses require for example. See this post for a comparison of AMD / CommonJS / UMD.
Mistake #2: in webpack.config.js, the "name" for the external semantic-ui-react should be semanticUIReact (case sensitive). This is what is defined in the window global when the script is loaded from the CDN (e.g. like jQuery or React).
I updated the repository with these fixes, and you should be able to reproduce that working example on your machine. This repository contains the bare minimum needed to get SemanticUI, React and Webpack working together. This would have saved me a lot of time, so hopefully other people get to benefit from that!
Everything works fine if I bundle them, but the bundle ends up around 4MB, which is quite big.
It's because you bundle them in "development" mode. Try using "production" in your script instead, it will be much smaller.
"build": "webpack --mode production"
If you bundle everything in production, without specifying external, it will be better for a standalone app.

Resources