tl;dr 东西在 github.com/shiyangzhao…

前言

前置资料,关于:

  • CSS Modules
  • Tailwind CSS

对于我来说,二者都是比较优秀的产品,在 VSCode 插件的加持下,都能得到比较好的开发体验。
CSS Modules:

一个 CSS Modules 转 Tailwind CSS 的东西
Tailwind CSS:
一个 CSS Modules 转 Tailwind CSS 的东西
先阐明一下,无论是 CSS in JS,或许 CSS Modules,又或许 Tailwind CSS,都是比较好的计划,选择合适自己的就好了(可保护,大小,namespace仍是要考虑的)。

我为什么要把 CSS Modules 转化成 Tailwind?

  1. 并不是每个人都能把 CSS Modules 用好,目前咱们的 CSS 文件,基本上很难保护了,在运用 CSS Modules 的前提下,仍然各种嵌套,抵触/覆盖的款式,然后整个文件的报错就类似下图(找了一个比较夸张的),当然它仍是正常作业的
    一个 CSS Modules 转 Tailwind CSS 的东西
  2. 咱们保护的多数是平台类型的项目,浅显点说,PC 端,咱们没有杂乱的款式和交互,在运用组件库的前提下,自定义的杂乱款式没有那么多。其次是,哪怕是咱们自己写的组件,我也是尽量把组件功用尽量细化,这样导致的结果是,经常某个独自功用的简略组件,哪怕 JS 的代码只要几十行乃至十几行,我仍然需求创建相应的 CSS 文件去束缚它的款式,CSS 文件也或许只要几行或许十几行,整个进程仍是比较苦楚。
  3. 做过 Tailwind CSS 的调研后,咱们都比较看好/喜爱(主要是我的 Leader ==)。
  4. 很多应用已经尝试运用了 CSS 原子化计划,例如 npm
    一个 CSS Modules 转 Tailwind CSS 的东西

关于可保护性问题,我的 Leader 是这样说的:“嗯,tailwind 是有学习本钱和记忆本钱在。utility classname 的命名规矩能够经过一些文档协助咱们下降记忆本钱。运用上的记忆本钱比较低,vscode tailwind 插件会提示每个 classname 的对应 css 明细,CR 会本钱提高。但 CR 一般也不看 CSS,一般只会 CR 代码逻辑。”

计划调研

最初决议去做转化的时分,列了两个计划,两个计划的差异也是比较大的

  1. 只修正 CSS 文件,class 命名仍是按照原有的命名方式。长处是更符合咱们新近的习气,apply 的效果类似于 mixin 和 composes,修正的本钱相对来说也比较小。缺陷是不那么原子化,官方也不是很推介运用 apply 的,本质上与 tailwind 的设计背道相驰
    // 转化前:
    .class {
        width: 100%;
        display: flex;
        align-items: center;
        justify-content: space-between;
    }
    // 转化后
    .class {
        @apply items-center flex justify-between w-full;
    }
    
  2. 对 CSS 文件处理的一起,处理组件的 className。长处是直接升级成原子化的 CSS,接入 tailwind 后,整体的代码风格也能得到统一;缺陷是手动改的话,危险和作业量都是巨大的。
    import style from 'index.module.css';
    // 转化前
    const Component = () => <div className={style.class}></div>
    // 转化后(抱负
    const Component = () =>
        <div className="items-center flex justify-between w-full"></div>
    

在不考虑作业量的前提下,计划二无疑是更好的,但咱们随意一个项目就有几百个 CSS 文件,计划二需求处理 CSS 文件的一起,也需求处理 JS 文件,那么咱们需求处理的文件大约就有 500+ 了,考虑投入产出比,咱们是不行能在这个上面投入很多人力和时间的,手动去做修正是不切实践的。

目标

手动修正的计划被否掉后,只能经过自动化东西的计划去处理相关问题。承认东西的功用和目标

  1. 处理 tailwind 初期接入时,咱们不太熟悉运用的状况下,能够运用该东西辅助转化 CSS Modules,即:虽然我接入了 tailwind,但并不是就一定要运用原子化的 CSS 进行开发。
  2. 处理整个工程项目的问题,我能够运用这个东西直接把我的项目进行转化,而不仅仅只要辅助效果。

计划承认

在承认计划之前,咱们先承认东西的进口,即我是怎么处理 CSS 文件和 JS 文件的,是否应该独自处理?我觉得不应独自处理,由于 JS 文件和 CSS 文件有显着的相关联系,CSS 文件是经过 import 声明引入到 JS 文件中的,咱们实践开发进程也是以组件为粒度开发的,那么咱们能够以 JS 文件为进口进行处理。

不以 CSS 文件作为东西进口的的另一个原因是,经过 JS 你能很轻松的查到引入的 CSS 文件,但假如以 CSS 文件为进口去查引证的话,这个查询是异常繁琐的,你需求查询每个 JS 文件,判断其是否引入了,当然你能够在初期查询悉数 JS 文件后树立相关联系,但如咱们前面所说,东西应该能够去处理单文件,所以仍是考虑 JS 作为东西进口。

不难看出,咱们是需求“修正代码“的,修正代码的意思是,东西需求改动你的代码内容,例如你能够运用replace根据旧的字符串来获取新的字符串,咱们这里其实也便是根据旧的代码发生新的代码,代码层次的修正,这种咱们一般称之为Codemod

这里咱们需求了解两个东西:

  1. jscodeshift
  2. postCSS

简略来说,两个东西都是能够处理 AST 树的,确定东西的流程

graph TD
Start --> TSX文件解析 --> 运用jscodeshit找到*.module.css --> 获取CSS路径 --> postCSS解析 --> 获取tailwindMap
获取tailwindMap --> 填充TSX的className --> Stop

或许遇到的问题

在承认计划今后,判断计划的可行性以及或许遇到的问题

CSS 依靠联系

CSS 文件存在循环依靠,一股脑解析会进入死循环,处理方式很简略,便是后解析+缓存的策略,由于 CSS 或许会被多个 JS 文件引证,缓存也能在一定程度避免重复解析形成的数据错误

一个 CSS Modules 转 Tailwind CSS 的东西

填充 className

项目中的 className 一般有以下几种状况

<div className="class1 class2"></div> // 1
<div className={style.selector}></div> // 2
<div className={style['class-test']}></div> // 3
<div className={style[`class-${count}`]}></div> // 4
<div className={`text ${style.selector}`}></div> // 5
<div className={classnames({ [style.class1]: boolean })}></div> // 6
<div className={classnames([style.class1, style.class2])}></div> // 7
const columns = [{
    // ... 
    className: style.class // 8
}];

依次进行分析

  1. 第1种状况是不需求考虑的,由于咱们运用的是 CSS modules
  2. 第3种经过 camlCase 的方式转成驼峰,实践作业中我是做了处理的,但我觉得这个和第4种都不应该处理,所以都列入不处理范围
  3. 2、5、6、7、8 类似都是也不同,AST 的操作会十分费事,考虑通用型,能够这样设计
    // 无论 style.class 在什么容器下,都去进行拆分
    {style.class} => {`${style.class} tailwind`}
    [style.class] => [`${style.class} tailwind`]
    { [style.class]: boolean } => { [`${style.class} tailwind`]: boolean }
    className: style.class  => className: `${style.class} tailwind`
    `${style.class} test` => `${`${style.class} tailwind`} test`
    
    缺陷便是会带来不必要的嵌套,但长处是肯定是没有问题的,能处理一切场景,AST 树的操作也不会太费事

是不是一切的 CSS 规矩声明都能提取出来

并不是,CSS Modules 里边写 global 款式,也有 atRule 的一些规矩,这些场景都不能直接提取 tailwind class,只能运用 apply 来辅助转化了

.class global: .global-class {
    display: flex;
}
.class:active {
    display: flex;
}
.selector1 {
    display: flex;
}
.selector2 {
    composes: selector1;
}
@supports (display: flex) {
    @media screen and (min-width: 900px) {
        .class {
            display: flex;
        }
    }
}

实践运用碰到的问题

解析 CSS 得到数据的缓存

由于大部分解析的逻辑都是写在 jscodeshift的 transform 逻辑中,jscodeshift考虑功能问题是会把文件的解析和操作分配到一个个子进程去履行的,假如你在 transform 中去缓存解析 CSS 得到的数据,一定是行不通的,由于每个进程是独立的,也便是数据是不行耐久化同享的,但是进程的分配是jscodeshift去做的,导致的结果是你也无法经过 IPC 通讯的方式把数据发到主进程里,让主进程去缓存解析的数据。

这里我是起了个 tcp 的 socket 服务,把数据的读写独自放到一个进程去履行了,transform 中需求存储数据只需求向这个 socket 服务发送音讯就能够了。

// 解析后缓存的内容
{
    'xx/index.module.css': {
        btn: ['text-base', 'cursor-pointer'],
    };
}

怎么提取 tailwind class

便是在一堆 CSS 规矩声明中提取被 tailwind 匹配的 class,考虑的主要问题是多个规矩组成的 tailwind class,例如:

一个 CSS Modules 转 Tailwind CSS 的东西
你需求在 n 个规矩声明中提取优先级最高(最多组成?)的 tailwind class,这里详细不细说了,感兴趣的话我再写自己的处理逻辑。

tailwind 的 class 权重问题

const Component = () => <div class={clsx(style.class1, style.class2)}></div>
.class1 {
    margin-left: 0.75rem;
}
.class2 {
    margin-left: 0.5rem;
}

margin-left 多少? 转化今后

const Component = () => <div class={clsx("ml-3", "ml-2")}></div>
// tailwind 中 'ml-3 ml-2' === 'ml-3'
// 但在你的 CSS 中,收效的是 0.5rem

结语

说的最多的是计划,技术细节说的不太多,技术方面触及的点有点多,细说要占很大篇幅,假如感兴趣,我再开一篇介绍jscodeshiftpostCSS的运用。 更多的运用阐明仍是看 github.com/shiyangzhao…

附上我当时处理完一个项目的截图(当时也写了个 CSS Modules 转 SCSS Modules的功用):

一个 CSS Modules 转 Tailwind CSS 的东西
处理 CSS Modules:
一个 CSS Modules 转 Tailwind CSS 的东西