requireModuleExtension 参数配置错引起的问题

2,605 阅读4分钟

受掘金上的一篇文章启发准备用vue练习写一个小界面,类似于书签管理。

我设计的界面很简单,在右上角创建一个悬浮的工具栏。思路是新建一个控件,里面用v-for加载方块,方块里放字体图标。

图片1.png

开始动工,新建一个index.less,主要负责项目的全局样式。然后下载字体图标 iconfont。引用字体图标有两种思路:

一是直接在main.jsimport,在打包时把css 抽离出来。

二是把iconfont当做静态资源打包时复制到指定位置,然后再 html中引用。

选择了第一种方法:在main.js中引用

    import "./assets/iconfont/iconfont.css"

为了省事从之前的项目中复制一段webpack配置

css: {
    extracttrue// 是否使用css分离插件 ExtractTextPlugin
    sourceMapfalse// 开启 CSS source maps?
    modulesfalse,
    loaderOptions: {
      less: {
        javascriptEnabledtrue //less 配置
      }
    }
  }

先编译一下试试,在终端里看到一行警告

WARN  "css.modules" option in vue.config.js is deprecated now, please use "css.requireModuleExtension" instead.

大意就是用新属性requireModuleExtension替换modules。之前遇到过这个警告,然后没在意就直接替换了。 把requireModuleExtension设为false。噩梦从此开始。

重新打包后发现字体图标不显示,也没加载woff文件

<div class="item">
    <i class='iconfont sk-tubiao_qingchu'></i>
</div>

查看css文件,发现css变成这种鬼样子了

.iconfont_iconfont_3MHMU {
  font-family: "iconfont" !important;
  font-size: 16px;
  font-style: normal;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}
 
.iconfont_sk-tubiao_qingchu_1wtxn:before {
  content: "\e85e";
}

多了个前缀和后缀。试着把iconfont 换成 iconfont_iconfont_3MHMUsk-tubiao_qingchu换成 iconfont_sk-tubiao_qingchu_1wtxn。正常显示了。

恕我孤陋寡闻之前没见过这种格式,初步猜测是css编译时出问题了。据我微薄的知识储备推测几个问题来源。

  1. 引用css方式不对
  2. css编译配置问题(css混淆,css压缩,css前缀后缀等)
  • 先看第一种推测
import "./assets/iconfont/iconfont.css"

并无不妥。换种加载方式在App.vue中引用

// app.vue
<style>
    @import "./assets/iconfont/iconfont.css";
</style>

编译正常。排除引用问题,也排除scope影响。

  • 看第二种推测

先搜索了关于css编译后加上前缀后缀的区别。搜出的结果看了一遍发现是在说使用postcss-loader针对浏览器不同添加的前缀,不是改变class名称。所以排除这种可能。

接下来再看css压缩:我设置的npm命令 为开发环境,没有改vue cli的配置,不会用到压缩,暂且跳过。

"serve""vue-cli-service serve --mode development"

然后就是css混淆了:一般使用 mini-css-extract-plugin 。查看参数说明没有找到添加前缀相关的参数。排除。


这几种猜测都没有找到答案,所以我只好转换思路,尝试了解vue cli默认配置,看是否能通过默认配置属性找出什么端倪。

vue cli配置文件在node_modules/@vue/cli-service/lib/config目录下。 配置文件一般有:

  • base.js: 默认公共的配置
  • prod.js: 生产环境的配置
  • app.js: 目标为app的相关配置
  • css.js: css、less、scss等样式相关的配置
  • dev.js: 开发环境配置

粗略浏览一遍css.js文件,发现一处眼熟的代码

 if (isCssModule) {
      cssLoaderOptions.modules = {
            localIdentName'[name]_[local]_[hash:base64:5]',
            ...cssLoaderOptions.modules
      }
}

结合 .iconfont_iconfont_3MHMU 类型大概可以推断3MHMUhash值。试着修改hash的位数[hash:base64:8],重新编译后发现css后缀变了。到这里,基本就可以确定问题所在了。

通过找isCssModule定义的位置,溯源找到

  // rules for normal CSS imports
  const normalRule = baseRule.oneOf('normal')
  applyLoaders(normalRule, !requireModuleExtension)
 
// requireModuleExtension即为 isCssModule 的值

requireModuleExtension 在入口函数内有定义

let { requireModuleExtension } = rootOptions.css || {}
    if (typeof requireModuleExtension === 'undefined') {
          if (loaderOptions.css && loaderOptions.css.modules) {
              throw new Error('`css.requireModuleExtension` is required when custom css modules options provided')
          }
      requireModuleExtension = true
}
 
// requireModuleExtension  根据css.requireModuleExtension 赋值,无则为true。

问题所在之处就在requireModuleExtension这个参数上了。经过搜索requireModuleExtensionmodules值相反。设requireModuleExtensiontrue即可解决问题。此参数是为解析 CSS Modules 模块使用的,为解决样式名冲突而在编译时按一定规则处理。

此刻羞愧难当,因为这个参数问题之前搜索过,我给忘了。

一次马虎造成的问题实属不该,特以此为戒。顺便总结一下这次通过问题寻找解决方法的思路,也为日后遇到其它问题提供一种结题思路。