Why I can not import directly from node_modules using SystemJS? - javascript

While there is a lot of questions and documentation on SystemJS, I still don't understand the import syntax.
Specifically, why can typescript not find ng-boostrap.js using this code:
import { createPlatform } from '../../node_modules/#ng-bootstrap/ng-bootstrap/bundles/ng-bootstrap',
which is directly importing the file, but this code works:
import {createPlatform } from './node_modules/#angular/core/bundles/core.umd.js';
where my map in systemjs.config.js contains the line:
'#angular/core': 'npm:#angular/core/bundles/core.umd.js'.
Why I can not import directly from node_modules using systemJS?

Note: Though the solution below works, some of the information is incorrect. Please see discussion below in comments.
First of all, TypeScript doesn't know anything about JS files. It knows how to produce them, but doesn't know how to compile against them. So I am not sure how you actually got
import {createPlatform } from './node_modules/#angular/core/bundles/core.umd.js';
to compile in your TypeScript code.
We are able to do
import {createPlatform } from '#angular/core';
in TypeScript, because TypeScript is already looking in the node_modules. And #angular/core, if you look inside your node_module, has the directory #angular/core, with an index.d.ts file. This is the file that our TypeScript code compiles against, not the JS file. The JS file (the one in the above first code snippet) is only used at runtime. TypeScript should know nothing about that file.
Using the second snippet above, when the TypeScript is compiled to JS, it looks like
var createPlatform = require('#angular/core').createPlatform;
As runtime, SystemJS see's this, then looks at the map configuration, and maps the #angular/core to the absolute file location, and is able to load that file
'#angular/core': 'npm:#angular/core/bundles/core.umd.js'
This is the pattern that you should follow with the ng-bootstrap. Use the import that points to the TypeScript definition file, so that it can compile
import { ... } from '#ng-bootstrap/ng-bootstrap';
If you look in the node_modules/#ng-bootstrap/ng-bootstrap directory, you should see the index.d.ts file. This is what TypeScript will use to compile against. When it is compiled to JS, it is compiled the following
var something = require('#ng-bootstrap/ng-bootstrap').something;
And in the SystemJS config, we need to map #ng-bootstrap/ng-bootstrap to the absolute path of the module file, otherwise SystemJS won't know how to resolve it.
'#ng-bootstrap/ng-bootstrap': 'npm:#ng-bootstrap/ng-bootstrap/bundles/ng-bootstrap.js'
One of the key take-aways from this, is to understand the difference between compile-time and runtime. Compile type is TypeScript, which doesn't know anything about JS files, as those are runtime files. SystemJS is the one that needs to know about the runtime (JS) files.

Related

Why do I need to add the .js extension when importing files in typescript but not in Angular imports?

In my typescript project I need to add the *.js extension when doing imports, otherwise the project will build but it will fail at runtime in the browser because it cannot find the file.
This is what my typescript imports look like:
import {MainApp} from './MainApp.js';
window.onload = () => {
var app = new MainApp(5);
app.doSomething();
};
From what I have read (Appending .js extension on relative import statements during Typescript compilation (ES6 modules) for example) it seems a normal thing for typescript that I cannot do this:
import {MainApp} from './MainApp.js';
But the thing is that in Angular using typescript I can do this:
import {MainApp} from './MainApp';
So, how it is Angular doing it? There is a way I can replicate that behavior in my non angular, pure typescript project?
Because angular cli first compiles all your source files into fewer files for the browser. All your code from multiple files lands in just one .js file. At compile time the compiler finds the MainApp related file and puts it into the output file.
Whereas the typescript compiler for the most part just removes the TS parts and keeps the TS parts. Otherwise it doesn't touch the files. The browser then at runtime requests all the source .js files.
If you don't want to care about file endings in import you'll need a bundler. There are many different ones like webpack, rollup, parcel, and many more.

What is the best way to point to common dependency from JS modules bundled together?

(Code below is a simple example, real scenario is bigger)
I have two modules, mod1.js and mod2.js, which are bundled together (using esbuild). They share a common dependency, util.js.
Problem is: when code in mod2.js imports util.js (using same alias), there's a conflict with names.
util.js:
export class Util {
...
}
mod1.js:
import Util from "./util.js";
...
mod2.js:
/* this raises an error of variable already declared */
import Util from "./util.js";
...
If I change alias in mod2.js, error goes away, as expected. But changing aliases every time I import util.js is a bit clumsy, and makes me think there has to be another way.
Is there a better approach to point to a common dependency from multiple modules which are bundled together?
Thanks in advance!
With help from #Bergi's comment, I figured out that I was not using esbuild to bundle my files, but rather using Hugo to concatenate them, and passing this to esbuild.
This leads to mentioned error because there are multiple imports in the same file, which esbuild correctly doesn't recognize as valid. Instead, using esbuild to bundle my files gives me correct results.
I'm still using Hugo, but I have a single entry point which it consumes, that imports all my scripts. From example, I have another file, say master.js:
master.js:
import mod1 from "./mod1.js";
import mod2 from "./mod2.js";
And I then pass this master.js to Hugo, using its js.Build function, which internally uses esbuild. This way I can import util.js using same alias, because these imports are in separate files, using ES6 linking from esbuild.

Typescript - Transform imports for npm distribution

I am working on an NPM package written in Typescript, and I am having trouble wrapping my head around module resolution when compiling the library to publish.
Throughout the project, I have been using non-relative imports to avoid the annoyance of ../../../. However, I read in the typescript documentation that relative imports should be used within a project.
A relative import is resolved relative to the importing file and cannot resolve to an ambient module declaration. You should use relative imports for your own modules that are guaranteed to maintain their relative location at runtime.
A non-relative import can be resolved relative to baseUrl, or through path mapping, which we’ll cover below. They can also resolve to ambient module declarations. Use non-relative paths when importing any of your external dependencies.
I would like to not have to sacrifice the nice, neat imports in favor of relative imports, but I am not sure how to set up the compiler settings in order to get this to work. When running tests, I specify NODE_PATH in order to resolve the modules, but this isn't working for post-compilation.
I would like to be able to write files using non-relative imports, but have them transformed in some way so that the dist/ files can resolve the imports.
The project is hosted on github here.
The relevant issue is that I end up with an index.d.ts file in my dist/ folder that looks like this:
import { Emitter } from 'emitter';
import { Schema } from 'migrations';
import { Model, model, relation } from 'model';
import { Builder } from 'query';
export { Builder, Emitter, Model, model, relation, Schema };
But all the modules have errors that the module cannot be resolved. How can I keep these imports in their current form, but transform them in some way when building so that when I publish the npm package, the modules can be correctly resolved.
I would follow the advice in the official Typescript docs:
https://www.typescriptlang.org/docs/handbook/declaration-files/publishing.html
Basically, the suggestion is to build your library just before publishing to npm. You will have two files, in output; let's call them main.js and main.d.ts.
The critical point, here, is that by tsc-ing your source files you resolve the dependencies before npm is involved at all, so you can keep your references as you wish.
In your package.json, include two lines (or change them accordingly, if you have them already):
{
...
"main": "./lib/main.js",
"types": "./lib/main.d.ts"
...
}
In this way, any consuming project doesn't need to know about the internals of your library: they can just use the compiled output, referencing the generated typings file.

typescript: relative modules were not found

I am converting code from javascript to typescript as a part of a migration process.
I need to import 'xyz.css' file in my another file like 'abc.ts'. both the files are in the same directory. I am importing the file as follows :
import '../../xyz.css';
and it gives me an error :
relative modules were not found:
'../../../xyz.css' in '../../dist/abc.js'
This error occurs while webpack compilation process.
Please give a suggestion to resolve same.
Thanks!
For non-node_modules based modules, you should use a path, usually a relative path. So if you're trying to reference xyz.css from the same directory as your typescript file, you would use
import './xyz.css';

Error: Can't resolve lodash-mixins in typescript angular2

I'm trying to add custom functionality to extend lodash (note lodash
is npm'ed in). But I keep getting a resolve error.
I've added a new file called lodash-mixins.js to my test project scripts folder e.g: project/frontend/src/web/Scripts/
var _ = require('lodash');
_.mixin({
mixinLoaded function () { console.log("lodash mixins are loaded");}
});
module.exports = _;
Overview Folder Structure (simplified)
project/frontend/src/web
...frontend.web.csproj
...angular-cli.json
project/frontend/src/web/Scripts/
...lodash-mixins.js
project/frontend/src/web/app/
...app.module.ts
I've manually added my "lodash-mixins.js" to the "angular-cli.json"
"apps": [
{
"scripts": [
"../node_modules/jquery/dist/jquery.min.js",
etc
"../node_modules/lodash/lodash.min.js",
"../Scripts/lodash-mixins.js", // <<<<< not picking up
"../Scripts/global-error-handler.js",
],
Test by changing existing reference in one of my test.service.ts
from:
"import * as _ from 'lodash';"
to:
"import * as _ from 'lodash-mixins';"
I've rebuilt my c# solution for good measure.
Run in CLI: $ng build --watch
ERROR in project/frontend/src/web/app/test/services/test.service.ts
Module not found: Error: Can't resolve 'lodash-mixins
Any ideas?
You're confusing two different things here.
1) The "scripts" config for Angular CLI tells WebPack to include those JavaScrip files in the output bundle. It doesn't add those as importable modules. They get loaded as if you added <script src="..."> tags to your HTML file (not exactly accurate, but gives the idea).
2) When you import using TypeScript it searches for the module. The reason it's giving the error is because the file isn't in one of the search paths. Had it actually found the file. It would have loaded it twice. Since you've already added it to the "scripts" config option.
JQuery, Lodash, etc.. etc.. can be loaded using modules or just plain global variables. When you add it to the "scripts" config, then I think this tells WebPack to load it in the global browser space.
When you use "import _ from 'lodash'" in TypeScript. You're actually resolving to the "#types/lodash" module which people often install so that TypeScript knows about the interface for lodash. When WebPack bundles everything for the browser it swaps out the #types for the real reference to the lodash instance.
So you have a couple of options.
1) Continue with what you've done. Add TypeScript definition file in the import path for your new module named "lodash-mixin". That module just defines the new interface with the new methods. The import will find that file and use it to compile the TypeScript, but the WebPack bundle will load your JS file in it's place.
2) Remove your "lodash-mixin" from the "scripts" config, then import using a relative path import _ from '../Scripts/lodash-mixins'. This is what I usually do. Note: You might have to add the file extension ".js"
3) Add your "Scripts" folder to your tsconfig.json as one of the type roots. This allows you to just use import _ from 'lodash-mixins'.
4) There is a way (and I forget exactly how), but you can tell TypeScript to alias lodash to your lodash-mixin so that all imports use that type instead. I don't recommend this approach.

Categories