0

I'm trying to create a simple TypeScript library and test it from within different TypeScript example projects, so that I can verify and show that the library is usable from TypeScript under different module formats, including AMD.

For the library, I'm compiling it to UMD code, outputting it together with declaration files into a "build" directory, which I later refer to from the package.json file. Like so:

tsconfig.json

{
    "compilerOptions": {
        "module": "umd",
        "outDir": "build",
        "declaration": true,
        ...
    },
    ...
}

package.json

{
    "main": "build/index.js",
    "types": "build/index.d.ts",
    ...
}

It's worth mentioning that the library is a compound of multiple files, and that index.js is simply gluing together the different parts of it, exposing them through a single entry point.

Now, from one of the example projects, my goal is to compile to AMD code, and use RequireJS to understand the outcome. I'm able to write the TypeScript code, the compiler is happy with all the imports, and the editor (VS Code) provides auto-completion properly. However, the compiled code does not include the code of the library mentioned above, which is one of its dependencies. And when running the code in a browser, RequireJS is not able to load the library. Here is how this project is configured:

tsconfig.json

{
    "compilerOptions": {
        "module": "amd",
        "outFile": "build/app.js"
    },
    ...
}

index.html

...
    <script src="node_modules/requirejs/require.js"></script>
    <script src="build/app.js"></script>
    <script>
        require(['main'])
    </script>
...

With this, RequireJS is trying to load the library from "/library-name.js", which of course does not exist.

I'm pretty clueless about how to proceed.

DanielM
  • 1,106
  • 3
  • 17
  • 27

2 Answers2

2

You need to provide a RequireJS configuration so that RequireJS can find your library. Either that, or you need to move your library to the location where RequireJS looks for it by default. Both are possible options.

For instance, if your development server happens to serve the node_modules for your example project, in which presumably library-name has been installed, then you could use a configuration like this:

require.config({
  paths: {
    "library-name": "/node_modules/library-name/build/index",
  },
});

I've assumed that node_modules is served from the root of the site established by your development server. The lack of .js extension in the path is not a mistake: you don't put .js there because RequireJS adds it.

Louis
  • 146,715
  • 28
  • 274
  • 320
  • Isn't this super dumb? I mean, TypeScript has all the information and can already find everything, but we need to manually connect everything again? – DanielM Dec 14 '17 at 18:39
  • In any case, I tried your idea and it worked -- for index.js. But then, index.js contains things such as `export { Component } from './core/Component';`, and RequireJS is again trying to find that at the root, i.e. it is requesting the nonexistent file "/core/Component.js" (and the same for all of them). – DanielM Dec 14 '17 at 18:43
  • TypeScript actually has no knowledge of how packages are set up at run time, and does not know which loader you are using. You could be using RequireJS, you could be using SystemJS, or some other loader that may support UMD or AMD. It should be possible to write a tool that produces a RequireJS configuration from a TypeScript project and a few general settings, but AFAIK, no one has done that. The kind of `export` you show in your second comment is not inherently problematic. I do it all the time with large libraries. How does TypeScript convert your `export` to AMD? – Louis Dec 14 '17 at 18:50
  • That TypeScript has no knowledge of the runtime is true, but when producing a single file, which they allow when targeting AMD or SystemJS, dependencies should come in too, or at least the option for that should exist. It feels a bit like buying a toy with batteries not included. As for the compiled code, I have an UMD-style file, and the AMD part corresponding to the export looks like this: `define(["require", "exports", "./core/Component", ...], factory);` plus `var Component_1 = require("./core/Component"); exports.Component = Component_1.Component;`. Your help is much appreciated, @Louis. – DanielM Dec 14 '17 at 20:56
  • One other important thing: it is quite painful to publish a library and have to include together with it relatively complex usage instructions, specific to that one library. There must be a way to avoid this over-complication. – DanielM Dec 15 '17 at 18:16
0

I think I've got it, with the help of this other SO question among other things: RequireJS relative paths.

So essentially I had to configure RequireJS in this way:

require.config({
    packages: [{
        name: 'module-name',
        location: 'node_modules/module-directory/build',
        main: 'index'
    }]
})

Otherwise, if I only include a path to the index file, all relative paths used by the index get resolved relative to the root, which is pretty silly, but OK...

One thing that surprised me is that the baseUrl and paths options of the TypeScript (2.6.2) compiler didn't help, and in fact the compiled code was exactly the same independently of whether those fields were filled or not (I tried using for them the same values that in RequireJS helped loading the index file).

Edit

In case someone is interested, here is the configuration I needed for the same case with SystemJS:

SystemJS.config({
    paths: {
        'module-name': 'node_modules/module-directory/build/index'
    },
    packages: {
        '': {
            defaultExtension: 'js'
        }
    }
})
DanielM
  • 1,106
  • 3
  • 17
  • 27