├── .gitignore
├── .babelrc
├── .editorconfig
├── index.html
├── src
├── counter.js
├── index.js
├── qnd-react-dom.js
└── qnd-react.js
├── webpack.dev.js
├── README.md
├── webpack.prod.js
└── package.json
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea
2 | /node_modules
3 | dist
4 |
--------------------------------------------------------------------------------
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "plugins": [
3 | ["@babel/plugin-transform-react-jsx", {
4 | "pragma": "QndReact.createElement", // default pragma is React.createElement
5 | "throwIfNamespace": false // defaults to true
6 | }]
7 | ]
8 | }
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # Editor configuration, see http://editorconfig.org
2 | root = true
3 |
4 | [*]
5 | charset = utf-8
6 | indent_style = space
7 | indent_size = 4
8 | insert_final_newline = true
9 | trim_trailing_whitespace = true
10 |
11 | [*.md]
12 | max_line_length = off
13 | trim_trailing_whitespace = false
14 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | QndReact
7 |
8 |
9 |
10 |
Hello World from webpack starter pack
11 |
12 | This is a very basic webpack setup with just ES6 support and everything else is left upto your imagination
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/src/counter.js:
--------------------------------------------------------------------------------
1 | import QndReact from './qnd-react';
2 |
3 | export default class Counter extends QndReact.Component {
4 | constructor(props) {
5 | super(props);
6 |
7 | this.state = {
8 | count: 0
9 | }
10 | }
11 |
12 | componentDidMount() {
13 | console.log('Component mounted');
14 | }
15 |
16 | render() {
17 | return (
18 |
19 |
Count: {this.state.count}
20 |
23 |
24 | )
25 | }
26 | }
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | // file: src/index.js
2 | // QndReact needs to be in scope for JSX to work
3 | import QndReact from "./qnd-react";
4 | import QndReactDom from "./qnd-react-dom";
5 | import Counter from "./counter";
6 |
7 | // functional component to welcome someone
8 | const Greeting = ({ name }) => Welcome {name}!
;
9 | const foods = [
10 | 'idly',
11 | 'dosa',
12 | 'vada'
13 | ]
14 |
15 | const App = (
16 |
17 |
18 | QndReact is Quick and dirty react
19 |
20 |
It is about building your own React in 90 lines of JavsScript
21 |
22 |
23 |
The following renders a list of food
24 |
25 | {foods.map(food => - {food}
)}
26 |
27 |
28 | );
29 |
30 | QndReactDom.render(App, document.getElementById("root"));
--------------------------------------------------------------------------------
/webpack.dev.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const HtmlWebpackPlugin = require('html-webpack-plugin');
3 |
4 | module.exports = {
5 | devtool: 'eval-cheap-module-source-map',
6 | entry: './src/index.js',
7 | devServer: {
8 | port: 3000,
9 | contentBase: path.join(__dirname, "dist")
10 | },
11 | node: {
12 | fs: 'empty'
13 | },
14 | module: {
15 | rules: [
16 | {
17 | test: /\.js$/,
18 | exclude: /node_modules/,
19 | loader: 'babel-loader',
20 | options: {
21 | presets: ['@babel/preset-env']
22 | }
23 | }
24 | ]
25 | },
26 | plugins: [
27 | new HtmlWebpackPlugin({
28 | template: './index.html',
29 | inject: true
30 | })
31 | ]
32 | };
33 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Quick and Dirty React :hammer:
2 |
3 | This is my quick and dirty implementation of react to help myself and others to understand what react does under the hood
4 |
5 | This goes along well with the article [Build your own React in 90 lines of JavaScript
6 | ](https://dev.to/ameerthehacker/build-your-own-react-in-90-lines-of-javascript-1je2)
7 |
8 | * The replication of ReactDom is available in __src/qnd-react-dom.js__
9 | * The replication of React is available in __src/qnd-react.js__
10 |
11 | ## How to run it?
12 |
13 | 1. Clone the repo
14 | 2. Install the dependencies
15 |
16 | ```sh
17 | npm install
18 | ```
19 |
20 | 3. Run the sample project using QndReact.js :heart:
21 |
22 | ```sh
23 | npm start
24 | ```
25 |
26 | ## Found any issue?
27 |
28 | Please feel free to raise an issue or PR :wink:
29 |
30 | Show your support by :star: the repo
31 |
32 | ## License
33 |
34 | MIT © [Ameer Jhan](mailto:ameerjhanprof@gmail.com)
--------------------------------------------------------------------------------
/webpack.prod.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 |
3 | const HtmlWebpackPlugin = require('html-webpack-plugin');
4 |
5 | const buildPath = path.resolve(__dirname, 'dist');
6 |
7 | module.exports = {
8 | devtool: 'source-map',
9 | entry: './src/index.js',
10 | output: {
11 | filename: '[name].[hash:20].js',
12 | path: buildPath
13 | },
14 | node: {
15 | fs: 'empty'
16 | },
17 | module: {
18 | rules: [
19 | {
20 | test: /\.js$/,
21 | exclude: /node_modules/,
22 | loader: 'babel-loader',
23 | options: {
24 | presets: ['@babel/preset-env']
25 | }
26 | }
27 | ]
28 | },
29 | plugins: [
30 | new HtmlWebpackPlugin({
31 | template: './index.html',
32 | // Inject the js bundle at the end of the body of the given template
33 | inject: 'body',
34 | })
35 | ]
36 | };
37 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "webpack-starter-pack",
3 | "version": "0.0.1",
4 | "description": "This is a very basic webpack setup with just ES6 support and everything else is left upto your creativity",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1",
8 | "start": "webpack-dev-server --config webpack.dev.js --mode development",
9 | "build": "webpack --config webpack.prod.js --mode production",
10 | "preview": "npm run build && http-server dist",
11 | "kickstart": "node kickstarter"
12 | },
13 | "repository": {
14 | "type": "git",
15 | "url": "git+https://github.com/ameerthehacker/webpack-starter-pack.git"
16 | },
17 | "keywords": [
18 | "react"
19 | ],
20 | "author": "ameerthehacker",
21 | "license": "MIT",
22 | "bugs": {
23 | "url": "https://github.com/ameerthehacker/webpack-starter-pack/issues"
24 | },
25 | "homepage": "https://github.com/ameerthehacker/webpack-starter-pack#readme",
26 | "dependencies": {
27 | "normalize.css": "^8.0.0",
28 | "snabbdom": "^0.7.3"
29 | },
30 | "devDependencies": {
31 | "@babel/core": "^7.6.4",
32 | "@babel/plugin-transform-react-jsx": "^7.3.0",
33 | "@babel/preset-env": "^7.6.3",
34 | "babel-loader": "^8.0.6",
35 | "html-webpack-plugin": "^3.1.0",
36 | "http-server": "^0.11.1",
37 | "rimraf": "^2.6.2",
38 | "source-map-loader": "^0.2.3",
39 | "webpack": "^4.20.2",
40 | "webpack-cli": "^3.1.1",
41 | "webpack-dev-server": "^3.1.11"
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/src/qnd-react-dom.js:
--------------------------------------------------------------------------------
1 | // file: src/qnd-react-dom.js
2 | import * as snabbdom from 'snabbdom';
3 | import propsModule from 'snabbdom/modules/props';
4 | import eventlistenersModule from 'snabbdom/modules/eventlisteners';
5 | import QndReact from './qnd-react';
6 |
7 | // propsModule -> this helps in patching text attributes
8 | // eventlistenersModule -> this helps in patching event attributes
9 | const reconcile = snabbdom.init([propsModule, eventlistenersModule]);
10 | // we need to maintain the latest rootVNode returned by render
11 | let rootVNode;
12 |
13 | // React.render(, document.getElementById('root'));
14 | // el ->
15 | // rootDomElement -> document.getElementById('root')
16 | const render = (el, rootDomElement) => {
17 | // logic to put el into the rootDomElement
18 | // ie. QndReactDom.render(, document.getElementById('root'));
19 | // happens when we call render for the first time
20 | if (rootVNode == null) {
21 | rootVNode = rootDomElement;
22 | }
23 |
24 | // remember the VNode that reconcile returns
25 | rootVNode = reconcile(rootVNode, el);
26 | }
27 |
28 | // QndReactDom telling React how to update DOM
29 | QndReact.__updater = (componentInstance) => {
30 | // logic on how to update the DOM when you call this.setState
31 |
32 | // get the oldVNode stored in __vNode
33 | const oldVNode = componentInstance.__vNode;
34 | // find the updated DOM node by calling the render method
35 | const newVNode = componentInstance.render();
36 |
37 | // update the __vNode property with updated __vNode
38 | componentInstance.__vNode = reconcile(oldVNode, newVNode);
39 | }
40 |
41 | // to be exported like ReactDom.render
42 | const QndReactDom = {
43 | render
44 | };
45 |
46 | export default QndReactDom;
--------------------------------------------------------------------------------
/src/qnd-react.js:
--------------------------------------------------------------------------------
1 | // file: src/qnd-react.js
2 | import { h } from 'snabbdom';
3 |
4 | const createElement = (type, props = {}, ...children) => {
5 | // flatten the children
6 | // this to make todos.map(todo => {todo}
) work in jsx
7 | // [['idly'], ['dosa', 'vada']] -> ['idly', 'dosa', 'vada']
8 | children = children.flat();
9 |
10 | // if type is a Class then
11 | // 1. create a instance of the Class
12 | // 2. call the render method on the Class instance
13 | if (type.prototype && type.prototype.isQndReactClassComponent) {
14 | const componentInstance = new type(props);
15 |
16 | // remember the current vNode instance
17 | componentInstance.__vNode = componentInstance.render();
18 |
19 | // add hook to snabbdom virtual node to know whether it was added to the actual DOM
20 | componentInstance.__vNode.data.hook = {
21 | create: () => {
22 | componentInstance.componentDidMount()
23 | }
24 | }
25 |
26 | return componentInstance.__vNode;
27 | }
28 | // if type is a function then call it and return it's value
29 | if (typeof (type) == 'function') {
30 | return type(props);
31 | }
32 |
33 | props = props || {};
34 | let dataProps = {};
35 | let eventProps = {};
36 |
37 | // This is to seperate out the text attributes and event listener attributes
38 | for(let propKey in props) {
39 | // event props always startwith on eg. onClick, onChange etc.
40 | if (propKey.startsWith('on')) {
41 | // onClick -> click
42 | const event = propKey.substring(2).toLowerCase();
43 |
44 | eventProps[event] = props[propKey];
45 | }
46 | else {
47 | dataProps[propKey] = props[propKey];
48 | }
49 | }
50 |
51 | // props -> snabbdom's internal text attributes
52 | // on -> snabbdom's internal event listeners attributes
53 | return h(type, { props: dataProps, on: eventProps }, children);
54 | };
55 |
56 | // component base class
57 | class Component {
58 | constructor() { }
59 |
60 | componentDidMount() { }
61 |
62 | setState(partialState) {
63 | // update the state by adding the partial state
64 | this.state = {
65 | ...this.state,
66 | ...partialState
67 | }
68 | // call the __updater function that QndReactDom gave
69 | QndReact.__updater(this);
70 | }
71 |
72 | render() { }
73 | }
74 |
75 | // add a static property to differentiate between a class and a function
76 | Component.prototype.isQndReactClassComponent = true;
77 |
78 | // to be exported like React.createElement, React.Component
79 | const QndReact = {
80 | createElement,
81 | Component
82 | };
83 |
84 | export default QndReact;
85 |
--------------------------------------------------------------------------------