All files index.js

91.4% Statements 85/93
84.34% Branches 70/83
85.71% Functions 6/7
94.38% Lines 84/89
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219  2x 2x 2x 2x 2x 2x   2x     102x 102x   6x         96x     96x 98x     98x     98x   91x   7x     98x 95x     98x     96x     2x 2x   737x 96x     96x           96x 1x         95x     95x     95x 7x       88x 7x       88x 1x             87x         2x 4x 4x 4x   4x       2x         2x 4x 4x             4x     2x 2x           85x     85x           87x     87x 4x           83x 353x   353x 353x     353x     50x 50x   50x 2x 2x       48x 21x   48x     48x 15x     48x       48x 5x       48x         48x   2x   2x 2x 4x     4x 1x     4x       2x     48x 48x                
 
import { join, resolve, relative, isAbsolute, dirname, basename, sep } from 'path';
import fs from 'fs';
import { StringLiteral } from 'babel-types';
import template from 'lodash.template';
import some from 'lodash.some';
import findUp from 'find-up';
 
const DEFAULT_CONFIG_NAMES = ['webpack.config.js', 'webpack.config.babel.js'];
 
function fileExists(path) {
    try {
        return !fs.accessSync(path, fs.F_OK);
    } catch (e) {
        return false;
    }
}
 
function getConfigPath(configPaths, findConfig) {
    let conf = null;
 
    // Try all config paths and return for the first found one
    some(configPaths, configPath => {
        Iif(!configPath) return;
 
        // Compile config using environment variables
        const compiledConfigPath = template(configPath)(process.env);
 
        let resolvedConfigPath;
        if(!findConfig) {
            // Get webpack config
            resolvedConfigPath = resolve(process.cwd(), compiledConfigPath);
        } else {
            resolvedConfigPath = findUp.sync(compiledConfigPath);
        }
 
        if(resolvedConfigPath && fileExists(resolvedConfigPath)) {
            conf = resolvedConfigPath;
        }
 
        return conf;
    });
 
    return conf;
}
 
export default function({ types: t }) {
    return {
        visitor: {
            CallExpression(path, {file: { opts: { filename } }, opts: { config: configPath, findConfig: findConfig = false, noOutputExtension = false } = {} }) {
                const configPaths = configPath ? [configPath, ...DEFAULT_CONFIG_NAMES] : DEFAULT_CONFIG_NAMES;
 
                // Get webpack config
                const confPath = getConfigPath(
                    configPaths,
                    findConfig
                );
 
                // If the config comes back as null, we didn't find it, so throw an exception.
                if(!confPath) {
                    throw new Error(`Cannot find any of these configuration files: ${configPaths.join(', ')}`);
                }
 
                // Because of babel-register, babel is actually run on webpack config files using themselves
                // as config, leading to odd errors
                Iif(filename === resolve(confPath)) return;
 
                // Require the config
                let conf = require(confPath);
 
                // if the object is empty, we might be in a dependency of the config - bail without warning
                if (!Object.keys(conf).length) {
                    return;
                }
 
                // In the case the webpack config is an es6 config, we need to get the default
                if (conf && conf.__esModule && conf.default) {
                    conf = conf.default;
                }
 
                // exit if there's no alias config and the config is not an array
                if(!(conf.resolve && conf.resolve.alias) && !Array.isArray(conf)) {
                    throw new Error('The resolved config file doesn\'t contain a resolve configuration');
                }
 
                // Get the webpack alias config
                let aliasConf;
                let extensionsConf;
 
                if (Array.isArray(conf)) {
                    // the exported webpack config is an array ...
                    // (i.e., the project is using webpack's multicompile feature) ...
 
                    // reduce the configs to a single alias object
                    aliasConf = conf.reduce((prev, curr) => {
                        const next = Object.assign({}, prev);
                        Eif (curr.resolve && curr.resolve.alias) {
                            Object.assign(next, curr.resolve.alias);
                        }
                        return next;
                    }, {});
 
                    // if the object is empty, bail
                    Iif (!Object.keys(aliasConf).length) {
                        return;
                    }
 
                    // reduce the configs to a single extensions array
                    extensionsConf = conf.reduce((prev, curr) => {
                        const next = [].concat(prev);
                        Iif (curr.resolve && curr.resolve.extensions && curr.resolve.extensions.length) {
                            curr.resolve.extensions.forEach(ext => {
                                if (next.indexOf(ext) === -1) {
                                    next.push(ext);
                                }
                            });
                        }
                        return next;
                    }, []);
 
                    Eif (!extensionsConf.length) {
                        extensionsConf = null;
                    }
                } else {
                    // the exported webpack config is a single object...
 
                    // use it's resolve.alias property
                    aliasConf = conf.resolve.alias;
 
                    // use it's resolve.extensions property, if available
                    extensionsConf =
                        (conf.resolve.extensions && conf.resolve.extensions.length) ?
                        conf.resolve.extensions :
                        null;
                }
 
                const { callee: { name: calleeName }, arguments: args } = path.node;
 
                // Exit if it's not a require statement
                if (calleeName !== 'require' || !args.length || !t.isStringLiteral(args[0])) {
                    return;
                }
 
                // Get the path of the StringLiteral
                const [{ value: filePath }] = args;
 
                for(const aliasFrom in aliasConf) {
                    Eif(aliasConf.hasOwnProperty(aliasFrom)) {
 
                        let aliasTo = aliasConf[aliasFrom];
                        const regex = new RegExp(`^${aliasFrom}(\/|$)`);
 
                        // If the regex matches, replace by the right config
                        if(regex.test(filePath)) {
 
                            // notModuleRegExp from https://github.com/webpack/enhanced-resolve/blob/master/lib/Resolver.js
                            const notModuleRegExp = /^\.$|^\.[\\\/]|^\.\.$|^\.\.[\/\\]|^\/|^[A-Z]:[\\\/]/i;
                            const isModule = !notModuleRegExp.test(aliasTo);
 
                            if(isModule) {
                                path.node.arguments = [StringLiteral(aliasTo)];
                                return;
                            }
 
                            // If the filepath is not absolute, make it absolute
                            if(!isAbsolute(aliasTo)) {
                                aliasTo = join(process.cwd(), aliasTo);
                            }
                            let relativeFilePath = relative(dirname(filename), aliasTo).split(sep).join('/');
 
                            // In case the file path is the root of the alias, need to put a dot to avoid having an absolute path
                            if(relativeFilePath.length === 0) {
                                relativeFilePath = '.';
                            }
 
                            let requiredFilePath = filePath.replace(aliasFrom, relativeFilePath);
 
                            // In the unfortunate case of a file requiring the current directory which is the alias, we need to add
                            // an extra slash
                            if(requiredFilePath === '.') {
                                requiredFilePath = './';
                            }
 
                            // In the case of a file requiring a child directory of the current directory, we need to add a dot slash
                            Iif (['.','/'].indexOf(requiredFilePath[0]) === -1) {
                                requiredFilePath = `./${requiredFilePath}`;
                            }
 
                            // In case the extension option is passed
                            if(extensionsConf && !noOutputExtension) {
                                // Get an absolute path to the file
                                const absoluteRequire = join(aliasTo, basename(filePath));
 
                                let extension = null;
                                some(extensionsConf, ext => {
                                    Iif(!ext) return;
 
                                    // If the file with this extension exists set it
                                    if(fileExists(absoluteRequire + ext)) {
                                        extension = ext;
                                    }
 
                                    return extension;
                                });
 
                                // Set the extension to the file path, or keep the original one
                                requiredFilePath += extension || '';
                            }
 
                            path.node.arguments = [StringLiteral(requiredFilePath)];
                            return;
                        }
                    }
                }
            },
        },
    };
}