TL;DR I want to create TypeScript typings for compiled PureScript modules, and distribute them in my npm package. I'm more than happy to maintain these typings manually, I just can't figure out what I need to put in tsconfig.json (up and downstream) and package.json.
I have a project where the core functionality is implemented in PureScript with a TypeScript CLI, and all of this is ultimately distributed as JavaScript via npm. I've created a similar project with a simpler layout:
.
├── 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
In src/People.purs
I define the core functionality:
module People where
type Person = { name :: String }
david :: Person
david = { name: "David "}
In types/People.d.ts
I define TypeScript types for PureScript modules so I can safely use them from TypeScript:
declare module "People" {
export interface Person {
name: string;
}
export const david: Person;
}
Finally, in cli/main.ts
, I want to import the compiled PureScript modules with the TypeScript types I've defined:
import * as People from "People";
console.log(People.david);
I can get this to build, but when I try to run the ultimate JavaScript (node cli/main.js
), the require
calls generated by tsc
fail because they don't use absolute paths--require("People")
should be require("./People")
in this case. Making the import
statements in TypeScript relative disassociates the typings.
I'm having a hard time meeting these goals:
require
calls for PureScript modules resolve at runtime.If you have experience requiring PureScript modules from TypeScript, and distributing all of this via npm, I would really appreciate some guidance!
Your Purescript module is called "Person", but should be "People". I think with relative path importing you might get partially there, but i don't think there's such thing as relative path typing in d.ts files.
I checked out your project and renamed the module, then I set allowJs
to true and typed the module inline:
import * as OutputPeople from "../output/People";
type Person = {
name: string;
};
type PeopleModule = {
david: Person
}
const People: PeopleModule = OutputPeople
console.log(People.david);
I put my modifications up on a branch here: https://github.com/justinwoo/ps-js-ts-interop/commit/fc7c1239f9d1bac1cf2bea35f7a07d30c46a5f64
If you definitely need to use the absolute path to require your PureScript packages (e.g. import * as People from 'People'
), I think you will need to hack around the node module resolution algorithm (by placing your PureScript output in a node_modules
subdirectory of ./output
, for example).
But if that is not as important and you're fine with relative imports (like import * as People from './People'
), you can do the following:
Use relative imports for PureScript modules:
cli/main.ts:
import * as People from './People';
Place your PureScript types side by side with your TypeScript sources:
.
├── People
│ └── index.d.ts
├── main.ts
└── tsconfig.json
Change your PureScript types to be local module declarations instead of global:
cli/People/index.d.ts:
export interface Person {
name: string;
}
export const david: Person;
Now, to make it work with Node module resolution, you need to place your JS output from both compilers in the same directory. We also want for TypeScript to emit type declarations for our TS files (to be consumable when published on npm). We're also removing the paths
TS compiler option (no longer needed), and adding global type reference for Node types.
cli/tsconfig.json:
{
"compilerOptions": {
"target": "es5",
"lib": ["es2015"],
"moduleResolution": "node",
"baseUrl": ".",
"outDir": "./../output",
"types": [
"node"
],
"declaration": true
},
"include": [
"*.ts"
]
}
Almost done. Now, we need to make sure that type definitions for PureScript modules end up in the output directory to be available for consumption. TypeScript will not copy .d.ts
files in its output when generating type definition output (since these were not created by the TS compiler) – we need to compensate for that. I don't know whether this is the best (or even a good?) way to achieve this, but the following worked for me:
package.json:
"scripts": {
"build": "pulp build && tsc -P cli/tsconfig.json && cd cli && rsync -am --include='*.d.ts' -f 'hide,! */' . ./../output"
},
...
Last but not least, we need to specify the entry point for JS and for type definitions for npm consumers:
package.json:
"main": "output/main.js",
"typings": "output/main.d.ts",
"files": [
"output/"
]
...
I believe this does not cover all the details of creating a mixed-language and mixed-type npm package, but hopefully this can serve as a starting point.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With