├── .gitignore ├── .vscode └── settings.json ├── LICENSE ├── README.md ├── README_EN.md ├── docs ├── .vitepress │ ├── config.ts │ └── theme │ │ ├── components │ │ ├── AsideSponsors.vue │ │ └── HomeSponsors.vue │ │ ├── composables │ │ └── sponsor.ts │ │ ├── index.ts │ │ └── styles │ │ └── vars.css ├── blog │ └── announcing-vite2.md ├── compiler-core │ └── index.md ├── images │ ├── bundler.png │ ├── esm.png │ ├── graph.png │ ├── reactive.jpg │ ├── vercel-configuration.png │ └── vite-plugin-inspect.png ├── index.md ├── public │ ├── _headers │ ├── cypress.svg │ ├── divriots.png │ ├── logo-with-shadow.png │ ├── logo-with-shadow.svg │ ├── logo.svg │ ├── mux.svg │ ├── og-image.png │ ├── plaid.svg │ ├── stackblitz.svg │ ├── tailwind-labs.svg │ ├── vite.mp3 │ ├── voice.svg │ └── vuejobs.png ├── reactivity │ ├── a.md │ ├── api-hmr.md │ ├── api-javascript.md │ ├── api-plugin.md │ ├── assets.md │ ├── b.md │ ├── backend-integration.md │ ├── build.md │ ├── comparisons.md │ ├── dep-pre-bundling.md │ ├── env-and-mode.md │ ├── features.md │ ├── index.md │ ├── migration.md │ ├── ssr.md │ ├── static-deploy.md │ ├── using-plugins.md │ └── why.md ├── runtime-core │ ├── build-options.md │ ├── dep-optimization-options.md │ ├── index.md │ ├── preview-options.md │ ├── server-options.md │ ├── shared-options.md │ ├── ssr-options.md │ └── worker-options.md ├── runtime-dom │ └── index.md └── vite.config.ts ├── example ├── app.js ├── foo.js ├── index.html └── main.js ├── index.ts ├── jest.config.js ├── netlify.toml ├── package.json ├── packages ├── compiler-core │ ├── __tests__ │ │ ├── __snapshots__ │ │ │ └── codegen.spec.ts.snap │ │ ├── codegen.spec.ts │ │ ├── parse.spec.ts │ │ └── transform.spec.ts │ ├── package.json │ ├── src │ │ ├── ast.ts │ │ ├── codegen.ts │ │ ├── compile.ts │ │ ├── index.ts │ │ ├── parse.ts │ │ ├── runtimeHelpers.ts │ │ ├── transform.ts │ │ ├── transforms │ │ │ ├── transformElement.ts │ │ │ ├── transformExpression.ts │ │ │ └── transformText.ts │ │ └── utils.ts │ └── tsup.config.ts ├── reactivity │ ├── .turbo │ │ ├── turbo-build.log │ │ └── turbo-dev.log │ ├── __tests__ │ │ ├── computed.spec.ts │ │ ├── dep.spec.ts │ │ ├── effect.spec.ts │ │ ├── reactive.spec.ts │ │ ├── readonly.spec.ts │ │ ├── ref.spec.ts │ │ └── shallowReadonly.spec.ts │ ├── dist │ │ ├── index.global.js │ │ ├── index.global.js.map │ │ ├── index.js │ │ ├── index.js.map │ │ ├── index.mjs │ │ └── index.mjs.map │ ├── package.json │ ├── src │ │ ├── baseHandlers.ts │ │ ├── computed.ts │ │ ├── dep.ts │ │ ├── effect.ts │ │ ├── index.ts │ │ ├── reactive.ts │ │ └── ref.ts │ └── tsup.config.ts ├── runtime-core │ ├── __tests__ │ │ ├── apiWatch.spec.ts │ │ ├── componentEmits.spec.ts │ │ ├── rendererComponent.spec.ts │ │ └── rendererElement.spec.ts │ ├── package.json │ ├── src │ │ ├── apiInject.ts │ │ ├── apiWatch.ts │ │ ├── component.ts │ │ ├── componentEmits.ts │ │ ├── componentProps.ts │ │ ├── componentPublicInstance.ts │ │ ├── componentRenderUtils.ts │ │ ├── componentSlots.ts │ │ ├── createApp.ts │ │ ├── h.ts │ │ ├── helpers │ │ │ └── renderSlot.ts │ │ ├── index.ts │ │ ├── renderer.ts │ │ ├── scheduler.ts │ │ ├── shapeFlag.ts │ │ └── vnode.ts │ └── tsup.config.ts ├── runtime-dom │ ├── package.json │ ├── src │ │ └── index.ts │ └── tsup.config.ts ├── runtime-test │ ├── src │ │ ├── index.ts │ │ ├── nodeOps.ts │ │ ├── patchProp.ts │ │ └── serialize.ts │ └── tsup.config.ts ├── shared │ ├── dist │ │ ├── index.global.js │ │ ├── index.global.js.map │ │ ├── index.js │ │ ├── index.js.map │ │ ├── index.mjs │ │ └── index.mjs.map │ ├── package.json │ ├── src │ │ ├── index.ts │ │ ├── shapeFlags.ts │ │ └── toDisplayString.ts │ └── tsup.config.ts └── vue │ ├── .DS_Store │ ├── dist │ ├── mini-vue.cjs.js │ ├── mini-vue.cjs.js.map │ ├── mini-vue.esm-bundler.js │ └── mini-vue.esm-bundler.js.map │ ├── example │ ├── apiInject │ │ ├── App.js │ │ └── index.html │ ├── compiler-base │ │ ├── App.js │ │ └── index.html │ ├── componentEmit.js │ │ ├── App.js │ │ ├── Child.js │ │ └── index.html │ ├── componentProxy │ │ ├── App.js │ │ ├── Child.js │ │ └── index.html │ ├── componentUpdate │ │ ├── App.js │ │ ├── Child.js │ │ └── index.html │ ├── createTextVnode │ │ ├── App.js │ │ └── index.html │ ├── customRenderer │ │ ├── App.js │ │ ├── game.js │ │ ├── index.html │ │ ├── main.js │ │ └── renderer.js │ ├── getCurrentInstance │ │ ├── App.js │ │ └── index.html │ ├── helloWorld │ │ ├── App.js │ │ ├── index.html │ │ └── main.js │ ├── nextTicker │ │ ├── App.js │ │ ├── NextTicker.js │ │ ├── index.html │ │ └── main.js │ ├── patchChildren │ │ ├── App.js │ │ ├── PatchChildren.js │ │ ├── index.html │ │ └── main.js │ ├── renderComponent │ │ ├── App.js │ │ ├── Child.js │ │ └── index.html │ ├── setupStateRenderComponent │ │ ├── App.js │ │ └── index.html │ └── slotsComponent │ │ ├── App.js │ │ ├── Child.js │ │ └── index.html │ ├── index.js │ ├── package.json │ ├── src │ └── index.ts │ └── tsup.config.ts ├── playground ├── .gitignore ├── .vscode │ └── extensions.json ├── README.md ├── index.html ├── package.json ├── public │ └── vite.svg ├── src │ ├── App.vue │ ├── assets │ │ └── vue.svg │ ├── components │ │ └── HelloWorld.vue │ ├── main.ts │ ├── style.css │ └── vite-env.d.ts ├── tsconfig.json ├── tsconfig.node.json └── vite.config.ts ├── pnpm-lock.yaml ├── pnpm-workspace.yaml ├── tsconfig.json ├── turbo.json └── x-mind ├── compiler-core.xmind ├── patchElement 的流程.xmind ├── reactivity.xmind ├── slot 流程.xmind ├── update 流程.xmind └── 完整的流程调用图.xmind /.gitignore: -------------------------------------------------------------------------------- 1 | # dependencies 2 | node_modules 3 | .pnp 4 | .pnp.js 5 | package-lock.json 6 | # testing 7 | coverage 8 | 9 | # next.js 10 | .next/ 11 | out/ 12 | # build 13 | 14 | # misc 15 | .DS_Store 16 | *.pem 17 | 18 | # debug 19 | npm-debug.log* 20 | yarn-debug.log* 21 | yarn-error.log* 22 | .pnpm-debug.log* 23 | 24 | # local env files 25 | .env.local 26 | .env.development.local 27 | .env.test.local 28 | .env.production.local 29 | 30 | # turbo 31 | .turbo 32 | 33 | dist 34 | 35 | turbo-dev.log -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "cSpell.words": [ 3 | "vnode" 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 zenoslin 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, libribute, 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Mini-Vue 2 | 3 | ## Shock source code ing 4 | 5 | 直接转行 go 6 | -------------------------------------------------------------------------------- /README_EN.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ErKeLost/extreme-vue/849882815db65d19d16f40bc70b014bec9922829/README_EN.md -------------------------------------------------------------------------------- /docs/.vitepress/theme/components/AsideSponsors.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 23 | -------------------------------------------------------------------------------- /docs/.vitepress/theme/components/HomeSponsors.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 17 | -------------------------------------------------------------------------------- /docs/.vitepress/theme/composables/sponsor.ts: -------------------------------------------------------------------------------- 1 | import { ref, onMounted } from 'vue' 2 | 3 | interface Sponsors { 4 | special: Sponsor[] 5 | platinum: Sponsor[] 6 | platinum_china: Sponsor[] 7 | gold: Sponsor[] 8 | silver: Sponsor[] 9 | bronze: Sponsor[] 10 | } 11 | 12 | interface Sponsor { 13 | name: string 14 | img: string 15 | url: string 16 | } 17 | 18 | // shared data across instances so we load only once. 19 | const data = ref() 20 | 21 | const dataHost = 'https://sponsors.vuejs.org' 22 | const dataUrl = `${dataHost}/vite.json` 23 | 24 | export function useSponsor() { 25 | onMounted(async () => { 26 | if (data.value) { 27 | return 28 | } 29 | 30 | const result = await fetch(dataUrl) 31 | const json = await result.json() 32 | 33 | data.value = mapSponsors(json) 34 | }) 35 | 36 | return { 37 | data 38 | } 39 | } 40 | 41 | function mapSponsors(sponsors: Sponsors) { 42 | return [ 43 | { 44 | tier: 'Platinum Sponsor', 45 | size: 'big', 46 | items: mapImgPath(sponsors['platinum']) 47 | }, 48 | { 49 | tier: 'Gold Sponsors', 50 | size: 'medium', 51 | items: mapImgPath(sponsors['gold']) 52 | } 53 | ] 54 | } 55 | 56 | function mapImgPath(sponsors: Sponsor[]) { 57 | return sponsors.map((sponsor) => ({ 58 | ...sponsor, 59 | img: `${dataHost}/images/${sponsor.img}` 60 | })) 61 | } 62 | -------------------------------------------------------------------------------- /docs/.vitepress/theme/index.ts: -------------------------------------------------------------------------------- 1 | import { h } from 'vue' 2 | import Theme from 'vitepress/theme' 3 | import './styles/vars.css' 4 | import HomeSponsors from './components/HomeSponsors.vue' 5 | import AsideSponsors from './components/AsideSponsors.vue' 6 | 7 | export default { 8 | ...Theme, 9 | Layout() { 10 | return h(Theme.Layout, null, { 11 | 'home-features-after': () => h(HomeSponsors), 12 | 'aside-ads-before': () => h(AsideSponsors) 13 | }) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /docs/.vitepress/theme/styles/vars.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Colors 3 | * -------------------------------------------------------------------------- */ 4 | 5 | :root { 6 | --vp-c-brand: #646cff; 7 | --vp-c-brand-light: #747bff; 8 | --vp-c-brand-lighter: #9499ff; 9 | --vp-c-brand-dark: #535bf2; 10 | --vp-c-brand-darker: #454ce1; 11 | --vp-c-brand-dimm: rgba(100, 108, 255, 0.08); 12 | } 13 | 14 | /** 15 | * Component: Button 16 | * -------------------------------------------------------------------------- */ 17 | 18 | :root { 19 | --vp-button-brand-border: var(--vp-c-brand-light); 20 | --vp-button-brand-text: var(--vp-c-text-dark-1); 21 | --vp-button-brand-bg: var(--vp-c-brand); 22 | --vp-button-brand-hover-border: var(--vp-c-brand-light); 23 | --vp-button-brand-hover-text: var(--vp-c-text-dark-1); 24 | --vp-button-brand-hover-bg: var(--vp-c-brand-light); 25 | --vp-button-brand-active-border: var(--vp-c-brand-light); 26 | --vp-button-brand-active-text: var(--vp-c-text-dark-1); 27 | --vp-button-brand-active-bg: var(--vp-button-brand-bg); 28 | } 29 | 30 | /** 31 | * Component: Home 32 | * -------------------------------------------------------------------------- */ 33 | 34 | :root { 35 | --vp-home-hero-name-color: transparent; 36 | --vp-home-hero-name-background: -webkit-linear-gradient( 37 | 315deg, 38 | #42d392 25%, 39 | #647eff 40 | ); 41 | 42 | --vp-home-hero-image-background-image: linear-gradient( 43 | 315deg, 44 | #42d392 25%, 45 | #647eff 46 | ); 47 | --vp-home-hero-image-filter: blur(40px); 48 | } 49 | 50 | @media (min-width: 640px) { 51 | :root { 52 | --vp-home-hero-image-filter: blur(56px); 53 | } 54 | } 55 | 56 | @media (min-width: 960px) { 57 | :root { 58 | --vp-home-hero-image-filter: blur(72px); 59 | } 60 | } 61 | 62 | /** 63 | * Component: Custom Block 64 | * -------------------------------------------------------------------------- */ 65 | 66 | :root { 67 | --vp-custom-block-tip-border: var(--vp-c-brand); 68 | --vp-custom-block-tip-text: var(--vp-c-brand-darker); 69 | --vp-custom-block-tip-bg: var(--vp-c-brand-dimm); 70 | } 71 | 72 | .dark { 73 | --vp-custom-block-tip-border: var(--vp-c-brand); 74 | --vp-custom-block-tip-text: var(--vp-c-brand-lighter); 75 | --vp-custom-block-tip-bg: var(--vp-c-brand-dimm); 76 | } 77 | 78 | /** 79 | * Component: Algolia 80 | * -------------------------------------------------------------------------- */ 81 | 82 | .DocSearch { 83 | --docsearch-primary-color: var(--vp-c-brand) !important; 84 | } 85 | -------------------------------------------------------------------------------- /docs/compiler-core/index.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ErKeLost/extreme-vue/849882815db65d19d16f40bc70b014bec9922829/docs/compiler-core/index.md -------------------------------------------------------------------------------- /docs/images/bundler.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ErKeLost/extreme-vue/849882815db65d19d16f40bc70b014bec9922829/docs/images/bundler.png -------------------------------------------------------------------------------- /docs/images/esm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ErKeLost/extreme-vue/849882815db65d19d16f40bc70b014bec9922829/docs/images/esm.png -------------------------------------------------------------------------------- /docs/images/graph.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ErKeLost/extreme-vue/849882815db65d19d16f40bc70b014bec9922829/docs/images/graph.png -------------------------------------------------------------------------------- /docs/images/reactive.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ErKeLost/extreme-vue/849882815db65d19d16f40bc70b014bec9922829/docs/images/reactive.jpg -------------------------------------------------------------------------------- /docs/images/vercel-configuration.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ErKeLost/extreme-vue/849882815db65d19d16f40bc70b014bec9922829/docs/images/vercel-configuration.png -------------------------------------------------------------------------------- /docs/images/vite-plugin-inspect.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ErKeLost/extreme-vue/849882815db65d19d16f40bc70b014bec9922829/docs/images/vite-plugin-inspect.png -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: home 3 | 4 | title: Vite 5 | titleTemplate: Next Generation Frontend Tooling 6 | 7 | hero: 8 | name: Vue Source Code 9 | text: 从0解析vue源码 10 | # tagline: Get ready for a development environment that can finally catch up with you. 11 | image: 12 | src: /logo-with-shadow.svg 13 | alt: Vite 14 | actions: 15 | - theme: brand 16 | text: Get Started 17 | link: /reactivity/ 18 | - theme: alt 19 | text: View on GitHub 20 | link: https://github.com/ErKeLost/mini-vue 21 | 22 | features: 23 | - icon: 💡 24 | title: 解析vue源码 25 | details: 从0解析 vue 源码 搭建 vue 框架 26 | # - icon: ⚡️ 27 | # title: Lightning Fast HMR 28 | # details: Hot Module Replacement (HMR) that stays fast regardless of app size. 29 | # - icon: 🛠️ 30 | # title: Rich Features 31 | # details: Out-of-the-box support for TypeScript, JSX, CSS and more. 32 | # - icon: 📦 33 | # title: Optimized Build 34 | # details: Pre-configured Rollup build with multi-page and library mode support. 35 | # - icon: 🔩 36 | # title: Universal Plugins 37 | # details: Rollup-superset plugin interface shared between dev and build. 38 | # - icon: 🔑 39 | # title: Fully Typed APIs 40 | # details: Flexible programmatic APIs with full TypeScript typing. 41 | --- 42 | -------------------------------------------------------------------------------- /docs/public/_headers: -------------------------------------------------------------------------------- 1 | /assets/* 2 | cache-control: max-age=31536000 3 | cache-control: immutable 4 | 5 | /*.svg 6 | cache-control: max-age=604800 7 | cache-control: immutable 8 | 9 | /*.png 10 | cache-control: max-age=604800 11 | cache-control: immutable 12 | -------------------------------------------------------------------------------- /docs/public/divriots.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ErKeLost/extreme-vue/849882815db65d19d16f40bc70b014bec9922829/docs/public/divriots.png -------------------------------------------------------------------------------- /docs/public/logo-with-shadow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ErKeLost/extreme-vue/849882815db65d19d16f40bc70b014bec9922829/docs/public/logo-with-shadow.png -------------------------------------------------------------------------------- /docs/public/logo-with-shadow.svg: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /docs/public/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /docs/public/mux.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 18 | 19 | 20 | 21 | 22 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 73 | 74 | 75 | 76 | 77 | 79 | 80 | -------------------------------------------------------------------------------- /docs/public/og-image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ErKeLost/extreme-vue/849882815db65d19d16f40bc70b014bec9922829/docs/public/og-image.png -------------------------------------------------------------------------------- /docs/public/plaid.svg: -------------------------------------------------------------------------------- 1 | plaid_yoko -------------------------------------------------------------------------------- /docs/public/stackblitz.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 10 | 12 | 15 | 18 | 19 | 23 | 24 | 26 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /docs/public/tailwind-labs.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /docs/public/vite.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ErKeLost/extreme-vue/849882815db65d19d16f40bc70b014bec9922829/docs/public/vite.mp3 -------------------------------------------------------------------------------- /docs/public/voice.svg: -------------------------------------------------------------------------------- 1 | 7 | -------------------------------------------------------------------------------- /docs/public/vuejobs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ErKeLost/extreme-vue/849882815db65d19d16f40bc70b014bec9922829/docs/public/vuejobs.png -------------------------------------------------------------------------------- /docs/reactivity/a.md: -------------------------------------------------------------------------------- 1 | # 实现 proxyRefs 功能 2 | 3 | ## 代理一个 ref 不需要返回 .value 4 | 5 | ```ts 6 | it("proxyRefs", () => { 7 | const user = { 8 | age: ref(10), 9 | name: "xiaohong", 10 | }; 11 | const proxyUser = proxyRefs(user); 12 | expect(user.age.value).toBe(10); 13 | expect(proxyUser.age).toBe(10); 14 | expect(proxyUser.name).toBe("xiaohong"); 15 | 16 | (proxyUser as any).age = 20; 17 | expect(proxyUser.age).toBe(20); 18 | expect(user.age.value).toBe(20); 19 | 20 | proxyUser.age = ref(10); 21 | expect(proxyUser.age).toBe(10); 22 | expect(user.age.value).toBe(10); 23 | }); 24 | ``` 25 | 26 | ```ts 27 | export function isRef(ref) { 28 | return !!ref.__v_isRef; 29 | } 30 | 31 | export function unRef(ref) { 32 | return isRef(ref) ? ref.value : ref; 33 | } 34 | 35 | export function proxyRefs(ref) { 36 | // 判断调用了 get 或者 set/ 37 | return new Proxy(ref, { 38 | get(target, key) { 39 | return unRef(Reflect.get(target, key)); 40 | }, 41 | // 判断set的时候 是不是一个 ref 是 ref 就调用 .value 42 | set(target, key, value) { 43 | if (isRef(target[key]) && !isRef(value)) { 44 | return (target[key].value = value); 45 | } else { 46 | return Reflect.set(target, key, value); 47 | } 48 | }, 49 | }); 50 | } 51 | ``` 52 | -------------------------------------------------------------------------------- /docs/reactivity/api-hmr.md: -------------------------------------------------------------------------------- 1 | # HMR API 2 | 3 | :::tip Note 4 | This is the client HMR API. For handling HMR update in plugins, see [handleHotUpdate](./api-plugin#handlehotupdate). 5 | 6 | The manual HMR API is primarily intended for framework and tooling authors. As an end user, HMR is likely already handled for you in the framework specific starter templates. 7 | ::: 8 | 9 | Vite exposes its manual HMR API via the special `import.meta.hot` object: 10 | 11 | ```ts 12 | interface ImportMeta { 13 | readonly hot?: ViteHotContext 14 | } 15 | 16 | type ModuleNamespace = Record & { 17 | [Symbol.toStringTag]: 'Module' 18 | } 19 | 20 | interface ViteHotContext { 21 | readonly data: any 22 | 23 | accept(): void 24 | accept(cb: (mod: ModuleNamespace | undefined) => void): void 25 | accept(dep: string, cb: (mod: ModuleNamespace | undefined) => void): void 26 | accept( 27 | deps: readonly string[], 28 | cb: (mods: Array) => void 29 | ): void 30 | 31 | dispose(cb: (data: any) => void): void 32 | decline(): void 33 | invalidate(): void 34 | 35 | // `InferCustomEventPayload` provides types for built-in Vite events 36 | on( 37 | event: T, 38 | cb: (payload: InferCustomEventPayload) => void 39 | ): void 40 | send(event: T, data?: InferCustomEventPayload): void 41 | } 42 | ``` 43 | 44 | ## Required Conditional Guard 45 | 46 | First of all, make sure to guard all HMR API usage with a conditional block so that the code can be tree-shaken in production: 47 | 48 | ```js 49 | if (import.meta.hot) { 50 | // HMR code 51 | } 52 | ``` 53 | 54 | ## `hot.accept(cb)` 55 | 56 | For a module to self-accept, use `import.meta.hot.accept` with a callback which receives the updated module: 57 | 58 | ```js 59 | export const count = 1 60 | 61 | if (import.meta.hot) { 62 | import.meta.hot.accept((newModule) => { 63 | if (newModule) { 64 | // newModule is undefined when SyntaxError happened 65 | console.log('updated: count is now ', newModule.count) 66 | } 67 | }) 68 | } 69 | ``` 70 | 71 | A module that "accepts" hot updates is considered an **HMR boundary**. 72 | 73 | Note that Vite's HMR does not actually swap the originally imported module: if an HMR boundary module re-exports imports from a dep, then it is responsible for updating those re-exports (and these exports must be using `let`). In addition, importers up the chain from the boundary module will not be notified of the change. 74 | 75 | This simplified HMR implementation is sufficient for most dev use cases, while allowing us to skip the expensive work of generating proxy modules. 76 | 77 | ## `hot.accept(deps, cb)` 78 | 79 | A module can also accept updates from direct dependencies without reloading itself: 80 | 81 | ```js 82 | import { foo } from './foo.js' 83 | 84 | foo() 85 | 86 | if (import.meta.hot) { 87 | import.meta.hot.accept('./foo.js', (newFoo) => { 88 | // the callback receives the updated './foo.js' module 89 | newFoo?.foo() 90 | }) 91 | 92 | // Can also accept an array of dep modules: 93 | import.meta.hot.accept( 94 | ['./foo.js', './bar.js'], 95 | ([newFooModule, newBarModule]) => { 96 | // the callback receives the updated modules in an Array 97 | } 98 | ) 99 | } 100 | ``` 101 | 102 | ## `hot.dispose(cb)` 103 | 104 | A self-accepting module or a module that expects to be accepted by others can use `hot.dispose` to clean-up any persistent side effects created by its updated copy: 105 | 106 | ```js 107 | function setupSideEffect() {} 108 | 109 | setupSideEffect() 110 | 111 | if (import.meta.hot) { 112 | import.meta.hot.dispose((data) => { 113 | // cleanup side effect 114 | }) 115 | } 116 | ``` 117 | 118 | ## `hot.data` 119 | 120 | The `import.meta.hot.data` object is persisted across different instances of the same updated module. It can be used to pass on information from a previous version of the module to the next one. 121 | 122 | ## `hot.decline()` 123 | 124 | Calling `import.meta.hot.decline()` indicates this module is not hot-updatable, and the browser should perform a full reload if this module is encountered while propagating HMR updates. 125 | 126 | ## `hot.invalidate()` 127 | 128 | For now, calling `import.meta.hot.invalidate()` simply reloads the page. 129 | 130 | ## `hot.on(event, cb)` 131 | 132 | Listen to an HMR event. 133 | 134 | The following HMR events are dispatched by Vite automatically: 135 | 136 | - `'vite:beforeUpdate'` when an update is about to be applied (e.g. a module will be replaced) 137 | - `'vite:beforeFullReload'` when a full reload is about to occur 138 | - `'vite:beforePrune'` when modules that are no longer needed are about to be pruned 139 | - `'vite:error'` when an error occurs (e.g. syntax error) 140 | 141 | Custom HMR events can also be sent from plugins. See [handleHotUpdate](./api-plugin#handlehotupdate) for more details. 142 | 143 | ## `hot.send(event, data)` 144 | 145 | Send custom events back to Vite's dev server. 146 | 147 | If called before connected, the data will be buffered and sent once the connection is established. 148 | 149 | See [Client-server Communication](/guide/api-plugin.html#client-server-communication) for more details. 150 | -------------------------------------------------------------------------------- /docs/reactivity/assets.md: -------------------------------------------------------------------------------- 1 | # Readonly 功能 2 | 3 | > Readonly 不可能被 set 只读 4 | 5 | ```ts 6 | export function readonly(raw) { 7 | return new Proxy(raw, { 8 | get(target, key) { 9 | const res = Reflect.get(target, key); 10 | // track 11 | // console.log("触发get"); 12 | return res; 13 | }, 14 | set(target, key, value) { 15 | console.warn(`${target} is read-only`); 16 | 17 | return true; 18 | }, 19 | }); 20 | } 21 | ``` 22 | 23 | ## 优化 reactive 模块代码 24 | 25 | ```ts 26 | import { mutableHandlers, readonlyHandlers } from "./baseHandlers"; 27 | // true false 判断是否是 readonly 28 | export function reactive(raw) { 29 | return createActiveObject(raw, mutableHandlers); 30 | } 31 | 32 | export function readonly(raw) { 33 | return createActiveObject(raw, readonlyHandlers); 34 | } 35 | 36 | function createActiveObject(raw, Handlers) { 37 | return new Proxy(raw, Handlers); 38 | } 39 | ``` 40 | 41 | ## 抽离 handler 模块 42 | 43 | ```ts 44 | import { track, trigger } from "./effect"; 45 | 46 | // 缓存一个getter & setter 47 | const get = createGetter(); 48 | const set = createSetter(); 49 | const readonlyGet = createGetter(true); 50 | // 高阶函数判断逻辑 51 | export function createGetter(isReadonly = false) { 52 | return function get(target, key) { 53 | const res = Reflect.get(target, key); 54 | // track 55 | if (!isReadonly) { 56 | track(target, key); 57 | } 58 | // console.log("触发get"); 59 | return res; 60 | }; 61 | } 62 | 63 | export function createSetter() { 64 | return function set(target, key, value) { 65 | const res = Reflect.set(target, key, value); 66 | // trigger 67 | trigger(target, key); 68 | // console.log("触发set"); 69 | return res; 70 | }; 71 | } 72 | 73 | export const mutableHandlers = { 74 | get, 75 | set, 76 | }; 77 | export const readonlyHandlers = { 78 | get: readonlyGet, 79 | set(target, key, value) { 80 | console.warn(`${key} is not change ${target} is read-only`); 81 | return true; 82 | }, 83 | }; 84 | ``` 85 | -------------------------------------------------------------------------------- /docs/reactivity/b.md: -------------------------------------------------------------------------------- 1 | # 实现 computed 计算属性 2 | 3 | > computed 和 ref 类型 都是返回一个 .value 但是 computed 有一个功能就是缓存机制 4 | 5 | ```ts 6 | describe('computed', () => { 7 | it('happy path', () => { 8 | const value = reactive({ 9 | foo: 1 10 | }) 11 | 12 | const getter = computed(() => { 13 | return value.foo 14 | }) 15 | 16 | expect(getter.value).toBe(1) 17 | }) 18 | 19 | it('should compute lazily', () => { 20 | const value = reactive({ 21 | foo: 1 22 | }) 23 | const getter = jest.fn(() => { 24 | console.log(666) 25 | return value.foo 26 | }) 27 | const cValue = computed(getter) 28 | 29 | // lazy 30 | expect(getter).not.toHaveBeenCalled() 31 | 32 | expect(cValue.value).toBe(1) 33 | expect(getter).toHaveBeenCalledTimes(1) 34 | 35 | // should not compute again 36 | cValue.value 37 | expect(getter).toHaveBeenCalledTimes(1) 38 | 39 | // should not compute until needed 40 | value.foo = 2 41 | expect(getter).toHaveBeenCalledTimes(1) 42 | 43 | // now it should compute 44 | expect(cValue.value).toBe(2) 45 | expect(getter).toHaveBeenCalledTimes(2) 46 | 47 | // should not compute again 48 | cValue.value 49 | expect(getter).toHaveBeenCalledTimes(2) 50 | }) 51 | }) 52 | ``` 53 | 54 | ```ts 55 | // import { createDep } from "./dep"; 56 | import { ReactiveEffect } from './effect' 57 | // import { trackRefValue, triggerRefValue } from "./ref"; 58 | 59 | class ComputedRefImpl { 60 | private _getter 61 | private _dirty: boolean = true 62 | private _value: any 63 | private _effect: ReactiveEffect 64 | constructor(getter) { 65 | this._getter = getter 66 | 67 | this._effect = new ReactiveEffect(getter, () => { 68 | if (!this._dirty) { 69 | this._dirty = true 70 | } 71 | }) 72 | } 73 | get value() { 74 | // 调用一次之后 做一个缓存机制 75 | // 等依赖的数据发生响应式之后 我们再次执行 getter 把dirty变成true 76 | if (this._dirty) { 77 | this._dirty = false 78 | this._value = this._effect.run() 79 | // this._value = this._getter() 80 | // return this._getter() 81 | } 82 | return this._value 83 | } 84 | } 85 | 86 | export function computed(getter) { 87 | return new ComputedRefImpl(getter) 88 | } 89 | ``` 90 | -------------------------------------------------------------------------------- /docs/reactivity/backend-integration.md: -------------------------------------------------------------------------------- 1 | # Is Proxy 2 | 3 | :::tip Note 4 | Is Proxy 是用来检测一个对象是不是被 reactive 或者 readonly 创建出来的 5 | 6 | ```ts 7 | export function isProxy(raw) { 8 | return isReactive(raw) || isReadonly(raw) 9 | } 10 | ``` 11 | -------------------------------------------------------------------------------- /docs/reactivity/build.md: -------------------------------------------------------------------------------- 1 | # 实现 isReactive 和 isReadonly 2 | 3 | > 判断当前是一个 reactive 或者 是一个 readonly 4 | > 如何判断 只要 触发了 proxy 的 get 操作 5 | 6 | ```ts 7 | export const enum ReactiveFlags { 8 | IS_REACTIVE = "_v_isReactive", 9 | IS_READONLY = "_v_isReadonly", 10 | } 11 | 12 | export function isReactive(raw) { 13 | // 如果是原始对象没有 proxy 代理 是undefined 转成 boolean 14 | return !!raw[ReactiveFlags.IS_REACTIVE]; 15 | } 16 | export function isReadonly(raw) { 17 | return !!raw[ReactiveFlags.IS_READONLY]; 18 | } 19 | 20 | export function createGetter(isReadonly = false) { 21 | return function get(target, key) { 22 | const res = Reflect.get(target, key); 23 | // track 24 | if (key === ReactiveFlags.IS_REACTIVE) { 25 | return !isReadonly; 26 | } else if (key === ReactiveFlags.IS_READONLY) { 27 | return isReadonly; 28 | } 29 | if (!isReadonly) { 30 | track(target, key); 31 | } 32 | // console.log("触发get"); 33 | return res; 34 | }; 35 | } 36 | ``` 37 | -------------------------------------------------------------------------------- /docs/reactivity/comparisons.md: -------------------------------------------------------------------------------- 1 | # Ref 2 | 3 | ## ref 解析 4 | 5 | > 因为响应式需要代理对象, ref 作为基于 reactive 的响应式对象, 支持基本类型,但是 proxy 需要代理的是对象,所以我们需要把 ref 变成一个对象这样才能变成响应式 6 | > ref 函数返回一个类 我们需要这个类去变成一个对象, 我们可以使用 类的 get, set 操作 7 | 8 | ```ts 9 | export function ref(value) { 10 | return new RefImpl(value); 11 | } 12 | 13 | class RefImpl { 14 | private _value: any; 15 | private _rawValue: any; 16 | // 创建一个dep 收集依赖 17 | public dep; 18 | constructor(value) { 19 | // 存储 原始普通对象的value值 用于对比 20 | this._rawValue = value; 21 | this._value = convert(value); 22 | // 判断value 是不是对象 是对象就装换成reactive 23 | this.dep = new Set(); 24 | } 25 | get value() { 26 | trackRefValue(this); 27 | // ref 只监听一个 value 的变化 value的依赖收集 28 | return this._value; 29 | } 30 | set value(newValue) { 31 | // 如果value 是一个 对象 就会被转换成reactive 新的值 是 proxy 对比 要转换成普通对象 32 | // if (hasChanged(newValue, this._value)) { 33 | if (hasChanged(newValue, this._rawValue)) { 34 | // if (Object.is(newValue, this._value)) return 35 | // 必须要先修改value的值 再去通知 36 | this._rawValue = newValue; 37 | this._value = convert(newValue); 38 | triggerEffect(this.dep); 39 | } 40 | } 41 | } 42 | 43 | export function trackEffect(dep) { 44 | if (dep.has(activeEffect)) return; 45 | dep.add(activeEffect); 46 | activeEffect.deps.push(dep); 47 | } 48 | 49 | export function triggerEffect(dep) { 50 | for (const effect of dep) { 51 | if (effect.scheduler) { 52 | effect.scheduler(); 53 | } else { 54 | effect.run(); 55 | } 56 | } 57 | } 58 | ``` 59 | -------------------------------------------------------------------------------- /docs/reactivity/dep-pre-bundling.md: -------------------------------------------------------------------------------- 1 | # 优化 Effect 代码 2 | 3 | > 1. cleanupEffect 函数抽离 4 | > 2. 频繁调用 stop 没有必要 性能优化 5 | > 3. onStop 功能 在调用 stop 的时候触发一个回调函数 在 effect 的 第二个参数中 6 | 7 | ```ts 8 | it("events: onStop", () => { 9 | const onStop = jest.fn(); 10 | const runner = effect(() => {}, { 11 | onStop, 12 | }); 13 | 14 | stop(runner); 15 | expect(onStop).toHaveBeenCalled(); 16 | }); 17 | ``` 18 | 19 | ```ts 20 | import { extend } from "@relaxed/shared"; // Object.assign 21 | 22 | let activeEffect; 23 | export class ReactiveEffect { 24 | deps = []; 25 | activeStop = true; 26 | // 可有可无 27 | onStop?: () => void; 28 | constructor(public fn, public scheduler?) {} 29 | run() { 30 | activeEffect = this; 31 | return this.fn(); 32 | } 33 | stop() { 34 | if (this.activeStop) { 35 | cleanupEffect(this); 36 | if (this.onStop) { 37 | this.onStop(); 38 | } 39 | this.activeStop = false; 40 | } 41 | } 42 | } 43 | function cleanupEffect(effect) { 44 | effect.deps.forEach((dep: any) => { 45 | dep.delete(effect); 46 | }); 47 | } 48 | const targetMap = new Map(); 49 | export function track(target, key) { 50 | // target -> key -> dep 51 | let depsMap = targetMap.get(target); 52 | if (!depsMap) { 53 | depsMap = new Map(); 54 | targetMap.set(target, depsMap); 55 | } 56 | let dep = depsMap.get(key); 57 | if (!dep) { 58 | dep = new Set(); 59 | depsMap.set(key, dep); 60 | } 61 | if (!activeEffect) return; 62 | // 一开始没有 activeEffect 63 | // console.log(activeEffect); 64 | dep.add(activeEffect); 65 | activeEffect.deps.push(dep); 66 | } 67 | 68 | export function trigger(target, key) { 69 | // 取出所有 收集到的依赖 进行遍历 70 | let depsMap = targetMap.get(target); 71 | let dep = depsMap.get(key); 72 | for (const effect of dep) { 73 | if (effect.scheduler) { 74 | effect.scheduler(); 75 | } else { 76 | effect.run(); 77 | } 78 | } 79 | } 80 | 81 | export function effect(fn, options: any = {}) { 82 | const _effect = new ReactiveEffect(fn, options.scheduler); 83 | extend(_effect, options); 84 | console.log(_effect); 85 | _effect.run(); 86 | const runner: any = _effect.run.bind(_effect); 87 | runner.effect = _effect; 88 | return runner; 89 | } 90 | 91 | export function stop(runner) { 92 | runner.effect.stop(); 93 | } 94 | ``` 95 | -------------------------------------------------------------------------------- /docs/reactivity/env-and-mode.md: -------------------------------------------------------------------------------- 1 | # 实现 reactive 和 readonly 嵌套对象转换功能 2 | 3 | ## 嵌套对象逻辑 4 | 5 | 如果 一个 对象中嵌套了 另一个对象,那麽如何做到对象嵌套类型为响应式 6 | 7 | ```ts 8 | const original = { 9 | nested: { 10 | foo: 1, 11 | }, 12 | array: [{ bar: 2 }], 13 | } 14 | const observed = reactive(original) 15 | expect(isReactive(observed.nested)).toBe(true) 16 | expect(isReactive(observed.array)).toBe(true) 17 | expect(isReactive(observed.array[0])).toBe(true) 18 | ``` 19 | 20 | :::tip 21 | 我们需要在 get 的时候 判断 proxy 代理的对象返回的 res 是不是一个对象,如果他是对象类型,就代表我们需要把 res 转换成响应式对象 22 | ::: 23 | 24 | ```ts 25 | function get(target, key) { 26 | const res = Reflect.get(target, key) 27 | // track 28 | if (key === ReactiveFlags.IS_REACTIVE) { 29 | return !isReadonly 30 | } else if (key === ReactiveFlags.IS_READONLY) { 31 | return isReadonly 32 | } 33 | // 嵌套reactive 如果 res 返回的是一个 object 那么我们就再次调用 34 | // 转换为reactive 35 | if (isObject(res)) { 36 | return isReadonly ? readonly(res) : reactive(res) 37 | } 38 | if (!isReadonly) { 39 | track(target, key) 40 | } 41 | // console.log("触发get"); 42 | return res 43 | } 44 | ``` 45 | -------------------------------------------------------------------------------- /docs/reactivity/features.md: -------------------------------------------------------------------------------- 1 | # Effect 的 scheduler 功能 2 | 3 | > 1. 调用 effect 传入一个 fn 可以传第二个参数 options, scheduler 接收一个函数 4 | > 2. 一开始第一次不会被调用,会执行 effect 第一个 fn, 5 | > 3. 当响应式对象值发生改变,scheduler 方法 会被执行, 不会再去调用第一个 fn 6 | > 4. 调用 scheduler 方法 把 run 得到 runner 函数 调用 runner 函数所以 run 执行 runner 函数 dummy 值会发生改变 7 | 8 | ```ts 9 | it("scheduler", () => { 10 | let dummy; 11 | let run: any; 12 | const scheduler = jest.fn(() => { 13 | run = runner; 14 | }); 15 | const obj = reactive({ foo: 1 }); 16 | const runner = effect( 17 | () => { 18 | dummy = obj.foo; 19 | }, 20 | { scheduler } 21 | ); 22 | expect(scheduler).not.toHaveBeenCalled(); 23 | expect(dummy).toBe(1); 24 | // should be called on first trigger 25 | obj.foo++; 26 | expect(scheduler).toHaveBeenCalledTimes(1); 27 | // // should not run yet 28 | expect(dummy).toBe(1); 29 | // // manually run 30 | run(); 31 | // // should have run 32 | expect(dummy).toBe(2); 33 | }); 34 | ``` 35 | 36 | ## 代码实现 37 | 38 | ```ts 39 | let activeEffect; 40 | export class ReactiveEffect { 41 | constructor(public fn, public scheduler?) {} 42 | run() { 43 | activeEffect = this; 44 | return this.fn(); 45 | } 46 | } 47 | 48 | const targetMap = new Map(); 49 | export function track(target, key) { 50 | // target -> key -> dep 51 | let depsMap = targetMap.get(target); 52 | if (!depsMap) { 53 | depsMap = new Map(); 54 | targetMap.set(target, depsMap); 55 | } 56 | let dep = depsMap.get(key); 57 | if (!dep) { 58 | dep = new Set(); 59 | depsMap.set(key, dep); 60 | } 61 | // console.log(activeEffect); 62 | dep.add(activeEffect); 63 | } 64 | 65 | export function trigger(target, key) { 66 | // 取出所有 收集到的依赖 进行遍历 67 | let depsMap = targetMap.get(target); 68 | let dep = depsMap.get(key); 69 | for (const effect of dep) { 70 | if (effect.scheduler) { 71 | effect.scheduler(); 72 | } else { 73 | effect.run(); 74 | } 75 | } 76 | } 77 | 78 | export function effect(fn, options: any = {}) { 79 | const scheduler = options.scheduler; 80 | const _effect = new ReactiveEffect(fn, scheduler); 81 | _effect.run(); 82 | return _effect.run.bind(_effect); 83 | } 84 | ``` 85 | -------------------------------------------------------------------------------- /docs/reactivity/index.md: -------------------------------------------------------------------------------- 1 | # Effect 返回 runner 2 | 3 | > 调用 effect 之后 返回一个函数 调用这个函数,他会再次执行传给 effect 内部的 fn,并且拿到内部 fn 的返回值 4 | 5 | ```ts 6 | let res = 666; 7 | const runner = effect(() => { 8 | res++; 9 | return 99999; 10 | }); 11 | console.log(res); // 667 12 | const w = runner(); 13 | console.log(w); // 99999 14 | console.log(res); // 668 15 | ``` 16 | 17 | ```ts 18 | let activeEffect; 19 | export class ReactiveEffect { 20 | constructor(public fn) {} 21 | run() { 22 | activeEffect = this; 23 | return this.fn(); 24 | } 25 | } 26 | 27 | const targetMap = new Map(); 28 | export function track(target, key) { 29 | // target -> key -> dep 30 | let depsMap = targetMap.get(target); 31 | if (!depsMap) { 32 | depsMap = new Map(); 33 | targetMap.set(target, depsMap); 34 | } 35 | let dep = depsMap.get(key); 36 | if (!dep) { 37 | dep = new Set(); 38 | depsMap.set(key, dep); 39 | } 40 | // console.log(activeEffect); 41 | dep.add(activeEffect); 42 | console.log(targetMap); 43 | } 44 | 45 | export function trigger(target, key) { 46 | // 取出所有 收集到的依赖 进行遍历 47 | let depsMap = targetMap.get(target); 48 | let dep = depsMap.get(key); 49 | for (const effect of dep) { 50 | effect.run(); 51 | } 52 | } 53 | 54 | export function effect(fn) { 55 | const _effect = new ReactiveEffect(fn); 56 | _effect.run(); 57 | return _effect.run.bind(_effect); 58 | } 59 | ``` 60 | -------------------------------------------------------------------------------- /docs/reactivity/migration.md: -------------------------------------------------------------------------------- 1 | # 实现 isRef 和 unRef 功能 2 | 3 | ## isRef 4 | 5 | > test 6 | 7 | ```ts 8 | it("isRef", () => { 9 | const a = ref(1); 10 | const user = reactive({ 11 | age: 1, 12 | }); 13 | expect(isRef(a)).toBe(true); 14 | expect(isRef(1)).toBe(false); 15 | expect(isRef(user)).toBe(false); 16 | }); 17 | 18 | it("unRef", () => { 19 | const a = ref(1); 20 | expect(unRef(a)).toBe(1); 21 | expect(unRef(1)).toBe(1); 22 | }); 23 | ``` 24 | 25 | > 通过给 ref 实例添加一个属性判断是否是 ref ` public __v_isRef = true;` unRef 就是 isRef 的语法糖 26 | 27 | ```ts 28 | class RefImpl { 29 | private _value: any; 30 | private _rawValue: any; 31 | // 判断是否是一个ref 32 | public __v_isRef = true; 33 | // 创建一个dep 收集依赖 34 | public dep; 35 | constructor(value) { 36 | // 存储 原始普通对象的value值 用于对比 37 | this._rawValue = value; 38 | this._value = convert(value); 39 | // 判断value 是不是对象 是对象就装换成reactive 40 | 41 | this.dep = new Set(); 42 | } 43 | get value() { 44 | trackRefValue(this); 45 | // ref 只监听一个 value 的变化 value的依赖收集 46 | return this._value; 47 | } 48 | set value(newValue) { 49 | // 如果value 是一个 对象 就会被转换成reactive 新的值 是 proxy 对比 要转换成普通对象 50 | // if (hasChanged(newValue, this._value)) { 51 | if (hasChanged(newValue, this._rawValue)) { 52 | // if (Object.is(newValue, this._value)) return 53 | // 必须要先修改value的值 再去通知 54 | this._rawValue = newValue; 55 | this._value = convert(newValue); 56 | triggerEffect(this.dep); 57 | } 58 | } 59 | } 60 | 61 | export function ref(value) { 62 | return new RefImpl(value); 63 | } 64 | 65 | export function isRef(ref) { 66 | return !!ref.__v_isRef; 67 | } 68 | 69 | export function unRef(ref) { 70 | return isRef(ref) ? ref.value : ref; 71 | } 72 | ``` 73 | -------------------------------------------------------------------------------- /docs/reactivity/ssr.md: -------------------------------------------------------------------------------- 1 | # 实现 shadowReadonly 2 | 3 | :::warning Experimental 4 | shallow就是表层的意思,如果是响应式对象,那么它只能是最外层的一层对象是响应式对象,我们把shallow和readonly结合起来就是 创建出来的一个响应式对象 最外层是一个 readonly 其他里面的层次对象不是 5 | ::: 6 | 7 | ```ts 8 | const shallowReadonlyGet = createGetter(true, true) 9 | 10 | export function createGetter(isReadonly = false, shallow = false) { 11 | return function get(target, key) { 12 | const res = Reflect.get(target, key) 13 | // track 14 | if (key === ReactiveFlags.IS_REACTIVE) { 15 | return !isReadonly 16 | } else if (key === ReactiveFlags.IS_READONLY) { 17 | return isReadonly 18 | } 19 | if (shallow) { 20 | return res 21 | } 22 | // 嵌套reactive 如果 res 返回的是一个 object 那么我们就再次调用 23 | // 转换为reactive 24 | if (isObject(res)) { 25 | return isReadonly ? readonly(res) : reactive(res) 26 | } 27 | if (!isReadonly) { 28 | track(target, key) 29 | } 30 | // console.log("触发get"); 31 | return res 32 | } 33 | } 34 | 35 | export function createSetter() { 36 | return function set(target, key, value) { 37 | const res = Reflect.set(target, key, value) 38 | // trigger 39 | trigger(target, key) 40 | // console.log("触发set"); 41 | return res 42 | } 43 | } 44 | 45 | export const mutableHandlers = { 46 | get, 47 | set, 48 | } 49 | export const readonlyHandlers = { 50 | get: readonlyGet, 51 | set(target, key, value) { 52 | console.warn(`${key} is not change ${target} is read-only`) 53 | return true 54 | }, 55 | } 56 | export const shadowReadonlyHandlers = extend({}, readonlyHandlers, { 57 | get: shallowReadonlyGet, 58 | }) 59 | ``` 60 | -------------------------------------------------------------------------------- /docs/reactivity/static-deploy.md: -------------------------------------------------------------------------------- 1 | # stop 优化 2 | 3 | 4 | > 如果 在触发响应式操作的时候 调用了 ++ 操作 类似 5 | > ```ts 6 | > obj.num++ 7 | > obj.num = obj.num + 1 8 | > ``` 9 | > 会同时触发 get 和 set 操作 他还是会把 依赖存进去 导致没有 删除dep stop失效 10 | ```ts 11 | import { extend } from "@relaxed/shared"; 12 | 13 | let activeEffect; 14 | let shouldTrack; 15 | export class ReactiveEffect { 16 | deps = []; 17 | activeStop = true; 18 | // 可有可无 19 | onStop?: () => void; 20 | constructor(public fn, public scheduler?) {} 21 | run() { 22 | // 1. 会收集依赖 23 | if (!this.activeStop) { 24 | return this.fn(); 25 | } 26 | shouldTrack = true; 27 | activeEffect = this; 28 | const res = this.fn(); 29 | shouldTrack = false; 30 | return res; 31 | } 32 | stop() { 33 | if (this.activeStop) { 34 | cleanupEffect(this); 35 | if (this.onStop) { 36 | this.onStop(); 37 | } 38 | this.activeStop = false; 39 | } 40 | } 41 | } 42 | function cleanupEffect(effect) { 43 | effect.deps.forEach((dep: any) => { 44 | dep.delete(effect); 45 | }); 46 | } 47 | const targetMap = new Map(); 48 | export function track(target, key) { 49 | // target -> key -> dep 50 | let depsMap = targetMap.get(target); 51 | if (!depsMap) { 52 | depsMap = new Map(); 53 | targetMap.set(target, depsMap); 54 | } 55 | let dep = depsMap.get(key); 56 | if (!dep) { 57 | dep = new Set(); 58 | depsMap.set(key, dep); 59 | } 60 | // 一开始没有 activeEffect 61 | if (!activeEffect) return; 62 | // 防止 同时触发 get 和 set 操作 他还是会收集到依赖 stop 会失效 63 | if (!shouldTrack) return; 64 | // console.log(activeEffect); 65 | dep.add(activeEffect); 66 | activeEffect.deps.push(dep); 67 | } 68 | 69 | export function trigger(target, key) { 70 | // 取出所有 收集到的依赖 进行遍历 71 | let depsMap = targetMap.get(target); 72 | let dep = depsMap.get(key); 73 | for (const effect of dep) { 74 | if (effect.scheduler) { 75 | effect.scheduler(); 76 | } else { 77 | effect.run(); 78 | } 79 | } 80 | } 81 | 82 | export function effect(fn, options: any = {}) { 83 | const _effect = new ReactiveEffect(fn, options.scheduler); 84 | extend(_effect, options); 85 | console.log(_effect); 86 | _effect.run(); 87 | const runner: any = _effect.run.bind(_effect); 88 | runner.effect = _effect; 89 | return runner; 90 | } 91 | 92 | export function stop(runner) { 93 | runner.effect.stop(); 94 | } 95 | ``` 96 | -------------------------------------------------------------------------------- /docs/reactivity/using-plugins.md: -------------------------------------------------------------------------------- 1 | # effect 的 stop 功能 2 | 3 | 4 | > 通过调用stop 阻止effect 更新操作 5 | ```ts 6 | it("stop", () => { 7 | let dummy; 8 | const obj = reactive({ prop: 1 }); 9 | const runner = effect(() => { 10 | dummy = obj.prop; 11 | }); 12 | obj.prop = 2; 13 | expect(dummy).toBe(2); 14 | stop(runner); 15 | // obj.prop = 3 16 | obj.prop++; 17 | expect(dummy).toBe(2); 18 | 19 | // stopped effect should still be manually callable 20 | runner(); 21 | expect(dummy).toBe(3); 22 | }); 23 | ``` 24 | -------------------------------------------------------------------------------- /docs/reactivity/why.md: -------------------------------------------------------------------------------- 1 | # 实现 effect & reactive & 依赖收集 & 触发依赖 2 | 3 | ## reactive 4 | 5 | > 实现响应式的核心 reactive 使用 reactive 创建一个响应式对象, 返回一个 proxy 对象,分别在我们调用属性或者给属性赋值的时候进行操作, 就是 get & set 6 | 7 | ```ts 8 | export function reactive(raw) { 9 | return new Proxy(raw, { 10 | get(target, key) { 11 | const res = Reflect.get(target, key); 12 | // track 13 | track(target, key); 14 | // console.log("触发get"); 15 | return res; 16 | }, 17 | set(target, key, value) { 18 | const res = Reflect.set(target, key, value); 19 | // trigger 20 | trigger(target, key); 21 | // console.log("触发set"); 22 | return res; 23 | }, 24 | }); 25 | } 26 | 27 | const counter = reactive({ num: 0 }); 28 | counter.num; // 触发 get 方法 29 | counter.num = 9; // 触发 set 方法 30 | ``` 31 | 32 | > 然后需要通过调用 get 的时候我们去收集依赖, 等到 set 的时候 我们就去触发依赖 33 | > get 调用 track 收集依赖 34 | > set 调用 trigger 触发依赖 35 | 36 | ## effect 37 | 38 | > effect 作为我们收集依赖的通道,返回一个函数,我们收集的依赖就是对应的当前调用属性的函数 39 | 40 | 举个例子 41 | 42 | > effect 中接收一个函数 函数调用了 counter 对象的 get 方法, 所以我们需要收集这个依赖并且和调用对象的属性一一对应 43 | > 比如 44 | 45 | ```ts 46 | const counter = { num: 11 }; 47 | effect(() => counter.num); 48 | ``` 49 | 50 | :::tip 51 | 这种情况我们需要收集依赖的对应关系就是, 一个对象对应一个属性, 一个属性对应一个需要触发的 effect 副作用函数 52 | counter -> num -> () => counter.num 53 | ::: 54 | 55 | 然后编写 effect 函数, 调用effect 立即执行 内部函数 56 | 57 | ```ts 58 | let activeEffect; 59 | class ReactiveEffect { 60 | constructor(public fn) {} 61 | run () { 62 | activeEffect = this 63 | this.fn() 64 | } 65 | } 66 | export function effect(fn) { 67 | const _effect = new ReactiveEffect(fn) 68 | _effect.run() 69 | } 70 | ``` 71 | 72 | ## track 73 | 74 | > 收集依赖 依赖就是 effect 传入的 函数指的也就是 ReactiveEffect 75 | > 依赖对应关系 counter -> num -> () => counter.num 76 | > 接下来我们就只要把依赖存起来, 因为依赖不能重复 我们使用set 存放 77 | > 放到哪里 每一个key 对应各自的依赖 每一个对象 对应多个 key 使用 78 | > map保存 79 | 80 | ![reactive.jpg](/images/reactive.jpg) 81 | ```ts 82 | // 创建 对象容器 83 | const targetMap = new WeakMap() 84 | export function track (target, key) { 85 | // target -> key -> dep 86 | // 初始化没有我们就创建一个 87 | // 收集所有key 88 | 89 | let depsMap = targetMap.get(target) 90 | if (!depsMap) { 91 | depsMap = new Map() 92 | targetMap.set(target, depsMap) 93 | } 94 | 95 | // 获取 依赖 96 | let dep = depsMap.get(key) 97 | if (!dep) { 98 | dep = new Set() 99 | depsMap.set(key, dep) 100 | } 101 | dep.add(activeEffect) 102 | } 103 | 104 | // 触发set的时候重新执行 effect 副作用函数 就能变成响应式了 105 | export function trigger (target, key) { 106 | let depsMap = targetMap.get(target) 107 | let deps = depsMap.get(key) 108 | for(const effect of deps) { 109 | effect.run() 110 | } 111 | } 112 | 113 | ``` -------------------------------------------------------------------------------- /docs/runtime-core/dep-optimization-options.md: -------------------------------------------------------------------------------- 1 | # Dep Optimization Options 2 | 3 | - **Related:** [Dependency Pre-Bundling](/guide/dep-pre-bundling) 4 | 5 | ## optimizeDeps.entries 6 | 7 | - **Type:** `string | string[]` 8 | 9 | By default, Vite will crawl all your `.html` files to detect dependencies that need to be pre-bundled (ignoring `node_modules`, `build.outDir`, `__tests__` and `coverage`). If `build.rollupOptions.input` is specified, Vite will crawl those entry points instead. 10 | 11 | If neither of these fit your needs, you can specify custom entries using this option - the value should be a [fast-glob pattern](https://github.com/mrmlnc/fast-glob#basic-syntax) or array of patterns that are relative from Vite project root. This will overwrite default entries inference. Only `node_modules` and `build.outDir` folders will be ignored by default when `optimizeDeps.entries` is explicitly defined. If other folders needs to be ignored, you can use an ignore pattern as part of the entries list, marked with an initial `!`. 12 | 13 | ## optimizeDeps.exclude 14 | 15 | - **Type:** `string[]` 16 | 17 | Dependencies to exclude from pre-bundling. 18 | 19 | :::warning CommonJS 20 | CommonJS dependencies should not be excluded from optimization. If an ESM dependency is excluded from optimization, but has a nested CommonJS dependency, the CommonJS dependency should be added to `optimizeDeps.include`. Example: 21 | 22 | ```js 23 | export default defineConfig({ 24 | optimizeDeps: { 25 | include: ['esm-dep > cjs-dep'] 26 | } 27 | }) 28 | ``` 29 | 30 | ::: 31 | 32 | ## optimizeDeps.include 33 | 34 | - **Type:** `string[]` 35 | 36 | By default, linked packages not inside `node_modules` are not pre-bundled. Use this option to force a linked package to be pre-bundled. 37 | 38 | ## optimizeDeps.esbuildOptions 39 | 40 | - **Type:** [`EsbuildBuildOptions`](https://esbuild.github.io/api/#simple-options) 41 | 42 | Options to pass to esbuild during the dep scanning and optimization. 43 | 44 | Certain options are omitted since changing them would not be compatible with Vite's dep optimization. 45 | 46 | - `external` is also omitted, use Vite's `optimizeDeps.exclude` option 47 | - `plugins` are merged with Vite's dep plugin 48 | 49 | ## optimizeDeps.force 50 | 51 | - **Type:** `boolean` 52 | 53 | Set to `true` to force dependency pre-bundling, ignoring previously cached optimized dependencies. 54 | -------------------------------------------------------------------------------- /docs/runtime-core/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 实现初始化 component 主流程 3 | --- 4 | 5 | # 实现初始化 component 主流程 6 | 7 | .vue 文件 template 模版最终都被编译成 render 函数 和 setup 函数, 8 | 我们先实现从组件模式下的流程 9 | 10 | ```ts 11 | const app = { 12 | render() { 13 | return h( 14 | "div", 15 | { 16 | id: "root", 17 | class: ["red", "blue"], 18 | }, 19 | `hi mini-vue3 ${this.msg}` 20 | ); 21 | }, 22 | setup() { 23 | return { 24 | msg: "我是 msg", 25 | }; 26 | }, 27 | }; 28 | ``` 29 | 30 | 我们需要创建一个 createApp 函数 接收一个根组件, 然后返回一个对象,这个对象有一个 mount 函数这个函数我们需要挂载到我们指定的 dom 节点上 31 | 32 | ```ts 33 | function createApp(rootComponent) { 34 | return { 35 | mount(rootContainer) { 36 | // 根组件接下来会有一堆其他的子组件 我们所有的操作 都要基于vnode 处理 37 | const vnode = createVNode(rootComponent); 38 | // 然后我们在把 vnode 解析出来之后 我们在去渲染到 我们给定的 rootcontainer 39 | render(vnode, rootContainer); 40 | }, 41 | }; 42 | } 43 | ``` 44 | 45 | 然后我们第一步先生成一个虚拟节点 虚拟 dom 实际就是一个对象 46 | 我们当前传递的是根 app 组件 type 就是我们当前的 app 实例 47 | 48 | ```ts 49 | function createVNode(type, props?, children?) { 50 | const vnode = { 51 | type, 52 | props, 53 | children, 54 | }; 55 | return vnode; 56 | } 57 | ``` 58 | 59 | 然后我们去实现 render 函数 接收一个 vnode 并且需要一个 contianer 挂载 60 | 61 | ```ts 62 | function render(vnode, container) { 63 | // 调用patch方法 进行后续判断type 是否是 标签还是 组件 children 递归处理 64 | // 我们先判断 类型进行递归处理 65 | patch(vnode, container); 66 | } 67 | 68 | function patch(vnode, container) { 69 | // 处理组件 70 | processComponent(vnode, container); 71 | } 72 | // 处理组件流程 73 | function processComponent(vnode, container) { 74 | mountComponent(vnode, container); 75 | } 76 | // 挂载组件 77 | function mountComponent(vnode, container) { 78 | // 拿到当前组件实例 我们先简单一点 就是虚拟节点 79 | const instance = createComponentInstance(vnode, container); 80 | // 设置setupcomponent 81 | setupComponent(instance); 82 | } 83 | 84 | // 创建组件实例 我们需要抽离出来一个instance 后续挂载什么 props啊 slots 啥的 85 | function createComponentInstance(vnode, container) { 86 | const component = { 87 | vnode, 88 | type: vnode.type 89 | }; 90 | return component; 91 | } 92 | 93 | function setupComponent(instance) { 94 | // 初始化 props 和 初始化 slots 95 | // initProps() 96 | // initSlots() 97 | 98 | setupStatefulComponent(instance); 99 | } 100 | function setupStatefulComponent(instance) { 101 | const component = instance.vnode.type; 102 | const { setup } = component; 103 | if (setup) { 104 | // 这个setup 可能会返回一个function 或者 返回一个object 105 | // 如果返回一个function 那么就是代表 setup返回的是一个render 函数 106 | // 如果返回的是一个object 那么我们可以直接注入到当前组件上下文中 107 | const setupResult = setup(); 108 | // 处理result结果 109 | handleSetupResult(instance, setupResult); 110 | } 111 | } 112 | 113 | // 处理 result 是什么类型的结果 114 | function handleSetupResult(instance, setupResult) { 115 | // 我们先只实现setup返回对象的情况 就是带有返回值的情况 116 | if(type of setupResult === 'object') { 117 | // 我们就把 instance 中 注入一下 我们返回的上下文数据 118 | instance.setupState = setupResult 119 | } 120 | // 因为我们setup返回的是一个对象, 那么我们就要保证我们instance 实例中必须有render函数 121 | finishComponentSetup(instance) { 122 | const component = instance.type 123 | // 写了render 我们就给实例也赋值一个render 124 | if (component.render) { 125 | instance.render = component.render 126 | } 127 | } 128 | } 129 | 130 | // 我们在刚才挂载 component的时候先把instance 获取出来 然后配置 setup的state 131 | // 接下来我们就需要调用render函数 类似一个 render effect 132 | function mountComponent(vnode, container) { 133 | // 拿到当前组件实例 我们先简单一点 就是虚拟节点 134 | const instance = createComponentInstance(vnode, container); 135 | // 设置setupcomponent 136 | setupComponent(instance); 137 | 138 | // effect 函数 139 | setupRenderEffect(instance, container) 140 | } 141 | 142 | // 开始渲染调用render 函数 143 | function setupRenderEffect(instance, container) { 144 | const subTree = instance.render() 145 | // vnode 树 是一个 h函数 146 | // 我们需要在继续调用patch 147 | // 我们返回的是一个 element 在处理 element的时候 148 | patch(subTree, container) 149 | } 150 | 151 | // 我们需要判断是不是element 152 | function patch(vnode, container) { 153 | // 处理组件 154 | // 判断vnode 是不是一个element 如果是 element 就处理元素类型 155 | if (typeof vnode.type === 'string') { 156 | processElement(vnode, container) 157 | } else if (isObject(vnode.type) { 158 | processComponent(vnode, container); 159 | } 160 | } 161 | 162 | // 处理element 163 | function processElement(vnode, container) { 164 | mountElement(vnode, container) 165 | } 166 | 167 | function mountElement(vnode, container) { 168 | const el = document.createElement(vnode.type); 169 | el.textContent = vnode.children 170 | for (const item in vnode.props) { 171 | el.setAttribute(item, vnode.props[item]) 172 | } 173 | container.append(el) 174 | } 175 | 176 | ``` 177 | -------------------------------------------------------------------------------- /docs/runtime-core/preview-options.md: -------------------------------------------------------------------------------- 1 | # Preview Options 2 | 3 | ## preview.host 4 | 5 | - **Type:** `string | boolean` 6 | - **Default:** [`server.host`](./server-options#server-host) 7 | 8 | Specify which IP addresses the server should listen on. 9 | Set this to `0.0.0.0` or `true` to listen on all addresses, including LAN and public addresses. 10 | 11 | This can be set via the CLI using `--host 0.0.0.0` or `--host`. 12 | 13 | ::: tip NOTE 14 | 15 | There are cases when other servers might respond instead of Vite. 16 | See [`server.host`](./server-options#server-host) for more details. 17 | 18 | ::: 19 | 20 | ## preview.port 21 | 22 | - **Type:** `number` 23 | - **Default:** `4173` 24 | 25 | Specify server port. Note if the port is already being used, Vite will automatically try the next available port so this may not be the actual port the server ends up listening on. 26 | 27 | **Example:** 28 | 29 | ```js 30 | export default defineConfig({ 31 | server: { 32 | port: 3030 33 | }, 34 | preview: { 35 | port: 8080 36 | } 37 | }) 38 | ``` 39 | 40 | ## preview.strictPort 41 | 42 | - **Type:** `boolean` 43 | - **Default:** [`server.strictPort`](./server-options#server-strictport) 44 | 45 | Set to `true` to exit if port is already in use, instead of automatically trying the next available port. 46 | 47 | ## preview.https 48 | 49 | - **Type:** `boolean | https.ServerOptions` 50 | - **Default:** [`server.https`](./server-options#server-https) 51 | 52 | Enable TLS + HTTP/2. Note this downgrades to TLS only when the [`server.proxy` option](./server-options#server-proxy) is also used. 53 | 54 | The value can also be an [options object](https://nodejs.org/api/https.html#https_https_createserver_options_requestlistener) passed to `https.createServer()`. 55 | 56 | ## preview.open 57 | 58 | - **Type:** `boolean | string` 59 | - **Default:** [`server.open`](./server-options#server-open) 60 | 61 | Automatically open the app in the browser on server start. When the value is a string, it will be used as the URL's pathname. If you want to open the server in a specific browser you like, you can set the env `process.env.BROWSER` (e.g. `firefox`). See [the `open` package](https://github.com/sindresorhus/open#app) for more details. 62 | 63 | ## preview.proxy 64 | 65 | - **Type:** `Record` 66 | - **Default:** [`server.proxy`](./server-options#server-proxy) 67 | 68 | Configure custom proxy rules for the dev server. Expects an object of `{ key: options }` pairs. If the key starts with `^`, it will be interpreted as a `RegExp`. The `configure` option can be used to access the proxy instance. 69 | 70 | Uses [`http-proxy`](https://github.com/http-party/node-http-proxy). Full options [here](https://github.com/http-party/node-http-proxy#options). 71 | 72 | ## preview.cors 73 | 74 | - **Type:** `boolean | CorsOptions` 75 | - **Default:** [`server.cors`](./server-options#server-cors) 76 | 77 | Configure CORS for the dev server. This is enabled by default and allows any origin. Pass an [options object](https://github.com/expressjs/cors) to fine tune the behavior or `false` to disable. 78 | -------------------------------------------------------------------------------- /docs/runtime-core/ssr-options.md: -------------------------------------------------------------------------------- 1 | # SSR Options 2 | 3 | - **Related:** [SSR Externals](/guide/ssr#ssr-externals) 4 | 5 | :::warning Experimental 6 | SSR options may be adjusted in minor releases. 7 | ::: 8 | 9 | ## ssr.external 10 | 11 | - **Type:** `string[]` 12 | 13 | Force externalize dependencies for SSR. 14 | 15 | ## ssr.noExternal 16 | 17 | - **Type:** `string | RegExp | (string | RegExp)[] | true` 18 | 19 | Prevent listed dependencies from being externalized for SSR. If `true`, no dependencies are externalized. 20 | 21 | ## ssr.target 22 | 23 | - **Type:** `'node' | 'webworker'` 24 | - **Default:** `node` 25 | 26 | Build target for the SSR server. 27 | 28 | ## ssr.format 29 | 30 | - **Type:** `'esm' | 'cjs'` 31 | - **Default:** `esm` 32 | - **Experimental** 33 | 34 | Build format for the SSR server. Since Vite v3 the SSR build generates ESM by default. `'cjs'` can be selected to generate a CJS build, but it isn't recommended. The option is left marked as experimental to give users more time to update to ESM. CJS builds requires complex externalization heuristics that aren't present in the ESM format. 35 | -------------------------------------------------------------------------------- /docs/runtime-core/worker-options.md: -------------------------------------------------------------------------------- 1 | # Worker Options 2 | 3 | ## worker.format 4 | 5 | - **Type:** `'es' | 'iife'` 6 | - **Default:** `iife` 7 | 8 | Output format for worker bundle. 9 | 10 | ## worker.plugins 11 | 12 | - **Type:** [`(Plugin | Plugin[])[]`](./shared-options#plugins) 13 | 14 | Vite plugins that apply to worker bundle. Note that [config.plugins](./shared-options#plugins) does not apply to workers, it should be configured here instead. 15 | 16 | ## worker.rollupOptions 17 | 18 | - **Type:** [`RollupOptions`](https://rollupjs.org/guide/en/#big-list-of-options) 19 | 20 | Rollup options to build worker bundle. 21 | -------------------------------------------------------------------------------- /docs/runtime-dom/index.md: -------------------------------------------------------------------------------- 1 | # Plugins 2 | 3 | :::tip NOTE 4 | Vite aims to provide out-of-the-box support for common web development patterns. Before searching for a Vite or Compatible Rollup plugin, check out the [Features Guide](../guide/features.md). A lot of the cases where a plugin would be needed in a Rollup project are already covered in Vite. 5 | ::: 6 | 7 | Check out [Using Plugins](../guide/using-plugins) for information on how to use plugins. 8 | 9 | ## Official Plugins 10 | 11 | ### [@vitejs/plugin-vue](https://github.com/vitejs/vite/tree/main/packages/plugin-vue) 12 | 13 | - Provides Vue 3 Single File Components support. 14 | 15 | ### [@vitejs/plugin-vue-jsx](https://github.com/vitejs/vite/tree/main/packages/plugin-vue-jsx) 16 | 17 | - Provides Vue 3 JSX support (via [dedicated Babel transform](https://github.com/vuejs/jsx-next)). 18 | 19 | ### [@vitejs/plugin-react](https://github.com/vitejs/vite/tree/main/packages/plugin-react) 20 | 21 | - Provides all-in-one React Support. 22 | 23 | ### [@vitejs/plugin-legacy](https://github.com/vitejs/vite/tree/main/packages/plugin-legacy) 24 | 25 | - Provides legacy browsers support for the production build. 26 | 27 | ## Community Plugins 28 | 29 | Check out [awesome-vite](https://github.com/vitejs/awesome-vite#plugins) - you can also submit a PR to list your plugins there. 30 | 31 | ## Rollup Plugins 32 | 33 | [Vite plugins](../guide/api-plugin) are an extension of Rollup's plugin interface. Check out the [Rollup Plugin Compatibility section](../guide/api-plugin#rollup-plugin-compatibility) for more information. 34 | -------------------------------------------------------------------------------- /docs/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | 3 | export default defineConfig({ 4 | ssr: { 5 | format: 'cjs' 6 | }, 7 | legacy: { 8 | buildSsrCjsExternalHeuristics: true 9 | }, 10 | optimizeDeps: { 11 | // vitepress is aliased with replacement `join(DIST_CLIENT_PATH, '/index')` 12 | // This needs to be excluded from optimization 13 | exclude: ['vitepress'] 14 | } 15 | }) 16 | -------------------------------------------------------------------------------- /example/app.js: -------------------------------------------------------------------------------- 1 | import { h } from "../packages/runtime-core/dist/index.mjs"; 2 | import { foo } from "./foo.js"; 3 | window.self = null; 4 | const app = { 5 | render() { 6 | window.self = this; 7 | return h( 8 | "div", 9 | { 10 | id: "root", 11 | class: ["red"], 12 | onClick() { 13 | // console.log("被点击了"); 14 | }, 15 | onMousedown() { 16 | // console.log("鼠标按下了"); 17 | }, 18 | }, 19 | [ 20 | h("div", { class: "" }, `hi mini-vue3 ${this.msg}`), 21 | h("div", { class: "red" }, "我是第一个子节点"), 22 | h("div", { class: "bbb" }, "我是第二个子节点"), 23 | h(foo, { 24 | count: 123, 25 | onAdd(res) { 26 | console.log(res, "我是app 组件发生的点击"); 27 | }, 28 | onAddFoo(res) { 29 | console.log("我是涮羊肉"); 30 | } 31 | }), 32 | ] 33 | ); 34 | }, 35 | setup() { 36 | return { 37 | msg: "我是 msg", 38 | }; 39 | }, 40 | }; 41 | 42 | export default app; 43 | -------------------------------------------------------------------------------- /example/foo.js: -------------------------------------------------------------------------------- 1 | import { h } from "../packages/runtime-core/dist/index.mjs"; 2 | 3 | export const foo = { 4 | render() { 5 | const btn = h( 6 | "button", 7 | { 8 | onClick: this.emitAdd, 9 | }, 10 | "emitAdd" 11 | ); 12 | const foo = h("h2", {}, "h2 & foo"); 13 | return h("h1", { class: "color" }, [foo, this.$slots, btn]); 14 | }, 15 | setup(props, { emit }) { 16 | console.log(props); 17 | props.count = 10; 18 | const emitAdd = () => { 19 | console.log("emit add"); 20 | emit("add", 456456465); 21 | emit("add-foo", 456456465); 22 | }; 23 | return { 24 | msg: "我是foo子组件", 25 | emitAdd, 26 | }; 27 | }, 28 | }; 29 | -------------------------------------------------------------------------------- /example/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | -------------------------------------------------------------------------------- /example/main.js: -------------------------------------------------------------------------------- 1 | import App from "./app.js"; 2 | import { createApp } from "../packages/runtime-core/dist/index.mjs"; 3 | const rootContainer = document.querySelector("#app"); 4 | console.log(rootContainer); 5 | createApp(App).mount(rootContainer); 6 | -------------------------------------------------------------------------------- /index.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ErKeLost/extreme-vue/849882815db65d19d16f40bc70b014bec9922829/index.ts -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */ 2 | module.exports = { 3 | preset: "ts-jest", 4 | testEnvironment: "node", 5 | watchPathIgnorePatterns: ["/node_modules/", "/dist/", "/.git/"], 6 | moduleFileExtensions: ["ts", "tsx", "js", "json"], 7 | moduleNameMapper: { 8 | "^@relaxed/(.*?)$": "/packages/$1/src", 9 | }, 10 | rootDir: __dirname, 11 | testMatch: ["/packages/**/__tests__/**/*spec.[jt]s?(x)"], 12 | testPathIgnorePatterns: ["/node_modules/"], 13 | }; 14 | -------------------------------------------------------------------------------- /netlify.toml: -------------------------------------------------------------------------------- 1 | [build.environment] 2 | NODE_VERSION = "16" 3 | NPM_FLAGS = "--version" # prevent Netlify npm install 4 | 5 | [build] 6 | publish = "docs/.vitepress/dist" 7 | command = "npx pnpm i --store=node_modules/.pnpm-store && npm run build:docs" 8 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "version": "0.0.1", 4 | "description": "Help you learn more efficiently vue3 source code", 5 | "scripts": { 6 | "dev": "turbo run dev --filter=playground --filter=@relaxed/runtime-core", 7 | "build": "turbo run dev --filter=@relaxed/reactivity", 8 | "docs": "vitepress dev docs", 9 | "test": "jest --no-cache", 10 | "build:docs": "vitepress build docs" 11 | }, 12 | "devDependencies": { 13 | "@types/jest": "^26.0.24", 14 | "jest": "^27.5.1", 15 | "rollup": "^2.17.1", 16 | "ts-jest": "^27.0.5", 17 | "tslib": "^2.3.1", 18 | "tsup": "^6.1.3", 19 | "turbo": "^1.4.6", 20 | "typescript": "^4.4.3", 21 | "vitepress": "^1.0.0-alpha.15", 22 | "vue": "3.2.25" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /packages/compiler-core/__tests__/__snapshots__/codegen.spec.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`element and interpolation 1`] = ` 4 | " 5 | const { toDisplayString : _toDisplayString, createElementVNode : _createElementVNode} = Vue 6 | 7 | 8 | return function render(_ctx) {return _createElementVNode('div', null, 'hi,' + _toDisplayString(_ctx.msg))}" 9 | `; 10 | 11 | exports[`interpolation module 1`] = ` 12 | " 13 | const { toDisplayString : _toDisplayString} = Vue 14 | 15 | 16 | return function render(_ctx) {return _toDisplayString(_ctx.hello)}" 17 | `; 18 | -------------------------------------------------------------------------------- /packages/compiler-core/__tests__/codegen.spec.ts: -------------------------------------------------------------------------------- 1 | import { generate } from "../src/codegen"; 2 | import { baseParse } from "../src/parse"; 3 | import { transform } from "../src/transform"; 4 | import { transformElement } from "../src/transforms/transformElement"; 5 | import { transformExpression } from "../src/transforms/transformExpression"; 6 | import { transformText } from "../src/transforms/transformText"; 7 | 8 | test("interpolation module", () => { 9 | const ast = baseParse("{{hello}}"); 10 | transform(ast, { 11 | nodeTransforms: [transformExpression], 12 | }); 13 | 14 | const { code } = generate(ast); 15 | expect(code).toMatchSnapshot(); 16 | }); 17 | 18 | test("element and interpolation", () => { 19 | const ast = baseParse("
hi,{{msg}}
"); 20 | transform(ast, { 21 | nodeTransforms: [transformElement, transformText, transformExpression], 22 | }); 23 | 24 | const { code } = generate(ast); 25 | expect(code).toMatchSnapshot(); 26 | }); 27 | -------------------------------------------------------------------------------- /packages/compiler-core/__tests__/parse.spec.ts: -------------------------------------------------------------------------------- 1 | import { ElementTypes, NodeTypes } from "../src/ast"; 2 | import { baseParse } from "../src/parse"; 3 | 4 | describe("parser", () => { 5 | describe("text", () => { 6 | test("simple text", () => { 7 | const ast = baseParse("some text"); 8 | const text = ast.children[0]; 9 | 10 | expect(text).toStrictEqual({ 11 | type: NodeTypes.TEXT, 12 | content: "some text", 13 | }); 14 | }); 15 | 16 | test("simple text with invalid end tag", () => { 17 | const ast = baseParse("some text"); 18 | const text = ast.children[0]; 19 | 20 | expect(text).toStrictEqual({ 21 | type: NodeTypes.TEXT, 22 | content: "some text", 23 | }); 24 | }); 25 | 26 | test("text with interpolation", () => { 27 | const ast = baseParse("some {{ foo + bar }} text"); 28 | const text1 = ast.children[0]; 29 | const text2 = ast.children[2]; 30 | 31 | // ast.children[1] 应该是 interpolation 32 | expect(text1).toStrictEqual({ 33 | type: NodeTypes.TEXT, 34 | content: "some ", 35 | }); 36 | expect(text2).toStrictEqual({ 37 | type: NodeTypes.TEXT, 38 | content: " text", 39 | }); 40 | }); 41 | }); 42 | 43 | describe("Interpolation", () => { 44 | test("simple interpolation", () => { 45 | // 1. 看看是不是一个 {{ 开头的 46 | // 2. 是的话,那么就作为 插值来处理 47 | // 3. 获取内部 message 的内容即可 48 | const ast = baseParse("{{message}}"); 49 | const interpolation = ast.children[0]; 50 | 51 | expect(interpolation).toStrictEqual({ 52 | type: NodeTypes.INTERPOLATION, 53 | content: { 54 | type: NodeTypes.SIMPLE_EXPRESSION, 55 | content: `message`, 56 | }, 57 | }); 58 | }); 59 | }); 60 | 61 | describe("Element", () => { 62 | test("simple div", () => { 63 | const ast = baseParse("
hello
"); 64 | const element = ast.children[0]; 65 | 66 | expect(element).toStrictEqual({ 67 | type: NodeTypes.ELEMENT, 68 | tag: "div", 69 | tagType: ElementTypes.ELEMENT, 70 | children: [ 71 | { 72 | type: NodeTypes.TEXT, 73 | content: "hello", 74 | }, 75 | ], 76 | }); 77 | }); 78 | 79 | test("element with interpolation", () => { 80 | const ast = baseParse("
{{ msg }}
"); 81 | const element = ast.children[0]; 82 | 83 | expect(element).toStrictEqual({ 84 | type: NodeTypes.ELEMENT, 85 | tag: "div", 86 | tagType: ElementTypes.ELEMENT, 87 | children: [ 88 | { 89 | type: NodeTypes.INTERPOLATION, 90 | content: { 91 | type: NodeTypes.SIMPLE_EXPRESSION, 92 | content: `msg`, 93 | }, 94 | }, 95 | ], 96 | }); 97 | }); 98 | 99 | test("element with interpolation and text", () => { 100 | const ast = baseParse("
hi,{{ msg }}
"); 101 | const element = ast.children[0]; 102 | 103 | expect(element).toStrictEqual({ 104 | type: NodeTypes.ELEMENT, 105 | tag: "div", 106 | tagType: ElementTypes.ELEMENT, 107 | children: [ 108 | { 109 | type: NodeTypes.TEXT, 110 | content: "hi,", 111 | }, 112 | { 113 | type: NodeTypes.INTERPOLATION, 114 | content: { 115 | type: NodeTypes.SIMPLE_EXPRESSION, 116 | content: "msg", 117 | }, 118 | }, 119 | ], 120 | }); 121 | }); 122 | 123 | test("should throw error when lack end tag ", () => { 124 | expect(() => { 125 | baseParse("
"); 126 | }).toThrow("缺失结束标签:span"); 127 | }); 128 | }); 129 | }); 130 | -------------------------------------------------------------------------------- /packages/compiler-core/__tests__/transform.spec.ts: -------------------------------------------------------------------------------- 1 | import { baseParse } from "../src/parse"; 2 | import { TO_DISPLAY_STRING } from "../src/runtimeHelpers"; 3 | import { transform } from "../src/transform"; 4 | describe("Compiler: transform", () => { 5 | test("context state", () => { 6 | const ast = baseParse(`
hello {{ world }}
`); 7 | console.log(ast); 8 | 9 | // manually store call arguments because context is mutable and shared 10 | // across calls 11 | const calls: any[] = []; 12 | const plugin = (node, context) => { 13 | calls.push([node, { ...context }]); 14 | }; 15 | 16 | transform(ast, { 17 | nodeTransforms: [plugin], 18 | }); 19 | 20 | const div = ast.children[0]; 21 | expect(calls.length).toBe(4); 22 | expect(calls[0]).toMatchObject([ 23 | ast, 24 | {}, 25 | // TODO 26 | // { 27 | // parent: null, 28 | // currentNode: ast, 29 | // }, 30 | ]); 31 | expect(calls[1]).toMatchObject([ 32 | div, 33 | {}, 34 | // TODO 35 | // { 36 | // parent: ast, 37 | // currentNode: div, 38 | // }, 39 | ]); 40 | expect(calls[2]).toMatchObject([ 41 | div.children[0], 42 | {}, 43 | // { 44 | // parent: div, 45 | // currentNode: div.children[0], 46 | // }, 47 | ]); 48 | expect(calls[3]).toMatchObject([ 49 | div.children[1], 50 | {}, 51 | // { 52 | // parent: div, 53 | // currentNode: div.children[1], 54 | // }, 55 | ]); 56 | }); 57 | 58 | test("should inject toString helper for interpolations", () => { 59 | const ast = baseParse(`{{ foo }}`); 60 | transform(ast, {}); 61 | expect(ast.helpers).toContain(TO_DISPLAY_STRING); 62 | }); 63 | }); 64 | -------------------------------------------------------------------------------- /packages/compiler-core/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@relaxed/compiler-core", 3 | "version": "1.0.0", 4 | "description": "@relaxed/compiler-core", 5 | "scripts": { 6 | "test": "echo \"Error: no test specified\" && exit 1" 7 | }, 8 | "keywords": [], 9 | "author": "", 10 | "license": "ISC", 11 | "dependencies": { 12 | "@relaxed/shared": "workspace:^1.0.0" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /packages/compiler-core/src/ast.ts: -------------------------------------------------------------------------------- 1 | import { CREATE_ELEMENT_VNODE } from "./runtimeHelpers"; 2 | 3 | export const enum NodeTypes { 4 | TEXT, 5 | ROOT, 6 | INTERPOLATION, 7 | SIMPLE_EXPRESSION, 8 | ELEMENT, 9 | COMPOUND_EXPRESSION 10 | } 11 | 12 | export const enum ElementTypes { 13 | ELEMENT, 14 | } 15 | 16 | export function createSimpleExpression(content) { 17 | return { 18 | type: NodeTypes.SIMPLE_EXPRESSION, 19 | content, 20 | }; 21 | } 22 | 23 | export function createInterpolation(content) { 24 | return { 25 | type: NodeTypes.INTERPOLATION, 26 | content: content, 27 | }; 28 | } 29 | 30 | export function createVNodeCall(context, tag, props?, children?) { 31 | if (context) { 32 | context.helper(CREATE_ELEMENT_VNODE); 33 | } 34 | 35 | return { 36 | // TODO vue3 里面这里的 type 是 VNODE_CALL 37 | // 是为了 block 而 mini-vue 里面没有实现 block 38 | // 所以创建的是 Element 类型就够用了 39 | type: NodeTypes.ELEMENT, 40 | tag, 41 | props, 42 | children, 43 | }; 44 | } 45 | -------------------------------------------------------------------------------- /packages/compiler-core/src/codegen.ts: -------------------------------------------------------------------------------- 1 | import { isString } from "@relaxed/shared"; 2 | import { NodeTypes } from "./ast"; 3 | import { 4 | CREATE_ELEMENT_VNODE, 5 | helperNameMap, 6 | TO_DISPLAY_STRING, 7 | } from "./runtimeHelpers"; 8 | 9 | export function generate(ast, options = {}) { 10 | // 先生成 context 11 | const context = createCodegenContext(ast, options); 12 | const { push, mode } = context; 13 | 14 | // 1. 先生成 preambleContext 15 | 16 | if (mode === "module") { 17 | genModulePreamble(ast, context); 18 | } else { 19 | genFunctionPreamble(ast, context); 20 | } 21 | 22 | const functionName = "render"; 23 | 24 | const args = ["_ctx"]; 25 | 26 | // _ctx,aaa,bbb,ccc 27 | // 需要把 args 处理成 上面的 string 28 | const signature = args.join(", "); 29 | push(`function ${functionName}(${signature}) {`); 30 | // 这里需要生成具体的代码内容 31 | // 开始生成 vnode tree 的表达式 32 | push("return "); 33 | genNode(ast.codegenNode, context); 34 | 35 | push("}"); 36 | 37 | return { 38 | code: context.code, 39 | }; 40 | } 41 | 42 | function genFunctionPreamble(ast: any, context: any) { 43 | const { runtimeGlobalName, push, newline } = context; 44 | const VueBinging = runtimeGlobalName; 45 | 46 | const aliasHelper = (s) => `${helperNameMap[s]} : _${helperNameMap[s]}`; 47 | 48 | if (ast.helpers.length > 0) { 49 | push( 50 | ` 51 | const { ${ast.helpers.map(aliasHelper).join(", ")}} = ${VueBinging} 52 | 53 | ` 54 | ); 55 | } 56 | 57 | newline(); 58 | push(`return `); 59 | } 60 | 61 | function genNode(node: any, context: any) { 62 | // 生成代码的规则就是读取 node ,然后基于不同的 node 来生成对应的代码块 63 | // 然后就是把代码快给拼接到一起就可以了 64 | 65 | switch (node.type) { 66 | case NodeTypes.INTERPOLATION: 67 | genInterpolation(node, context); 68 | break; 69 | case NodeTypes.SIMPLE_EXPRESSION: 70 | genExpression(node, context); 71 | break; 72 | 73 | case NodeTypes.ELEMENT: 74 | genElement(node, context); 75 | break; 76 | 77 | case NodeTypes.COMPOUND_EXPRESSION: 78 | genCompoundExpression(node, context); 79 | break; 80 | 81 | case NodeTypes.TEXT: 82 | genText(node, context); 83 | break; 84 | 85 | default: 86 | break; 87 | } 88 | } 89 | 90 | function genCompoundExpression(node: any, context: any) { 91 | const { push } = context; 92 | for (let i = 0; i < node.children.length; i++) { 93 | const child = node.children[i]; 94 | if (isString(child)) { 95 | push(child); 96 | } else { 97 | genNode(child, context); 98 | } 99 | } 100 | } 101 | 102 | function genText(node: any, context: any) { 103 | // Implement 104 | const { push } = context; 105 | 106 | push(`'${node.content}'`); 107 | } 108 | 109 | function genElement(node, context) { 110 | const { push, helper } = context; 111 | const { tag, props, children } = node; 112 | 113 | push(`${helper(CREATE_ELEMENT_VNODE)}(`); 114 | 115 | genNodeList(genNullableArgs([tag, props, children]), context); 116 | 117 | push(`)`); 118 | } 119 | 120 | function genNodeList(nodes: any, context: any) { 121 | const { push } = context; 122 | for (let i = 0; i < nodes.length; i++) { 123 | const node = nodes[i]; 124 | 125 | if (isString(node)) { 126 | push(`${node}`); 127 | } else { 128 | genNode(node, context); 129 | } 130 | // node 和 node 之间需要加上 逗号(,) 131 | // 但是最后一个不需要 "div", [props], [children] 132 | if (i < nodes.length - 1) { 133 | push(", "); 134 | } 135 | } 136 | } 137 | 138 | function genNullableArgs(args) { 139 | // 把末尾为null 的都删除掉 140 | // vue3源码中,后面可能会包含 patchFlag、dynamicProps 等编译优化的信息 141 | // 而这些信息有可能是不存在的,所以在这边的时候需要删除掉 142 | let i = args.length; 143 | // 这里 i-- 用的还是特别的巧妙的 144 | // 当为0 的时候自然就退出循环了 145 | while (i--) { 146 | if (args[i] != null) break; 147 | } 148 | 149 | // 把为 falsy 的值都替换成 "null" 150 | return args.slice(0, i + 1).map((arg) => arg || "null"); 151 | } 152 | 153 | function genExpression(node: any, context: any) { 154 | context.push(node.content, node); 155 | } 156 | 157 | function genInterpolation(node: any, context: any) { 158 | const { push, helper } = context; 159 | push(`${helper(TO_DISPLAY_STRING)}(`); 160 | genNode(node.content, context); 161 | push(")"); 162 | } 163 | 164 | function genModulePreamble(ast, context) { 165 | // preamble 就是 import 语句 166 | const { push, newline, runtimeModuleName } = context; 167 | 168 | if (ast.helpers.length) { 169 | // 比如 ast.helpers 里面有个 [toDisplayString] 170 | // 那么生成之后就是 import { toDisplayString as _toDisplayString } from "vue" 171 | const code = `import {${ast.helpers 172 | .map((s) => `${helperNameMap[s]} as _${helperNameMap[s]}`) 173 | .join(", ")} } from ${JSON.stringify(runtimeModuleName)}`; 174 | 175 | push(code); 176 | } 177 | 178 | newline(); 179 | push(`export `); 180 | } 181 | 182 | function createCodegenContext( 183 | ast: any, 184 | { runtimeModuleName = "vue", runtimeGlobalName = "Vue", mode = "function" } 185 | ): any { 186 | const context = { 187 | code: "", 188 | mode, 189 | runtimeModuleName, 190 | runtimeGlobalName, 191 | helper(key) { 192 | return `_${helperNameMap[key]}`; 193 | }, 194 | push(code) { 195 | context.code += code; 196 | }, 197 | newline() { 198 | // 换新行 199 | // TODO 需要额外处理缩进 200 | context.code += "\n"; 201 | }, 202 | }; 203 | 204 | return context; 205 | } 206 | -------------------------------------------------------------------------------- /packages/compiler-core/src/compile.ts: -------------------------------------------------------------------------------- 1 | import { generate } from "./codegen"; 2 | import { baseParse } from "./parse"; 3 | import { transform } from "./transform"; 4 | import { transformExpression } from "./transforms/transformExpression"; 5 | import { transformElement } from "./transforms/transformElement"; 6 | import { transformText } from "./transforms/transformText"; 7 | 8 | export function baseCompile(template, options) { 9 | // 1. 先把 template 也就是字符串 parse 成 ast 10 | const ast = baseParse(template); 11 | // 2. 给 ast 加点料(- -#) 12 | transform( 13 | ast, 14 | Object.assign(options, { 15 | nodeTransforms: [transformElement, transformText, transformExpression], 16 | }) 17 | ); 18 | 19 | // 3. 生成 render 函数代码 20 | return generate(ast); 21 | } 22 | -------------------------------------------------------------------------------- /packages/compiler-core/src/index.ts: -------------------------------------------------------------------------------- 1 | export { baseCompile } from "./compile"; 2 | -------------------------------------------------------------------------------- /packages/compiler-core/src/runtimeHelpers.ts: -------------------------------------------------------------------------------- 1 | export const TO_DISPLAY_STRING = Symbol(`toDisplayString`); 2 | export const CREATE_ELEMENT_VNODE = Symbol("createElementVNode"); 3 | 4 | export const helperNameMap = { 5 | [TO_DISPLAY_STRING]: "toDisplayString", 6 | [CREATE_ELEMENT_VNODE]: "createElementVNode" 7 | }; 8 | -------------------------------------------------------------------------------- /packages/compiler-core/src/transform.ts: -------------------------------------------------------------------------------- 1 | import { NodeTypes } from "./ast"; 2 | import { TO_DISPLAY_STRING } from "./runtimeHelpers"; 3 | 4 | export function transform(root, options = {}) { 5 | // 1. 创建 context 6 | 7 | const context = createTransformContext(root, options); 8 | 9 | // 2. 遍历 node 10 | traverseNode(root, context); 11 | 12 | createRootCodegen(root, context); 13 | 14 | root.helpers.push(...context.helpers.keys()); 15 | } 16 | 17 | function traverseNode(node: any, context) { 18 | const type: NodeTypes = node.type; 19 | 20 | // 遍历调用所有的 nodeTransforms 21 | // 把 node 给到 transform 22 | // 用户可以对 node 做处理 23 | const nodeTransforms = context.nodeTransforms; 24 | const exitFns: any = []; 25 | for (let i = 0; i < nodeTransforms.length; i++) { 26 | const transform = nodeTransforms[i]; 27 | 28 | const onExit = transform(node, context); 29 | if (onExit) { 30 | exitFns.push(onExit); 31 | } 32 | } 33 | 34 | switch (type) { 35 | case NodeTypes.INTERPOLATION: 36 | // 插值的点,在于后续生成 render 代码的时候是获取变量的值 37 | context.helper(TO_DISPLAY_STRING); 38 | break; 39 | 40 | case NodeTypes.ROOT: 41 | case NodeTypes.ELEMENT: 42 | 43 | traverseChildren(node, context); 44 | break; 45 | 46 | default: 47 | break; 48 | } 49 | 50 | 51 | 52 | let i = exitFns.length; 53 | // i-- 这个很巧妙 54 | // 使用 while 是要比 for 快 (可以使用 https://jsbench.me/ 来测试一下) 55 | while (i--) { 56 | exitFns[i](); 57 | } 58 | } 59 | 60 | function traverseChildren(parent: any, context: any) { 61 | // node.children 62 | parent.children.forEach((node) => { 63 | // TODO 需要设置 context 的值 64 | traverseNode(node, context); 65 | }); 66 | } 67 | 68 | function createTransformContext(root, options): any { 69 | const context = { 70 | root, 71 | nodeTransforms: options.nodeTransforms || [], 72 | helpers: new Map(), 73 | helper(name) { 74 | // 这里会收集调用的次数 75 | // 收集次数是为了给删除做处理的, (当只有 count 为0 的时候才需要真的删除掉) 76 | // helpers 数据会在后续生成代码的时候用到 77 | const count = context.helpers.get(name) || 0; 78 | context.helpers.set(name, count + 1); 79 | }, 80 | }; 81 | 82 | return context; 83 | } 84 | 85 | function createRootCodegen(root: any, context: any) { 86 | const { children } = root; 87 | 88 | // 只支持有一个根节点 89 | // 并且还是一个 single text node 90 | const child = children[0]; 91 | 92 | // 如果是 element 类型的话 , 那么我们需要把它的 codegenNode 赋值给 root 93 | // root 其实是个空的什么数据都没有的节点 94 | // 所以这里需要额外的处理 codegenNode 95 | // codegenNode 的目的是专门为了 codegen 准备的 为的就是和 ast 的 node 分离开 96 | if (child.type === NodeTypes.ELEMENT && child.codegenNode) { 97 | const codegenNode = child.codegenNode; 98 | root.codegenNode = codegenNode; 99 | } else { 100 | root.codegenNode = child; 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /packages/compiler-core/src/transforms/transformElement.ts: -------------------------------------------------------------------------------- 1 | import { createVNodeCall, NodeTypes } from "../ast"; 2 | 3 | export function transformElement(node, context) { 4 | if (node.type === NodeTypes.ELEMENT) { 5 | return () => { 6 | // 没有实现 block 所以这里直接创建 element 7 | 8 | // TODO 9 | // 需要把之前的 props 和 children 等一系列的数据都处理 10 | const vnodeTag = `'${node.tag}'`; 11 | // TODO props 暂时不支持 12 | const vnodeProps = null; 13 | let vnodeChildren = null; 14 | if (node.children.length > 0) { 15 | if (node.children.length === 1) { 16 | // 只有一个孩子节点 ,那么当生成 render 函数的时候就不用 [] 包裹 17 | const child = node.children[0]; 18 | vnodeChildren = child; 19 | } 20 | } 21 | 22 | // 创建一个新的 node 用于 codegen 的时候使用 23 | node.codegenNode = createVNodeCall( 24 | context, 25 | vnodeTag, 26 | vnodeProps, 27 | vnodeChildren 28 | ); 29 | }; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /packages/compiler-core/src/transforms/transformExpression.ts: -------------------------------------------------------------------------------- 1 | import { NodeTypes } from "../ast"; 2 | 3 | export function transformExpression(node) { 4 | if (node.type === NodeTypes.INTERPOLATION) { 5 | node.content = processExpression(node.content); 6 | } 7 | } 8 | 9 | function processExpression(node) { 10 | node.content = `_ctx.${node.content}`; 11 | 12 | return node 13 | } 14 | -------------------------------------------------------------------------------- /packages/compiler-core/src/transforms/transformText.ts: -------------------------------------------------------------------------------- 1 | import { NodeTypes } from "../ast"; 2 | import { isText } from "../utils"; 3 | 4 | export function transformText(node, context) { 5 | if (node.type === NodeTypes.ELEMENT) { 6 | // 在 exit 的时期执行 7 | // 下面的逻辑会改变 ast 树 8 | // 有些逻辑是需要在改变之前做处理的 9 | return () => { 10 | // hi,{{msg}} 11 | // 上面的模块会生成2个节点,一个是 text 一个是 interpolation 的话 12 | // 生成的 render 函数应该为 "hi," + _toDisplayString(_ctx.msg) 13 | // 这里面就会涉及到添加一个 “+” 操作符 14 | // 那这里的逻辑就是处理它 15 | 16 | // 检测下一个节点是不是 text 类型,如果是的话, 那么会创建一个 COMPOUND 类型 17 | // COMPOUND 类型把 2个 text || interpolation 包裹(相当于是父级容器) 18 | 19 | const children = node.children; 20 | let currentContainer; 21 | 22 | for (let i = 0; i < children.length; i++) { 23 | const child = children[i]; 24 | 25 | if (isText(child)) { 26 | // 看看下一个节点是不是 text 类 27 | for (let j = i + 1; j < children.length; j++) { 28 | const next = children[j]; 29 | if (isText(next)) { 30 | // currentContainer 的目的是把相邻的节点都放到一个 容器内 31 | if (!currentContainer) { 32 | currentContainer = children[i] = { 33 | type: NodeTypes.COMPOUND_EXPRESSION, 34 | loc: child.loc, 35 | children: [child], 36 | }; 37 | } 38 | 39 | currentContainer.children.push(` + `, next); 40 | // 把当前的节点放到容器内, 然后删除掉j 41 | children.splice(j, 1); 42 | // 因为把 j 删除了,所以这里就少了一个元素,那么 j 需要 -- 43 | j--; 44 | } else { 45 | currentContainer = undefined; 46 | break; 47 | } 48 | } 49 | } 50 | } 51 | }; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /packages/compiler-core/src/utils.ts: -------------------------------------------------------------------------------- 1 | import { NodeTypes } from "./ast"; 2 | 3 | export function isText(node) { 4 | return node.type === NodeTypes.INTERPOLATION || node.type === NodeTypes.TEXT; 5 | } 6 | -------------------------------------------------------------------------------- /packages/compiler-core/tsup.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'tsup' 2 | 3 | export default defineConfig({ 4 | entry: ['./src/index.ts'], 5 | target: 'esnext', 6 | format: ['esm', 'cjs', 'iife'], 7 | splitting: false, 8 | sourcemap: true, 9 | clean: true, 10 | watch: true, 11 | external: ['vue'] 12 | }) 13 | -------------------------------------------------------------------------------- /packages/reactivity/.turbo/turbo-build.log: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ErKeLost/extreme-vue/849882815db65d19d16f40bc70b014bec9922829/packages/reactivity/.turbo/turbo-build.log -------------------------------------------------------------------------------- /packages/reactivity/.turbo/turbo-dev.log: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ErKeLost/extreme-vue/849882815db65d19d16f40bc70b014bec9922829/packages/reactivity/.turbo/turbo-dev.log -------------------------------------------------------------------------------- /packages/reactivity/__tests__/computed.spec.ts: -------------------------------------------------------------------------------- 1 | import { computed } from '../src/computed' 2 | import { reactive } from '../src/reactive' 3 | 4 | describe('computed', () => { 5 | it('happy path', () => { 6 | const value = reactive({ 7 | foo: 1 8 | }) 9 | 10 | const getter = computed(() => { 11 | return value.foo 12 | }) 13 | 14 | expect(getter.value).toBe(1) 15 | }) 16 | 17 | it('should compute lazily', () => { 18 | const value = reactive({ 19 | foo: 1 20 | }) 21 | const getter = jest.fn(() => { 22 | console.log(666) 23 | return value.foo 24 | }) 25 | const cValue = computed(getter) 26 | 27 | // lazy 28 | expect(getter).not.toHaveBeenCalled() 29 | 30 | expect(cValue.value).toBe(1) 31 | expect(getter).toHaveBeenCalledTimes(1) 32 | 33 | // should not compute again 34 | cValue.value 35 | expect(getter).toHaveBeenCalledTimes(1) 36 | 37 | // should not compute until needed 38 | value.foo = 2 39 | expect(getter).toHaveBeenCalledTimes(1) 40 | 41 | // now it should compute 42 | expect(cValue.value).toBe(2) 43 | expect(getter).toHaveBeenCalledTimes(2) 44 | 45 | // should not compute again 46 | cValue.value 47 | expect(getter).toHaveBeenCalledTimes(2) 48 | }) 49 | }) 50 | -------------------------------------------------------------------------------- /packages/reactivity/__tests__/dep.spec.ts: -------------------------------------------------------------------------------- 1 | 2 | describe('Dep', () => { 3 | 4 | 5 | it('new Set ', () => { 6 | // const set = new Set([1]) 7 | 8 | // console.log(set) 9 | 10 | 11 | }); 12 | 13 | 14 | }); -------------------------------------------------------------------------------- /packages/reactivity/__tests__/effect.spec.ts: -------------------------------------------------------------------------------- 1 | import { reactive } from "../src/reactive"; 2 | import { effect, stop } from "../src/effect"; 3 | 4 | describe("effect", () => { 5 | // it("should run the passed function once (wrapped by a effect)", () => { 6 | // const fnSpy = jest.fn(() => {}); 7 | // effect(fnSpy); 8 | // expect(fnSpy).toHaveBeenCalledTimes(1); 9 | // }); 10 | 11 | it("should observe basic properties", () => { 12 | let dummy; 13 | const counter = reactive({ num: 0 }); 14 | effect(() => (dummy = counter.num)); 15 | 16 | expect(dummy).toBe(0); 17 | counter.num = 7; 18 | expect(dummy).toBe(7); 19 | }); 20 | 21 | // it("should observe multiple properties", () => { 22 | // let dummy; 23 | // const counter = reactive({ num1: 0, num2: 0 }); 24 | // effect(() => (dummy = counter.num1 + counter.num1 + counter.num2)); 25 | 26 | // expect(dummy).toBe(0); 27 | // counter.num1 = counter.num2 = 7; 28 | // expect(dummy).toBe(21); 29 | // }); 30 | // it("should handle multiple effects", () => { 31 | // let dummy1, dummy2; 32 | // const counter = reactive({ num: 0 }); 33 | // effect(() => (dummy1 = counter.num)); 34 | // effect(() => (dummy2 = counter.num)); 35 | 36 | // expect(dummy1).toBe(0); 37 | // expect(dummy2).toBe(0); 38 | // counter.num++; 39 | // expect(dummy1).toBe(1); 40 | // expect(dummy2).toBe(1); 41 | // }); 42 | 43 | it("should observe nested properties", () => { 44 | let dummy; 45 | const counter = reactive({ nested: { num: 0 } }); 46 | effect(() => (dummy = counter.nested.num)); 47 | 48 | expect(dummy).toBe(0); 49 | counter.nested.num = 8; 50 | expect(dummy).toBe(8); 51 | }); 52 | 53 | it("should observe function call chains", () => { 54 | let dummy; 55 | const counter = reactive({ num: 0 }); 56 | effect(() => (dummy = getNum())); 57 | 58 | function getNum() { 59 | return counter.num; 60 | } 61 | 62 | expect(dummy).toBe(0); 63 | counter.num = 2; 64 | expect(dummy).toBe(2); 65 | }); 66 | it("scheduler", () => { 67 | let dummy; 68 | let run: any; 69 | const scheduler = jest.fn(() => { 70 | run = runner; 71 | }); 72 | const obj = reactive({ foo: 1 }); 73 | const runner = effect( 74 | () => { 75 | dummy = obj.foo; 76 | }, 77 | { scheduler } 78 | ); 79 | expect(scheduler).not.toHaveBeenCalled(); 80 | expect(dummy).toBe(1); 81 | // should be called on first trigger 82 | obj.foo++; 83 | expect(scheduler).toHaveBeenCalledTimes(1); 84 | // // should not run yet 85 | expect(dummy).toBe(1); 86 | // // manually run 87 | run(); 88 | // // should have run 89 | expect(dummy).toBe(2); 90 | }); 91 | 92 | it("stop", () => { 93 | let dummy; 94 | const obj = reactive({ prop: 1 }); 95 | const runner = effect(() => { 96 | dummy = obj.prop; 97 | }); 98 | obj.prop = 2; 99 | expect(dummy).toBe(2); 100 | stop(runner); 101 | // obj.prop = 3 102 | obj.prop++; 103 | expect(dummy).toBe(2); 104 | 105 | // stopped effect should still be manually callable 106 | runner(); 107 | expect(dummy).toBe(3); 108 | }); 109 | 110 | it("events: onStop", () => { 111 | const onStop = jest.fn(); 112 | const runner = effect(() => {}, { 113 | onStop, 114 | }); 115 | 116 | stop(runner); 117 | expect(onStop).toHaveBeenCalled(); 118 | }); 119 | }); 120 | -------------------------------------------------------------------------------- /packages/reactivity/__tests__/reactive.spec.ts: -------------------------------------------------------------------------------- 1 | import { isReactive, reactive } from '../src/reactive' 2 | 3 | describe('reactive', () => { 4 | it('happy reactive', () => { 5 | const original = { foo: 1 } 6 | const observed = reactive(original) 7 | expect(observed).not.toBe(original) 8 | expect(observed.foo).toBe(1) 9 | }) 10 | }) 11 | // describe("reactive", () => { 12 | // test("Object", () => { 13 | // const original = { foo: 1 }; 14 | // const observed = reactive(original); 15 | // expect(observed).not.toBe(original); 16 | // expect(isReactive(observed)).toBe(true); 17 | // expect(isReactive(original)).toBe(false); 18 | // // get 19 | // expect(observed.foo).toBe(1); 20 | // // // has 21 | // expect("foo" in observed).toBe(true); 22 | // // // ownKeys 23 | // expect(Object.keys(observed)).toEqual(["foo"]); 24 | // }); 25 | 26 | test('nested reactives', () => { 27 | const original = { 28 | nested: { 29 | foo: 1, 30 | }, 31 | array: [{ bar: 2 }], 32 | } 33 | const observed = reactive(original) 34 | expect(isReactive(observed.nested)).toBe(true) 35 | expect(isReactive(observed.array)).toBe(true) 36 | expect(isReactive(observed.array[0])).toBe(true) 37 | }) 38 | 39 | // test("toRaw", () => { 40 | // const original = { foo: 1 }; 41 | // const observed = reactive(original); 42 | // expect(toRaw(observed)).toBe(original); 43 | // expect(toRaw(original)).toBe(original); 44 | // }); 45 | // }); 46 | -------------------------------------------------------------------------------- /packages/reactivity/__tests__/readonly.spec.ts: -------------------------------------------------------------------------------- 1 | import { isProxy, isReactive, isReadonly, readonly } from "../src/reactive"; 2 | 3 | describe("readonly", () => { 4 | it("should make nested values readonly", () => { 5 | const original = { foo: 1, bar: { baz: 2 } }; 6 | const wrapped = readonly(original); 7 | expect(wrapped).not.toBe(original); 8 | expect(isProxy(wrapped)).toBe(true); 9 | expect(isReactive(wrapped)).toBe(false); 10 | expect(isReadonly(wrapped)).toBe(true); 11 | expect(isReactive(original)).toBe(false); 12 | expect(isReadonly(original)).toBe(false); 13 | expect(isReactive(wrapped.bar)).toBe(false); 14 | expect(isReadonly(wrapped.bar)).toBe(true); 15 | expect(isReactive(original.bar)).toBe(false); 16 | expect(isReadonly(original.bar)).toBe(false); 17 | // get 18 | expect(wrapped.foo).toBe(1); 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /packages/reactivity/__tests__/ref.spec.ts: -------------------------------------------------------------------------------- 1 | import { effect } from "../src/effect"; 2 | import { reactive } from "../src/reactive"; 3 | import { isRef, ref, unRef,proxyRefs } from "../src/ref"; 4 | describe("ref", () => { 5 | it("should be reactive", () => { 6 | const a = ref(1); 7 | let dummy; 8 | let calls = 0; 9 | effect(() => { 10 | calls++; 11 | dummy = a.value; 12 | }); 13 | expect(calls).toBe(1); 14 | expect(dummy).toBe(1); 15 | a.value = 2; 16 | expect(calls).toBe(2); 17 | expect(dummy).toBe(2); 18 | // same value should not trigger 19 | a.value = 2; 20 | expect(calls).toBe(2); 21 | expect(dummy).toBe(2); 22 | }); 23 | 24 | it("should make nested properties reactive", () => { 25 | const a = ref({ 26 | count: 1, 27 | }); 28 | let dummy; 29 | effect(() => { 30 | dummy = a.value.count; 31 | }); 32 | expect(dummy).toBe(1); 33 | a.value.count = 2; 34 | expect(dummy).toBe(2); 35 | }); 36 | 37 | it("proxyRefs", () => { 38 | const user = { 39 | age: ref(10), 40 | name: "xiaohong", 41 | }; 42 | const proxyUser = proxyRefs(user); 43 | expect(user.age.value).toBe(10); 44 | expect(proxyUser.age).toBe(10); 45 | expect(proxyUser.name).toBe("xiaohong"); 46 | 47 | (proxyUser as any).age = 20; 48 | expect(proxyUser.age).toBe(20); 49 | expect(user.age.value).toBe(20); 50 | 51 | proxyUser.age = ref(10); 52 | expect(proxyUser.age).toBe(10); 53 | expect(user.age.value).toBe(10); 54 | }); 55 | 56 | it("isRef", () => { 57 | const a = ref(1); 58 | const user = reactive({ 59 | age: 1, 60 | }); 61 | expect(isRef(a)).toBe(true); 62 | expect(isRef(1)).toBe(false); 63 | expect(isRef(user)).toBe(false); 64 | }); 65 | 66 | it("unRef", () => { 67 | const a = ref(1); 68 | expect(unRef(a)).toBe(1); 69 | expect(unRef(1)).toBe(1); 70 | }); 71 | }); 72 | -------------------------------------------------------------------------------- /packages/reactivity/__tests__/shallowReadonly.spec.ts: -------------------------------------------------------------------------------- 1 | import { isReactive, isReadonly, readonly, shallowReadonly } from "../src/reactive"; 2 | 3 | describe("shallowReadonly", () => { 4 | test("should not make non-reactive properties reactive", () => { 5 | const props = shallowReadonly({ n: { foo: 1 } }); 6 | expect(isReactive(props.n)).toBe(false); 7 | }); 8 | test("should differentiate from normal readonly calls", async () => { 9 | const original = { foo: {} }; 10 | const shallowProxy = shallowReadonly(original); 11 | const reactiveProxy = readonly(original); 12 | expect(shallowProxy).not.toBe(reactiveProxy); 13 | expect(isReadonly(shallowProxy.foo)).toBe(false); 14 | expect(isReadonly(reactiveProxy.foo)).toBe(true); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /packages/reactivity/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@relaxed/reactivity", 3 | "version": "1.0.0", 4 | "description": "@relaxed/reactivity", 5 | "main": "dist/index.mjs", 6 | "scripts": { 7 | "test": "jest", 8 | "dev": "tsup --watch" 9 | }, 10 | "keywords": [], 11 | "author": "", 12 | "license": "ISC", 13 | "dependencies": { 14 | "@relaxed/shared": "workspace:^1.0.0" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /packages/reactivity/src/baseHandlers.ts: -------------------------------------------------------------------------------- 1 | import { extend, isObject } from '../../shared/src/index' 2 | import { track, trigger } from './effect' 3 | import { reactive, ReactiveFlags, readonly } from './reactive' 4 | 5 | // 缓存一个getter & setter 6 | const get = createGetter() 7 | const set = createSetter() 8 | const readonlyGet = createGetter(true) 9 | const shallowReadonlyGet = createGetter(true, true) 10 | // 高阶函数判断逻辑 11 | export function createGetter(isReadonly = false, shallow = false) { 12 | return function get(target, key) { 13 | const res = Reflect.get(target, key) 14 | // track 15 | if (key === ReactiveFlags.IS_REACTIVE) { 16 | return !isReadonly 17 | } else if (key === ReactiveFlags.IS_READONLY) { 18 | return isReadonly 19 | } 20 | if (shallow) { 21 | return res 22 | } 23 | // 嵌套reactive 如果 res 返回的是一个 object 那么我们就再次调用 24 | // 转换为reactive 25 | if (isObject(res)) { 26 | return isReadonly ? readonly(res) : reactive(res) 27 | } 28 | if (!isReadonly) { 29 | track(target, key) 30 | } 31 | // console.log("触发get"); 32 | return res 33 | } 34 | } 35 | 36 | export function createSetter() { 37 | return function set(target, key, value) { 38 | const res = Reflect.set(target, key, value) 39 | // trigger 40 | trigger(target, key) 41 | // console.log("触发set"); 42 | return res 43 | } 44 | } 45 | 46 | export const mutableHandlers = { 47 | get, 48 | set, 49 | } 50 | export const readonlyHandlers = { 51 | get: readonlyGet, 52 | set(target, key, value) { 53 | console.warn(`${key} is not change ${target} is read-only`) 54 | return true 55 | }, 56 | } 57 | 58 | export const shadowReadonlyHandlers = extend({}, readonlyHandlers, { 59 | get: shallowReadonlyGet, 60 | }) 61 | 62 | // import { ReactiveEffect, track, trigger } from "./effect"; 63 | // import { 64 | // reactive, 65 | // ReactiveFlags, 66 | // reactiveMap, 67 | // readonly, 68 | // readonlyMap, 69 | // shallowReadonlyMap, 70 | // } from "./reactive"; 71 | // import { isObject } from "@relaxed/shared"; 72 | 73 | // const get = createGetter(); 74 | // const set = createSetter(); 75 | // const readonlyGet = createGetter(true); 76 | // const shallowReadonlyGet = createGetter(true, true); 77 | 78 | // function createGetter(isReadonly = false, shallow = false) { 79 | // return function get(target, key, receiver) { 80 | // const isExistInReactiveMap = () => 81 | // key === ReactiveFlags.RAW && receiver === reactiveMap.get(target); 82 | 83 | // const isExistInReadonlyMap = () => 84 | // key === ReactiveFlags.RAW && receiver === readonlyMap.get(target); 85 | 86 | // const isExistInShallowReadonlyMap = () => 87 | // key === ReactiveFlags.RAW && receiver === shallowReadonlyMap.get(target); 88 | 89 | // if (key === ReactiveFlags.IS_REACTIVE) { 90 | // return !isReadonly; 91 | // } else if (key === ReactiveFlags.IS_READONLY) { 92 | // return isReadonly; 93 | // } else if ( 94 | // isExistInReactiveMap() || 95 | // isExistInReadonlyMap() || 96 | // isExistInShallowReadonlyMap() 97 | // ) { 98 | // return target; 99 | // } 100 | 101 | // const res = Reflect.get(target, key, receiver); 102 | 103 | // // 问题:为什么是 readonly 的时候不做依赖收集呢 104 | // // readonly 的话,是不可以被 set 的, 那不可以被 set 就意味着不会触发 trigger 105 | // // 所有就没有收集依赖的必要了 106 | 107 | // if (!isReadonly) { 108 | // // 在触发 get 的时候进行依赖收集 109 | // track(target, "get", key); 110 | // } 111 | 112 | // if (shallow) { 113 | // return res; 114 | // } 115 | 116 | // if (isObject(res)) { 117 | // // 把内部所有的是 object 的值都用 reactive 包裹,变成响应式对象 118 | // // 如果说这个 res 值是一个对象的话,那么我们需要把获取到的 res 也转换成 reactive 119 | // // res 等于 target[key] 120 | // return isReadonly ? readonly(res) : reactive(res); 121 | // } 122 | 123 | // return res; 124 | // }; 125 | // } 126 | 127 | // function createSetter() { 128 | // return function set(target, key, value, receiver) { 129 | // const result = Reflect.set(target, key, value, receiver); 130 | 131 | // // 在触发 set 的时候进行触发依赖 132 | // trigger(target, "set", key); 133 | 134 | // return result; 135 | // }; 136 | // } 137 | 138 | // export const readonlyHandlers = { 139 | // get: readonlyGet, 140 | // set(target, key) { 141 | // // readonly 的响应式对象不可以修改值 142 | // console.warn( 143 | // `Set operation on key "${String(key)}" failed: target is readonly.`, 144 | // target 145 | // ); 146 | // return true; 147 | // }, 148 | // }; 149 | 150 | // export const mutableHandlers = { 151 | // get, 152 | // set, 153 | // }; 154 | 155 | // export const shallowReadonlyHandlers = { 156 | // get: shallowReadonlyGet, 157 | // set(target, key) { 158 | // // readonly 的响应式对象不可以修改值 159 | // console.warn( 160 | // `Set operation on key "${String(key)}" failed: target is readonly.`, 161 | // target 162 | // ); 163 | // return true; 164 | // }, 165 | // }; 166 | -------------------------------------------------------------------------------- /packages/reactivity/src/computed.ts: -------------------------------------------------------------------------------- 1 | // import { createDep } from "./dep"; 2 | import { ReactiveEffect } from './effect' 3 | // import { trackRefValue, triggerRefValue } from "./ref"; 4 | 5 | class ComputedRefImpl { 6 | private _getter 7 | private _dirty: boolean = true 8 | private _value: any 9 | private _effect: ReactiveEffect 10 | constructor(getter) { 11 | this._getter = getter 12 | 13 | this._effect = new ReactiveEffect(getter, () => { 14 | if (!this._dirty) { 15 | this._dirty = true 16 | } 17 | }) 18 | } 19 | get value() { 20 | // 调用一次之后 做一个缓存机制 21 | // 等依赖的数据发生响应式之后 我们再次执行 getter 把dirty变成true 22 | if (this._dirty) { 23 | this._dirty = false 24 | this._value = this._effect.run() 25 | // this._value = this._getter() 26 | // return this._getter() 27 | } 28 | return this._value 29 | } 30 | } 31 | 32 | export function computed(getter) { 33 | return new ComputedRefImpl(getter) 34 | } 35 | 36 | // import { createDep } from "./dep"; 37 | // import { ReactiveEffect } from "./effect"; 38 | // import { trackRefValue, triggerRefValue } from "./ref"; 39 | 40 | // export class ComputedRefImpl { 41 | // public dep: any; 42 | // public effect: ReactiveEffect; 43 | 44 | // private _dirty: boolean; 45 | // private _value 46 | 47 | // constructor(getter) { 48 | // this._dirty = true; 49 | // this.dep = createDep(); 50 | // this.effect = new ReactiveEffect(getter, () => { 51 | // // scheduler 52 | // // 只要触发了这个函数说明响应式对象的值发生改变了 53 | // // 那么就解锁,后续在调用 get 的时候就会重新执行,所以会得到最新的值 54 | // if (this._dirty) return; 55 | 56 | // this._dirty = true; 57 | // triggerRefValue(this); 58 | // }); 59 | // } 60 | 61 | // get value() { 62 | // // 收集依赖 63 | // trackRefValue(this); 64 | // // 锁上,只可以调用一次 65 | // // 当数据改变的时候才会解锁 66 | // // 这里就是缓存实现的核心 67 | // // 解锁是在 scheduler 里面做的 68 | // if (this._dirty) { 69 | // this._dirty = false; 70 | // // 这里执行 run 的话,就是执行用户传入的 fn 71 | // this._value = this.effect.run(); 72 | // } 73 | 74 | // return this._value; 75 | // } 76 | // } 77 | 78 | // export function computed(getter) { 79 | // return new ComputedRefImpl(getter); 80 | // } 81 | -------------------------------------------------------------------------------- /packages/reactivity/src/dep.ts: -------------------------------------------------------------------------------- 1 | // 用于存储所有的 effect 对象 2 | export function createDep(effects?) { 3 | const dep = new Set(effects); 4 | return dep; 5 | } 6 | -------------------------------------------------------------------------------- /packages/reactivity/src/index.ts: -------------------------------------------------------------------------------- 1 | export { 2 | reactive, 3 | readonly, 4 | shallowReadonly, 5 | isReadonly, 6 | isReactive, 7 | isProxy, 8 | } from './reactive' 9 | 10 | export { ref, proxyRefs, unRef, isRef } from "./ref"; 11 | // export { ref } from './ref' 12 | 13 | export { effect } from './effect' 14 | 15 | export { computed } from "./computed"; 16 | -------------------------------------------------------------------------------- /packages/reactivity/src/reactive.ts: -------------------------------------------------------------------------------- 1 | import { mutableHandlers, readonlyHandlers, shadowReadonlyHandlers } from './baseHandlers' 2 | 3 | // 是一个reactive 4 | export const enum ReactiveFlags { 5 | IS_REACTIVE = '_v_isReactive', 6 | IS_READONLY = '_v_isReadonly', 7 | } 8 | // true false 判断是否是 readonly 9 | export function reactive(raw) { 10 | return createReactiveObject(raw, mutableHandlers) 11 | } 12 | 13 | export function readonly(raw) { 14 | return createReactiveObject(raw, readonlyHandlers) 15 | } 16 | 17 | export function shallowReadonly(raw) { 18 | return createReactiveObject(raw, shadowReadonlyHandlers) 19 | } 20 | 21 | export function isProxy(raw) { 22 | return isReactive(raw) || isReadonly(raw) 23 | } 24 | 25 | export function isReactive(raw) { 26 | // 如果是原始对象没有 proxy 代理 是undefined 转成 boolean 27 | return !!raw[ReactiveFlags.IS_REACTIVE] 28 | } 29 | export function isReadonly(raw) { 30 | return !!raw[ReactiveFlags.IS_READONLY] 31 | } 32 | 33 | function createReactiveObject(raw, Handlers) { 34 | return new Proxy(raw, Handlers) 35 | } 36 | 37 | // import { 38 | // mutableHandlers, 39 | // readonlyHandlers, 40 | // shallowReadonlyHandlers, 41 | // } from "./baseHandlers"; 42 | 43 | // export const reactiveMap = new WeakMap(); 44 | // export const readonlyMap = new WeakMap(); 45 | // export const shallowReadonlyMap = new WeakMap(); 46 | 47 | // export const enum ReactiveFlags { 48 | // IS_REACTIVE = "__v_isReactive", 49 | // IS_READONLY = "__v_isReadonly", 50 | // RAW = "__v_raw", 51 | // } 52 | 53 | // export function reactive(target) { 54 | // return createReactiveObject(target, reactiveMap, mutableHandlers); 55 | // } 56 | 57 | // export function readonly(target) { 58 | // return createReactiveObject(target, readonlyMap, readonlyHandlers); 59 | // } 60 | 61 | // export function shallowReadonly(target) { 62 | // return createReactiveObject( 63 | // target, 64 | // shallowReadonlyMap, 65 | // shallowReadonlyHandlers 66 | // ); 67 | // } 68 | 69 | // export function isProxy(value) { 70 | // return isReactive(value) || isReadonly(value); 71 | // } 72 | 73 | // export function isReadonly(value) { 74 | // return !!value[ReactiveFlags.IS_READONLY]; 75 | // } 76 | 77 | // export function isReactive(value) { 78 | // // 如果 value 是 proxy 的话 79 | // // 会触发 get 操作,而在 createGetter 里面会判断 80 | // // 如果 value 是普通对象的话 81 | // // 那么会返回 undefined ,那么就需要转换成布尔值 82 | // return !!value[ReactiveFlags.IS_REACTIVE]; 83 | // } 84 | 85 | // export function toRaw(value) { 86 | // // 如果 value 是 proxy 的话 ,那么直接返回就可以了 87 | // // 因为会触发 createGetter 内的逻辑 88 | // // 如果 value 是普通对象的话, 89 | // // 我们就应该返回普通对象 90 | // // 只要不是 proxy ,只要是得到了 undefined 的话,那么就一定是普通对象 91 | // // TODO 这里和源码里面实现的不一样,不确定后面会不会有问题 92 | // if (!value[ReactiveFlags.RAW]) { 93 | // return value; 94 | // } 95 | 96 | // return value[ReactiveFlags.RAW]; 97 | // } 98 | 99 | // function createReactiveObject(target, proxyMap, baseHandlers) { 100 | // // 核心就是 proxy 101 | // // 目的是可以侦听到用户 get 或者 set 的动作 102 | 103 | // // 如果命中的话就直接返回就好了 104 | // // 使用缓存做的优化点 105 | // const existingProxy = proxyMap.get(target); 106 | // if (existingProxy) { 107 | // return existingProxy; 108 | // } 109 | 110 | // const proxy = new Proxy(target, baseHandlers); 111 | 112 | // // 把创建好的 proxy 给存起来, 113 | // proxyMap.set(target, proxy); 114 | // return proxy; 115 | // } 116 | -------------------------------------------------------------------------------- /packages/reactivity/src/ref.ts: -------------------------------------------------------------------------------- 1 | // import { trackEffects, triggerEffects, isTracking } from "./effect"; 2 | // import { createDep } from "./dep"; 3 | import { isObject, hasChanged } from "../../shared/src/index"; 4 | import { reactive } from "./reactive"; 5 | import { trackEffect, triggerEffect, isTracking } from "./effect"; 6 | 7 | class RefImpl { 8 | private _value: any; 9 | private _rawValue: any; 10 | // 判断是否是一个ref 11 | public __v_isRef = true; 12 | // 创建一个dep 收集依赖 13 | public dep; 14 | constructor(value) { 15 | // 存储 原始普通对象的value值 用于对比 16 | this._rawValue = value; 17 | this._value = convert(value); 18 | // 判断value 是不是对象 是对象就装换成reactive 19 | 20 | this.dep = new Set(); 21 | } 22 | get value() { 23 | trackRefValue(this); 24 | // ref 只监听一个 value 的变化 value的依赖收集 25 | return this._value; 26 | } 27 | set value(newValue) { 28 | // 如果value 是一个 对象 就会被转换成reactive 新的值 是 proxy 对比 要转换成普通对象 29 | // if (hasChanged(newValue, this._value)) { 30 | if (hasChanged(newValue, this._rawValue)) { 31 | // if (Object.is(newValue, this._value)) return 32 | // 必须要先修改value的值 再去通知 33 | this._rawValue = newValue; 34 | this._value = convert(newValue); 35 | triggerEffect(this.dep); 36 | } 37 | } 38 | } 39 | 40 | export function ref(value) { 41 | return new RefImpl(value); 42 | } 43 | 44 | export function isRef(ref) { 45 | return !!ref.__v_isRef; 46 | } 47 | 48 | export function unRef(ref) { 49 | return isRef(ref) ? ref.value : ref; 50 | } 51 | 52 | export function proxyRefs(ref) { 53 | // 判断调用了 get 或者 set/ 54 | return new Proxy(ref, { 55 | get(target, key) { 56 | return unRef(Reflect.get(target, key)); 57 | }, 58 | // 判断set的时候 是不是一个 ref 是 ref 就调用 .value 59 | set(target, key, value) { 60 | if (isRef(target[key]) && !isRef(value)) { 61 | return (target[key].value = value); 62 | } else { 63 | return Reflect.set(target, key, value); 64 | } 65 | }, 66 | }); 67 | } 68 | 69 | function trackRefValue(ref) { 70 | if (isTracking()) { 71 | trackEffect(ref.dep); 72 | } 73 | } 74 | 75 | // 判断 返回 reactive 对象 还是普通对象 76 | function convert(value) { 77 | return isObject(value) ? reactive(value) : value; 78 | } 79 | 80 | // export class RefImpl { 81 | // private _rawValue: any; 82 | // private _value: any; 83 | // public dep; 84 | // public __v_isRef = true; 85 | 86 | // constructor(value) { 87 | // this._rawValue = value; 88 | // // 看看value 是不是一个对象,如果是一个对象的话 89 | // // 那么需要用 reactive 包裹一下 90 | // this._value = convert(value); 91 | // this.dep = createDep(); 92 | // } 93 | 94 | // get value() { 95 | // // 收集依赖 96 | // trackRefValue(this); 97 | // return this._value; 98 | // } 99 | 100 | // set value(newValue) { 101 | // // 当新的值不等于老的值的话, 102 | // // 那么才需要触发依赖 103 | // if (hasChanged(newValue, this._rawValue)) { 104 | // // 更新值 105 | // this._value = convert(newValue); 106 | // this._rawValue = newValue; 107 | // // 触发依赖 108 | // triggerRefValue(this); 109 | // } 110 | // } 111 | // } 112 | 113 | // export function ref(value) { 114 | // return createRef(value); 115 | // } 116 | 117 | // function convert(value) { 118 | // return isObject(value) ? reactive(value) : value; 119 | // } 120 | 121 | // function createRef(value) { 122 | // const refImpl = new RefImpl(value); 123 | 124 | // return refImpl; 125 | // } 126 | 127 | // export function triggerRefValue(ref) { 128 | // triggerEffects(ref.dep); 129 | // } 130 | 131 | // export function trackRefValue(ref) { 132 | // if (isTracking()) { 133 | // trackEffects(ref.dep); 134 | // } 135 | // } 136 | 137 | // // 这个函数的目的是 138 | // // 帮助解构 ref 139 | // // 比如在 template 中使用 ref 的时候,直接使用就可以了 140 | // // 例如: const count = ref(0) -> 在 template 中使用的话 可以直接 count 141 | // // 解决方案就是通过 proxy 来对 ref 做处理 142 | 143 | // const shallowUnwrapHandlers = { 144 | // get(target, key, receiver) { 145 | // // 如果里面是一个 ref 类型的话,那么就返回 .value 146 | // // 如果不是的话,那么直接返回value 就可以了 147 | // return unRef(Reflect.get(target, key, receiver)); 148 | // }, 149 | // set(target, key, value, receiver) { 150 | // const oldValue = target[key]; 151 | // if (isRef(oldValue) && !isRef(value)) { 152 | // return (target[key].value = value); 153 | // } else { 154 | // return Reflect.set(target, key, value, receiver); 155 | // } 156 | // }, 157 | // }; 158 | 159 | // // 这里没有处理 objectWithRefs 是 reactive 类型的时候 160 | // // TODO reactive 里面如果有 ref 类型的 key 的话, 那么也是不需要调用 ref.value 的 161 | // // (but 这个逻辑在 reactive 里面没有实现) 162 | // export function proxyRefs(objectWithRefs) { 163 | // return new Proxy(objectWithRefs, shallowUnwrapHandlers); 164 | // } 165 | 166 | // // 把 ref 里面的值拿到 167 | // export function unRef(ref) { 168 | // return isRef(ref) ? ref.value : ref; 169 | // } 170 | 171 | // export function isRef(value) { 172 | // return !!value.__v_isRef; 173 | // } 174 | -------------------------------------------------------------------------------- /packages/reactivity/tsup.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'tsup' 2 | 3 | export default defineConfig({ 4 | entry: ['./src/index.ts'], 5 | target: 'esnext', 6 | format: ['esm', 'cjs', 'iife'], 7 | splitting: false, 8 | sourcemap: true, 9 | clean: true, 10 | watch: true, 11 | external: ['vue'] 12 | }) 13 | -------------------------------------------------------------------------------- /packages/runtime-core/__tests__/apiWatch.spec.ts: -------------------------------------------------------------------------------- 1 | import { reactive } from "@relaxed/reactivity"; 2 | import { watchEffect } from "../src/apiWatch"; 3 | import { nextTick } from "../src/scheduler"; 4 | 5 | describe("api: watch", () => { 6 | it("effect", async () => { 7 | const state = reactive({ count: 0 }); 8 | let dummy; 9 | watchEffect(() => { 10 | dummy = state.count; 11 | }); 12 | expect(dummy).toBe(0); 13 | 14 | state.count++; 15 | await nextTick(); 16 | expect(dummy).toBe(1); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /packages/runtime-core/__tests__/componentEmits.spec.ts: -------------------------------------------------------------------------------- 1 | import { nodeOps, render, h } from "@relaxed/runtime-test"; 2 | 3 | describe("component: emits", () => { 4 | test("trigger handlers", () => { 5 | const Foo = { 6 | render() { 7 | return h("foo"); 8 | }, 9 | setup(props, { emit }) { 10 | // the `emit` function is bound on component instances 11 | emit("foo"); 12 | emit("bar"); 13 | }, 14 | }; 15 | 16 | const onfoo = jest.fn(); 17 | const onBar = jest.fn(); 18 | const Comp = { 19 | render() { 20 | return h(Foo, { onfoo, onBar }); 21 | }, 22 | }; 23 | render(h(Comp), nodeOps.createElement("div")); 24 | 25 | expect(onfoo).not.toHaveBeenCalled(); 26 | // only capitalized or special chars are considered event listeners 27 | expect(onBar).toHaveBeenCalled(); 28 | }); 29 | 30 | test("trigger camelCase handler", () => { 31 | const Foo = { 32 | render() { 33 | return h("foo"); 34 | }, 35 | setup(props, { emit }) { 36 | emit("test-event"); 37 | }, 38 | }; 39 | 40 | const fooSpy = jest.fn(); 41 | const Comp = { 42 | render() { 43 | return h(Foo, { onTestEvent: fooSpy }); 44 | }, 45 | }; 46 | render(h(Comp), nodeOps.createElement("div")); 47 | 48 | expect(fooSpy).toHaveBeenCalledTimes(1); 49 | }); 50 | 51 | test("trigger kebab-case handler", () => { 52 | const Foo = { 53 | render() { 54 | return h("foo"); 55 | }, 56 | setup(props, { emit }) { 57 | emit("test-event"); 58 | }, 59 | }; 60 | 61 | const fooSpy = jest.fn(); 62 | 63 | const Comp = { 64 | render() { 65 | return h(Foo, { "onTest-event": fooSpy }); 66 | }, 67 | }; 68 | render(h(Comp), nodeOps.createElement("div")); 69 | 70 | expect(fooSpy).toHaveBeenCalledTimes(1); 71 | }); 72 | }); 73 | -------------------------------------------------------------------------------- /packages/runtime-core/__tests__/rendererComponent.spec.ts: -------------------------------------------------------------------------------- 1 | import { h } from "@relaxed/runtime-dom"; 2 | import { nodeOps, render, serializeInner } from "@relaxed/runtime-test"; 3 | 4 | describe("renderer: component", () => { 5 | it("should create an Component ", () => { 6 | const Comp = { 7 | render: () => { 8 | return h("div"); 9 | }, 10 | }; 11 | const root = nodeOps.createElement("div"); 12 | render(h(Comp), root); 13 | expect(serializeInner(root)).toBe(`
`); 14 | }); 15 | 16 | it("should create an Component with direct text children", () => { 17 | const Comp = { 18 | render: () => { 19 | return h("div", null, "test"); 20 | }, 21 | }; 22 | const root = nodeOps.createElement("div"); 23 | render(h(Comp), root); 24 | expect(serializeInner(root)).toBe(`
test
`); 25 | }); 26 | }); 27 | -------------------------------------------------------------------------------- /packages/runtime-core/__tests__/rendererElement.spec.ts: -------------------------------------------------------------------------------- 1 | import { h } from "@relaxed/runtime-core"; 2 | import { nodeOps, render, serializeInner as inner } from "@relaxed/runtime-test"; 3 | 4 | describe("renderer: element", () => { 5 | let root; 6 | 7 | beforeEach(() => { 8 | root = nodeOps.createElement("div"); 9 | }); 10 | 11 | it("should create an element", () => { 12 | render(h("div"), root); 13 | expect(inner(root)).toBe("
"); 14 | }); 15 | 16 | it('should create an element with props', () => { 17 | render(h('div', { id: 'foo', class: 'bar' },[]), root) 18 | expect(inner(root)).toBe('
') 19 | }) 20 | it('should create an element with direct text children and props', () => { 21 | render(h('div', { id: 'foo' }, "bar"), root) 22 | expect(inner(root)).toBe('
bar
') 23 | }) 24 | }); 25 | 26 | 27 | -------------------------------------------------------------------------------- /packages/runtime-core/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@relaxed/runtime-core", 3 | "version": "1.0.0", 4 | "description": "@relaxed/runtime-core", 5 | "scripts": { 6 | "test": "jest", 7 | "build": "tsup --watch" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC", 12 | "dependencies": { 13 | "@relaxed/reactivity": "workspace:^1.0.0", 14 | "@relaxed/shared": "workspace:^1.0.0" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /packages/runtime-core/src/apiInject.ts: -------------------------------------------------------------------------------- 1 | import { getCurrentInstance } from "./component"; 2 | 3 | export function provide(key, value) { 4 | const currentInstance = getCurrentInstance(); 5 | 6 | if (currentInstance) { 7 | let { provides } = currentInstance; 8 | 9 | const parentProvides = currentInstance.parent?.provides; 10 | 11 | // 这里要解决一个问题 12 | // 当父级 key 和 爷爷级别的 key 重复的时候,对于子组件来讲,需要取最近的父级别组件的值 13 | // 那这里的解决方案就是利用原型链来解决 14 | // provides 初始化的时候是在 createComponent 时处理的,当时是直接把 parent.provides 赋值给组件的 provides 的 15 | // 所以,如果说这里发现 provides 和 parentProvides 相等的话,那么就说明是第一次做 provide(对于当前组件来讲) 16 | // 我们就可以把 parent.provides 作为 currentInstance.provides 的原型重新赋值 17 | // 至于为什么不在 createComponent 的时候做这个处理,可能的好处是在这里初始化的话,是有个懒执行的效果(优化点,只有需要的时候在初始化) 18 | if (parentProvides === provides) { 19 | provides = currentInstance.provides = Object.create(parentProvides); 20 | } 21 | 22 | provides[key] = value; 23 | } 24 | } 25 | 26 | export function inject(key, defaultValue) { 27 | const currentInstance = getCurrentInstance(); 28 | 29 | if (currentInstance) { 30 | const provides = currentInstance.parent?.provides; 31 | 32 | if (key in provides) { 33 | return provides[key]; 34 | } else if (defaultValue) { 35 | if (typeof defaultValue === "function") { 36 | return defaultValue(); 37 | } 38 | return defaultValue; 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /packages/runtime-core/src/apiWatch.ts: -------------------------------------------------------------------------------- 1 | import { ReactiveEffect } from "@relaxed/reactivity"; 2 | import { queuePreFlushCb } from "./scheduler"; 3 | 4 | // Simple effect. 5 | export function watchEffect(effect) { 6 | doWatch(effect); 7 | } 8 | 9 | function doWatch(source) { 10 | // 把 job 添加到 pre flush 里面 11 | // 也就是在视图更新完成之前进行渲染(待确认?) 12 | // 当逻辑执行到这里的时候 就已经触发了 watchEffect 13 | const job = () => { 14 | effect.run(); 15 | }; 16 | 17 | // 当触发 trigger 的时候会调用 scheduler 18 | // 这里用 scheduler 的目的就是在更新的时候 19 | // 让回调可以在 render 前执行 变成一个异步的行为(这里也可以通过 flush 来改变) 20 | const scheduler = () => queuePreFlushCb(job); 21 | 22 | // 这里是在执行 effect.run 的时候就会调用的 23 | const getter = () => { 24 | source(); 25 | }; 26 | 27 | const effect = new ReactiveEffect(getter, scheduler); 28 | 29 | // 这里执行的就是 getter 30 | effect.run(); 31 | } 32 | -------------------------------------------------------------------------------- /packages/runtime-core/src/componentEmits.ts: -------------------------------------------------------------------------------- 1 | import { camelize, hyphenate, toHandlerKey } from "../../shared/src/index"; 2 | export function emit(instance, event: string, ...rawArgs) { 3 | console.log(instance); 4 | console.log(event); 5 | 6 | // 1. emit 是基于 props 里面的 onXXX 的函数来进行匹配的 7 | // 所以我们先从 props 中看看是否有对应的 event handler 8 | const props = instance.props; 9 | console.log(props); 10 | 11 | // ex: event -> click 那么这里取的就是 onClick 12 | // 让事情变的复杂一点如果是烤肉串命名的话,需要转换成 change-page -> changePage 13 | // 需要得到事件名称 14 | let handler = props[toHandlerKey(camelize(event))]; 15 | 16 | // 如果上面没有匹配的话 那么在检测一下 event 是不是 kebab-case 类型 17 | if (!handler) { 18 | handler = props[toHandlerKey(hyphenate(event))]; 19 | } 20 | 21 | if (handler) { 22 | handler(...rawArgs); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /packages/runtime-core/src/componentProps.ts: -------------------------------------------------------------------------------- 1 | export function initProps(instance, rawProps) { 2 | console.log("initProps"); 3 | 4 | // TODO 5 | // 应该还有 attrs 的概念 6 | // attrs 7 | // 如果组件声明了 props 的话,那么才可以进入 props 属性内 8 | // 不然的话是需要存储在 attrs 内 9 | // 这里暂时直接赋值给 instance.props 即可 10 | instance.props = rawProps || {}; 11 | } 12 | -------------------------------------------------------------------------------- /packages/runtime-core/src/componentPublicInstance.ts: -------------------------------------------------------------------------------- 1 | // import { hasOwn } from "@relaxed/shared"; 2 | // import { hasOwn } from "@relaxed/shared"; 3 | import { hasOwn } from "../../shared"; 4 | const publicPropertiesMap = { 5 | // 当用户调用 instance.proxy.$emit 时就会触发这个函数 6 | // i 就是 instance 的缩写 也就是组件实例对象 7 | $el: (i) => i.vnode.el, 8 | $emit: (i) => i.emit, 9 | $slots: (i) => i.slots, 10 | $props: (i) => i.props, 11 | }; 12 | 13 | // todo 需要让用户可以直接在 render 函数内直接使用 this 来触发 proxy 14 | export const PublicInstanceProxyHandlers = { 15 | get({ _: instance }, key) { 16 | // 用户访问 proxy[key] 17 | // 这里就匹配一下看看是否有对应的 function 18 | // 有的话就直接调用这个 function 19 | const { setupState, props } = instance; 20 | console.log(instance); 21 | 22 | console.log(`触发 proxy hook , key -> : ${key}`); 23 | 24 | if (key[0] !== "$") { 25 | // 说明不是访问 public api 26 | // 先检测访问的 key 是否存在于 setupState 中, 是的话直接返回 27 | if (hasOwn(setupState, key)) { 28 | return setupState[key]; 29 | } else if (hasOwn(props, key)) { 30 | // 看看 key 是不是在 props 中 31 | // 代理是可以访问到 props 中的 key 的 32 | return props[key]; 33 | } 34 | } 35 | 36 | const publicGetter = publicPropertiesMap[key]; 37 | 38 | if (publicGetter) { 39 | return publicGetter(instance); 40 | } 41 | }, 42 | 43 | set({ _: instance }, key, value) { 44 | const { setupState } = instance; 45 | 46 | if (hasOwn(setupState, key)) { 47 | // 有的话 那么就直接赋值 48 | setupState[key] = value; 49 | } 50 | 51 | return true; 52 | }, 53 | }; 54 | -------------------------------------------------------------------------------- /packages/runtime-core/src/componentRenderUtils.ts: -------------------------------------------------------------------------------- 1 | export function shouldUpdateComponent(prevVNode, nextVNode) { 2 | const { props: prevProps } = prevVNode; 3 | const { props: nextProps } = nextVNode; 4 | // const emits = component!.emitsOptions; 5 | 6 | // 这里主要是检测组件的 props 7 | // 核心:只要是 props 发生改变了,那么这个 component 就需要更新 8 | 9 | // 1. props 没有变化,那么不需要更新 10 | if (prevProps === nextProps) { 11 | return false; 12 | } 13 | // 如果之前没有 props,那么就需要看看现在有没有 props 了 14 | // 所以这里基于 nextProps 的值来决定是否更新 15 | if (!prevProps) { 16 | return !!nextProps; 17 | } 18 | // 之前有值,现在没值,那么肯定需要更新 19 | if (!nextProps) { 20 | return true; 21 | } 22 | 23 | // 以上都是比较明显的可以知道 props 是否是变化的 24 | // 在 hasPropsChanged 会做更细致的对比检测 25 | return hasPropsChanged(prevProps, nextProps); 26 | } 27 | 28 | function hasPropsChanged(prevProps, nextProps): boolean { 29 | // 依次对比每一个 props.key 30 | 31 | // 提前对比一下 length ,length 不一致肯定是需要更新的 32 | const nextKeys = Object.keys(nextProps); 33 | if (nextKeys.length !== Object.keys(prevProps).length) { 34 | return true; 35 | } 36 | 37 | // 只要现在的 prop 和之前的 prop 不一样那么就需要更新 38 | for (let i = 0; i < nextKeys.length; i++) { 39 | const key = nextKeys[i]; 40 | if (nextProps[key] !== prevProps[key]) { 41 | return true; 42 | } 43 | } 44 | return false; 45 | } 46 | -------------------------------------------------------------------------------- /packages/runtime-core/src/componentSlots.ts: -------------------------------------------------------------------------------- 1 | import { ShapeFlags } from "../../shared/src/index"; 2 | export function initSlots(instance, children) { 3 | const { vnode } = instance; 4 | 5 | console.log("初始化 slots"); 6 | 7 | if (vnode.shapeFlag & ShapeFlags.SLOTS_CHILDREN) { 8 | normalizeObjectSlots(children, (instance.slots = {})); 9 | } 10 | } 11 | 12 | const normalizeSlotValue = (value) => { 13 | // 把 function 返回的值转换成 array ,这样 slot 就可以支持多个元素了 14 | return Array.isArray(value) ? value : [value]; 15 | }; 16 | 17 | const normalizeObjectSlots = (rawSlots, slots) => { 18 | for (const key in rawSlots) { 19 | const value = rawSlots[key]; 20 | if (typeof value === "function") { 21 | // 把这个函数给到slots 对象上存起来 22 | // 后续在 renderSlots 中调用 23 | // TODO 这里没有对 value 做 normalize, 24 | // 默认 slots 返回的就是一个 vnode 对象 25 | slots[key] = (props) => normalizeSlotValue(value(props)); 26 | } 27 | } 28 | }; 29 | -------------------------------------------------------------------------------- /packages/runtime-core/src/createApp.ts: -------------------------------------------------------------------------------- 1 | // import { createVNode } from "./vnode"; 2 | // import { render } from "./renderer"; 3 | // export function createApp(rootComponent) { 4 | // return { 5 | // mount(rootContainer) { 6 | // // 先转换成vnode 7 | 8 | import { render } from "./renderer"; 9 | import { createVNode } from "./vnode"; 10 | 11 | // // 后续所有操作都会基于vnode 12 | 13 | // const vnode = createVNode(rootComponent); 14 | 15 | // render(vnode, rootContainer); 16 | // }, 17 | // }; 18 | // } 19 | 20 | // // import { createVNode } from "./vnode"; 21 | 22 | // // export function createAppAPI(render) { 23 | // // return function createApp(rootComponent) { 24 | // // const app = { 25 | // // _component: rootComponent, 26 | // // mount(rootContainer) { 27 | // // console.log("基于根组件创建 vnode"); 28 | // // const vnode = createVNode(rootComponent); 29 | // // console.log("调用 render,基于 vnode 进行开箱"); 30 | // // render(vnode, rootContainer); 31 | // // }, 32 | // // }; 33 | // // return app; 34 | // // }; 35 | // // } 36 | 37 | // 创建 createapp 函数 38 | // 带入一个 app 组件 然后返回 mount 函数 挂载 rootContainer dom 节点 39 | export function createApp(rootComponent) { 40 | return { 41 | mount(rootContainer) { 42 | // 所有的逻辑操作都会转换成虚拟节点 43 | // 都是基于 虚拟节点做处理 44 | const vnode = createVNode(rootComponent); 45 | console.log(vnode, rootContainer); 46 | // 然后调用 render 函数 渲染到 rootContainer 上 47 | render(vnode, rootContainer); 48 | }, 49 | }; 50 | } 51 | -------------------------------------------------------------------------------- /packages/runtime-core/src/h.ts: -------------------------------------------------------------------------------- 1 | import { createVNode } from "./vnode"; 2 | export const h = (type: any , props: any = null, children: string | Array = []) => { 3 | return createVNode(type, props, children); 4 | }; 5 | -------------------------------------------------------------------------------- /packages/runtime-core/src/helpers/renderSlot.ts: -------------------------------------------------------------------------------- 1 | import { createVNode, Fragment } from "../vnode"; 2 | 3 | /** 4 | * Compiler runtime helper for rendering `` 5 | * 用来 render slot 的 6 | * 之前是把 slot 的数据都存在 instance.slots 内(可以看 componentSlot.ts), 7 | * 这里就是取数据然后渲染出来的点 8 | * 这个是由 compiler 模块直接渲染出来的 -可以参看这个 demo https://vue-next-template-explorer.netlify.app/#%7B%22src%22%3A%22%3Cdiv%3E%5Cn%20%20%3Cslot%3E%3C%2Fslot%3E%5Cn%3C%2Fdiv%3E%22%2C%22ssr%22%3Afalse%2C%22options%22%3A%7B%22mode%22%3A%22module%22%2C%22prefixIdentifiers%22%3Afalse%2C%22optimizeImports%22%3Afalse%2C%22hoistStatic%22%3Afalse%2C%22cacheHandlers%22%3Afalse%2C%22scopeId%22%3Anull%2C%22inline%22%3Afalse%2C%22ssrCssVars%22%3A%22%7B%20color%20%7D%22%2C%22bindingMetadata%22%3A%7B%22TestComponent%22%3A%22setup-const%22%2C%22setupRef%22%3A%22setup-ref%22%2C%22setupConst%22%3A%22setup-const%22%2C%22setupLet%22%3A%22setup-let%22%2C%22setupMaybeRef%22%3A%22setup-maybe-ref%22%2C%22setupProp%22%3A%22props%22%2C%22vMySetupDir%22%3A%22setup-const%22%7D%2C%22optimizeBindings%22%3Afalse%7D%7D 9 | * 其最终目的就是在 render 函数中调用 renderSlot 取 instance.slots 内的数据 10 | * TODO 这里应该是一个返回一个 block ,但是暂时还没有支持 block ,所以这个暂时只需要返回一个 vnode 即可 11 | * 因为 block 的本质就是返回一个 vnode 12 | * 13 | * @private 14 | */ 15 | export function renderSlot(slots, name: string, props = {}) { 16 | const slot = slots[name]; 17 | console.log(`渲染插槽 slot -> ${name}`); 18 | if (slot) { 19 | // 因为 slot 是一个返回 vnode 的函数,我们只需要把这个结果返回出去即可 20 | // slot 就是一个函数,所以就可以把当前组件的一些数据给传出去,这个就是作用域插槽 21 | // 参数就是 props 22 | const slotContent = slot(props); 23 | return createVNode(Fragment, {}, slotContent); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /packages/runtime-core/src/index.ts: -------------------------------------------------------------------------------- 1 | // export * from "./h"; 2 | // export * from "./createApp"; 3 | // // export { getCurrentInstance, registerRuntimeCompiler } from "./component"; 4 | // export { inject, provide } from "./apiInject"; 5 | // export { renderSlot } from "./helpers/renderSlot"; 6 | // // export { createTextVNode, createElementVNode } from "./vnode"; 7 | // // export { createRenderer } from "./renderer"; 8 | // export { toDisplayString } from "@relaxed/shared"; 9 | // export { watchEffect } from "./apiWatch"; 10 | // export { 11 | // // core 12 | // reactive, 13 | // ref, 14 | // readonly, 15 | // // utilities 16 | // unRef, 17 | // proxyRefs, 18 | // isReadonly, 19 | // isReactive, 20 | // isProxy, 21 | // isRef, 22 | // // advanced 23 | // shallowReadonly, 24 | // // effect 25 | // effect, 26 | // // stop, 27 | // computed, 28 | // } from "@relaxed/reactivity"; 29 | export * from "./h"; 30 | export * from "./createApp"; 31 | -------------------------------------------------------------------------------- /packages/runtime-core/src/scheduler.ts: -------------------------------------------------------------------------------- 1 | const queue: any[] = []; 2 | const activePreFlushCbs: any = []; 3 | 4 | const p = Promise.resolve(); 5 | let isFlushPending = false; 6 | 7 | export function nextTick(fn?) { 8 | return fn ? p.then(fn) : p; 9 | } 10 | 11 | export function queueJob(job) { 12 | if (!queue.includes(job)) { 13 | queue.push(job); 14 | // 执行所有的 job 15 | queueFlush(); 16 | } 17 | } 18 | 19 | function queueFlush() { 20 | // 如果同时触发了两个组件的更新的话 21 | // 这里就会触发两次 then (微任务逻辑) 22 | // 但是着是没有必要的 23 | // 我们只需要触发一次即可处理完所有的 job 调用 24 | // 所以需要判断一下 如果已经触发过 nextTick 了 25 | // 那么后面就不需要再次触发一次 nextTick 逻辑了 26 | if (isFlushPending) return; 27 | isFlushPending = true; 28 | nextTick(flushJobs); 29 | } 30 | 31 | export function queuePreFlushCb(cb) { 32 | queueCb(cb, activePreFlushCbs); 33 | } 34 | 35 | function queueCb(cb, activeQueue) { 36 | // 直接添加到对应的列表内就ok 37 | // todo 这里没有考虑 activeQueue 是否已经存在 cb 的情况 38 | // 然后在执行 flushJobs 的时候就可以调用 activeQueue 了 39 | activeQueue.push(cb); 40 | 41 | // 然后执行队列里面所有的 job 42 | queueFlush() 43 | } 44 | 45 | function flushJobs() { 46 | isFlushPending = false; 47 | 48 | // 先执行 pre 类型的 job 49 | // 所以这里执行的job 是在渲染前的 50 | // 也就意味着执行这里的 job 的时候 页面还没有渲染 51 | flushPreFlushCbs(); 52 | 53 | // 这里是执行 queueJob 的 54 | // 比如 render 渲染就是属于这个类型的 job 55 | let job; 56 | while ((job = queue.shift())) { 57 | if (job) { 58 | job(); 59 | } 60 | } 61 | } 62 | 63 | function flushPreFlushCbs() { 64 | // 执行所有的 pre 类型的 job 65 | for (let i = 0; i < activePreFlushCbs.length; i++) { 66 | activePreFlushCbs[i](); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /packages/runtime-core/src/shapeFlag.ts: -------------------------------------------------------------------------------- 1 | export const enum ShapeFlags { 2 | // 最后要渲染的 element 类型 3 | ELEMENT = 1, 4 | // 组件类型 5 | STATEFUL_COMPONENT = 1 << 2, 6 | // vnode 的 children 为 string 类型 7 | TEXT_CHILDREN = 1 << 3, 8 | // vnode 的 children 为数组类型 9 | ARRAY_CHILDREN = 1 << 4, 10 | // vnode 的 children 为 slots 类型 11 | SLOTS_CHILDREN = 1 << 5, 12 | } 13 | -------------------------------------------------------------------------------- /packages/runtime-core/src/vnode.ts: -------------------------------------------------------------------------------- 1 | import { ShapeFlags } from "./shapeFlag"; 2 | 3 | export function createVNode(type, props?, children?) { 4 | const vnode = { 5 | type, 6 | props, 7 | children, 8 | ShapeFlag: getShapeFlag(type), 9 | el: null, 10 | }; 11 | console.log(vnode); 12 | 13 | if (typeof children === "string") { 14 | vnode.ShapeFlag = vnode.ShapeFlag | ShapeFlags.TEXT_CHILDREN; 15 | } else if (Array.isArray(children)) { 16 | vnode.ShapeFlag = vnode.ShapeFlag | ShapeFlags.ARRAY_CHILDREN; 17 | } 18 | return vnode; 19 | } 20 | 21 | function getShapeFlag(type) { 22 | return typeof type === "string" 23 | ? ShapeFlags.ELEMENT 24 | : ShapeFlags.STATEFUL_COMPONENT; 25 | } 26 | // import { ShapeFlags } from "@relaxed/shared"; 27 | 28 | // export { createVNode as createElementVNode } 29 | 30 | // export const createVNode = function ( 31 | // type: any, 32 | // props?: any, 33 | // children?: string | Array 34 | // ) { 35 | // // 注意 type 有可能是 string 也有可能是对象 36 | // // 如果是对象的话,那么就是用户设置的 options 37 | // // type 为 string 的时候 38 | // // createVNode("div") 39 | // // type 为组件对象的时候 40 | // // createVNode(App) 41 | // const vnode = { 42 | // el: null, 43 | // component: null, 44 | // key: props?.key, 45 | // type, 46 | // props: props || {}, 47 | // children, 48 | // shapeFlag: getShapeFlag(type), 49 | // }; 50 | 51 | // // 基于 children 再次设置 shapeFlag 52 | // if (Array.isArray(children)) { 53 | // vnode.shapeFlag |= ShapeFlags.ARRAY_CHILDREN; 54 | // } else if (typeof children === "string") { 55 | // vnode.shapeFlag |= ShapeFlags.TEXT_CHILDREN; 56 | // } 57 | 58 | // normalizeChildren(vnode, children); 59 | 60 | // return vnode; 61 | // }; 62 | 63 | // export function normalizeChildren(vnode, children) { 64 | // if (typeof children === "object") { 65 | // // 暂时主要是为了标识出 slots_children 这个类型来 66 | // // 暂时我们只有 element 类型和 component 类型的组件 67 | // // 所以我们这里除了 element ,那么只要是 component 的话,那么children 肯定就是 slots 了 68 | // if (vnode.shapeFlag & ShapeFlags.ELEMENT) { 69 | // // 如果是 element 类型的话,那么 children 肯定不是 slots 70 | // } else { 71 | // // 这里就必然是 component 了, 72 | // vnode.shapeFlag |= ShapeFlags.SLOTS_CHILDREN; 73 | // } 74 | // } 75 | // } 76 | // // 用 symbol 作为唯一标识 77 | // export const Text = Symbol("Text"); 78 | // export const Fragment = Symbol("Fragment"); 79 | 80 | // /** 81 | // * @private 82 | // */ 83 | // export function createTextVNode(text: string = " ") { 84 | // return createVNode(Text, {}, text); 85 | // } 86 | 87 | // // 标准化 vnode 的格式 88 | // // 其目的是为了让 child 支持多种格式 89 | // export function normalizeVNode(child) { 90 | // // 暂时只支持处理 child 为 string 和 number 的情况 91 | // if (typeof child === "string" || typeof child === "number") { 92 | // return createVNode(Text, null, String(child)); 93 | // } else { 94 | // return child; 95 | // } 96 | // } 97 | 98 | // // 基于 type 来判断是什么类型的组件 99 | // function getShapeFlag(type: any) { 100 | // return typeof type === "string" 101 | // ? ShapeFlags.ELEMENT 102 | // : ShapeFlags.STATEFUL_COMPONENT; 103 | // } 104 | -------------------------------------------------------------------------------- /packages/runtime-core/tsup.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'tsup' 2 | 3 | export default defineConfig({ 4 | entry: ['./src/index.ts'], 5 | target: 'esnext', 6 | format: ['esm', 'cjs', 'iife'], 7 | // splitting: false, 8 | sourcemap: true, 9 | clean: true, 10 | watch: true 11 | }) 12 | -------------------------------------------------------------------------------- /packages/runtime-dom/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@relaxed/runtime-dom", 3 | "version": "1.0.0", 4 | "description": "@relaxed/runtime-dom", 5 | "module": "dist/shared.esm-bundler.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC", 12 | "dependencies": { 13 | "@relaxed/runtime-core": "workspace:^1.0.0", 14 | "@relaxed/shared": "workspace:^1.0.0" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /packages/runtime-dom/src/index.ts: -------------------------------------------------------------------------------- 1 | // 源码里面这些接口是由 runtime-dom 来实现 2 | // 这里先简单实现 3 | 4 | import { isOn } from "../../shared/src/index"; 5 | import { createRenderer } from "@relaxed/runtime-core"; 6 | 7 | // 后面也修改成和源码一样的实现 8 | function createElement(type) { 9 | console.log("CreateElement", type); 10 | const element = document.createElement(type); 11 | return element; 12 | } 13 | 14 | function createText(text) { 15 | return document.createTextNode(text); 16 | } 17 | 18 | function setText(node, text) { 19 | node.nodeValue = text; 20 | } 21 | 22 | function setElementText(el, text) { 23 | console.log("SetElementText", el, text); 24 | el.textContent = text; 25 | } 26 | 27 | function patchProp(el, key, preValue, nextValue) { 28 | // preValue 之前的值 29 | // 为了之后 update 做准备的值 30 | // nextValue 当前的值 31 | console.log(`PatchProp 设置属性:${key} 值:${nextValue}`); 32 | console.log(`key: ${key} 之前的值是:${preValue}`); 33 | 34 | if (isOn(key)) { 35 | // 添加事件处理函数的时候需要注意一下 36 | // 1. 添加的和删除的必须是一个函数,不然的话 删除不掉 37 | // 那么就需要把之前 add 的函数给存起来,后面删除的时候需要用到 38 | // 2. nextValue 有可能是匿名函数,当对比发现不一样的时候也可以通过缓存的机制来避免注册多次 39 | // 存储所有的事件函数 40 | const invokers = el._vei || (el._vei = {}); 41 | const existingInvoker = invokers[key]; 42 | if (nextValue && existingInvoker) { 43 | // patch 44 | // 直接修改函数的值即可 45 | existingInvoker.value = nextValue; 46 | } else { 47 | const eventName = key.slice(2).toLowerCase(); 48 | if (nextValue) { 49 | const invoker = (invokers[key] = nextValue); 50 | el.addEventListener(eventName, invoker); 51 | } else { 52 | el.removeEventListener(eventName, existingInvoker); 53 | invokers[key] = undefined; 54 | } 55 | } 56 | } else { 57 | if (nextValue === null || nextValue === "") { 58 | el.removeAttribute(key); 59 | } else { 60 | el.setAttribute(key, nextValue); 61 | } 62 | } 63 | } 64 | 65 | function insert(child, parent, anchor = null) { 66 | console.log("Insert"); 67 | parent.insertBefore(child, anchor); 68 | } 69 | 70 | function remove(child) { 71 | const parent = child.parentNode; 72 | if (parent) { 73 | parent.removeChild(child); 74 | } 75 | } 76 | 77 | let renderer; 78 | 79 | function ensureRenderer() { 80 | // 如果 renderer 有值的话,那么以后都不会初始化了 81 | return ( 82 | renderer || 83 | (renderer = createRenderer({ 84 | createElement, 85 | createText, 86 | setText, 87 | setElementText, 88 | patchProp, 89 | insert, 90 | remove, 91 | })) 92 | ); 93 | } 94 | 95 | export const createApp = (...args) => { 96 | return ensureRenderer().createApp(...args); 97 | }; 98 | 99 | export * from "@relaxed/runtime-core" 100 | -------------------------------------------------------------------------------- /packages/runtime-dom/tsup.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'tsup' 2 | 3 | export default defineConfig({ 4 | entry: ['./src/index.ts'], 5 | target: 'esnext', 6 | format: ['esm', 'cjs', 'iife'], 7 | splitting: false, 8 | sourcemap: true, 9 | clean: true, 10 | watch: true, 11 | external: ['vue'] 12 | }) 13 | -------------------------------------------------------------------------------- /packages/runtime-test/src/index.ts: -------------------------------------------------------------------------------- 1 | // todo 2 | // 实现 render 的渲染接口 3 | // 实现序列化 4 | import { createRenderer } from "@relaxed/runtime-core"; 5 | import { extend } from "../../shared/src/index"; 6 | import { nodeOps } from "./nodeOps"; 7 | import { patchProp } from "./patchProp"; 8 | 9 | export const { render } = createRenderer(extend({ patchProp }, nodeOps)); 10 | 11 | export * from "./nodeOps"; 12 | export * from "./serialize" 13 | export * from '@relaxed/runtime-core' -------------------------------------------------------------------------------- /packages/runtime-test/src/nodeOps.ts: -------------------------------------------------------------------------------- 1 | export const enum NodeTypes { 2 | ELEMENT = "element", 3 | TEXT = "TEXT", 4 | } 5 | 6 | let nodeId = 0; 7 | // 这个函数会在 runtime-core 初始化 element 的时候调用 8 | function createElement(tag: string) { 9 | // 如果是基于 dom 的话 那么这里会返回 dom 元素 10 | // 这里是为了测试 所以只需要反正一个对象就可以了 11 | // 后面的话 通过这个对象来做测试 12 | const node = { 13 | tag, 14 | id: nodeId++, 15 | type: NodeTypes.ELEMENT, 16 | props: {}, 17 | children: [], 18 | parentNode: null, 19 | }; 20 | 21 | return node; 22 | } 23 | 24 | function insert(child, parent) { 25 | parent.children.push(child); 26 | child.parentNode = parent; 27 | } 28 | 29 | function parentNode(node) { 30 | return node.parentNode; 31 | } 32 | 33 | function setElementText(el, text) { 34 | el.children = [ 35 | { 36 | id: nodeId++, 37 | type: NodeTypes.TEXT, 38 | text, 39 | parentNode: el, 40 | }, 41 | ]; 42 | } 43 | 44 | export const nodeOps = { createElement, insert, parentNode, setElementText }; 45 | -------------------------------------------------------------------------------- /packages/runtime-test/src/patchProp.ts: -------------------------------------------------------------------------------- 1 | export function patchProp(el, key, prevValue, nextValue) { 2 | el.props[key] = nextValue; 3 | } 4 | -------------------------------------------------------------------------------- /packages/runtime-test/src/serialize.ts: -------------------------------------------------------------------------------- 1 | // 把 node 给序列化 2 | // 测试的时候好对比 3 | 4 | import { NodeTypes } from "./nodeOps"; 5 | 6 | // 序列化: 把一个对象给处理成 string (进行流化) 7 | export function serialize(node) { 8 | if (node.type === NodeTypes.ELEMENT) { 9 | return serializeElement(node); 10 | } else { 11 | return serializeText(node); 12 | } 13 | } 14 | 15 | function serializeText(node) { 16 | return node.text; 17 | } 18 | 19 | export function serializeInner(node) { 20 | // 把所有节点变成一个string 21 | return node.children.map((c) => serialize(c)).join(``); 22 | } 23 | 24 | function serializeElement(node) { 25 | // 把 props 处理成字符串 26 | // 规则: 27 | // 如果 value 是 null 的话 那么直接返回 `` 28 | // 如果 value 是 `` 的话,那么返回 key 29 | // 不然的话返回 key = value(这里的值需要字符串化) 30 | const props = Object.keys(node.props) 31 | .map((key) => { 32 | const value = node.props[key]; 33 | return value == null 34 | ? `` 35 | : value === `` 36 | ? key 37 | : `${key}=${JSON.stringify(value)}`; 38 | }) 39 | .filter(Boolean) 40 | .join(" "); 41 | 42 | console.log("node---------", node.children); 43 | return `<${node.tag}${props ? ` ${props}` : ``}>${serializeInner(node)}`; 46 | } 47 | -------------------------------------------------------------------------------- /packages/runtime-test/tsup.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'tsup' 2 | 3 | export default defineConfig({ 4 | entry: ['./src/index.ts'], 5 | target: 'esnext', 6 | format: ['esm', 'cjs', 'iife'], 7 | splitting: false, 8 | sourcemap: true, 9 | clean: true, 10 | watch: true, 11 | external: ['vue'] 12 | }) 13 | -------------------------------------------------------------------------------- /packages/shared/dist/index.global.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | (() => { 3 | // src/shapeFlags.ts 4 | var ShapeFlags = /* @__PURE__ */ ((ShapeFlags2) => { 5 | ShapeFlags2[ShapeFlags2["ELEMENT"] = 1] = "ELEMENT"; 6 | ShapeFlags2[ShapeFlags2["STATEFUL_COMPONENT"] = 4] = "STATEFUL_COMPONENT"; 7 | ShapeFlags2[ShapeFlags2["TEXT_CHILDREN"] = 8] = "TEXT_CHILDREN"; 8 | ShapeFlags2[ShapeFlags2["ARRAY_CHILDREN"] = 16] = "ARRAY_CHILDREN"; 9 | ShapeFlags2[ShapeFlags2["SLOTS_CHILDREN"] = 32] = "SLOTS_CHILDREN"; 10 | return ShapeFlags2; 11 | })(ShapeFlags || {}); 12 | 13 | // src/toDisplayString.ts 14 | var toDisplayString = (val) => { 15 | return String(val); 16 | }; 17 | 18 | // src/index.ts 19 | var isObject = (val) => { 20 | return val !== null && typeof val === "object"; 21 | }; 22 | var isString = (val) => typeof val === "string"; 23 | var camelizeRE = /-(\w)/g; 24 | var camelize = (str) => { 25 | return str.replace(camelizeRE, (_, c) => c ? c.toUpperCase() : ""); 26 | }; 27 | var extend = Object.assign; 28 | var isOn = (key) => /^on[A-Z]/.test(key); 29 | function hasChanged(value, oldValue) { 30 | return !Object.is(value, oldValue); 31 | } 32 | function hasOwn(val, key) { 33 | return Object.prototype.hasOwnProperty.call(val, key); 34 | } 35 | var capitalize = (str) => str.charAt(0).toUpperCase() + str.slice(1); 36 | var toHandlerKey = (str) => str ? `on${capitalize(str)}` : ``; 37 | var hyphenateRE = /\B([A-Z])/g; 38 | var hyphenate = (str) => str.replace(hyphenateRE, "-$1").toLowerCase(); 39 | })(); 40 | //# sourceMappingURL=index.global.js.map -------------------------------------------------------------------------------- /packages/shared/dist/index.global.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["../src/shapeFlags.ts","../src/toDisplayString.ts","../src/index.ts"],"sourcesContent":["// 组件的类型\nexport const enum ShapeFlags {\n // 最后要渲染的 element 类型\n ELEMENT = 1,\n // 组件类型\n STATEFUL_COMPONENT = 1 << 2,\n // vnode 的 children 为 string 类型\n TEXT_CHILDREN = 1 << 3,\n // vnode 的 children 为数组类型\n ARRAY_CHILDREN = 1 << 4,\n // vnode 的 children 为 slots 类型\n SLOTS_CHILDREN = 1 << 5\n }\n ","export const toDisplayString = (val) => {\n return String(val);\n};\n","export * from \"../src/shapeFlags\";\nexport * from \"../src/toDisplayString\";\n\nexport const isObject = (val) => {\n return val !== null && typeof val === \"object\";\n};\n\nexport const isString = (val) => typeof val === \"string\";\n\nconst camelizeRE = /-(\\w)/g;\n/**\n * @private\n * 把烤肉串命名方式转换成驼峰命名方式\n */\nexport const camelize = (str: string): string => {\n return str.replace(camelizeRE, (_, c) => (c ? c.toUpperCase() : \"\"));\n};\n\nexport const extend = Object.assign;\n\n// 必须是 on+一个大写字母的格式开头\nexport const isOn = (key) => /^on[A-Z]/.test(key);\n\nexport function hasChanged(value, oldValue) {\n return !Object.is(value, oldValue);\n}\n\nexport function hasOwn(val, key) {\n return Object.prototype.hasOwnProperty.call(val, key);\n}\n\n/**\n * @private\n * 首字母大写\n */\nexport const capitalize = (str: string) =>\n str.charAt(0).toUpperCase() + str.slice(1);\n\n/**\n * @private\n * 添加 on 前缀,并且首字母大写\n */\nexport const toHandlerKey = (str: string) =>\n str ? `on${capitalize(str)}` : ``;\n\n// 用来匹配 kebab-case 的情况\n// 比如 onTest-event 可以匹配到 T\n// 然后取到 T 在前面加一个 - 就可以\n// \\BT 就可以匹配到 T 前面是字母的位置\nconst hyphenateRE = /\\B([A-Z])/g;\n/**\n * @private\n */\nexport const hyphenate = (str: string) =>\n str.replace(hyphenateRE, \"-$1\").toLowerCase();\n"],"mappings":";;;AACO,MAAW,aAAX,kBAAWA,gBAAX;AAEH,IAAAA,wBAAA,aAAU,KAAV;AAEA,IAAAA,wBAAA,wBAAqB,KAArB;AAEA,IAAAA,wBAAA,mBAAgB,KAAhB;AAEA,IAAAA,wBAAA,oBAAiB,MAAjB;AAEA,IAAAA,wBAAA,oBAAiB,MAAjB;AAVc,WAAAA;AAAA,KAAA;;;ACDX,MAAM,kBAAkB,CAAC,QAAQ;AACtC,WAAO,OAAO,GAAG;AAAA,EACnB;;;ACCO,MAAM,WAAW,CAAC,QAAQ;AAC/B,WAAO,QAAQ,QAAQ,OAAO,QAAQ;AAAA,EACxC;AAEO,MAAM,WAAW,CAAC,QAAQ,OAAO,QAAQ;AAEhD,MAAM,aAAa;AAKZ,MAAM,WAAW,CAAC,QAAwB;AAC/C,WAAO,IAAI,QAAQ,YAAY,CAAC,GAAG,MAAO,IAAI,EAAE,YAAY,IAAI,EAAG;AAAA,EACrE;AAEO,MAAM,SAAS,OAAO;AAGtB,MAAM,OAAO,CAAC,QAAQ,WAAW,KAAK,GAAG;AAEzC,WAAS,WAAW,OAAO,UAAU;AAC1C,WAAO,CAAC,OAAO,GAAG,OAAO,QAAQ;AAAA,EACnC;AAEO,WAAS,OAAO,KAAK,KAAK;AAC/B,WAAO,OAAO,UAAU,eAAe,KAAK,KAAK,GAAG;AAAA,EACtD;AAMO,MAAM,aAAa,CAAC,QACzB,IAAI,OAAO,CAAC,EAAE,YAAY,IAAI,IAAI,MAAM,CAAC;AAMpC,MAAM,eAAe,CAAC,QAC3B,MAAM,KAAK,WAAW,GAAG,MAAM;AAMjC,MAAM,cAAc;AAIb,MAAM,YAAY,CAAC,QACxB,IAAI,QAAQ,aAAa,KAAK,EAAE,YAAY;","names":["ShapeFlags"]} -------------------------------------------------------------------------------- /packages/shared/dist/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __defProp = Object.defineProperty; 3 | var __getOwnPropDesc = Object.getOwnPropertyDescriptor; 4 | var __getOwnPropNames = Object.getOwnPropertyNames; 5 | var __hasOwnProp = Object.prototype.hasOwnProperty; 6 | var __export = (target, all) => { 7 | for (var name in all) 8 | __defProp(target, name, { get: all[name], enumerable: true }); 9 | }; 10 | var __copyProps = (to, from, except, desc) => { 11 | if (from && typeof from === "object" || typeof from === "function") { 12 | for (let key of __getOwnPropNames(from)) 13 | if (!__hasOwnProp.call(to, key) && key !== except) 14 | __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); 15 | } 16 | return to; 17 | }; 18 | var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); 19 | 20 | // src/index.ts 21 | var src_exports = {}; 22 | __export(src_exports, { 23 | ShapeFlags: () => ShapeFlags, 24 | camelize: () => camelize, 25 | capitalize: () => capitalize, 26 | extend: () => extend, 27 | hasChanged: () => hasChanged, 28 | hasOwn: () => hasOwn, 29 | hyphenate: () => hyphenate, 30 | isObject: () => isObject, 31 | isOn: () => isOn, 32 | isString: () => isString, 33 | toDisplayString: () => toDisplayString, 34 | toHandlerKey: () => toHandlerKey 35 | }); 36 | module.exports = __toCommonJS(src_exports); 37 | 38 | // src/shapeFlags.ts 39 | var ShapeFlags = /* @__PURE__ */ ((ShapeFlags2) => { 40 | ShapeFlags2[ShapeFlags2["ELEMENT"] = 1] = "ELEMENT"; 41 | ShapeFlags2[ShapeFlags2["STATEFUL_COMPONENT"] = 4] = "STATEFUL_COMPONENT"; 42 | ShapeFlags2[ShapeFlags2["TEXT_CHILDREN"] = 8] = "TEXT_CHILDREN"; 43 | ShapeFlags2[ShapeFlags2["ARRAY_CHILDREN"] = 16] = "ARRAY_CHILDREN"; 44 | ShapeFlags2[ShapeFlags2["SLOTS_CHILDREN"] = 32] = "SLOTS_CHILDREN"; 45 | return ShapeFlags2; 46 | })(ShapeFlags || {}); 47 | 48 | // src/toDisplayString.ts 49 | var toDisplayString = (val) => { 50 | return String(val); 51 | }; 52 | 53 | // src/index.ts 54 | var isObject = (val) => { 55 | return val !== null && typeof val === "object"; 56 | }; 57 | var isString = (val) => typeof val === "string"; 58 | var camelizeRE = /-(\w)/g; 59 | var camelize = (str) => { 60 | return str.replace(camelizeRE, (_, c) => c ? c.toUpperCase() : ""); 61 | }; 62 | var extend = Object.assign; 63 | var isOn = (key) => /^on[A-Z]/.test(key); 64 | function hasChanged(value, oldValue) { 65 | return !Object.is(value, oldValue); 66 | } 67 | function hasOwn(val, key) { 68 | return Object.prototype.hasOwnProperty.call(val, key); 69 | } 70 | var capitalize = (str) => str.charAt(0).toUpperCase() + str.slice(1); 71 | var toHandlerKey = (str) => str ? `on${capitalize(str)}` : ``; 72 | var hyphenateRE = /\B([A-Z])/g; 73 | var hyphenate = (str) => str.replace(hyphenateRE, "-$1").toLowerCase(); 74 | // Annotate the CommonJS export names for ESM import in node: 75 | 0 && (module.exports = { 76 | ShapeFlags, 77 | camelize, 78 | capitalize, 79 | extend, 80 | hasChanged, 81 | hasOwn, 82 | hyphenate, 83 | isObject, 84 | isOn, 85 | isString, 86 | toDisplayString, 87 | toHandlerKey 88 | }); 89 | //# sourceMappingURL=index.js.map -------------------------------------------------------------------------------- /packages/shared/dist/index.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["../src/index.ts","../src/shapeFlags.ts","../src/toDisplayString.ts"],"sourcesContent":["export * from \"../src/shapeFlags\";\nexport * from \"../src/toDisplayString\";\n\nexport const isObject = (val) => {\n return val !== null && typeof val === \"object\";\n};\n\nexport const isString = (val) => typeof val === \"string\";\n\nconst camelizeRE = /-(\\w)/g;\n/**\n * @private\n * 把烤肉串命名方式转换成驼峰命名方式\n */\nexport const camelize = (str: string): string => {\n return str.replace(camelizeRE, (_, c) => (c ? c.toUpperCase() : \"\"));\n};\n\nexport const extend = Object.assign;\n\n// 必须是 on+一个大写字母的格式开头\nexport const isOn = (key) => /^on[A-Z]/.test(key);\n\nexport function hasChanged(value, oldValue) {\n return !Object.is(value, oldValue);\n}\n\nexport function hasOwn(val, key) {\n return Object.prototype.hasOwnProperty.call(val, key);\n}\n\n/**\n * @private\n * 首字母大写\n */\nexport const capitalize = (str: string) =>\n str.charAt(0).toUpperCase() + str.slice(1);\n\n/**\n * @private\n * 添加 on 前缀,并且首字母大写\n */\nexport const toHandlerKey = (str: string) =>\n str ? `on${capitalize(str)}` : ``;\n\n// 用来匹配 kebab-case 的情况\n// 比如 onTest-event 可以匹配到 T\n// 然后取到 T 在前面加一个 - 就可以\n// \\BT 就可以匹配到 T 前面是字母的位置\nconst hyphenateRE = /\\B([A-Z])/g;\n/**\n * @private\n */\nexport const hyphenate = (str: string) =>\n str.replace(hyphenateRE, \"-$1\").toLowerCase();\n","// 组件的类型\nexport const enum ShapeFlags {\n // 最后要渲染的 element 类型\n ELEMENT = 1,\n // 组件类型\n STATEFUL_COMPONENT = 1 << 2,\n // vnode 的 children 为 string 类型\n TEXT_CHILDREN = 1 << 3,\n // vnode 的 children 为数组类型\n ARRAY_CHILDREN = 1 << 4,\n // vnode 的 children 为 slots 类型\n SLOTS_CHILDREN = 1 << 5\n }\n ","export const toDisplayString = (val) => {\n return String(val);\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACCO,IAAW,aAAX,kBAAWA,gBAAX;AAEH,EAAAA,wBAAA,aAAU,KAAV;AAEA,EAAAA,wBAAA,wBAAqB,KAArB;AAEA,EAAAA,wBAAA,mBAAgB,KAAhB;AAEA,EAAAA,wBAAA,oBAAiB,MAAjB;AAEA,EAAAA,wBAAA,oBAAiB,MAAjB;AAVc,SAAAA;AAAA,GAAA;;;ACDX,IAAM,kBAAkB,CAAC,QAAQ;AACtC,SAAO,OAAO,GAAG;AACnB;;;AFCO,IAAM,WAAW,CAAC,QAAQ;AAC/B,SAAO,QAAQ,QAAQ,OAAO,QAAQ;AACxC;AAEO,IAAM,WAAW,CAAC,QAAQ,OAAO,QAAQ;AAEhD,IAAM,aAAa;AAKZ,IAAM,WAAW,CAAC,QAAwB;AAC/C,SAAO,IAAI,QAAQ,YAAY,CAAC,GAAG,MAAO,IAAI,EAAE,YAAY,IAAI,EAAG;AACrE;AAEO,IAAM,SAAS,OAAO;AAGtB,IAAM,OAAO,CAAC,QAAQ,WAAW,KAAK,GAAG;AAEzC,SAAS,WAAW,OAAO,UAAU;AAC1C,SAAO,CAAC,OAAO,GAAG,OAAO,QAAQ;AACnC;AAEO,SAAS,OAAO,KAAK,KAAK;AAC/B,SAAO,OAAO,UAAU,eAAe,KAAK,KAAK,GAAG;AACtD;AAMO,IAAM,aAAa,CAAC,QACzB,IAAI,OAAO,CAAC,EAAE,YAAY,IAAI,IAAI,MAAM,CAAC;AAMpC,IAAM,eAAe,CAAC,QAC3B,MAAM,KAAK,WAAW,GAAG,MAAM;AAMjC,IAAM,cAAc;AAIb,IAAM,YAAY,CAAC,QACxB,IAAI,QAAQ,aAAa,KAAK,EAAE,YAAY;","names":["ShapeFlags"]} -------------------------------------------------------------------------------- /packages/shared/dist/index.mjs: -------------------------------------------------------------------------------- 1 | // src/shapeFlags.ts 2 | var ShapeFlags = /* @__PURE__ */ ((ShapeFlags2) => { 3 | ShapeFlags2[ShapeFlags2["ELEMENT"] = 1] = "ELEMENT"; 4 | ShapeFlags2[ShapeFlags2["STATEFUL_COMPONENT"] = 4] = "STATEFUL_COMPONENT"; 5 | ShapeFlags2[ShapeFlags2["TEXT_CHILDREN"] = 8] = "TEXT_CHILDREN"; 6 | ShapeFlags2[ShapeFlags2["ARRAY_CHILDREN"] = 16] = "ARRAY_CHILDREN"; 7 | ShapeFlags2[ShapeFlags2["SLOTS_CHILDREN"] = 32] = "SLOTS_CHILDREN"; 8 | return ShapeFlags2; 9 | })(ShapeFlags || {}); 10 | 11 | // src/toDisplayString.ts 12 | var toDisplayString = (val) => { 13 | return String(val); 14 | }; 15 | 16 | // src/index.ts 17 | var isObject = (val) => { 18 | return val !== null && typeof val === "object"; 19 | }; 20 | var isString = (val) => typeof val === "string"; 21 | var camelizeRE = /-(\w)/g; 22 | var camelize = (str) => { 23 | return str.replace(camelizeRE, (_, c) => c ? c.toUpperCase() : ""); 24 | }; 25 | var extend = Object.assign; 26 | var isOn = (key) => /^on[A-Z]/.test(key); 27 | function hasChanged(value, oldValue) { 28 | return !Object.is(value, oldValue); 29 | } 30 | function hasOwn(val, key) { 31 | return Object.prototype.hasOwnProperty.call(val, key); 32 | } 33 | var capitalize = (str) => str.charAt(0).toUpperCase() + str.slice(1); 34 | var toHandlerKey = (str) => str ? `on${capitalize(str)}` : ``; 35 | var hyphenateRE = /\B([A-Z])/g; 36 | var hyphenate = (str) => str.replace(hyphenateRE, "-$1").toLowerCase(); 37 | export { 38 | ShapeFlags, 39 | camelize, 40 | capitalize, 41 | extend, 42 | hasChanged, 43 | hasOwn, 44 | hyphenate, 45 | isObject, 46 | isOn, 47 | isString, 48 | toDisplayString, 49 | toHandlerKey 50 | }; 51 | //# sourceMappingURL=index.mjs.map -------------------------------------------------------------------------------- /packages/shared/dist/index.mjs.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["../src/shapeFlags.ts","../src/toDisplayString.ts","../src/index.ts"],"sourcesContent":["// 组件的类型\nexport const enum ShapeFlags {\n // 最后要渲染的 element 类型\n ELEMENT = 1,\n // 组件类型\n STATEFUL_COMPONENT = 1 << 2,\n // vnode 的 children 为 string 类型\n TEXT_CHILDREN = 1 << 3,\n // vnode 的 children 为数组类型\n ARRAY_CHILDREN = 1 << 4,\n // vnode 的 children 为 slots 类型\n SLOTS_CHILDREN = 1 << 5\n }\n ","export const toDisplayString = (val) => {\n return String(val);\n};\n","export * from \"../src/shapeFlags\";\nexport * from \"../src/toDisplayString\";\n\nexport const isObject = (val) => {\n return val !== null && typeof val === \"object\";\n};\n\nexport const isString = (val) => typeof val === \"string\";\n\nconst camelizeRE = /-(\\w)/g;\n/**\n * @private\n * 把烤肉串命名方式转换成驼峰命名方式\n */\nexport const camelize = (str: string): string => {\n return str.replace(camelizeRE, (_, c) => (c ? c.toUpperCase() : \"\"));\n};\n\nexport const extend = Object.assign;\n\n// 必须是 on+一个大写字母的格式开头\nexport const isOn = (key) => /^on[A-Z]/.test(key);\n\nexport function hasChanged(value, oldValue) {\n return !Object.is(value, oldValue);\n}\n\nexport function hasOwn(val, key) {\n return Object.prototype.hasOwnProperty.call(val, key);\n}\n\n/**\n * @private\n * 首字母大写\n */\nexport const capitalize = (str: string) =>\n str.charAt(0).toUpperCase() + str.slice(1);\n\n/**\n * @private\n * 添加 on 前缀,并且首字母大写\n */\nexport const toHandlerKey = (str: string) =>\n str ? `on${capitalize(str)}` : ``;\n\n// 用来匹配 kebab-case 的情况\n// 比如 onTest-event 可以匹配到 T\n// 然后取到 T 在前面加一个 - 就可以\n// \\BT 就可以匹配到 T 前面是字母的位置\nconst hyphenateRE = /\\B([A-Z])/g;\n/**\n * @private\n */\nexport const hyphenate = (str: string) =>\n str.replace(hyphenateRE, \"-$1\").toLowerCase();\n"],"mappings":";AACO,IAAW,aAAX,kBAAWA,gBAAX;AAEH,EAAAA,wBAAA,aAAU,KAAV;AAEA,EAAAA,wBAAA,wBAAqB,KAArB;AAEA,EAAAA,wBAAA,mBAAgB,KAAhB;AAEA,EAAAA,wBAAA,oBAAiB,MAAjB;AAEA,EAAAA,wBAAA,oBAAiB,MAAjB;AAVc,SAAAA;AAAA,GAAA;;;ACDX,IAAM,kBAAkB,CAAC,QAAQ;AACtC,SAAO,OAAO,GAAG;AACnB;;;ACCO,IAAM,WAAW,CAAC,QAAQ;AAC/B,SAAO,QAAQ,QAAQ,OAAO,QAAQ;AACxC;AAEO,IAAM,WAAW,CAAC,QAAQ,OAAO,QAAQ;AAEhD,IAAM,aAAa;AAKZ,IAAM,WAAW,CAAC,QAAwB;AAC/C,SAAO,IAAI,QAAQ,YAAY,CAAC,GAAG,MAAO,IAAI,EAAE,YAAY,IAAI,EAAG;AACrE;AAEO,IAAM,SAAS,OAAO;AAGtB,IAAM,OAAO,CAAC,QAAQ,WAAW,KAAK,GAAG;AAEzC,SAAS,WAAW,OAAO,UAAU;AAC1C,SAAO,CAAC,OAAO,GAAG,OAAO,QAAQ;AACnC;AAEO,SAAS,OAAO,KAAK,KAAK;AAC/B,SAAO,OAAO,UAAU,eAAe,KAAK,KAAK,GAAG;AACtD;AAMO,IAAM,aAAa,CAAC,QACzB,IAAI,OAAO,CAAC,EAAE,YAAY,IAAI,IAAI,MAAM,CAAC;AAMpC,IAAM,eAAe,CAAC,QAC3B,MAAM,KAAK,WAAW,GAAG,MAAM;AAMjC,IAAM,cAAc;AAIb,IAAM,YAAY,CAAC,QACxB,IAAI,QAAQ,aAAa,KAAK,EAAE,YAAY;","names":["ShapeFlags"]} -------------------------------------------------------------------------------- /packages/shared/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@relaxed/shared", 3 | "version": "1.0.0", 4 | "description": "@relaxed/shared", 5 | "scripts": { 6 | "test": "echo \"Error: no test specified\" && exit 1", 7 | "build": "tsup" 8 | }, 9 | "main": "dist/index.mjs", 10 | "keywords": [], 11 | "author": "", 12 | "license": "ISC" 13 | } 14 | -------------------------------------------------------------------------------- /packages/shared/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from "../src/shapeFlags"; 2 | export * from "../src/toDisplayString"; 3 | 4 | export const isObject = (val) => { 5 | return val !== null && typeof val === "object"; 6 | }; 7 | 8 | export const isString = (val) => typeof val === "string"; 9 | 10 | const camelizeRE = /-(\w)/g; 11 | /** 12 | * @private 13 | * 把烤肉串命名方式转换成驼峰命名方式 14 | */ 15 | export const camelize = (str: string): string => { 16 | return str.replace(camelizeRE, (_, c) => (c ? c.toUpperCase() : "")); 17 | }; 18 | 19 | export const extend = Object.assign; 20 | 21 | // 必须是 on+一个大写字母的格式开头 22 | export const isOn = (key) => /^on[A-Z]/.test(key); 23 | 24 | export function hasChanged(value, oldValue) { 25 | return !Object.is(value, oldValue); 26 | } 27 | 28 | export function hasOwn(val, key) { 29 | return Object.prototype.hasOwnProperty.call(val, key); 30 | } 31 | 32 | /** 33 | * @private 34 | * 首字母大写 35 | */ 36 | export const capitalize = (str: string) => 37 | str.charAt(0).toUpperCase() + str.slice(1); 38 | 39 | /** 40 | * @private 41 | * 添加 on 前缀,并且首字母大写 42 | */ 43 | export const toHandlerKey = (str: string) => 44 | str ? `on${capitalize(str)}` : ``; 45 | 46 | // 用来匹配 kebab-case 的情况 47 | // 比如 onTest-event 可以匹配到 T 48 | // 然后取到 T 在前面加一个 - 就可以 49 | // \BT 就可以匹配到 T 前面是字母的位置 50 | const hyphenateRE = /\B([A-Z])/g; 51 | /** 52 | * @private 53 | */ 54 | export const hyphenate = (str: string) => 55 | str.replace(hyphenateRE, "-$1").toLowerCase(); 56 | -------------------------------------------------------------------------------- /packages/shared/src/shapeFlags.ts: -------------------------------------------------------------------------------- 1 | // 组件的类型 2 | export const enum ShapeFlags { 3 | // 最后要渲染的 element 类型 4 | ELEMENT = 1, 5 | // 组件类型 6 | STATEFUL_COMPONENT = 1 << 2, 7 | // vnode 的 children 为 string 类型 8 | TEXT_CHILDREN = 1 << 3, 9 | // vnode 的 children 为数组类型 10 | ARRAY_CHILDREN = 1 << 4, 11 | // vnode 的 children 为 slots 类型 12 | SLOTS_CHILDREN = 1 << 5 13 | } 14 | -------------------------------------------------------------------------------- /packages/shared/src/toDisplayString.ts: -------------------------------------------------------------------------------- 1 | export const toDisplayString = (val) => { 2 | return String(val); 3 | }; 4 | -------------------------------------------------------------------------------- /packages/shared/tsup.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "tsup"; 2 | 3 | export default defineConfig({ 4 | entry: ["./src/index.ts"], 5 | target: "esnext", 6 | format: ["esm", "cjs", "iife"], 7 | splitting: false, 8 | sourcemap: true, 9 | clean: true, 10 | watch: true, 11 | external: ["vue"], 12 | }); 13 | -------------------------------------------------------------------------------- /packages/vue/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ErKeLost/extreme-vue/849882815db65d19d16f40bc70b014bec9922829/packages/vue/.DS_Store -------------------------------------------------------------------------------- /packages/vue/example/apiInject/App.js: -------------------------------------------------------------------------------- 1 | // 组件 provide 和 inject 功能 2 | import { 3 | h, 4 | provide, 5 | inject, 6 | } from "../../dist/mini-vue.esm-bundler.js"; 7 | 8 | const ProviderOne = { 9 | setup() { 10 | provide("foo", "foo"); 11 | provide("bar", "bar"); 12 | return () => h(ProviderTwo); 13 | }, 14 | }; 15 | 16 | const ProviderTwo = { 17 | setup() { 18 | // override parent value 19 | provide("foo", "fooOverride"); 20 | provide("baz", "baz"); 21 | const foo = inject("foo"); 22 | // 这里获取的 foo 的值应该是 "foo" 23 | // 这个组件的子组件获取的 foo ,才应该是 fooOverride 24 | if (foo !== "foo") { 25 | throw new Error("Foo should equal to foo"); 26 | } 27 | return () => h(Consumer); 28 | }, 29 | }; 30 | 31 | const Consumer = { 32 | setup() { 33 | const foo = inject("foo"); 34 | const bar = inject("bar"); 35 | const baz = inject("baz"); 36 | return () => { 37 | return h("div", {}, `${foo}-${bar}-${baz}`); 38 | }; 39 | }, 40 | }; 41 | 42 | export default { 43 | name: "App", 44 | setup() { 45 | return () => h("div", {}, [h("p", {}, "apiInject"), h(ProviderOne)]); 46 | }, 47 | }; 48 | -------------------------------------------------------------------------------- /packages/vue/example/apiInject/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Document 7 | 8 | 9 |
10 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /packages/vue/example/compiler-base/App.js: -------------------------------------------------------------------------------- 1 | // 最简单的情况 2 | // template 只有一个 interpolation 3 | // export default { 4 | // template: `{{msg}}`, 5 | // setup() { 6 | // return { 7 | // msg: "vue3 - compiler", 8 | // }; 9 | // }, 10 | // }; 11 | 12 | 13 | // 复杂一点 14 | // template 包含 element 和 interpolation 15 | export default { 16 | template: `

{{msg}}

`, 17 | setup() { 18 | return { 19 | msg: "vue3 - compiler", 20 | }; 21 | }, 22 | }; 23 | -------------------------------------------------------------------------------- /packages/vue/example/compiler-base/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Document 7 | 8 | 9 |
10 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /packages/vue/example/componentEmit.js/App.js: -------------------------------------------------------------------------------- 1 | // 组件 emit 逻辑 demo 2 | // click emit 发出 change, 可以触发 App 组件内定义好的侦听函数 3 | // 允许接收多个参数 4 | import { h, ref, reactive } from "../../dist/mini-vue.esm-bundler.js"; 5 | import Child from "./Child.js"; 6 | 7 | export default { 8 | name: "App", 9 | setup() {}, 10 | 11 | render() { 12 | return h("div", {}, [ 13 | h("div", {}, "你好"), 14 | h(Child, { 15 | msg: "your name is child", 16 | onChange(a, b) { 17 | console.log("---------------change------------------"); 18 | console.log(a, b); 19 | }, 20 | onChangePageName(a, b) { 21 | console.log("---------------change-page-name------------------"); 22 | console.log(a, b); 23 | }, 24 | }), 25 | ]); 26 | }, 27 | }; 28 | -------------------------------------------------------------------------------- /packages/vue/example/componentEmit.js/Child.js: -------------------------------------------------------------------------------- 1 | import { h, ref, reactive } from "../../dist/mini-vue.esm-bundler.js"; 2 | export default { 3 | name: "Child", 4 | setup(props, { emit }) { 5 | emit("change", "aaaaa", "bbbbbb"); 6 | // 支持多个 - 7 | emit("change-page-name", "start", "game"); 8 | }, 9 | render() { 10 | return h("div", {}, "child"); 11 | }, 12 | }; 13 | -------------------------------------------------------------------------------- /packages/vue/example/componentEmit.js/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Document 7 | 8 | 9 |
10 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /packages/vue/example/componentProxy/App.js: -------------------------------------------------------------------------------- 1 | // 在 render 中使用 proxy 调用 emit 函数 2 | // 也可以直接使用 this 3 | // 验证 proxy 的实现逻辑 4 | import { h, ref, reactive } from "../../dist/mini-vue.esm-bundler.js"; 5 | import Child from "./Child.js"; 6 | 7 | export default { 8 | name: "App", 9 | setup() {}, 10 | 11 | render() { 12 | return h("div", {}, [ 13 | h("div", {}, "你好"), 14 | h(Child, { 15 | msg: "your name is child", 16 | onChange(a, b) { 17 | console.log("---------------change------------------"); 18 | console.log(a, b); 19 | }, 20 | }), 21 | ]); 22 | }, 23 | }; 24 | -------------------------------------------------------------------------------- /packages/vue/example/componentProxy/Child.js: -------------------------------------------------------------------------------- 1 | import { h, ref, reactive } from "../../dist/mini-vue.esm-bundler.js"; 2 | export default { 3 | name: "Child", 4 | setup(props, { emit }) {}, 5 | render(proxy) { 6 | const self = this 7 | return h("div", {}, [ 8 | h( 9 | "button", 10 | { 11 | onClick() { 12 | console.log(proxy); 13 | console.log("click"); 14 | proxy.$emit("change", "aaa", "bbbb"); 15 | // 使用 this 16 | console.log(this) 17 | self.$emit("change", "ccc", "ddd"); 18 | }, 19 | }, 20 | "emit" 21 | ), 22 | ]); 23 | }, 24 | }; 25 | -------------------------------------------------------------------------------- /packages/vue/example/componentProxy/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Document 7 | 8 | 9 |
10 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /packages/vue/example/componentUpdate/App.js: -------------------------------------------------------------------------------- 1 | // 在 render 中使用 proxy 调用 emit 函数 2 | // 也可以直接使用 this 3 | // 验证 proxy 的实现逻辑 4 | import { h, ref } from "../../dist/mini-vue.esm-bundler.js"; 5 | import Child from "./Child.js"; 6 | 7 | export default { 8 | name: "App", 9 | setup() { 10 | const msg = ref("123"); 11 | window.msg = msg 12 | 13 | const changeChildProps = () => { 14 | msg.value = "456"; 15 | }; 16 | 17 | return { msg, changeChildProps }; 18 | }, 19 | 20 | render() { 21 | return h("div", {}, [ 22 | h("div", {}, "你好"), 23 | h( 24 | "button", 25 | { 26 | onClick: this.changeChildProps, 27 | }, 28 | "change child props" 29 | ), 30 | h(Child, { 31 | msg: this.msg, 32 | }), 33 | ]); 34 | }, 35 | }; 36 | -------------------------------------------------------------------------------- /packages/vue/example/componentUpdate/Child.js: -------------------------------------------------------------------------------- 1 | import { h, ref, reactive } from "../../dist/mini-vue.esm-bundler.js"; 2 | export default { 3 | name: "Child", 4 | setup(props, { emit }) {}, 5 | render(proxy) { 6 | return h("div", {}, [h("div", {}, "child" + this.$props.msg)]); 7 | }, 8 | }; 9 | -------------------------------------------------------------------------------- /packages/vue/example/componentUpdate/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Document 7 | 8 | 9 |
10 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /packages/vue/example/createTextVnode/App.js: -------------------------------------------------------------------------------- 1 | import { h, ref, reactive, createTextVNode } from "../../dist/mini-vue.esm-bundler.js"; 2 | 3 | export default { 4 | name: "App", 5 | setup() {}, 6 | 7 | render() { 8 | return h("div", {}, [ 9 | h("div", {}, "你好"), 10 | createTextVNode("这是通过 createTextVNode 创建的节点"), 11 | ]); 12 | }, 13 | }; 14 | -------------------------------------------------------------------------------- /packages/vue/example/createTextVnode/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Document 7 | 8 | 9 |
10 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /packages/vue/example/customRenderer/App.js: -------------------------------------------------------------------------------- 1 | import { h, ref } from "../../dist/mini-vue.esm-bundler.js"; 2 | import { game } from "./game.js"; 3 | 4 | export default { 5 | name: "App", 6 | setup() { 7 | // 通过 ticker 来去更新 x 的值 8 | 9 | const x = ref(0); 10 | const y = ref(0); 11 | let dir = 1; 12 | const speed = 2; 13 | 14 | game.ticker.add(() => { 15 | if (x.value > 400) { 16 | dir = -1; 17 | } else if (x.value < 0) { 18 | dir = 1; 19 | } 20 | 21 | x.value += speed * dir; 22 | }); 23 | 24 | return { 25 | x, 26 | y, 27 | }; 28 | }, 29 | 30 | render() { 31 | return h("rect", { x: this.x, y: this.y }); 32 | }, 33 | }; 34 | -------------------------------------------------------------------------------- /packages/vue/example/customRenderer/game.js: -------------------------------------------------------------------------------- 1 | export const game = new PIXI.Application({ 2 | width: 500, 3 | height: 500, 4 | }); 5 | 6 | document.body.append(game.view); 7 | 8 | export function createRootContainer() { 9 | return game.stage; 10 | } 11 | -------------------------------------------------------------------------------- /packages/vue/example/customRenderer/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Document 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /packages/vue/example/customRenderer/main.js: -------------------------------------------------------------------------------- 1 | import App from "./App.js"; 2 | import { createApp } from "./renderer.js"; 3 | import { createRootContainer } from "./game.js"; 4 | 5 | createApp(App).mount(createRootContainer()); 6 | -------------------------------------------------------------------------------- /packages/vue/example/customRenderer/renderer.js: -------------------------------------------------------------------------------- 1 | import { createRenderer } from "../../dist/mini-vue.esm-bundler.js"; 2 | 3 | // 给基于 pixi.js 的渲染函数 4 | const renderer = createRenderer({ 5 | createElement(type) { 6 | const rect = new PIXI.Graphics(); 7 | rect.beginFill(0xff0000); 8 | rect.drawRect(0, 0, 100, 100); 9 | rect.endFill(); 10 | 11 | return rect; 12 | }, 13 | 14 | patchProp(el, key, prevValue, nextValue) { 15 | el[key] = nextValue; 16 | }, 17 | 18 | insert(el, parent) { 19 | parent.addChild(el); 20 | }, 21 | }); 22 | 23 | export function createApp(rootComponent) { 24 | return renderer.createApp(rootComponent); 25 | } 26 | -------------------------------------------------------------------------------- /packages/vue/example/getCurrentInstance/App.js: -------------------------------------------------------------------------------- 1 | // 可以在 setup 中使用 getCurrentInstance 获取组件实例对象 2 | import { h, getCurrentInstance } from "../../dist/mini-vue.esm-bundler.js"; 3 | 4 | export default { 5 | name: "App", 6 | setup() { 7 | 8 | console.log(getCurrentInstance()) 9 | 10 | 11 | 12 | return () => h("div", {}, [h("p", {}, "getCurrentInstance")]); 13 | }, 14 | }; 15 | -------------------------------------------------------------------------------- /packages/vue/example/getCurrentInstance/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Document 7 | 8 | 9 |
10 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /packages/vue/example/helloWorld/App.js: -------------------------------------------------------------------------------- 1 | import { h, ref } from "../../dist/mini-vue.esm-bundler.js"; 2 | 3 | const count = ref(0); 4 | 5 | const HelloWorld = { 6 | name: "HelloWorld", 7 | setup() {}, 8 | // TODO 第一个小目标 9 | // 可以在使用 template 只需要有一个插值表达式即 10 | // 可以解析 tag 标签 11 | // template: ` 12 | //
hi {{msg}}
13 | // 需要编译成 render 函数 14 | // `, 15 | render() { 16 | return h( 17 | "div", 18 | { tId: "helloWorld" }, 19 | `hello world: count: ${count.value}` 20 | ); 21 | }, 22 | }; 23 | 24 | export default { 25 | name: "App", 26 | setup() {}, 27 | 28 | render() { 29 | return h("div", { tId: 1 }, [h("p", {}, "主页"), h(HelloWorld)]); 30 | }, 31 | }; 32 | -------------------------------------------------------------------------------- /packages/vue/example/helloWorld/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /packages/vue/example/helloWorld/main.js: -------------------------------------------------------------------------------- 1 | import { createApp } from "../../dist/mini-vue.esm-bundler.js"; 2 | import App from "./App.js"; 3 | 4 | const rootContainer = document.querySelector("#root"); 5 | createApp(App).mount(rootContainer); 6 | -------------------------------------------------------------------------------- /packages/vue/example/nextTicker/App.js: -------------------------------------------------------------------------------- 1 | import { h, ref, reactive } from "../../dist/mini-vue.esm-bundler.js"; 2 | import NextTicker from "./NextTicker.js"; 3 | 4 | export default { 5 | name: "App", 6 | setup() {}, 7 | 8 | render() { 9 | return h("div", { tId: 1 }, [h("p", {}, "主页"), h(NextTicker)]); 10 | }, 11 | }; 12 | -------------------------------------------------------------------------------- /packages/vue/example/nextTicker/NextTicker.js: -------------------------------------------------------------------------------- 1 | // 测试 nextTick 逻辑 2 | import { h, ref } from "../../dist/mini-vue.esm-bundler.js"; 3 | 4 | // 如果 for 循环改变 count 的值 100 次的话 5 | // 会同时触发 100 次的 update 页面逻辑 6 | // 这里可以把 update 页面的逻辑放到微任务中执行 7 | // 避免更改了响应式对象就会执行 update 的逻辑 8 | // 因为只有最后一次调用 update 才是有价值的 9 | window.count = ref(1); 10 | 11 | // 如果一个响应式变量同时触发了两个组件的 update 12 | // 会发生什么有趣的事呢? 13 | const Child1 = { 14 | name: "NextTickerChild1", 15 | setup() {}, 16 | render() { 17 | return h("div", {}, `child1 count: ${window.count.value}`); 18 | }, 19 | }; 20 | 21 | const Child2 = { 22 | name: "NextTickerChild2", 23 | setup() {}, 24 | render() { 25 | return h("div", {}, `child2 count: ${window.count.value}`); 26 | }, 27 | }; 28 | 29 | export default { 30 | name: "NextTicker", 31 | setup() {}, 32 | render() { 33 | return h( 34 | "div", 35 | { tId: "nextTicker" }, 36 | [h(Child1), h(Child2)] 37 | // `for nextTick: count: ${window.count.value}` 38 | ); 39 | }, 40 | }; 41 | -------------------------------------------------------------------------------- /packages/vue/example/nextTicker/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /packages/vue/example/nextTicker/main.js: -------------------------------------------------------------------------------- 1 | import {createApp} from "../../dist/mini-vue.esm-bundler.js"; 2 | import App from "./App.js"; 3 | 4 | const rootContainer = document.querySelector("#root"); 5 | createApp(App).mount(rootContainer); 6 | -------------------------------------------------------------------------------- /packages/vue/example/patchChildren/App.js: -------------------------------------------------------------------------------- 1 | import { h } from "../../dist/mini-vue.esm-bundler.js"; 2 | import PatchChildren from "./PatchChildren.js"; 3 | 4 | export default { 5 | name: "App", 6 | setup() {}, 7 | 8 | render() { 9 | return h("div", { tId: 1 }, [h("p", {}, "主页"), h(PatchChildren)]); 10 | }, 11 | }; 12 | -------------------------------------------------------------------------------- /packages/vue/example/patchChildren/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /packages/vue/example/patchChildren/main.js: -------------------------------------------------------------------------------- 1 | import { createApp } from "../../dist/mini-vue.esm-bundler.js"; 2 | import App from "./App.js"; 3 | 4 | const rootContainer = document.querySelector("#root"); 5 | createApp(App).mount(rootContainer); 6 | -------------------------------------------------------------------------------- /packages/vue/example/renderComponent/App.js: -------------------------------------------------------------------------------- 1 | import { h, ref, reactive } from "../../dist/mini-vue.esm-bundler.js"; 2 | import Child from "./Child.js"; 3 | 4 | export default { 5 | name: "App", 6 | setup() {}, 7 | 8 | render() { 9 | return h("div", {}, [ 10 | h("div", {}, "你好"), 11 | h(Child, { 12 | msg: "your name is child", 13 | }), 14 | ]); 15 | }, 16 | }; 17 | -------------------------------------------------------------------------------- /packages/vue/example/renderComponent/Child.js: -------------------------------------------------------------------------------- 1 | import { h, ref, reactive } from "../../dist/mini-vue.esm-bundler.js"; 2 | export default { 3 | name: "Child", 4 | setup(props, context) { 5 | console.log("props------------------>", props); 6 | console.log("context---------------->", context); 7 | }, 8 | render() { 9 | return h("div", {}, "child"); 10 | }, 11 | }; 12 | -------------------------------------------------------------------------------- /packages/vue/example/renderComponent/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Document 7 | 8 | 9 |
10 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /packages/vue/example/setupStateRenderComponent/App.js: -------------------------------------------------------------------------------- 1 | // 在 render 中可以通过 this.xxx 访问到 setup 返回的对象 2 | import { h, ref, reactive } from "../../dist/mini-vue.esm-bundler.js"; 3 | 4 | export default { 5 | name: "App", 6 | setup() { 7 | const count = ref(0); 8 | const handleClick = () => { 9 | console.log("click"); 10 | count.value++; 11 | }; 12 | 13 | return { 14 | count, 15 | handleClick, 16 | }; 17 | }, 18 | 19 | render() { 20 | console.log(this.count); 21 | return h("div", {}, [ 22 | h("div", {}, String(this.count)), 23 | h("button", { onClick: this.handleClick }, "click"), 24 | ]); 25 | }, 26 | }; 27 | -------------------------------------------------------------------------------- /packages/vue/example/setupStateRenderComponent/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Document 7 | 8 | 9 |
10 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /packages/vue/example/slotsComponent/App.js: -------------------------------------------------------------------------------- 1 | import { h, ref, reactive } from "../../dist/mini-vue.esm-bundler.js"; 2 | import Child from "./Child.js"; 3 | 4 | export default { 5 | name: "App", 6 | setup() {}, 7 | 8 | render() { 9 | return h("div", {}, [ 10 | h("div", {}, "你好"), 11 | h( 12 | Child, 13 | { 14 | msg: "your name is child", 15 | }, 16 | { 17 | default: ({ age }) => [ 18 | h("p", {}, "我是通过 slot 渲染出来的第一个元素 "), 19 | h("p", {}, "我是通过 slot 渲染出来的第二个元素"), 20 | h("p", {}, `我可以接收到 age: ${age}`), 21 | ], 22 | } 23 | ), 24 | ]); 25 | }, 26 | }; 27 | -------------------------------------------------------------------------------- /packages/vue/example/slotsComponent/Child.js: -------------------------------------------------------------------------------- 1 | import { h, ref, reactive, renderSlot } from "../../dist/mini-vue.esm-bundler.js"; 2 | export default { 3 | name: "Child", 4 | setup(props, context) {}, 5 | render() { 6 | return h("div", {}, [ 7 | h("div", {}, "child"), 8 | // renderSlot 会返回一个 vnode 9 | // 其本质和 h 是一样的 10 | // 第三个参数给出数据 11 | renderSlot(this.$slots, "default", { 12 | age: 16, 13 | }), 14 | ]); 15 | }, 16 | }; 17 | -------------------------------------------------------------------------------- /packages/vue/example/slotsComponent/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Document 7 | 8 | 9 |
10 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /packages/vue/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | if (process.env.NODE_ENV === 'production') { 4 | // module.exports = require('./dist/mini-vue.cjs.prod.js') 5 | } else { 6 | module.exports = require('./dist/mini-vue.cjs.js') 7 | } 8 | -------------------------------------------------------------------------------- /packages/vue/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mini-vue", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [], 10 | "author": "erkelost", 11 | "license": "ISC", 12 | "dependencies": { 13 | "@relaxed/compiler-core": "workspace:^1.0.0", 14 | "@relaxed/runtime-dom": "workspace:^1.0.0", 15 | "@relaxed/shared": "workspace:^1.0.0" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /packages/vue/src/index.ts: -------------------------------------------------------------------------------- 1 | // 这个文件充当 vue 模块 2 | import * as runtimeDom from "@relaxed/runtime-dom"; 3 | import { registerRuntimeCompiler } from "@relaxed/runtime-dom"; 4 | 5 | import { baseCompile } from "@relaxed/compiler-core"; 6 | 7 | export * from "@relaxed/runtime-dom"; 8 | 9 | 10 | function compileToFunction(template, options = {}) { 11 | const { code } = baseCompile(template, options); 12 | 13 | // 调用 compile 得到的代码在给封装到函数内, 14 | // 这里会依赖 runtimeDom 的一些函数,所以在这里通过参数的形式注入进去 15 | const render = new Function("Vue", code)(runtimeDom); 16 | 17 | return render; 18 | } 19 | 20 | registerRuntimeCompiler(compileToFunction); -------------------------------------------------------------------------------- /packages/vue/tsup.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'tsup' 2 | 3 | export default defineConfig({ 4 | entry: ['./src/index.ts'], 5 | target: 'esnext', 6 | format: ['esm', 'cjs', 'iife'], 7 | splitting: false, 8 | sourcemap: true, 9 | clean: true, 10 | watch: true, 11 | external: ['vue'] 12 | }) 13 | -------------------------------------------------------------------------------- /playground/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /playground/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["Vue.volar"] 3 | } 4 | -------------------------------------------------------------------------------- /playground/README.md: -------------------------------------------------------------------------------- 1 | # Vue 3 + TypeScript + Vite 2 | 3 | This template should help get you started developing with Vue 3 and TypeScript in Vite. The template uses Vue 3 ` 12 | 13 | 14 | -------------------------------------------------------------------------------- /playground/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "playground", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "vue-tsc --noEmit && vite build", 9 | "preview": "vite preview" 10 | }, 11 | "dependencies": { 12 | "vue": "^3.2.37", 13 | "@relaxed/reactivity": "workspace:*" 14 | }, 15 | "devDependencies": { 16 | "@vitejs/plugin-vue": "^3.1.0", 17 | "typescript": "^4.6.4", 18 | "vite": "^3.1.0", 19 | "vue-tsc": "^0.40.4" 20 | } 21 | } -------------------------------------------------------------------------------- /playground/public/vite.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /playground/src/App.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 19 | 20 | 33 | -------------------------------------------------------------------------------- /playground/src/assets/vue.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /playground/src/components/HelloWorld.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 33 | 34 | 39 | -------------------------------------------------------------------------------- /playground/src/main.ts: -------------------------------------------------------------------------------- 1 | import { createApp } from 'vue' 2 | import './style.css' 3 | import App from './App.vue' 4 | 5 | createApp(App).mount('#app') 6 | -------------------------------------------------------------------------------- /playground/src/style.css: -------------------------------------------------------------------------------- 1 | :root { 2 | font-family: Inter, Avenir, Helvetica, Arial, sans-serif; 3 | font-size: 16px; 4 | line-height: 24px; 5 | font-weight: 400; 6 | 7 | color-scheme: light dark; 8 | color: rgba(255, 255, 255, 0.87); 9 | background-color: #242424; 10 | 11 | font-synthesis: none; 12 | text-rendering: optimizeLegibility; 13 | -webkit-font-smoothing: antialiased; 14 | -moz-osx-font-smoothing: grayscale; 15 | -webkit-text-size-adjust: 100%; 16 | } 17 | 18 | a { 19 | font-weight: 500; 20 | color: #646cff; 21 | text-decoration: inherit; 22 | } 23 | a:hover { 24 | color: #535bf2; 25 | } 26 | 27 | body { 28 | margin: 0; 29 | display: flex; 30 | place-items: center; 31 | min-width: 320px; 32 | min-height: 100vh; 33 | } 34 | 35 | h1 { 36 | font-size: 3.2em; 37 | line-height: 1.1; 38 | } 39 | 40 | button { 41 | border-radius: 8px; 42 | border: 1px solid transparent; 43 | padding: 0.6em 1.2em; 44 | font-size: 1em; 45 | font-weight: 500; 46 | font-family: inherit; 47 | background-color: #1a1a1a; 48 | cursor: pointer; 49 | transition: border-color 0.25s; 50 | } 51 | button:hover { 52 | border-color: #646cff; 53 | } 54 | button:focus, 55 | button:focus-visible { 56 | outline: 4px auto -webkit-focus-ring-color; 57 | } 58 | 59 | .card { 60 | padding: 2em; 61 | } 62 | 63 | #app { 64 | max-width: 1280px; 65 | margin: 0 auto; 66 | padding: 2rem; 67 | text-align: center; 68 | } 69 | 70 | @media (prefers-color-scheme: light) { 71 | :root { 72 | color: #213547; 73 | background-color: #ffffff; 74 | } 75 | a:hover { 76 | color: #747bff; 77 | } 78 | button { 79 | background-color: #f9f9f9; 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /playground/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | declare module '*.vue' { 4 | import type { DefineComponent } from 'vue' 5 | const component: DefineComponent<{}, {}, any> 6 | export default component 7 | } 8 | -------------------------------------------------------------------------------- /playground/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "useDefineForClassFields": true, 5 | "module": "ESNext", 6 | "moduleResolution": "Node", 7 | "strict": true, 8 | "jsx": "preserve", 9 | "sourceMap": true, 10 | "resolveJsonModule": true, 11 | "isolatedModules": true, 12 | "esModuleInterop": true, 13 | "lib": ["ESNext", "DOM"], 14 | "skipLibCheck": true 15 | }, 16 | "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"], 17 | "references": [{ "path": "./tsconfig.node.json" }] 18 | } 19 | -------------------------------------------------------------------------------- /playground/tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "module": "ESNext", 5 | "moduleResolution": "Node", 6 | "allowSyntheticDefaultImports": true 7 | }, 8 | "include": ["vite.config.ts"] 9 | } 10 | -------------------------------------------------------------------------------- /playground/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "vite"; 2 | import vue from "@vitejs/plugin-vue"; 3 | 4 | // https://vitejs.dev/config/ 5 | export default defineConfig({ 6 | plugins: [vue()], 7 | server: { 8 | port: 3000, 9 | }, 10 | }); 11 | -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | - "packages/*" 3 | - playground 4 | - docs 5 | - example 6 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "strict": true, 5 | "rootDir": ".", 6 | "moduleResolution": "node", 7 | "esModuleInterop": true, 8 | "target": "es2016", 9 | "module": "esnext", 10 | "noImplicitAny": false, 11 | "removeComments": true, 12 | "preserveConstEnums": true, 13 | "sourceMap": true, 14 | "downlevelIteration": true, 15 | "lib": ["esnext", "DOM"], 16 | "types": ["jest"], 17 | "paths": { 18 | "@relaxed/*": ["packages/*/src"] 19 | } 20 | }, 21 | "include": ["packages/*/src", "packages/*/__tests__"] 22 | } 23 | -------------------------------------------------------------------------------- /turbo.json: -------------------------------------------------------------------------------- 1 | { 2 | "pipeline": { 3 | "dev": {}, 4 | "build": {} 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /x-mind/compiler-core.xmind: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ErKeLost/extreme-vue/849882815db65d19d16f40bc70b014bec9922829/x-mind/compiler-core.xmind -------------------------------------------------------------------------------- /x-mind/patchElement 的流程.xmind: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ErKeLost/extreme-vue/849882815db65d19d16f40bc70b014bec9922829/x-mind/patchElement 的流程.xmind -------------------------------------------------------------------------------- /x-mind/reactivity.xmind: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ErKeLost/extreme-vue/849882815db65d19d16f40bc70b014bec9922829/x-mind/reactivity.xmind -------------------------------------------------------------------------------- /x-mind/slot 流程.xmind: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ErKeLost/extreme-vue/849882815db65d19d16f40bc70b014bec9922829/x-mind/slot 流程.xmind -------------------------------------------------------------------------------- /x-mind/update 流程.xmind: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ErKeLost/extreme-vue/849882815db65d19d16f40bc70b014bec9922829/x-mind/update 流程.xmind -------------------------------------------------------------------------------- /x-mind/完整的流程调用图.xmind: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ErKeLost/extreme-vue/849882815db65d19d16f40bc70b014bec9922829/x-mind/完整的流程调用图.xmind --------------------------------------------------------------------------------