4
votes

I'm going around in circles here - let me layout my understanding of modules as I currently understand it. Correct me if I'm wrong on any points.

Modules are useful to avoid namespace pollution (ie. variables with the same names), while also allowing you to write your code such that you know that you are depending on a module.

(ie. the alternative is a day where you are writing a bit of code that assumes that jquery exists as a global variable, and that makes it sketchy deleting various dependencies as you're not sure whether they are being used or not.)

CommonJS is the solution that was created for Nodejs, while RequireJS (aka AMD) was the solution for browsers.

However - as of ES2015 there is a standardised specification for modules in Javascript - and modern browsers support it, while it's in the works for NodeJS.

Typescript - My understanding is that typescript will compile in to which ever module strategy is specified in compilerOptions.module- ie. "module": "commonjs" will compile to the syntax that CommonJS uses, while "module" : "amd" will compile to the require syntax. I assume that "module": "es2015" compiles to ES2015 standard module syntax.

What I'm trying to do:

I am trying to write a couple of libraries containing simple ES6 classes. These libraries will then be used by another library - which will also use Typescript.

To start:

mkdir foo bar biz 

In each directory:

tsc --init && npm init

This will give us a default tsconfig.json of

{
  "compilerOptions": {
    /* Basic Options */
    "target": "es5",                          /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017','ES2018' or 'ESNEXT'. */
    "module": "commonjs",                     /* Specify module code */
    "strict": true,                           /* Enable all strict type-checking options. */
    "esModuleInterop": true                   /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */

  }
}

But it's a bit messy to have the .ts and the compiled .js in the same folder, and also we want declaration files, so I'll change it to this on each folder:

{
    "compilerOptions": {
      /* Basic Options */
      "target": "es5",                          /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017','ES2018' or 'ESNEXT'. */
      "module": "commonjs",                     /* Specify module code */
      "declaration": true,                   /* Generates corresponding '.d.ts' file. */
      "declarationMap": true,                /* Generates a sourcemap for each corresponding '.d.ts' file. */
      "outDir": "./lib/",                        /* Redirect output structure to the directory. */
      "rootDir": "./src/",                       /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
      "strict": true,                           /* Enable all strict type-checking options. */
      "esModuleInterop": true                   /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */

    }
  }

In package.json we also need to change this:

"main": "lib/index.js",

(but questions about this later).

In foo:

**src/index.ts**


export function hello() : string {
    return "hello!"; 
}

export function myRandom() : number{
    return Math.random(); 
}

And we'll run:

tsc 
sudo npm link 

this will give us in the lib/ folder:

lib/index.d.ts

export declare function hello(): string;
export declare function myRandom(): number;
//# sourceMappingURL=index.d.ts.map

lib/index.d.ts.map

{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,wBAAgB,KAAK,IAAK,MAAM,CAE/B;AAED,wBAAgB,QAAQ,IAAK,MAAM,CAElC"}

lib/index.js

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
function hello() {
    return "hello!";
}
exports.hello = hello;
function myRandom() {
    return Math.random();
}
exports.myRandom = myRandom;

In bar:

src/alpha.ts

import {hello, myRandom} from "foo"; 

export class Alpha {

    blurp () : string {
        return hello() + myRandom(); 
    }
}

src/beta.ts

export class Beta {


    flurf () : number {
        return Math.random(); 
    }
}

and we'll link this to our foo module and compile with:

sudo npm link foo tsc sudo npm link

And this compiles fine: in our lib/ folder we have:

 - alpha.d.ts
 - alpha.d.ts.map 
 - alpha.js
 - beta.d.ts
 - beta.d.ts.map
 - beta.js

And the interesting file there is going to be lib/alpha.js

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var foo_1 = require("foo");
var Alpha = /** @class */ (function () {
    function Alpha() {
    }
    Alpha.prototype.blurp = function () {
        return foo_1.hello() + foo_1.myRandom();
    };
    return Alpha;
}());
exports.Alpha = Alpha;

But this is where things fall down for me:

In biz

I want to be able to do something like this:

src/app.ts

import {hello} from "foo"; 
import {Alpha, Beta} from "bar"; 

const a = new Alpha(); 
const b = new Beta(); 

console.log(a.blurp());  
console.log(b.flurf()); 
console.log(hello()); 

But what we get here is [ts] Cannot find module 'bar'.

Now this makes sense perhaps. The package.json of bar is pointing to a index.js which doesn't exist.

Now I could try change bar's tsconfig to set the outfile to lib/index.js - but typescript doesn't like that:

tsconfig.json:5:5 - error TS6082: Only 'amd' and 'system' modules are supported alongside --outFile.

    5     "module": "commonjs",                     /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */
          ~~~~~~~~

    tsconfig.json:13:6 - error TS6082: Only 'amd' and 'system' modules are supported alongside --outFile.

    13      "outFile": "./lib/index.js",                       /* Concatenate and emit output to single file. */

So I could change module to "amd", but then it won't find the package 'foo'

src/alpha.ts:1:31 - error TS2307: Cannot find module 'foo'.

1 import {hello, myRandom} from "foo";

You get the picture.

Has anyone got some big picture advice about what I need to understand here?

My preference is for using the import {ItemA, ItemB} from "my-module"; syntax, but one thing that concerns if I'm writing modules that require specific import configuration on their end.

I've put all the code here:

https://github.com/dwjohnston/typescript-issues

1
The solution is to use yet another tool - lerna or maybe yarn workspaces. The idea is to have foo and bar as dependencies in biz package.json, then lerna will put them as symlinks in biz node_modules, and typescript will find them there. For small projects, you probably could manage it manually, all that's necessary in your example is to create node_modules in biz, and create symlinks to foo and bar there.artem

1 Answers

1
votes

The best solution I've got is to create an index.ts file and fill it with:

   export * from "./alpha.ts"; 
   export * from "./beta.ts"; 

etc.

This also gives you control over which modules you're actually exporting.