1
0
Fork 0
This commit is contained in:
Travis Fischer 2018-03-04 15:43:18 -05:00 committed by Joost De Cock
parent e80c3b2298
commit 907845feb1
27 changed files with 11082 additions and 0 deletions

View file

@ -0,0 +1,27 @@
{
"parser": "babel-eslint",
"extends": [
"standard",
"standard-react"
],
"env": {
"es6": true
},
"plugins": [
"react"
],
"parserOptions": {
"sourceType": "module",
"allowImportExportEverywhere": true
},
"rules": {
// don't force es6 functions to include space before paren
"space-before-function-paren": 0,
// allow specifying true explicitly for boolean props
"react/jsx-boolean-value": 0,
// allow imports mixed with non-import statements at top of file
"import/first": 0
}
}

View file

@ -0,0 +1,20 @@
# See https://help.github.com/ignore-files/ for more about ignoring files.
# dependencies
node_modules
# builds
build
dist
# misc
.DS_Store
.env
.env.local
.env.development.local
.env.test.local
.env.production.local
npm-debug.log*
yarn-debug.log*
yarn-error.log*

View file

@ -0,0 +1,107 @@
# react-modern-library-cli
> CLI for easily creating and publishing modern React modules with Rollup and example usage via create-react-app.
[![NPM](https://img.shields.io/npm/v/react-modern-library-cli.svg)](https://www.npmjs.com/package/react-modern-library-cli) [![JavaScript Style Guide](https://img.shields.io/badge/code_style-standard-brightgreen.svg)](https://standardjs.com)
## Intro
*Note*: Modern means modern as of March, 2018.. I'm sure everything will change in a month... :joy: :joy:
This is a super easy-to-use CLI version of this previously published [boilerplate](https://github.com/transitive-bullshit/react-modern-library-boilerplate), which you can read about in this [article](https://hackernoon.com/publishing-baller-react-modules-2b039d84bce7).
**The purpose of this CLI is to make publishing your own React components as simple as possible.**
## Features
- Support all modern JS language features for component development out of the box
- Build process to convert source to `umd` and `es` module formats for publishing to npm
- Comes with an `example` app using a standard [create-react-app](https://github.com/facebookincubator/create-react-app), serving 2 purposes
- Local, hot-reload server for developing your module
- Easily publishable to github pages so users can quickly play with your module
- Use [Rollup](https://rollupjs.org/) for build process and [Babel](https://babeljs.io/) for transpilation
- See the [blog post](https://hackernoon.com/publishing-baller-react-modules-2b039d84bce7) for an explanation of Rollup vs Webpack
- Allow the use of `npm` modules within your library, either as dependencies or peer-dependencies
- Support importing CSS in your components (with css modules enabled by default)
- Note that CSS support will be a noop if you're using css-in-js
- Testing with [Jest](https://facebook.github.io/jest/), using `react-scripts` from `create-react-app`
- Sourcemap creation enabled by default
- Thorough documentation written by someone who cares :heart_eyes:
## Install
```bash
npm install -g react-modern-library-cli
```
## Creating a New Module
```bash
react-modern-library-cli
```
After you answer some basic prompts about your module, the CLI will perform the following:
- copy over the template to a new folder in the current directory
- initialize yarn or npm package manager
- link packages together for development
- initialize local git repo
At this point, you're new module is all setup for local development.
## Development
#### Local Development
Local development is broken into two parts.
Note: if you're using yarn, you can skip the `yarn link` steps, as the generated module's example includes a local-link by default.
```bash
npm start # runs rollup with watch flag => dist
```
The second part will be running our `example/` create-react-app that's linked to the local version of your module.
```bash
# (in another tab)
cd example
npm link <your-module-name> # you may skip this if using yarn
npm start # runs create-react-app dev server
```
Now, anytime you make a change to your component in `src/` or to the example app's `example/src`, `create-react-app` will live-reload your local dev server so you can iterate on your component in real-time.
![](https://media.giphy.com/media/12NUbkX6p4xOO4/giphy.gif)
#### NPM Stuffs
The only difference when publishing your component to **npm** is to make sure you add any npm modules you want as peer dependencies are properly marked as `peerDependencies` in `package.json`. The rollup config will automatically recognize them as peer dependencies and not try to bundle them in your module.
Then publish as per usual.
```bash
# note this will build `commonjs` and `es`versions of your module to dist/
npm publish
```
#### Github Pages
Deploying the example to github pages is simple. We create a production build of our example `create-react-app` that showcases your library and then run `gh-pages` to deploy the resulting bundle. This can be done as follows:
```bash
npm run deploy
```
## Examples
### Multiple Named Exports
Here is a [branch](https://github.com/transitive-bullshit/react-modern-library-boilerplate/tree/feature/multiple-exports) which demonstrates how to use multiple named exports. The module in this branch exports two components, `Foo` and `Bar`, and shows how to use them from the example app.
### Material-UI
Here is a [branch](https://github.com/transitive-bullshit/react-modern-library-boilerplate/tree/feature/material-ui) which demonstrates how to make use of a relatively complicated peer dependency, [material-ui](https://github.com/mui-org/material-ui). It shows the power of [rollup-plugin-peer-deps-external](https://www.npmjs.com/package/rollup-plugin-peer-deps-external) which makes it a breeze to create reusable modules that include complicated material-ui subcomponents without having them bundled as a part of your module.
## License
MIT © [Travis Fischer](https://github.com/transitive-bullshit)

View file

@ -0,0 +1,50 @@
#!/usr/bin/env node
'use strict'
const meow = require('meow')
const getLibraryDefaults = require('./lib/get-library-defaults')
const createLibrary = require('./lib/create-library')
const promptLibraryInfo = require('./lib/prompt-library-info')
module.exports = async () => {
const defaults = await getLibraryDefaults()
const info = await promptLibraryInfo(defaults)
await createLibrary(info)
return info
}
if (!module.parent) {
meow(`
Usage
$ react-modern-library-cli
`)
module.exports()
.then((info) => {
console.log(`
Your module has been created at ${info.dest}.
To get started, in one tab, run:
$ cd ${info.name} && ${info.manager} start
And in another tab, run the create-react-app devserver:
$ cd ${info.name}/example && ${info.manager} start
`)
if (info.manager === 'npm') {
console.log(`
Because you're using npm, you'll need to publish a dummy version of ${info.name} first before you can "npm link" your package into the example app.
`)
}
process.exit(0)
})
.catch((err) => {
console.error(err)
process.exit(1)
})
}

View file

@ -0,0 +1,110 @@
'use strict'
const consolidate = require('consolidate')
const execa = require('execa')
const fs = require('fs')
const globby = require('globby')
const mkdirp = require('make-dir')
const ora = require('ora')
const path = require('path')
const pEachSeries = require('p-each-series')
const pkg = require('../package')
module.exports = async (info) => {
const {
dest,
manager
} = info
await mkdirp(dest)
const source = path.join(__dirname, '../template')
const files = await globby(source, {
dot: true
})
{
const promise = pEachSeries(files, async (file) => {
return module.exports.copyTemplateFile({
file,
source,
dest,
info
})
})
ora.promise(promise, `Copying template to ${dest}`)
await promise
}
{
const promise = module.exports.initPackageManager({ dest, info })
ora.promise(promise, `Running ${manager} install and ${manager} link`)
await promise
}
{
const promise = module.exports.initGitRepo({ dest })
ora.promise(promise, 'Initializing git repo')
await promise
}
}
module.exports.copyTemplateFile = async (opts) => {
const {
file,
source,
dest,
info
} = opts
const fileRelativePath = path.relative(source, file)
const destFilePath = path.join(dest, fileRelativePath)
const destFileDir = path.parse(destFilePath).dir
const content = await consolidate.handlebars(file, info)
await mkdirp(destFileDir)
fs.writeFileSync(destFilePath, content, 'utf8')
return fileRelativePath
}
module.exports.initPackageManager = async (opts) => {
const {
dest,
info
} = opts
const example = path.join(dest, 'example')
const commands = [
{
cmd: `${info.manager}`,
cwd: dest
},
{
cmd: `${info.manager} link`,
cwd: dest
}
].concat(info.manager === 'yarn' ? [
{
cmd: `${info.manager}`,
cwd: example
}
] : [ ]
)
return pEachSeries(commands, async ({ cmd, cwd }) => {
return execa.shell(cmd, { cwd })
})
}
module.exports.initGitRepo = async (opts) => {
const {
dest
} = opts
const cmd = `git init && git add . && git commit -m "init ${pkg.name}@${pkg.version}"`
return execa.shell(cmd, { cwd: dest })
}

View file

@ -0,0 +1,33 @@
'use strict'
const getGitConfigPath = require('git-config-path')
const githubUsername = require('github-username')
const parseGitConfig = require('parse-git-config')
const which = require('which')
module.exports = async () => {
const defaults = {
author: undefined,
manager: 'npm'
}
try {
const gitConfigPath = getGitConfigPath('global')
if (gitConfigPath) {
const gitConfig = parseGitConfig.sync({ path: gitConfigPath })
if (gitConfig.github && gitConfig.github.user) {
defaults.author = gitConfig.github.user
} else if (gitConfig.user && gitConfig.user.email) {
defaults.author = await githubUsername(gitConfig.user.email)
}
}
if (which.sync('yarn', { nothrow: true })) {
defaults.manager = 'yarn'
}
} catch (err) { }
return defaults
}

View file

@ -0,0 +1,54 @@
'use strict'
const inquirer = require('inquirer')
const isValidNpmName = require('is-valid-npm-name')
const path = require('path')
module.exports = async (defaults) => {
const info = await inquirer.prompt([
{
type: 'input',
name: 'name',
message: 'Package Name',
validate: (name) => {
return name && name.length && isValidNpmName(name)
}
},
{
type: 'input',
name: 'description',
message: 'Package Description',
default: ''
},
{
type: 'input',
name: 'author',
message: 'Author\'s GitHub Handle',
default: defaults.author
},
{
type: 'input',
name: 'repo',
message: 'GitHub Repo Path',
default: (info) => `${info.author}/${info.name}`
},
{
type: 'input',
name: 'license',
message: 'License',
default: 'MIT'
},
{
type: 'list',
name: 'manager',
message: 'Package Manager',
choices: [ 'npm', 'yarn' ],
default: defaults.manager
}
])
info.yarn = (info.manager === 'yarn')
info.dest = path.join(process.cwd(), info.name)
return info
}

View file

@ -0,0 +1,24 @@
{
"name": "react-modern-library-cli",
"version": "1.0.0",
"description": "CLI for easily bootstrapping modern react libraries",
"main": "index.js",
"license": "MIT",
"dependencies": {
"consolidate": "^0.15.0",
"cp-file": "^5.0.0",
"execa": "^0.9.0",
"git-config-path": "^1.0.1",
"github-username": "^4.1.0",
"globby": "^8.0.1",
"handlebars": "^4.0.11",
"inquirer": "^5.1.0",
"is-valid-npm-name": "^0.0.4",
"make-dir": "^1.2.0",
"meow": "^4.0.0",
"ora": "^2.0.0",
"p-each-series": "^1.0.0",
"parse-git-config": "^2.0.0",
"which": "^1.3.0"
}
}

View file

@ -0,0 +1,12 @@
{
"presets": [
["env", {
"modules": false
}],
"stage-0",
"react"
],
"plugins": [
"external-helpers"
]
}

View file

@ -0,0 +1,27 @@
{
"parser": "babel-eslint",
"extends": [
"standard",
"standard-react"
],
"env": {
"es6": true
},
"plugins": [
"react"
],
"parserOptions": {
"sourceType": "module",
"allowImportExportEverywhere": true
},
"rules": {
// don't force es6 functions to include space before paren
"space-before-function-paren": 0,
// allow specifying true explicitly for boolean props
"react/jsx-boolean-value": 0,
// allow imports mixed with non-import statements at top of file
"import/first": 0
}
}

View file

@ -0,0 +1,20 @@
# See https://help.github.com/ignore-files/ for more about ignoring files.
# dependencies
node_modules
# builds
build
dist
# misc
.DS_Store
.env
.env.local
.env.development.local
.env.test.local
.env.production.local
npm-debug.log*
yarn-debug.log*
yarn-error.log*

View file

@ -0,0 +1,31 @@
# {{name}}
> {{description}}
[![NPM](https://img.shields.io/npm/v/{{name}}.svg)](https://www.npmjs.com/package/{{name}}) [![JavaScript Style Guide](https://img.shields.io/badge/code_style-standard-brightgreen.svg)](https://standardjs.com)
## Install
```bash
npm install --save {{name}}
```
## Usage
```jsx
import React, { Component } from 'react'
import MyComponent from '{{name}}'
class Example extends Component {
render () {
return (
<MyComponent />
)
}
}
```
## License
{{license}} © [{{author}}](https://github.com/{{repo}})

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,20 @@
{
"name": "{{name}}-example",
"homepage": "https://{{author}}.github.io/{{name}}",
"version": "0.0.0",
"private": true,
"license": "MIT",
"dependencies": {
"prop-types": "^15.6.1",
"react": "^16.2.0",
"react-dom": "^16.2.0",
"{{name}}": "{{#if yarn}}link:..{{else}}*{{/if}}",
"react-scripts": "^1.1.1"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test --env=jsdom",
"eject": "react-scripts eject"
}
}

View file

@ -0,0 +1,20 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="theme-color" content="#000000">
<link rel="manifest" href="%PUBLIC_URL%/manifest.json">
<title>{{name}}</title>
</head>
<body>
<noscript>
You need to enable JavaScript to run this app.
</noscript>
<div id="root"></div>
</body>
</html>

View file

@ -0,0 +1,8 @@
{
"short_name": "{{name}}",
"name": "{{name}}",
"start_url": "./index.html",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}

View file

@ -0,0 +1,13 @@
import React, { Component } from 'react'
import ExampleComponent from '{{name}}'
export default class App extends Component {
render () {
return (
<div>
<ExampleComponent text='Modern React component module' />
</div>
)
}
}

View file

@ -0,0 +1,5 @@
body {
margin: 0;
padding: 0;
font-family: sans-serif;
}

View file

@ -0,0 +1,7 @@
import React from 'react'
import ReactDOM from 'react-dom'
import './index.css'
import App from './App'
ReactDOM.render(<App />, document.getElementById('root'))

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,43 @@
{
"name": "{{name}}",
"version": "1.0.0",
"description": "{{description}}",
"author": "{{author}}",
"license": "{{license}}",
"repository": "{{repo}}",
"main": "dist/index.js",
"module": "dist/index.es.js",
"jsnext:main": "dist/index.es.js",
"scripts": {
"test": "react-scripts test --env=jsdom",
"build": "rollup -c",
"start": "rollup -c -w",
"prepare": "{{manager}} run build",
"predeploy": "cd example && {{manager}} install && {{manager}} run build",
"deploy": "gh-pages -d example/build"
},
"dependencies": {},
"peerDependencies": {
"prop-types": "^15.5.4",
"react": "^15.0.0 || ^16.0.0",
"react-dom": "^15.0.0 || ^16.0.0"
},
"devDependencies": {
"babel-core": "^6.26.0",
"babel-eslint": "^8.2.1",
"babel-plugin-external-helpers": "^6.22.0",
"babel-preset-env": "^1.6.0",
"babel-preset-react": "^6.24.1",
"babel-preset-stage-0": "^6.24.1",
"gh-pages": "^1.1.0",
"rollup": "^0.54.0",
"rollup-plugin-babel": "^3.0.3",
"rollup-plugin-commonjs": "^8.2.1",
"rollup-plugin-node-resolve": "^3.0.2",
"rollup-plugin-peer-deps-external": "^2.0.0",
"rollup-plugin-postcss": "^1.1.0"
},
"files": [
"dist"
]
}

View file

@ -0,0 +1,32 @@
import babel from 'rollup-plugin-babel'
import commonjs from 'rollup-plugin-commonjs'
import external from 'rollup-plugin-peer-deps-external'
import postcss from 'rollup-plugin-postcss'
import resolve from 'rollup-plugin-node-resolve'
import pkg from './package.json'
export default {
input: 'src/index.js',
output: [
{
file: pkg.main,
format: 'cjs'
},
{
file: pkg.module,
format: 'es'
}
],
plugins: [
external(),
postcss({
modules: true
}),
babel({
exclude: 'node_modules/**'
}),
resolve(),
commonjs()
]
}

View file

@ -0,0 +1,8 @@
{
"env": {
"mocha": true
},
"globals": {
"expect": true
}
}

View file

@ -0,0 +1,26 @@
/**
* @class ExampleComponent
*/
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import styles from './styles.css'
export default class ExampleComponent extends Component {
static propTypes = {
text: PropTypes.string
}
render() {
const {
text
} = this.props
return (
<div className={styles.test}>
Example Component: {text}
</div>
)
}
}

View file

@ -0,0 +1,8 @@
/* add css styles here (optional) */
.test {
display: inline-block;
margin: 2em auto;
border: 2px solid #000;
font-size: 2em;
}

View file

@ -0,0 +1,7 @@
import ExampleComponent from './'
describe('ExampleComponent', () => {
it('is truthy', () => {
expect(ExampleComponent).toBeTruthy()
})
})

File diff suppressed because it is too large Load diff