I would like to import an external module from node_modules in node using typescript using the import syntax. I've added the type definitions to my project so I can call install() without typescript returning errors (I know I can do an any cast require but I would obviously like to keep everything typed if possible).
/// <reference path="typings/source-map-support/source-map-support.d.ts" />
import * as sourceMapSupport from 'source-map-support';
sourceMapSupport.install();
However when my code is output it returns the following:
/// <reference path="../typings/source-map-support/source-map-support.d.ts" />
//# sourceMappingURL=server.js.map
Could someone explain to me why it doesn't output a require in the resulting js file? Also I'm compiling to ES6 as I need a access to the async & await keywords - my tsconfig.json is as follows.
{
"compilerOptions": {
"target": "es6",
"sourceMap": true,
"outFile": "build/server.js"
},
"exclude": [
"node_modules",
"typings"
]
}
Could someone explain to me why it doesn't output a require in the resulting js file
Because you are using outFile in tsconfig "outFile": "build/server.js". Don't use outFile if you are using a module format. That is a job a bundler e.g. webpack.
{
"compilerOptions": {
"target": "es6",
"sourceMap": true,
"module": "commonjs"
},
"exclude": [
"node_modules",
"typings"
]
}
PS
Note : There are edge cases here e.g. systemjs / amd support outFile. But that's not most likely not relevant here.
Related
I'm coding up a TS library that exports an object like this:
export const MyLibName: SDKExport = { ... }
This library will be distributed on npm and I want others to be able to import it regardless of their setup, i.e. it should be able to be used in the following ways:
// ES6
import { MyLibName } from './myLib';
// CommonJS
const { MyLibName } = require('./myLib');
// Browser
<script src="https://cdn.jsdelivr.net/npm/myLib/dist/dist.min.js"></script>
<script console.log(MyLibName) </script>
The browser part is proving to be the most tricky since it requires "polluting" the global namespace with the MyLibName object. I can do that, but it's unclear to me how to do it in a way that doesn't pollute the global namespace when importing the library explicitly using CommonJS/ES6.
Here's my tsconfig.json:
{
"compilerOptions": {
"lib": ["ES2019"],
"module": "es6",
"target": "es5",
"moduleResolution": "node",
"noUnusedLocals": true,
"noUnusedParameters": true,
"sourceMap": true,
"outDir": "build",
"strict": true,
"noImplicitAny": true,
},
"include": ["./**/*.ts"],
"exclude": ["node_modules", ".vscode", "test"]
}
It's clear that the script you're importing in the browser must be a different thing than your CommonJS/ES6 import if you want one to pollute the global namespace but not the other. Just create a file like this one and use your bundler on it to create a "browser build" of your library:
import { MyLibName } from './myLib';
window.MyLibName = MyLibName;
(You seem to have a bundler/minifier in your project already so I won't go into detail on how to set up those.)
I tried using the approach suggested by Vojtěch Strnad with browserify, but encountered issues that were hard to debug due to all the extra code that browserify adds to the exported files. I'm accepting that answer as it's the correct one for complex codebases.
For my own project, which only includes a single file, I found that it worked well to export the TS code using the "module": "ES6" TS compiler options and run sed -i 's/\export //g' dist/index.js to remove the export keyword. That way, the exported file simply has const MyLibName = { ... } and thereby adds the object to the global namespace.
I have this very simple file structure consisting of
src
index.ts
services
my-service.ts
In index.ts I have the following
export const a = 3
and in my-service.ts I do,
import { a } from '../index'
console.log(a);
this logs undefined.
When I do
setTimeout(() => console.log(a), 100);
it logs 3.
I am running the solution using
ts-node -r tsconfig-paths/register ./src/index.ts
my tsconfig looks like
{
"extends": "../../tsconfig.json",
// "include": ["src/**/*.ts"],
"compilerOptions": {
"outDir": "dist",
"allowJs": false,
"baseUrl": ".",
"rootDir": "src",
"composite": true
},
"references": [
{
"path": "../shared-tools"
}
],
"exclude": ["dist"],
"include": ["src/**/*.ts"]
}
Which extends the following root tsconfig
{
"compilerOptions": {
"module": "commonjs",
"target": "es6",
// "baseUrl": ".",
"allowJs": false,
"sourceMap": true,
"declaration": true,
"declarationMap": true,
"allowSyntheticDefaultImports": true,
"esModuleInterop": true,
"skipLibCheck": true,
"resolveJsonModule": true,
"types": [],
"typeRoots": [
"./node_modules/#types"
],
"paths": {
"#project/shared-tools": [
"../shared-tools/src"
]
}
}
}
That symptom suggests you have a cycle of dependencies between index.ts and my-services.ts — in addition to the dependency you've described on a in index.ts, index.ts relies on something supplied (directly or indirectly) by my-services.ts. This is called a cyclic dependency. A depends on B which depends on A.
With JavaScript's now-standard ESM static module syntax, cycles are possible but normally they can be resolved without your having to worry about them. In cases where they can't, instead of getting undefined, you get an error (if you use const as you have).
But you're telling TypeScript to rewrite your module syntax into Node's original CommonJS-inspired syntax (module.exports and require() calls). With CJS, cycles can result in your seeing undefined for things that later have a value assigned when all the module code has been run.
Ideally, the solution is to remove the cyclic dependency. If index.ts has to depend on my-services.ts, perhaps move whatever my-services.ts imports from index.ts to something else they both import from. That way, A and B both depend on C rather than on each other.
If for some reason you can't do that, you might try telling TypeScript to output JavaScript module syntax instead ("module": "ES2015" or "module": "ESNext"). Since that does static resolution (if you don't use dynamic import()), it may be able to handle the cycles for you. Node now has robust support for ESM.
Or you may not need to handle it. Even in CJS, cyclic dependencies are really only a problem if you use the dependencies in the initial module code rather than in code that runs later in response to an event (as you saw by wrapping your code using a in a setTimeout). So if the resources involved in the cycles are only needed to (for instance) respond to a network request, you may not need to worry about it at all. There's a bit more about cycles in CJS modules in Node here.
I have written a project on Typescript and setup to bundle it with webpack.
Main functionality is wrapped in one Main class, but it imports also other classes.
Need to export Main class and all dependent in to one bundle.js library to use in in other sites.
I've tried to bundle classes in separate project (without webpack just tsc).
this is tsconfig.json:
{
"compilerOptions": {
"module": "amd",
"target": "ES2015",
"declaration": true,
"outDir": "./dist",
"outFile": "./dist/index.js",
},
"include": [
"src/**/*"
]
}
but it is not usable since the error:
ReferenceError: define is not defined
compilled code contains edfine
define("Globals", ["require", "exports"], function (require, exports) {
and also with webpack but not sure have correct setup
the bundle file start with
(function(modules) { // webpackBootstrap
/******/ // The module cache
/******/ var installedModules = {};
in other words I have some classes:
Main.ts
A.ts
B.ts
C.ts
and I expected to get bundle.js file and use like this:
<script type="text/javascript" src="mylib/dist/index.js"></script>
//...
var a = new Main();
but stuck with understanding how it should Typescript and Jsvascript work together.
Will appreciate any help
Update:
Managed to solve with webpack. Main is asccessible from entry app.ts when compiled. So inside app.ts added window.onload handler and assigned class to window scope.
window.onload = function () {
window['MyClass'] = Main;
May be missing something but couldn't find good documentation about writing library on TypeScript for browsers.
Update2:
Ok. found answer. webpack option library works fine.
output: {
filename: "bundle.js",
path: __dirname + "/dist",
library: "MyMainClass"
},
also had to change tsconfig.json target option from es6 to es5
{
"compilerOptions": {
"baseUrl": ".",
"paths": { "*": ["types/*"] },
"lib": [
"dom",
"es6",
"dom.iterable",
"scripthost"
],
"module": "commonjs",
"target": "es5"
},
"exclude": [
"node_modules"
]
}
I want to switch from JavaScript to TypeScript for our web app's scripts. However, when generating the JavaScript it always puts the following lines on top of the script:
"use strict";
exports.__esModule = true;
var $ = require("jquery");
I receive browser errors for this. How to prevent TypeScript from doing so?
I read TypeScript: Avoid require statements in compiled JavaScript but it can't be the answer to switch to "any", this forfeits the whole TypeScript idea.
I also read Typescript importing exported class emits require(...) which produces browser errors but this still generates the <reference... stuff into the JS file, which is also not what I want.
How to create "clean" JS files?
My tsconfig.json looks like this:
{
"compilerOptions": {
"noImplicitAny": true,
"noEmitOnError": true,
"sourceMap": true,
"target": "es5"
},
"compileOnSave": true
}
My gulp call is:
var ts = require('gulp-typescript');
...
var tsProject = ts.createProject("tsconfig.json");
...
gulp.task("ts", function () {
return gulp.src(tsInputFiles)
.pipe(tsProject())
.js
.pipe(gulp.dest("wwwroot/js"));
});
Just drop the module loading in the beginning of your typescript file, if you do not want to load any modules?
file1.ts:
$(function () {
});
If you try to compile this, you'll get an error:
file1.ts(2,1): error TS2581: Cannot find name '$'. Do you need to
install type definitions for jQuery? Try `npm i #types/jquery` and
then add `jquery` to the types field in your tsconfig.
Run npm as told above (npm init first, if npm has not been initialized in the directory).
Then add typeRoots and types to tsconfig:
{
"compilerOptions": {
"noImplicitAny": true,
"noEmitOnError": true,
"sourceMap": true,
"target": "es5",
"typeRoots": [ "node_modules/types" ],
"types": [ "jquery" ]
},
"compileOnSave": true
}
After that, the compiling works, and you still have the strong typing in place (jquery types applied in compilation).
Browsers don't support JavaScript modules, so you'll need to tell the TypeScript compiler that you're trying to compile code that will be run on a browser. I don't know what the rest of your project looks like, but in your configuration, try adding "module": "es2015" or "module": "system" to your configuration, while also adding an outFile defining where you want to file to be generated :
in your tsconfig file :
{
"compilerOptions": {
"noImplicitAny": true,
"noEmitOnError": true,
"sourceMap": true,
"target": "es5",
"module": "es2015",
"outFile": "script.js",
},
"compileOnSave": true
}
I wanted to use TypeScript files injected directly into a HTML file via gulp task (compiled into es5).
At first adding type="module" to <script> helped. But then more problems appeared, so finally I've end up using this notation instead of import:
/// <reference types="#types/jquery" />
/// <reference path="my_custom_file.ts" />
/// <reference path="my_externalTypes.d.ts" />
and namespace in each of the file to separate them from each other.
That should work for a simple, small projects.
I started to study Angular, but i can't get trough the problem with convert typescript file to javascript. I've created AngularCLI project and simple typescript file:
class Example {
msg = 'Some message';
}
and i tried to convert it to js with tsc command: tsc example.tsc. This is javascript file which was generated:
var Example = /** #class */ (function () {
function Example() {
this.msg = 'Some message';
}
return Example;
}());
And i've got some errors
node_modules/#types/selenium-webdriver/remote.d.ts:139:29 - error TS2583: Cannot find name 'Map'. Do you need to change your target library? Try changing the `lib` compiler option to es2015 or later.
I think (or even sure) the problem is in javascript version (even error says that). This is tsconfig.json file:
{
"compileOnSave": false,
"compilerOptions": {
"baseUrl": "./",
"outDir": "./dist/out-tsc",
"sourceMap": true,
"declaration": false,
"module": "es2015",
"moduleResolution": "node",
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"importHelpers": true,
"target": "es5",
"typeRoots": [
"node_modules/#types"
],
"lib": [
"es2018",
"dom"
]
}
}
So i did what was suggested. I changed module, target, lib to atleast es2015 (
everything together and each separately) - it didnt help. I tried a lot of solutions from SO, jetbrain forum etc but nothing worked for me.
I use webstorm 2018.3 version. Node version is 10.14.1. Tsc version is 3.1.6.
Your updated tsconfig.json looks good now that it targets es2018. The problem now is how you are compiling. When I asked how you compile you wrote:
... with command line in webstorm using: "tsc filename.tsc" comand.
When we pass files to the compiler, the compiler ignores our tsconfig.json. The official tsconfig.json documentation says this:
When input files are specified on the command line, tsconfig.json files are ignored.
If you want to pass specific files on the command line and to compile with specific options, pass those options (e.g. the lib option) on the command line.
tsc filename.ts --lib es2018