拆分TypeScript项目的学习收获:处理编译缓存和包缓存,引用本地项目,使用相对路径

最近需要将工作中的一个TS包拆出一部分代码,以便在多个团队和项目中共享。原以为这会是一项特别简单的工作,但是也花了两天才大致拆成功。因此记录一下,也给有类似需求的同学一点经验。

所拆项目的大致功能:整个项目的结构大致分为:

  1. 一个基类和多个实现类,我们需要拆出一个实现类到包里,因此基类也得放到这个包里
  2. 一个代码生成工具,会读取目录下的所有配置文件并生成ts代码,这个工具也得放到包里

我们希望拆完之后的项目满足这些条件:

  1. 拆出的包(以下称子包或子项目)可以独立发布,方便外部用户使用
  2. 为了快速验证和减少开发过程中的额外步骤,原项目(以下也可能称母项目)可以本地引用子项目,而不用每次有修改都先提一个PR发布新版本,再通过升级版本号引用最新的改动

总而言之,就是我们虽然对外发布了这个包,且外部会通过包名+版本号来引用,但是团队内部项目开发时还是希望能通过本地引用直接引用到最新的改变。下面总结一下如何引用本地包和拆包后的代码需要注意什么。

小心缓存带来的编译、包导入不生效问题

由于接下来需要经常修改子包的配置,所以要特别注意缓存带来的问题,这样如果遇到奇怪的问题还能有印象是缓存带来的问题。

虽然TS老手可能已经知道缓存的坑,但是作为新手还是很容易被缓存导致的问题搞得很迷惑。如果遇到奇怪的问题,比如

  1. 敲了tsc --build却没有生成编译后的文件
  2. 有的ts编译生成了.d.ts,有的却没有,但是.js文件都存在,造成子项目引用时找不到类型
  3. 删除node_modules,再跑yarn install也不会安装依赖
  4. 子项目没有升版本就打包,母项目引用时还是安装的没有修改之前的版本

这三个都是我在拆子包的开发过程中遇到的问题,经常让我百思不得其解,还以为是自己改了什么配置改错了,但是改回来之后还是不工作。

其中1&2都是由于ts编译缓存造成的,缓存文件的文件名叫tsconfig.tsbuildinfo,它会记录最近一次编译,用于支持ts的增量编译功能。根据配置的不同(是否开启 composite=true),可能生成在项目根目录或者是构建输出目录下。如果删除了整个构建输出目录(比如下文我们会配置/lib为输出目录)但是没有清除缓存,那重新跑构建命令,也不会生成构建目录!因为ts编译器是通过对比源文件和增量编译缓存文件的差别来决定是否要重新编译的,而如果擅自删除了输出目录,缓存文件和输出文件很就存在不同步的情况,这就是为什么重新构建可能不生效的原因!

3&4的问题是因为我们的项目中使用了yarn,而npm/yarn会有缓存。比如3#就是要删除yarn.lock/package-lock.json文件后再yarn install

而4#最简单的办法是本地引用包时,一旦子包修改,就升一个版本再打包,等要push代码的时候再改回原来的版本。也有更麻烦的办法,使用yarn cache clean {packageName}手动清除缓存之后再add回来

引用本地包的方法

在介绍拆包需要怎么改代码之前,我们先说怎么本地引用包,这是因为如果不知道拆完的包该怎么被本地引用,那根本没法编译原来的项目,更别提怎么测试子项目功能是否正常了。

通过查找多种资料(包括问AI),大概有以下几种引用本地包的方法:

  1. tsconfig.ts中配置包的本地映射:在compilerOptions.paths中加上一行"{packageName}":["{pathToLocalPackage}"],个人感觉这种方法对于测试堪称完美,子项目的源码修改会立即反映到别的项目中。它的缺陷是没法很好地测到子包被发布出去之后,其它项目通过package.json引用时是否能正常工作,这是因为子项目中的代码都是源码级引用,随母项目的编译而编译,因此会掩盖一些问题,比如使用绝对路径(比如import xx from src/moduleA来导入模块可能并不会报错,但是通过package.json引用时就会找不到指定模块。
  2. 直接在package.json里使用引用本地项目的目录:使用yarn add或者直接修改package.json,添加"{packageName}":"file:{subPeojectFolderPath}"。这种方式也能实现源码级引用,但是它有一个很大的缺陷,file引用目录时,会将整个项目文件夹复制到node_modules内,导致子项目里的node_modules也会被原样拷贝过去。占用空间不说,它会导致子项目、母项目对同一包的引用出现冲突。AI还提供了一些使用peerDependencies的建议,实际上并没有用:它并非引用包的不同版本出现了冲突,而是子项目和母项目里的node_modules起了冲突!那可能有人会说,如果我子项目不安装依赖呢?如果子项目不安装依赖,那也没法编译,也会造成很多问题。所以实际上我最不推荐这种做法。
  3. 使用yarn link:先在子项目下执行yarn link,然后在母项目下执行yarn link "{packageName}"。这种方式和1#很类似,但是子包是通过编译后代码来引用的(去决定于子包里package.json如何配置),更能测试出一些引用问题。
  4. 先将子项目本地打包,再通过文件引用打包出的tar ball:先在子项目下执行yarn pack,然后在母项目下执行yarn add"{packageTarBallPath}"。这种方法是最能模拟子包发布之后,被其它项目引用的行为的,因为它可以测试我们的打包配置是否正确,比如后文提到的tsconfig.jsonpackage.json这两个配置文件里,错误的配置会导致包无法被正确引用。
  5. yarn workspace:创建一个workspace,将两个项目添加到workspace中。两个项目会共享一个node_modules依赖,又保持独立性,避免了2#中的问题,很优雅。缺点是可能需要改变项目的目录结构,需要用一个父目录来包含多个子项目。

综上,这些方法中不推荐2#,如果可以接受改变目录结构则5#看起来是最优雅的办法。

如果不能改变目录结构,那追求便捷度首推1#,因为修改可以立刻反映到母项目,IDE可以立刻检查出有没有语法错误,编译过程也很流畅,同时开发中也无需跑额外的命令来关联两个项目;如果按照与真实引用环境的差别排序,首推4#。剩下的3#有点鸡肋,因为它不是通过修改package.json来改变项目的引用关系,而是需要跑两个额外的命令临时关联两个项目,因此只适合临时测试。

我在自己的项目中,用的是方法4#。因为我们依赖的库如果使用方法1#会导致一些奇怪的错误,子项目变动也不会很大,因此我们主要考虑保证项目能正确运行。只不过这样每次如果需要修改并测试子项目,都要重新编译。实际开发可以考虑1#和4#结合的方法,先用1#保证编译通过,再用4#保证运行正确。

修改子项目

除了把代码都拷到另一个独立的项目之外,还有一些值得注意的点,主要是注意

  1. 配置好package.jsontsconfig.json,确保打包的代码能被正确引用
  2. 避免绝对路径代码,要使用位置无关性代码,确保子包中代码运行结果符合预期

tsconfig.json和package.json

我们需要修改tsconfig.json来保证编译后的代码会生成在正确的目录,同时为了保证发布的包可以被正确引用我们需要配置package.json,写明项目的入口文件。

tsconfg

  • compilerOptions.outDir指明了编译后的js/ts文件放在哪个目录下。比如我们配置成lib,那就会在lib文件夹下看到编译后的代码
  • compilerOptions.rootDir指明了源码文件的储存路径。编译后生成的文件会保持rootDir为根目录的目录结构。通常我们的源代码放在src目录下,如果不设置rootDir时,默认会以项目所在的目录为根目录,编译后的代码会放在lib/src目录下。当rootDir设置成了src,那生成的编译后代码就会放在lib下而不是lib/src下,使打包出的目录层级更清晰(否则保持src目录会让人很迷惑,一般src用于存放源代码而不是生成的代码)

package.json

以下配置需要匹配tsconfig.json的配置,如果不太确定,可以跑tsc编译看看生成的目录里对应的文件放在哪了。

  • types:指定了编译出的.d.ts文件。它是ts的类型声明文件,通常用于保证ts编译期类型系统正常工作
  • main:指定了编译出的.js文件入口。它包含了真正的代码实现(而编译后的.d.ts只包含类型声明,有点类似于接口与实现或者头文件和源文件的差别)

测试

由于这两个配置文件主要影响子包中的代码能否被正确引用,所以通过在母项目中跑tsc --build编译就能看出配置是否正确。

本地模块import改为相对路径引用

本地模块导入指的是通过类似import {xx} from "../classA"这样的代码来导入本地某个模块。如果我们的包不需要给别人用,那我们完全可以写一个绝对路径,比如import {xx} from "src/folderA/classA"。但是另一个项目B引用时如果遇到绝对路径的导入,一般会以项目B的根目录作为根目录来查找这个模块,而子包里写的这句import则是以子包的根目录作为根目录,自然就会导致找不到模块的错误。

可以搜索一下子项目里有没有用到"src这样的关键字,用到的话记得改掉。

使用外部传入的路径来计算外部文件的真实路径

假如子包中有一些读取外部文件(非子包文件)的代码,比如读取某个目录下所有的配置文件,读取某个TS模块等,我们都要甄别这些路径,把读取的路径改为真实路径。比如曾经我们的代码在拆之前,可能相对于读取的目录有固定的相对路径比如__dirname/../anotherFolder/anotherModule,拆分后得让代码调用方把真实路径传进来,而不能还用之前基于当前模块路径计算的方法。从代码层面看,这些方法需要传的参数变多了。

可以搜索一下子项目里有没有用到__dirname__filename这样的关键字,用到的话记得改掉。

发布

确认好发布的包可以正常工作后,发布基本上是最简单的步骤了。通常工作中会使用独立的npm仓库,可以用如下命令发布某个目录下的包,或者打包好的tarball到指定仓库

yarn publish [<tarball>|<folder>] --registry <url>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/news/920910.shtml
繁体地址,请注明出处:http://hk.pswp.cn/news/920910.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

瑞芯微RK3576平台FFmpeg硬件编解码移植及性能测试实战攻略

本文介绍瑞芯微RK3576平台&#xff0c;FFmpeg硬件编解码移植及性能测试方法。 FFmpeg简介与实测数据 FFmpeg简介 FFmpeg是一套多媒体框架&#xff0c;能够解码、编码、转码、复用、解复用、流、过滤和播放数字音频、视频&#xff0c;提供了录制、转换以及流化音视频的完整解…

【网络安全入门基础教程】网络安全零基础学习方向及需要掌握的技能

最近总有同学问我&#xff0c;0基础怎么学网络安全&#xff1f;0基础可以转行做网络安全吗&#xff1f;网络安全有哪些学习方向&#xff1f;每个方向需要掌握哪些技能&#xff1f;今天给大家简单写一下。 我的回答是先了解&#xff0c;再入行。 具体怎么做呢&#xff1f; 首…

Altium Designer中的Net-Tie:解决多网络合并与电气隔离的利器

Altium Designer中的Net-Tie:解决多网络合并与电气隔离的利器 在复杂的PCB设计中,我们常常会遇到一些特殊的电气连接需求。例如,需要将两个或多个逻辑上独立但物理上需要连接的网络(如不同电源域的GND)在特定点进行连接(单点连接),同时又要保持其网络标识的独立性。 …

计算机毕设项目 基于Python与机器学习的B站视频热度分析与预测系统 基于随机森林算法的B站视频内容热度预测系统

&#x1f495;&#x1f495;作者&#xff1a;计算机源码社 &#x1f495;&#x1f495;个人简介&#xff1a;本人八年开发经验&#xff0c;擅长Java、Python、PHP、.NET、Node.js、Spark、hadoop、Android、微信小程序、爬虫、大数据、机器学习等&#xff0c;大家有这一块的问题…

百胜软件×OceanBase深度合作,赋能品牌零售数字化实践降本增效

8月28日&#xff0c;由OceanBase主办的“2025零售数据底座创新大会”在上海举行。大会重磅发布了由爱分析、OceanBase携手王歆、沈刚两位行业专家联合编制的《零售一体化云数据库白皮书》。白皮书系统梳理了从“大促流量应对”到“AI应用落地”的全流程方法论&#xff0c;并为不…

2025年Java在中国开发语言排名分析报告

引言 在软件定义世界的2025年&#xff0c;编程语言的战略价值已超越工具属性&#xff0c;成为产业数字化转型的核心支撑与开发者思维模式的延伸载体。TIOBE指数作为全球技术市场变化的重要晴雨表&#xff0c;通过追踪工程师分布、课程设置、供应商动态及搜索引擎数据&#xff0…

TDengine 日期时间函数 DAYOFWEEK 使用手册

DAYOFWEEK 函数使用手册 函数描述 DAYOFWEEK 函数用于返回指定日期是一周中的第几天。该函数遵循标准的星期编号约定&#xff0c;返回值范围为 1-7&#xff0c;其中&#xff1a; 1 星期日 (Sunday)2 星期一 (Monday)3 星期二 (Tuesday)4 星期三 (Wednesday)5 星期四 (T…

从RNN到BERT

目录 序列模型简介RNN循环神经网络LSTM长短期记忆网络Transformer架构BERT模型详解实践项目 序列模型简介 什么是序列数据&#xff1f; 序列数据是按照特定顺序排列的数据&#xff0c;其中元素的顺序包含重要信息。常见的序列数据包括&#xff1a; 文本&#xff1a;单词或字…

椭圆曲线的数学基础

一、引言 椭圆曲线密码学&#xff08;Elliptic Curve Cryptography, ECC&#xff09;是现代公钥密码学的核心工具之一。 相比传统的 RSA&#xff0c;ECC 可以用 更短的密钥长度 提供 同等甚至更高的安全性&#xff0c;因此被广泛应用于区块链、TLS、移动设备加密等场景。 要理解…

从能耗黑洞到精准智控:ASCB2智慧空开重构高校宿舍用电能效模型

随着智慧校园建设不断推进&#xff0c;校园宿舍的用电管理面临着安全性、智能化与可视化的多重挑战。传统用电监控手段在数据采集、实时控制和故障响应方面存在明显不足。安科瑞ASCB2系列物联网断路器通过集成多种智能感知、保护控制与通信手段&#xff0c;为高校宿舍提供了一种…

前端学习——JavaScript基础

前面我们已经学习了前端代码的骨架——HTML和前端美化工具——CSS。但是作为界面与客户进行交互我们还需要一个语言工具——JavaScript。 因此实际上HTML、CSS、JavaScript三者是这样的关系&#xff1a; HTML: 网页的结构(骨) CSS: 网页的表现(皮) JavaScript: 网页的行为(魂) …

Ubuntu下的压缩及解压缩

一、Linxu 下常用的压缩格式 Linux 下常用的压缩扩展名有&#xff1a;.tar 、.tar.bz2、 .tar.gz 。 二、Windows 下 7ZIP 软件的安装 因为 Linux 下很多文件是 .bz2 &#xff0c; .gz 结尾的压缩文件&#xff0c;因此需要在 windows 下安装 7ZIP 软件。 7-Zip 三、Ubuntu…

金融数据安全

安全框架金融数据生命周期是指金融业机构在开展业务和进行经营管理的过程中&#xff0c;对金融数据进行采集、 传输、存储、使用、删除、销毁的整个过程。数据生命周期安全框架,遵循数据安全原则&#xff0c;以 数据安全分级为基础&#xff0c;建立覆盖数据生命周期全过程的安全…

Unity抖音小游戏快捷立项准备/改动

本文由 NRatel 历史笔记整理而来&#xff0c;如有错误欢迎指正。 1、熟读抖音接入文档&#xff0c;记录要点 Unity 小游戏接入指南_抖音开放平台 2、创建Git仓库&#xff0c;开通成员权限 美术目录&#xff0c;对程序、美术、策划全开 程序目录&#xff0c;对程序全开、对部…

Labview使用modbus或S7与PLC通信

一、modbus 1.使用VI Package Manager (VIPM)安装modbus库 2.安装好后如下显示会有Modbus Library 3.Master API作为客户端&#xff0c;如下有一个例程 4.Slave API作为服务端&#xff0c;如下有一个例程 上述两个例程是通过IP 127.0.0.1可以互相通信的。数据是一直存在服务端…

Docker Swarm 与 Kubernetes (K8s) 全面对比教程

一、引言&#xff1a;为什么需要了解这两种编排工具&#xff1f;在容器化应用部署中&#xff0c;Docker Swarm 和 Kubernetes (K8s) 是两个最主流的容器编排工具。作为一名开发者或运维工程师&#xff0c;理解它们的区别和适用场景至关重要。本教程将通过对比分析&#xff0c;帮…

开源协作白板 – 轻量级多用户实时协作白板系统 – 支持多用户绘图、文字编辑、图片处理

项目概述 Whiteboard 是一个基于 Node.js 的轻量级协作白板/画板系统&#xff0c;支持多用户实时协作绘图、文字编辑、图片处理等功能。该项目采用现代化的 Web 技术栈&#xff0c;提供直观的用户界面和丰富的交互功能。 核心特性 &#x1f3a8; 绘图功能 多种绘图工具&…

Spark自定义累加器实现高效WordCount

目录 1. 代码功能概述 2. 代码逐段解析 主程序逻辑 自定义累加器 MyAccumulator 3. Spark累加器原理 累加器的作用 AccumulatorV2 vs AccumulatorV1 累加器执行流程 4. 代码扩展与优化建议 支持多词统计 线程安全优化 使用内置累加器 5. Spark累加器的适用场景 6…

开源 | 推荐一套企业级开源AI人工智能训练推理平台(数算岛):完整代码包含多租户、分布式训练、模型市场、多框架支持、边缘端适配、云边协同协议:

&#x1f525; Github 主仓库&#xff08;优先更新&#xff09;https://github.com/roinli/SSD-GPU-POOL | Gitee 镜像仓库 > 原仓库因故暂停使用&#xff0c;本仓库为镜像项目。开源版本将持续迭代优化&#xff0c;欢迎提交 Issue 或加入社群交流。 GPU 池化平台 | AI 全…

pprint:美观打印数据结构

文章目录一、pprint.pprint()&#xff1a;美观化打印二、pprint.pformat()&#xff1a;格式化成字符串表示三、pprint() 处理包含__repr__() 方法的类四、递归引用&#xff1a;Recursion on {typename} with id{number}五、depth 参数控制 pprint() 方法的输出深度六、width 参…