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