iconfont乱码分析与解决方案

问题:

项目使用 talebase-ui 组件的 iconfont 会偶尔出现乱码的情况:
1

问题定位分析:

定位到图标元素可以看到,组件的图标 Unicode 格式经 sass 编译后,都变成乱码格式了(错误地将 Unicode 字符作为文字字符编译输出):

图标未编译前:

1
2
3
.t-icon-search:before {
content: '\e651';
}

经 sass 编译后:

2

如果通过浏览器限速,模拟慢网速状态,则图标乱码的情况几乎每次必出现:
3

解决方案:

(1)node-sass 替换 sass(dart-sass):这是网上出现最多的建议,但考虑到目前项目使用的是 sass,切换 node-sass 需要环境配置的兼容,可能会有许多未知的问题出现,故没有采用。

(2) 升级 sass(推荐)

4

查看 sass 的更新日志,幸运的是在sass@1.38.0版本时修复了这个问题,将 Unicode 字符作为转义序列而不是文字字符输出。
目前项目安装的 sass 依赖版本是 1.32.0,升级至 1.48.0 最新版后:
5

(Unicode图标编译成功)

注意:升级 sass 版本后,项目可能会出现代码语法格式引起的报错(以下是宽度计算-号之间需加空格),修改成符合语法即可:
6

(3)采用自定义的 webpack loader:

问题原因是 sass 将 Unicode 字符当成文字字符进行编译了,也就是说可以通过在 webpack 配置中添加自定义的 loader 在 sass-loader 之前,实现将文字字符转译成Unicode字符再由sass编译。

转译原理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 文字字符转译:
.sass::after {
content: "中国";
}
.icon-content::after {
content: "?";
}

// Unicode字符转译:
.sass::after {
content: "\4e2d\56fd";
}
.icon-content::after {
content: "\e6df";
}

webpack loader 实现:

1
2
3
4
5
6
7
8
9
10
11
12
// css-unicode-loader.js
const UNICODE_MATCH_REG = /[^\x00-\xff]/g;
const CONTENT_MATCH_REG = /(?<!-)content\s*:\s*([^;\}]+)/g;

module.exports = function (source) {
source = source.replace(CONTENT_MATCH_REG, function (m) {
return m.replace(UNICODE_MATCH_REG, function (m) {
return "\\" + m.charCodeAt(0).toString(16);
});
});
return source;
}

webpack 配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// vue.config.js
module.exports = {
configureWebpack: config => {
const sassLoader = require.resolve('sass-loader')
config.module.rules
.filter(rule => {
return rule.test.toString().indexOf('scss') !== -1
})
.forEach(rule => {
rule.oneOf.forEach(oneOfRule => {
const sassLoaderIndex = oneOfRule.use.findIndex(
item => item.loader === sassLoader
)
oneOfRule.use.splice(sassLoaderIndex, 0, {
loader: require.resolve('./css-unicode-loader')
})
})
})
}
}