Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.
Ben Nadel at Scotch On The Rocks (SOTR) 2011 (Edinburgh) with: Matthew Bourke
Ben Nadel at Scotch On The Rocks (SOTR) 2011 (Edinburgh) with: Matthew Bourke@gummatt )

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".



Looking For A New Job?

Ooops, there are no jobs. Post one now for only $29 and own this real estate!

100% of job board revenue is donated to Kiva. Loans that change livesFind out more »

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.

Reply to this Comment

@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!

Reply to this Comment

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.

Reply to this Comment

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.

Reply to this Comment

@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.

Reply to this Comment

@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.

Reply to this Comment

@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.

Reply to this Comment

Post A Comment

You — Get Out Of My Dreams, Get Into My Comments
Live in the Now
Oops!
NEW: Some basic markdown formatting is now supported: bold, italic, blockquotes, lists, fenced code-blocks. Read more about markdown syntax »
Comment Etiquette: Please do not post spam. Please keep the comments on-topic. Please do not post unrelated questions or large chunks of code. And, above all, please be nice to each other - we're trying to have a good conversation here.