├── .babelrc ├── .eslintrc ├── .gitignore ├── .npmignore ├── LICENSE ├── README.md ├── config ├── jest │ ├── cssTransform.js │ ├── fileTransform.js │ └── setup.js └── polyfills.js ├── examples ├── .babelrc ├── Example.js ├── dist │ └── index.html ├── index.js ├── webpack.config.babel.js └── webpack.config.live.babel.js ├── index.js ├── jest.config.js ├── package.json ├── src ├── MyComponent │ ├── MyComponent.js │ ├── MyComponent.style.scss │ ├── MyComponent.test.js │ └── index.js └── index.js └── webpack.config.babel.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["@babel/preset-env", "@babel/preset-react"] 3 | } -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | "env": { 4 | "browser": true, 5 | "node": true, 6 | "es6": true, 7 | "jest": true 8 | }, 9 | "parserOptions": { 10 | "ecmaVersion": 7, 11 | "sourceType": "module", 12 | "ecmaFeatures": { 13 | "jsx": true 14 | } 15 | }, 16 | "plugins": [ 17 | "react" 18 | ], 19 | "extends": ["eslint:recommended", "plugin:react/recommended"], 20 | "rules": { 21 | "no-multiple-empty-lines": ["error", { "max": 2 }], 22 | "indent": ["error", 2, { "SwitchCase": 1 }], 23 | "no-console": 1 24 | } 25 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist/*.js 3 | examples/dist/*.js 4 | coverage 5 | 6 | .idea 7 | 8 | # lock files 9 | package-lock.json 10 | yarn.lock 11 | 12 | # others 13 | .DS_Store 14 | yarn-error.log 15 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | *.log 2 | npm-debug.log* 3 | yarn-error.log 4 | 5 | # Coverage directory used by tools like istanbul 6 | coverage 7 | .nyc_output 8 | 9 | # Dependency directories 10 | node_modules 11 | 12 | # npm package lock 13 | package-lock.json 14 | yarn.lock 15 | 16 | # project files 17 | src 18 | test 19 | examples 20 | CHANGELOG.md 21 | .travis.yml 22 | .editorconfig 23 | .eslintignore 24 | .eslintrc 25 | .babelrc 26 | .gitignore 27 | 28 | 29 | # lock files 30 | package-lock.json 31 | yarn.lock 32 | 33 | # others 34 | .DS_Store 35 | .idea -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2018 Dinesh Pandiyan 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # React NPM Package Boilerplate 2 | 3 | Boilerplate code for publishing a React NPM package. 4 | 5 | * Bundled with [Webpack](https://webpack.js.org/) 6 | * Develop with Hot Module Replacement [(HMR)](https://webpack.js.org/concepts/hot-module-replacement/) 7 | * Includes linting with [ESLint](http://eslint.org/) 8 | * Testing with [Jest](http://facebook.github.io/jest/). 9 | 10 | ## Usage 11 | 12 | 1. Install modules - `yarn` 13 | 14 | 2. Start example and start coding - `yarn start` 15 | 16 | 3. Run tests - `yarn test` 17 | 18 | 4. Bundle with - `yarn build` 19 | 20 | 5. To test if it works correctly in another project you can use npm `npm install -S ../react-npm-component-boilerplate` Note the relative path 21 | 22 | E.g. this folder structure 23 | 24 | ``` 25 | ./workspace/ 26 | MyProject 27 | react-npm-boilerplate 28 | ``` 29 | 30 | ## Extra 31 | 32 | Adjust your `.eslintrc` config file to your own preference. 33 | 34 | ## NPM equivalent 35 | 36 | yarn | npm 37 | ---- | --- 38 | `yarn` | `npm install` 39 | `yarn test` | `npm run test` 40 | `yarn build` | `npm run build` 41 | 42 | ## License 43 | 44 | MIT © Dinesh Pandiyan -------------------------------------------------------------------------------- /config/jest/cssTransform.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // This is a custom Jest transformer turning style imports into empty objects. 4 | // http://facebook.github.io/jest/docs/tutorial-webpack.html 5 | 6 | module.exports = { 7 | process() { 8 | return 'module.exports = {};'; 9 | }, 10 | getCacheKey() { 11 | // The output is always the same. 12 | return 'cssTransform'; 13 | }, 14 | }; 15 | -------------------------------------------------------------------------------- /config/jest/fileTransform.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const path = require('path'); 3 | 4 | module.exports = { 5 | process(src, filename, config, options) { 6 | return 'module.exports = ' + JSON.stringify(path.basename(filename)) + ';'; 7 | } 8 | }; 9 | -------------------------------------------------------------------------------- /config/jest/setup.js: -------------------------------------------------------------------------------- 1 | import Enzyme from 'enzyme'; 2 | import Adapter from 'enzyme-adapter-react-16'; 3 | // React 16 Enzyme adapter 4 | Enzyme.configure({ adapter: new Adapter() }); 5 | // Make Enzyme functions available in all test files without importing 6 | -------------------------------------------------------------------------------- /config/polyfills.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | if (typeof Promise === 'undefined') { 4 | // Rejection tracking prevents a common issue where React gets into an 5 | // inconsistent state due to an error, but it gets swallowed by a Promise, 6 | // and the user has no idea what causes React's erratic future behavior. 7 | require('promise/lib/rejection-tracking').enable(); 8 | window.Promise = require('promise/lib/es6-extensions.js'); 9 | } 10 | 11 | // fetch() polyfill for making API calls. 12 | require('whatwg-fetch'); 13 | 14 | // Object.assign() is commonly used with React. 15 | // It will use the native implementation if it's present and isn't buggy. 16 | Object.assign = require('object-assign'); 17 | -------------------------------------------------------------------------------- /examples/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["@babel/preset-env", "@babel/preset-react"] 3 | } -------------------------------------------------------------------------------- /examples/Example.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import MyComponent from '../src/index'; 4 | 5 | class Example extends React.Component { 6 | constructor(props) { 7 | super(props); 8 | } 9 | 10 | componentDidMount() {} 11 | 12 | render() { 13 | return ( 14 |
15 | 16 |
17 | ); 18 | } 19 | } 20 | 21 | export default Example; 22 | -------------------------------------------------------------------------------- /examples/dist/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Example 5 | 6 | 7 |
8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /examples/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | 4 | import Example from './Example'; 5 | import { AppContainer } from 'react-hot-loader'; 6 | // AppContainer is a necessary wrapper component for HMR 7 | 8 | const render = (Component) => { 9 | ReactDOM.render( 10 | 11 | 12 | , 13 | document.getElementById('root') 14 | ); 15 | }; 16 | 17 | render(Example); 18 | 19 | // Hot Module Replacement API 20 | if (module.hot) { 21 | module.hot.accept('./Example', () => { 22 | render(Example) 23 | }); 24 | } 25 | -------------------------------------------------------------------------------- /examples/webpack.config.babel.js: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import webpack from 'webpack'; //eslint-disable-line 3 | import CleanWebpackPlugin from 'clean-webpack-plugin'; 4 | 5 | export default () => ({ 6 | mode: 'production', 7 | entry: { 8 | index: path.join(__dirname, './index.js') 9 | }, 10 | 11 | output: { 12 | filename: 'bundle.js', 13 | path: path.resolve(__dirname, 'dist') 14 | }, 15 | 16 | module: { 17 | rules: [ 18 | { 19 | test: /.jsx?$/, 20 | exclude: /node_modules/, 21 | 22 | use: [ 23 | { 24 | loader: 'babel-loader', 25 | options: { 26 | presets: ['@babel/preset-env', '@babel/preset-react'] 27 | } 28 | } 29 | ] 30 | }, 31 | { 32 | test: /\.(scss)$/, 33 | loader: 'style-loader!css-loader!sass-loader' 34 | } 35 | ] 36 | }, 37 | 38 | resolve: { 39 | extensions: ['.js', '.jsx', '.scss'] 40 | }, 41 | 42 | plugins: [ 43 | // Clean dist folder 44 | new CleanWebpackPlugin(['./dist/build.js']) 45 | ] 46 | }); 47 | -------------------------------------------------------------------------------- /examples/webpack.config.live.babel.js: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import webpack from 'webpack'; 3 | 4 | export default () => ({ 5 | mode: 'development', 6 | entry: [ 7 | 'react-hot-loader/patch', 8 | // activate HMR for React 9 | 10 | 'webpack-dev-server/client?http://localhost:8080', 11 | // bundle the client for webpack-dev-server 12 | // and connect to the provided endpoint 13 | 14 | 'webpack/hot/only-dev-server', 15 | // bundle the client for hot reloading 16 | // only- means to only hot reload for successful updates 17 | 18 | './examples/index.js' 19 | // the entry point of our app 20 | ], 21 | 22 | output: { 23 | filename: 'bundle.js', 24 | path: path.resolve(__dirname, 'dist'), 25 | publicPath: '/' 26 | // necessary for HMR to know where to load the hot update chunks 27 | }, 28 | 29 | devtool: 'inline-source-map', 30 | 31 | devServer: { 32 | hot: true, 33 | // enable HMR on the server 34 | 35 | contentBase: path.resolve(__dirname, 'dist'), 36 | // match the output path 37 | 38 | publicPath: '/', 39 | // match the output `publicPath` 40 | 41 | stats: 'minimal' 42 | }, 43 | 44 | module: { 45 | rules: [ 46 | { 47 | test: /.jsx?$/, 48 | exclude: /node_modules/, 49 | use: [ 50 | { 51 | loader: 'babel-loader', 52 | options: { 53 | presets: ['@babel/preset-env', '@babel/preset-react'] 54 | } 55 | } 56 | ] 57 | }, 58 | { 59 | test: /\.(scss)$/, 60 | loader: 'style-loader!css-loader!sass-loader' 61 | } 62 | ] 63 | }, 64 | 65 | resolve: { 66 | extensions: ['.js', '.jsx', '.scss'] 67 | }, 68 | 69 | plugins: [new webpack.HotModuleReplacementPlugin()], 70 | optimization: { 71 | namedModules: true 72 | } 73 | }); 74 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | require('./dist/manifest'); 2 | require('./dist/vendor'); 3 | module.exports = require('./dist/index').default; -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | coverageReporters: [ 3 | 'json', 4 | 'lcov', 5 | 'text-summary' 6 | ], 7 | moduleFileExtensions: [ 8 | 'js', 9 | 'jsx', 10 | 'scss' 11 | ], 12 | modulePaths: [ 13 | './src' 14 | ], 15 | setupFiles: [ 16 | '/config/jest/setup.js' 17 | ], 18 | transform: { 19 | '^.+\\.(js|jsx)$': '/node_modules/babel-jest', 20 | '^.+\\.css$': '/config/jest/cssTransform.js', 21 | '^(?!.*\\.(js|jsx|css|json)$)': '/config/jest/fileTransform.js' 22 | }, 23 | transformIgnorePatterns: [ 24 | '[/\\\\]node_modules[/\\\\].+\\.(js|jsx)$' 25 | ] 26 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-npm-package-boilerplate", 3 | "version": "1.0.0", 4 | "description": "Boilerplate to quickstart React component development", 5 | "main": "./dist/index.js", 6 | "scripts": { 7 | "build": "webpack --config webpack.config.babel.js", 8 | "build-examples": "webpack --config examples/webpack.config.babel.js --progress", 9 | "clean": "rm -rf dist coverage", 10 | "coverage": "jest --coverage", 11 | "lint": "eslint ./src", 12 | "prepublish": "npm run clean && npm run test && npm run build", 13 | "start": "webpack-dev-server --config examples/webpack.config.live.babel.js", 14 | "test": "npm run lint && npm run coverage" 15 | }, 16 | "devDependencies": { 17 | "@babel/core": "^7.1.2", 18 | "@babel/preset-env": "^7.1.0", 19 | "@babel/preset-react": "^7.0.0", 20 | "@babel/register": "^7.0.0", 21 | "babel-eslint": "^10.0.1", 22 | "babel-jest": "^23.6.0", 23 | "babel-loader": "^8.0.4", 24 | "chai": "^4.1.2", 25 | "clean-webpack-plugin": "^0.1.16", 26 | "css-loader": "^1.0.1", 27 | "enzyme": "^3.3.0", 28 | "enzyme-adapter-react-16": "^1.1.1", 29 | "eslint": "^5.8.0", 30 | "eslint-plugin-react": "^7.4.0", 31 | "jest-cli": "^23.6.0", 32 | "node-sass": "^4.7.2", 33 | "prop-types": "^15.6.0", 34 | "react-addons-test-utils": "^15.5.1", 35 | "react": "^16.6.0", 36 | "react-dom": "^16.6.0", 37 | "react-hot-loader": "next", 38 | "react-test-renderer": "^16.0.0", 39 | "regenerator-runtime": "^0.12.1", 40 | "sass-loader": "^7.0.1", 41 | "style-loader": "^0.23.1", 42 | "webpack": "^4.9.1", 43 | "webpack-cli": "^3.1.2", 44 | "webpack-dev-server": "^3.1.4" 45 | }, 46 | "peerDependencies": { 47 | "react": ">= 16.3.0", 48 | "react-dom": ">= 16.3.0" 49 | }, 50 | "resolutions": { 51 | "babel-core": "7.0.0-bridge.0" 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/MyComponent/MyComponent.js: -------------------------------------------------------------------------------- 1 | // Demo component 2 | // this is only example component 3 | 4 | import React from 'react'; 5 | import PropTypes from 'prop-types'; 6 | import './MyComponent.style'; 7 | 8 | class MyComponent extends React.Component { 9 | constructor(props) { 10 | super(props); 11 | } 12 | 13 | componentDidMount() {} 14 | 15 | render() { 16 | const { name } = this.props; 17 | return ( 18 |
19 | One

20 | Two

21 | Three 22 |
My name is - {name}
23 |
24 | ) 25 | } 26 | } 27 | 28 | MyComponent.propTypes = { 29 | name: PropTypes.string 30 | }; 31 | 32 | export default MyComponent; 33 | -------------------------------------------------------------------------------- /src/MyComponent/MyComponent.style.scss: -------------------------------------------------------------------------------- 1 | .my-component { 2 | background-color: yellow; 3 | } 4 | -------------------------------------------------------------------------------- /src/MyComponent/MyComponent.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { shallow } from 'enzyme'; 3 | import MyComponent from 'index'; 4 | 5 | describe('MyComponent', () => { 6 | it('renders without crashing', () => { 7 | const wrapper = shallow(); 8 | expect(wrapper).toHaveLength(1); 9 | }); 10 | 11 | it('should render name from prop', () => { 12 | const wrapper = shallow(); 13 | expect(wrapper.find('.name-holder').text()).toContain('My name is - Jack'); 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /src/MyComponent/index.js: -------------------------------------------------------------------------------- 1 | import MyComponent from './MyComponent'; 2 | 3 | export default MyComponent; 4 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import MyComponent from './MyComponent'; 2 | 3 | export default MyComponent; 4 | -------------------------------------------------------------------------------- /webpack.config.babel.js: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import CleanWebpackPlugin from 'clean-webpack-plugin'; 3 | const packageJson = require('./package.json'); 4 | 5 | export default () => ({ 6 | mode: 'production', 7 | entry: { 8 | index: path.join(__dirname, 'src/index.js') 9 | }, 10 | 11 | output: { 12 | path: path.join(__dirname, 'dist'), 13 | filename: '[name].js', 14 | library: packageJson.name, 15 | libraryTarget: 'umd', 16 | globalObject: 'this' 17 | }, 18 | 19 | module: { 20 | rules: [ 21 | { 22 | test: /.jsx?$/, 23 | exclude: /node_modules/, 24 | include: path.join(__dirname, 'src'), 25 | use: [ 26 | { 27 | loader: 'babel-loader', 28 | options: { 29 | presets: ['@babel/preset-env', '@babel/preset-react'] 30 | } 31 | } 32 | ] 33 | }, 34 | { 35 | test: /\.(scss)$/, 36 | loader: 'style-loader!css-loader!sass-loader' 37 | } 38 | ] 39 | }, 40 | 41 | resolve: { 42 | extensions: ['.js', '.jsx', '.scss'] 43 | }, 44 | 45 | externals: { 46 | react: 'react', 47 | reactDOM: 'react-dom' 48 | }, 49 | 50 | plugins: [new CleanWebpackPlugin(['dist/*.*'])], 51 | optimization: { 52 | splitChunks: { 53 | name: 'vendor', 54 | minChunks: 2 55 | } 56 | } 57 | }); 58 | --------------------------------------------------------------------------------