2
votes

If I use target:"es5" to transpile TypeScript code that uses es6-style classes & React, my intended entry point (here a file called App.ts) has transpiled code that looks like this:

Object.defineProperty(exports, "__esModule", { value: true });
var react_1 = require("react");
var react_router_1 = require("react-router");

When that first line in this snippet gets hit, I get an Uncaught ReferenceError: exports is not defined error. A similar issue is described on TypeScript's GitHub Issues pages here.

The quick answer is that exports will exist in an environment that supports class loading.

commonjs means there's an exports variable available. You can't run commonjs code directly in a browser.

Here's my html wrapper:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <title>Hello World</title>
    <script src="https://unpkg.com/react@16/umd/react.development.js"></script>
    <script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
    <script src="./components/App.js"></script>
  </head>
  <body>
    <div id="root"></div>

    <script>
      ReactDOM.render(
        React.createElement('App', null),
        document.getElementById('root')
      );
    </script>
  </body>
</html>

It would appear (1, 2, 3) that the usual response is to put an additional transpilation step between your TypeScript and its release to a client on a browser. Wait, what? I need to transpile the transpiled code? This kinda smells like one of those "I thought the dishwasher washed the dishes" commercials.

Another recommendation is to use a module loading library like RequireJS or SystemJS (1, 2). That makes sense, but I haven't seen an example of adding a module loading lib to TypeScript compiled code for delivery to a browser. And, again, it seems like TypeScript could've intelligently (as in "with minimally duplicated code") included such a lib when it was transpiling.

(It also seems like RequireJS is at least conventionally used with a different syntax with require where you require(['lib1', 'lib2', 'lib3'], function (lib1, lib2, lib3) {...}}) rather than the simpler var lib1 = require('lib1') TypeScript is producing, though perhaps I'm missing something?)

So at least two related questions here:

  1. What's the expected use case for the target="es5" TypeScript transpiled code?
  2. How does one deliver es6 TypeScript code to the browser without transpilation steps beyond what TypeScript can do?
    • That is, is there a platform:"browser"-style setting I've missed?
    • If not, should RequireJS work with the code TS has produced? How should that be "bootstrapped"/started?

UPDATE: (Moving my comments from @felixmosh's answer into the question)

I have tried adding "module":"es2015" to my tsconfig.json, but the code started lighting up with errors on lines like this: import Home from './pages/Home'; that read error TS2307: Cannot find module './pages/Home'.

My impression was that the module option defined the way modules are defined in the TypeScript rather than how they'll be transpiled in the resultant JavaScript code. That is, if this option only affected output, why would my untranspiled code throw errors once the module setting is added/changed rather than simply change the resulting JavaScript files' syntax?

@felixmosh also [helpfully] suggests using something like RollUp.js, but that does an end run around loading modules by, well, it says compiles, but it's really just conglomerating "small pieces of code into something larger and more complex". Putting all the modules into one file is sort of cheating. (Though why doesn't TS at least do this conglomeration?)

So +1 to felix & thanks for the help, but I still don't understand why TS stops where it stops -- What's the use case for the code it does produce? Does TypeScript really require a step outside of TS to use es6-style classes in code delivered to browsers?

Why do I care? I've worked with RequireJS based codebases before, and it is nice to have your files broken out in a way that you can debug more easily than a monolithic codebase. But I also don't understand why TS seems like it doesn't produce deployable code in this use case.

2
Just a small note: You can skip all the BS setup in the future by using the typescript-directed create-react-app create-react-app my-app --scripts-version=react-scripts-tsAndrew

2 Answers

1
votes

I see you are getting started with building TypeScript web applications. If so this is my advice:

Transpiling TypeScript to JS is unavoidable,

Don't use RequireJS or SystemJS and instead use a bundler tool like parcel, webpack, rollup, browserify. Particularly I recommend parcel since is super easy and plays nice with TypeScript

  • With a bundler, get rid of all those those defines() requires() SystemJs.calls() and use just standard JavaScript import declarations (import {foo} form 'bar'). I strongly recommend this too

  • BTW avoid using require.js, AMD, UMD, SystemJS or similar. Is not a good advice getting started using those. Only use them if really really necessary.

Good luck

1
votes

If you want to keep the import/export of es6 you should define module:"es2015", by default, it converts those to cjs style that is used within node.

Modern browsers are no supports node's cjs style but es2015 style, so you need to specify that module type, and specify on your script tag an attribute of type="module", something like that:

<script type="module" src="entry-point.js"></script>

Currently there is a native support of modules only in chrome, therefore these days the best practice is to create a bundle from the dependency tree that you have created using imports.

The common boundlers are Webpack & RollUp, you can read their configs in-order to generate bundle that runs in browsers.

Small ref for typescript & webpack config: https://webpack.js.org/guides/typescript/