在 Razzle 中使用 TypeScript:razzle-plugin-typescript 插件与 ts-loader 配置实战
2026/9/24 2:39:29 网站建设 项目流程
  • 前端
  • 构建工具
  • 前端构建
  • 后端

【免费下载链接】razzle

✨ Create server-rendered universal JavaScript applications with no configuration

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

本指南基于 Razzle 官方示例项目 with-typescript-plugin 及其配套插件源码 razzle-plugin-typescript,系统讲解如何在零配置的 Razzle 通用渲染应用中接入 TypeScript。读完本文,你将掌握两种 TypeScript 接入路径(内置 Babel 支持与 ts-loader 插件方案)的取舍、插件三个核心配置项(useBabeltsLoaderforkTsChecker)的语义与底层实现,以及如何改造 Jest 配置让 TS/TSX 测试与快照覆盖率正常工作。

为什么需要专门的 TypeScript 插件

Razzle 的核心承诺是"零配置创建服务端渲染的通用 JavaScript 应用",它的默认工具链基于 Babel。因此 Razzle 本身就已内置了通过 Babel 处理 TypeScript 的能力(@babel/preset-typescript一类预设负责剥离类型注解)。

那么razzle-plugin-typescript存在的意义是什么?从插件 README(见 packages/razzle-plugin-typescript/README.md)可以明确读到:Razzle 现已内置对 TypeScript 的 Babel 支持,除非有特殊需求,官方推荐直接使用内置支持(即 with-typescript 示例的用法)。本插件的价值在于:当你需要ts-loader这类类型检查 + 转译一体的编译路径,而不是仅做类型擦除时,用它把 webpack 中的babel-loader替换为ts-loader

两种方案的差异,用官方示例文档(examples/with-typescript-plugin/README.md)中的原话来说:Babel 和 TypeScript 都能转译 ES6 代码,如果同时跑两个 loader,等于让 Razzle 做两遍工作,在大应用上会显著拖慢 HMR。因此该示例采用"用ts-loader覆盖babel-loader"的单 loader 策略。

快速开始:创建并运行示例

官方示例可以直接用脚手架一键拉取。在 examples/with-typescript-plugin/README.md 中给出的命令如下:

npx create-razzle-app --example with-typescript-plugin with-typescript-plugin cd with-typescript-plugin yarn start

启动后默认监听3000端口(服务端入口见 src/index.ts,PORT环境变量可覆盖默认端口)。

start外,package.json 还提供了完整的生命周期脚本:

{ "scripts": { "start": "razzle start", "build": "razzle build", "test": "razzle test --env=jsdom", "start:prod": "NODE_ENV=production node build/server.js" } }
  • razzle build产出build/目录,其中build/server.js是可直接用 Node 运行的服务端 bundle;
  • razzle test基于 Jest,配合--env=jsdom让组件测试拥有 DOM 环境。

示例的源码结构清晰体现了"同构应用"的代码组织方式:

  • src/index.ts:服务端进程入口,负责启动 Express 并挂载 HMR 热更新逻辑;
  • src/server.tsx:服务端渲染逻辑,使用renderToString+StaticRouter输出 HTML,并通过RAZZLE_ASSETS_MANIFEST读取构建产物清单注入 CSS/JS 标签;
  • src/client.tsx:浏览器端入口,使用hydrate+BrowserRouter完成水合;
  • src/App.tsx 与 src/Home.tsx:路由组件;
  • src/App.test.tsx:基于@testing-library风格(此处用react-domrender)编写的组件冒烟测试。

插件的两种接入方式

方式一:默认配置(推荐)

在 razzle.config.js 中,整个配置只有一行:

'use strict'; module.exports = { plugins: ['typescript'], };

插件通过 Razzle 的插件系统按名称解析(对应 npm 包razzle-plugin-typescript),其modifyWebpackConfig钩子会自动完成下文所述的全部 webpack 改造。安装依赖时需确保 package.json 中的razzle-plugin-typescriptrazzlerazzle-dev-utils版本一致,并额外安装ts-loaderts-jesttypescript等。

方式二:自定义 options

来自 packages/razzle-plugin-typescript/README.md 的完整示例,把每个可调项都显式写了出来:

// razzle.config.js module.exports = { plugins: [ { name: 'typescript', options: { useBabel: false, tsLoader: { transpileOnly: true, experimentalWatchApi: true, }, forkTsChecker: { eslint: { files: ['*.js', '*.jsx', '*.ts', '*.tsx'], } }, }, }, ], };

配置项的语义与默认值

结合 packages/razzle-plugin-typescript/index.js 源码中的defaultOptions,三个配置项说明如下:

useBabel: boolean(默认false

是否在 TS 文件的编译链中保留 Babel。从源码 packages/razzle-plugin-typescript/index.js 可以看到其行为差异:

  • false(默认):不仅把babel-loader从 TS 文件上排除,还会把 webpack 规则中所有babel-loader规则直接过滤掉,TS/TSX 文件只走ts-loader
  • true:把babel-loaderuse链排在ts-loader之前([...babelLoader.use, ...tsLoader.use]),这样既保留 JS/TS 互操作,也能让 Babel 插件(如babel-plugin-styled-components)作用于 TSX 文件。

需要特别说明:源码第 40 行babelLoader.exclude = [/\.ts$/, /\.tsx$/]会在任何情况下先让babel-loader跳过 TS 文件——这是防止"双份转译"的关键防线。

tsLoader: TSLoaderOptions(默认{ transpileOnly: true, experimentalWatchApi: true }

透传给ts-loader的选项。transpileOnly: true表示只做转译不做类型检查(类型检查由fork-ts-checker-webpack-plugin在独立进程中承担,见下文);experimentalWatchApi: true启用ts-loader的实验性监听 API,可显著加速开发期的增量编译。在 index.js 中,插件会复用babel-loaderinclude字段(即 Razzle 确定的待转译目录),避免把node_modules等目录纳入 TS 编译范围。

forkTsChecker: TSCheckerOptions(默认启用类型检查 + ESLint)

透传给fork-ts-checker-webpack-plugin的选项,该插件把类型检查和 lint 放到独立进程执行,避免阻塞 webpack 编译主流程。默认行为是async(在开发模式下异步执行)、typescript: trueformatter: 'codeframe',且 index.js 默认对./src/**/*.{ts,tsx,js,jsx}开启 ESLint 检查。

插件源码剖析:它到底对 webpack 做了什么

razzle-plugin-typescript的核心逻辑全部在 modifyWebpackConfig 钩子中,共五步,与官方示例 README 中"手动改造 webpack"的思路一一对应:

1. 扩展模块解析后缀

config.resolve.extensions = [...config.resolve.extensions, '.ts', '.tsx'];

让 webpack 在import时可以省略.ts/.tsx后缀解析模块。

2. 定位babel-loader规则

通过razzle-dev-utilsmakeLoaderFinder('babel-loader')(见 packages/razzle-plugin-typescript/helpers.js)在 Razzle 内部 webpack 配置中安全地找到babel-loader。如果找不到会直接抛出错误(index.js),因为需要借用它的include来推导ts-loader的编译范围。

3. 排除 TS 文件,避免 Babel 重复转译

babelLoader.exclude = [/\.ts$/, /\.tsx$/];

4. 注入ts-loader规则

新增test: /\.tsx?$/的规则,usets-loader并合并用户自定义选项(index.js)。

5. 增加类型检查与开发期性能优化

仅在客户端(opts.env.target === 'web')构建中追加ForkTsCheckerWebpackPlugin;开发模式下还会关闭output.pathinfo并精简optimizationremoveAvailableModulesremoveEmptyChunkssplitChunks均置为false),这些优化手段来自微软 Outlook 团队关于"webpack × TypeScript 增量构建提速"的实践经验(源码注释见 index.js)。

也就是说:插件方案与示例 README 中"手动定位 babel-loader 并换装 ts-loader"的做法在原理上完全等价,插件只是把这个过程自动化、参数化,并额外附赠了类型检查与开发期构建优化。

手动改造方案(不依赖插件)

官方示例文档 examples/with-typescript-plugin/README.md 也给出了不依赖插件的手动思路:在razzle.config.js中通过modify钩子定位运行babel-loader的规则并换成ts-loader,同时扩展.ts/.tsx解析后缀。这与插件内部实现完全一致,适合希望完全掌控 webpack 配置的进阶用户。

渐进迁移:两种 loader 并存的情况

文档特别强调了一个场景:如果你是从 JS 项目渐进式迁移到 TypeScript,可能希望babel-loaderts-loader同时运行。此时必须避免 Babel 重复处理 TS 文件(否则 Razzle 会做两遍转译,大项目上 HMR 会非常慢)。如果选择并存,需要在 Jest 的transform中补充 JS 转译规则:

"^.+\\.(js|jsx)$": "<rootDir>/node_modules/razzle/config/jest/babelTransform.js",

这样.js/.jsx文件继续由 Babel 转换,而.ts/.tsx交给ts-jest,两者互不干扰。

改造 Jest:让 TS/TSX 测试跑起来

TSX 组件测试依赖 Jest 的转换器配置。示例在 package.json 中通过jest字段覆盖了 Razzle 的默认 Jest 配置(官方示例 README 中给出的是指向ts-jest/preprocessor.js的等价写法,当前示例实际使用"ts-jest"字符串形式,两者等效):

{ "jest": { "transform": { "\\.(ts|tsx)$": "ts-jest", "\\.css$": "<rootDir>/node_modules/razzle/config/jest/cssTransform.js", "^(?!.*\\.(js|jsx|css|json)$)": "<rootDir>/node_modules/razzle/config/jest/fileTransform.js" }, "testMatch": [ "<rootDir>/src/**/__tests__/**/*.(ts|js)?(x)", "<rootDir>/src/**/?(*.)(spec|test).(ts|js)?(x)" ], "moduleFileExtensions": [ "ts", "tsx", "js", "json" ], "collectCoverageFrom": [ "src/**/*.{js,jsx,ts,tsx}" ] } }

各字段的作用:

  • transform:三类文件的转换器。ts-jest负责 TS/TSX;CSS 和静态资源文件分别走 Razzle 提供的 cssTransform.js 与 fileTransform.js(位于razzle/config/jest/下),避免 Jest 因无法解析 CSS/图片而报错;
  • testMatch:测试文件的匹配范围,兼容__tests__目录与*.test.ts(x)/*.spec.ts(x)命名;
  • moduleFileExtensions:将tstsx加入模块解析后缀,使测试中的import './App'能解析到App.tsx
  • collectCoverageFrom:覆盖率统计覆盖 JS/JSX/TS/TSX 全部源码。

对应的 devDependencies 中需要出现ts-jest@types/jest(见 package.json),这是示例 README 明确提到的前提条件。

TypeScript 编译器配置与资源声明

tsconfig.json 要点

tsconfig.json 沿用了微软官方 TypeScript-React-Starter 的配置风格(示例 README 有明确说明),关键项包括:

  • "jsx": "react":编译 JSX 为React.createElement
  • "module": "commonjs":与 Node 端服务端渲染的运行环境匹配;
  • "esModuleInterop": true"allowSyntheticDefaultImports": true:支持import express from 'express'这类默认导入写法(src/server.tsx 中正是如此使用);
  • "strictNullChecks": true"noImplicitAny": true等严格性开关保持开启,但"strict": false,便于从 JS 渐进迁移;
  • "exclude"中排除了node_modulesbuildrazzle.config.js等无需类型检查的目录。

资源模块的类型声明

webpack 会处理 SVG 等静态资源,但 TypeScript 不知道import './react.svg'是什么类型,因此示例在 typings/index.d.ts 中声明了通配模块:

declare module '*.svg' { const content: any; export default content; }

同时 tsconfig 的"types": ["typePatches", "node", "webpack-env"](tsconfig.json)确保这些补丁声明、Node 类型与 webpack 环境变量类型(如module.hotprocess.env.RAZZLE_ASSETS_MANIFEST)在全局生效。

总结与选型建议

在 Razzle 项目中使用 TypeScript,建议按以下原则决策:

  1. 常规新项目:优先使用 Razzle 内置的 Babel 对 TypeScript 的支持(参照 with-typescript 示例),配置量最小;
  2. 需要独立类型检查或 Babel 插件作用于 TSX:选择razzle-plugin-typescript,它会在编译期通过ts-loader+fork-ts-checker-webpack-plugin提供类型检查与 ESLint,并自动优化开发期构建性能;
  3. 正在从 JS 渐进迁移:可让两种 loader 并存,但务必配置好 Jest 的babelTransform规则,并理解"双 loader 意味着双倍转译开销"的代价。

无论走哪条路径,Jest 的ts-jest转换器、moduleFileExtensions扩展与资源类型声明(typings/index.d.ts)都是让 TSX 代码在测试与构建链路上顺畅运行的必要配套,这也是本示例项目最有参考价值的完整闭环配置。

  • 前端
  • 构建工具
  • 前端构建
  • 后端

【免费下载链接】razzle

✨ Create server-rendered universal JavaScript applications with no configuration

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

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询