Node “Console App” & Debugging TypeScript 2.0 in VS Code

This post is an update of my first post outlining how to get started debugging TypeScript 2.0. The core ideas remain the same but there are a few updates in the steps as well as file contents. This example has also been expanded to support Webpack for serving and bundling your solutions.

Setup the Project

As simple as it sounds the first step is setting up our TypeScript project. You can always add any of the libraries you like, but this will get us started for the example. Once these setup steps are done you can re-use the application by just updating the source code.

 npm init
npm install gulp gulp-sourcemaps gulp-tslint gulp-typescript gulp-util node-fetch ts-loader typescript webpack-dev-server webpack-stream tslint --save-dev
npm install sp-pnp-js --save

Next create a tsconfig.json file in the root of the project and add the below content and save.

 {
  "compilerOptions": {
    "target": "es5",
    "module": "commonjs",
    "jsx": "react",
    "declaration": false,
    "sourceMap": true,
    "removeComments": true
  }
}

Finally we need a gulp file to build, run, serve, and bundle our program. Create a file named gulpfile.js in the root directory and add the below content.

 var gulp = require("gulp"),
  tsc = require("gulp-typescript"),
  maps = require('gulp-sourcemaps'),
  exec = require("child_process").exec,
  gutil = require("gulp-util"),
  tslint = require("gulp-tslint"),
  webpack = require('webpack'),
  server = require("webpack-dev-server"),
  config = require("./webpack.config.js");

/*
 This task will transpile the solution suing src/main.ts as the entry point
*/
gulp.task("build", function () {

  var src = ["src/**/*.ts"];

  var project = tsc.createProject("tsconfig.json");
  return gulp.src(src)
    .pipe(maps.init())
    .pipe(project()).js
    .pipe(maps.write(".", { sourceRoot: "../src" }))
    .pipe(gulp.dest("build"));
});

/*
 This task will "run" the app using src/main.ts as the entry point
*/
gulp.task("run", ["build"], function (cb) {

  exec('node build/main.js', function (err, stdout, stderr) {
    console.log(stdout);
    console.log(stderr);
    cb(err);
  });
});

/*
 Provide linting support
*/
gulp.task("lint", () => {
  return gulp.src("./src/**/*.ts")
    .pipe(tslint({
      formatter: "verbose"
    }))
    .pipe(tslint.report());
});

/*
 Provide a default action, in this case serve
*/
gulp.task("default", ["serve"]);

/*
 This task will bundle the project using webpack
*/
gulp.task("bundle", ["lint", "webpack:build"]);

/*
 This task does the actual webpack bundling of the solution
*/
gulp.task("webpack:build", function (callback) {
  // modify some webpack config options
  var buildConfig = Object.create(config);
  buildConfig.plugins = buildConfig.plugins.concat(
    new webpack.DefinePlugin({
      "process.env": {
        // This has effect on the react lib size
        "NODE_ENV": JSON.stringify("production")
      }
    }),
    new webpack.optimize.DedupePlugin(),
    new webpack.optimize.UglifyJsPlugin()
  );

  // run webpack
  webpack(buildConfig, function (err, stats) {
    if (err) throw new gutil.PluginError("webpack:build", err);
    gutil.log("[webpack:build]", stats.toString({
      colors: true
    }));
    callback();
  });
});

// modify some webpack config options
var devConfig = Object.create(config);
devConfig.devtool = "sourcemap";
devConfig.debug = true;

// create a single instance of the compiler to allow caching
var devCompiler = webpack(devConfig);

gulp.task("webpack:build-dev", function (callback) {
  // run webpack
  devCompiler.run(function (err, stats) {
    if (err) throw new gutil.PluginError("webpack:build-dev", err);
    gutil.log("[webpack:build-dev]", stats.toString({
      colors: true
    }));
    callback();
  });
});

/**
 * Run a web server which watches files and rebuils your app when it changes
 */
gulp.task("serve", function (callback) {
  // modify some webpack config options
  var serveConfig = Object.create(config);
  serveConfig.devtool = "sourcemap";
  serveConfig.debug = true;

  // Start a webpack-dev-server
  new server(webpack(serveConfig), {
    publicPath: serveConfig.output.publicPath,
    stats: {
      colors: true
    },
    contentBase: "dist"
  }).listen(8080, "localhost", function (err) {
    if (err) throw new gutil.PluginError("serve", err);
    gutil.log("[serve]", "https://localhost:8080/assets/main.js");
  });
});

This gulp file providers the following tasks:

  • build - transpiles the TypeScript code in "src" to JavaScript files written to the "build" folder
  • run - starts node to execute the JavaScript files in the "build" folder
  • lint - performs linting of the code based on the tslint.json file's settings (included in the project download)
  • default - runs the serve gulp task
  • bundle - runs the webpack bundler to produce a single minified JavaScript file in the "dist" folder
  • serve - runs a webpack server that also watches your project files and will rebuild the bundle when you save changes to any of the files

Setup Webpack

To control webpack you need to create a webpack.config file in the root of your project and add the below:

 var path = require("path");

module.exports = {
  cache: true,
   entry: './src/main.ts',
   output: {
     path: path.join(__dirname, "dist"),
     publicPath: "/assets/",
     filename: "main.js"
   },
   resolve: {
     extensions: ['', '.webpack.js', '.web.js', '.ts', '.js']
   },
   plugins: [],
   module: {
     loaders: [
       { test: /\.ts$/, loader: 'ts-loader' }
     ]
   }
}

Write some Code!

Now that we setup our project we can actually do something. Start by creating a "src" folder in your project and adding a main.ts file to that folder. Then add the below contents to main.ts:

 console.log("Hello world!");

Now in your console type the command "gulp run" and you should see your project get built and "Hello World!" written out to the console. Awesome! Now we can get down to business. Update your main.ts to include the below:

 import pnp from "sp-pnp-js";

pnp.setup({
 nodeClientOptions: {
   clientId: "{your client id}",
   clientSecret: "{your client secret}",
   siteUrl: "https://{your tenant}.sharepoint.com/sites/dev"
 }
});

pnp.sp.web.select("Title").get().then((r) => console.log(JSON.stringify(r)));

You will need to update the clientId, clientSecret and siteUrl values once you register the add-in permissions in your site. If you have properly registered the permissions you can once again use the "gulp run" command and you should see the title of your web written to the console. You can then begin using your app to perform any tasks you want in your site. Remember you need to ensure you've requested the appropriate permissions on /_layouts/appinv.aspx.

Debugging

Once we can run code it would be fantastic if we could debug it to see what is going on. In Visual Studio Code this means adding a launch.json file. First create a folder named ".vscode" in the root of the project and add a file named "launch.json" with the contents:

 {
  "version": "0.2.0",
  "configurations": [
    {
      "name": "Launch",
      "type": "node",
      "request": "launch",
      "program": "${workspaceRoot}/src/main.ts",
      "stopOnEntry": false,
      "args": [],
      "cwd": "${workspaceRoot}",
      "preLaunchTask": "build",
      "runtimeExecutable": null,
      "runtimeArgs": [
        "--nolazy"
      ],
      "env": {
      "NODE_ENV": "development"
      },
      "console": "internalConsole",
      "sourceMaps": true,
      "outDir": "${workspaceRoot}/build"
    }
  ]
}

This file will instruct Visual Studio Code how to launch when you hit F5. In your main.ts file set a break point and hit F5. You should see the breakpoint hit and you can examine locals and add watches on the debug tab accessed on the left hand navigation bar. If your breakpoint is not hit, try restarting Visual Studio Code.

Gotcha: If you continue to have issues hitting your break point or are getting the message "Breakpoint ignored because generated code not found (source map problem?)" - ensure you have correctly set the "sourceRoot" property of the source maps write call found in the gulpfile.js's build task. This needs to be a relative path from your built output to your source files.

Now that you can debug your code and use the Patterns and Practices Core Library from a console app you can rapidly develop and test your code - or perform quick queries and updates to your SharePoint sites. Happy coding!

Ensure you are Using TypeScript 2.0

Depending on your version of Visual Studio Code you may get a message which says "This workspace folder contains TypeScript 2.0.3. Do you want to use this version instead of the bundled version 1.8.10?" as seen below.

use 2.0 in code

You can select use 2.0.3 each time or create a settings.json file in the .vscode directory and merge the below content to instruct Code to always use the version installed in this project's node_modules folder. You can also adjust the settings for the user by putting these contents in the user preferences file and it will apply to all projects.

 // Place your settings in this file to overwrite the default settings
{
  "typescript.tsdk": "node_modules/typescript/lib"
}

Download the Sample Project

You can download the starter project as well: NodeConsoleApplication2. You will need to run the following commands to load the dependencies:

 npm install

What is the JS Core Component?

The Patterns and Practices JavaScript Core Library was created to help developers by simplifying common operations within SharePoint and the SharePoint Framework. Currently it contains a fluent API for working with the full SharePoint REST API as well as utility and helper functions. This takes the guess work out of creating REST requests, letting developers focus on the what and less on the how.

"Sharing is Caring"