news 2026/9/14 6:14:26

typescript-eslint 浏览器端构建:解读 website-eslint 如何在浏览器中运行 ESLint 与 TypeScript Lint

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
typescript-eslint 浏览器端构建:解读 website-eslint 如何在浏览器中运行 ESLint 与 TypeScript Lint

typescript-eslint 浏览器端构建:解读 website-eslint 如何在浏览器中运行 ESLint 与 TypeScript Lint

【免费下载链接】typescript-eslint:sparkles: Monorepo for all the tooling which enables ESLint to support TypeScript项目地址: https://gitcode.com/GitHub_Trending/ty/typescript-eslint

website-eslint是 typescript-eslint 仓库中一个不起眼却关键的基础包:它把 ESLint 的Linter与内置规则、typescript-eslint 全家桶以及 TypeScript 编译器能力捆绑成一个可在浏览器中运行的 CommonJS 产物,专门服务于website包中的在线 Playground。本文将以packages/website-eslint/README.md为核心骨架,结合build.mtssrc/index.js等源码,完整解析它的构建流程、打包内容、浏览器适配技巧与对外 API,帮你理解"ESLint 上浏览器"这条工程链路的真实实现。

一、这个包是什么:为 Playground 而生的浏览器版 ESLint

website-eslint的定位非常纯粹——它是一个bundled version of ESLint plustypescript-eslint, made to work in a browser,即"能在浏览器里工作的 ESLint + typescript-eslint 打包版"。它被用于website包中的在线 Playground,不参与任何 Node.js 侧的 lint 流程,也不面向最终用户单独发布(package.json"private": true明确标记为私有包)。

也就是说,当你在 typescript-eslint 官网上打开 Playground 输入一段 TypeScript 代码并得到即时 lint 反馈时,背后执行 lint 的并不是常见的 Node 进程,而是被 esbuild 打包进浏览器 bundle 的这个website-eslint产物。

从源码结构看,整个包非常精简:

  • packages/website-eslint/build.mts:构建脚本(README 中写作build.ts,实际仓库中的文件名是build.mts,由package.json中 nx 配置"command": "tsx build.mts"驱动);
  • packages/website-eslint/src/index.js:运行时入口,负责组装 Linter、配置与规则;
  • packages/website-eslint/src/mock/:一组 Node 专属模块的浏览器桩实现(pathutilassertlru-cache等)。

二、构建流程:一条命令产出浏览器 bundle

构建命令非常简单:

pnpm run build

package.json中该脚本为pnpm exec nx build,而 nx target 最终执行tsx build.mts,即用tsx直接运行build.mts。脚本内部调用esbuild(版本约~0.28.0)完成打包,核心配置如下:

  • 格式与目标format: 'cjs'platform: 'browser'target: 'es2020',输出到./dist/,同时开启minify: true(压缩)与treeShaking: true(摇树);
  • 入口./src/index.js,产物名index(对应dist/index.js),并生成 linked sourcemap;
  • 外部依赖external: [],即所有依赖全部打进 bundle,不保留任何运行时 require;
  • 输出约定package.jsonexports字段将"."指向./dist/index.js,类型声明指向./dist/index.d.ts

构建过程在build.mtsbuildPackage函数中完成,函数末尾通过console.time('building eslint for web')记录整体耗时,便于在 CI 中观测打包性能。

三、打包内容三件套:Linter、TypeScript、typescript-eslint

README 明确指出这个 bundle 包含三大块内容,下面逐一结合源码展开。

1. ESLint 的 Linter 类与内置规则

bundle 包含 ESLint 的Linter类(通过eslint/lib/linter/linter.js直接引入)以及其全部内置规则(eslint/lib/rules/index.js)。

src/index.js中,对外暴露了工厂方法:

exports.createLinter = function () { return new Linter(); };

同时内置规则通过eslint/use-at-your-own-riskbuiltinRules原样导出(exports.builtinRules = builtinRules;)。Playground 可以据此拿到与 Node 端一致的全量内置规则集,而不必为浏览器单独裁剪 ESLint 核心功能。

2. TypeScript:按需在线加载的 "Monaco web" 兼容 bundle

这是最巧妙的工程点。由于typescript包体积庞大,不能直接打包进页面,website-eslint采用了一个 wrapper:typescripttypescript/lib/tsserverlibrary两个模块入口被"按需下载"(downloaded on the fly)

具体机制藏在build.mts的 esbuildalias配置中:typescripttypescript/lib/tsserverlibrary都会被 alias 到./src/mock/typescript.js,而后者只有一行代码:

module.exports = window.ts;

即运行时直接读取浏览器全局对象window.ts。这个window.ts由网站侧在需要时加载(与 TypeScript Playground 使用同一套源码方案),从而得到 README 所说的"Monaco web" compatible bundle——即与 Monaco Editor 的 TypeScript 语言服务兼容的浏览器版 TypeScript。这让 Playground 既能完整使用 TypeScript 的类型系统能力(解析、分析、报错),又不把数 MB 的编译器塞进首屏 bundle。

3. typescript-eslint 全家桶

bundle 同时打包进 typescript-eslint 的核心包,包括:

  • @typescript-eslint/eslint-plugin全部 configs 与规则
  • @typescript-eslint/parser@typescript-eslint/typescript-estree:负责把 TypeScript 源码解析为 ESLint 可处理的 AST。

这背后的打包细节非常讲究。构建脚本通过 esbuild 插件把相关包全部解析到源码的index.ts(见build.mtsonResolve逻辑):

  • 形如@typescript-eslint/<pkg>/<sub>的导入被重定向到<pkg>/src/<sub>/index.ts
  • 形如@typescript-eslint/<pkg>的导入被重定向到<pkg>/src/index.ts
  • @typescript-eslint/typescript-estree及其/use-at-your-own-risk深链接则被重定向到use-at-your-own-risk.ts

也就是说,打包的是各包未经编译的 TypeScript 源码而非已构建的 dist 产物,这一步正是实现精细摇树的关键前提。

四、深链接 /use-at-your-own-risk 与 tree-shaking 的工程哲学

README 专门强调:构建文件刻意使用各包内部的深层/use-at-your-own-risk导入(deep imports)。这是有明确收益的设计:

This is so that esbuild can properly tree-shake and only include the necessary code. This saves us having to mock unnecessary things and reduces our website bundle size.

即:通过深链接直接指向源码内部模块,esbuild 才能做精细的 tree-shaking,只把真正被用到的代码打进 bundle,从而避免为无关代码编写 mock,并显著压缩网站 bundle 体积。

src/index.js中可以直观看到这种导入风格:

import rawPlugin from '@typescript-eslint/eslint-plugin/use-at-your-own-risk/raw-plugin'; import { analyze } from '@typescript-eslint/scope-manager'; import { astConverter, getScriptKind, } from '@typescript-eslint/typescript-estree/use-at-your-own-risk'; import { visitorKeys } from '@typescript-eslint/visitor-keys'; import { Linter } from 'eslint'; import { builtinRules } from 'eslint/use-at-your-own-risk';

入口只挑选 Playground 运行时真正需要的能力,如 AST 转换器astConverter、脚本类型判断getScriptKind、作用域分析analyze、访问者键表visitorKeys与查询库esquery,其余未被引用到的主包代码(如getParsedConfigFileESLintRuleTesterCLIEngine等 Node 专用入口)都会被 esbuild 摇树剔除。

五、浏览器环境的"去 Node 化":define 与 mock 模块

ESLint 及其依赖链包含大量 Node 专属模块,直接打包必然在浏览器里爆炸。website-eslint用两层手段解决:

1. 全局 define 替换

build.mts中,esbuild 的define配置把 Node 环境变量在编译期全部替换为浏览器值:

define: { 'define.amd': 'false', global: 'window', 'process.emitWarning': 'console.warn', 'process.env.DEBUG': 'false', 'process.env.IGNORE_TEST_WIN32': 'true', 'process.env.NODE_DEBUG': 'false', 'process.env.NODE_ENV': '"production"', 'process.env.TIMING': 'undefined', 'process.platform': '"browser"', }

比如process.platform被固定为"browser",避免代码里出现基于平台的require('child_process')等路径。

2. mock 模块(alias 桩)

对于无法用 define 解决的模块,build.mts通过alias把它们全部指向packages/website-eslint/src/mock/下的桩实现:

  • Node 内置的utilassertpath每个都同时注册xxnode:xx两种写法);
  • npm 包lru-cache——桩实现是一个基于Map的极简LruCachesrc/mock/lru-cache.js);
  • typescripttypescript/lib/tsserverlibrary——桩为window.tssrc/mock/typescript.js)。

此外,esbuild 插件还通过onLoad对若干关键文件做了内容级替换build.mts):

  • getParsedConfigFile.tsts-eslint/ESLint.tsts-eslint/RuleTester.tsts-eslint/CLIEngine.ts全部替换为空模块(src/mock/empty.js);
  • ESLint 的lib/api.js被替换为src/mock/eslint.js,其中RuleTesterSourceCode是空壳类,Linter则通过vt:eslint/linter虚拟标识符重定向到真实的eslint/lib/linter/linter.js
  • ESLint 的unsupported-api.js被替换为src/mock/eslint-rules.jsbuiltinRules通过vt:eslint/rules指向真实的eslint/lib/rules/index.js
  • @typescript-eslint/parser/src/parser.ts被替换为src/mock/parser.js,其parseForESLint为空操作——因为在 Playground 场景下,AST 转换实际由typescript-estreeastConverter直接完成,不需要 parser 重新走一遍解析流程。

这套组合拳保证了 bundle 内不会残留任何fschild_processos等浏览器不存在的模块引用。

六、运行时 API 与配置的浏览器化处理

src/index.js最终对外暴露的完整 API 如下:

exports.getScriptKind // 来自 typescript-estree,判断脚本类型 exports.analyze // 来自 @typescript-eslint/scope-manager,作用域分析 exports.visitorKeys // 来自 @typescript-eslint/visitor-keys exports.astConverter // 来自 typescript-estree,TS AST → ESTree exports.esquery // esquery,供 Playground 做 AST 查询 exports.createLinter // () => new Linter() exports.configs // js/* 与 @typescript-eslint/* 全套配置 exports.plugin // rawPlugin.plugin(typescript-eslint 插件本体) exports.builtinRules // ESLint 内置规则

其中configs的组装有一个非常实用的细节(src/index.js):

const stripParser = config => { if (!config.languageOptions?.parser) { return config; } const { parser: _parser, ...languageOptions } = config.languageOptions; return { ...config, languageOptions }; };

源码注释解释了原因:部分配置自带languageOptions.parser(默认的 Node 版 parser),这会把 Playground 使用的浏览器兼容 parser 覆盖掉并导致报错,因此在导出配置前统一剥离parser字段。随后:

for (const [name, value] of Object.entries(js.configs)) { configs[`js/${name}`] = stripParserFromConfig(value); } for (const [name, value] of Object.entries(rawPlugin.flatConfigs)) { configs[`@typescript-eslint/${name}`] = stripParserFromConfig(value); }

最终得到以js/<name>@typescript-eslint/<name>命名的全套扁平配置(flat config),供 Playground 按预设选择 lint 规则组合。

另外值得一提的是 esbuild 的banner/footer配置(build.mts):

banner: { js: `define(['exports', 'vs/language/typescript/tsWorker'], function (exports) {` }, footer: { js: `});` },

这是针对 esbuild#819 的已知 workaround:将产物包进一个 AMD 风格的define调用中,使其能无缝对接 Monaco/vs/language/typescript/tsWorker的加载环境,这正是"Monaco web 兼容"在产物形态上的具体体现。

七、构建产物如何被 Playground 使用

构建完成后,dist/index.jswebsite包(网站/Playground 应用)在浏览器侧加载。package.jsonexports["."]default指向./dist/index.js,配合tsx build.mts产出的类型声明dist/index.d.ts,使得网站侧可以像使用普通 npm 包一样获得类型提示。

整个依赖关系是单向的:website-eslint依赖@typescript-eslint/eslint-plugin@typescript-eslint/parser@typescript-eslint/scope-manager@typescript-eslint/typescript-estree@typescript-eslint/visitor-keys以及eslint@eslint/jsesquery等(见package.jsondevDependencies,全部为workspace:*catalog:版本),它自身则只被website引用,不参与任何发布给用户的产物链路。

八、小结:一条可复用的"ESLint 上浏览器"工程范式

纵观README.md与其实现,website-eslint给出了一个非常典型的浏览器化 Node 工具链方案,其要点可归纳为:

  1. 构建期裁剪:esbuildalias+onLoad替换 +define,把 Node 专属模块在编译期替换为桩或常量;
  2. 源码级深链接:通过/use-at-your-own-risk直接引用包内源码,让 tree-shaking 精准到函数粒度,最小化 bundle;
  3. 按需加载重型依赖:TypeScript 编译器不打包,而是暴露window.ts由宿主按需注入,与 Monaco/TS Playground 方案共用同一套加载机制;
  4. 运行时收口:入口文件只暴露 Playground 真正需要的少量 API(createLinterconfigspluginbuiltinRules等),并针对浏览器环境对配置做必要的"净化"(剥离 Node parser)。

如果你需要在自己的项目中让 ESLint + typescript-eslint 跑在浏览器里(例如在线 IDE、文档站 Playground、代码沙箱),website-eslint的这套build.mts+src/mock+src/index.js组合是可直接参照的完整参考实现——从构建脚本、桩模块到运行时 API,每一个环节都能在本文引用的仓库文件里找到一一对应的代码依据。

【免费下载链接】typescript-eslint:sparkles: Monorepo for all the tooling which enables ESLint to support TypeScript项目地址: https://gitcode.com/GitHub_Trending/ty/typescript-eslint

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

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

车载Android串口开发实战:UART/RS232/RS485通信与调试指南

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

作者头像 李华
网站建设 2026/9/14 6:13:16

如何生成 Authelia 的 ML-DSA 后量子密钥与证书

如何生成 Authelia 的 ML-DSA 后量子密钥与证书 【免费下载链接】authelia The Single Sign-On Multi-Factor portal for web apps. OpenID Certified™ and Post-Quantum Cryptography Ready. 项目地址: https://gitcode.com/GitHub_Trending/au/authelia Authelia 提供…

作者头像 李华
网站建设 2026/9/14 6:13:11

CloddsBot:基于OpenAPI与规则引擎的云资源自动化巡检实践

1. 为什么会有 CloddsBot&#xff1a;从“看板疲劳”到自动化值守 先交代一个背景&#xff1a;我手上同时维护着几个不同云厂商的账号&#xff0c;加起来有几十台云主机、数据库实例、负载均衡、对象存储桶。每天早上开工第一件事&#xff0c;就是挨个登录控制台&#xff0c;刷…

作者头像 李华
网站建设 2026/9/14 6:12:37

MEEMD程序详解:从EEMD到排列熵的MATLAB实现与参数调优

简介&#xff1a;MEEMD&#xff08;改进集合经验模态分解&#xff09;与EEMD的MATLAB源码包&#xff0c;面向信号处理、故障诊断等领域的科研人员与工程师&#xff0c;用于解决非线性、非平稳信号的分解与特征提取问题&#xff0c;适用于课程设计、论文复现和工程预研。资源采用…

作者头像 李华
网站建设 2026/9/14 6:12:08

yuzu Switch 模拟器入门指南:从安装到调优快速跑通

yuzu Switch 模拟器入门指南&#xff1a;从安装到调优快速跑通 【免费下载链接】yuzu 任天堂 Switch 模拟器 项目地址: https://gitcode.com/GitHub_Trending/yu/yuzu yuzu 是一个用 C 编写的开源任天堂 Switch 模拟器&#xff0c;支持 Windows、Linux、Android 三大平台…

作者头像 李华