news 2026/10/13 1:39:32

深入解析 @farmfe/plugin-vue-jsx:Farm 原生的 Vue JSX/TSX 编译插件设计与实现

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
深入解析 @farmfe/plugin-vue-jsx:Farm 原生的 Vue JSX/TSX 编译插件设计与实现
  • 前端构建
  • 构建工具
  • 开发工具

【免费下载链接】farm

Extremely fast Vite-compatible web build tool written in Rust

项目地址:https://gitcode.com/gh_mirrors/fa/farm
点击查看免费下载

本文围绕 Farm 仓库中的设计文档 2026-05-30-plugin-vue-jsx-design.md 展开,系统讲解@farmfe/plugin-vue-jsx这款 Farm 原生 Rust 插件如何把 Vue JSX/TSX 语法(v-model、v-show、v-html、插槽等指令)在构建期转换为 Vue 3 运行时调用。文章将带你走通插件从配置、Hook 触发、AST 变换到产物生成的完整链路,掌握全部 7 个配置项的作用与默认值,并理解其与旧方案(@vitejs/plugin-vue-jsx/swc-plugin-vue-jsx)的架构差异。读者读完后,可以独立配置并调优该插件,也能直接读懂其 Rust 源码与测试夹具。

背景:为什么需要 Farm 原生 Vue JSX 插件

在@farmfe/plugin-vue-jsx出现之前,Farm 用户要在 Vue 项目里使用 JSX/TSX,通常需要两条路:

  1. 借助 Farm 的 Vite 插件适配器,把@vitejs/plugin-vue-jsx挂载到vitePlugins上运行;
  2. 使用以 WASM 字节码形式分发的 SWC 插件swc-plugin-vue-jsx。

这两种方案都绕了路:Vite 适配器带来一层额外的兼容开销;SWC 插件以 WASM 形式运行,与 Farm 主链路存在二进制交互成本。而 Farm 自身就是通过farmfe_toolkit以原生库方式集成 SWC 的——那么与其在 WASM 沙箱里跑一个 JSX 转换器,不如直接把 SWC 的 visitor 以原生 Rust 插件形态嵌入 Farm 的编译管线,一次性消除 WASM 开销和 Vite 适配器依赖。这正是设计文档 Summary 给出的核心动机。

从仓库实际结构看,该设计已经落地:插件以独立 crate 形式存在于 rust-plugins/vue-jsx/,npm 包名为@farmfe/plugin-vue-jsx(见 package.json),并在 website/docs/plugins/official-plugins/vue-jsx.mdx 提供了官方文档。

插件目录结构与工程形态

设计文档规划的目录结构与仓库实际实现一一对应:

rust-plugins/vue-jsx/ ├── Cargo.toml # crate: farmfe_plugin_vue_jsx,cdylib + rlib ├── package.json # @farmfe/plugin-vue-jsx ├── options.d.ts # 共享的 TS 选项类型声明 ├── scripts/ │ ├── func.js # export default (options) => [binPath, options] │ ├── index.js # 平台二进制解析器 │ └── index.d.ts # TypeScript 声明 ├── npm/ # 各平台 npm 包(由 farm-plugin-tools 生成) ├── src/ │ ├── lib.rs # 插件实现:new()、transform hook │ ├── options.rs # Options 结构体 + serde 反序列化 │ ├── directive.rs # v-model/v-html/v-text/v-show/v-slots/自定义指令解析 │ ├── patch_flags.rs # Vue PatchFlags 位标志 │ ├── slot_flag.rs # SlotFlag 枚举(Stable / Dynamic) │ ├── util.rs # build_slot_helper、dedupe_props、decouple_v_models、transform_text、is_on、is_constant │ └── resolve_type.rs # defineComponent props/emits 的 TS 类型解析 └── tests/ ├── mod.rs # 集成测试 └── fixtures/ # 从 swc-plugin-vue-jsx/test/fixture/ 移植而来

工程形态上有几个值得注意的点:

  • crate 双形态:Cargo.toml 中crate-type = ["cdylib", "rlib"],cdylib用于编译成原生二进制供 Farm 通过 napi 加载,rlib用于本地单元测试直接链接调用;
  • 平台二进制分发:package.json 的napi.targets列出了 x64/arm64、linux-gnu/musl、windows-msvc、darwin 共 9 个目标平台,scripts/index.js 按process.platform/process.arch运行时解析对应平台的.farm二进制(musl 通过process.report的glibcVersionRuntime判断);
  • JS 入口极薄:scripts/func.js 只有一行——export default (options) => [binPath, options],把用户传入的 options 原样透传给 Rust 侧,符合 Farm 原生插件“options 以 JSON 字符串传给 Rust”的约定。

插件数据流:从配置到产物

设计文档给出了 4 步数据流,与 lib.rs 的Plugin for FarmPluginVueJsx实现完全吻合:

  1. 用户配置:import vueJsx from '@farmfe/plugin-vue-jsx'; plugins: [vueJsx({...})],JS 侧将 options 透传;
  2. 插件实例化:Farm 调用FarmPluginVueJsx::new(config, options_json),把 options JSON 字符串存入options_json字段;get_options()在每次 transform 时通过serde_json::from_str反序列化(空字符串或解析失败则回落到Options::default());
  3. 逐模块 transform:对每个.jsx/.tsx模块,transformhook 先检查param.module_type是否为ModuleType::Jsx | ModuleType::Tsx(否则直接return Ok(None)放行),随后经farmfe_toolkit的parse_module把源码解析为 SWC AST,构造VueJsxTransformVisitor(携带 options、unresolved_mark、comments、resolved_path),执行ast.visit_mut_with(&mut visitor);
  4. 生成产物:用create_codegen_config+codegen_module重新代码生成,返回PluginTransformHookResult { content, module_type: Some(param.module_type.clone()), source_map: None, ignore_previous_source_map: false }。

一个细节:TSX 模块使用Syntax::Typescript(TsSyntax { tsx: true, .. })解析,普通 JSX 则用Syntax::Es(Default::default())——这保证了.tsx中的类型语法能被 SWC 正确消费。

Plugin Hook 面:只依赖 transform

设计文档明确:该插件只需要transform一个 hook,不需要load、config或update_modules。这一点在 lib.rs 中得到验证——实现里只有name()(返回"FarmPluginVueJsx")、priority()(返回 100)和transform()。

transform的输入输出契约如下:

输入类型说明
param.module_typeModuleType::Jsx \| ModuleType::Tsx仅处理 JSX/TSX 模块
param.contentString原始源码
param.resolved_path&str文件路径(用于错误上报)
输出类型说明
contentString转换后的 JS/TS 代码
module_typeOption<ModuleType>保持原类型(Jsx→Jsx,Tsx→Tsx)
source_mapOption<String>初始为None
ignore_previous_source_mapbool设计文档为true,实际实现为false,源码注释说明原因:AST 重写会使旧的 source map 失效

全部 7 个配置项详解

插件的 Options 结构体定义在 options.rs,使用#[serde(rename_all = "camelCase", default)]反序列化,保证与 options.d.ts 的 TS 类型一一对应。下表完整列出设计文档中的 7 个配置项:

interface VueJsxPluginOptions { /** Convert `on` / `nativeOn` attrs to use `@vue/babel-helper-vue-transform-on` */ transformOn?: boolean; // default: false /** Inject PatchFlags for optimized VNode updates */ optimize?: boolean; // default: false /** Custom element detection patterns (regex strings) */ customElementPatterns?: string[]; // default: [] /** Merge attribute objects with `mergeProps` from Vue */ mergeProps?: boolean; // default: true /** Enable object slot detection via `_isSlot` helper */ enableObjectSlots?: boolean; // default: true /** Custom pragma (e.g. `h`), overrides `createVNode` */ pragma?: string; // default: undefined (uses createVNode) /** Resolve TypeScript types for `defineComponent` props/emits */ resolveType?: boolean; // default: false }

transformOn:on/nativeOn 的运行时转换

当transformOn: true时,属性on与nativeOn会被包成对@vue/babel-helper-vue-transform-on默认导出的调用(lib.rs 中生成_transformOn(...)调用,并在模块头部自动插入import _transformOn from '@vue/babel-helper-vue-transform-on',见 lib.rs)。

需要特别提醒:这是一个用户侧的外部 JS 依赖,不随插件分发。开启该选项后必须在项目里手动安装:

pnpm add @vue/babel-helper-vue-transform-on

这与swc-plugin-vue-jsx、Vite 插件的既有行为保持一致(见设计文档 "Dependency mapping" 一节与 vue-jsx.mdx 的 transformOn 说明)。

optimize:VNode 更新优化的 PatchFlags 注入

optimize: true时插件会分析属性与插槽结构,向createVNode调用注入 PatchFlags 数字参数和动态属性名数组,让 Vue 3 运行时在 diff 时跳过不必要的检查。patch_flags.rs 定义了完整位标志:

pub struct PatchFlags: i16 { const TEXT = 1; // 1 << 0 const CLASS = 1 << 1; const STYLE = 1 << 2; const PROPS = 1 << 3; const FULL_PROPS = 1 << 4; const HYDRATE_EVENTS = 1 << 5; const STABLE_FRAGMENT = 1 << 6; const KEYED_FRAGMENT = 1 << 7; const UNKEYED_FRAGMENT = 1 << 8; const NEED_PATCH = 1 << 9; const DYNAMIC_SLOTS = 1 << 10; const HOISTED = -1; const BAIL = -2; }

分析逻辑在transform_attrs(lib.rs):出现展开属性(SpreadElement)或动态计算键时置FULL_PROPS;否则按class绑定 →CLASS、style绑定 →STYLE、非空动态属性集 →PROPS、非onclick/onUpdate:modelValue的事件绑定 →HYDRATE_EVENTS逐项累加;若同时存在ref或指令,则叠加NEED_PATCH。

customElementPatterns:自定义元素识别

该配置是一个正则字符串数组。is_component判断(lib.rs)会遍历这些 pattern:若标签名匹配任一正则,则视为自定义元素(按原生元素处理、不转换为组件插槽);同时标准 HTML/SVG 标签(来自css_dataset::tags)永远按原生元素处理。测试夹具 custom-element 展示了用法:

{ "customElementPatterns": ["foo"] }

配合transform_tag(lib.rs):对于匹配 pattern 的元素,生成的createVNode第一个参数是字符串字面量,而不是组件标识符。

mergeProps:属性对象合并

mergeProps: true(默认)时,遇到展开属性({...attrs})会把已有 props 收集起来,与展开对象一起交给 Vue 的mergeProps(...)调用合并(lib.rs)。展开属性还会触发has_dynamic_keys = true,从而产生FULL_PROPSPatchFlag。合并过程中 util.rs 的dedupe_props会做键去重:对重复的class、style和on*事件属性,把多个值收进数组;其余重复键保留首个。

enableObjectSlots:对象插槽检测

该选项控制是否注入_isSlot辅助函数。generate_slot_helper惰性生成名为_isSlot的私有标识符,build_slot_helper(util.rs)生成如下形态的函数定义并注入模块头部:

function _isSlot(s) { return typeof s === 'function' || (Object.prototype.toString.call(s) === '[object Object]' && !isVNode(s)) }

当组件 JSX 的唯一子节点是标识符/表达式时,插件会生成_isSlot(x) ? x : wrapChildren(...)三元表达式:如果子节点是函数或普通对象(且不是 VNode),说明调用方传的是“具名插槽对象”,直接原样透传;否则包成默认插槽。关闭该选项(disable-object-slot 夹具)则退化为总是包默认插槽。

pragma:自定义创建 VNode 的函数

优先级为:源码注释中的@jsxpragma > options.pragma > 默认createVNode。search_jsx_pragma(lib.rs)从模块及其语句的 leading comments 中扫描@jsx标记;get_pragma(lib.rs)再兜底 options.pragma,最后才从vue导入createVNode。测试夹具 custom-pragma-in-options 验证了 options 路径({"pragma": "custom"}),custom-pragma-in-comment 验证了注释路径。

resolveType:defineComponent 的类型推断

resolveType: true时,插件会从defineComponent(setup)的 setup 函数参数类型注解中提取 props/emits 类型,反推为运行时选项注入defineComponent的第二个参数(lib.rs 的visit_mut_call_expr与inject_define_component_option)。例如 resolve-props-types/options-object 夹具:

defineComponent((props: { foo: number }) => {}, { props: { foo: { type: Number, required: true } } });

底层 resolve_type.rs 实现了相当完整的类型降级:支持 interface / type alias、extends继承、interface 合并、索引访问类型(T[K])、交叉/联合类型、Partial/Required/Pick/Omit工具类型、以及把string|number|boolean等关键字类型映射为运行时构造器。生成mergeDefaults(...)调用时还会通过 comments 机制附加/*#__PURE__*/注解(resolve_type.rs),便于压缩器做无用代码消除。

指令转换机制:directive.rs 的解析核心

directive.rs 是整个转换器的指令解析核心。识别入口is_directive判断属性名是否以v-或v+ 大写字母开头(matches!(name.as_bytes(), [b'v', b'-' | b'A'..=b'Z', ..])),随后parse_directive按名称分发到五类:

  • v-html/v-text:分别转换为innerHTML/textContent属性,并标记为动态属性(见 v-html 夹具:<h1 v-html="<div>foo</div>" />→_createVNode("h1",{"innerHTML":"<div>foo</div>"},null));
  • v-model:解析出 value、argument(v-model:xxx)与 modifiers,按目标元素类型选择运行时指令——select→vModelSelect、textarea→vModelText,普通元素按type属性区分vModelCheckbox(checkbox)、vModelRadio(radio)、vModelText(其余字符串/无 type),动态类型则用vModelDynamic(lib.rs);
  • v-show:转换为withDirectives(vnode, [[vShow, value]]);
  • v-slots:提取标识符/对象形式的插槽对象,合并进组件 children;
  • 自定义指令(v-xxx):统一转换为resolveDirective("xxx")运行时解析。

v-model在组件与原生元素上的形态不同(lib.rs):

  • 组件上:编译为modelValue/onUpdate:modelValue属性和modelModifiers对象(对应v-model:xxx则是xxx/onUpdate:xxx/xxxModifiers),并通过$event => (value) = $event箭头函数完成双向绑定;
  • 原生元素上:走withDirectives+ 运行时指令(如vModelCheckbox)。

v-model的修饰符解析很灵活:属性名中的_后缀(v-model_trim)、数组字面量(v-model={[value, "arg", ["modifier"]]})、v-model:arg_modifier命名空间写法均支持。修饰符集合最终转换为对象字面量,如{"modifier": true}。

v-models(多模型绑定)是较特殊的一条路径:visit_mut_jsx_opening_element(lib.rs)在进入元素时先把v-models的二维数组属性解耦成多个独立v-model属性(util.rs 的decouple_v_models),再由常规指令流程逐一处理。看 v-models 夹具的转换结果:

// 输入 <C v-models={[[foo, ["modifier"]], [bar, "bar", ["modifier1", "modifier2"]]]} /> // 输出 _createVNode(C,{ "modelValue":foo,"modelModifiers":{"modifier":true},"onUpdate:modelValue":$event=>(foo)=$event, "bar":bar,"barModifiers":{"modifier1":true,"modifier2":true},"onUpdate:bar":$event=>(bar)=$event },null);

组件、插槽与子节点转换规则

is_component(lib.rs)决定 JSX 元素被当作组件还是原生元素:标准 HTML/SVG 标签、匹配customElementPatterns的标签、以及Fragment(已有导入时)和KeepAlive特判之外,大写开头的标识符、成员表达式均视为组件。transform_tag对组件的处理分三种:

  • 首字母大写、可在当前文件解析的标识符 → 保留原标识符;
  • 带unresolved_mark(无法从当前文件解析)的标识符 → 转换为resolveComponent("Name")调用;
  • JSXMemberExpr(如<A.B>)→ 保留成员表达式。

子节点转换规则(transform_children,lib.rs):

  • JSX 文本 →createTextVNode("...")(空文本被过滤;文本先经 util.rs 的transform_text做首尾 trim 与多行折叠);
  • 单个标识符/表达式子节点(组件场景)→ 结合enableObjectSlots生成_isSlot三元判断;
  • 函数/箭头函数子节点 → 包成{ default: () => ... }对象插槽;
  • 对象字面量子节点 → 直接作为插槽对象,optimize开启时追加_槽位标志;
  • 其余多节点 → 原生元素用数组、组件用{ default: () => [...] }包装。

SlotFlag(slot_flag.rs,Stable = 1/Dynamic = 2)在optimize模式下通过slot_flag_stack向上冒泡:子表达式若是本地标识符引用则标记为Dynamic,最终写入插槽对象的_键(与 Vue 编译器的SLOT_FLAG约定一致),并随插槽对象透传给运行时。

从 swc-plugin-vue-jsx 移植的关键差异

设计文档的 "Migration Change Log" 一节记录了移植过程中最有价值的工程决策,这些都在源码中得到了落实:

错误处理:HANDLER.with(...) → Result::Err

原 SWC 插件使用HANDLER.with(handler, handler.span_err(...))处理约 10 处诊断(如 v-text 缺表达式、类型引用无法解析、v-model 形状非法)。Farm 插件模型没有线程局部错误处理器,因此所有错误点统一返回Err(CompilationError::TransformError { resolved_path, msg })——例如解析失败、codegen 失败、非法 UTF-8 输出都会带[vue-jsx]前缀信息返回给编译管线(lib.rs)。另有一部分“软错误”直接用eprintln!("[vue-jsx] ...")打印提示后跳过该元素(如 v-models 参数不是二维数组),保证单点错误不拖垮整个模块编译。

注释基础设施

swc_plugin通过 SWC 插件运行器提供metadata.comments;Farm 侧则从swc_ecma_parser的解析选项中获得 comments。parse_module返回的comments会被传入 visitor,并在 codegen 时通过CodeGenCommentsConfig重新写回。注释机制服务于两处(设计文档明确指出):

  • resolve_type:在生成的mergeDefaults(...)调用上附加/*#__PURE__*/;
  • pragma 搜索:从 leading comments 中读取@jsx指令。

依赖映射

swc-plugin-vue-jsxfarmfe-plugin-vue-jsx
swc_corefarmfe_core+farmfe_toolkit+farmfe_macro_plugin
bitflagsbitflags(同 crate)
css_datasetcss_dataset(同 crate)
fnvfxhash(Farm 约定)
indexmapindexmap(同 crate)
regexfarmfe_core::regex(实际为regex+ 自定义 serde 包装,见 options.rs)
serdeserde(同 crate)

对应 Cargo.toml 的依赖声明:farmfe_core、farmfe_macro_plugin(提供#[farm_plugin]宏)、farmfe_toolkit(re-export SWC 的 parser/codegen/visit/atoms)、farmfe_toolkit_plugin_types(SWC 插件代理类型)、serde、bitflags、css_dataset、indexmap、fxhash、regex。其中regex被包装成Regex新类型并实现自定义Deserialize(options.rs),非法正则字符串会在反序列化时报出明确的a valid regex错误。

测试体系:43 个夹具的移植与验证

设计文档要求“移植 swc-plugin-vue-jsx 的全部 43 个测试夹具”。从 tests/fixtures/ 看,每个夹具包含input.jsx+output.js,需要不同选项的夹具额外携带config.json;resolve-props-types下还有disabled子目录验证“resolveType 关闭时不输出 props 选项”的反向行为。

测试入口 tests/mod.rs 提供两个核心工具函数:

  • make_plugin(options: &str):用Config::default()构造FarmPluginVueJsx;
  • assert_transform(fixture_name):读取夹具的 input/expected,以ModuleType::Tsx跑 transform 后做 trim 后的全等比较。

测试用例覆盖(与设计文档的示例完全一致):

#[test] fn v_model_with_checkbox() { assert_transform("v-model-with-checkbox"); }

具体夹具示例(v-model-with-checkbox):

// input.jsx <input type="checkbox" v-model={test} /> // output.js import{createVNode as _createVNode,vModelCheckbox as _vModelCheckbox,withDirectives as _withDirectives}from"vue"; _withDirectives(_createVNode("input",{"type":"checkbox","onUpdate:modelValue":$event=>(test)=$event},null),[[_vModelCheckbox,test]]);

这个输出同时印证了前文所述的多条规则:type="checkbox"选择vModelCheckbox运行时指令、组件内(此处为原生 input 元素特殊处理)生成onUpdate:modelValue赋值箭头函数、vue命名导入自动合并为单条 import 语句。此外还有一个专门测试ignores_non_jsx_files,验证.js模块会返回None而不被误转换。

v-show夹具则展示了 withDirectives 与文本节点转换的协同:

// <div v-show={x}>vShow</div> import{createTextVNode as _createTextVNode,createVNode as _createVNode,vShow as _vShow,withDirectives as _withDirectives}from"vue"; _withDirectives(_createVNode("div",null,[_createTextVNode("vShow")]),[[_vShow,x]]);

边界与不属于本插件的职责

设计文档 "Out of Scope" 明确划清了插件边界,避免与 Farm 其他模块混淆:

  • HMR:不需要实现。JSX 转换是幂等的,转换结果仍是普通 JS 模块,Farm 内置 HMR 直接接管;
  • Vue SFC 编译:.vue单文件组件由既有的@farmfe/plugin-vue处理(见 rust-plugins/vue/);
  • @vue/babel-helper-vue-transform-on打包:属于用户侧依赖,不随插件分发。

这也解释了为什么该插件只需transform一个 hook——SFC 解析、模块依赖更新都不在它的职责内,JSX/TSX 纯函数式变换与 Farm 编译管线的其他环节天然解耦。

官方集成方式与文档现状

按照 website/docs/frameworks/vue.mdx 的 "Integrating jsx" 一节,在 Vue 项目中启用该插件的标准配置为:

// farm.config.ts import { defineConfig } from '@farmfe/core'; import vue from '@farmfe/plugin-vue'; import vueJsx from '@farmfe/plugin-vue-jsx'; export default defineConfig({ plugins: [vue(), vueJsx()], });

官方插件文档 website/docs/plugins/official-plugins/vue-jsx.mdx 给出了带选项的写法(如同时开启optimize、transformOn、resolveType)与安装命令(npm/yarn/pnpm 三选一),并提醒 transformOn 模式下需额外安装@vue/babel-helper-vue-transform-on。设计文档中规划的“在官网框架页替换旧 jsx 方案、在 official-plugins 新增 vue-jsx.mdx”两项更新均已落地。

总结

@farmfe/plugin-vue-jsx是一个典型的 Farm 原生 Rust 插件样板:入口极薄、单 hook、纯函数式 AST 变换、用大量夹具做输出快照测试。它把 Vue 官方 JSX 编译器的能力(指令、插槽、PatchFlags、类型推导)以原生二进制形态嵌入 Farm 构建链,为 Vue + JSX/TSX 项目提供了零 JS 运行时开销的编译路径。无论你是要接入该插件,还是参考它编写自己的 Farm Rust 插件,设计文档 与 rust-plugins/vue-jsx/ 源码都是一份自洽、可验证的完整参考资料。

  • 前端构建
  • 构建工具
  • 开发工具

【免费下载链接】farm

Extremely fast Vite-compatible web build tool written in Rust

项目地址:https://gitcode.com/gh_mirrors/fa/farm
点击查看免费下载
上一篇:WiFi-DensePose医疗应用:远程患者监测的新突破
下一篇:推荐一款优雅的iOS阴影效果库:ShadowImageView

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/13 1:38:32

java项目实战滑雪场管理系统[文档+开题+PPT

&#x1f345;选题推荐——以防找不到我们&#xff0c;点击上方订阅专栏✌✌\ Java毕设实战项目 Python毕设项目源代码 asp.net毕业设计项目 Uniapp安卓毕业设计项目 node.js毕业设计项目 python毕业设计 微信小程序毕业设计项目 php毕业设计 &#x1f447;&#x1f3fb;&#…

作者头像 李华
网站建设 2026/10/13 1:38:29

AI 时代的:一个老程序员的转型、困境与自白

一、我曾经的"黄金时代"作为一个写了二十多年代码的传统程序员&#xff0c;我至今怀念以前的工作模式。那时候&#xff0c;一个性能问题、一个 bug、甚至一个莫名其妙的坑&#xff0c;常常让我在代码里兜兜转转几天、几个星期。但解决之后那种满满的成就感&#xff0…

作者头像 李华
网站建设 2026/10/13 1:37:41

图分析引擎技术方案调研:需求拆解、选型维度与落地路线

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/13 1:37:34

Spring AOP拦截器链与proceed()递归调用原理深度解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/13 1:37:18

PyTorch官方指南翻译实践:从术语表到代码验证的完整复盘

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华