Repository: kentcdodds/babel-macros
Branch: main
Commit: fc04c3ed721c
Files: 50
Total size: 106.9 KB
Directory structure:
gitextract_7a72cab4/
├── .all-contributorsrc
├── .gitattributes
├── .github/
│ ├── ISSUE_TEMPLATE.md
│ ├── PULL_REQUEST_TEMPLATE.md
│ └── workflows/
│ └── validate.yml
├── .gitignore
├── .huskyrc.js
├── .npmrc
├── .prettierignore
├── .prettierrc.js
├── CHANGELOG.md
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── other/
│ ├── MAINTAINING.md
│ ├── USERS.md
│ ├── docs/
│ │ ├── author.md
│ │ └── user.md
│ ├── manual-releases.md
│ └── mock-modules/
│ ├── @scope/
│ │ └── package/
│ │ └── macro.js
│ ├── babel-plugin-macros-test-error-thrower/
│ │ └── macro.js
│ ├── babel-plugin-macros-test-error-thrower.macro/
│ │ └── index.js
│ ├── babel-plugin-macros-test-fake/
│ │ └── macro.js
│ └── babel-plugin-path-replace/
│ └── index.js
├── package.json
└── src/
├── __tests__/
│ ├── __snapshots__/
│ │ ├── create-macros.js.snap
│ │ └── index.js.snap
│ ├── create-macros.js
│ ├── fixtures/
│ │ ├── config/
│ │ │ ├── babel-plugin-macros.config.js
│ │ │ ├── cjs-code.js
│ │ │ ├── code.js
│ │ │ └── configurable.macro.js
│ │ ├── emotion-esm.macro.js
│ │ ├── emotion.macro.js
│ │ ├── error-thrower.macro.js
│ │ ├── eval-macro.js
│ │ ├── eval.macro.js
│ │ ├── jsx-id-prefix.macro.js
│ │ ├── jsx-id-prefix.plugin.js
│ │ ├── keep-imports.macro.js
│ │ ├── macro-error-thrower.macro.js
│ │ ├── non-wrapped.macro.js
│ │ ├── path-replace-issue/
│ │ │ ├── .babelrc
│ │ │ └── variable-assignment.js
│ │ └── primitive-config/
│ │ ├── babel-plugin-macros.config.js
│ │ ├── code.js
│ │ └── configurable.macro.js
│ └── index.js
└── index.js
================================================
FILE CONTENTS
================================================
================================================
FILE: .all-contributorsrc
================================================
{
"projectName": "babel-plugin-macros",
"projectOwner": "kentcdodds",
"imageSize": 100,
"commit": false,
"contributorsPerLine": 7,
"repoHost": "https://github.com",
"repoType": "github",
"skipCi": false,
"files": [
"README.md"
],
"contributors": [
{
"login": "kentcdodds",
"name": "Kent C. Dodds",
"avatar_url": "https://avatars.githubusercontent.com/u/1500684?v=3",
"profile": "https://kentcdodds.com",
"contributions": [
"code",
"doc",
"infra",
"test"
]
},
{
"login": "threepointone",
"name": "Sunil Pai",
"avatar_url": "https://avatars1.githubusercontent.com/u/18808?v=3",
"profile": "https://github.com/threepointone",
"contributions": [
"ideas"
]
},
{
"login": "suchipi",
"name": "Lily Scott",
"avatar_url": "https://avatars0.githubusercontent.com/u/1341513?v=4",
"profile": "http://suchipi.com",
"contributions": [
"question",
"doc"
]
},
{
"login": "dralletje",
"name": "Michiel Dral",
"avatar_url": "https://avatars1.githubusercontent.com/u/767261?v=4",
"profile": "http://twitter.com/dralletje",
"contributions": [
"ideas"
]
},
{
"login": "tkh44",
"name": "Kye Hohenberger",
"avatar_url": "https://avatars2.githubusercontent.com/u/662750?v=4",
"profile": "https://github.com/tkh44",
"contributions": [
"ideas"
]
},
{
"login": "mitchellhamilton",
"name": "Mitchell Hamilton",
"avatar_url": "https://avatars1.githubusercontent.com/u/11481355?v=4",
"profile": "https://hamil.town",
"contributions": [
"code",
"test"
]
},
{
"login": "wKovacs64",
"name": "Justin Hall",
"avatar_url": "https://avatars1.githubusercontent.com/u/1288694?v=4",
"profile": "https://github.com/wKovacs64",
"contributions": [
"doc"
]
},
{
"login": "PiereDome",
"name": "Brian Pedersen",
"avatar_url": "https://avatars3.githubusercontent.com/u/1903016?v=4",
"profile": "https://github.com/PiereDome",
"contributions": [
"code",
"doc"
]
},
{
"login": "apalm",
"name": "Andrew Palm",
"avatar_url": "https://avatars3.githubusercontent.com/u/4495237?v=4",
"profile": "https://github.com/apalm",
"contributions": [
"code"
]
},
{
"login": "evenchange4",
"name": "Michael Hsu",
"avatar_url": "https://avatars1.githubusercontent.com/u/1527371?v=4",
"profile": "https://michaelhsu.tw/",
"contributions": [
"doc",
"plugin"
]
},
{
"login": "citycide",
"name": "Bo Lingen",
"avatar_url": "https://avatars2.githubusercontent.com/u/16605186?v=4",
"profile": "https://github.com/citycide",
"contributions": [
"code"
]
},
{
"login": "tylerthehaas",
"name": "Tyler Haas",
"avatar_url": "https://avatars1.githubusercontent.com/u/11150235?v=4",
"profile": "https://github.com/tylerthehaas",
"contributions": [
"doc"
]
},
{
"login": "FWeinb",
"name": "FWeinb",
"avatar_url": "https://avatars0.githubusercontent.com/u/1250430?v=4",
"profile": "https://github.com/FWeinb",
"contributions": [
"code"
]
},
{
"login": "tricoder42",
"name": "Tomáš Ehrlich",
"avatar_url": "https://avatars2.githubusercontent.com/u/827862?v=4",
"profile": "http://www.tomasehrlich.cz",
"contributions": [
"bug",
"code"
]
},
{
"login": "jgierer12",
"name": "Jonas Gierer",
"avatar_url": "https://avatars0.githubusercontent.com/u/4331946?v=4",
"profile": "https://github.com/jgierer12",
"contributions": [
"doc"
]
},
{
"login": "lPadier",
"name": "Loïc Padier",
"avatar_url": "https://avatars2.githubusercontent.com/u/4009640?v=4",
"profile": "http://loicpadier.com",
"contributions": [
"code"
]
},
{
"login": "pshrmn",
"name": "Paul Sherman",
"avatar_url": "https://avatars0.githubusercontent.com/u/1127037?v=4",
"profile": "https://www.pshrmn.com",
"contributions": [
"code"
]
},
{
"login": "conartist6",
"name": "Conrad Buck",
"avatar_url": "https://avatars1.githubusercontent.com/u/540777?v=4",
"profile": "http://burningpotato.com",
"contributions": [
"code",
"test",
"doc"
]
},
{
"login": "InvictusMB",
"name": "InvictusMB",
"avatar_url": "https://avatars3.githubusercontent.com/u/3091209?v=4",
"profile": "https://github.com/InvictusMB",
"contributions": [
"test"
]
},
{
"login": "coderberry",
"name": "Eric Berry",
"avatar_url": "https://avatars2.githubusercontent.com/u/12481?v=4",
"profile": "https://codefund.io",
"contributions": [
"fundingFinding"
]
},
{
"login": "futagoza",
"name": "Futago-za Ryuu",
"avatar_url": "https://avatars1.githubusercontent.com/u/1943570?v=4",
"profile": "http://futagoza.github.io/",
"contributions": [
"code",
"test"
]
},
{
"login": "lucleray",
"name": "Luc",
"avatar_url": "https://avatars3.githubusercontent.com/u/6616955?v=4",
"profile": "https://luc.im",
"contributions": [
"code"
]
},
{
"login": "wintercounter",
"name": "Victor Vincent",
"avatar_url": "https://avatars2.githubusercontent.com/u/963776?v=4",
"profile": "http://wintercounter.me",
"contributions": [
"code"
]
},
{
"login": "mvasilkov",
"name": "я котик пур-пур",
"avatar_url": "https://avatars3.githubusercontent.com/u/140257?v=4",
"profile": "http://mvasilkov.ovh",
"contributions": [
"doc"
]
},
{
"login": "soska",
"name": "Armando Sosa",
"avatar_url": "https://avatars0.githubusercontent.com/u/139577?v=4",
"profile": "http://armandososa.com",
"contributions": [
"doc"
]
},
{
"login": "matvp91",
"name": "Matthias",
"avatar_url": "https://avatars3.githubusercontent.com/u/12699796?v=4",
"profile": "https://github.com/matvp91",
"contributions": [
"code"
]
},
{
"login": "JoviDeCroock",
"name": "Jovi De Croock",
"avatar_url": "https://avatars3.githubusercontent.com/u/17125876?v=4",
"profile": "https://www.jovidecroock.com/",
"contributions": [
"code",
"test"
]
},
{
"login": "VictorArowo",
"name": "Victor Arowo",
"avatar_url": "https://avatars0.githubusercontent.com/u/25545108?v=4",
"profile": "http://victorarowo.com",
"contributions": [
"doc"
]
},
{
"login": "alexanderchan",
"name": "Alex Chan",
"avatar_url": "https://avatars.githubusercontent.com/u/1864372?v=4",
"profile": "https://twitter.com/alexandermchan",
"contributions": [
"doc"
]
},
{
"login": "probablyup",
"name": "Evan Jacobs",
"avatar_url": "https://avatars.githubusercontent.com/u/570070?v=4",
"profile": "https://probablyup.com",
"contributions": [
"code"
]
}
]
}
================================================
FILE: .gitattributes
================================================
* text=auto eol=lf
================================================
FILE: .github/ISSUE_TEMPLATE.md
================================================
<!--
Thanks for your interest in the project. I appreciate bugs filed and PRs submitted!
Please make sure that you are familiar with and follow the Code of Conduct for
this project (found in the CODE_OF_CONDUCT.md file).
Please fill out this template with all the relevant information so we can
understand what's going on and fix the issue.
I'll probably ask you to submit the fix (after giving some direction). If you've
never done that before, that's great! Check this free short video tutorial to
learn how: http://kcd.im/pull-request
-->
- `babel-plugin-macros` version:
- `node` version:
- `npm` version:
Relevant code or config
```javascript
```
What you did:
What happened:
<!-- Please provide the full error message/screenshots/anything -->
Reproduction repository:
<!--
If possible, please create a repository that reproduces the issue with the
minimal amount of code possible.
-->
Problem description:
Suggested solution:
================================================
FILE: .github/PULL_REQUEST_TEMPLATE.md
================================================
<!--
Thanks for your interest in the project. Bugs filed and PRs submitted are appreciated!
Please make sure that you are familiar with and follow the Code of Conduct for
this project (found in the CODE_OF_CONDUCT.md file).
Also, please make sure you're familiar with and follow the instructions in the
contributing guidelines (found in the CONTRIBUTING.md file).
If you're new to contributing to open source projects, you might find this free
video course helpful: http://kcd.im/pull-request
Please fill out the information below to expedite the review and (hopefully)
merge of your pull request!
-->
<!-- What changes are being made? (What feature/bug is being fixed here?) -->
**What**:
<!-- Why are these changes necessary? -->
**Why**:
<!-- How were these changes implemented? -->
**How**:
<!-- Have you done all of these things? -->
**Checklist**:
<!-- add "N/A" to the end of each line that's irrelevant to your changes -->
<!-- to check an item, place an "x" in the box like so: "- [x] Documentation" -->
- [ ] Documentation
- [ ] Tests
- [ ] Ready to be merged
<!-- In your opinion, is this ready to be merged as soon as it's reviewed? -->
<!-- feel free to add additional comments -->
================================================
FILE: .github/workflows/validate.yml
================================================
name: validate
on:
push:
branches:
- '+([0-9])?(.{+([0-9]),x}).x'
- 'main'
- 'next'
- 'next-major'
- 'beta'
- 'alpha'
- '!all-contributors/**'
pull_request: {}
jobs:
main:
# ignore all-contributors PRs
if: ${{ !contains(github.head_ref, 'all-contributors') }}
strategy:
matrix:
node: [10.13, 12, 14, 15]
runs-on: ubuntu-latest
steps:
- name: ⬇️ Checkout repo
uses: actions/checkout@v2
- name: ⎔ Setup node
uses: actions/setup-node@v1
with:
node-version: ${{ matrix.node }}
- name: 📥 Download deps
uses: bahmutov/npm-install@v1
with:
useLockFile: false
- name: ▶️ Run validate script
run: npm run validate
- name: ⬆️ Upload coverage report
uses: codecov/codecov-action@v1
release:
needs: main
runs-on: ubuntu-latest
if:
${{ github.repository == 'kentcdodds/babel-plugin-macros' &&
contains('refs/heads/main,refs/heads/beta,refs/heads/next,refs/heads/alpha',
github.ref) && github.event_name == 'push' }}
steps:
- name: ⬇️ Checkout repo
uses: actions/checkout@v2
- name: ⎔ Setup node
uses: actions/setup-node@v1
with:
node-version: 14
- name: 📥 Download deps
uses: bahmutov/npm-install@v1
with:
useLockFile: false
- name: 🚀 Release
uses: cycjimmy/semantic-release-action@v2
with:
semantic_version: 17
branches: |
[
'+([0-9])?(.{+([0-9]),x}).x',
'main',
'next',
'next-major',
{name: 'beta', prerelease: true},
{name: 'alpha', prerelease: true}
]
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
================================================
FILE: .gitignore
================================================
node_modules
coverage
dist
.DS_Store
# these cause more harm than good
# when working with contributors
package-lock.json
yarn.lock
================================================
FILE: .huskyrc.js
================================================
module.exports = require('kcd-scripts/husky')
================================================
FILE: .npmrc
================================================
registry=https://registry.npmjs.org/
================================================
FILE: .prettierignore
================================================
node_modules
coverage
dist
================================================
FILE: .prettierrc.js
================================================
module.exports = require('kcd-scripts/prettier')
================================================
FILE: CHANGELOG.md
================================================
# CHANGELOG
The changelog is automatically updated using
[semantic-release](https://github.com/semantic-release/semantic-release). You
can see it on the [releases page](../../releases).
================================================
FILE: CODE_OF_CONDUCT.md
================================================
# Contributor Covenant Code of Conduct
## Our Pledge
We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible disability, ethnicity, sex characteristics, gender
identity and expression, level of experience, education, socio-economic status,
nationality, personal appearance, race, religion, or sexual identity and
orientation.
We pledge to act and interact in ways that contribute to an open, welcoming,
diverse, inclusive, and healthy community.
## Our Standards
Examples of behavior that contributes to a positive environment for our
community include:
- Demonstrating empathy and kindness toward other people
- Being respectful of differing opinions, viewpoints, and experiences
- Giving and gracefully accepting constructive feedback
- Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience
- Focusing on what is best not just for us as individuals, but for the overall
community
Examples of unacceptable behavior include:
- The use of sexualized language or imagery, and sexual attention or advances of
any kind
- Trolling, insulting or derogatory comments, and personal or political attacks
- Public or private harassment
- Publishing others' private information, such as a physical or email address,
without their explicit permission
- Other conduct which could reasonably be considered inappropriate in a
professional setting
## Enforcement Responsibilities
Community leaders are responsible for clarifying and enforcing our standards of
acceptable behavior and will take appropriate and fair corrective action in
response to any behavior that they deem inappropriate, threatening, offensive,
or harmful.
Community leaders have the right and responsibility to remove, edit, or reject
comments, commits, code, wiki edits, issues, and other contributions that are
not aligned to this Code of Conduct, and will communicate reasons for moderation
decisions when appropriate.
## Scope
This Code of Conduct applies within all community spaces, and also applies when
an individual is officially representing the community in public spaces.
Examples of representing our community include using an official e-mail address,
posting via an official social media account, or acting as an appointed
representative at an online or offline event.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement at
me+coc@kentcdodds.com. All complaints will be reviewed and investigated promptly
and fairly.
All community leaders are obligated to respect the privacy and security of the
reporter of any incident.
## Enforcement Guidelines
Community leaders will follow these Community Impact Guidelines in determining
the consequences for any action they deem in violation of this Code of Conduct:
### 1. Correction
**Community Impact**: Use of inappropriate language or other behavior deemed
unprofessional or unwelcome in the community.
**Consequence**: A private, written warning from community leaders, providing
clarity around the nature of the violation and an explanation of why the
behavior was inappropriate. A public apology may be requested.
### 2. Warning
**Community Impact**: A violation through a single incident or series of
actions.
**Consequence**: A warning with consequences for continued behavior. No
interaction with the people involved, including unsolicited interaction with
those enforcing the Code of Conduct, for a specified period of time. This
includes avoiding interactions in community spaces as well as external channels
like social media. Violating these terms may lead to a temporary or permanent
ban.
### 3. Temporary Ban
**Community Impact**: A serious violation of community standards, including
sustained inappropriate behavior.
**Consequence**: A temporary ban from any sort of interaction or public
communication with the community for a specified period of time. No public or
private interaction with the people involved, including unsolicited interaction
with those enforcing the Code of Conduct, is allowed during this period.
Violating these terms may lead to a permanent ban.
### 4. Permanent Ban
**Community Impact**: Demonstrating a pattern of violation of community
standards, including sustained inappropriate behavior, harassment of an
individual, or aggression toward or disparagement of classes of individuals.
**Consequence**: A permanent ban from any sort of public interaction within the
community.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
version 2.0, available at
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
Community Impact Guidelines were inspired by
[Mozilla's code of conduct enforcement ladder](https://github.com/mozilla/diversity).
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see the FAQ at
https://www.contributor-covenant.org/faq. Translations are available at
https://www.contributor-covenant.org/translations.
================================================
FILE: CONTRIBUTING.md
================================================
# Contributing
Thanks for being willing to contribute!
**Working on your first Pull Request?** You can learn how from this _free_
series [How to Contribute to an Open Source Project on GitHub][egghead]
## Project setup
1. Fork and clone the repo
2. Run `npm run setup -s` to install dependencies and run validation
3. Create a branch for your PR with `git checkout -b pr/your-branch-name`
> Tip: Keep your `main` branch pointing at the original repository and make pull
> requests from branches on your fork. To do this, run:
>
> ```
> git remote add upstream https://github.com/kentcdodds/babel-plugin-macros
> git fetch upstream
> git branch --set-upstream-to=upstream/main main
> ```
>
> This will add the original repository as a "remote" called "upstream," Then
> fetch the git information from that remote, then set your local `main` branch
> to use the upstream main branch whenever you run `git pull`. Then you can make
> all of your pull request branches based on this `main` branch. Whenever you
> want to update your version of `main`, do a regular `git pull`.
## Committing and Pushing changes
Please make sure to run the tests before you commit your changes. You can run
`npm run test:update` which will update any snapshots that need updating. Make
sure to include those changes (if they exist) in your commit.
## Help needed
Please checkout the [the open issues][issues]
Also, please watch the repo and respond to questions/bug reports/feature
requests! Thanks!
<!-- prettier-ignore-start -->
[egghead]: https://app.egghead.io/playlists/how-to-contribute-to-an-open-source-project-on-github
[all-contributors]: https://github.com/all-contributors/all-contributors
[issues]: https://github.com/kentcdodds/babel-plugin-macros/issues
<!-- prettier-ignore-end -->
================================================
FILE: LICENSE
================================================
The MIT License (MIT)
Copyright (c) 2020 Kent C. Dodds
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================
<div align="center">
<h1>babel-plugin-macros 🎣</h1>
<p>Allows you to build simple compile-time libraries</p>
</div>
---
<!-- prettier-ignore-start -->
[![Build Status][build-badge]][build]
[![Code Coverage][coverage-badge]][coverage]
[![version][version-badge]][package]
[![downloads][downloads-badge]][npmtrends]
[![MIT License][license-badge]][license]
[![All Contributors][all-contributors-badge]](#contributors-)
[![PRs Welcome][prs-badge]][prs]
[![Code of Conduct][coc-badge]][coc]
<!-- prettier-ignore-end -->
## The problem
Check out
[this guest post](https://babeljs.io/blog/2017/09/11/zero-config-with-babel-macros)
on the Babel.js blog for a complete write up on the problem, motivation, and
solution.
Currently, each babel plugin in the babel ecosystem requires that you configure
it individually. This is fine for things like language features, but can be
frustrating overhead for libraries that allow for compile-time code
transformation as an optimization.
## This solution
babel-plugin-macros defines a standard interface for libraries that want to use
compile-time code transformation without requiring the user to add a babel
plugin to their build system (other than `babel-plugin-macros`, which is ideally
already in place).
<details>
<summary>Expand for more details on the motivation</summary>
For instance, many css-in-js libraries have a css tagged template string
function:
```js
const styles = css`
.red {
color: red;
}
`
```
The function compiles your css into (for example) an object with generated class
names for each of the classes you defined in your css:
```js
console.log(styles) // { red: "1f-d34j8rn43y587t" }
```
This class name can be generated at runtime (in the browser), but this has some
disadvantages:
- There is cpu usage/time overhead; the client needs to run the code to generate
these classes every time the page loads
- There is code bundle size overhead; the client needs to receive a CSS parser
in order to generate these class names, and shipping this makes the amount of
js the client needs to parse larger.
To help solve those issues, many css-in-js libraries write their own babel
plugin that generates the class names at compile-time instead of runtime:
```js
// Before running through babel:
const styles = css`
.red {
color: red;
}
`
// After running through babel, with the library-specific plugin:
const styles = {red: '1f-d34j8rn43y587t'}
```
If the css-in-js library supported babel-plugin-macros instead, then they
wouldn't need their own babel plugin to compile these out; they could instead
rely on babel-plugin-macros to do it for them. So if a user already had
`babel-plugin-macros` installed and configured with babel, then they wouldn't
need to change their babel configuration to get the compile-time benefits of the
library. This would be most useful if the boilerplate they were using came with
`babel-plugin-macros` out of the box, which is true for
[`create-react-app`][cra].
Although css-in-js is the most common example, there are lots of other things
you could use `babel-plugin-macros` for, like:
- Compiling GraphQL fragments into objects so that the client doesn't need a
GraphQL parser
- Eval-ing out code at compile time that will be baked into the runtime code,
for instance to get a list of directories in the filesystem (see
[preval][preval])
</details>
## Table of Contents
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
- [Installation](#installation)
- [Usage](#usage)
- [User docs](#user-docs)
- [Author docs](#author-docs)
- [Caveats](#caveats)
- [FAQ](#faq)
- [How do I find available macros?](#how-do-i-find-available-macros)
- [What's the difference between babel plugins and macros?](#whats-the-difference-between-babel-plugins-and-macros)
- [In what order are macros executed?](#in-what-order-are-macros-executed)
- [Does it work with function calls only?](#does-it-work-with-function-calls-only)
- [How about implicit optimizations at compile time?](#how-about-implicit-optimizations-at-compile-time)
- [Inspiration](#inspiration)
- [Other Solutions](#other-solutions)
- [Issues](#issues)
- [🐛 Bugs](#-bugs)
- [💡 Feature Requests](#-feature-requests)
- [Contributors ✨](#contributors-)
- [LICENSE](#license)
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
## Installation
This module is distributed via [npm][npm] which is bundled with [node][node] and
should be installed as one of your project's `devDependencies`:
```
npm install --save-dev babel-plugin-macros
```
## Usage
> You may like to watch
> [this YouTube video](https://www.youtube.com/watch?v=1queadQ0048&list=PLV5CVI1eNcJgCrPH_e6d57KRUTiDZgs0u)
> to get an idea of what macros is and how it can be used.
### User docs
Are you trying to use `babel-plugin-macros`? Go to
[`other/docs/user.md`](other/docs/user.md).
### Author docs
Are you trying to make your own macros that works with `babel-plugin-macros`? Go
to [`other/docs/author.md`](other/docs/author.md). (you should probably read the
user docs too).
### Caveats
#### Babel cache problem
> **Note:** This issue is not present when used in Create React App.
Most of the time you'll probably be using this with the babel cache enabled in
webpack to rebuild faster. If your macro function is **not pure** which gets
different output with same code (e.g., IO side effects) it will cause recompile
mechanism fail. Unfortunately you'll also experience this problem while
developing your macro as well. If there's not a change to the source code that's
being transpiled, then babel will use the cache rather than running your macro
again.
For now, to force recompile the code you can simply add a cache busting comment
in the file:
```diff
import macro from 'non-pure.macro';
-// Do some changes of your code or
+// add a cache busting comment to force recompile.
macro('parameters');
```
This problem is still being worked on and is not unique to
`babel-plugin-macros`. For more details and workarounds, please check related
issues below:
- babel-plugin-preval:
[How to force recompile? #19](https://github.com/kentcdodds/babel-plugin-preval/issues/19)
- graphql.macro:
[Recompile problem (babel cache) #6](https://github.com/evenchange4/graphql.macro/issues/6)
- twin.macro:
[Can't change taliwind config #37](https://github.com/ben-rogerson/twin.macro/discussions/37)
## FAQ
### How do I find available macros?
You can write your own without publishing them to `npm`, but if you'd like to
see existing macros you can add to your project, then take a look at the
[Awesome babel macros](https://github.com/jgierer12/awesome-babel-macros)
repository.
Please add any you don't see listed!
### What's the difference between babel plugins and macros?
Let's use
[`babel-plugin-console`](https://www.npmjs.com/package/babel-plugin-console) as
an example.
If we used `babel-plugin-console`, it would look like this:
1. Add `babel-plugin-console` to `.babelrc`
2. Use it in a code:
```js
function add100(a) {
const oneHundred = 100
console.scope('Add 100 to another number')
return add(a, oneHundred)
}
function add(a, b) {
return a + b
}
```
When that code is run, the `scope` function does some pretty nifty things:
**Browser:**

**Node:**
<img alt="Node console scoping add100" src="https://github.com/mattphillips/babel-plugin-console/raw/53536cba919d5be49d4f66d957769c07ca7a4207/assets/add100-node.png" width="372" />
Instead, let's use the macro it's shipped with like this:
1. Add `babel-plugin-macros` to `.babelrc` (only once for all macros)
2. Use it in a code:
```js
import scope from 'babel-plugin-console/scope.macro'
function add100(a) {
const oneHundred = 100
scope('Add 100 to another number')
return add(a, oneHundred)
}
function add(a, b) {
return a + b
}
```
The result is exactly the same, but this approach has a few advantages:
**Advantages:**
- requires only one entry in `.babelrc` for all macros used in project. Add that
once and you can use all the macros you want
- toolkits (like [create-react-app][cra]) may already support
`babel-plugin-macros`, so no configuration is needed at all
- it's explicit. With `console.scope` people may be fooled that it's just a
normal `console` API when there's really a babel transpilation going on. When
you import `scope`, it's obvious that it's macro and does something with the
code at compile time. Some ESLint rules may also have issues with plugins that
look for "global" variables
- macros are safer and easier to write, because they receive exactly the AST
node to process
- If you misconfigure `babel-plugin-console` you wont find out until you run the
code. If you misconfigure `babel-plugin-macros` you'll get a compile-time
error.
**Drawbacks:**
- Cannot (should not) be used for implicit transpilations (like syntax plugins)
- Explicitness is more verbose. Which some people might consider a drawback...
### In what order are macros executed?
This is another advantage of `babel-plugin-macros` over regular plugins. The
user of the macro is in control of the ordering! The order of execution is the
same order as imported. The order of execution is clear, explicit and in full
control of the user:
```js
import preval from 'preval.macro'
import idx from 'idx.macro'
// preval macro is evaluated first, then idx
```
This differs from the current situation with babel plugins where it's
prohibitively difficult to control the order plugins run in a particular file.
### Does it work with function calls only?
No! Any AST node type is supported.
It can be tagged template literal:
```js
import eval from 'eval.macro'
const val = eval`7 * 6`
```
A function:
```js
import eval from 'eval.macro'
const val = eval('7 * 6')
```
JSX Element:
```js
import Eval from 'eval.macro'
const val = <Eval>7 * 6</Eval>
```
Really, anything...
See the [testing snapshot](src/__tests__/__snapshots__/index.js.snap) for more
examples.
### How about implicit optimizations at compile time?
All examples above were _explicit_ - a macro was imported and then evaluated
with a specific AST node.
Completely different story are _implicit_ babel plugins, like
[transform-react-constant-elements](https://babeljs.io/docs/plugins/transform-react-constant-elements/),
which process whole AST tree.
Explicit is often a better pattern than implicit because it requires others to
understand how things are globally configured. This is in this spirit are
`babel-plugin-macros` designed. However, some things _do_ need to be implicit,
and those kinds of babel plugins can't be turned into macros.
## Inspiration
- [threepointone/babel-plugin-macros](https://github.com/threepointone/babel-plugin-macros)
- [facebookincubator/create-react-app#2730][cra-issue]
Thank you to [@phpnode](https://github.com/phpnode) for donating the npm package
`babel-plugin-macros`.
## Other Solutions
- [sweetjs](http://sweetjs.org/)
## Issues
_Looking to contribute? Look for the [Good First Issue][good-first-issue]
label._
### 🐛 Bugs
Please file an issue for bugs, missing documentation, or unexpected behavior.
[**See Bugs**][bugs]
### 💡 Feature Requests
Please file an issue to suggest new features. Vote on feature requests by adding
a 👍. This helps maintainers prioritize what to work on.
[**See Feature Requests**][requests]
## Contributors ✨
Thanks goes to these people ([emoji key][emojis]):
<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->
<!-- prettier-ignore-start -->
<!-- markdownlint-disable -->
<table>
<tr>
<td align="center"><a href="https://kentcdodds.com"><img src="https://avatars.githubusercontent.com/u/1500684?v=3?s=100" width="100px;" alt=""/><br /><sub><b>Kent C. Dodds</b></sub></a><br /><a href="https://github.com/kentcdodds/babel-plugin-macros/commits?author=kentcdodds" title="Code">💻</a> <a href="https://github.com/kentcdodds/babel-plugin-macros/commits?author=kentcdodds" title="Documentation">📖</a> <a href="#infra-kentcdodds" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a> <a href="https://github.com/kentcdodds/babel-plugin-macros/commits?author=kentcdodds" title="Tests">⚠️</a></td>
<td align="center"><a href="https://github.com/threepointone"><img src="https://avatars1.githubusercontent.com/u/18808?v=3?s=100" width="100px;" alt=""/><br /><sub><b>Sunil Pai</b></sub></a><br /><a href="#ideas-threepointone" title="Ideas, Planning, & Feedback">🤔</a></td>
<td align="center"><a href="http://suchipi.com"><img src="https://avatars0.githubusercontent.com/u/1341513?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Lily Scott</b></sub></a><br /><a href="#question-suchipi" title="Answering Questions">💬</a> <a href="https://github.com/kentcdodds/babel-plugin-macros/commits?author=suchipi" title="Documentation">📖</a></td>
<td align="center"><a href="http://twitter.com/dralletje"><img src="https://avatars1.githubusercontent.com/u/767261?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Michiel Dral</b></sub></a><br /><a href="#ideas-dralletje" title="Ideas, Planning, & Feedback">🤔</a></td>
<td align="center"><a href="https://github.com/tkh44"><img src="https://avatars2.githubusercontent.com/u/662750?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Kye Hohenberger</b></sub></a><br /><a href="#ideas-tkh44" title="Ideas, Planning, & Feedback">🤔</a></td>
<td align="center"><a href="https://hamil.town"><img src="https://avatars1.githubusercontent.com/u/11481355?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Mitchell Hamilton</b></sub></a><br /><a href="https://github.com/kentcdodds/babel-plugin-macros/commits?author=mitchellhamilton" title="Code">💻</a> <a href="https://github.com/kentcdodds/babel-plugin-macros/commits?author=mitchellhamilton" title="Tests">⚠️</a></td>
<td align="center"><a href="https://github.com/wKovacs64"><img src="https://avatars1.githubusercontent.com/u/1288694?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Justin Hall</b></sub></a><br /><a href="https://github.com/kentcdodds/babel-plugin-macros/commits?author=wKovacs64" title="Documentation">📖</a></td>
</tr>
<tr>
<td align="center"><a href="https://github.com/PiereDome"><img src="https://avatars3.githubusercontent.com/u/1903016?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Brian Pedersen</b></sub></a><br /><a href="https://github.com/kentcdodds/babel-plugin-macros/commits?author=PiereDome" title="Code">💻</a> <a href="https://github.com/kentcdodds/babel-plugin-macros/commits?author=PiereDome" title="Documentation">📖</a></td>
<td align="center"><a href="https://github.com/apalm"><img src="https://avatars3.githubusercontent.com/u/4495237?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Andrew Palm</b></sub></a><br /><a href="https://github.com/kentcdodds/babel-plugin-macros/commits?author=apalm" title="Code">💻</a></td>
<td align="center"><a href="https://michaelhsu.tw/"><img src="https://avatars1.githubusercontent.com/u/1527371?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Michael Hsu</b></sub></a><br /><a href="https://github.com/kentcdodds/babel-plugin-macros/commits?author=evenchange4" title="Documentation">📖</a> <a href="#plugin-evenchange4" title="Plugin/utility libraries">🔌</a></td>
<td align="center"><a href="https://github.com/citycide"><img src="https://avatars2.githubusercontent.com/u/16605186?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Bo Lingen</b></sub></a><br /><a href="https://github.com/kentcdodds/babel-plugin-macros/commits?author=citycide" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/tylerthehaas"><img src="https://avatars1.githubusercontent.com/u/11150235?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Tyler Haas</b></sub></a><br /><a href="https://github.com/kentcdodds/babel-plugin-macros/commits?author=tylerthehaas" title="Documentation">📖</a></td>
<td align="center"><a href="https://github.com/FWeinb"><img src="https://avatars0.githubusercontent.com/u/1250430?v=4?s=100" width="100px;" alt=""/><br /><sub><b>FWeinb</b></sub></a><br /><a href="https://github.com/kentcdodds/babel-plugin-macros/commits?author=FWeinb" title="Code">💻</a></td>
<td align="center"><a href="http://www.tomasehrlich.cz"><img src="https://avatars2.githubusercontent.com/u/827862?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Tomáš Ehrlich</b></sub></a><br /><a href="https://github.com/kentcdodds/babel-plugin-macros/issues?q=author%3Atricoder42" title="Bug reports">🐛</a> <a href="https://github.com/kentcdodds/babel-plugin-macros/commits?author=tricoder42" title="Code">💻</a></td>
</tr>
<tr>
<td align="center"><a href="https://github.com/jgierer12"><img src="https://avatars0.githubusercontent.com/u/4331946?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Jonas Gierer</b></sub></a><br /><a href="https://github.com/kentcdodds/babel-plugin-macros/commits?author=jgierer12" title="Documentation">📖</a></td>
<td align="center"><a href="http://loicpadier.com"><img src="https://avatars2.githubusercontent.com/u/4009640?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Loïc Padier</b></sub></a><br /><a href="https://github.com/kentcdodds/babel-plugin-macros/commits?author=lPadier" title="Code">💻</a></td>
<td align="center"><a href="https://www.pshrmn.com"><img src="https://avatars0.githubusercontent.com/u/1127037?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Paul Sherman</b></sub></a><br /><a href="https://github.com/kentcdodds/babel-plugin-macros/commits?author=pshrmn" title="Code">💻</a></td>
<td align="center"><a href="http://burningpotato.com"><img src="https://avatars1.githubusercontent.com/u/540777?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Conrad Buck</b></sub></a><br /><a href="https://github.com/kentcdodds/babel-plugin-macros/commits?author=conartist6" title="Code">💻</a> <a href="https://github.com/kentcdodds/babel-plugin-macros/commits?author=conartist6" title="Tests">⚠️</a> <a href="https://github.com/kentcdodds/babel-plugin-macros/commits?author=conartist6" title="Documentation">📖</a></td>
<td align="center"><a href="https://github.com/InvictusMB"><img src="https://avatars3.githubusercontent.com/u/3091209?v=4?s=100" width="100px;" alt=""/><br /><sub><b>InvictusMB</b></sub></a><br /><a href="https://github.com/kentcdodds/babel-plugin-macros/commits?author=InvictusMB" title="Tests">⚠️</a></td>
<td align="center"><a href="https://codefund.io"><img src="https://avatars2.githubusercontent.com/u/12481?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Eric Berry</b></sub></a><br /><a href="#fundingFinding-coderberry" title="Funding Finding">🔍</a></td>
<td align="center"><a href="http://futagoza.github.io/"><img src="https://avatars1.githubusercontent.com/u/1943570?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Futago-za Ryuu</b></sub></a><br /><a href="https://github.com/kentcdodds/babel-plugin-macros/commits?author=futagoza" title="Code">💻</a> <a href="https://github.com/kentcdodds/babel-plugin-macros/commits?author=futagoza" title="Tests">⚠️</a></td>
</tr>
<tr>
<td align="center"><a href="https://luc.im"><img src="https://avatars3.githubusercontent.com/u/6616955?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Luc</b></sub></a><br /><a href="https://github.com/kentcdodds/babel-plugin-macros/commits?author=lucleray" title="Code">💻</a></td>
<td align="center"><a href="http://wintercounter.me"><img src="https://avatars2.githubusercontent.com/u/963776?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Victor Vincent</b></sub></a><br /><a href="https://github.com/kentcdodds/babel-plugin-macros/commits?author=wintercounter" title="Code">💻</a></td>
<td align="center"><a href="http://mvasilkov.ovh"><img src="https://avatars3.githubusercontent.com/u/140257?v=4?s=100" width="100px;" alt=""/><br /><sub><b>я котик пур-пур</b></sub></a><br /><a href="https://github.com/kentcdodds/babel-plugin-macros/commits?author=mvasilkov" title="Documentation">📖</a></td>
<td align="center"><a href="http://armandososa.com"><img src="https://avatars0.githubusercontent.com/u/139577?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Armando Sosa</b></sub></a><br /><a href="https://github.com/kentcdodds/babel-plugin-macros/commits?author=soska" title="Documentation">📖</a></td>
<td align="center"><a href="https://github.com/matvp91"><img src="https://avatars3.githubusercontent.com/u/12699796?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Matthias</b></sub></a><br /><a href="https://github.com/kentcdodds/babel-plugin-macros/commits?author=matvp91" title="Code">💻</a></td>
<td align="center"><a href="https://www.jovidecroock.com/"><img src="https://avatars3.githubusercontent.com/u/17125876?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Jovi De Croock</b></sub></a><br /><a href="https://github.com/kentcdodds/babel-plugin-macros/commits?author=JoviDeCroock" title="Code">💻</a> <a href="https://github.com/kentcdodds/babel-plugin-macros/commits?author=JoviDeCroock" title="Tests">⚠️</a></td>
<td align="center"><a href="http://victorarowo.com"><img src="https://avatars0.githubusercontent.com/u/25545108?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Victor Arowo</b></sub></a><br /><a href="https://github.com/kentcdodds/babel-plugin-macros/commits?author=VictorArowo" title="Documentation">📖</a></td>
</tr>
<tr>
<td align="center"><a href="https://twitter.com/alexandermchan"><img src="https://avatars.githubusercontent.com/u/1864372?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Alex Chan</b></sub></a><br /><a href="https://github.com/kentcdodds/babel-plugin-macros/commits?author=alexanderchan" title="Documentation">📖</a></td>
<td align="center"><a href="https://probablyup.com"><img src="https://avatars.githubusercontent.com/u/570070?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Evan Jacobs</b></sub></a><br /><a href="https://github.com/kentcdodds/babel-plugin-macros/commits?author=probablyup" title="Code">💻</a></td>
</tr>
</table>
<!-- markdownlint-restore -->
<!-- prettier-ignore-end -->
<!-- ALL-CONTRIBUTORS-LIST:END -->
This project follows the [all-contributors][all-contributors] specification.
Contributions of any kind welcome!
## LICENSE
MIT
<!-- prettier-ignore-start -->
[npm]: https://www.npmjs.com
[node]: https://nodejs.org
[build-badge]: https://img.shields.io/github/workflow/status/kentcdodds/babel-plugin-macros/validate?logo=github&style=flat-square
[build]: https://github.com/kentcdodds/babel-plugin-macros/actions?query=workflow%3Avalidate
[coverage-badge]: https://img.shields.io/codecov/c/github/kentcdodds/babel-plugin-macros.svg?style=flat-square
[coverage]: https://codecov.io/github/kentcdodds/babel-plugin-macros
[version-badge]: https://img.shields.io/npm/v/babel-plugin-macros.svg?style=flat-square
[package]: https://www.npmjs.com/package/babel-plugin-macros
[downloads-badge]: https://img.shields.io/npm/dm/babel-plugin-macros.svg?style=flat-square
[npmtrends]: http://www.npmtrends.com/babel-plugin-macros
[license-badge]: https://img.shields.io/npm/l/babel-plugin-macros.svg?style=flat-square
[license]: https://github.com/kentcdodds/babel-plugin-macros/blob/main/LICENSE
[prs-badge]: https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square
[prs]: http://makeapullrequest.com
[coc-badge]: https://img.shields.io/badge/code%20of-conduct-ff69b4.svg?style=flat-square
[coc]: https://github.com/kentcdodds/babel-plugin-macros/blob/main/CODE_OF_CONDUCT.md
[emojis]: https://github.com/all-contributors/all-contributors#emoji-key
[all-contributors]: https://github.com/all-contributors/all-contributors
[all-contributors-badge]: https://img.shields.io/github/all-contributors/kentcdodds/babel-plugin-macros?color=orange&style=flat-square
[bugs]: https://github.com/kentcdodds/babel-plugin-macros/issues?utf8=%E2%9C%93&q=is%3Aissue+is%3Aopen+sort%3Acreated-desc+label%3Abug
[requests]: https://github.com/kentcdodds/babel-plugin-macros/issues?utf8=%E2%9C%93&q=is%3Aissue+is%3Aopen+sort%3Areactions-%2B1-desc+label%3Aenhancement
[good-first-issue]: https://github.com/kentcdodds/babel-plugin-macros/issues?utf8=%E2%9C%93&q=is%3Aissue+is%3Aopen+sort%3Areactions-%2B1-desc+label%3Aenhancement+label%3A%22good+first+issue%22
[preval]: https://github.com/kentcdodds/babel-plugin-preval
[cra]: https://github.com/facebook/create-react-app
[cra-issue]: https://github.com/facebook/create-react-app/issues/2730
<!-- prettier-ignore-end -->
================================================
FILE: other/MAINTAINING.md
================================================
# Maintaining
This is documentation for maintainers of this project.
## Code of Conduct
Please review, understand, and be an example of it. Violations of the code of
conduct are taken seriously, even (especially) for maintainers.
## Issues
We want to support and build the community. We do that best by helping people
learn to solve their own problems. We have an issue template and hopefully most
folks follow it. If it's not clear what the issue is, invite them to create a
minimal reproduction of what they're trying to accomplish or the bug they think
they've found.
Once it's determined that a code change is necessary, point people to
[makeapullrequest.com](http://makeapullrequest.com) and invite them to make a
pull request. If they're the one who needs the feature, they're the one who can
build it. If they need some hand holding and you have time to lend a hand,
please do so. It's an investment into another human being, and an investment
into a potential maintainer.
Remember that this is open source, so the code is not yours, it's ours. If
someone needs a change in the codebase, you don't have to make it happen
yourself. Commit as much time to the project as you want/need to. Nobody can ask
any more of you than that.
## Pull Requests
As a maintainer, you're fine to make your branches on the main repo or on your
own fork. Either way is fine.
When we receive a pull request, a github action is kicked off automatically (see
the `.github/workflows/validate.yml` for what runs in the action). We avoid
merging anything that breaks the validate action.
Please review PRs and focus on the code rather than the individual. You never
know when this is someone's first ever PR and we want their experience to be as
positive as possible, so be uplifting and constructive.
When you merge the pull request, 99% of the time you should use the
[Squash and merge](https://help.github.com/articles/merging-a-pull-request/)
feature. This keeps our git history clean, but more importantly, this allows us
to make any necessary changes to the commit message so we release what we want
to release. See the next section on Releases for more about that.
## Release
Our releases are automatic. They happen whenever code lands into `main`. A
github action gets kicked off and if it's successful, a tool called
[`semantic-release`](https://github.com/semantic-release/semantic-release) is
used to automatically publish a new release to npm as well as a changelog to
GitHub. It is only able to determine the version and whether a release is
necessary by the git commit messages. With this in mind, **please brush up on
[the commit message convention][commit] which drives our releases.**
> One important note about this: Please make sure that commit messages do NOT
> contain the words "BREAKING CHANGE" in them unless we want to push a major
> version. I've been burned by this more than once where someone will include
> "BREAKING CHANGE: None" and it will end up releasing a new major version. Not
> a huge deal honestly, but kind of annoying...
## Thanks!
Thank you so much for helping to maintain this project!
[commit]:
https://github.com/conventional-changelog-archived-repos/conventional-changelog-angular/blob/ed32559941719a130bb0327f886d6a32a8cbc2ba/convention.md
================================================
FILE: other/USERS.md
================================================
# Users
If you or your company uses this project, add your name to this list! Eventually
we may have a website to showcase these (wanna build it!?)
> No users have been added yet!
<!--
This file should just be a bulleted list like this:
- [Company/Project/Person](https://example.com) uses it in [some app](https://example.com)
-->
================================================
FILE: other/docs/author.md
================================================
# `babel-plugin-macros` Usage for macros authors
> See also:
> [the `user` docs](https://github.com/kentcdodds/babel-plugin-macros/blob/master/other/docs/user.md).
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 a macro
> You might appreciate
> [this example repo](https://github.com/kentcdodds/cra-macro-example) which
> shows how to write and use macros in a create-react-app application.
A macro is a JavaScript module that exports a function. Here's a simple example:
```javascript
const {createMacro} = require('babel-plugin-macros')
// `createMacro` is simply a function that ensures your macro is only
// called in the context of a babel transpilation and will throw an
// error with a helpful message if someone does not have babel-plugin-macros
// configured correctly
module.exports = createMacro(myMacro)
function myMacro({references, state, babel}) {
// state is the second argument you're passed to a visitor in a
// normal babel plugin. `babel` is the `babel-plugin-macros` module.
// do whatever you like to the AST paths you find in `references`
// read more below...
}
```
It can be published to the npm registry (for generic macros, like a css-in-js
library) or used locally (for domain-specific macros, like handling some special
case for your company's localization efforts).
> Before you write a custom macro, you might consider whether
> [`babel-plugin-preval`][preval] help you do what you want as it's pretty
> powerful.
There are two parts to the `babel-plugin-macros` API:
1. The filename convention
2. The function you export
### Filename
The way that `babel-plugin-macros` determines whether to run a macro is based on
the source string of the `import` or `require` statement. It must match this
regex: `/[./]macro(\.c?js)?$/` for example:
_matches_:
```
'my.macro'
'my.macro.js'
'my.macro.cjs'
'my/macro'
'my/macro.js'
'my/macro.cjs'
```
_does not match_:
```
'my-macro'
'my.macro.is-sweet'
'my/macro/rocks'
```
> So long as your file can be required at a matching path, you're good. So you
> could put it in: `my/macro/index.js` and people would: `require('my/macro')`
> which would work fine.
**If you're going to publish this to npm,** the most ergonomic thing would be to
name it something that ends in `.macro`. If it's part of a larger package, then
calling the file `macro.js` or placing it in `macro/index.js` is a great way to
go as well. Then people could do:
```js
import Nice from 'nice.macro'
// or
import Sweet from 'sweet/macro'
```
In addition, please publish your macro with the [`keyword`][keyword] of
`babel-plugin-macros` (note the "s"). That way folks can easily find macros by
searching for the [`babel-plugin-macros` keyword on
npm][npm-babel-plugin-macros]. In addition, and you can add this badge to the
top of your README:
[](https://github.com/kentcdodds/babel-plugin-macros)
```
[](https://github.com/kentcdodds/babel-plugin-macros)
```
### Function API
The macro you create should export a function. That function accepts a single
parameter which is an object with the following properties:
#### state
The state of the file being traversed. It's the second argument you receive in a
visitor function in a normal babel plugin.
#### babel
This is the same thing you get as an argument to normal babel plugins. It is
also the same thing you get if you `require('babel-core')`.
#### references
This is an object that contains arrays of all the references to things imported
from macro keyed based on the name of the import. The items in each array are
the paths to the references.
<details>
<summary>Some examples:</summary>
```javascript
import MyMacro from './my.macro'
MyMacro(
{someOption: true},
`
some stuff
`,
)
// references: { default: [BabelPath] }
```
```javascript
import {foo as FooMacro} from './my.macro'
FooMacro(
{someOption: true},
`
some stuff
`,
)
// references: { foo: [BabelPath] }
```
```javascript
import {foo as FooMacro} from './my.macro'
// no usage...
// references: {}
```
</details>
From here, it's just a matter of doing stuff with the `BabelPath`s that
you're given. For that check out [the babel handbook][babel-handbook].
> One other thing to note is that after your macro has run, babel-plugin-macros
> will remove the import/require statement for you.
#### source
This is a string used as import declaration's source - i.e. `'./my.macro'`.
#### config
There is a 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`
The content of the config will be merged with the content of the babel macros
plugin options. Config options take priority.
All together specifying and using the config might look like this:
```javascript
// .babel-plugin-macros.config.js
module.exports = {
taggedTranslations: {locale: 'en_US'},
}
// .babel.config.js
module.exports = {
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
}
```
Note that in the above example if both files were specified, the final locale
value would be `en_US`, since that is the value in the plugin config file.
### Keeping imports
As said before, `babel-plugin-macros` automatically removes an import statement
of macro. If you want to keep it because you have other plugins processing
macros, return `{ keepImports: true }` from your macro:
```javascript
const {createMacro} = require('babel-plugin-macros')
module.exports = createMacro(taggedTranslationsMacro)
function taggedTranslationsMacro({references, state, babel}) {
// process node from references
return {
keepImports: true,
}
}
```
## Throwing Helpful Errors
Debugging stuff that transpiles your code is the worst, especially for
beginners. That's why it's important that you make assertions, and catch errors
to throw more meaningful errors with helpful information for the developer to
know what to do to resolve the issue.
In an effort to make this easier for you, `babel-plugin-macros` will wrap the
invocation of your plugin in a `try/catch` and throw as helpful an error message
as possible for you.
To make it even better, you can throw your own with more context. For example:
```javascript
const {createMacro, MacroError} = require('babel-plugin-macros')
module.exports = createMacro(myMacro)
function myMacro({references, state, babel}) {
// something unexpected happens:
throw new MacroError(
'Some helpful and contextual message. Learn more: ' +
'https://github.com/your-org/your-repo/blob/master/docs/errors.md#learn-more-about-eror-title',
)
}
```
## Testing your macro
The best way to test your macro is using [`babel-plugin-tester`][tester]:
```javascript
import pluginTester from 'babel-plugin-tester'
import plugin from 'babel-plugin-macros'
pluginTester({
plugin,
snapshot: true,
babelOptions: {filename: __filename},
tests: [
`
import MyMacro from '../my.macro'
MyMacro({someOption: true}, \`
some stuff
\`)
`,
],
})
```
There is currently no way to get code coverage for your macro this way however.
If you want code coverage, you'll have to call your macro yourself.
Contributions to improve this experience are definitely welcome!
## Async logic
Unfortunately, babel plugins are synchronous so you can't do anything
asynchronous with `babel-plugin-macros`. However, you can cheat a bit by running
`child_process`'s `spawnSync` to synchronously execute a file. It's definitely a
hack and is not great for performance, but in most cases it's fast enough™️.
Luckily, [@Zemnmez](https://github.com/Zemnmez) created
[`do-sync`](https://github.com/Zemnmez/do-sync) which makes doing this much more
straightforward:
```javascript
const {doSync} = require('do-sync')
const {createMacro, MacroError} = require('babel-plugin-macros')
module.exports = createMacro(myMacro)
const getTheFlowers = doSync(async (arg1, arg2) => {
const dep = require('some-dependency')
const flowers = await dep(arg1, arg2.stuff)
return flowers
})
function myMacro({references, state, babel}) {
const flowers = getTheFlowers('...', {stuff: '...'})
// ... more sync stuff
}
```
[preval]: https://github.com/kentcdodds/babel-plugin-preval
[babel-handbook]:
https://github.com/thejameskyle/babel-handbook/blob/master/translations/en/plugin-handbook.md
[tester]: https://github.com/babel-utils/babel-plugin-tester
[keyword]: https://docs.npmjs.com/files/package.json#keywords
[npm-babel-plugin-macros]:
https://www.npmjs.com/browse/keyword/babel-plugin-macros
[cosmiconfig]: https://www.npmjs.com/package/cosmiconfig
================================================
FILE: other/docs/user.md
================================================
# `babel-plugin-macros` Usage for users
> See also:
> [the `author` docs](https://github.com/kentcdodds/babel-plugin-macros/blob/master/other/docs/author.md).
## Adding the plugin to your config
### Via `.babelrc` (Recommended)
**.babelrc**
```json
{
"plugins": ["macros"]
}
```
### Via [`babel.config.js`](https://babeljs.io/docs/en/configuration#babelconfigjs)
**babel.config.js**
```javascript
module.exports = function (api) {
return {
plugins: ['macros'],
}
}
```
### Via CLI
```shell
babel --plugins babel-plugin-macros script.js
```
### Via Node API
```js
require('babel-core').transform('code', {
plugins: ['macros'],
})
```
## Using a macro
With the `babel-plugin-macros` plugin added to your config, we can now use a
macro that works with the `babel-plugin-macros` API. Let's assume we have such a
module in our project called `eval.macro.js`. To use it, we `import` or
`require` the macro module in our code like so:
```javascript
import MyEval from './eval.macro'
// or
const MyEval = require('./eval.macro')
```
Then we use that variable however the documentation for the macro says.
Incidentally, `eval.macro.js` actually exists in the tests for
`babel-plugin-macros` [here][eval-macro] and you can see how it transforms our
code in [the `babel-plugin-macros` snapshots][eval-snapshots].
> Note here that the real benefit is that we don't need to configure anything
> for every macro you add. We simply configure `babel-plugin-macros`, then we
> can use any macro available. This is part of the benefit of using
> `babel-plugin-macros`.
[eval-macro]:
https://github.com/kentcdodds/babel-plugin-macros/blob/master/src/__tests__/fixtures/eval.macro.js
[eval-snapshots]:
https://github.com/kentcdodds/babel-plugin-macros/blob/master/src/__tests__/__snapshots__/index.js.snap
### Using with create-react-app
> [Checkout the CRA Macro Example repo](https://github.com/kentcdodds/cra-macro-example)
`babel-plugin-macros` ships with `react-scripts` 2.0! This is awesome because it
allows for babel to be configured in a nice way without having to eject from
`create-react-app`!
Before deciding to use this however you should be aware of a few things:
1. Features may be broken or not work as expected
2. Documentation for new features is still sparse, so look through the pull
requests for how they're expected to work
With that being said you can use all the awesomeness of `babel-plugin-macros`
inside `create-react-app` by running one of the following commands based on your
situation.
```
$ # Create a new application
$ npx create-react-app my-app
$ # Upgrade an existing application
$ yarn upgrade react-scripts
```
### config
There is a feature that allows you to configure your macro. 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`
You need to specify your `configName`. EG: For configuring [styled-components
macro][styled-components], the `configName` is `"styledComponents"`:
```js
// babel-plugin-macros.config.js
module.exports = {
// ...
// Other macros config
styledComponents: {
pure: true,
},
}
```
[cosmiconfig]: https://www.npmjs.com/package/cosmiconfig
[styled-components]: https://www.styled-components.com/docs/tooling#babel-macro
================================================
FILE: other/manual-releases.md
================================================
# manual-releases
This project has an automated release set up. So things are only released when
there are useful changes in the code that justify a release. But sometimes
things get messed up one way or another and we need to trigger the release
ourselves. When this happens, simply bump the number below and commit that with
the following commit message based on your needs:
**Major**
```
fix(release): manually release a major version
There was an issue with a major release, so this manual-releases.md
change is to release a new major version.
Reference: #<the number of a relevant pull request, issue, or commit>
BREAKING CHANGE: <mention any relevant breaking changes (this is what triggers the major version change so don't skip this!)>
```
**Minor**
```
feat(release): manually release a minor version
There was an issue with a minor release, so this manual-releases.md
change is to release a new minor version.
Reference: #<the number of a relevant pull request, issue, or commit>
```
**Patch**
```
fix(release): manually release a patch version
There was an issue with a patch release, so this manual-releases.md
change is to release a new patch version.
Reference: #<the number of a relevant pull request, issue, or commit>
```
The number of times we've had to do a manual release is: 0
================================================
FILE: other/mock-modules/@scope/package/macro.js
================================================
// this is used to make sure that you can require macro from node_modules
const {createMacro} = require('../../../src')
const innerFn = jest.fn()
module.exports = createMacro(innerFn)
module.exports.innerFn = innerFn
================================================
FILE: other/mock-modules/babel-plugin-macros-test-error-thrower/macro.js
================================================
// const printAST = require('ast-pretty-print')
const {createMacro} = require('../../src')
module.exports = createMacro(evalMacro)
function evalMacro() {
throw new Error('not helpful')
}
================================================
FILE: other/mock-modules/babel-plugin-macros-test-error-thrower.macro/index.js
================================================
// const printAST = require('ast-pretty-print')
const {createMacro} = require('../../src')
module.exports = createMacro(evalMacro)
function evalMacro() {
throw new Error('not helpful')
}
================================================
FILE: other/mock-modules/babel-plugin-macros-test-fake/macro.js
================================================
// this is used to make sure that you can require macro from node_modules
const {createMacro} = require('../../src')
const innerFn = jest.fn()
module.exports = createMacro(innerFn)
module.exports.innerFn = innerFn
================================================
FILE: other/mock-modules/babel-plugin-path-replace/index.js
================================================
const types = require('@babel/types')
const problematicVisitor = {
VariableDeclarator: {
enter(path) {
const initPath = path.get('init')
initPath.replaceWith(
types.sequenceExpression([
types.stringLiteral('foobar'),
initPath.node,
]),
)
},
},
}
module.exports = () => ({
visitor: {
Program: {
enter(path) {
path.traverse(problematicVisitor)
},
},
},
})
================================================
FILE: package.json
================================================
{
"name": "babel-plugin-macros",
"version": "0.0.0-semantically-released",
"description": "Allows you to build compile-time libraries",
"main": "src/index.js",
"scripts": {
"lint": "kcd-scripts lint",
"setup": "npm install && npm run validate -s",
"test": "kcd-scripts test",
"test:update": "npm test -- --updateSnapshot --coverage",
"validate": "kcd-scripts validate"
},
"files": [
"src/index.js"
],
"keywords": [
"babel-plugin",
"macros",
"macro",
"babel-macro",
"babel-plugin-macro",
"babel-macros",
"babel-plugin-macros"
],
"author": "Kent C. Dodds <me@kentcdodds.com> (https://kentcdodds.com)",
"license": "MIT",
"dependencies": {
"cosmiconfig": "^7.0.0",
"resolve": "^1.19.0"
},
"devDependencies": {
"@babel/core": "^7.12.9",
"@babel/parser": "^7.12.7",
"@babel/plugin-transform-modules-commonjs": "^7.16.7",
"@babel/types": "^7.12.7",
"ast-pretty-print": "^2.0.1",
"babel-plugin-tester": "^10.0.0",
"babel-plugin-transform-es2015-modules-commonjs": "^6.26.2",
"cpy": "^8.1.1",
"kcd-scripts": "^7.1.0"
},
"eslintConfig": {
"extends": "./node_modules/kcd-scripts/eslint.js"
},
"eslintIgnore": [
"node_modules",
"coverage",
"dist"
],
"babel": {
"plugins": [
"@babel/transform-modules-commonjs"
]
},
"repository": {
"type": "git",
"url": "https://github.com/kentcdodds/babel-plugin-macros"
},
"bugs": {
"url": "https://github.com/kentcdodds/babel-plugin-macros/issues"
},
"homepage": "https://github.com/kentcdodds/babel-plugin-macros#readme",
"engines": {
"node": ">=10",
"npm": ">=6"
}
}
================================================
FILE: src/__tests__/__snapshots__/create-macros.js.snap
================================================
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`attempting to create a macros with the configName of options throws an error 1`] = `"You cannot use the configName \\"options\\". It is reserved for babel-plugin-macros."`;
exports[`throws error if it is not transpiled 1`] = `"The macro you imported from \\"untranspiled.macro\\" is being executed outside the context of compilation with babel-plugin-macros. This indicates that you don't have the babel plugin \\"babel-plugin-macros\\" configured correctly. Please see the documentation for how to configure babel-plugin-macros properly: https://github.com/kentcdodds/babel-plugin-macros/blob/master/other/docs/user.md"`;
================================================
FILE: src/__tests__/__snapshots__/index.js.snap
================================================
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`unknown plugin Macros are applied in the order respecting plugins order: Macros are applied in the order respecting plugins order 1`] = `
import Wrap from "./fixtures/jsx-id-prefix.macro";
const bar = Wrap(<div id="d1"><p id="p1"></p></div>);
↓ ↓ ↓ ↓ ↓ ↓
const bar = Wrap(
<div id="plugin-macro-d1">
<p id="plugin-macro-p1"></p>
</div>,
)
`;
exports[`unknown plugin Supports named imports: Supports named imports 1`] = `
import {css as CSS, styled as STYLED} from './fixtures/emotion.macro'
const red = CSS\`
background-color: red;
\`
const Div = STYLED.div\`
composes: \${red}
color: blue;
\`
↓ ↓ ↓ ↓ ↓ ↓
const red = 'background-color: red;'
const Div = STYLED.div\`composes: background-color: red;
color: blue;\`
`;
exports[`unknown plugin Works as a JSXElement: Works as a JSXElement 1`] = `
import MyEval from './fixtures/eval.macro'
const x = <MyEval>34 + 45</MyEval>
↓ ↓ ↓ ↓ ↓ ↓
const x = 79
`;
exports[`unknown plugin appends the npm URL for errors thrown by node modules with a slash: appends the npm URL for errors thrown by node modules with a slash 1`] = `
import errorThrower from 'babel-plugin-macros-test-error-thrower/macro'
errorThrower('hi')
↓ ↓ ↓ ↓ ↓ ↓
Error: babel-plugin-macros-test-error-thrower/macro: not helpful Learn more: https://www.npmjs.com/package/babel-plugin-macros-test-error-thrower
`;
exports[`unknown plugin appends the npm URL for errors thrown by node modules: appends the npm URL for errors thrown by node modules 1`] = `
import errorThrower from 'babel-plugin-macros-test-error-thrower.macro'
errorThrower('hi')
↓ ↓ ↓ ↓ ↓ ↓
Error: babel-plugin-macros-test-error-thrower.macro: not helpful Learn more: https://www.npmjs.com/package/babel-plugin-macros-test-error-thrower.macro
`;
exports[`unknown plugin does nothing but remove macros if it is unused: does nothing but remove macros if it is unused 1`] = `
import foo from "./fixtures/eval.macro";
const bar = 42;
↓ ↓ ↓ ↓ ↓ ↓
const bar = 42
`;
exports[`unknown plugin forwards MacroErrors thrown by the macro: forwards MacroErrors thrown by the macro 1`] = `
import errorThrower from './fixtures/macro-error-thrower.macro'
errorThrower('hey')
↓ ↓ ↓ ↓ ↓ ↓
MacroError: very helpful
`;
exports[`unknown plugin macros can set their configName and get their config: macros can set their configName and get their config 1`] = `
import configured from './configurable.macro'
// eslint-disable-next-line babel/no-unused-expressions
configured\`stuff\`
↓ ↓ ↓ ↓ ↓ ↓
// eslint-disable-next-line babel/no-unused-expressions
configured\`stuff\`
`;
exports[`unknown plugin optionally keep imports (import declaration): optionally keep imports (import declaration) 1`] = `
import macro from './fixtures/keep-imports.macro'
const red = macro('noop');
↓ ↓ ↓ ↓ ↓ ↓
import macro from './fixtures/keep-imports.macro'
const red = macro('noop')
`;
exports[`unknown plugin optionally keep imports (variable assignment): optionally keep imports (variable assignment) 1`] = `
const macro = require('./fixtures/keep-imports.macro')
const red = macro('noop');
↓ ↓ ↓ ↓ ↓ ↓
const macro = require('./fixtures/keep-imports.macro')
const red = macro('noop')
`;
exports[`unknown plugin optionally keep imports in combination with babel-preset-env (#80): optionally keep imports in combination with babel-preset-env (#80) 1`] = `
import macro from './fixtures/keep-imports.macro'
const red = macro('noop')
↓ ↓ ↓ ↓ ↓ ↓
'use strict'
var _keepImports = require('./fixtures/keep-imports.macro')
var _keepImports2 = _interopRequireDefault(_keepImports)
function _interopRequireDefault(obj) {
return obj && obj.__esModule ? obj : {default: obj}
}
const red = (0, _keepImports2.default)('noop')
`;
exports[`unknown plugin prepends the relative path for errors thrown by the macro: prepends the relative path for errors thrown by the macro 1`] = `
import errorThrower from './fixtures/error-thrower.macro'
errorThrower('hey')
↓ ↓ ↓ ↓ ↓ ↓
Error: ./fixtures/error-thrower.macro: very unhelpful
`;
exports[`unknown plugin raises an error if macro does not exist: raises an error if macro does not exist 1`] = `
import foo from './some-macros-that-doesnt-even-need-to-exist.macro'
export default 'something else'
↓ ↓ ↓ ↓ ↓ ↓
Error: Cannot find module './some-macros-that-doesnt-even-need-to-exist.macro' from '<PROJECT_ROOT>/src/__tests__'
`;
exports[`unknown plugin supports compiled macros (\`__esModule\` + \`export default\`): supports compiled macros (\`__esModule\` + \`export default\`) 1`] = `
import {css, styled} from './fixtures/emotion-esm.macro'
const red = css\`
background-color: red;
\`
const Div = styled.div\`
composes: \${red}
color: blue;
\`
↓ ↓ ↓ ↓ ↓ ↓
const red = css\`
background-color: red;
\`
const Div = styled.div\`
composes: \${red}
color: blue;
\`
`;
exports[`unknown plugin supports macros from node_modules with scope: supports macros from node_modules with scope 1`] = `
import fakeMacro from '@scope/package/macro'
fakeMacro('hi')
↓ ↓ ↓ ↓ ↓ ↓
fakeMacro('hi')
`;
exports[`unknown plugin supports macros from node_modules: supports macros from node_modules 1`] = `
import fakeMacro from 'babel-plugin-macros-test-fake/macro'
fakeMacro('hi')
↓ ↓ ↓ ↓ ↓ ↓
fakeMacro('hi')
`;
exports[`unknown plugin throws an error if the macro is not properly wrapped: throws an error if the macro is not properly wrapped 1`] = `
import unwrapped from './fixtures/non-wrapped.macro'
unwrapped('hey')
↓ ↓ ↓ ↓ ↓ ↓
Error: The macro imported from "./fixtures/non-wrapped.macro" must be wrapped in "createMacro" which you can get from "babel-plugin-macros". 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
`;
exports[`unknown plugin when a custom isMacrosName option is used on a import: when a custom isMacrosName option is used on a import 1`] = `
import myEval from './fixtures/eval-macro.js'
const x = myEval\`34 + 45\`
↓ ↓ ↓ ↓ ↓ ↓
const x = 79
`;
exports[`unknown plugin when a custom isMacrosName option is used on a require: when a custom isMacrosName option is used on a require 1`] = `
const evaler = require('./fixtures/eval-macro.js')
const x = evaler\`34 + 45\`
↓ ↓ ↓ ↓ ↓ ↓
const x = 79
`;
exports[`unknown plugin when a plugin that replaces paths is used, macros still work properly: when a plugin that replaces paths is used, macros still work properly 1`] = `
import myEval from '../eval.macro'
const result = myEval\`+('4' + '2')\`
global.result = result
↓ ↓ ↓ ↓ ↓ ↓
const result = ('foobar', 42)
global.result = result
`;
exports[`unknown plugin when configuration is specified in plugin options: when configuration is specified in plugin options 1`] = `
import configured from './configurable.macro'
// eslint-disable-next-line babel/no-unused-expressions
configured\`stuff\`
↓ ↓ ↓ ↓ ↓ ↓
// eslint-disable-next-line babel/no-unused-expressions
configured\`stuff\`
`;
exports[`unknown plugin when configuration is specified in plugin options: when configuration is specified in plugin options 2`] = `
const configured = require('./configurable.macro')
// eslint-disable-next-line babel/no-unused-expressions
configured\`stuff\`
↓ ↓ ↓ ↓ ↓ ↓
// eslint-disable-next-line babel/no-unused-expressions
configured\`stuff\`
`;
exports[`unknown plugin when configuration is specified incorrectly in plugin options: when configuration is specified incorrectly in plugin options 1`] = `
import configured from './configurable.macro'
// eslint-disable-next-line babel/no-unused-expressions
configured\`stuff\`
↓ ↓ ↓ ↓ ↓ ↓
// eslint-disable-next-line babel/no-unused-expressions
configured\`stuff\`
`;
exports[`unknown plugin when plugin options configuration cannot be merged with file configuration: when plugin options configuration cannot be merged with file configuration 1`] = `
import configured from './configurable.macro'
// eslint-disable-next-line babel/no-unused-expressions
configured\`stuff\`
↓ ↓ ↓ ↓ ↓ ↓
Error: <PROJECT_ROOT>/src/__tests__/fixtures/primitive-config/babel-plugin-macros.config.js specified a configurableMacro config of type object, but the the macros plugin's options.configurableMacro did contain an object. Both configs must contain objects for their options to be mergeable.
`;
exports[`unknown plugin when there is an error reading the config, a helpful message is logged 1`] = `
Array [
There was an error trying to load the config "configurableMacro" for the macro imported from "./configurable.macro. Please see the error thrown for more information.,
]
`;
exports[`unknown plugin when there is an error reading the config, a helpful message is logged: when there is an error reading the config, a helpful message is logged 1`] = `
import configured from './configurable.macro'
// eslint-disable-next-line babel/no-unused-expressions
configured\`stuff\`
↓ ↓ ↓ ↓ ↓ ↓
Error: this is a cosmiconfig error
`;
exports[`unknown plugin when there is no config to load, then no config is passed: when there is no config to load, then no config is passed 1`] = `
import configured from './configurable.macro'
// eslint-disable-next-line babel/no-unused-expressions
configured\`stuff\`
↓ ↓ ↓ ↓ ↓ ↓
// eslint-disable-next-line babel/no-unused-expressions
configured\`stuff\`
`;
exports[`unknown plugin works with function calls: works with function calls 1`] = `
import myEval from './fixtures/eval.macro'
const x = myEval('34 + 45')
↓ ↓ ↓ ↓ ↓ ↓
const x = 79
`;
exports[`unknown plugin works with import: works with import 1`] = `
import myEval from './fixtures/eval.macro'
const x = myEval\`34 + 45\`
↓ ↓ ↓ ↓ ↓ ↓
const x = 79
`;
exports[`unknown plugin works with require destructuring and aliasing: works with require destructuring and aliasing 1`] = `
const {css: CSS, styled: STYLED} = require('./fixtures/emotion.macro')
const red = CSS\`
background-color: red;
\`
const Div = STYLED.div\`
composes: \${red}
color: blue;
\`
↓ ↓ ↓ ↓ ↓ ↓
const red = 'background-color: red;'
const Div = STYLED.div\`composes: background-color: red;
color: blue;\`
`;
exports[`unknown plugin works with require destructuring: works with require destructuring 1`] = `
const {css, styled} = require('./fixtures/emotion.macro')
const red = css\`
background-color: red;
\`
const Div = styled.div\`
composes: \${red}
color: blue;
\`
↓ ↓ ↓ ↓ ↓ ↓
const red = 'background-color: red;'
const Div = styled.div\`composes: background-color: red;
color: blue;\`
`;
exports[`unknown plugin works with require: works with require 1`] = `
const evaler = require('./fixtures/eval.macro')
const x = evaler\`34 + 45\`
↓ ↓ ↓ ↓ ↓ ↓
const x = 79
`;
================================================
FILE: src/__tests__/create-macros.js
================================================
const {createMacro} = require('../')
test('throws error if it is not transpiled', () => {
const untranspiledMacro = createMacro(() => {})
expect(() =>
untranspiledMacro({source: 'untranspiled.macro'}),
).toThrowErrorMatchingSnapshot()
})
test('attempting to create a macros with the configName of options throws an error', () => {
expect(() =>
createMacro(() => {}, {configName: 'options'}),
).toThrowErrorMatchingSnapshot()
})
================================================
FILE: src/__tests__/fixtures/config/babel-plugin-macros.config.js
================================================
module.exports = {
configurableMacro: {
fileConfig: true,
someConfig: true,
},
}
================================================
FILE: src/__tests__/fixtures/config/cjs-code.js
================================================
const configured = require('./configurable.macro')
// eslint-disable-next-line babel/no-unused-expressions
configured`stuff`
================================================
FILE: src/__tests__/fixtures/config/code.js
================================================
import configured from './configurable.macro'
// eslint-disable-next-line babel/no-unused-expressions
configured`stuff`
================================================
FILE: src/__tests__/fixtures/config/configurable.macro.js
================================================
const {createMacro} = require('../../..')
const configName = 'configurableMacro'
const realMacro = jest.fn()
module.exports = createMacro(realMacro, {configName})
// for testing purposes only
Object.assign(module.exports, {
realMacro,
configName,
})
================================================
FILE: src/__tests__/fixtures/emotion-esm.macro.js
================================================
const {createMacro} = require('../../')
export default createMacro(evalMacro)
function evalMacro() {
// we're lazy right now
// we don't want to eval
}
================================================
FILE: src/__tests__/fixtures/emotion.macro.js
================================================
// this is a fake version of emotion
// const printAST = require('ast-pretty-print')
const {createMacro} = require('../../')
module.exports = createMacro(emotionMacro)
function emotionMacro({references, babel}) {
const {types: t} = babel
references.css.forEach(cssRef => {
if (cssRef.parentPath.type === 'TaggedTemplateExpression') {
cssRef.parentPath.replaceWith(
t.stringLiteral(cssRef.parentPath.get('quasi').evaluate().value.trim()),
)
}
})
references.styled.forEach(styledRef => {
if (styledRef.parentPath.parentPath.type === 'TaggedTemplateExpression') {
const quasi = styledRef.parentPath.parentPath.get('quasi')
const val = quasi.evaluate().value.trim()
const replacement = t.templateLiteral(
[t.templateElement({raw: val, cooked: val})],
[],
)
quasi.replaceWith(replacement)
}
})
}
================================================
FILE: src/__tests__/fixtures/error-thrower.macro.js
================================================
// const printAST = require('ast-pretty-print')
const {createMacro} = require('../../')
module.exports = createMacro(evalMacro)
function evalMacro() {
throw new Error('very unhelpful')
}
================================================
FILE: src/__tests__/fixtures/eval-macro.js
================================================
module.exports = require('./eval.macro')
================================================
FILE: src/__tests__/fixtures/eval.macro.js
================================================
const {parse} = require('@babel/parser')
// const printAST = require('ast-pretty-print')
const {createMacro} = require('../../')
module.exports = createMacro(evalMacro)
function evalMacro({references, state}) {
references.default.forEach(referencePath => {
if (referencePath.parentPath.type === 'TaggedTemplateExpression') {
asTag(referencePath.parentPath.get('quasi'), state)
} else if (referencePath.parentPath.type === 'CallExpression') {
asFunction(referencePath.parentPath.get('arguments'), state)
} else if (referencePath.parentPath.type === 'JSXOpeningElement') {
asJSX(
{
attributes: referencePath.parentPath.get('attributes'),
children: referencePath.parentPath.parentPath.get('children'),
},
state,
)
} else {
// TODO: throw a helpful error message
}
})
}
function asTag(quasiPath) {
const value = quasiPath.parentPath.get('quasi').evaluate().value
quasiPath.parentPath.replaceWith(evalToAST(value))
}
function asFunction(argumentsPaths) {
const value = argumentsPaths[0].evaluate().value
argumentsPaths[0].parentPath.replaceWith(evalToAST(value))
}
// eslint-disable-next-line no-unused-vars
function asJSX({attributes, children}) {
// It's a shame you cannot use evaluate() with JSX
const value = children[0].node.value
children[0].parentPath.replaceWith(evalToAST(value))
}
function evalToAST(value) {
let x
// eslint-disable-next-line
eval(`x = ${value}`)
return thingToAST(x)
}
function thingToAST(object) {
const fileNode = parse(`var x = ${JSON.stringify(object)}`)
return fileNode.program.body[0].declarations[0].init
}
================================================
FILE: src/__tests__/fixtures/jsx-id-prefix.macro.js
================================================
// adds "prefix-" to each `id` attribute
const {createMacro} = require('../../')
module.exports = createMacro(wrapWidget)
function wrapWidget({references, babel}) {
const {types: t} = babel
references.default.forEach(wrap => {
wrap.parentPath.traverse({
JSXAttribute(path) {
const name = path.get('name')
if (t.isJSXIdentifier(name) && name.node.name === 'id') {
const value = path.get('value')
if (t.isStringLiteral(value))
value.replaceWith(t.stringLiteral(`macro-${value.node.value}`))
}
},
})
})
}
================================================
FILE: src/__tests__/fixtures/jsx-id-prefix.plugin.js
================================================
// babel-plugin adding `plugin-` prefix to each "id" JSX attribute
module.exports = main
function main({types: t}) {
return {
visitor: {
// intentionally traversing from Program,
// if it matches JSXAttribute here the issue won't be reproduced
Program(progPath) {
progPath.traverse({
JSXAttribute(path) {
const name = path.get('name')
if (t.isJSXIdentifier(name) && name.node.name === 'id') {
const value = path.get('value')
if (t.isStringLiteral(value))
value.replaceWith(t.stringLiteral(`plugin-${value.node.value}`))
}
},
})
},
},
}
}
================================================
FILE: src/__tests__/fixtures/keep-imports.macro.js
================================================
const {createMacro} = require('../../')
module.exports = createMacro(keepImportMacro)
function keepImportMacro() {
return {keepImports: true}
}
================================================
FILE: src/__tests__/fixtures/macro-error-thrower.macro.js
================================================
// const printAST = require('ast-pretty-print')
const {createMacro, MacroError} = require('../../')
module.exports = createMacro(evalMacro)
function evalMacro() {
throw new MacroError('very helpful')
}
================================================
FILE: src/__tests__/fixtures/non-wrapped.macro.js
================================================
module.exports = () => {}
================================================
FILE: src/__tests__/fixtures/path-replace-issue/.babelrc
================================================
{
"plugins": ["path-replace"]
}
================================================
FILE: src/__tests__/fixtures/path-replace-issue/variable-assignment.js
================================================
import myEval from '../eval.macro'
const result = myEval`+('4' + '2')`
global.result = result
================================================
FILE: src/__tests__/fixtures/primitive-config/babel-plugin-macros.config.js
================================================
module.exports = {
configurableMacro: 4,
}
================================================
FILE: src/__tests__/fixtures/primitive-config/code.js
================================================
import configured from './configurable.macro'
// eslint-disable-next-line babel/no-unused-expressions
configured`stuff`
================================================
FILE: src/__tests__/fixtures/primitive-config/configurable.macro.js
================================================
const {createMacro} = require('../../..')
const configName = 'configurableMacro'
const realMacro = jest.fn()
module.exports = createMacro(realMacro, {configName})
// for testing purposes only
Object.assign(module.exports, {
realMacro,
configName,
})
================================================
FILE: src/__tests__/index.js
================================================
import path from 'path'
import {cosmiconfigSync as cosmiconfigSyncMock} from 'cosmiconfig'
import cpy from 'cpy'
import babel from '@babel/core'
import pluginTester from 'babel-plugin-tester'
import plugin from '../'
const projectRoot = path.join(__dirname, '../../')
jest.mock('cosmiconfig', () => {
const cosmiconfigExports = jest.requireActual('cosmiconfig')
const actualCosmiconfigSync = cosmiconfigExports.cosmiconfigSync
function fakeCosmiconfigSync(...args) {
fakeCosmiconfigSync.explorer = actualCosmiconfigSync(...args)
return fakeCosmiconfigSync.explorer
}
return {...cosmiconfigExports, cosmiconfigSync: fakeCosmiconfigSync}
})
beforeAll(() => {
// copy our mock modules to the node_modules directory
// so we can test how things work when importing a macro
// from the node_modules directory.
return cpy(['**/*.js'], path.join('..', '..', 'node_modules'), {
parents: true,
cwd: path.join(projectRoot, 'other', 'mock-modules'),
})
})
beforeEach(() => {
jest.spyOn(console, 'error').mockImplementation(() => {})
})
afterEach(() => {
console.error.mockRestore()
jest.clearAllMocks()
})
expect.addSnapshotSerializer({
print(val) {
return (
val
.split(projectRoot)
.join('<PROJECT_ROOT>/')
.replace(/\\/g, '/')
// Remove the path of file which thrown an error
.replace(/Error:[^:]*:/, 'Error:')
)
},
test(val) {
return typeof val === 'string'
},
})
pluginTester({
plugin,
snapshot: true,
babelOptions: {
filename: __filename,
parserOpts: {
plugins: ['jsx'],
},
generatorOpts: {quotes: 'double'},
},
tests: [
{
title: 'does nothing to code that does not import macro',
snapshot: false,
code: `
import foo from './some-file-without-macro'
const bar = require('./some-other-file-without-macro')
`,
},
{
title: 'does nothing but remove macros if it is unused',
snapshot: true,
code: `
import foo from "./fixtures/eval.macro";
const bar = 42;
`,
},
{
title: 'raises an error if macro does not exist',
error: true,
code: `
import foo from './some-macros-that-doesnt-even-need-to-exist.macro'
export default 'something else'
`,
},
{
title: 'works with import',
code: `
import myEval from './fixtures/eval.macro'
const x = myEval\`34 + 45\`
`,
},
{
title: 'works with require',
code: `
const evaler = require('./fixtures/eval.macro')
const x = evaler\`34 + 45\`
`,
},
{
title: 'works with require destructuring',
code: `
const {css, styled} = require('./fixtures/emotion.macro')
const red = css\`
background-color: red;
\`
const Div = styled.div\`
composes: \${red}
color: blue;
\`
`,
},
{
title: 'works with require destructuring and aliasing',
code: `
const {css: CSS, styled: STYLED} = require('./fixtures/emotion.macro')
const red = CSS\`
background-color: red;
\`
const Div = STYLED.div\`
composes: \${red}
color: blue;
\`
`,
},
{
title: 'works with function calls',
code: `
import myEval from './fixtures/eval.macro'
const x = myEval('34 + 45')
`,
},
{
title: 'Works as a JSXElement',
code: `
import MyEval from './fixtures/eval.macro'
const x = <MyEval>34 + 45</MyEval>
`,
},
{
title: 'Supports named imports',
code: `
import {css as CSS, styled as STYLED} from './fixtures/emotion.macro'
const red = CSS\`
background-color: red;
\`
const Div = STYLED.div\`
composes: \${red}
color: blue;
\`
`,
},
{
title: 'supports compiled macros (`__esModule` + `export default`)',
code: `
import {css, styled} from './fixtures/emotion-esm.macro'
const red = css\`
background-color: red;
\`
const Div = styled.div\`
composes: \${red}
color: blue;
\`
`,
},
{
title: 'supports macros from node_modules',
code: `
import fakeMacro from 'babel-plugin-macros-test-fake/macro'
fakeMacro('hi')
`,
teardown() {
try {
// kinda abusing the babel-plugin-tester API here
// to make an extra assertion
// eslint-disable-next-line
const fakeMacro = require('babel-plugin-macros-test-fake/macro')
expect(fakeMacro.innerFn).toHaveBeenCalledTimes(1)
expect(fakeMacro.innerFn).toHaveBeenCalledWith({
references: expect.any(Object),
source: expect.stringContaining(
'babel-plugin-macros-test-fake/macro',
),
state: expect.any(Object),
babel: expect.any(Object),
isBabelMacrosCall: true,
})
expect(fakeMacro.innerFn.mock.calls[0].babel).toBe(babel)
} catch (e) {
console.error(e)
throw e
}
},
},
{
title: 'supports macros from node_modules with scope',
code: `
import fakeMacro from '@scope/package/macro'
fakeMacro('hi')
`,
teardown() {
try {
// kinda abusing the babel-plugin-tester API here
// to make an extra assertion
// eslint-disable-next-line
const fakeMacro = require('@scope/package/macro')
expect(fakeMacro.innerFn).toHaveBeenCalledTimes(1)
expect(fakeMacro.innerFn).toHaveBeenCalledWith({
references: expect.any(Object),
source: expect.stringContaining('@scope/package/macro'),
state: expect.any(Object),
babel: expect.any(Object),
isBabelMacrosCall: true,
})
expect(fakeMacro.innerFn.mock.calls[0].babel).toBe(babel)
} catch (e) {
console.error(e)
throw e
}
},
},
{
title: 'optionally keep imports (variable assignment)',
code: `
const macro = require('./fixtures/keep-imports.macro')
const red = macro('noop');
`,
},
{
title: 'optionally keep imports (import declaration)',
code: `
import macro from './fixtures/keep-imports.macro'
const red = macro('noop');
`,
},
{
title:
'optionally keep imports in combination with babel-preset-env (#80)',
code: `
import macro from './fixtures/keep-imports.macro'
const red = macro('noop')
`,
babelOptions: {
plugins: [
require.resolve('babel-plugin-transform-es2015-modules-commonjs'),
],
},
},
{
title: 'throws an error if the macro is not properly wrapped',
error: true,
code: `
import unwrapped from './fixtures/non-wrapped.macro'
unwrapped('hey')
`,
},
{
title: 'forwards MacroErrors thrown by the macro',
error: true,
code: `
import errorThrower from './fixtures/macro-error-thrower.macro'
errorThrower('hey')
`,
},
{
title: 'prepends the relative path for errors thrown by the macro',
error: true,
code: `
import errorThrower from './fixtures/error-thrower.macro'
errorThrower('hey')
`,
},
{
title: 'appends the npm URL for errors thrown by node modules',
error: true,
code: `
import errorThrower from 'babel-plugin-macros-test-error-thrower.macro'
errorThrower('hi')
`,
},
{
title:
'appends the npm URL for errors thrown by node modules with a slash',
error: true,
code: `
import errorThrower from 'babel-plugin-macros-test-error-thrower/macro'
errorThrower('hi')
`,
},
{
title: 'macros can set their configName and get their config',
fixture: path.join(__dirname, 'fixtures/config/code.js'),
teardown() {
try {
const babelMacrosConfig = require('./fixtures/config/babel-plugin-macros.config')
const configurableMacro = require('./fixtures/config/configurable.macro')
expect(configurableMacro.realMacro).toHaveBeenCalledTimes(1)
expect(configurableMacro.realMacro.mock.calls[0][0].config).toEqual(
babelMacrosConfig[configurableMacro.configName],
)
configurableMacro.realMacro.mockClear()
} catch (e) {
console.error(e)
throw e
}
},
},
{
title:
'when there is an error reading the config, a helpful message is logged',
error: true,
fixture: path.join(__dirname, 'fixtures/config/code.js'),
setup() {
jest
.spyOn(cosmiconfigSyncMock.explorer, 'search')
.mockImplementationOnce(() => {
throw new Error('this is a cosmiconfig error')
})
jest.spyOn(console, 'error').mockImplementationOnce(() => {})
return function teardown() {
try {
expect(console.error).toHaveBeenCalledTimes(1)
expect(console.error.mock.calls[0]).toMatchSnapshot()
console.error.mockClear()
} catch (e) {
console.error(e)
console.error.mockClear()
throw e
}
}
},
},
{
title: 'when there is no config to load, then no config is passed',
fixture: path.join(__dirname, 'fixtures/config/code.js'),
setup() {
jest
.spyOn(cosmiconfigSyncMock.explorer, 'search')
.mockImplementationOnce(() => {
return null
})
return function teardown() {
try {
const configurableMacro = require('./fixtures/config/configurable.macro')
expect(configurableMacro.realMacro).toHaveBeenCalledTimes(1)
expect(configurableMacro.realMacro.mock.calls[0][0].config).toEqual(
{},
)
configurableMacro.realMacro.mockClear()
} catch (e) {
console.error(e)
throw e
}
}
},
},
{
title: 'when configuration is specified in plugin options',
pluginOptions: {
configurableMacro: {
someConfig: false,
somePluginConfig: true,
},
},
fixture: path.join(__dirname, 'fixtures/config/code.js'),
teardown() {
try {
const configurableMacro = require('./fixtures/config/configurable.macro')
expect(configurableMacro.realMacro).toHaveBeenCalledTimes(1)
expect(configurableMacro.realMacro.mock.calls[0][0].config).toEqual({
fileConfig: true,
someConfig: true,
somePluginConfig: true,
})
configurableMacro.realMacro.mockClear()
} catch (e) {
console.error(e)
throw e
}
},
},
{
title: 'when configuration is specified in plugin options',
pluginOptions: {
configurableMacro: {
someConfig: false,
somePluginConfig: true,
},
},
fixture: path.join(__dirname, 'fixtures/config/cjs-code.js'),
teardown() {
try {
const configurableMacro = require('./fixtures/config/configurable.macro')
expect(configurableMacro.realMacro).toHaveBeenCalledTimes(1)
expect(configurableMacro.realMacro.mock.calls[0][0].config).toEqual({
fileConfig: true,
someConfig: true,
somePluginConfig: true,
})
configurableMacro.realMacro.mockClear()
} catch (e) {
console.error(e)
throw e
}
},
},
{
title: 'when configuration is specified incorrectly in plugin options',
fixture: path.join(__dirname, 'fixtures/config/code.js'),
pluginOptions: {
configurableMacro: 2,
},
teardown() {
try {
const configurableMacro = require('./fixtures/config/configurable.macro')
expect(configurableMacro.realMacro).toHaveBeenCalledTimes(1)
expect(configurableMacro.realMacro).not.toHaveBeenCalledWith(
expect.objectContaining({
config: expect.any,
}),
)
configurableMacro.realMacro.mockClear()
} catch (e) {
console.error(e)
throw e
}
},
},
{
title: 'when a custom isMacrosName option is used on a import',
pluginOptions: {
isMacrosName(v) {
return v.endsWith('-macro.js')
},
},
code: `
import myEval from './fixtures/eval-macro.js'
const x = myEval\`34 + 45\`
`,
},
{
title: 'when a custom isMacrosName option is used on a require',
pluginOptions: {
isMacrosName(v) {
return v.endsWith('-macro.js')
},
},
code: `
const evaler = require('./fixtures/eval-macro.js')
const x = evaler\`34 + 45\`
`,
},
{
title:
'when plugin options configuration cannot be merged with file configuration',
error: true,
fixture: path.join(__dirname, 'fixtures/primitive-config/code.js'),
pluginOptions: {
configurableMacro: {},
},
},
{
title:
'when a plugin that replaces paths is used, macros still work properly',
fixture: path.join(
__dirname,
'fixtures/path-replace-issue/variable-assignment.js',
),
babelOptions: {
babelrc: true,
},
},
{
title: 'Macros are applied in the order respecting plugins order',
code: `
import Wrap from "./fixtures/jsx-id-prefix.macro";
const bar = Wrap(<div id="d1"><p id="p1"></p></div>);
`,
babelOptions: {
presets: [{plugins: [require('./fixtures/jsx-id-prefix.plugin')]}],
},
},
],
})
/* eslint no-console:0 */
================================================
FILE: src/index.js
================================================
const p = require('path')
const resolve = require('resolve')
// const printAST = require('ast-pretty-print')
const macrosRegex = /[./]macro(\.c?js)?$/
const testMacrosRegex = v => macrosRegex.test(v)
// https://stackoverflow.com/a/32749533/971592
class MacroError extends Error {
constructor(message) {
super(message)
this.name = 'MacroError'
/* istanbul ignore else */
if (typeof Error.captureStackTrace === 'function') {
Error.captureStackTrace(this, this.constructor)
} else if (!this.stack) {
this.stack = new Error(message).stack
}
}
}
let _configExplorer = null
function getConfigExplorer() {
return (_configExplorer =
_configExplorer ||
// Lazy load cosmiconfig since it is a relatively large bundle
require('cosmiconfig').cosmiconfigSync('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',
],
packageProp: 'babelMacros',
}))
}
function createMacro(macro, options = {}) {
if (options.configName === 'options') {
throw new Error(
`You cannot use the configName "options". It is reserved for babel-plugin-macros.`,
)
}
macroWrapper.isBabelMacro = true
macroWrapper.options = options
return macroWrapper
function macroWrapper(args) {
const {source, isBabelMacrosCall} = args
if (!isBabelMacrosCall) {
throw new MacroError(
`The macro you imported from "${source}" is being executed outside the context of compilation with babel-plugin-macros. ` +
`This indicates that you don't have the babel plugin "babel-plugin-macros" configured correctly. ` +
`Please see the documentation for how to configure babel-plugin-macros properly: ` +
'https://github.com/kentcdodds/babel-plugin-macros/blob/master/other/docs/user.md',
)
}
return macro(args)
}
}
function nodeResolvePath(source, basedir) {
return resolve.sync(source, {
basedir,
extensions: ['.js', '.ts', '.tsx', '.mjs', '.cjs', '.jsx'],
// This is here to support the package being globally installed
// read more: https://github.com/kentcdodds/babel-plugin-macros/pull/138
paths: [p.resolve(__dirname, '../../')],
})
}
function macrosPlugin(
babel,
// istanbul doesn't like the default of an object for the plugin options
// but I think older versions of babel didn't always pass options
// istanbul ignore next
{
require: _require = require,
resolvePath = nodeResolvePath,
isMacrosName = testMacrosRegex,
...options
} = {},
) {
function interopRequire(path) {
// eslint-disable-next-line import/no-dynamic-require
const o = _require(path)
return o && o.__esModule && o.default ? o.default : o
}
return {
name: 'macros',
visitor: {
Program(progPath, state) {
progPath.traverse({
ImportDeclaration(path) {
const isMacros = looksLike(path, {
node: {
source: {
value: v => isMacrosName(v),
},
},
})
if (!isMacros) {
return
}
const imports = path.node.specifiers.map(s => ({
localName: s.local.name,
importedName:
s.type === 'ImportDefaultSpecifier'
? 'default'
: s.imported.name,
}))
const source = path.node.source.value
const result = applyMacros({
path,
imports,
source,
state,
babel,
interopRequire,
resolvePath,
options,
})
if (!result || !result.keepImports) {
path.remove()
}
},
VariableDeclaration(path) {
const isMacros = child =>
looksLike(child, {
node: {
init: {
callee: {
type: 'Identifier',
name: 'require',
},
arguments: args =>
args.length === 1 && isMacrosName(args[0].value),
},
},
})
path
.get('declarations')
.filter(isMacros)
.forEach(child => {
const imports = child.node.id.name
? [{localName: child.node.id.name, importedName: 'default'}]
: child.node.id.properties.map(property => ({
localName: property.value.name,
importedName: property.key.name,
}))
const call = child.get('init')
const source = call.node.arguments[0].value
const result = applyMacros({
path: call,
imports,
source,
state,
babel,
interopRequire,
resolvePath,
options,
})
if (!result || !result.keepImports) {
child.remove()
}
})
},
})
},
},
}
}
// eslint-disable-next-line complexity
function applyMacros({
path,
imports,
source,
state,
babel,
interopRequire,
resolvePath,
options,
}) {
/* istanbul ignore next (pretty much only useful for astexplorer I think) */
const {
file: {
opts: {filename = ''},
},
} = state
let hasReferences = false
const referencePathsByImportName = imports.reduce(
(byName, {importedName, localName}) => {
const binding = path.scope.getBinding(localName)
byName[importedName] = binding.referencePaths
hasReferences = hasReferences || Boolean(byName[importedName].length)
return byName
},
{},
)
const isRelative = source.indexOf('.') === 0
const requirePath = resolvePath(source, p.dirname(getFullFilename(filename)))
const macro = interopRequire(requirePath)
if (!macro.isBabelMacro) {
throw new Error(
`The macro imported from "${source}" must be wrapped in "createMacro" ` +
`which you can get from "babel-plugin-macros". ` +
`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, options)
let result
try {
/**
* Other plugins that run before babel-plugin-macros might use path.replace, where a path is
* put into its own replacement. Apparently babel does not update the scope after such
* an operation. As a remedy, the whole scope is traversed again with an empty "Identifier"
* visitor - this makes the problem go away.
*
* See: https://github.com/kentcdodds/import-all.macro/issues/7
*/
state.file.scope.path.traverse({
Identifier() {},
})
result = macro({
references: referencePathsByImportName,
source,
state,
babel,
config,
isBabelMacrosCall: true,
})
} catch (error) {
if (error.name === 'MacroError') {
throw error
}
error.message = `${source}: ${error.message}`
if (!isRelative) {
error.message = `${
error.message
} Learn more: https://www.npmjs.com/package/${source.replace(
// remove everything after package name
// @org/package/macro -> @org/package
// package/macro -> package
/^((?:@[^/]+\/)?[^/]+).*/,
'$1',
)}`
}
throw error
}
return result
}
function getConfigFromFile(configName, filename) {
try {
const loaded = getConfigExplorer().search(filename)
if (loaded) {
return {
options: loaded.config[configName],
path: loaded.filepath,
}
}
} catch (e) {
return {error: e}
}
return {}
}
function getConfigFromOptions(configName, options) {
if (options.hasOwnProperty(configName)) {
if (options[configName] && typeof options[configName] !== 'object') {
// eslint-disable-next-line no-console
console.error(
`The macro plugin options' ${configName} property was not an object or null.`,
)
} else {
return {options: options[configName]}
}
}
return {}
}
function getConfig(macro, filename, source, options) {
const {configName} = macro.options
if (configName) {
const fileConfig = getConfigFromFile(configName, filename)
const optionsConfig = getConfigFromOptions(configName, options)
if (
optionsConfig.options === undefined &&
fileConfig.options === undefined &&
fileConfig.error !== undefined
) {
// eslint-disable-next-line no-console
console.error(
`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 fileConfig.error
}
if (
fileConfig.options !== undefined &&
optionsConfig.options !== undefined &&
typeof fileConfig.options !== 'object'
) {
throw new Error(
`${fileConfig.path} specified a ${configName} config of type ` +
`${typeof optionsConfig.options}, but the the macros plugin's ` +
`options.${configName} did contain an object. Both configs must ` +
`contain objects for their options to be mergeable.`,
)
}
return {
...optionsConfig.options,
...fileConfig.options,
}
}
return undefined
}
/*
istanbul ignore next
because this is hard to test
and not worth it...
*/
function getFullFilename(filename) {
if (p.isAbsolute(filename)) {
return filename
}
return p.join(process.cwd(), filename)
}
function looksLike(a, b) {
return (
a &&
b &&
Object.keys(b).every(bKey => {
const bVal = b[bKey]
const aVal = a[bKey]
if (typeof bVal === 'function') {
return bVal(aVal)
}
return isPrimitive(bVal) ? bVal === aVal : looksLike(aVal, bVal)
})
)
}
function isPrimitive(val) {
// eslint-disable-next-line
return val == null || /^[sbn]/.test(typeof val)
}
module.exports = macrosPlugin
Object.assign(module.exports, {
createMacro,
MacroError,
})
gitextract_7a72cab4/
├── .all-contributorsrc
├── .gitattributes
├── .github/
│ ├── ISSUE_TEMPLATE.md
│ ├── PULL_REQUEST_TEMPLATE.md
│ └── workflows/
│ └── validate.yml
├── .gitignore
├── .huskyrc.js
├── .npmrc
├── .prettierignore
├── .prettierrc.js
├── CHANGELOG.md
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── other/
│ ├── MAINTAINING.md
│ ├── USERS.md
│ ├── docs/
│ │ ├── author.md
│ │ └── user.md
│ ├── manual-releases.md
│ └── mock-modules/
│ ├── @scope/
│ │ └── package/
│ │ └── macro.js
│ ├── babel-plugin-macros-test-error-thrower/
│ │ └── macro.js
│ ├── babel-plugin-macros-test-error-thrower.macro/
│ │ └── index.js
│ ├── babel-plugin-macros-test-fake/
│ │ └── macro.js
│ └── babel-plugin-path-replace/
│ └── index.js
├── package.json
└── src/
├── __tests__/
│ ├── __snapshots__/
│ │ ├── create-macros.js.snap
│ │ └── index.js.snap
│ ├── create-macros.js
│ ├── fixtures/
│ │ ├── config/
│ │ │ ├── babel-plugin-macros.config.js
│ │ │ ├── cjs-code.js
│ │ │ ├── code.js
│ │ │ └── configurable.macro.js
│ │ ├── emotion-esm.macro.js
│ │ ├── emotion.macro.js
│ │ ├── error-thrower.macro.js
│ │ ├── eval-macro.js
│ │ ├── eval.macro.js
│ │ ├── jsx-id-prefix.macro.js
│ │ ├── jsx-id-prefix.plugin.js
│ │ ├── keep-imports.macro.js
│ │ ├── macro-error-thrower.macro.js
│ │ ├── non-wrapped.macro.js
│ │ ├── path-replace-issue/
│ │ │ ├── .babelrc
│ │ │ └── variable-assignment.js
│ │ └── primitive-config/
│ │ ├── babel-plugin-macros.config.js
│ │ ├── code.js
│ │ └── configurable.macro.js
│ └── index.js
└── index.js
SYMBOL INDEX (43 symbols across 13 files)
FILE: other/mock-modules/babel-plugin-macros-test-error-thrower.macro/index.js
function evalMacro (line 6) | function evalMacro() {
FILE: other/mock-modules/babel-plugin-macros-test-error-thrower/macro.js
function evalMacro (line 6) | function evalMacro() {
FILE: other/mock-modules/babel-plugin-path-replace/index.js
method enter (line 5) | enter(path) {
method enter (line 21) | enter(path) {
FILE: src/__tests__/fixtures/emotion-esm.macro.js
function evalMacro (line 5) | function evalMacro() {
FILE: src/__tests__/fixtures/emotion.macro.js
function emotionMacro (line 7) | function emotionMacro({references, babel}) {
FILE: src/__tests__/fixtures/error-thrower.macro.js
function evalMacro (line 6) | function evalMacro() {
FILE: src/__tests__/fixtures/eval.macro.js
function evalMacro (line 7) | function evalMacro({references, state}) {
function asTag (line 27) | function asTag(quasiPath) {
function asFunction (line 32) | function asFunction(argumentsPaths) {
function asJSX (line 38) | function asJSX({attributes, children}) {
function evalToAST (line 44) | function evalToAST(value) {
function thingToAST (line 51) | function thingToAST(object) {
FILE: src/__tests__/fixtures/jsx-id-prefix.macro.js
function wrapWidget (line 6) | function wrapWidget({references, babel}) {
FILE: src/__tests__/fixtures/jsx-id-prefix.plugin.js
function main (line 4) | function main({types: t}) {
FILE: src/__tests__/fixtures/keep-imports.macro.js
function keepImportMacro (line 5) | function keepImportMacro() {
FILE: src/__tests__/fixtures/macro-error-thrower.macro.js
function evalMacro (line 6) | function evalMacro() {
FILE: src/__tests__/index.js
function fakeCosmiconfigSync (line 13) | function fakeCosmiconfigSync(...args) {
method print (line 40) | print(val) {
method test (line 50) | test(val) {
method teardown (line 182) | teardown() {
method teardown (line 211) | teardown() {
method teardown (line 303) | teardown() {
method setup (line 324) | setup() {
method setup (line 347) | setup() {
method teardown (line 377) | teardown() {
method teardown (line 402) | teardown() {
method teardown (line 424) | teardown() {
method isMacrosName (line 443) | isMacrosName(v) {
method isMacrosName (line 455) | isMacrosName(v) {
FILE: src/index.js
class MacroError (line 9) | class MacroError extends Error {
method constructor (line 10) | constructor(message) {
function getConfigExplorer (line 23) | function getConfigExplorer() {
function createMacro (line 41) | function createMacro(macro, options = {}) {
function nodeResolvePath (line 65) | function nodeResolvePath(source, basedir) {
function macrosPlugin (line 75) | function macrosPlugin(
function applyMacros (line 183) | function applyMacros({
function getConfigFromFile (line 268) | function getConfigFromFile(configName, filename) {
function getConfigFromOptions (line 284) | function getConfigFromOptions(configName, options) {
function getConfig (line 298) | function getConfig(macro, filename, source, options) {
function getFullFilename (line 344) | function getFullFilename(filename) {
function looksLike (line 351) | function looksLike(a, b) {
function isPrimitive (line 366) | function isPrimitive(val) {
Condensed preview — 50 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (118K chars).
[
{
"path": ".all-contributorsrc",
"chars": 7670,
"preview": "{\n \"projectName\": \"babel-plugin-macros\",\n \"projectOwner\": \"kentcdodds\",\n \"imageSize\": 100,\n \"commit\": false,\n \"cont"
},
{
"path": ".gitattributes",
"chars": 19,
"preview": "* text=auto eol=lf\n"
},
{
"path": ".github/ISSUE_TEMPLATE.md",
"chars": 945,
"preview": "<!--\nThanks for your interest in the project. I appreciate bugs filed and PRs submitted!\nPlease make sure that you are f"
},
{
"path": ".github/PULL_REQUEST_TEMPLATE.md",
"chars": 1217,
"preview": "<!--\nThanks for your interest in the project. Bugs filed and PRs submitted are appreciated!\n\nPlease make sure that you a"
},
{
"path": ".github/workflows/validate.yml",
"chars": 1918,
"preview": "name: validate\non:\n push:\n branches:\n - '+([0-9])?(.{+([0-9]),x}).x'\n - 'main'\n - 'next'\n - 'nex"
},
{
"path": ".gitignore",
"chars": 133,
"preview": "node_modules\ncoverage\ndist\n.DS_Store\n\n# these cause more harm than good\n# when working with contributors\npackage-lock.js"
},
{
"path": ".huskyrc.js",
"chars": 46,
"preview": "module.exports = require('kcd-scripts/husky')\n"
},
{
"path": ".npmrc",
"chars": 37,
"preview": "registry=https://registry.npmjs.org/\n"
},
{
"path": ".prettierignore",
"chars": 27,
"preview": "node_modules\ncoverage\ndist\n"
},
{
"path": ".prettierrc.js",
"chars": 49,
"preview": "module.exports = require('kcd-scripts/prettier')\n"
},
{
"path": "CHANGELOG.md",
"chars": 187,
"preview": "# CHANGELOG\n\nThe changelog is automatically updated using\n[semantic-release](https://github.com/semantic-release/semanti"
},
{
"path": "CODE_OF_CONDUCT.md",
"chars": 5222,
"preview": "# Contributor Covenant Code of Conduct\n\n## Our Pledge\n\nWe as members, contributors, and leaders pledge to make participa"
},
{
"path": "CONTRIBUTING.md",
"chars": 1786,
"preview": "# Contributing\n\nThanks for being willing to contribute!\n\n**Working on your first Pull Request?** You can learn how from "
},
{
"path": "LICENSE",
"chars": 1079,
"preview": "The MIT License (MIT)\nCopyright (c) 2020 Kent C. Dodds\n\nPermission is hereby granted, free of charge, to any person obta"
},
{
"path": "README.md",
"chars": 24718,
"preview": "<div align=\"center\">\n<h1>babel-plugin-macros 🎣</h1>\n\n<p>Allows you to build simple compile-time libraries</p>\n</div>\n\n--"
},
{
"path": "other/MAINTAINING.md",
"chars": 3289,
"preview": "# Maintaining\n\nThis is documentation for maintainers of this project.\n\n## Code of Conduct\n\nPlease review, understand, an"
},
{
"path": "other/USERS.md",
"chars": 336,
"preview": "# Users\n\nIf you or your company uses this project, add your name to this list! Eventually\nwe may have a website to showc"
},
{
"path": "other/docs/author.md",
"chars": 10235,
"preview": "# `babel-plugin-macros` Usage for macros authors\n\n> See also:\n> [the `user` docs](https://github.com/kentcdodds/babel-pl"
},
{
"path": "other/docs/user.md",
"chars": 3571,
"preview": "# `babel-plugin-macros` Usage for users\n\n> See also:\n> [the `author` docs](https://github.com/kentcdodds/babel-plugin-ma"
},
{
"path": "other/manual-releases.md",
"chars": 1313,
"preview": "# manual-releases\n\nThis project has an automated release set up. So things are only released when\nthere are useful chang"
},
{
"path": "other/mock-modules/@scope/package/macro.js",
"chars": 218,
"preview": "// this is used to make sure that you can require macro from node_modules\nconst {createMacro} = require('../../../src')\n"
},
{
"path": "other/mock-modules/babel-plugin-macros-test-error-thrower/macro.js",
"chars": 191,
"preview": "// const printAST = require('ast-pretty-print')\nconst {createMacro} = require('../../src')\n\nmodule.exports = createMacro"
},
{
"path": "other/mock-modules/babel-plugin-macros-test-error-thrower.macro/index.js",
"chars": 191,
"preview": "// const printAST = require('ast-pretty-print')\nconst {createMacro} = require('../../src')\n\nmodule.exports = createMacro"
},
{
"path": "other/mock-modules/babel-plugin-macros-test-fake/macro.js",
"chars": 215,
"preview": "// this is used to make sure that you can require macro from node_modules\nconst {createMacro} = require('../../src')\n\nco"
},
{
"path": "other/mock-modules/babel-plugin-path-replace/index.js",
"chars": 455,
"preview": "const types = require('@babel/types')\n\nconst problematicVisitor = {\n VariableDeclarator: {\n enter(path) {\n cons"
},
{
"path": "package.json",
"chars": 1697,
"preview": "{\n \"name\": \"babel-plugin-macros\",\n \"version\": \"0.0.0-semantically-released\",\n \"description\": \"Allows you to build com"
},
{
"path": "src/__tests__/__snapshots__/create-macros.js.snap",
"chars": 677,
"preview": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`attempting to create a macros with the configName of options throws"
},
{
"path": "src/__tests__/__snapshots__/index.js.snap",
"chars": 10995,
"preview": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`unknown plugin Macros are applied in the order respecting plugins o"
},
{
"path": "src/__tests__/create-macros.js",
"chars": 448,
"preview": "const {createMacro} = require('../')\n\ntest('throws error if it is not transpiled', () => {\n const untranspiledMacro = c"
},
{
"path": "src/__tests__/fixtures/config/babel-plugin-macros.config.js",
"chars": 93,
"preview": "module.exports = {\n configurableMacro: {\n fileConfig: true,\n someConfig: true,\n },\n}\n"
},
{
"path": "src/__tests__/fixtures/config/cjs-code.js",
"chars": 126,
"preview": "const configured = require('./configurable.macro')\n\n// eslint-disable-next-line babel/no-unused-expressions\nconfigured`s"
},
{
"path": "src/__tests__/fixtures/config/code.js",
"chars": 121,
"preview": "import configured from './configurable.macro'\n\n// eslint-disable-next-line babel/no-unused-expressions\nconfigured`stuff`"
},
{
"path": "src/__tests__/fixtures/config/configurable.macro.js",
"chars": 255,
"preview": "const {createMacro} = require('../../..')\n\nconst configName = 'configurableMacro'\nconst realMacro = jest.fn()\nmodule.exp"
},
{
"path": "src/__tests__/fixtures/emotion-esm.macro.js",
"chars": 158,
"preview": "const {createMacro} = require('../../')\n\nexport default createMacro(evalMacro)\n\nfunction evalMacro() {\n // we're lazy r"
},
{
"path": "src/__tests__/fixtures/emotion.macro.js",
"chars": 885,
"preview": "// this is a fake version of emotion\n// const printAST = require('ast-pretty-print')\nconst {createMacro} = require('../."
},
{
"path": "src/__tests__/fixtures/error-thrower.macro.js",
"chars": 191,
"preview": "// const printAST = require('ast-pretty-print')\nconst {createMacro} = require('../../')\n\nmodule.exports = createMacro(ev"
},
{
"path": "src/__tests__/fixtures/eval-macro.js",
"chars": 41,
"preview": "module.exports = require('./eval.macro')\n"
},
{
"path": "src/__tests__/fixtures/eval.macro.js",
"chars": 1669,
"preview": "const {parse} = require('@babel/parser')\n// const printAST = require('ast-pretty-print')\nconst {createMacro} = require('"
},
{
"path": "src/__tests__/fixtures/jsx-id-prefix.macro.js",
"chars": 586,
"preview": "// adds \"prefix-\" to each `id` attribute\nconst {createMacro} = require('../../')\n\nmodule.exports = createMacro(wrapWidge"
},
{
"path": "src/__tests__/fixtures/jsx-id-prefix.plugin.js",
"chars": 691,
"preview": "// babel-plugin adding `plugin-` prefix to each \"id\" JSX attribute\nmodule.exports = main\n\nfunction main({types: t}) {\n "
},
{
"path": "src/__tests__/fixtures/keep-imports.macro.js",
"chars": 148,
"preview": "const {createMacro} = require('../../')\n\nmodule.exports = createMacro(keepImportMacro)\n\nfunction keepImportMacro() {\n r"
},
{
"path": "src/__tests__/fixtures/macro-error-thrower.macro.js",
"chars": 206,
"preview": "// const printAST = require('ast-pretty-print')\nconst {createMacro, MacroError} = require('../../')\n\nmodule.exports = cr"
},
{
"path": "src/__tests__/fixtures/non-wrapped.macro.js",
"chars": 26,
"preview": "module.exports = () => {}\n"
},
{
"path": "src/__tests__/fixtures/path-replace-issue/.babelrc",
"chars": 34,
"preview": "{\n \"plugins\": [\"path-replace\"]\n}\n"
},
{
"path": "src/__tests__/fixtures/path-replace-issue/variable-assignment.js",
"chars": 96,
"preview": "import myEval from '../eval.macro'\n\nconst result = myEval`+('4' + '2')`\n\nglobal.result = result\n"
},
{
"path": "src/__tests__/fixtures/primitive-config/babel-plugin-macros.config.js",
"chars": 45,
"preview": "module.exports = {\n configurableMacro: 4,\n}\n"
},
{
"path": "src/__tests__/fixtures/primitive-config/code.js",
"chars": 121,
"preview": "import configured from './configurable.macro'\n\n// eslint-disable-next-line babel/no-unused-expressions\nconfigured`stuff`"
},
{
"path": "src/__tests__/fixtures/primitive-config/configurable.macro.js",
"chars": 255,
"preview": "const {createMacro} = require('../../..')\n\nconst configName = 'configurableMacro'\nconst realMacro = jest.fn()\nmodule.exp"
},
{
"path": "src/__tests__/index.js",
"chars": 14240,
"preview": "import path from 'path'\nimport {cosmiconfigSync as cosmiconfigSyncMock} from 'cosmiconfig'\nimport cpy from 'cpy'\nimport "
},
{
"path": "src/index.js",
"chars": 10630,
"preview": "const p = require('path')\nconst resolve = require('resolve')\n// const printAST = require('ast-pretty-print')\n\nconst macr"
}
]
About this extraction
This page contains the full source code of the kentcdodds/babel-macros GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 50 files (106.9 KB), approximately 29.5k tokens, and a symbol index with 43 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.