5

I want to create a library using TypeScript. It should be available for Node and Browser environments.

I started with the package.json file and installed esbuild as a dependency and typescript as a dev dependency. The result is

{
  "name": "my-lib",
  "version": "1.0.0",
  "main": "dist/index.js",
  "types": "dist/index.d.ts",
  "scripts": {
    "build": "tsc && esbuild ./dist/lib/index.js --bundle --minify --sourcemap --outfile=./bundle/index.js"
  },
  "dependencies": {
    "esbuild": "0.14.36"
  },
  "devDependencies": {
    "typescript": "4.6.3"
  }
}

The tsconfig.json file contains

{
  "compilerOptions": {
    "baseUrl": ".",
    "declaration": true,
    "esModuleInterop": true,
    "lib": [ "dom", "esnext" ],
    "module": "commonjs",
    "outDir": "dist",
    "resolveJsonModule": true,
    "strict": true,
    "target": "es2019",
  },
  "include": [
    "./**/*.ts"
  ],
  "exclude": [
    "./dist"
  ]
}

Inside the root directory I have a lib directory holding an index.ts file which exports all the "public" functions, e.g.

const sum = function (a: number, b: number): number { return a + b; };

export { sum };

Besides that I have a test directory right next to the lib directory, holding some .ts files. I want to test the build script and added the following index.html to the root directory

<!DOCTYPE html>
<html>
    <head>
      <script type='text/javascript' src='./bundle/index.js'></script>
    </head>
    <body>
        <div>
            Loading bundle...
        </div>
    </body>
</html>

<script type="text/javascript">
window.onload = () => {
  console.log('ready');

  const result = sum(1, 2);

  console.log({ result });

  const anotherResult = window.sum(1, 2);

  console.log({ anotherResult });
};
</script>

Unfortunately I get an Uncaught ReferenceError because it is not able to find the function sum. The folder structure before running npm install && npm run build is

enter image description here

Does someone know what's wrong or missing here?

  • Why `"module": "commonjs"` if you want to use the library in a browser? – Bergi Apr 18 '22 at 23:40
  • @Bergi this package should run inside Node and Browser environments. Should I use `"module": "umd"` instead? –  Apr 19 '22 at 11:36
  • Yes, if you want to build a file that can be distributed to either, UMD will be necessary. Or you distribute two separate files in the package, one for node and one for browsers. – Bergi Apr 19 '22 at 12:52
  • Also it's not really clear what you're trying to achieve here. Why is `esbuild` not a *dev* dependency? Why do you want to bundle your library at all, shouldn't that be done by the application that uses the library? – Bergi Apr 19 '22 at 12:53
  • how are you bundling function using esbuild? – Yilmaz Apr 21 '22 at 22:30

3 Answers3

2

If you want include your bundled javascript into html file via script tag (as in your example), you should add sum variable to page global variables scope like that:

const sum = function (a: number, b: number): number { return a + b; };
window.sum = sum;
// or `globalThis.sum = sum;`

Defining variable in global page scope makes it sure that no minification or renaming processes in bundler will break your app.

  • `export { sum }` is not a typo, it's valid syntax that achieves the same thing as `export const sum =…;` – Bergi Apr 21 '22 at 15:32
  • 1
    Oh, that's my fault. I've removed that block of text about "typo" and preserved only blocks about using bundled script on html page. – Michael Sereniti Apr 21 '22 at 15:38
1

It would be better for you to check the concept of Commonjs and ECMAScript Module(ESM) before working. The compilerOptions.module in tsconfig.json is Commonjs, which is the bundling method used by Node.js. This does not work with browser. This is because the browser does not have the require method which can recognize the exports object and the module object, which is available globally in Node.js.

Therefore, when creating modules that support browsers, usually bundle them in UMD format using Webpack. If you don't support older browsers, you can think of bundling in the ESM way, even without a Webpack.

First, modify tsconfig.json as follows:

{
  "compilerOptions": {
    "baseUrl": ".",
    "declaration": true,
    "esModuleInterop": true,
    "lib": [ "dom", "esnext" ],
    "module": "esnext", // update
    "outDir": "dist",
    // "resolveJsonModule": true,
    "strict": true,
    "target": "es2019",
  },
  "include": [
    "./**/*.ts"
  ],
  "exclude": [
    "./dist"
  ]
}

Next, modify the following HTML as follows:

<!DOCTYPE html>
<html>
  <head>
    <script type="module">
      import {sum} from './dist/index.js';
    
      window.onload = () => {
        console.log('ready');
        const result = sum(1, 2);
        console.log({ result });
        const anotherResult = sum(1, 2);
        console.log({ anotherResult });
      };
    </script>
  </head>
  <body>
      <div>
        Loading bundle...
      </div>
  </body>
</html>

You can create library that work in your browser after npm install && npm run build.

If your library can be imported from another bundler (like a webpack), you want it to work in a browser, and you want to implement it using tsc only, see the following link.

lowfront
  • 639
  • 1
  • 5
  • when changing the HTML I get the following CORS error `Access to script at 'file:///C:/Users/.../bundle/index.js' from origin 'null' has been blocked by CORS policy: Cross origin requests are only supported for protocol schemes: http, data, chrome, chrome-extension, chrome-untrusted, https.`. Currently I'm not using Angular/React/Vue, this is just a plain .html file –  Apr 19 '22 at 11:44
  • @spa7Pdnt Yes, it is a method that can be used in a simple html file without js frameworks. Addresses starting with `file://` in HTML are not supported for security issues. You should create a local server and test it. Open the local server using library [serve](https://www.npmjs.com/package/serve) or upload the file to the server to test it. – lowfront Apr 19 '22 at 12:29
-1

Your code is fine, the problem is that you are using bad export. Exports a function that is never used and that is why the error. Then I show you how to solve this.

You must first bear in mind that nothing is exported in the index.ts. Rather the index.ts receives functions from other JS modules. Then create a Sum.TS file that will be a module where you will write the following code:

sum.ts

const sum = function (a: number, b: number): number { return a + b; };

export { sum };

In Index.ts imports the sum function of the module sum.ts the file must remain like this:

index.ts

import { sum } from "./sum";

sum

In this way you already have the sum function available to use in the index.html.

When you export functions these are not available in the compiled code. Only imports are available in the compiled code.

If you want to use the index.ts to store public functions the code should be like this:

const sum = function (a: number, b: number): number { return a + b; };
sum

update

Just change your index.ts to this:

 const sum = function (a: number, b: number): number { return a + b; };
    sum

and your index.html to this:

<!DOCTYPE html>
<html>
    <head>
      <script type='text/javascript' src='./bundle/index.js'></script>
    </head>
    <body>
        <div>
            Loading bundle...
        </div>
    </body>
</html>

<script type="text/javascript">
window.onload = () => {
  console.log('ready');

  const result = sum(1, 2);

  console.log({ result });

  const anotherResult = sum(1, 2);

  console.log({ anotherResult });
};
</script>

Don't export functions in index.ts as you won't be able to do them in the browser.

Usiel
  • 671
  • 3
  • 14
  • thanks for your efforts but unfortunately I wasn't able to understand your suggestions. If my code is fine, would you mind explaining what should be changed to make it work? The index.ts file exports all the consumable functions, but how can the local index.html file consume them? –  Apr 19 '22 at 11:49
  • check the update @spa7Pdnt – Usiel Apr 19 '22 at 12:27