Assign separate entrypoint scripts to separate HtmlWebpackPlugin instances - javascript

Given an entry/output like
entry: {
app: 'src/client/app.js',
unauthApp.js: 'src/client/unauth.js'
},
output: {
path: 'dist',
filename: 'scripts/[name]-[chunkhash].js'
},
...
plugins: [
new HtmlWebpackPlugin({
filename: 'views/index.html'
}),
new HtmlWebpackPlugin({
filename: 'views/index-inauth.html'
})
]
is it possible using two instances of HtmlWebpackPlugin to put the app script into one template and the unauthApp script into the other. So far all I have been able to do is put both scripts in both. I'm also playing with using htmlWebpackTemplate so perhaps there is an option there I have not seen.
webpack#^4.6.0

Have you tried the chunks field? It Allows you to add only some chunks (e.g only the unit-test chunk)
plugins: [
new HtmlWebpackPlugin({
filename: 'views/index.html',
chunks: ['app'],
inject: true
}),
new HtmlWebpackPlugin({
filename: 'views/index-unauth.html',
chunks: ['unauthApp.js'],
inject: true
})
]

Related

WebPack: problem when compiling JS files in html

I am having a problem while working with Webpack. I'm using a JS file to call an API, but this API should be called only in escudos.html, but when I do the Webpack build, the JS file calls the API int both (index.html, escudos.html). I only want that the ./src/js/teams.js call API when I am in the escudos.html, not in in both (index.html, escudos.html) HTML.
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: {
index: './src/js/index.js',
teams: './src/js/teams.js',
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'js/[name].bundle.js',
},
plugins: [
new HtmlWebpackPlugin({
filename: 'index.html',
template: './src/index.html',
}),
new HtmlWebpackPlugin({
filename: 'escudos.html',
template: './src/escudos.html',
}),
],
devServer: {
static: './dist',
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: { loader: 'babel-loader' },
},
],
},
};
for some reason my webpacked file index.html has the teams.js too
The problem (and the solution) is in HtmlWebpackPlugin. HtmlWebpackPlugin injects all entries in every page by default.
There exists three solutions I can think of:
inject: false: This disables auto injection of <script> tags into the templete. You have to manually write <script>tag(s) with the proper src. (Psss: don't)
chunks: specifiy which entries you want to be included for this template. E.G: (Solves the OP problem, what you will need/use in most cases)
new HtmlWebpackPlugin({
template: "./src/index.html",
chunks: ["index"]
}),
new HtmlWebpackPlugin({
template: "./src/escudos.html",
chunks: ["teams"]
})
exclude: inject all entries except the ones specified in this array. E.G
new HtmlWebpackPlugin({
template: "./src/test.html",
exclude: ["index"]
})

Webpack, optimization chunking gives "Conflict: Multiple chunks emit assets to the same filename" error

Info
I am trying to generate my own webpack config and have some problems getting it working.
Problem
When trying to use optimization to split files into chunks I get the a error like underneath
Error: Conflict: Multiple chunks emit assets to the same filename static/js/bundle.js (chunks main and vendors-node_modules_react-hot-loader_patch_js-node_modules_react_jsx-dev-runtime_js-node_mod-4610d2)
If I remove the optimization section it works but without chunking. I have looked to the create react app webpack.config.js to get something to reference while generating this.
As you can see they have the optimization section working with chunking in both development and production. Why do I get the conflict error when using it?
Code
Minified/simplified version of my config (runtimeChunk disabled, as it also gives the same conflict error)
webpack.config.js
module.exports = () => {
process.env.NODE_ENV = "development";
process.env.BABEL_ENV = "development";
return {
mode: "development",
entry: ["react-hot-loader/patch", "./src"],
output: {
path: undefined,
publicPath: "/",
filename: "static/js/bundle.js",
chunkFilename: "static/js/[name].chunk.js",
},
optimization: {
minimize: false,
splitChunks: {
chunks: "all",
name: false
},
// runtimeChunk: {
// name: (entrypoint) => `runtime-${entrypoint.name}`,
// },
},
resolve: {
modules: [path.join(__dirname, "src"), "node_modules"],
alias: {
"react-dom": "#hot-loader/react-dom",
},
},
module: {
rules: [
{
test: /\.(js|mjs|jsx|ts|tsx)$/,
include: path.resolve(__dirname, "./src"),
exclude: /node_modules/,
use: ["babel-loader"],
},
],
},
plugins: [
new HtmlWebpackPlugin({
inject: true,
template: path.resolve(__dirname, "./public/index.html"),
}),
new webpack.HotModuleReplacementPlugin(),
],
devServer: {
compress: true,
hot: true,
contentBase: "./build",
historyApiFallback: true,
},
devtool: "inline-source-map",
};
};
.babelrc
{"presets": [["react-app", {"runtime": "automatic"}]]}
Got it to work had to change filename: "static/js/bundle.js" to filename: "static/js/[name].js"
output: {
path: undefined,
publicPath: "/",
filename: "static/js/[name].js",
chunkFilename: "static/js/[name].chunk.js",
}
If you are working on an ejected Create React App and you get a similar error
Multiple chunks emit assets to the same filename static/js/bundle.js
(chunks main and runtime-main)
you can just change the filename property in the output configuration from
filename: isEnvProduction
? 'static/js/[name].[contenthash:8].js'
: isEnvDevelopment && 'static/js/bundle.js',
to
filename: isEnvProduction
? 'static/js/[name].[contenthash:8].js'
: isEnvDevelopment && 'static/js/[name].js',
Where the [name] placeholder is giving a different name to each output bundle instead of a fixed one.
In my case it was caused by the runtime-main.js file which I was generating with the runtimeChunk property inside optimization.
runtimeChunk: {
name: entrypoint => `runtime-${entrypoint.name}`,
},

How to properly setup webpack config to include common chunks used in multiple page (and entry) app?

Imagine having below structure of html files:
./home.html
./settings.html
./contact.html
Also having below js files
./nav.js <-- common - used in all html files
./home.js <-- used only in home.html, below follow the same rule
./settings.js
./contact.js
And some modules from node_modules:
"jquery"
"moment"
that are being imported as if when required:
./home.js
import $ from "jquery";
(...)
I have setup webpack to have each entry point as each file name. Now what would be the way to include common js files such as `./nav.js" into each file?
entry: {
home: "./resources/js/sections/home.js",
settings: "./resources/js/sections/settings.js",
(...)
}
(...)
output: {
filename: '[name].js',
}
// Option A
Import raw nav.js like another module in every subpage (home.js, contact.js, settings.js)
import nav from ./nav.js
nav();
// Option B
create another entry for ./nav.js and manually add bundled nav.js to each html alongside other bundled files.
entry: {
(...)
nav: "./resources/js/sections/nav.js"
}
You may use HtmlWebPackPlugin in order to append scripts dynamically to your HTML pages.
First of all install the plugin:
npm install html-loader html-webpack-plugin --save-dev
Then use the config:
const HtmlWebPackPlugin = require("html-webpack-plugin");
module.exports = {
entry: {
nav: './resources/js/sections/nav.js',
home: './resources/js/sections/home.js',
settings: './resources/js/sections/settings.js',
contact: './resources/js/sections/contact.js',
},
output: {
filename: '[name].js',
path: path.resolve(__dirname, 'dist'), // folder where all tranformed files will be placed
},
rules: [
{
test: /\.html$/,
use: [{
loader: "html-loader",
options: { minimize: true }
}]
}
],
plugins: [
// create an instance of HtmlWebPackPlugin for every page of a multipage website
new HtmlWebPackPlugin({
template: "./resources/html/home.html", // take html from this path
filename: "./home.html", // name it 'home.html' and insert to the root of output folder
chunks: ['nav', 'home'] // insert dymamically nav.js and home.js to home.html
}),
new HtmlWebPackPlugin({
template: "./resources/html/settings.html",
filename: "./settings.html",
chunks: ['nav', 'settings']
}),
new HtmlWebPackPlugin({
template: "./resources/html/contact.html",
filename: "./contact.html",
chunks: ['nav', 'contact']
}),
]
}

A clean way to loop HTMLWebpackPlugin with Webpack?

I'm using Webpack 4 with the HTMLWebPackPlugin. I'm currently at the point where I am dealing with close to 30 different pages and more moving forward. Here is a sample of what I have in code...
new HTMLWebpackPlugin({
template: './src/game11/index.html',
mViewPort: 'width=device-width, initial-scale=1.0',
favicon: './src/game11/media/favicon-16x16.png',
title: 'Game 11 Training',
filename: 'game11.htm',
chunks: ['game11']
}),
new HTMLWebpackPlugin({
template: './src/game12/index.html',
mViewPort: 'width=device-width, initial-scale=1.0',
favicon: './src/game12/media/favicon-16x16.png',
title: 'Game 12 Training',
filename: 'game12.htm',
chunks: ['game12']
}),
I have 30 of these so far in my webpack.config.js file. But I would prefer to do something like this instead...
['game11','game12','something-else','etc'].forEach((event) => {
new HtmlWebpackPlugin({
template: './src/${event}/index.html',
mViewPort: 'width=device-width, initial-scale=1.0',
favicon: './src/${event}/media/favicon-16x16.png',
title: '${event} Training',
filename: '${event}.htm',
chunks: ['${event}']
}),
}),
The above code does not work and it only a sketch. But is it possible to do something like that today without adding additional plugins or modifying my outputs? I simply want to add array values which will create a new instance in itself.
Many thanks!
Following the same logic you suggested on your question, you could use map instead of forEach to build the plugins array like so:
webpack.config.js
{
plugins: [
new MiniCSSExtractPlugin(),
...['game11', 'game12', 'something-else', 'etc'].map((event) => {
return new HtmlWebpackPlugin({
template: `./src/${event}/index.html`,
mViewPort: `width=device-width, initial-scale=1.0`,
favicon: `./src/${event}/media/favicon-16x16.png`,
title: `${event} Training`,
filename: `${event}.htm`,
chunks: [`${event}`]
})
})
]
}

How to use Webpack 4 SplitChunksPlugin with HtmlWebpackPlugin for Multiple Page Application?

I'm trying to utilize the SplitChunksPlugin to produce separate bundles per each page/template in a MPA. When I use the HtmlWebpackPlugin, I get an html file for each page with a script tag pointing to the correct bundle. That is great! However, the trouble I'm having is with my vendor files. I want separate html files to point to only the vendor bundles they need. I can't get each separate html file to point to the correct vendor bundles when the SplitChunksPlugin creates multiple vendor bundles. The bundles produced are:
home.bundle.js
product.bundle.js
cart.bundle.js
vendors~cart~home~product.bundle.js
vendors~cart~product.bundle.js
So basically the home template should reference home.bundle.js, vendors~cart~home~product.bundle.js, and not the second vendor bundle. Only the cart and product templates should reference both vendor bundles. I am utilizing the chunks option for the HtmlWebpackPlugin but can't get it to pull the correct vendor bundles unless I explicitly reference the name of it like so:
chunks: ['vendors~cart~home~product.bundle','home']
But this kinda defeats the purpose of dynamically rendering your script tags. I've tried creating a vendor entry point but this lumps all my vendors together.
Is there some simple config I'm missing?
My webpack.config.js:
const path = require('path');
const webpack = require('webpack');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const Visualizer = require('webpack-visualizer-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
mode: 'development',
devtool: 'cheap-module-eval-source-map',
entry: {
home: './src/js/page-types/home.js',
product: './src/js/page-types/product.js',
cart: './src/js/page-types/cart.js'
},
output: {
filename: '[name].bundle.js',
path: path.resolve(__dirname, 'dist/js')
},
optimization: {
splitChunks: {
chunks: 'all'
}
},
plugins: [
new CleanWebpackPlugin(['dist']),
new Visualizer(),
new HtmlWebpackPlugin({
filename: 'home.html',
chunks: ['vendors','home']
}),
new HtmlWebpackPlugin({
filename: 'product.html',
chunks: ['vendors','product']
}),
new HtmlWebpackPlugin({
filename: 'cart.html',
chunks: ['vendors~cart~product','cart']
}),
], ...
My js modules:
/* home.js */
import jQuery from 'jquery';
import 'bootstrap';
cart and product also reference the react library:
/* cart.js */
import jQuery from 'jquery';
import 'bootstrap';
import React from 'react';
import ReactDOM from 'react-dom';
/* product.js */
import jQuery from 'jquery';
import 'bootstrap';
import React from 'react';
import ReactDOM from 'react-dom';
Example html output home.html:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Webpack App</title>
</head>
<body>
<script type="text/javascript" src="home.bundle.js"></script></body>
</html>
Use version4 of html-webpack-plugin (which is in beta now), and only include the entry chunk in the chunks option.
npm i -D html-webpack-plugin#next
and
module.exports = {
new HtmlWebpackPlugin({
filename: 'home.html',
chunks: ['home']
}),
new HtmlWebpackPlugin({
filename: 'product.html',
chunks: ['product']
}),
new HtmlWebpackPlugin({
filename: 'cart.html',
chunks: ['cart']
}),
};
This will include related chunks automatically.
One option is to manually create your vendor chunks and then include whichever of those chunks needed for a page in the chunks option of HtmlWebpackPlugin.
webpack.config.js:
const path = require('path');
const webpack = require('webpack');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const Visualizer = require('webpack-visualizer-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
mode: 'development',
devtool: 'cheap-module-eval-source-map',
entry: {
home: './src/js/page-types/home.js',
product: './src/js/page-types/product.js',
cart: './src/js/page-types/cart.js'
},
output: {
filename: '[name].bundle.js',
path: path.resolve(__dirname, 'dist/js')
},
optimization: {
splitChunks: {
cacheGroups: {
'vendor-bootstrap': {
name: 'vendor-bootstrap',
test: /[\\/]node_modules[\\/](jquery|bootstrap)[\\/]/,
chunks: 'initial',
priority: 2
},
'vendor-react': {
name: 'vendor-react',
test: /[\\/]node_modules[\\/]react.*?[\\/]/,
chunks: 'initial',
priority: 2
},
'vendor-all': {
name: 'vendor-all',
test: /[\\/]node_modules[\\/]/,
chunks: 'initial',
priority: 1
},
}
}
},
plugins: [
new CleanWebpackPlugin(['dist']),
new Visualizer(),
new HtmlWebpackPlugin({
filename: 'home.html',
chunks: ['vendor-bootstrap', 'vendor-all', 'home']
}),
new HtmlWebpackPlugin({
filename: 'product.html',
chunks: ['vendor-bootstrap', 'vendor-react', 'vendor-all', 'product']
}),
new HtmlWebpackPlugin({
filename: 'cart.html',
chunks: ['vendor-bootstrap', 'vendor-react', 'vendor-all', 'cart']
}),
], ...
The vendor-all chunk is to catch any other vendor libraries that are not included in the other chunks.

Categories