Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Expose emitted module specifiers on AMD outfile emit #35052

Open
5 tasks done
kitsonk opened this issue Nov 12, 2019 · 4 comments
Open
5 tasks done

Expose emitted module specifiers on AMD outfile emit #35052

kitsonk opened this issue Nov 12, 2019 · 4 comments
Labels
In Discussion Not yet reached consensus Suggestion An idea for TypeScript

Comments

@kitsonk
Copy link
Contributor

kitsonk commented Nov 12, 2019

Search Terms

amd outfile module specifier

Suggestion

Currently when utilising the compiler APIs to emit as AMD to a single outfile, the emitted module specifier in the outfile is not exposed, but does not match the source file SourceFile.fileName or any other attribute.

It appears that there are internals of the TypeScript emitter which determine the module specifier that is included output. For example, if given /foo/bar/baz.ts and /foo/qat/qux.ts being part of the same program, when emitted as AMD in a single bundle the module specifier, the outfile will define() a module named bar/baz and qat/qux. It appears that the algorithm used looks for the common root and removes the extension to determine the module specifier.

Use Cases

When loading the bundle with an AMD loader, knowing what a module specifier is in the outfile is needed to be able to specify a module to load in a require() statement. So if we were automating a custom build, outputting a "bundle" and determining which module to require() to bootstrap the application, we have to "guess" and hope that the emitter doesn't change its algorithm of determining the module specifiers.

Examples

I'm not totally sure, but a public API that would take a SourceFile[] and provide back a string[] array of module specifiers. This could be used in Host.writeFile() to determine what module specifiers are contained in the written file.

Checklist

My suggestion meets these guidelines:

  • This wouldn't be a breaking change in existing TypeScript/JavaScript code
  • This wouldn't change the runtime behavior of existing JavaScript code
  • This could be implemented without emitting different JS based on the types of the expressions
  • This isn't a runtime feature (e.g. library functionality, non-ECMAScript syntax with JavaScript output, etc.)
  • This feature would agree with the rest of TypeScript's Design Goals.
@andrewbranch
Copy link
Member

I’m not familiar with this behavior (don’t think I’ve ever targeted AMD before), but just to clarify a few things:

  • where is the project root in this example scenario?
  • does baseUrl or rootDir have any impact on the output?
  • if the output module specifiers were consistently predictable from project file structure, would that be sufficient?

I think I would personally expect module specifiers to be relative to the rootDir, and would feel pretty safe about this not changing. I’m kind of surprised if that’s not already the case, but it seems like I might be missing something about your request?

@andrewbranch andrewbranch added In Discussion Not yet reached consensus Suggestion An idea for TypeScript labels Dec 11, 2019
@kitsonk
Copy link
Contributor Author

kitsonk commented Dec 12, 2019

@andrewbranch I've created an example repo which recreates an environment which relates to this request.

  • where is the project root in this example scenario?

I don't believe this has an impact. In my example the project root contains a src directory, but all the module IDs are emitted based on their common root.

  • does baseUrl or rootDir have any impact on the output?

I don't believe so, I experimented in the example project and setting the baseUrl to ./ doesn't add src to the specifiers. It is the same output. I tried rootDir but I only tried one (setting it to [ "./" ] and again no impact.

  • if the output module specifiers were consistently predictable from project file structure, would that be sufficient?

They are, as far as I can tell, and I am using the logic I detected to determine the emitted specifiers. But that means that I am coupling to a behaviour that is not really part of the contract. It feels like something that I should be able to determine from the SourceFile.

I believe that SourceFile.moduleName is populated when you use the AMD directive (e.g. ///<amd-module name="foo"/>) and the module will always be emitted with that name. Typically when you target AMD and don't use outFile, the modules are written without any module name. It is only when you set and outFile or use the directive. If SourceFile.moduleName as populated on writeFile with the module names that were used in the emit, that would ensure that there is no doubt about what the module name is in the out file.

I’m kind of surprised if that’s not already the case, but it seems like I might be missing something about your request?

I was surprised too. 😁 Hopefully that adds a bit of clarity.

@andrewbranch
Copy link
Member

andrewbranch commented Dec 12, 2019

Cool, thanks for the example. It looks like this comes from the getCommonSourceDirectory on EmitHost (which is internal), and despite some 4-year-old JSDoc that says

 * The emitted output name can be different from the input if:
 *  1. The module has a /// <amd-module name="<new name>" />
 *  2. --out or --outFile is used, making the name relative to the rootDir

it looks like it’s intentionally finding the common source directory instead of using the rootDir.

If SourceFile.moduleName as populated on writeFile with the module names that were used in the emit, that would ensure that there is no doubt about what the module name is in the out file.

So is it undefined right now? Are you using transpileModule or program.emit? If the former, you can provide a module name as an option. (I guess you’re not using transpileModule since that doesn’t take any kind of host object.) But either way, it seems reasonable to me to add the module name (it’s already been calculated at the top of the function) to the transformed source file here:

addEmitHelpers(updated, context.readEmitHelpers());
return updated;

@rbuckton thoughts?

@kitsonk
Copy link
Contributor Author

kitsonk commented Dec 12, 2019

So is it undefined right now?

Correct, on writeFile() on the host, the sourceFiles that are all there, just .moduleName is always undefined (I suspect unless I use the directive, but I haven't tried).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
In Discussion Not yet reached consensus Suggestion An idea for TypeScript
Projects
None yet
Development

No branches or pull requests

2 participants