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

The npm installation encountered an error with the command 'bower install' and exited with error code 1

I am looking to develop widgets for the XBee ZigBee Cloud Kit. In order to get started, I need to set everything up properly. I have been following this guide. After facing issues with the automated setup, I decided to proceed with the manual setup. I su ...

How can I make TypeScript properly export function names for closure-compiler?

Here is the TypeScript code I am working with: namespace CompanyName.HtmlTools.Cookie { export function eraseCookie(name:string, path:string) { createCookie(name, "", path, -1); } export function readCookie(name:string) { ...

Is there a workaround for utilizing a custom hook within the useEffect function?

I have a custom hook named Api that handles fetching data from my API and managing auth tokens. In my Main app, there are various ways the state variable "postId" can be updated. Whenever it changes, I want the Api to fetch new content for that specific p ...

Encountering the message "error fetch failed" in the terminal upon initiating a new Astro project

Recently, I decided to delve into the world of website building and chose to start with the Astro static website generator. Following the tutorial in their documentation, I attempted to create a new blog project by running the command npm create astro@late ...

Accessing the property of an object with TypeScript

I am working with an array of objects, where each object contains two properties: {key:count} When configuring my chart, I need to set the data source in this format: {meta: "unknown", value: [the count of unknown]}, {meta: "male", value: [the count of ...

Initial position of the range slider in IONIC 2

I have been searching the web extensively to find a solution to this particular issue. I am working with a range slider and trying to set its default starting values, but so far, I haven't had any luck. I've checked both the official documentatio ...

Tips for implementing collapsible functionality that activates only when a specific row is clicked

I need to update the functionality so that when I click on a row icon, only that specific row collapses and displays its data. Currently, when I click on any row in the table, it expands all rows to display their content. {data.map((dataRow ...

Encountering a peculiar error while attempting to install firebase-tools

Currently in the process of deploying my application on Firebase by following a tutorial. I encountered an issue after executing the command npm install -g firebase-tools: npm WARN deprecated <a href="/cdn-cgi/l/email-protection" class="__cf_email__" d ...

Module not found in Node.js Express JS

I've read several topics on this issue here at stackoverflow, but I am still unable to get my node application running. When I try to run the command: node app.js local I receive the following error: Error: Cannot find module './config' ...

The FirebaseX Ionic native plugin received 2 arguments instead of the expected 3-4

Trying to implement Firebase Phone Auth with the FirebaseX plugin, I encountered an issue. Here is the code snippet I used: async getVerificationCode(): void { const res:any = await this.firebaseX.verifyPhoneNumber('+16505553434', 60); ...

The react-leaflet-heatmap-layer-v3 source directory could not be located

Upon attempting to utilize the npm package react-leaflet-heatmap-layer-v3 in my React TypeScript application, I successfully installed it and ran yarn start. However, I encountered the following warning messages: WARNING in ./node_modules/react-leaflet-hea ...

Sending the :id parameter to the Service component

In the early days of my Angular journey, I have a simple question. Currently, I am utilizing the WordPress REST API to showcase a list of posts from a specific category by using posts?categories={ID HERE}. However, I am facing an issue in passing the ID f ...

Setting up Dependabot to connect to GitHub Packages repository

Looking to integrate dependabot into a private GitHub repository named RepoA. The project consists of npm packages that rely on other private repositories hosted via Github Packages, known as LibA and LibB. After researching online, I attempted the follow ...

Unable to retrieve the key value from a child object in Angular 2 when working with JSON Data

Currently, I am using Angular and attempting to extract data from the child object's key value. Here is the JSON data provided: "other_lessons": [ { "id": 290, "name": "Christmas Test #290", "course": { "id": ...

Updates made in the type declaration files are not being displayed

I'm currently working on an express app and I have been trying to add a new property to the Request's class instance. To achieve this, I created a type.d.ts file at the root of my project that looks like this: type User = { name: string } de ...

The content of the string within the .ts file sourced from an external JSON document

I'm feeling a bit disoriented about this topic. Is it feasible to insert a string from an external JSON file into the .ts file? I aim to display the URLs of each item in an IONIC InAppBrowser. For this reason, I intend to generate a variable with a sp ...

challenges with template inheritance: when one template takes precedence over another

I have encountered an issue with my two HTML templates, login.html and signup.html. Both of these files inherit from the base.html file, but there seems to be a problem where one file is overriding the title and content of the other. So when I visit /login ...

What is the method to merge min and max validation errors when using React Hook Form?

<input {...register("subject", { maxLength: 50, minLength: 2, required: true, })} disabled={isLoading} id="subject" autoComplete=&q ...

Creating Angular models for complex nested JSON structures

I'm a beginner with Angular and I'm dealing with nested API responses from a Strapi application. I've set up a model using interfaces, but I'm having trouble accessing attributes from the product model when trying to access product data ...

Troubleshooting npm Err ERESOLVE peer dependencies in ReactJS for Vercel Hosting deployment

Summary After a month of work, I finally deployed my project by using Git to upload it to GitHub Repository and hosting it on Vercel. However, I encountered an error with npm code ERESOLVE which took me hours to resolve. Background Upon completing my ...