问题
I want to create a TypeScript library as private npm package which can be used in Node.js (including 6.x)
using ES6 with @types
support and TypeScript.
The goal of the library is to extend the Request
type from express
and provide additional properties.
I created a new Node.js project and add this tsconfig.json
:
{
"compilerOptions": {
"target": "es2015",
"module": "commonjs",
"sourceMap": true,
"declaration": true,
"outDir": "./dist",
"strict": true,
"types": ["mocha"]
}
}
These are the relevant parts of the package.json
:
{
"name": "@myscope/my-lib",
"main": "dist",
"scripts": {
"build": "rm -rf ./dist && ./node_modules/.bin/tsc",
"test": "./node_modules/.bin/mocha test"
},
"private": true,
"dependencies": {
"joi": "11.4.0"
},
"devDependencies": {
"mocha": "^5.2.0",
"express": "^4.16.4",
"@types/express": "^4.16.1",
"@types/joi": "^14.3.0",
"@types/mocha": "^5.2.5",
"typescript": "^3.2.4"
}
}
My folder structure is this:
- dist
- src
- http
- security
- test
I created a new TypeScript file AuthenticatedRequest.ts
in src/http
:
import {Request} from "express";
import {UserReference} from "../security/UserReference";
export interface AuthenticatedRequest extends Request {
user: UserReference
}
src/security
contains a UserReference.ts
:
import {Claim} from "./Claim";
export interface UserReference {
claims: Claim[];
}
and a Claim.ts
:
import {IClaim} from "./IClaim";
export class Claim implements IClaim {
type: string;
value: string;
constructor(type: string, value: string) {
this.type = type;
this.value = value;
}
}
IClaim.ts
looks like this:
export interface IClaim {
type: string,
value: string
}
In test
, I created AuthenticatedRequestTests.js
(plain ES6, no TypeScript here to validation code completion and usage from ES6):
'use strict';
const assert = require('assert');
const Claim = require("../dist/security/Claim").Claim;
describe('req', () => {
it('should ', done => {
/** @type {AuthenticatedRequest} */
const req = {};
req.user = { claims: [new Claim('tenantId', '123')] };
assert.equal(req.user.claims[ 0 ].type, 'tenantId');
assert.equal(req.user.claims[ 0 ].value, '123');
return done();
});
});
Now I have sevaral questions:
- Is this the expected TypeScript way to solve this?
- Is it possible to just use
require("../dist/security/Claim");
instead ofrequire("../dist/security/Claim").Claim;
? - Instead of using this
jsdoc
statement/** @type {AuthenticatedRequest} */
I would like to use/** @type {myLib.http.AuthenticatedRequest} */
I also created a local test project for integration and installed my library via npm link
.
But instead of using
const Claim = require("@scope/my-lib/security/Claim").Claim;
I have to use
const Claim = require("@scope/my-lib/dist/security/Claim").Claim;
How can I get rid of the dist
folder name here?
Also, using the jsdoc
comment for AuthenticatedRequest
in the integration test project, I get the error that the type cannot be found:
回答1:
package.json
There should be a field called
types
(ortypings
) telling your library consumers where are the type definitions for your project. If they are generated by TypeScript and saved todist/index.d.ts
, then that's the path that should be used."types": "./dist/index.d.ts"
There should be a field called
files
containing an array of files/directories that will be delivered to your end users.
Running tests
Is this the expected TypeScript way to solve this?
If you're using TypeScript to develop your library, there is no reason not to use TypeScript for your tests. There are TypeScript-compliant test runners out there (
ts-jest
used to be popular, and now Jest is capable of understanding TypeScript out of the box).Is it possible to just use
require("../dist/security/Claim");
instead ofrequire("../dist/security/Claim").Claim;
?With TypeScript, a few kinds of syntax are possible. You could export
Claim
using a default export and do:import Claim from "../dist/security/Claim";
or:
const Claim = require("../dist/security/Claim");
Instead of using this jsdoc statement
/** @type {AuthenticatedRequest} */
I would like to use/** @type {myLib.http.AuthenticatedRequest} */
.You will need an import type. They look like that:
/** * @type {import('path/to/AuthenticatedRequest')} */ const req {}
The path can be relative or absolute. If you'd like to treat the local codebase as if it were installed from npm, you can create another
package.json
file in your test directory and use a relative path to resolve your library module."dependencies": { "my-library": "../path/to/the/root" }
Also, using the jsdoc comment for
AuthenticatedRequest
in the integration test project, I get the error that the type cannot be found:That's also solved by import types. Unless a type is not in the global scope, you need to import it before you can use it. Use
import('path/to/AuthenticatedRequest')
instead.
There are a few things going on. If you could create a public repository to demonstrate your problems, I'm sure it would be easier for us to help you address them.
回答2:
I have an alternate answer for one part of your question. You asked
instead of using
const Claim = require("@scope/my-lib/security/Claim").Claim;
I have to use
const Claim = require("@scope/my-lib/dist/security/Claim").Claim;
How can I get rid of the dist folder name here?
As Karol points out, you can use files
in package.json
so that when your library is published, you send the dist
directory as the package root (plus package.json, README, etc). This is a great, established pattern, but has some downsides: installing the package from github:
instead of NPM won't work, nor will npm link
for local development.
As of very recent Node versions, you can use the exports property instead:
{
"exports": {
"./": "./dist/"
}
}
Now, require("my_lib/security/Claim")
resolves to node_modules/my_lib/dist/security/Claim
. It would be great if Typescript handled this automatically, but as far as I can tell you also have to specify a baseUrl
and paths
in your tsconfig.json
, at least for now.
来源:https://stackoverflow.com/questions/54357435/create-a-typescript-library-and-use-it-from-node-js-with-es6-and-typescript