UNPKG

babel-plugin-alias-config

Version:
345 lines (272 loc) 13.2 kB
"use strict"; function _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest(); } function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); } function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i]; return arr2; } function _iterableToArrayLimit(arr, i) { var _i = arr == null ? null : typeof Symbol !== "undefined" && arr[Symbol.iterator] || arr["@@iterator"]; if (_i == null) return; var _arr = []; var _n = true; var _d = false; var _s, _e; try { for (_i = _i.call(arr); !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"] != null) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; } const _require = require('path'), join = _require.join, resolve = _require.resolve, relative = _require.relative, isAbsolute = _require.isAbsolute, dirname = _require.dirname, basename = _require.basename; const _require2 = require('@babel/helper-plugin-utils'), declare = _require2.declare; const _require3 = require('@babel/core'), t = _require3.types; const fs = require('fs'); const template = require('lodash.template'); const some = require('lodash.some'); const findUp = require('find-up'); const escapeStringRegexp = require('escape-string-regexp'); const requireJSON5 = require('require-json5'); const REQUIRE = 'require'; const DEFAULT_CONFIG_NAMES = ['alias.config.js', 'app.config.js', 'tsconfig.json', 'jsconfig.json', 'webpack.config.js', 'webpack.config.babel.js']; function fileExists(path) { try { return !fs.accessSync(path, fs.F_OK); } catch (e) { return false; } } function getConfigPath(filename, configPaths, findConfig) { let conf = null; // Try all config paths and return for the first found one some(configPaths, configPath => { if (!configPath) return false; // 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, { cwd: dirname(filename), type: 'file' }); } if (resolvedConfigPath && fileExists(resolvedConfigPath)) { conf = resolvedConfigPath; } return conf; }, false); return conf; } const cached = {}; /** * * @param {string} filename * @param {string} source * @param {{ * configPath?: string, * findConfig?: boolean, * noOutputExtension?: boolean, * }} options * @returns */ function resolveAlias(filename, source) { let _ref = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}, _ref$configPath = _ref.configPath, configPath = _ref$configPath === void 0 ? '' : _ref$configPath, _ref$findConfig = _ref.findConfig, findConfig = _ref$findConfig === void 0 ? false : _ref$findConfig, _ref$noOutputExtensio = _ref.noOutputExtension, noOutputExtension = _ref$noOutputExtensio === void 0 ? false : _ref$noOutputExtensio; const configPaths = configPath ? [configPath].concat(DEFAULT_CONFIG_NAMES) : DEFAULT_CONFIG_NAMES; // Get webpack config const confPath = getConfigPath(filename, configPaths, findConfig); // If the config comes back as null, we didn't find it, so throw an exception. if (!confPath) return; // Because of babel-register, babel is actually run on webpack config files using themselves // as config, leading to odd errors if (filename === resolve(confPath)) return; const isTsconfig = /[jt]sconfig.json$/.test(confPath); let aliasConf; let extensionsConf; let cwd; let aliases; let cache = cached[confPath]; if (cache) { if (!cache.conf) return; if (cache.error) throw cache.error; // eslint-disable-next-line aliasConf = cache.aliasConf; // eslint-disable-next-line extensionsConf = cache.extensionsConf; // eslint-disable-next-line cwd = cache.cwd; // eslint-disable-next-line aliases = cache.aliases; } else { // Require the config let conf = isTsconfig ? requireJSON5(confPath) : 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; } cwd = dirname(confPath); cache = { conf, cwd }; cached[confPath] = cache; // In the case the webpack config is an es6 config, we need to get the default // eslint-disable-next-line if (conf && conf.__esModule && conf.default) { conf = conf.default; } if (isTsconfig) { aliasConf = conf.compilerOptions && conf.compilerOptions.paths; const baseUrl = conf.compilerOptions && conf.compilerOptions.baseUrl || '.'; if (aliasConf) { const tsconfigRegx = /(.*[^/*])(\/\*)?$/; aliasConf = Object.keys(aliasConf).reduce((p, key) => { const _ref2 = key.match(tsconfigRegx) || [], _ref3 = _slicedToArray(_ref2, 3), name = _ref3[1], nameSuffix = _ref3[2]; if (!name) return p; let aliasValue = aliasConf[key]; if (Array.isArray(aliasValue)) aliasValue = aliasValue[0] || ''; const _ref4 = aliasValue.match(tsconfigRegx) || [], _ref5 = _slicedToArray(_ref4, 3), _ref5$ = _ref5[1], value = _ref5$ === void 0 ? '' : _ref5$, _ref5$2 = _ref5[2], valueSuffix = _ref5$2 === void 0 ? '' : _ref5$2; p[`${name}${nameSuffix && valueSuffix ? '' : '$'}`] = resolve(cwd, baseUrl, value); return p; }, {}); } cache.extensionsConf = null; } else if (Array.isArray(conf)) { // Get the webpack alias config // 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); const alias = curr.alias || curr.resolve && curr.resolve.alias; if (alias) { Object.assign(next, alias); } return next; }, {}); // reduce the configs to a single extensions array extensionsConf = conf.reduce((prev, curr) => { const next = [].concat(prev); const extensions = curr.extensions || curr.resolve && curr.resolve.extensions || []; if (extensions.length) { extensions.forEach(ext => { if (next.indexOf(ext) === -1) { next.push(ext); } }); } return next; }, []); if (!extensionsConf.length) { extensionsConf = null; } } else { // the exported webpack config is a single object... // use it's resolve.alias property aliasConf = conf.alias || conf.resolve && conf.resolve.alias; // use it's resolve.extensions property, if available extensionsConf = conf.extensions || conf.resolve && conf.resolve.extensions || []; if (!extensionsConf) extensionsConf = null; } aliases = aliasConf ? Object.keys(aliasConf) : []; cache.aliases = aliases; cache.aliasConf = aliasConf; cache.extensionsConf = extensionsConf; } for (let alias of aliases) { let aliasDestination = aliasConf[alias]; const isFull = /\$$/.test(alias); if (isFull) alias = alias.substr(0, alias.length - 1); const regex = new RegExp(`^${escapeStringRegexp(alias)}${isFull ? '$' : '(\/|$)'}`); if (regex.test(source)) { // notModuleRegExp from https://github.com/webpack/enhanced-resolve/blob/master/lib/Resolver.js const notModuleRegExp = /^\.$|^\.[\\\/]|^\.\.$|^\.\.[\/\\]|^\/|^[A-Z]:[\\\/]/i; const isModule = !notModuleRegExp.test(aliasDestination); if (isModule) { return source.replace(alias, aliasDestination); } if (!isAbsolute(aliasDestination)) { aliasDestination = join(cwd, aliasDestination); } let relativeFilePath = relative(dirname(filename), aliasDestination); // 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 = source.replace(alias, relativeFilePath); // If the file is requiring the current directory which is the alias, 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 if (['.', '/'].indexOf(requiredFilePath[0]) === -1) { requiredFilePath = `./${requiredFilePath}`; } // TODO: should honor enforceExtension and then use extensionConf to make sure extension // In case the extension option is passed if (extensionsConf && !noOutputExtension) { // Get an absolute path to the file const absoluteRequire = join(aliasDestination, basename(source)); let extension = null; some(extensionsConf, ext => { if (!ext) return false; // If the file with this extension exists set it if (fileExists(absoluteRequire + ext)) { extension = ext; } return extension; }, false); // Set the extension to the file path, or keep the original one requiredFilePath += extension || ''; } return requiredFilePath.replace(/\\/g, '/'); } } } const plugin = declare(api => { api.assertVersion(7); return { name: 'babel-plugin-alias-config', visitor: { ImportDeclaration(path, state) { const filename = state.filename, _state$opts = state.opts, _state$opts2 = _state$opts === void 0 ? {} : _state$opts, _state$opts2$config = _state$opts2.config, configPath = _state$opts2$config === void 0 ? '' : _state$opts2$config, _state$opts2$findConf = _state$opts2.findConfig, findConfig = _state$opts2$findConf === void 0 ? false : _state$opts2$findConf, _state$opts2$noOutput = _state$opts2.noOutputExtension, noOutputExtension = _state$opts2$noOutput === void 0 ? false : _state$opts2$noOutput; const node = path.node; const filePath = node.source.value; const newFilename = resolveAlias(filename, filePath, { configPath, findConfig, noOutputExtension }); if (newFilename) node.source = t.StringLiteral(newFilename); }, CallExpression(path, _ref6) { let filename = _ref6.file.opts.filename, _ref6$opts = _ref6.opts, _ref6$opts2 = _ref6$opts === void 0 ? {} : _ref6$opts, _ref6$opts2$config = _ref6$opts2.config, configPath = _ref6$opts2$config === void 0 ? '' : _ref6$opts2$config, _ref6$opts2$findConfi = _ref6$opts2.findConfig, findConfig = _ref6$opts2$findConfi === void 0 ? false : _ref6$opts2$findConfi, _ref6$opts2$noOutputE = _ref6$opts2.noOutputExtension, noOutputExtension = _ref6$opts2$noOutputE === void 0 ? false : _ref6$opts2$noOutputE, _ref6$opts2$dynamicIm = _ref6$opts2.dynamicImport, dynamicImport = _ref6$opts2$dynamicIm === void 0 ? true : _ref6$opts2$dynamicIm; const nodeArguments = path.node.arguments; // If not a require statement do nothing if (t.isIdentifier(path.node.callee, { name: REQUIRE }) || dynamicImport && path.node.callee.type === 'Import') { // Make sure required value is a string if (nodeArguments.length === 0 || !t.isStringLiteral(nodeArguments[0])) { return; } const _nodeArguments = _slicedToArray(nodeArguments, 1), filePath = _nodeArguments[0].value; const newFilename = resolveAlias(filename, filePath, { configPath, findConfig, noOutputExtension }); if (newFilename) path.node.arguments = [t.StringLiteral(newFilename)]; } } } }; }); module.exports = plugin; module.exports.resolveAlias = resolveAlias;