Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/allow production message #200

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 19 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,25 @@ if (!condition) {
```

- rollup: use [rollup-plugin-replace](https://github.com/rollup/rollup-plugin-replace) and set `NODE_ENV` to `production` and then `rollup` will treeshake out the unused code
- Webpack: [instructions](https://webpack.js.org/guides/production/#specify-the-mode)
- Webpack: [instructions](https://webpack.js.org/guides/production/#specify-the-mode)

## Using `tiny-invariant/keep-messages`

For cases where you need the invariant messages to be available in production, you can import the keep-messages version of the library. This can be useful if you are using a monitoring system like Sentry to capture error messages in production environments. Note that this will result in slightly larger bundles due to the inclusion of the messages.

```ts
import invariant from 'tiny-invariant/keep-messages';

invariant(condition, 'This message will be available in production');
// In production: Error('Invariant violation: This message will be available in production')
```

```ts
import invariant from 'tiny-invariant';

invariant(condition, 'This message will be not available in production');
// In production: Error('Invariant violation')
```

## Builds

Expand Down
30 changes: 27 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,13 @@
"types": "./dist/tiny-invariant.d.ts",
"default": "./dist/tiny-invariant.cjs.js"
}
},
"./keep-messages": {
"import": "./dist/esm/tiny-invariant-keep-messages.js",
"default": {
"types": "./dist/tiny-invariant-keep-messages.d.ts",
"default": "./dist/tiny-invariant-keep-messages.cjs.js"
}
}
},
"sideEffects": false,
Expand All @@ -51,6 +58,23 @@
"path": "dist/tiny-invariant.esm.js",
"import": "foo",
"limit": "112B"
},
{
"path": "dist/tiny-invariant-keep-messages.min.js",
"limit": "251B"
},
{
"path": "dist/tiny-invariant-keep-messages.js",
"limit": "267B"
},
{
"path": "dist/tiny-invariant-keep-messages.cjs.js",
"limit": "171B"
},
{
"path": "dist/tiny-invariant-keep-messages.esm.js",
"import": "foo",
"limit": "112B"
}
],
"scripts": {
Expand All @@ -61,9 +85,9 @@
"typescript:check": "tsc --noEmit",
"validate": "yarn prettier:check && yarn typescript:check",
"build:clean": "rimraf dist",
"build:flow": "cp src/tiny-invariant.js.flow dist/tiny-invariant.cjs.js.flow",
"build:typescript": "tsc ./src/tiny-invariant.ts --emitDeclarationOnly --declaration --outDir ./dist",
"build:typescript:esm": "tsc ./src/tiny-invariant.ts --emitDeclarationOnly --declaration --outDir ./dist/esm",
"build:flow": "cp src/tiny-invariant.js.flow dist/tiny-invariant.cjs.js.flow && cp src/tiny-invariant-keep-messages.js.flow dist/tiny-invariant-keep-messages.cjs.js.flow",
"build:typescript": "tsc ./src/tiny-invariant.ts ./src/tiny-invariant-keep-messages.ts --emitDeclarationOnly --declaration --outDir ./dist",
"build:typescript:esm": "tsc ./src/tiny-invariant.ts ./src/tiny-invariant-keep-messages.ts --emitDeclarationOnly --declaration --outDir ./dist/esm",
"build:dist": "yarn rollup --config rollup.config.mjs",
"build": "yarn build:clean && yarn build:dist && yarn build:typescript && yarn build:typescript:esm",
"prepublishOnly": "yarn build"
Expand Down
63 changes: 31 additions & 32 deletions rollup.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -2,53 +2,51 @@ import typescript from '@rollup/plugin-typescript';
import replace from '@rollup/plugin-replace';
import { terser } from 'rollup-plugin-terser';

const input = 'src/tiny-invariant.ts';
const inputs = ['src/tiny-invariant.ts', 'src/tiny-invariant-keep-messages.ts'];

export default [
// Universal module definition (UMD) build
{
input,
output: {
file: 'dist/tiny-invariant.js',
format: 'umd',
name: 'invariant',
},
plugins: [typescript({ module: 'ESNext' })],
const createConfig = (input, fileSuffix = '') => ({
input,
output: {
file: `dist/${input.split('/').pop().replace('.ts', fileSuffix + '.js')}`,
format: 'umd',
name: input.includes('keep-messages') ? 'invariantKeepMessages' : 'invariant',
},
// Universal module definition (UMD) build (production)
{
input,
output: {
file: 'dist/tiny-invariant.min.js',
format: 'umd',
name: 'invariant',
},
plugins: [typescript({ module: 'ESNext' })],
});

export default [
// UMD build
...inputs.map(input => createConfig(input)),

// UMD build (production)
...inputs.map(input => ({
...createConfig(input, '.min'),
plugins: [
// Setting production env before running other steps
replace({ 'process.env.NODE_ENV': JSON.stringify('production'), preventAssignment: true }),
typescript({ module: 'ESNext' }),
terser(),
],
},
})),

// ESM build
{
...inputs.map(input => ({
input,
output: {
file: 'dist/tiny-invariant.esm.js',
file: `dist/${input.split('/').pop().replace('.ts', '.esm.js')}`,
format: 'esm',
},
plugins: [typescript({ module: 'ESNext' })],
},
// ESM build for "module": "node16" TypeScript projects (https://github.com/alexreardon/tiny-invariant/issues/144)
{
})),

// ESM build for "module": "node16" TypeScript projects
...inputs.map(input => ({
input,
output: {
file: 'dist/esm/tiny-invariant.js',
file: `dist/esm/${input.split('/').pop().replace('.ts', '.js')}`,
format: 'esm',
},
plugins: [
typescript({ module: 'ESNext' }),
// https://github.com/rollup/rollup/blob/69ff4181e701a0fe0026d0ba147f31bc86beffa8/build-plugins/emit-module-package-file.ts
{
generateBundle() {
this.emitFile({
Expand All @@ -60,15 +58,16 @@ export default [
name: 'emit-module-package-file',
},
],
},
})),

// CommonJS build
{
...inputs.map(input => ({
input,
output: {
file: 'dist/tiny-invariant.cjs.js',
file: `dist/${input.split('/').pop().replace('.ts', '.cjs.js')}`,
format: 'cjs',
exports: 'default',
},
plugins: [typescript({ module: 'ESNext' })],
},
})),
];
12 changes: 12 additions & 0 deletions src/tiny-invariant-keep-messages.flow.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// @flow
// This file is not actually executed
// It is just used by flow for typing

const prefix: string = 'Invariant failed';

export default function invariant(condition: mixed, message?: string | (() => string)) {
if (condition) {
return;
}
throw new Error(`${prefix}: ${message || ''}`);
}
42 changes: 42 additions & 0 deletions src/tiny-invariant-keep-messages.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
const prefix: string = 'Invariant failed';

/**
* invariant` is used to [assert](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-7.html#assertion-functions) that the `condition` is [truthy](https://github.com/getify/You-Dont-Know-JS/blob/bdbe570600d4e1107d0b131787903ca1c9ec8140/up%20%26%20going/ch2.md#truthy--falsy).
*
* 💥 `invariant` will `throw` an `Error` if the `condition` is [falsey](https://github.com/getify/You-Dont-Know-JS/blob/bdbe570600d4e1107d0b131787903ca1c9ec8140/up%20&%20going/ch2.md#truthy--falsy)
*
* 📢 `message`s are displayed in production environments to aid in debugging, which may result in slightly larger bundles
*
* @example
*
* ```ts
* const value: Person | null = { name: 'Alex' };
* invariant(value, 'Expected value to be a person');
* // type of `value`` has been narrowed to `Person`
* ```
*/
export default function invariant(
condition: any,
// Not providing an inline default argument for message as the result is smaller
/**
* Can provide a string, or a function that returns a string for cases where
* the message takes a fair amount of effort to compute
*/
message?: string | (() => string),
): asserts condition {
if (condition) {
return;
}
// Condition not passed

// When not in production we allow the message to pass through
// *This block will be removed in production builds*

const provided: string | undefined = typeof message === 'function' ? message() : message;

// Options:
// 1. message provided: `${prefix}: ${provided}`
// 2. message not provided: prefix
const value: string = provided ? `${prefix}: ${provided}` : prefix;
throw new Error(value);
}
72 changes: 40 additions & 32 deletions test/message-behaviour.spec.ts
Original file line number Diff line number Diff line change
@@ -1,37 +1,45 @@
// @flow
import invariant from '../src/tiny-invariant';
import invariantKeepMessages from '../src/tiny-invariant-keep-messages';

it('should include a default message when an invariant does throw and no message is provided', () => {
try {
invariant(false);
} catch (e) {
invariant(e instanceof Error);
expect(e.message).toEqual('Invariant failed');
}
});
describe.each([
['invariant', invariant],
['invariantKeepMessages', invariantKeepMessages]
])("%s", (_, invariantFunction: typeof invariant)=> {
it('should include a default message when an invariant does throw and no message is provided', () => {
try {
invariantFunction(false);
} catch (e) {
invariantFunction(e instanceof Error);
expect(e.message).toEqual('Invariant failed');
}
});

it('should include a provided message when an invariant does throw', () => {
try {
invariantFunction(false, 'my message');
} catch (e) {
invariantFunction(e instanceof Error);
expect(e.message).toEqual('Invariant failed: my message');
}
});

it('should not execute a message function if the invariant does not throw', () => {
const message = jest.fn(() => 'lazy message');
invariantFunction(true, message);
expect(message).not.toHaveBeenCalled();
});

it('should execute a message function if the invariant does throw', () => {
const message = jest.fn(() => 'lazy message');
try {
invariantFunction(false, message);
} catch (e) {
invariantFunction(e instanceof Error);
expect(message).toHaveBeenCalled();
expect(e.message).toEqual('Invariant failed: lazy message');
}
});
})

it('should include a provided message when an invariant does throw', () => {
try {
invariant(false, 'my message');
} catch (e) {
invariant(e instanceof Error);
expect(e.message).toEqual('Invariant failed: my message');
}
});

it('should not execute a message function if the invariant does not throw', () => {
const message = jest.fn(() => 'lazy message');
invariant(true, message);
expect(message).not.toHaveBeenCalled();
});

it('should execute a message function if the invariant does throw', () => {
const message = jest.fn(() => 'lazy message');
try {
invariant(false, message);
} catch (e) {
invariant(e instanceof Error);
expect(message).toHaveBeenCalled();
expect(e.message).toEqual('Invariant failed: lazy message');
}
});
25 changes: 25 additions & 0 deletions test/production-behavior.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// @flow
process.env.NODE_ENV = "production"
import invariant from '../src/tiny-invariant';
import invariantKeepMessages from '../src/tiny-invariant-keep-messages';

it('invariant should not display custom messages in production', () => {
try {
invariant(false, 'This is a custom message');
} catch (e) {
invariant(e instanceof Error);
expect(e.message).toEqual('Invariant failed');
}
});

it('invariantKeepMessages should display custom messages in production', () => {
try {
invariantKeepMessages(false, 'This is a custom message');
} catch (e) {
invariant(e instanceof Error);
expect(e.message).toEqual('Invariant failed: This is a custom message');
}
});