受掘金上的一篇文章启发准备用vue练习写一个小界面,类似于书签管理。
我设计的界面很简单,在右上角创建一个悬浮的工具栏。思路是新建一个控件,里面用v-for加载方块,方块里放字体图标。
开始动工,新建一个index.less,主要负责项目的全局样式。然后下载字体图标
iconfont。引用字体图标有两种思路:
一是直接在main.js中import,在打包时把css
抽离出来。
二是把iconfont当做静态资源打包时复制到指定位置,然后再
html中引用。
选择了第一种方法:在main.js中引用
import "./assets/iconfont/iconfont.css"
为了省事从之前的项目中复制一段webpack配置
css: {
extract: true, // 是否使用css分离插件 ExtractTextPlugin
sourceMap: false, // 开启 CSS source maps?
modules: false,
loaderOptions: {
less: {
javascriptEnabled: true //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_3MHMU,sk-tubiao_qingchu换成 iconfont_sk-tubiao_qingchu_1wtxn。正常显示了。
恕我孤陋寡闻之前没见过这种格式,初步猜测是css编译时出问题了。据我微薄的知识储备推测几个问题来源。
- 引用css方式不对
- 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 类型大概可以推断3MHMU为hash值。试着修改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这个参数上了。经过搜索requireModuleExtension 与 modules值相反。设requireModuleExtension 为true即可解决问题。此参数是为解析 CSS Modules 模块使用的,为解决样式名冲突而在编译时按一定规则处理。
此刻羞愧难当,因为这个参数问题之前搜索过,我给忘了。
一次马虎造成的问题实属不该,特以此为戒。顺便总结一下这次通过问题寻找解决方法的思路,也为日后遇到其它问题提供一种结题思路。