UNPKG

pathmodify

Version:

Rewrite (alias) and expose `require()` IDs in browserify.

322 lines (266 loc) 7.56 kB
var rs = require('readable-stream'), plugin, slash = {fwd: '/', back: '\\'}, util = require('util'); module.exports = plugin = pathmodify; /** * Main plugin. */ function pathmodify (b, opts) { // Temporarily allow people to continue using this by calling it as a factory // function, even though they should be mindful of semver. if (! arguments.length) { return util.deprecate( pathmodify, "Deprecated: pathmodify: Don't call the export as a factory. The export is now the plugin function." ); } opts = opts || {}; opts = {mods: [].concat(opts.mods || [])}; // Map resolved pathnames to expose IDs. opts.mappings = {}; // Record of already processed require() ID's, keyed on parent filename. opts.visited = {}; opts.bify = b; opts.expose = function expose (key, val) { b._expose[key] = val; opts.pack.hasExports = true; }; // expose opts.custom_resolver = make_resolver(opts); b.on('reset', function () { init(opts); }); init(opts); } // pathmodify /** * Common logic between initial setup and bundle reset. */ function init (opts) { var b = opts.bify, deps = b.pipeline.get('deps'); // TODO `0` needs to be changed to a label. opts.pack = b.pipeline.get('pack').get(0); // TODO Should probably make sure this ends up directly after module-deps. deps.push(aliaser({mappings: opts.mappings})); // TODO `0` needs to be changed to a label. deps = deps.get(0); opts.deps = deps; opts.resolver = deps.resolver; deps.resolver = opts.custom_resolver; } // init /** * Make a custom resolve function to override module-deps resolver. */ function make_resolver (opts) { var expose = opts.expose, modifiers = Array.isArray(opts.mods) ? opts.mods : [], mappings = opts.mappings, visited = opts.visited, bify = opts.bify; // These change at reset: resolve function current (prop) { return opts[prop]; } // current return alias_resolver; /** * Custom resolve function. Same signature as node-resolve and * node-browser-resolve. */ function alias_resolver (id, opts, cb) { var rec = {id: id, opts: {filename: opts.filename}}, resolver = current('resolver'), mappings = current('mappings'), visited = current('visited'), par_vis = visited[opts.filename] || {}, // boolean Whether the id has already been processed. processed; visited[opts.filename] = par_vis; // Retrieve any existing record for this id for this parent. processed = par_vis[rec.id]; // Process modifiers for this module if it hasn't previously been processed. rec = processed || modify(rec); processed = !! processed; // Assign processed record to the records for its parent. if (! processed) par_vis[rec.id] = rec; // Calling walk() is the alternative to pushing the pipeline step (as // above). Currently unused. Would look like this: /* // Aliased, exposed, and not previously visited. if ( ! processed && rec.alias.expose ) { return opts.deps.walk({ id: rec.alias.expose || rec.id, file: rec.id, }, opts, cb); } */ // Delegate to original resolver. return resolver(rec.alias.id || rec.id, opts, function (err, res, pkg) { if (! err) { if (rec.alias.expose) { mappings[res] = rec.alias.expose; expose(rec.alias.expose, res); } bify.emit('pathmodify:resolved', { rec: rec, file: res, }); } cb(err, res, pkg); }); } // alias_resolver /** * Process exposure configuration. */ function set_expose (rec, alias, modifier) { if (typeof modifier.expose === 'string') { alias.expose = modifier.expose; } else if (typeof modifier.expose === 'function') { alias.expose = modifier.expose(rec, alias); } if (modifier.expose === true || alias.expose === true) { alias.expose = rec.id; } return alias; } // set_expose /** * Process a modification that's a function or where the replacement is a * function. */ function alias_with_func (mod, rec, opts) { var ret = {}, opts = opts || {}; ret.alias = (typeof mod === 'function' ? mod : mod.to).call(mod, rec, opts); ret.matched = !! (ret.alias && ret.alias.id !== rec.id); return ret; } // alias_with_func /** * Apply modifiers configured by the user. */ function modify (rec) { var // Switch for terminating looping over the modifiers once a match is // found. matched, // Restore this in the end in case the user modifies it. id = rec.id, alias, temp = {}, path_sep; // Heuristically detect path separator. [slash.fwd, slash.back, slash.fwd].every(function (sep) { path_sep = sep; return rec.id.indexOf(path_sep) === -1; }); modifiers.every(function (modifier) { matched = true; // Reset so modifiers can't munge data for later ones. rec.id = id; alias = {}; if ( ( modifier.type === 'dir' && rec.id.indexOf(modifier.from + path_sep) === 0 ) || (modifier.type === 'id' && rec.id === modifier.from) ) { if (typeof modifier.to === 'function') { temp = alias_with_func(modifier, rec); alias = temp.alias; matched = temp.matched; } else { alias.id = modifier.to + rec.id.substr(modifier.from.length); } } else if (typeof modifier === 'function') { temp = alias_with_func(modifier, rec); alias = temp.alias; matched = temp.matched; } else if ( modifier.type === 're' && (matched = modifier.from[ typeof modifier.to === 'function' ? 'exec' : 'test' ](rec.id)) ) { if (typeof modifier.to === 'function') { temp = alias_with_func(modifier, rec, {matches: matched}); alias = temp.alias; matched = temp.matched; } else { alias.id = rec.id.replace(modifier.from, modifier.to); } } else matched = false; if (matched) set_expose(rec, alias, modifier); return ! matched; }); rec.id = id; if (! (alias && alias.id)) alias = {}; rec.alias = alias; return rec; } // modify } // make_resolver /** * Following module-deps, update record id's to reflect exposure configuration. */ function aliaser (opts) { opts = opts || {}; var stream = new rs.Transform({objectMode: true}), mappings = opts.mappings; stream._transform = write; function write (rec, enc, cb) { // User exposed this file under an alternate id. if (mappings[rec.file]) { rec.id = mappings[rec.file]; } this.push(rec); cb(); } // write stream.label = "pathmodify:deps:post"; return stream; } // aliaser function simple (from, to, expose, type) { return {from: from, to: to, expose: expose, type: type}; } // simple // Functions for generating entries in opts.mods. plugin.mod = {}; [ ['id'], ['dir'], ['re'], ].forEach(function (type_def) { var type = type_def[0], aliases = type_def[1]; plugin.mod[type] = function (from, to, expose) { return simple(from, to, expose, type); }; if (Array.isArray(aliases)) { aliases.forEach(function (alias) { plugin.mod[alias] = plugin.mod[type]; }); } });