umijs / father

NPM package development tool
MIT License
2.12k stars 273 forks source link

[RFC] 提供 CSS 预处理器编译能力 #600

Open wjq990112 opened 1 year ago

wjq990112 commented 1 year ago

背景

514

配置项

在公共配置的中新增一项配置:

css

css.preprocessorsOptions

配置预处理器额外的配置项。

css.postcssOptions

umd.postcssOptions,兼容。

css.autoprefixer

umd.autoprefixer,兼容。

css.theme

umd.theme,兼容。

同时在 esm/cjs/umd 的子配置项下均可覆盖公共配置,但覆盖方式为了兼容 umd 目前已有的 postcssOptions 等选项,因此去掉 css 这一层级。

// .fatherrc.ts
import { defineConfig } from 'father';

export default defineConfig({
  css: {
    preprocessors: {
      less: {},
    },
    postcssOptions: {},
  },
});

同时,也支持在 esm/cjs 的子配置项中覆盖。

// .fatherrc.ts
import { defineConfig } from 'father';

export default defineConfig({
  esm: {
    preprocessors: {
      less: {},
    },
    postcssOptions: {},
  },
});

产物

Bundless 的产物目前不会对引入 CSS 预处理器的 import 语句做转换,因此还需要额外的 JS 编译流程将 import 语句转换成 CSS:

- import './App.less';
+ import './App.css';

同时 CSS 预处理器的代码也应该被编译成 CSS:

源码:

@class-prefix-button: ~'adm-button';

.@{class-prefix-button} {
  display: inline-block;
  &:focus {
    outline: none;
  }
}

产物:

.adm-button {
  display: inline-block;
}
.adm-button:focus {
  outline: none;
}

如何实现

初步了解发现可以通过在 builder/bundless/loaders 中实现 CSS 预处理器相关的 transformer 即可。

由于需要额外的 JS 编译流程将 import 语句转换成 CSS,因此可能需要修改 js-loader 中的部分代码,在配置项开启时使用 babel | esbuild | swc 插件将 import 语句进行转换。

PeachScript commented 1 year ago

几点想法:

  1. 预处理器的包有额外尺寸,建议默认不集成任何预处理器,用户依赖里装了什么就处理什么(比如装了 less 才能编译 *.less
  2. preprocessor => style.preprocessors(或者不要复数?没想好),子项用于启用 + 传递预处理器配置项(比如 style.preprocessors.less 的类型可以是 { implemention: xxx, lessOptions: xxx },与 lessLoader 类似)
  3. umd.theme 可以作为基础公共配置了,自动组装给 2 的配置项
  4. 要不要支持类似 style.keepSource 的选项,继续平行输出原始样式表文件,这点没想好;纠结的点在于,输出是 antd v4 的实践,会被大量组件库借鉴,但 antd v5 又抛弃了该实践,支持又变得守旧,可能背后的问题是:构建工具是否应该从根源上推大家用 CSS-in-JS 的动态主题方案
  5. 怎么约束用户的用法,比如:不能引入三方包的 less/sass/stylus
wjq990112 commented 1 year ago
  1. 预处理器的包有额外尺寸,建议默认不集成任何预处理器,用户依赖里装了什么就处理什么(比如装了 less 才能编译 *.less)

可以借鉴 Vite 的解决方案,预处理器作为运行时依赖,当配置项开启但缺少预处理器依赖时,抛出异常

  1. preprocessor => style.preprocessors(或者不要复数?没想好),子项用于启用 + 传递预处理器配置项(比如 style.preprocessors.less 的类型可以是 { implemention: xxx, lessOptions: xxx },与 lessLoader 类似)

good idea,这点我觉得也可以参考 Shared Options | Vite

  1. umd.theme 可以作为基础公共配置了,自动组装给 2 的配置项

get

  1. 要不要支持类似 style.keepSource 的选项,继续平行输出原始样式表文件,这点没想好;纠结的点在于,输出是 antd v4 的实践,会被大量组件库借鉴,但 antd v5 又抛弃了该实践,支持又变得守旧,可能背后的问题是:构建工具是否应该从根源上推大家用 CSS-in-JS 的动态主题方案

社区中对 CSS-in-JS 的争论仍然非常多,我不认为 father 作为构建这一层的工具,应该输出观点给到上层,或者说,对于非标准中的内容,father 本身应该是没有观点的,应该是见仁见智的。我觉得 CSS-in-JS 即使已经是 mui/antd 所推崇的组件库样式解决方案,但这只是冰山一角,太多的组件库仍然在使用传统的样式解决方案,且 CSS 变量出现后传统的样式解决方案也没有明显的短板。如果 father 在这个问题上携带观点,从技术产品的纬度上来看,会失去这部分使用传统样式解决方案的组件库的市场,我认为是不值得的

  1. 怎么约束用户的用法,比如:不能引入三方包的 less/sass/stylus

这里的用户指的是 father 的用户,还是使用 father 构建的组件库的用户? 如果是前者,或许可以在 father doctor 中加一条规则;如果是后者,貌似没有什么解决方案,后者的行为确实是不可控的 除了不能引入三方包的 less/sass/stylus 的这个问题,另外需要约束用户可能还有两个问题:

不能使用 CSS Modules 的问题应该比较好解决,使用了 CSS Modules 的语法直接报错 不能重复引入的避免打包产物冗余的问题,我觉得应该也比较好解决,有了 CSS 变量之后,在 less/sass/stylus 中引入 vars 文件的操作已经基本上用不到了,全局公共的样式管理可以按照 antdm 的方式来做,理论上已经可以避免打包产物冗余的问题,father 可以给一份指南

fz6m commented 1 year ago

预处理转换 less / scss 到 css 不难,但原文件中的 import 'style.less' 是很难转到 import 'style.css' 的,esbuild 是二进制的工具,不支持改 AST ,否则相当于用 AST 工具(如 babel 再读一遍),这就失去了打包快速的意义,此时选择 bundleless 不是好的选择。

一个逃生口是 swc 作为转译器是用 rust 写一个 swc 插件支持重写 .less -> .css (注意静态导入 import '' 和动态导入 import('') 两种场景),做好之后可以 at 我一下 🌹

wjq990112 commented 1 year ago

预处理转换 less / scss 到 css 不难,但原文件中的 import 'style.less' 是很难转到 import 'style.css' 的,esbuild 是二进制的工具,不支持改 AST ,否则相当于用 AST 工具(如 babel 再读一遍),这就失去了打包快速的意义,此时选择 bundleless 不是好的选择。

这点我觉得可以注明 SLA,明确告知用户会有构建速度上的损耗,没办法既要又要 同时默认使用的 transformer 是 babel,所以我理解这方面的影响还好

wjq990112 commented 1 year ago

@PeachScript @fz6m RFC updated.

fz6m commented 1 year ago

尽量用 rust ,以后都是 rust 的发展趋势,用 js 写的生命周期过一年两年就被迭代掉了还要改。

wjq990112 commented 1 year ago

尽量用 rust ,以后都是 rust 的发展趋势,用 js 写的生命周期过一年两年就被迭代掉了还要改。

Maybe it is too difficult for me.

wjq990112 commented 1 year ago

尽量用 rust ,以后都是 rust 的发展趋势,用 js 写的生命周期过一年两年就被迭代掉了还要改。

先渐进式实现吧,把 babel 的实现了再看看实现 swc/esbuild 的能力,我看到 #535 有自定义 esbuild 插件的需求,或许需要跟这个能力一起实现。