发现问题

最近在使用公司组件库中的穿梭框组件时发现icon图标全都乱码了

分析问题

经排查发现,组件样式文件(scss)引入的iconfont矢量图标字体,构建时,\e601这类Unicode字符在经过sass编译后就变成了文字字符(双字节字符),导致出现乱码

.icon-ok:before {
  content: "\e601";
}

Sass编译后

.icon-ok:before {
  content: "";
}

解决问题

(1)升级sass版本

[email protected]版本中对这个问题做了修复,可以将项目中使用的版本上级到1.38.0+,详情查看Sass更新日志

// index.scss
.icon-ok:before {
  content: "\e601";
}

执行npx [email protected] index.scss index.css

// index.css
.icon-ok:before {
  content: "\e601";
}

/*# sourceMappingURL=index.css.map */

(2)自定义webpack loader

上面分析问题时说到构建时SassUnicode字符编译成文字字符,那么我们能不能在loader队列中sass-loader后加入我们自定义的loader,将CSS中的文字字符转译成Unicode字符呢?当然是可以的

const CONTENT_MATCH_REG = /(?<!-)content\s*:\s*([^;\}]+)/g; // 找出伪元素里content那块内容
const UNICODE_MATCH_REG = /[^\x00-\xff]/g; // 找出非单字节符
function fun(source) {
  this.cacheable(); // 利用缓存来提高编译效率
  source = source.replace(CONTENT_MATCH_REG, function (m, p1) {
    return m.replace(UNICODE_MATCH_REG, function (m) {
      return "\\" + m.charCodeAt(0).toString(16); //   m.charCodeAt(0)返回字符串第一个字符的 Unicode 编码,后面再转16进制,前面加一斜杠
    });
  });
  return source;
}

测试

let test = `.el-icon-ice-cream-square:before {
    content: "";
  }
`;
console.log(fun(test));
// 打印结果
// .el-icon-ice-cream-square:before {
//     content: "\e6da";
// }

可以参考:https://github.com/styzhang/c...

(3)自定义webpack plugin

开发webpack插件需要知道的几个必要条件:

  • 获取编译器 compiler 对象,通过这个对象能过获取包括config配置,资源文件,编译信息,钩子函数等信息
  • 编译阶段的生命周期函数,找到适合的钩子函数处理对应逻辑
  • 返回结果支持同步和异步两种方式
/**
 * 编码unicode字符串
 * encodeUnicodeChar('•') // 等价于 '\\2022';
 */
module.exports.encodeUnicodeChar = function (str) {
  let content = "";
  for (let i = 0; i < str.length; i++) {
    const codePoint = str.codePointAt(i);
    if (codePoint > 127) {
      content += "\\" + str.codePointAt(i).toString(16);
    } else {
      content += str.charAt(i);
    }
  }
  return content;
};

module.exports.encodeCssContentUnicodeChar = function (css) {
  return css.replace(
    /([{\s;]content:\s?['"])(.+?)(['"][;\s}])/g,
    (match, p1, p2, p3, offset, str) => {
      return p1 + module.exports.encodeUnicodeChar(p2) + p3;
    }
  );
};

在将构建后的生成的资源文件输出到目标目录之前执行,emit是一个异步系列钩子

const { encodeCssContentUnicodeChar } = require("./utils");

class CssContentUnicodePlugin {
  apply(compiler) {
    compiler.hooks.emit.tap("CssUnicodePlugin", function (compilation) {
      Object.keys(compilation.assets)
        .filter((filename) => /\.css$/.test(filename))
        .forEach((filename) => {
          const asset = compilation.assets[filename];
          let fileContent = asset.source();
          fileContent = encodeCssContentUnicodeChar(fileContent);
          asset.source = () => fileContent;
          asset.size = () => fileContent.length;
        });
    });
  }
}

module.exports = CssContentUnicodePlugin;

参考:

Sass更新日志

sass unicode字符编码问题

Sass:unicode转义没有保存在.css文件中

webpack篇-插件plugin开发

如何开发一个webpack loader

webpack loader开发入门

03-06 00:08