Add import resolution plugin hook#1455
Conversation
|
I found some issues with paths in this, currently working through it. Let me know if you have any thoughts on what I can add or change. The particular issue was with using |
|
Fixed the issue and moved the plugin hook call to |
the getPlugins call again
| result: EmitFile[] | ||
| ) => ts.Diagnostic[] | void; | ||
|
|
||
| onImportResolutionFailure?: (packageRoot : string, dependency : string) => string | undefined |
There was a problem hiding this comment.
A module resolution plugin is something we have long been considering, and I think a better interface would be something like:
interface ModuleResolutionRequest {
moduleIdentifier: string; // "foo.bar.baz" from require("foo.bar.baz")
requiringFile: string; // path of the (lua) file containing the require
// maybe some other stuff I'm forgetting now?
// maybe a 'defaultResolve: (ModuleResolutionRequest) => string' to call the default behavior?
// Not sure why anyone would want that
}
moduleResolution?: (moduleToBeResolved: ModuleResolutionRequest) => string | undefinedThen crucially this plugin would be executed before our regular module resolution process, allowing users to intercept before it even starts. If it returns undefined it will go into the default behavior. I'm just wondering if we want to somehow maybe also be able to indicate that a specific import should not be emitted, but maybe that's for a future version.
There was a problem hiding this comment.
I think this interface makes more sense, it gives more control to someone writing a plugin to identify the package they wish to use via the module identifier. Ill start working on it.
| result: EmitFile[] | ||
| ) => ts.Diagnostic[] | void; | ||
|
|
||
| onImportResolutionFailure?: (packageRoot : string, dependency : string) => string | undefined |
There was a problem hiding this comment.
I don't like you passing packageRoot to the outside world, that's really something internal to the tstl implementation. What is the reason you need this in your case? I'm trying to understand the usage scenario
There was a problem hiding this comment.
In my particular case, I was getting the path that was being searched for the package and the attempted path to the dependency and transforming that to what I needed it to be, so when the plugin. Before I made the changes to packageRoot, the path being passed to the plugin was inconsistent. This was just much more consistent for me to work with. I think the changes to the interface and location for the plugins call will resolve this issue.
| } | ||
| } | ||
|
|
||
| if (this.plugins != null) { |
There was a problem hiding this comment.
I don't think this should be exclusive to nodeModules paths, I think a nicer place for this would be in resolveImport, before the call to resolveDependencyPath (going with the idea I mentioned on the plugin interface above, where users can intercept these calls before we do all of this quite complicated work on the file system while we already know we won't find anything)
| path.join(resolvedPath, "init.lua"), // lua looks for <require>/init.lua if it cannot find <require>.lua | ||
| ]; | ||
|
|
||
| if (resolvedPath.endsWith(".lua")) { |
There was a problem hiding this comment.
I don't think this if statement should be here at all. If the resolved path ends with .lua we do not have to search for it, because we apparently we already have a lua file path. I don't think this is possible in regular tstl module resolution, but I guess your plugin triggers this? I don't think this code path should really be possible ever.
There was a problem hiding this comment.
I put this here as a convince thing for me. When I was constructing the path it I included .lua in the resolved path. This was not being found since the possible files above appends .lua to the resolve path, the path was being set to /my/path/file.lua.lua. I didn't want to just add resolvedPath itself to the possible files, because someone could intentionally provide a path to a non lua file. I think the changes you mentioned above would make this a non issue though.
changes to the interface
|
I made some of the changes requested. I didnt include |
Perryvw
left a comment
There was a problem hiding this comment.
Starting to look good, need a test still, and of course you should make sure this works for your use-case too still. Also to avoid our formatter complaining, you can run npm run lint and npm run fix:prettier locally to make sure there are no formatting or linting complaints.
| } | ||
|
|
||
| private resolveDependencyPathsWithPlugins(required: ProcessedFile, dependency: string) { | ||
| if (this.plugins != null) { |
There was a problem hiding this comment.
This check is not required (and is missing the case where it is undefined instead of null). Tstl is using strictest TS rules, it will not allow you to pass null to this (and will prevent you from trying to use something that could be).
You can just remove it
| const dependencyPath = requiredFromLuaFile ? luaRequireToPath(dependency) : dependency; | ||
|
|
||
| for (const p of this.plugins) { | ||
| try { |
There was a problem hiding this comment.
I'd just get rid of this try/catch. The plugin shouldn't throw, and if it does I'm fine with tstl just aborting
| result: EmitFile[] | ||
| ) => ts.Diagnostic[] | void; | ||
|
|
||
| moduleResolution?: (moduleIdentifier : string, requiringFile : string) => string | undefined |
There was a problem hiding this comment.
you probably also want to pass the options and emitHost to this plugin, like the functions above it have
interface - removed try catch on plugin execution
- added test case
|
Fixed an issue I found with the path resolution and added the test specified. I ran and fixed the formatting with the linter and ran the entire test suite with everything passing. I wasn't sure if I should add a test in the plugins spec as well. I included the moduleResolution plugin for the test in the plugins folder, but I wasn't sure if that was the appropriate place for it. Let me know if there's anything else I should include in the test or anything that needs to be changed. |
| if (plugin.moduleResolution != null) { | ||
| const pluginResolvedPath = plugin.moduleResolution( | ||
| dependency, | ||
| dependencyPath, |
There was a problem hiding this comment.
this parameter is the requiringFile, shouldn't this be required.fileName?
There was a problem hiding this comment.
I missed that, which make sense why I ran into the issues I did, I'll fix that ty
| const isRelative = ["/", "./", "../"].some(p => pluginResolvedPath.startsWith(p)); | ||
|
|
||
| // // If the import is relative, always resolve it relative to the requiring file | ||
| // // If the import is not relative, resolve it relative to options.baseUrl if it is set | ||
| const fileDirectory = path.dirname(required.fileName); | ||
| const relativeTo = isRelative ? fileDirectory : this.options.baseUrl ?? fileDirectory; | ||
|
|
||
| // // Check if file is a file in the project | ||
| const resolvedPath = path.join(relativeTo, pluginResolvedPath); | ||
| const fileFromPath = this.getFileFromPath(resolvedPath); |
There was a problem hiding this comment.
This looks just straight up duplicate from logic in resolveDependencyPath, please move to a shared function they both can use
| const plugin: tstl.Plugin = { | ||
| moduleResolution(moduleIdentifier, requiringFile) { | ||
| if (moduleIdentifier.includes("foo")) { | ||
| return requiringFile.replace("foo", "bar"); |
There was a problem hiding this comment.
the requiring file is called main.ts (or .lua?), not foo, so I'm confused by this plugin. See also my other comment regarding the requirintFile parameter
the format specified in the interface
|
Sorry about the silly mistakes, this should make more sense now. I removed |
Perryvw
left a comment
There was a problem hiding this comment.
Looks good to me. Thanks for your contribution!

I was having issues with files with a . in the file name (e.g physics-api.component.ts) and needed some method of providing custom filepath resolution login when importing files from from a library into another project bundling the library.