I have set up Webpack and want to make it usable for almost anything you can imagine. But when I want to implement jest for testing it gives me an error on Babel.
My webpack.config.js
const path = require("path");
const ReactRefreshWebpackPlugin = require("#pmmmwh/react-refresh-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
const HtmlWebpackPlugin = require("html-webpack-plugin");
let mode = "development";
let target = "web";
const plugins = [
new CleanWebpackPlugin(),
new MiniCssExtractPlugin(),
new HtmlWebpackPlugin({
template: "./src/index.html",
}),
];
if (process.env.NODE_ENV === "production") {
mode = "production";
// Temporary workaround for 'browserslist' bug that is being patched in the near future
target = "browserslist";
}
if (process.env.SERVE) {
// We only want React Hot Reloading in serve mode
plugins.push(new ReactRefreshWebpackPlugin());
}
module.exports = {
// mode defaults to 'production' if not set
mode: mode,
// This is unnecessary in Webpack 5, because it's the default.
// However, react-refresh-webpack-plugin can't find the entry without it.
entry: "./src/index.js",
output: {
// output path is required for `clean-webpack-plugin`
path: path.resolve(__dirname, "dist"),
// this places all images processed in an image folder
assetModuleFilename: "images/[hash][ext][query]",
},
module: {
rules: [
{
test: /\.(s[ac]|c)ss$/i,
use: [
{
loader: MiniCssExtractPlugin.loader,
// This is required for asset imports in CSS, such as url()
options: { publicPath: "" },
},
"css-loader",
"postcss-loader",
// according to the docs, sass-loader should be at the bottom, which
// loads it first to avoid prefixes in your sourcemaps and other issues.
"sass-loader",
],
},
{
test: /\.(png|jpe?g|gif|svg)$/i,
/**
* The `type` setting replaces the need for "url-loader"
* and "file-loader" in Webpack 5.
*
* setting `type` to "asset" will automatically pick between
* outputing images to a file, or inlining them in the bundle as base64
* with a default max inline size of 8kb
*/
type: "asset",
/**
* If you want to inline larger images, you can set
* a custom `maxSize` for inline like so:
*/
// parser: {
// dataUrlCondition: {
// maxSize: 30 * 1024,
// },
// },
},
{
test: /\.jsx?$/,
exclude: /node_modules/,
use: {
// without additional settings, this will reference .babelrc
loader: "babel-loader",
options: {
/**
* From the docs: When set, the given directory will be used
* to cache the results of the loader. Future webpack builds
* will attempt to read from the cache to avoid needing to run
* the potentially expensive Babel recompilation process on each run.
*/
cacheDirectory: true,
},
},
},
],
},
plugins: plugins,
target: target,
devtool: "source-map",
resolve: {
extensions: [".js", ".jsx"],
},
// required if using webpack-dev-server
devServer: {
contentBase: "./dist",
hot: true,
},
};
And the package.json
{
"name": "webpack-starters",
"version": "1.0.0",
"private": true,
"description": "A collection of different Webpack setups for quick referencing or starting from",
"scripts": {
"start": "cross-env SERVE=true webpack serve",
"watch": "webpack --watch",
"build": "cross-env NODE_ENV=production webpack",
"build-dev": "webpack",
"clean": "rm -rf ./dist",
"test": "jest --coverage"
},
"author": "Mike van Eckendonk",
"license": "MIT",
"devDependencies": {
"#babel/core": "^7.16.5",
"#babel/preset-env": "^7.16.5",
"#babel/preset-react": "^7.16.5",
"#pmmmwh/react-refresh-webpack-plugin": "^0.5.4",
"babel": "^6.23.0",
"babel-jest": "^27.4.5",
"babel-loader": "^8.2.3",
"clean-webpack-plugin": "^3.0.0",
"cross-env": "^7.0.3",
"css-loader": "^5.2.7",
"html-webpack-plugin": "^5.5.0",
"jest-webpack": "^0.3.1",
"mini-css-extract-plugin": "^2.1.0",
"postcss": "^8.4.4",
"postcss-loader": "^4.3.0",
"postcss-preset-env": "^6.7.0",
"react-refresh": "^0.9.0",
"sass": "^1.44.0",
"sass-loader": "^10.2.0",
"webpack": "^5.65.0",
"webpack-cli": "^4.9.1",
"webpack-dev-server": "^4.7.1"
},
"dependencies": {
"react": "^17.0.1",
"react-dom": "^17.0.1"
}
}
babel.config.js
// Cannot load "react-refresh/babel" in production
const plugins = [];
if (process.env.NODE_ENV !== "production") {
plugins.push("react-refresh/babel");
}
module.exports = {
presets: [
"#babel/preset-env",
// Runtime automatic with React 17+ allows not importing React
// in files only using JSX (no state or React methods)
["#babel/preset-react", { runtime: "automatic" }],
],
plugins: plugins,
};
Does anyone have an idea of how to solve these errors? The test files don't have an error, because without Webpack they are correct.
Either remove react-refresh/babel from babel config for test environment or just change the if condition and it should work.
if (process.env.NODE_ENV !== "production" && process.env.NODE_ENV !== "test") {...}
Edit: Updated the if to use and instead of or
Related
I'm new to webpack so sorry if this is something obvious. I'm trying to get the package antlr4-webpack-loader to work, generating some source code from a .g4 file. I think I'm most of the way there, as I have a javascript file with require in it, and the .bundle. file seems to contain the output of antlr4, however it also has this:
module.exports = __webpack_require__(!(function webpackMissingModule() { var e = new Error("Cannot find module 'antlr4/index'"); e.code = 'MODULE_NOT_FOUND'; throw e; }()));
contentScript.js starts as below:
import 'jquery';
import 'antlr4'; // This line doesn't cause the error
import '../anaplan/AnaplanFormula.g4'; // This line causes the MODULE_NOT_FOUND error
webpack.config.js as below:
var webpack = require("webpack"),
path = require("path"),
fileSystem = require("fs"),
env = require("./utils/env"),
CleanWebpackPlugin = require("clean-webpack-plugin").CleanWebpackPlugin,
CopyWebpackPlugin = require("copy-webpack-plugin"),
HtmlWebpackPlugin = require("html-webpack-plugin"),
WriteFilePlugin = require("write-file-webpack-plugin");
var options = {
mode: process.env.NODE_ENV || "development",
entry: {
contentScript: path.join(__dirname, "src", "js", "contentScript.js"),
background: path.join(__dirname, "src", "js", "background.js"),
},
output: {
path: path.join(__dirname, "build"),
filename: "[name].bundle.js"
},
module: {
rules: [
{
test: /\.css$/,
loader: "style-loader!css-loader",
exclude: /node_modules/
},
{
test: new RegExp('.(jpg|jpeg|png|gif|eot|otf|svg|ttf|woff|woff2)$'),
loader: "file-loader?name=[name].[ext]",
exclude: /node_modules/
},
{
test: /\.html$/,
loader: "html-loader",
exclude: /node_modules/
},
{
test: /\.g4/,
exclude: /(node_modules)/,
use: {
loader:'antlr4-webpack-loader'
}
}
]
},
resolve: {
modules: [path.resolve(__dirname, 'src'), 'node_modules']
},
node: { module: "empty", net: "empty", fs: "empty" },
plugins: [
// clean the build folder
new CleanWebpackPlugin(),
// expose and write the allowed env vars on the compiled bundle
new webpack.EnvironmentPlugin(["NODE_ENV"]),
new CopyWebpackPlugin([{
from: "src/manifest.json",
transform: function (content, path) {
// generates the manifest file using the package.json informations
return Buffer.from(JSON.stringify({
description: process.env.npm_package_description,
version: process.env.npm_package_version,
...JSON.parse(content.toString())
}))
}
}]),
new WriteFilePlugin(),
new webpack.ProvidePlugin({
$: 'jquery',
jQuery: 'jquery',
'window.jQuery': 'jquery'
})
]
};
if (env.NODE_ENV === "development") {
options.devtool = "cheap-module-source-map";
}
module.exports = options;
From what I can tell the antlr4-webpack-loader plugin spawns a new webpack process for just the .g4 file and generates a bundle.js file from that as it's output, which then gets bundled into the 'parent' file. I can step through the code within antlr4-webpack-loader and that does appear to work (like I say, the output from it appears to be within my contentScript.bundle.js file. I can see it as something about externals: [ 'antlr4/index' ], which I guess is because the files it generates from the .g4 file require it, but the reference should get resolved by the script that required the g4.
For reference, package.json below which doesn't include the antlr4 package in devDependencies, however I get the same error when i include it in both devDependencies and dependencies:
{
"name": "anaplanextension",
"version": "1.0.0",
"description": "",
"main": "background.js",
"directories": {
"lib": "lib"
},
"dependencies": {
"antlr4": "^4.9.2",
"arrive": "^2.4.1",
"jquery": "^3.6.0"
},
"devDependencies": {
"antlr4-webpack-loader": "^0.1.1",
"clean-webpack-plugin": "3.0.0",
"copy-webpack-plugin": "5.0.5",
"css-loader": "3.2.0",
"file-loader": "4.3.0",
"fs-extra": "8.1.0",
"html-loader": "0.5.5",
"html-webpack-plugin": "3.2.0",
"style-loader": "1.0.0",
"webpack": "4.41.2",
"webpack-cli": "3.3.10",
"webpack-dev-server": "3.9.0",
"write-file-webpack-plugin": "4.5.1"
},
"scripts": {
"build": "node utils/build.js",
"start": "node utils/webserver.js"
},
"repository": {
"type": "git",
"url": "git+https://github.com/georgeduckett/AnaplanExtension.git"
},
"author": "",
"license": "ISC",
"bugs": {
"url": "https://github.com/georgeduckett/AnaplanExtension/issues"
},
"homepage": "https://github.com/georgeduckett/AnaplanExtension#readme"
}
I recommend to go a different route here. That webpack loader has not been updated in the last 4 years, uses a pretty old ANTLR4 jar (current version is 4.9.2) and uses the Javascript runtime, which is known for certain problems.
Instead I recommend that you switch to the antlr4ts runtime and use antlr4ts-cli to generate your files from the grammar. Both are still marked as being alpha, but I have used these packages for years already (e.g. in my vscode extension vscode-antlr4).
With that in place you can remove the webpack loader and generate the parser/lexer files as part of your build process.
We have an application (a website) with some React components, css and js compiled with webpack.
Our workflow is to npm run start in the /src/ folder while developing locally, which generates CSS and JS files in /dist/ then run npm run build to clear down refresh all the files in the /dist/ folder before deploying to live. That is the intent, anyway.
The problem is, when we deploy a change to the live environment, it seems the browser still has previous versions of the CSS/JS files cached, or not reading correctly from the new versions. This only happens with the hashed/chunked (React component) files (see ** in file structure below), not the main.js or main.scss file.
We thought webpack produced new 'chunks'/files with each build. Is there a way we can force webpack to do this so the files are read as new when they change, or the filenames are different? I do want the files to be cached by the browser, but I also want new changes to be accounted for.
Example File Structure
--/src/
----/scss/
------main.scss
----/js/
------main.js (imports js components)
------/components/
--------banner.js
--------ReactComponent.jsx (imports ReactComponent.scss)
--------ReactComponent.scss
--/dist/
----/css/
------main.css
------2.css (react component css) (**)
------6.css (react component css) (**)
----/js/
------main.js
------0_39cd0323ec029f4edc2f.js (react component js) (**)
------1_c03b31c54dc165cb590e.js (react component js) (**)
** these are the files that seem to get cached or not read properly when changes are made.
webpack.config.js
const webpack = require("webpack");
const path = require("path");
const autoprefixer = require("autoprefixer");
const TerserJSPlugin = require("terser-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const OptimizeCSSAssetsPlugin = require("optimize-css-assets-webpack-plugin");
module.exports = {
entry: {
main: ["./js/main.js", "./scss/main.scss"],
},
output: {
filename: "js/[name].js",
chunkFilename: "js/[name]_[chunkhash].js",
path: path.resolve(__dirname, "../dist/"),
publicPath: "/app/themes/[package]/dist/",
jsonpFunction: "o3iv79tz90732goag"
},
module: {
rules: [
{
test: /\.(js|jsx)$/,
exclude: /(node_modules)/,
use: {
loader: "babel-loader"
}
},
{
test: /\.(sass|scss|css)$/,
exclude: "/node_modules/",
use: [
MiniCssExtractPlugin.loader,
{
loader: "css-loader",
options: {
importLoaders: 2,
sourceMap: true
}
},
{
loader: "postcss-loader",
options: {
plugins: () => [require("precss"), require("autoprefixer")],
sourceMap: true
}
},
{
loader: "sass-loader",
options: {
sourceMap: true,
includePaths: [path.resolve(__dirname, "../src/scss")]
}
}
]
},
{
test: /\.(png|svg|jpg|gif)$/,
use: ["file-loader"]
},
{
test: /\.(woff|woff2|eot|ttf|otf)$/,
use: ["file-loader"]
}
]
},
optimization: {
minimizer: [
new TerserJSPlugin({
cache: true,
parallel: true,
sourceMap: true
}),
new OptimizeCSSAssetsPlugin({
cssProcessorOptions: {
safe: true,
zindex: false,
discardComments: {
removeAll: true
}
},
canPrint: true
})
]
},
plugins: [
new MiniCssExtractPlugin({
filename: "css/[name].css",
chunkFilename: "css/[id].css"
})
]
};
package.json
{
"name": "packagename",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "(rm -rf ./../dist/*) & webpack --mode production",
"start": "webpack --mode development --watch "
},
"keywords": [],
"author": "Sarah",
"license": "ISC",
"browserslist": [
"last 4 versions"
],
"devDependencies": {
"#babel/core": "^7.9.0",
"#babel/plugin-proposal-object-rest-spread": "^7.9.5",
"#babel/plugin-syntax-dynamic-import": "^7.8.3",
"#babel/plugin-transform-arrow-functions": "^7.2.0",
"#babel/plugin-transform-classes": "^7.9.5",
"#babel/plugin-transform-flow-strip-types": "^7.9.0",
"#babel/plugin-transform-react-jsx": "^7.9.4",
"#babel/preset-env": "^7.9.5",
"#babel/preset-flow": "^7.9.0",
"#babel/preset-react": "^7.0.0",
"autoprefixer": "^7.1.1",
"babel-loader": "^8.1.0",
"babel-plugin-transform-es2015-shorthand-properties": "^6.24.1",
"babel-preset-env": "^1.7.0",
"browser-sync": "^2.26.7",
"browser-sync-webpack-plugin": "^2.2",
"copy-webpack-plugin": "^4.0.1",
"css-loader": "^3.5.3",
"cssnano": "^4.1.10",
"mini-css-extract-plugin": "^0.8.2",
"node-sass": "^4.14.0",
"optimize-css-assets-webpack-plugin": "^5.0.3",
"postcss-loader": "^2.0.5",
"precss": "^4.0.0",
"resolve-url-loader": "^2.0.2",
"sass-loader": "^6.0.5",
"terser-webpack-plugin": "^2.3.6",
"webpack": "^4.43.0",
"webpack-cli": "^3.3.11"
},
"dependencies": {
"axios": "^0.19.2",
"body-scroll-lock": "^2.7.1",
"can-autoplay": "^3.0.0",
"debounce": "^1.0.2",
"file-loader": "^5.1.0",
"lazysizes": "^4.1.8",
"moment": "^2.24.0",
"objectFitPolyfill": "^2.3.0",
"promise-polyfill": "^8.1.3",
"react": "^16.9.0",
"react-content-loader": "^5.0.4",
"react-device-detect": "^1.12.1",
"react-dom": "^16.9.0",
"react-html-parser": "^2.0.2",
"react-intersection-observer": "^8.26.2",
"react-moment": "^0.9.7",
"react-pdf": "^4.1.0",
"scrollmonitor": "^1.2.4",
"socket.io": "^2.3.0"
}
}
In order to bust a cache on a build, you need to change the url of static asset (js / css).
The best way to do so is to generate random string based on content of the file (called hash), the benefit of this approach is that if the final file didn't changed between deploys it will generate the same hash => clients will use the cached file. If it does changed => hash changed => file name change => clients will fetch a new file.
Webpack has a built it method for this.
// webpack.config.js
module.exports = {
entry: {
main: ["./js/main.js", "./scss/main.scss"],
},
output: {
filename: process.env.NODE_ENV === 'production'? "js/[name]-[hash].js": "js/[name].js", // this will attach the hash of the asset to the filename when building for production
chunkFilename: "js/[name]_[chunkhash].js",
path: path.resolve(__dirname, "../dist/"),
publicPath: "/app/themes/[package]/dist/",
jsonpFunction: "o3iv79tz90732goag"
},
...
}
Edit:
In order to update your HTML file with the new filename (which now will contain hash) you can use HTMLWebpackPlugin which was created specifically for this purpose.
It supports custom template if you need to provide your own html, or creating one. Review the docs.
Just a small update over brilliant #felixmosh solution.
For Webpack 5.x I'm using this:
//webpack.prod.js
const webpackConfig = {
module: {
...
},
output: {
filename: '[name]-[contenthash].js',
chunkFilename: '[name]_[contenthash].js',
},
[contenthash] looks better then [hash] - contenthash only changes when the content of the asset actually changes. I wonder why this is not the default setting for Webpack ?
I love you guys #felixmosh #Sarah for figuring this out !
it's almost 2am and I'm just going insane seeking for a mistake.
"Error: webpack.optimize.UglifyJsPlugin has been removed, please use config.optimization.minimize instead." console told me for 100 time...
I've tried to modify my webpack.config.js like this:
optimization: {
minimize: false
}
and this
optimization: {
minimizer: [
// we specify a custom UglifyJsPlugin here to get source maps in production
new UglifyJsPlugin({
cache: true,
parallel: true,
uglifyOptions: {
compress: false,
ecma: 6,
mangle: true
},
sourceMap: true
})
]
},
and always the same problem...
My files are ok because it's bundled as well, but when I try to open it by
"start": "webpack-dev-server --mode development --open --hot"
or
"start": "opener http://localhost:3000 & httpster -p 3000 -d ./dist"
well, it doesn't matter, I had read many articles about this, it's some kind of problem with webpack3 -> webpack4 version, but I've copied some code for configuration and just can't figure out by myself how to fix it (maybe it's because I'm 12+ hours with laptop one by one and tired as hell, but I'm going to sleep and just hope that when I woke up someone, great person, as well will help me to solve this.
If you some kind of a person that wants to give me an article instead of an answer - it's great too! I'm full about learning new stuff.
But, if you help with an answer and article - it'll give you +100 to your luck :)
my webpack.config.js and package.json below:
(I left this const UglifyJsPlugin = require('uglifyjs-webpack-plugin'); just to show you that I tried to do some optimization with this as well)
/webpack.config.js
var webpack = require("webpack")
var path = require("path")
const UglifyJsPlugin = require('uglifyjs-webpack-plugin');
process.noDeprecation = true
module.exports = {
entry: "./src/index.js",
output: {
path:path.join(__dirname, 'dist', 'assets'),
filename: "bundle.js",
sourceMapFilename: 'bundle.map'
},
devtool: '#source-map',
module: {
rules: [
{
test: /\.js$/,
exclude: /(node_modules)/,
loader: 'babel-loader',
query: {
presets: ['env', 'stage-0', 'react']
}
},
{
test: /\.css$/,
use: ['style-loader','css-loader', {
loader: 'postcss-loader',
options: {
plugins: () => [require('autoprefixer')]
}}]
},
{
test: /\.scss/,
use: ['style-loader','css-loader', {
loader: 'postcss-loader',
options: {
plugins: () => [require('autoprefixer')]
}}, 'sass-loader']
}
]
},
plugins: [
new webpack.DefinePlugin({
"process.env": {
NODE_ENV: JSON.stringify("production")
}
}),
new webpack.optimize.UglifyJsPlugin({
sourceMap: true,
warnings: false,
mangle: false
})
]
}
/package.json
{
"name": "try",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"start": "webpack-dev-server --mode development --open --hot",
"build": "webpack --mode production"
},
"keywords": [
"React",
"state",
"setState",
"explicitly",
"passing",
"props"
],
"author": "andrii",
"license": "ISC",
"devDependencies": {
"babel-core": "^6.26.3",
"babel-loader": "^7.1.5",
"babel-preset-env": "^1.7.0",
"babel-preset-react": "^6.24.1",
"css-loader": "^1.0.0",
"html-webpack-plugin": "^3.2.0",
"postcss-loader": "2.0.6",
"style-loader": "^0.23.0",
"uuid": "^3.3.2",
"sass-loader": "6.0.6",
"webpack": "^4.19.0",
"webpack-cli": "^3.1.0"
},
"dependencies": {
"babel-plugin-syntax-object-rest-spread": "^6.13.0",
"babel-preset-stage-2": "^6.24.1",
"httpster": "1.0.3",
"isomorphic-fetch": "^2.2.1",
"prop-types": "^15.6.2",
"react": "^16.5.1",
"react-dom": "^16.5.1",
"react-icons": "^3.1.0",
"react-redux": "5.0.6",
"react-router-dom": "^4.3.1",
"uglifyjs-webpack-plugin": "^2.0.1",
"webpack-dev-server": "^3.1.8"
}
}
Also, to avoid any angry mood I listen to this: http://prntscr.com/l31bam on replay over 2+ hours if you like classic and piano as well - this composition is brilliant.
Thank you for your time and have a nice day!
I use webpack4 on production and have to use UglifyJsPlugin as well.
First of all I would ensure that you have proper version of webpack and UglifyJsPlugin in your package.json
I currently have
"webpack": "4.20.2", and "uglifyjs-webpack-plugin": "2.0.1",
To ensure that they are properly installed I would advise to double check that proper versions are installed by running:
rm -rf node_modules && npm install OR rm -rf node_modules && yarn install whatever works for you.
Next I would check the config. My webpack.production.js which works is the following
// ...
const UglifyJsPlugin = require('uglifyjs-webpack-plugin');
// ...
mode: 'production',
optimization: {
minimizer: [
new UglifyJsPlugin({
cache: true,
parallel: true,
sourceMap: false, // set to true if you want JS source maps
}),
],
},
module: {
//...
},
I'm having problems with Webpack 4 on a Linux machine. The build works fine in dev mode, but fail in production. It also seems to be working on a windows machine. I did try do downgrade webpack to an older version and nothing.
Nodejs:
v10.2.1
*TypeError: Cannot read property 'length' of undefined* at node_modules/uglifyjs-webpack-plugin/dist/uglify/index.js:59
this.workers = workers === true ? _os2.default.cpus().length - 1 : Math.min(Number(workers) || 0, _os2.default.cpus().length - 1);
packge.json
{
"name": "webpack-demo",
"version": "1.0.0",
"license": "MIT",
"scripts": {
"build": "webpack -p"
},
"devDependencies": {},
"dependencies": {
"#types/node": "^10.5.1",
"css-loader": "^0.28.11",
"global": "^4.3.2",
"node-sass": "^4.9.1",
"npm": "^6.1.0",
"sass-loader": "^7.0.3",
"style-loader": "^0.21.0",
"ts-loader": "^4.4.2",
"typescript": "^2.9.2",
"uglifyjs-webpack-plugin": "1.0.0-beta.2",
"webpack": "^4.15.1",
"webpack-cli": "^3.0.8"
}
}
webpack.config.js
const path = require('path');
const UglifyJsPlugin = require('uglifyjs-webpack-plugin');
var webpack = require('webpack');
module.exports = {
entry: './src/index.ts',
devtool: 'source-map',
mode: 'production',
module: {
rules: [{
test: /\.tsx?$/,
use: 'ts-loader',
exclude: /node_modules/
},
{
test: /\.scss$/,
use: ['style-loader', 'css-loader', 'sass-loader'],
exclude: /node_modules/
}
],
},
resolve: {
extensions: ['.tsx', '.ts', '.js','.css','.scss']
},
plugins: [
new UglifyJsPlugin()
],
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'main.js'
}
}
Setting mode to production in Webpack v4 should do enough optimisations, so there's no need to specifically require the Uglify plugin. Try remove uglifyjs-webpack-plugin and there's also no need for passing the -p flag for the build script.
If you want to customise the Uglify plugin, you can also do so in Webpack's optimization config, see https://webpack.js.org/configuration/optimization/
const UglifyJsPlugin = require('uglifyjs-webpack-plugin');
module.exports = {
//...
optimization: {
minimizer: [
new UglifyJsPlugin({ /* your config */ })
]
}
};
Finally, I have a basic webpack v4 starter boilerplate with all the latest ecosystem on Github, take a look and see if it will help you or not
I'm pretty new to React and javascript dev. I'm used to java build tools, so now using NPM i've got a new landscape of build tools to learn. I am just getting going into my project and I noticed that my minified, uglified bundle is still ~275kb and I'm wondering how this could scale to a large size app. My raw source code itself is only 34kb, but of course I have to pull in all those frameworks and whatnot.
So - how can I keep the size of my app small as my app grows? It's a bit hard for me to follow stuff I read online because many folks seem to be using Grunt, but i'm just using npm start and npm run build on the package below.
Should I be managing my requires() in different ways to prevent duplicate packaging? I'm not sure where to start...
Here's my package.json:
{
"name": "someapp",
"version": "0.0.1",
"description": "foo",
"repository": "",
"main": "js/app.js",
"dependencies": {
"classnames": "^2.1.3",
"flux": "^2.0.1",
"jquery": "^2.2.0",
"keymirror": "~0.1.0",
"object-assign": "^1.0.0",
"react": "^0.12.0"
},
"devDependencies": {
"browserify": "^6.2.0",
"envify": "^3.0.0",
"jest-cli": "^0.4.3",
"reactify": "^0.15.2",
"uglify-js": "~2.4.15",
"watchify": "^2.1.1"
},
"scripts": {
"start": "watchify -o js/bundle.js -v -d js/app.js",
"build": "browserify . -t [envify --NODE_ENV production] | uglifyjs -cm > js/bundle.min.js",
"test": "jest"
},
"author": "Some Guy",
"browserify": {
"transform": [
"reactify",
"envify"
]
},
"jest": {
"rootDir": "./js"
}
}
I was able to achieve pretty good results with Webpack. I wrote about this in Optimizing Webpack Prod Build for React + ES6 Apps
Here's my Webpack config:
var webpack = require('webpack');
var path = require('path');
var nodeEnv = process.env.NODE_ENV || 'development';
var isProd = nodeEnv === 'production';
module.exports = {
devtool: isProd ? 'cheap-module-source-map' : 'eval',
context: path.join(__dirname, './client'),
entry: {
jsx: './index.js',
html: './index.html',
vendor: ['react']
},
output: {
path: path.join(__dirname, './static'),
filename: 'bundle.js',
},
module: {
loaders: [
{
test: /\.html$/,
loader: 'file?name=[name].[ext]'
},
{
test: /\.css$/,
loaders: [
'style-loader',
'css-loader'
]
},
{
test: /\.(js|jsx)$/,
exclude: /node_modules/,
loaders: [
'react-hot',
'babel-loader'
]
},
],
},
resolve: {
extensions: ['', '.js', '.jsx'],
root: [
path.resolve('./client')
]
},
plugins: [
new webpack.optimize.CommonsChunkPlugin('vendor', 'vendor.bundle.js'),
new webpack.optimize.UglifyJsPlugin({
compress: {
warnings: false
},
sourceMap: false
}),
new webpack.DefinePlugin({
'process.env': { NODE_ENV: JSON.stringify(nodeEnv) }
})
],
devServer: {
contentBase: './client',
hot: true
}
};
Two key points to consider:
devtool: isProd ? 'cheap-module-source-map' : 'eval',
This one will output minimal sourcemaps, and will use external files for that, which is good for your final bundle size.
plugins: [
new webpack.optimize.CommonsChunkPlugin('vendor', 'vendor.bundle.js'),
new webpack.optimize.UglifyJsPlugin({
compress: {
warnings: false
},
sourceMap: false
}),
new webpack.DefinePlugin({
'process.env': { NODE_ENV: JSON.stringify(nodeEnv) }
})
],
Uglify - well you probably know what it does. Coupled with the process.env setting will weed out quite a bit of dev code from React lib.
CommonsChunkPlugin will allow you to bundle libraries (or other chunks per your liking) to separate build files. This is particularly awesome as it allows you to set up different caching patterns for vendor libraries. E.g. you can cache those more aggressively than your business logic files.
Oh, and if you care to see my package.json that matches this webpack config:
"scripts": {
"start": "webpack-dev-server --history-api-fallback --hot --inline --progress --colors --port 3000",
"build": "NODE_ENV=production webpack --progress --colors"
},
"devDependencies": {
"babel-core": "^6.3.26",
"babel-loader": "^6.2.0",
"babel-plugin-transform-runtime": "^6.3.13",
"babel-preset-es2015": "^6.3.13",
"babel-preset-react": "^6.3.13",
"file-loader": "^0.8.4",
"webpack": "^1.12.2",
"webpack-dev-server": "^1.12.0",
"webpack-hot-middleware": "^2.2.0"
}
Edit: Tree shaking is a shiny new version expected in Webpack 2 (currently in beta). Coupled with the config above, it will be a killer feature that will minify your final bundle significantly.
Edit 2: Webpack 2 I modified an existing sample app to use Webpack 2 config. It resulted in additional 28% savings. See the project here:Webpack 2 sample config project