Sass將Unicode編譯成文字字元導致icon亂碼問題

記得要微笑發表於2021-10-19

發現問題

最近在使用公司元件庫中的穿梭框元件時發現icon圖示全都亂碼了

image-20211017142911623.png

分析問題

經排查發現,元件樣式檔案(scss)引入的iconfont向量圖示字型,構建時,\e601這類Unicode字元在經過sass編譯後就變成了文字字元(雙位元組字元),導致出現亂碼

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

Sass編譯後

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

解決問題

(1)升級sass版本

image-20211017151101980.png

Sass@1.38.0版本中對這個問題做了修復,可以將專案中使用的版本上級到1.38.0+,詳情檢視Sass更新日誌

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

執行npx sass@1.38.0 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開發入門

相關文章