Typescript 2 — using ES6 import & require?

16,213

Solution 1

Since version 1.5, Typescript has adopted the ES6 style of imports.

You can keep your code "as is"

//File = init.todos.ts

export class Init {
   load() {
      ...
    }
}

//File = todo.service.ts

import { Init } from './init.todos'

By moving the property module in tsconfig.json to commonjs, the transpiler will generate commonjs compatible javascript which will allow you to benefit from, and contribute to, the full nodeJS ecosystem.

"compileOptions": {
    ...
    "module": "commonjs"
    ...
}

If you wish to import an existing NodeJS module in your typescript code, a few things may happen

  • the module ships with embedded Typescript Definition Files (there is a "typings" entry in "package.json"). In that case, import the module using the ES6 syntax and you are good to go (import * as blah from 'blah'). immutable.js is a good example of such a library

  • the module does not ship with Definition files but there is one available on DefinitelyTyped, simply

    • run npm install @types/blah to get the definitions in your project
    • and import the module as usual: import * as blah from 'blah'
  • the module does not have a definition file

    • you can craft one, simply add it to your project after naming it blah.d.ts. DefinitelyTyped has a guide on this
    • you can decide to go without typings, simply use NodeJS require() const blah = require('blah'), and blah will have type any (which really means that blah opted out of typing)

(for sake of completeness, I could add the allowJS compiler option available since 1.8, but that is a whole subject in itself)

Solution 2

TypeScript is ES6 compliant. Use ES6 module loading syntax within your TypeScript source files:

import  { SomeType } from './some.module';

When TypeScript compiles to regular JavaScript, you can target a module system of your choosing:

"compileOptions": {
    "module": "commonjs" (or system, umd, amd)
}

Whatever target module system you choose, you need to ensure that you include the necessary scripts and configuration to load the module. i.e. SystemJS, RequireJS, etc.

As an exercise, try targeting different module loaders, and inspect the .js file - you'll see what I mean.

Share:
16,213
Royi Namir
Author by

Royi Namir

Updated on June 04, 2022

Comments

  • Royi Namir
    Royi Namir almost 2 years

    I have an exported class in my working Angular2 app using ES6 module:

    //File = init.todos.ts
    
    export class Init {
       load() {
          ...
        }
    }
    

    I'm importing this class from another component via :

    //File = todo.service.ts
    
    import { Init } from './init.todos'
    

    It works as expected.

    However if I change the loading mechanism to commonjs :

    //File = init.todos.ts
    export class Init {
        load() {
           ...
        }
    }
     module.exports.Init = Init;
    

    Requiring it:

    //File = todo.service.ts
    var  Init = require("./init.todos");
    

    — I get those errors :

    ...myApp/src/app/todo.service.ts (4,13): Cannot find name 'require'.) ...myApp/src/app/todo.service.ts (12,14): Property 'load' does not exist on type 'TodoService'.)

    enter image description here

    Question:

    How can I also load commonjs modules using require ?

    Tsconfig.json:

    {
        "compileOnSave": false,
        "compilerOptions": {
            "outDir": "./dist/out-tsc",
            "baseUrl": "src",
            "module": "system",
            "sourceMap": true,
            "declaration": false,
            "moduleResolution": "node",
            "emitDecoratorMetadata": true,
            "experimentalDecorators": true,
            "target": "es5",
            "typeRoots": [
                "node_modules/@types"
            ],
            "lib": [
                "es2016",
                "dom"
            ]
        }
    }
    

    Here are the config files :

    tslint.json

    tsconfig.json

    package.json

    angular-cli.json

    webpack.config.js

    • Estus Flask
      Estus Flask about 7 years
      This happens because you have "module": "system". There's no require function in SystemJS.
    • Royi Namir
      Royi Namir about 7 years
    • Bruno Grieder
      Bruno Grieder about 7 years
      in tsconfig, change "module" to "commonjs" and keep the ES6 syntax e.g. `import { Init } from './init.todos'.
    • Royi Namir
      Royi Namir about 7 years
      @BrunoGrieder Oh , so the imported module will have module.exports but I still should import it via es6 syntax ?
    • Bruno Grieder
      Bruno Grieder about 7 years
      Yes. Typescript adopted ES6 style imports some time ago (1.5 or 1.7). If you use const x = require('blah'), you will be using NodeJS/CommonJS require and "lose" typing since xwill be mapped to any. This may be useful to import JS librairies which are not typed though
    • Royi Namir
      Royi Namir about 7 years
    • Bruno Grieder
      Bruno Grieder about 7 years
      I see. In init.todo.ts use export class Init { ...}, do not touch modulle.exports. When transpiled to JS, the module.exports line will be generated in the javascript file. Typescript is exactly (~) like ES6 for modules
    • Royi Namir
      Royi Namir about 7 years
      @BrunoGrieder but if a friend sends me a library which is a commonjs , he wouldn't have that export class . He would only have module.export . I don't always have control about the other libraries
    • Bruno Grieder
      Bruno Grieder about 7 years
      That is true. You have two solutions: 1) There are type definitions available inside the library or on DefintelyTyped or 2) use commonjs/nodeJS require i.e. const blah = require('blah') and you will not have types on blah (it will be typed to any)
    • Bruno Grieder
      Bruno Grieder about 7 years
      I have tried to concatenate the whole discussion in answer
  • Royi Namir
    Royi Namir about 7 years
    I'm sorry but did you mean in the second case : "the module does not ship with Definition files but there is one available on DefinitelyTyped, simply" - that I can import it via a "regular require". right ? (I mean sure I can get it via ES6 import , but the whole point is that the d.ts allows TS to recognize the require , no?)
  • Bruno Grieder
    Bruno Grieder about 7 years
    TS does not "recognize the require". The point of having definition files is that the JS module can be used as if it were written in typescript and hence imported using typescript import syntax, which is the ES6 import syntax
  • Royi Namir
    Royi Namir about 7 years
    Yeah , that's what I meant , it allows the TS compiler to recognize new d.ts'ed keywords. (correct me if I 'm wrong). but still I want to ask about the second case you mentioned. Even If I add blah.d.ts which my friend sent me with his blah library ( commonjs) - Typescript only will be muting errors regarding the require ('blah') which I'll have in my code. right ? I mean I will have to have another module loader ( browserify/webpack) in order for the final JS to actual reference those required file. right?
  • Bruno Grieder
    Bruno Grieder about 7 years
    Yes. You need a module bundler such as browserify or webpack. Using require or allowJs you may be able to avoid using one but you lose the benefit of typings
  • Royi Namir
    Royi Namir about 7 years
    :-) ( finally i'm starting to get it.) - Ok if so - what difference does it make if I change the module in tsconfig ? I mean - what does it have to do with TS at first place ? From My testings - TS search es6 syntax and convert it. It doesn't care what you have in your code even if it's invalid. Do you see where is my misunderstanding ? what does TS has to do with the tsconfig's module settings ? I mean it's up to webpack/browserify to "require" libraries (except es6 import built inTS).
  • Bruno Grieder
    Bruno Grieder about 7 years
    It changes the way your javascript code is generated by the compiler.. and the bundlers bundle that code. It does not change the way you write typescript