-
-
Notifications
You must be signed in to change notification settings - Fork 180
Add import resolution plugin hook #1455
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
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
src/transpilation/plugins.ts
Outdated
@@ -45,6 +45,8 @@ export interface Plugin { | |||
emitHost: EmitHost, | |||
result: EmitFile[] | |||
) => ts.Diagnostic[] | void; | |||
|
|||
onImportResolutionFailure?: (packageRoot : string, dependency : string) => string | undefined |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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 | undefined
Then 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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
src/transpilation/plugins.ts
Outdated
@@ -45,6 +45,8 @@ export interface Plugin { | |||
emitHost: EmitHost, | |||
result: EmitFile[] | |||
) => ts.Diagnostic[] | void; | |||
|
|||
onImportResolutionFailure?: (packageRoot : string, dependency : string) => string | undefined |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
src/transpilation/resolve.ts
Outdated
} | ||
} | ||
|
||
if (this.plugins != null) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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)
src/transpilation/resolve.ts
Outdated
@@ -235,6 +255,11 @@ class ResolutionContext { | |||
path.join(resolvedPath, "index.lua"), // lua index file in sources | |||
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
src/transpilation/resolve.ts
Outdated
@@ -93,6 +95,45 @@ class ResolutionContext { | |||
} | |||
} | |||
|
|||
private resolveDependencyPathsWithPlugins(required: ProcessedFile, dependency: string) { | |||
if (this.plugins != null) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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
src/transpilation/resolve.ts
Outdated
const dependencyPath = requiredFromLuaFile ? luaRequireToPath(dependency) : dependency; | ||
|
||
for (const p of this.plugins) { | ||
try { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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
src/transpilation/plugins.ts
Outdated
@@ -45,6 +45,8 @@ export interface Plugin { | |||
emitHost: EmitHost, | |||
result: EmitFile[] | |||
) => ts.Diagnostic[] | void; | |||
|
|||
moduleResolution?: (moduleIdentifier : string, requiringFile : string) => string | undefined |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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. |
src/transpilation/resolve.ts
Outdated
if (plugin.moduleResolution != null) { | ||
const pluginResolvedPath = plugin.moduleResolution( | ||
dependency, | ||
dependencyPath, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this parameter is the requiringFile, shouldn't this be required.fileName
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I missed that, which make sense why I ran into the issues I did, I'll fix that ty
src/transpilation/resolve.ts
Outdated
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.