javascripthtmltypescriptsystemjscommonjs

Calling typescript function from HTML using a module system


I would like to call a TypeScript (ultimetely JavaScript) function from HTML. The Problem is that I would also like to use a module System (systemjs, commonjs etc.) and also webpack.

Here is an example:

example.ts:

export class Example {
    myFunction() {
        alert('Test');
    }
}

example2.ts:

export function doSomething() {
    alert('Test2');
}

example.html:

<html>
    <head>
        <script src="path/to/the/compiled/js/file.js"></script>
        <script>
            $(document).ready(function() {
                $('#test-btn').click(function(){
                    var example = new Example(); //error1
                    example.myFunction();
                });
            });
        </script>
    </head>
    <body>
        <button id="test-btn">Click me</button>
        <a href="javascript:doSomething()">Click me too</a> <!-- error2 -->
    </body>
</html>

The error comments indicate where the Errors happen: The exported class/function is not found because a module System is used. If I look into the compiled js files I can see that the class and function declarations are put within some module functions, so they are not accessible from HTML.

Currently I am using a Workaround by moving the entire $(document).ready... section into a TypeScript file. But for the <a href... example I don't have any Workaround.

So my final question: Is it actually possible to call a TypeScript function/class from HTML and using a module System?


Solution

  • Yes. There are 2 ways that webpack supports this

    1) Output using library option. This will write the exports of the entry file to a namespace variable in global scope of your choosing

    myEntryFile.ts

    import {Example} from './example'
    import {doSomething} from './example2'
    
    export {
      Example,
      doSomething
    }
    

    webpack.config.js

    output: {
      //... your normal output config her
      library: 'MyExposedNamespace'
    }
    

    This will now be available at window.MyExposedNamespace

    window.MyExposedNamespace.doSomething()
    const myExample = new window.MyExposedNamespace.Example()
    

    For more advanced usage you can also change the scope that the namespace is applied to via libraryTarget so doesn't pollute the global scope

    2) Using expose-loader

    webpack.config.js

    module: {
      rules: [
        { test: /example\.ts$/, 
          use: [
           { loader: 'expose-loader', options: 'Example'},
           'awesome-typescript-loader' // must transpile as file is typescript
          ]
        },
        { test: /example2\.ts$/, 
          use: [
            { loader: 'expose-loader', options: 'doSomeThing'},
            'awesome-typescript-loader'
          ]
        }
      ]
    }
    

    You must also remember to import these files in your project somewhere, otherwise webpack will ignore them. You don't have to actually use them, its just for webpack to trace dependencies. Most logical place is your entry file

    myEntryFile.ts

    import './example'
    import './example2'
    

    These can then be called via the global scope (in browser this is of course window)

    window.doSomeThing()
    const myExample = new window.Example()
    

    This one may become trickier due to the typescript transpilation step also needing to occur before being exposed, which depending on your other config, could get more complex than the simple version above

    Therefore I'd recommend the first solution as the easiest