index.js
/*!
* resolve-plugins-sync <https://github.com/tunnckoCore/resolve-plugins-sync>
*
* Copyright (c) Charlike Mike Reagent <@tunnckoCore> (https://i.am.charlike.online)
* Released under the MIT license.
*/
'use strict'
const path = require('path')
const { getInstalledPathSync } = require('get-installed-path')
/**
* > Babel/Browserify-style resolve of a `plugins` array
* and optional `opts` options object, where
* each "plugin" (item in the array) can be
* 1) string, 2) function, 3) object or 4) array.
* Useful for loading complex and meaningful configs like
* exactly all of them - Babel, ESLint, Browserify. It would
* be great if they use that package one day :)
* The [rolldown][] bundler already using it as default
* resolution for resolving [rollup][] plugins. :)
*
* @example
* const resolve = require('resolve-plugins-sync')
*
* // fake
* const baz = require('tool-plugin-baz')
* const qux = require('tool-plugin-qux')
*
* resolve([
* 'foo',
* ['bar', { some: 'options here' }],
* [baz, { a: 'b' }],
* qux
* ], {
* prefix: 'tool-plugin-'
* })
*
* @param {Array|String} [plugins] array of "plugins/transforms/presets" or single string,
* which is arrayified so returned `result`
* is always an array
* @param {Object} opts optional custom configuration
* @param {String} `opts.prefix` useful like `babel-plugin-` or `rollup-plugin-`
* @param {Any} `opts.context` custom context to be passed to plugin function,
* using the `.apply` method
* @param {Any} `opts.first` pass first argument for plugin function, if it is
* given, then it will pass plugin options as 2nd argument,
* that's useful for browserify-like transforms where
* first argument is `filename`, second is transform `options`
* @param {Array} `opts.args` pass custom arguments to the resolved plugin function,
* if given - respected more than `opts.first`
* @return {Array} `result` resolved plugins, always an array
* @name resolvePluginsSync
* @public
*/
const resolvePluginsSync = (plugins, opts) => {
plugins = arrayify(plugins).filter(Boolean)
if (!plugins.length) {
return []
}
opts = Object.assign({ prefix: '', cwd: process.cwd() }, opts)
return arrayify(plugins).map((plugin) => {
// e.g. `plugins: ['foo', 'bar', 'baz']`
if (typeof plugin === 'string') {
return resolveFromString(opts, plugin)
}
// allows nesting and passing options to each plugin
// e.g. `plugins: [ fn, ['foo', {opts: 'here'}], 'bar', quix() ]
if (Array.isArray(plugin)) {
return resolveFromArray(opts, plugin)
}
// e.g. `plugins: [fn1, fn2]`
if (typeof plugin === 'function') {
return plugin.apply(opts.context, opts.args)
}
// just pass to the tool, like Rollup expect each plugin
// to return an object, so you can directly pass objects
// e.g. `plugins: [{name: 'rollup-plugin-foo', transform: fn}]`
if (typeof plugin === 'object') {
return plugin
}
let message = 'Plugin item should be function, string, object or array'
throw new TypeError(message)
})
}
/**
* > Make an array from any value.
*
* @param {Any} `val` some long description
* @return {Array}
* @private
*/
let arrayify = (val) => {
if (!val) return []
if (Array.isArray(val)) return val
return [val]
}
/**
* > Resolve a plugin from string. Below
* example uses `rollup` plugins. Will find the
* plugin if installed, require it and call it
* without options been passed.
*
* @example
* const resolve = require('resolve-plugins-sync')
* const plugins = [
* 'commonjs',
* 'node-resolve',
* 'buble'
* ]
*
* const result = resolve(plugins, {
* prefix: 'rollup-plugin-'
* })
* console.log(result) // => Array of objects
* ```
*
* @param {Object} `opts`
* @return {String} `plugin`
* @private
*/
const resolveFromString = (opts, plugin) => {
const func = resolver(opts, plugin)
const first = opts.hasOwnProperty('first') ? [opts.first] : []
let argz = opts.args ? opts.args : first
return typeof func === 'function' ? func.apply(opts.context, argz) : func
}
/**
* > Resolve a plugin from array. Below
* example uses `rollup` plugins. Will find the
* plugin if installed, require it and call it
* with given options.
*
* First argument of the array
* can be string (name of the plugin without the prefix)
* or directly the plugin function.
*
* Second argument is optional, but can be `options`
* object which will be passed to the resolved plugin.
*
* Very much how Babel and Browserify resolves their
* transforms, presets and plugins.
*
* @example
* const resolve = require('resolve-plugins-sync')
* const plugins = [
* 'commonjs',
* ['node-resolve', { jsnext: true }],
* [buble, { target: { node: '4' } }]
* ]
*
* const result = resolve(plugins, {
* prefix: 'rollup-plugin-'
* })
* console.log(result) // => Array of objects
*
* @param {Object} `opts`
* @return {String} `plugin`
* @api private
*/
const resolveFromArray = (opts, plugin) => {
plugin = plugin.filter(Boolean)
let second = opts.first ? plugin[1] : undefined
let first = opts.first ? opts.first : plugin[1]
let args = opts.args ? opts.args : [first, second]
// e.g. `plugins: [ ['foo'], ['bar', opts] ]`
if (typeof plugin[0] === 'string') {
const res = resolver(opts, plugin[0])
return typeof res === 'function' ? res.apply(opts.context, args) : res
}
// e.g. `plugins: [ [fn], [fn, opts] ]`
if (typeof plugin[0] === 'function') {
let fn = plugin[0]
return fn.apply(opts.context, args)
}
// e.g. `plugins: [{ name: 'plugin-name', transform: fn }, { name: 'foo' }]`
if (typeof plugin[0] === 'object') {
return plugin[0]
}
let msg = 'First item of array should be function, string or object'
throw new TypeError(msg)
}
function resolver (opts, item) {
if (item.charAt(0) === '.') {
return require(path.join(opts.cwd, item))
}
if (!item.startsWith(opts.prefix)) {
item = opts.prefix + item
}
let filepath = null
try {
filepath = getInstalledPathSync(item, { local: true })
} catch (e) {
try {
filepath = getInstalledPathSync(item)
} catch (e) {
return require(item)
}
}
return require(filepath)
}
module.exports = resolvePluginsSync