Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

simplify webpack config #1

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@

2. Разработка:

> `npm run dev`
> `npm start`

devServer со html страницами будет доступен по адресу
http://localhost:8080/html/
Expand Down
7,796 changes: 3,848 additions & 3,948 deletions package-lock.json

Large diffs are not rendered by default.

36 changes: 14 additions & 22 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"description": "",
"main": "",
"scripts": {
"dev": "webpack serve --mode development",
"start": "webpack serve --mode development",
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Defaults, for development should be used the start script name.

"build": "webpack --mode=production"
},
"keywords": [],
Expand All @@ -14,29 +14,21 @@
"defaults"
],
"devDependencies": {
"@babel/core": "^7.17.10",
"@babel/preset-env": "^7.17.10",
"@babel/core": "^7.23.0",
"@babel/preset-env": "^7.22.20",
"@nurminen/html-beautify-webpack-plugin": "^1.1.3",
"autoprefixer": "^10.4.7",
"babel-loader": "^8.2.5",
"css-loader": "^6.7.1",
"css-minimizer-webpack-plugin": "^3.4.1",
"filemanager-webpack-plugin": "^7.0.0-beta.0",
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove unused filemanager-webpack-plugin

"html-loader": "^3.1.0",
"html-webpack-plugin": "^5.5.0",
"html-webpack-skip-assets-plugin": "^1.0.3",
"autoprefixer": "^10.4.16",
"babel-loader": "^9.1.3",
"css-loader": "^6.8.1",
"css-minimizer-webpack-plugin": "^5.0.1",
"html-bundler-webpack-plugin": "^2.14.2",
"image-webpack-loader": "^8.1.0",
"mini-css-extract-plugin": "^2.6.0",
"postcss-loader": "^6.2.1",
"resolve-url-loader": "^5.0.0",
"sass": "^1.51.0",
"sass-loader": "^12.6.0",
"style-loader": "^3.3.1",
"terser-webpack-plugin": "^5.3.1",
"webpack": "^5.72.1",
"webpack-cli": "^4.9.2",
"webpack-dev-server": "^4.9.0",
"webpack-remove-empty-scripts": "^0.8.0"
"postcss-loader": "^7.3.3",
"sass": "^1.68.0",
"sass-loader": "^13.3.2",
"webpack": "^5.88.2",
"webpack-cli": "^5.1.4",
"webpack-dev-server": "^4.15.1"
},
"dependencies": {}
}
Binary file added src/images/favicon.ico
Binary file not shown.
9 changes: 0 additions & 9 deletions src/pages/index.html

This file was deleted.

9 changes: 9 additions & 0 deletions src/views/pages/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<%~ include('partials/head.html') %>
Copy link
Author

@webdiscus webdiscus Oct 2, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can use any templating engine to include partials, e.g. very fast Eta (ESJ like).

<body class="page page-main">
<%~ include('partials/header.html') %>

<main>
MAIN
</main>

<%~ include('partials/footer.html') %>
File renamed without changes.
3 changes: 3 additions & 0 deletions src/pages/partials/head.html → src/views/partials/head.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,7 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Webpack 5 Project Starter</title>
<link href="@images/favicon.ico" rel="icon" />
<link href="@styles/style.scss" rel="stylesheet" />
<script src="@scripts/global.js" defer="defer"></script>
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you can use the source files of styles, scripts, images etc. directly in the HTML using the relative path to HTML file or an alias specified in Webpack config

</head>
File renamed without changes.
204 changes: 45 additions & 159 deletions webpack.config.js
Original file line number Diff line number Diff line change
@@ -1,174 +1,68 @@
const fs = require('fs');
const path = require('path');
const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const HtmlBeautifyPlugin = require('@nurminen/html-beautify-webpack-plugin');
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In production mode use the minfy of HTML (good practice), not beautify to avoid some issues with spaces in HTML.

const HtmlWebpackSkipAssetsPlugin = require('html-webpack-skip-assets-plugin').HtmlWebpackSkipAssetsPlugin;
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
const RemoveEmptyScriptsPlugin = require('webpack-remove-empty-scripts');
const TerserPlugin = require("terser-webpack-plugin");
const HtmlBundlerPlugin = require('html-bundler-webpack-plugin');

const isDev = process.env.WEBPACK_SERVE;

function entryPoints(dir) {
let entry_points = {
style: './src/scss/style.scss'
};

function each_file(dir) {
try {
fs.readdirSync(dir, {withFileTypes: true}).forEach(function(item) {
const file = item.name;
const parts = file.split('.');
if (parts[1] !== 'js') return;
var file_path = dir + '/' + file;
entry_points[parts[0]] = file_path;
});
} catch (e) {
console.log(e);
}
}

each_file(dir);

return entry_points;
}

function generateHtmlPlugins(templateDir) {
const templateFiles = fs.readdirSync(path.resolve(__dirname, templateDir), {withFileTypes: true});
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The HtmlBundlerPlugin can automatically handle HTML templates from the entry path option.


return templateFiles.reduce((result, item) => {
const parts = item.name.split('.');
const name = parts[0];
const extension = parts[1];

if (extension === 'html') result.push(
new HtmlWebpackPlugin({
filename: path.resolve(__dirname, `./theme/html/${name}.html`),
template: path.resolve(__dirname, `${templateDir}/${name}.${extension}`),
chunks: ['style', 'vendors', 'global', name],
chunksSortMode: 'manual',
excludeAssets: 'style.js'
})
);

return result;
}, []);
}

function plugins() {
let plugins = [...generateHtmlPlugins('./src/pages/')];

if (isDev) {
plugins.push(new webpack.HotModuleReplacementPlugin());
} else {
plugins = [
...plugins,
new HtmlBeautifyPlugin({
config: {
html: {
end_with_newline: true,
indent_size: 4,
indent_with_tabs: true,
indent_inner_html: true,
preserve_newlines: true,
unformatted: ['strong', 'i', 'b'],
inline: []
}
}
}),
new HtmlWebpackSkipAssetsPlugin({
excludeAssets: ['style.js']
}),
new RemoveEmptyScriptsPlugin(),
new MiniCssExtractPlugin({
filename: ({chunk}) => chunk.name === 'style' ? '[name].css' : 'css/[name].css'
})
]
}

return plugins;
}

function processNestedHtml(content, loaderContext) {
const INCLUDE_PATTERN = /\<include src=\"(.+)\"\/?\>(?:\<\/include\>)?/gi;

return !INCLUDE_PATTERN.test(content)
? content
: content.replace(INCLUDE_PATTERN, (m, src) => {
const pathToPartial = path.resolve(loaderContext.context, src);
loaderContext.addDependency(pathToPartial);
return processNestedHtml(fs.readFileSync(pathToPartial, 'utf8'), {...loaderContext, context: path.dirname(pathToPartial)});
});
}

function styleLoaders() {
if (isDev) {
return [
"style-loader",
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The HtmlBundlerPlugin can inline extracted CSS into HTML,
no style-loader is required.

"css-loader",
"resolve-url-loader",
{
loader: "sass-loader",
options: {
sourceMap: true, // <-- !!IMPORTANT!! for "resolve-url-loader"
}
}
]
}

return [
MiniCssExtractPlugin.loader,
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The HtmlBundlerPlugin extracts generated CSS self,
the MiniCssExtractPlugin is not needed.

"css-loader",
"postcss-loader",
"resolve-url-loader",
{
loader: "sass-loader",
options: {
sourceMap: true, // <-- !!IMPORTANT!! for "resolve-url-loader"
}
}
]
}


module.exports = {
entry: entryPoints('./src/js'),
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using the HtmlBundlerPlugin, an entry point is HTML, not JS.
All HTML templates you can specify in the entry option of the plugin.


output: {
filename: 'js/[name].js',
path: path.resolve(__dirname, './theme'),
path: path.resolve(__dirname, './theme'),
assetModuleFilename: 'images/[name][ext]',
clean: isDev ? false : {
keep(asset) {
const targets = ['fonts/', 'html/', 'css/', 'images/', 'js/', '.css'];
return !targets.some(v => asset.includes(v));
}
}
},
},

resolve: {
alias: {
// aliases used in template for source assets
'@scripts': path.join(__dirname, 'src/js'),
'@styles': path.join(__dirname, 'src/scss'),
'@images': path.join(__dirname, 'src/images'),
'@fonts': path.join(__dirname, 'src/fonts'),
}
},

plugins: plugins(),
plugins: [
new HtmlBundlerPlugin({
// define a relative or absolute path to entry templates
// note: the partials dir must be outside the pages
entry: 'src/views/pages/',
outputPath: path.resolve(__dirname, './theme/html'),
js: {
// output filename of compiled JavaScript, used if `inline` option is false (defaults)
filename: 'js/[name].[contenthash:8].js',
//inline: true, // inlines JS into HTML
},
css: {
// output filename of extracted CSS, used if `inline` option is false (defaults)
//filename: 'css/[name].[contenthash:8].css',
filename: ({chunk}) => chunk.name === 'style' ? '[name].[contenthash:8].css' : 'css/[name].[contenthash:8].css'
//inline: true, // inlines CSS into HTML
},
preprocessor: 'eta', // use the template engine
preprocessorOptions: {
views: 'src/views', // path for includes in template
},
minify: 'auto', // minify html in production mode only
}),
],

externals: {
jquery: 'jQuery',
},

module: {
rules: [
{
test: /\.html$/i,
use: {
loader: 'html-loader',
options: {
minimize: false,
preprocessor: processNestedHtml
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use the preprocessor option of the HtmlBundlerPlugin to specify a template engine.
This rule for .html is not needed anymore.

}
}
},
{
test: /\.s[ac]ss|\.css/i,
use: styleLoaders()
use: [
"css-loader",
isDev ? null : "postcss-loader",
"sass-loader", // minimizes generated CSS in production mode
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you don't need to specify sourceMap: true options because the HTML Bundler Plugin resolves URLs in CSS/SCSS without the resolve-url-loader

]
},
{
test: /\.js$/i,
Expand All @@ -181,7 +75,7 @@ module.exports = {
}
},
{
test: /\.(png|jpg|gif)$/i,
test: /\.(png|jpg|gif|ico)$/i,
type: 'asset/resource',
use: isDev ? void(0) : 'image-webpack-loader'
},
Expand Down Expand Up @@ -210,7 +104,7 @@ module.exports = {
directory: path.join(__dirname, './theme/html'),
},
watchFiles: ['src/pages/**/*'],
hot: true,
hot: true,
host: '0.0.0.0',
port: 8080
},
Expand All @@ -219,19 +113,11 @@ module.exports = {
splitChunks: {
cacheGroups: {
commons: {
test: /[\\/]node_modules[\\/]/,
test: /[\\/]node_modules[\\/].+\.(js|ts)$/, // split js only
name: 'vendors',
chunks (chunk) {
// exclude `style`
return chunk.name !== 'style';
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you can specify source SCSS file directly in HTML, not in a JS file,
therefore this option is not needed

}
}
}
},
minimizer: [
new CssMinimizerPlugin(),
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sass-loader minify generated CSS in production mode,
the CssMinimizerPlugin is not needed

new TerserPlugin({ extractComments: false })
Copy link
Author

@webdiscus webdiscus Oct 2, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The HTML Bundler Plugin avoid the extracting of comments into a file,
the TerserPlugin is not needed for that

],
runtimeChunk: 'single'
}
};