diff --git a/content/docs/nav.yml b/content/docs/nav.yml index 71ef81aa7c7..cbd111e1478 100644 --- a/content/docs/nav.yml +++ b/content/docs/nav.yml @@ -60,6 +60,8 @@ title: Integrating with Other Libraries - id: accessibility title: Accessibility + - id: publishing-a-component + title: Publishing a Component - title: Reference items: - id: react-api @@ -94,4 +96,4 @@ - id: implementation-notes title: Implementation Notes - id: design-principles - title: Design Principles \ No newline at end of file + title: Design Principles diff --git a/content/docs/publishing-a-component.md b/content/docs/publishing-a-component.md new file mode 100644 index 00000000000..ec66f534e02 --- /dev/null +++ b/content/docs/publishing-a-component.md @@ -0,0 +1,416 @@ +--- +id: publishing-a-component +title: Publishing a Component +permalink: docs/publishing-a-component.html +--- + +A [React component](/docs/components-and-props.html) can be published so that it is reusable and shareable. This guide will show you the necessary steps and offer a few recommendations to publish your own React component. + +### Assumptions + +The instructions in this guide assume you have [Node](https://nodejs.org/en/download/), [Yarn](https://yarnpkg.com/lang/en/docs/install/), and [Git](https://git-scm.com/downloads) installed. +You should know [how to create a repository on GitHub and commit changes to it](https://help.github.com/articles/create-a-repo/). +You should also have basic proficiency in Node, Yarn, Git, GitHub, JavaScript and React. + +## Create + +We will use and expand upon the `Welcome` React component that was introduced in the [Components and Props](/docs/components-and-props.html) quick start section of the docs. + +First, we will create a new repository for our project on GitHub by going to [https://github.com/new](https://github.com/new). We will enter a repository name for our component, a description, select `Initialize this repository with a README`, choose `Node` from the `Add .gitignore` dropdown menu and choose a license from the `Add a license` drop down menu. We will use the `MIT` license for this component. Click `Create Repository` to finish. + +Next, we will copy the location of our newly created remote repository and clone it to our local system: +```bash +git clone git@github.com:mateoholman/react-welcome-user.git +``` + +Then, we will create a `package.json` file. We will be using [Yarn](https://yarnpkg.com/lang/en/docs/install/) as our package manager and will initialize our project with a `package.json` by executing the following command: + +```bash +yarn init +``` + +This command walks through several steps to create our package.json file. Yarn also has several [other configuration commands and options](https://yarnpkg.com/en/docs/cli/init). + +After we create our package.json file, we will save the current changes to our project: + +```bash +git add -A +``` +Stage the changes to be committed to our remote repository: +```bash +git commit -m "Initial commit" +``` + + and make our initial commit to GitHub. + ```bash + git push -u origin master + ``` + + Next, we will install our primary dependencies, React and prop-types. + ```bash + yarn add react prop-types + ``` + + We will be using ES6 in our code, but will want to transpile it to ES5 for broader use in other projects. Let's create a `/src` for our ES6 source and a `/dist` directory to hold our transpiled files. After creating the two directories, we will create `/src/index.js` to hold the source code of our React component: + + ``` javascript + import React from 'react'; + import PropTypes from 'prop-types'; + + const Welcome = props => { + return

Welcome {props.user}!

; + }; + + Welcome.defaultProps = { + user: 'User' + }; + + Welcome.propTypes = { + user: PropTypes.string.isRequired + }; + + export default Welcome; + ``` + + Our component is a stateless functional component that will render a welcome message to whatever user is passed through props. Please refer to the [Components and Props](https://facebook.github.io/react/docs/components-and-props.html) section of the docs for more information on stateless functional components. + +It is a best practice not to force our users to pass any props to our component in order to use it. The defaultProps will be used to ensure props.user has a value if none is specified. We will use the `prop-types` package to typecheck any props being passed to our component. Please refer to the [Typechecking with propTypes](/docs/typechecking-with-proptypes.html#proptypes) section of the docs for more information about Typechecking and default props. + +Now we are ready to move on to the build. + +## Build + +We used several [ES6](https://developer.mozilla.org/en-US/docs/Web/JavaScript/New_in_JavaScript/ECMAScript_2015_support_in_Mozilla) features in our component code. Although ES6 is becoming widely supported, it is best practice to compile our code to [ES5](https://developer.mozilla.org/en-US/docs/Web/JavaScript/New_in_JavaScript/ECMAScript_5_support_in_Mozilla) for broader support. We will be using [Babel](https://babeljs.io/), several Babel [presets](http://babeljs.io/docs/plugins), [babel-loader](https://github.com/babel/babel-loader) and [Webpack](https://webpack.js.org/) to transpile and bundle our code for publishing. + +First, we will install all the dependencies that will allow us to compile our code to ES5: + +``` bash +yarn add --dev babel-core babel-preset-env babel-preset-react babel-loader webpack path +``` + +Next, we will create a `.babelrc` file to store our presets: + +``` +{ + "presets": ["env", "react"] +} +``` + +Then, we will create a `webpack.config.js` file to store our webpack configuration options: + +``` javascript +const webpack = require('webpack'); +const path = require('path'); + +const BUILD_DIR = path.resolve(__dirname, 'dist'); +const APP_DIR = path.resolve(__dirname, 'src'); + +module.exports = { + entry: APP_DIR + '/index.js', + output: { + path: BUILD_DIR, + filename: 'index.js', + libraryTarget: 'umd', + library: 'Welcome' + }, + module: { + rules: [ + { + loader: 'babel-loader', + test: /\.js$/, + exclude: /node_modules/, + include: APP_DIR, + options: { + presets: ['env', 'react'] + } + } + ] + } +}; +``` + +For more information on the webpack configuration, please refer to the [webpack configuration docs](https://webpack.js.org/concepts/configuration/). + +Next, we will add a "build" script to transpile our code with webpack. We will add the following to the "scripts" section of our `package.json` file: + +``` javascript +"build": "webpack" +``` + +Now that we have setup our build, let's generate the build file with the following command: + +``` bash +yarn build +``` + +After running the build, our transpiled code should be available in the `/dist` directory. + +## Test + +Testing is not required, but is another best practice if you are publishing your component for others to use on their projects. + +We will use [Jest](https://facebook.github.io/jest/), [Enzyme](http://airbnb.io/enzyme/), [babel-jest](https://github.com/facebook/jest/tree/master/packages/babel-jest), [enzyme-adapter-react-16](), [react-dom](), and [react-test-renderer](https://www.npmjs.com/package/react-test-renderer) for test coverage. + +We will add our new dependencies to our `package.json` with the following command: + +```bash +yarn add --dev jest enzyme babel-jest enzyme-adapter-react-16 react-dom react-test-renderer +``` + +Next, we will add a "test" script and configure Jest to run with Enzyme within our `package.json`: +``` javascript +"scripts": { + "test": "jest", + "build": "webpack" +}, +"jest": { + "setupTestFrameworkScriptFile": "./enzymeSetup.js" +} +``` + +Then we will create our [Enzyme setup file](http://airbnb.io/enzyme/docs/installation/), which is required when working with React 16. We will create a new file called `enzymeSetup.js` and add the following code: + +``` javascript +import { configure } from 'enzyme'; +import Adapter from 'enzyme-adapter-react-16'; + +configure({ adapter: new Adapter() }); +``` + +Next, we will add some minimal test coverage to both our source and distribution component. Create an `index.test.js` file and add the following code: + +``` javascript +import React from 'react'; +import { shallow, render } from 'enzyme'; +import Welcome from './src/index'; +import WelcomeDist from './dist/index'; + +describe('Welcome', () => { + describe('when rendered', () => { + it('should not throw an error', () => { + const wrapper = shallow(); + expect(wrapper.length).toBe(1); + }); + + it('should render "Welcome User!" when no props are passed', () => { + expect(render().text()).toBe('Welcome User!'); + }); + + it('should render "Welcome Matt!" when "Matt" is passed as a user prop', () => { + expect(render().text()).toBe('Welcome Matt!'); + }); + }); +}); + +describe('WelcomeDist', () => { + describe('when rendered', () => { + it('should not throw an error', () => { + const wrapper = shallow(); + expect(wrapper.length).toBe(1); + }); + + it('should render "Welcome User!" when no props are passed', () => { + expect(render().text()).toBe('Welcome User!'); + }); + + it('should render "Welcome Matt!" when "Matt" is passed as a user prop', () => { + expect(render().text()).toBe( + 'Welcome Matt!' + ); + }); + }); +}); +``` + +We can then run our test from the command line with: +```bash +yarn test +``` + +## Document + +Although not required, it is a best practice to add documentation to our React component because we are publishing it. At the very least we should edit our current `Readme.md` file and add some wording that informs other potential users what the Welcome component is and how they can use it in their projects. After editing our `Readme.md` we will be ready to publish. + +## Publish + +Let's finally publish our React component to the npm registry! + +First, we will need to sign up on [npmjs](https://npmjs.com/signup). + +After we have set up our account on the npm registry, we will use npm to locally store our credentials: + +```bash +npm login +``` + +It will prompt us for our `username`, `password` and `e-mail`. + +Before we publish, we need to make sure that `package.json` lists our "main" as `./dist/index.js` so that we are publishing the transpiled component. We also want to check the npm registry to make sure that someone else has not already published a package with the same name. + +Next, we will issue the command to publish our React component. Once a package is published, you can never modify that specific version, so take care before publishing! + +```bash +npm publish +``` +Our React component has now been published under the name we specified in our `package.json` which in the case for our component is `react-welcome-user`. + +We can go to https://npmjs.com/package/react-welcome-user to make sure the package was successfully published. + +Now we can simply install our package and consume it in any other react project by importing it. + +``` javascript +import Welcome from 'react-welcome-user'; +``` + +Congratulations, you have published a React component! + +## Final Results + +Our final `package.json`: +``` javascript +{ + "name": "react-welcome-user", + "version": "1.0.4", + "description": "A simple React welcome component", + "main": "./dist/index.js", + "repository": "git@github.com:mateoholman/react-welcome-user.git", + "author": "Matthew Holman ", + "license": "MIT", + "dependencies": { + "prop-types": "^15.6.0", + "react": "^16.0.0" + }, + "devDependencies": { + "babel-core": "^6.26.0", + "babel-jest": "^21.2.0", + "babel-loader": "^7.1.2", + "babel-preset-env": "^1.6.1", + "babel-preset-react": "^6.24.1", + "enzyme": "^3.1.0", + "enzyme-adapter-react-16": "^1.0.2", + "jest": "^21.2.1", + "path": "^0.12.7", + "react-dom": "^16.0.0", + "react-test-renderer": "^16.0.0", + "webpack": "^3.8.1" + }, + "jest": { + "setupTestFrameworkScriptFile": "./enzymeSetup.js" + }, + "scripts": { + "test": "jest", + "build": "webpack" + } +} +``` +Our final `webpack.config.js`: + +``` javascript +const webpack = require('webpack'); +const path = require('path'); + +const BUILD_DIR = path.resolve(__dirname, 'dist'); +const APP_DIR = path.resolve(__dirname, 'src'); + +module.exports = { + entry: APP_DIR + '/index.js', + output: { + path: BUILD_DIR, + filename: 'index.js', + libraryTarget: 'umd', + library: 'Welcome' + }, + module: { + rules: [ + { + loader: 'babel-loader', + test: /\.js$/, + exclude: /node_modules/, + include: APP_DIR, + options: { + presets: ['env', 'react'] + } + } + ] + } +}; +``` + +Our final `/src/index.js`: +``` javascript +import React from 'react'; +import PropTypes from 'prop-types'; + +const Welcome = props => { + return

Welcome {props.user}!

; +}; + +Welcome.defaultProps = { + user: 'User' +}; + +Welcome.propTypes = { + user: PropTypes.string.isRequired +}; + +export default Welcome; +``` +Our final `index.test.js`: +``` javascript +import React from 'react'; +import { shallow, render } from 'enzyme'; +import Welcome from './src/index'; +import WelcomeDist from './dist/index'; + +describe('Welcome', () => { + describe('when rendered', () => { + it('should not throw an error', () => { + const wrapper = shallow(); + expect(wrapper.length).toBe(1); + }); + + it('should render "Welcome User!" when no props are passed', () => { + expect(render().text()).toBe('Welcome User!'); + }); + + it('should render "Welcome Matt!" when "Matt" is passed as a user prop', () => { + expect(render().text()).toBe('Welcome Matt!'); + }); + }); +}); + +describe('WelcomeDist', () => { + describe('when rendered', () => { + it('should not throw an error', () => { + const wrapper = shallow(); + expect(wrapper.length).toBe(1); + }); + + it('should render "Welcome User!" when no props are passed', () => { + expect(render().text()).toBe('Welcome User!'); + }); + + it('should render "Welcome Matt!" when "Matt" is passed as a user prop', () => { + expect(render().text()).toBe( + 'Welcome Matt!' + ); + }); + }); +}); +``` + +Our final `enzymeSetup.js`: +``` javascript +import { configure } from 'enzyme'; +import Adapter from 'enzyme-adapter-react-16'; + +configure({ adapter: new Adapter() }); +``` + +Our final `.babelrc`: + +``` javascript +{ + "presets": ["env", "react"] +} +```