Transpile typescript into plain Javascript - javascript

Something is a bit cloudy in my mind which is the following.
I have a module written in typescript which will be imported later in some html pages.
sdk.ts
export class PM {
header: any;
headerLink: string;
headerDiv: any;
/**
* #todo remove constructor.
*/
constructor(mode: string) {
if (mode == null || mode == undefined) {
this.buildGUI();
}
}
/**
* Build GUI.
* It builds the GUI by wrapping the body in a container, adding the header and sidebar.
*/
buildGUI(): void {
this.initAndCreateComponents();
this.insertScript(this.headerLink);
}
/**
* Insert script.
* It inserts the script's import tag in the head of the document.
* #param {string} scriptLink - script's link to be loaded.
*/
insertScript(scriptLink: string): void {
const script = document.createElement('script');
script.src = scriptLink;
document.body.appendChild(script);
};
/**
* Init and Create Components.
* It initialises the variables values and it creates the components.
*/
initAndCreateComponents(): void {
this.headerLink = '/header/pm-header.js';
this.header = document.createElement("pm-header");
this.headerDiv = document.createElement("div");
this.headerDiv.classList.add('pm-header-wrapper');
this.headerDiv.appendChild(this.header);
document.body.insertBefore(this.headerDiv, document.body.firstChild);
}
}
new PM(null);
and this is my tsconfig.json
{
"compileOnSave": false,
"include": [
"src",
"test"
],
"exclude": [
"dist",
"node_modules"
],
"compilerOptions": {
"sourceMap": false,
"outDir": "./dist",
"declaration": true,
"moduleResolution": "node",
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"target": "es5",
"typeRoots": [
"node_modules/#types"
],
"types": [
"#types/jasmine",
"#types/node"
],
"lib": [
"es2017",
"dom",
"es2015.generator",
"es2015.iterable",
"es2015.promise",
"es2015.symbol",
"es2015.symbol.wellknown",
"esnext.asynciterable"
]
}
}
now when I run tsc I get and sdk.js that looks like this:
define(["require", "exports"], function (require, exports) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var PM = /** #class */ (function () {
/**
* #todo remove constructor.
*/
function PM(mode) {
if (mode == null || mode == undefined) {
this.buildGUI();
}
}
/**
* Build GUI.
* It builds the GUI by wrapping the body in a container, adding the header and sidebar.
*/
PM.prototype.buildGUI = function () {
this.initAndCreateComponents();
this.insertScript(this.headerLink);
};
...
Now this generated file is supposed to be imported in several html pages, and when I did my research I found that it could only be loaded using require like this:
<script data-main="/sdk/sdk.js" src="/sdk/require.js"></script>
What I want is a way to load my script without the use of any library, to be loaded like any regular plain javascript file.

If you don't want to use a module system (although I highly recommend you look into using one) you should remove export from your class (and from any other symbol in your file) , this will make your module be treated as a simple script file.
You should also add "module": "none" to your tsconfig.json to let the compiler know you will not be using a module system. This should trigger errors anywhere your code depends on modules (either because you export something or you use an import)
Note Since you will not be using a module system any class/variable/function you declare in your script file will be in the global scope (as they would be for any js file). You may want to consider using namespaces to organize your code and get out of the global scope.

Related

Is there a way to find only unused JSDoc type definitions?

For example:
/**
* A number, or a string containing a number.
* #typedef {(number|string)} NumberLike
*
* Dummy type
* #typedef {(string|null)} StringLike
*/
/**
* Set the magic number.
* #param {NumberLike} x - The magic number.
*/
function setMagicNumber(x) {
}
As you can see NumberLike is used, but the StringLike type is not used and I'd like to find all such unused type definitions on our project. It's a Nodejs project with typescript installed.
Here's our tsconfig.json file:
{
"compilerOptions": {
"baseUrl": ".",
"jsx": "react",
"allowJs": true,
"checkJs": true,
"target": "ESNext",
"noEmit": true,
"moduleResolution": "node",
"isolatedModules": true,
"esModuleInterop": true,
"resolveJsonModule": true,
"strictNullChecks": true,
},
}
I'm not presently familiar with AST tools (which I think would be the domain of tools for finding this information). However, here's an example to illustrate an idea:
Let's say that the example JavaScript file in your post is saved at ./module.mjs. Here's another module, ./find-typedefs.mjs:
import {promises as fs} from 'fs';
function getTypedefNames (sourceText) {
const regex = /#typedef\s+{[^}]+}\s+[A-Za-z]+/g;
return [...new Set((sourceText.match(regex) ?? [])
.map(str => str.split(/\s+/).slice(-1)[0]))];
}
async function main () {
const [filePath] = process.argv.slice(2)
const text = await fs.readFile(filePath, {encoding: 'utf8'});
const names = getTypedefNames(text);
console.log(names.join('\n'));
}
main();
Running the file outputs this:
$ node find-typedefs.mjs module.mjs
NumberLike
StringLike
By using the function getTypedefNames as a basis for potentially more abstractions, you could automate searching the files in your project to produce a list of names that you can then search (and, for example, count) in each file. If you are using an editor like VS Code, you can also use regular expressions (like the one in that function) directly in the editor's Find menu.
Hopefully this will save you some manual work of eye-scanning.

Link runtime dependencies, for VSCode autocomplete

I can't get VSCode to link in-code function parameters to their dependencies (vars, functions, modules), and get autocomplete suggestions in code.
Is there a way, with JSDoc, to make VScode recognize what module corresponds to a certain parameter?
// const database = require('./data-access/mongodb-adapter.js')
/**
* Factory for our service
*
* QUESTION: Can JSDoc refer a module in another file?
* (and instruct VSCode to wire it)
*
* #param {module('./data-access/mongodb-adapter.js')} database The adapter to the app database
*/
function makeService(database) {
return {
find: query => database.find(query)
}
}
module.exports = makeService
In PHP, with PHPDoc, I would type-hint the variable $database, adding an annotation /* #var MongodbAdapter $database */ in the same scope.
VSCode makes it clickable (when holding cmd ⌘) and links me to the referenced module/file/function.
Is there a way to specify the same on factory/constructor dependencies? (without transpiling)
in some cases you can if you are you using js files you need to create a jsconfig.json at the root of your project
{
"compilerOptions": {
"target": "es5",
"lib": [
"dom",
"ES5",
"dom.iterable",
"esnext",
"ES2015",
"ES2016",
"ES2017",
"ES2018",
"ES2019",
"ES2020"
],
"allowJs": true,
"checkJs": true,
"module": "commonjs",
"moduleResolution": "node",
"resolveJsonModule": true,
"declaration": true,
"declarationDir": "./typings",
"pretty": true,
"sourceMap": true,
"type":[],
"jsx": "react",
"baseUrl": "./node_modules",
"paths": {
"*": [
"./#types/*/index.d.ts",
"./#types/*/types.d.ts",
"./*/index.d.ts",
"./*/types.d.ts",
"../types/*/index.d.ts"
]
},
"typeRoots": [
"./#types",
"../types",
"../typings"
]
},
"typeAcquisition": {
"enable": true
},
"include": [
"src",
"types"
],
"exclude": [
"node_modules"
]
}
a jsconfig.json is the same as a tsconfig.json so if there are some types from your dependencies that you would like to use you need to install them and add them into the "type" array.
the way of using it is with for example we want to use express first we add
$ npm i -D #types/express
at the "type":[] array from the jsconfig we add express
...
"type":["express"]
...
now on your code
/**
* #description
* your desc
* #type {import("express").RequestHandler} RequestHandler
* #param {import("express").Request} req request
* #param {import("express").Response} res response
* #author Ernesto Jara Olveda
* #copyright (C) 14
*/
export const renderLoginPage = (req, res) => {
res.setHeader("Content-Type", "text/html; charset=uft-8");
res.render("login.hbs", {
canonical: "http://localhost:3000",
theme: "theme-light",
lang: "es",
highlight: "highlight-red",
gradient: "body-default",
token: req.csrfToken(),
});
};
if by any means the code u want to reference is not a package but a file in your src folder you have a problem, it cannot be done that way. you need to create a *.d.ts for example
src
--controller
----user
------user.js
------user.d.ts
let`s imagine we want to reference some from. user.js then we need to create a user.d.ts file and add all them definitions of your user.js
you can use https://www.typescriptlang.org/play?#code to help u out but as you can see there is alot you need to. write down. I recommend you to instead of doing all that create you project in typescript instead
enter image description here

Reference a TS interface from jsDoc

Using VSCode is almost possible to have the benefits from Typescript in plain .js thanks to jsDoc notation and a tsconfig.json file:
{
"compileOnSave": false,
"compilerOptions": {
"noEmit": true,
"allowJs": true,
"checkJs": true,
"target": "es6",
"resolveJsonModule": true,
"moduleResolution": "node",
},
"include": ["index.js", "./src"]
}
/**
* #return {Promise<string>}
*/
const foo = Promise.resolve('hi');
module.exports = foo;
Now, is it possible to reference an interface defined in a d.ts at node_modules? in particular I'm returning a -let's call- "my_dependency.Storage" object but I'm unable to reference it using plain javascript:
/**
* #param {Storage} storage
*/
const goo = storage => ...
will understand that I'm refering to Web Storage API from lib.dom.d.ts
The equivalent typescript would be:
import {Storage} from "my_dependency"
I've tried using triple slash directives unsuccessfully
///<reference path="node_modules/my_dependency/lib/index.d.ts" />
I'm expecting something like (pseudo-code)
/**
* #param {my_module.Storage} storage
*/
As #Akxe mention this is how you can do this for referring a type from a lib:
/**
* #param {import("my_dependency").Storage} storage
*/
const goo = storage => ...
Now, I found myself repeating that statement over and over again so I created an ambient alias as following:
types.d.ts
declare namespace MyProject {
type St = import("my_dependency").Storage; // alias: reference my_dependency.Storage from plain js code
}
index.js
/// <reference path="types.d.ts"/>
...
src/foo.js
/**
* #param {MyProject.St} storage
*/
const goo = storage => ...
==EDIT==
Here is a post at dev.to that expands this topic

Javascript: Unexpected token { - occurs when try to import custom class in typescript file into javascript file

I am writing simple CLI scripts for react-native project. But I am not available to import class from typescript to javascript. It throw me following error.
import { TestResult } from './src/lib/modules/test-result';
SyntaxError: Unexpected token {...
package.json
...
"script": {
...
"formular:convert": "node convertor.js"
...
}
...
convertor.js
import {TestResult} from './src/lib/models/test-result';
...
test-result.ts
import uuid from 'uuid/v4';
import { Exception } from '../../errors';
import { OilTypes, RawMaterialTypes } from '../oils';
import { CalculatedResult } from './calculated-result';
import { DeviceSettings } from './device-settings';
import { DeviceStatus } from './device-status';
import { DisplayedResult } from './displayed-result';
import { Measurement } from './measurement';
import { PhoneInfo } from './phone-info';
import { PrimaryCompound } from './primary-compound';
export class TestResult {
constructor(
public id: string = uuid(),
public createdAt: Date = new Date(),
public updatedAt: Date = new Date(),
public errors: Exception[] = [],
public alias: string = '',
public analyteTemp: number = 75,
public flowerWeight: number = 0,
public hidden: boolean = false,
public note: string = '',
public oilType: OilTypes = OilTypes.OliveOil,
public primaryCompound: PrimaryCompound = PrimaryCompound.UNDEFINED,
public units: string = '',
public user: string = '',
public air310Meas: Measurement = new Measurement(),
public air280Meas: Measurement = new Measurement(),
public analyte310Meas: Measurement = new Measurement(),
public analyte280Meas: Measurement = new Measurement(),
public calculated: CalculatedResult = new CalculatedResult(),
public displayed: DisplayedResult = new DisplayedResult(),
public status: DeviceStatus = new DeviceStatus(),
public settings: DeviceSettings = new DeviceSettings(),
public phoneInfo: PhoneInfo = new PhoneInfo(),
public rawMaterialType: RawMaterialTypes = RawMaterialTypes.Isolate,
) {
}
}
tsconfig.json
{
"compilerOptions": {
/* Basic Options */
"target": "es5", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017','ES2018' or 'ESNEXT'. */
"module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */
// "lib": [], /* Specify library files to be included in the compilation. */
"allowJs": true, /* Allow javascript files to be compiled. */
// "checkJs": true, /* Report errors in .js files. */
"jsx": "react", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
/* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
"removeComments": true, /* Do not emit comments to output. */
"noEmit": true, /* Do not emit outputs. */
/* Strict Type-Checking Options */
"strict": true, /* Enable all strict type-checking options. */
/* Additional Checks */
"noUnusedLocals": true, /* Report errors on unused locals. */
"noUnusedParameters": true, /* Report errors on unused parameters. */
"noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
"noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
/* Module Resolution Options */
"moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
"baseUrl": "./", /* Base directory to resolve non-absolute module names. */
"types": [ /* Type declaration files to be included in compilation. */
"jest",
"mocha",
"detox"
],
"allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
"esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
/* Experimental Options */
"experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
"emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
"skipLibCheck": true, /* Skip type checking of declaration files. Requires TypeScript version 2.0 or later. */
"forceConsistentCasingInFileNames": true,/* Disallow inconsistently-cased references to the same file. */
"resolveJsonModule": true /* Include modules imported with '.json' extension. Requires TypeScript version 2.9 or later. */
},
"exclude": [
"node_modules",
"babel.config.js",
"metro.config.js",
"jest.config.js"
]
}
I stuggled to find the solution, and found some answers here and there, but all of them doesn't work.
Thanks for your help.
You are running node convertor.js and convertor.js contains an import statement: import {TestResult} from './src/lib/models/test-result'; This is not valid syntax in the version of node you are running, hence the error.
Fix
You need to transpile the non-transpiled .js files with something like TypeScript (allowJs) or Babel.
I ran into this issue as well while trying to use the react-native-device-info library with react-native 0.59. It turned out I was missing the #babel/core library in my package.json. After adding this module, the error went away and I was able to build the application.
Install using npm
npm i -s #babel/core
Install using yarn
yarn add #babel/core

Build for both browser and nodejs

I am trying to make a library that works both in the browser as well as in node.
I have three json config files where the latter two extend tsconfig.json
tsconfig.json (just contains files for the build)
tsconfig.browser.json
tsconfig.node.json
tsconfig.browser.json
{
"extends": "./tsconfig.json",
"compilerOptions": {
"target": "es6",
"module": "system",
"outFile": "../dist/browser/libjs.js",
"removeComments": true,
"declaration": true
}
}
tsconfig.node.json
{
"extends": "./tsconfig.json",
"compilerOptions": {
"module": "commonjs",
"moduleResolution": "node",
"outDir": "../dist/node",
"removeComments": true,
"declaration": true,
"declarationDir": "../dist/node/typings"
},
"files": [
"./index"
]
}
I have this index.ts file (only included on the node build):
export { collect } from './components/collections'
export { query } from './components/query'
Then I have this in the collections.ts file:
export namespace libjs {
export function collect<T>(data: T[]) {
// Do some stuff
}
export class collection(){}
}
And this query.ts file:
export namespace libjs {
export class query<T> {
private _results: collection<T>
}
}
The issue I am having, is when I try to build to node, the index file cannot find the collect function, and when I build to the browser the query class cannot find the collection class. What is the best way to code this so I can build to both node and the browser? If I remove the export on the namespace I can build to the browser fine, but I cannot build to node.
The way I would like to use these are as follows:
Nodejs
const libjs = require('libjs')
let c = libjs.collect([1, 123, 123, 1231, 32, 4])
Browser
<script src="/js/libjs.js"></script>
<script>
let c = libjs.collect([1, 123, 123, 1231, 32, 4])
</script>
When you compile files that have export ... at the top level, each file is treated as a module with its own scope, and namespace libjs in each file is distinct and separate from libjs in every other file.
If you want to generate a single script that can be used in a browser without module loader (defining libjs as global), you have to remove all toplevel exports, and don't set module at all in tsconfig:
components/collections.ts
namespace libjs {
export function collect<T>(data: T[]) {
// Do some stuff
}
export class collection<T>{}
}
components/query.ts
namespace libjs {
export class query<T> {
private _results: collection<T>
}
}
Now, you can use the same generated script in node too, if you add code that detects node environment at runtime and assigns libjs to module.exports:
index.ts
namespace libjs {
declare var module: any;
if (typeof module !== "undefined" && module.exports) {
module.exports = libjs;
}
}
single tsconfig.json for browser and node (note that I changed output to dist from ../dist)
{
"compilerOptions": {
"outFile": "./dist/libjs.js",
"removeComments": true,
"declaration": true
},
"files": [
"components/collections.ts",
"components/query.ts",
"index.ts"
]
}
You can use generated script right away in node in javascript:
test-js.js
const lib = require('./dist/libjs')
console.log(typeof lib.collect);
let c = lib.collect([1, 123, 123, 1231, 32, 4])
Unfortunately, you can't use it in node in typescript with generated libjs.d.ts because it declares libjs as global. For node, you need separate libjs.d.ts that contains one additional export = libjs statement that you have to add manually or as part of build process:
complete dist/libjs.d.ts for node
declare namespace libjs {
function collect<T>(data: T[]): void;
class collection<T> {
}
}
declare namespace libjs {
class query<T> {
private _results;
}
}
declare namespace libjs {
}
// this line needs to be added manually after compilation
export = libjs;
test-ts.ts
import libjs = require('./dist/libjs');
console.log(typeof libjs.collect);
let c = libjs.collect([1, 123, 123, 1231, 32, 4])
tsconfig.test.json
{
"compilerOptions": {
"module": "commonjs",
"moduleResolution": "node",
"removeComments": true
},
"files": [
"./test-ts.ts",
"./dist/libjs.d.ts"
]
}
You can choose to go a completely different route and build a library composed of modules, but for that you have to use module loader in the browser (or build with webpack), and you probably need to read this answer explaining why namespace libjs is totally unnecessary with modules.

Categories