编译优化-Bazel方案

当 iOS 工程的规模突破 CocoaPods / Xcode 原生构建体系的舒适区,Bazel 就成为下一代构建系统的首选。字节跳动的头条、抖音、Airbnb、Uber、Lyft、Pinterest、Square、Bilibili 等都已把 iOS 工程迁到 Bazel。本文介绍 Bazel 的核心原理、在 iOS 上的生态(rules_apple / rules_swift / rules_xcodeproj)以及大厂落地实践。 为什么是 Bazel CocoaPods + Xcode 在超大工程上的固有瓶颈: 粗粒度依赖:以 Pod / Target 为单位,增量编译颗粒粗 隐式依赖:Build Phases 隐含顺序,难以沙箱化 难以远程缓存:编译环境非 hermetic,hash 易变 难以远程执行:Xcode Build System 绑定本地 macOS Bazel 针对这些问题从设计之初就提供了: 能力 Bazel Xcode/CocoaPods 依赖粒度 文件级 Target 级 依赖显式化 必须声明 部分隐式 Sandbox 默认开启 无 远程缓存 原生 需第三方(ccache/Rugby) 远程执行 原生 不支持 跨语言 一流(Swift/OC/C++/Go/Rust/JS) 主要 Apple 平台 多平台 天生 iOS/macOS 为主 Bazel 核心概念 Workspace 与 Package WORKSPACE # 仓库根,声明外部依赖 foo/ ├── BUILD.bazel # package,本目录的构建声明 ├── main.swift └── util/ └── BUILD.bazel # 子 package Workspace:整个 Bazel 仓库,一个 WORKSPACE 文件一个仓 Package:任何包含 BUILD.bazel 的目录 Target:BUILD 文件里的一个 rule 调用,比如 swift_library(name = "foo") Label:Target 的全局 ID,形如 //foo/util:util Rule Rule 是构建函数,输入 sources + deps,输出 artifacts: ...

May 2, 2026

前端模块化

一、模块化的理解 1.什么是模块? 将一个复杂的程序依据一定的规则(规范)封装成几个块(文件), 并进行组合在一起 块的内部数据与实现是私有的, 只是向外部暴露一些接口(方法)与外部其它模块通信 2.模块化的进化过程 全局function模式 : 将不同的功能封装成不同的全局函数 编码: 将不同的功能封装成不同的全局函数 问题: 污染全局命名空间, 容易引起命名冲突或数据不安全,而且模块成员之间看不出直接关系 function m1(){ //... } function m2(){ //... } namespace模式 : 简单对象封装 作用: 减少了全局变量,解决命名冲突 问题: 数据不安全(外部可以直接修改模块内部的数据) let myModule = { data: 'www.baidu.com', foo() { console.log(`foo() ${this.data}`) }, bar() { console.log(`bar() ${this.data}`) } } myModule.data = 'other data' //能直接修改模块内部的数据 myModule.foo() // foo() other data 这样的写法会暴露所有模块成员,内部状态可以被外部改写。 IIFE模式:匿名函数自调用(闭包) 作用: 数据是私有的, 外部只能通过暴露的方法操作 编码: 将数据和行为封装到一个函数内部, 通过给window添加属性来向外暴露接口 问题: 如果当前这个模块依赖另一个模块怎么办? // index.html文件 <script type="text/javascript" src="module.js"></script> <script type="text/javascript"> myModule.foo() myModule.bar() console.log(myModule.data) //undefined 不能访问模块内部数据 myModule.data = 'xxxx' //不是修改的模块内部的data myModule.foo() //没有改变 </script> // module.js文件 (function(window) { let data = 'www.baidu.com' //操作数据的函数 function foo() { //用于暴露有函数 console.log(`foo() ${data}`) } function bar() { //用于暴露有函数 console.log(`bar() ${data}`) otherFun() //内部调用 } function otherFun() { //内部私有的函数 console.log('otherFun()') } //暴露行为 window.myModule = { foo, bar } //ES6写法 })(window) 最后得到的结果: ...

October 5, 2024

编译优化-CocoaPods优化

在 iOS 生态中,CocoaPods 依然是大多数中大型工程的依赖管理工具。当 Pod 数量达到几百个子组件上千个时,pod install 的耗时会变成研发流程里的硬性卡点。抖音在其 seer-optimize 项目中系统化地优化了 CocoaPods 的整个生命周期,全量 Pod Install 耗时减少 50%、增量减少 65%。本文系统介绍这些优化背后的原理。 关于 CocoaPods 整体架构的深度介绍可以参考 CocoaPods源码导读-架构总览。 Pod Install 耗时构成 Installer#install! 的六个阶段: flowchart LR A[prepare] --> B[resolve_dependencies] B --> C[download_dependencies] C --> D[validate_targets] D --> E[generate_pods_project] E --> F[integrate_user_project] 抖音公开的典型耗时分布: 阶段 典型耗时 说明 prepare < 0.01s 初始化 resolve_dependencies 数秒到数分钟 依赖决议,瓶颈 download_dependencies 数秒到数分钟 依赖下载,IO 密集 validate_targets 0.1s 校验 generate_pods_project 数秒到数十秒 生成 xcodeproj integrate_user_project 0.01s 集成主工程 resolve_dependencies 优化 Source 仓库更新 默认 CocoaPods 在 pod install --repo-update 时会更新所有 source 仓库。抖音改造为: ...

May 2, 2026

Webpack与Vite

前言 vite比webpack快? vite比webpack简单? 今天我们就来分析一下,到底有什么不一样的地方! 定位 对比之前,我们先要搞懂,vite与webpack的定位以及关系才可以。 那前端社区中常谈到的这些工具webpack、rollup、parcel、esbuild、vite、vue-cli、create-react-app、umi他们之间的关系是怎样的。 webpack、rollup、parcel、esbuild都是打包工具,代码写好之后,我们需要对代码进行压缩、合并、转换、分割、打包等操作,这些工作需要打包工具去完成。 vue-cli、create-react-app、umi 是基于webpack的上层封装,通过简单的配置就可以快速创建出一个项目,把更多的时间放在业务开发上。 vite开发环境依赖esbuild进行预构建,生产环境则依赖rollup进行打包,并且充分利用了现代浏览器的特性,比如http2、ES module,vite是站在众多巨人肩膀上的一个产物, 类似webpack + webpack-dev-server的结合体,是一个非常棒的前端项目的构建工具。 运行原理 首先,我们从运行原理上分析一下,vite为什么比webpack快。 webpack运行原理 当我们使用webpack启动项目时,webpack会根据我们配置文件(webpack.config.js) 中的入口文件(entry),分析出项目项目所有依赖关系,然后打包成一个文件(bundle.js),交给浏览器去加载渲染。 这样就会带来一个问题,项目越大,需要打包的东西越多,启动时间越长。 关于ES module 在讲vite运行原理之前,我们先说一下ES module 目前,绝大多数现代浏览器都已经支持ES module了, 我们只需要在<script>标签中添加type="module",就可以使用ES module了。 下面这段代码是可以直接在浏览器中运行的。 // test.js export default function hello() { console.log('hello world'); } // index.html <script type="module"> import hello from './test.js'; hello(); // hello world </scirpt> vite运行原理 在<script type="module">中,浏览器遇到内部的import引用时,会自动发起http请求,去加载对应的模块。 vite也正是利用了ES module这个特性,使用vite运行项目时,首先会用esbuild进行预构建,将所有模块转换为es module,不需要对我们整个项目进行编译打包,而是在浏览器需要加载某个模块时,拦截浏览器发出的请求,根据请求进行按需编译,然后返回给浏览器。 这样一来,首次启动项目(冷启动)时,自然也就比webpack快很多了,并且项目大小对vite启动速度的影响也很小。 构建方式 我们再来看一下,vite与webpack在项目构建上有哪些区别。 webpack webpack是基于nodejs运行的,但js只能单线程运行,无法利用多核CPU的优势,当项目越来越大时,构建速度也就越来越慢了。 vite vite预构建与按需编译的过程,都是使用esbuild完成的。 esbuild是用go语言编写的,可以充分利用多核CPU的优势,所以vite开发环境下的预构建与按需编译速度,都是非常快的。 http2 vite充分利用了http2可以并发请求的优势,这也是速度快的一个主要原因。 接下来,我们了解一下http2的来龙去脉。 在之前http1的时候,浏览器对同一个域名的请求,是有并发限制的,一般为6个,如果并发请求6个以上,就会造成阻塞问题,所以在http1的时代,我们要减少打包产物的文件数量,减少并发请求,来提高项目的加载速度。 2015年以后,http2出现了,他可以并发发送多个请求,不会出现http1的并发限制。这时候,将打包产物分成多个小模块,并行去加载,反而会更快。 vite也充分利用了这一优势,对项目资源进行了合理的拆分,访问项目时,同时加载多个模块,来提升项目访问速度。 热更新 vite速度快的另一个原因是与webpack不同的热更新机制。 ...

October 5, 2024

编译优化-Explicit Modules

Apple 从 Xcode 15 开始在 Swift 上引入 Explicit Modules,Xcode 16 在 C/C++/Objective-C 上全面铺开,Xcode 17 进一步默认启用并与细粒度依赖追踪结合。这是近五年 Apple 构建系统最重要的一次变革,直接影响到大部分项目的编译模型。 模块(Module)基础 为什么需要模块 C 家族语言的 #include 机制有两个致命问题: 文本替换:头文件是纯文本替换,每个源文件都要重新解析一遍所有包含的头文件 宏污染:先引入的头文件宏会影响后续头文件的行为,没有隔离 Clang 在 2012 年引入 Clang Modules,用一个预编译的二进制模块(.pcm)替代文本包含,同一 module 在一次构建里只解析一次。Swift 的 .swiftmodule 从设计之初就是模块化的。 flowchart LR A["源文件"] -->|"include 头文件"| B["Foundation.h 文本"]; B --> C["每次都重新解析"]; D["源文件"] -->|"@import Foundation"| E["Foundation.pcm 预编译"]; E --> F["一次解析,多次复用"]; 模块的组成 类型 载体 描述 Clang Module .pcm C/OC 模块的序列化 AST Swift Module .swiftmodule Swift 模块的接口 + SIL Swift Interface .swiftinterface 可被不同 Swift 版本解析的文本接口 Module Map module.modulemap 描述哪些头文件构成某个 module Implicit Modules 的问题 原理 在 Xcode 16 之前,Clang Modules 以 隐式 方式工作: ...

May 5, 2026

任务自动化

在前端开发中,我们经常需要执行各种重复性任务,比如编译代码、启动本地服务器、监听文件变化、运行测试、部署代码等。虽然可以使用不同的工具来完成这些任务,但npm本身提供了一个强大的功能:npm scripts。通过npm scripts,我们可以将这些任务脚本化并集中管理,从而提高开发效率。本文将专注于如何使用npm scripts自动化前端开发任务,以及它在项目管理中的作用。 什么是npm scripts? npm scripts是npm提供的一种机制,允许我们在项目的package.json文件中定义一组命令,方便执行各种任务。在package.json文件中,scripts字段可以包含多个脚本,每个脚本都是一个键值对,其中键是脚本的名称,值是实际要执行的命令。例如: { "scripts": { "build": "webpack --config webpack.config.js", "test": "jest" } } 在上面的示例中,定义了两个脚本:build 和 test。可以通过以下方式在命令行中运行这些脚本: npm run build npm run test npm会执行对应的命令,自动帮我们完成编译和测试任务。 为什么使用npm scripts? 在现代前端开发流程中,自动化任务是不可或缺的,而npm scripts的使用有以下几大优势: 减少对全局依赖的需求:在传统项目中,开发者通常需要全局安装各种命令行工具,如webpack、eslint、babel等。而npm scripts可以直接调用项目的本地依赖,无需全局安装。这确保了不同开发环境中的工具版本一致,减少了版本冲突和兼容性问题。 集中管理任务:npm scripts将所有任务定义集中在package.json文件中,项目中的所有开发人员都可以直观地看到可用的任务列表,无需了解每个工具的详细使用方法。只需运行npm run <task>,便可一键完成常见任务。 支持跨平台执行:npm scripts中的命令可以跨平台使用,在Windows、macOS和Linux系统上表现一致,尤其适合团队协作。 便于组合和链式执行:npm允许通过逻辑操作符来组合脚本,实现复杂任务的自动化执行。例如可以通过&&和||来串联多个任务,使得多个步骤一次完成。 如何定义和使用npm scripts? 让我们通过具体的示例来看看npm scripts如何自动化前端开发任务。 1. 设置基本的开发任务 假设我们正在开发一个React项目,我们可以在package.json中定义以下基本任务: { "scripts": { "start": "webpack serve --mode development", "build": "webpack --mode production", "test": "jest", "lint": "eslint src/**/*.js" } } 这里的脚本分别执行以下任务: ...

October 5, 2024

编译优化-Swift编译优化

Swift 的类型推导、泛型约束求解、WMO / CMO 是编译性能的核心影响因素。理解它们能让源码层面的小改动带来显著的编译加速。本文聚焦"代码写法 + 编译开关"两方面的优化。 Swift 编译流程速览 相比 C/OC,Swift 编译多出大量工作: flowchart LR A[Parse] --> B[Sema类型检查] B --> C[SILGen生成 SIL] C --> D[SIL Optimization] D --> E[IRGenLLVM IR] E --> F[LLVM Optimization] F --> G[CodeGen] 其中 Sema(语义分析) 阶段负责类型推导,是 Swift 特有的性能热点。-debug-time-compilation 看到的 “Type checking” 时间基本都集中在这里。 类型推导与 “Too Complex” 错误 约束求解 Swift 的类型系统比大多数语言复杂: 函数重载 + 运算符重载 隐式字面量类型(Int、Double、Float80…) 泛型 + 协议 + 关联类型 闭包参数类型推导 SwiftUI 风格的 ViewBuilder / Result Builders 遇到表达式 let x = a + b * c - d,编译器需要给 + * - 每个运算符枚举所有可能的重载,在多个候选里做约束传播。当候选组合爆炸时,Sema 会放弃并抛出: ...

May 2, 2026

前端构建工具详解

谈到构建工具,大家首先想到的肯定就是 Webpack 以及现在最🔥的 Vite。 Webpack,功能强大,生态丰富,从面世到今天,一直是很受大家欢迎;Vite 采用 unbundle 构建模式,带来了极致的开发体验,给开发人员以新的选择。 在这两个构建工具之外,还有其他的构建工具,如和 Webpack、Vite 类似的 Rollup、Parcel、Esbuild,自动化构建工具 grunt、gulp,以及更加久远的 YUI Tool。 这些工具的存在,构成了前端构建工具的发展史。 YUI Tool + Ant YUI tool 是 07 年左右出现的一个构建工具,功能比较简单,用于压缩混淆 css 和 js 代码,需要配合 java 的 Ant 使用。 当时 web 应用开发主要采用 JSP,还不像现在这样前后端分离,通常是由 java 开发人员来编写 js、css 代码,前端代码都是和后端 java 代码放在一起的。因此前端代码的压缩混淆也就基于 java 实现了。 Grunt / Gulp Grunt / Gulp 都是运行在 node 环境上的自动化工具。 在开发过程中,我们可以将一些常见操作如解析 html、es6 代码转换为 es5、less / sass 代码转换为 css 代码、代码检查、代码压缩、代码混淆配置成一系列任务,然后通过 Grunt / Gulp 自动执行这些任务。 Grunt 和 Gulp 的不同点: ...

August 7, 2025

编译优化-Xcode构建系统

Xcode 的构建系统由多个组件协同完成,从 Cmd+B 到生成 .app 经历了完整的任务图构建、依赖分析、并行调度流程。理解这套体系是后续一切优化的基础。 构建系统组成 Xcode 10 之后默认采用 Swift Build System(基于 llbuild),整体架构如下: flowchart TD A[Xcode IDE] --> B[XCBBuildServiceDaemon] B --> C[Build Description任务图 JSON] C --> D[llbuild调度内核] D --> E1[Swift Driver / swiftc] D --> E2[clang] D --> E3[ld / ld-prime] D --> E4[actool / ibtool / 脚本] E1 & E2 & E3 & E4 --> F[产物缓存] 组件 职责 XCBBuildService 独立进程,为 Xcode / xcodebuild 提供构建 RPC 服务 Build Description 根据 Scheme、Target、Build Settings 生成的任务图 llbuild 通用构建引擎,执行任务图、做增量与并行调度 swift-driver Swift 编译驱动,生成 frontend job 并驱动 swiftc clang / swift-frontend 真正的编译器前端 ld / ld-prime 链接器 XCBBuildService Xcode 和 xcodebuild 都是客户端,真正的构建逻辑在 XCBBuildService 这个独立进程。它通过 XPC 接收请求,生成并执行任务。社区工具 XCBBuildServiceProxy 利用这一架构在中间插一层代理,实现自定义构建(Bazel、远程执行等)。字节 BitSky、Tuist Cloud 都基于此类模式。 ...

May 2, 2026

前端任务自动化(npm scripts实现)

在前端开发中,我们经常需要执行各种重复性任务,比如编译代码、启动本地服务器、监听文件变化、运行测试、部署代码等。虽然可以使用不同的工具来完成这些任务,但npm本身提供了一个强大的功能:npm scripts。通过npm scripts,我们可以将这些任务脚本化并集中管理,从而提高开发效率。 本文将专注于如何使用npm scripts自动化前端开发任务,以及它在项目管理中的作用。 什么是npm scripts? npm scripts是npm提供的一种机制,允许我们在项目的package.json文件中定义一组命令,方便执行各种任务。在package.json文件中,scripts字段可以包含多个脚本,每个脚本都是一个键值对,其中键是脚本的名称,值是实际要执行的命令。例如: { "scripts": { "build": "webpack --config webpack.config.js", "test": "jest" } } 在上面的示例中,定义了两个脚本:build 和 test。可以通过以下方式在命令行中运行这些脚本: npm run build npm run test npm会执行对应的命令,自动帮我们完成编译和测试任务。 为什么使用npm scripts? 在现代前端开发流程中,自动化任务是不可或缺的,而npm scripts的使用有以下几大优势: 减少对全局依赖的需求:在传统项目中,开发者通常需要全局安装各种命令行工具,如webpack、eslint、babel等。而npm scripts可以直接调用项目的本地依赖,无需全局安装。这确保了不同开发环境中的工具版本一致,减少了版本冲突和兼容性问题。 集中管理任务:npm scripts将所有任务定义集中在package.json文件中,项目中的所有开发人员都可以直观地看到可用的任务列表,无需了解每个工具的详细使用方法。只需运行npm run <task>,便可一键完成常见任务。 支持跨平台执行:npm scripts中的命令可以跨平台使用,在Windows、macOS和Linux系统上表现一致,尤其适合团队协作。 便于组合和链式执行:npm允许通过逻辑操作符来组合脚本,实现复杂任务的自动化执行。例如可以通过&&和||来串联多个任务,使得多个步骤一次完成。 如何定义和使用npm scripts? 让我们通过具体的示例来看看npm scripts如何自动化前端开发任务。 1. 设置基本的开发任务 假设我们正在开发一个React项目,我们可以在package.json中定义以下基本任务: { "scripts": { "start": "webpack serve --mode development", "build": "webpack --mode production", "test": "jest", "lint": "eslint src/**/*.js" } } 这里的脚本分别执行以下任务: ...

August 7, 2025