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

Angular 11升级到13后,LESS文件引用图片无法打包到dist目录

Angular13升级后自定义Webpack处理LESS背景图片路径异常问题

问题说明

使用angular-builders/custom-webpack自定义Webpack配置编译LESS到CSS,Angular11中LESS引用的图片能正常复制到dist目录;升级到Angular13后功能失效:背景图片URL变为本地绝对路径,且图片未被打包到dist目录。

自定义Webpack配置文件

const path = require('path');
const SpritesmithPlugin = require('webpack-spritesmith');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const CopyPlugin = require('copy-webpack-plugin');
const TIMESTAMP = Date.now();

module.exports = {
    entry: {
        courses: [path.resolve(__dirname, '../assets/less/courses.less')],
        styles: [path.resolve(__dirname, '../assets/less/styles.less')]
    },
    plugins: [
        new SpritesmithPlugin({
            src: {
                cwd: path.resolve(__dirname, '../assets/img/sprite_source'),
                glob: '*.{png,gif}'
            },
            target: {
                image: path.resolve(__dirname, '../assets/img/sprite.' + TIMESTAMP + '.png'),
                css: path.resolve(__dirname, '../assets/less/base/_sprite-source.less')
            },
            apiOptions: {
                cssImageRef: '../img/sprite.' + TIMESTAMP + '.png'
            }
        }),
        new MiniCssExtractPlugin({
            filename: '[name].css'
        }),
        new CopyPlugin({
            patterns: [
              { from: path.resolve(__dirname, "../assets/js/lib/conditional/site.js"), to: path.resolve(__dirname, "../assets/dist/js") },
            ],
          }),
    ],
    module: {
        rules: [
            {
                test: /\.less$/,
                exclude: [
                    path.resolve(__dirname, 'src'),
                ],
                use: [
                    MiniCssExtractPlugin.loader,
                    'css-loader',
                    'less-loader',
                ],
            },
        ]
    },
    resolve: {
        modules: ["node_modules", "spritesmith-generated"],
        alias: {
            Assets: path.resolve(__dirname, '../assets')
        }
    },
};

angular.json配置

{
  "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
  "version": 1,
  "newProjectRoot": "projects",
  "projects": {
    "xpl": {
      "root": "",
      "sourceRoot": "src",
      "projectType": "application",
      "architect": {
        "build": {
          "builder": "@angular-builders/custom-webpack:browser",
          "options": {
            "customWebpackConfig": {
              "path": "./webpack.config.js",
              "replaceDuplicatePlugins": false
            },
            "outputPath": "../assets/dist/ng-new",
            "index": "src/index.html",
            "main": "src/main.ts",
            "tsConfig": "src/tsconfig.app.json",
            "polyfills": "src/polyfills.ts",
            "stylePreprocessorOptions": {
              "includePaths": [
                "src/styles"
              ]
            },
            "assets": [
              "src/styles/svgs"
            ],
            "styles": [
              "src/styles.less",
              "./custom-bootstrap.scss"
            ],
            "scripts": []
          },
          "configurations": {
            "production": {
              "optimization": true,
              "outputHashing": "none",
              "sourceMap": false,
              "namedChunks": false,
              "aot": true,
              "extractLicenses": true,
              "vendorChunk": false,
              "buildOptimizer": true,
              "fileReplacements": [
                {
                  "replace": "src/environments/environment.ts",
                  "with": "src/environments/environment.prod.ts"
                }
              ]
            },
            "development": {
              "buildOptimizer": false,
              "optimization": false,
              "vendorChunk": true,
              "extractLicenses": false,
              "sourceMap": true,
              "namedChunks": true
            }
          }
        },
        "serve": {
          "builder": "@angular-builders/custom-webpack:dev-server",
          "options": {
            "browserTarget": "xpl:build",
            "customWebpackConfig": {
              "path": "./webpack.config.js",
              "replaceDuplicatePlugins": true
            }
          },
          "configurations": {
            "production": {
              "browserTarget": "xpl:build:production"
            }
          }
        },
        "extract-i18n": {
          "builder": "@angular-devkit/build-angular:extract-i18n",
          "options": {
            "browserTarget": "xpl:build"
          }
        },
        "test": {
          "builder": "@angular-devkit/build-angular:karma",
          "options": {
            "main": "src/test.ts",
            "karmaConfig": "./karma.conf.js",
            "polyfills": "src/polyfills.ts",
            "tsConfig": "src/tsconfig.spec.json",
            "scripts": [],
            "styles": [
              "src/styles.less",
              "./custom-bootstrap.scss"
            ],
            "assets": []
          }
        }
      }
    },
    "xpl-e2e": {
      "root": "e2e",
      "sourceRoot": "e2e",
      "projectType": "application",
      "architect": {
        "e2e": {
          "builder": "@angular-devkit/build-angular:protractor",
          "options": {
            "protractorConfig": "./protractor.conf.js",
            "devServerTarget": "xpl:serve"
          }
        }
      }
    }
  },
  "defaultProject": "xpl",
  "schematics": {
    "@schematics/angular:component": {
      "prefix": "xpl",
      "style": "less"
    },
    "@schematics/angular:directive": {
      "prefix": "xpl"
    }
  }
}

编译结果对比

Angular11生成的styles.css片段

.icon-export-white {
  background-image: url(sprite.1660595463966.png);
  background-position: -83px -588px;
  width: 16px;
  height: 20px;
}

Angular13生成的styles.css片段

.icon-export-white {
    background-image: url(file:///Users/bkoo/Documents/workspace/xplore/xplore-web/src/main/webapp/assets/img/sprite.1660598549301.png);
    background-position: -83px -588px;
    width: 16px;
    height: 20px
}

目录结构对比

Angular13的dist目录

Angular13的dist目录

Angular11的dist目录

Angular11的dist目录


解决办法

1. 修复css-loader的资源处理配置

Angular13适配的Webpack 5中,css-loader默认不再自动处理背景图等资源,需显式开启并添加资源规则:
修改自定义Webpack的module.rules:

module: {
    rules: [
        {
            test: /\.less$/,
            exclude: [path.resolve(__dirname, 'src')],
            use: [
                MiniCssExtractPlugin.loader,
                {
                    loader: 'css-loader',
                    options: {
                        url: true, // 强制开启URL解析
                        import: true
                    }
                },
                'less-loader'
            ]
        },
        // 添加图片资源打包规则
        {
            test: /\.(png|gif)$/,
            type: 'asset/resource',
            generator: {
                filename: 'img/[name].[contenthash][ext]' // 输出到dist/img下
            }
        }
    ]
},

2. 调整SpritesmithPlugin的输出路径

把雪碧图直接生成到dist目录,避免中间目录的路径问题:

new SpritesmithPlugin({
    src: {
        cwd: path.resolve(__dirname, '../assets/img/sprite_source'),
        glob: '*.{png,gif}'
    },
    target: {
        image: path.resolve(__dirname, '../assets/dist/ng-new/img/sprite.' + TIMESTAMP + '.png'),
        css: path.resolve(__dirname, '../assets/less/base/_sprite-source.less')
    },
    apiOptions: {
        cssImageRef: './img/sprite.' + TIMESTAMP + '.png' // 相对于CSS文件的路径
    }
}),

3. 补充angular.json的assets配置

确保原始图片资源能被Angular CLI打包:

"assets": [
    "src/styles/svgs",
    {
        "glob": "**/*",
        "input": "./assets/img",
        "output": "./img"
    }
]

4. 设置MiniCssExtractPlugin的publicPath

如果CSS和图片不在同级目录,需指定publicPath:

new MiniCssExtractPlugin({
    filename: '[name].css',
    publicPath: './' // 根据实际目录结构调整,确保能正确找到图片
})

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.22 15:33:34