Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.
Ben Nadel at the Angular NYC Meetup (Dec. 2018) with: Ben Lesh
Ben Nadel at the Angular NYC Meetup (Dec. 2018) with: Ben Lesh@BenLesh )

Using Import = Require Syntax With TypeScript 2.2 In Angular 2.4.9

By Ben Nadel on

When using TypeScript, importing non-TypeScript modules has a somewhat irritating "* as" syntax. For example, if you wanted to import lodash into your TypeScript application, you might use the following import statement:

import * as _ from "lodash";

I think earlier versions of TypeScript allowed for a simplified default import syntax; but, I believe that the TypeScript team dropped it in favor of the more explicit syntax. Over the weekend, however, I was reading through Basarat Ali Syed's book, "TypeScript Deep Dive", when I saw him use a very simple "import =" expression that I had not seen before. Though somewhat inconsistent with the rest of my import statements, the simplified syntax makes it quite nice. And, something I thought would be worth sharing.

Run this demo in my JavaScript Demos project on GitHub.

Ironically, the "import =" syntax is right there in the Modules portion of the TypeScript documentation. However, I have yet to sit down and actually "read the manual" on TypeScript. So, it was just lucky that I discovered it in Bararat's book:

import _ = require( "lodash" );

It seems a little unusual to mash together the ES6 module syntax with the CommonJS module syntax. But, it's still better than the "* as" syntax mentioned above.

To see this "import = require" syntax in action, I put together a small Angular 2 demo in which I import lodash and use it to map one array of values onto another array of values:

  • // Import the core angular services.
  • // --
  • // NOTE: When I'm including the LODASH library, I'm using the "import =" syntax since
  • // the lodash library has a single top-level export.
  • import _ = require( "lodash" );
  • import { Component } from "@angular/core";
  •  
  • interface Friend {
  • id: number;
  • name: string;
  • }
  •  
  • @Component({
  • selector: "my-app",
  • styleUrls: [ "./app.component.css" ],
  • template:
  • `
  • <p>
  • <strong>Friends:</strong> {{ names | json }}
  • </p>
  • `
  • })
  • export class AppComponent {
  •  
  • public friends: Friend[];
  • public names: string[];
  •  
  •  
  • // I initialize the app component.
  • constructor() {
  •  
  • this.friends = [
  • { id: 1, name: "Kim" },
  • { id: 2, name: "Sarah" },
  • { id: 3, name: "Joanna" },
  • { id: 4, name: "Libby" }
  • ];
  • this.names = this.pluckNames( this.friends );
  •  
  • }
  •  
  •  
  • // ---
  • // PRIVATE METHODS.
  • // ---
  •  
  •  
  • // I return the names property as an array, plucked from the given collection.
  • private pluckNames( collection: Friend[] ) : string[] {
  •  
  • // NOTE: I need to explicitly cast the return value here because lodash
  • // overloads the .map() method instead of having an explicit "pluck" method.
  • // As such, the definition file gets confused on the return type.
  • return( <string[]>_.map( collection, "name" ) );
  •  
  • }
  •  
  • }

The example is silly; but, the TypeScript compiler executes without any error. And, when we run the above code, we get the following output:


 
 
 

 
 Using the import equals require syntax in TypeScript 2. 
 
 
 

As you can see, this "import = require" syntax works and, is in my opinion, much nicer than the "* as" syntax. Even though it does break the consistent form of the other import statements.

Now, since I've started using Webpack to build my Angular 2 demos, I've started creating a "vendors" file for things like lodash, so that Webpack's CommonsChunkPlugin plugin can properly isolate code that changes at a different rate. For this demo, my "main.vendor.ts" file looks like this:

  • // Import these libraries for their side-effects.
  • // --
  • // CAUTION: As you add more "import" statements to your application code, you will have
  • // to come back to this file and add those imports here as well (otherwise that imported
  • // content may get bundled with your main application bundle, not your vendor bundle.
  • import "@angular/core";
  • import "@angular/platform-browser-dynamic";
  • import "lodash";

As you can see, I am importing lodash using the TypeScript "side effect" syntax. This is compatible with the aforementioned "import = require" syntax and Webpack is able to successfully siphon the lodash library off into the vendor file.

I personally find this syntax, though not without its drawbacks, to be more pleasant than the "* as" syntax that I've been using up until now. You can technically use this syntax with TypeScript files as well (if they use the "export =" syntax); however, I see this primarily as a way to consume non-TypeScript files, like lodash, in a TypeScript application.

NOTE: You still need to provide type definition files for your non-TypeScript modules; or, TypeScript will complain that they have an implicit type of "any".



Reader Comments

There is a typescript compiler option called allowSyntheticDefaultImports which allows you to import without the strange * syntax.

Though I'm not sure if it's a best practice to use it.

@Leon,

Do you know, I've tried to use that several times, both when I was working with System.js and I think when i was using the TypeScript compiler and I could never get it to work. The default export was always coming back as undefined. If you could point me towards a working example anywhere, I would be much appreciated!

Hmm... I personally prefer `import * as _ from "lodash"`. Since more consistent with the TypeScript default one, rather than CommonJs like. Thank you for showing the alternative though and thank you for keeping the blog post short.

I get ERROR in /Projects/shopping-list/src/app/app.component.ts (5,1): Import assignment cannot be used when targeting ECMAScript 2015 modules. Consider using 'import * as ns from
"mod"', 'import {a} from "mod"', 'import d from "mod"', or another module format instead.

@Demisx,

Yeah, I definitely have an emotional problem with it being a different syntax. Seems, somehow, dirty. But, I also find the "* as foo" to be different than my other quasi-destructuring statements... so, to some degree, it's 6 of one, half-a-dozen of the other. I'm still trying to working around in my head.

@Jesse,

What version of TypeScript are you using? It's possible that the "require()" syntax was added later. I am not entirely sure of its history.

@Leon,

Interesting - I was able to download and run your demo successfully. But, when I try to do it in my own demo:

https://github.com/bennadel/JavaScript-Demos/tree/master/demos/import-require-angular2

... it still doesn't work. Without the "allowSyntheticDefaultImports" flag, the tsc CLI gives me the following error:

> error TS1192: Module '"/Users/ben/Sites/bennadel.com/projects/javascript_demos/vendor/angular2/2.4.9-webpack/node_modules/@types/lodash/index"' has no default export.

... and if I add the "allowSyntheticDefaultImports" flag, the tsc CLI doesn't complain, but the import "_" value is undefined in the app runtime.

There must be some fundamental difference between our two configurations / compilers; but, I am not knowledgeable enough to see it (mostly I know enough about TypeScript and the tsc compiler to just "get it working" and not much more than that).

It looks like this flag may also be failing in ts-node (which I can confirm):

https://github.com/TypeStrong/ts-node/issues/86

Ah, and from this ticket, it looks like this might not be a TypeScript issue at all, but a loader issue: https://github.com/Microsoft/TypeScript/issues/7518

> allowSyntheticDefaultImports does not change the output.
> All what allowSyntheticDefaultImports is tells the compiler that at
> runtime your loader (e.g. SystemJs) will perform this operation of
> mapping modules to default imports, and thus avoids the error at
> build time.

... so it could be that the my Webpack build is somehow not making it available? I'm super new to Webpack, so this is quite a bit over my head.