optimize webpack config for faster build website

- switch to modern html-bundler-webpack-plugin
- replace deprecated `file-loader` and `url-loader` with Webpack 5 assets module
- create single webpack config instead of two configs
This commit is contained in:
biodiscus 2023-09-02 22:20:55 +02:00
parent 3732d59249
commit 818f752714
12 changed files with 1731 additions and 1293 deletions

View file

@ -111,17 +111,14 @@
"eslint-plugin-optimize-regex": "1.2.1", "eslint-plugin-optimize-regex": "1.2.1",
"eslint-plugin-promise": "6.1.1", "eslint-plugin-promise": "6.1.1",
"eslint-plugin-sonarjs": "0.20.0", "eslint-plugin-sonarjs": "0.20.0",
"file-loader": "6.2.0",
"handlebars": "4.7.8", "handlebars": "4.7.8",
"handlebars-loader": "1.7.3", "html-bundler-webpack-plugin": "2.10.1",
"html-webpack-plugin": "5.5.3",
"husky": "^8.0.1", "husky": "^8.0.1",
"image-webpack-loader": "8.1.0", "image-webpack-loader": "8.1.0",
"is-ci-cli": "2.2.0", "is-ci-cli": "2.2.0",
"jest": "29.6.2", "jest": "29.6.2",
"lint-staged": "13.2.3", "lint-staged": "13.2.3",
"markdown-toc": "^1.2.0", "markdown-toc": "^1.2.0",
"mini-css-extract-plugin": "2.7.6",
"mkdirp": "3.0.1", "mkdirp": "3.0.1",
"nopt": "7.2.0", "nopt": "7.2.0",
"postcss": "8.4.27", "postcss": "8.4.27",
@ -134,7 +131,6 @@
"ts-loader": "9.4.4", "ts-loader": "9.4.4",
"ts-node": "10.9.1", "ts-node": "10.9.1",
"typescript": "5.1.6", "typescript": "5.1.6",
"url-loader": "4.1.1",
"webpack": "5.88.2", "webpack": "5.88.2",
"webpack-cli": "5.1.4", "webpack-cli": "5.1.4",
"webpack-dev-server": "4.15.1", "webpack-dev-server": "4.15.1",

View file

@ -1,28 +1,40 @@
import path from 'path'; import path from 'path';
import HtmlBundlerPlugin from 'html-bundler-webpack-plugin';
import webpack from 'webpack';
import HtmlWebpackPlugin from 'html-webpack-plugin';
import MiniCssExtractPlugin from 'mini-css-extract-plugin';
// eslint-disable-next-line import/default // eslint-disable-next-line import/default
import CopyPlugin from 'copy-webpack-plugin'; import CopyPlugin from 'copy-webpack-plugin';
const pages = ['index', 'demo']; const config = {
output: {
type Plugin = ((this: webpack.Compiler, compiler: webpack.Compiler) => void) | webpack.WebpackPluginInstance; path: path.resolve(__dirname, './docs'),
},
function plugins(page: string): Plugin[] { resolve: {
return [ extensions: ['.tsx', '.ts', '.jsx', '.js'],
new MiniCssExtractPlugin({ },
filename: '[name].css', plugins: [
chunkFilename: '[id].css', new HtmlBundlerPlugin({
}), entry: {
new HtmlWebpackPlugin({ // define templates here
hash: true, index: './website/templates/pages/index/index.handlebars', // => docs/index.html,
inject: true, demo: './website/templates/pages/demo/demo.handlebars', // => docs/demo.html
title: `${page} page`, },
filename: `${page}.html`, js: {
template: `./website/templates/pages/${page}/${page}.handlebars`, // output filename of compiled JavaScript, used if `inline` option is false (defaults)
minify: { filename: '[name].[contenthash:8].js',
//inline: true, // inlines JS into HTML
},
css: {
// output filename of extracted CSS, used if `inline` option is false (defaults)
filename: '[name].[contenthash:8].css',
//inline: true, // inlines CSS into HTML
},
preprocessor: 'handlebars', // use the handlebars compiler
preprocessorOptions: {
knownHelpersOnly: false,
helpers: [path.join(__dirname, 'website/templates/helpers')],
partials: [path.join(__dirname, 'website/templates/partials'), path.join(__dirname, 'website/templates/pages')],
},
minify: 'auto', // minify in production mode only
minifyOptions: {
html5: true, html5: true,
collapseWhitespace: true, collapseWhitespace: true,
caseSensitive: true, caseSensitive: true,
@ -45,107 +57,74 @@ function plugins(page: string): Plugin[] {
{ from: 'website/sitemap.xml', to: 'sitemap.xml' }, { from: 'website/sitemap.xml', to: 'sitemap.xml' },
], ],
}), }),
]; ],
} module: {
rules: [
const config: webpack.Configuration[] = pages.map(page => { {
return { test: /\.tsx?$/,
entry: { use: 'ts-loader',
[page]: `./website/templates/pages/${page}/${page}.ts`, exclude: /node_modules/,
}, },
output: { {
path: path.resolve(__dirname, './docs'), test: /\.(gif|png|jpe?g|webp)$/i,
}, type: 'asset/resource',
resolve: { generator: {
extensions: ['.tsx', '.ts', '.jsx', '.js'], filename: 'images/[name][ext]?[hash]',
},
module: {
rules: [
{
test: /\.tsx?$/,
use: 'ts-loader',
exclude: /node_modules/,
}, },
{ use: [
test: /\.(html)$/, {
use: { loader: 'image-webpack-loader',
loader: 'html-loader',
options: { options: {
attrs: ['img:src'], mozjpeg: {
progressive: true,
quality: 65,
},
optipng: {
enabled: true,
},
pngquant: {
quality: [0.65, 0.9],
speed: 4,
},
gifsicle: {
interlaced: false,
},
webp: {
quality: 75,
},
}, },
}, },
}, ],
{ },
test: /\.handlebars$/, {
loader: 'handlebars-loader', test: /\.(css)$/,
options: { use: [{ loader: 'css-loader', options: { importLoaders: 1 } }, 'postcss-loader'],
inlineRequires: '/images/', },
precompileOptions: { {
knownHelpersOnly: false, test: /\.woff(2)?(\?v=\d\.\d\.\d)?$/,
}, type: 'asset',
helperDirs: [path.join(__dirname, 'website/templates/helpers')], parser: {
partialDirs: [path.join(__dirname, 'website/templates')], dataUrlCondition: {
maxSize: 1000,
}, },
}, },
{ },
test: /\.(gif|png|jpe?g|webp)$/i, {
use: [ test: /\.(ttf|eot|svg)(\?v=\d\.\d\.\d)?$/,
{ type: 'asset/resource',
loader: 'file-loader', },
options: { ],
name: '[name].[ext]?[hash]', },
outputPath: 'images', // enable live reload after changes
esModule: false, devServer: {
}, static: path.resolve(__dirname, './docs'),
}, watchFiles: {
{ paths: ['website/**/*.*'],
loader: 'image-webpack-loader', options: {
options: { usePolling: true,
mozjpeg: { },
progressive: true,
quality: 65,
},
optipng: {
enabled: true,
},
pngquant: {
quality: [0.65, 0.9],
speed: 4,
},
gifsicle: {
interlaced: false,
},
webp: {
quality: 75,
},
},
},
],
},
{
test: /\.(css)$/,
use: [MiniCssExtractPlugin.loader, { loader: 'css-loader', options: { importLoaders: 1 } }, 'postcss-loader'],
},
{
test: /\.woff(2)?(\?v=\d\.\d\.\d)?$/,
use: [
{
loader: 'url-loader',
options: {
limit: 1000,
mimetype: 'application/font-woff',
},
},
],
},
{
test: /\.(ttf|eot|svg)(\?v=\d\.\d\.\d)?$/,
loader: 'file-loader',
},
],
}, },
plugins: plugins(page), },
}; };
});
export default config; export default config;

View file

@ -0,0 +1,24 @@
'use strict';
/* eslint-disable @typescript-eslint/no-var-requires */
const Handlebars = require('handlebars');
/** @typedef {import('handlebars').HelperOptions} HelperOptions */
/**
* @param {string} name
* @param {HelperOptions}
* @return {string}
*/
module.exports = function (name, options) {
// eslint-disable-next-line
const context = this;
let partial = context._blocks[name] || options.fn;
if (typeof partial === 'string') {
partial = Handlebars.compile(partial);
context._blocks[name] = partial;
}
return partial(context, { data: options.hash });
};

View file

@ -1,15 +0,0 @@
import handlebars, { HelperOptions } from 'handlebars';
const loadPartial = <T>(name: string): handlebars.Template<T> => {
let partial = handlebars.partials[name];
if (typeof partial === 'string') {
partial = handlebars.compile(partial);
handlebars.partials[name] = partial;
}
return partial;
};
export default (name: string, options: HelperOptions): string => {
const partial = loadPartial(name) || options.fn;
return partial(this, { data: options.hash });
};

View file

@ -0,0 +1,20 @@
'use strict';
/** @typedef {import('handlebars').HelperOptions} HelperOptions */
/**
* @param {string} name
* @param {HelperOptions} options
* @return {void}
*/
module.exports = function (name, options) {
// don't modify `this` in code directly, because it will be compiled in `exports` as an immutable object
// eslint-disable-next-line
const context = this;
if (!context._blocks) {
context._blocks = {};
}
context._blocks[name] = options.fn;
};

View file

@ -1,5 +0,0 @@
import handlebars, { HelperOptions } from 'handlebars';
export default (name: string, options: HelperOptions): void => {
handlebars.registerPartial(name, options.fn);
};

View file

@ -1,4 +1,8 @@
{{#partial "content"}} {{#partial 'scripts'}}
{{> content}} {{! define here page-specific source script files }}
<script src="./demo.ts" defer="defer"></script>
{{/partial}} {{/partial}}
{{> ../../template}} {{#partial "content"}}
{{> demo/content}}
{{/partial}}
{{> template}}

View file

@ -1,6 +1,6 @@
import { Diff2HtmlUI, defaultDiff2HtmlUIConfig, Diff2HtmlUIConfig } from '../../../../src/ui/js/diff2html-ui-slim'; import { Diff2HtmlUI, defaultDiff2HtmlUIConfig, Diff2HtmlUIConfig } from '../../../../src/ui/js/diff2html-ui-slim';
import '../../../main.ts'; import '../../../main';
import '../../../main.css'; import '../../../main.css';
import 'highlight.js/styles/github.css'; import 'highlight.js/styles/github.css';
import '../../../../src/ui/css/diff2html.css'; import '../../../../src/ui/css/diff2html.css';

View file

@ -1,4 +1,8 @@
{{#partial "content"}} {{#partial 'scripts'}}
{{> content}} {{! define here page-specific source script files }}
<script src="./index.ts" defer="defer"></script>
{{/partial}} {{/partial}}
{{> ../../template}} {{#partial "content"}}
{{> index/content}}
{{/partial}}
{{> template}}

View file

@ -1,6 +1,6 @@
import Clipboard from 'clipboard'; import Clipboard from 'clipboard';
import '../../../main.ts'; import '../../../main';
import '../../../main.css'; import '../../../main.css';
import './index.css'; import './index.css';

View file

@ -40,6 +40,8 @@
ga('create', 'UA-78351861-2', 'auto'); ga('create', 'UA-78351861-2', 'auto');
ga('send', 'pageview'); ga('send', 'pageview');
</script> </script>
{{#block 'scripts'}}{{/block}}
</head> </head>
<body> <body>

2717
yarn.lock

File diff suppressed because it is too large Load diff