Understanding TypeScript and SystemJS WITHOUT Angular
Asked Answered
H

1

14

I've been researching and doing simple "hello worlds" with TypeScript recently. There is something I think I can't wrap my head around and that is how to use System.js with TypeScript. Every single tutorial or demo out there on the internet is about Angular2 and I don't want to get involved with Angular 2 yet.

As an example, I have the following project structure:

RootFolder
| 
| _lib
| ...... ts (where .ts files are)
|
| components (where compiled .js files are)
| libraries
| ......... systemjs (where system.js is)
|
| index.html
| tsconfig.json

My tsconfig.json file is looking like:

{
  "compileOnSave": true,
  "compilerOptions": {
    "noImplicitAny": true,
    "noEmitOnError": true,
    "removeComments": false,
    "sourceMap": true,
    "target": "es5",
    "module": "system",
    "moduleResolution": "node",
    "outDir": "./components"
  },
  "exclude": [
    "node_modules",
    "wwwroot"
  ],
  "include": [
    "./_lib/ts/**/*"
  ]
}

TypeScript compilation works as expected and there are no problems with that. I have created a simple class named "Alerter" contains the following code:

//alerter.ts
class Alerter {
    showMessage(): void {
        alert("Message displayed.");
    }
}

export default Alerter

And an app.ts (which is my "main" application file) with the following code:

//app.ts
import Alerter from "./alerter";

console.log("app.js included & executed");

function test() {
    console.log("test called");
    const alt = new Alerter();
    alt.showMessage();
};

And in my index.html I just want to import this app.js with System.js and simply want to call "test" function from the console. But it does not work. No matter what I did I simply can't access the function. I see the first console.log line being executed but when I try to call test() from chrome console it is undefined.

If I remove the "alerter" class dependency from my main.ts everything works. Because compiled app.js only contains console.log calls and the function definition.

Here is my System.js calls in index.html

System.config({
    packages: {
        "components": {
            defaultExtension: "js"
        }
    }
});

System.import("components/app");

I'm really desperate now and think I should simply go back to Jquery days. This is so simple yet can't make it work.

Horsefaced answered 30/3, 2017 at 11:28 Comment(4)
I made a simple TS + SystemJS project setup for a presentation maybe it will help you. github.com/Toskv/talk.timjs.typescript/tree/master/modules/liveMeg
@Meg Thank you it's really helpful so far. I'm still inspecting but already gave me good insight.Horsefaced
if you want to access the function from index.html you'll have to load to export it so you can use it once the module loading is finished. System.import returns a promise and the response will contain the module you ask for. :)Meg
this might be helpful in understanding the my last comment. #36313593Meg
V
10

I see what's going on here. This is related to both proper usage of TypeScript export keyword and SystemJS.

From your description you basically want to use SystemJS to import a JavaScript file similarly to using just the <script> tag and then use its global defined functions.

But this means you need to be aware of how TypeScript compiles your files. The documentation at https://www.typescriptlang.org/docs/handbook/modules.html says:

In TypeScript, just as in ECMAScript 2015, any file containing a top-level import or export is considered a module.

This is what you're doing. The app.ts file has one import and the alerter.ts file has one export statement so these both are going to be compiled as modules. Then from your tsconfig.json I see you're using the system format (however, it doesn't matter here).

One major benefit of modules is that they don't leak any global objects outside the of their scope. So when you call System.import("components/app") the test() function exists only within that module. But you can export the function and call it after the module is loaded:

This means you need to export the function first:

// app.ts
export function test() {
  ...
};

Then System.import() returns a Promise that is resolved with the module exports object so we can call the test() method there.

System.import("components/app").then(function(m) {
  m.test();
});

This really works as you expect.

However, it looks like you wanted to define the test() function globaly. In such case you need to define the function on the window global object yourself:

// app.ts
function test() {
  ...
}
declare const window;
window.test = test;

Now you can use it whenever you want after importing the package:

System.import("components/app").then(function(m) {
  test();
});

SystemJS has multiple ways to manipulate with global objects but I don't think there's any easier way to use them when your imported package has dependencies that need to be resolved as well (otherwise have a look at this but it's not your use-case https://github.com/systemjs/systemjs/blob/master/docs/module-formats.md#exports).

Vizcacha answered 31/3, 2017 at 9:51 Comment(2)
Excellent explanation! Exporting my test() function solved the problem like you said and I got the idea of how it works thanks to your answer. One thing though, what is the best practice to include my own "local / internal" modules to each other? Should I always and always implement "each file is a module" and use import / export or <reference path="..."> is enough?Horsefaced
@Alaminut I think <reference path="..."> is used only by the compiler and doesn't generate import statements so the code will won't work. The <reference path="..."> was used in older version of TypeScript to tell the compiler about your *d.ts files. I don't think I has any common usage now so I'd stick to using only import and export.Vizcacha

© 2022 - 2024 — McMap. All rights reserved.