├── .gitignore
├── README.md
├── gulpfile.js
├── package-lock.json
├── package.json
├── src
├── cats.js
├── client.jsx
└── index.js
└── webpack.config.js
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | lib/
3 | dist/
4 | npm-debug.log
5 | .DS_Store
6 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Intro to React
2 |
3 | Quick intro to [React](https://reactjs.org/).
4 |
5 | ### Contents
6 |
7 | 1. [What is React?](https://github.com/mjhea0/react-intro#what-is-react)
8 | 1. [Project setup](https://github.com/mjhea0/react-intro#project-setup)
9 | 1. [Lint the code](https://github.com/mjhea0/react-intro#lint-the-code)
10 | 1. [Add a cat](https://github.com/mjhea0/react-intro#add-a-cat)
11 | 1. [React setup](https://github.com/mjhea0/react-intro#react-setup)
12 | 1. [Webpack setup](https://github.com/mjhea0/react-intro#webpack-setup)
13 |
14 | ### Objectives
15 |
16 | By the end of this tutorial, you should be able to:
17 |
18 | 1. Explain what React is and how it compares to Angular and Vue
19 | 1. Set up a modern React environment with Babel and Webpack
20 | 1. Create and render a React component in the browser
21 |
22 | ## What is React?
23 |
24 | > YOUR TURN: What is React? How does it compare to Angular and Vue?
25 |
26 | ## Project setup
27 |
28 | Create a new project directory:
29 |
30 | ```sh
31 | $ mkdir react-intro
32 | $ cd react-intro
33 | $ npm init -y
34 | ```
35 |
36 | Install gulp and babel:
37 |
38 | ```sh
39 | $ npm install --save-dev gulp@4.0.2 gulp-babel@8.0.0 @babel/core@7.21.0 @babel/preset-env@7.20.2
40 | ```
41 |
42 | > YOUR TURN: What's babel? What does `@babel/preset-env` do?
43 |
44 |
45 | Create a *gulpfile.js* file in the project root:
46 |
47 | ```javascript
48 | const gulp = require('gulp');
49 | const babel = require('gulp-babel');
50 |
51 | gulp.task('build', (done) => {
52 | gulp.src(['src/**/*.js'])
53 | .pipe(babel({ presets: ['@babel/preset-env'] }))
54 | .pipe(gulp.dest('lib'));
55 | done();
56 | });
57 | ```
58 |
59 | Now add a "src" and "lib" folder, and then add an *index.js* file to the "src" to test babel:
60 |
61 | ```javascript
62 | console.log('hello, world!');
63 | ```
64 |
65 | Finally, add a `start` script to *package.json*:
66 |
67 | ```json
68 | "scripts": {
69 | "start": "gulp build && node lib/index.js"
70 | },
71 | ```
72 |
73 | Sanity Check:
74 |
75 | ```sh
76 | $ npm start
77 |
78 | > react-intro@0.0.0 start /react-intro
79 | > gulp build && node lib/index.js
80 |
81 | [18:00:21] Using gulpfile ~/react-intro/gulpfile.js
82 | [18:00:21] Starting 'build'...
83 | [18:00:22] Finished 'build' after 361 ms
84 | hello, world!
85 | ```
86 |
87 | > YOUR TURN: What happened?
88 |
89 | ## Lint the code
90 |
91 | Install:
92 |
93 | ```sh
94 | $ npm install --save-dev eslint@8.35.0 eslint-config-airbnb@18.2.1
95 | $ npm install --save-dev eslint-plugin-import@2.27.5 eslint-plugin-jsx-a11y@6.7.1
96 | $ npm install --save-dev eslint-plugin-react@7.32.2 gulp-eslint@6.0.0
97 | ```
98 |
99 | > YOUR TURN: Why lint? What do those packages do?
100 |
101 | Add the config to *package.json*:
102 |
103 | ```json
104 | "eslintConfig": {
105 | "extends": "airbnb",
106 | "plugins": [
107 | "import"
108 | ]
109 | },
110 | ```
111 |
112 | Update *gulpfile.js* with a new task:
113 |
114 | ```javascript
115 | gulp.task('lint', (done) => {
116 | gulp.src([
117 | 'gulpfile.js',
118 | 'src/**/*.js',
119 | ])
120 | .pipe(eslint())
121 | .pipe(eslint.format())
122 | .pipe(eslint.failAfterError());
123 | done();
124 | });
125 | ```
126 |
127 | Make sure to add the dependency:
128 |
129 | ```javascript
130 | const eslint = require('gulp-eslint');
131 | ```
132 |
133 | Then add the `lint` task to the `build`:
134 |
135 | ```javascript
136 | gulp.task('build', gulp.series('lint', (done) => {
137 | gulp.src(['src/**/*.js'])
138 | .pipe(babel({ presets: ['@babel/preset-env'] }))
139 | .pipe(gulp.dest('lib'));
140 | done();
141 | }));
142 | ```
143 |
144 | Run the linter:
145 |
146 | ```sh
147 | $ npm start
148 | ```
149 |
150 | You should see a warning:
151 |
152 | ```sh
153 | /react-intro/src/index.js
154 | 1:1 warning Unexpected console statement no-console
155 |
156 | ✖ 1 problem (0 errors, 1 warning)
157 | ```
158 |
159 | Ignore it.
160 |
161 | > YOUR TURN: Why are we ignoring it?
162 |
163 | ## Add a cat
164 |
165 | Within "src" add a new file called *cats.js*:
166 |
167 | ```javascript
168 | class Cat {
169 | constructor(name) {
170 | this.name = name;
171 | }
172 |
173 | meow() {
174 | return `Meow meow, I am ${this.name}`;
175 | }
176 | }
177 |
178 | module.exports = Cat;
179 | ```
180 |
181 | Update *index.js*:
182 |
183 | ```javascript
184 | const Cat = require('./cats');
185 |
186 | const toby = new Cat('Toby');
187 | console.log(toby.meow());
188 | ```
189 |
190 | Run `npm start`:
191 |
192 | ```sh
193 | Meow meow, I am Toby
194 | ```
195 |
196 | > YOUR TURN: What's happening here?
197 |
198 | ## React setup
199 |
200 | Install:
201 |
202 | ```sh
203 | $ npm install --save react@18.2.0 react-dom@18.2.0 prop-types@15.8.1
204 | ```
205 |
206 | > YOUR TURN: What's React DOM?
207 |
208 | Then add a "dist" folder with an *index.html* file:
209 |
210 | ```html
211 |
212 |
213 |
214 | React Intro
215 |
216 |
217 |
218 |
219 |
220 | ```
221 |
222 | > YOUR TURN: What's bundle.js?
223 |
224 | Add a `div` to the *index.html* file, just above the `script` tag:
225 |
226 | ```html
227 |
228 | ```
229 |
230 | Create a new file in "src" called *client.jsx*:
231 |
232 | ```javascript
233 | import React from 'react';
234 | import ReactDOM from 'react-dom';
235 | import PropTypes from 'prop-types';
236 | import Cat from './cats';
237 |
238 | const catMeow = new Cat('Browser Cat').meow();
239 |
240 | const App = (props) => {
241 | const { message } = props;
242 | return (
243 |
244 | The cat says:
245 | { message }
246 |
247 | );
248 | };
249 |
250 | App.propTypes = {
251 | message: PropTypes.string.isRequired,
252 | };
253 |
254 | ReactDOM.render(, document.querySelector('.app'));
255 | ```
256 |
257 | > YOUR TURN: Is that HTML in a JS file? Why? What's JSX? Also, what does @babel/preset-react do?
258 |
259 | To process the *.jsx* file, install:
260 |
261 | ```sh
262 | $ npm install --save-dev @babel/preset-react@7.18.6
263 | ```
264 |
265 | Add the preset to the `build` task in *gulpfile.js*:
266 |
267 | ```javascript
268 | gulp.task('build', gulp.series('lint', (done) => {
269 | gulp.src(['src/**/*.js'])
270 | .pipe(babel({ presets: ['@babel/preset-env', '@babel/preset-react'] }))
271 | .pipe(gulp.dest('lib'));
272 | done();
273 | }));
274 | ```
275 |
276 | Finally, update the `lint` task to handle *.jsx* files:
277 |
278 | ```javascript
279 | gulp.task('lint', (done) => {
280 | gulp.src([
281 | 'gulpfile.js',
282 | 'src/**/*.js',
283 | 'src/**/*.jsx',
284 | ])
285 | .pipe(eslint())
286 | .pipe(eslint.format())
287 | .pipe(eslint.failAfterError());
288 | done();
289 | });
290 | ```
291 |
292 | Run the linter:
293 |
294 | ```sh
295 | $ ./node_modules/.bin/gulp lint
296 | ```
297 |
298 | You should see the following error:
299 |
300 | ```sh
301 | /react-intro/src/client.jsx
302 | 22:44 error 'document' is not defined no-undef
303 | ```
304 |
305 | > YOUR TURN: Why did we get this error?
306 |
307 | To correct this, update the `eslintConfig` in *package.json*:
308 |
309 | ```javascript
310 | "eslintConfig": {
311 | "extends": "airbnb",
312 | "plugins": [
313 | "import"
314 | ],
315 | "env": {
316 | "browser": true
317 | }
318 | },
319 | ```
320 |
321 | ## Webpack setup
322 |
323 | Install:
324 |
325 | ```sh
326 | $ npm install --save-dev webpack@5.75.0 webpack-cli@5.0.1 babel-loader@9.1.2 ajv@8.12.0
327 | ```
328 |
329 | > YOUR TURN: What's webpack? What does babel-loader do? Why all these damn tools?!?!
330 |
331 | Then add *webpack.config.js* to the project root:
332 |
333 | ```javascript
334 | const path = require('path');
335 |
336 | module.exports = {
337 | mode: 'development',
338 | entry: './src/client.jsx',
339 | output: {
340 | path: path.resolve(__dirname, 'dist'),
341 | filename: 'bundle.js',
342 | },
343 | module: {
344 | rules: [
345 | {
346 | test: /\.jsx?$/,
347 | exclude: /node_modules/,
348 | use: [
349 | {
350 | loader: 'babel-loader',
351 | options: {
352 | presets: [
353 | '@babel/preset-env',
354 | '@babel/preset-react',
355 | ]
356 | },
357 | },
358 | ],
359 | },
360 | ],
361 | },
362 | resolve: {
363 | extensions: ['*', '.js', '.jsx'],
364 | },
365 | }
366 | ```
367 |
368 | Update the `start` script in *package.json*:
369 |
370 | ```json
371 | "start": "gulp lint && webpack"
372 | ```
373 |
374 | Run:
375 |
376 | ```sh
377 | $ npm start
378 | ```
379 |
380 | Then open the *index.html* file within "dist" in your browser. You should see:
381 |
382 | ```
383 | The cat says: Meow meow, I am Browser Cat
384 | ```
385 |
--------------------------------------------------------------------------------
/gulpfile.js:
--------------------------------------------------------------------------------
1 | const gulp = require('gulp');
2 | const babel = require('gulp-babel');
3 | const eslint = require('gulp-eslint');
4 |
5 | gulp.task('lint', (done) => {
6 | gulp.src([
7 | 'gulpfile.js',
8 | 'src/**/*.js',
9 | 'src/**/*.jsx',
10 | ])
11 | .pipe(eslint())
12 | .pipe(eslint.format())
13 | .pipe(eslint.failAfterError());
14 | done();
15 | });
16 |
17 | gulp.task('build', gulp.series('lint', (done) => {
18 | gulp.src(['src/**/*.js'])
19 | .pipe(babel({ presets: ['@babel/preset-env', '@babel/preset-react'] }))
20 | .pipe(gulp.dest('lib'));
21 | done();
22 | }));
23 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-intro",
3 | "version": "1.0.0",
4 | "description": "Quick intro to [React](https://reactjs.org/).",
5 | "main": "index.js",
6 | "scripts": {
7 | "start": "gulp lint && webpack"
8 | },
9 | "repository": {
10 | "type": "git",
11 | "url": "git+https://github.com/mjhea0/react-intro.git"
12 | },
13 | "keywords": [],
14 | "author": "",
15 | "license": "ISC",
16 | "bugs": {
17 | "url": "https://github.com/mjhea0/react-intro/issues"
18 | },
19 | "homepage": "https://github.com/mjhea0/react-intro#readme",
20 | "devDependencies": {
21 | "@babel/core": "^7.21.0",
22 | "@babel/preset-env": "^7.20.2",
23 | "@babel/preset-react": "^7.18.6",
24 | "ajv": "^8.12.0",
25 | "babel-loader": "^9.1.2",
26 | "eslint": "^8.35.0",
27 | "eslint-config-airbnb": "^18.2.1",
28 | "eslint-plugin-import": "^2.27.5",
29 | "eslint-plugin-jsx-a11y": "^6.7.1",
30 | "eslint-plugin-react": "^7.32.2",
31 | "gulp": "^4.0.2",
32 | "gulp-babel": "^8.0.0",
33 | "gulp-eslint": "^6.0.0",
34 | "webpack": "^5.75.0",
35 | "webpack-cli": "^5.0.1"
36 | },
37 | "eslintConfig": {
38 | "extends": "airbnb",
39 | "plugins": [
40 | "import"
41 | ],
42 | "env": {
43 | "browser": true
44 | }
45 | },
46 | "dependencies": {
47 | "prop-types": "^15.8.1",
48 | "react": "^18.2.0",
49 | "react-dom": "^18.2.0"
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/src/cats.js:
--------------------------------------------------------------------------------
1 | class Cat {
2 | constructor(name) {
3 | this.name = name;
4 | }
5 |
6 | meow() {
7 | return `Meow meow, I am ${this.name}`;
8 | }
9 | }
10 |
11 | module.exports = Cat;
12 |
--------------------------------------------------------------------------------
/src/client.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import PropTypes from 'prop-types';
4 | import Cat from './cats';
5 |
6 | const catMeow = new Cat('Browser Cat').meow();
7 |
8 | const App = (props) => {
9 | const { message } = props;
10 | return (
11 |
12 | The cat says:
13 | { message }
14 |
15 | );
16 | };
17 |
18 | App.propTypes = {
19 | message: PropTypes.string.isRequired,
20 | };
21 |
22 | ReactDOM.render(, document.querySelector('.app'));
23 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | const Cat = require('./cats');
2 |
3 | const toby = new Cat('Toby');
4 | console.log(toby.meow());
5 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 |
3 | module.exports = {
4 | mode: 'development',
5 | entry: './src/client.jsx',
6 | output: {
7 | path: path.resolve(__dirname, 'dist'),
8 | filename: 'bundle.js',
9 | },
10 | module: {
11 | rules: [
12 | {
13 | test: /\.jsx?$/,
14 | exclude: /node_modules/,
15 | use: [
16 | {
17 | loader: 'babel-loader',
18 | options: {
19 | presets: [
20 | '@babel/preset-env',
21 | '@babel/preset-react',
22 | ]
23 | },
24 | },
25 | ],
26 | },
27 | ],
28 | },
29 | resolve: {
30 | extensions: ['*', '.js', '.jsx'],
31 | },
32 | }
33 |
--------------------------------------------------------------------------------