A clean way to loop HTMLWebpackPlugin with Webpack? - javascript

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}`]
})
})
]
}

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"]
})

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']
}),
]
}

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.

Assign separate entrypoint scripts to separate HtmlWebpackPlugin instances

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
})
]

Error with purify-webpack plugin when using HtmlWebpackPlugin

I am using the purifycss-webpack plugin in order to remove unused css from my webpack build. A little context, our app is split into chunks based on the route using System.import. Also, I generate the built html using HtmlWebpackPlugin using an .ejs template.
Plugin Configuration
{
plugins: [
// clean build dir on every compilation
new CleanWebpackPlugin(cleanPaths, cleanOptions),
// replaces moment/locale/*.js to retrieve only the en locale
new ContextReplacementPlugin(/moment[\/\\]locale$/, /en/),
// Minify and optimize the JavaScript
new webpack.optimize.UglifyJsPlugin({
options: {
quiet: true
},
beautify: false,
mangle: {
screw_ie8: true,
keep_fnames: true
},
compress: {
screw_ie8: true
},
comments: false
}),
new OptimizeJsPlugin({
sourceMap: false
}),
// Minify and optimize the index.html
new HtmlWebpackPlugin({
template: "app/index.ejs",
inject: true
}),
// Extract the CSS into a seperate file
extractCss,
extractVendor,
// create a basic vendor chunk which is not fancy
new CommonsChunkPlugin({
name: "vendor"
}),
// extract the webpack runtime into a seperate chunk for long term caching
new CommonsChunkPlugin({
name: "manifest",
minChunks: Infinity
}),
// extract all common modules from chunks into a seperate chunk
new CommonsChunkPlugin({
name: "main",
async: "common-async",
children: true,
minChunks: 2
}),
// injects webpack stats into index.html after seperarating them from the runtime
// useful for long term caching
/*
// unsolved problem, cannot extract a json file and move it into html file
// without needing three plugins
new ChunkManifestPlugin({
filename: "chunk-manifest.json",
manifestVariable: "webpackManifest",
inlineManifest: true
}),
*/
// analyze bundle size visually to determine any issues
new PurifyCssPlugin({
paths: [path.join("./app/index.ejs")],
moduleExtensions: [".html"],
verbose: true,
purifyOptions: {
whitelist: ["*purify*"]
}
}),
new BundleAnalyzerPlugin({
analyzerMode: process.env.BUNDLE_ANALYZER_MODE,
reportFilename: "perf/report.html",
generateStatsFile: true,
statsFilename: "perf/stats.json"
}),
// add gzip compression as part of the webpack output
new CompressionPlugin({
asset: "[path].gz[query]",
algorithm: "gzip",
test: /\.(js|css|html)$/,
threshold: 0,
minRatio: 0.8
})
]
}
Error
Error: undefined:30:2: missing '}'
at error (/Users/localuser/lendi/lendi-app/node_modules/css/lib/parse/index.js:62:15)
at declarations (/Users/localuser/lendi/lendi-app/node_modules/css/lib/parse/index.js:259:26)
at rule (/Users/localuser/lendi/lendi-app/node_modules/css/lib/parse/index.js:560:21)
at rules (/Users/localuser/lendi/lendi-app/node_modules/css/lib/parse/index.js:117:70)
at stylesheet (/Users/localuser/lendi/lendi-app/node_modules/css/lib/parse/index.js:81:21)
at module.exports (/Users/localuser/lendi/lendi-app/node_modules/css/lib/parse/index.js:564:20)
at rework (/Users/localuser/lendi/lendi-app/node_modules/rework/index.js:27:21)
at CssTreeWalker.beginReading (/Users/localuser/lendi/lendi-app/node_modules/purify-css/lib/purifycss.js:568:24)
at purify (/Users/localuser/lendi/lendi-app/node_modules/purify-css/lib/purifycss.js:1009:10)
at /Users/localuser/lendi/lendi-app/node_modules/purifycss-webpack/dist/index.js:95:99
at Array.forEach (native)
at /Users/localuser/lendi/lendi-app/node_modules/purifycss-webpack/dist/index.js:81:28
at Array.forEach (native)
at Compilation.<anonymous> (/Users/localuser/lendi/lendi-app/node_modules/purifycss-webpack/dist/index.js:65:30)
at next (/Users/localuser/lendi/lendi-app/node_modules/tapable/lib/Tapable.js:140:14)
at ExtractTextPlugin.<anonymous> (/Users/localuser/lendi/lendi-app/node_modules/extract-text-webpack-plugin/index.js:341:4)
at next (/Users/localuser/lendi/lendi-app/node_modules/tapable/lib/Tapable.js:140:14)
at ExtractTextPlugin.<anonymous> (/Users/localuser/lendi/lendi-app/node_modules/extract-text-webpack-plugin/index.js:341:4)
at Compilation.applyPluginsAsyncSeries (/Users/localuser/lendi/lendi-app/node_modules/tapable/lib/Tapable.js:142:13)
at sealPart2 (/Users/localuser/lendi/lendi-app/node_modules/webpack/lib/Compilation.js:631:9)
at next (/Users/localuser/lendi/lendi-app/node_modules/tapable/lib/Tapable.js:138:11)
at ExtractTextPlugin.<anonymous> (/Users/localuser/lendi/lendi-app/node_modules/extract-text-webpack-plugin/index.js:313:5)
at /Users/localuser/lendi/lendi-app/node_modules/async/dist/async.js:421:16
at iteratorCallback (/Users/localuser/lendi/lendi-app/node_modules/async/dist/async.js:998:13)
at /Users/localuser/lendi/lendi-app/node_modules/async/dist/async.js:906:16
at /Users/localuser/lendi/lendi-app/node_modules/extract-text-webpack-plugin/index.js:297:6
at /Users/localuser/lendi/lendi-app/node_modules/async/dist/async.js:421:16
at iteratorCallback (/Users/localuser/lendi/lendi-app/node_modules/async/dist/async.js:998:13)
at /Users/localuser/lendi/lendi-app/node_modules/async/dist/async.js:906:16
at /Users/localuser/lendi/lendi-app/node_modules/extract-text-webpack-plugin/index.js:287:9
error Command failed with exit code 1.
Error looks like related to style parsing verify your style file if you are using .css , If you are using .scss then you can use styleExtensions: ['.scss'] in PurifyCssPlugin.
new PurifyCssPlugin({
paths: [path.join("./app/index.ejs")],
styleExtensions: ['.scss'],
moduleExtensions: [".html"],
verbose: true,
purifyOptions: {
whitelist: ["*purify*"]
}
})

Categories