├── .editorconfig ├── .gitignore ├── .npmignore ├── LICENSE ├── README.md ├── karma.conf.js ├── package.json ├── src ├── index.js └── style.css ├── test └── index.js ├── webpack.build.config.js ├── webpack.config.js └── webpack.universal.config.js /.editorconfig: -------------------------------------------------------------------------------- 1 | ; http://editorconfig.org 2 | 3 | root = true 4 | 5 | [*] 6 | indent_style = space 7 | indent_size = 2 8 | end_of_line = lf 9 | charset = utf-8 10 | trim_trailing_whitespace = true 11 | insert_final_newline = true 12 | 13 | [*.md] 14 | indent_size = 4 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.log 2 | /node_modules/ 3 | /dist/ 4 | /gh-pages/ 5 | /example/bundle.js 6 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | *.log 2 | /node_modules/ 3 | /example/ 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Mateusz Wijas 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 Password Strength ![build status](https://codeship.com/projects/0fd512b0-c9f6-0134-86e7-125925b29f4b/status?branch=master) 2 | 3 | A password strength indicator field using [zxcvbn](https://github.com/dropbox/zxcvbn) to calculate a password strength score. 4 | 5 | _Note: zxcvbn is a large library it's recommended you split the codebase to manage bundle size._ 6 | 7 | [Try it live!](https://reactpasswordstrength.netlify.com) 8 | 9 | ## Install in your project 10 | 11 | `npm install --save react-password-strength` 12 | 13 | _Note: react/react-dom is a peer dependency. You should be using this in a React project._ 14 | 15 | ## Run the example locally 16 | 17 | See the [example repo](https://github.com/mmw/react-password-strength-example) 18 | 19 | ## Using the tool 20 | 21 | ``` 22 | 31 | ``` 32 | 33 | ### Importing 34 | 35 | If using ES6 imports: 36 | `import ReactPasswordStrength from 'react-password-strength';` 37 | 38 | Using CommonJS require: 39 | `var ReactPasswordStrength = require('react-password-strength');` 40 | 41 | Using in a __Universal JS App__ (server-side rendering): 42 | - Import component from `react-password-strength/dist/universal` 43 | - Include default style from `react-password-strength/dist/style.css`. 44 | 45 | ### Props 46 | 47 | #### ClassName 48 | 49 | - ClassName to render with default container classes 50 | 51 | #### Style 52 | 53 | - Style object to customize container 54 | 55 | #### minLength (Default: 5) 56 | 57 | - Minimum password length acceptable for password to be considered valid 58 | 59 | #### minScore (Default: 2) 60 | 61 | - Minimum score acceptable for password to be considered valid 62 | - Scale from 0 - 4 denoting too guessable to very unguessable 63 | - See [zxcvbn](https://github.com/dropbox/zxcvbn) docs for more detail 64 | 65 | #### scoreWords (Default: ['weak', 'weak', 'okay', 'good', 'strong']) 66 | 67 | - An array denoting the words used to describe respective score values in the UI 68 | 69 | #### tooShortWord (Default: 'too short') 70 | 71 | - A string to describe when password is too short (based on minLength prop). 72 | 73 | #### changeCallback 74 | 75 | - Callback after input has changed (and score was recomputed) 76 | - React Password Strength passes two objects to the callback function: 77 | - current app state (`score`, `password`, `isValid`) 78 | - full result produced by [zxcvbn](https://github.com/dropbox/zxcvbn) including `feedback` (see docs for more properties) 79 | 80 | #### inputProps 81 | 82 | - Props to pass down to the `input` element of the component. Things like `name`, `id`, etc 83 | - Protected props: `className`, `onChange`, `ref`, `value` 84 | - Passing in `className` will append to the existing classes 85 | - The remaining props will be ignored 86 | 87 | #### defaultValue 88 | 89 | - A default value to set for the password field. If a non-empty string is provided the `changeCallback` will be called in `componentDidMount`. 90 | 91 | #### userInputs 92 | 93 | - An array of strings that zxcvbn will treat as an extra dictionary. 94 | 95 | #### namespaceClassName (Default: 'ReactPasswordStrength') 96 | 97 | - Used to control the CSS class namespace. CSS classes created by RPS will be prepended with this string. 98 | - If you change this prop you have to provide all CSS and it's recommended to import RSP from the universal JS build (`react-password-strength/dist/universal`) 99 | 100 | ### Classes 101 | 102 | _All styling is applied with CSS classes to allow custom styling and overriding. Note that if you change the `namespaceClassName` prop the below classnames will be affected._ 103 | - `ReactPasswordStrength` - namespace class and component wrapper 104 | - `is-strength-{0-4}` - modifier class indicating password strength 105 | - `ReactPasswordStrength-input` - password input field 106 | - `is-password-valid` - modifier class indicating valid password 107 | - `is-password-invalid` - modifier class indicating invalid password (only applies if password length > 0) 108 | - `ReactPasswordStrength-strength-bar` - color bar indicating password strength 109 | - `ReactPasswordStrength-strength-desc` - text indicating password strength 110 | 111 | 112 | ### Functions 113 | 114 | _Access through `ref` handle of ReactPasswordStrength._ 115 | - `clear` - reset password field to initial state 116 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration 2 | 3 | module.exports = function(config) { 4 | config.set({ 5 | autoWatch: false, 6 | colors: true, 7 | concurrency: Infinity, 8 | logLevel: config.LOG_INFO, 9 | port: 9876, 10 | singleRun: true, 11 | 12 | basePath: '', 13 | files: [ 14 | 'node_modules/es6-shim/es6-shim.js', 15 | 'test/*.js', 16 | ], 17 | exclude: [], 18 | 19 | browsers: ['PhantomJS'], 20 | frameworks: ['jasmine'], 21 | reporters: ['spec'], 22 | preprocessors: { 23 | './test/*.js': 'webpack' 24 | }, 25 | 26 | webpack: { 27 | entry: './src/index.js', 28 | module: { 29 | rules: [ 30 | { 31 | test: /\.js$/, 32 | include: [/src/, /test/], 33 | loader: 'babel-loader', 34 | query: { 35 | presets: ['react', 'es2015', 'stage-2'], 36 | }, 37 | }, { 38 | test: /\.css$/, 39 | use: ["style-loader", "css-loader"], 40 | }, 41 | ], 42 | }, 43 | }, 44 | webpackMiddleware: { 45 | state: 'errors-only' 46 | } 47 | }) 48 | } 49 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-password-strength", 3 | "version": "2.4.0", 4 | "engines": { 5 | "node": ">6.11.1" 6 | }, 7 | "description": "A password strength indicator field for use in React projects", 8 | "main": "dist/index.js", 9 | "scripts": { 10 | "build-module": "./node_modules/.bin/webpack --config=webpack.build.config.js", 11 | "build-prod": "npm run build-module -p & npm run build-universal-module -p", 12 | "build-universal-module": "./node_modules/.bin/webpack --config=webpack.universal.config.js", 13 | "build-watch": "./node_modules/.bin/webpack --config=webpack.build.config.js --watch", 14 | "example-build": "./node_modules/.bin/webpack --config=webpack.config.js", 15 | "example-publish": "git subtree push --prefix example origin gh-pages", 16 | "example": "./node_modules/.bin/webpack-dev-server --content-base example/", 17 | "start": "npm run example & npm run build-watch", 18 | "test": "./node_modules/karma/bin/karma start" 19 | }, 20 | "repository": { 21 | "type": "git", 22 | "url": "git+https://github.com/mmw/react-password-strength.git" 23 | }, 24 | "keywords": [ 25 | "react" 26 | ], 27 | "author": "Mateusz Wijas", 28 | "license": "MIT", 29 | "bugs": { 30 | "url": "https://github.com/mmw/react-password-strength/issues" 31 | }, 32 | "homepage": "https://github.com/mmw/react-password-strength#readme", 33 | "devDependencies": { 34 | "babel-core": "^6.11.4", 35 | "babel-loader": "^6.2.4", 36 | "babel-preset-es2015": "^6.9.0", 37 | "babel-preset-react": "^6.11.1", 38 | "babel-preset-stage-2": "^6.24.1", 39 | "css-loader": "^0.23.1", 40 | "es6-shim": "^0.35.3", 41 | "extract-text-webpack-plugin": "^3.0.2", 42 | "jasmine": "^2.8.0", 43 | "karma": "^1.7.1", 44 | "karma-jasmine": "^1.1.0", 45 | "karma-phantomjs-launcher": "^1.0.4", 46 | "karma-spec-reporter": "0.0.31", 47 | "karma-webpack": "^2.0.6", 48 | "react": "^16.1.1", 49 | "react-dom": "^16.1.1", 50 | "style-loader": "^0.13.1", 51 | "webpack": "^3.8.1", 52 | "webpack-dev-server": "^2.9.4" 53 | }, 54 | "peerDependencies": { 55 | "react": "^16.0.0", 56 | "react-dom": "^16.0.0" 57 | }, 58 | "dependencies": { 59 | "prop-types": "^15.6.0", 60 | "zxcvbn": "^4.3.0" 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import './style.css'; 2 | 3 | import React, { Component } from 'react'; 4 | import zxcvbn from 'zxcvbn'; 5 | import PropTypes from 'prop-types'; 6 | 7 | const isTooShort = (password, minLength) => password.length < minLength; 8 | 9 | export default class ReactPasswordStrength extends Component { 10 | static propTypes = { 11 | changeCallback: PropTypes.func, 12 | className: PropTypes.string, 13 | defaultValue: PropTypes.string, 14 | inputProps: PropTypes.object, 15 | minLength: PropTypes.number, 16 | minScore: PropTypes.number, 17 | namespaceClassName: PropTypes.string, 18 | scoreWords: PropTypes.array, 19 | style: PropTypes.object, 20 | tooShortWord: PropTypes.string, 21 | userInputs: PropTypes.array, 22 | } 23 | 24 | static defaultProps = { 25 | changeCallback: null, 26 | className: '', 27 | defaultValue: '', 28 | minLength: 5, 29 | minScore: 2, 30 | namespaceClassName: 'ReactPasswordStrength', 31 | scoreWords: ['weak', 'weak', 'okay', 'good', 'strong'], 32 | tooShortWord: 'too short', 33 | userInputs: [], 34 | } 35 | 36 | state = { 37 | score: 0, 38 | isValid: false, 39 | password: '', 40 | } 41 | 42 | componentDidMount() { 43 | const { defaultValue } = this.props; 44 | 45 | if (defaultValue.length > 0) { 46 | this.setState({ password: defaultValue }, this.handleChange); 47 | } 48 | } 49 | 50 | clear = () => { 51 | const { changeCallback } = this.props; 52 | 53 | this.setState({ 54 | score: 0, 55 | isValid: false, 56 | password: '', 57 | }, () => { 58 | this.reactPasswordStrengthInput.value = ''; 59 | 60 | if (changeCallback !== null) { 61 | changeCallback(this.state); 62 | } 63 | }); 64 | } 65 | 66 | handleChange = () => { 67 | const { changeCallback, minScore, userInputs, minLength } = this.props; 68 | const password = this.reactPasswordStrengthInput.value; 69 | 70 | let score = 0; 71 | let result = null; 72 | 73 | // always sets a zero score when min length requirement is not met 74 | // avoids unnecessary zxcvbn computations (CPU intensive) 75 | if (isTooShort(password, minLength) === false) { 76 | result = zxcvbn(password, userInputs); 77 | score = result.score; 78 | } 79 | 80 | this.setState({ 81 | isValid: score >= minScore, 82 | password, 83 | score, 84 | }, () => { 85 | if (changeCallback !== null) { 86 | changeCallback(this.state, result); 87 | } 88 | }); 89 | } 90 | 91 | render() { 92 | const { score, password, isValid } = this.state; 93 | const { 94 | className, 95 | inputProps, 96 | minLength, 97 | namespaceClassName, 98 | scoreWords, 99 | style, 100 | tooShortWord, 101 | } = this.props; 102 | 103 | const inputClasses = [ `${namespaceClassName}-input` ]; 104 | const wrapperClasses = [ 105 | namespaceClassName, 106 | className ? className : '', 107 | password.length > 0 ? `is-strength-${score}` : '', 108 | ]; 109 | const strengthDesc = ( 110 | isTooShort(password, minLength) 111 | ? tooShortWord 112 | : scoreWords[score] 113 | ); 114 | 115 | if (isValid === true) { 116 | inputClasses.push('is-password-valid'); 117 | } else if (password.length > 0) { 118 | inputClasses.push('is-password-invalid'); 119 | } 120 | 121 | if (inputProps && inputProps.className) { 122 | inputClasses.push(inputProps.className); 123 | } 124 | 125 | return ( 126 |
127 | this.reactPasswordStrengthInput = ref} 133 | value={password} 134 | /> 135 | 136 |
137 | {strengthDesc} 138 |
139 | ); 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /src/style.css: -------------------------------------------------------------------------------- 1 | .ReactPasswordStrength { 2 | border: 1px solid #c6c6c6; 3 | box-sizing: border-box; 4 | color: #090809; 5 | font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; 6 | font-size: 18px; 7 | line-height: 1; 8 | position: relative; 9 | } 10 | 11 | .ReactPasswordStrength-input { 12 | border: none; 13 | box-sizing: border-box; 14 | font-size: 18px; 15 | padding: 14px 0 12px 14px; 16 | width: calc(85% - 28px); 17 | } 18 | 19 | .ReactPasswordStrength-input:not(:focus).is-password-invalid { color: #D1462F; } 20 | .ReactPasswordStrength-input:focus { outline: none; } 21 | 22 | .ReactPasswordStrength-strength-desc { 23 | color: transparent; 24 | font-style: italic; 25 | padding: 14px 12px; 26 | position: absolute; top: 1px; right: 0; 27 | text-align: right; 28 | transition: color 250ms ease-in-out; 29 | width: 15%; 30 | } 31 | 32 | .ReactPasswordStrength.is-strength-0 .ReactPasswordStrength-strength-desc { color: #D1462F; } 33 | .ReactPasswordStrength.is-strength-1 .ReactPasswordStrength-strength-desc { color: #D1462F; } 34 | .ReactPasswordStrength.is-strength-2 .ReactPasswordStrength-strength-desc { color: #57B8FF; } 35 | .ReactPasswordStrength.is-strength-3 .ReactPasswordStrength-strength-desc { color: #57B8FF; } 36 | .ReactPasswordStrength.is-strength-4 .ReactPasswordStrength-strength-desc { color: #2FBF71; } 37 | 38 | .ReactPasswordStrength-strength-bar { 39 | box-sizing: border-box; 40 | height: 2px; 41 | position: relative; top: 1px; right: 1px; 42 | transition: width 300ms ease-out; 43 | width: 0; 44 | } 45 | 46 | .ReactPasswordStrength.is-strength-0 .ReactPasswordStrength-strength-bar { 47 | background: #D1462F; 48 | width: 20%; 49 | } 50 | 51 | .ReactPasswordStrength.is-strength-1 .ReactPasswordStrength-strength-bar { 52 | background: #D1462F; 53 | width: 40%; 54 | } 55 | 56 | .ReactPasswordStrength.is-strength-2 .ReactPasswordStrength-strength-bar { 57 | background: #57B8FF; 58 | width: 60%; 59 | } 60 | 61 | .ReactPasswordStrength.is-strength-3 .ReactPasswordStrength-strength-bar { 62 | background: #57B8FF; 63 | width: 80%; 64 | } 65 | 66 | .ReactPasswordStrength.is-strength-4 .ReactPasswordStrength-strength-bar { 67 | background: #2FBF71; 68 | width: calc(100% + 2px); 69 | } 70 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { 3 | findRenderedDOMComponentWithClass, 4 | findRenderedDOMComponentWithTag, 5 | renderIntoDocument, 6 | Simulate, 7 | } from 'react-dom/test-utils'; 8 | 9 | import PassStrength from '../src/index'; 10 | 11 | describe('ReactPasswordStrength', () => { 12 | it('is rendered', () => { 13 | const render = renderIntoDocument(); 14 | const result = findRenderedDOMComponentWithClass(render, 'ReactPasswordStrength'); 15 | const strengthBar = findRenderedDOMComponentWithClass(render, 'ReactPasswordStrength-strength-bar'); 16 | const description = findRenderedDOMComponentWithClass(render, 'ReactPasswordStrength-strength-desc'); 17 | 18 | expect(result).toBeDefined(); 19 | expect(strengthBar.className).toBe('ReactPasswordStrength-strength-bar'); 20 | expect(description.className).toBe('ReactPasswordStrength-strength-desc'); 21 | }); 22 | 23 | it('passes inputProps to input elem', () => { 24 | const inputProps = { 25 | className: 'test', 26 | value: 'value-test', // should be rejected 27 | }; 28 | 29 | const render = renderIntoDocument(); 30 | const input = findRenderedDOMComponentWithTag(render, 'input'); 31 | 32 | expect(input.className).toBe('ReactPasswordStrength-input test'); 33 | expect(input.value).not.toBe('value-test'); 34 | }); 35 | 36 | it('accepts className prop', () => { 37 | const render = renderIntoDocument(); 38 | const result = findRenderedDOMComponentWithClass(render, 'ReactPasswordStrength'); 39 | 40 | expect(result.className).toContain("testClassName"); 41 | expect(result.className).toContain("ReactPasswordStrength"); 42 | }); 43 | 44 | it('accepts style prop', () => { 45 | const render = renderIntoDocument(); 46 | const result = findRenderedDOMComponentWithClass(render, 'ReactPasswordStrength'); 47 | 48 | expect(result.style.margin).toBe("10px"); 49 | }); 50 | }); 51 | 52 | describe('ReactPasswordStrength Events', () => { 53 | it('handle basic input', () => { 54 | const result = renderIntoDocument(); 55 | let input = findRenderedDOMComponentWithClass(result, 'ReactPasswordStrength-input'); 56 | 57 | input.value = '123'; 58 | 59 | Simulate.change(input); 60 | 61 | expect(result.state.password).toBe('123'); 62 | expect(result.state.score).toBe(0); 63 | expect(result.state.isValid).toBe(false); 64 | }); 65 | 66 | it('handle strong passwords', () => { 67 | const scoreWords = ['not ok', 'not ok', 'not ok', 'not ok', 'ok']; 68 | const result = renderIntoDocument(); 69 | const scoreDesc = findRenderedDOMComponentWithClass(result, 'ReactPasswordStrength-strength-desc'); 70 | let input = findRenderedDOMComponentWithClass(result, 'ReactPasswordStrength-input'); 71 | 72 | input.value = 'abcd1234ABCDxymv123'; 73 | 74 | Simulate.change(input); 75 | 76 | expect(result.state.password).toBe('abcd1234ABCDxymv123'); 77 | expect(result.state.score).toBe(4); 78 | expect(result.state.isValid).toBe(true); 79 | 80 | expect(scoreDesc.textContent).toBe('ok'); 81 | }); 82 | 83 | it('call changeCallback', () => { 84 | const callBack = jasmine.createSpy('spy'); 85 | const result = renderIntoDocument(); 86 | let input = findRenderedDOMComponentWithClass(result, 'ReactPasswordStrength-input'); 87 | 88 | input.value = '123'; 89 | 90 | Simulate.change(input); 91 | 92 | expect(callBack).toHaveBeenCalledWith({ 93 | score: 0, 94 | isValid: false, 95 | password: '123', 96 | }, null); 97 | }); 98 | 99 | it('reset state on clear', () => { 100 | const result = renderIntoDocument(); 101 | let input = findRenderedDOMComponentWithClass(result, 'ReactPasswordStrength-input'); 102 | 103 | input.value = 'abcd1234ABCDxymv123'; 104 | 105 | Simulate.change(input); 106 | 107 | expect(result.state.password).toBe('abcd1234ABCDxymv123'); 108 | expect(result.state.score).toBe(4); 109 | expect(result.state.isValid).toBe(true); 110 | 111 | result.clear(); 112 | 113 | expect(result.state.password).toBe(''); 114 | expect(result.state.score).toBe(0); 115 | expect(result.state.isValid).toBe(false); 116 | }); 117 | 118 | it('validate based on minScore and minLength', () => { 119 | const result = renderIntoDocument(); 120 | let input = findRenderedDOMComponentWithClass(result, 'ReactPasswordStrength-input'); 121 | 122 | input.value = '123'; 123 | 124 | Simulate.change(input); 125 | 126 | expect(result.state.password).toBe('123'); 127 | expect(result.state.score).toBe(0); 128 | expect(result.state.isValid).toBe(true); 129 | }) 130 | 131 | it('invalidates too short passwords', () => { 132 | const result = renderIntoDocument(); 133 | let input = findRenderedDOMComponentWithClass(result, 'ReactPasswordStrength-input'); 134 | 135 | // this normally gives a good zxcvbn score but must fail 136 | // regardless of that because it does not meet the min length 137 | input.value = '~=0o%'; 138 | 139 | Simulate.change(input); 140 | 141 | expect(result.state.password).toBe('~=0o%'); 142 | expect(result.state.score).toBe(0); 143 | expect(result.state.isValid).toBe(false); 144 | }) 145 | 146 | it('adds strings in userInputs to zxcvbn dictionary', () => { 147 | const knownKeyword = 'longwordthatiscommon'; 148 | const result = renderIntoDocument(); 149 | let input = findRenderedDOMComponentWithClass(result, 'ReactPasswordStrength-input'); 150 | 151 | input.value = knownKeyword; 152 | 153 | Simulate.change(input); 154 | 155 | expect(result.state.password).toBe(knownKeyword); 156 | expect(result.state.score).toBe(0); 157 | expect(result.state.isValid).toBe(false); 158 | }) 159 | }); 160 | -------------------------------------------------------------------------------- /webpack.build.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | entry: './src/index.js', 3 | output: { 4 | path: __dirname, 5 | filename: './dist/index.js', 6 | libraryTarget: 'umd', 7 | library: 'ReactPasswordStrength', 8 | }, 9 | module: { 10 | rules: [ 11 | { 12 | test: /\.js$/, 13 | loader: 'babel-loader', 14 | include: /src/, 15 | query: { 16 | presets: ['react', 'es2015', 'stage-2'], 17 | }, 18 | }, { 19 | test: /\.css$/, 20 | use: [ 'style-loader', 'css-loader' ], 21 | }, 22 | ], 23 | }, 24 | externals: { 25 | 'react': 'react' 26 | }, 27 | resolve: { 28 | extensions: ['.js'], 29 | }, 30 | }; 31 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | entry: './example/index.js', 3 | output: { 4 | filename: 'bundle.js', 5 | path: __dirname + '/example', 6 | }, 7 | module: { 8 | rules: [{ 9 | test: /\.js$/, 10 | loader: 'babel-loader', 11 | include: /example/, 12 | query: { 13 | presets: ['react', 'es2015', 'stage-2'], 14 | }, 15 | }], 16 | }, 17 | resolve: { 18 | extensions: ['.js'], 19 | }, 20 | } 21 | -------------------------------------------------------------------------------- /webpack.universal.config.js: -------------------------------------------------------------------------------- 1 | var ExtractTextPlugin = require('extract-text-webpack-plugin'); 2 | 3 | module.exports = { 4 | entry: './src/index.js', 5 | output: { 6 | path: __dirname, 7 | filename: './dist/universal.js', 8 | libraryTarget: 'umd', 9 | library: 'ReactPasswordStrength', 10 | }, 11 | module: { 12 | rules: [ 13 | { 14 | test: /\.js$/, 15 | include: /src/, 16 | loader: 'babel-loader', 17 | query: { 18 | presets: ['react', 'es2015', 'stage-2'], 19 | }, 20 | }, { 21 | test: /\.css$/, 22 | use: ExtractTextPlugin.extract({ 23 | fallback: "style-loader", 24 | use: "css-loader", 25 | }), 26 | }, 27 | ], 28 | }, 29 | plugins: [ 30 | new ExtractTextPlugin('./dist/style.css'), 31 | ], 32 | externals: { 33 | 'react': 'react', 34 | }, 35 | }; 36 | --------------------------------------------------------------------------------