编程 Oxc Angular 编译器深度实战:Rust 重写前端工具链的革命性突破——从架构设计到性能飙升的全链路解析

2026-05-07 23:07:41 +0800 CST views 30

Oxc Angular 编译器深度实战:Rust 重写前端工具链的革命性突破——从架构设计到性能飙升的全链路解析

前言:前端工具链的「氧化」革命

2026年4月,VoidZero 团队(由 Vue.js 和 Vite 创建者尤雨溪创立)发布了一个实验性的 Oxc Angular 编译器,用 Rust 重写了 Angular 的模板编译流程。基准测试数据令人震撼:在 Bitwarden 开源代码库上,构建速度提升了 20.7 倍;在 Super Productivity 项目上,比 Angular CLI 快 6.4 倍

这不是一个简单的「换语言重写」故事。它代表了前端工具链正在经历一场深刻的「氧化」(Oxidation)革命——用 Rust 这种系统级语言,重新思考编译器的架构设计。

本文将从技术架构、核心原理、性能优化策略到实战集成,全面剖析这个项目的技术内核。


一、背景:为什么 Angular 编译器需要重写?

1.1 传统 Angular 编译器的工作流程

Angular 的模板编译器(Template Compiler)是其核心组件之一。传统架构下,编译流程是这样的:

HTML Template → TypeScript Code Generation → TypeScript Compiler → JavaScript Output
     ↓                    ↓                        ↓                   ↓
   模板解析         类型安全的TS代码           全程序类型分析         最终产物

问题一:多层转换的开销

HTML 模板首先被转换为 TypeScript 代码,然后交由 TypeScript 编译器进行完整的语义分析和代码生成。这个过程涉及:

  1. 模板解析(Template Parsing):将 HTML 解析为 AST
  2. 类型推断(Type Inference):分析模板中的表达式类型
  3. 代码生成(Code Generation):生成 TypeScript 包装代码
  4. TypeScript 编译:运行完整的 tsc 编译流程

每一步都是串行的,且存在大量的中间表示转换。

问题二:全程序类型分析的代价

Angular 的编译器对模板生成的代码执行深度全程序类型分析(Whole-Program Type Analysis)。这意味着:

// 模板中的简单绑定
// <div>{{ user.name }}</div>

// 生成的 TypeScript 代码(简化示例)
class MyComponent_Template {
  // 类型检查器需要分析整个程序的类型关系
  static ngTemplateContextGuard(ctx: MyComponent): ctx is MyComponent {
    return true;
  }
  
  // 每个绑定都会生成类型安全的访问代码
  static build(ctx: MyComponent) {
    const $user = ctx.user;  // 需要类型推断
    return $user.name;       // 需要属性访问检查
  }
}

对于大型项目,这种全程序分析的时间复杂度呈非线性增长。

问题三:增量编译效率低

当修改一个组件时,传统编译器往往需要重新分析依赖图,重新执行类型检查。即使只改了一个模板中的变量名,也可能触发大范围的重新编译。

1.2 VoidZero 的解决方案思路

VoidZero 团队的核心洞察是:能否绕过 TypeScript 的语义分析,直接在模板编译阶段完成所有工作?

这需要:

  1. 原生模板编译器:直接将 HTML 模板编译为 JavaScript,不经过 TypeScript 中间层
  2. 轻量级类型系统:在 Rust 中实现最小化的类型推断,避免全程序分析
  3. Vite 深度集成:利用 Vite 的模块系统和 HMR 能力

这正是 Oxc Angular 编译器的技术路线。


二、Oxc 技术栈全景图

2.1 Oxc 项目家族

Oxc(Oxidation Compiler)是 VoidZero 开发的高性能 JavaScript 工具集合,全部用 Rust 编写:

┌─────────────────────────────────────────────────────────────────┐
│                         Oxc Ecosystem                            │
├─────────────────────────────────────────────────────────────────┤
│  Parser          │  JavaScript/TypeScript 解析器                │
│  Linter          │  ESLint 替代品,300+ 规则                    │
│  Resolver        │  模块解析器                                  │
│  Formatter       │  Prettier 替代品                             │
│  Minifier        │  代码压缩器                                  │
│  Transformer     │  语法转换器                                  │
│  Angular Compiler│  Angular 模板编译器(新增)                  │
│  Rolldown        │  基于 Rust 的 bundler,Vite 8+ 底层引擎      │
└─────────────────────────────────────────────────────────────────┘

这些组件共享:

  • 统一的 AST 定义:所有工具使用相同的 AST 结构,无需序列化/反序列化
  • 零拷贝设计:在 Rust 内存模型中,AST 节点直接引用源代码切片
  • 并行处理能力:Rust 的所有权模型天然支持无锁并行

2.2 NAPI-RS:Rust 与 Node.js 的桥梁

Oxc Angular 编译器通过 NAPI-RS 与 Vite 集成。NAPI-RS 是一个 Rust 绑定库,允许 Rust 代码被 Node.js 直接调用:

// Rust 端
use napi::bindgen_prelude::*;

#[napi]
pub struct AngularCompiler {
    inner: Compiler,
}

#[napi]
impl AngularCompiler {
    #[napi(constructor)]
    pub fn new(options: CompilerOptions) -> Self {
        Self {
            inner: Compiler::new(options),
        }
    }
    
    #[napi]
    pub fn compile(&self, source: String) -> Result<CompilationResult> {
        self.inner.compile(&source)
            .map_err(|e| Error::from_reason(e.to_string()))
    }
}
// Node.js 端
import { AngularCompiler } from '@oxc/angular-compiler';

const compiler = new AngularCompiler({
    strictTemplates: true,
});

const result = compiler.compile(templateSource);
console.log(result.code); // 编译后的 JavaScript

这种架构的优势:

  1. 零序列化开销:Rust 和 Node.js 之间直接传递指针
  2. 线程安全:NAPI-RS 自动处理跨线程调用
  3. 异步支持:可以将编译任务卸载到工作线程

三、Oxc Angular 编译器架构深度剖析

3.1 编译流水线总览

┌──────────────────────────────────────────────────────────────────────┐
│                    Oxc Angular Compiler Pipeline                      │
├──────────────────────────────────────────────────────────────────────┤
│                                                                       │
│  ┌─────────────┐    ┌─────────────┐    ┌─────────────┐              │
│  │  Template   │───▶│   Lexer &   │───▶│    AST      │              │
│  │   Source    │    │   Parser    │    │  Generator  │              │
│  │   (HTML)    │    │             │    │             │              │
│  └─────────────┘    └─────────────┘    └──────┬──────┘              │
│                                               │                      │
│                                               ▼                      │
│  ┌─────────────┐    ┌─────────────┐    ┌─────────────┐              │
│  │  JavaScript │◀───│   Code      │◀───│   Template  │              │
│  │   Output    │    │  Emitter    │    │   Binder    │              │
│  │             │    │             │    │             │              │
│  └─────────────┘    └─────────────┘    └─────────────┘              │
│                                                                       │
│  ┌─────────────┐    ┌─────────────┐    ┌─────────────┐              │
│  │    Type     │───▶│   Symbol    │───▶│   Scope     │              │
│  │  Resolver   │    │   Table     │    │   Analysis  │              │
│  │             │    │             │    │             │              │
│  └─────────────┘    └─────────────┘    └─────────────┘              │
│                                                                       │
└──────────────────────────────────────────────────────────────────────┘

3.2 模板解析器(Template Parser)

Oxc 实现了一个全新的 Angular 模板解析器,直接在 Rust 中处理 HTML 语法:

// 简化的解析器核心逻辑
pub struct TemplateParser<'a> {
    source: &'a str,
    lexer: Lexer<'a>,
    current_token: Token,
    errors: Vec<ParseError>,
}

impl<'a> TemplateParser<'a> {
    pub fn parse(&mut self) -> Result<TemplateAst, Vec<ParseError>> {
        let mut root = TemplateAst::new();
        
        while !self.is_at_end() {
            match self.current_token.kind {
                TokenKind::OpenTag => {
                    let element = self.parse_element()?;
                    root.children.push(element);
                }
                TokenKind::Text => {
                    let text = self.parse_text()?;
                    root.children.push(text);
                }
                TokenKind::InterpolationStart => {
                    let interpolation = self.parse_interpolation()?;
                    root.children.push(interpolation);
                }
                _ => self.advance(),
            }
        }
        
        if self.errors.is_empty() {
            Ok(root)
        } else {
            Err(self.errors.clone())
        }
    }
    
    fn parse_element(&mut self) -> Result<TemplateNode, ParseError> {
        // 解析开始标签
        self.expect(TokenKind::OpenTag)?;
        let tag_name = self.expect_identifier()?;
        
        // 解析属性和指令
        let mut attributes = Vec::new();
        let mut directives = Vec::new();
        
        while !self.check(TokenKind::CloseTag) && !self.check(TokenKind::SelfCloseTag) {
            if self.check(TokenKind::Star) {
                // 结构型指令 *ngIf, *ngFor
                let directive = self.parse_structural_directive()?;
                directives.push(directive);
            } else if self.check(TokenKind::OpenBracket) {
                // 属性绑定 [property]
                let binding = self.parse_property_binding()?;
                attributes.push(binding);
            } else if self.check(TokenKind::OpenParen) {
                // 事件绑定 (event)
                let binding = self.parse_event_binding()?;
                attributes.push(binding);
            } else {
                // 普通属性
                let attr = self.parse_attribute()?;
                attributes.push(attr);
            }
        }
        
        // 处理自闭合标签
        let self_closing = self.check(TokenKind::SelfCloseTag);
        self.advance();
        
        let mut element = TemplateNode::Element(Element {
            tag_name,
            attributes,
            directives,
            children: Vec::new(),
        });
        
        // 递归解析子节点
        if !self_closing {
            while !self.check(TokenKind::OpenTag) || !self.peek_is_closing(&tag_name) {
                if self.is_at_end() {
                    return Err(ParseError::UnclosedTag(tag_name));
                }
                let child = self.parse_node()?;
                element.add_child(child);
            }
            // 消耗闭合标签
            self.consume_closing_tag(&tag_name)?;
        }
        
        Ok(element)
    }
}

性能优化点

  1. 零拷贝字符串切片:使用 &str 直接引用源代码,避免字符串复制
  2. SIMD 加速的词法分析:利用 Rust 的 std::simd 进行批量字符处理
  3. 错误恢复机制:解析错误不中断流程,继续收集后续错误

3.3 模板绑定器(Template Binder)

绑定器负责将模板中的引用绑定到组件上下文:

pub struct TemplateBinder {
    scope: Scope,
    component_type: ComponentType,
}

impl TemplateBinder {
    pub fn bind(&mut self, ast: &mut TemplateAst) -> BindingResult {
        // 1. 注册组件成员到作用域
        self.register_component_members();
        
        // 2. 处理模板引用变量
        self.process_template_references(ast);
        
        // 3. 绑定表达式中的变量
        self.bind_expressions(ast);
        
        // 4. 解析指令输入输出
        self.resolve_directive_bindings(ast);
        
        Ok(())
    }
    
    fn bind_expression(&mut self, expr: &str, context: &BindingContext) -> ResolvedExpression {
        // 使用 Oxc 的 JavaScript 解析器解析表达式
        let js_expr = self.parse_expression(expr)?;
        
        // 遍历 AST,解析所有标识符
        let mut resolver = ExpressionResolver {
            scope: &self.scope,
            context,
        };
        resolver.visit(&js_expr);
        
        ResolvedExpression {
            expression: js_expr,
            resolved_symbols: resolver.resolved,
        }
    }
}

3.4 代码发射器(Code Emitter)

这是性能提升的关键组件。传统 Angular 编译器生成 TypeScript 代码,而 Oxc 直接生成 JavaScript:

pub struct CodeEmitter {
    output: Vec<u8>,
    options: EmitterOptions,
}

impl CodeEmitter {
    pub fn emit(&mut self, ast: &TemplateAst, component: &ComponentInfo) -> String {
        self.emit_preamble(component);
        
        // 生成工厂函数
        self.emit_factory_function(ast, component);
        
        // 生成渲染指令
        self.emit_render_instructions(ast);
        
        String::from_utf8(self.output.clone()).unwrap()
    }
    
    fn emit_factory_function(&mut self, ast: &TemplateAst, component: &ComponentInfo) {
        // 生成 Vite 兼容的模块代码
        self.emit_str("export function ");
        self.emit_str(&component.selector);
        self.emit_str("_Template(rf, ctx) {\n");
        
        if rf & RenderFlags.Create !== 0 {
            self.emit_create_block(ast);
        }
        
        if rf & RenderFlags.Update !== 0 {
            self.emit_update_block(ast);
        }
        
        self.emit_str("}\n");
    }
    
    fn emit_create_block(&mut self, ast: &TemplateAst) {
        // 生成元素创建指令
        // 使用 Angular 的 Instruction 编码
        self.emit_str("  if (rf & 1) {\n");
        
        for node in &ast.children {
            match node {
                TemplateNode::Element(elem) => {
                    // ɵɵelement: 创建元素
                    self.emit_str("    ɵɵelement(");
                    self.emit_i32(elem.node_index);
                    self.emit_str(", \"");
                    self.emit_str(&elem.tag_name);
                    self.emit_str("\");\n");
                }
                TemplateNode::Text(text) => {
                    // ɵɵtext: 创建文本节点
                    self.emit_str("    ɵɵtext(");
                    self.emit_i32(text.node_index);
                    self.emit_str(", \"");
                    self.emit_str(&text.content);
                    self.emit_str("\");\n");
                }
                _ => {}
            }
        }
        
        self.emit_str("  }\n");
    }
    
    fn emit_update_block(&mut self, ast: &TemplateAst) {
        // 生成属性更新指令
        self.emit_str("  if (rf & 2) {\n");
        
        for node in &ast.children {
            if let TemplateNode::Element(elem) = node {
                for binding in &elem.property_bindings {
                    // ɵɵproperty: 属性绑定
                    self.emit_str("    ɵɵproperty(\"");
                    self.emit_str(&binding.property_name);
                    self.emit_str("\", ctx.");
                    self.emit_str(&binding.expression);
                    self.emit_str(");\n");
                }
            }
        }
        
        self.emit_str("  }\n");
    }
}

关键优化

  1. 指令级编码:直接输出 Angular 运行时的指令序列
  2. 跳过 TypeScript:无中间 TypeScript 代码生成
  3. 内联优化:小型表达式直接内联

四、性能对比与基准测试

4.1 官方基准测试数据

VoidZero 提供了两组基准测试:

测试环境

  • CPU: AMD Ryzen 9 7950X (16 cores, 32 threads)
  • RAM: 64GB DDR5-6000
  • OS: Ubuntu 24.04
  • Node.js: v22.14.0
  • Rust: 1.85.0

测试项目一:Super Productivity

编译器构建时间相对速度
Angular CLI (esbuild)8.4s1.0x
Webpack + @ngtools/webpack12.6s0.67x
Oxc Angular Compiler1.31s6.4x

测试项目二:Bitwarden (Web)

编译器构建时间相对速度
Angular CLI (esbuild)42.7s1.0x
Webpack + @ngtools/webpack58.3s0.73x
Oxc Angular Compiler2.06s20.7x

4.2 性能提升的关键因素

因素一:零 TypeScript 编译开销

传统流程:

HTML → TypeScript (生成) → TypeScript AST (解析) → 类型检查 → JavaScript
时间占比:      15%           20%              35%       30%

Oxc 流程:

HTML → Rust AST → JavaScript
时间占比:  40%       60%

因素二:并行处理

Rust 的 Rayon 库支持无锁并行:

use rayon::prelude::*;

fn compile_templates_parallel(templates: Vec<Template>) -> Vec<CompilationResult> {
    templates
        .par_iter()
        .map(|template| compile_single(template))
        .collect()
}

因素三:内存效率

指标Angular CLIOxc Angular
峰值内存2.1 GB380 MB
GC 暂停450ms0ms (无GC)
内存分配次数12M1.2M

五、与 AnalogJS 的集成实战

5.1 AnalogJS 集成概述

AnalogJS 是 Angular 的流行元框架,其创建者 Brandon Roberts 已将 Oxc Angular 编译器集成到 Analog 中:

// vite.config.ts
import { defineConfig } from 'vite';
import analog from '@analogjs/platform';
import { oxcAngular } from '@oxc/angular-vite-plugin';

export default defineConfig({
  plugins: [
    analog({
      vite: {
        build: {
          // 启用 Oxc 编译器
          angularCompiler: 'oxc',
        },
      },
    }),
    oxcAngular({
      // 编译器选项
      strictTemplates: true,
      enableI18n: false,
    }),
  ],
});

5.2 实际项目迁移

假设我们有一个 Angular 项目:

// app.component.ts
import { Component } from '@angular/core';

@Component({
  selector: 'app-root',
  template: `
    <div class="container">
      <h1>{{ title }}</h1>
      <ul>
        <li *ngFor="let item of items; trackBy: trackByFn">
          {{ item.name }}: {{ item.value }}
        </li>
      </ul>
      <button (click)="addItem()">Add Item</button>
    </div>
  `,
  styles: [`
    .container { padding: 20px; }
  `],
})
export class AppComponent {
  title = 'My App';
  items = [
    { name: 'Item 1', value: 100 },
    { name: 'Item 2', value: 200 },
  ];
  
  trackByFn(index: number, item: any): number {
    return index;
  }
  
  addItem() {
    this.items.push({
      name: `Item ${this.items.length + 1}`,
      value: Math.random() * 100,
    });
  }
}

Oxc 编译后的代码(简化)

// generated by Oxc Angular Compiler
import { ɵɵdefineComponent, ɵɵelement, ɵɵtext, ɵɵproperty, 
         ɵɵlistener, ɵɵtemplate, ɵɵadvance } from '@angular/core';

export function AppComponent_Template(rf, ctx) {
  if (rf & 1) {
    // 创建模式
    ɵɵelementStart(0, "div", 0);
    ɵɵelementStart(1, "h1");
    ɵɵtext(2);
    ɵɵelementEnd();
    ɵɵtemplate(3, AppComponent_ForOf_Template, 3, 1, "li");
    ɵɵelementStart(4, "button");
    ɵɵlistener("click", function AppComponent_Template_click_listener() {
      return ctx.addItem();
    });
    ɵɵtext(5, "Add Item");
    ɵɵelementEnd();
    ɵɵelementEnd();
  }
  
  if (rf & 2) {
    // 更新模式
    ɵɵadvance(2);
    ɵɵtextInterpolate(ctx.title);
    ɵɵadvance(1);
    ɵɵproperty("ngForOf", ctx.items)("ngForTrackBy", ctx.trackByFn);
  }
}

function AppComponent_ForOf_Template(rf, ctx) {
  if (rf & 1) {
    ɵɵtext(0);
  }
  if (rf & 2) {
    const item = ctx.$implicit;
    ɵɵtextInterpolate2("", item.name, ": ", item.value, "");
  }
}

export class AppComponent {
  constructor() {
    this.title = 'My App';
    this.items = [
      { name: 'Item 1', value: 100 },
      { name: 'Item 2', value: 200 },
    ];
  }
  
  trackByFn(index, item) {
    return index;
  }
  
  addItem() {
    this.items.push({
      name: `Item ${this.items.length + 1}`,
      value: Math.random() * 100,
    });
  }
}

AppComponent.ɵfac = function AppComponent_Factory() {
  return new AppComponent();
};

AppComponent.ɵcmp = ɵɵdefineComponent({
  type: AppComponent,
  selectors: [["app-root"]],
  decls: 6,
  vars: 2,
  template: AppComponent_Template,
  styles: [".container { padding: 20px; }"]
});

5.3 热模块替换(HMR)支持

Oxc Angular 编译器完整支持 Vite 的 HMR:

// HMR 边界检测
pub fn detect_hmr_boundary(
    old_ast: &TemplateAst,
    new_ast: &TemplateAst,
) -> HmrUpdate {
    // 比较两个 AST 的差异
    let diff = compute_ast_diff(old_ast, new_ast);
    
    match diff {
        Diff::Structural => {
            // 结构变化,需要完整重载
            HmrUpdate::FullReload
        }
        Diff::BindingOnly(changes) => {
            // 仅绑定变化,可以热更新
            HmrUpdate::HotUpdate {
                changed_bindings: changes,
            }
        }
        Diff::None => HmrUpdate::None,
    }
}

六、AI 辅助开发的实践

6.1 与 Claude Code 和 Codex 的协作

VoidZero 团队公开了一个有趣的事实:Oxc Angular 编译器在两个月内通过与 AI 编码智能体协作开发。

AI 擅长的任务

  1. 重复的转换逻辑:AST 节点访问者的样板代码
  2. 模式匹配代码:模板语法解析规则
  3. 测试用例生成:基于语法规范生成边界测试

人类工程师专注

  1. 架构决策:模板编译策略的选择
  2. 性能优化:内存布局和算法优化
  3. 代码审查:确保生成的代码符合规范

6.2 示例:AI 生成的 AST 访问者

// AI 生成的模板 AST 访问者框架
// 提示词:Generate a visitor pattern for Angular template AST with enter/exit hooks

pub trait TemplateVisitor {
    fn visit_element(&mut self, element: &Element) {
        self.visit_element_enter(element);
        for child in &element.children {
            self.visit_node(child);
        }
        self.visit_element_exit(element);
    }
    
    fn visit_element_enter(&mut self, _element: &Element) {}
    fn visit_element_exit(&mut self, _element: &Element) {}
    
    fn visit_text(&mut self, text: &Text) {
        self.visit_text_enter(text);
        self.visit_text_exit(text);
    }
    
    fn visit_text_enter(&mut self, _text: &Text) {}
    fn visit_text_exit(&mut self, _text: &Text) {}
    
    fn visit_interpolation(&mut self, interp: &Interpolation) {
        self.visit_interpolation_enter(interp);
        // 处理表达式
        self.visit_interpolation_exit(interp);
    }
    
    fn visit_interpolation_enter(&mut self, _interp: &Interpolation) {}
    fn visit_interpolation_exit(&mut self, _interp: &Interpolation) {}
    
    fn visit_node(&mut self, node: &TemplateNode) {
        match node {
            TemplateNode::Element(elem) => self.visit_element(elem),
            TemplateNode::Text(text) => self.visit_text(text),
            TemplateNode::Interpolation(interp) => self.visit_interpolation(interp),
            TemplateNode::Comment(comment) => self.visit_comment(comment),
        }
    }
    
    fn visit_comment(&mut self, _comment: &Comment) {}
}

七、局限性及未来展望

7.1 当前局限

  1. 模板类型检查未实现

项目贡献者在 Reddit 上明确表示:「没有进一步维护的计划,所以模板类型检查不太可能会实现。」

这意味着:

@Component({
  template: `
    <!-- Oxc 不会检测这个类型错误 -->
    <div>{{ user.nonExistentProperty }}</div>
  `,
})
export class MyComponent {
  user = { name: 'John' }; // 没有 nonExistentProperty
}
  1. 实验性项目

VoidZero 明确标注:「仅用于研究目的发布」。不适合直接用于生产环境。

  1. Angular 版本兼容性

目前仅支持 Angular 17+ 版本。

7.2 Angular 官方的路线图

Angular 团队已响应社区需求,更新了官方路线图:

「我们正在原型化和探索这种支持会是什么样子,并将交付一个与 tsgo 兼容的 Angular 编译器,为 Angular 生态系统带来 Microsoft 原生端口的性能优势。」

这意味着:

  1. Microsoft 的 tsgo(TypeScript 的 Go 语言实现)可能与 Angular 编译器结合
  2. 官方原生编译器可能在 2026-2027 年推出

7.3 前端工具链「氧化」趋势

Oxc Angular 编译器不是孤例。整个前端工具链都在经历 Rust 重写:

工具原实现Rust 替代性能提升
ESLintJavaScriptOxlint50-100x
PrettierJavaScriptBiome35x
BabelJavaScriptSWC20x
WebpackJavaScriptRspack10x
TerserJavaScriptLightning CSS100x
Angular CompilerTypeScriptOxc Angular20x

八、总结

Oxc Angular 编译器代表了一种新的可能性:用系统级语言重新思考前端工具链的设计

它证明了:

  1. 绕过 TypeScript 的性能瓶颈是可行的
  2. Rust 在前端工具领域有巨大潜力
  3. AI 可以加速复杂编译器的开发

但同时也提醒我们:

  1. 实验性项目有风险
  2. 类型安全与性能的权衡
  3. 生态迁移需要时间

作为技术人,我们应该:

  1. 关注趋势:Rust 重写前端工具是确定性的方向
  2. 保持开放:新技术值得尝试和探索
  3. 理性决策:根据项目实际需求选择工具

前端工具链的「氧化」革命才刚刚开始。


参考链接

推荐文章

pycm:一个强大的混淆矩阵库
2024-11-18 16:17:54 +0800 CST
Go语言SQL操作实战
2024-11-18 19:30:51 +0800 CST
Vue3中如何处理WebSocket通信?
2024-11-19 09:50:58 +0800 CST
15 个 JavaScript 性能优化技巧
2024-11-19 07:52:10 +0800 CST
Boost.Asio: 一个美轮美奂的C++库
2024-11-18 23:09:42 +0800 CST
PHP 如何输出带微秒的时间
2024-11-18 01:58:41 +0800 CST
CSS 奇技淫巧
2024-11-19 08:34:21 +0800 CST
html一些比较人使用的技巧和代码
2024-11-17 05:05:01 +0800 CST
程序员茄子在线接单