Fable: [求助/设计] 改进 TypeScript 集成

创建于 2019-08-31  ·  29评论  ·  资料来源: fable-compiler/Fable

什么?

基本上,我想改进与 TypeScript 的互操作性并寻求帮助和指导。

这意味着在两个方面扩展寓言编译器:

  1. 发出 TypeScript 绑定(我们目前是手动编写的)
  2. 使用/导入 TypeScript(我们目前为此使用 ts2fable)

请随时在整个帖子中纠正我,建议替代实现,甚至指出为什么这是一个坏主意。 欢迎任何有关设计、实施或其他方面的帮助。 随意通过 Twitter 或 Slack(或邮件)私下联系。

为什么?

链接的相关问题(见本文末尾)应该给出几个合理的用例。 但特别是:

  • 在“1”之后。 在 npm 上发布基于寓言的库更容易,无需任何额外的手动工作。
  • “1。” 应该允许在具有适当 IDE 支持的 TypeScript/Javascript 中使用import "./MyFile.fs" 。 (目前还不清楚我们需要在这里做哪些额外的事情来让 IDE 获取类型)
  • “2。” 将使新手在 npm 生态系统中使用包变得更加容易
  • “2。” 可能让我们减少维护和更新类型的工作。
  • (未来的讨论,只是想想我们的声音)在 1 + 2 之后,我们可能会在 npm 上发布包。 对于 F# 工具,我们可能仍然需要包含一个参考程序集(或者只是 .net 程序集,考虑到 WebAssembly...)。 然而,还有很多其他问题需要解决。 如果这是我们最终想要的,请将其放在这里以供讨论。

如何?

我现在只想谈谈 1(稍后再扩展)

发出 TypeScript 绑定

看看可用的 API 和相关讨论,我觉得最好的选择是使用TypeScript API ,因为有一些在线工具可以很容易理解和使用。

我玩了一些代码库并注意到以下内容:

  • 我们可以在fable-compiler-js中使用 TypeScript-API,我们只需要几个绑定或使用不安全的调用。
  • 但是,在基于 .NET 的常规寓言中,我们不能使用 TypeScript API,因此我们需要一个中间可序列化数据结构来用于从Fable.AST构建的类型声明。 它看起来类似于Fable.AST但只有类型特定的东西(所有表达式的东西都将被删除)
  • 我还没有弄清楚通过 fable-loader 将 TypeScript 定义注入 webpack 的最佳方法是什么,但作为第一步,我将在某处输出.d.ts文件(以及.fs文件例如)。

是的,这可能是一项工作,但这一切对我来说听起来都是可行的。 在实践中:

  • 我们可以从一个非常小的实现开始,只支持简单的接口(例如)并使用any来处理其他所有内容
  • 在我们更有信心之前,我们可能想制作这个opt-in
  • 我们可以为特性扩展这个特性(即随着时间的推移,打字会变得更好)

使用/导入 TypeScript

我将在稍后或整个讨论中扩展本节。 但我目前的想法是:

  • 创建一个“内置”类型提供程序type MyTypings = TscImport("typings.d.ts")
  • 编写.fs文件,例如基于属性 -> 通过 globbing 添加到项目文件

相关问题

本期为以下内容的延续:

最有用的评论

@matthid以上是 Babel 生成的有效 TypeScript,只需将.js重命名为.ts即可。
但正如我所说,PR 需要一些爱来添加正确的类型,自从@alfonsogarciacaro很久很久以前在 Fable 1 中首次添加类型注释以来,它并没有真正改变。

所有29条评论

前几天我正在努力让 ts2fable 更好地翻译类型。 但是在我开始遇到需要类型标记联合的问题后,我就停下来了。 https://github.com/fsharp/fslang-suggestions/issues/538
此外,我没有找到用 F# 列多态性表示 Pick 行多态性的好方法。 它们不太适合 1:1 的类型转换。

也许我们可以做相反的事情,将所有 TypeScript AST 转换为 F# AST。 [插入邪恶的疯狂计算科学家笑]

非常感谢@matthid 这个详细的问题。 正如我们讨论过的一些类型,确实我对 Typescript 集成有点怀疑,因为正如 @Luiz-Monad 所说,两种语言的类型系统有很大不同。 但我很想被证明是错误的,看看我们如何利用 .d.ts 声明。

我可以看到您已经完成了作业并列出了围绕该主题的较旧问题,因此我不需要重复信息......或者实际上自己寻找它,因为我已经忘记了它:) 但我仍然会尝试添加一些评论,因为我记得事情。 现在有两个快速说明:

  • @ncave已经在进行工作,以恢复 Babel AST #1615 中的注释。 Babel 现在可以解析 Typescript,但我不记得它是否可以反过来工作(Babel AST 中的注释用于发出 Typescript)。
  • 关于将 F# 文件导入 Typescript,我注意到问题是 Typescript 不允许您声明具有相对路径的非 js 模块。 现在在文档中有一个关于这个的注释

谢谢!

但是在我开始遇到需要类型标记联合的问题后,我就停下来了。
我也没有找到一种用 F# 列多态性来表示 Pick 行多态性的好方法

正如我们讨论过的一些类型,确实我对 Typescript 集成有点怀疑,因为正如 @Luiz-Monad 所说,两种语言的类型系统有很大不同。

是的,我认为这确实是一个问题,但我目前的想法是这只是“2”的一个问题。 不是“1”。 (或者您知道任何“1.”问题的例子吗?)。 一般来说,感觉 TypeScript 类型系统比 F# 更强大。 这很可能是生态系统中的一个普遍问题,因为 TypeScript 只是试图“键入”现有的 javascript。 所以这基本上意味着我们在将现有代码映射到我们的类型系统时遇到了问题。

为“2”解决这个问题。 我们也没有迷路:

  • 长期:像您一样提出建议,但我们还需要游说讨论以保持对寓言有用(如您在 https://github.com/fsharp/fslang-suggestions/issues/538 中所见)
  • 短期:我们可以将未知类型映射到“obj”或联合的通用基本类型。
  • 中期:我们是一个编译器,所以我们可以进行代码生成并发出访问器,类似于我们手动编写它们的方式。 但是我们需要考虑兼容性。

@ncave已经在进行工作,以恢复 Babel AST #1615 中的注释。 Babel 现在可以解析 Typescript,但我不记得它是否可以反过来工作(Babel AST 中的注释用于发出 Typescript)。

我用谷歌做了一些研究,但我不知道这样做有什么好处。 如果 babel 不能从中写入.d.ts文件,它有什么用? 也许你或@ncave可以解释一下?

关于将 F# 文件导入 Typescript,我注意到问题是 Typescript 不允许您声明具有相对路径的非 js 模块。 现在在文档中有一个关于这个的注释。

是的,我认为我们需要一些摆弄才能使导入工作完美,但在最坏的情况下,我们还可以生成一个.ts文件,该文件导入声明和正确的.fs导入,然后我们可以告诉人们导入.ts而不是.fs

但我不记得它是否反过来(Babel AST 中的注释用于发出 Typescript)

如果我没记错的话,Babel 只支持将 TypeScript 编译成 JavaScript。 它不会生成 TypeScript。

起初 Fable Conf 至少解释说,由于所有 Babel 生态系统、配置和优化,他们希望支持 TypeScopt -> JavaScript 转译并比 TypeScript 编译器做得“更好”。

在第一次尝试生成 .d.ts 文件时,我使用了babel-dts-generator ,它能够从 Babel AST 中提取注释来创建声明。 但是现在似乎不支持该插件:/另一个选择是将注释输出为 JSDoc 注释,因为 Typescript 也可以使用它们来提供智能感知

如果我没记错的话, @ncave的目的是让 Babel 将类型注释输出为注释,然后再执行一遍以删除它们,最后查看结果是否与这些针对 WebAssembly 的 Typescript 子集(如AssemblyScript )兼容。 是的,像往常一样的黑魔法 :wink: 但是如果在这两种情况下的目的都是在输出中包含类型注释,那么这将是一个用一块石头抓住两只鸟的机会。

另一种选择是将注释输出为 JSDoc 注释,

我已经看到了这个建议,但我怀疑这比通过 API 编写 TypeScript 定义更容易。 另一方面,如果我们从 babel “免费”获得它们,那将是另一回事,而且可能是更简单的方法。

问题是这如何适合图书馆? 我想我们会建议在捆绑包中包含评论?

@alfonsogarciacaro

  • 我们也许可以按原样合并 #1615,它位于编译器选项的后面,因此它是非侵入性的。 如果选项在 UI 中公开,让人们在 REPL 上使用它会很好。
  • #1615 在去年没有太大变化(只是重新定位),但它已经让您在输出类型注释方面取得了很大进展(并且可以轻松添加更多内容)。
  • 之后的第一步是确保fable-library编译为 TypeScript 编译器可以接受的内容。
  • 需要修复的第一个类型注释是函数参数,需要取消curry以匹配调用站点的uncurrying。

边注:

  • 我计划使用该 PR 来生成用于使用 AssemblyScript 编译的类型。 不幸的是,该项目并没有像我去年所希望的那样取得进展。 它获得了引用计数 GC,但仍然缺乏对闭包和迭代器等一些基本特性的支持(可能是因为 webassembly 本身在添加所需特性以支持开箱即用方面很慢)。

我希望这在未来会有所改变,但同时定位 TypeScript 应该更容易。

之后的第一步是确保 fable-library 编译为 TypeScript 编译器可以接受的内容。

我希望这在未来会有所改变,但与此同时,定位 TypeScript 应该会容易得多。

不知道这些是什么意思。 这是否意味着前进的方向是将 JSDoc 格式的输出转发到 TypeScript 编译器,该编译器本身将编写.d.ts文件? 还是您的意思是从 fable 本身编写.d.ts文件? 你能澄清@ncave吗?

或者换一种说法:我愿意在这个问题上投入一些时间,但是经过所有这些讨论后,我仍然不确定目前我们最好的选择是什么?

@mattid在 Babel AST 中添加类型注释会直接在 Babel 输出中生成类型。 这只是完整性问题(为所有极端情况输出正确的类型注释)。 例如:

let rec factorial n =
    if n = 0 then 1
    else n * factorial (n-1)

let iterate action (array: 'T[]) =
    for i = 0 to array.Length - 1 do
        action array.[i]

let rec sum xs =
    match xs with
    | []    -> 0
    | y::ys -> y + sum ys

编译为(打开类型注释):

export function factorial(n: number): number {
  if (n === 0) {
    return 1;
  } else {
    return n * factorial(n - 1) | 0;
  }
}
export function iterate<T>(action: (arg0: T) => void, array: Array<T>): void {
  for (let i = 0; i <= array.length - 1; i++) {
    action(array[i]);
  }
}
export function sum(xs: any): number {
  if (xs.tail != null) {
    const ys = xs.tail;
    const y = xs.head | 0;
    return y + sum(ys) | 0;
  } else {
    return 0;
  }
}

正如你所看到的,它并不完美,还有一些工作要做(相当多的类型现在只是被存根为any ),但 IMO 你可以走得很远。

@ncave我猜 TypeScript 可以使用带注释的 JavaScript ootb? 还是我们需要进一步处理?

@matthid以上是 Babel 生成的有效 TypeScript,只需将.js重命名为.ts即可。
但正如我所说,PR 需要一些爱来添加正确的类型,自从@alfonsogarciacaro很久很久以前在 Fable 1 中首次添加类型注释以来,它并没有真正改变。

周末会仔细看,谢谢澄清

我将很快为下一个主要版本创建一个next分支以合并 #1839。 我们也可以使用它来合并 #1615 并进行试验 :)

只是想插话说我对在我的工作中介绍 Fable 非常感兴趣。 我们有 200k+ LOC TypeScript 代码库,因此在 Fable 中进行某种程度的 TypeScript 集成将是 _huge_。

我主要感兴趣的是让 Fable 发出 TypeScript 绑定,以便更容易地将 Fable 库合并到我们现有的 TypeScript 代码库中。

我一直在修改 ncave 最近工作的一些输出,试图找到一种方法来构建 TypeScrypt 类型,以便准确键入 F# 联合,并且可以在不破坏现有对象结构的情况下使用 switch 语句进行区分。 这是发表一些想法的正确地方吗?

@chrisvanderpennen当然,如果它是相关的,为什么不呢。 或者,如果您愿意,您可以打开一个单独的讨论问题。

移至#2096

@chrisvanderpennen感谢您的详细解释,最好将其转换为自己的问题 [功能请求],因为它可能超出了仅添加类型的初始范围。

当然,我将创建一个并编辑上面的内容以指向它,这样它就不会使讨论变得混乱。

到目前为止,我认为这个想法是避免使用 javascript/typescript,但如果你能接受它呢?

我最近与 [Bolero] 一起工作,它只是 web 程序集,它执行 javascript 互操作的方式是执行函数调用

https://github.com/AngelMunoz/Mandadin/blob/master/src/Mandadin.Client/Views/Notes.fs#L79

// next version of blazor will actually change
// to import the whole module (store the ref) then invoke the function
// instead of the actual Global Namespacing
Cmd.OfJS.either js "Namespace.MyFn" [||] Success Error

而在 javascript 方面,我仍然需要手动编写代码

https://github.com/AngelMunoz/Mandadin/tree/master/src/Mandadin.Client/wwwroot/js

并包括我的 js 库,如果它变得足够大,我相信你无论如何都需要捆绑这些文件和依赖项

Fable 已经使用了 Webpack,它只是 javascript typescript/javascript 文件,最后,至少我是这么认为的。
类型/依赖关系只是一个“npm install”。

// interop/my-file.ts
// to be included in the final fable bundle
import { libraryFn } from 'the-library-i-wont-write-bindings-to'
// hide the library interop/specifical JS work
function parseThingsAndWorkWithLibraries() { /* ... */ }
function imDoingStuff(someParam)  {
    // code 
    let someParam = someParam['something'] = myfn();
    const parsed = parseThingsAndWorkWithLibraries(someParam)
    return return  { parsed }
}

// export only the F# interop bits
export async function myInteropFn(paramA: string, paramB: number): { my: string, fsharpType: boolean }} {
    try {
        const [unshapedResult, anotherResult] = await Promise.all([
            libraryFn(paramA, paramB), imDoingStuff(paramA)
        ]);
        return { my: unshapedResult.my_value, fsharpType: anotherResult.secondValue };
    } catch(err) {
        return Promise.reject(err.message);
    }
}

并且可以像这样以类似的方式消费

// in the fable code somewhere 
[<ImportMember("my-file")>]
let myInteropFn(params: string * number ): {| my: string; fsharpType: boolean |} = jsNative

Cmd.OfJS.either js myInteropFn ("value", 10) Success Error

我的想法是,如果 Fable 可以在类似的问题上实现互操作选项,那么 js 互操作的复杂性将在 F#/JS 边界,而不是缺乏人手来投资工具/绑定

现在,这将是“最坏情况”,这意味着只有当库对你自己来说真的足够大或者社区没有努力编写一些绑定时,你才会求助于这个,安全性总是在 F# 端

我相信大多数时候你并不需要外部库,流行的库可能已经被覆盖了

无论如何...这只是我与 Bolero 合作时的一个想法,我相信 Fable 比 Blazor/Bolero 有更好的机会改进该互操作模型,因为这些包已经存在于 npm 中,用于 Blazor/ 的浏览器准备库构建太少了波莱罗上班,最后,我觉得他们还是会以这样或那样的方式诉诸捆绑

非常感谢您的评论@AngelMunoz! 我不确定我是否理解,已经有几种方法可以以类型或非类型方式与 Fable 中的 JS 代码进行互操作,您具体在寻找什么? https://fable.io/docs/communicate/js-from-fable.html

我的评论是关于第三方库集成,自动化很复杂(ts2fable)并且可能容易出错,唯一的其他选择是为第三方库创建绑定,有时没有足够的手,因此需要改进打字稿集成,或者这就是我从问题线程中理解的。

摘要如下
如果 Fable 在同一个应用程序中包含任何用户编写的 javascript/typescript 包,您应该使用 fable 通常机制中的 js,而不必为每个库编写绑定,绑定将用于您的特定代码

也许这是一个不同的问题,我不明白这个想法

@alfonsogarciacaro
从昨天开始,这就是我的意思
https://github.com/AngelMunoz/fable-plus-typescript-files-poc/blob/master/src/App.fs#L8
https://github.com/AngelMunoz/fable-plus-typescript-files-poc/blob/master/src/tsfiles/interop.ts#L18

这不能解决第 1 点

发出 TypeScript 绑定(我们目前是手动编写的)

但它为问题的第 2 点提供了较少的摩擦

使用/导入 TypeScript(我们目前为此使用 ts2fable)

它当然带来了它自己的一系列问题,我能想到的第一个是安全性,typescript 类型系统可以非常好,但需要用户增强,而不像 F# 那样默认更安全

关于第 1 点, @ncave正在努力,尽管在我们发布稳定的 Fable 3 之前工作有点搁置。第 2 点是关于以类型安全的方式使用绑定(或自动生成的绑定)直接使用 typescript 文件苍蝇)。 我认为这很复杂,但可以使用类型提供程序或代码生成工具来完成。

无论如何,如上所述,已经可以通过使用动态运算符或编写一些临时绑定来使用 Typescript 或 JS 文件(我一直这样做)。 不需要任何额外的机制。

@alfonsogarciacaro :当您说第 1 点正在进行中时,这是否意味着在寓言 3 中使用“--typescript”? 这就是你在说的吗?
如果是,似乎这个标志确实创建了真正的打字稿代码(不仅仅是声明)
我的目标:在 Fable 中创建一个库,我想在另一个库中使用它(打字稿中的第二个)。 使用“--typescript”标志时出现一些错误

import { decimal } from "./.fable/fable-library.3.0.1/Decimal.js"; // error : ... has no exported member decimal ...

我知道这是一项正在进行的工作,但你能添加一个工作链接吗?
我们应该在哪里添加有关该特定功能的问题?
目前在另一个打字稿库中使用 Fable 库的正确方法是什么?

@CedricDumont是的,没错。 虽然我必须承认我没有在这个功能上工作过😅所以我真的不知道目前的状态是什么。 @ncave可能可以更好地回答这个问题。 IIRC 寓言库导入确实会产生问题,因为 atm 我们没有打包编译为打字稿的寓言库文件。 我们可以尝试解决这个问题,但我不确定是否还有其他问题悬而未决。 如果我们想给这个特性一个冲动,我们应该尝试在编译到 Typescript 时让测试运行。

目前在另一个打字稿库中使用 Fable 库的正确方法是什么?

现在,基本上你自己为你从 TS 使用的 Fable 方法编写 .d.ts 声明。 我发现以下配置可以工作:

App.fs
App.fs.js # generated
App.d.ts # manually written

例如,如果您的 App.d.ts 声明包含以下导出(对应于 App.fs.js 中的实际导出):

export function foo(x: number): {
    data: string[]
};

您可以像这样从 Typescript 使用它:

import { foo } from "./App"

const result = foo(5);

请注意,在导入中省略了扩展名。 您需要编辑 webpack.config.js,以便 Webpack 在这种情况下查找扩展名为.fs.js的文件:

module.exports = {
    resolve: {
        extensions: ['.fs.js', '.mjs', '.js'], // Add other extensions you may want to use
    },
    ...
}

请注意,如果您仅使用带有// @ts-check语句的 Javascript,这对于使用 Fable 代码也很有用。

以下是基于此评论的 TypeScript 支持的当前状态:

就进展而言,我们能够在 Fable 2 中将 fable-library 编译为严格的 TypeScript。
在寓言 3 中,由于许多变化,我们有一点倒退,但它又接近了。
在那之后,下一个大目标是将所有测试编译为严格的 TypeScript,但范围要大得多。

因此,我们首先要处理一个小驼峰,然后是一个更大的驼峰,但如果我们将fable-library的 TS 版本与 Fable 编译器捆绑在一起,希望它可以在第一个之后半可用。 欢迎提出意见和贡献,上面提到的评论中概述了 TS fable-library的构建步骤。

关闭,因为目前在这个方向上没有任何工作,如果有人想为 TS 集成做出贡献,请重新打开。

此页面是否有帮助?
0 / 5 - 0 等级

相关问题

forki picture forki  ·  3评论

alfonsogarciacaro picture alfonsogarciacaro  ·  3评论

funlambda picture funlambda  ·  4评论

MangelMaxime picture MangelMaxime  ·  3评论

krauthaufen picture krauthaufen  ·  3评论