Using ES modules with TypeScript, Webpack, and Jasmine: A comprehensive guide

My Package Workflow

For my personal projects, I have a consistent structure for the packages I create and reuse. In production, I write these packages in TypeScript and compile them to JavaScript using `tsc` before publishing them to npm. This allows me to seamlessly use them in my other projects. I also run tests on these packages using Jasmine.

During development, I utilize Webpack with `ts-loader` to compile the TypeScript code in a way that supports running example scripts in a browser. Although the projects consuming these packages recognize them as JavaScript files only (due to pre-compilation before publishing), I still use Webpack for consistency.

Uniformly using ES module syntax in all contexts is vital to me. This avoids the hassle of switching between CommonJS for Node.js and ES module syntax for browsers. To ensure this consistency in Jasmine, I have configured my `jasmine.json` file with `"jsLoader": "import"`.

However, I am facing an issue with writing import statements, specifically how to specify file extensions to work seamlessly across different scenarios while developing these packages.


No File Extension Approach

When using `import { foo } from './foo';` in TypeScript, the Node module resolution assumes the file as `foo.ts`. Webpack's `resolve` configuration, specifying `extensions: ['.js', '.ts']`, also aligns with TypeScript's behavior in resolving imports with no file extensions.

Unfortunately, while this approach works for TypeScript and Webpack, running Jasmine tests with `import` without file extensions results in unresolved module errors.


Using .js File Extension Approach

Alternatively, including the `.js` file extension in imports like `import { foo } from './foo.js';` is supported in TypeScript since version 2. This allows successful compilation with `tsc` and resolves Jasmine tests as well.

However, Webpack struggles to interpret `./foo.js` as `./foo.ts`. This hinders building the code for showcasing examples in the browser.


.js Extension with Webpack Aliases

To address this limitation, I resorted to using Webpack's `resolve.alias` configuration to explicitly map `./foo.js` to `./foo.ts`. While this workaround works, it feels cumbersome and lacks scalability.

Is there a better, more optimal solution to handle file extensions in import statements across TypeScript, Webpack, and Jasmine without the need for numerous aliases?

Answer №1

Latest Update: 2022-10-20

Webpack v5.74.0 was officially released in July 2022, introducing a new feature called resolve.extensionAlias. This feature addresses the issue at hand without the need for additional plugins.

For instance, with the following configuration:

resolve: {
    extensionAlias: {
        '.js': ['.ts', '.js'],
    },
}

Previous Solution: 2021-09-07

Credit goes to amosq's insight on a related question, which provided a solution to the problem originally raised.

While Jasmine may have appeared to be a factor, the true crux of the matter lay in the ES Module resolution attempt within the setup. The issue stemmed from Webpack's module resolution inability to mimic TypeScript's module resolution behavior, specifically the treatment of *.js paths as potentially leading to *.ts files.

Thanks to amosq's recommendation, resolution came in the form of a Webpack resolver plugin: resolve-typescript-plugin

Although the plugin's instructions primarily catered to CommonJS syntax, I managed to incorporate it into my ES Module-based Webpack configuration as follows:

import resolveTypeScriptPluginModule from 'resolve-typescript-plugin';
const ResolveTypeScriptPlugin = resolveTypeScriptPluginModule.default;

const config = {
    // ...
    resolve: {
        fullySpecified: true,
        plugins: [new ResolveTypeScriptPlugin()],
        // ...
    },
    // ...
};

This setup successfully enabled the usage of Webpack alongside ts-loader for compiling TypeScript files featuring *.js import statements, operating harmoniously with my existing tsc workflow. Consequently, the workaround involving Webpack aliases outlined in my initial query became obsolete.

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

Having trouble setting State in React with Typescript?

I have encountered an issue with merging strings in an array. Despite successfully joining two strings and logging the result using console.log('Dates: ' + mergedActions), the merged string doesn't seem to be set in this.state.MergedAllActio ...

Stop MatDialog instance from destroying

In my application, I have a button that triggers the opening of a component in MatDialog. This component makes API calls and is destroyed when the MatDialog is closed. However, each time I open the MatDialog for the second time by clicking the button agai ...

What is the best way to handle API requests within an Angular component?

I am currently diving into the world of Angular at my workplace, even though I do not have a background in web development. One challenge I am facing is how to encapsulate API calls within one of my components without knowing where to begin. The componen ...

What steps do I need to take to integrate FontAwesome with the vue-cli webpack-simple template?

First, I create a new project using vue-cli: vue init webpack-simple grid-prototype Next, I add FontAwesome to my project via npm: npm install --save fontawesome Once installed, I include it in my main.js file with the following code: import 'fon ...

Is there a way to prevent certain folders that have .vue files from being included in the VueJS build process?

https://i.sstatic.net/YU1rB.png module.exports = { presets: [ '@vue/app' ], module:{ rules: [ { test: /\.vue$/, exclude: [ './src/components/Homepages/number1', './src ...

Is it possible for Webpack to compile a Vue template using the coffee-loader?

Every time I attempt to execute the command npm run dev, it crashes when running npm run build. I suspect that a parameter needs to be passed to uglifyjs or vue-loader. Adding a Babel preset for es2015 did not solve the issue. Error in build.js from Ugli ...

Ready to Opt-Out in RxJS 6?

My test is functioning properly at the moment, but changing the active value causes it to break. Therefore, I am looking to unsubscribe a (which is the current active Observable) before proceeding with any other tests: let a:Observable<Todo> = store ...

Using TypeScript with Angular UI Router for managing nested named views in the application

Hey there! I am new to typescript and have a bit of experience with Angular. Lately, I've been struggling to make a common angular-ui-router setup work with typescript. I have a nested named views structure that just doesn't seem to load correctl ...

When attempting to specify the path in the angular.json file, Angular encounters difficulty accessing Bootstrap from the node_modules directory

I have been attempting to integrate Bootstrap into my Angular project. Firstly, I used npm install --save bootstrap to add Bootstrap to my project. Following that, I installed jQuery as well. I then specified the path for Bootstrap. Displayed below is an ...

What is the best way to implement a nested fetch call?

I have a piece of code similar to the one below that is functioning properly: const url = "https://something"; let data2 = JSON.stringify({ source: "https://someimage.jpg" }); const test1 = fetch(url, { method: "POST", hea ...

What is the best way to generate an object in TypeScript with a variety of fields as well as specific fields and methods?

In JavaScript, I can achieve this using the following code: var obj = { get(k) { return this[k] || ''; }, set(k, v) { this[k] = v; return this; } }; obj.set('a', 'A'); obj.get('a'); // returns &ap ...

Adding a timestamp to an array in Angular/Typescript services

I've been struggling with adding a timestamp OnInnit to track the time a user visited a page. I want to include the timestamp in an array within my services, but I keep encountering errors and can't seem to figure it out on my own. Any assistance ...

What distinguishes ReadonlyArray from the declaration "readonly arr: T[]"?

Check out this unique playground. interface Feature { readonly name: string; } // <Test> // This is OK interface Foo1 { readonly arr: ReadonlyArray<Feature>; } function save1(foo: Foo1) { console.log(foo); } // </Test> // <Tes ...

Adding JavaScript files into Templates or HTML using webpack

Imagine having a server (like Node & Express) that serves HTML pages to clients using template files (such as pug, ejs, handlebars, twig, marko, etc.). It's a typical multi-page website setup where each page has its own JavaScript files. Can webpa ...

Can you explain the true meaning behind this specific type definition?

Since starting to dive into TypeScript recently, I came across an express server written in TS while browsing the Internet. However, I am struggling to comprehend the type definition of the 'middlewares' argument. Despite attempting to research i ...

What is the best way to call a method within a TypeScript class using its name as a string while already inside the class itself?

Currently, I am developing a class that automates the creation of routes for Express and invokes a function in a controller. However, upon trying to execute this[methodName](req, res), I come across an error message stating: 'Element implicitly has an ...

Navigating with the Angular router leads to an unexpected destination: "mat-radio-group-0=true"

I'm facing an issue with a close button in my HTML template that triggers a close() function in the component: HTML template: <div> <label id="radio-group-label">Please specify: </label> <mat-radio-grou ...

What is the best way to list the choices associated with a specific category?

The Node.js package I'm currently working with requires an argument of a specific type, which I can see is defined through a TypeScript declaration as follows: export declare type ArgType = 'A' | 'B' | 'C'; I am interes ...

Error encountered in Jest mockImplementation: Incompatible types - 'string[]' cannot be assigned to 'Cat[]' type

Recently, I've been writing a unit test for my API using Jest and leveraging some boilerplate code. However, I hit a snag when an error popped up that left me scratching my head. Here is the snippet of code that's causing trouble: describe(' ...

The TypeScript error ts2322 occurs when using a generic constraint that involves keyof T and a

Trying to create a generic class that holds a pair of special pointers to keys of a generic type. Check out an example in this playground demo. const _idKey = Symbol('_idKey') const _sortKey = Symbol('_sortKey') export interface BaseSt ...