Skip to content

Commit

Permalink
Convert to typescript (#88)
Browse files Browse the repository at this point in the history
* refactor: switch to typescript

* fix: package scripts
  • Loading branch information
icd2k3 authored Jan 24, 2020
1 parent ea5edad commit 06a1854
Show file tree
Hide file tree
Showing 11 changed files with 222 additions and 40 deletions.
3 changes: 2 additions & 1 deletion .eslintrc
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{
"extends": "airbnb",
"parser": "babel-eslint",
"parser": "@typescript-eslint/parser",
"plugins": ["@typescript-eslint"],
"env": { "jasmine": true },
"rules": {
"object-curly-newline": 0,
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
</h3>

<p align="center">
A small (~1.5kb gzip), flexible, <a href="https://reactjs.org/docs/higher-order-components.html">higher order component</a> for rendering breadcrumbs with <a href="https://github.com/ReactTraining/react-router">react-router</a> 4 & 5
A small (~1.6kb gzip), flexible, <a href="https://reactjs.org/docs/higher-order-components.html">higher order component</a> for rendering breadcrumbs with <a href="https://github.com/ReactTraining/react-router">react-router</a> 4 & 5
</p>

<p align="center">
Expand Down
1 change: 1 addition & 0 deletions babel.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ module.exports = function(api) {
presets: [
'@babel/preset-env',
'@babel/preset-react',
'@babel/preset-typescript'
],
plugins: [],
};
Expand Down
15 changes: 9 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "react-router-breadcrumbs-hoc",
"version": "3.2.4",
"version": "3.2.5",
"description": "small, flexible, higher order component for rendering breadcrumbs with react-router 4.x",
"repository": "icd2k3/react-router-breadcrumbs-hoc",
"main": "dist/cjs/index.js",
Expand All @@ -12,15 +12,17 @@
"build": "rollup -c",
"test": "jest",
"test-build": "sh ./scripts/test-build.sh",
"types": "tsc -p types/react-router-breadcrumbs-hoc",
"types": "yarn type-src && yarn type-descriptions",
"type-src": "tsc -p tsconfig.json",
"type-descriptions": "tsc -p types/react-router-breadcrumbs-hoc",
"travis": "sh ./scripts/travis.sh",
"lint": "eslint ./src/**"
},
"husky": {
"hooks": {
"commit-msg": "commitlint -E HUSKY_GIT_PARAMS",
"pre-commit": "yarn lint && yarn test",
"pre-push": "yarn types && yarn lint && yarn test-build"
"pre-commit": "yarn build && yarn lint && yarn test",
"pre-push": "yarn build && yarn types && yarn lint && yarn test-build"
}
},
"author": "Justin Schrader ([email protected])",
Expand All @@ -34,16 +36,17 @@
"devDependencies": {
"@babel/cli": "^7.8.3",
"@babel/core": "^7.8.3",
"@babel/plugin-transform-modules-commonjs": "^7.8.3",
"@babel/preset-env": "^7.8.3",
"@babel/preset-react": "^7.8.3",
"@babel/preset-typescript": "^7.8.3",
"@commitlint/cli": "^8.3.5",
"@commitlint/config-conventional": "^8.3.4",
"@rollup/plugin-commonjs": "^11.0.1",
"@rollup/plugin-node-resolve": "^7.0.0",
"@types/react": "^16.9.19",
"@types/react-router-dom": "^5.1.3",
"babel-core": "^7.0.0-0",
"@typescript-eslint/eslint-plugin": "^2.17.0",
"@typescript-eslint/parser": "^2.17.0",
"babel-eslint": "^10.0.2",
"babel-jest": "^25.1.0",
"coveralls": "^3.0.9",
Expand Down
6 changes: 5 additions & 1 deletion rollup.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,16 @@ const pkg = require('./package.json');

const external = Object.keys(pkg.peerDependencies);

const extensions = ['.js', '.tsx'];

const plugins = [
babel({
exclude: 'node_modules/**',
extensions,
}),
resolve({
mainFields: ['module', 'main', 'umd'],
extensions,
}),
];

Expand All @@ -28,7 +32,7 @@ const globals = {
};

export default exports.map((item) => ({
input: 'src/index.js',
input: 'src/index.tsx',
plugins: item.plugins,
external,
output: {
Expand Down
1 change: 0 additions & 1 deletion scripts/test-build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@

set -e

yarn build && \
TEST_BUILD=cjs yarn test --coverage=0 && \
TEST_BUILD=umd yarn test --coverage=0 && \
TEST_BUILD=es yarn test --coverage=0
3 changes: 2 additions & 1 deletion src/index.test.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
// @ts-nocheck
/* eslint-disable react/no-array-index-key */
/* eslint-disable react/jsx-filename-extension */
import React from 'react';
import PropTypes from 'prop-types';
import { mount } from 'enzyme';
import { MemoryRouter as Router } from 'react-router';
import { NavLink } from 'react-router-dom';
import withBreadcrumbs, { getBreadcrumbs } from './index';
import withBreadcrumbs, { getBreadcrumbs } from './index.tsx';

// imports to test compiled builds
import withBreadcrumbsCompiledES, { getBreadcrumbs as getBreadcrumbsCompiledES } from '../dist/es/index';
Expand Down
87 changes: 67 additions & 20 deletions src/index.js → src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@
import React, { createElement } from 'react';
import { matchPath, withRouter } from 'react-router';

/* eslint-disable-next-line */
import * as types from '../types/react-router-breadcrumbs-hoc/index'

const DEFAULT_MATCH_OPTIONS = { exact: true };
const NO_BREADCRUMB = 'NO_BREADCRUMB';

Expand All @@ -29,7 +32,7 @@ const NO_BREADCRUMB = 'NO_BREADCRUMB';
* we used to use the humanize-string package, but it added a lot of bundle
* size and issues with compilation. This 4-liner seems to cover most cases.
*/
const humanize = (str) => str
const humanize = (str: string): string => str
.replace(/^[\s_]+|[\s_]+$/g, '')
.replace(/[_\s]+/g, ' ')
.replace(/^[a-z]/, (m) => m.toUpperCase());
Expand All @@ -39,12 +42,20 @@ const humanize = (str) => str
* with `match`, `location`, and `key` props.
*/
const render = ({
component: reactRouterConfigComponent,
breadcrumb: Breadcrumb,
match,
location,
...rest
}) => {
}: {
breadcrumb: React.ComponentType | string,
match: { url: string },
location: types.Location
}): {
match: { url: string },
location: types.Location,
key: string,
breadcrumb: React.ReactNode
} => {
const componentProps = { match, location, key: match.url, ...rest };

return {
Expand All @@ -58,8 +69,18 @@ const render = ({
/**
* Small helper method to get a default breadcrumb if the user hasn't provided one.
*/
const getDefaultBreadcrumb = ({ pathSection, currentSection, location }) => {
const match = matchPath(pathSection, { ...DEFAULT_MATCH_OPTIONS, path: pathSection });
const getDefaultBreadcrumb = ({
currentSection,
location,
pathSection,
}: {
currentSection: string,
location: types.Location,
pathSection: string,
}) => {
const match = matchPath(pathSection, { ...DEFAULT_MATCH_OPTIONS, path: pathSection })
/* istanbul ignore next: this is hard to mock in jest :( */
|| { url: 'not-found' };

return render({
breadcrumb: humanize(currentSection),
Expand All @@ -79,12 +100,23 @@ const getBreadcrumbMatch = ({
location,
pathSection,
routes,
}: {
currentSection: string,
disableDefaults?: boolean,
excludePaths?: string[],
location: { pathname: string },
pathSection: string,
routes: types.BreadcrumbsRoute[]
}) => {
let breadcrumb;

// Check the optional `exludePaths` option in `options` to see if the
// Check the optional `excludePaths` option in `options` to see if the
// current path should not include a breadcrumb.
const getIsPathExcluded = (path) => matchPath(pathSection, { path, exact: true, strict: false });
const getIsPathExcluded = (path: string) => matchPath(pathSection, {
path,
exact: true,
strict: false,
});
if (excludePaths && excludePaths.some(getIsPathExcluded)) {
return NO_BREADCRUMB;
}
Expand Down Expand Up @@ -151,8 +183,18 @@ const getBreadcrumbMatch = ({
* Splits the pathname into sections, then search for matches in the routes
* a user-provided breadcrumb OR a sensible default.
*/
export const getBreadcrumbs = ({ routes, location, options = {} }) => {
const matches = [];
export const getBreadcrumbs = (
{
routes,
location,
options = {},
}: {
routes: types.BreadcrumbsRoute[],
location: types.Location,
options?: types.Options
},
): Array<React.ReactNode | string> => {
const matches:Array<React.ReactNode | string> = [];
const { pathname } = location;

pathname
Expand All @@ -162,7 +204,7 @@ export const getBreadcrumbs = ({ routes, location, options = {} }) => {
// Split pathname into sections.
.split('/')
// Reduce over the sections and call `getBreadcrumbMatch()` for each section.
.reduce((previousSection, currentSection) => {
.reduce((previousSection: string, currentSection: string) => {
// Combine the last route section with the currentSection.
// For example, `pathname = /1/2/3` results in match checks for
// `/1`, `/1/2`, `/1/2/3`.
Expand All @@ -184,7 +226,7 @@ export const getBreadcrumbs = ({ routes, location, options = {} }) => {
}

return pathSection === '/' ? '' : pathSection;
}, null);
}, '');

return matches;
};
Expand All @@ -193,18 +235,23 @@ export const getBreadcrumbs = ({ routes, location, options = {} }) => {
* Takes a route array and recursively flattens it IF there are
* nested routes in the config.
*/
const flattenRoutes = (routes) => (routes || []).reduce((arr, route) => {
if (route.routes) {
return arr.concat([route, ...flattenRoutes(route.routes)]);
}
return arr.concat(route);
}, []);
const flattenRoutes = (routes: types.BreadcrumbsRoute[]) => (routes)
.reduce((arr, route: types.BreadcrumbsRoute): types.BreadcrumbsRoute[] => {
if (route.routes) {
return arr.concat([route, ...flattenRoutes(route.routes)]);
}
return arr.concat(route);
}, [] as types.BreadcrumbsRoute[]);

export default (routes = [], options) => (Component) => withRouter(
(props) => createElement(Component, {
export default (
routes?: types.BreadcrumbsRoute[],
options?: types.Options,
) => (Component: React.ComponentType) => withRouter(
(props: { location: types.Location }) => createElement(Component, {
...props,
// @ts-ignore-next-line
breadcrumbs: getBreadcrumbs({
routes: flattenRoutes(routes),
routes: flattenRoutes(routes || []),
location: props.location,
options,
}),
Expand Down
25 changes: 25 additions & 0 deletions tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"compilerOptions": {
"allowJs": true,
"checkJs": true,
"esModuleInterop": true,
"jsx": "react",
"lib": ["es2015", "dom"],
"module": "commonjs",
"moduleResolution": "node",
"noEmit": true,
"noImplicitAny": true,
"noImplicitReturns": true,
"noImplicitThis": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"outDir": "./dist",
"strictFunctionTypes": true,
"strictNullChecks": true,
"strictPropertyInitialization": true,
"target": "es5"
},
"files": [
"src/index.tsx"
]
}
9 changes: 7 additions & 2 deletions types/react-router-breadcrumbs-hoc/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ export interface Options {
pathSection?: string;
}

export interface Location {
pathname: string
}

export interface MatchOptions {
exact?: boolean;
strict?: boolean;
Expand All @@ -21,12 +25,13 @@ export interface MatchOptions {

export interface BreadcrumbsRoute {
path: string;
breadcrumb: React.ReactNode | string;
breadcrumb?: React.ComponentType | React.ElementType | string;
matchOptions?: MatchOptions;
routes?: BreadcrumbsRoute[];
}

export interface BreadcrumbsProps<T = {}> extends RouteComponentProps<T> {
breadcrumb: React.ReactNode | string;
breadcrumb: React.ComponentType | string;
}

export interface InjectedProps<P = {}> extends RouteComponentProps<P> {
Expand Down
Loading

0 comments on commit 06a1854

Please sign in to comment.