Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/asapach/babel-plugin-rewire-exports

Babel plugin for stubbing [ES6, ES2015] module exports
https://github.com/asapach/babel-plugin-rewire-exports

babel babel-plugin es2015 es6 esm javascript rewire rollup stub systemjs unit-testing webpack

Last synced: 2 days ago
JSON representation

Babel plugin for stubbing [ES6, ES2015] module exports

Awesome Lists containing this project

README

        

# babel-plugin-rewire-exports

[![NPM Version][npm-image]][npm-url]
[![NPM Downloads][downloads-image]][downloads-url]
[![Build Status][travis-image]][travis-url]

Babel plugin for stubbing (ES6, ES2015) module exports.
It allows to rewire the exported values in all the importing modules.
Unlike [babel-plugin-rewire](https://github.com/speedskater/babel-plugin-rewire) it doesn't modify the module internals
(e.g. imports and top-level variables and functions).
See [How it works](#how-it-works) section for implementation details.

## Exports
Plugin transforms module exports in such a way that they can be stubbed (or "rewired") via the following API:
* default export - plugin exports additional `rewire(stub)` function that allows to replace the original
* named exports - for each export (e.g. `export var foo`) an additional function `rewire$foo(stub)` is exported
* `restore()` function allows to restore the exports to their original values
* if there are existing `rewire` or `restore` top-level identifiers, the generated exports will be named
`rewire$default` and `restore$rewire` respectively

### Example

Named export:
```js
//------ text.js ------
export let message = 'Hello world!'

//------ logger.js ------
import {message} from './text.js'

export default function () {
console.log(message)
}

//------ main.js ------
import {rewire$message, restore} from './text.js'
import logger from './logger.js'

logger() // 'Hello world!'
rewire$message('I am now rewired')
logger() // 'I am now rewired'
restore()
logger() // 'Hello world!'
```

Default export:
```js
//------ fetch.js ------
export default function (url) {
// perform some expensive remote call
}

//------ adapter.js ------
import fetch from './fetch.js'

export function fetchItems() {
return fetch('/items')
}

//------ test.js ------
import {rewire, restore} from './fetch.js'
import {fetchItems} from './adapter.js'

// Jasmine example
describe('adapter', function () {
beforeEach(function () {
rewire(this.spy = jasmine.createSpy('fetch'))
})
afterAll(restore)

it('should call fetch', function () {
fetchItems()
expect(this.spy).toHaveBeenCalledWith('/items')
})
})

// Mocha/Chai and Sinon example
describe('adapter', function () {
var spy

beforeEach(function () {
rewire(spy = sinon.spy())
})
after(restore)

it('should call fetch', function () {
fetchItems()
expect(spy.withArgs('/items').calledOnce).to.be.true
})
})
```

## Compatibility
* ❀️ Works with modern browsers:
* πŸ•• Safari 10.1+
* πŸ‰ Chrome 61+
* 🦊 Firefox 60+
* πŸ’Ž Edge 16+
* 🍏 Node:
* [`@std/esm`](https://github.com/standard-things/esm)
* `8.5.0+` behind [`--experimental-modules` flag](https://github.com/nodejs/node/blob/master/doc/changelogs/CHANGELOG_V8.md#8.5.0)
* ✨ SystemJS
* 🌟 Webpack 2+
* πŸ’₯ Rollup
* πŸ“¦ Works with CommonJS, including running in Node and bundling with webpack/browserify:
* Use [`env`](https://babeljs.io/docs/en/babel-preset-env/) preset
* When specifying `plugins` directly make sure that `"rewire-exports"` goes
before [`"@babel/plugin-transform-modules-commonjs"`](https://babeljs.io/docs/en/babel-plugin-transform-modules-commonjs)
* Works with [πŸƒ Jest](https://jestjs.io/) using [babel-jest](https://github.com/facebook/jest/tree/master/packages/babel-jest)

## How it works
[In ES6, imports are live read-only views on exported values](
http://exploringjs.com/es6/ch_modules.html#_in-es6-imports-are-live-read-only-views-on-exported-values):

```js
//------ lib.js ------
export let counter = 3;
export function incCounter() {
counter++;
}

//------ main1.js ------
import { counter, incCounter } from './lib';

// The imported value `counter` is live
console.log(counter); // 3
incCounter();
console.log(counter); // 4

// The imported value can’t be changed
counter++; // TypeError
```

This allows for any exports to be overwritten from within the module -
and imports will be automatically updated via their bindings.

### Transformations
Here's how various kinds of export declarations are transformed:
* Literals (`export default 'foo'`) - the original value is copied to a variable to be stubbed and restored later:
`export {_default as default}`

* Variables:
- named exports (`export var foo`, `export let bar` or `export {baz}`) are left intact,
but their initial values are similarly copied to temp variables.
- default export (`export default foo`) is converted to a named export to enable live binding:
`export {foo as default}`

* Constants (`export const foo = 'bar'` or `export default foo`) are treated similar to variables,
but their values are **not modified** _within_ the module (since they are read-only) - only exported values are rewired:
- named exports: `export { _foo as foo }`
- default export: `export { _default as default }`

You can use `unsafeConst` [option](#options) to convert `const` to `let` in order to enable live binding.

* Functions (`export default function () {…}` or `export function foo() {…}`)
are converted into a function expression and exported variable by the same name.
The variable initialization is hoisted to the top of the scope to preserve existing behavior.

* Classes (`export default class foo {…}` or `export class foo {…}`) are handled similarly to functions
except the variables are not hoisted (again to preserve the existing behavior).

* Re-exports (`export * from './foo.js'` or `export {bar} from 'baz'`) are ignored.
They can be rewired in the original modules.

* Immutable values such as `undefined`, *globals* and *imports* are copied similar to literals.

## Installation

```sh
$ npm install babel-plugin-rewire-exports
```

## Usage

### Via `.babelrc` (Recommended)

**.babelrc**

```javascript
// without options
{
"plugins": ["rewire-exports"]
}

// with options
{
"plugins": [
["rewire-exports", {
"unsafeConst": true
}]
]
}
```

### Via CLI

```sh
$ babel --plugins rewire-exports script.js
```

### Via Node API

```javascript
require("@babel/core").transform("code", {
plugins: ["rewire-exports"]
});
```

## Options

### `unsafeConst`
`boolean`, defaults to `false`.

Constants cannot be rewired if you're targeting ES2015+,
because the plugin relies on variables being assign-able in order to work.
However setting `unsafeConst: true` will convert `export const foo = 'bar'` to `export let foo = 'bar'`.
This will allow to treat constant exports as regular variables.
This is *potentially unsafe* if your code relies on constants being read-only.

[npm-image]: https://img.shields.io/npm/v/babel-plugin-rewire-exports.svg?style=flat
[npm-url]: https://npmjs.org/package/babel-plugin-rewire-exports
[travis-image]: https://img.shields.io/travis/asapach/babel-plugin-rewire-exports.svg?style=flat
[travis-url]: https://travis-ci.org/asapach/babel-plugin-rewire-exports
[downloads-image]: https://img.shields.io/npm/dm/babel-plugin-rewire-exports.svg?style=flat
[downloads-url]: https://npmjs.org/package/babel-plugin-rewire-exports