@xyezir/vux-loader
Version:
extended loader for vux
159 lines (129 loc) • 6.36 kB
JavaScript
var types = require('babel-types');
var isValidPath = require('is-valid-path');
var camel = require('lodash.camelcase');
var findKey = require('lodash.findkey');
var kebab = require('lodash.kebabcase');
var snake = require('lodash.snakecase');
var pathLib = require('path');
function findOptionFromSource(source, state) {
var opts = state.opts;
if (opts[source]) return source;
return
var opt = findKey(opts, function (o, _opt) {
return !isValidPath(_opt) && new RegExp(_opt).test(source);
});
if (opt) return opt;
var isRelativePath = source.match(/^\.{0,2}\//);
// This block handles relative paths, such as ./components, ../../components, etc.
if (isRelativePath) {
var _source = pathLib.resolve(pathLib.join(
source[0] === '/' ? '' : pathLib.dirname(state.file.opts.filename),
source
));
if (opts[_source]) {
return _source;
}
}
}
function getMatchesFromSource(opt, source) {
var regex = new RegExp(opt, 'g');
var matches = [];
var m;
while ((m = regex.exec(source)) !== null) {
if (m.index === regex.lastIndex) regex.lastIndex++;
m.forEach(function(match) {
matches.push(match);
});
}
return matches;
}
function barf(msg) {
throw new Error('babel-plugin-transform-imports: ' + msg);
}
function transform(transformOption, importName, matches) {
var isFunction = typeof transformOption === 'function';
if (/\.js$/i.test(transformOption) || isFunction) {
var transformFn;
try {
transformFn = isFunction ? transformOption : require(transformOption);
} catch (error) {
barf('failed to require transform file ' + transformOption);
}
if (typeof transformFn !== 'function') {
barf('expected transform function to be exported from ' + transformOption);
}
return transformFn(importName, matches);
}
return transformOption.replace(/\$\{\s?([\w\d]*)\s?\}/ig, function(str, g1) {
if (g1 === 'member') return importName;
return matches[g1];
});
}
module.exports = function() {
return {
visitor: {
ImportDeclaration: function (path, state) {
// https://github.com/babel/babel/tree/master/packages/babel-types#timportdeclarationspecifiers-source
// path.node has properties 'source' and 'specifiers' attached.
// path.node.source is the library/module name, aka 'react-bootstrap'.
// path.node.specifiers is an array of ImportSpecifier | ImportDefaultSpecifier | ImportNamespaceSpecifier
var source = path.node.source.value;
var opt = findOptionFromSource(source, state);
var isRegexp = opt && !isValidPath(opt);
var opts = state.opts[opt];
var hasOpts = !!opts;
if (hasOpts) {
if (!opts.transform) {
barf('transform option is required for module ' + source);
}
var transforms = [];
var fullImports = path.node.specifiers.filter(function(specifier) { return specifier.type !== 'ImportSpecifier' });
var memberImports = path.node.specifiers.filter(function(specifier) { return specifier.type === 'ImportSpecifier' });
if (fullImports.length > 0) {
// Examples of "full" imports:
// import * as name from 'module'; (ImportNamespaceSpecifier)
// import name from 'module'; (ImportDefaultSpecifier)
if (opts.preventFullImport) {
barf('import of entire module ' + source + ' not allowed due to preventFullImport setting');
}
if (memberImports.length > 0) {
// Swap out the import with one that doesn't include member imports. Member imports should each get their own import line
// transform this:
// import Bootstrap, { Grid } from 'react-bootstrap';
// into this:
// import Bootstrap from 'react-bootstrap';
transforms.push(types.importDeclaration(fullImports, types.stringLiteral(source)));
}
}
var matches = isRegexp ? getMatchesFromSource(opt, source) : [];
memberImports.forEach(function(memberImport) {
// Examples of member imports:
// import { member } from 'module'; (ImportSpecifier)
// import { member as alias } from 'module' (ImportSpecifier)
// transform this:
// import { Grid as gird } from 'react-bootstrap';
// into this:
// import gird from 'react-bootstrap/lib/Grid';
// or this, if skipDefaultConversion = true:
// import { Grid as gird } from 'react-bootstrap/lib/Grid';
var importName = memberImport.imported.name;
if (opts.camelCase) importName = camel(importName);
if (opts.kebabCase) importName = kebab(importName);
if (opts.snakeCase) importName = snake(importName);
var replace = transform(opts.transform, importName, matches);
var newImportSpecifier = (opts.skipDefaultConversion)
? memberImport
: types.importDefaultSpecifier(types.identifier(memberImport.local.name));
transforms.push(types.importDeclaration(
[newImportSpecifier],
types.stringLiteral(replace + ' // COMMENT')
));
});
if (transforms.length > 0) {
path.replaceWithMultiple(transforms);
}
}
}
}
}
}