One-of-a-kind npm module for typescript

As part of my project, I am enhancing an existing library to make it compatible with TypeScript. To showcase this modification, I have condensed it into a succinct Minimal working example

The specified requirements

  • To ensure backward compatibility, the library should remain importable using a straightforward <script> tag.
  • To simplify future usage, importing the library with a single TypeScript import statement (without requiring <script> tags in HTML) should be possible.
  • I opted for rollup for compilation, but I welcome alternative methods such as gulp or webpack to achieve the desired outcome.

Current progress update

The Library Structure

Organization of files:

│   package.json
│   rollup.config.js
│   tsconfig.json
│   yarn-error.log
│   yarn.lock
│
└───src
        lib.ts
        options.ts

package.json:

{
  "name": "library",
  "version": "1.0.8",
  "description": "example library",
  "main": "dist/lib.umd.js",
  "module":"dist/lib.esm.js",
  "types":"dist/types/lib.d.ts",
  "license": "MIT",
  "private": true,
  "devDependencies": {
    "@rollup/plugin-typescript": "^8.3.0",
    "rollup": "^2.67.0",
    "tslib": "^2.3.1",
    "typescript": "^4.5.5"
  },
  "exports": {
    "import": "./dist/lib.esm.js",
    "require": "./dist/lib.umd.js"
  },
  "scripts": {
    "build:types": "tsc -d --emitDeclarationOnly",
    "build:js": "rollup -c rollup.config.js",
    "build:minjs:umd": "terser dist/index.umd.js --compress --mangle > dist/index.umd.min.js",
    "build:minjs:esm": "terser dist/index.esm.js --compress --mangle > dist/index.esm.min.js",
    "build:minjs": "npm run build:minjs:esm -s && npm run build:minjs:umd -s",
    "build": "npm run build:js -s && npm run build:minjs -s && npm run build:types -s",
    "prepublishOnly": "npm run lint -s && npm test -s && npm run build",
    "semantic-release": "semantic-release"
  },
  "type":"module"
}

rollup.config.js:

import typescript from '@rollup/plugin-typescript';

export default {
  input: 'src/lib.ts',
  output: [
    {
      file: 'dist/lib.esm.js',
      format: 'es',
    },
    {
      file: 'dist/lib.umd.js',
      format: 'umd',
      name: 'Lib',
    },
  ],
  plugins: [typescript({tsconfig:'./tsconfig.json'})],
};

tsconfig.json:

{
  "compilerOptions": {
    "target": "es2016",
    "module": "esnext",
    "moduleResolution": "node",
    "declaration": true,
    "outDir": "./types",
    "forceConsistentCasingInFileNames": true,
    "strict": true,
    "skipLibCheck": true
  },
}

src/lib.ts:

import Options from "./options";

export default class Lib{
    constructor(options:Options){
        console.log("It works!");
        console.log(options.message);
    }
}

options.ts:

export default interface Options {
    message: string;
}

To compile all these components, I utilize yarn build:js.

Library Implementation

Using <script> tags

Upon embedding the resultant lib.umd.js into a directory and setting up an index.html:

<script src=lib.umd.js></script>
<script>
    var a = new Lib({message:"message here"});
</script>

The functionality is operational without issues at this stage.

Incorporating into another TypeScript project

Subsequently, I developed a basic TypeScript project that integrates my modified library.

Folder structure:

│   gulpfile.js
│   package.json
│   tsconfig.json
│   yarn.lock
│
├───dist
│       index.html
│
└───Scripts
        Index.ts

In package.json, I include my library as a dependency, alongside jQuery to eliminate any configuration discrepancies within this project:

{
    "private": true,
    "version": "1.0.0",
    "scripts": {
        "preinstall": "npx use-yarn",
        "gulp": "node_modules/.bin/gulp"
    },
    "name": "ts-import",
    "devDependencies": {
        "@types/jquery": "^3.5.13",
        "gulp": "^4.0.2",
        "gulp-browserify": "^0.5.1",
        "gulp-clean": "^0.4.0",
        "gulp-concat": "^2.6.1",
        "gulp-sourcemaps": "^3.0.0",
        "gulp-typescript": "^6.0.0-alpha.1",
        "gulp-uglify": "^3.0.2",
        "typescript": "^4.5.5",
        "vinyl-source-stream": "^2.0.0"
    },
    "dependencies": {
        "jquery": "*",
        "library": "file:../../library"
    }
}

tsconfig.json:

{
    "compilerOptions": {
        "noEmitOnError": true,
        "noImplicitAny": true,
        "sourceMap": true,
        "target": "es5",
        "moduleResolution": "node",
        "outDir":"./js"
    },
    "compileOnSave": true,
    "exclude": [
        "**/node_modules/**"
    ],
    "include":[
        "./Scripts"
    ]
}

Gulp is employed for TypeScript compilation and import resolution - gulpfile.js:

const gulp = require('gulp');
const {series} = require('gulp');
const clean = require('gulp-clean');
const ts = require('gulp-typescript');
const sourcemaps = require('gulp-sourcemaps');
const browserify = require('gulp-browserify');
const concat = require('gulp-concat');
const uglify = require('gulp-uglify');

function cleanAll(cb) {
  return gulp.src([
      "./tmp/",
      "./dist/js"
    ], { read: false, allowEmpty: true })
    .pipe(clean());
}

function transpileTS() {
  const tsproject = ts.createProject('./tsconfig.json');
  return tsproject
      .src()
      .pipe(sourcemaps.init())
      .pipe(tsproject()).js
      .pipe(sourcemaps.write('./sourcemaps'))
      .pipe(gulp.dest('./tmp/js'));
}

function minJS() {
  return gulp
      .src('./tmp/js/Index.js')
      .pipe(sourcemaps.init({ loadMaps: true }))
      .pipe(browserify())
    //   .pipe(uglify())
      .pipe(concat('index.min.js'))
      .pipe(sourcemaps.write('./sourcemaps'))
      .pipe(gulp.dest('./dist/js'))
}

exports.default = series( cleanAll, transpileTS, minJS );

The import and utilization of my library are done as follows - Scripts/Index.ts:

import * as $ from "jquery";
import Lib from "library";

$(()=>{
    console.log("creating lib instance.");
    new Lib({message:"example message here"});
});

However, upon launching this in a browser - dist/index.html:

<script src="js/Index.min.js"></script>

An error occurs:

Uncaught TypeError: library_1.default is not a constructor

Indeed, library_1.default is nonexistent while library_1 exists:

> library_1
< class Lib {
        constructor(options) {
            console.log("It works!");
            console.log(options.message);
        }
    }

How can this issue be rectified? It seems likely that the error resides within the library, though pinpointing the exact origin eludes me.

Answer №1

I have successfully resolved the issue. In my lib.ts, I made adjustments to how I export my class:

import Options from "./options";

class Lib{
    constructor(options:Options){
        console.log("It works!");
        console.log(options.message);
    }
}

export default Lib;

Subsequently, in rollup.config.js, I modified it by adding exports:'default' to maintain global availability of the exported class:

...
{
      file: 'dist/lib.umd.js',
      format: 'umd',
      name: 'Lib',
      exports:'default'
},
...

Further changes were required in the library's package.json - I directed main to my ES module since it seems like browserify disregards the module directive.
I decided to remove the exports section as some other projects (not utilizing browserify) were still using the require file instead of the import one:

{
  "name": "library",
  "version": "1.0.16",
  "description": "example library",
  "main": "dist/lib.esm.js",
  "module":"dist/lib.esm.js",
  "types":"dist/types/lib.d.ts",
  "license": "MIT",
  "private": true,
  "devDependencies": {
    "@rollup/plugin-typescript": "^8.3.0",
    "rollup": "^2.67.0",
    "tslib": "^2.3.1",
    "typescript": "^4.5.5"
  },
  "exports": {
    "import": "./dist/lib.esm.js",
    "require": "./dist/lib.umd.js"
  },
  "scripts": {
    "build": "rollup -c rollup.config.js"
  }
}

Lastly, to avoid the

ParseError: 'import' and 'export' may appear only with 'sourceType: module'
error when running browserify, I had to incorporate babelify into my consumer project.

You can access the complete source code here

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Using a dictionary of objects as the type for useState() in TypeScript and React functional components

I am in the process of transitioning from using classes to function components. If I already have an interface called datasets defined and want to create a state variable for it datasets: {[fieldName: string]: Dataset}; Example: {"a": dataset ...

Tips for executing a type-secure object mapping in Typescript

I am currently working on creating a function in Typescript that will map an object while ensuring that it retains the same keys. I have attempted different methods, but none seem to work as intended: function mapObject1<K extends PropertyKey, A, B>( ...

Issues with JSONPATH in typescript failing to grab any values

Searching for a specific config item validity using JSON path can be achieved by specifying the key name condition. This process works seamlessly on platforms like , accurately extracting the desired value: In Typescript, the code implementation would loo ...

Error encountered when starting Npm: events.js:292 triggers an unhandled exception

As I dive into learning React, I decided to create a new React app using the create-react-app tool. However, upon running npm start after successfully creating the app, I encountered the following error message: events.js:292 throw er; // Unhandled ...

Mastering the use of forever with npm is essential for ensuring the

Attempting to execute the following command on my Debian 10 VPS forever start npm start results in an error in the terminal: warn: --minUptime not set. Defaulting to: 1000ms warn: --spinSleepTime not set. Your script will exit if it does not stay up ...

RxJS - Only emit if another source does not emit within a specified time frame

Imagine having two observables. Whenever the first one emits, there should be a 2-second pause to check if the other observable emits something within that timeframe. If it does, then no emission should occur. However, if it doesn't emit anything, the ...

I'm curious if someone can provide an explanation for `<->` in TypeScript

Just getting started with TypeScript. Can someone explain the meaning of this symbol <->? And, is ProductList actually a function in the code below? export const ProductList: React.FC<-> = ({ displayLoader, hasNextPage, notFound, on ...

Data retrieval from DynamoDB DocumentClient does not occur following a put operation

I am currently working on testing a lambda function using the serverless framework in conjunction with the sls offline command. The purpose of this lambda is to connect to my local DynamoDB, which has been initialized with a docker-compose image, and inser ...

When attempting to open an Angular modal window that contains a Radio Button group, an error may occur with the message "ExpressionChanged

I am brand new to Angular and have been trying to grasp the concept of lifecycle hooks, but it seems like I'm missing something. In my current project, there is a Radio Button Group nested inside a modal window. This modal is triggered by a button cl ...

Tips for tidying up duplicated typescript content sourced from a pre-existing library

Seeking guidance on implementing best practices and gaining a better understanding of my approach. After discovering the library react-google-calendar-api, I successfully installed it using npm in my React project. However, I wanted to expand its function ...

Is Npm not yet installed on your system?

After successfully downloading node.js from nodejs.org and installing it on my Windows system using gitbash, I encountered an issue while checking the versions. When I ran node -v, it displayed the version number as expected. However, upon running npm -v, ...

SystemJS is loading classes that are extending others

In my Angular2 application, I have two classes where one extends the other. The first class is defined in the file course.ts (loaded as js) export class Course { id:string; } The second class is in schoolCourse.ts (also loaded as js) import {Cours ...

Connecting CMD commands to WSL in a Node.js script: A guide

I have a nodeserver that needs to have all its dependencies started before it can be launched. The software package is designed for Windows but relies on a multi-container docker setup in WSL. My attempted solution was: "scripts": { "start": "wsl & ...

What is the minimum required node.js version for my project to run?

Is there a command in NPM that can display the minimum required Node version based on the project's modules? ...

Npm install failing to detect updated dependencies

Here is the scenario: project-A (main project) package.json points to git+ssh://example.com/.../library-B#branch1 as devDependency library-B#branch1 package.json points to https://github.com/.../library-C/tarball/branch2 as devDependency I made modificat ...

What is the method for developing a Typescript-connected High-Order React Component with Redux?

I am looking to develop a React Higher-Order Component that can safeguard routes within my application from unauthorized users without an access token. I aim to use this HOC to wrap a Component like so in the parent component: <Route exact path ...

Encountering issues in d3.js following the transition to Angular 8

After upgrading my Angular 4 app to Angular 8, I encountered an issue where the application works fine in development build but breaks in production build. Upon loading the application, the following error is displayed. Uncaught TypeError: Cannot read p ...

Using Webpack 4 and React Router, when trying to navigate to a sub path,

I'm currently working on setting up a router for my page, but I've encountered a problem. import * as React from 'react'; import {Route, Router, Switch, Redirect} from 'react-router-dom'; import { createBrowserHistory } from ...

Postman is having trouble communicating with an express router and is unable to send requests

Currently, I am experiencing some challenges while trying to grasp the concepts of express and node with typescript, particularly when setting up a router. In my bookRoutes.ts file, I have defined my router in the following manner: import express, { Expre ...

Error TS2322 occurs during compilation in Typescript when using ng.IPromise

Having some issues while using Angular 1.x with Typescript. Here is the code causing trouble: get(id): ng.IPromise<Server.MyItem> { return this.$http.get(`${this.baseAddress}/${id}`).then(d => d.data); } After compiling with tsc, I am encoun ...