-
-
Notifications
You must be signed in to change notification settings - Fork 1.4k
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
Plugin API adjustments for chunking #2126
Conversation
I've added two additional plugin hooks here as well - The reason I needed these is for worker pooling in plugins, which needs the ability to track when to create the worker processes and when to shut them down again, otherwise the process will hang and not be able to close down. |
src/rollup/index.ts
Outdated
timeEnd('GENERATE', 1); | ||
|
||
const output = { | ||
name: relative(process.cwd(), resolve(outputOptions.file || inputOptions.input)), |
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 can't remember if process is ok to use in the browser or not, would be good to check this.
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.
It's not, accessing process
throws an error 😉. But then I am not exactly sure what it should return in a browser...
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, I've added a detection here. Yes the name becomes somewhat guesswork on the edge cases...
f4c887d
to
aa328c5
Compare
src/rollup/index.ts
Outdated
chunks.map(chunk => { | ||
return chunk.render(outputOptions, addons).then(rendered => { | ||
const output = { | ||
name: chunk.id, |
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.
We could possibly call this filename
as well. The idea is that it is possible to determine the output file path from this using path.resolve(outputOptions.dir || process.cwd(), chunk.name)
within the transformChunk hook to reliably output eg a CSS file per chunk,
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.
Looking through our options, I guess the best name would actually be file
as this is the output option it would correspond to in the single input case. name
is already used for something else.
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.
Another reason we might still want a name when not writing is because we do have file names for chunking, but its only the single file build that doesn't have a rule here. So if we want the APIs to be unified at some point, ideally we can always assign a name - so that was the thinking behind using the input
fallback.
Will change to file for now and keep it always-populated, but we can reconsider this.
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 other thing is name
aligns with the naming of chunkNames
and entryNames
which are actually naming files in the same space... so would be good to try converge on terminology here actually.
Looking forward to this one, hope to give it a proper review soon. |
a78856a
to
1978df8
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 great work, especially the unified plugin context is something we needed for quite some time. Some thoughts:
- The hooks that are attached to
graph
should all be bound to the right context. It does not make sense to have to remember everywhere that they need to be called with.call(graph.pluginContext,...)
and this is easily forgotten in the future. - Speaking of breaking functionality in the future, there are no tests for the new plugin context functions as well as the new hooks
- Right now, the plugin hooks are a mixture of alllowercasenames and camelCasedNames. I know that
ongenerate
andonwrite
created some precedence here but my suggestion would actually be to go the opposite way as plugin hooks are near impossible to change (though we might have a chance with the 1.0 release if we communicate it well):- use camel case for the
onBuildStart
andonBuildEnd
hooks - create aliases
onGenerate
andonWrite
for the existing hook, the same way it is done for thetransformBundle
hook - maybe we can deprecate the all lowercase names with the 1.0 (but then we should start warning about their usage today to give people a change to update)
- use camel case for the
@@ -634,7 +634,8 @@ export default class Module { | |||
const declaration = otherModule.traceExport(importDeclaration.name); | |||
|
|||
if (!declaration) { | |||
this.graph.handleMissingExport( | |||
this.graph.handleMissingExport.call( | |||
this.graph.pluginContext, |
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.
It seems to me overly complicated and redundant to always hand the handler on this.graph
its own this.graph.pluginContext
.
As performance is probably not an issue for this handler, I would suggest to instead let the graph itself take care of using the right context (e.g. via wrapping the handler appropriately) so that modules do not need to care about something that is of no concern to them.
write: (options: OutputOptions) => Promise<{ [chunkName: string]: OutputChunk }>; | ||
getTimings?: () => SerializedTimings; | ||
} | ||
|
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.
👍
src/rollup/index.ts
Outdated
}; | ||
|
||
return Promise.all( | ||
graph.plugins |
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.
So with this change, generate
will now wait for all ongenerate
hooks that return a promise instead of just going on. Probably makes sense (one can just not return a Promise to not stall the generate
call) but we should definitely not forget to document this on the plugin wiki!
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.
Yes, until now only onwrite could return a promise.
src/rollup/index.ts
Outdated
}); | ||
} | ||
return generate(outputOptions).then(result => { | ||
return writeChunk(graph, outputOptions.file, result, outputOptions).then( |
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.
Nice extraction!
Looks even nicer here if you shorthand the arrow function 😉
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.
Would be even nicer if we can finally update our source to async/await... getting Rollup itself on autocompile is a near-term goal towards this, but this PR has to land first I guess :)
src/rollup/index.ts
Outdated
|
||
return writeFile(filename, code) | ||
.then(() => writeSourceMapPromise) | ||
.then(() => { |
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.
shorthand?
footer?: string; | ||
intro?: string; | ||
outro?: string; | ||
banner?: string | (() => string | Promise<string>); |
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.
Good catch!
Just wondering, even though this is not a plugin hook, isn't the signature still AddonHook
, i.e. aren't we using the graph context when calling anyway?
@@ -30,40 +28,36 @@ export function createAddons(graph: Graph, options: OutputOptions): Promise<Addo | |||
|
|||
function collectAddon( | |||
graph: Graph, | |||
initialAddon: string, | |||
initialAddon: string | (() => string | Promise<string>), |
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.
Again, isn't this just AddonHook
as we are definitely calling it using the right context?
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.
Yeah these repeats are all down to the context typing issue - there's no way for a bound function to have the same type as the unbound version without using .call
.
RawSourceMap | ||
} from '../rollup/types'; | ||
import Program from '../ast/nodes/Program'; | ||
import { TransformContext } from '../rollup/types'; | ||
|
||
function augmentCodeLocation<T extends RollupError | RollupWarning>({ |
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.
Very nice extractions!
@@ -130,7 +157,13 @@ export default class Graph { | |||
importedModule: string, | |||
importerStart?: number | |||
) => { | |||
return missingExport(importingModule.id, exportName, importedModule, importerStart); | |||
return missingExport.call( |
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.
Looking at this, it seems to me there is no reason at all why handleMissingExport
is called in Module
using the graph context.
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 same goes for the other hooks that are attached to graph: resolveDynamicImport
, isExternal
, ...
.concat(resolveId(options)) | ||
); | ||
|
||
this.pluginContext.resolveId = this.resolveId; | ||
|
||
const loaders = this.plugins.map(plugin => plugin.load).filter(Boolean); |
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 would prefer adding the context to the loaders here instead of having to remember this everywhere where this.load
is called, or graph.load
for that matter.
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.
With regards to this feedback on having the context bound - the issue here is that we then have to duplicate the types of everything as one type will have the 'this' binding and one type without. Personally I think the type maintenance is more work than just applying the right call, and TypeScript will error if the right call context isn't provided as well.
Yes I will pad out tests, let's first keep the review moving on the functionality if you don't mind? This PR is kind of tracking what I find I need to get a chunked CSS workflow going... I might try and sneak in a super simple asset emission API as well in the next couple of days (I wasn't going to do this, but it is useful for in-memory builds for eg SSR use cases). Then will lock down tests when we're agreed on functionality. Better than running the context setup through 'bind' I think it might even be easier to simply use a plugin driver instead of calling hooks directly. Would also help avoid duplicate typing issues. Let me see if I can make some progress on this. If we're doing renaming of onWrite and onGenerate, perhaps we can drop the "on" and just make the plugin hooks |
If we're going with |
Yes, I think the more concise form without "on" looks good for the hooks.
Sounds good to me! |
@lukastaegert just a quick update here - I'm getting pulled back into some other work for a bit, so I'm having to put this plugin and code splitting unification work on hold for at most a month. Will keep you posted when I'm back on this. The other PRs should all be good to land for now though. |
e33c418
to
1de6b19
Compare
I've finally got back to updating this PR. It also includes the code splitting refactoring and unification path now as well as well as a much simpler asset API than originally planned. To summarize (repeating from above as well):
|
Unfortunately I messed up the core use case here entirely of being able to substitute asset URLs... will revise next week - ignore for now. |
Closing to reopen with the new approach. |
So I've basically implemented the
transformChunk
hook ideas here from #2043, although with the following differences:transformBundle
remains exactly as it is, except taking a new argumentchunk
.transformChunk
is an exact alias fortransformBundle
onbuildstart
andonbuildend
, which are synchronous. This is important for lifecycle handling in plugins - my particular use case here is being able to create a worker pool in onbuildstart, and clean up the pool in onbuildend, otherwise we have no idea when to do the cleanup work and the process stalls indefinitely as the workers stay open.context
of the plugin like the transform plugin to the other plugins. This allows other plugin hooks to accessthis.error
,this.warn
,this.parse
andthis.resolveId
. This starts to refactor some of the plugin hooking, but I didn't go too deep into it. Hopefully we can leave the door open to further refactoring that could perhaps be based on a more generic "plugin driver" for generalised error messages / warnings etc instead of having the logic repeated everywhere, but this was a bit too much to attempt here.This supersedes #2115.
None of these changes are breaking, and then hopefully a further deprecation release can remove
transformBundle
entirely withtransformChunk
replacing it (which is really just a name change).With this extra chunk argument I think we should be set for CSS code splitting techniques in plugins.