简要说明: 在使用提供给 HtmlWebpackPlugin 的 .html 模板中的链接标记内引用的 .css 文件以及使用 MiniCssExtractPlugin 和一些加载器时,我遇到了意外的输出/问题。
目录结构:
| package.json
| webpack.prod.js
| src
| css
| main.css
| another.css
| ...
| images
| image1.png
...
| scripts
| script1.js
| script2.js
| templates
| file1.html
| file2.html
...
| ...
以上目录结构中文件内容详细说明: script1.js 包含对 script2.js 的引用,但没有任何对 css 文件的引用。 .css 文件仅使用 .src/templates 文件夹中包含的 .html 文件中的标签进行引用。
webpack.prod.js 内容:
const path = require('path');
const fs = require('fs');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
const TerserPlugin = require('terser-webpack-plugin');
const htmlTemplates = fs.readdirSync(path.resolve(__dirname, 'src/templates')).filter(file => /\.html$/i.test(file));
module.exports = {
mode: "production",
entry: {
index: "./src/scripts/script1.js",
},
output: {
filename: 'static/[name]-[contenthash].bundle.js',
path: path.resolve(__dirname, 'build'),
publicPath: '/',
assetModuleFilename: "static/[name]-markedByAssetModuleFilename-[contenthash].bundle[ext]",
clean: true,
},
optimization: {
minimizer: [
new CssMinimizerPlugin(),
new TerserPlugin({
extractComments: false, //omit the creation of a separate LICENSE file
terserOptions: {
format: {
comments: false, //remove the comments from the bundled output
},
}
}),
],
},
plugins: [
...htmlTemplates.map((file) => {
return new HtmlWebpackPlugin({
template: `./src/templates/${file}`,
filename: `htmlFiles/${file.replace(/\.html$/, '.bundle.html')}`,
inject: 'head',
scriptLoading: 'module',
minify: {
collapseWhitespace: true,
removeComments: true,
removeRedundantAttributes: true,
removeScriptTypeAttributes: true,
removeStyleLinkTypeAttributes: true,
useShortDoctype: true,
minifyJS: true,
minifyCSS: true
}
});
}),
new MiniCssExtractPlugin({
filename: 'static/[name]-[contenthash].bundle.css',
}),
],
module: {
rules: [
{
test: /\.js$/i,
exclude: [/node_modules/],
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env'],
},
},
},
{
test: /\.html$/i,
use: ["html-loader"]
},
{
test: /\.(png|jpe?g|gif|svg)$/i,
type: "asset/resource",
},
{
test: /\.css$/i,
use: [
MiniCssExtractPlugin.loader,
'css-loader'
],
}
],
},
};
package.json
"devDependencies": {
"@babel/preset-env": "^7.23.6",
"babel-loader": "^9.1.3",
"css-loader": "^6.8.1",
"css-minimizer-webpack-plugin": "^5.0.1",
"html-loader": "^4.2.0",
"html-webpack-plugin": "^5.6.0",
"mini-css-extract-plugin": "^2.7.6",
"terser-webpack-plugin": "^5.3.10",
"webpack": "^5.89.0",
"webpack-cli": "^5.1.4",
"webpack-dev-server": "^4.15.1"
...
...
}
我期望的输出:
当我运行
webpack --config webpack.prod.js
时,我应该得到 build 文件夹,其中 htmlFiles 文件夹包含所有“已处理”的 .html 文件。这些 html 文件应该具有捆绑 script1.js 的 script 标签,并且应该具有指向已处理的 css 文件的链接标签(这些 css 文件应根据为 MiniCssExtractPlugin 指定的文件名命名,而不是根据 output.assetModuleFilename 命名。css 文件不应是空了。
我得到的输出:
./build/htmlFiles 文件夹下的 .html 文件具有指向 .build 文件夹中输出的 css 文件和脚本的链接标签和脚本标签。到目前为止一切顺利。
我发现 ./build/static 文件夹中的 .css 文件都是根据 ouput.assetModuleFilename 下指定的名称命名的。所有 .css 文件也是空的,如下所示:
// extracted by mini-css-extract-plugin
export {};
我发现,如果我修改 module.rules 使其具有以下包含属性(如下所示)并指向一个不存在的文件,那么 ./build/static 内的所有 .css 文件都包含 css 内容,但不再包含说
// extracted by mini-css-extract-plugin
虽然,它们仍然是根据assetModuleFilename命名的。如果 include 确实指向存在的实际文件,则 .build/static 内的该文件将没有 css 内容并且为空。 .build/static 中其他已处理的 .css 文件将包含内容。我不明白这是怎么回事。
{
test: /\.css$/i,
use: [
MiniCssExtractPlugin.loader, //2. Extract css into files
'css-loader' //1. Turns css into commonjs
],
include: path.resolve(__dirname, './src/css/nonExistentFile.css'),
}
如何获得我期望的输出?我想使用我的 html 模板中的所有 css,使用指向构建目录中已处理的 .css 文件的链接标记。我不想在共享 script1.js 文件中包含 css import 语句。每个 .html 模板文件使用链接标记指向其自己的 .css 文件。
过去 2 天我尝试阅读文档,但目前还无法弄清楚 webpack。这是我第一次使用 webpack。
- 我想使用链接标签在我的 html 模板中使用所有 css
- 我不想在共享 script1.js 文件中包含 css import 语句
您可以使用 html-bundler-webpack-plugin。
此插件允许直接在 HTML 标签中使用 JS 和 CSS/SCSS 源文件。
模板中的所有源文件路径都将被解析并自动替换为捆绑输出中的正确 URL。解析后的资源将通过 Webpack 插件/加载器进行处理并放入输出目录中。
例如,有一个 HTML 模板包含对 SCSS、JS 和图像的引用:
<html>
<head>
<!-- relative path to SCSS source file -->
<link href="./styles.scss" rel="stylesheet" />
<!-- relative path to JS source file -->
<script src="./main.js" defer="defer"></script>
</head>
<body>
<h1>Hello World!</h1>
<!-- relative path to image source file -->
<img src="./picture.png" />
</body>
</html>
生成的 HTML 包含输出文件名:
<html>
<head>
<link href="css/styles.05e4dd86.css" rel="stylesheet" />
<script src="js/main.f4b855d8.js" defer="defer"></script>
</head>
<body>
<h1>Hello World!</h1>
<img src="img/picture.58b43bd8.png" />
</body>
</html>
简单的 Webpack 配置:
const HtmlBundlerPlugin = require('html-bundler-webpack-plugin');
module.exports = {
plugins: [
new HtmlBundlerPlugin({
// define a relative or absolute path to entry templates
entry: 'src/views/',
// OR define many templates manually
entry: {
index: 'src/views/home.html', // => dist/index.html
'news/sport': 'src/views/news/sport/index.html', // => dist/news/sport.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',
//inline: true, // inlines CSS into HTML
},
}),
],
// loaders for styles, images, etc.
module: {
rules: [
{
test: /\.(css|sass|scss)$/,
use: ['css-loader', 'sass-loader'],
},
{
test: /\.(ico|png|jp?g|webp|svg)$/,
type: 'asset/resource',
},
],
},
};
注意
使用 HTML 捆绑器插件,HTML 模板是入口点,而不是 JS。您不再需要在 Webpack 入口中定义 SCSS 和 JS 文件。
HTML 捆绑器插件取代了 html-webpack-plugin 和 mini-css-extract-plugin 的功能。