├── app ├── vendors │ └── .gitkeep ├── assets │ ├── scss │ │ └── main.scss │ └── images │ │ └── react_logo_512x512.png ├── index.html ├── config │ └── Root.js ├── components │ └── App.js └── main.js ├── config └── jest │ ├── shim.js │ └── assetsTransformer.js ├── .babelrc ├── .github └── ISSUE_TEMPLATE.md ├── .travis.yml ├── tests └── components │ └── App.test.js ├── .eslintrc ├── LICENSE ├── .gitignore ├── README.md ├── package.json ├── webpack.production.config.js └── webpack.config.js /app/vendors/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/assets/scss/main.scss: -------------------------------------------------------------------------------- 1 | $color: #262828; 2 | 3 | body { 4 | color: $color; 5 | } -------------------------------------------------------------------------------- /app/assets/images/react_logo_512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KleoPetroff/react-webpack-boilerplate/HEAD/app/assets/images/react_logo_512x512.png -------------------------------------------------------------------------------- /config/jest/shim.js: -------------------------------------------------------------------------------- 1 | // A solution for React 16 complaining of missing rAF. 2 | 3 | global.requestAnimationFrame = function(callback) { 4 | setTimeout(callback, 0); 5 | }; 6 | -------------------------------------------------------------------------------- /config/jest/assetsTransformer.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | module.exports = { 4 | process(src, filename, config, options) { 5 | return 'module.exports = ' + JSON.stringify(path.basename(filename)) + ';'; 6 | }, 7 | }; 8 | -------------------------------------------------------------------------------- /app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ReactJS Boilerplate 6 | 7 | 8 | 9 | 10 |
11 | 12 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "@babel/preset-react", 4 | [ "@babel/preset-env", { 5 | "targets": { 6 | "browsers": "> 2%" 7 | } 8 | }] 9 | ], 10 | 11 | "plugins": [ 12 | "react-hot-loader/babel" 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## The problem 2 | 3 | Briefly describe the issue you are experiencing (or the change you are proposing). Tell what you are trying to do and what happened instead. 4 | 5 | ## Details 6 | 7 | Describe in more detail the problem you have been experiencing, if necessary. If you are proposing a change, describe the intensions and gains of your propose. 8 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: node_js 3 | cache: 4 | directories: 5 | - node_modules 6 | branches: 7 | only: 8 | - master 9 | - /^greenkeeper/.*$/ 10 | notifications: 11 | email: false 12 | node_js: 13 | - "node" 14 | - "lts/*" 15 | before_install: 16 | - npm i -g npm 17 | before_script: 18 | - npm prune 19 | script: 20 | - npm run test 21 | install: npm install -------------------------------------------------------------------------------- /app/config/Root.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { BrowserRouter as Router, Route, Switch } from 'react-router-dom'; 3 | import App from '../components/App'; 4 | 5 | const Root = () => { 6 | return ( 7 | 8 | 9 | 10 | 11 | 12 | ); 13 | }; 14 | 15 | export default Root; 16 | 17 | -------------------------------------------------------------------------------- /app/components/App.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import img from '../assets/images/react_logo_512x512.png'; 3 | 4 | const App = () => { 5 | return ( 6 |
7 |

Hello ReactJS

8 | React Logo 16 |
17 | ); 18 | }; 19 | 20 | export default App; 21 | -------------------------------------------------------------------------------- /app/main.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import { AppContainer } from 'react-hot-loader'; 4 | 5 | import Root from './config/Root'; 6 | 7 | const render = (Component) => { 8 | ReactDOM.render( 9 | 10 | 11 | , 12 | document.getElementById('root'), 13 | ); 14 | }; 15 | 16 | render(Root); 17 | 18 | if (module.hot) { 19 | module.hot.accept('./config/Root', () => { 20 | const newApp = require('./config/Root').default; 21 | render(newApp); 22 | }); 23 | } 24 | -------------------------------------------------------------------------------- /tests/components/App.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { shallow, configure } from 'enzyme'; 3 | import Adapter from 'enzyme-adapter-react-16'; 4 | 5 | import App from '../../app/components/App'; 6 | 7 | configure({ adapter: new Adapter() }); 8 | 9 | describe('App Component', () => { 10 | let wrapper; 11 | 12 | beforeEach(() => { 13 | wrapper = shallow(); 14 | }); 15 | 16 | it('should exist', () => { 17 | expect(wrapper).toBeTruthy(); 18 | }); 19 | 20 | it('should have one heading', () => { 21 | expect(wrapper.find('#heading').type()).toEqual('h2'); 22 | }); 23 | }); -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "airbnb", 3 | "parser": "babel-eslint", 4 | "parserOptions": { 5 | "ecmaVersion": 6, 6 | "ecmaFeatures": { 7 | "jsx": true 8 | } 9 | }, 10 | "env": { 11 | "browser": true, 12 | "node": true, 13 | "mocha": true, 14 | "es6": true 15 | }, 16 | "rules": { 17 | "space-before-function-paren": "off", 18 | "react/prefer-stateless-function": "warn", 19 | "react/jsx-one-expression-per-line": "off", 20 | "react/jsx-filename-extension": [1, { "extensions": [".js", ".jsx"] }], 21 | "linebreak-style": "off", 22 | "global-require": "off", 23 | "semi": "warn", 24 | "arrow-body-style": "off", 25 | "no-multiple-empty-lines": ["warn", {"max": 1}] 26 | } 27 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Kliment Petrov 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 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | ### JetBrains template 3 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio 4 | 5 | *.iml 6 | 7 | ## Directory-based project format: 8 | .idea/ 9 | # if you remove the above rule, at least ignore the following: 10 | 11 | # User-specific stuff: 12 | # .idea/workspace.xml 13 | # .idea/tasks.xml 14 | # .idea/dictionaries 15 | 16 | # Sensitive or high-churn files: 17 | # .idea/dataSources.ids 18 | # .idea/dataSources.xml 19 | # .idea/sqlDataSources.xml 20 | # .idea/dynamic.xml 21 | # .idea/uiDesigner.xml 22 | 23 | # Gradle: 24 | # .idea/gradle.xml 25 | # .idea/libraries 26 | 27 | # Mongo Explorer plugin: 28 | # .idea/mongoSettings.xml 29 | 30 | ## File-based project format: 31 | *.ipr 32 | *.iws 33 | 34 | ## Plugin-specific files: 35 | 36 | # IntelliJ 37 | /out/ 38 | 39 | # mpeltonen/sbt-idea plugin 40 | .idea_modules/ 41 | 42 | # JIRA plugin 43 | atlassian-ide-plugin.xml 44 | 45 | # Crashlytics plugin (for Android Studio and IntelliJ) 46 | com_crashlytics_export_strings.xml 47 | crashlytics.properties 48 | crashlytics-build.properties 49 | 50 | # NodeJS modules folder 51 | node_modules 52 | 53 | # npm debug log 54 | npm-debug.log 55 | 56 | # vs code 57 | .vscode 58 | 59 | # coverage 60 | 61 | /coverage 62 | 63 | # Dist folder 64 | dist/ 65 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ES6 React boilerplate using Webpack 2 | 3 | [![Travis](https://img.shields.io/travis/KleoPetroff/react-webpack-boilerplate/master.svg?style=flat-square)](https://github.com/KleoPetroff/react-webpack-boilerplate) [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)](http://makeapullrequest.com) 4 | 5 | :warning: :warning: :warning: 6 | 7 | As of 29.12.2018, the project is **DEPRECATED** and no further development is planned. If you are looking for an alternative, check [create-react-app](https://github.com/facebook/create-react-app). 8 | 9 | Simple and optimized React boilerplate. It includes: 10 | 11 | - [x] React 16.5.2 12 | - [x] ECMAScript 6+ and JSX support 13 | - [x] React Router v4 14 | - [x] Component testing using [Enzyme](https://github.com/airbnb/enzyme) and [Jest](https://facebook.github.io/jest) 15 | - [x] Code Coverage 16 | - [x] Latest Webpack (v.4.16.5), Babel 7 and Webpack Dev Server (v.4.19.1) with Scope Hoisting enabled 17 | - [x] Hot Module Replacement using [react-hot-loader](https://github.com/gaearon/react-hot-loader) 18 | - [x] ES6 linting with continuous linting on file change 19 | - [x] SASS support 20 | - [x] Separate CSS stylesheets generation 21 | - [x] Automatic HTML generation 22 | - [x] Production Config 23 | - [x] Custom Babel Preset with Decorators, Class Properties, Rest/Spread operator support 24 | - [x] Export Separate Vendor Files 25 | 26 | ## Starting the dev server 27 | 28 | Make sure you have the latest Stable or LTS version of Node.js installed. 29 | 30 | 1. `git clone https://github.com/KleoPetroff/react-webpack-boilerplate.git` 31 | 2. Run `npm install` or `yarn install` 32 | 3. Start the dev server using `npm start` 33 | 3. Open [http://localhost:8080](http://localhost:8080) 34 | 35 | ## Available Commands 36 | 37 | - `npm start` - start the dev server 38 | - `npm clean` - delete the dist folder 39 | - `npm run production` - create a production ready build in `dist` folder 40 | - `npm run lint` - execute an eslint check 41 | - `npm test` - run all tests 42 | - `npm run test:watch` - run all tests in watch mode 43 | - `npm run coverage` - generate code coverage report in the `coverage` folder 44 | 45 | ## Vendor Exporting 46 | 47 | You can export specific vendors in separate files and load them. All vendors should be included in `app/vendors` and will be exported in a `vendors` folder under `dist`. The main idea is to serve independent JavaScript and CSS libraries, though currently all file formats are supported. 48 | 49 | ! Don't forget to add the vendors in `app/index.html` and `build/index.html`. 50 | 51 | ## Code Coverage 52 | 53 | The project is using the Jest Code Coverage tool. The reports are generated by running `npm run coverage`. All configurations are located in `package.json`, inside the `jest` object. 54 | 55 | The coverage report consists of an HTML reporter, which can be viewed in the browser and some helper coverage files like the coverage json and xml file. 56 | 57 | ## Production code 58 | 59 | Run `npm run production`. The production-ready code will be located under `dist` folder. 60 | 61 | ## Licence 62 | 63 | _react-webpack-boilerplate_ is available under MIT. 64 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-webpack-boilerplate", 3 | "version": "2.1.0", 4 | "description": "Minimalistic ES6 React boilerplate", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "webpack-dev-server", 8 | "clean": "rimraf dist", 9 | "production": "cross-env npm run clean && webpack --config webpack.production.config.js --progress --profile --colors", 10 | "lint": "eslint ./app/**/**.js", 11 | "test": "jest", 12 | "test:watch": "jest --watch", 13 | "coverage": "jest --coverage", 14 | "precommit": "cross-env npm run lint && npm test", 15 | "prepush": "cross-env npm run lint && npm test", 16 | "webpack:dev": "webpack --colors", 17 | "webpack:prod": "webpack --config webpack.production.config.js --colors", 18 | "heroku-postbuild": "npm run production" 19 | }, 20 | "author": "Kliment Petrov ", 21 | "license": "MIT", 22 | "jest": { 23 | "setupFiles": [ 24 | "/config/jest/shim.js" 25 | ], 26 | "collectCoverageFrom": [ 27 | "app/**/*.{js,jsx,ts,tsx}" 28 | ], 29 | "moduleNameMapper": { 30 | "\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": "/config/jest/assetsTransformer.js", 31 | "\\.(css|less)$": "/config/jest/assetsTransformer.js" 32 | }, 33 | "testMatch": [ 34 | "**/?(*.)(spec|test).js?(x)" 35 | ], 36 | "transform": { 37 | "\\.js$": "babel-jest" 38 | } 39 | }, 40 | "dependencies": { 41 | "cross-env": "5.2.0", 42 | "react": "16.6.1", 43 | "react-dom": "16.6.1", 44 | "react-hot-loader": "4.6.3", 45 | "react-router-dom": "4.3.0" 46 | }, 47 | "devDependencies": { 48 | "@babel/cli": "7.2.3", 49 | "@babel/core": "^7.2.2", 50 | "@babel/preset-env": "7.2.3", 51 | "@babel/preset-react": "7.0.0", 52 | "@babel/preset-stage-2": "7.0.0", 53 | "@babel/register": "7.0.0", 54 | "babel-core": "^7.0.0-bridge.0", 55 | "babel-eslint": "9.0.0", 56 | "babel-jest": "^23.6.0", 57 | "babel-loader": "8.0.3", 58 | "babel-preset-react-hmre": "1.1.1", 59 | "copy-webpack-plugin": "4.6.0", 60 | "css-hot-loader": "1.4.3", 61 | "css-loader": "2.1.0", 62 | "enzyme": "3.7.0", 63 | "enzyme-adapter-react-16": "1.7.1", 64 | "eslint": "5.0.0", 65 | "eslint-config-airbnb": "17.1.0", 66 | "eslint-loader": "2.1.1", 67 | "eslint-plugin-import": "2.13.0", 68 | "eslint-plugin-jsx-a11y": "6.1.2", 69 | "eslint-plugin-react": "7.11.1", 70 | "extract-text-webpack-plugin": "4.0.0-beta.0", 71 | "file-loader": "3.0.1", 72 | "html-webpack-plugin": "3.2.0", 73 | "husky": "1.3.1", 74 | "jest": "23.6.0", 75 | "node-sass": "4.11.0", 76 | "open-browser-webpack-plugin": "0.0.5", 77 | "react-addons-test-utils": "15.6.2", 78 | "react-test-renderer": "16.6.0", 79 | "regenerator-runtime": "0.13.0", 80 | "rimraf": "2.6.2", 81 | "sass-loader": "7.1.0", 82 | "style-loader": "0.23.1", 83 | "uglifyjs-webpack-plugin": "2.1.1", 84 | "url-loader": "1.1.2", 85 | "webpack": "4.27.0", 86 | "webpack-cli": "3.1.2", 87 | "url-loader": "1.1.2", 88 | "webpack": "4.27.0", 89 | "webpack-cli": "3.1.2", 90 | "webpack-dev-server": "3.1.10" 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /webpack.production.config.js: -------------------------------------------------------------------------------- 1 | const { resolve } = require('path'); 2 | const webpack = require('webpack'); 3 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 4 | const CopyWebpackPlugin = require('copy-webpack-plugin'); 5 | const ExtractTextPlugin = require('extract-text-webpack-plugin'); 6 | const UglifyJsPlugin = require('uglifyjs-webpack-plugin'); 7 | 8 | const config = { 9 | stats: { 10 | maxModules: 0 11 | }, 12 | mode: 'production', 13 | devtool: 'cheap-module-source-map', 14 | 15 | entry: [ 16 | './main.js', 17 | './assets/scss/main.scss', 18 | ], 19 | 20 | context: resolve(__dirname, 'app'), 21 | 22 | output: { 23 | filename: '[name].[chunkhash].js', 24 | chunkFilename: '[name].[chunkhash].js', 25 | path: resolve(__dirname, 'dist'), 26 | publicPath: '', 27 | }, 28 | 29 | plugins: [ 30 | new webpack.optimize.ModuleConcatenationPlugin(), 31 | new HtmlWebpackPlugin({ 32 | template: `${__dirname}/app/index.html`, 33 | filename: 'index.html', 34 | inject: 'body', 35 | }), 36 | new webpack.optimize.OccurrenceOrderPlugin(), 37 | new webpack.LoaderOptionsPlugin({ 38 | minimize: true, 39 | debug: false, 40 | }), 41 | new webpack.DefinePlugin({ 'process.env': { NODE_ENV: JSON.stringify('production') } }), 42 | new ExtractTextPlugin({ filename: './styles/style.css', disable: false, allChunks: true }), 43 | new CopyWebpackPlugin([{ from: './vendors', to: 'vendors' }]), 44 | ], 45 | 46 | optimization: { 47 | runtimeChunk: false, 48 | splitChunks: { 49 | cacheGroups: { 50 | commons: { 51 | test: /[\\/]node_modules[\\/]/, 52 | name: 'vendors', 53 | chunks: 'all', 54 | }, 55 | }, 56 | }, 57 | minimizer: [ 58 | new UglifyJsPlugin({ 59 | cache: true, 60 | parallel: true, 61 | sourceMap: true 62 | }) 63 | ] 64 | }, 65 | 66 | resolve: { 67 | extensions: ['.js', '.jsx'], 68 | }, 69 | 70 | module: { 71 | rules: [ 72 | { 73 | test: /\.jsx?$/, 74 | exclude: /node_modules/, 75 | loader: 'babel-loader', 76 | }, 77 | { 78 | test: /\.scss$/, 79 | exclude: /node_modules/, 80 | use: ExtractTextPlugin.extract({ 81 | fallback: 'style-loader', 82 | use: [ 83 | 'css-loader', 84 | { loader: 'sass-loader', query: { sourceMap: false } }, 85 | ], 86 | publicPath: '../' 87 | }), 88 | }, 89 | { 90 | test: /\.(png|jpg|gif)$/, 91 | use: [ 92 | { 93 | loader: 'url-loader', 94 | options: { 95 | limit: 8192, 96 | mimetype: 'image/png', 97 | name: 'images/[name].[ext]', 98 | } 99 | } 100 | ], 101 | }, 102 | { 103 | test: /\.eot(\?v=\d+.\d+.\d+)?$/, 104 | use: [ 105 | { 106 | loader: 'file-loader', 107 | options: { 108 | name: 'fonts/[name].[ext]' 109 | } 110 | } 111 | ], 112 | }, 113 | { 114 | test: /\.woff(2)?(\?v=[0-9]\.[0-9]\.[0-9])?$/, 115 | use: [ 116 | { 117 | loader: 'url-loader', 118 | options: { 119 | limit: 8192, 120 | mimetype: 'application/font-woff', 121 | name: 'fonts/[name].[ext]', 122 | } 123 | } 124 | ], 125 | }, 126 | { 127 | test: /\.[ot]tf(\?v=\d+.\d+.\d+)?$/, 128 | use: [ 129 | { 130 | loader: 'url-loader', 131 | options: { 132 | limit: 8192, 133 | mimetype: 'application/octet-stream', 134 | name: 'fonts/[name].[ext]', 135 | } 136 | } 137 | ], 138 | }, 139 | { 140 | test: /\.svg(\?v=\d+\.\d+\.\d+)?$/, 141 | use: [ 142 | { 143 | loader: 'url-loader', 144 | options: { 145 | limit: 8192, 146 | mimetype: 'image/svg+xml', 147 | name: 'images/[name].[ext]', 148 | } 149 | } 150 | ], 151 | }, 152 | ] 153 | }, 154 | }; 155 | 156 | module.exports = config; 157 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const { resolve } = require('path'); 2 | 3 | const webpack = require('webpack'); 4 | const ExtractTextPlugin = require('extract-text-webpack-plugin'); 5 | const CopyWebpackPlugin = require('copy-webpack-plugin'); 6 | const OpenBrowserPlugin = require('open-browser-webpack-plugin'); 7 | const UglifyJsPlugin = require('uglifyjs-webpack-plugin'); 8 | 9 | const config = { 10 | stats: { 11 | maxModules: 0 12 | }, 13 | mode: 'development', 14 | devtool: 'cheap-module-eval-source-map', 15 | 16 | entry: [ 17 | 'react-hot-loader/patch', 18 | 'webpack-dev-server/client?http://localhost:8080', 19 | 'webpack/hot/only-dev-server', 20 | './main.js', 21 | './assets/scss/main.scss', 22 | ], 23 | 24 | output: { 25 | filename: 'bundle.js', 26 | path: resolve(__dirname, 'dist'), 27 | publicPath: '', 28 | }, 29 | 30 | context: resolve(__dirname, 'app'), 31 | 32 | devServer: { 33 | hot: true, 34 | contentBase: resolve(__dirname, 'build'), 35 | historyApiFallback: true, 36 | publicPath: '/' 37 | }, 38 | 39 | resolve: { 40 | extensions: ['.js', '.jsx'], 41 | }, 42 | 43 | module: { 44 | rules: [ 45 | { 46 | enforce: "pre", 47 | test: /\.jsx?$/, 48 | exclude: /node_modules/, 49 | loader: "eslint-loader" 50 | }, 51 | { 52 | test: /\.jsx?$/, 53 | loaders: [ 54 | 'babel-loader', 55 | ], 56 | exclude: /node_modules/, 57 | }, 58 | { 59 | test: /\.scss$/, 60 | exclude: /node_modules/, 61 | use: ['css-hot-loader'].concat(ExtractTextPlugin.extract({ 62 | fallback: 'style-loader', 63 | use: [ 64 | 'css-loader', 65 | { 66 | loader: 'sass-loader', 67 | query: { 68 | sourceMap: false, 69 | }, 70 | }, 71 | ], 72 | publicPath: '../' 73 | })), 74 | }, 75 | { 76 | test: /\.(png|jpg|gif)$/, 77 | use: [ 78 | { 79 | loader: 'url-loader', 80 | options: { 81 | limit: 8192, 82 | mimetype: 'image/png', 83 | name: 'images/[name].[ext]', 84 | } 85 | } 86 | ], 87 | }, 88 | { 89 | test: /\.eot(\?v=\d+.\d+.\d+)?$/, 90 | use: [ 91 | { 92 | loader: 'file-loader', 93 | options: { 94 | name: 'fonts/[name].[ext]' 95 | } 96 | } 97 | ], 98 | }, 99 | { 100 | test: /\.woff(2)?(\?v=[0-9]\.[0-9]\.[0-9])?$/, 101 | use: [ 102 | { 103 | loader: 'url-loader', 104 | options: { 105 | limit: 8192, 106 | mimetype: 'application/font-woff', 107 | name: 'fonts/[name].[ext]', 108 | } 109 | } 110 | ], 111 | }, 112 | { 113 | test: /\.[ot]tf(\?v=\d+.\d+.\d+)?$/, 114 | use: [ 115 | { 116 | loader: 'url-loader', 117 | options: { 118 | limit: 8192, 119 | mimetype: 'application/octet-stream', 120 | name: 'fonts/[name].[ext]', 121 | } 122 | } 123 | ], 124 | }, 125 | { 126 | test: /\.svg(\?v=\d+\.\d+\.\d+)?$/, 127 | use: [ 128 | { 129 | loader: 'url-loader', 130 | options: { 131 | limit: 8192, 132 | mimetype: 'image/svg+xml', 133 | name: 'images/[name].[ext]', 134 | } 135 | } 136 | ], 137 | }, 138 | ] 139 | }, 140 | 141 | plugins: [ 142 | new webpack.NamedModulesPlugin(), 143 | new webpack.LoaderOptionsPlugin({ 144 | test: /\.jsx?$/, 145 | options: { 146 | eslint: { 147 | configFile: resolve(__dirname, '.eslintrc'), 148 | cache: false, 149 | } 150 | }, 151 | }), 152 | new webpack.optimize.ModuleConcatenationPlugin(), 153 | new ExtractTextPlugin({ filename: './styles/style.css', disable: false, allChunks: true }), 154 | new CopyWebpackPlugin([{ from: 'vendors', to: 'vendors' }]), 155 | new OpenBrowserPlugin({ url: 'http://localhost:8080' }), 156 | new webpack.HotModuleReplacementPlugin(), 157 | ] 158 | }; 159 | 160 | module.exports = config; 161 | --------------------------------------------------------------------------------