Skip to content

Commit

Permalink
Add typescript support (#99)
Browse files Browse the repository at this point in the history
* Add typescript support

* Add typescript support for boolean and func

* Add boolean support

* Add custom react-docgen-typescript

* Commit libs

* Fix package.json

* Fix componentsIndex

* Add bin support

* Update react-docgen

* Add typings

* Do not include tests folder

* bump version

* Add exclude option

* Fix tests

* Add exclude option description and bump version

* Remove lib from git

* Add lib to gitignore

* Update README and bump version

* Fix tests

* Fix typo

* Update README localStorage
  • Loading branch information
ondrejbartas authored and imtoo committed Mar 1, 2017
1 parent 4ea11d2 commit 4d031d1
Show file tree
Hide file tree
Showing 12 changed files with 128 additions and 23 deletions.
17 changes: 16 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,15 @@ DEMO here: [http://bluekit.blueberry.io](http://bluekit.blueberry.io)
$ npm install --save react-bluekit
```

You can use BlueKit via npm script or gulp

## Npm script
```js
"scripts": {
"bluekit": "bluekit --baseDir ./components --paths . --exclude \"./(Layout|StyledComponent).tsx\""
}
```

## Gulpfile configuration

```js
Expand All @@ -24,6 +33,8 @@ createBlueKit({
baseDir: `${__dirname}/src/browser`,
// relative paths from base dir where to look for components
paths: ['./components/', './auth'],
// relative paths from base dir of files or directories you want to exclude from indexing
exclude: ['./components/Foo'],
// set to true when providing simple components such as `export default function MyComponent() { <div>Hello</div> }`
noSpecialReplacements: true
});
Expand Down Expand Up @@ -75,6 +86,10 @@ You can also pass `children` to BlueKit, which will be displayed above the searc

To add jsdoc **descriptions** see example [example_components/Checkbox.react.js](https://github.com/blueberryapps/react-bluekit/blob/master/example_components/Checkbox.react.js).

## Typescript support

Bluekit automatically finds `.tsx` files and uses [react-docgen-typescript](https://github.com/imtoo/react-docgen-typescript) parser for it.

## BlueKit development
```
npm install
Expand All @@ -101,7 +116,7 @@ gulp eslint

BlueKit automatically hides props that don’t affect the component’s look.

If you get some kind of weird error and BlueKit doesn't load at all, try to reset localStorage by running `localStorage.clear();`. We are working on automatic checks of localStorage values.
If you get some kind of weird error and BlueKit doesn't load at all, try to reset localStorage by running `localStorage.clear();`.

## License

Expand Down
37 changes: 37 additions & 0 deletions bin/bluekit.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
#! /usr/bin/env node
const createBlueKit = require('../lib/createBlueKit').default;
const path = require('path');

const argv = require('yargs')
.usage('Usage: \nyarn bluekit -- --baseDir [./src/browser] --paths [./components/ ./auth]')
.help('h')
.alias('h', 'help')

.describe('baseDir', 'your directory where components are located')

.array('paths')
.describe('paths', 'relative paths from base dir where to look for components')

.boolean('noSpecialReplacements')
.describe('noSpecialReplacements', 'set to true when providing simple components such as `export default function MyComponent() { <div>Hello</div> }`')

.example('--baseDir ./src --paths ./components')
.demand(['baseDir', 'paths'])

.array('exclude')
.describe('exclude', 'exclude files or directories from components listing`')
.argv;

const baseDir = path.join(process.cwd(), argv.baseDir);

const config = {
baseDir: baseDir,
paths: [].concat(argv.paths),
exclude: [].concat(argv.exclude),
noSpecialReplacements: argv.noSpecialReplacements,
gulp: {
task: function() { }
}
}

createBlueKit(config)();
2 changes: 1 addition & 1 deletion circle.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,4 @@ test:
- cd example && gulp:
background: true
- sleep 10
- curl --retry 10 --retry-delay 5 -v http://localhost:8001/
- curl --retry 10 --retry-delay 5 -v http://localhost:3000/
2 changes: 1 addition & 1 deletion example/webpack.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ const webpack = require('webpack');
module.exports = {
devtool: 'eval',
entry: [
'webpack-dev-server/client?http://localhost:8001',
'webpack-dev-server/client?http://localhost:3000',
'webpack/hot/only-dev-server',
'./src/index'
],
Expand Down
10 changes: 10 additions & 0 deletions index.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import * as React from 'react';

interface BluekitProps {
componentsIndex: Object;
inline?: boolean;
}

export default class Bluekit extends React.Component<BluekitProps, any> {

}
5 changes: 3 additions & 2 deletions nunjucks/componentsIndex.nunjucks
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@ import {{ component.name }} from '{{component.file | escapeJsString}}';
{%- endfor %}

function dispatchEvent(data) {
if (typeof window !== 'undefined')
if (typeof window !== 'undefined') {
const BluekitEvent = require('react-bluekit/lib/libraries/BluekitEvent');
document.dispatchEvent(new BluekitEvent('functionTriggered', data));
else
} else
console.log('Bluekit received function triggered', data)
}

Expand Down
8 changes: 7 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
{
"name": "react-bluekit",
"version": "0.3.2",
"version": "0.4.1",
"description": "Visualisation and Playground generated from Components",
"main": "lib/index.js",
"jsnext:main": "src/index.js",
"bin": {
"bluekit": "bin/bluekit.js"
},
"scripts": {
"build": "npm run build:lib",
"build:lib": "babel src --out-dir lib",
Expand All @@ -26,6 +29,7 @@
"es7",
"babel",
"react-component",
"typescript",
"visualization",
"playground",
"development",
Expand All @@ -35,6 +39,7 @@
"node"
],
"author": "Ondrej Bartas",
"typings": "index",
"license": "MIT",
"bugs": {
"url": "https://github.com/blueberryapps/react-bluekit/issues"
Expand Down Expand Up @@ -82,6 +87,7 @@
"react-color": ">=2",
"react-copy-to-clipboard": ">=4",
"react-docgen": ">=2.4",
"react-docgen-typescript": "https://github.com/imtoo/react-docgen-typescript.git",
"react-highlight": ">=0.5",
"react-pure-render": ">=1",
"react-responsive": "^1.1.3",
Expand Down
2 changes: 1 addition & 1 deletion src/app/StateProvider.react.js
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ export default function StateProvider(Wrapped) {
value = event.value

// fix string to valid type
if (type === 'bool' && typeof value !== 'boolean')
if ((type === 'bool' || type === 'boolean') && typeof value !== 'boolean')
value = event.target.checked
else if (type === 'number')
value = parseInt(value, 10)
Expand Down
10 changes: 7 additions & 3 deletions src/app/component/PropsTable.react.js
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ export default class PropsTable extends Component {
fullWidth && styles.prop.fullWidth,
triggered && {backgroundColor: colors.GRAY_BRIGHT}]}
>
{name === 'func'
{(name === 'func' || name.match(/\(.*\)\s*=>/))
? 'func()'
: this.renderValueSelection(key, data.get('type').toJS(), scope)
}
Expand All @@ -110,7 +110,7 @@ export default class PropsTable extends Component {
renderNameOfProp(name, kind) {
const {handlePropsNameClick} = this.props

if (['string', 'number', 'bool', 'enum'].indexOf(kind) === -1)
if (['string', 'number', 'bool', 'boolean', 'enum'].indexOf(kind) === -1)
return name
else
return (
Expand All @@ -134,14 +134,18 @@ export default class PropsTable extends Component {
value: fromJS(componentProps).getIn(scope.concat([key]))
}

switch (type.name) {
switch (type.name.replace('React.', '')) {
case 'any': return <Input key={name} type='text' {...defaultProps} />
case 'array': return <JsonEditor key={name} name={name} {...defaultProps} />
case 'arrayOf': return <JsonEditor key={name} name={name} {...defaultProps} />
case 'bool': return <Checkbox key={name} {...{...defaultProps, checked: defaultProps.value, name: key}} />
case 'boolean': return <Checkbox key={name} {...{...defaultProps, checked: defaultProps.value, name: key}} />
case 'element': return <HtmlEditor key={name} name={name} {...defaultProps} />
case 'enum' : return this.renderEnum(name, type, defaultProps)
case 'node': return <HtmlEditor key={name} name={name} {...defaultProps} />
case 'Children': return <HtmlEditor key={name} name={name} {...defaultProps} />
case 'ReactNode': return <HtmlEditor key={name} name={name} {...defaultProps} />
case 'ReactElement': return <HtmlEditor key={name} name={name} {...defaultProps} />
case 'number': return <Input key={name} type='number' {...defaultProps} />
case 'object': return <JsonEditor key={name} name={name} {...defaultProps} />
case 'shape': return <JsonEditor key={name} name={name} {...defaultProps} />
Expand Down
40 changes: 29 additions & 11 deletions src/createBlueKit.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,14 @@ import {parse as docgenParse} from 'react-docgen';
const nunjuckEnv = nunjucks.configure(`${__dirname}/../nunjucks/`, {autoescape: false});
nunjuckEnv.addFilter('escapeJsString', input => JSON.stringify(input).replace(/'/g, '\\\'').slice(1, -1));

function getAllFilesInDir(dir, relativeDirectory = []) {
function isExcluded(filename, exclude) {
const relativeFilename = `./${filename}`;
return exclude.reduce((acc, item) => {
return relativeFilename.match(new RegExp(`^${item}`)) !== null || acc;
}, false);
}

function getAllFilesInDir(dir, relativeDirectory = [], exclude = []) {
const resolvedDir = path.join(dir, relativeDirectory);

if (!fs.existsSync(resolvedDir)) return null
Expand All @@ -19,13 +26,15 @@ function getAllFilesInDir(dir, relativeDirectory = []) {
const absolutePath = path.join(dir, relativeDirectory, file);

if (fs.lstatSync(absolutePath).isDirectory()) {
return getAllFilesInDir(dir, path.join(relativeDirectory, file));
return getAllFilesInDir(dir, path.join(relativeDirectory, file), exclude);
}

const filePath = path.join(`./${relativeDirectory}`, file);
if (!filePath.match(/\.(js|jsx)$/))
if (!filePath.match(/\.(js|jsx|tsx)$/))
return null
if (filePath.match(/__tests?__/))
return null
if (filePath.match(/__test__/))
if (isExcluded(filePath, exclude))
return null
return filePath
}));
Expand All @@ -37,25 +46,34 @@ function objectToString(object) {

function getImportFile(directory, file) {
if (directory.match(/node_modules/)) {
const pathParts = file.replace(/\.(js|jsx)$/, '').split(path.sep)
const pathParts = file.replace(/\.(js|jsx|tsx)$/, '').split(path.sep)
pathParts[1] = 'lib'
return pathParts.join(path.sep)
}

return file[0] === '.' ? file : `./${file}`
}

function generateComponentData(config, file, directory) {
const filePath = path.join(directory, file);
function getDocgen(config, filePath) {
if (filePath.match(/\.tsx$/))
return require('react-docgen-typescript').parse(filePath);

let content = fs.readFileSync(filePath).toString()
if (!config.noSpecialReplacements) {
content = content
.replace('_interopRequireDefault(_react)', 'require("react")')
.replace(/import Component from ["']react-pure-render\/component["']/, 'import {Component} from "react"')
.replace(/export default .*\((\w*)\)+/m, 'export default $1')
}
return docgenParse(content);
}

function generateComponentData(config, file, directory) {
const filePath = path.join(directory, file);

try {
const docgen = docgenParse(content);
const docgen = getDocgen(config, filePath);

const doc = {
...docgen,
propsDefinition: objectToString(docgen.props)
Expand All @@ -65,7 +83,7 @@ function generateComponentData(config, file, directory) {
const menu = normalizedFile
.replace(/\.\.\//g, '')
.replace('.react', '')
.replace(/\.(js|jsx)$/, '')
.replace(/\.(js|jsx|tsx)$/, '')
.replace(/(?:^|\/)(\w)/g, (_, c) => c ? ` ${c.toUpperCase()}` : '')
.replace(/(?:^|[-_])(\w)/g, (_, c) => c ? `${c.toUpperCase()}` : '')
.replace(/\//g, '')
Expand Down Expand Up @@ -112,7 +130,7 @@ export default function createBlueKit(config) {

const watch = function() {
const watchPaths = config.paths.map(file => (
path.relative(process.cwd(), path.join(config.baseDir, file, '**/*.{js,jsx}'))
path.relative(process.cwd(), path.join(config.baseDir, file, '**/*.{js,jsx,tsx}'))
));

console.log('Watching BlueKit in and automatically rebuilding on paths:') // eslint-disable-line no-console
Expand All @@ -131,7 +149,7 @@ export default function createBlueKit(config) {

function generate() {
const files = config.paths.map(file => (
getAllFilesInDir(config.baseDir, file)
getAllFilesInDir(config.baseDir, file, config.exclude)
));

const components = getValidFiles(files).map(file => {
Expand Down
6 changes: 5 additions & 1 deletion src/libraries/buildProps.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,20 @@ export default function buildProps(propsDefinition, allProps = false) {
}

function calculateProp(type, prop) {
switch (type.name) {
switch (`${type.name}`.replace('React.', '')) {
case 'any': return `ANY ${prop}`
case 'node': return `NODE ${prop}`
case 'Children': return `NODE ${prop}`
case 'ReactNode': return `NODE ${prop}`
case 'ReactElement': return `NODE ${prop}`
case 'string': return `${prop}`
case 'bool': return true
case 'boolean': return true
case 'number': return 1
case 'array': return []
case 'object': return {}
case 'func': return eval(`[function () { dispatchEvent({detail: {prop: "${prop}"}}) }][0]`) // eslint-disable-line no-eval
case /\(.*\)\s*=>/: return eval(`[function () { dispatchEvent({detail: {prop: "${prop}"}}) }][0]`) // eslint-disable-line no-eval
case 'enum': return (typeof type.value === 'string' ? '' : (type.value[0].value && type.value[0].value.replace(/'/g, ''))) || ''
case 'shape': return Map(type.value)
.map((subType, name) => calculateProp(subType, name))
Expand Down
12 changes: 11 additions & 1 deletion yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2701,7 +2701,7 @@ ignore@^3.1.5:
version "3.2.0"
resolved "https://registry.yarnpkg.com/ignore/-/ignore-3.2.0.tgz#8d88f03c3002a0ac52114db25d2c673b0bf1e435"

immutable@3.8.1, immutable@^3.7.6:
immutable@^3.7.6, immutable@^3.8.1:
version "3.8.1"
resolved "https://registry.yarnpkg.com/immutable/-/immutable-3.8.1.tgz#200807f11ab0f72710ea485542de088075f68cd2"

Expand Down Expand Up @@ -4299,6 +4299,12 @@ react-deep-force-update@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/react-deep-force-update/-/react-deep-force-update-1.0.1.tgz#f911b5be1d2a6fe387507dd6e9a767aa2924b4c7"

"react-docgen-typescript@https://github.com/imtoo/react-docgen-typescript.git":
version "0.0.1-m01"
resolved "https://github.com/imtoo/react-docgen-typescript.git#b0b57c9f98cec31ab0e040251aa5a97bb15cdf63"
dependencies:
typescript "^1.8.10"

react-docgen@>=2.4:
version "2.12.1"
resolved "https://registry.yarnpkg.com/react-docgen/-/react-docgen-2.12.1.tgz#64fe750832967acbc9755f37c25d563d407c9149"
Expand Down Expand Up @@ -5086,6 +5092,10 @@ typedarray@~0.0.5:
version "0.0.6"
resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"

typescript@^1.8.10:
version "1.8.10"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-1.8.10.tgz#b475d6e0dff0bf50f296e5ca6ef9fbb5c7320f1e"

ua-parser-js@^0.7.9:
version "0.7.10"
resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.10.tgz#917559ddcce07cbc09ece7d80495e4c268f4ef9f"
Expand Down

0 comments on commit 4d031d1

Please sign in to comment.