Exposing your custom config as an npm package


On this page

    In this guide, we will wrap our custom config into a new npm package, so that we can expose it as a proper reusable query engine. This package will be able to do everything packages such as Comunica SPARQL (@comunica/actor-init-sparql) can do. This means that this package will have a CLI tool, and that it will expose a JavaScript API for use in other packages.

    A fully functional example can be found here.

    1. Initialize a new package

    Initialize a new empty npm package as follows:

    $ npm init

    All init packages have to extend from Comunica SPARQL. As such, add it as a dependency as follows:

    $ npm install @comunica/actor-init-sparql

    We recommend to also install TypeScript as a dev dependency:

    $ npm install -D typescript
    If your custom config also depends on other packages that are not included in Comunica SPARQL, you have to install them here as well.

    2. Add a config file

    2.1. Create config file

    We assume here that you already have created a custom config file. Click here to learn how to create one should you not have done this already.

    Create a config/ folder, and add your config file (config-default.json) in here.

    If you config file includes other config sets, you can include them in this folder as well. In this case, you also have to make sure to include the context file in components/context.jsonld.

    The only requirement here is that there is at least a file config/config-default.json.

    2.2. Declare config options in package.json

    If your config file is decomposed into several files, you may already have done this step.

    Before we can refer to other files within our config file, we have to add some entries to our package.json file so that the config files can be found during engine initialization.

    Concretely, we need to add the following entries to package.json:

    {
      ...
      "lsd:module": "https://linkedsoftwaredependencies.org/bundles/npm/my-package",
      "lsd:contexts": {
        "https://linkedsoftwaredependencies.org/bundles/npm/my-package/^1.0.0/components/context.jsonld": "components/context.jsonld"
      },
      "lsd:importPaths": {
        "https://linkedsoftwaredependencies.org/bundles/npm/my-package/^1.0.0/components/": "components/",
        "https://linkedsoftwaredependencies.org/bundles/npm/my-package/^1.0.0/config/": "config/"
      }
      ...
    }

    On each line, make sure to replace my-package with your package name.

    If you want to learn more about what these config entries mean, read our guide on Components.js, a dependency injection framework that Comunica uses.

    3. Compiling the config into JavaScript

    In order to make the query engine start as fast as possible, we will pre-compile our config file into a JavaScript file.

    We will configure this in such as way that we can still modify our config file if needed, and recompile the JavaScript file easily.

    For this, add the following scripts to our package.json file:

    {
      ...
      "scripts": {
        ...
        "prepublishOnly": "npm run build",
        "build:engine": "comunica-compile-config config/config-default.json > engine-default.js",
        "build:lib": "tsc",
        "build": "npm run build:lib && npm run build:engine",
        "postinstall": "npm run build"
      },
    }

    Try if your script works correctly by running:

    $ npm run build

    Afterwards, you should have an engine-default.js file in your folder.

    4. Creating command line tools

    In this step, we will create three command line tools:

    Each of these CLI tools are optional, and you only have to create those you want. For this, create the following files:

    bin/query.js:

    #!/usr/bin/env node
    import {runArgsInProcessStatic} from "@comunica/runner-cli";
    runArgsInProcessStatic(require('../engine-default.js'));

    bin/http.js:

    #!/usr/bin/env node
    import {HttpServiceSparqlEndpoint} from "@comunica/actor-init-sparql";
    HttpServiceSparqlEndpoint.runArgsInProcess(process.argv.slice(2), process.stdout, process.stderr,
      __dirname + '/../', process.env, __dirname + '/../config/config-default.json', () => process.exit(1));

    bin/http.js:

    #!/usr/bin/env node
    import {runArgsInProcess} from "@comunica/runner-cli";
    runArgsInProcess(__dirname + '/../', __dirname + '/../config/config-default.json');

    As a final step, we have to make sure that we expose our CLI tools from the package. As such, add the following bin entries to package.json:

    {
      ...
      "bin": {
        "my-comunica": "./bin/query.js",
        "my-comunica-http": "./bin/http.js",
        "my-comunica-dynamic": "./bin/query-dynamic.js"
      },
    }

    _You can replace my-comunica with any name you want.`

    5. Exposing a JavaScript API

    In order to use your query engine as a dependency in other packages, we have to expose its JavaScript API. We will also immediately make it browser-friendly.

    For this, first create a file index-browser.ts:

    import {ActorInitSparql} from '@comunica/actor-init-sparql/lib/ActorInitSparql-browser';
    
    /**
     * Create a new comunica engine from the default config.
     * @return {ActorInitSparql} A comunica engine.
     */
    export function newEngine(): ActorInitSparql {
      return require('./engine-default.js');
    }

    Next, create a file index.ts:

    export {newEngine} from './index-browser';
    
    import {ActorInitSparql} from '@comunica/actor-init-sparql/lib/ActorInitSparql-browser';
    import {IQueryOptions, newEngineDynamicArged} from "@comunica/actor-init-sparql/lib/QueryDynamic";
    
    /**
     * Create a new dynamic comunica engine from a given config file.
     * @param {IQueryOptions} options Optional options on how to instantiate the query evaluator.
     * @return {Promise<QueryEngine>} A promise that resolves to a fully wired comunica engine.
     */
    export function newEngineDynamic(options?: IQueryOptions): Promise<ActorInitSparql> {
      return newEngineDynamicArged(options || {}, __dirname, __dirname + '/config/config-default.json');
    }

    As a final step, make sure to expose the following entries in your package.json file:

    {
      ...
      "main": "index.js",
      "browser": {
        "./index.js": "./index-browser.js"
      }
    }

    6. Indicating what files should be published

    Not all files should be published to npm when releasing the package, and not all files should be added to git repositories.

    For this create the following files:

    .npmignore

    .npmignore MUST exist and MUST be empty.

    .gitignore

    engine-default.js
    node_modules
    lib/**/*.js
    lib/**/*.js.map
    lib/**/*.d.ts
    test/**/*.js
    test/**/*.js.map
    test/**/*.d.ts
    bin/**/*.js
    bin/**/*.js.map
    bin/**/*.d.ts
    index.js
    index.js.map
    index.d.ts
    index-browser.js
    index-browser.js.map
    index-browser.d.ts

    As a final step, add following entries to package.json:

    {
      ...
      "files": [
        "components",
        "config",
        "bin/**/*.d.ts",
        "bin/**/*.js",
        "index.js",
        "index.d.ts",
        "index-browser.d.ts",
        "index-browser.js",
        "engine-default.js"
      ],
    }

    7. Publish to npm

    Now, you are ready to publish your package to npm, and allow other to use it via the CLI or via the JavaScript API.