Skip to content

鼓励作者:欢迎 star 或打赏犒劳

解决 Rsbuild 和 Vite 中 sass 编译 Element 字体图标乱码问题

记录下我猪哥遇到的坑

最近我猪哥在把项目从 Vue CLI 迁移到 Rsbuild 的时候,遇到一个很头疼的问题:Element UI 的字体图标突然全都变成了乱码!

  1. 将原本基于 Vue CLI 的项目迁移到 Rsbuild
  2. sass 编译器从 node-sass 替换为 dart-sass
  3. 编译 Element UI 的样式文件时出现了字体图标乱码的问题

编译结果乱码(编辑器中显示成方块)

编译结果乱码

浏览器展示乱码(显示成方块或问号)

浏览器展示乱码

解决方案:强制将 content 内容转为 Unicode 编码

经过深入研究(找了些资料)和测试(改了些代码),我们找到了解决方案:

Sass 编译后、CSS 输出前,对字体图标的 content 属性值进行 Unicode 编码转换

转换编码

Vue CLI 配置

安装 css-unicode-loader

bash
npm install --save-dev css-unicode-loader

配置 vue.config.js

js
module.exports = {
  configureWebpack: (config) => {
    config.module.rules
      .filter((rule) => {
        return rule.test.toString().indexOf('scss') !== -1
      })
      .forEach((rule) => {
        rule.oneOf.forEach((oneOfRule) => {
          oneOfRule.use.splice(oneOfRule.use.indexOf(require.resolve('sass-loader')), 0, {
            loader: require.resolve('css-unicode-loader')
          })
        })
      })
  }
}

[Bug Report] 使用 dart-sass 打包 element icon 出现乱码

编写 Rsbuild 自定义插件处理乱码

js
import path from 'node:path'
import fs from 'node:fs'
import { defineConfig } from '@rsbuild/core'
import { pluginVue2 } from '@rsbuild/plugin-vue2'

export default defineConfig({
  source: {
    entry: {
      index: './src/main.js'
    }
  },
  plugins: [pluginVue2()],
  tools: {
    rspack: {
      plugins: [
        {
          name: 'rspack:content-unicode',
          apply(compiler) {
            compiler.hooks.afterEmit.tapPromise('ContentUnicodePlugin', async (compilation) => {
              // 构建输出目录
              const distDir = compilation.outputOptions.path
              // 获取所有资源列表
              const assets = compilation.getAssets()

              await Promise.all(
                assets.map(async ({ name }) => {
                  if (!name.endsWith('.css')) return

                  const filePath = path.join(distDir, name)
                  let css = await fs.promises.readFile(filePath, 'utf-8')
                  css = css.replace(/content:\s*(['"])(.*?)\1/g, (match, quote, contentStr) => {
                    const encoded = [...contentStr]
                      .map((char) => {
                        const code = char.charCodeAt(0)
                        if (code > 255) {
                          // 小写 Unicode,并补齐到 4 位
                          return '\\' + code.toString(16).toUpperCase().padStart(4, '0')
                        }
                        return char
                      })
                      .join('')
                    return `content: ${quote}${encoded}${quote}`
                  })
                  await fs.promises.writeFile(filePath, css, 'utf-8')
                })
              )
            })
          }
        }
      ]
    }
  }
})

编写 Vite 自定义插件处理乱码

js
import { defineConfig } from 'vite'
import { createVuePlugin } from 'vite-plugin-vue2'

// https://vite.dev/config/
export default defineConfig({
  plugins: [
    createVuePlugin(),
    {
      name: 'vite-plugin-content-unicode',
      apply: 'build',
      enforce: 'post',
      generateBundle(_, bundle) {
        for (const fileName in bundle) {
          const chunk = bundle[fileName]

          if (chunk.type === 'asset' && fileName.endsWith('.css')) {
            let css = chunk.source.toString()

            css = css.replace(/content:\s*(['"])(.*?)\1/g, (match, quote, contentStr) => {
              const converted = [...contentStr]
                .map((char) => {
                  const code = char.charCodeAt(0)
                  if (code > 255) {
                    return '\\' + code.toString(16).toUpperCase().padStart(4, '0')
                  }
                  return char
                })
                .join('')
              return `content: ${quote}${converted}${quote}`
            })

            chunk.source = css
          }
        }
      }
    }
  ],
  css: {
    preprocessorOptions: {
      scss: {
        api: 'modern-compiler',
        additionalData: `@import "element-ui/lib/theme-chalk/index.css";`
      }
    }
  },
  build: {
    assetsInlineLimit: 0,
    rollupOptions: {
      output: {
        manualChunks: undefined
      }
    }
  }
})

如有转载或 CV 的请标注本站原文地址