Skip to content
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

Update extractTextPlugin transformation #390

Merged
merged 6 commits into from
Apr 18, 2018
Merged

Update extractTextPlugin transformation #390

merged 6 commits into from
Apr 18, 2018

Conversation

bitpshr
Copy link
Member

@bitpshr bitpshr commented Apr 9, 2018

What kind of change does this PR introduce?
Feature

Did you add tests for your changes?
Yes

If relevant, did you update the documentation?
N/A

Summary
After some discussion the decision was made to update our migration transformations to actively support migrating only from the previous version to the current version, which currently means v3 - v4. Migrating older versions such as v1 or v2 will no longer be supported. This PR begins this work by updating the extractTextPlugin transformation. Resolves #373.

Note: v3 configurations that contain multiple ExtractTextPlugin instances are not yet supported due to a limitation with MiniCssExtractPlugin.

Does this PR introduce a breaking change?
Yes. This PR changes ExtractTextPlugin migration behavior and produces a v4-specific configuration.

Other information
This PR only includes updates to the extractTextPlugin transformation. The following issues have been opened for the remaining transformations:

use: ExtractTextPlugin.extract('style-loader', 'css-loader')
use: ExtractTextPlugin.extract({
use: 'css-loader',
fallback: ['style-loader', { loader: 'test-object-loader' }],
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried to fully demonstrate that string, object, and array loader configurations all work via this test case.

// Add new loader
loaderPath && Object.assign(loaderPath, {
value: currentFallbackLoader ?
j.conditionalExpression(
Copy link
Member Author

@bitpshr bitpshr Apr 9, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MiniCssExtractPlugin does not support fallback and disabled options. Instead, users should manually swap out MiniCssExtractPlugin.loader with the loader that was normally used as a fallback, which is what we're doing with this ternary. More information can be found here.

@matheus1lva
Copy link
Member

matheus1lva commented Apr 9, 2018

I found some problems. I have this configuration (that was not created by me) and i think it is a good "point out of the curve", since people can still have things like this: https://gist.github.com/PlayMa256/0a51b41e1b0eba910869764f378bf932

This was the generated config: https://gist.github.com/PlayMa256/587b2d3890c9c96e3f3dbe567c8ecfce

As you can see, few problems occurred:

  • It has 2 empty variables, which were kept in the plugins list.
  • MiniCssPlugin has null parameters. I think it is better to remove them other then having them null.
  • Is there a way to remove the extractCSS.extract statements and replace with MiniCssExtractPlugin.loader?

@bitpshr
Copy link
Member Author

bitpshr commented Apr 9, 2018

Good catch and feedback. On it now.

@ematipico ematipico added the Semver: minor ⚙️ When delivering new features that don't break label Apr 9, 2018
@matheus1lva
Copy link
Member

matheus1lva commented Apr 9, 2018

Well, the null problems seems to be gone. In the example i sent you (which is kind absurd i
admit), it still have (extractSCSS.extract...). We can't control that much, neither handle the problem in this case, where there are fallbacks and such.

Could you create a test for multiple loaders in a rule?
I mean:

[
          'style-loader',
          'css-loader',
          ExtractTextPlugin...
]

A scenario similar to this? That could show problems if it fails.

@bitpshr
Copy link
Member Author

bitpshr commented Apr 9, 2018

@playma256 @ematipico I updated the logic so options are only passed if they're defined and null is never passed. I also updated the tests to verify cases when ExtractTextPlugin.extract is an argument to a function call (like your example, ['css-hot-loader'].concat(ExtractTextPlugin.extract(...)), and cases when ExtractTextPlugin.extract is a member of an array (like your multiple loaders scenario above.) All are working correctly.

After digging into multiple-instance support a bit more, it looks like we need to only support migrating configurations with a single instance of ExtractTextPlugin until true multiple-instance support lands in MiniCssExtractPlugin. See webpack-contrib/mini-css-extract-plugin#45. Until that happens, it's not possible to translate per-instance options such as filename. We could technically translate extractCss.extract-type call statements to ExtractTextPlugin.extract statements, but it wouldn't accomplish much since MiniCssExtractPlugin only supports one definition in the plugins array for now.

This is the best we can do for now and we can put a caveat in the documentation until webpack-contrib/mini-css-extract-plugin#45 is resolved.

@matheus1lva
Copy link
Member

Oh, got it! I did not know about this problem, i just found this tricky webpack config and it could be a nice way to debug this feature :). No problem not supporting this, we could insert this disclaimer in the docs. I'm about to go home, as i arrive and have time to test, i can give you more feedback if necessary! Good job @billyjanitsch !!

@evenstensberg
Copy link
Member

@ematipico this is a nice PR for you to test & to get more knowledge of the migrate internals. Easy testable as well.

@ematipico ematipico self-requested a review April 12, 2018 12:01
Copy link
Member

@evenstensberg evenstensberg left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you abstract this into JScodeshift utilities? A loot of specific code for the nodes, would be nice to use things like j(p.identifier) etc

@bitpshr
Copy link
Member Author

bitpshr commented Apr 13, 2018

Thanks, I moved most functionality to ast-utils and made it generic so other transformations can use it going forward. This should be ready for another review when you have time @ev1stensberg @ematipico.

*
* @param {Object} j - jscodeshift top-level import
* @param {Node} ast - jscodeshift ast to transform
* @returns {Node} ast - jscodeshift ast
*/

module.exports = function(j, ast) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

named functions

* @param {any} j — jscodeshift API
* @param {Node} node - Node to start search from
* @param {String} pluginName - name of the plugin to remove
* @param {Node | Void} - path to the root webpack configuration object if plugin is found
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing return

* Calls an function with a path wrapped in an array, using path.elements if available
*
* @param {NodePath} path - path to wrap in array
* @param {Function} action - called with array path
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing return

Copy link
Member Author

@bitpshr bitpshr Apr 14, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added @returns {Void}.

)
);
p.value.arguments = [newArgs];
const devLoaders = [];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you abstract all this into more functions that can be reused, like find value "filename" for a given prop

Copy link
Member Author

@bitpshr bitpshr Apr 14, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure I follow; we already abstracted functionality into simple, single-purpose functions like getPluginOptionValue and getLoaderOptionValue. If we start getting even more abstract than this, we risk targeting nodes that aren't actually the nodes we care about. For example, someone could define filename completely outside of the webpack configuration object for unrelated purposes, so we need to be very careful which filename identifier we actually target.

The functions I already pulled out (createPluginByName, removePluginByName, getLoaderOptionValue, getPluginOptionValue, and removeRequire) are reusable and safe.

@webpack-bot
Copy link

@bitpshr Thanks for your update.

I labeled the Pull Request so reviewers will review it again.

@ev1stensberg Please review the new changes.

let pluginExists;
let requireExists;

const filename = getPluginOptionValue(j, ast, "ExtractTextPlugin", "filename");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you extract these into more functions that could be reused?

* @param {any} j — jscodeshift API
* @param {Node} node - Node to start search from
* @param {String} pluginName - ex. `webpack.LoaderOptionsPlugin`
* @param {Object} options - plugin options
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

returns?

* @param {any} j — jscodeshift API
* @param {Node} node - Node to start search from
* @param {String} constName - Name of require
* @param {String} packagePath - path of required package
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

returns

@bitpshr
Copy link
Member Author

bitpshr commented Apr 14, 2018

@ev1stensberg thanks for the reviews. Everything has been addressed except for the following comment:

Could you extract these into more functions that could be reused?

What type of functions were you imagining? I already abstracted almost all logic into ast-utils so that this transform is mostly one-liner calls into ast-utils. The functions I already added (createPluginByName, removePluginByName, getLoaderOptionValue, getPluginOptionValue, and removeRequire) are reusable and safe, and as abstract as any other function in ast-utils.

@bitpshr
Copy link
Member Author

bitpshr commented Apr 16, 2018

Thanks @ematipico. Could you please see my comment above and take another look when you have a chance @ev1stensberg? Thanks in advance!

pluginExists && createPluginByName(j, ast, "MiniCssExtractPlugin", loaderOptions);

// Remove old loader
ast.find(j.Identifier, { name: "ExtractTextPlugin" })
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could be abstracted into an util, maybe you can make it general so that we can reuse it for later?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I pulled most of this logic out into a new findLoaderFunction utility function. It allows us to target loaders that use call expressions, e.g. use: SomeIdentifier(...). I could not pull everything out into a removeLoaderFunction because we need information during the traversal. This is the best we can do.

});

// Add new loader
const loader = currentFallbackLoader ?
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This might be another util, addLoader


// Add new loader
const loader = currentFallbackLoader ?
j.conditionalExpression(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might be too specific, is there another way of adding a loader?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I created an addConditionalLoader utility function per your comment above. I think it'll be useful in the long run, as it makes it easy to add loaders based on NODE_ENV.

@bitpshr
Copy link
Member Author

bitpshr commented Apr 17, 2018

Thanks @ev1stensberg. This should be as generic as possible now. Let me know your thoughts.

@webpack-bot
Copy link

Thank you for your pull request! The most important CI builds succeeded, we’ll review the pull request soon.

Copy link
Member

@evenstensberg evenstensberg left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, we can always change it later :shipit:

@bitpshr
Copy link
Member Author

bitpshr commented Apr 18, 2018

Thanks @ev1stensberg. Rebased.

@ematipico ematipico merged commit 936b80c into webpack:master Apr 18, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
PR: CI-ok PR: review-outdated Semver: minor ⚙️ When delivering new features that don't break
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants