browserify-shim
Version:
Makes CommonJS-incompatible modules browserifyable.
219 lines (179 loc) • 7.81 kB
JavaScript
var path = require('path')
, fs = require('fs')
, util = require('util')
, parseInlineShims = require('./parse-inline-shims')
, mothership = require('mothership')
, format = require('util').format
var shimsCache = {}
, shimsByPath = {};
function inspect(obj, depth) {
return util.inspect(obj, false, depth || 5, true);
}
function isPath(s) {
return (/^[.]{0,2}[/\\]/).test(s);
}
function validate(key, config, dir) {
var msg
, details = 'When evaluating shim "' + key + '": ' + inspect(config) + '\ninside ' + dir + '\n';
if (!config.hasOwnProperty('exports')) {
msg = 'browserify-shim needs at least a path and exports to do its job, you are missing the exports. ' +
'\nIf this module has no exports, specify exports as null.'
throw new Error(details + msg);
}
}
function updateCache(packageDir, pack, resolvedShims, exposeGlobals) {
shimsCache[packageDir] = { pack: pack, shims: resolvedShims, exposeGlobals: exposeGlobals };
Object.keys(resolvedShims).forEach(function(fullPath) {
var shim = resolvedShims[fullPath];
validate(fullPath, shim, packageDir);
shimsByPath[fullPath] = shim;
});
}
function resolveDependsRelativeTo(dir, browser, depends, packDeps, messages) {
var resolved;
if (!depends) return undefined;
return Object.keys(depends).reduce(function (acc, k) {
if (browser[k]){
acc[k] = depends[k];
messages.push(format('Found depends "%s" exposed in browser field', k));
} else if (!isPath(k)) {
acc[k] = depends[k];
if (packDeps[k]) {
messages.push(format('Found depends "%s" as an installed dependency of the package', k));
} else {
messages.push(format('WARNING, depends "%s" is not a path, nor is it exposed in the browser field, nor was it found in package dependencies.', k));
}
} else {
// otherwise resolve the path
resolved = path.resolve(dir, k);
acc[resolved] = depends[k];
messages.push(format('Depends "%s" was resolved to be at [%s]', k, resolved));
}
return acc;
}, {})
}
function resolvePaths (packageDir, shimFileDir, browser, shims, packDeps, messages) {
return Object.keys(shims)
.reduce(function (acc, relPath) {
var shim = shims[relPath];
var exposed = browser[relPath];
var shimPath;
if (exposed) {
// lib exposed under different name/path in package.json's browser field
// and it is referred to by this alias in the shims (either external or in package.json)
// i.e.: 'non-cjs': { ... } -> browser: { 'non-cjs': './vendor/non-cjs.js }
shimPath = path.resolve(packageDir, exposed);
messages.push(format('Found "%s" in browser field referencing "%s" and resolved it to "%s"', relPath, exposed, shimPath));
} else if (shimFileDir) {
// specified via relative path to shim file inside shim file
// i.e. './vendor/non-cjs': { exports: .. }
shimPath = path.resolve(shimFileDir, relPath);
messages.push(format('Resolved "%s" found in shim file to "%s"', relPath, shimPath));
} else {
// specified via relative path in package.json browserify-shim config
// i.e. 'browserify-shim': { './vendor/non-cjs': 'noncjs' }
shimPath = path.resolve(packageDir, relPath);
messages.push(format('Resolved "%s" found in package.json to "%s"', relPath, shimPath));
}
var depends = resolveDependsRelativeTo(shimFileDir || packageDir, browser, shim.depends, packDeps, messages);
acc[shimPath] = { exports: shim.exports, depends: depends };
return acc;
}, {});
}
function mapifyExposeGlobals(exposeGlobals) {
return Object.keys(exposeGlobals)
.reduce(function (acc, k) {
var val = exposeGlobals[k];
var parts = val.split(':');
if (parts.length < 2 || !parts[1].length) {
throw new Error(
'Expose Globals need to have the format "global:expression.\n"'
+ inspect({ key: k, value: val }) + 'does not.'
);
}
// this also handle unlikely cases of 'global:_.someFunc(':')' with a `:` in the actual global expression
parts.shift();
acc[k] = parts.join(':');
return acc;
}, {});
}
function separateExposeGlobals(shims) {
var onlyShims = {}
, exposeGlobals = {};
Object.keys(shims).forEach(function (k) {
// https://github.com/thlorenz/browserify-shim/issues/245
if (k === '__proto__' || k === 'constructor') {
return;
}
var val = shims[k]
, exp = val && val.exports;
if (exp && /^global\:/.test(exp)) {
exposeGlobals[k] = exp;
} else {
onlyShims[k] = val;
}
});
return { shims: onlyShims, exposeGlobals: mapifyExposeGlobals(exposeGlobals) };
}
function resolveFromShimFile(packageDir, pack, shimField, messages) {
var shimFile = path.join(packageDir, shimField)
, shimFileDir = path.dirname(shimFile);
var allShims = require(shimFile);
var separated = separateExposeGlobals(allShims);
var resolvedShims = resolvePaths(packageDir, shimFileDir, pack.browser || {}, separated.shims, pack.dependencies || {}, messages);
return { shims: resolvedShims, exposeGlobals: separated.exposeGlobals };
}
function resolveInlineShims(packageDir, pack, shimField, messages) {
var allShims = parseInlineShims(shimField);
var separated = separateExposeGlobals(allShims);
var resolvedShims = resolvePaths(packageDir, null, pack.browser || {}, separated.shims, pack.dependencies || {}, messages);
return { shims: resolvedShims, exposeGlobals: separated.exposeGlobals };
}
var resolve = module.exports = function resolveShims (file, messages, cb) {
// find the package.json that defines browserify-shim config for this file
mothership(file, function (pack) { return !! pack['browserify-shim'] }, function (err, res) {
if (err) return cb(err);
if (!res || !res.pack) return cb(new Error('Unable to find a browserify-shim config section in the package.json for ' + file));
var pack = res.pack;
var packFile = res.path;
var packageDir = path.dirname(packFile);
// we cached this before which means it was also grouped by file
var cached = shimsCache[packageDir];
// if it was cached, that means any package fixes were applied as well
if (cached) {
return cb(null, {
package_json : packFile
, packageDir : packageDir
, resolvedPreviously : true
, shim : shimsByPath[file]
, exposeGlobals : cached.exposeGlobals
, browser : pack.browser
, 'browserify-shim' : pack['browserify-shim']
, dependencies : pack.dependencies
});
}
try {
pack = require(packFile);
var shimField = pack['browserify-shim'];
if (!shimField) return cb(null, { package_json: packFile, shim: undefined });
var resolved = typeof shimField === 'string'
? resolveFromShimFile(packageDir, pack, shimField, messages)
: resolveInlineShims(packageDir, pack, shimField, messages);
messages.push({ resolved: resolved.shims });
updateCache(packageDir, pack, resolved.shims, resolved.exposeGlobals);
cb(null, {
package_json : packFile
, packageDir : packageDir
, shim : shimsByPath[file]
, exposeGlobals : resolved.exposeGlobals
, browser : pack.browser
, 'browserify-shim' : pack['browserify-shim']
, dependencies : pack.dependencies
});
} catch (err) {
console.trace();
return cb(err);
}
});
}
;