Correctly writing and importing custom node modules - javascript

EDIT
This is NOT a TypeScript question but rather a best-practices one. The example in TypeScript but really, the question here is how do I, correctly, expose multiple exports from multiple files that compose a module, and how to correctly import them.
EDIT 2
There's still something not right
So, on my Module2's index I have this:
export * from "./queryFilter";
On Module1 user.ts I import it by using
import { queryFilter as QueryFilter } from "Module2";
(...)
var User = {...}
User.getById = (userId: string) => {
...
}
export { User }
And my Module1's index.ts I export it as
export * from "./model/user";
Then, on my main project I import it using
import * as Schema from "Module1";
var User = Schema.User;
However, this throws an error whenever I try to call User.getById:
Debug: internal, implementation, error
TypeError: Uncaught error: Cannot read property 'getByEmail' of undefined
From this approach, what am I doing wrong???
I'm writing a couple of node modules for the first time ever and I do have some questions regarding proper declaration/usage of this.
So I'm creating a module that will require another custom module, let's call them Module1 and Module2.
So, Module1 is required by the main application, but Module1 also requires Module2.
Now on Module2 I have a bunch of files and on each one I export what I need. Here's a sample:
Module2 - Utils.ts:
"use strict"
const RandomString = require("randomstring");
let randomString = (numCharacters: number) => {
return RandomString.generate({
length: numCharacters,
capitalization: 'uppercase',
charset: 'alphanumeric'
});
}
module.exports.randomString = randomString;
Module2 - queryFilter.ts:
"use strict"
export default class QueryFilter {
name: string;
op: string;
value: any;
scope: string;
constructor(name: string, op: string, value: any, scope: string) {
this.name = name;
this.op = op;
this.value = value;
this.scope = scope;
}
public static GetConditionQuery(filters: QueryFilter[], params: string[]) {
(...)
}
public static queryComparator(value1: any, value2: any, operator: string): any {
(...)
}
}
On Module1, I added Module2 to package.json so when I want to use QueryFilter, for instance, I require it like this:
Module1 - Class.ts:
import { QueryFilter } = require("Module2").queryFilter;
Now my question is, is it enough to export individual items from each file in my Module2 and use it in this fashion? Or should I have a index that would export every file from Module2 so that Module1 can see them?
Something along the lines of:
Module2 - index.ts:
export "./utils"
export "./queryFilter"
(...)
What is the correct way of doing this? I'm a total newb to this and the docs I've read didn't shed any light on this..
Best Regards

Following your last comment, this the 'typescript solution' for creating a module/library. The solution would be similar for "pure" ES6; typings in Typescript just add a small layer of difficulty.
Assuming you write a module/library with multiple files:
func1.ts
export func1(param1: number): string { ... }
func2.ts
export func2(): number { ....}
Create an index.ts which re-exports the interesting parts of your module/lib
export * from './func1'
export * from './func2'
....
Obviously you can choose to (re)export and make public only part of the code e.g. export { thisFunc, thatClass } from './anotheFile'
Compile the module with declarations and commonjs flags, and make sure that the package.json main points to index.js, while the typings entry points to the generated index.d.ts
This module/library can now be used from Javascript or Typescript using an ES6 style import syntax
import { func1, func2 } from 'mymodule'
If this is Typescript, the typings of the 2 functions will automatically be imported too.

Related

How to include functions in namespace when don't have module?

How to include functions in namespace / module when don't have module?
example: I have a namespace:
namespace Java {}
in typescript, usually when we need a function in namespace or module, we add 'export':
namespace Java {
export function version() {
return "java8";
}
}
namespace JavaScript {
function JavaVer() {
return Java.version();
}
}
But if I set module to none:
// tsconfig
"module": "none",
If module is none, I cannot use import / export:
website/sourcecode/main.ts:17:21 - error TS1148: Cannot use imports, exports, or module augmentations when '--module' is 'none'.
17 export function version() {}
so what can I do?
image:
You can use a plain object:
TS Playground
const Java = {
version () {
return "java8";
},
};
const JavaScript = {
JavaVer () {
return Java.version();
},
};
const vJ = Java.version(); // string
const vJS = JavaScript.JavaVer(); // string
console.log(vJ === vJS); // true
It was answered in here: It's actually "can't have any imports or exports at the top level". Imports and exports inside namespaces are fine because they don't export anything from .ts file at runtime, and as long as file remains 'not a module' module=None works fine. Long explanation here

Why is this exported typescript const not available for being imported from transpiled and bundled code?

I have some typescript code like so:
// file.ts
export const MyValues: {
[key: string]: string;
} = {
value: 'This is a value'
};
Which is then transpiled by tsc (module: ES2015) to:
// file.js
export var MyValues = {
value: 'This is a value'
};
along with typings like so:
// file..d.ts
export declare const MyValues: {
[key: string]: string;
};
...which seem correct to me.
The js file is then bundled with webpack. It results in:
// <webpack boostrap stuff>
var MyValues= {
value: 'This is a value'
};
But when I use this built library, and try to import this exported const, the IDE can't auto-import (even though the typings are there and if I Ctrl + click on this constant, it finds it), and if I import manually like so:
import {MyValues} from 'mylibrary';
It comes as null, not as the object value being assigned on that var declaration.
Why is this happening and how to fix it?
**UPDATE: ** I noticed at the bottom of the webpack bundled code, I see:
__webpack_require__.d(__webpack_exports__, "MyValues", function() { return MyValues; });
But still, can't access a value for it. It remains undefined if I import it manually.
Thanks!

How to create a definition for 'module.exports' function

I am exporting a function like this which works.
module.exports = function (options: any): RequestHandler {
// Do stuff
}
I am trying to add a definition for the exported function, but I am not sure if this is the right way to do it:
declare global {
export function tsm(options: any): RequestHandler
}
When I try to test it both of the following say that this is valid:
const tsm = require('ts-middleware')
global.tsm() // Gives intellisense
tsm() // Also gives intellisense
It shouldn't give information about global.tsm(), so I think that I created my definition wrong. What is the correct way to create a function definition?
I don't want to use the function like this:
const tsm = require('ts-middleware')
tsm.tsm()
But I do want to use it like this:
const tsm = require('ts-middleware')
tsm()
To define typings for a module, use declare module 'x' {...}. i.e.:
declare module 'ts-middleware' {
export = (option: any): RequestHandler
}
However, you do not really need to do that because you wrote your code in TypeScript. The compiler can generate the typings for you automatically.
You just need to add declaration: true in your tsconfig.json:
// tsconfig.json
{
"compilerOptions": {
"declaration": true
}
}
Also, I would strongly recommend you to write your code using ESM. i.e. instead of module.exports = ...:
// named export, preferred
export function foo(options: any): RequestHandler { ... }
// or default export
export default function foo(options: any): RequestHandler { ... }
// import in TypeScript for named export
import { foo } from 'ts-middleware'
// import in TypeScript for default export
import foo from 'ts-middleware'
// require in JavaScript commonjs
const middleware = require('ts-middleware')
middleware.foo(...)

Export additional interfaces for CommonJS module (Typescript)

I'm trying to use a simple JS library in Typescript/React, but am unable to create a definition file for it. The library is google-kgsearch (https://www.npmjs.com/package/google-kgsearch). It exports a single function in the CommonJS style. I can successfully import and call the function, but can't figure out how to reference the type of the arguments to the result callback.
Here is most of the library code:
function KGSearch (api_key) {
this.search = (opts, callback) => {
....
request({ url: api_url, json: true }, (err, res, data) => {
if (err) callback(err)
callback(null, data.itemListElement)
})
....
return this
}
}
module.exports = (api_key) => {
if (!api_key || typeof api_key !== 'string') {
throw Error(`[kgsearch] missing 'api_key' {string} argument`)
}
return new KGSearch(api_key)
}
And here is my attempt to model it. Most of the interfaces model the results returned by service:
declare module 'google-kgsearch' {
function KGSearch(api: string): KGS.KGS;
export = KGSearch;
namespace KGS {
export interface SearchOptions {
query: string,
types?: Array<string>,
languages?: Array<string>,
limit?: number,
maxDescChars?: number
}
export interface EntitySearchResult {
"#type": string,
result: Result,
resultScore: number
}
export interface Result {
"#id": string,
name: string,
"#type": Array<string>,
image: Image,
detailedDescription: DetailedDescription,
url: string
}
export interface Image {
contentUrl: string,
url: string
}
export interface DetailedDescription {
articleBody: string,
url: string,
license: string
}
export interface KGS {
search: (opts: SearchOptions, callback: (err: string, items: Array<EntitySearchResult>) => void) => KGS.KGS;
}
}
}
My issue is that from another file I am unable to reference the KGS.EntitySearchResult array returned by the search callback. Here is my use of the library:
import KGSearch = require('google-kgsearch');
const kGraph = KGSearch(API_KEY);
interface State {
value: string;
results: Array<KGS.EntitySearchResult>; // <-- Does not work!!
}
class GKGQuery extends React.Component<Props, object> {
state : State;
handleSubmit(event: React.FormEvent<HTMLFormElement>) {
kGraph.search({ query: this.state.value }, (err, items) => { this.setState({results: items}); });
event.preventDefault();
}
....
}
Any suggestions for how to make the result interfaces visible to my calling code without messing up the default export is very greatly appreciated.
The issue here is easily resolved. The problem is that while you have exported KGSearch, you have not exported the namespace KGS that contains the types. There are several ways to go about this, but the one I recommend is to take advantage of Declaration Merging
Your code will change as follows
declare module 'google-kgsearch' {
export = KGSearch;
function KGSearch(api: string): KGSearch.KGS;
namespace KGSearch {
// no changes.
}
}
Then from consuming code
import KGSearch = require('google-kgsearch');
const kGraph = KGSearch(API_KEY);
interface State {
value: string;
results: Array<KGSearch.EntitySearchResult>; // works!!
}
Unfortunately, whenever we introduce an ambient external module declaration, as we have by writing declare module 'google-kgsearch' at global scope, we pollute the global namespace of ambient external modules (that is a mouthful I know). Although it is unlikely to cause a conflict in your specific project for the time being, it means that if someone adds an #types package for google-kgsearch and you have a dependency which in turn depends on this #types package or if google-kgsearch every starts to ship its own typings, we will run into errors.
To resolve this we can use a non-ambient module to declare our custom declarations but this involves a bit more configuration.
Here is how we can go about this
tsconfig.json
{
"compilerOptions": {
"baseUrl": "." // if not already set
"paths": { // if you already have this just add the entry below
"google-kgsearch": [
"custom-declarations/google-kgsearch"
]
}
}
}
custom-declarations/google-kgsearch.d.ts (name does not matter just needs to match paths)
// do not put anything else in this file
// note that there is no `declare module 'x' wrapper`
export = KGSearch;
declare function KGSearch(api: string): KGSearch.KGS;
declare namespace KGSearch {
// ...
}
This encapsulates us from version conflicts and transitive dependency issues by defining it as an external module instead of an ambient external module.
One last thing to seriously consider is sending a pull request to krismuniz/google-kgsearch that adds your typings (the second version) in a file named index.d.ts. Also, if the maintainers do not wish to include them, consider creating an #types/google-kgsearch package by sending a pull request to DefinitelyTyped

Ionic 2 Angular 2 Global Import Extension methods

I've made some extensions to the Date prototype like:
interface Date {
YearsFromToday(): number;
}
Date.prototype.YearsFromToday = function (): number {
// implementation
}
I'm using the ionic2 tutorial --v2 template, which is a pretty standard layout - app.html, app.ts, app.module etc.
I was wondering if there was an easy way to have this declared globally. I'm not sure exactly where to put this in the project?
Put your monkey patch code in a file.
You might call it monkey-patch-date.ts, for example:
monkey-patch-date.ts
interface Date {
YearsFromToday(): number;
}
Date.prototype.yearsFromToday = function (): number {
// implementation
}
and then import it in main.ts or whatever your entry module is:
main.ts
import './monkey-patch-date';
Alternately. You can make it a module that exports its monkey-patcher if you want to be extra explicit to call out that you are doing something dangerous.
monkey-patch-date.ts
declare global {
interface Date {
yearsFromToday(): number;
}
}
export default function () {
Date.prototype.yearsFromToday = function (): number {
// implementation
};
}
And import it like
main.ts
import monkeyPatchDate from './monkey-patch-date';
monkeyPatchDate();
Another alternative, especially useful for library authors is to allow monkey-patching but not require it while still exposing the functionality.
Here is an example:
date-augmentations/index.ts
export function yearsFromToday(date: Date): number {
// implementation
}
date-augmentations/monkey-patch.ts
import {yearsFromToday} from './index';
declare global {
interface Date {
yearsFromToday(): number;
}
}
Date.prototype.yearsFromToday = function() {
return yearsFromToday(this);
}
Now a consumer can either monkey patch the Date prototype by running
import 'date-augmentations/monkey-patch';
Can access the functionality by the export without monkey patching anything
import {yearsFromToday} from 'date-augmentations';
const date = new Date('12-12-2023');
const yft = yearsFromToday(date);
console.log(yft); // prints 6

Categories

Resources