@css-blocks/cli 实战指南:CSS Blocks 命令行校验与 BEM 转换工具全解析

原创2026-10-06 09:58:071,331 阅读
文章标签:前端构建工具

@css-blocks/cli 实战指南:CSS Blocks 命令行校验与 BEM 转换工具全解析

导读

@css-blocks/cli 是 CSS Blocks 项目提供的一个轻量命令行界面,用于在不引入完整构建工具链的情况下,对 block 文件进行语法校验(validate)和 BEM 样式到 block 语法的迁移转换(convert)。本文基于 packages/@css-blocks/cli/CHANGELOG.md、packages/@css-blocks/cli/README.md 与其源码、测试,系统讲解该 CLI 的安装方式、两条核心命令、全局选项、错误输出的演进历程与源码级实现原理,读完即可在命令行中完成 block 文件的校验、导入解析、预处理器接入与 BEM 迁移。注意:本工具定位为“简单交互界面”,官方明确表示不打算取代正式的构建工具集成,适合快速验证与小型迁移场景。

一、CLI 的诞生与演进脉络

@css-blocks/cli 的版本历史完整记录在其 CHANGELOG.md 中,从这些变更可以看出它从无到有、逐步成熟的过程,也是理解其能力边界的钥匙:

  • 0.22.0(2019-05-02):完成 @css-blocks/cli 的初始实现,并同时加入预处理器(preprocessor)支持——CLI 由此可以校验 .scss 等非原生 CSS 的 block 文件。
  • 0.23.0(2019-05-08):修复并声明 bin 脚本,使 css-blocks 成为可直接调用的命令;同年 0.23.1 补充缺失的 bin 脚本。
  • 0.23.1 → 0.24.0(2019-09-16):错误输出进入“体验优化期”,引入通过 sourcemap 显示选择器错误位置、显示 block 导入引用、从仅记录起始位置改为记录完整范围(range),并在上下文中展示源文件内容。
  • 1.0.0-alpha.0(2019-11-22):CLI 开始使用配置文件(基于 @css-blocks/config 的搜索机制)。
  • 1.0.0-alpha.5(2020-02-14):为 BEM 转换新增专门包 @css-blocks/bem-to-blocks,并修复 :scope 选择器与状态输出、将 parseSync 重命名为 parseRoot。
  • 1.0.0(2020-04-04):移除对 Node 6/8/11 的支持(破坏性变更),Node 10 与 12+ 成为受支持版本。
  • 1.4.0(2020-09-04):正确统计 block 文件中的错误数;支持向 validate 命令传递目录并自动发现其中的 block 文件。
  • 1.5.0(2020-09-16):仅版本号升级,无功能变更。

这些演进在今天的命令行帮助文本与源码中均有对应体现,下面逐一展开。

二、安装与快速上手

安装方式

作为 npm 包安装到项目依赖:

yarn add @css-blocks/cli

也可以不安装、直接通过 npx 调用(适合一次性校验或迁移):

npx @css-blocks/cli validate blocks/*.block.css
npx @css-blocks/cli convert my-bem-file.css

安装后,包会通过 bin 字段注册 css-blocks 命令(见 package.json 中的 "bin": { "css-blocks": "./bin/css-blocks" })。环境要求方面,package.json 中声明 "engines": { "node": "10.* || >= 12.*" },与 CHANGELOG 中 1.0.0 移除 Node 6/8/11 支持的破坏性变更一致——旧版 Node 用户需升级到 Node 10 或 12+ 才能使用。

命令总览

执行 css-blocks --help 可看到完整的用法说明:

css-blocks <cmd> [options] block-dir-or-file...

Commands:
  css-blocks validate <blocks..>  Validate block file syntax.
  css-blocks convert <files..>    Convert BEM syntax to block file syntax.

Options:
  --version        Show version number                                [boolean]
  --preprocessors  A JS file that exports an object that maps          [string]
                   extensions to a preprocessor function for that type.
  --npm            Allow importing from node_modules                  [boolean]
  --alias          Define an import alias. Requires two arguments:      [array]
                   an alias and a directory.
  --help           Show help                                          [boolean]

从源码看(src/index.ts),参数解析基于 yargs:scriptName("css-blocks") 设置命令名,.strict() 开启严格模式(未知命令/选项会报错),.demandCommand(1, "No command was provided.") 强制至少提供一个命令。

三、validate:block 文件语法校验

基本用法

校验一个或多个 block 文件:

css-blocks validate blocks/*.block.css
css-blocks validate simple.block.css error.block.css

校验通过时输出绿色 ok 标记与相对当前工作目录的文件路径;存在错误时输出红色 error 标记、错误信息、出错位置(文件:行:列)与带上下文的代码片段,最后汇总错误总数并以非零退出码结束。

校验目录并自动发现 block 文件(1.4.0 新增)

自 1.4.0 起,validate 命令支持传入目录:CLI 会用 glob 以 **/*.block.* 模式递归发现目录下的所有 block 文件并逐一校验(源码见 src/index.ts 中 fse.statSync(blockFile).isDirectory() 分支)。例如:

css-blocks validate path/to/blocks-dir

对应的测试用例 "can check syntax for all block files in a directory"(见 test/cli-test.ts)会 chdir 到 fixtures 目录后校验整个 basic 目录,并断言退出码为 6(即目录下 6 个文件共发现 6 个错误)。

退出码与错误计数(1.4.0 修复)

CHANGELOG 1.4.0 修复了“Correctly count the errors in block files”。源码中 validate 方法维护 errorCount:单个 CssBlockError 计 1;MultipleCssBlockErrors 会遍历其 errors 逐个累加;CascadingError 则递归下钻到根因错误(error.cause)计数。校验结束后:

if (errorCount) {
  this.println(`Found ${this.chalk.redBright(`${errorCount} error${errorCount > 1 ? "s" : ""}`)} in ${resolvedBlockFiles.size} file${resolvedBlockFiles.size > 1 ? "s" : ""}.`);
}
this.exit(errorCount);

即以错误总数作为进程退出码——测试中目录校验返回 6、单文件错误返回 1、无错误返回 0 都印证了这一点。因此 CI 中可以直接用 css-blocks validate 的退出码判断是否通过。

错误输出的演进与源码级实现

0.24.0 是错误输出体验的分水岭,以下能力全部体现在当前 handleCssBlockError / displayError / displaySnippet 的实现中(src/index.ts):

  • 显示文件与错误范围:fileForError 通过 error.location 获取出错文件,并用 errorHasRange(loc) 判断是否携带完整范围信息(0.24.0“Track ranges instead of only the start position”)。
  • 展示源代码上下文:displaySnippet 调用 extractLinesFromSource(src/extract-lines-from-source.ts)读取源文件并截取错误行前后各 1 行的片段,错误行以红色加下划线高亮,行号前缀加粗。
  • 导入引用链:CascadingError 场景下会先输出根因文件的 error,再输出引用方文件的 caused 行(对应 0.24.0“Display block import references in error output”)。测试用例 "correctly displays errors in referenced blocks." 完整展示了这种两层输出:先打印被导入的 error.block.css 中的 .foo.bar 冲突,再打印 transitive-error.block.css 中 @block error from "./error.block.css" 处的 caused 信息。
  • sourcemap 映射:displayError 中若 hasMappedPosition(loc) 为真,会先显示 “At compiled output of …”,再显示 “Source Mapped to …” 两段代码片段(对应 0.24.0“Display selector error locations using sourcemaps”与“Use sourcemaps for errors involving non-selector nodes”)。测试中 SCSS 校验用例的输出正是这种“编译产物定位 + 源码映射定位”的双段格式。

一个最小可复现的错误示例——将两个类选择器写在同一元素上(Two distinct classes cannot be selected on the same element,见 test/fixtures/basic/error.block.css):

.foo.bar {
  color: red;
}

执行 css-blocks validate error.block.css 的输出大致为:

error	test/fixtures/basic/error.block.css
	Two distinct classes cannot be selected on the same element: .foo.bar
	At test/fixtures/basic/error.block.css:1:5
	1: .foo.bar {
	2:   color: red;
Found 1 error in 1 file.

四、全局选项:preprocessors、npm 与 alias

三个全局选项(global: true)对所有命令生效,定义在 src/index.ts 的 argumentParser 中。

--preprocessors:接入 SCSS 等预处理器(0.22.0 起)

css-blocks validate --preprocessors ./preprocessors.js blocks/*.block.scss

参数值是一个 JS 文件的路径,该文件导出一个以扩展名映射预处理函数的对象。以仓库测试中的 test/preprocessors.ts 为例,它导出 { scss },其中 scss 是一个异步函数,接收 (fullPath, content, configuration, sourceMap) 四个参数,内部用 node-sass 渲染 SCSS,返回 { content, sourceMap, dependencies }:

const scss: Preprocessors["scss"] = async (fullPath, content, _configuration, _sourceMap) => {
  return new Promise((resolve, reject) => {
    sass.render(
      {
        file: fullPath,
        outFile: fullPath.replace("scss", "css"),
        data: content,
        sourceMap: true,
        outputStyle: "expanded",
        indentedSyntax: false,
      },
      (err, result) => {
        if (err) {
          reject(err);
        } else {
          resolve({
            content: result.css.toString(),
            sourceMap: result.map.toString(),
            dependencies: result.stats.includedFiles,
          });
        }
      },
    );
  });
};

预处理产物会经过 BlockFactory 解析,且 sourceMap 字段正是上一节“编译产物定位 + 源码映射定位”双段错误输出得以成立的数据基础。源码中 validate 通过 require(path.resolve(options.preprocessors)) 加载该模块(src/index.ts)。

--npm 与 --alias:解析导入(0.23.0 起)

# 允许从 node_modules 导入 block
css-blocks validate --npm npm.block.css

# 定义导入别名(需要两个参数:别名与目录)
css-blocks validate --npm --alias basic ../basic alias.block.css
  • --npm:布尔开关,开启后 CLI 使用 NodeJsImporter(来自 @css-blocks/core)解析导入。
  • --alias:数组选项,必须成对提供“别名 + 目录”,且 implies: "npm" 意味着使用 --alias 时必须同时开启 --npm。源码中按每两个参数为一组构造 {alias, path} 列表并传入 NodeJsImporter(src/index.ts)。

npm 与 alias 均对应 CHANGELOG 0.23.0 的 “Imports via npm and aliases”。测试用例 "can import from node_modules" 与 "can import with aliases"(test/cli-test.ts)验证了这两种导入方式。

五、配置文件支持:从命令行参数到 css-blocks.config

1.0.0-alpha.0 起,CLI 使用配置文件(“CLI will use configuration files now”)。其实现是:validate 会以第一个 block 文件所在目录(无 block 文件时用当前工作目录)为起点,调用 @css-blocks/config 的 searchForConfiguration 搜索配置;CLI 命令行传入的 preprocessors 与 importer 会覆盖配置文件中的对应项(src/index.ts)。

配置搜索与加载逻辑见 packages/@css-blocks/config/src/index.ts:基于 cosmiconfig 查找 css-blocks.config 系列文件(JSON/JS 等),并在 transform 阶段把相对路径解析为绝对路径、把 preprocessors / importer 的字符串路径 require 成真实模块、支持 extends 继承其他配置。仓库测试夹具 test/fixtures/config-file/css-blocks.config.json 展示了最简配置形态:

{
  "preprocessors": "../../../dist/test/preprocessors.js"
}

配合该配置,css-blocks validate simple.block.scss 无需再传 --preprocessors 即可完成 SCSS 校验(对应测试 "can check syntax for a valid block file")。这样团队可以把公共的预处理器、导入器写进配置,命令行只保留文件路径参数。

六、convert:BEM 语法到 block 语法迁移(1.0.0-alpha.5 起)

第二个核心命令用于把传统 BEM 命名的 CSS 文件转换为 CSS Blocks 的 block 文件语法:

css-blocks convert my-bem-file.css

CHANGELOG 1.0.0-alpha.5 显示,为此新建了独立包 @css-blocks/bem-to-blocks,CLI 的 convert 命令只是其薄封装:argumentParser 中 convert 命令直接调用 convertBemToBlocks(argv.files),成功退出码 0、失败退出码 1(src/index.ts)。

转换行为由 test/convert-test.ts 给出实例:给定 BEM 文件

.nav { }
.nav--open { }
.nav__item { }
.nav__item--is-selected { }

执行 convert nav.css 后会生成 nav.block.css,且产物中必然包含 :scope(block 根选择器)。BEM 的 block(.nav)、元素(.nav__item)、修饰符(.nav--open、.nav__item--is-selected)会分别映射为 block 根、子样式与状态。转换器内部的完整实现可进一步阅读 packages/@css-blocks/bem-to-blocks/src 目录下的源码与 test 用例。CLI 侧在 1.0.0-alpha.5 还修复了转换相关测试失败与“Incorrect :scope selector and state output”问题。

七、CLI 在项目中的定位与使用建议

  • 定位是“轻量工具”而非构建插件:README 明确“Not meant to replace a proper build tool integration”。需要完整分析、重写与产物优化时,应使用仓库中 packages/@css-blocks/webpack、packages/@css-blocks/ember-cli、packages/@css-blocks/glimmer 等集成方案。
  • 适合快速验证与脚本化:validate 支持单文件、多文件与目录发现,退出码即错误数,天然适合接入 CI 或 npm scripts 做语法门禁;convert 可在一键迁移 BEM 存量样式时充当脚手架。
  • 错误输出可读性强:自带文件路径、行号、上下文高亮、导入引用链与 sourcemap 双重定位,调试 block 语法问题时无需额外工具。
  • 保持依赖与 Node 版本匹配:当前包要求 Node 10.* 或 >= 12.*,且 1.0.0 起不再支持 Node 6/8/11。

八、参考资源

登录后查看全文
css-blocks