├── .eslintignore ├── .eslintrc.json ├── .gitignore ├── babel.config.js ├── index.html ├── index.js ├── jest.config.js ├── package-lock.json ├── package.json ├── readme.md ├── setup_tests ├── __mocks__ │ ├── fileMock.js │ └── styleMock.js └── setupTests.js ├── src ├── App.jsx └── components │ ├── Container.jsx │ ├── Container.less │ └── __tests__ │ ├── Container.test.jsx │ └── __snapshots__ │ └── Container.test.jsx.snap └── webpack.config.js /.eslintignore: -------------------------------------------------------------------------------- 1 | webpack.config.js 2 | jest.config.js 3 | babel.config.js 4 | setupTests.js -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "airbnb", 3 | "parser": "babel-eslint", 4 | "env": { 5 | "es6": true, 6 | "browser": true, 7 | "node": true, 8 | "jest": true 9 | }, 10 | "parserOptions": { 11 | "ecmaVersion": 2018, 12 | "sourceType": "module", 13 | "ecmaFeatures": { 14 | "jsx": true, 15 | "spread": true 16 | } 17 | }, 18 | "rules": { 19 | "semi": 2, 20 | "import/no-dynamic-require": 0, 21 | "global-require": 0, 22 | "react/destructuring-assignment": 0, 23 | "react/button-has-type": 0, 24 | "react/jsx-one-expression-per-line": 0, 25 | "react/prop-types": 0, 26 | "react/prefer-stateless-function": 0, 27 | "object-curly-newline": 0, 28 | "jsx-quotes": 0, 29 | "react/self-closing-comp": 0, 30 | "import/no-cycle": 0, 31 | "import/named": 0, 32 | "consistent-return": 0, 33 | "react/sort-comp": 0, 34 | "no-shadow": 0, 35 | "operator-linebreak": 0, 36 | "react/no-array-index-key": 0, 37 | "jsx-a11y/anchor-is-valid": 0, 38 | "react/jsx-no-bind": 0, 39 | "class-methods-use-this": 0, 40 | "jsx-a11y/click-events-have-key-events": 0, 41 | "jsx-a11y/no-static-element-interactions": 0, 42 | "arrow-body-style": 0, 43 | "prefer-template": 0, 44 | "arrow-parens": 0, 45 | "react/no-multi-comp": 0, 46 | "no-unused-expressions": 0, 47 | "react/jsx-wrap-multilines": 0, 48 | "import/prefer-default-export": 0, 49 | "import/no-named-as-default": 0, 50 | "no-use-before-define": 0, 51 | "no-restricted-globals": 0, 52 | "react/jsx-max-props-per-line": 0, 53 | "react/jsx-closing-bracket-location": 0, 54 | "react/require-default-props": 0, 55 | "no-underscore-dangle": 0, 56 | "quotes": 0, 57 | "func-names": 0, 58 | "jsx-a11y/label-has-for": 0, 59 | "jsx-a11y/label-has-associated-control": 0, 60 | "require-yield": 0, 61 | "react/no-render-return-value": 0, 62 | "react/jsx-closing-tag-location": 0, 63 | "react/no-children-prop": 0, 64 | "react/jsx-first-prop-new-line": 0 65 | } 66 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | .vscode 4 | bundle 5 | coverage -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | ["@babel/preset-env", { "targets": { "chrome": "60" } }], 4 | "@babel/preset-react" 5 | ], 6 | plugins: [ 7 | '@babel/plugin-proposal-class-properties' 8 | ] 9 | } -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Barebones React App 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | import './src/App'; 2 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | // For a detailed explanation regarding each configuration property, visit: 2 | // https://jestjs.io/docs/en/configuration.html 3 | 4 | module.exports = { 5 | // All imported modules in your tests should be mocked automatically 6 | // automock: false, 7 | 8 | // Stop running tests after `n` failures 9 | // bail: 0, 10 | 11 | // Respect "browser" field in package.json when resolving modules 12 | // browser: false, 13 | 14 | // The directory where Jest should store its cached dependency information 15 | // cacheDirectory: "/private/var/folders/lt/x47ypyy92w30y6qmfp78dvyr0000gn/T/jest_dx", 16 | 17 | // Automatically clear mock calls and instances between every test 18 | // clearMocks: false, 19 | 20 | // Indicates whether the coverage information should be collected while executing the test 21 | // collectCoverage: false, 22 | 23 | // An array of glob patterns indicating a set of files for which coverage information should be collected 24 | // collectCoverageFrom: null, 25 | 26 | // The directory where Jest should output its coverage files 27 | // coverageDirectory: null, 28 | 29 | // An array of regexp pattern strings used to skip coverage collection 30 | // coveragePathIgnorePatterns: [ 31 | // "/node_modules/" 32 | // ], 33 | 34 | // A list of reporter names that Jest uses when writing coverage reports 35 | // coverageReporters: [ 36 | // "json", 37 | // "text", 38 | // "lcov", 39 | // "clover" 40 | // ], 41 | 42 | // An object that configures minimum threshold enforcement for coverage results 43 | // coverageThreshold: null, 44 | 45 | // A path to a custom dependency extractor 46 | // dependencyExtractor: null, 47 | 48 | // Make calling deprecated APIs throw helpful error messages 49 | // errorOnDeprecated: false, 50 | 51 | // Force coverage collection from ignored files usin a array of glob patterns 52 | // forceCoverageMatch: [], 53 | 54 | // A path to a module which exports an async function that is triggered once before all test suites 55 | // globalSetup: null, 56 | 57 | // A path to a module which exports an async function that is triggered once after all test suites 58 | // globalTeardown: null, 59 | 60 | // A set of global variables that need to be available in all test environments 61 | // globals: {}, 62 | 63 | // An array of directory names to be searched recursively up from the requiring module's location 64 | // moduleDirectories: [ 65 | // "node_modules" 66 | // ], 67 | 68 | // An array of file extensions your modules use 69 | // moduleFileExtensions: [ 70 | // "js", 71 | // "json", 72 | // "jsx", 73 | // "ts", 74 | // "tsx", 75 | // "node" 76 | // ], 77 | 78 | // A map from regular expressions to module names that allow to stub out resources with a single module 79 | moduleNameMapper: { 80 | "\\.(css|less|sass|scss)$": "/setup_tests/__mocks__/styleMock.js", 81 | "\\.(gif|ttf|eot|svg)$": "/setup_tests/__mocks__/fileMock.js" 82 | }, 83 | 84 | // An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader 85 | // modulePathIgnorePatterns: [], 86 | 87 | // Activates notifications for test results 88 | // notify: false, 89 | 90 | // An enum that specifies notification mode. Requires { notify: true } 91 | // notifyMode: "failure-change", 92 | 93 | // A preset that is used as a base for Jest's configuration 94 | // preset: null, 95 | 96 | // Run tests from one or more projects 97 | // projects: null, 98 | 99 | // Use this configuration option to add custom reporters to Jest 100 | // reporters: undefined, 101 | 102 | // Automatically reset mock state between every test 103 | // resetMocks: false, 104 | 105 | // Reset the module registry before running each individual test 106 | // resetModules: false, 107 | 108 | // A path to a custom resolver 109 | // resolver: null, 110 | 111 | // Automatically restore mock state between every test 112 | // restoreMocks: false, 113 | 114 | // The root directory that Jest should scan for tests and modules within 115 | // rootDir: null, 116 | 117 | // A list of paths to directories that Jest should use to search for files in 118 | // roots: [ 119 | // "" 120 | // ], 121 | 122 | // Allows you to use a custom runner instead of Jest's default test runner 123 | // runner: "jest-runner", 124 | 125 | // The paths to modules that run some code to configure or set up the testing environment before each test 126 | // setupFiles: [], 127 | 128 | // A list of paths to modules that run some code to configure or set up the testing framework before each test 129 | setupFilesAfterEnv: [ 130 | 'setup_tests/setupTests.js', 131 | ], 132 | 133 | // A list of paths to snapshot serializer modules Jest should use for snapshot testing 134 | // snapshotSerializers: [], 135 | 136 | // The test environment that will be used for testing 137 | // testEnvironment: "jest-environment-jsdom", 138 | 139 | // Options that will be passed to the testEnvironment 140 | // testEnvironmentOptions: {}, 141 | 142 | // Adds a location field to test results 143 | // testLocationInResults: false, 144 | 145 | // The glob patterns Jest uses to detect test files 146 | // testMatch: [ 147 | // "**/__tests__/**/*.[jt]s?(x)", 148 | // "**/?(*.)+(spec|test).[tj]s?(x)" 149 | // ], 150 | 151 | // An array of regexp pattern strings that are matched against all test paths, matched tests are skipped 152 | // testPathIgnorePatterns: [ 153 | // "/node_modules/" 154 | // ], 155 | 156 | // The regexp pattern or array of patterns that Jest uses to detect test files 157 | // testRegex: [], 158 | 159 | // This option allows the use of a custom results processor 160 | // testResultsProcessor: null, 161 | 162 | // This option allows use of a custom test runner 163 | // testRunner: "jasmine2", 164 | 165 | // This option sets the URL for the jsdom environment. It is reflected in properties such as location.href 166 | // testURL: "http://localhost", 167 | 168 | // Setting this value to "fake" allows the use of fake timers for functions such as "setTimeout" 169 | // timers: "real", 170 | 171 | // A map from regular expressions to paths to transformers 172 | // transform: null, 173 | 174 | // An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation 175 | // transformIgnorePatterns: [ 176 | // "/node_modules/" 177 | // ], 178 | 179 | // An array of regexp pattern strings that are matched against all modules before the module loader will automatically return a mock for them 180 | // unmockedModulePathPatterns: undefined, 181 | 182 | // Indicates whether each individual test should be reported during the run 183 | // verbose: null, 184 | 185 | // An array of regexp patterns that are matched against all source file paths before re-running tests in watch mode 186 | // watchPathIgnorePatterns: [], 187 | 188 | // Whether to use watchman for file crawling 189 | // watchman: true, 190 | }; 191 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-setup", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "scripts": { 6 | "server": "live-server", 7 | "webpack": "webpack --watch", 8 | "test": "jest --watchAll" 9 | }, 10 | "keywords": [], 11 | "author": "", 12 | "license": "MIT", 13 | "devDependencies": { 14 | "@babel/core": "^7.6.4", 15 | "@babel/plugin-proposal-class-properties": "^7.5.5", 16 | "@babel/preset-env": "^7.6.3", 17 | "@babel/preset-react": "^7.6.3", 18 | "babel-eslint": "^10.0.3", 19 | "babel-loader": "^8.0.6", 20 | "css-loader": "^3.2.0", 21 | "enzyme": "^3.10.0", 22 | "enzyme-adapter-react-16": "^1.15.1", 23 | "eslint": "^6.5.1", 24 | "eslint-config-airbnb": "^18.0.1", 25 | "eslint-plugin-import": "^2.18.2", 26 | "eslint-plugin-jsx-a11y": "^6.2.3", 27 | "eslint-plugin-react": "^7.16.0", 28 | "jest": "^24.9.0", 29 | "less": "^3.10.3", 30 | "less-loader": "^5.0.0", 31 | "live-server": "^1.2.1", 32 | "style-loader": "^1.0.0", 33 | "webpack": "^4.41.1", 34 | "webpack-bundle-analyzer": "^3.5.2", 35 | "webpack-cli": "^3.3.9" 36 | }, 37 | "dependencies": { 38 | "react": "^16.10.2", 39 | "react-dom": "^16.10.2" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # How to Run this App 2 | * Clone repository. 3 | * Run `npm install`. 4 | * Run `npm run webpack` to bundle the app with a watcher. 5 | * Run `npm run server` to launch the development web server. 6 | * Run `npm test` to run tests. 7 | 8 | # Objectives 9 | * Learn to set up a React project from scratch. 10 | * Learn about Eslint, Jest, Enzyme, Babel and Webpack. 11 | * Appreciate the things Create React App does for us. 12 | * Feel warm and fuzzy that we don't _need_ CRA to start a project. 13 | 14 | # Requirements 15 | We need `node` version 8.1+ & `npm` version 5.2+. VSCode's `eslint` extension is recommended. Commands are run inside the project folder. 16 | 17 | # Steps 18 | ## 1. Project Structure 19 | * We need the following dev-dependencies: 20 | * live-server 21 | * Run `npm add -D live-server`. 22 | * Create `index.html` and `index.js` files at the root of the project. 23 | * Inside `index.html`, scaffold a basic html document and create a script tag pointing to a non-existing (for now!) bundle file: 24 | * `````` 25 | * Inside `index.html`, create a div element with an `id` of `target`, which we'll use to attach our React application to the DOM. 26 | * Initialize the project by running `npm init -y`. 27 | * Open the generated `package.json` and edit the `scripts` property: 28 | ```javascript 29 | // etc 30 | "scripts": { 31 | "server": "live-server", 32 | "webpack": "webpack --watch", 33 | "test": "jest --watchAll" 34 | }, 35 | // etc 36 | ``` 37 | 38 | ## 2. Configuring the Linter 39 | * **Talking points:** the importance of linting. The eslint webpage. 40 | * Initialize using the command: `npx eslint --init`. You'll be presented with choices. 41 | * Choose `Use a popular style guide` option. 42 | * Choose `Airbnb`, with `React`, and `JSON` format. 43 | * Allow required dependencies to be installed with npm. 44 | * Edit the automatically created `.eslintrc.json` file to look like this (intellisense helps): 45 | ```javascript 46 | { 47 | "extends": "airbnb", 48 | "env": { 49 | "es6": true, 50 | "browser": true, 51 | "node": true, 52 | "jest": true 53 | }, 54 | "parserOptions": { 55 | "ecmaVersion": 2018, 56 | "sourceType": "module", 57 | "ecmaFeatures": { 58 | "jsx": true, 59 | "spread": true 60 | } 61 | }, 62 | "rules": { 63 | "semi": 2 64 | } 65 | } 66 | ``` 67 | 68 | ## 3. Configuring Testing with Jest 69 | * **Talking points:** the importance of testing. Untested code is legacy code. 70 | * Install `jest` using the command `npm add -D jest`. 71 | * Run `npx jest --init` to get an automatically generated jest configuration file. You will be asked some questions. 72 | * When asked, choose environment to use: `jsdom` (browser). 73 | * Create a top-level folder named `setup_tests`. 74 | * Inside `setup_tests`, create a `__mocks__` folder. 75 | * Inside `__mocks__` folder, create `fileMock.js` and `styleMock.js` files. 76 | * Inside `fileMock.js` place the following code: 77 | ```javascript 78 | module.exports = 'test-file-stub'; 79 | ``` 80 | * Inside `styleMock.js` place the following code: 81 | ```javascript 82 | module.exports = {}; 83 | ``` 84 | * Find the `jest.config.js` file at the root of the project. This was generated automatically. 85 | * Uncomment `moduleNameMapper` and add the following value: 86 | ```javascript 87 | moduleNameMapper: { 88 | "\\.(css|less|sass|scss)$": "/setup_tests/__mocks__/styleMock.js", 89 | "\\.(gif|ttf|eot|svg)$": "/setup_tests/__mocks__/fileMock.js" 90 | }, 91 | ``` 92 | 93 | ## 4. Configuring UI Testing with Enzyme 94 | * We need the following dev-dependencies: 95 | * enzyme 96 | * enzyme-adapter-react-16 97 | * Run `npm add -D enzyme enzyme-adapter-react-16`. 98 | * Create a `setupTests.js` file inside the `setup_tests` top-level folder. 99 | * Add the following code to `setupTests.js`: 100 | ```javascript 101 | import { configure } from 'enzyme'; 102 | import Adapter from 'enzyme-adapter-react-16'; 103 | 104 | configure({ adapter: new Adapter() }); 105 | ``` 106 | * Find the `jest.config.js` file at the root of the project. 107 | * Uncomment `setupFilesAfterEnv` and add the following value: 108 | ```javascript 109 | setupFilesAfterEnv: [ 110 | 'setup_tests/setupTests.js', 111 | ], 112 | ``` 113 | 114 | ## 5. Configuring Babel 115 | * **Talking points:** transcompiling, the babel website. 116 | * We need the following dev-dependencies: 117 | * @babel/core 118 | * @babel/preset-env 119 | * @babel/preset-react 120 | * Run `npm add -D @babel/core @babel/preset-env @babel/preset-react`. 121 | * Create a `babel.config.js` file at the root of the project. 122 | * Put this configuration inside `babel.config.js`: 123 | ```javascript 124 | module.exports = { 125 | presets: [ 126 | ["@babel/preset-env", { "targets": { "chrome": "60" } }], 127 | "@babel/preset-react" 128 | ] 129 | } 130 | ``` 131 | * `@babel/preset-env` will configure itself according to the desired targets. Edit browser name and version to get more or less aggressive transpiling of the code. Without this `{ "targets": etc etc } ` configuration object, all the JavaScript code will get transpiled down to ES5, which might be overkill depending on project requirements. 132 | 133 | ## 6. Configuring Webpack 134 | * **Talking points:** concatenation, minification, uglification, the problem of fetching many assets over http. 135 | * We need the following dev-dependencies: 136 | * webpack 137 | * webpack-cli 138 | * babel-loader 139 | * Run `npm add -D webpack webpack-cli babel-loader`. 140 | * Create a `webpack.config.js` file at the root of the project. 141 | * Edit `webpack.config.js` file to add a mode of operation, entry and output points, and extensions: 142 | ```javascript 143 | var path = require('path'); 144 | 145 | module.exports = { 146 | // 'production' mode would minify and uglify the code, and use React's production code 147 | mode: 'development', 148 | // entry is the starting point for the web made by our files through imports and exports 149 | entry: path.resolve(__dirname, 'index.js'), 150 | // all code will get concatenated into a single bundle.js inside a bundle folder 151 | output: { 152 | path: path.resolve(__dirname, 'bundle'), 153 | filename: 'bundle.js', 154 | }, 155 | // types of files we want Webpack to bundle 156 | resolve: { 157 | extensions: ['.js', '.jsx'], 158 | }, 159 | }; 160 | ``` 161 | * Add a new `module` key to `webpack.config.js` file: 162 | ```javascript 163 | var path = require('path'); 164 | 165 | module.exports = { 166 | // etc 167 | // mode, entry, output, resolve 168 | // etc 169 | module: { 170 | rules: [ 171 | { 172 | test: /\.(js|jsx)$/, 173 | exclude: /node_modules/, 174 | use: { 175 | loader: 'babel-loader' 176 | } 177 | }, 178 | ] 179 | } 180 | } 181 | ``` 182 | 183 | ## 7. Creating a Barebones React App 184 | * **Talking points:** difference between React and React DOM. 185 | * Install React and React DOM: `npm add react react-dom` 186 | * Edit index.js as follows: 187 | ```javascript 188 | import React from 'react'; 189 | import ReactDOM from 'react-dom'; 190 | 191 | function App() { 192 | return ( 193 |
Hello World
194 | ); 195 | } 196 | 197 | ReactDOM.render( 198 | , document.querySelector('#target'), 199 | ); 200 | ``` 201 | * Run Webpack: `npm run dev` 202 | * Run Live Server in a separate terminal: `live-server` 203 | * Run tests in a separate terminal: `npm test` 204 | 205 | ## 8. Adding Support for CSS and LESS 206 | * We need the following dev-dependencies: 207 | * less 208 | * less-loader 209 | * css-loader 210 | * style-loader 211 | * Run `npm add -D less less-loader css-loader style-loader`. 212 | * Edit the `extensions` part inside `webpack.config.js` to look for `.less` files: 213 | ```javascript 214 | { 215 | // etc 216 | resolve: { 217 | extensions: ['.js', '.jsx', '.less', '.css'] 218 | }, 219 | // etc 220 | } 221 | ``` 222 | * Add the following objects to the `rules` array inside `webpack.config.js`: 223 | ```javascript 224 | { 225 | test: /\.less$/, 226 | exclude: /node_modules/, 227 | use: [ 228 | { loader: 'style-loader' }, 229 | { loader: 'css-loader' }, 230 | { loader: 'less-loader' } 231 | ] 232 | }, 233 | { 234 | test: /\.css$/, 235 | exclude: /node_modules/, 236 | use: [ 237 | { loader: 'style-loader' }, 238 | { loader: 'css-loader' } 239 | ] 240 | } 241 | ``` 242 | * Create an `index.less` file at the root, add some styles and import it into `index.js` using the following syntax: 243 | * `import './index.less';` 244 | * Remember to restart webpack in order to get the configuration changes. 245 | 246 | ## 9. Adding Support for Class Properties in React Components 247 | * Run `npm add -D @babel/plugin-proposal-class-properties`. 248 | * Add a `plugins` key to `babel.config.js`: 249 | ```javascript 250 | module.exports = { 251 | presets: [ 252 | ["@babel/preset-env", { "targets": { "chrome": "60" } }], 253 | "@babel/preset-react" 254 | ], 255 | plugins: [ 256 | '@babel/plugin-proposal-class-properties' 257 | ] 258 | } 259 | ``` 260 | 261 | ## 10. Removing Linting Error with Arrow Functions in React Components 262 | * With arrow functions we don't need to bind `this` in the constructor. 263 | * Run `npm add -D babel-eslint`. 264 | * Inside `.eslintrc` add a `parser` key: 265 | ```javascript 266 | { 267 | "extends": "airbnb", 268 | "parser": "babel-eslint", 269 | // etc 270 | } 271 | ``` 272 | ## 11. Have the Bundler Generate a Source Map 273 | * Add the following field to the configuration in `webpack.config.js`: 274 | ```javascript 275 | devtool: 'source-map', 276 | ``` 277 | 278 | ## 12. Use Webpack Bundle Analyzer Plugin 279 | * This allows to visually inspect the weight of our npm dependencies. 280 | * Install Webpack Bundle Analyzer by running `npm add -D webpack-bundle-analyzer`. 281 | * Pull the analyzer in at the top of `webpack.config.js`. 282 | ```javascript 283 | var BundleAnalyzer = require('webpack-bundle-analyzer').BundleAnalyzerPlugin; 284 | ``` 285 | * Create a plugins property on the module.exports object in `webpack.config.js`. 286 | ```javascript 287 | plugins: [ 288 | new BundleAnalyzer() 289 | ], 290 | ``` 291 | 292 | ## 13. Final Touches 293 | * Initialize git repository. 294 | * Add `node_modules` and `bundle` folders to `.gitignore`. 295 | * Prevent configuration files from being linted by creating a top-level `.eslintignore` file. 296 | * Add annoying Airbnb rules to the `eslintrc.json` file with a value of `0` (1 means warning, 2 means error): 297 | ```javascript 298 | { 299 | // etc 300 | "rules": { 301 | "semi": 2, 302 | "annoying-rule-we-want-disabled": 0, 303 | // etc 304 | } 305 | } 306 | ``` -------------------------------------------------------------------------------- /setup_tests/__mocks__/fileMock.js: -------------------------------------------------------------------------------- 1 | module.exports = 'test-file-stub'; 2 | -------------------------------------------------------------------------------- /setup_tests/__mocks__/styleMock.js: -------------------------------------------------------------------------------- 1 | module.exports = {}; 2 | -------------------------------------------------------------------------------- /setup_tests/setupTests.js: -------------------------------------------------------------------------------- 1 | import { configure } from 'enzyme'; 2 | import Adapter from 'enzyme-adapter-react-16'; 3 | 4 | configure({ adapter: new Adapter() }); 5 | -------------------------------------------------------------------------------- /src/App.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import Container from './components/Container'; 4 | 5 | 6 | ReactDOM.render( 7 | , 8 | document.querySelector('#target'), 9 | ); 10 | -------------------------------------------------------------------------------- /src/components/Container.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import './Container.less'; 3 | 4 | 5 | export default function Container() { 6 | return ( 7 |
8 | Hello, World 9 |
10 | ); 11 | } 12 | -------------------------------------------------------------------------------- /src/components/Container.less: -------------------------------------------------------------------------------- 1 | .container-hello-world { 2 | color: red; 3 | } -------------------------------------------------------------------------------- /src/components/__tests__/Container.test.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { shallow } from 'enzyme'; 3 | import Container from '../Container'; 4 | 5 | 6 | const wrapper = shallow(); 7 | 8 | describe('Hello World', () => { 9 | it('renders correctly', () => { 10 | expect(wrapper).toMatchSnapshot(); 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /src/components/__tests__/__snapshots__/Container.test.jsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`Hello World renders correctly 1`] = `ShallowWrapper {}`; 4 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var BundleAnalyzer = require('webpack-bundle-analyzer').BundleAnalyzerPlugin; 3 | 4 | 5 | module.exports = { 6 | mode: 'development', 7 | entry: path.resolve(__dirname, 'index.js'), 8 | output: { 9 | path: path.resolve(__dirname, 'bundle'), 10 | filename: 'bundle.js' 11 | }, 12 | devtool: 'source-map', 13 | resolve: { 14 | extensions: ['.js', '.jsx', '.less', '.css'] 15 | }, 16 | plugins: [ 17 | // new BundleAnalyzer() 18 | ], 19 | devServer: { 20 | publicPath: path.resolve(__dirname, '/bundle/'), 21 | historyApiFallback: true, 22 | port: 9000 23 | }, 24 | module: { 25 | rules: [ 26 | { 27 | test: /\.(js|jsx)$/, 28 | exclude: /node_modules/, 29 | use: { 30 | loader: 'babel-loader' 31 | } 32 | }, 33 | { 34 | test: /\.less$/, 35 | use: [ 36 | { loader: 'style-loader' }, 37 | { loader: 'css-loader' }, 38 | { loader: 'less-loader' } 39 | ] 40 | }, 41 | { 42 | test: /\.css$/, 43 | use: [ 44 | { loader: 'style-loader' }, 45 | { loader: 'css-loader' } 46 | ] 47 | } 48 | ] 49 | } 50 | } 51 | --------------------------------------------------------------------------------