123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379 |
- const utils = require("../../utils/ast-utils");
- /**
- *
- * Transform for loaders. Transforms pre- and postLoaders into enforce options,
- * moves loader configuration into rules array, transforms query strings and
- * props into loader options, and adds -loader suffix to loader names.
- *
- * @param {Object} j - jscodeshift top-level import
- * @param {Node} ast - jscodeshift ast to transform
- * @returns {Node} ast - jscodeshift ast
- */
- module.exports = function(j, ast) {
- /**
- * Creates an Array expression out of loaders string
- *
- *
- * For syntaxes like
- *
- * {
- * loader: 'style!css`
- * }
- *
- * or
- *
- * {
- * loaders: ['style', 'css']
- * }
- *
- * or
- *
- * loaders: [{
- * loader: 'style'
- * },
- * {
- * loader: 'css',
- * }]
- *
- * it should generate
- *
- * {
- * use: [{
- * loader: 'style'
- * },
- * {
- * loader: 'css'
- * }]
- * }
- *
- * @param {Node} path - object expression ast
- * @returns {Node} path - object expression ast with array expression instead of loaders string
- */
- const createArrayExpressionFromArray = function(path) {
- const value = path.value;
- // Find paths with `loaders` keys in the given Object
- const paths = value.properties.filter(prop =>
- prop.key.name.startsWith("loader")
- );
- // For each pair of key and value
- paths.forEach(pair => {
- // Replace 'loaders' Identifier with 'use'
- pair.key.name = "use";
- // If the value is an Array
- if (pair.value.type === j.ArrayExpression.name) {
- // replace its elements
- const pairValue = pair.value;
- pair.value = j.arrayExpression(
- pairValue.elements.map(arrElement => {
- // If items of the array are Strings
- if (arrElement.type === j.Literal.name) {
- // Replace with `{ loader: LOADER }` Object
- return j.objectExpression([
- utils.createProperty(j, "loader", arrElement.value)
- ]);
- }
- // otherwise keep the existing element
- return arrElement;
- })
- );
- // If the value is String of loaders like 'style!css'
- } else if (pair.value.type === j.Literal.name) {
- // Replace it with Array expression of loaders
- const literalValue = pair.value;
- pair.value = j.arrayExpression(
- literalValue.value.split("!").map(loader => {
- return j.objectExpression([
- utils.createProperty(j, "loader", loader)
- ]);
- })
- );
- }
- });
- return path;
- };
- /**
- *
- * Puts query parameters from loader value into options object
- *
- * @param {Node} p - object expression ast for loader object
- * @returns {Node} objectExpression - an new object expression ast containing the query parameters
- */
- const createLoaderWithQuery = p => {
- let properties = p.value.properties;
- let loaderValue = properties.reduce(
- (val, prop) => (prop.key.name === "loader" ? prop.value.value : val),
- ""
- );
- let loader = loaderValue.split("?")[0];
- let query = loaderValue.split("?")[1];
- let options = query.split("&").map(option => {
- const param = option.split("=");
- const key = param[0];
- const val = param[1] || true; // No value in query string means it is truthy value
- return j.objectProperty(j.identifier(key), utils.createLiteral(j, val));
- });
- let loaderProp = utils.createProperty(j, "loader", loader);
- let queryProp = j.property(
- "init",
- j.identifier("options"),
- j.objectExpression(options)
- );
- return j.objectExpression([loaderProp, queryProp]);
- };
- /**
- *
- * Determine whether a loader has a query string
- *
- * @param {Node} p - object expression ast for loader object
- * @returns {Boolean} hasLoaderQueryString - whether the loader object contains a query string
- */
- const findLoaderWithQueryString = p => {
- return p.value.properties.reduce((predicate, prop) => {
- return (
- (utils.safeTraverse(prop, ["value", "value", "indexOf"]) &&
- prop.value.value.indexOf("?") > -1) ||
- predicate
- );
- }, false);
- };
- /**
- * Check if the identifier is the `loaders` prop in the `module` object.
- * If the path value is `loaders` and it’s located in `module` object
- * we assume it’s the loader's section.
- *
- * @param {Node} path - identifier ast
- * @returns {Boolean} isLoadersProp - whether the identifier is the `loaders` prop in the `module` object
- */
- const checkForLoader = path =>
- path.value.name === "loaders" &&
- utils.safeTraverse(path, [
- "parent",
- "parent",
- "parent",
- "node",
- "key",
- "name"
- ]) === "module";
- /**
- * Puts pre- or postLoader into `loaders` object and adds the appropriate `enforce` property
- *
- * @param {Node} p - object expression ast that has a key for either 'preLoaders' or 'postLoaders'
- * @returns {Node} p - object expression with a `loaders` object and appropriate `enforce` properties
- */
- const fitIntoLoaders = p => {
- let loaders;
- p.value.properties.map(prop => {
- const keyName = prop.key.name;
- if (keyName === "loaders") {
- loaders = prop.value;
- }
- });
- p.value.properties.map(prop => {
- const keyName = prop.key.name;
- if (keyName !== "loaders") {
- const enforceVal = keyName === "preLoaders" ? "pre" : "post";
- prop.value.elements.map(elem => {
- elem.properties.push(utils.createProperty(j, "enforce", enforceVal));
- if (loaders && loaders.type === "ArrayExpression") {
- loaders.elements.push(elem);
- } else {
- prop.key.name = "loaders";
- }
- });
- }
- });
- if (loaders) {
- p.value.properties = p.value.properties.filter(
- prop => prop.key.name === "loaders"
- );
- }
- return p;
- };
- /**
- * Find pre and postLoaders in the ast and put them into the `loaders` array
- *
- * @returns {Node} ast - jscodeshift ast
- */
- const prepostLoaders = () =>
- ast
- .find(j.ObjectExpression)
- .filter(p => utils.findObjWithOneOfKeys(p, ["preLoaders", "postLoaders"]))
- .forEach(fitIntoLoaders);
- /**
- * Convert top level `loaders` to `rules`
- *
- * @returns {Node} ast - jscodeshift ast
- */
- const loadersToRules = () =>
- ast
- .find(j.Identifier)
- .filter(checkForLoader)
- .forEach(p => (p.value.name = "rules"));
- /**
- * Convert `loader` and `loaders` to Array of {Rule.Use}
- *
- * @returns {Node} ast - jscodeshift ast
- */
- const loadersToArrayExpression = () =>
- ast
- .find(j.ObjectExpression)
- .filter(path => utils.findObjWithOneOfKeys(path, ["loader", "loaders"]))
- .filter(
- path =>
- utils.safeTraverse(path, [
- "parent",
- "parent",
- "node",
- "key",
- "name"
- ]) === "rules"
- )
- .forEach(createArrayExpressionFromArray);
- /**
- * Find loaders with options encoded as a query string and replace the string with an options object
- *
- * i.e. for loader like
- *
- * {
- * loader: 'css?modules&importLoaders=1&string=test123'
- * }
- *
- * it should generate
- * {
- * loader: 'css-loader',
- * options: {
- * modules: true,
- * importLoaders: 1,
- * string: 'test123'
- * }
- * }
- *
- * @returns {Node} ast - jscodeshift ast
- */
- const loaderWithQueryParam = () =>
- ast
- .find(j.ObjectExpression)
- .filter(p => utils.findObjWithOneOfKeys(p, ["loader"]))
- .filter(findLoaderWithQueryString)
- .replaceWith(createLoaderWithQuery);
- /**
- * Find nodes with a `query` key and replace it with `options`
- *
- * i.e. for
- * {
- * query: { ... }
- * }
- *
- * it should generate
- *
- * {
- * options: { ... }
- * }
- *
- * @returns {Node} ast - jscodeshift ast
- */
- const loaderWithQueryProp = () =>
- ast
- .find(j.Identifier)
- .filter(p => p.value.name === "query")
- .replaceWith(j.identifier("options"));
- /**
- * Add required `-loader` suffix to a loader with missing suffix
- * e.g. for `babel` it should generate `babel-loader`
- *
- * @returns {Node} ast - jscodeshift ast
- */
- const addLoaderSuffix = () =>
- ast.find(j.ObjectExpression).forEach(path => {
- path.value.properties.forEach(prop => {
- if (
- prop.key.name === "loader" &&
- utils.safeTraverse(prop, ["value", "value"]) &&
- !prop.value.value.endsWith("-loader")
- ) {
- prop.value = j.literal(prop.value.value + "-loader");
- }
- });
- });
- /**
- *
- * Puts options object outside use object into use object
- *
- * @param {Node} p - object expression ast that has a key for either 'options' or 'use'
- * @returns {Node} objectExpression - an use object expression ast containing the options and loader
- */
- const fitOptionsToUse = p => {
- let options;
- p.value.properties.forEach(prop => {
- const keyName = prop.key.name;
- if (keyName === "options") {
- options = prop;
- }
- });
- if (options) {
- p.value.properties = p.value.properties.filter(
- prop => prop.key.name !== "options"
- );
- p.value.properties.forEach(prop => {
- const keyName = prop.key.name;
- if (keyName === "use") {
- prop.value.elements[0].properties.push(options);
- }
- });
- }
- return p;
- };
- /**
- * Move `options` inside the Array of {Rule.Use}
- *
- * @returns {Node} ast - jscodeshift ast
- */
- const moveOptionsToUse = () =>
- ast
- .find(j.ObjectExpression)
- .filter(p => utils.findObjWithOneOfKeys(p, ["use"]))
- .forEach(fitOptionsToUse);
- const transforms = [
- prepostLoaders,
- loadersToRules,
- loadersToArrayExpression,
- loaderWithQueryParam,
- loaderWithQueryProp,
- addLoaderSuffix,
- moveOptionsToUse
- ];
- transforms.forEach(t => t());
- return ast;
- };
|