-
Notifications
You must be signed in to change notification settings - Fork 18
Creating server side components (node modules)
Many things can be done client side: however, if you wish to access a database, or connect to a message bus, you also need to expand the server side functionality. In case you need a very specific purpose, you can simply add your extra functionality to (a fork of) csMap
as an internal module. That's the most easy way to go. However, if you want to offer this functionality too others too, please read on.
As we are dealing with the node server, it makes sense to offer this additional server side functionality as components that are delivered as node modules, so a developer can pick and choose the components that he needs. What follows is a description of the development of one such node module, and publish it as a node package.
The example is based on the development of the cs-offline-search
node package. It's purpose is to create server side an index file of the currently available layers, which can be used by the csWeb.offlineSearch
directive on the client.
To start, create a new folder OfflineSearch
in the csMap
project. In this folder, I've created the OfflineSearchManager and OfflineSearcher classes, including several helper classes and interfaces. As you may know, an internal module in node uses the name of the file to create a module (the file name is considered a kind of namespace). So instead of starting each file with module ModuleName { ... }
, as we did on the client side, here we don't need them. Unfortunately.
Why unfortunately, you may ask? Well, because most of the helper interfaces and classes that I needed to create are copies of the ones that are used server side, but without the module
part. This is quite a pain, since I cannot (or, better put, do not know how to) reuse the files that I created for the client. And any change on the client side may actually break my module. For example, I need to parse the solution file (still named projects.json), and if its layout is changed on the client side, I cannot parse it anymore.
Anyways, after some meddling I finished a working internal module of the offline search. As we are not interested in this particular functionality, let's move on to the next step.
Basically, I just copied everything I had to csServerComp
. At first, I thought I could put the helper classes and interfaces in a main folder, and have one tsconfig.json
compile many different modules, but that bird didn't fly. Instead, I needed to copy every file I needed to a new folder, adequately named OfflineSearch
, and add the tsconfig
file there too. The folder structure is, therefore, as follows:
- csServerComp
- OfflineSearch
- Helpers\
- Scripts\node
- public\data\projects\...
ts files
In addition to the existing files, I also needed to add four new files:
-
tsconfig.json
, so I can specify what to compile. -
package.json
, which specifies how my node module is published -
Scripts/typings/node/node.d.ts
so I can reference basic node functionality (like the file system or path) -
readme.md
, a required description of your package
Minor side-note: also in this case, we cannot use these helpers across node modules, and any change client side is going to impact every node module that we are developing!
My tsconfig.json
is quite similar to most others you have come across, I guess. See here or here for more details. I've just specified the output to the dist
folder, so the generated JavaScript files don't end up between my TypeScript files. If everything went OK, you can press CTRL-SHIFT-B (in Atom using the atom-typescript package) to compile it.
{
"compilerOptions": {
"target": "es5",
"module": "commonjs",
"declaration": true,
"noImplicitAny": false,
"removeComments": false,
"noLib": false,
"outDir": "dist"
},
"filesGlob": [
"./**/*.ts",
"!./node_modules/**/*.ts"
]
}
As this was my first node package, I've read this tutorial and this one about how to proceed. Please do so too. A brief walk-through:
- Before creating node packages, I've added some default information about myself:
npm set init.author.name "Your Name"
npm set init.author.email "[email protected]"
npm set init.author.url "http://www.your.website"
npm adduser <your username>
-
npm view cs-your-package-name
: by convention, all our packages should start with a cs, so presumably, your package name should be unique. Also note that your package name should be in lower case in order for the node package repository to accept it.
-
npm init
: which will walk you through a list of questions to generate thepackage.json
description. Mine looked as follows:
{
"name": "cs-offline-search",
"version": "0.0.4",
"description": "Create an index file of the locally stored geojson files.",
"main": "./dist/OfflineSearch/index.js",
"scripts": {
"test": "node ./dist/OfflineSearch/test.js"
},
"repository": {
"type": "git",
"url": "https://github.com/TNOCS/csWeb"
},
"keywords": [
"offline",
"search"
],
"author": "Erik Vullings <erik dot vullings at gmail dot com> (https://nl.linkedin.com/in/erikvullings)",
"license": "MIT",
"readmeFilename": "readme.md",
"bugs": {
"url": "https://github.com/TNOCS/csWeb/issues"
}
}
Besides the main functionality, I've also added a public
folder with test data, and a very simple script to test my new package, test.ts
(which is also referenced in the package.json
below the scripts
tag).
import OfflineSearchManager = require('./index');
import IOfflineSearchOptions = require('./IOfflineSearchOptions');
var offlineSearchOptions: IOfflineSearchOptions = {
propertyNames: ['Name', 'GeoAddress'],
stopWords: ['de', 'het', 'een', 'en', 'van', 'aan']
};
var offlineSearchManager = new OfflineSearchManager('public/data/projects/projects.json', offlineSearchOptions);
Enter npm test
to test the functionality.
In addition, there are two other ways you can test your package before publishing"
- Use
node
in the command line to test your functionality. When you enternode
, a new command line appears, in which you can enter valid JavaScript. - In addition, you can use
npm link
to create a symbolic link before publishing your package.
cd OfflineSearch
npm link
cs YourApp
npm link cs-offline-search
See 'Using your External Module' below to proceed.
When everything works as expected, you can finally publish your package, assuming you have added everything to your git repository already.
git tag 0.0.1
git push origin master --tags
npm version 0.0.1
npm pack
npm publish
Now that you've published your node module, you can easily use it in your own project too. Just install is as a regular node module using npm install cs-offline-search --save
(the --save
option automatically adds the package to your package.json).
Next, add the following code to server.ts
to invoke it.
import offlineSearch = require('cs-offline-search');
/**
* Create a search index file which can be loaded statically.
*/
var offlineSearchManager = new offlineSearch('public/data/projects/projects.json', {
propertyNames: ['Name', ''postcode', 'Postcode'],
stopWords : ['de', 'het', 'een', 'en', 'van', 'aan']
});
Unfortunately, TypeScript will show a number of errors when you do this, since it does not know your package. You still need to create a definition file. Unfortunately, I could not use the definitions that were created automatically, as they didn't use the correct module name, so I had to create one myself. Note the most important thing at the end export = OfflineSearchManager', which is the magic word in order to use it in
server.ts. I've also added this file to the
dist/Scripts/typings/cs-offline-search` folder, so others can use it too.
declare module "cs-offline-search" {
interface IProjectLocation {
title: string;
url: string;
}
/**
* Specify the offline search options.
*/
interface IOfflineSearchOptions {
/**
* Words you wish to exclude from the index.
* @type {string[]}
*/
stopWords: string[];
/**
* The property types that you wish to use for generating the index.
* @type {osr.PropertyType}
*/
propertyNames: string[];
}
/**
* Offline Search reads the solution file, and creates an OfflineSearchResult for each project it contains.
*/
class OfflineSearchManager {
private solutionsFile;
private options;
constructor(solutionsFile: string, options: IOfflineSearchOptions);
private openSolution();
private processProject(project: IProjectLocation): void;
}
export = OfflineSearchManager;
}