Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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>
like image 929
Get Off My Lawn Avatar asked Sep 05 '25 03:09

Get Off My Lawn


1 Answers

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.

like image 91
artem Avatar answered Sep 07 '25 21:09

artem