Thursday, 11 October 2018

Introduction to TSLint

TSLint is a TypeScript linter.

Source code of the project which uses TSLint is on GitHub.

Installation

Let's install it as a development dependency:

..\demo-typescript>npm install tslint --save-dev
+ tslint@5.11.0
added 40 packages from 20 contributors and audited 51 packages in 5.225s
found 0 vulnerabilities

CLI Usage

Let's run tslint with no arguments specified, from the project's root directory:

..\demo-typescript>npx tslint
No files specified. Use --project to lint a project folder.

Let's find what is --project argument and see what are the other command line arguments:

..\demo-typescript>npx tslint --help
Usage:  [options]

Options:
  -v, --version                          output the version number
  -c, --config [config]                  configuration file
  -e, --exclude <exclude>                exclude globs from path expansion (default: [])
  --fix                                  fixes linting errors for select rules (this may overwrite linted files)
  --force                                return status code 0 even if there are lint errors
  -i, --init                             generate a tslint.json config file in the current working directory
  -o, --out [out]                        output file
  --outputAbsolutePaths                  whether or not outputted file paths are absolute
  -r, --rules-dir [rules-dir]            rules directory
  -s, --formatters-dir [formatters-dir]  formatters directory
  -t, --format [format]                  output format (prose, json, stylish, verbose, pmd, msbuild, checkstyle, vso, fileslist, codeFrame)
  --test                                 test that tslint produces the correct output for the specified directory
  -p, --project [project]                tsconfig.json file
  --type-check                           (deprecated) check for type errors before linting the project
  -h, --help                             output usage information
tslint accepts the following commandline options:

    -c, --config:
        The location of the configuration file that tslint will use to
        determine which rules are activated and what options to provide
        to the rules. If no option is specified, the config file named
        tslint.json is used, so long as it exists in the path.
        The format of the file is { rules: { /* rules list */ } },
        where /* rules list */ is a key: value comma-separated list of
        rulename: rule-options pairs. Rule-options can be either a
        boolean true/false value denoting whether the rule is used or not,
        or a list [boolean, ...] where the boolean provides the same role
        as in the non-list case, and the rest of the list are options passed
        to the rule that will determine what it checks for (such as number
        of characters for the max-line-length rule, or what functions to ban
        for the ban rule).

    -e, --exclude:
        A filename or glob which indicates files to exclude from linting.
        This option can be supplied multiple times if you need multiple
        globs to indicate which files to exclude.

    --fix:
        Fixes linting errors for select rules. This may overwrite linted files.

    --force:
        Return status code 0 even if there are any lint errors.
        Useful while running as npm script.

    -i, --init:
        Generates a tslint.json config file in the current working directory.

    -o, --out:
        A filename to output the results to. By default, tslint outputs to
        stdout, which is usually the console where you're running it from.

    --outputAbsolutePaths:
        If true, all paths in the output will be absolute.

    -r, --rules-dir:
        An additional rules directory, for user-created rules.
        tslint will always check its default rules directory, in
        node_modules/tslint/lib/rules, before checking the user-provided
        rules directory, so rules in the user-provided rules directory
        with the same name as the base rules will not be loaded.

    -s, --formatters-dir:
        An additional formatters directory, for user-created formatters.
        Formatters are files that will format the tslint output, before
        writing it to stdout or the file passed in --out. The default
        directory, node_modules/tslint/build/formatters, will always be
        checked first, so user-created formatters with the same names
        as the base formatters will not be loaded.

    -t, --format:
        The formatter to use to format the results of the linter before
        outputting it to stdout or the file passed in --out. The core
        formatters are prose (human readable), json (machine readable)
        and verbose. prose is the default if this option is not used.
        Other built-in options include pmd, msbuild, checkstyle, and vso.
        Additional formatters can be added and used if the --formatters-dir
        option is set.

    --test:
        Runs tslint on matched directories and checks if tslint outputs
        match the expected output in .lint files. Automatically loads the
        tslint.json files in the directories as the configuration file for
        the tests. See the full tslint documentation for more details on how
        this can be used to test custom rules.

    -p, --project:
        The path to the tsconfig.json file or to the directory containing
        the tsconfig.json file. The file will be used to determine which
        files will be linted. This flag also enables rules that require the
        type checker.

    --type-check:
        (deprecated) Checks for type errors before linting a project.
        --project must be specified in order to enable type checking.

    -v, --version:
        The current version of tslint.

    -h, --help:
        Prints this help message.

Let's specify path to tsconfig.json file (which is in the project's root where we're executing tsling from):

..\demo-typescript>npx tslint --project ./
no-unused-variable is deprecated. Since TypeScript 2.9. Please use the built-in compiler checks instead.

Could not find implementations for the following rules specified in the configuration:
    label-undefined
    no-duplicate-key
    no-trailing-comma
    no-unreachable
    use-strict
Try upgrading TSLint and/or ensuring that you have all necessary custom rules installed.
If TSLint was recently upgraded, you may have old rules configured which need to be cleaned up.


ERROR: C:/dev/github/demo-typescript/src/index.ts[3, 5]: expected variable-declaration: 'dayTimeGreetingGenerator' to have a typedef
ERROR: C:/dev/github/demo-typescript/src/index.ts[5, 10]: expected call-signature: 'createPersonalGreeting' to have a typedef
ERROR: C:/dev/github/demo-typescript/src/index.ts[9, 5]: expected variable-declaration: 'user' to have a typedef
ERROR: C:/dev/github/demo-typescript/src/index.ts[12, 5]: expected variable-declaration: 'greeting' to have a typedef
ERROR: C:/dev/github/demo-typescript/src/module/dayTimeGreeting.ts[2, 19]: ' should be "
ERROR: C:/dev/github/demo-typescript/src/module/dayTimeGreeting.ts[3, 21]: ' should be "
ERROR: C:/dev/github/demo-typescript/src/module/dayTimeGreeting.ts[4, 19]: ' should be "
ERROR: C:/dev/github/demo-typescript/src/module/dayTimeGreetingGenerator.ts[1, 57]: trailing whitespace
ERROR: C:/dev/github/demo-typescript/src/module/dayTimeGreetingGenerator.ts[5, 25]: expected nospace before colon in call-signature
ERROR: C:/dev/github/demo-typescript/src/module/dayTimeGreetingGenerator.ts[7, 33]: ' should be "
ERROR: C:/dev/github/demo-typescript/src/module/greetingGenerator.ts[1, 18]: interface name must start with a capitalized I
ERROR: C:/dev/github/demo-typescript/src/module/greetingGenerator.ts[2, 18]: expected nospace before colon in call-signature

Good, linting works!

Configuration


If we run:

..\demo-typescript>npx tslint --init

...tslint creates TSLint configuration file - tslint.json in the current directory.  tslint.json for now only has its default content:

{
    "defaultSeverity": "error",
    "extends": [
        "tslint:recommended"
    ],
    "jsRules": {},
    "rules": {},
    "rulesDirectory": []
}

After having TSLint config file, its output contains only linting messages:

..\demo-typescript>npx tslint --project ./

ERROR: C:/dev/github/demo-typescript/src/index.ts[3, 5]: Identifier 'dayTimeGreetingGenerator' is never reassigned; use 'const' instead of 'let'.
ERROR: C:/dev/github/demo-typescript/src/index.ts[5, 39]: expected nospace before colon in parameter
ERROR: C:/dev/github/demo-typescript/src/index.ts[9, 5]: Identifier 'user' is never reassigned; use 'const' instead of 'let'.
ERROR: C:/dev/github/demo-typescript/src/index.ts[12, 5]: Identifier 'greeting' is never reassigned; use 'const' instead of 'let'.
ERROR: C:/dev/github/demo-typescript/src/index.ts[14, 1]: Calls to 'console.log' are not allowed.
ERROR: C:/dev/github/demo-typescript/src/index.ts[14, 23]: file should end with a newline
ERROR: C:/dev/github/demo-typescript/src/module/dayTimeGreeting.ts[2, 19]: ' should be "
ERROR: C:/dev/github/demo-typescript/src/module/dayTimeGreeting.ts[3, 21]: ' should be "
ERROR: C:/dev/github/demo-typescript/src/module/dayTimeGreeting.ts[4, 19]: ' should be "
ERROR: C:/dev/github/demo-typescript/src/module/dayTimeGreeting.ts[4, 33]: Missing trailing comma
ERROR: C:/dev/github/demo-typescript/src/module/dayTimeGreeting.ts[5, 2]: file should end with a newline
ERROR: C:/dev/github/demo-typescript/src/module/dayTimeGreetingGenerator.ts[1, 57]: trailing whitespace
ERROR: C:/dev/github/demo-typescript/src/module/dayTimeGreetingGenerator.ts[2, 1]: Import sources within a group must be alphabetized.
ERROR: C:/dev/github/demo-typescript/src/module/dayTimeGreetingGenerator.ts[5, 25]: expected nospace before colon in call-signature
ERROR: C:/dev/github/demo-typescript/src/module/dayTimeGreetingGenerator.ts[6, 20]: expected nospace before colon in variable-declaration
ERROR: C:/dev/github/demo-typescript/src/module/dayTimeGreetingGenerator.ts[7, 21]: expected nospace before colon in variable-declaration
ERROR: C:/dev/github/demo-typescript/src/module/dayTimeGreetingGenerator.ts[7, 33]: ' should be "
ERROR: C:/dev/github/demo-typescript/src/module/dayTimeGreetingGenerator.ts[8, 21]: missing whitespace
ERROR: C:/dev/github/demo-typescript/src/module/dayTimeGreetingGenerator.ts[17, 3]: file should end with a newline
ERROR: C:/dev/github/demo-typescript/src/module/greetingGenerator.ts[1, 18]: interface name must start with a capitalized I
ERROR: C:/dev/github/demo-typescript/src/module/greetingGenerator.ts[2, 18]: expected nospace before colon in call-signature
ERROR: C:/dev/github/demo-typescript/src/module/greetingGenerator.ts[3, 2]: file should end with a newline


We can now add linting to TS build task in package.json. TSLint documentation says "Please ensure that the TypeScript source files compile correctly before running the linter." and we'll place linting after transpilation:

"build-ts": "tsc && tslint -p ./",

If we run npm task we'll see that TSLint process exited with error code:

..\demo-typescript>npm run build-ts

> demo-typescript@1.0.0 build-ts C:\dev\github\demo-typescript
> tsc && tslint -p ./


ERROR: C:/dev/github/demo-typescript/src/index.ts[3, 5]: Identifier 'dayTimeGreetingGenerator' is never reassigned; use 'const' instead of 'let'.
ERROR: C:/dev/github/demo-typescript/src/index.ts[5, 39]: expected nospace before colon in parameter
ERROR: C:/dev/github/demo-typescript/src/index.ts[9, 5]: Identifier 'user' is never reassigned; use 'const' instead of 'let'.
ERROR: C:/dev/github/demo-typescript/src/index.ts[12, 5]: Identifier 'greeting' is never reassigned; use 'const' instead of 'let'.
ERROR: C:/dev/github/demo-typescript/src/index.ts[14, 1]: Calls to 'console.log' are not allowed.
ERROR: C:/dev/github/demo-typescript/src/index.ts[14, 23]: file should end with a newline
ERROR: C:/dev/github/demo-typescript/src/module/dayTimeGreeting.ts[2, 19]: ' should be "
ERROR: C:/dev/github/demo-typescript/src/module/dayTimeGreeting.ts[3, 21]: ' should be "
ERROR: C:/dev/github/demo-typescript/src/module/dayTimeGreeting.ts[4, 19]: ' should be "
ERROR: C:/dev/github/demo-typescript/src/module/dayTimeGreeting.ts[4, 33]: Missing trailing comma
ERROR: C:/dev/github/demo-typescript/src/module/dayTimeGreeting.ts[5, 2]: file should end with a newline
ERROR: C:/dev/github/demo-typescript/src/module/dayTimeGreetingGenerator.ts[1, 57]: trailing whitespace
ERROR: C:/dev/github/demo-typescript/src/module/dayTimeGreetingGenerator.ts[2, 1]: Import sources within a group must be alphabetized.
ERROR: C:/dev/github/demo-typescript/src/module/dayTimeGreetingGenerator.ts[5, 25]: expected nospace before colon in call-signature
ERROR: C:/dev/github/demo-typescript/src/module/dayTimeGreetingGenerator.ts[6, 20]: expected nospace before colon in variable-declaration
ERROR: C:/dev/github/demo-typescript/src/module/dayTimeGreetingGenerator.ts[7, 21]: expected nospace before colon in variable-declaration
ERROR: C:/dev/github/demo-typescript/src/module/dayTimeGreetingGenerator.ts[7, 33]: ' should be "
ERROR: C:/dev/github/demo-typescript/src/module/dayTimeGreetingGenerator.ts[8, 21]: missing whitespace
ERROR: C:/dev/github/demo-typescript/src/module/dayTimeGreetingGenerator.ts[17, 3]: file should end with a newline
ERROR: C:/dev/github/demo-typescript/src/module/greetingGenerator.ts[1, 18]: interface name must start with a capitalized I
ERROR: C:/dev/github/demo-typescript/src/module/greetingGenerator.ts[2, 18]: expected nospace before colon in call-signature
ERROR: C:/dev/github/demo-typescript/src/module/greetingGenerator.ts[3, 2]: file should end with a newline

npm ERR! code ELIFECYCLE
npm ERR! errno 2
npm ERR! demo-typescript@1.0.0 build-ts: `tsc && tslint -p ./`
npm ERR! Exit status 2
npm ERR!
npm ERR! Failed at the demo-typescript@1.0.0 build-ts script.
npm ERR! This is probably not a problem with npm. There is likely additional logging output above.

npm ERR! A complete log of this run can be found in:
npm ERR!     C:\Users\komazec\AppData\Roaming\npm-cache\_logs\2018-10-11T16_35_49_031Z-debug.log

Having TSLint exit with Exit status 2 is expected as TSLint reported suggestions with ERROR severity and its documentation says that status 2 indeed means "Linting failed with one or more rule violations with severity error".

We can see that linter complaints about using single quotation marks instead of double ones (' should be "). TS coding conventions actually prefers single quotes so we want to change the rule for TSLinter. The rule we want to change in tslint.json is quotemark:

    ...
    "rules": {
        "quotemark": [true, "single"]
    },
    ...

If we run tslint again, we'll see that it does not emit ' should be " messages anymore.

VSCode Extension

TSLint

It will start showing errors/suggestions as soon as plugin is installed and VSCode reloaded.

References:


https://palantir.github.io/tslint/
tslint says calls to console.log are not allowed - How do I allow this?

Introduction to TypeScript

This article describes how to create a Node.js application written in TypeScript. Full code can be found in this GitHub repository.

Let's first initialize our Node project:

>npm init -y


Installation


TypeScript has a command-line compiler which transforms TypeScript code into JavaScript. It comes in a form of a Node package named typescript. Let's install it as a development dependency:

>npm install typescript --save-dev
npm notice created a lockfile as package-lock.json. You should commit this file.
+ typescript@3.1.1
added 1 package from 1 contributor and audited 1 package in 1.318s
found 0 vulnerabilities

package.json now contains:

{
    ...
    "devDependencies": {
        "typescript": "^3.1.1"
    }
}

TypeScript Compiler


Let's see what are the command line arguments and options of tsc:

>npx tsc
Version 3.1.1
Syntax:   tsc [options] [file...]

Examples: tsc hello.ts
          tsc --outFile file.js file.ts
          tsc @args.txt
          tsc --build tsconfig.json

Options:
 -h, --help                                         Print this message.
 -w, --watch                                        Watch input files.
 --all                                              Show all compiler options.
 -v, --version                                      Print the compiler's version.
 --init                                             Initializes a TypeScript project and creates a tsconfig.json file.
 -p FILE OR DIRECTORY, --project FILE OR DIRECTORY  Compile the project given the path to its configuration file, or to a folder with a 'tsconfig.json'.
 -b, --build                                        Build one or more projects and their dependencies, if out of date
 --pretty                                           Stylize errors and messages using color and context (experimental).
 -t VERSION, --target VERSION                       Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017','ES2018' or 'ESNEXT'.
 -m KIND, --module KIND                             Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'.
 --lib                                              Specify library files to be included in the compilation.
                                                      'es5' 'es6' 'es2015' 'es7' 'es2016' 'es2017' 'es2018' 'esnext' 'dom' 'dom.iterable' 'webworker' 'webworker.importscripts' 'scripthost' 'es2015.core' 'es2015.collection' 'es2015.generator' 'es2015.iterable' 'es2015.promise' 'es2015.proxy' 'es2015.reflect' 'es2015.symbol' 'es2015.symbol.wellknown' 'es2016.array.include' 'es2017.object' 'es2017.sharedmemory' 'es2017.string' 'es2017.intl' 'es2017.typedarrays' 'es2018.intl' 'es2018.promise' 'es2018.regexp' 'esnext.array' 'esnext.symbol' 'esnext.asynciterable' 'esnext.intl'
 --allowJs                                          Allow javascript files to be compiled.
 --jsx KIND                                         Specify JSX code generation: 'preserve', 'react-native', or 'react'.
 -d, --declaration                                  Generates corresponding '.d.ts' file.
 --declarationMap                                   Generates a sourcemap for each corresponding '.d.ts' file.
 --sourceMap                                        Generates corresponding '.map' file.
 --outFile FILE                                     Concatenate and emit output to single file.
 --outDir DIRECTORY                                 Redirect output structure to the directory.
 --removeComments                                   Do not emit comments to output.
 --noEmit                                           Do not emit outputs.
 --strict                                           Enable all strict type-checking options.
 --noImplicitAny                                    Raise error on expressions and declarations with an implied 'any' type.
 --strictNullChecks                                 Enable strict null checks.
 --strictFunctionTypes                              Enable strict checking of function types.
 --strictPropertyInitialization                     Enable strict checking of property initialization in classes.
 --noImplicitThis                                   Raise error on 'this' expressions with an implied 'any' type.
 --alwaysStrict                                     Parse in strict mode and emit "use strict" for each source file.
 --noUnusedLocals                                   Report errors on unused locals.
 --noUnusedParameters                               Report errors on unused parameters.
 --noImplicitReturns                                Report error when not all code paths in function return a value.
 --noFallthroughCasesInSwitch                       Report errors for fallthrough cases in switch statement.
 --types                                            Type declaration files to be included in compilation.
 --esModuleInterop                                  Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'.
 @<file>                                            Insert command line options and files from a file.

Compiling


Let's add to the project some TypeScript file.

..\demo-typescript\src\index.ts:

function greeter(person : string) {
    return "Hello, " + person;
}

let user = "Bojan";
let greeting = greeter(user);
console.log(greeting);

Let's follow an example from tsc --help in order to get TS code compiled into JavaScript:

..\demo-typescript>npx tsc --outFile build/index.js src/index.ts

After this command is executed build/index.js file is created and it looks like this:

function greeter(person) {
    return "Hello, " + person;
}
var user = "Bojan";
var greeting = greeter(user);
console.log(greeting);

We can run it with Node.js:

\demo-typescript>node build/index.js
Hello, Bojan

It is not possible to specify compiling files from entire directory via command line arguments - all files have to be explicitly listed. This and also listing all arguments in the command line is not practical. Solution for this is specifying all options in the configuration file and instructing tsc to read everything from it.

Configuration


TypeScript transpiler uses a config file tsconfig.json. To create , use:

..\demo-typescript>npx tsc --init
message TS6071: Successfully created a tsconfig.json file.


Default content of tsconfig.json is:

{
  "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": "preserve",                     /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
    // "declaration": true,                   /* Generates corresponding '.d.ts' file. */
    // "declarationMap": true,                /* Generates a sourcemap for each corresponding '.d.ts' file. */
    // "sourceMap": true,                     /* Generates corresponding '.map' file. */
    // "outFile": "./",                       /* Concatenate and emit output to single file. */
    // "outDir": "./",                        /* Redirect output structure to the directory. */
    // "rootDir": "./",                       /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
    // "composite": true,                     /* Enable project compilation */
    // "removeComments": true,                /* Do not emit comments to output. */
    // "noEmit": true,                        /* Do not emit outputs. */
    // "importHelpers": true,                 /* Import emit helpers from 'tslib'. */
    // "downlevelIteration": true,            /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
    // "isolatedModules": true,               /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */

    /* Strict Type-Checking Options */
    "strict": true,                           /* Enable all strict type-checking options. */
    // "noImplicitAny": true,                 /* Raise error on expressions and declarations with an implied 'any' type. */
    // "strictNullChecks": true,              /* Enable strict null checks. */
    // "strictFunctionTypes": true,           /* Enable strict checking of function types. */
    // "strictPropertyInitialization": true,  /* Enable strict checking of property initialization in classes. */
    // "noImplicitThis": true,                /* Raise error on 'this' expressions with an implied 'any' type. */
    // "alwaysStrict": true,                  /* Parse in strict mode and emit "use strict" for each source file. */

    /* 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. */
    // "paths": {},                           /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
    // "rootDirs": [],                        /* List of root folders whose combined content represents the structure of the project at runtime. */
    // "typeRoots": [],                       /* List of folders to include type definitions from. */
    // "types": [],                           /* Type declaration files to be included in compilation. */
    // "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'. */
    // "preserveSymlinks": true,              /* Do not resolve the real path of symlinks. */

    /* Source Map Options */
    // "sourceRoot": "",                      /* Specify the location where debugger should locate TypeScript files instead of source locations. */
    // "mapRoot": "",                         /* Specify the location where debugger should locate map files instead of generated locations. */
    // "inlineSourceMap": true,               /* Emit a single file with source maps instead of having a separate file. */
    // "inlineSources": true,                 /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */

    /* Experimental Options */
    // "experimentalDecorators": true,        /* Enables experimental support for ES7 decorators. */
    // "emitDecoratorMetadata": true,         /* Enables experimental support for emitting type metadata for decorators. */
  }
}


If we execute

>npx tsc

...tsc will compile all .ts files in the project and place matching .js files next to .ts files.

To specify output directory (named e.g. "bundle") into which we want output structure to be placed in, we can set in config file:

"outDir": "./bundle/",

npx tsc will now output all .js files (preserving their original directory tree) in the bundle directory.


Let's now add build and start scripts to package.json so we don't have to use npx and run tools manually but follow the common approach - use npm as the task runner:

..\demo-typescript\package.json:

  "scripts": {
    ...
    "build-ts" : "tsc",
    "start" : "node bundle/index.js"
  },

Let's verify it:

..\demo-typescript>npm run build-ts

> demo-typescript@1.0.0 build-ts ..\demo-typescript
> tsc


..\demo-typescript>npm run start

> demo-typescript@1.0.0 start ..\demo-typescript
> node bundle/index.js

Hello, Bojan


Syntax

Variables

const x: number = 0;

Functions


Function Arguments


function foo(s: string){}
function foo(n: number){}

Modules


import { MyInterface, MyClass, MyFunction, MyEnum,  MyType } from '../myDomain/myModule';


References:

http://www.typescriptlang.org/docs/handbook/typescript-in-5-minutes.html
https://www.typescriptlang.org/docs/handbook/tsconfig-json.html
Style guide: https://github.com/basarat/typescript-book/blob/master/docs/styleguide/styleguide.md
https://www.quora.com/topic/TypeScript
Typescript. The bad, the worse, and the ugly.
JavaScript - TypeScript: Making .NET Developers Comfortable with JavaScript (2013)
Configuring TypeScript compiler
How to run typescript compiler as a package.json script without grunt or gulp?
How to set up a TypeScript project

Wednesday, 10 October 2018

Transpiling TypeScript with Babel within Rollup.js setup

Demo project is on GitHub.

References:

https://rollupjs.org/guide/en
https://www.npmjs.com/package/rollup-plugin-babel
https://github.com/rollup/rollup-plugin-babel
https://github.com/rollup/rollup-plugin-babel/issues/253
https://code.lengstorf.com/learn-rollup-js/

How to set custom text and background color for the Terminal window in VSCode

Apply these custom settings in your User or Workspace settings (read here about what is the difference between them in order to decide):

{
    "workbench.colorCustomizations": {
        "terminal.foreground" : "#00FD61",
        "terminal.background" : "#383737"
    }
}

Transpiling TypeScript with Babel

Since version 7 Babel is capable of transpiling TypeScript code into a JavaScript.

Inspired by TypeScript and Babel 7 I created a simple project to demonstrate that. Source code is on GitHub.

Further reading:

https://github.com/Microsoft/TypeScript-Babel-Starter
https://babeljs.io/docs/en/babel-preset-typescript
https://www.npmjs.com/package/@babel/preset-typescript
https://www.npmjs.com/package/@babel/preset-typescript/v/7.0.0-beta.32


How to install Node.js on Ubuntu 18.04

I installed npm without installing Node.js by executing:

$ sudo apt install npm

...but this installed its very old version:

$ npm --version
3.5.2

npm comes with Node.js and I wanted to install their latest version. After reading this Q&A thread I decided to use nvm.

$ curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.33.11/install.sh | bash
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100 12819  100 12819    0     0  34093      0 --:--:-- --:--:-- --:--:-- 34093
=> Downloading nvm from git to '/home/bojan/.nvm'
=> Cloning into '/home/bojan/.nvm'...
remote: Enumerating objects: 267, done.
remote: Counting objects: 100% (267/267), done.
remote: Compressing objects: 100% (242/242), done.
remote: Total 267 (delta 31), reused 86 (delta 15), pack-reused 0
Receiving objects: 100% (267/267), 119.47 KiB | 385.00 KiB/s, done.
Resolving deltas: 100% (31/31), done.
=> Compressing and cleaning up git repository
=> Appending nvm source string to /home/bojan/.bashrc
=> Appending bash_completion source string to /home/bojan/.bashrc
=> Close and reopen your terminal to start using nvm or run the following to use it now:

export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"  # This loads nvm
[ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion"  # This loads nvm bash_completion

Let's now install the latest version of Node.js. To check which one is the latest, go here. At the time of writing it was 10.11.0. In a new terminal session do the following:

$ nvm install 10.11.0
Downloading and installing node v10.11.0...
Downloading https://nodejs.org/dist/v10.11.0/node-v10.11.0-linux-x64.tar.xz...
################################################################################################################# 100.0%
Computing checksum with sha256sum
Checksums matched!
Now using node v10.11.0 (npm v6.4.1)
Creating default alias: default -> 10.11.0 (-> v10.11.0)

Let's check the versions now:

$ node --version
v10.11.0

$ npm --version
6.4.1

Tuesday, 9 October 2018

Using Rollup.js to bundle TypeScript modules

I wrote earlier about how to use Rollup.js to bundle modules written in JavaScript but what if source files are written in TypeScript? Rollup itself can't do transpiling so we have to instruct it to use a Typescript transpiler (typescript package).

If we have TS source file C:\dev\github\demo-typescript-rollup\src\index.ts:

function foo(s: string) {
    console.log(s);
};

foo("Hello, world!"); 

and C:\dev\github\demo-typescript-rollup\rollup.config.js:

export default [{
    input: 'src/index.ts',
    output: {
        file: 'dist/bundle.js',
        format: 'cjs'
    }
}];

...running Rollup would report an error like:

C:\dev\github\demo-typescript-rollup>npx rollup -c

src/index.ts → dist/bundle.js...
[!] Error: Unexpected token (Note that you need plugins to import files that are not JavaScript)
src\index.ts (1:14)
1: function foo(s: string) {};
                 ^
Error: Unexpected token (Note that you need plugins to import files that are not JavaScript)
    at error (C:\dev\github\demo-typescript-rollup\node_modules\rollup\dist\rollup.js:3460:30)
    at Module.error (C:\dev\github\demo-typescript-rollup\node_modules\rollup\dist\rollup.js:13371:9)
    at tryParse (C:\dev\github\demo-typescript-rollup\node_modules\rollup\dist\rollup.js:13029:16)
    at Module.setSource (C:\dev\github\demo-typescript-rollup\node_modules\rollup\dist\rollup.js:13102:33)
    at C:\dev\github\demo-typescript-rollup\node_modules\rollup\dist\rollup.js:21768:20
    at process._tickCallback (internal/process/next_tick.js:68:7)
    at Function.Module.runMain (internal/modules/cjs/loader.js:745:11)
    at findNodeScript.then.existing (C:\Users\komazec\AppData\Roaming\npm\node_modules\npm\node_modules\libnpx\index.js:268:14)

We need to install rollup-plugin-typescript (among other packages):

C:\dev\github\demo-typescript-rollup>npm install --save-dev typescript rollup rollup-plugin-typescript tslib
npm notice created a lockfile as package-lock.json. You should commit this file.
+ tslib@1.9.3
+ rollup@0.66.5
+ typescript@3.1.1
+ rollup-plugin-typescript@1.0.0
added 48 packages from 70 contributors in 13.156s

...and modify config file to:

import typescript from 'rollup-plugin-typescript';

export default [{
    input: 'src/index.ts',
    output: {
        file: 'dist/bundle.js',
        format: 'cjs'
    },
    plugins: [
        typescript()
      ]
}];

If we run Rollup now, it will use typescript plugin to transpile TS code to JS which will be successfully bundled:

C:\dev\github\demo-typescript-rollup>npx rollup -c

src/index.ts → dist/bundle.js...
created dist/bundle.js in 71ms

To see the entire project on GitHub, go here.

References:

https://github.com/rollup/rollup-plugin-typescript
https://medium.com/@paleo.said/how-to-bundle-an-npm-package-with-typescript-and-rollup-f80e0f196189

TODO:


Try using https://www.npmjs.com/package/rollup-plugin-typescript2.

Wednesday, 26 September 2018

JavaScript modules, export and import

Let's consider web app project root directory which contains the following files:

scripts/common.js
scripts/array_demo.js
views/array_demo.html


Without modules:


scripts/common.js:

function clearElement(id) {...}
function log(text) {...}

scripts/array_demo.js:

function foo() {
    clearElement(...);
    log(...);
};

In HTML file scripts have to be added in order so functions, objects, or primitive values from previously included scripts are visible in scripts that use them:

views/array_demo.html:

         ...
         <script src='/scripts/common.js'></script>
         <script src='/scripts/array_demo.js'></script>
     </body>
 </html>

This can be a problem in case when there are many and/or complex dependencies. The solution is using ECMAScript modules. This will work in HTML5-compliant browsers.


With modules:


What is the difference between a common (classic) JavaScript script and a module?


A classic script is just a standard JavaScript script as you know it. A module script is one that contains an ES6 module, i.e. it uses (or: can use) import and export declarations. [source]

scripts/common.js:

function clearElement(id) {...}
function log(text) {...}

export {
    clearElement,
    log
};

scripts/array_demo.js:

import { clearElement, log} from './common.js';

function foo() {
    clearElement(...);
    log(...);
};

views/array_demo.html:

         ...
         <script type="module" src='/scripts/array_demo.js'></script>
     </body>
 </html>


export and import statements are defined in ECMAScript 2015 (6th Edition, ECMA-262).

module script type is available only in HTML5-compliant browsers [source].

Modules VS Require.JS


Require.JS "modularizes" JS scripts and basically emulates import/export functionality. With ES6 modules available there is no need to use Require.JS.

Further reading:


ECMAScript modules in browsers
ES6 In Depth: Modules
HTML/Element/script (MDN)
ECMAScript 6 modules: the final syntax
export (MDN)
import (MDN)
CommonJS vs AMD vs RequireJS vs ES6 Modules

How to have two pages with same titles withing the same space in Confluence

If you try to create and save a page in Confluence which has the same title as some other page in the same space, you'll get this error:


This is because it is NOT possible to have multiple pages with the same name in a single space. Workarounds include prepending page name with (shortened) parent page name or adding invisible Unicode characters.

References:

https://community.atlassian.com/t5/Questions/Pages-with-same-name-in-space/qaq-p/443236
https://jira.atlassian.com/browse/CONFSERVER-1095
https://community.atlassian.com/t5/Confluence-questions/How-to-create-two-pages-with-the-same-name-in-different/qaq-p/875755
https://community.atlassian.com/t5/Confluence-questions/Not-able-to-create-a-page-with-the-same-title-as-another-page/qaq-p/332910
https://community.atlassian.com/t5/Confluence-questions/Create-multiple-pages-with-the-same-name-within-a-space/qaq-p/138708


Tuesday, 25 September 2018

ESLint VSCode plugin

If we install ESLint plugin for VSCode but not ESLint package itself, we'll get this error in VSCode:



In my post Introduction to ESLint I followed the ESLint guide for its local installation which instructs creating its config file in node_modules/.bin. This might not be the optimal (or better to say, working) solution if we want to use ESLint plugin for VSCode.

I installed that plugin and opened a directory of a test web application which contains JS files and has locally installed eslint. .eslintrc.json was created in .\node_modules\.bin. After couple of seconds upon opening project directory I got this error in ESLint output:

[Info  - 3:18:08 PM] ESLint server stopped.
[Info  - 3:18:08 PM] ESLint server running in node v8.9.3
[Info  - 3:18:08 PM] ESLint server is running.
[Info  - 3:18:11 PM] ESLint library loaded from: c:\dev\github\demo-html-css-js\node_modules\eslint\lib\api.js
[Error - 3:18:11 PM]
Failed to load plugin react: Cannot find module 'eslint-plugin-react'
Happened while validating C:\dev\github\demo-html-css-js\scripts\objects_demo.js
This can happen for a couple of reasons:
1. The plugin name is spelled incorrectly in an ESLint configuration file (e.g. .eslintrc).
2. If ESLint is installed globally, then make sure 'eslint-plugin-react' is installed globally as well.
3. If ESLint is installed locally, then 'eslint-plugin-react' isn't installed correctly.

Consider running eslint --debug C:\dev\github\demo-html-css-js\scripts\objects_demo.js from a terminal to obtain a trace about the configuration files used.


I found here that global .eslintrc located in my user directory could be the cause of this. And indeed, I found c:\Users\User\.eslintrc with the following content:

{
/* See all the pre-defined configs here: https://www.npmjs.com/package/eslint-config-defaults */
"extends": "defaults/configurations/eslint",
"parser": "babel-eslint",
"ecmaFeatures": {
"jsx": true
},
"plugins": [
"react"
],
"env": {
"amd": true,
"browser": true,
"jquery": true,
"node": true,
"es6": true,
"worker": true
},
"rules": {

"eqeqeq": 2,
"comma-dangle": 1,
"no-console": 0,
"no-debugger": 1,
"no-extra-semi": 1,
"no-extra-parens": 1,
"no-irregular-whitespace": 0,
"no-undef": 0,
"no-unused-vars": 0,
"semi": 1,
"semi-spacing": 1,
"valid-jsdoc": [
2,
{ "requireReturn": false }
],

"react/display-name": 2,
"react/forbid-prop-types": 1,
"react/jsx-boolean-value": 1,
"react/jsx-closing-bracket-location": 1,
"react/jsx-curly-spacing": 1,
"react/jsx-indent-props": 1,
"react/jsx-max-props-per-line": 0,
"react/jsx-no-duplicate-props": 1,
"react/jsx-no-literals": 0,
"react/jsx-no-undef": 1,
"react/jsx-sort-prop-types": 1,
"react/jsx-sort-props": 0,
"react/jsx-uses-react": 1,
"react/jsx-uses-vars": 1,
"react/no-danger": 1,
"react/no-did-mount-set-state": 1,
"react/no-did-update-set-state": 1,
"react/no-direct-mutation-state": 1,
"react/no-multi-comp": 1,
"react/no-set-state": 0,
"react/no-unknown-property": 1,
"react/prop-types":0,
"react/react-in-jsx-scope": 0,
"react/require-extension": 1,
"react/self-closing-comp": 1,
"react/sort-comp": 1,
"react/wrap-multilines": 1
}
}


I moved my .eslintrc.json from node_modules\.bin\ to the project's root and added in it:

"root": true

After this I re-opened project's workspace in VSCode and ESLint didn't report any errors and worked with no issues.

Introduction to ESLint


What is ESLint?


JavaScript linting utility

What is linting?


Code linting is a type of static analysis that is frequently used to find problematic patterns or code that doesn’t adhere to certain style guidelines.

Why do we need linting in JavaScript?


JavaScript, being a dynamic and loosely-typed language, is especially prone to developer error. Without the benefit of a compilation process, JavaScript code is typically executed in order to find syntax or other errors. Linting tools like ESLint allow developers to discover problems with their JavaScript code without executing it.

Installation


ESLint community is advocating local installation although a global installation is used in the guide on ESLint's webpage [source].

"If you want to include ESLint as part of your project’s build system, we recommend installing it locally." [Local Installation and Usage]

To install ESLint locally (after running npm init):

> npm install eslint --save-dev

package.json now contains:

"devDependencies": {
  "eslint": "^5.6.0"
}

Configuration


To set up configuration file use:

C:\dev\github\demo-html-css-js\node_modules\.bin>eslint --init
? How would you like to configure ESLint? (Use arrow keys)
  Use a popular style guide
> Answer questions about your style
  Inspect your JavaScript file(s)

NOTE: Read here why it might be better to run this command and create config file in the root directory of your project. If you choose to do so, navigate to root directory and run npx eslint --init.

This option makes eslint asking a set of questions:

? How would you like to configure ESLint? Answer questions about your style
? Which version of ECMAScript do you use? ES2018
? Are you using ES6 modules? Yes
? Where will your code run? Browser
? Do you use CommonJS? No
? Do you use JSX? No
? What style of indentation do you use? Spaces
? What quotes do you use for strings? Single
? What line endings do you use? Windows
? Do you require semicolons? Yes
? What format do you want your config file to be in? JSON
Successfully created .eslintrc.json file in C:\dev\github\demo-html-css-js\node_modules\.bin

This config file looks like this:

..\node_modules\.bin>type .eslintrc.json
{
    "env": {
        "browser": true,
        "es6": true
    },
    "extends": "eslint:recommended",
    "parserOptions": {
        "ecmaVersion": 2018,
        "sourceType": "module"
    },
    "rules": {
        "indent": [
            "error",
            4
        ],
        "linebreak-style": [
            "error",
            "windows"
        ],
        "quotes": [
            "error",
            "single"
        ],
        "semi": [
            "error",
            "always"
        ]
    }
}


Usage


We can now call ESLint from command line:

..\node_modules\.bin> eslint <path_to_JavaScript_file>

References:


ESLint
How to disable “unexpected console statement” in Node.js?
disallow the use of console (no-console)

Introduction to Node Package Manager (npm)

What is npm?

  • package manager
  • task runner that can serve as a replacement for Gulp

How to install npm?


Upon installation user variable Path (in environment variables) gets a new entry:
C:\Users\user\AppData\Roaming\npm


How to see command line arguments?




How to check its version?




How to update npm?




To verify it:



Packages


See here a complete definition of the package.

Node applications usually use (or depend on) multiple packages. There are three types of packages and each of them is listed within the object with the same name in packages.json:

  • regular (dependencies) - used in development in production
  • development (devDependencies) - packages are used only during application development and testing; we don't want to include them in the production and make users of our app unnecessarily download and build them 
  • optional (optionalDependencies) - dependencies which are used optionally - if they are found; their absence does not make application to fail

Configuration


npm gets its config settings from:

  • command line
  • environment variables
  • npmrc files
  • package.json file (in some cases)

.npmrc 


Global (per machine) 

Location: C:\Users\user\AppData\Roaming\npm\etc\npmrc

To find its location use:

>npm config get globalconfig
C:\Users\user\AppData\Roaming\npm\etc\npmrc

User-specific

Location:  %USERPROFILE%\.npmrc

To find its location use:

>npm config get userconfig
C:\Users\user\.npmrc

Local (per project) 

Location: in project's root directory.

It defines where can npm look for and fetch packages - package registries by listing their URLs:

# use npmjs registry by default
registry=https://registry.npmjs.org/

# use xyz registry for packages in @xyz scope
@xyz:registry=https://xyz.com/npm

package.json


It lets npm know what the name of your package is as well as what dependencies it uses.

It is created and initialized via npm init command. This can be done retroactively - npm init can be executed for the already existing project...it will only add to it package.json file.

It is a manifest of your project that includes the packages and applications it depends on, information about its unique source control, and specific metadata like the project's name, description, and author. [source]

The biggest reason for using package.json to specify a project’s dependencies is portability. For example, when you clone someone else’s code, all you have to do is run npm i in the project root and npm will resolve and fetch all of the necessary packages for you to run the app. [source]

All modules from package.json are installed to ./node_modules/. [source] This is npm install's default behavior. [source]

npm opens and reads package.json from the current directory. If it can't find it, it issues an error like:


Working with package.json
package.json - Specifics of npm's package.json handling
What's the difference between tilde(~) and caret(^) in package.json?


Properties


scripts


If within the project we have some tool we want to call frequently e.g. after every code change we don't want to type it every time but want to automate the process by adding a command within scripts object. See Babel example.



-w instructs npm to watch for changes in the src folder. Every time you make a change to a file in src, this command is automatically executed.


package-lock.json

package-lock.json is automatically generated for any operations where npm modifies either the node_modules tree, or package.json. [package-lock.json - A manifestation of the manifest]

Is there a way to force npm to generate package-lock.json?
Shall package-lock.json be added to version control?


CLI commands

adduser
alias: login

>npm login
Username: bojan
Password:
Email: (this IS public) bojan@example.com

Logged in as bojan on https://registry.npmjs.org/.

If logging for the first time and if %USERPROFILE%/.npmrc does not already exist, it will be created with the content similar to this one:

//registry.npmjs.org/:_authToken=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx


cache
Used to add, list, or clean the npm cache folder.[npm-cache]
Try clearing the npm cache

cache clean

Cleans the npm cache.  Deletes all data out of the cache folder.

config
used to update and edit the contents of the user and global npmrc files.
Sub-commands:
config get


init
Creates package.json. It is used when creating/initializing a package (not when installing it).

install
Installs in the local node_modules folder all dependencies listed in package.json.

install --production
npm will not install modules listed in devDependencies. Same applies when --production is omitted but the NODE_ENV environment variable is set to production.

install -g npm
Updates npm package itself.

install --save-dev package1 package2...
Installs packages locally (in  project's devDependencies)


list
Lists all packages installed locally (for the current project) and also their dependencies.

list --depth=0
Lists all packages installed locally but without their dependencies.

Example:



list -g
Lists all packages installed globally and also full path to the installation directory.
On Windows, that directory is: C:\Users\user\AppData\Roaming\npm.

Example:



list -g --depth=0
Lists globally installed packages but not their dependencies.

Example:


login
see adduser

run script_name
run-script script_name
Runs an arbitrary command (script) from a package's scripts key (scripts object in package.json). Optional argument: command. If no command is provided, it will list the available scripts (all properties of "scripts" object). [npm-run-script]

search search_terms...
Searches the registry for packages matching the search terms. [npm-search]

start
(short for run start)
npm runs the start script which is a command defined under scripts key in packages.json. This command usually starts the application with special configuration options (all listed in packages.json).

uninstall
uninstalls a package, completely removing everything npm installed on its behalf.
-S, --save: Package will be removed from your dependencies.
-D, --save-dev: Package will be removed from your devDependencies.
-O, --save-optional: Package will be removed from your optionalDependencies.

If all packages are uninstalled package.json contains empty dependency list:

"dependencies": {}



uninstall -g package_name
uninstall --global package_name
Uninstalls globally installed package

update
Updates local packages to their latest versions allowed by the version specified with syntax (^ or ~) in package.json. Changes package.json and package-lock.json.

update -g 
Updates global packages.

view package_name
Shows data about a package. This can be used to check if some package actually exists as if package doesn't exist a message "404 Not found : package_name" is printed out. [npm-view]

    Packages


    Products (tools, apps) used to come with a single package which contained both the core functionality and CLI.  Nowadays products are moving away from coupling the CLI and library together. The CLI now usually lives in the xxx-cli package.

    Installing package locally vs globally


    Sometimes it is better to install a package locally (project by project) then globally. There are couple of reasons for this:
    • Different projects on the same machine can depend on different versions of the package allowing you to update them individually.
    • Not having an implicit dependency on the environment you are working in makes your project far more portable and easier to setup.

    Where to install development dependencies?


    Shall development dependencies (e.g. Rollup, Webpack, Browserify, Gulp...) be installed globally or locally? Looking at reasons listed in the previous paragraph it makes more sense to install them locally. Benefits:
    • if required, different versions of these tools can be used in different projects 
    • all required dependencies can be installed in one go with npm install. There is no need for extra steps for installing packages globally (which, in turn, might also require root or admin privileges opening potential security holes...)

    How to run locally installed packages?


    • use npx to run their binaries directly (usually for some quick demonstrations, not in the real-world projects)
    • add them to scripts section of package.json and call them via npm run
    • add them to gulpfile and then Gulp takes care of executing them

    How to find quickly the version of some package installed on the local machine?


    >npm list dtrace-provider
    my-app@0.1.0 C:\dev\my-app
    `-- web-ext@2.9.1
    `-- bunyan@1.8.12
    `-- dtrace-provider@0.8.7

    Use -g to look for package installed globally on the machine.

    Resources

    https://stackoverflow.com/questions/18412129/how-can-i-update-npm-on-windows
    https://www.npmjs.com/package/npm-windows-upgrade
    http://thecodebarbarian.com/3-neat-tricks-with-npm-run
    Common npm mistakes