Skip to content

Commit

Permalink
Allow macro config to come from plugin options in babel config.
Browse files Browse the repository at this point in the history
  • Loading branch information
conartist6 committed May 25, 2019
1 parent e0ebca7 commit 2c3520d
Show file tree
Hide file tree
Showing 2 changed files with 91 additions and 45 deletions.
66 changes: 38 additions & 28 deletions other/docs/author.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@
Is this your first time working with ASTs? Here are some resources:

* [Writing custom Babel and ESLint plugins with ASTs](https://youtu.be/VBscbcm2Mok?list=PLV5CVI1eNcJgNqzNwcs4UKrlJdhfDjshf): A 53 minute talk by [@kentcdodds](https://twitter.com/kentcdodds)
* [babel-handbook](https://github.com/thejameskyle/babel-handbook): A guided handbook on how to use Babel and how to create plugins for Babel by [@thejameskyle](https://twitter.com/thejameskyle)
* [Code Transformation and Linting](https://kentcdodds.com/workshops/#code-transformation-and-linting): A workshop (recording available on Frontend Masters) with exercises of making custom Babel and ESLint plugins
- [Writing custom Babel and ESLint plugins with ASTs](https://youtu.be/VBscbcm2Mok?list=PLV5CVI1eNcJgNqzNwcs4UKrlJdhfDjshf): A 53 minute talk by [@kentcdodds](https://twitter.com/kentcdodds)
- [babel-handbook](https://github.com/thejameskyle/babel-handbook): A guided handbook on how to use Babel and how to create plugins for Babel by [@thejameskyle](https://twitter.com/thejameskyle)
- [Code Transformation and Linting](https://kentcdodds.com/workshops/#code-transformation-and-linting): A workshop (recording available on Frontend Masters) with exercises of making custom Babel and ESLint plugins

## Writing a macro

Expand Down Expand Up @@ -164,43 +164,53 @@ This is a string used as import declaration's source - i.e. `'./my.macro'`.

#### config (EXPERIMENTAL!)

There is an experimental feature that allows users to configure your macro. We
use [`cosmiconfig`][cosmiconfig] to read a `babel-plugin-macros` configuration which
There is an experimental feature that allows users to configure your macro.

To specify that your plugin is configurable, you pass a `configName` to `createMacro`.

A configuration is created from data combined from two sources:
We use [`cosmiconfig`][cosmiconfig] to read a `babel-plugin-macros` configuration which
can be located in any of the following files up the directories from the
importing file:

* `.babel-plugin-macrosrc`
* `.babel-plugin-macrosrc.json`
* `.babel-plugin-macrosrc.yaml`
* `.babel-plugin-macrosrc.yml`
* `.babel-plugin-macrosrc.js`
* `babel-plugin-macros.config.js`
* `babelMacros` in `package.json`
- `.babel-plugin-macrosrc`
- `.babel-plugin-macrosrc.json`
- `.babel-plugin-macrosrc.yaml`
- `.babel-plugin-macrosrc.yml`
- `.babel-plugin-macrosrc.js`
- `babel-plugin-macros.config.js`
- `babelMacros` in `package.json`

To specify that your plugin is configurable, you pass a `configName` to
`createMacro`:
The content of the config will be merged with the content of the babel macros plugin
options. Plugin options take priority.

All together specifying and using the config might look like this:

```javascript
const {createMacro} = require('babel-plugin-macros')
const configName = 'taggedTranslations'
module.exports = createMacro(taggedTranslationsMacro, {configName})
function taggedTranslationsMacro({references, state, babel, config}) {
// config would be taggedTranslations portion of the config as loaded from `cosmiconfig`
// .babel-plugin-macros.config.js
module.exports = {
taggedTranslations: { locale: 'en_US' }
}
```

Then to configure this, users would do something like this:

```javascript
// babel-plugin-macros.config.js
// .babel.config.js
module.exports = {
taggedTranslations: {
someConfig: {},
},
plugins: [
['macros': {
taggedTranslations: { locale: 'en_GB' }
}]
]
}

// taggedTranslations.macro.js
const {createMacro} = require('babel-plugin-macros')
module.exports = createMacro(taggedTranslationsMacro, {configName: 'taggedTranslations'})
function taggedTranslationsMacro({references, state, babel, config}) {
const { locale = 'en' } = config;
}
```

And the `config` object you would receive would be: `{someConfig: {}}`.
Note that in the above example if both files were sepcified the finale locale value would
be `en_GB`, since that is the value in the plugin options.

### Keeping imports

Expand Down
70 changes: 53 additions & 17 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ function nodeResolvePath(source, basedir) {

function macrosPlugin(
babel,
{require: _require = require, resolvePath = nodeResolvePath} = {},
{require: _require = require, resolvePath = nodeResolvePath, ...options} = {},
) {
function interopRequire(path) {
// eslint-disable-next-line import/no-dynamic-require
Expand Down Expand Up @@ -88,6 +88,7 @@ function macrosPlugin(
babel,
interopRequire,
resolvePath,
options,
})

if (!result || !result.keepImports) {
Expand Down Expand Up @@ -152,6 +153,7 @@ function applyMacros({
babel,
interopRequire,
resolvePath,
options,
}) {
/* istanbul ignore next (pretty much only useful for astexplorer I think) */
const {
Expand Down Expand Up @@ -183,7 +185,7 @@ function applyMacros({
`Please refer to the documentation to see how to do this properly: https://github.com/kentcdodds/babel-plugin-macros/blob/master/other/docs/author.md#writing-a-macro`,
)
}
const config = getConfig(macro, filename, source)
const config = getConfig(macro, filename, source, options)

let result
try {
Expand Down Expand Up @@ -228,9 +230,20 @@ function applyMacros({
return result
}

// eslint-disable-next-line consistent-return
function getConfig(macro, filename, source) {
if (macro.options.configName) {
function getConfig(macro, filename, source, options) {
const {configName} = macro.options
if (configName) {
let callOptions, configOptions, configError, configPath

if (Object.prototype.hasOwnProperty.call(options, configName)) {
if (configName && !typeof options[configName] === 'object') {
throw new Error(
`The macro plugin options' ${configName} property was not an object or null.`,
)
}
callOptions = options[configName]
}

try {
// lazy-loading it here to avoid perf issues of loading it up front.
// No I did not measure. Yes I'm a bad person.
Expand All @@ -240,32 +253,55 @@ function getConfig(macro, filename, source) {
const explorer = require('cosmiconfig')('babel-plugin-macros', {
searchPlaces: [
'package.json',
`.babel-plugin-macrosrc`,
`.babel-plugin-macrosrc.json`,
`.babel-plugin-macrosrc.yaml`,
`.babel-plugin-macrosrc.yml`,
`.babel-plugin-macrosrc.js`,
`babel-plugin-macros.config.js`,
'.babel-plugin-macrosrc',
'.babel-plugin-macrosrc.json',
'.babel-plugin-macrosrc.yaml',
'.babel-plugin-macrosrc.yml',
'.babel-plugin-macrosrc.js',
'babel-plugin-macros.config.js',
],
packageProp: 'babelMacros',
sync: true,
})
const loaded = explorer.searchSync(filename)
if (loaded) {
return loaded.config[macro.options.configName]
configOptions = loaded.config[configName]
configPath = loaded.filepath
}
} catch (error) {
} catch (e) {
configError = e
}

if (callOptions === undefined && configOptions === undefined) {
// eslint-disable-next-line no-console
console.error(
`There was an error trying to load the config "${
macro.options.configName
}" ` +
`There was an error trying to load the config "${configName}" ` +
`for the macro imported from "${source}. ` +
`Please see the error thrown for more information.`,
)
throw error
if (configError !== undefined) {
throw configError
}
}

if (
configOptions &&
callOptions !== undefined &&
typeof callOptions !== 'object'
) {
throw new Error(
`${configPath} specified a ${configName} config of type ${typeof configOptions}, `,
`but the the macros plugin's options.${configName} did contain an object.`,
`Both configs must contain objects for their options to be mergeable.`,
)
}

return {
...configOptions,
...callOptions,
}
}
return undefined
}

/*
Expand Down

0 comments on commit 2c3520d

Please sign in to comment.