How to load named exports with SystemJS - javascript

If I have a lib, say utils.js which looks like this
exports.foo = function () {
return 'foo';
};
exports.bar = function () {
return 'bar';
};
Which can be used as follows
import {foo} from './libs/utils';
console.log(foo());
Not very spectacular, but I get the feeling that this problem is the origin of the issue described in this post. Anyway I cannot get this to work in combination with SystemJS. I have to change the code to fix it
import utils from './libs/utils';
console.log(utils.foo());
Here is my systemjs-config file:
SystemJS.config({
map: {
'plugin-babel': 'node_modules/systemjs-plugin-babel/plugin-babel.js',
'systemjs-babel-build': 'node_modules/systemjs-plugin-babel/systemjs-babel-browser.js',
},
packages: {
'.': {
defaultJSExtensions: 'js'
}
},
transpiler: 'plugin-babel'
});
So, it seems only the exports object can be loaded and not the named export. Can this somehow be fixed?
UPDATE I get the impression it could be fixed with formats
meta: {
'./libs/utils.js': {
format: 'cjs'
}
}
But so far it gives the same problems

This behavior is not SystemJS specific. SystemJS behaves like this since version 0.20 because this is what ES6 module interoperability is being standardized to.
When, as in your question, you are importing CommonJS modules (exported via module.exports) using ES6 import, you will only get the entire export, and you cannot immediately destructure the exported names.
However, when you are importing modules which are exported via ES6 export, you will be able to destructure the exported names.
So, it's all by design. Guy Bedford wrote about this on his blog and referenced the module standardization that is going on for NodeJS:
... named exports will no longer be permitted when importing a
CommonJS module from an ES module, and is discussed at
https://github.com/nodejs/CTC/pull/60/files#diff-2b572743d67d8a47685ae4bcb9bec651R217.
That is, import { name } from 'cjs.js', where cjs.js is a CommonJS
module will no longer be supported, and instead will require
import cjs from 'cjs.js'; cjs.name.
An interop workaround by using __esModule:
We will continue to support the __esModule flag in interop though,
allowing lifting of named exports for these cases.
So if the cjs.js module was written:
exports.__esModule = true;
exports.name = function () { ... }
then it would be possible to have import { name } from 'cjs.js';, even
though cjs.js is a CommonJS module, although this __esModule will
eventually in the longer term be deprecated as well.

Related

module export in ts file is not a module error

I have a config.ts and I do
module.exports = { something: 123 }
when I import like import { something } from './config.ts' I got error of config.ts' is not a module, what's the issue? my typescript is configured rightly and it's working in other places.
If you're using import { something } from './config.ts', you're using JavaScript modules, but your code in config.ts is using CommonJS modules. Some bundlers and such may let you mix them, but it's best not to.
To make config.ts a JavaScript module compatible with that import declaration (which expects a named export called something), change it to:
export const something = 123;
Or, of course, to use config.ts via CommonJS, your code using it would be:
const { something } = require("./config.ts");
...but given the error you're getting, I think your project is set up to use JavaScript modules (import/export), which here in almost 2021 is probably best (now that we have dynamic import to handle the cases where static modules don't quite do the job).

0x800a1391 - JavaScript runtime error: 'module' is not defined

I am from a C++ background and have a day's experience in TypeScript. I am trying to implement a design (interface/class) that is split between multiple files. It is flashing runtime error : 0x800a1391 - JavaScript runtime error: 'module' is not defined
Info.ts
export = Test; <----------- 0x800a1391 - JavaScript runtime error: 'module' is not defined
namespace Test {
export class Info {
}
}
TestWrapper.ts
import { Info } from "./Info";
namespace Test {
class TestWrapper {
public GetInfo(): Info {
return this.m_info;
}
}
}
Am I using something in the wrong way?
A guess:
export = Something
Compiles to something like
module.exports = Something
module.exports is a construct of the so-called "commonjs module system", something that is not available in browsers, but in node.js. So if you run the generated code in a browser through a direct <script> import, it will bring an error like that. TS transpiles imports and exports to module systems that can be specified in the tsconfig.json, but TS itself is not responsible for implementing the module system.
What to do?
If this code is really supposed to run in the browser, you could opt on one of the 2 following options:
Run the code through a module bundler (webpack), which will generate a browser compatible "bundle" (a big JS) concatenating all your files. Webpack has ways of including typescript as a plugin, so that you dont need to make your "build" a 2-step process; Module bundlers are usually fairly complicated.
Make your files module-less (which means: no top-levem imports or exports) and import them through vanilla <script> tags.
Am I using something in the wrong way?
While namespaces are commonly used in other languages, they do not exist in JavaScript, so the typescript version is just some rarely used way to add proper typing to some things (I never used it yet). In your case you actually don't need a namespace. Just:
export default class Info { /*...*/ }
Then you can
import Info from ".Info";
export default class TestWrapper { /*...*/ }
PS: That said, I actually don't know how to make this work with namespaces to resolve the error
It looks like you are trying to mix modules and namespaces. I would suggest reading Namespaces and Modules.
You don't need to to export at the top level using export = Test, you also shouldn't import { Info } from "./Info"; because Info is part of the Test namespace which TestWrapper is a member of.
If you want to go the namespace route, consider:
Info.ts
namespace Test {
export class Info {}
}
TestWrapper.ts
namespace Test {
export class TestWrapper {
m_info: Info;
public GetInfo(): Info {
return this.m_info;
}
}
}
Consumer.ts
console.log(Test.TestWrapper);
Compile this using:
tsc --outFile foo.js Info.ts TestWrapper.ts Consumer.ts
Then run using:
node foo.js
Which prints:
[Function: TestWrapper]

How to map TypeScript ES6 default imports to return values of AMD modules?

For example, I have an AMD module in vanilla JS (not touched by tsc):
// Foo.js
define([], function() {
return class Foo {}
})
with accompanying declaration file:
// Foo.d.ts
declare class Foo {
// ...
}
export default Foo
then I have a typescript module:
// app.ts
import Foo from './Foo'
new Foo // Uncaught TypeError: Foo_1.default is not a constructor
When I compile my typescript module to AMD format with tsc and run it in my RequireJS environment, there's an error that default is undefined because typescript compiles to:
define("app", ["require", "exports", "Foo"], function (require, exports, Foo_1) {
"use strict";
console.log(Foo_1["default"]);
});
I don't want it to grab the default property. I want it to treat a default import like it is simply grabbing the returned value of my define module. Every single module in my project is a define() module, or a typescript module compiled to define() (AMD). I'd simply like to get that return object.
Is there a way to do (configure) this?
EDIT:
I know I can import * as Foo from './Foo', but that is sort of hacky, and it generates errors because the declared module doesn't have named exports. I would like a non-error way to do it, if possible.
EDIT: I learned I can just do
// app.ts
import Foo = require('./Foo')
new Foo // no error
to achieve the result. The only problem with that is it is not ES6-compatible. This means that if we tell typescript to leave ES6 import statements as-is, then other tools won't understand typescript-specific import/export statements. I need a way to use only official ES6 module syntax in a reasonable way.
I'm think that my solution might be to just do a post-tsc-compile transform step to convert default import access points.
Try
import * as Foo from './Foo'

How should I define a global TypeScript variable in a definition file so that it can be imported?

I have an external JS library with a global parameter:
function Thing() { ... }
...
var thing = new Thing();
There is a TypeScript definition file, so in thing.d.ts:
declare var thing: ThingStatic;
export default thing;
export interface ThingStatic {
functionOnThing(): ThingFoo;
}
export interface ThingFoo {
... and so on
Then I import this into my own TS files with:
import thing from 'thing';
import {ThingFoo} from 'thing';
...
const x:ThingFoo = thing.functionOnThing();
The problem is that transpiles to:
const thing_1 = require("thing");
...
thing_1.default.functionOnThing();
Which throws an error. I've asked about that in another question, and the suggestion is to use:
import * as thing from 'thing';
That doesn't fix it - it gives me thing.default in TS but then that's undefined once transpiled to JS.
I think there's something wrong with thing.d.ts - there must be a way to define a typed global parameter that can be imported.
How should I write thing.d.ts so that it represents the JS correctly and doesn't transpile to include default or other properties not actually present?
If the only way to use that library is by accessing its globals (as opposed to importing it as node module or amd or umd module), then the easiest way to go is have a declaration file without any exports at top level. Just declaring a variable is enough. To use it, you have to include that declaration file when compiling your typescript code, either by adding it to files or include in tsconfig.json, or directly on command line. You also have to include the library with a <script> tag at runtime.
Example: thing.d.ts
declare var thing: ThingStatic;
declare interface ThingStatic {
functionOnThing(): ThingFoo;
}
declare interface ThingFoo {
}
test-thing.ts
const x:ThingFoo = thing.functionOnThing();
can be compiled together
./node_modules/.bin/tsc test-thing.ts thing.d.ts
the result in test-thing.js:
var x = thing.functionOnThing();
See also this question about ambient declarations.
Note: there are module loaders out there that allow using global libraries as if they were modules, so it's possible to use import statement instead of <script> tag, but how to configure these module loaders to do that is another, more complicated question.

Rollup with CommonJS module exporting an unnamed function as the module?

I have a CommonJS module, called inner.js, that defines a function and then exports that function as the entire module:
// inner.js, a legacy CommonJS module
var foo = function() { return 42; };
module.exports = foo;
In Node, I can readily verify this works as-is.
> var inner = require('./inner.js');
> inner() // prints 42
But that’s a legacy module that I’d like to use from a ES6 module, called outer.js:
// outer.js, an ES6 module
import * as inner from "./inner.js";
export function bar() { return inner(); }
I see that rollup-plugin-commonjs is commonly used in these situations but I can’t get it to work when the CommonJS inner.js module exports a function as the whole module. If, after running rollup and dumping the result to loadme.js, I try to run load the ES6 outer module and try to call the function originally defined in the inner CommonJS module, I get an error:
> var outer = require('./loadme.js')
undefined
> outer.bar()
TypeError: inner$2 is not a function
at Object.bar (/.../so-rollup-question/loadme.js:27:25)
I think I’m just failing to load the CommonJS module correctly, in such a way that the module itself functions as a function. I’m not familiar enough with UMD to get anything meaningful out of inspecting the rollup-output.
The rest of this post is about a minimum example.
Here’s my very simple index.js:
// index.js
export {bar} from "./outer.js";
which is read by my rollup config:
// rollup.config.js
import npm from "rollup-plugin-node-resolve";
import commonjs from 'rollup-plugin-commonjs';
export default {
entry : "index.js",
format : "umd",
moduleName : "sphereModule",
plugins : [ npm({jsnext : true}), commonjs() ],
dest : "loadme.js"
};
I have a complete clonable repository demonstrating the problem.
Assigning directly to module.exports is basically equivalent to having a default export. Hence importing the module as follows should work:
import inner from "./inner.js";

Categories