├── vite.config.js
├── README.md
├── package.json
├── index.html
├── LICENSE
├── .gitignore
├── yarn.lock
└── index.js
/vite.config.js:
--------------------------------------------------------------------------------
1 | import { defineConfig } from "vite";
2 |
3 | // https://vitejs.dev/config/
4 | export default defineConfig({});
5 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # mini-vue
2 | 尤雨溪用 200 行写的 mini vue。
3 |
4 | ## Quick start
5 |
6 | ```
7 | yarn
8 |
9 | yarn dev
10 | ```
11 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "scripts": {
3 | "dev": "vite"
4 | },
5 | "devDependencies": {
6 | "vite": "^2.3.6"
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Vue
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 liuwei
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | lerna-debug.log*
8 |
9 | # Diagnostic reports (https://nodejs.org/api/report.html)
10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
11 |
12 | # Runtime data
13 | pids
14 | *.pid
15 | *.seed
16 | *.pid.lock
17 |
18 | # Directory for instrumented libs generated by jscoverage/JSCover
19 | lib-cov
20 |
21 | # Coverage directory used by tools like istanbul
22 | coverage
23 | *.lcov
24 |
25 | # nyc test coverage
26 | .nyc_output
27 |
28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
29 | .grunt
30 |
31 | # Bower dependency directory (https://bower.io/)
32 | bower_components
33 |
34 | # node-waf configuration
35 | .lock-wscript
36 |
37 | # Compiled binary addons (https://nodejs.org/api/addons.html)
38 | build/Release
39 |
40 | # Dependency directories
41 | node_modules/
42 | jspm_packages/
43 |
44 | # TypeScript v1 declaration files
45 | typings/
46 |
47 | # TypeScript cache
48 | *.tsbuildinfo
49 |
50 | # Optional npm cache directory
51 | .npm
52 |
53 | # Optional eslint cache
54 | .eslintcache
55 |
56 | # Microbundle cache
57 | .rpt2_cache/
58 | .rts2_cache_cjs/
59 | .rts2_cache_es/
60 | .rts2_cache_umd/
61 |
62 | # Optional REPL history
63 | .node_repl_history
64 |
65 | # Output of 'npm pack'
66 | *.tgz
67 |
68 | # Yarn Integrity file
69 | .yarn-integrity
70 |
71 | # dotenv environment variables file
72 | .env
73 | .env.test
74 |
75 | # parcel-bundler cache (https://parceljs.org/)
76 | .cache
77 |
78 | # Next.js build output
79 | .next
80 |
81 | # Nuxt.js build / generate output
82 | .nuxt
83 | dist
84 |
85 | # Gatsby files
86 | .cache/
87 | # Comment in the public line in if your project uses Gatsby and *not* Next.js
88 | # https://nextjs.org/blog/next-9-1#public-directory-support
89 | # public
90 |
91 | # vuepress build output
92 | .vuepress/dist
93 |
94 | # Serverless directories
95 | .serverless/
96 |
97 | # FuseBox cache
98 | .fusebox/
99 |
100 | # DynamoDB Local files
101 | .dynamodb/
102 |
103 | # TernJS port file
104 | .tern-port
105 |
--------------------------------------------------------------------------------
/yarn.lock:
--------------------------------------------------------------------------------
1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
2 | # yarn lockfile v1
3 |
4 |
5 | colorette@^1.2.2:
6 | version "1.2.2"
7 | resolved "https://registry.yarnpkg.com/colorette/-/colorette-1.2.2.tgz#cbcc79d5e99caea2dbf10eb3a26fd8b3e6acfa94"
8 | integrity sha512-MKGMzyfeuutC/ZJ1cba9NqcNpfeqMUcYmyF1ZFY6/Cn7CNSAKx6a+s48sqLqyAiZuaP2TcqMhoo+dlwFnVxT9w==
9 |
10 | esbuild@^0.12.5:
11 | version "0.12.6"
12 | resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.12.6.tgz#85bc755c7cf3005d4f34b4f10f98049ce0ee67ce"
13 | integrity sha512-RDvVLvAjsq/kIZJoneMiUOH7EE7t2QaW7T3Q7EdQij14+bZbDq5sndb0tTanmHIFSqZVMBMMyqzVHkS3dJobeA==
14 |
15 | fsevents@~2.3.1:
16 | version "2.3.2"
17 | resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a"
18 | integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==
19 |
20 | function-bind@^1.1.1:
21 | version "1.1.1"
22 | resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d"
23 | integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==
24 |
25 | has@^1.0.3:
26 | version "1.0.3"
27 | resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796"
28 | integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==
29 | dependencies:
30 | function-bind "^1.1.1"
31 |
32 | is-core-module@^2.2.0:
33 | version "2.4.0"
34 | resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.4.0.tgz#8e9fc8e15027b011418026e98f0e6f4d86305cc1"
35 | integrity sha512-6A2fkfq1rfeQZjxrZJGerpLCTHRNEBiSgnu0+obeJpEPZRUooHgsizvzv0ZjJwOz3iWIHdJtVWJ/tmPr3D21/A==
36 | dependencies:
37 | has "^1.0.3"
38 |
39 | nanoid@^3.1.23:
40 | version "3.1.23"
41 | resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.1.23.tgz#f744086ce7c2bc47ee0a8472574d5c78e4183a81"
42 | integrity sha512-FiB0kzdP0FFVGDKlRLEQ1BgDzU87dy5NnzjeW9YZNt+/c3+q82EQDUwniSAUxp/F0gFNI1ZhKU1FqYsMuqZVnw==
43 |
44 | path-parse@^1.0.6:
45 | version "1.0.7"
46 | resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735"
47 | integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==
48 |
49 | postcss@^8.2.10:
50 | version "8.3.0"
51 | resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.3.0.tgz#b1a713f6172ca427e3f05ef1303de8b65683325f"
52 | integrity sha512-+ogXpdAjWGa+fdYY5BQ96V/6tAo+TdSSIMP5huJBIygdWwKtVoB5JWZ7yUd4xZ8r+8Kvvx4nyg/PQ071H4UtcQ==
53 | dependencies:
54 | colorette "^1.2.2"
55 | nanoid "^3.1.23"
56 | source-map-js "^0.6.2"
57 |
58 | resolve@^1.19.0:
59 | version "1.20.0"
60 | resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.20.0.tgz#629a013fb3f70755d6f0b7935cc1c2c5378b1975"
61 | integrity sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==
62 | dependencies:
63 | is-core-module "^2.2.0"
64 | path-parse "^1.0.6"
65 |
66 | rollup@^2.38.5:
67 | version "2.50.6"
68 | resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.50.6.tgz#24e2211caf9031081656e98a5e5e94d3b5e786e2"
69 | integrity sha512-6c5CJPLVgo0iNaZWWliNu1Kl43tjP9LZcp6D/tkf2eLH2a9/WeHxg9vfTFl8QV/2SOyaJX37CEm9XuGM0rviUg==
70 | optionalDependencies:
71 | fsevents "~2.3.1"
72 |
73 | source-map-js@^0.6.2:
74 | version "0.6.2"
75 | resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-0.6.2.tgz#0bb5de631b41cfbda6cfba8bd05a80efdfd2385e"
76 | integrity sha512-/3GptzWzu0+0MBQFrDKzw/DvvMTUORvgY6k6jd/VS6iCR4RDTKWH6v6WPwQoUO8667uQEf9Oe38DxAYWY5F/Ug==
77 |
78 | vite@^2.3.6:
79 | version "2.3.6"
80 | resolved "https://registry.yarnpkg.com/vite/-/vite-2.3.6.tgz#1f7cfde88a51a802d69000c7bac85d481c2e871c"
81 | integrity sha512-fsEpNKDHgh3Sn66JH06ZnUBnIgUVUtw6ucDhlOj1CEqxIkymU25yv1/kWDPlIjyYHnalr0cN6V+zzUJ+fmWHYw==
82 | dependencies:
83 | esbuild "^0.12.5"
84 | postcss "^8.2.10"
85 | resolve "^1.19.0"
86 | rollup "^2.38.5"
87 | optionalDependencies:
88 | fsevents "~2.3.1"
89 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | let activeEffect;
2 |
3 | class Dep {
4 | // imeplement this
5 | subscribers = new Set();
6 |
7 | constructor(value) {
8 | this._value = value;
9 | }
10 |
11 | get value() {
12 | this.depend();
13 | return this._value;
14 | }
15 |
16 | set value(value) {
17 | this._value = value;
18 | this.notify();
19 | }
20 |
21 | depend() {
22 | if (activeEffect) {
23 | this.subscribers.add(activeEffect);
24 | }
25 | }
26 |
27 | notify() {
28 | this.subscribers.forEach((effect) => {
29 | effect();
30 | });
31 | }
32 | }
33 |
34 | function watchEffect(effect) {
35 | activeEffect = effect;
36 | effect();
37 | activeEffect = null;
38 | }
39 |
40 | // proxy version
41 | const reactiveHandlers = {
42 | get(target, key) {
43 | // how do we get the dep for this key?
44 | const value = getDep(target, key).value;
45 | if (value && typeof value === "object") {
46 | return reactive(value);
47 | } else {
48 | return value;
49 | }
50 | },
51 | set(target, key, value) {
52 | getDep(target, key).value = value;
53 | },
54 | };
55 |
56 | const targetToHashMap = new WeakMap();
57 |
58 | function getDep(target, key) {
59 | let depMap = targetToHashMap.get(target);
60 | if (!depMap) {
61 | depMap = new Map();
62 | targetToHashMap.set(target, depMap);
63 | }
64 |
65 | let dep = depMap.get(key);
66 | if (!dep) {
67 | dep = new Dep(target[key]);
68 | depMap.set(key, dep);
69 | }
70 |
71 | return dep;
72 | }
73 |
74 | function reactive(obj) {
75 | return new Proxy(obj, reactiveHandlers);
76 | }
77 |
78 | function h(tag, props, children) {
79 | return { tag, props, children };
80 | }
81 |
82 | function mount(vnode, container, anchor) {
83 | const el = document.createElement(vnode.tag);
84 | vnode.el = el;
85 | // props
86 | if (vnode.props) {
87 | for (const key in vnode.props) {
88 | if (key.startsWith("on")) {
89 | el.addEventListener(key.slice(2).toLowerCase(), vnode.props[key]);
90 | } else {
91 | el.setAttribute(key, vnode.props[key]);
92 | }
93 | }
94 | }
95 | if (vnode.children) {
96 | if (typeof vnode.children === "string") {
97 | el.textContent = vnode.children;
98 | } else {
99 | vnode.children.forEach((child) => {
100 | mount(child, el);
101 | });
102 | }
103 | }
104 | if (anchor) {
105 | container.insertBefore(el, anchor);
106 | } else {
107 | container.appendChild(el);
108 | }
109 | }
110 |
111 | function patch(n1, n2) {
112 | // Implement this
113 | // 1. check if n1 and n2 are of the same type
114 | if (n1.tag !== n2.tag) {
115 | // 2. if not, replace
116 | const parent = n1.el.parentNode;
117 | const anchor = n1.el.nextSibling;
118 | parent.removeChild(n1.el);
119 | mount(n2, parent, anchor);
120 | return;
121 | }
122 |
123 | const el = (n2.el = n1.el);
124 |
125 | // 3. if yes
126 | // 3.1 diff props
127 | const oldProps = n1.props || {};
128 | const newProps = n2.props || {};
129 | for (const key in newProps) {
130 | const newValue = newProps[key];
131 | const oldValue = oldProps[key];
132 | if (newValue !== oldValue) {
133 | if (newValue != null) {
134 | el.setAttribute(key, newValue);
135 | } else {
136 | el.removeAttribute(key);
137 | }
138 | }
139 | }
140 | for (const key in oldProps) {
141 | if (!(key in newProps)) {
142 | el.removeAttribute(key);
143 | }
144 | }
145 | // 3.2 diff children
146 | const oc = n1.children;
147 | const nc = n2.children;
148 | if (typeof nc === "string") {
149 | if (nc !== oc) {
150 | el.textContent = nc;
151 | }
152 | } else if (Array.isArray(nc)) {
153 | if (Array.isArray(oc)) {
154 | // array diff
155 | const commonLength = Math.min(oc.length, nc.length);
156 | for (let i = 0; i < commonLength; i++) {
157 | patch(oc[i], nc[i]);
158 | }
159 | if (nc.length > oc.length) {
160 | nc.slice(oc.length).forEach((c) => mount(c, el));
161 | } else if (oc.length > nc.length) {
162 | oc.slice(nc.length).forEach((c) => {
163 | el.removeChild(c.el);
164 | });
165 | }
166 | } else {
167 | el.innerHTML = "";
168 | nc.forEach((c) => mount(c, el));
169 | }
170 | }
171 | }
172 |
173 | function patch(n1, n2) {
174 | // Implement this
175 | // 1. check if n1 and n2 are of the same type
176 | if (n1.tag !== n2.tag) {
177 | // 2. if not, replace
178 | const parent = n1.el.parentNode;
179 | const anchor = n1.el.nextSibling;
180 | parent.removeChild(n1.el);
181 | mount(n2, parent, anchor);
182 | return;
183 | }
184 |
185 | const el = (n2.el = n1.el);
186 |
187 | // 3. if yes
188 | // 3.1 diff props
189 | const oldProps = n1.props || {};
190 | const newProps = n2.props || {};
191 | for (const key in newProps) {
192 | const newValue = newProps[key];
193 | const oldValue = oldProps[key];
194 | if (newValue !== oldValue) {
195 | if (newValue != null) {
196 | el.setAttribute(key, newValue);
197 | } else {
198 | el.removeAttribute(key);
199 | }
200 | }
201 | }
202 | for (const key in oldProps) {
203 | if (!(key in newProps)) {
204 | el.removeAttribute(key);
205 | }
206 | }
207 | // 3.2 diff children
208 | const oc = n1.children;
209 | const nc = n2.children;
210 | if (typeof nc === "string") {
211 | if (nc !== oc) {
212 | el.textContent = nc;
213 | }
214 | } else if (Array.isArray(nc)) {
215 | if (Array.isArray(oc)) {
216 | // array diff
217 | const commonLength = Math.min(oc.length, nc.length);
218 | for (let i = 0; i < commonLength; i++) {
219 | patch(oc[i], nc[i]);
220 | }
221 | if (nc.length > oc.length) {
222 | nc.slice(oc.length).forEach((c) => mount(c, el));
223 | } else if (oc.length > nc.length) {
224 | oc.slice(nc.length).forEach((c) => {
225 | el.removeChild(c.el);
226 | });
227 | }
228 | } else {
229 | el.innerHTML = "";
230 | nc.forEach((c) => mount(c, el));
231 | }
232 | }
233 | }
234 |
235 | function createApp(Component, container) {
236 | // implement this
237 | const state = reactive(Component.data());
238 | let isMount = true;
239 | let prevTree;
240 | watchEffect(() => {
241 | const tree = Component.render.call(state);
242 | if (isMount) {
243 | mount(tree, container);
244 | isMount = false;
245 | } else {
246 | patch(prevTree, tree);
247 | }
248 | prevTree = tree;
249 | });
250 | }
251 |
252 | const Component = {
253 | data() {
254 | return {
255 | count: 0,
256 | };
257 | },
258 | render() {
259 | return h(
260 | "button",
261 | {
262 | onClick: () => {
263 | this.count++;
264 | },
265 | },
266 | String(this.count)
267 | );
268 | },
269 | };
270 |
271 | // calling this should actually mount the component.
272 | createApp(Component, document.getElementById("app"));
273 |
--------------------------------------------------------------------------------