html-webpack-plugin adding type attribute to script tag - html-webpack-plugin

Version 2.30.1
new HtmlWebpackPlugin({
title: 'Project',
minify: {
collapseWhitespace: true,
preserveLineBreaks: false
},
hash: true,
template: './src/index.html'
})
Results in:
<script type="text/javascript" src="bundle.js?d944356bf9245ce4bab5">
but should be:
<script src="bundle.js?d944356bf9245ce4bab5">
Either the configuration is wrong or missing something or this is a bug?

minify: { /* your props */, removeScriptTypeAttributes: true }
html-webpack-plugin 3.1.0
html-minifier 3.2.3

Related

How to configure Vite to run eslint after building?

I'm trying to run eslint AFTER vite bundles my code
import eslint from 'vite-plugin-eslint'
const outDir = 'out'
export default defineConfig({
plugins: [
{
...eslint({
include: outDir + '/**/*',
overrideConfigFile: '.eslintrc.js',
fix: true,
failOnError: true,
failOnWarning: true
}),
enforce: 'post',
apply: 'build',
}
],
build: {
emptyOutDir: false,
outDir,
sourcemap: false,
watch: {},
minify: false,
lib: {
entry: {
server_scripts : "./src/server/index.ts",
startup_scripts : "./src/startup/index.ts",
client_scripts : "./src/client/index.ts",
},
fileName: '[name]/bundle',
formats: ["cjs"],
},
},
})
here's what i have so far, i thought doing enforce:'post' should work but it doesn't, eslintrc already has the rules and doing eslint --fix manually fixes it. and i'm sure it's using the right eslint because it throws the error when i do lintOnStart option

How do I use ejs templates/partials with webpack?

I'm trying to create a full stack app using Express and ejs, and using webpack to bundle the client side assets. When I set it up without ejs it all works perfectly. The problem has come when I've tried to change the index.html to an index.ejs that uses partials for header, footer etc. Ultimately this will be a multipage app which is why I'm using the ejs templates.
I've tried using ejs-loader (https://www.npmjs.com/package/ejs-loader), ejs-compiled-loader (https://github.com/bazilio91/ejs-compiled-loader) and ejs-html-loader (https://www.npmjs.com/package/ejs-html-loader) without any joy.
Any help would be appreciated. I'm sure it's really straightforward but for the life of me I can't work out how to link up ejs to webpack and get the templates to work.
I've included the code that works with template.html below. I need to know how to change that to use index.ejs?
My file structure looks like this:
--dist (output from webpack)
--src (containing index.ejs)
----partials (containing partials for index.ejs)
app.js
webpack.common.js
webpack.dev.js
webpack.prod.js
package.json
etc.
Index.ejs
<!DOCTYPE html>
<html lang="en">
<head>
<% include partials/head %>
</head>
<body>
<main>
<% include partials/main %>
</main>
<% include partials/footer %>
</body>
</html>
And my config files look as follows (I've only included the two that merge to make the build congfig):
webpack.common.js
const path = require("path")
module.exports = {
entry: {
main: "./src/index.js",
vendor: "./src/vendor.js"
},
module: {
rules: [
{
test: /\.html$/,
use: ["html-loader"]
},
{
test: /\.(svg|png|jpg|gif)$/,
use: {
loader: "file-loader",
options: {
name: "[name].[hash].[ext]",
outputPath: "imgs"
}
}
}
]
},
}
webpack.prod.js
const path = require("path")
const common = require("./webpack.common")
const merge = require("webpack-merge")
const {
CleanWebpackPlugin
} = require('clean-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin');
const TerserPlugin = require("terser-webpack-plugin");
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = merge(common, {
mode: "production",
output: {
filename: "[name].[contentHash].js",
path: path.resolve(__dirname, "dist")
},
optimization: {
minimizer: [
new OptimizeCssAssetsPlugin(),
new TerserPlugin(),
new HtmlWebpackPlugin({
template: "./src/template.html",
minify: {
removeAttributeQuotes: true,
collapseWhitespace: true,
removeComments: true,
}
})]
},
plugins: [
new MiniCssExtractPlugin({
filename: "[name].[contentHash].css",
}),
new CleanWebpackPlugin(),
],
module: {
rules: [{
test: /\.scss$/,
use: [
MiniCssExtractPlugin.loader,
"css-loader",
"sass-loader"
]
}]
}
});
This is my first post, if I've not included anything crucial please let me know!
Thanks!

Webpack 4 - create vendor chunk

In a webpack 3 configuration I would use the code below to create separate vendor.js chunk:
entry: {
client: ['./client.js'],
vendor: ['babel-polyfill', 'react', 'react-dom', 'redux'],
},
output: {
filename: '[name].[chunkhash].bundle.js',
path: '../dist',
chunkFilename: '[name].[chunkhash].bundle.js',
publicPath: '/',
},
plugins: [
new webpack.HashedModuleIdsPlugin(),
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor',
}),
new webpack.optimize.CommonsChunkPlugin({
name: 'runtime',
}),
],
With all the changes I'm not sure how to do it with Webpack 4. I know that CommonChunksPlugin was removed, so there is a different way to achieve that. I've also read this tutorial but I'm still not sure about extracting runtime chunk and properly defining output property.
EDIT:
Unfortunately, I was experiencing issues with the most popular answer here. Check out my answer.
In order to reduce the vendor JS bundle size. We can split the node module packages into different bundle files. I referred this blog for splitting the bulky vendor file generated by Webpack. Gist of that link which I used initially:
optimization: {
runtimeChunk: 'single',
splitChunks: {
chunks: 'all',
maxInitialRequests: Infinity,
minSize: 0,
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name(module) {
// get the name. E.g. node_modules/packageName/not/this/part.js
// or node_modules/packageName
const packageName = module.context.match(/[\\/]node_modules[\\/](.*?)([\\/]|$)/)[1];
// npm package names are URL-safe, but some servers don't like # symbols
return `npm.${packageName.replace('#', '')}`;
},
},
},
},
}
If one wants to group multiple packages and chunk then into different bundles then refer following gist.
optimization: {
runtimeChunk: 'single',
splitChunks: {
chunks: 'all',
maxInitialRequests: Infinity,
minSize: 0,
cacheGroups: {
reactVendor: {
test: /[\\/]node_modules[\\/](react|react-dom)[\\/]/,
name: "reactvendor"
},
utilityVendor: {
test: /[\\/]node_modules[\\/](lodash|moment|moment-timezone)[\\/]/,
name: "utilityVendor"
},
bootstrapVendor: {
test: /[\\/]node_modules[\\/](react-bootstrap)[\\/]/,
name: "bootstrapVendor"
},
vendor: {
test: /[\\/]node_modules[\\/](!react-bootstrap)(!lodash)(!moment)(!moment-timezone)[\\/]/,
name: "vendor"
},
},
},
}
In order to separate the vendors and the runtime you need to use the optimization option.
Possible Webpack 4 configuration:
// mode: 'development' | 'production' | 'none'
entry: {
client: ['./client.js'],
vendor: ['babel-polyfill', 'react', 'react-dom', 'redux'],
},
output: {
filename: '[name].[chunkhash].bundle.js',
path: '../dist',
chunkFilename: '[name].[chunkhash].bundle.js',
publicPath: '/',
},
optimization: {
runtimeChunk: 'single',
splitChunks: {
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
enforce: true,
chunks: 'all'
}
}
}
}
More info related with W4 can be found in this Webpack-Demo.
Also, you can achieve the same changing the optimization.splitChunks.chunks property to "all". Read more here
Note: You can configure it via optimization.splitChunks. The examples say something about chunks, by default it only works for async chunks, but with optimization.splitChunks.chunks: "all" the same would be true for initial chunks.
There are a few examples located here:
https://github.com/webpack/webpack/tree/master/examples
Based on your example i believe this translate to:
// mode: "development || "production",
entry: {
client: './client.js',
},
output: {
path: path.join(__dirname, '../dist'),
filename: '[name].chunkhash.bundle.js',
chunkFilename: '[name].chunkhash.bundle.js',
publicPath: '/',
},
optimization: {
splitChunks: {
cacheGroups: {
vendor: {
chunks: 'initial',
name: 'vendor',
test: 'vendor',
enforce: true
},
}
},
runtimeChunk: true
}
You could remove vendor out of the entry property and set the optimization property like so...
entry: {
client: './client.js'
},
output: {
path: path.join(__dirname, '../dist'),
filename: '[name].chunkhash.bundle.js',
chunkFilename: '[name].chunkhash.bundle.js',
publicPath: '/',
},
optimization: {
splitChunks: {
cacheGroups: {
vendor: {
test: /node_modules/,
chunks: 'initial',
name: 'vendor',
enforce: true
},
}
}
}
Check this source webpack examples
After some time I found out that this configuration:
entry: {
vendor: ['#babel/polyfill', 'react', 'react-dom', 'redux'],
client: './client.js',
},
optimization: {
splitChunks: {
cacheGroups: {
vendor: {
chunks: 'initial',
name: 'vendor',
test: 'vendor',
enforce: true
},
}
},
runtimeChunk: true
}
was failing to somehow to load #babel/polyfill which was causing browser incompatibility errors... So recently I looked up to the updated webpack documentation and found a way to create explicit vendor chunk that was properly loading #babel/polyfill:
const moduleList = ["#babel/polyfill", "react", "react-dom"];
...
entry: {
client: ["#babel/polyfill", "../src/client.js"]
}
optimization: {
runtimeChunk: "single",
splitChunks: {
cacheGroups: {
vendor: {
test: new RegExp(
`[\\/]node_modules[\\/](${moduleList.join("|")})[\\/]`
),
chunks: "initial",
name: "vendors",
enforce: true
}
}
}
}
Notice that I create one entry with all of the code included and then I specify with splitChunks.cacheGroups.vendor.test which modules should be split out to the vendor chunk.
Webpack documentation on SplitChunksPlugin.
Webpack guide on caching
Webpack author answer on the same problem
Still, I'm not sure if this is 100% correct or if it could be improved as this is literally one of the most confusing things ever. However, this seems to be closest to the documentation, seems to produce correct chunks when I inspect them with webpack-bundle-analyzer (only updates the chunks that were changed and rest of them stays the same across builds) and fixes the issue with polyfill.
I found a much shorter way to do this:
optimization: {
splitChunks: { name: 'vendor', chunks: 'all' }
}
When splitChunks.name is given as a string, the documentation says: "Specifying either a string or a function that always returns the same string will merge all common modules and vendors into a single chunk."
In combination with splitChunks.chunks, it will extract all dependencies.
I think if you do this:
optimization: {
splitChunks: {
chunks: 'all',
},
runtimeChunk: true,
}
It will create a vendors~ and runtime~ chunk for you. Sokra said the default for splitChunks is this:
splitChunks: {
chunks: "async",
minSize: 30000,
minChunks: 1,
maxAsyncRequests: 5,
maxInitialRequests: 3,
name: true,
cacheGroups: {
default: {
minChunks: 2,
priority: -20
reuseExistingChunk: true,
},
vendors: {
test: /[\\/]node_modules[\\/]/,
priority: -10
}
}
}
Which already includes a vendors and default bundle. In testing, I haven't seen a default bundle appear.
I don't know what the expected workflow for including these files is, but I wrote this helper function in PHP:
public static function webpack_asset($chunkName, $extensions=null, $media=false) {
static $stats;
if($stats === null) {
$stats = WxJson::loadFile(WX::$path.'/webpack.stats.json');
}
$paths = WXU::array_get($stats,['assetsByChunkName',$chunkName],false);
if($paths === false) {
throw new \Exception("webpack asset not found: $chunkName");
}
foreach($stats['assetsByChunkName'] as $cn => $files) {
if(self::EndsWith($cn, '~' . $chunkName)) {
// prepend additional supporting chunks
$paths = array_merge($files, $paths);
}
}
$html = [];
foreach((array)$paths as $p) {
$ext = WXU::GetFileExt($p);
if($extensions) {
if(is_array($extensions)) {
if(!in_array($ext,$extensions)) {
continue;
}
} elseif(is_string($extensions)) {
if($ext !== $extensions) {
continue;
}
} else {
throw new \Exception("Unexpected type for \$extensions: ".WXU::get_type($extensions));
}
}
switch($ext) {
case 'js':
$html[] = WXU::html_tag('script',['src'=>$stats['publicPath'].$p,'charset'=>'utf-8'],'');
break;
case 'css':
$html[] = WXU::html_tag('link',['href'=>$stats['publicPath'].$p,'rel'=>'stylesheet','type'=>'text/css','media'=>$media],null); // "charset=utf-8" doesn't work in IE8
break;
}
}
return implode(PHP_EOL, $html);
}
Which works with my assets plugin (updated for WP4):
{
apply: function(compiler) {
//let compilerOpts = this._compiler.options;
compiler.plugin('done', function(stats, done) {
let assets = {};
stats.compilation.namedChunks.forEach((chunk, name) => {
assets[name] = chunk.files;
});
fs.writeFile('webpack.stats.json', JSON.stringify({
assetsByChunkName: assets,
publicPath: stats.compilation.outputOptions.publicPath
}), done);
});
}
},
All of this spits out something like:
<script src="/assets/runtime~main.a23dfea309e23d13bfcb.js" charset="utf-8"></script>
<link href="/assets/chunk.81da97be08338e4f2807.css" rel="stylesheet" type="text/css"/>
<script src="/assets/chunk.81da97be08338e4f2807.js" charset="utf-8"></script>
<link href="/assets/chunk.b0b8758057b023f28d41.css" rel="stylesheet" type="text/css"/>
<script src="/assets/chunk.b0b8758057b023f28d41.js" charset="utf-8"></script>
<link href="/assets/chunk.00ae08b2c535eb95bb2e.css" rel="stylesheet" type="text/css" media="print"/>
Now when I modify one of my custom JS files, only one of those JS chunks changes. Neither the runtime nor the vendors bundle needs to be updated.
If I add a new JS file and require it, the runtime still isn't updated. I think because the new file will just be compiled into the main bundle -- it doesn't need to be in the mapping because it's not dynamically imported. If I import() it, which causes code-splitting, then the runtime gets updated. The vendors bundle also appears to have changed -- I'm not sure why. I thought that was supposed to be avoided.
I also haven't figured out how to do per-file hashes. If you modify a .js file which is the same chunk as a .css file, both their filenames will change with [chunkhash].
I updated the assets plugin above. I think the order in which you include the <script> tags might matter... this will maintain that order AFAICT:
const fs = require('fs');
class EntryChunksPlugin {
constructor(options) {
this.filename = options.filename;
}
apply(compiler) {
compiler.plugin('done', (stats, done) => {
let assets = {};
// do we need to use the chunkGraph instead to determine order??? https://gist.github.com/sokra/1522d586b8e5c0f5072d7565c2bee693#gistcomment-2381967
for(let chunkGroup of stats.compilation.chunkGroups) {
if(chunkGroup.name) {
let files = [];
for(let chunk of chunkGroup.chunks) {
files.push(...chunk.files);
}
assets[chunkGroup.name] = files;
}
}
fs.writeFile(this.filename, JSON.stringify({
assetsByChunkName: assets,
publicPath: stats.compilation.outputOptions.publicPath
}), done);
});
}
}
module.exports = EntryChunksPlugin;
It seems the order of entry files also matter. Since you have client.js before vendor, the bundling doesn't happen of vendor before your main app.
entry: {
vendor: ['react', 'react-dom', 'react-router'],
app: paths.appIndexJs
},
Now with the SplitChunks optimisation you can specify the output file name and refer to the entry name vendor as:
optimization: {
splitChunks: {
cacheGroups: {
// match the entry point and spit out the file named here
vendor: {
chunks: 'initial',
name: 'vendor',
test: 'vendor',
filename: 'vendor.js',
enforce: true,
},
},
},
},

OpenLayers v3 with Webpack

Hello I am currently using OpenLayers v3 with Webpack. It works fine when I am not using UglifyJS. But the moment I added my uglifyJS config, it just won't compile anymore
My UglifyJS config
new webpack.optimize.UglifyJsPlugin({
minimize: true,
sourceMap: false,
compress: {
warnings: false,
sequences: true,
dead_code: true,
conditionals: true,
booleans: true,
unused: true,
if_return: true,
join_vars: true,
drop_console: true
}
}),
And i get this error
from UglifyJs RangeError: Maximum call stack size exceeded
I am using the dist/ol.js file.
Any idea how to solve this?
This is how I load ol3 in my webpack project :
webpack.config.js :
...
resolve: {
alias: {
openlayers3: "openlayers/dist/ol.js",
},
},
plugins: [
new webpack.ProvidePlugin({
ol: "openlayers3",
}),
]
...
ProvidePlugin load modules and make them globally available in your app. You don't have to import ol3 in your js files anymore.
https://webpack.github.io/docs/list-of-plugins.html#provideplugin
Had the same issue here. Resolved by moving openlayers into its own chunk using CommonsChunkPlugin, then ignoring that chunk in UglifyJsPlugin.
entries:
entry: {
vendor: Object.keys(packageDotJson.dependencies),
openlayers: ['openlayers'],
app: './src/index.jsx'
}
CommonsChunkPlugin:
new webpack.optimize.CommonsChunkPlugin({
names: ["openlayers", "vendor"],
minChunks: Infinity,
filename: "[name].[hash].js"
}),
UglifyJsPlugin:
new webpack.optimize.UglifyJsPlugin({
compress: {
warnings: false
},
exclude: 'openlayers'
}),
Then included in the html template using HtmlWebpackPlugin:
<% for (var chunk in htmlWebpackPlugin.files.chunks) { %>
<script src="<%=htmlWebpackPlugin.files.chunks[chunk].entry %>"></script>
<% } %>

Source maps in uglified requirejs project

I have a requirejs project, I'm compiling with grunt-requirejs ("grunt-contrib-requirejs": "~0.4.1") into 1 big file: main.js. This task has source map generation enabled:
requirejs: {
compile: {
options: {
baseUrl: 'source/js',
name: 'main',
optimize: 'none',
generateSourceMaps: true,
out: 'build/js/main.js',
wrap: true,
shim: requireJsConfig.shim,
paths: requireJsConfig.paths
}
}
}
After that I minify this main.js with grunt-uglify ("grunt-contrib-uglify": "~0.2.7") using this configuration:
app: {
options: {
beautify : {
quote_keys: true
},
compress: false,
report: 'min',
sourceMap: 'build/js/main.js.map',
sourceMapIn: 'build/js/main.js.map', // input from requirejs
sourceMapIncludeSources: true
},
files: {
'build/js/main.js': ['build/js/main.js']
}
}
I would like to have a source map that will tell me an error in the source files (the ones requirejs consumes), but instead source map refuses to work at all. Please help me to get there as I'm feeling helpless already.
grunt-require comes with it's own uglify package built in:
eg.
requirejs: {
compile: {
options: {
generateSourceMaps: true,
logLevel: 4,
baseUrl: "common/scripts/",
include: "./main",
out: "common/dist/main.js",
preserveLicenseComments: false,
optimize: "uglify2",
mainConfigFile: "common/scripts/main.js"
}
}
}

Resources