├── .gitignore
├── README.md
├── package.json
├── patch
├── .babelrc.js
├── dist
│ ├── dong.js
│ └── index.js
├── dong.js
├── index.html
├── index.js
└── package.json
├── render-component
├── .babelrc.js
├── dist
│ ├── dong.js
│ └── index.js
├── dong.js
├── index.html
├── index.js
└── package.json
├── render-fiber
├── .babelrc.js
├── README.md
├── dist
│ ├── dong.js
│ └── index.js
├── dong.js
├── index.html
├── index.js
└── package.json
├── render-jsx
├── .babelrc.js
├── README.md
├── dist
│ ├── dong.js
│ └── index.js
├── dong.js
├── index.html
├── index.js
└── package.json
└── render-vdom
├── dong.js
├── index.html
├── index.js
└── package.json
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | *-lock.json
3 | yarn-*
4 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # frontend-framework-exercize
2 |
3 |
4 | [实现 vdom 的渲染](./render-vdom)
5 |
6 | [jsx 的编译,然后渲染](./render-vdom)
7 |
8 | [组件渲染](./render-component)
9 |
10 | [fiber 版 react](./render-jsx)
11 | ## run
12 | ```
13 | npx http-server .
14 | ```
15 | 然后访问对应目录
16 |
17 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "frontend-framework-exercize",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1"
8 | },
9 | "repository": {
10 | "type": "git",
11 | "url": "git+https://github.com/QuarkGluonPlasma/frontend-framework-exercize.git"
12 | },
13 | "keywords": [],
14 | "author": "",
15 | "license": "ISC",
16 | "bugs": {
17 | "url": "https://github.com/QuarkGluonPlasma/frontend-framework-exercize/issues"
18 | },
19 | "homepage": "https://github.com/QuarkGluonPlasma/frontend-framework-exercize#readme",
20 | "devDependencies": {
21 | "@babel/cli": "^7.16.8",
22 | "@babel/core": "^7.16.10",
23 | "@babel/preset-react": "^7.16.7"
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/patch/.babelrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: [
3 | [
4 | '@babel/preset-react',
5 | {
6 | pragma: 'createElement'
7 | }
8 | ]
9 | ]
10 | }
--------------------------------------------------------------------------------
/patch/dist/dong.js:
--------------------------------------------------------------------------------
1 | function isTextVdom(vdom) {
2 | return typeof vdom == 'string' || typeof vdom == 'number';
3 | }
4 |
5 | function isElementVdom(vdom) {
6 | return typeof vdom == 'object' && typeof vdom.type == 'string';
7 | }
8 |
9 | function isComponentVdom(vdom) {
10 | return typeof vdom.type == 'function';
11 | }
12 |
13 | const render = (vdom, parent = null) => {
14 | const mount = parent ? el => parent.appendChild(el) : el => el;
15 |
16 | if (isTextVdom(vdom)) {
17 | return mount(document.createTextNode(vdom));
18 | } else if (isElementVdom(vdom)) {
19 | const dom = mount(document.createElement(vdom.type));
20 |
21 | for (const child of [].concat(...vdom.children)) {
22 | // children 元素也是 数组,要拍平
23 | render(child, dom);
24 | }
25 |
26 | for (const prop in vdom.props) {
27 | setAttribute(dom, prop, vdom.props[prop]);
28 | }
29 |
30 | return dom;
31 | } else if (isComponentVdom(vdom)) {
32 | return renderComponent(vdom, parent);
33 | } else {
34 | throw new Error(`Invalid VDOM: ${vdom}.`);
35 | }
36 | };
37 |
38 | function renderComponent(vdom, parent) {
39 | const props = Object.assign({}, vdom.props, {
40 | children: vdom.children
41 | });
42 |
43 | if (Component.isPrototypeOf(vdom.type)) {
44 | const instance = new vdom.type(props);
45 | instance.componentWillMount();
46 | const componentVdom = instance.render();
47 | instance.dom = render(componentVdom, parent);
48 | instance.dom.__instance = instance;
49 | instance.dom.__key = vdom.props.key;
50 | instance.componentDidMount();
51 | return instance.dom;
52 | } else {
53 | const componentVdom = vdom.type(props);
54 | return render(componentVdom, parent);
55 | }
56 | }
57 |
58 | function patch(dom, vdom, parent = dom.parentNode) {
59 | const replace = parent ? el => {
60 | parent.replaceChild(el, dom);
61 | return el;
62 | } : el => el;
63 |
64 | if (isComponentVdom(vdom)) {
65 | const props = Object.assign({}, vdom.props, {
66 | children: vdom.children
67 | });
68 |
69 | if (dom.__instance && dom.__instance.constructor == vdom.type) {
70 | dom.__instance.componentWillReceiveProps(props);
71 |
72 | dom.__instance.props = props;
73 | return patch(dom, dom.__instance.render(), parent);
74 | } else if (Component.isPrototypeOf(vdom.type)) {
75 | const componentDom = renderComponent(vdom, parent);
76 |
77 | if (parent) {
78 | parent.replaceChild(componentDom, dom);
79 | return componentDom;
80 | } else {
81 | return componentDom;
82 | }
83 | } else if (!Component.isPrototypeOf(vdom.type)) {
84 | return patch(dom, vdom.type(props), parent);
85 | }
86 | } else if (dom instanceof Text) {
87 | if (typeof vdom === 'object') {
88 | return replace(render(vdom, parent));
89 | } else {
90 | return dom.textContent != vdom ? replace(render(vdom, parent)) : dom;
91 | }
92 | } else if (dom.nodeName !== vdom.type.toUpperCase() && typeof vdom === 'object') {
93 | return replace(render(vdom, parent));
94 | } else if (dom.nodeName === vdom.type.toUpperCase() && typeof vdom === 'object') {
95 | const active = document.activeElement;
96 | const oldDoms = {};
97 | [].concat(...dom.childNodes).map((child, index) => {
98 | const key = child.__key || `__index_${index}`;
99 | oldDoms[key] = child;
100 | });
101 | [].concat(...vdom.children).map((child, index) => {
102 | const key = child.props && child.props.key || `__index_${index}`;
103 | dom.appendChild(oldDoms[key] ? patch(oldDoms[key], child) : render(child, dom));
104 | delete oldDoms[key];
105 | });
106 |
107 | for (const key in oldDoms) {
108 | const instance = oldDoms[key].__instance;
109 | if (instance) instance.componentWillUnmount();
110 | oldDoms[key].remove();
111 | }
112 |
113 | for (const attr of dom.attributes) dom.removeAttribute(attr.name);
114 |
115 | for (const prop in vdom.props) setAttribute(dom, prop, vdom.props[prop]);
116 |
117 | active.focus();
118 | return dom;
119 | }
120 | }
121 |
122 | function isEventListenerAttr(key, value) {
123 | return typeof value == 'function' && key.startsWith('on');
124 | }
125 |
126 | function isStyleAttr(key, value) {
127 | return key == 'style' && typeof value == 'object';
128 | }
129 |
130 | function isPlainAttr(key, value) {
131 | return typeof value != 'object' && typeof value != 'function';
132 | }
133 |
134 | function isRefAttr(key, value) {
135 | return key === 'ref' && typeof value === 'function';
136 | }
137 |
138 | const setAttribute = (dom, key, value) => {
139 | if (isEventListenerAttr(key, value)) {
140 | const eventType = key.slice(2).toLowerCase();
141 | dom.__handlers = dom.__handlers || {};
142 | dom.removeEventListener(eventType, dom.__handlers[eventType]);
143 | dom.__handlers[eventType] = value;
144 | dom.addEventListener(eventType, dom.__handlers[eventType]);
145 | } else if (key == 'checked' || key == 'value' || key == 'className') {
146 | dom[key] = value;
147 | } else if (isRefAttr(key, value)) {
148 | value(dom);
149 | } else if (isStyleAttr(key, value)) {
150 | Object.assign(dom.style, value);
151 | } else if (key == 'key') {
152 | dom.__key = value;
153 | } else if (isPlainAttr(key, value)) {
154 | dom.setAttribute(key, value);
155 | }
156 | };
157 |
158 | const createElement = (type, props, ...children) => {
159 | if (props === null) props = {};
160 | return {
161 | type,
162 | props,
163 | children
164 | };
165 | };
166 |
167 | class Component {
168 | constructor(props) {
169 | this.props = props || {};
170 | this.state = null;
171 | }
172 |
173 | setState(nextState) {
174 | this.state = Object.assign(this.state, nextState);
175 |
176 | if (this.dom && this.shouldComponentUpdate(this.props, nextState)) {
177 | patch(this.dom, this.render());
178 | }
179 | }
180 |
181 | shouldComponentUpdate(nextProps, nextState) {
182 | return nextProps != this.props || nextState != this.state;
183 | }
184 |
185 | componentWillMount() {}
186 |
187 | componentDidMount() {}
188 |
189 | componentWillReceiveProps() {}
190 |
191 | componentWillUnmount() {}
192 |
193 | }
--------------------------------------------------------------------------------
/patch/dist/index.js:
--------------------------------------------------------------------------------
1 | function Item(props) {
2 | return createElement("li", {
3 | className: "item",
4 | style: props.style
5 | }, props.children, " ", createElement("a", {
6 | href: "#",
7 | onClick: props.onRemoveItem
8 | }, "X "));
9 | }
10 |
11 | class List extends Component {
12 | constructor(props) {
13 | super();
14 | this.state = {
15 | list: [{
16 | text: 'aaa',
17 | color: 'pink'
18 | }, {
19 | text: 'bbb',
20 | color: 'orange'
21 | }, {
22 | text: 'ccc',
23 | color: 'yellow'
24 | }]
25 | };
26 | }
27 |
28 | handleItemRemove(index) {
29 | this.setState({
30 | list: this.state.list.filter((item, i) => i !== index)
31 | });
32 | }
33 |
34 | handleAdd() {
35 | this.setState({
36 | list: [...this.state.list, {
37 | text: this.ref.value
38 | }]
39 | });
40 | }
41 |
42 | render() {
43 | return createElement("div", null, createElement("ul", {
44 | className: "list"
45 | }, this.state.list.map((item, index) => {
46 | return createElement(Item, {
47 | style: {
48 | background: item.color,
49 | color: this.state.textColor
50 | },
51 | onRemoveItem: () => this.handleItemRemove(index)
52 | }, item.text);
53 | })), createElement("div", null, createElement("input", {
54 | ref: ele => {
55 | this.ref = ele;
56 | }
57 | }), createElement("button", {
58 | onClick: this.handleAdd.bind(this)
59 | }, "add")));
60 | }
61 |
62 | }
63 |
64 | render(createElement(List, {
65 | textColor: '#000'
66 | }), document.getElementById('root'));
--------------------------------------------------------------------------------
/patch/dong.js:
--------------------------------------------------------------------------------
1 |
2 | function isTextVdom(vdom) {
3 | return typeof vdom == 'string' || typeof vdom == 'number';
4 | }
5 |
6 | function isElementVdom(vdom) {
7 | return typeof vdom == 'object' && typeof vdom.type == 'string';
8 | }
9 |
10 | function isComponentVdom(vdom) {
11 | return typeof vdom.type == 'function';
12 | }
13 |
14 | const render = (vdom, parent = null) => {
15 | const mount = parent ? (el => parent.appendChild(el)) : (el => el);
16 | if (isTextVdom(vdom)) {
17 | return mount(document.createTextNode(vdom));
18 | } else if (isElementVdom(vdom)) {
19 | const dom = mount(document.createElement(vdom.type));
20 | for (const child of [].concat(...vdom.children)) {// children 元素也是 数组,要拍平
21 | render(child, dom);
22 | }
23 | for (const prop in vdom.props) {
24 | setAttribute(dom, prop, vdom.props[prop]);
25 | }
26 | return dom;
27 | } else if (isComponentVdom(vdom)) {
28 | return renderComponent(vdom, parent);
29 | } else {
30 | throw new Error(`Invalid VDOM: ${vdom}.`);
31 | }
32 | };
33 |
34 | function renderComponent(vdom, parent) {
35 | const props = Object.assign({}, vdom.props, {
36 | children: vdom.children
37 | });
38 |
39 | if (Component.isPrototypeOf(vdom.type)) {
40 | const instance = new vdom.type(props);
41 |
42 | instance.componentWillMount();
43 |
44 | const componentVdom = instance.render();
45 | instance.dom = render(componentVdom, parent);
46 | instance.dom.__instance = instance;
47 | instance.dom.__key = vdom.props.key;
48 |
49 | instance.componentDidMount();
50 |
51 | return instance.dom;
52 | } else {
53 | const componentVdom = vdom.type(props);
54 | return render(componentVdom, parent);
55 | }
56 | }
57 |
58 | function patch(dom, vdom, parent = dom.parentNode) {
59 | const replace = parent ? el => {
60 | parent.replaceChild(el, dom);
61 | return el;
62 | } : (el => el);
63 |
64 | if(isComponentVdom(vdom)) {
65 | const props = Object.assign({}, vdom.props, {children: vdom.children});
66 | if (dom.__instance && dom.__instance.constructor == vdom.type) {
67 | dom.__instance.componentWillReceiveProps(props);
68 | dom.__instance.props = props;
69 | return patch(dom, dom.__instance.render(), parent);
70 | } else if (Component.isPrototypeOf(vdom.type)) {
71 | const componentDom = renderComponent(vdom, parent);
72 | if (parent){
73 | parent.replaceChild(componentDom, dom);
74 | return componentDom;
75 | } else {
76 | return componentDom
77 | }
78 | } else if (!Component.isPrototypeOf(vdom.type)) {
79 | return patch(dom, vdom.type(props), parent);
80 | }
81 | }else if (dom instanceof Text) {
82 | if (typeof vdom === 'object') {
83 | return replace(render(vdom, parent));
84 | } else {
85 | return dom.textContent != vdom ? replace(render(vdom, parent)) : dom;
86 | }
87 | } else if (dom.nodeName !== vdom.type.toUpperCase() && typeof vdom === 'object') {
88 | return replace(render(vdom, parent));
89 | } else if(dom.nodeName === vdom.type.toUpperCase() && typeof vdom === 'object'){
90 | const active = document.activeElement;
91 |
92 | const oldDoms = {};
93 | [].concat(...dom.childNodes).map((child, index) => {
94 | const key = child.__key || `__index_${index}`;
95 | oldDoms[key] = child;
96 | });
97 | [].concat(...vdom.children).map((child, index) => {
98 | const key = child.props && child.props.key || `__index_${index}`;
99 | dom.appendChild(oldDoms[key] ? patch(oldDoms[key], child) : render(child, dom));
100 | delete oldDoms[key];
101 | });
102 | for (const key in oldDoms) {
103 | const instance = oldDoms[key].__instance;
104 | if (instance) instance.componentWillUnmount();
105 | oldDoms[key].remove();
106 | }
107 | for (const attr of dom.attributes) dom.removeAttribute(attr.name);
108 | for (const prop in vdom.props) setAttribute(dom, prop, vdom.props[prop]);
109 |
110 | active.focus();
111 |
112 | return dom;
113 | }
114 | }
115 |
116 | function isEventListenerAttr(key, value) {
117 | return typeof value == 'function' && key.startsWith('on');
118 | }
119 |
120 | function isStyleAttr(key, value) {
121 | return key == 'style' && typeof value == 'object';
122 | }
123 |
124 | function isPlainAttr(key, value) {
125 | return typeof value != 'object' && typeof value != 'function';
126 | }
127 |
128 | function isRefAttr(key, value) {
129 | return key === 'ref' && typeof value === 'function';
130 | }
131 |
132 | const setAttribute = (dom, key, value) => {
133 | if (isEventListenerAttr(key, value)) {
134 | const eventType = key.slice(2).toLowerCase();
135 | dom.__handlers = dom.__handlers || {};
136 | dom.removeEventListener(eventType, dom.__handlers[eventType]);
137 | dom.__handlers[eventType] = value;
138 | dom.addEventListener(eventType, dom.__handlers[eventType]);
139 | } else if (key == 'checked' || key == 'value' || key == 'className') {
140 | dom[key] = value;
141 | } else if(isRefAttr(key, value)) {
142 | value(dom);
143 | } else if (isStyleAttr(key, value)) {
144 | Object.assign(dom.style, value);
145 | } else if (key == 'key') {
146 | dom.__key = value;
147 | } else if (isPlainAttr(key, value)) {
148 | dom.setAttribute(key, value);
149 | }
150 | }
151 |
152 | const createElement = (type, props, ...children) => {
153 | if (props === null) props = {};
154 | return {type, props, children};
155 | };
156 |
157 | class Component {
158 | constructor(props) {
159 | this.props = props || {};
160 | this.state = null;
161 | }
162 |
163 | setState(nextState) {
164 | this.state = Object.assign(this.state, nextState);
165 | if(this.dom && this.shouldComponentUpdate(this.props, nextState)) {
166 | patch(this.dom, this.render());
167 | }
168 | }
169 |
170 | shouldComponentUpdate(nextProps, nextState) {
171 | return nextProps != this.props || nextState != this.state;
172 | }
173 |
174 | componentWillMount() {}
175 |
176 | componentDidMount() {}
177 |
178 | componentWillReceiveProps() {}
179 |
180 | componentWillUnmount() {}
181 | }
182 |
--------------------------------------------------------------------------------
/patch/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Document
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/patch/index.js:
--------------------------------------------------------------------------------
1 | function Item(props) {
2 | return {props.children} X ;
3 | }
4 |
5 | class List extends Component {
6 | constructor(props) {
7 | super();
8 | this.state = {
9 | list: [
10 | {
11 | text: 'aaa',
12 | color: 'pink'
13 | },
14 | {
15 | text: 'bbb',
16 | color: 'orange'
17 | },
18 | {
19 | text: 'ccc',
20 | color: 'yellow'
21 | }
22 | ]
23 | }
24 | }
25 |
26 | handleItemRemove(index) {
27 | this.setState({
28 | list: this.state.list.filter((item, i) => i !== index)
29 | });
30 | }
31 |
32 | handleAdd() {
33 | this.setState({
34 | list: [
35 | ...this.state.list,
36 | {
37 | text: this.ref.value
38 | }
39 | ]
40 | });
41 | }
42 |
43 | render() {
44 | return
45 |
46 | {this.state.list.map((item, index) => {
47 | return - this.handleItemRemove(index)}>{item.text}
48 | })}
49 |
50 |
51 | {this.ref = ele}}/>
52 |
53 |
54 |
;
55 | }
56 | }
57 |
58 | render(, document.getElementById('root'));
59 |
--------------------------------------------------------------------------------
/patch/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "patch",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1",
8 | "build": "babel dong.js index.js -d ./dist",
9 | "dev": "babel dong.js index.js -d ./dist --watch"
10 | },
11 | "keywords": [],
12 | "author": "",
13 | "license": "ISC",
14 | "devDependencies": {
15 | "@babel/cli": "^7.16.8",
16 | "@babel/core": "^7.16.12",
17 | "@babel/preset-react": "^7.16.7"
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/render-component/.babelrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: [
3 | [
4 | '@babel/preset-react',
5 | {
6 | pragma: 'createElement'
7 | }
8 | ]
9 | ]
10 | }
--------------------------------------------------------------------------------
/render-component/dist/dong.js:
--------------------------------------------------------------------------------
1 | function isTextVdom(vdom) {
2 | return typeof vdom == 'string' || typeof vdom == 'number';
3 | }
4 |
5 | function isElementVdom(vdom) {
6 | return typeof vdom == 'object' && typeof vdom.type == 'string';
7 | }
8 |
9 | function isComponentVdom(vdom) {
10 | return typeof vdom.type == 'function';
11 | }
12 |
13 | const render = (vdom, parent = null) => {
14 | const mount = parent ? el => parent.appendChild(el) : el => el;
15 |
16 | if (isTextVdom(vdom)) {
17 | return mount(document.createTextNode(vdom));
18 | } else if (isElementVdom(vdom)) {
19 | const dom = mount(document.createElement(vdom.type));
20 |
21 | for (const child of [].concat(...vdom.children)) {
22 | render(child, dom);
23 | }
24 |
25 | for (const prop in vdom.props) {
26 | setAttribute(dom, prop, vdom.props[prop]);
27 | }
28 |
29 | return dom;
30 | } else if (isComponentVdom(vdom)) {
31 | const props = Object.assign({}, vdom.props, {
32 | children: vdom.children
33 | });
34 |
35 | if (Component.isPrototypeOf(vdom.type)) {
36 | const instance = new vdom.type(props);
37 | instance.componentWillMount();
38 | const componentVdom = instance.render();
39 | instance.dom = render(componentVdom, parent);
40 | instance.componentDidMount();
41 | return instance.dom;
42 | } else {
43 | const componentVdom = vdom.type(props);
44 | return render(componentVdom, parent);
45 | }
46 | } else {
47 | throw new Error(`Invalid VDOM: ${vdom}.`);
48 | }
49 | };
50 |
51 | function isEventListenerAttr(key, value) {
52 | return typeof value == 'function' && key.startsWith('on');
53 | }
54 |
55 | function isStyleAttr(key, value) {
56 | return key == 'style' && typeof value == 'object';
57 | }
58 |
59 | function isPlainAttr(key, value) {
60 | return typeof value != 'object' && typeof value != 'function';
61 | }
62 |
63 | const setAttribute = (dom, key, value) => {
64 | if (isEventListenerAttr(key, value)) {
65 | const eventType = key.slice(2).toLowerCase();
66 | dom.addEventListener(eventType, value);
67 | } else if (isStyleAttr(key, value)) {
68 | Object.assign(dom.style, value);
69 | } else if (isPlainAttr(key, value)) {
70 | dom.setAttribute(key, value);
71 | }
72 | };
73 |
74 | const createElement = (type, props, ...children) => {
75 | if (props === null) props = {};
76 | return {
77 | type,
78 | props,
79 | children
80 | };
81 | };
82 |
83 | class Component {
84 | constructor(props) {
85 | this.props = props || {};
86 | this.state = null;
87 | }
88 |
89 | setState(nextState) {
90 | this.state = nextState;
91 | }
92 |
93 | componentWillMount() {
94 | return undefined;
95 | }
96 |
97 | componentDidMount() {
98 | return undefined;
99 | }
100 |
101 | }
--------------------------------------------------------------------------------
/render-component/dist/index.js:
--------------------------------------------------------------------------------
1 | function Item(props) {
2 | return createElement("li", {
3 | className: "item",
4 | style: props.style,
5 | onClick: props.onClick
6 | }, props.children);
7 | }
8 |
9 | class List extends Component {
10 | constructor(props) {
11 | super();
12 | this.state = {
13 | list: [{
14 | text: 'aaa',
15 | color: 'blue'
16 | }, {
17 | text: 'bbb',
18 | color: 'orange'
19 | }, {
20 | text: 'ccc',
21 | color: 'red'
22 | }],
23 | textColor: props.textColor
24 | };
25 | }
26 |
27 | render() {
28 | return createElement("ul", {
29 | className: "list"
30 | }, this.state.list.map((item, index) => {
31 | return createElement(Item, {
32 | style: {
33 | background: item.color,
34 | color: this.state.textColor
35 | },
36 | onClick: () => alert(item.text)
37 | }, item.text);
38 | }));
39 | }
40 |
41 | }
42 |
43 | render(createElement(List, {
44 | textColor: 'pink'
45 | }), document.getElementById('root'));
--------------------------------------------------------------------------------
/render-component/dong.js:
--------------------------------------------------------------------------------
1 |
2 | function isTextVdom(vdom) {
3 | return typeof vdom == 'string' || typeof vdom == 'number';
4 | }
5 |
6 | function isElementVdom(vdom) {
7 | return typeof vdom == 'object' && typeof vdom.type == 'string';
8 | }
9 |
10 | function isComponentVdom(vdom) {
11 | return typeof vdom.type == 'function';
12 | }
13 |
14 | const render = (vdom, parent = null) => {
15 | const mount = parent ? (el => parent.appendChild(el)) : (el => el);
16 | if (isTextVdom(vdom)) {
17 | return mount(document.createTextNode(vdom));
18 | } else if (isElementVdom(vdom)) {
19 | const dom = mount(document.createElement(vdom.type));
20 | for (const child of [].concat(...vdom.children)) {
21 | render(child, dom);
22 | }
23 | for (const prop in vdom.props) {
24 | setAttribute(dom, prop, vdom.props[prop]);
25 | }
26 | return dom;
27 | } else if (isComponentVdom(vdom)) {
28 | const props = Object.assign({}, vdom.props, {
29 | children: vdom.children
30 | });
31 |
32 | if (Component.isPrototypeOf(vdom.type)) {
33 | const instance = new vdom.type(props);
34 | instance.componentWillMount();
35 | const componentVdom = instance.render();
36 | instance.dom = render(componentVdom, parent);
37 | instance.componentDidMount();
38 | return instance.dom;
39 | } else {
40 | const componentVdom = vdom.type(props);
41 | return render(componentVdom, parent);
42 | }
43 | } else {
44 | throw new Error(`Invalid VDOM: ${vdom}.`);
45 | }
46 | };
47 |
48 | function isEventListenerAttr(key, value) {
49 | return typeof value == 'function' && key.startsWith('on');
50 | }
51 |
52 | function isStyleAttr(key, value) {
53 | return key == 'style' && typeof value == 'object';
54 | }
55 |
56 | function isPlainAttr(key, value) {
57 | return typeof value != 'object' && typeof value != 'function';
58 | }
59 |
60 | const setAttribute = (dom, key, value) => {
61 | if (isEventListenerAttr(key, value)) {
62 | const eventType = key.slice(2).toLowerCase();
63 | dom.addEventListener(eventType, value);
64 | } else if (isStyleAttr(key, value)) {
65 | Object.assign(dom.style, value);
66 | } else if (isPlainAttr(key, value)) {
67 | dom.setAttribute(key, value);
68 | }
69 | }
70 |
71 | const createElement = (type, props, ...children) => {
72 | if (props === null) props = {};
73 | return {type, props, children};
74 | };
75 |
76 | class Component {
77 | constructor(props) {
78 | this.props = props || {};
79 | this.state = null;
80 | }
81 |
82 | setState(nextState) {
83 | this.state = nextState;
84 | }
85 |
86 | componentWillMount() {
87 | return undefined;
88 | }
89 |
90 | componentDidMount() {
91 | return undefined;
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/render-component/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Document
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/render-component/index.js:
--------------------------------------------------------------------------------
1 | function Item(props) {
2 | return {props.children};
3 | }
4 |
5 | class List extends Component {
6 | constructor(props) {
7 | super();
8 | this.state = {
9 | list: [
10 | {
11 | text: 'aaa',
12 | color: 'blue'
13 | },
14 | {
15 | text: 'bbb',
16 | color: 'orange'
17 | },
18 | {
19 | text: 'ccc',
20 | color: 'red'
21 | }
22 | ],
23 | textColor: props.textColor
24 | }
25 | }
26 |
27 | render() {
28 | return
29 | {this.state.list.map((item, index) => {
30 | return - alert(item.text)}>{item.text}
31 | })}
32 |
;
33 | }
34 | }
35 |
36 | render(, document.getElementById('root'));
37 |
--------------------------------------------------------------------------------
/render-component/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "render-component",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1",
8 | "build": "babel dong.js index.js -d ./dist",
9 | "dev": "babel dong.js index.js -d ./dist --watch"
10 | },
11 | "keywords": [],
12 | "author": "",
13 | "license": "ISC",
14 | "devDependencies": {
15 | "@babel/cli": "^7.16.8",
16 | "@babel/core": "^7.16.10",
17 | "@babel/preset-react": "^7.16.7"
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/render-fiber/.babelrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: [
3 | [
4 | '@babel/preset-react',
5 | {
6 | pragma: 'Dong.createElement'
7 | }
8 | ]
9 | ]
10 | }
--------------------------------------------------------------------------------
/render-fiber/README.md:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/QuarkGluonPlasma/frontend-framework-exercize/0613f59a19c0c4539ff491a784ea6dab9e566258/render-fiber/README.md
--------------------------------------------------------------------------------
/render-fiber/dist/dong.js:
--------------------------------------------------------------------------------
1 | function createElement(type, props, ...children) {
2 | return {
3 | type,
4 | props: { ...props,
5 | children: children.map(child => typeof child === "object" ? child : createTextElement(child))
6 | }
7 | };
8 | }
9 |
10 | function createTextElement(text) {
11 | return {
12 | type: "TEXT_ELEMENT",
13 | props: {
14 | nodeValue: text,
15 | children: []
16 | }
17 | };
18 | }
19 |
20 | let nextFiberReconcileWork = null;
21 | let wipRoot = null;
22 |
23 | function workLoop(deadline) {
24 | let shouldYield = false;
25 |
26 | while (nextFiberReconcileWork && !shouldYield) {
27 | nextFiberReconcileWork = performNextWork(nextFiberReconcileWork);
28 | shouldYield = deadline.timeRemaining() < 1;
29 | }
30 |
31 | if (!nextFiberReconcileWork && wipRoot) {
32 | commitRoot();
33 | }
34 |
35 | requestIdleCallback(workLoop);
36 | }
37 |
38 | requestIdleCallback(workLoop);
39 |
40 | function render(element, container) {
41 | wipRoot = {
42 | dom: container,
43 | props: {
44 | children: [element]
45 | }
46 | };
47 | nextFiberReconcileWork = wipRoot;
48 | }
49 |
50 | function performNextWork(fiber) {
51 | reconcile(fiber);
52 |
53 | if (fiber.child) {
54 | return fiber.child;
55 | }
56 |
57 | let nextFiber = fiber;
58 |
59 | while (nextFiber) {
60 | if (nextFiber.sibling) {
61 | return nextFiber.sibling;
62 | }
63 |
64 | nextFiber = nextFiber.return;
65 | }
66 | }
67 |
68 | function reconcile(fiber) {
69 | if (!fiber.dom) {
70 | fiber.dom = createDom(fiber);
71 | }
72 |
73 | reconcileChildren(fiber, fiber.props.children);
74 | }
75 |
76 | function reconcileChildren(wipFiber, elements) {
77 | let index = 0;
78 | let prevSibling = null;
79 |
80 | while (index < elements.length) {
81 | const element = elements[index];
82 | let newFiber = {
83 | type: element.type,
84 | props: element.props,
85 | dom: null,
86 | return: wipFiber,
87 | effectTag: "PLACEMENT"
88 | };
89 |
90 | if (index === 0) {
91 | wipFiber.child = newFiber;
92 | } else if (element) {
93 | prevSibling.sibling = newFiber;
94 | }
95 |
96 | prevSibling = newFiber;
97 | index++;
98 | }
99 | }
100 |
101 | function commitRoot() {
102 | commitWork(wipRoot.child);
103 | wipRoot = null;
104 | }
105 |
106 | function commitWork(fiber) {
107 | if (!fiber) {
108 | return;
109 | }
110 |
111 | let domParentFiber = fiber.return;
112 |
113 | while (!domParentFiber.dom) {
114 | domParentFiber = domParentFiber.return;
115 | }
116 |
117 | const domParent = domParentFiber.dom;
118 |
119 | if (fiber.effectTag === "PLACEMENT" && fiber.dom != null) {
120 | domParent.appendChild(fiber.dom);
121 | }
122 |
123 | commitWork(fiber.child);
124 | commitWork(fiber.sibling);
125 | }
126 |
127 | function createDom(fiber) {
128 | const dom = fiber.type == "TEXT_ELEMENT" ? document.createTextNode("") : document.createElement(fiber.type);
129 |
130 | for (const prop in fiber.props) {
131 | setAttribute(dom, prop, fiber.props[prop]);
132 | }
133 |
134 | return dom;
135 | }
136 |
137 | function isEventListenerAttr(key, value) {
138 | return typeof value == 'function' && key.startsWith('on');
139 | }
140 |
141 | function isStyleAttr(key, value) {
142 | return key == 'style' && typeof value == 'object';
143 | }
144 |
145 | function isPlainAttr(key, value) {
146 | return typeof value != 'object' && typeof value != 'function';
147 | }
148 |
149 | const setAttribute = (dom, key, value) => {
150 | if (key === 'children') {
151 | return;
152 | }
153 |
154 | if (key === 'nodeValue') {
155 | dom.textContent = value;
156 | } else if (isEventListenerAttr(key, value)) {
157 | const eventType = key.slice(2).toLowerCase();
158 | dom.addEventListener(eventType, value);
159 | } else if (isStyleAttr(key, value)) {
160 | Object.assign(dom.style, value);
161 | } else if (isPlainAttr(key, value)) {
162 | dom.setAttribute(key, value);
163 | }
164 | };
165 |
166 | const Dong = {
167 | createElement,
168 | render
169 | };
--------------------------------------------------------------------------------
/render-fiber/dist/index.js:
--------------------------------------------------------------------------------
1 | const data = {
2 | item1: 'bb',
3 | item2: 'cc'
4 | };
5 | const jsx = Dong.createElement("ul", {
6 | className: "list"
7 | }, Dong.createElement("li", {
8 | className: "item",
9 | style: {
10 | background: 'blue',
11 | color: 'pink'
12 | },
13 | onClick: () => alert(2)
14 | }, "aa"), Dong.createElement("li", {
15 | className: "item"
16 | }, data.item1, Dong.createElement("i", null, "xxx")), Dong.createElement("li", {
17 | className: "item"
18 | }, data.item2));
19 | console.log(JSON.stringify(jsx, null, 4));
20 | Dong.render(jsx, document.getElementById("root"));
--------------------------------------------------------------------------------
/render-fiber/dong.js:
--------------------------------------------------------------------------------
1 | function createElement(type, props, ...children) {
2 | return {
3 | type,
4 | props: {
5 | ...props,
6 | children: children.map(child =>
7 | typeof child === "object"
8 | ? child
9 | : createTextElement(child)
10 | ),
11 | }
12 | }
13 | }
14 |
15 | function createTextElement(text) {
16 | return {
17 | type: "TEXT_ELEMENT",
18 | props: {
19 | nodeValue: text,
20 | children: [],
21 | },
22 | }
23 | }
24 |
25 |
26 | let nextFiberReconcileWork = null;
27 | let wipRoot = null;
28 |
29 | function workLoop(deadline) {
30 | let shouldYield = false;
31 | while (nextFiberReconcileWork && !shouldYield) {
32 | nextFiberReconcileWork = performNextWork(
33 | nextFiberReconcileWork
34 | );
35 | shouldYield = deadline.timeRemaining() < 1;
36 | }
37 |
38 | if (!nextFiberReconcileWork && wipRoot) {
39 | commitRoot();
40 | }
41 |
42 | requestIdleCallback(workLoop);
43 | }
44 |
45 | requestIdleCallback(workLoop);
46 |
47 |
48 |
49 | function render(element, container) {
50 | wipRoot = {
51 | dom: container,
52 | props: {
53 | children: [element],
54 | }
55 | }
56 | nextFiberReconcileWork = wipRoot
57 | }
58 |
59 |
60 |
61 | function performNextWork(fiber) {
62 |
63 | reconcile(fiber);
64 |
65 | if (fiber.child) {
66 | return fiber.child;
67 | }
68 | let nextFiber = fiber;
69 | while (nextFiber) {
70 | if (nextFiber.sibling) {
71 | return nextFiber.sibling;
72 | }
73 | nextFiber = nextFiber.return;
74 | }
75 | }
76 |
77 | function reconcile(fiber) {
78 | if (!fiber.dom) {
79 | fiber.dom = createDom(fiber)
80 | }
81 | reconcileChildren(fiber, fiber.props.children)
82 | }
83 |
84 |
85 | function reconcileChildren(wipFiber, elements) {
86 | let index = 0
87 | let prevSibling = null
88 |
89 | while (
90 | index < elements.length
91 | ) {
92 | const element = elements[index]
93 | let newFiber = {
94 | type: element.type,
95 | props: element.props,
96 | dom: null,
97 | return: wipFiber,
98 | effectTag: "PLACEMENT",
99 | }
100 |
101 | if (index === 0) {
102 | wipFiber.child = newFiber
103 | } else if (element) {
104 | prevSibling.sibling = newFiber
105 | }
106 |
107 | prevSibling = newFiber
108 | index++
109 | }
110 | }
111 |
112 | function commitRoot() {
113 | commitWork(wipRoot.child);
114 | wipRoot = null
115 | }
116 |
117 | function commitWork(fiber) {
118 | if (!fiber) {
119 | return
120 | }
121 |
122 | let domParentFiber = fiber.return
123 | while (!domParentFiber.dom) {
124 | domParentFiber = domParentFiber.return
125 | }
126 | const domParent = domParentFiber.dom
127 |
128 | if (
129 | fiber.effectTag === "PLACEMENT" &&
130 | fiber.dom != null
131 | ) {
132 | domParent.appendChild(fiber.dom)
133 | }
134 | commitWork(fiber.child)
135 | commitWork(fiber.sibling)
136 | }
137 |
138 |
139 | function createDom(fiber) {
140 | const dom = fiber.type == "TEXT_ELEMENT" ? document.createTextNode("") : document.createElement(fiber.type);
141 |
142 | for (const prop in fiber.props) {
143 | setAttribute(dom, prop, fiber.props[prop]);
144 | }
145 |
146 | return dom;
147 | }
148 |
149 | function isEventListenerAttr(key, value) {
150 | return typeof value == 'function' && key.startsWith('on');
151 | }
152 |
153 | function isStyleAttr(key, value) {
154 | return key == 'style' && typeof value == 'object';
155 | }
156 |
157 | function isPlainAttr(key, value) {
158 | return typeof value != 'object' && typeof value != 'function';
159 | }
160 |
161 | const setAttribute = (dom, key, value) => {
162 | if (key === 'children') {
163 | return;
164 | }
165 |
166 | if (key === 'nodeValue') {
167 | dom.textContent = value;
168 | } else if (isEventListenerAttr(key, value)) {
169 | const eventType = key.slice(2).toLowerCase();
170 | dom.addEventListener(eventType, value);
171 | } else if (isStyleAttr(key, value)) {
172 | Object.assign(dom.style, value);
173 | } else if (isPlainAttr(key, value)) {
174 | dom.setAttribute(key, value);
175 | }
176 | };
177 |
178 |
179 | const Dong = {
180 | createElement,
181 | render
182 | }
183 |
--------------------------------------------------------------------------------
/render-fiber/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Document
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/render-fiber/index.js:
--------------------------------------------------------------------------------
1 | const data = {
2 | item1: 'bb',
3 | item2: 'cc'
4 | }
5 |
6 | const jsx =
7 | - alert(2)}>aa
8 | - {data.item1}xxx
9 | - {data.item2}
10 |
;
11 |
12 | console.log(JSON.stringify(jsx, null, 4));
13 |
14 | Dong.render(jsx, document.getElementById("root"));
15 |
--------------------------------------------------------------------------------
/render-fiber/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "render-fiber",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": ".babelrc.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1",
8 | "build": "babel dong.js index.js -d ./dist",
9 | "dev": "babel dong.js index.js -d ./dist --watch"
10 | },
11 | "keywords": [],
12 | "author": "",
13 | "license": "ISC",
14 | "devDependencies": {
15 | "@babel/cli": "^7.16.8",
16 | "@babel/core": "^7.16.10",
17 | "@babel/preset-react": "^7.16.7"
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/render-jsx/.babelrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: [
3 | [
4 | '@babel/preset-react',
5 | {
6 | pragma: 'createElement'
7 | }
8 | ]
9 | ]
10 | }
--------------------------------------------------------------------------------
/render-jsx/README.md:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/QuarkGluonPlasma/frontend-framework-exercize/0613f59a19c0c4539ff491a784ea6dab9e566258/render-jsx/README.md
--------------------------------------------------------------------------------
/render-jsx/dist/dong.js:
--------------------------------------------------------------------------------
1 | function isTextVdom(vdom) {
2 | return typeof vdom == 'string' || typeof vdom == 'number';
3 | }
4 |
5 | function isElementVdom(vdom) {
6 | return typeof vdom == 'object' && typeof vdom.type == 'string';
7 | }
8 |
9 | const render = (vdom, parent = null) => {
10 | const mount = parent ? el => parent.appendChild(el) : el => el;
11 |
12 | if (isTextVdom(vdom)) {
13 | return mount(document.createTextNode(vdom));
14 | } else if (isElementVdom(vdom)) {
15 | const dom = mount(document.createElement(vdom.type));
16 |
17 | for (const child of vdom.children) {
18 | render(child, dom);
19 | }
20 |
21 | for (const prop in vdom.props) {
22 | setAttribute(dom, prop, vdom.props[prop]);
23 | }
24 |
25 | return dom;
26 | } else {
27 | throw new Error(`Invalid VDOM: ${vdom}.`);
28 | }
29 | };
30 |
31 | function isEventListenerAttr(key, value) {
32 | return typeof value == 'function' && key.startsWith('on');
33 | }
34 |
35 | function isStyleAttr(key, value) {
36 | return key == 'style' && typeof value == 'object';
37 | }
38 |
39 | function isPlainAttr(key, value) {
40 | return typeof value != 'object' && typeof value != 'function';
41 | }
42 |
43 | const setAttribute = (dom, key, value) => {
44 | if (isEventListenerAttr(key, value)) {
45 | const eventType = key.slice(2).toLowerCase();
46 | dom.addEventListener(eventType, value);
47 | } else if (isStyleAttr(key, value)) {
48 | Object.assign(dom.style, value);
49 | } else if (isPlainAttr(key, value)) {
50 | dom.setAttribute(key, value);
51 | }
52 | };
53 |
54 | const createElement = (type, props, ...children) => {
55 | if (props === null) props = {};
56 | return {
57 | type,
58 | props,
59 | children
60 | };
61 | };
--------------------------------------------------------------------------------
/render-jsx/dist/index.js:
--------------------------------------------------------------------------------
1 | const data = {
2 | item1: 'bbb',
3 | item2: 'ddd'
4 | };
5 | const jsx = createElement("ul", {
6 | className: "list"
7 | }, createElement("li", {
8 | className: "item",
9 | style: {
10 | background: 'blue',
11 | color: 'pink'
12 | },
13 | onClick: () => alert(2)
14 | }, "aaa"), createElement("li", {
15 | className: "item"
16 | }, data.item1, createElement("i", null, "aaa")), createElement("li", {
17 | className: "item"
18 | }, data.item2));
19 | render(jsx, document.getElementById('root'));
--------------------------------------------------------------------------------
/render-jsx/dong.js:
--------------------------------------------------------------------------------
1 |
2 | function isTextVdom(vdom) {
3 | return typeof vdom == 'string' || typeof vdom == 'number';
4 | }
5 |
6 | function isElementVdom(vdom) {
7 | return typeof vdom == 'object' && typeof vdom.type == 'string';
8 | }
9 |
10 | const render = (vdom, parent = null) => {
11 | const mount = parent ? (el => parent.appendChild(el)) : (el => el);
12 | if (isTextVdom(vdom)) {
13 | return mount(document.createTextNode(vdom));
14 | } else if (isElementVdom(vdom)) {
15 | const dom = mount(document.createElement(vdom.type));
16 | for (const child of [].concat(...vdom.children)) {// children 元素也是 数组,要拍平
17 | render(child, dom);
18 | }
19 | for (const prop in vdom.props) {
20 | setAttribute(dom, prop, vdom.props[prop]);
21 | }
22 | return dom;
23 | } else {
24 | throw new Error(`Invalid VDOM: ${vdom}.`);
25 | }
26 | };
27 |
28 | function isEventListenerAttr(key, value) {
29 | return typeof value == 'function' && key.startsWith('on');
30 | }
31 |
32 | function isStyleAttr(key, value) {
33 | return key == 'style' && typeof value == 'object';
34 | }
35 |
36 | function isPlainAttr(key, value) {
37 | return typeof value != 'object' && typeof value != 'function';
38 | }
39 |
40 | const setAttribute = (dom, key, value) => {
41 | if (isEventListenerAttr(key, value)) {
42 | const eventType = key.slice(2).toLowerCase();
43 | dom.addEventListener(eventType, value);
44 | } else if (isStyleAttr(key, value)) {
45 | Object.assign(dom.style, value);
46 | } else if (isPlainAttr(key, value)) {
47 | dom.setAttribute(key, value);
48 | }
49 | }
50 |
51 | const createElement = (type, props, ...children) => {
52 | if (props === null) props = {};
53 | return {type, props, children};
54 | };
55 |
56 |
--------------------------------------------------------------------------------
/render-jsx/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Document
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/render-jsx/index.js:
--------------------------------------------------------------------------------
1 | const data = {
2 | item1: 'bbb',
3 | item2: 'ddd'
4 | }
5 | const jsx =
6 | - alert(2)}>aaa
7 | - {data.item1}aaa
8 | - {data.item2}
9 |
10 |
11 | render(jsx, document.getElementById('root'));
12 |
13 |
14 | const jsx =
15 | - alert(2)}>aaa
16 | - bbb
17 |
18 |
19 | const vdom = {
20 | type: 'ul',
21 | props: {
22 | className: 'list',
23 | children: [
24 | {
25 | type: 'li',
26 | props: {
27 | className: 'item',
28 | style: {
29 | background: 'blue',
30 | color: 'pink'
31 | },
32 | onClick: () => alert(2),
33 | children: [
34 | 'aaa'
35 | ]
36 | }
37 | },
38 | {
39 | type: 'li',
40 | props: {
41 | className: 'item',
42 | children: ['bbb']
43 | }
44 | }
45 | ]
46 | }
47 | };
48 |
49 |
50 | const fiberRoot = vdom;
51 |
52 | let currentFiber = fiberRoot;
53 |
54 |
55 |
56 | function reconcileChildren(wipFiber, elements) {
57 | let index = 0
58 | let oldFiber =
59 | wipFiber.alternate && wipFiber.alternate.child
60 | let prevSibling = null
61 |
62 | while (
63 | index < elements.length ||
64 | oldFiber != null
65 | ) {
66 | const element = elements[index]
67 | let newFiber = null
68 |
69 | const sameType =
70 | oldFiber &&
71 | element &&
72 | element.type == oldFiber.type
73 |
74 | if (sameType) {
75 | newFiber = {
76 | type: oldFiber.type,
77 | props: element.props,
78 | dom: oldFiber.dom,
79 | parent: wipFiber,
80 | alternate: oldFiber,
81 | effectTag: "UPDATE",
82 | }
83 | }
84 | if (element && !sameType) {
85 | newFiber = {
86 | type: element.type,
87 | props: element.props,
88 | dom: null,
89 | parent: wipFiber,
90 | alternate: null,
91 | effectTag: "PLACEMENT",
92 | }
93 | }
94 | if (oldFiber && !sameType) {
95 | oldFiber.effectTag = "DELETION"
96 | deletions.push(oldFiber)
97 | }
98 |
99 | if (oldFiber) {
100 | oldFiber = oldFiber.sibling
101 | }
102 |
103 | if (index === 0) {
104 | wipFiber.child = newFiber
105 | } else if (element) {
106 | prevSibling.sibling = newFiber
107 | }
108 |
109 | prevSibling = newFiber
110 | index++
111 | }
112 | }
113 |
--------------------------------------------------------------------------------
/render-jsx/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "render-jsx",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": ".babelrc.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1",
8 | "build": "babel dong.js index.js -d ./dist",
9 | "dev": "babel dong.js index.js -d ./dist --watch"
10 | },
11 | "keywords": [],
12 | "author": "",
13 | "license": "ISC",
14 | "devDependencies": {
15 | "@babel/cli": "^7.16.8",
16 | "@babel/core": "^7.16.10",
17 | "@babel/preset-react": "^7.16.7"
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/render-vdom/dong.js:
--------------------------------------------------------------------------------
1 |
2 | function isTextVdom(vdom) {
3 | return typeof vdom == 'string' || typeof vdom == 'number';
4 | }
5 |
6 | function isElementVdom(vdom) {
7 | return typeof vdom == 'object' && typeof vdom.type == 'string';
8 | }
9 |
10 | const render = (vdom, parent = null) => {
11 | const mount = parent ? (el => parent.appendChild(el)) : (el => el);
12 | if (isTextVdom(vdom)) {
13 | return mount(document.createTextNode(vdom));
14 | } else if (isElementVdom(vdom)) {
15 | const dom = mount(document.createElement(vdom.type));
16 | for (const child of vdom.children) {
17 | render(child, dom);
18 | }
19 | for (const prop in vdom.props) {
20 | setAttribute(dom, prop, vdom.props[prop]);
21 | }
22 | return dom;
23 | } else {
24 | throw new Error(`Invalid VDOM: ${vdom}.`);
25 | }
26 | };
27 |
28 | function isEventListenerAttr(key, value) {
29 | return typeof value == 'function' && key.startsWith('on');
30 | }
31 |
32 | function isStyleAttr(key, value) {
33 | return key == 'style' && typeof value == 'object';
34 | }
35 |
36 | function isPlainAttr(key, value) {
37 | return typeof value != 'object' && typeof value != 'function';
38 | }
39 |
40 | const setAttribute = (dom, key, value) => {
41 | if (isEventListenerAttr(key, value)) {
42 | const eventType = key.slice(2).toLowerCase();
43 | dom.addEventListener(eventType, value);
44 | } else if (isStyleAttr(key, value)) {
45 | Object.assign(dom.style, value);
46 | } else if (isPlainAttr(key, value)) {
47 | dom.setAttribute(key, value);
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/render-vdom/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Document
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/render-vdom/index.js:
--------------------------------------------------------------------------------
1 |
2 | const vdom = {
3 | type: 'ul',
4 | props: {
5 | className: 'list'
6 | },
7 | children: [
8 | {
9 | type: 'li',
10 | props: {
11 | className: 'item',
12 | style: {
13 | background: 'blue',
14 | color: '#fff'
15 | },
16 | onClick: function() {
17 | alert(1);
18 | }
19 | },
20 | children: [
21 | 'aaaa'
22 | ]
23 | },
24 | {
25 | type: 'li',
26 | props: {
27 | className: 'item'
28 | },
29 | children: [
30 | 'bbbbddd'
31 | ]
32 | },
33 | {
34 | type: 'li',
35 | props: {
36 | className: 'item'
37 | },
38 | children: [
39 | 'cccc'
40 | ]
41 | }
42 | ]
43 | };
44 |
45 | render(vdom, document.getElementById('root'));
46 |
--------------------------------------------------------------------------------
/render-vdom/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "render-vdom",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "dong.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1"
8 | },
9 | "keywords": [],
10 | "author": "",
11 | "license": "ISC"
12 | }
13 |
--------------------------------------------------------------------------------