├── .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 
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 |
--------------------------------------------------------------------------------