【问题标题】:How do I configure a TypeScript project that uses JavaScript modules compiled from PureScript?如何配置使用从 PureScript 编译的 JavaScript 模块的 TypeScript 项目?
【发布时间】:2018-03-12 08:21:16
【问题描述】:

TL;DR 我想为已编译的 PureScript 模块创建 TypeScript 类型,并将它们分发到我的 npm 包中。我很乐意手动维护这些类型,我只是不知道我需要在 tsconfig.json(上游和下游)和 package.json 中添加什么。


我有一个project,其核心功能是在 PureScript 中使用 TypeScript CLI 实现的,所有这些最终都通过 npm 作为 JavaScript 分发。我创建了一个布局更简单的similar project

.
├── cli                   # TypeScript CLI project
│   ├── main.ts
│   └── tsconfig.json
│
├── output                # Compiled PureScript modules
│   └── People
│       ├── externs.json
│       └── index.js
│
├── src                   # PureScript source
│   └── People.purs
│
└── types                 # TypeScript types for PureScript modules
    └── People.d.ts

src/People.purs我定义了核心功能:

module People where

type Person = { name :: String }

david :: Person
david = { name: "David "}

types/People.d.ts 中,我为 PureScript 模块定义了 TypeScript 类型,以便我可以安全地从 TypeScript 中使用它们:

declare module "People" {
    export interface Person {
        name: string;
    }

    export const david: Person;
}

最后,在cli/main.ts 中,我想用我定义的 TypeScript 类型导入已编译的 PureScript 模块:

import * as People from "People";

console.log(People.david);

我可以构建它,但是当我尝试运行终极 JavaScript (node cli/main.js) 时,tsc 生成的 require 调用失败,因为它们不使用绝对路径--require("People") 应该在这种情况下是require("./People")。将 TypeScript 中的 import 语句设为 relative 会解除类型的关联。

我很难实现这些目标:

  • 维护从 TypeScript 导入的所有 PureScript 模块的 TypeScript 类型。
  • 确保 TypeScript 类型检查。
  • 确保对 PureScript 模块的 require 调用在运行时解析。
  • 使用 npm 包分发 TypeScript 类型,以便 TypeScript 消费者也可以使用这些类型。

如果您有需要来自 TypeScript 的 PureScript 模块并通过 npm 分发所有这些模块的经验,我非常感谢您的指导!

【问题讨论】:

    标签: node.js typescript npm purescript


    【解决方案1】:

    您的 Purescript 模块称为“Person”,但应该是“People”。我认为通过相对路径导入,您可能会部分到达那里,但我不认为在 d.ts 文件中输入相对路径之类的东西。

    我检查了您的项目并重命名了模块,然后我将 allowJs 设置为 true 并内联输入了模块:

    import * as OutputPeople from "../output/People";
    
    type Person = {
      name: string;
    };
    
    type PeopleModule = {
      david: Person
    }
    
    const People: PeopleModule = OutputPeople
    
    console.log(People.david);
    

    我把我的修改放在了这里的一个分支上:https://github.com/justinwoo/ps-js-ts-interop/commit/fc7c1239f9d1bac1cf2bea35f7a07d30c46a5f64

    【讨论】:

    • 啊,是的,我看到了错字,但这不是问题。我尝试了您建议的手动转换并且它有效,但是类型没有与 npm 一起分发,所以我的库的客户没有得到类型。
    【解决方案2】:

    如果您确实需要使用绝对路径来获取您的 PureScript 包(例如 import * as People from 'People'),我认为您将需要破解节点模块解析算法(通过将您的 PureScript 输出放在 node_modules 的子目录中) ./output,例如)。

    但如果这不那么重要并且您可以使用相对导入(例如 import * as People from './People'),您可以执行以下操作:

    1. 对 PureScript 模块使用相对导入:

      cli/main.ts:

      import * as People from './People';
      
    2. 将 PureScript 类型与 TypeScript 源并排放置:

      .
      ├── People
      │   └── index.d.ts
      ├── main.ts
      └── tsconfig.json
      
    3. 将您的 PureScript 类型更改为本地模块声明而不是全局:

      cli/People/index.d.ts:

      export interface Person {
          name: string;
      }
      
      export const david: Person;
      
    4. 现在,要使其与 Node 模块解析一起使用,您需要将两个编译器的 JS 输出放在同一目录中。我们还希望 TypeScript 为我们的 TS 文件发出类型声明(在 npm 上发布时可以使用)。我们还将删除 paths TS 编译器选项(不再需要),并为 Node 类型添加全局类型引用。

      cli/tsconfig.json:

      {
        "compilerOptions": {
          "target": "es5",
          "lib": ["es2015"],
          "moduleResolution": "node",
          "baseUrl": ".",
          "outDir": "./../output",
          "types": [
            "node"
          ],
          "declaration": true
        },
        "include": [
            "*.ts"
        ]
      }
      
    5. 快完成了。现在,我们需要确保 PureScript 模块的类型定义最终在输出目录中可供使用。 TypeScript 在生成类型定义输出时不会在其输出中复制 .d.ts 文件(因为这些不是由 TS 编译器创建的)——我们需要对此进行补偿。我不知道这是否是实现这一目标的最佳(甚至是好的?)方法,但以下方法对我有用:

      package.json:

      "scripts": {
        "build": "pulp build && tsc -P cli/tsconfig.json && cd cli && rsync -am --include='*.d.ts' -f 'hide,! */' . ./../output"
      },
      ...
      
    6. 最后但同样重要的是,我们需要指定 JS 的入口点和 npm 消费者的类型定义:

      package.json:

      "main": "output/main.js",
      "typings": "output/main.d.ts",
      "files": [
        "output/"
      ]
      ...
      

    我相信这并没有涵盖创建混合语言和混合类型 npm 包的所有细节,但希望这可以作为一个起点。

    【讨论】:

    • 是的,据我所知,这绝对是在其他设置中完成的方式。为了确保您对各个类型都有正确的类型,我认为使用我的 Typescript codegen 库之类的方法最好,如下所示:github.com/justinwoo/ps-js-ts-interop/tree/ohyes-codegen
    • 啊,也许这个“本地模块”概念会比我的命名空间方法更好?一个比另一个有优势吗?
    • @DavidSiegel 我的回答中的方法允许您对模块和文件系统进行一对一的映射——就像您用 TypeScript 或 JavaScript 编写这些一样。这也是正确的映射 wr/t 如何从 TypeScript 导入这些模块(在您的情况下导入的正确方法仍然是 import * as People from './People')。您使用命名空间的方法使它们全局可用(例如,可从任何地方导入顶级模块),如果您要拥有不完全平坦的模块结构(命名冲突、命名空间污染),这可能不是您想要的。
    【解决方案3】:

    声明命名空间而不是模块使一切正常。比如在types/People.d.ts:

    declare namespace People {
        export interface Person {
            name: string;
        }
    
        export const david: Person;
    }
    
    export = People;
    export as namespace People;
    

    在编译 PureScript 模块之后,在编译 TypeScript 之前,我cp types/People.d.ts output/People/index.d.ts。这使得 TypeScript 代码对相同的绝对导入(例如 import * as People from "People";)感到满意,并且 TypeScript 库也可以在没有额外配置的情况下看到这些类型。

    但仍有一些问题:

    • 我仍然需要在编译的 TypeScript 中相对化 PureScript 模块导入(通过在构建后步骤中添加 ../);不过,我的图书馆的消费者不必这样做,因为它通过 npm 并且神奇地使它工作。
    • 我不知道如何导出带句点的命名空间,所以像 Data.Maybe 这样的模块由像 Data_Maybe 这样的命名空间表示。
    • 我不太了解命名空间与模块,因此可能还有其他注意事项。

    【讨论】:

      猜你喜欢
      • 2016-06-17
      • 2017-01-22
      • 2017-02-13
      • 1970-01-01
      • 2021-01-12
      • 1970-01-01
      • 1970-01-01
      • 2019-03-19
      • 1970-01-01
      相关资源
      最近更新 更多