I want to minimize my Javascript code for production. However I don't want to minimize the vendors' code because they already have a minimize version.
My current webpack.config.js splits the output code in two chunks.
module.exports = {
entry: {
vendor: ['jquery','angular'],
app: ['./Client/app.start.js']
},
output: {
filename: 'bundle.js',
path: __dirname
},
resolve : {
alias : {
'angular' : 'angular/angular.min.js',
'jquery' : 'jquery/dist/jquery.min.js'
}
},
plugins: [
new webpack.optimize.CommonsChunkPlugin("vendor", "vendor.bundle.js"),
new webpack.optimize.UglifyJsPlugin({minimize: true})
]
}
When I run >> webpack, both chunks ("bundle.js" and "vendor.bundle.js") are minimized. How can I configure Webpack to only minimize "bundle.js"?
Thanks
If, for some reason, you really want to minify only one bundle you could use the test option of the UglifyJsPlugin. Use the bundle name and don't test for individual modules with a regex because when the code is consumed by UglifyJsPlugin it's already bundled.
new webpack.optimize.UglifyJsPlugin({
compress: { warnings: false },
test: /(vendor.bundle.js\.js)+/i
})
See the docs: https://webpack.github.io/docs/list-of-plugins.html#uglifyjsplugin
test, include, exclude (RegExp|Array): configure filtering of processed files (default: test: /.js($|\?)/i)
Usually, you would have different configs (one with the uglify and another without), for production and development, you would minimize only in production, if that's what you want.
You probably already know this, but is good to be thorough. What you may not know, is that webpack does the job better and it is recommended to use untouched code and let webpack do its thing. I don't believe that the uglifyJsPlugin is able to target chunks, maybe there is another plugin that could do it, but i am unaware.
As a side note, you don't have to worry about double minification, it adds a small effect, considering that it is a production environment and that it doesn't change by the minute.
This can be achieved via a hack as you can see in below code in webpack.config.js file:
`
const UglifyJsPlugin = require('uglifyjs-webpack-plugin');
const PRODUCTION = process.env.NODE_ENV === 'production';
const plugins = [
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor', filename: './assets/js/vendor.min.js',
minChunks: Infinity
}),
new webpack.DefinePlugin({
'process.env': {
'NODE_ENV': JSON.stringify(process.env.NODE_ENV)
}
}),
(...etc other plugins)
];
if (PRODUCTION) {
plugins.push(new UglifyJsPlugin({
include: /\.min\.js$/
}));
}
`
Check if you are creating production build.
Then, you can name the chunks you want to create as minified with".min.js" extension.
Rest unglifyjsplugin -> include filter will ensure that only these chuks will be minified.
Here, in this case, it'll only minify the vendor.min.js file.
Related
I have a static Javascript project (no react, vue, etc.) where I am trying to transpile, bundle, and minify my js with webpack. I would like to have bundle.js on my layout page which will include a bunch of global js that runs on all pages and then a page_x.js file that will be on individual pages as needed. The bundle.js file might consist of several other files and should be transpiled to es5 and minified.
With my current setup, the files are running twice. I'm not sure how to fix this. I want the file included globally but also want to be able to call the function as needed. If I delete the import statement from page.js I get the console error, "doSomething" is undefined. If I only include page.js on page.html and not on _layout.html common.js is only logged out on page.html. I want "common" to be logged once on every page and I want doSomething() to be available only on page.js.
Here is an example of it running twice:
common.js
console.log("common");
export function doSomething() {
console.log("do something");
}
page.js
import {doSomething} from "/common.js";
$(button).click(doSomething);
The expected output on page load (before clicking anything) would be:
"common"
Instead I'm seeing
"common"
"common"
My webpack.config.js file is as follows:
const path = require("path");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const RemoveEmptyScriptsPlugin = require("webpack-remove-empty-scripts");
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
const WebpackWatchedGlobEntries = require("webpack-watched-glob-entries-plugin");
const CssnanoPlugin = require("cssnano");
const TerserPlugin = require("terser-webpack-plugin");
const dirName = "wwwroot/dist";
module.exports = (env, argv) => {
return {
mode: argv.mode === "production" ? "production" : "development",
entry: WebpackWatchedGlobEntries.getEntries(
[
path.resolve(__dirname, "src/scripts/**/*.js"),
path.resolve(__dirname, "src/scss/maincss.scss")
]),
output: {
filename: "[name].js",
path: path.resolve(__dirname, dirName)
},
devtool: "source-map",
module: {
rules: [
{
test: /\.s[c|a]ss$/,
use:
[
MiniCssExtractPlugin.loader,
"css-loader?sourceMap",
{
loader: "postcss-loader?sourceMap",
options: {
postcssOptions: {
plugins: [
CssnanoPlugin
],
config: true
},
sourceMap: true
}
},
{ loader: "sass-loader", options: { sourceMap: true } },
]
},
{
test: /\.(svg|gif|png|eot|woff|ttf)$/,
use: [
"url-loader",
],
},
{
test: /\.m?js$/,
exclude: /(node_modules|bower_components)/,
use: {
loader: "babel-loader",
options: {
presets: ["#babel/preset-env"]
}
}
}
]
},
plugins: [
new WebpackWatchedGlobEntries(),
new CleanWebpackPlugin(),
new RemoveEmptyScriptsPlugin(),
new MiniCssExtractPlugin({
filename: "[name].css"
})
],
optimization: {
minimize: true,
minimizer: [
new TerserPlugin({
extractComments: false,
})
]
}
};
};
Any help would be greatly appreciated.
Webpack is about building a dependency graph of your application files and finally producing one single bundle.
With your configuration, you are actually trying to use Webpack as a Multi-entry object configuration as explained in Webpack documents. The culprit here is WebpackWatchedGlobEntries plugin. For each file matched by a glob pattern, it would create a bundle which is not what you want ever. For exmaple, if you have following structure:
- src/scripts
- common.js
- some
- page1.js
- other
- page2.js
This plugin will produce multi-page application. So, you configuration:
entry: WebpackWatchedGlobEntries.getEntries(
[
path.resolve(__dirname, "src/scripts/**/*.js"),
path.resolve(__dirname, "src/scss/maincss.scss")
]),
will internally return an object as:
entry: {
"common": "src/scripts/common.js",
"some/page1": "src/scripts/some/page1.js",
"other/page2": "src/scripts/other/page2.js"
}
It means if you import common.js into page1.js and page2.js, then you are in producing three bundles and all those bundles will possess the common module which would be executed three times.
The solution really depends on how to you want to configure your bundle:
If you need to bundle as a multi-page application, then you must use splitChunk optimization that allows you to create page specific bundle while keeping shared code separate (common.js for example). Keep in mind that you do not really need to manually create a separate bundle for common.js. with split chunks, Webpack should do that automatically for you.
If you need a single bundle, you can literally go ahead and create a single bundle for entire application (most typical workflow with Webpack) and use the same bundle on each page. You can have a common function like run that can figure the code to call using URL or some unique page specific identifier. In modern SPA, that is done using routing module.
What I will suggest is to keep things simple. Do not use WebpackWatchedGlobEntries plugin. That will complicate things if you are not familiar with Webpack. Keep entry simple like this:
entry: {
// Note that you don't need common module here. It would be picked up as part of your page1 and page2 dependency graph
"page1": "src/scripts/some/page1.js",
"page2": "src/scripts/other/page2.js"
}
Then, enable the splitchunk optimization as:
optimization: {
splitChunks: {
chunks: 'all'
}
}
Again, there are multiple options to choose from. You can read more details here about preventing code duplication.
I have a create-react-app nodejs app using:
npx create-react-app client
Added the Cesium library
cd client
npm install --save cesium
ejected the app:
npm run eject
and modified webpack config by following the instructions described in Cesium and Webpack
And I am getting the following error:
./src/index.js
Module not found: Can't resolve 'cesium/Cesium' in '/home/client/src'
the import statement in index.js is:
import Cesium from 'cesium/Cesium';
Any pointers to what I should do?
I commented out the ModuleScopePlugin statement in the plugins section.
As suggested in the cesium page, I added an alias like so:
const cesiumSource = 'node_modules/cesium/Source';
...
'cesium': path.resolve(__dirname, cesiumSource)
but apparently it is trying to import it from src.
I have pasted the whole webpack.config.prod.js file here:
'use strict';
const autoprefixer = require('autoprefixer');
const path = require('path');
const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CaseSensitivePathsPlugin = require('case-sensitive-paths-webpack-plugin');
const InterpolateHtmlPlugin = require('react-dev-utils/InterpolateHtmlPlugin');
const WatchMissingNodeModulesPlugin = require('react-dev-utils/WatchMissingNodeModulesPlugin');
const eslintFormatter = require('react-dev-utils/eslintFormatter');
const ModuleScopePlugin = require('react-dev-utils/ModuleScopePlugin');
const getClientEnvironment = require('./env');
const paths = require('./paths');
const CopywebpackPlugin = require('copy-webpack-plugin');
// Webpack uses `publicPath` to determine where the app is being served from.
// In development, we always serve from the root. This makes config easier.
const publicPath = '/';
// `publicUrl` is just like `publicPath`, but we will provide it to our app
// as %PUBLIC_URL% in `index.html` and `process.env.PUBLIC_URL` in JavaScript.
// Omit trailing slash as %PUBLIC_PATH%/xyz looks better than %PUBLIC_PATH%xyz.
const publicUrl = '';
// Get environment variables to inject into our app.
const env = getClientEnvironment(publicUrl);
// The path to the Cesium source code
const cesiumSource = 'node_modules/cesium/Source';
const cesiumWorkers = '../Build/Cesium/Workers';
// This is the development configuration.
// It is focused on developer experience and fast rebuilds.
// The production configuration is different and lives in a separate file.
module.exports = {
// You may want 'eval' instead if you prefer to see the compiled output in DevTools.
// See the discussion in https://github.com/facebookincubator/create-react-app/issues/343.
devtool: 'cheap-module-source-map',
// These are the "entry points" to our application.
// This means they will be the "root" imports that are included in JS bundle.
// The first two entry points enable "hot" CSS and auto-refreshes for JS.
entry: [
// We ship a few polyfills by default:
require.resolve('./polyfills'),
// Include an alternative client for WebpackDevServer. A client's job is to
// connect to WebpackDevServer by a socket and get notified about changes.
// When you save a file, the client will either apply hot updates (in case
// of CSS changes), or refresh the page (in case of JS changes). When you
// make a syntax error, this client will display a syntax error overlay.
// Note: instead of the default WebpackDevServer client, we use a custom one
// to bring better experience for Create React App users. You can replace
// the line below with these two lines if you prefer the stock client:
// require.resolve('webpack-dev-server/client') + '?/',
// require.resolve('webpack/hot/dev-server'),
require.resolve('react-dev-utils/webpackHotDevClient'),
// Finally, this is your app's code:
paths.appIndexJs,
// We include the app code last so that if there is a runtime error during
// initialization, it doesn't blow up the WebpackDevServer client, and
// changing JS code would still trigger a refresh.
],
output: {
// Add /* filename */ comments to generated require()s in the output.
pathinfo: true,
// This does not produce a real file. It's just the virtual path that is
// served by WebpackDevServer in development. This is the JS bundle
// containing code from all our entry points, and the Webpack runtime.
filename: 'static/js/bundle.js',
// There are also additional JS chunk files if you use code splitting.
chunkFilename: 'static/js/[name].chunk.js',
// This is the URL that app is served from. We use "/" in development.
publicPath: publicPath,
// Point sourcemap entries to original disk location (format as URL on Windows)
devtoolModuleFilenameTemplate: info => path.resolve(info.absoluteResourcePath).replace(/\\/g, '/'),
// Needed to compile multiline strings in Cesium
sourcePrefix: ''
},
amd: {
// Enable webpack-friendly use of require in Cesium
toUrlUndefined: true
},
resolve: {
// This allows you to set a fallback for where Webpack should look for modules.
// We placed these paths second because we want `node_modules` to "win"
// if there are any conflicts. This matches Node resolution mechanism.
// https://github.com/facebookincubator/create-react-app/issues/253
modules: ['node_modules', paths.appNodeModules].concat(
// It is guaranteed to exist because we tweak it in `env.js`
process.env.NODE_PATH.split(path.delimiter).filter(Boolean)
),
// These are the reasonable defaults supported by the Node ecosystem.
// We also include JSX as a common component filename extension to support
// some tools, although we do not recommend using it, see:
// https://github.com/facebookincubator/create-react-app/issues/290
// `web` extension prefixes have been added for better support
// for React Native Web.
extensions: ['.web.js', '.mjs', '.js', '.json', '.web.jsx', '.jsx'],
alias: {
// Support React Native Web
// https://www.smashingmagazine.com/2016/08/a-glimpse-into-the-future-with-react-native-for-web/
'react-native': 'react-native-web',
// Cesium module name
cesium: path.join(__dirname, cesiumSource)
},
plugins: [
// Prevents users from importing files from outside of src/ (or node_modules/).
// This often causes confusion because we only process files within src/ with babel.
// To fix this, we prevent you from importing files out of src/ -- if you'd like to,
// please link the files into your node_modules/ and let module-resolution kick in.
// Make sure your source files are compiled, as they will not be processed in any way.
//new ModuleScopePlugin(paths.appSrc, [paths.appPackageJson]),
],
},
module: {
strictExportPresence: true,
rules: [
// TODO: Disable require.ensure as it's not a standard language feature.
// We are waiting for https://github.com/facebookincubator/create-react-app/issues/2176.
// { parser: { requireEnsure: false } },
// First, run the linter.
// It's important to do this before Babel processes the JS.
{
test: /\.(js|jsx|mjs)$/,
enforce: 'pre',
use: [
{
options: {
formatter: eslintFormatter,
eslintPath: require.resolve('eslint'),
},
loader: require.resolve('eslint-loader'),
},
],
include: paths.appSrc,
},
{
// "oneOf" will traverse all following loaders until one will
// match the requirements. When no loader matches it will fall
// back to the "file" loader at the end of the loader list.
oneOf: [
// "url" loader works like "file" loader except that it embeds assets
// smaller than specified limit in bytes as data URLs to avoid requests.
// A missing `test` is equivalent to a match.
{
test: [/\.bmp$/, /\.gif$/, /\.jpe?g$/, /\.png$/, /\.svg$/, /\.xml$/],
loader: require.resolve('url-loader'),
options: {
limit: 10000,
name: 'static/media/[name].[hash:8].[ext]',
},
},
// Process JS with Babel.
{
test: /\.(js|jsx|mjs)$/,
include: paths.appSrc,
loader: require.resolve('babel-loader'),
options: {
// This is a feature of `babel-loader` for webpack (not Babel itself).
// It enables caching results in ./node_modules/.cache/babel-loader/
// directory for faster rebuilds.
cacheDirectory: true,
},
},
// "postcss" loader applies autoprefixer to our CSS.
// "css" loader resolves paths in CSS and adds assets as dependencies.
// "style" loader turns CSS into JS modules that inject <style> tags.
// In production, we use a plugin to extract that CSS to a file, but
// in development "style" loader enables hot editing of CSS.
{
test: /\.css$/,
use: [
require.resolve('style-loader'),
{
loader: require.resolve('css-loader'),
options: {
importLoaders: 1,
},
},
{
loader: require.resolve('postcss-loader'),
options: {
// Necessary for external CSS imports to work
// https://github.com/facebookincubator/create-react-app/issues/2677
ident: 'postcss',
plugins: () => [
require('postcss-flexbugs-fixes'),
autoprefixer({
browsers: [
'>1%',
'last 4 versions',
'Firefox ESR',
'not ie < 9', // React doesn't support IE8 anyway
],
flexbox: 'no-2009',
}),
],
},
},
],
},
// "file" loader makes sure those assets get served by WebpackDevServer.
// When you `import` an asset, you get its (virtual) filename.
// In production, they would get copied to the `build` folder.
// This loader doesn't use a "test" so it will catch all modules
// that fall through the other loaders.
{
// Exclude `js` files to keep "css" loader working as it injects
// its runtime that would otherwise processed through "file" loader.
// Also exclude `html` and `json` extensions so they get processed
// by webpacks internal loaders.
exclude: [/\.(js|jsx|mjs)$/, /\.html$/, /\.json$/],
loader: require.resolve('file-loader'),
options: {
name: 'static/media/[name].[hash:8].[ext]',
},
},
],
},
// ** STOP ** Are you adding a new loader?
// Make sure to add the new loader(s) before the "file" loader.
],
},
plugins: [
// Copy Cesium Assets, Widgets, and Workers to a static directory
new CopywebpackPlugin([ { from: path.join(cesiumSource, cesiumWorkers), to: 'Workers' } ]),
new CopywebpackPlugin([ { from: path.join(cesiumSource, 'Assets'), to: 'Assets' } ]),
new CopywebpackPlugin([ { from: path.join(cesiumSource, 'Widgets'), to: 'Widgets' } ]),
// Makes some environment variables available in index.html.
// The public URL is available as %PUBLIC_URL% in index.html, e.g.:
// <link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">
// In development, this will be an empty string.
new InterpolateHtmlPlugin(env.raw),
// Generates an `index.html` file with the <script> injected.
new HtmlWebpackPlugin({
inject: true,
template: paths.appHtml,
}),
// Add module names to factory functions so they appear in browser profiler.
new webpack.NamedModulesPlugin(),
// Makes some environment variables available to the JS code, for example:
// if (process.env.NODE_ENV === 'development') { ... }. See `./env.js`.
new webpack.DefinePlugin(env.stringified),
// This is necessary to emit hot updates (currently CSS only):
new webpack.HotModuleReplacementPlugin(),
// Watcher doesn't work well if you mistype casing in a path so we use
// a plugin that prints an error when you attempt to do this.
// See https://github.com/facebookincubator/create-react-app/issues/240
new CaseSensitivePathsPlugin(),
// If you require a missing module and then `npm install` it, you still have
// to restart the development server for Webpack to discover it. This plugin
// makes the discovery automatic so you don't have to restart.
// See https://github.com/facebookincubator/create-react-app/issues/186
new WatchMissingNodeModulesPlugin(paths.appNodeModules),
// Moment.js is an extremely popular library that bundles large locale files
// by default due to how Webpack interprets its code. This is a practical
// solution that requires the user to opt into importing specific locales.
// https://github.com/jmblog/how-to-optimize-momentjs-with-webpack
// You can remove this if you don't use Moment.js:
new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/)
],
// Some libraries import Node modules but don't use them in the browser.
// Tell Webpack to provide empty mocks for them so importing them works.
node: {
dgram: 'empty',
fs: 'empty',
net: 'empty',
tls: 'empty',
child_process: 'empty',
},
// Turn off performance hints during development because we don't do any
// splitting or minification in interest of speed. These warnings become
// cumbersome.
performance: {
hints: false,
},
};
Can you confirm you are using it with ES6?
If no, then first install ES6 because this statement import statement works with ES6.
import Cesium from 'cesium/Cesium';
If you are running it normally just after installing React then use this to import.
var Cesium = require('cesium/Cesium');
Use this link to setup app with ES6
- ECMAScript 6 application using Babel and Webpack
Having some issues with webpack. It builds fine but when I open my site, i get: Getting error: "Uncaught ReferenceError: webpackJsonp is not defined"
I believe my CommonsChunkPlugin is running before my app is bundled?
It may help to know I have my config in src/config/webpack.config.js which is building to dist/js/.
Have read https://medium.com/#MarkEwersDev/note-to-self-if-you-ever-get-this-uncaught-referenceerror-webpackjsonp-is-not-defined-message-and-d354f5c4d335#.9cysuil5p and https://github.com/webpack/webpack/issues/368 but neither seem to help unless I am missing something.
devtool: 'source-map',
entry: {
vendor: [
'react', 'react-dom', 'react-router', 'react-helmet', 'react-redux', 'moment-timezone', 'cookies-js', 'superagent', 'classnames', 'es6-promise'
],
app: [
'./src/client/entry',
'./scss/main.scss',
]
}
output:{
path: __dirname + '../../dist/js',
filename: 'app.js'
}
plugins:[
new webpack.optimize.CommonsChunkPlugin('vendor', 'vendor.js'),
new ExtractTextPlugin('../css/app.css', {
allChunks: true
}),
new webpack.DefinePlugin({
'process.env':{
'NODE_ENV': JSON.stringify('production')
}
}),
new webpack.optimize.AggressiveMergingPlugin(),
new webpack.optimize.DedupePlugin(),
new webpack.optimize.UglifyJsPlugin({
compress: {
warnings: true
},
output: {
comments: false
}
}),
...persistentPlugins
],
The webpackJsonp function is defined by the commons chunk, so you must always put the commons chunk (vendor.js in your case) first when writing <script> tags. You also can't use <script async>.
Another possibility is that your vendor chunk is being overwritten by the entry chunk as you set output.filename to a constant string. Try naming it [name].js instead of app.js.
I'm facing the same problem on my production environment. I don't know why, but when I upload just the bundle + vendors, built with the production webpack config, I get the same error. I just solved this error restarting the node application. But I know that it's not a good approach.
I'm using exactly the same plugins on my webpack production config as you, and I'm quite sure that the reason is one of the webpack optimization plugins.
My suggestion is:
Try to comment one by one of those optimization plugins (AggressiveMergingPlugin, DedupePlugin and UglifyJsPlugin), and you might find out which one is causing the problem.
I can't test it right now because it just happens on my production environment and I can't stop/test it at this time. But, if it works for you, please let me know :)
I'm trying to get webpack to output correct sourcemaps for a React project that's split into app and vendor chunks using CommonsChunkPlugin and minified using UglifyJsPlugin. This is for production environment, so I:
don't want a huge sourcemap for vendor bundle generated.
don't want the webpack:// sources in map file
don't want map files for css
need actual map file output and linked to from the js file so that error monitoring tools can load it
All this seems to be a bit too much for the devtool config option so I'm trying to use SourceMapDevToolPlugin directly with devtool: false.
The relevant parts of webpack config look like this:
entry: production ? {
app: './src/index.jsx',
vendor: Object.keys(packageDotJson.dependencies)
} : './src/index.jsx',
output: {
path: production ? './dist' : './assets',
publicPath: production ? '' : '/',
filename: production ? 'app.[hash].js' : 'app.js'
},
plugins: production ? [
new webpack.optimize.CommonsChunkPlugin(/* chunkName= */"vendor", /* filename= */"vendor.bundle.[hash].js"),
new webpack.optimize.DedupePlugin(),
new webpack.optimize.UglifyJsPlugin({
compress: {
warnings: false
}
}),
new ExtractTextPlugin("app.[hash].css"),
new webpack.SourceMapDevToolPlugin({
test: [/\.js$/, /\.jsx$/],
filename: "app.[hash].js.map",
append: "//# sourceMappingURL=[url]",
moduleFilenameTemplate: "[absolute-resource-path]",
fallbackModuleFilenameTemplate: "[absolute-resource-path]",
columns: false
}),
new HtmlWebpackPlugin({...})
] : [
new HtmlWebpackPlugin({...})
]
Sadly, what I'm getting with this is a map file named correctly after my app js file, but containing:
{"version":3,"file":"vendor.bundle.05d4e19a02044f47a73a.js","sources":["vendor.bundle.05d4e19a02044f47a73a.js","*"]...}
Changing test to test: /^app\.(.*)\.js$/, creates a similar map file that maps app.05d4e19a02044f47a73a.js to itself. I can't seem to get the original js and jsx source files in sources.
I've tried playing with the plugin order but that didn't change anything.
What am I doing wrong?
I also find it unclear whether test/include/exclude should point to original sources or the minified js files. With other loaders and plugins it's kinda obvious but not with SourceMapDevToolPlugin.
Ok, figured out a solution on my own. My SourceMapDevToolPlugin options actually weren't filtering out the vendor bundle, but the filename was set to "app.[hash].js.map", which cause the vendor chunk map to be generated and to overwrite the app chunk map with the same filename.
The correct options are:
new webpack.SourceMapDevToolPlugin({
test: [/\.js$/, /\.jsx$/],
exclude: 'vendor',
filename: "app.[hash].js.map",
append: "//# sourceMappingURL=[url]",
moduleFilenameTemplate: '[resource-path]',
fallbackModuleFilenameTemplate: '[resource-path]',
})
The minified app.[hash].js is included in the sources but this doesn't seem to cause any problems for the browsers.
columns: false is what caused the plugin to only map the minified file to itself for some reason.
I'm trying to do something that I believe should be possible, but I really can't understand how to do it just from the webpack documentation.
I am writing a JavaScript library with several modules that may or not depend on each other. On top of that, jQuery is used by all modules and some of them may need jQuery plugins. This library will then be used on several different websites which may require some or all modules.
Defining the dependencies between my modules was very easy, but defining their third-party dependencies seems to be harder then I expected.
What I would like to achieve: for each app I want to have two bundle files one with the necessary third-party dependencies and other with the necessary modules from my library.
Example:
Let's imagine that my library has the following modules:
a (requires: jquery, jquery.plugin1)
b (requires: jquery, a)
c (requires: jquery, jquery.ui, a, b)
d (requires: jquery, jquery.plugin2, a)
And I have an app (see it as a unique entry file) that requires modules a, b and c. Webpack for this case should generate the following files:
vendor bundle: with jquery, jquery.plugin1 and jquery.ui;
website bundle: with modules a, b and c;
In the end, I would prefer to have jQuery as a global so I don't need to require it on every single file (I could require it only on the main file, for example). And jQuery plugins would just extend the $ global in case they are required (it is not a problem if they are available to other modules that don't need them).
Assuming this is possible, what would be an example of a webpack configuration file for this case? I tried several combinations of loaders, externals, and plugins on my configuration file, but I don't really get what they are doing and which ones should I use. Thank you!
in my webpack.config.js (Version 1,2,3) file, I have
function isExternal(module) {
var context = module.context;
if (typeof context !== 'string') {
return false;
}
return context.indexOf('node_modules') !== -1;
}
in my plugins array
plugins: [
new CommonsChunkPlugin({
name: 'vendors',
minChunks: function(module) {
return isExternal(module);
}
}),
// Other plugins
]
Now I have a file that only adds 3rd party libs to one file as required.
If you want get more granular where you separate your vendors and entry point files:
plugins: [
new CommonsChunkPlugin({
name: 'common',
minChunks: function(module, count) {
return !isExternal(module) && count >= 2; // adjustable
}
}),
new CommonsChunkPlugin({
name: 'vendors',
chunks: ['common'],
// or if you have an key value object for your entries
// chunks: Object.keys(entry).concat('common')
minChunks: function(module) {
return isExternal(module);
}
})
]
Note that the order of the plugins matters a lot.
Also, this is going to change in version 4. When that's official, I update this answer.
Update: indexOf search change for windows users
I am not sure if I fully understand your problem but since I had similar issue recently I will try to help you out.
Vendor bundle.
You should use CommonsChunkPlugin for that. in the configuration you specify the name of the chunk (e.g. vendor), and file name that will be generated (vendor.js).
new webpack.optimize.CommonsChunkPlugin("vendor", "vendor.js", Infinity),
Now important part, you have to now specify what does it mean vendor library and you do that in an entry section. One one more item to entry list with the same name as the name of the newly declared chunk (i.e. 'vendor' in this case). The value of that entry should be the list of all the modules that you want to move to vendor bundle.
in your case it should look something like:
entry: {
app: 'entry.js',
vendor: ['jquery', 'jquery.plugin1']
}
JQuery as global
Had the same problem and solved it with ProvidePlugin. here you are not defining global object but kind of shurtcuts to modules. i.e. you can configure it like that:
new webpack.ProvidePlugin({
$: "jquery"
})
And now you can just use $ anywhere in your code - webpack will automatically convert that to
require('jquery')
I hope it helped. you can also look at my webpack configuration file that is here
I love webpack, but I agree that the documentation is not the nicest one in the world... but hey.. people were saying same thing about Angular documentation in the begining :)
Edit:
To have entrypoint-specific vendor chunks just use CommonsChunkPlugins multiple times:
new webpack.optimize.CommonsChunkPlugin("vendor-page1", "vendor-page1.js", Infinity),
new webpack.optimize.CommonsChunkPlugin("vendor-page2", "vendor-page2.js", Infinity),
and then declare different extenral libraries for different files:
entry: {
page1: ['entry.js'],
page2: ['entry2.js'],
"vendor-page1": [
'lodash'
],
"vendor-page2": [
'jquery'
]
},
If some libraries are overlapping (and for most of them) between entry points then you can extract them to common file using same plugin just with different configuration. See this example.
In case you're interested in bundling automatically your scripts separately from vendors ones:
var webpack = require('webpack'),
pkg = require('./package.json'), //loads npm config file
html = require('html-webpack-plugin');
module.exports = {
context : __dirname + '/app',
entry : {
app : __dirname + '/app/index.js',
vendor : Object.keys(pkg.dependencies) //get npm vendors deps from config
},
output : {
path : __dirname + '/dist',
filename : 'app.min-[hash:6].js'
},
plugins: [
//Finally add this line to bundle the vendor code separately
new webpack.optimize.CommonsChunkPlugin('vendor', 'vendor.min-[hash:6].js'),
new html({template : __dirname + '/app/index.html'})
]
};
You can read more about this feature in official documentation.
Also not sure if I fully understand your case, but here is config snippet to create separate vendor chunks for each of your bundles:
entry: {
bundle1: './build/bundles/bundle1.js',
bundle2: './build/bundles/bundle2.js',
'vendor-bundle1': [
'react',
'react-router'
],
'vendor-bundle2': [
'react',
'react-router',
'flummox',
'immutable'
]
},
plugins: [
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor-bundle1',
chunks: ['bundle1'],
filename: 'vendor-bundle1.js',
minChunks: Infinity
}),
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor-bundle2',
chunks: ['bundle2'],
filename: 'vendor-bundle2-whatever.js',
minChunks: Infinity
}),
]
And link to CommonsChunkPlugin docs: http://webpack.github.io/docs/list-of-plugins.html#commonschunkplugin