简介
最近团队推荐使用UnoCSS来进行CSS样式的书写,它有以下优势:
- 配置简单:仅需简单的几步即可完成。
- 原子CSS:你可以重复的书写很多相同的属性而不必担心其占用过多的体积。
- 书写体验佳:我们开发时不必再绞尽脑汁先给元素想个名字,然后上下滑动来改变对应元素的样式。
- 所见即所得:如
flex
表示弹性布局p-10px
表示10像素
的padding
等。
但也有很多缺点:
阅读体验差
:一个元素会因为多个class变得很长很长,同时也失去了语义化
,不好排查bug
。编译缓存问题
:Vue2中使用UnoCSS如果不删除cache-loader
,会导致UnoCSS样式不生效,也是本文研究的重点。
UnoCSS 是一个具有多种一流构建工具集成的同构引擎(包括PostCSS 插件)。这意味着 UnoCSS 可以在不同的地方更加灵活地使用(例如,CDN 运行时,可以动态生成 CSS),并且可以与构建工具深度集成,提供更好的 HMR、性能和开发者体验(例如,Inspector)。
VSCode Debug Terminial使用
如果你现在还不会使用VSCode进行本地npm
包(node环境)debug的话,那么赶快学习一下吧。VSCode Debug Terminal非常简单,很快就能学会,他能帮助我们快速定位问题
。
- 打开一个Debug Terminal:
- 运行指令:
在这里运行指令和我们在普通Terminal中运行指令一模一样,如:npm run serve
,但这样运行是没有断点的,我们需要去关键路径上打断点,如:
打完断点之后再运行指令,我们就能愉快的进行调试了,如:
进入之后对源码进行分析,在你认为是关键路径的地方打上断点,一点一点摸索即可。
原理
画图工具:excalidraw
整体架构
首先UnoCSS主包入口位于:node_modules/@unocss/webpack
中,主要流程为:
- 导出一个
WebpackPlugin
函数。 WebpackPlugin
函数调用位于node_modules/unplugin
导出对象的createUnplugin
方法。- 返回满足一个接收用户
自定义参数
的函数,执行后返回符合webpack plugin
协议的对象(包含一个apply方法
)。
编译流程
任务收集
在apply
方法里,他会判断传递进来的plugin
参数是否包含一个transform
方法,包含的话就会加载其同级webpack
目录下的transform loader
。
这个loader每次执行(rules匹配Vue文件等),都会调用plugin的transform方法,这个方法会收集task
任务,任务传递一个提取模块儿中关键字的extract
方法,执行后返回Promise对象,主要作用就是解析每个Vue文件中template
模版中的原子CSS声明
。
async function extract(code, id) {
if (id)
modules.set(id, code);
const len = tokens.size;
await uno.applyExtractors(code.replace(SKIP_COMMENT_RE, ""), id, tokens);
if (tokens.size > len)
invalidate();
}
清空任务
unocss plugin
订阅了webpack的optimizeAssets hook,在这个hook里,他清空了所有的任务,从而收集了模板里所有的token
。
compilation.hooks.optimizeAssets.tapPromise
钩子的执行时机是在 webpack 构建过程的资源优化阶段。具体来说,它在 compilation 对象的optimizeAssets
阶段触发。Webpack 的构建过程分为多个阶段,其中一个阶段是优化资源。在这个阶段,Webpack 会对生成的资源进行一些优化,例如压缩、混淆等。
compilation.hooks.optimizeAssets
钩子就是在这个优化资源的阶段触发的。
主要代码如下:(有删改)
compiler.hooks.compilation.tap(PLUGIN_NAME, (compilation) => {
compilation.hooks.optimizeAssets.tapPromise(PLUGIN_NAME, async () => {
await flushTasks();
const result = await uno.generate(tokens, { minify: true });
code = result.getLayer(layer)
compilation.assets[file] = new WebpackSources__default.RawSource(code);
});
});
cache-loader 缓存
了解了编译原理,那么就很好解释为何在Vue2中配置UnoCSS要加一条config:
config.module.rule('vue').uses.delete('cache-loader')
如果不加这一句,首次编译没有问题,因为所有的模块儿都没有缓存都能走完完整的loader链路
,也就会触发token
的收集,从而在最终的webpack hook:optimizeAssets
中可以统一的收集到所有的原子CSS
定义,从而通过unocss核心的工具方法generate
生成最终的CSS代码,并替换掉原来的静态CSS资源。
那么再次编译的时候(重新启动等),因为所有文件都有缓存了,那么只有被修改的文件可以收集到token
,从而出现所有的样式都不生效,而我们去改动某一个组件又发现仅仅这个组件的样式生效了(仅这个组件重新触发了loader链路,收集了token),而他依赖的组件的样式还是未生效的情况。
总结
- 在
loader
中收集token
。 - 在
optimizeAssets
钩子中清空所有task,收集全部token
。 - 调用
generate
方法生成相关css规则。 - 替换原有的CSS代码。
- UnoCSS样式是
运行时的
,由输入(变动的模块)决定输出(调整的CSS)。