0

I'm using Typescript, ES6 module syntax, and SystemJS / builder.

The basic requirements I'm trying to do are:

  • Enable typescript code completion by using @types packages (for global npm installs as well)
  • Using ES6 module import syntax (e.g. import * as _ from 'lodash' )
  • Have the builder exclude the globals from the build and still have them properly imported using CDN urls in dev / production.

The config I'm using for the build and dev / production env just to get it up and going:

System.config({
  meta: {
    "lodash": {
      "format": "global",
      "build": false,
      "exports": "_"
    }
    // ...more meta
  }, 
  map: {
    "lodash": "https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.13.1/lodash.min.js",
     // ...more maps
  }
});

From here I have a npm task which transpiles to ES6 and then bundles everything into a single file through babel plugin. The script is loaded directly on the production page fine and it loads. The issue is once a global dependency imports I keep getting errors like "_.clone is not a function" etc due to systemjs wrapping the CDN imports with an object like

{default: _ } //_ is the actual lodash export

I've been successful in changing the import to import _ from 'lodash' but then I get IDE errors since lodash (nor any other global script like angular) does not export a default value and I lose code completion.

What's the correct way to meet the requirements with systemjs / builder here?

As a side note I'm fine with using script tag loading instead of systemjs CDN imports if that works better.

Ryan Q
  • 10,273
  • 2
  • 34
  • 39

1 Answers1

1

TypeScript has a flag --allowSyntheticDefaultImports for just this scenario. Specifically, it informs the typechecker that another transpiler, loader, or bundler provides maps module.exports to exports.default in a later step.

You can specify this flag in under compiler options in tsconfig.json

{
  "compilerOptions": {
    "target": "esnext",
    "module": "es2015",
    "allowSyntheticDefaultImports": true,
    "moduleResolution": "node",
    "baseUrl": "."
  }

Note that when the module format is set to "system", this flag is set implicitly.

Now you can write

import _ from "lodash";

and TypeScript will understand it, typecheck it, and validate that it is used correctly. However, this only affects typechecking. You still need, and in this case already have, a loader or intermediate transpiler that provides the runtime synthesis.

import * as _ from "lodash";

violates the proposed NodeJS -> ESM interop proposal and, if you call it as in _([1, 2]).map(x => x ** 2) also violates the ES spec which forbids a Module Namespace Object from being callable., so you are doing well to take advantage of the --allowSyntheticDefaultImports flag and the interop provided by SystemJS.

Aluan Haddad
  • 29,886
  • 8
  • 72
  • 84
  • For future viewers: What ended up working is a combination of Aluan's answer and setting TypeScripts compile module format to "System" ```"module": "System",```. If I only switched syntheticDefaultImports on the global was then correct, but 'default' key was undefined. – Ryan Q May 15 '17 at 17:39
  • @RyanQ that shouldn't be the case I think you were missing package configuration in SystemJS. Using `"module": "system"` is a good option and is recommended, but it is not required. Recall that `--allowSyntheticDefaultImports` only affects type checking. If SystemJS is not automatically providing the default then there is almost certainly some missing configuration but it's difficult to say what without seeing what you have currently. – Aluan Haddad May 16 '17 at 20:06
  • 1
    You are correct on the config part. I'll post back here when I end up finding the solution, but currently I've yet to find a good config to work in both dev / production. The allowSyntheticDefaultImports at least helped on the IDE side of things. – Ryan Q May 16 '17 at 20:40