Plugin system refactoring - #2382
Conversation
|
Note this is based to the asset-refactoring PR branch because that had some needed refactorings for the plugin context interface that were used here. |
|
This also happens to include #2375. |
|
Cool! However it's hard to read with big tabs. Could you also ignore |
| this.isExternal = options.external; | ||
| } else { | ||
| const ids = ensureArray(options.external); | ||
| this.isExternal = id => ids.indexOf(id) !== -1; |
There was a problem hiding this comment.
i realize that this is out of scope of this PR, but maybe this function could be memoized? could get some minor perf boost for bigger projects with a lot of externals
There was a problem hiding this comment.
The indexOf operation shouldn't be costly here at all, so the main optimization case would be user-provided isExternal functions. In those cases I think custom memoization might be best in the hook itself?
There was a problem hiding this comment.
An alternative would be to change this to ensureSet and call ids.has(id) - if we're concerned with speed.
|
|
||
| const err = new Error(props.message); | ||
| export default function error(base: Error | RollupError, props?: RollupError) { | ||
| if (base instanceof Error === false) base = Object.assign(new Error(base.message), base); |
There was a problem hiding this comment.
personally I think if (!(base instanceof Error)) { is less confusing, but ofc its just a personal taste of mine
| if (existingPluginNames.indexOf(plugin.name) !== -1) | ||
| error({ | ||
| code: 'DUPLICATE_PLUGIN_NAME', | ||
| message: `The plugin name ${ |
There was a problem hiding this comment.
while this is true - plugins should have unique names, I'm not sure if you should fail the build in this case - this might cause trouble for rollup users because of a rollup plugin author's mistake
also - it seems reasonable to i.e. use rollup-plugin-babel multiple times (with different babel configs on different paths - using exclude/include options)
|
Could u also address not always reporting Love errors getting more contextual info ❤️ |
|
Does it fix #2308? |
|
👍 from me! Looks like this addresses most of #2308 as well, as errors/warnings will get consistent |
There was a problem hiding this comment.
While I'm not a fan of the patterns that webpack chose in declaring hooks, I think the differences here work well. I'm also a huge fan of this being backwards compatible - not having to programmatically declare which hooks you want to use is a massive plus and good thing™️ for the developer experience.
This may be slight bikeshedding, but food for thought. The use of "hook" to define these is technically accurate by definition for most of the plugin hooks. However, the documentation up to this point has referred to them only as "properties". To provide distinction between Rollup and Webpack, and for clarity about what is technically a hook and what is not technically a hook (hooks seem to be by definition a function that can modify up/downstream), perhaps a different term could be considered. Nothing close to a serious concern, but wanted to toss that bit in there for posterity.
|
Yes this does fix #2308 as well I believe. |
|
@Andarist help with pinpointing the exact issue you're referring to there would be appreciated. |
|
The issue was that CLI swallows error.stack if error.frame is present. I believe while most users dont care about the stack its very useful for fixing problems upstream and should always be reported. As it being less useful for majority of users im OK with reporting it in grey color or smth (so it doesnt catch ur eye as easily) and keep red for message and frame |
6fb973d to
82b3213
Compare
|
I've rebased this to master, and included a Set fix for ensureArray. |
|
Among other things (some of them are resolved by this PR) - yes! Once this gets published I'm going to check the CLI error outputs, close my issue & open more focused ones (if needed) for stuff that didnt get addressed
Should I create a PR to this PR? 😄 |
|
@Andarist that would be a great help. |
|
I'll probably do it (wanted to do it anyway), not sure if I can manage to do it before this PR gets merged in though. Anyway - it's not blocking anything here, so don't want for me and I'll just do my thing when I get time to do it. |
lukastaegert
left a comment
There was a problem hiding this comment.
Now this is a PR that I just love! Especially the abstractions to how to run hooks in a unified way are just awesome!
Just some minor comments here and there.
| let writePromise: Promise<any> = Promise.resolve(); | ||
| for (const output of outputOptions) | ||
| writePromise = writePromise.then(() => <Promise<any>>bundle.write(output)); | ||
| return writePromise.then(() => bundle); |
There was a problem hiding this comment.
I like the simplification but I wonder if it is possible to run the writes in parallel via Promise.all or if this may break things. Changing the code did not create any red tests for me but if these are potential ordering issues for hooks, it may be hard to reproduce them in tests.
If there are, we should at least add a comment why this cannot be changed, otherwise I would change to a Promise.all for the performance benefit.
There was a problem hiding this comment.
That would be a perf benefit with no observable difference, well spotted, will add.
| parentId: string, | ||
| isResolved: boolean | ||
| ) => Promise<boolean | void> | boolean | void; | ||
| export type IsExternal = (id: string, parentId: string, isResolved: boolean) => boolean | void; |
There was a problem hiding this comment.
This is potentially breaking in that previously, it seems it was possible that users returned a Promise for the external option which would have propagated here while this is now prevented. However since I could find no documentation for this, I prefer this simplification.
There was a problem hiding this comment.
Previously we said in the type that it would permit a Promise, but the implementation didn't actually handle the promise. So this actually corrects the type to match what happens.
|
|
||
| return { intro, outro, banner, footer, hash }; | ||
| }); | ||
| function strOrFn(strOrFn: string | (() => string | Promise<string>)) { |
There was a problem hiding this comment.
Maybe normalizeStrOfFn or evaluateIfFn or similar as otherwise, it only describes the argument but not what we do with it. We could also explicitly add the return type to the signature so that it is immediately visible this generates either strings or Promises of string.
There was a problem hiding this comment.
Good suggestion.
| ].map(({ pluginName, source }, idx) => { | ||
| if (!source) return; | ||
| const reduceSep = (out: string, next: string) => (next ? `${out}\n${next}` : out); | ||
| const reduceDblSep = (out: string, next: string) => (next ? `${out}\n\n${next}` : out); |
There was a problem hiding this comment.
I know the name implies it is meant for a reduce but maybe concatSep and concatDblSep would work as well but tell us at a glance what the result is?
| if (typeof specifier === 'string') | ||
| return <Promise<string>>this.resolveId(specifier, parentId); | ||
| } | ||
| }; |
There was a problem hiding this comment.
Really nice idea handling it like this 👍
| export function getRollupDefaultPlugin(options: InputOptions): Plugin { | ||
| return { | ||
| name: 'Rollup Core', | ||
| resolveId: resolveId(options), |
There was a problem hiding this comment.
I wonder if we should rename the rhs function to getResolveId or createResolveId to distinguish it from the actual hook which is the return value of this function?
There was a problem hiding this comment.
Would probably trigger some renames in Graph as well, though.
| if (err.code) err.pluginCode = err.code; | ||
| err.code = 'PLUGIN_ERROR'; | ||
| err.plugin = plugin.name || '(anonymous plugin)'; | ||
| error(err); |
There was a problem hiding this comment.
Maybe the code to normalize warnings and errors should actually be extracted into a shared function so that this can become something like
error(normalizePluginError('PLUGIN_ERROR')) and `graph.warn(normalizePluginError('PLUGIN_WARNING'))?
There was a problem hiding this comment.
Agreed this should be a next step handling error centralization. Would prefer to do that in successive PR work.
| warning.plugin = plugin.name || '(anonymous plugin)'; | ||
| warning.hook = 'transform'; | ||
| graph.warn(warning); | ||
| }, |
There was a problem hiding this comment.
Again, the code for normalizing warnings and errors looks nearly identical. Maybe it could even be a central utility function to normalize warnings and errors from plugins with parameters to customize it for the respective hook and if it is a warning or error?
That way we ensure the warning/error handling is always consistent across hooks and have a single place to do changes?
There was a problem hiding this comment.
Again, I didn't want to get tooo deep into the errors right now. There's a lot that can be done here.
| } else { | ||
| const external = options.external; | ||
| const ids = new Set(Array.isArray(external) ? external : external ? [external] : []); | ||
| this.isExternal = id => ids.has(id); |
There was a problem hiding this comment.
Definitely an improvement! As all the ids are strings, we could also put them as keys into an object {[id: string]: true} for another very slight performance boost? Might be a micro-optimization, though.
There was a problem hiding this comment.
JS sets should be equal or better in performance according to theory.
There was a problem hiding this comment.
Definitely looks a little cleaner so let's follow the theory. Googling shows that results are definitely inconclusive which is not necessarily what I would have expected, so the cleaner solution should win here 👍
|
Thanks for the review! Yes this has been on the list for a while, was glad to have a reason to get to it. |
| } | ||
|
|
||
| export function createResolveId(options: InputOptions) { | ||
| function createResolveId(options: InputOptions) { |
There was a problem hiding this comment.
I removed the unnecessary export here which was probably an artefact.
This should be an almost-backwards-compatible refactoring, with some minor error differences that will be fine to release under a new 0.x version here.
Changes:
.cacheWe can certainly work on improving the output further for coming majors, and hopefully by unifying the code paths this will now be easier.
Closes #2250.
@shellscape should recognise the base plugin approach from Webpack well :)