You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Webpack5配置问题:为Joomla4模板生成CSS压缩与未压缩资源失败

问题描述

我要给自定义Joomla4模板生成压缩和未压缩的资源,写了如下webpack.config.js配置:

const path = require('path');

const CopyPlugin = require('copy-webpack-plugin');
const TerserPlugin = require("terser-webpack-plugin");

const dist_path = path.resolve(__dirname, 'dist');
const media_root_path = 'media/templates/site/tofu';

module.exports = {
  devtool: 'source-map',
  entry: {
    'js/template.js': './src/js/template.js',
    'css/template.css': './src/scss/template.scss',
    'js/template.min.js': './src/js/template.js',
    'css/template.min.css': './src/scss/template.scss',
  },
  output: {
    path: dist_path, 
    filename: "media/templates/site/tofu/[name]",
  },
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        use: [],
      },
      {
        test: /\.scss$/,
        exclude: /node_modules/,
        type: 'asset/resource',
        generator: {
          filename: media_root_path + '/css/template.min.css',
        },
        use: [
          // Compiles Sass to CSS
          {
            loader: 'sass-loader',
            options: {
              sourceMap: true,
              // Prefer `dart-sass`
              implementation: require('sass'),
            },
          },
        ],
      },
    ],
  },
  plugins: [
    new CopyPlugin({
      patterns: [
        { from: path.resolve(__dirname, 'src/index.php'), to: path.resolve(dist_path, 'index.php') },
        { from: path.resolve(__dirname, 'src/joomla.asset.json'), to: path.resolve(dist_path, 'joomla.asset.json') },
        { from: path.resolve(__dirname, 'src/templateDetails.xml'), to: path.resolve(dist_path, 'templateDetails.xml') },
        { from: path.resolve(__dirname, 'src/images'), to: path.resolve(dist_path, media_root_path, 'images') },
      ],
    })
  ],
  optimization: {
    minimize: true,
    minimizer: [new TerserPlugin({
      include: /\.min\.js$/
    })],
  },
  stats: {
    loggingDebug: ['sass-loader']
  },
};

JavaScript部分正常,但SCSS转CSS并生成*.css、*.min.css和对应的source map时出问题:

  1. 原配置报错:
[webpack-cli] Error: Conflict: Multiple chunks emit assets to the same filename media/templates/site/tofu/css/template.min.css (chunks 61 and 301)
    at /Users/denis/Projects/tofu/node_modules/webpack/lib/Compilation.js:4625:12
    at /Users/denis/Projects/tofu/node_modules/webpack/lib/Cache.js:93:5
    at Hook.eval [as callAsync] (eval at create (/Users/denis/Projects/tofu/node_modules/tapable/lib/HookCodeFactory.js:33:10), <anonymous>:6:1)
    at Cache.get (/Users/denis/Projects/tofu/node_modules/webpack/lib/Cache.js:75:18)
    at ItemCacheFacade.get (/Users/denis/Projects/tofu/node_modules/webpack/lib/CacheFacade.js:115:15)
    at /Users/denis/Projects/tofu/node_modules/webpack/lib/Compilation.js:4571:22
    at arrayEach (/Users/denis/Projects/tofu/node_modules/neo-async/async.js:2405:9)
    at Object.each (/Users/denis/Projects/tofu/node_modules/neo-async/async.js:2846:9)
    at /Users/denis/Projects/tofu/node_modules/webpack/lib/Compilation.js:4560:14
    at symbolIterator (/Users/denis/Projects/tofu/node_modules/neo-async/async.js:3482:9)
  1. 注释掉入口项'css/template.min.css': './src/scss/template.scss',后,Webpack能构建,但生成的template.css是无效的Webpack引导代码:
/******/ (() => { // webpackBootstrap
/******/    "use strict";
/******/    var __webpack_modules__ = ({

/***/ 797:
/***/ ((module, __unused_webpack_exports, __webpack_require__) => {

module.exports = __webpack_require__.p + "media/templates/site/tofu/css/template.min.css";

/***/ })

/******/    });
/************************************************************************/
/******/    // The module cache
/******/    var __webpack_module_cache__ = {};
/******/    
/******/    // The require function
/******/    function __webpack_require__(moduleId) {
/******/        // Check if module is in cache
/******/        var cachedModule = __webpack_module_cache__[moduleId];
/******/        if (cachedModule !== undefined) {
/******/            return cachedModule.exports;
/******/        }
/******/        // Create a new module (and put it into the cache)
/******/        var module = __webpack_module_cache__[moduleId] = {
/******/            // no module.id needed
/******/            // no module.loaded needed
/******/            exports: {}
/******/        };
/******/    
/******/        // Execute the module function
/******/        __webpack_modules__[moduleId](module, module.exports, __webpack_require__);
/******/    
/******/        // Return the exports of the module
/******/        return module.exports;
/******/    }
/******/    
/************************************************************************/
/******/    /* webpack/runtime/global */
/******/    (() => {
/******/        __webpack_require__.g = (function() {
/******/            if (typeof globalThis === 'object') return globalThis;
/******/            try {
/******/                return this || new Function('return this')();
/******/            } catch (e) {
/******/                if (typeof window === 'object') return window;
/******/            }
/******/        })();
/******/    })();
/******/    
/******/    /* webpack/runtime/publicPath */
/******/    (() => {
/******/        var scriptUrl;
/******/        if (__webpack_require__.g.importScripts) scriptUrl = __webpack_require__.g.location + "";
/******/        var document = __webpack_require__.g.document;
/******/        if (!scriptUrl && document) {
/******/            if (document.currentScript)
/******/                scriptUrl = document.currentScript.src;
/******/            if (!scriptUrl) {
/******/                var scripts = document.getElementsByTagName("script");
/******/                if(scripts.length) {
/******/                    var i = scripts.length - 1;
/******/                    while (i > -1 && !scriptUrl) scriptUrl = scripts[i--].src;
/******/                }
/******/            }
/******/        }
/******/        // When supporting browsers where an automatic publicPath is not supported you must specify an output.publicPath manually via configuration
/******/        // or pass an empty string ("") and set the __webpack_public_path__ variable from your code to use your own logic.
/******/        if (!scriptUrl) throw new Error("Automatic publicPath is not supported in this browser");
/******/        scriptUrl = scriptUrl.replace(/#.*$/, "").replace(/\?.*$/, "").replace(/\/[^\/]+$/, "/");
/******/        __webpack_require__.p = scriptUrl + "../../../../../";
/******/    })();
/******/    
/************************************************************************/
/******/    
/******/    // startup
/******/    // Load entry module and return exports
/******/    // This entry module doesn't tell about it's top-level declarations so it can't be inlined
/******/    var __webpack_exports__ = __webpack_require__(797);
/******/    
/******/ })()
;
/*# sourceMappingURL=template.css.map*/

请问配置哪里错了?


问题原因与修复方案

核心错误点

  1. SCSS规则硬编码输出文件名:SCSS处理规则里的generator.filename固定写死了template.min.css,所有SCSS入口都会输出到这个路径,导致两个SCSS入口冲突。
  2. 未正确处理SCSS入口:Webpack默认把入口当JS模块处理,你直接将SCSS写入entry却没配置完整的loader链(缺css-loader和提取CSS的插件),导致Webpack生成了引用CSS资源的JS模块,而非真正的CSS文件。
  3. 缺少CSS压缩配置:仅配置了JS的Terser压缩,没有处理CSS压缩,无法生成真正的.min.css。

修复后的配置

需要引入mini-css-extract-plugin提取CSS,css-minimizer-webpack-plugin压缩CSS,调整入口和规则如下:

const path = require('path');
const CopyPlugin = require('copy-webpack-plugin');
const TerserPlugin = require("terser-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");

const dist_path = path.resolve(__dirname, 'dist');
const media_root_path = 'media/templates/site/tofu';

module.exports = {
  devtool: 'source-map',
  entry: {
    // JS入口:只保留一个,通过优化生成压缩版
    'js/template': './src/js/template.js',
    // SCSS入口:只保留一个,通过优化生成压缩版
    'css/template': './src/scss/template.scss',
  },
  output: {
    path: dist_path, 
    filename: `${media_root_path}/[name].js`,
    // 每次构建清理dist目录
    clean: true
  },
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        // 可选:添加babel转译JS,兼容旧浏览器
        use: ['babel-loader']
      },
      {
        test: /\.scss$/,
        exclude: /node_modules/,
        use: [
          // 将CSS提取为独立文件
          MiniCssExtractPlugin.loader,
          // 解析CSS中的@import和url()
          'css-loader',
          // 编译SCSS到CSS
          {
            loader: 'sass-loader',
            options: {
              sourceMap: true,
              implementation: require('sass'),
            },
          },
        ],
      },
    ],
  },
  plugins: [
    new CopyPlugin({
      patterns: [
        { from: './src/index.php', to: `${dist_path}/index.php` },
        { from: './src/joomla.asset.json', to: `${dist_path}/joomla.asset.json` },
        { from: './src/templateDetails.xml', to: `${dist_path}/templateDetails.xml` },
        { from: './src/images', to: `${dist_path}/${media_root_path}/images` },
      ],
    }),
    new MiniCssExtractPlugin({
      // 生成未压缩的CSS文件
      filename: `${media_root_path}/[name].css`,
    }),
  ],
  optimization: {
    minimize: true,
    minimizer: [
      // 为未压缩JS生成.min.js
      new TerserPlugin({
        include: /\.js$/,
        exclude: /\.min\.js$/,
        filename: `[name].min.js`,
      }),
      // 为未压缩CSS生成.min.css
      new CssMinimizerPlugin({
        include: /\.css$/,
        exclude: /\.min\.css$/,
        filename: `[name].min.css`,
      }),
    ],
  },
};

关键调整说明

  • 合并重复入口:只保留一个JS和一个SCSS入口,通过压缩插件自动生成对应的.min版本,避免文件名冲突。
  • 完整SCSS loader链:用MiniCssExtractPlugin.loader提取CSS,css-loader解析CSS语法,sass-loader编译SCSS,最终生成真正的CSS文件。
  • 自动生成压缩文件:通过TerserPlugin和CssMinimizerPlugin分别处理JS和CSS的压缩,无需手动配置多个入口。

安装依赖

执行以下命令安装新增的依赖:

npm install mini-css-extract-plugin css-minimizer-webpack-plugin css-loader babel-loader @babel/core @babel/preset-env --save-dev

内容的提问来源于stack exchange,提问作者Denis Steinman

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.18 18:56:59