Create a TypeScript Library and use it from Node.js with ES6 and TypeScript - node.js

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 of require("../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:

package.json
There should be a field called types (or typings) telling your library consumers where are the type definitions for your project. If they are generated by TypeScript and saved to dist/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 of require("../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.

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.

Related

ASP.NET 5 (Core) Website with TypeScript and Node (or ESM) modules in Visual Studio [not Code] 2019?

So I have followed this guide to setup TypeScript in a ASP.NET 5 website project (with Razor Page). I want to have TypeScript typings with Node modules instead of just importing as any.
Now I want to add a Node module, like EthersJS, so I have:
"dependencies": {
"ethers": "^5.4.1"
}
This code would not compile:
import { ethers } from "ethers";
export class EthService {
pro: ethers.providers.BaseProvider;
constructor() {
const pro = this.pro = new ethers.providers.WebSocketProvider("");
}
async getBlockNumberAsync() {
return await this.pro.getBlockNumber();
}
}
unless I add "moduleResolution": "Node" to tsconfig.json. Obviously this wouldn't actually run in a browser.
How should I set it up so the Node module get compiled somehow? I think the problem can be solved if I can do either of these:
Make TypeScript/Gulp/MSBuild change import { ethers } from "ethers"; into proper path (I can copy the compiled lib file manually into wwwroot).
Manually set import path to import { ethers } from "path/to/compiled/ethers.js";, but I need to somehow tell TypeScript that that ethers typing is from ethers Node module.
UPDATE: I think a third possibility is very nice as well if I can just have a declare import (I will just add the ESM file into the global scope) like this:
declare import { ethers } from "ethers";
Is any of the above option possible? Or is there any way? Thanks.
Note: I know there is Triple-Slash Directives and it may solve my problem but I don't really understand what they do yet.
The current gulpfile.js:
/// <binding AfterBuild='default' Clean='clean' />
/*
This file is the main entry point for defining Gulp tasks and using Gulp plugins.
Click here to learn more. http://go.microsoft.com/fwlink/?LinkId=518007
*/
var gulp = require("gulp");
var del = require("del");
var paths = {
scripts: ["Scripts/**/*.js", "Scripts/**/*.ts", "Scripts/**/*.map"],
};
gulp.task("clean", function () {
return del(["wwwroot/Scripts/**/*"]);
});
gulp.task("default", function () {
gulp.src(paths.scripts).pipe(gulp.dest("wwwroot/Scripts"));
});
tsconfig.json:
{
"compilerOptions": {
"noImplicitAny": true,
"noEmitOnError": true,
"removeComments": false,
"sourceMap": true,
"target": "ESNext",
"module": "ESNext",
"moduleResolution": "Node"
},
"exclude": [
"wwwroot",
"node_modules"
],
"compileOnSave": true
}
I am able to solve the problem by using a standalone ethers.js script file to expose ethers namespace to the global scope:
<script defer src="/libs/ethers.umd.min.js" asp-append-version="true"></script>
Now you need to tell TypeScript that there is an ethers "thing" (namespace/module) in the global scope. I found a solution thanks to this article. Create a .d.ts file (any will work, I name it globals.d.ts):
import { ethers as eth } from "ethers";
declare global {
// #ts-ignore: export typing
export { eth as ethers };
}
Now you can use ethers anywhere without needing any declaration. For example my whole EthService.ts file:
export class EthService {
pro: ethers.providers.WebSocketProvider;
init(server: string) {
this.pro = new ethers.providers.WebSocketProvider(server);
}
async getBlockNoAsync() {
return await this.pro.getBlockNumber();
}
}

Writing a Node module in TypeScript for consumption by both TS and JS projects

I have an Express middleware project written in TypeScript. I'd like to consume it in both JS & TS based Node projects.
I'm having trouble configuring my projects to ensure that
the upstream project is outputting modules that can be consumed by Node
my module can be consumed in JS projects in the format myGreatFunction = require('myGreatFunction') // typeof = function
my module can be consumed in the format import myGreatFunction from 'myGreatFunction' // typeof = function
my module is not being either output as an object with a .default when that is not expected, or, vice-versa, not being done so when that is indeed expected.
It feels as though I can only achieve some of these aims but not others.
What is the correct incantation of TSConfig properties (upstream & downstream) to ensure this is so?
In the end I settled on a compromise - see below.
Library
tsconfig.json:
{
"compilerOptions": {
"module": "commonjs",
"target": "es5",
"noEmitHelpers": true
},
}
module.ts:
export class MyClass {
static Version: string = "1.0";
}
When we compile this module we'll get:
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var MyClass = /** #class */ (function () {
function MyClass() {
}
MyClass.Version = "1.0";
return MyClass;
}());
exports.MyClass = MyClass;
TS Client
client.ts:
import {MyClass} from "./../src/module";
console.log(MyClass.Version);
compile and run node client.js - see "1.0"
JS Client
Just grad the same code from compiled ts client :
var module_1 = require("./../src/module");
console.log(module_1.MyClass.Version);
same output obviously
Using a typescript file in typescript.
Assuming B.ts is the typescript file that you want to use in A.ts, then:
import { B's module that are exported } from 'path/to/B'; // notice there is no extension mentioned here.
Also B must have a export module in it.
Using a ts file in a js file.
B.ts = file which you want to use in your main file.
A.js = your main file.
In your A.js:
var external = require('path/to/B'); // notice there is no extension mentioned here.
In the end I settled on a compromise:
Library TSConfig:
{
"compilerOptions": {
"target": "es6",
"module": "commonjs",
"declaration": true
}
}
Library:
export = function myGroovyFunction() {...}
Downstream project, TypeScript
import * as myGroovyFunction from 'mygroovyfunction';
Downstream project, JavaScript
const myGroovyFunction = require('mygroovyfunction');
The TypeScript import isn't quite as concise as I'd like, but I can deal.

TypeScript 2, React JS and Express Server-side Rendering Issue

SO...
I am running into the issue...
Invariant Violation: Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: object.
...and I have found the same response multiple times regarding "export default". Hopefully the solution to my problem is something simple regarding my TypeScript compilation, ECMA version compatibility, etc. but any help is appreciated.
tsconfig.json
{
"compilerOptions": {
"jsx": "react",
"outDir": "dist"
},
"include": [
"src/**/*"
]
}
I realize I am not specifying a "target" so tsc defaults to "es3" which I thought is best for backwards compatibility. I have tried updating to "es5" and this did not fix my issue.
server.ts
this.app.set("views", path.join(__dirname, "views"))
this.app.set("view engine", "js")
var engines = require('consolidate')
this.app.engine('js', engines.react)
Since I am specifying "react" for the "jsx" property in my tsconfig.json, my compiled files will be .js, but will still contain React.createElement, etc. calls, so I am specifying my express view engine for JS files to use the consolidate project's react engine. Previously I was using express-react-views, any input on my strategy here would be helpful.
index.tsx
import * as React from 'react'
interface HelloMessageProps {
name: string
}
class HelloMessage extends React.Component<HelloMessageProps, {}> {
render() {
return <div>Hello {this.props.name}!</div>;
}
}
index.ts
// ...
// routing code
// ...
let options: Object = {
"name": "World"
};
res.render("index", options);
...any help is much appreciated!
Found out that my problem was that I was trying to "render" a view file (html, jade, etc.) when I actually didn't want to. So I don't need express-react-views nor consolidate and I can remove my code...
this.app.set("views", path.join(__dirname, "views"))
this.app.set("view engine", "js")
var engines = require('consolidate')
this.app.engine('js', engines.react)
...and update my index.ts file to be...
// ...
// routing code
// ...
let options: Object = {
"name": "World"
};
const components = require('../../components')
const HelloMessage = React.createFactory(components.HelloMessage)
const ReactDOM = require('react-dom/server')
res.send(ReactDOM.renderToString(HelloMessage(options)));
...the key here being to perform the "rendering" (i.e. transformation to the final HTML) using the ReactDOM's renderToString method and simply sending that output to the response (res.send(...)) instead of attempting to render it (res.render(...)).

Node.js module lookup in Electron+Angular 2 & TypeScript application

I'm developing an Electron application with Angular 2.
I followed this tutorial to initially setup the environment.
My setup is a bit more complicated but in general is very similar.
tsconfig.json:
{
"compilerOptions": {
"target": "es5",
"module": "commonjs",
"moduleResolution": "node",
"sourceMap": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"removeComments": false,
"noImplicitAny": false
},
"exclude": [
"node_modules",
"app/node_modules",
"dist"
]
}
systemjs.config.js is just like in the tutorial as well as index.html
One of my modules (which resides somewhere inside the app folder - app/*/*/*/module) depends on node-ffi. So I've added the necessary typings to typings.json:
{
"globalDependencies": {
"core-js": "registry:dt/core-js",
"jasmine": "registry:dt/jasmine",
"node": "registry:dt/node",
"ref": "registry:dt/ref",
"ref-struct": "registry:dt/ref-struct",
"ffi": "registry:dt/node-ffi"
}
}
Now, the module tries to use ffi like this:
import { DynamicLibrary, Library, types } from 'ffi';
export class CppProxy {
//Some code
}
Which is eventually transpiled to:
var ffi_1 = require('ffi');
//Utilize ffi_1 exported stuff
According to this article, there is a well-defined way for node.js module lookup and according to this way it should find the node-ffi module as node-ffi resides in app/node_modules. However, it doesn't. It only looks in app folder for ffi.js and obviously fails to find it.
My first attempt to fix it was adding ffi entry to the map section of systemjs.config.js, like this:
var map = {
'app': '.', // 'dist',
'#angular': 'node_modules/#angular',
'angular2-in-memory-web-api': 'node_modules/angular2-in-memory-web-api',
'rxjs': 'node_modules/rxjs',
'node-binary': 'node_modules/systemjs-plugin-node-binary/node-binary.js',
'ffi': 'node_modules/ffi/lib/ffi.js'
};
It helped to load ffi, but brought up new problems. ffi itself depends on other modules:
var ref = require('ref')
var assert = require('assert')
var debug = require('debug')('ffi:ffi')
var Struct = require('ref-struct')
var bindings = require('./bindings')
So now the application failed to find these modules. I've tried to add some of them to the map too, but again it just solved one level of dependencies.
It doesn't seem right to me, require shouldn't be looking only in app folder and I don't feel like adding all the dependencies recursively to map section of systemjs.config.js.
What am I doing wrong?
Upd:
There another question dealing with a pretty similar issue, but it is asking specifically about using require('remote').
I'm asking how can I use a Node.js module resolution mechanism while still using System.js as a module loader.
As Pace mentioned in one of his comments, System.js overrides Node.js's require method and uses it's own resolution mechanism. This is why the require method won't follow the Node.js lookup mechanism. However there is a way to use the latter:
System.js stores the Node.js's require in _nodeRequire variable. So the way to load a module using Node.js mechanism is to load it by
var module = System._nodeRequire('./path/to/module/')
Here is the discussion that helped me to come up with this solution.

How do you setup a require.js config with typescript?

Ok, I've been reading a lot of questions and answers about this, and a lot of it is rubbish.
I have a very simple question. How do I do the equivalent of this:
require.config({
paths: {
"blah": '/libs/blah/blah',
}
});
require(['blah'], function(b) {
console.log(b);
});
In typescript?
This doesn't work:
declare var require;
require.config({
paths: {
"blah": '/libs/blah/blah',
}
});
import b = require('blah');
console.log(b);
s.ts(8,1): error TS2071: Unable to resolve external module ''blah''.
s.ts(8,1): error TS2072: Module cannot be aliased to a non-module type.
error TS5037: Cannot compile external modules unless the '--module' flag is provided.
Compiling with the --module flag, with a dummy blah.ts shim compiles, but the output is:
define(["require", "exports", 'blah'], function(require, exports, b) {
require.config({
paths: {
"blah": '/libs/blah/blah'
}
});
console.log(b);
});
Looks like it might work, but actually no, the require.config is inside the require block, it is set after it is already needed.
SO! I've ended up so far with this as a solution:
class RequireJS {
private _r:any = window['require'];
public config(config:any):void {
this._r['config'](config);
}
public require(reqs:string[], callback:any):void {
this._r(reqs, callback);
}
}
var rjs = new RequireJS();
rjs.config({
paths: {
"jquery": '/libs/jquery/jquery',
"slider": '/js/blah/slider'
}
});
rjs.require(['slider'], function(slider) {
console.log(slider);
});
Which seems terrible.
So be clear, inside modules that depend on each other, this sort of thing works perfectly fine:
import $ = require('jquery');
export module blah {
...
}
I just need a proper way to setting the requirejs config at a top level, so that the imported paths for the various named modules are correct.
(...and this is because, largely, 3rd party dependencies are resolved using bower, and installed in the /lib/blah, where as the shim files I have for their definitions are in src/deps/blah.d.ts, so the default import paths are incorrect after moving the generated modules files into /js/ on the site)
NB. I've mentioned jquery here, but the problem is not that jquery doesn't work property as an AMD module; I have a shim jquery.ts.d file for this. The issue here is the requirejs paths.
Yesterday I wrote up a solution to this exact issue on my blog - http://edcourtenay.co.uk/musings-of-an-idiot/2014/11/26/typescript-requirejs-and-jquery:
TL;DR - create a config file config.ts that looks something like:
requirejs.config({
paths: {
"jquery": "Scripts/jquery-2.1.1"
}
});
require(["app"]);
and ensure your RequireJS entry point points to the new config file:
<script src="Scripts/require.js" data-main="config"></script>
You can now use the $ namespace from within your TypeScript files by simply using
import $ = require("jquery")
Hope this helps
This post is 3 years old, and there's a lot of changes that have been made when using Typescript. Anyway, after some search on the web,some research on TS documentation-these guys made some good job, I found something that could help.
so this can apply to the latest current of TS (2.2.1)
you probably know that you can use
npm install --save #types/jquery
do the same for your 3rd party JS librairies such as require
now you need to define what your TypeScript Compiler has to do, and how, so create a tsconfig.json file that contains:
// tsconfig.json file
{
"compilerOptions": {
"allowJs": true,
"baseUrl": "./Scripts",//Same as require.config
"module": "amd",
"moduleResolution": "Node",//to consider node_modules/#types folders
"noImplicitAny": false,
"target": "es5", // or whatever you want
"watch": true
}
now, let's focus on require's configuration
// require-config.ts
declare var require: any;
require.config({
baseUrl: "./Scripts/",
paths: {
"jquery": "vendor/jquery/jquery.x.y.z"
// add here all the paths your want
// personnally, I just add all the 3rd party JS librairies I used
// do not forget the shims with dependencies if needed...
}
});
so far so good
now focus on your module written in TS that would use jquery and that is located in Scripts/Module folder:
// module/blah.ts
/// <amd-module name="module/blah" />
import $ = require("jquery");
export function doSomething(){
console.log("jQuery version :", $.version);
}
So this answer looks the same as Ed Courtenay's, doesn't it?
and user210757 mentioned that it does NOT work!!!
and it does not! if you type in your console tsc -w --traceResolution, you'll see that tsc cannot find any definition for jquery.
Here's how to alleviate assuming you previously launch npm install --save #types/jquery by doing this, in a folder named node_modules\#types, you should get the TS definition for jquery
select the package.json file in jquery subfolder
look for the "main" property
set it to "jquery", the same as the alias you are using in your require.config
and done! your module would be transpiled as
define("module/blah", ["require", "exports", "jquery"], function (require, exports, $) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
function doSomething() {
console.log("jQuery version:", $.fn.jQuery);
}
exports.doSomething = doSomething;
});
and that JS code looks good to me!
I just don't like the fact that our module dependencies list "require" & "exports", that sounds like a TS issue, but anyway IT WORKS!
if you want to use import for javascript modules you need to tell typescript about it so,
declare var require;
require.config({
paths: {
"blah": '/libs/blah/blah',
}
});
// Important, place in an external.d.ts:
declare module 'blah'{
// your opportunity to give typescript more information about this js file
// for now just stick with "any"
var mainobj:any;
export = mainobj;
}
import b = require('blah');
console.log(b);
alternatively you could simply do:
var b = require('blah'); and it should work as well

Resources