From 9dfd7b85d0f370277f58ac885aba42c4bc949fa8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Ruci=C5=84ski?= Date: Sat, 1 Apr 2017 22:42:21 +0200 Subject: [PATCH] feat: Process alias and regexps together (#5) Breaking change: "npm:" support is dropped. Breaking change: package-like aliases are additionally resolved using root and cwd. Breaking change: relative aliases are left as-is to be consistend with how `getRealPath` treats relative paths. --- src/getRealPath.js | 85 ++++++++++++------------------------ src/index.js | 94 ++-------------------------------------- src/normalizeOptions.js | 96 +++++++++++++++++++++++++++++++++++++++++ test/index.test.js | 34 +++++++-------- 4 files changed, 144 insertions(+), 165 deletions(-) create mode 100644 src/normalizeOptions.js diff --git a/src/getRealPath.js b/src/getRealPath.js index 98b2689..35a3053 100644 --- a/src/getRealPath.js +++ b/src/getRealPath.js @@ -6,12 +6,12 @@ import mapToRelative from 'mapToRelative'; import { toLocalPath, toPosixPath, replaceExtension } from 'utils'; -function findPathInRoots(sourcePath, rootDirs, extensions) { - // Search the source path inside every custom root directory +function findPathInRoots(sourcePath, { extensions, root }) { let resolvedSourceFile; - rootDirs.some((basedir) => { + + root.some((basedir) => { try { - // check if the file exists (will throw if not) + // Check if the file exists (will throw if not) resolvedSourceFile = requireResolve.sync(`./${sourcePath}`, { basedir, extensions, @@ -25,8 +25,8 @@ function findPathInRoots(sourcePath, rootDirs, extensions) { return resolvedSourceFile; } -function getRealPathFromRootConfig(sourcePath, absCurrentFile, rootDirs, cwd, extensions) { - const absFileInRoot = findPathInRoots(sourcePath, rootDirs, extensions); +function getRealPathFromRootConfig(sourcePath, currentFile, opts) { + const absFileInRoot = findPathInRoots(sourcePath, opts); if (!absFileInRoot) { return null; @@ -35,48 +35,18 @@ function getRealPathFromRootConfig(sourcePath, absCurrentFile, rootDirs, cwd, ex const realSourceFileExtension = extname(absFileInRoot); const sourceFileExtension = extname(sourcePath); - // map the source and keep its extension if the import/require had one + // Map the source and keep its extension if the import/require had one const ext = realSourceFileExtension === sourceFileExtension ? realSourceFileExtension : ''; return toLocalPath(toPosixPath(replaceExtension( - mapToRelative(cwd, absCurrentFile, absFileInRoot), + mapToRelative(opts.cwd, currentFile, absFileInRoot), ext, ))); } -function getRealPathFromAliasConfig(sourcePath, absCurrentFile, alias, cwd) { - const moduleSplit = sourcePath.split('/'); - - let aliasPath; - while (moduleSplit.length) { - const m = moduleSplit.join('/'); - if ({}.hasOwnProperty.call(alias, m)) { - aliasPath = alias[m]; - break; - } - moduleSplit.pop(); - } - - // no alias mapping found - if (!aliasPath) { - return null; - } - - // remove legacy "npm:" prefix for npm packages - aliasPath = aliasPath.replace(/^(npm:)/, ''); - const newPath = sourcePath.replace(moduleSplit.join('/'), aliasPath); - - // alias to npm module don't need relative mapping - if (aliasPath[0] !== '.') { - return newPath; - } - - return toLocalPath(toPosixPath(mapToRelative(cwd, absCurrentFile, newPath))); -} - -function getRealPathFromRegExpConfig(sourcePath, regExps) { +function getRealPathFromAliasConfig(sourcePath, currentFile, opts) { let aliasedSourceFile; - regExps.find(([regExp, substitute]) => { + opts.alias.find(([regExp, substitute]) => { const execResult = regExp.exec(sourcePath); if (execResult === null) { @@ -87,6 +57,19 @@ function getRealPathFromRegExpConfig(sourcePath, regExps) { return true; }); + if (!aliasedSourceFile) { + return null; + } + + if (aliasedSourceFile[0] === '.') { + return aliasedSourceFile; + } + + const realPathFromRoot = getRealPathFromRootConfig(aliasedSourceFile, currentFile, opts); + if (realPathFromRoot) { + return realPathFromRoot; + } + return aliasedSourceFile; } @@ -95,33 +78,19 @@ export default function getRealPath(sourcePath, { file, opts }) { return sourcePath; } - // file param is a relative path from the environment current working directory + // File param is a relative path from the environment current working directory // (not from cwd param) - const currentFile = file.opts.filename; - const absCurrentFile = resolve(currentFile); - - const { cwd, root, extensions, alias, regExps } = opts; + const currentFile = resolve(file.opts.filename); - const sourceFileFromRoot = getRealPathFromRootConfig( - sourcePath, absCurrentFile, root, cwd, extensions, - ); + const sourceFileFromRoot = getRealPathFromRootConfig(sourcePath, currentFile, opts); if (sourceFileFromRoot) { return sourceFileFromRoot; } - const sourceFileFromAlias = getRealPathFromAliasConfig( - sourcePath, absCurrentFile, alias, cwd, - ); + const sourceFileFromAlias = getRealPathFromAliasConfig(sourcePath, currentFile, opts); if (sourceFileFromAlias) { return sourceFileFromAlias; } - const sourceFileFromRegExp = getRealPathFromRegExpConfig( - sourcePath, regExps, - ); - if (sourceFileFromRegExp) { - return sourceFileFromRegExp; - } - return sourcePath; } diff --git a/src/index.js b/src/index.js index 5c59c5a..e6421a6 100644 --- a/src/index.js +++ b/src/index.js @@ -1,96 +1,8 @@ -import fs from 'fs'; -import { dirname, resolve } from 'path'; - -import findBabelConfig from 'find-babel-config'; -import glob from 'glob'; - +import normalizeOptions from 'normalizeOptions'; import transformCall from 'transformers/call'; import transformImport from 'transformers/import'; -const defaultExtensions = ['.js', '.jsx', '.es', '.es6']; - -function isRegExp(string) { - return string.startsWith('^') || string.endsWith('$'); -} - -function normalizeCwd(file) { - const { opts } = this; - - if (opts.cwd === 'babelrc') { - const startPath = (file.opts.filename === 'unknown') - ? './' - : file.opts.filename; - - const { file: babelPath } = findBabelConfig.sync(startPath); - - opts.cwd = babelPath - ? dirname(babelPath) - : null; - } - - if (!opts.cwd) { - opts.cwd = process.cwd(); - } -} - -function normalizePluginOptions(file) { - const { opts } = this; - - normalizeCwd.call(this, file); - - if (opts.root) { - if (!Array.isArray(opts.root)) { - opts.root = [opts.root]; - } - - opts.root = opts.root - .map(dirPath => resolve(opts.cwd, dirPath)) - .reduce((resolvedDirs, absDirPath) => { - if (glob.hasMagic(absDirPath)) { - const roots = glob.sync(absDirPath) - .filter(path => fs.lstatSync(path).isDirectory()); - - return [...resolvedDirs, ...roots]; - } - - return [...resolvedDirs, absDirPath]; - }, []); - } else { - opts.root = []; - } - - opts.regExps = []; - - if (opts.alias) { - Object.keys(opts.alias) - .filter(isRegExp) - .forEach((key) => { - const parts = opts.alias[key].split('\\\\'); - - function substitute(execResult) { - return parts - .map(part => - part.replace(/\\\d+/g, number => execResult[number.slice(1)] || ''), - ) - .join('\\'); - } - - opts.regExps.push([new RegExp(key), substitute]); - - delete opts.alias[key]; - }); - } else { - opts.alias = {}; - } - - if (!opts.extensions) { - opts.extensions = defaultExtensions; - } - - return opts; -} - export default ({ types }) => { const importVisitors = { CallExpression(nodePath, state) { @@ -105,7 +17,9 @@ export default ({ types }) => { }; return { - pre: normalizePluginOptions, + pre(file) { + normalizeOptions(this.opts, file); + }, visitor: { Program: { diff --git a/src/normalizeOptions.js b/src/normalizeOptions.js new file mode 100644 index 0000000..eab7a2e --- /dev/null +++ b/src/normalizeOptions.js @@ -0,0 +1,96 @@ +import fs from 'fs'; +import { dirname, resolve } from 'path'; + +import findBabelConfig from 'find-babel-config'; +import glob from 'glob'; + + +const defaultExtensions = ['.js', '.jsx', '.es', '.es6']; + +function isRegExp(string) { + return string.startsWith('^') || string.endsWith('$'); +} + +function normalizeCwd(opts, file) { + if (opts.cwd === 'babelrc') { + const startPath = (file.opts.filename === 'unknown') + ? './' + : file.opts.filename; + + const { file: babelPath } = findBabelConfig.sync(startPath); + + opts.cwd = babelPath + ? dirname(babelPath) + : null; + } + + if (!opts.cwd) { + opts.cwd = process.cwd(); + } +} + +function normalizeRoot(opts) { + if (opts.root) { + if (!Array.isArray(opts.root)) { + opts.root = [opts.root]; + } + + opts.root = opts.root + .map(dirPath => resolve(opts.cwd, dirPath)) + .reduce((resolvedDirs, absDirPath) => { + if (glob.hasMagic(absDirPath)) { + const roots = glob.sync(absDirPath) + .filter(path => fs.lstatSync(path).isDirectory()); + + return [...resolvedDirs, ...roots]; + } + + return [...resolvedDirs, absDirPath]; + }, []); + } else { + opts.root = []; + } +} + +function getAliasPair(key, value) { + const parts = value.split('\\\\'); + + function substitute(execResult) { + return parts + .map(part => + part.replace(/\\\d+/g, number => execResult[number.slice(1)] || ''), + ) + .join('\\'); + } + + return [new RegExp(key), substitute]; +} + +function normalizeAlias(opts) { + if (opts.alias) { + const { alias } = opts; + const aliasKeys = Object.keys(alias); + + const nonRegExpAliases = aliasKeys + .filter(key => !isRegExp(key)) + .map(key => getAliasPair(`^${key}((?:/|).*)`, `${alias[key]}\\1`)); + + const regExpAliases = aliasKeys + .filter(isRegExp) + .map(key => getAliasPair(key, alias[key])); + + opts.alias = [...nonRegExpAliases, ...regExpAliases]; + } else { + opts.alias = []; + } +} + +export default function normalizeOptions(opts, file) { + normalizeCwd(opts, file); + normalizeRoot(opts); + normalizeAlias(opts); + + if (!opts.extensions) { + opts.extensions = defaultExtensions; + } +} diff --git a/test/index.test.js b/test/index.test.js index a7cd80f..7628354 100644 --- a/test/index.test.js +++ b/test/index.test.js @@ -222,7 +222,6 @@ describe('module-resolver', () => { components: './test/testproject/src/components', '~': './test/testproject/src', 'awesome/components': './test/testproject/src/components', - abstract: 'npm:concrete', underscore: 'lodash', prefix: 'prefix/lib', '^@namespace/foo-(.+)': 'packages/\\1', @@ -307,14 +306,6 @@ describe('module-resolver', () => { }); }); - it('(legacy) should support aliasing a node module with "npm:"', () => { - testWithImport( - 'abstract/thing', - 'concrete/thing', - aliasTransformerOpts, - ); - }); - it('should support aliasing a node modules', () => { testWithImport( 'underscore/map', @@ -416,14 +407,15 @@ describe('module-resolver', () => { [plugin, { root: './testproject/src', alias: { - test: './testproject/test', + constantsRelative: './constants', + constantsNonRelative: 'constants', }, cwd: resolve('test'), }], ], }; - it('should resolve the sub file path', () => { + it('should resolve the file path', () => { testWithImport( 'components/Root', './test/testproject/src/components/Root', @@ -431,10 +423,18 @@ describe('module-resolver', () => { ); }); - it('should alias the sub file path', () => { + it('should alias the relative path while ignoring cwd and root options', () => { testWithImport( - 'test/tools', - './test/testproject/test/tools', + 'constantsRelative/actions', + './constants/actions', + transformerOpts, + ); + }); + + it('should alias the non-relative path while considering cwd and root options', () => { + testWithImport( + 'constantsNonRelative/actions', + './test/testproject/src/constants/actions', transformerOpts, ); }); @@ -488,7 +488,7 @@ describe('module-resolver', () => { [plugin, { root: './src', alias: { - test: './test', + actions: 'constants/actions', }, cwd: 'babelrc', }], @@ -506,8 +506,8 @@ describe('module-resolver', () => { it('should alias the sub file path', () => { testWithImport( - 'test/tools', - '../test/tools', + 'actions', + './constants/actions', transformerOpts, ); });