Why webpack commonChunk duplicates packages between my bundles? - javascript

I want to create a vendor.bundle.js from my modules.
I configured webpack:
...
entry: {
app: ['./src/index.js'],
vendor: [
'axios',
'lodash',
'recharts',
'moment',
......
'react-select',
'react-tooltip',
'react-toggle',
'buffer',
'jstz',
'pikaday',
'qrcode-js',
'notie',
'tippy.js',
'react-dropzone',
'react-dd-menu',
'html-to-draftjs',
'react-virtualized',
'react-draft-wysiwyg',
'js-search',
'qr.js'
]
},
...
output: {
path: '/Users/ben/pro/qbpanel2.0/qbpanel-2.0/qbpanel/app/assets/javascripts',
publicPath: '/',
filename: 'bundle.js'
},
...
plugins: [
new BundleAnalyzerPlugin({
analyzerPort: 9998
}),
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor',
filename: 'vendor.bundle.js',
path: '/Users/ben/pro/qbpanel2.0/qbpanel-2.0/qbpanel/app/assets/javascripts',
publicPath: '/',
minChunks: Infinity
}),
...
]
}
I expect to see all the packages listed in entry.vendor to end in the vendor.bundle.js, and for most of them it happens, but in my some of them show up in both bundle.js and vendor.bundle.js files.
Why?
bundle.js I was not expecting to find tippy.js or react-virtualized or lodash
vendor.bundle.js, there are the same packages that are in the bundle.js and I expect to be only here.

Which version of webpack are you using ?
To do what you're looking for, you can use the commonsChunkPluggin
The configuration could look like something like this in webpack 2:
plugins: [
new webpack.optimize.CommonsChunkPlugin({
name: ['node-modules'],
minChunks(module) {
const context = module.context;
return context && context.indexOf('node_modules') >= 0;
},
}),
new webpack.optimize.CommonsChunkPlugin({
name: ['vendor'],
chunks: ['node-modules'],
minChunks(module) {
return module.context && /.*\/axios|losash|allTheModulesYouWant.*/.test(module.context);
},
}),
]
This creates a chunk with all the node modules and takes out of it some of them (that you need to name) to put them into vendor

Related

Prevent Webpack from modifying javascript files

I have webpack in my plain HTML, CSS, and Javascript application. I use webpack to convert scss to CSS. I want my javascript to be the same untouched in my dist folder, as I want to edit it later my wordpress projects. Webpack is adding a lot of code, which makes the JS files hard to edit later. Here is my config.js
const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const { HotModuleReplacementPlugin } = require("webpack");
const HtmlWebpackPartialsPlugin = require("html-webpack-partials-plugin");
module.exports = {
mode: "development",
entry: {
index: "./src/js/index.js",
about: "./src/js/about.js",
courses: "./src/js/courses.js",
contactUs: "./src/js/contact-us.js",
},
output: {
filename: "js/[name].bundle.js",
path: path.resolve(__dirname, "dist"),
assetModuleFilename: "images/[name][ext]",
},
devServer: {
static: { directory: path.join(__dirname, "dist") },
port: 9000,
hot: true,
},
plugins: [
new MiniCssExtractPlugin({
filename: "css/[name].css",
}),
new HtmlWebpackPlugin({
title: "Leads",
filename: "index.html",
template: "./src/pages/index.html",
chunks: ["index"],
}),
new HtmlWebpackPlugin({
title: "Leads About",
filename: "about-us.html",
template: "./src/pages/about-us.html",
chunks: ["about"],
}),
new HtmlWebpackPlugin({
title: "Courses",
filename: "courses.html",
template: "./src/pages/courses.html",
chunks: ["courses"],
}),
new HtmlWebpackPlugin({
title: "Contact Us",
filename: "contact-us.html",
template: "./src/pages/contact-us.html",
chunks: ["contactUs"],
}),
new HtmlWebpackPartialsPlugin({
path: path.join(__dirname, "./src/partials/footer.html"),
location: "partialfooter",
template_filename: [
"index.html",
"about-us.html",
"courses.html",
"contact-us.html",
],
}),
new HtmlWebpackPartialsPlugin({
path: path.join(
__dirname,
"./src/partials/components/infrastructure.html"
),
location: "infrastructure",
template_filename: [
"index.html",
"about-us.html",
"courses.html",
"contact-us.html",
],
}),
new HotModuleReplacementPlugin(),
],
module: {
rules: [
{
test: /\.js$/,
exclude: "/node_modules",
use: {
loader: "babel-loader",
options: {
presets: ["#babel/preset-env"],
},
},
},
{
test: /\.s[ac]ss$/i,
use: [
MiniCssExtractPlugin.loader,
// Creates `style` nodes from JS strings
// "style-loader",
// Translates CSS into CommonJS
"css-loader",
// Compiles Sass to CSS
"sass-loader",
],
},
{
test: /\.(png|svg|jpg|jpeg|gif)$/i,
type: "asset/resource",
},
],
},
optimization: {
minimize: false,
},
};
How can I get webpack to convert my sass files, but simply copy my JS files?
Perhaps you could use copy-webpack-plugin https://webpack.js.org/plugins/copy-webpack-plugin/ to copy files from your src static folder to your build destination folder. Roughly like this -
const CopyPlugin = require("copy-webpack-plugin");
module.exports = {
plugins: [
new CopyPlugin({
patterns: [
{ from: "source", to: "dest" },
{ from: "other", to: "public" },
],
}),
],
};
The code that Webpack adds can allow many features for JavaScript like transpiling ES6 JavaScript to ES5, bundling, code-splitting and dynamic imports. But if you don't need them a straight copy might do.

How to exclude link tags (or more specifically .css files extracted by the mini-css-extract-plugin) from being generated by html-webpack-plugin?

Due to how the (legacy) project is currently set up, I cannot benefit from the caching capabilities of webpack in the standard way, but instead have to generate the scripts in empty files (like main.html5 example) and include those in the project's head or body.
That works just fine with the config I currently have but I find myself lacking the capability to reference the main.[hash].js and main.[hash].css extracted by mini-css-extract-plugin in seperate ejs files so that i could include each generated html5 in different locations of my html5 template (link tag in head and script in body).
webpack.common.js :
module.exports = {
entry: {
main: path.resolve(__dirname, "./react/src/Index.js"),
styles: path.resolve(__dirname, "./react/src/Styles.js"),
vendor: path.resolve(__dirname, "./react/src/Vendor.js"),
},
output: {
path: path.resolve(__dirname, "./dist"),
filename: "[name].[contenthash].js",
assetModuleFilename: "images/[hash][ext][query]",
},
plugins: [
new CleanWebpackPlugin(),
new HtmlWebpackPlugin({
template: path.resolve(__dirname, "./react/main.ejs"),
filename: "main.html5",
inject: false,
publicPath: "/dist/",
chunks: ["main"], // would be great to only reference .js here, then create another instance for .css
}),
new MiniCssExtractPlugin({
filename: "[name].[contenthash].css",
}),
],
main.ejs only has :
<%= htmlWebpackPlugin.tags.headTags %>
Generated main.html5 file :
<script defer="" src="/dist/main.534ca9564003f8d93251.js"></script><link href="/dist/main.0864e3dfa0f6edc21e68.css" rel="stylesheet">
Then i include main.html5 into my project's template like so:
<body>
// ---- html code goes here
<?php include_once 'dist/main.html5'; ?>
</body>
The problem is that this loads both tags at the end of the body tag whereas i would like to include the css in the head tags.So Ideally, I would have 2 html5 files generated. One containing script tag and the other the link tag. I have read the documentation for webpack but fail to see any possible solution that would work for my case. If i could exclude tags from the ejs file that could be a solution but i fail to find anything about that in the HtmlWebpackPlugin Documentation.
So, I found out about html-webpack-exclude-assets-plugin which is doing exactly what i want but unfortunately it is not working and has not been updated for about 5 years. Thankfully, there is an alternative with html-webpack-skip-assets-plugin. Here is how I use it :
webpack.common.js :
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const HtmlWebpackSkipAssetsPlugin =
require("html-webpack-skip-assets-plugin").HtmlWebpackSkipAssetsPlugin;
module.exports = {
entry: {
main: path.resolve(__dirname, "./react/src/Index.js"),
},
output: {
path: path.resolve(__dirname, "./dist"),
filename: "[name].[contenthash].js",
assetModuleFilename: "images/[hash][ext][query]",
},
optimization: {
minimizer: [`...`, new CssMinimizerPlugin()],
splitChunks: {
cacheGroups: {
styles: {
name: "styles",
type: "css/mini-extract",
chunks: "all",
enforce: true,
},
},
},
},
plugins: [
new CleanWebpackPlugin(),
new HtmlWebpackPlugin({
template: path.resolve(__dirname, "./react/vendor.ejs"),
filename: "vendor.html5",
inject: false,
publicPath: "/dist/",
chunks: ["vendor"],
excludeAssets: [
/\/dist\/styles.*.css/,
(asset) => asset.attributes && asset.attributes["x-skip"],
],
}),
new HtmlWebpackPlugin({
template: path.resolve(__dirname, "./react/styles.ejs"),
filename: "styles.html5",
inject: false,
publicPath: "/dist/",
chunks: ["main"],
// excludeAssets: [/\.css$/i]
excludeAssets: [
/\/dist\/main.*.js/,
(asset) => asset.attributes && asset.attributes["x-skip"],
],
}),
new MiniCssExtractPlugin({
filename: "[name].[contenthash].css",
}),
new HtmlWebpackSkipAssetsPlugin(),
],
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: ["babel-loader"],
},
{
test: /\.css$/i,
exclude: /node_modules/,
use: [MiniCssExtractPlugin.loader, "css-loader"],
},
{
test: /\.(?:ico|gif|png|jpg|jpeg)$/i,
type: "asset/resource", //* less performance heavy than asset/inline
},
{
test: /\.(woff(2)?|eot|ttf|otf|svg)$/i,
type: "asset/inline", //* only for small assets. if webpack complains about asset size limit, change to asset/ressource type or asset. Had these as inline before (woff(2)?|eot|ttf|otf|svg),
},
],
},
There you go, we can now generate the css link tag in an html file of its own and include it wherever needed in our project's .html5 template.

How do I compile multiple SCSS files in Webpack?

I have separate SCSS files I want to compile into separate CSS files. Here is a part of my Webpack config file:
module.exports = {
entry: ['./src/js/main.js', './src/scss/main.scss', './src/scss/additional.scss'],
output: {
filename: './assets/js/main.bundle.js',
},
plugins: [
new MiniCssExtractPlugin({
filename: './assets/css/main.bundle.css',
}),
],
modules: {...}
}
The example above will only compile main.scss and omit additional.scss. Here is what I tried:
module.exports = {
entry: {
main: ['./src/js/main.js', './src/scss/main.scss'],
additional: './src/scss/additional.scss',
},
output: {
filename: './assets/js/[name].bundle.js',
},
plugins: [
new MiniCssExtractPlugin({
filename: './assets/css/[name].bundle.css',
}),
],
modules: {...}
}
This kinda works, however this will also generate an additional.bundle.js which I don't need (I only need one JavaScript output and two CSS files). Any ideas?
Here is the solution I can up with. You basically need to create separate config objects for scss and everything else:
module.exports = [{
entry: {
main: ['./src/js/main.js'],
},
output: {
filename: './assets/js/[name].bundle.js',
},
modules: {...}
}, {
entry: {
main: ['./src/scss/main.scss'],
additional: ['./src/scss/additional.scss'],
},
output: {
filename: './.leftover/[name].bundle.js',
},
plugins: [
new MiniCssExtractPlugin({
filename: './assets/css/[name].bundle.css',
}),
],
modules: {...}
}]
then remove the .leftover directory on npm run build.
package.json:
"build": "webpack --mode production && del-cli dist/.leftover"
Not the cleanest solution, however that's the only one I can think of.

Webpack Tree-Shaking Dynamic Imports seems not to be working

History:
I recently discovered an odd behaviour with using Webpack and dynamic imports. First I thought it might be the 3rd-party library 'Loadable Components' I used, so I opened a bug issue (https://github.com/gregberge/loadable-components/issues/517) on their end. The author replied telling me that the behaviour is coming from Webpack and the dynamic imports themselves.
I can stand the fact that it does not tree-shake the dynamic import, for me it is more important to understand why that is the case.
Demo repository to demonstrate the behaviour can be found here: https://github.com/dazlious/treeshaking-dynamic-imports
Short description of the problem: From my perspective, an imported named export should not force all the exported code to be bundled within it.
In the demo case we have a component (./lib/index.jsx) that has two sub components called module1 (./lib/module1/module1.jsx) and module2 (./lib/module1/module2.jsx). Module1 exports a constant called FOO_BAR that is then imported by Module2 as a named import.
When looking at the build output, you'll find Module2 containing Module1 in whole and not only the string that is specifically imported.
Is anyone with deep knowledge of Webpack and/or dynamic imports around here? Would be happy to learn more about the behaviour.
I edited the webpack.config to be:
const path = require('path');
const TerserPlugin = require('terser-webpack-plugin');
const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');
const baseDir = path.resolve(__dirname);
const config = {
mode: process.env.NODE_ENV,
stats: 'minimal',
resolve: {
extensions: ['.js', '.jsx'],
symlinks: false,
},
plugins: [
new BundleAnalyzerPlugin({
analyzerMode: 'static',
openAnalyzer: false,
reportFilename: 'analyze.html',
}),
],
target: 'web',
devtool: 'hidden-source-map',
entry: {
bundle: [path.resolve(baseDir, 'lib')],
},
optimization: {
minimize: true,
minimizer: [
new TerserPlugin({
parallel: true,
}),
],
mangleWasmImports: true,
splitChunks: {
cacheGroups: {
default: false,
vendors: false,
vendor: {
name: 'vendor',
chunks: 'all',
test: /node_modules/,
priority: 20
},
common: {
name: 'common',
minChunks: 2,
chunks: 'all',
priority: 10,
reuseExistingChunk: true,
enforce: true
}
}
},
},
output: {
chunkFilename: '[name].[chunkhash].js',
publicPath: '/',
path: path.join(baseDir, 'dist'),
filename: '[name].[hash].js',
},
module: {
rules: [
{
test: /^.*\.jsx?$/,
include: [path.resolve(baseDir, 'lib')],
loader: 'babel-loader?cacheDirectory',
},
{
test: /\.mjs$/,
include: /node_modules/,
type: 'javascript/auto',
},
],
},
};
module.exports = config;
I think this has the result you are looking for?
image of bunde analyzer showing modules in their own bundles
I think it requires the splitChunks option to actually tree-shake the components properly.
I have spend a lot of time trying to figure webpack out, but I'm still guessing here.

Is there a way to expose module globally from Webpack DllPlugin vendor bundle?

Using Webpack 4, I'm creating a bundle and a vendors bundle. Vendor bundle contains jQuery and the code bundle references jquery using 'import * from jquery'. This works perfectly.
However, I now need to use a 3rd party (already minified) javascript file. That file expects jquery to be exposed as '$' on the global window object. My bundle needs to be loaded after the new minified file, also.
So I have:
<script src='./libs/jquery-3.2.1.min.js'></script>
<script src='../vendor.js'></script>
<script src="./libs/newMinifiedFile.js"></script>
<script src="../bundle.js"></script>
as my current workaround. The static jquery file links the $ objects to the global namespace, and then the vendor.js file allows me to keep using 'import' in my bundle.
So, how do I only load jquery once, and use it in both ways? This seems to be a slightly different problem than most I've seen online because of how I'm loading things.
Here's a small example of my configs right now:
const config = (isDebug) => {
const isDevBuild = isDebug;
const extractCSS = new MiniCssExtractPlugin({filename: 'vendor.css'});
const sharedConfig = {
mode: isDevBuild ? 'development' : 'production',
stats: { modules: false },
resolve: {
extensions: [ '.js' ]
},
module: {
rules: [
{ test: /\.(png|woff|woff2|eot|ttf|svg)(\?|$)/, use: 'url-loader?limit=100000' },
]
},
entry: {
vendor: [
'jquery'
],
},
output: {
publicPath: 'dist/',
filename: '[name].js',
library: '[name]_[hash]',
},
plugins: [
new webpack.NormalModuleReplacementPlugin(/\/iconv-loader$/, require.resolve('node-noop')) // Workaround for https://github.com/andris9/encoding/issues/16
]
};
const clientBundleConfig = merge(sharedConfig, {
output: {
path: path.join(__dirname, 'wwwroot', 'dist'),
pathinfo: false
},
module: {
rules: [
{
test: /\.css(\?|$)/, include: path.resolve(__dirname, "client"), exclude: [/webviewer/, /redux/, /helpers/],
use: [
MiniCssExtractPlugin.loader,
{
loader: 'css-loader',
options: {
minimize: isDevBuild,
sourceMap: isDevBuild
}
}
]
}
]
},
plugins: [
extractCSS,
new webpack.DllPlugin({
path: path.join(__dirname, 'wwwroot', 'dist', '[name]-manifest.json'),
name: '[name]_[hash]'
})
],
optimization: {
minimize: !isDevBuild,
removeAvailableModules: false,
removeEmptyChunks: false,
splitChunks: false,
},
});
And in my normal config I use:
new webpack.DllReferencePlugin({
context: __dirname,
manifest: require(path.join(__dirname, 'wwwroot', 'dist', 'vendor-manifest.json'))
})
So, I can find jquery in the global 'vendor_hash' object, but I can't get it to map it to $ no matter what I try (Most plugins seem to see that $ is never used in any of the code I'm supplying for the bundle and thus don't seem to include it). If I add something like this in my 'entry.js' file then it still doesn't work:
window.$ = window.jQuery = require("jquery");

Categories

Resources