TypeScript成为一种非常流行的JavaScript语言是有原因的。它的类型系统和编译器会在您的软件运行之前在编译时捕获各种错误,并且添加的代码编辑器功能使其成为开发人员的高效环境。但是,如果您想用TypeScript编写一个库或包,同时又想用JavaScript分发它,这样您的最终用户就不必手动编译您的代码,会发生什么情况呢?我们如何在获得TypeScript的所有好处的同时使用ES模块等现代JavaScript功能进行编写?本文旨在解决所有这些问题,并为您提供一个设置,让您可以自信地编写和共享TypeScript库,并为您的包的使用者提供轻松的体验。获取本文完整代码:在公众号:typescript-es-modules目录下回复关键字[toc]入门我们首先要做的是新建一个项目。在本教程中,我们将创建一个基本的数学包——而不是一个服务于任何实际目的的包——因为它可以让我们在不偏离包的实际功能的情况下演示我们需要的所有TypeScript。首先,创建一个空目录并运行npminit-y来创建一个新项目。这将创建您的package.json并为您提供一个空项目来处理:$mkdirmaths-package$cdmaths-package$npminit-y现在,我们可以添加我们的第一个也是最重要的依赖项:TypeScript!$npminstall--save-devtypescript安装TypeScript后,您可以通过运行tsc--init来初始化TypeScript项目。tsc是“TypeScriptCompiler”的缩写,是TypeScript的命令行工具。为确保您运行的是我们刚刚在本地安装的TypeScript编译器,请在命令前加上npx前缀。npx是一个很棒的工具,它会在node_modules文件夹中查找您提供的命令,因此通过为命令添加前缀,我们确保我们使用的是本地版本,而不是您可能安装的任何其他TypeScript全局版本。$npxtsc--init这将创建一个tsconfig.json文件,该文件负责配置我们的TypeScript项目。你会看到该文件有数百个选项,其中大部分都被注释掉了(TypeScript支持tsconfig.json文件中的注释)。我已将文件缩减为仅启用的设置,如下所示:"forceConsistentCasingInFileNames":true}}我们需要对这个配置进行一些更改,使我们能够发布带有ES模块的包,所以让我们现在看看选项。配置tsconfig.json选项如果您正在寻找所有可能的tsconfig选项的完整列表,您可以在TypeScript网站上找到这个方便的参考。让我们从目标开始,它定义了您将在浏览器中提供代码的JavaScript支持级别。如果您必须使用可能不具备所有最新和最强大功能的旧版浏览器,您可以将其设置为ES2015。如果你真的需要最大的浏览器覆盖率,TypeScript甚至会支持ES3。我们将在此模块中使用ES2015,但可以随意进行相应更改。例如,如果我正在为自己构建一个快速的副项目,并且只关心最先进的浏览器,我很乐意将其设置为ES2020。选择模块系统接下来,我们必须决定将用于该项目的模块系统。请注意,这不是我们要编写的模块系统,而是TypeScript的编译器在输出代码时将使用的模块系统。发布模块时我喜欢做的事情是发布两个版本:一个带有ES模块的现代版本,这样捆绑工具可以巧妙地对未使用的代码进行tree–shaking,这样支持ES模块的浏览器可以使用CommonJS导入文件版本模块的一部分(如果您在Node中工作,您将习惯于要求代码),因此较旧的构建工具和Node.js环境可以轻松地运行该代码我们稍后将看到如何使用不同的选项进行两次捆绑,但是对于现在,让我们配置TypeScript来导出ES模块。我们可以通过将模块设置设置为ES2020来做到这一点。你的tsconfig.json文件现在应该是这样的:}}写一些代码在我们谈论捆绑代码之前,我们需要写一些代码!让我们创建两个小模块,它们既可以导出函数,又可以为导出所有代码的模块提供一个主入口文件。我喜欢将所有TypeScript代码放在src目录中,因为这意味着我们可以将TypeScript编译器直接指向它,所以我将使用以下代码创建src/add.ts:exportconstadd=(x:number,y:number):number=>{returnx+y;}我还将创建src/subtract.ts:exportconstsubtract=(x:number,y:number):number=>{returnx-y;}最后,src/index.ts将导入我们所有的API方法并再次导出:import{add}from'./add.js'import{subtract}from'./subtract.js'export{add,subtract}这意味着,用户可以得到我们的功能通过只导入他们需要的东西,或者通过获取所有东西来实现。从“数学包”导入{添加};import*asMathsPackagefrom'maths-package';请注意,在src/index.ts中,我的导入包含文件扩展名。如果你只想支持Node.js和构建工具(例如webpack),你不需要这个,但如果你想支持支持ES模块的浏览器,你需要文件扩展名。使用TypeScript编译让我们看看是否可以使用TypeScript来编译我们的代码。在执行以下操作之前,我们需要对tsconfig.json文件进行一些调整:{“compilerOptions”:{“target”:“ES2015”,“module”:“ES2020”,“strict”:true,“esModuleInterop”:true,"forceConsistentCasingInFileNames":true,"outDir":"./lib",},"include":["./src"]}我们做了两个更改:compilerOptions.outDir-这告诉TypeScript获取我们的代码被编译成一个目录。在本例中,我已告诉它命名目录lib,但您可以随意命名。include-告诉TypeScript我们希望将哪些文件包含在编译过程中。在我们的例子中,我们所有的代码都在src目录中,所以我将其传入。这就是为什么我喜欢将所有TS源文件保存在一个文件夹中,这使得配置非常容易让我们试试看会发生什么!我发现在调整我的TypeScript配置时,最适合我的是调整、编译、检查输出,然后调整。不要害怕尝试这些设置,看看它们如何影响最终结果。要编译TypeScript,我们将运行tsc并告诉它tsconfig.json在哪里使用-p标志(“project”的缩写):npxtsc-ptsconfig.json如果您有任何类型错误或配置问题,他们会出现在这里。如果没有,您应该什么也看不到-但请注意,您有一个新的lib目录,里面有文件!TypeScript编译不会将任何文件合并在一起,而是将每个模块转换成其对应的JavaScript。让我们看看三个输出文件://lib/add.jsexportconstadd=(x,y)=>{returnx+y;};//lib/subtract.jsexportconstsubtract=(x,y)=>{returnx-y;};//lib/index.jsimport{add}from'./add.js';import{subtract}from'./subtract.js';export{add,subtract};它们看起来与我们的输入非常相似,但没有我们添加的类型注释。这是意料之中的:我们在ES模块中编写代码,并告诉TypeScript也以这种形式输出它。如果我们使用任何比ES2015更新的JavaScript功能,TypeScript会将它们转换为ES2015友好的语法,但在我们的例子中,我们没有使用它,所以TypeScript基本上保留了所有内容。该模块现在可以发布到npm上供其他用户使用,但是我们有两个问题需要解决:我们不会在代码中发布任何类型信息。这不会对我们的用户造成干扰,但这是一个错失的机会:如果我们也发布类型信息,使用支持TypeScript的编辑器或使用TypeScript编写应用程序的人将获得更好的体验。Node还不支持开箱即用的ES模块。发布CommonJS版本也很好,因此Node.js没有额外的工作。ES模块支持将出现在Node13及更高版本中,但生态系统需要一段时间才能赶上。发布类型定义我们可以通过要求TypeScript在编写代码时发出声明文件来解决类型信息问题。该文件以.d.ts结尾,将包含有关我们代码的类型信息。将其视为源代码,除了它不包含类型和实现之外,它只包含类型。让我们添加"declaration":true到tsconfig.json(在"compilerOptions"部分),然后再次运行npxtsc-ptsconfig.json。提示:我想在我的package.json文件中添加一个脚本来编译,所以不用输入:"scripts":{"tsc":"tsc-ptsconfig.json"}然后我可以运行npmruntsc来编译我的代码。您现在将在每个JavaScript文件(例如add.js)旁边看到一个等效的add.d.ts文件,如下所示://lib/add.d.tsexportdeclareconstadd:(x:number,y:number)=>数字;所以现在当用户使用我们的模块时,TypeScript编译器将能够选择所有这些类型。发布到CommonJS的最后一块拼图也是配置TypeScript以输出使用CommonJS的代码版本。为此,我们可以制作两个tsconfig.json文件,一个用于ES模块,一个用于CommonJS。但是,我们可以让CommonJS配置扩展我们的默认值并覆盖模块设置,而不是复制所有配置。让我们创建tsconfig-cjs.json:{"extends":"./tsconfig.json","compilerOptions":{"module":"CommonJS","outDir":"./lib/cjs"},}重要的是第一行,表示这个配置默认会继承tsconfig.json的所有设置。这很重要,因为您不需要跨多个JSON文件同步设置。然后覆盖需要更改的设置。我相应地更新了模块,然后将outDir设置更新为lib/cjs,这样我们就可以输出到lib中的子文件夹。此时,我还更新了package.json中的tsc脚本:"scripts":{"tsc":"tsc-ptsconfig.json&&tsc-ptsconfig-cjs.json"}现在,当我们运行npmruntsc,我们将编译两次,我们的lib目录将如下所示:这有点乱,让我们通过更新tsconfig中的outDir选项将ESM输出更新到lib/esm接下来,我们将设置模块属性。这是应该链接到我们包的ES模块版本的属性。支持此功能的工具将能够使用此版本的软件包。所以它应该设置为./lib/esm/index.js。接下来,我们将文件条目添加到package.json。在这里,我们定义了发布模块时应包含的所有文件。我喜欢使用这种方法在最终模块中明确定义要推送到npm的文件。这样我们就可以减少模块的大小。例如,我们不发布src文件,而是发布lib目录。如果您在文件条目中提供了一个目录,则默认情况下会包含其所有文件和子目录,因此您不必将它们全部列出。提示:如果您想查看模块中将包含哪些文件,请运行npxpkgfiles以获取列表。现在我们的package.json中有以下三个附加字段:"main":"./lib/cjs/index.js","module":"./lib/esm/index.js","files":["lib/"],最后一步。由于我们要发布lib目录,因此在运行npmpublish时需要确保lib目录是最新的。npm文档中有一节介绍如何执行此操作——我们可以使用prepublishOnly脚本。当我们运行npmpublish时,这个脚本会自动为我们运行:"scripts":{"tsc":"tsc-ptsconfig.json&&tsc-ptsconfig-cjs.json","prepublish":"npmruntsc"},请注意还有一个名为prepublish的脚本,这使得选择哪个脚本有点混乱。npm文档提到了这一点:prepublishisdeprecated,如果你只想在发布时运行你的代码,你应该使用prepublishOnly。这样,运行npmpublish将运行我们的TypeScript编译器并在线发布模块!我把这个包贴在@jackfranklin/maths-package-for-blog-post下,虽然我不推荐使用它,你可以浏览文件看看。我还将所有代码上传到CodeSandbox,因此您可以根据需要下载或破解它。就这样结束了!我希望本教程向您展示了开始使用TypeScript并不像乍看起来那么困难,并且通过一些调整,您可以让TypeScript轻松输出您可能需要的多种格式。
