-
Notifications
You must be signed in to change notification settings - Fork 916
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
Allow plugins that transform code to return source maps, and compose them together. #1048
Allow plugins that transform code to return source maps, and compose them together. #1048
Conversation
This pull request is being automatically deployed with Vercel (learn more). 🔍 Inspect: https://vercel.com/pikapkg/snowpack/mq18sj86z |
I don't understand the test failure -- it looks as if the |
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.
Reading your code a bit more, now I realized you’ve placed all your functionality in our legacy plugin API. I realize it’s easy to miss with the removal of comments (I could have sworn we had comments stating “V2 API“ vs “Legacy API” for these blocks, or they got removed, or I’m thinking of another place). Basically:
if (typeof result === 'string' || Buffer.isBuffer(result)) {
// This is our V2 API aka the way to do it going forward
} else if (result && typeof result === 'object' && (result as {result: string}).result) {
// This is our legacy plugin API that will be deprecated in Snowpack 3.x
// All your code went into here 😅
Again, I apologize, as this isn’t clear at all from the code, and it should be.
Instead, I think we should do the following:
Part 1: changes to build-pipeline.ts
if (typeof result === 'string' || Buffer.isBuffer(result)) {
// V2 API: simple string is returned… don’t touch this
+ } else if (result && typeof result === 'object' && (result as PluginTransformResult).contents) {
+ // V2 API: maps are handled (this is where your additions should go)
} else if (result && typeof result === 'object' && (result as {result: string}).result) {
// legacy API… don’t touch this either
Also, if you could add comments explaining the flow here (or even assigning these conditions as variables like isV2StringAPI
, isLegacyAPI
, etc.), that’d be awesome.
Part 2: changes to types/snowpack.ts
I also think we should update the types in types/snowpack.ts
to reflect this new structure: for transform()
plugins, either:
/** map of extensions -> code (e.g. { ".js": "[code]", ".css": "[code]" }) */
export type PluginLoadResult = string | SnowpackBuildMap;
+
+ export type PluginTransformResult = { contents: string; map: any }`
export interface PluginOptimizeOptions {
buildDirectory: string;
}
export interface SnowpackPlugin {
/** name of the plugin */
name: string;
/** Tell Snowpack how the load() function will resolve files. */
resolve?: {
/** file extensions that this load function takes as input (e.g. [".jsx", ".js", …]) */
input: string[];
/** file extensions that this load function outputs (e.g. [".js", ".css"]) */
output: string[];
};
/** load a file that matches resolve.input */
load?(options: PluginLoadOptions): Promise<PluginLoadResult | null | undefined | void>;
/** transform a file that matches resolve.input */
- transform?(options: PluginTransformOptions): Promise<string | null | undefined | void>;
+ transform?(options: PluginTransformOptions): Promise<PluginTransformResult | null | undefined | void>;
Unsure about the map
type… would love your feedback there.
transform()
API like you’re doing is actually pretty far-reaching and we have to make sure that it doesn’t break the dev server (I haven’t tested your PR extensively but I fear it would)
Anyway, hope this is a little bit clearer! Would love to add this functionality, and apologies again for the solution not being straightforward.
7af07ff
to
a9f4877
Compare
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.
Thanks for the clarifications @drwpow! I changed things around according to your recommendations -- see some extra comments below. Also, the test is still failing on GitHub even though it runs fine on my machine and I don't understand what's wrong; your assistance in figuring it out would be appreciated. Thanks!
@@ -47,7 +48,9 @@ export interface PluginRunOptions { | |||
} | |||
|
|||
/** map of extensions -> code (e.g. { ".js": "[code]", ".css": "[code]" }) */ | |||
export type PluginLoadResult = string | SnowpackBuildMap; | |||
export type PluginLoadResult = SnowpackBuildMap; |
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 shifted the string
part of the type to the method definition for consistency with PluginTransformResult
. (Including the string
in that type makes casting a lot less useful.)
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.
Thanks, but we actually don’t want that as part of the SnowpackBuildMap
. We do allow polymorphism when it comes to interfacing with plugins for flexibility and a better plugin DX. But for our own internal types we want them to be very strict. Moving that type up to SnowpackBuildMap
means we have to add many more typechecks in our app than we would otherwise.
So basically we want plugin APIs to be loose and polymorphic; internal types to be strict.
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 pushed the string
down to load()
, not up to SnowpackBuildMap
. It's basically a no-op (just one other spot in the code had to be changed) and SnowpackBuildMap
hasn't changed one bit. It just looked weird to have PluginLoadResult
include string
and PluginTransformResult
not.
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.
Ah understood. Sorry for misunderstanding
baseMap.destroy(); | ||
transformedMap.destroy(); | ||
} | ||
} |
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 is clean! Also, nice optimization with the Promise.all
👍🏻
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.
Thanks for the clarifications @drwpow! I changed things around according to your recommendations -- see some extra comments below.
🙏🏻 Awesome work! This is looking great. Thanks again for putting up with this unclear part of the codebase.
Also, the test is still failing on GitHub even though it runs fine on my machine and I don't understand what's wrong; your assistance in figuring it out would be appreciated. Thanks!
As far as the test goes, I’m actually getting the same results as the CI failure on my machine (macOS). Are you on Windows, by any chance?
Judging by the insignificant difference, I think it’s safe to update it to the expected output (I’m not totally sure what’s going on there, either, but I’m not worried about it):
- "{\"version\":3,\"sources\":[\"/home/sweet/home/snowpack/test/build/transform-sourcemap/src/submodule.js\"],\"names\":[],\"mappings\":\";AAAA\",\"sourcesContent\":[\"console.log(\\\"ts loaded\\\");\"]}"
+ "{\"version\":3,\"sources\":[\"/home/sweet/home/snowpack/test/build/transform-sourcemap/src/submodule.ts\"],\"names\":[],\"mappings\":\";AAAA\",\"sourcesContent\":[\"console.log('ts loaded');\"]}"
Maybe modifying the snapshot manually would work, at least to pass CI? But if you’re on Windows, be sure to test it on your machine, too, because we do run the entire CI suite once in Linux, and again in Windows (you’re just only seeing the Linux failure because it’s faster on GitHub to spin up, and it will always run/fail first, and cancel the in-progress Windows tests)
That's the problem though: I am on Windows, and that's where the snapshot comes from, so it looks like we have some anomalous platform-specific behavior. :( I'm concerned because the Linux snapshot appears to imply that the plugin is receiving the raw TypeScript code from |
Sorry for the delay; I haven’t taken the time to really dig into the core discrepancy here. We’ve dealt with similar problems before. On the one hand, I know this is frustrating. But on the other, it’s better for us to have Windows tests and make sure they’re deep tests, but the snapshots do become difficult. I’ll try and dig into this this afternoon and come up with a better solution. Thanks for your patience on this. |
Dug in a little more and I think this snapshot diff may be helped by just rebasing from latest |
a9f4877
to
e1cef60
Compare
I rebased and now it's completely broken... For some reason, |
@pkaminski no you’ve done an excellent job! Our snapshots have thrashed around a bit and it can be easily fixed after merging. Checking to see what @FredKSchott thinks but I’d like to just merge this as-is because you just happened to open a PR during a test change, and it’s easier to fix after merging IMO. |
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.
LGTM! will update and confirm snapshots post-merge
Changes
Adds support for a plugin's
transform
callback to return a{result, map}
structure, so that transformed code can be correctly mapped back to the original source. Correctly composes multiple maps together, including a base one obtained from the initialload
step.Per the discussion in #1046.
Testing
I added a simple test to check that both map pass-through and composition. I hand-checked the resulting maps (by dumping them to a human-readable format) and they looked right. We may want to set up a more complex test with "real" code and more extensive transforms, but I'm not sure how we'd verify the resulting maps.
This change is