typescript-bundle icon indicating copy to clipboard operation
typescript-bundle copied to clipboard

source file may or may not be a `define`d module

Open jacobdweightman opened this issue 6 years ago • 3 comments
trafficstars

Consider a simple project with a single source file main.ts, containing

console.log("hello world!");

Compiling this project with --entryPoint main and then running with node bin/bundle.js gives

Error: module "main" not found.

This module can be compiled and run, producing the expected result, if the --entryPoint argument is removed from the compile step. However, if I add a second source file, other.ts, containing

export default "Hello world!";

and modify main.ts to import greeting from './other' and log it, then compiling with an explicit --entryPoint main compiles and runs as expected. The difference between the two is clear from the generated JavaScript: the latter produces a call to define("main", ... whereas in the former the log statement appears directly in the top-level IIFE.

It is not clear to me what determines whether code ends up directly in the IIFE vs in a call to define, but this behavior was certainly unexpected to me and feels like a bug.

jacobdweightman avatar Jul 13 '19 22:07 jacobdweightman

Hi, Thanks for drafting up the issue.

The behavior of whether a define('main', ...) is produced is actually a decision made of the TypeScript compiler. It's behavior is to only emit a define('main', ....) if the module (main.ts in this case) is using import or export qualifiers for objects and types within the module itself. If the module doesn't contain either import or export it just emits as is.

Note also that TypeScript-Bundle mandates on AMD as a module target with outFile as the option to concat AMD definitions.

In terms of the IIFE loader shim around the emitted content. TypeScript-Bundle naively wraps whatever the compiler happens to be emitting in either case. The compiler emitted content is injected here. To get a sense of the compiler behaviors, try running tsc with the following...

tsc ./main.ts --module amd --outFile bundle.js --watch

with the following code inside main.ts

console.log('hello')

export const foo = 'world'; // comment this line

So, the --entryPoint flag of tsc-bundle is used to override the default entry module (the last module in the file). Because the TypeScript compiler doesn't actually emit that define() when the module itself isn't exporting anything, that is the source of this error.

Do you have recommendations on what you would like to see here? It would be possible to emit a default main if the TypeScript compiler hasn't emitted one, but Id have some reservations about overriding the default output of the compiler. Another option might be to provide better error messaging.

Thoughts?

sinclairzx81 avatar Jul 14 '19 00:07 sinclairzx81

Thanks for the detailed explanation!

I definitely understand your reservations about overriding the default output of the compiler. Perhaps the heart of the issue here is that, as a new user, I saw in the documentation for entry point that

By default, the last module within the bundle is treated as the default entry point and will be evaluated first. If this is undesirable, the --entryPoint option allows for the selection of any other module located within the bundle to be evaluated first.

Since I almost always want to control which module runs first, I thought that I needed to specify it. Giving an error when I specify a starting module which actually exists "feels" like it violates the contract that the documentation outlines for this argument. I see a couple of possible ways forward:

  1. if tsc does not emit a define() for the entry point, then simply ignore the entry point (it must be run anyway when the IIFE runs, right?). We should be careful that a .ts file with that name is still compiled, to prevent hard-to-catch errors when there is a typo in the entry point name.
  2. change the documentation to clearly define what is meant by "the last module within the bundle".
  3. if tsc does not emit a define(), then wrap the output in one.

jacobdweightman avatar Jul 14 '19 00:07 jacobdweightman

Yes, let me have a think about it. I'll leave this issue open and try to take a look at it this next week. I think options 2 and 3 are workable (with option 2 being the easiest :D), but just weighing your option 3.

I think a case could be made (as an option 4) to omit the IIFE entirely if TS does not emit define. The naive wrapping of TS content irrespective of the content might be a bit 'too naive'. So ill give it some thought.

Thanks for your feedback !

sinclairzx81 avatar Jul 14 '19 03:07 sinclairzx81