├── .DS_Store ├── .gitignore ├── .vscode └── launch.json ├── lerna.json ├── package.json ├── packages ├── mini-express │ ├── .github │ │ └── workflows │ │ │ ├── main.yml │ │ │ └── size.yml │ ├── .gitignore │ ├── LICENSE │ ├── README.md │ ├── index.ts │ ├── package.json │ ├── src │ │ ├── application.ts │ │ ├── compose.ts │ │ ├── middleware │ │ │ └── query.ts │ │ ├── router.ts │ │ └── type.ts │ ├── test │ │ ├── complicated.js │ │ ├── compose.js │ │ ├── io-test │ │ │ ├── multiple1.js │ │ │ ├── readme.md │ │ │ ├── server.js │ │ │ ├── server2.js │ │ │ └── util.js │ │ ├── middleware │ │ │ ├── logger.js │ │ │ ├── response-time.js │ │ │ ├── test-middleware.js │ │ │ └── upcase-transform.js │ │ ├── module.js │ │ ├── router.js │ │ └── util.js │ ├── tsconfig.json │ └── yarn.lock ├── mini-inversify │ ├── index.ts │ ├── package.json │ ├── readme.md │ ├── src │ │ ├── inversify.ts │ │ └── reflect-metadata.ts │ ├── test │ │ ├── entities.ts │ │ ├── index.ts │ │ ├── interfaces.ts │ │ ├── inversify.config.ts │ │ ├── test2.ts │ │ ├── types.ts │ │ └── util.ts │ └── tsconfig.json ├── mini-koa │ ├── .gitignore │ ├── LICENSE │ ├── README.md │ ├── package.json │ ├── src │ │ ├── application.ts │ │ ├── compose.ts │ │ ├── context.ts │ │ ├── helper │ │ │ ├── delagate.ts │ │ │ └── send.ts │ │ ├── index.ts │ │ ├── middleware │ │ │ └── query.ts │ │ ├── public-middleware │ │ │ ├── logger.ts │ │ │ ├── response-time.ts │ │ │ └── static.ts │ │ ├── router.ts │ │ ├── type.ts │ │ └── util.ts │ ├── test │ │ └── router.ts │ ├── tsconfig.json │ └── yarn.lock ├── mini-mp-framework │ ├── .gitignore │ ├── demo │ │ ├── .gitignore │ │ ├── favicon.svg │ │ ├── index.html │ │ ├── package.json │ │ ├── src │ │ │ ├── main.ts │ │ │ ├── style.css │ │ │ └── vite-env.d.ts │ │ └── tsconfig.json │ ├── favicon.svg │ ├── package.json │ ├── readme.md │ ├── src │ │ ├── index.ts │ │ ├── main.ts │ │ ├── parent.ts │ │ ├── type.ts │ │ └── worker.ts │ └── tsconfig.json ├── mini-mq │ ├── example │ │ ├── index.ts │ │ └── readme.md │ ├── nodemon.json │ ├── package.json │ ├── readme.md │ └── tsconfig.json ├── mini-os │ ├── .cargo │ │ └── config │ ├── .gitignore │ ├── Cargo.lock │ ├── Cargo.toml │ ├── readme.md │ └── src │ │ ├── lang_items.rs │ │ └── main.rs ├── mini-react │ ├── demo │ │ ├── README.md │ │ ├── index.html │ │ ├── package.json │ │ ├── src │ │ │ ├── demo.tsx │ │ │ ├── main.tsx │ │ │ ├── use-effect.tsx │ │ │ ├── use-memo.tsx │ │ │ ├── use-reducer.tsx │ │ │ ├── use-ref.tsx │ │ │ └── use-state.tsx │ │ └── vite.config.js │ ├── package.json │ ├── ppt.md │ ├── readme.md │ ├── src │ │ ├── dom.ts │ │ ├── h.ts │ │ ├── hooks.ts │ │ ├── index.ts │ │ ├── render.ts │ │ ├── type.ts │ │ └── util.ts │ ├── todos.md │ └── tsconfig.json ├── mini-rpc │ ├── .gitignore │ ├── index.ts │ ├── package.json │ ├── ppt.md │ ├── readme.md │ ├── src │ │ ├── client.ts │ │ ├── registry.ts │ │ ├── server.ts │ │ └── util.ts │ ├── test │ │ └── sofa-rpc │ │ │ ├── client.ts │ │ │ ├── data.ts │ │ │ ├── proto │ │ │ ├── employee.proto │ │ │ └── math.proto │ │ │ ├── readme.md │ │ │ └── server.ts │ ├── todo.md │ └── tsconfig.json ├── mini-svelte │ ├── .gitignore │ ├── demo │ │ ├── .gitignore │ │ ├── README.md │ │ ├── index.html │ │ ├── package.json │ │ ├── public │ │ │ └── favicon.ico │ │ ├── src │ │ │ ├── App.svelte │ │ │ ├── assets │ │ │ │ └── svelte.png │ │ │ ├── lib │ │ │ │ ├── Counter.svelte │ │ │ │ └── Prop.svelte │ │ │ ├── main.ts │ │ │ └── vite-env.d.ts │ │ ├── svelte.config.js │ │ ├── tsconfig.json │ │ └── vite.config.js │ ├── package.json │ ├── readme.md │ ├── src │ │ ├── index.ts │ │ ├── internal │ │ │ ├── index.ts │ │ │ └── scheduler.ts │ │ ├── parse │ │ │ ├── compile-script │ │ │ │ ├── Tree.ts │ │ │ │ ├── compile.ts │ │ │ │ ├── context.ts │ │ │ │ ├── environment │ │ │ │ │ └── Environment.ts │ │ │ │ ├── expression.ts │ │ │ │ ├── index.ts │ │ │ │ ├── statements.ts │ │ │ │ ├── type.ts │ │ │ │ └── variableDeclaration.ts │ │ │ ├── compile-template │ │ │ │ ├── codeGen.ts │ │ │ │ ├── context.ts │ │ │ │ ├── index.ts │ │ │ │ ├── parseTemplate.ts │ │ │ │ └── type.ts │ │ │ ├── compileScript.ts │ │ │ ├── compileStyle.ts │ │ │ ├── compileTemplate.ts │ │ │ ├── index.ts │ │ │ ├── parseMain.ts │ │ │ ├── type.ts │ │ │ └── util.ts │ │ └── vite-plugin │ │ │ ├── index.ts │ │ │ ├── readme.md │ │ │ └── transformMain.ts │ ├── test │ │ ├── index.ts │ │ └── result.js │ ├── thinking.md │ └── tsconfig.json ├── mini-vite │ ├── .github │ │ └── workflows │ │ │ ├── main.yml │ │ │ └── size.yml │ ├── .gitignore │ ├── LICENSE │ ├── README.md │ ├── bin │ │ └── mini-vite.js │ ├── nodemon.json │ ├── package.json │ ├── ppt-vite.md │ ├── src │ │ ├── client │ │ │ ├── client.ts │ │ │ └── env.ts │ │ ├── constants.ts │ │ ├── context.ts │ │ ├── hmr.ts │ │ ├── index.ts │ │ ├── middleware │ │ │ ├── hmrPing.ts │ │ │ ├── htmlRewrite.ts │ │ │ ├── static.ts │ │ │ └── transform.ts │ │ ├── moduleGraph.ts │ │ ├── optimizer │ │ │ ├── esbuildDepPlugin.ts │ │ │ ├── index.ts │ │ │ └── scan.ts │ │ ├── plugin.ts │ │ ├── pluginContainer.ts │ │ ├── plugins │ │ │ ├── css.ts │ │ │ ├── esbuild.ts │ │ │ ├── html.ts │ │ │ ├── importAnalysis.ts │ │ │ ├── index.ts │ │ │ ├── plugin-vue │ │ │ │ ├── descriptor.ts │ │ │ │ ├── handleHotUpdate.ts │ │ │ │ ├── index.ts │ │ │ │ └── query.ts │ │ │ └── resolve.ts │ │ ├── send.ts │ │ ├── transformRequest.ts │ │ ├── type │ │ │ └── hmr.d.ts │ │ ├── util.ts │ │ └── ws.ts │ ├── template-vue-ts │ │ ├── .gitignore │ │ ├── README.md │ │ ├── index.html │ │ ├── mini-vite.config.ts │ │ ├── package.json │ │ ├── public │ │ │ └── favicon.ico │ │ ├── src │ │ │ ├── App.vue │ │ │ ├── assets │ │ │ │ └── logo.png │ │ │ ├── components │ │ │ │ └── HelloWorld.vue │ │ │ ├── main.ts │ │ │ ├── mimic-store │ │ │ │ ├── index.ts │ │ │ │ ├── module1.ts │ │ │ │ └── module2.ts │ │ │ ├── router.ts │ │ │ ├── shims-vue.d.ts │ │ │ └── vite-env.d.ts │ │ └── tsconfig.json │ ├── tsconfig.json │ └── yarn.lock ├── mini-vue │ ├── demo-ssr │ │ ├── README.md │ │ ├── index.html │ │ ├── package.json │ │ ├── server.js │ │ ├── src │ │ │ ├── App.jsx │ │ │ ├── Layout.jsx │ │ │ ├── child.jsx │ │ │ ├── entry-client.ts │ │ │ ├── entry-server.ts │ │ │ ├── main.ts │ │ │ └── router.ts │ │ ├── vite.config.js │ │ └── vue-shim.d.ts │ ├── demo │ │ ├── README.md │ │ ├── index.html │ │ ├── package.json │ │ ├── src │ │ │ ├── App.jsx │ │ │ ├── child.jsx │ │ │ └── main.ts │ │ ├── vite.config.js │ │ └── vue-shim.d.ts │ ├── legacy │ │ ├── index.html │ │ └── index.js │ ├── package.json │ ├── pnpm-lock.yaml │ ├── pnpm-workspace.yaml │ ├── readme.md │ ├── src │ │ ├── component.ts │ │ ├── createApp.ts │ │ ├── dep.ts │ │ ├── effect.ts │ │ ├── h.ts │ │ ├── hydration.ts │ │ ├── index.ts │ │ ├── nodeOpts.ts │ │ ├── reactive.ts │ │ ├── ref.ts │ │ ├── scheduler.ts │ │ ├── server-renderer │ │ │ ├── index.ts │ │ │ ├── render.ts │ │ │ ├── renderToStream.ts │ │ │ └── renderToString.ts │ │ ├── type.ts │ │ └── util.ts │ └── tsconfig.json └── mini-webpack │ ├── .github │ └── workflows │ │ ├── main.yml │ │ └── size.yml │ ├── .gitignore │ ├── LICENSE │ ├── README.md │ ├── package.json │ ├── src │ └── index.ts │ ├── test │ ├── examples │ │ ├── index.js │ │ ├── message.js │ │ └── word.js │ ├── fixtures │ │ └── mini │ │ │ ├── index.html │ │ │ ├── src │ │ │ ├── b.js │ │ │ ├── index.js │ │ │ ├── message.js │ │ │ └── word.js │ │ │ └── webpack.config.js │ ├── index.test.ts │ └── test_dist │ │ └── main.js │ ├── tsconfig.json │ ├── webpack-demo │ ├── _dist │ │ ├── 384.main.js │ │ └── main.js │ ├── index.html │ ├── package.json │ ├── src │ │ ├── b.js │ │ ├── index.js │ │ ├── message.js │ │ └── word.js │ └── webpack.config.js │ └── yarn.lock └── readme.md /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wizardpisces/experiment/3e24f6597819cb3918e0ba1c8d443acde70795a6/.DS_Store -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | package-lock.json 2 | node_modules -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | 8 | { 9 | "name": "mini-svelte", 10 | "type": "node", 11 | "request": "launch", 12 | "stopOnEntry": false, 13 | "cwd": "${workspaceFolder}", 14 | "runtimeArgs": [ 15 | "-r", 16 | "ts-node/register" 17 | ], 18 | "args": [ 19 | "${workspaceFolder}/packages/mini-svelte/test/index.ts" 20 | ], 21 | "preLaunchTask": null, 22 | "runtimeExecutable": null, 23 | "env": { 24 | "NODE_ENV": "development", 25 | "TS_NODE_FILES": "true" 26 | }, 27 | "console": "integratedTerminal", 28 | "sourceMaps": true 29 | } 30 | ] 31 | } -------------------------------------------------------------------------------- /lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "packages": [ 3 | "packages/*" 4 | ], 5 | "version": "independent" 6 | } 7 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "root", 3 | "private": true, 4 | "devDependencies": { 5 | "lerna": "^4.0.0", 6 | "ts-node": "^10.4.0", 7 | "typescript": "^4.5.4" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/mini-express/.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: [push] 3 | jobs: 4 | build: 5 | name: Build, lint, and test on Node ${{ matrix.node }} and ${{ matrix.os }} 6 | 7 | runs-on: ${{ matrix.os }} 8 | strategy: 9 | matrix: 10 | node: ['10.x', '12.x', '14.x'] 11 | os: [ubuntu-latest, windows-latest, macOS-latest] 12 | 13 | steps: 14 | - name: Checkout repo 15 | uses: actions/checkout@v2 16 | 17 | - name: Use Node ${{ matrix.node }} 18 | uses: actions/setup-node@v1 19 | with: 20 | node-version: ${{ matrix.node }} 21 | 22 | - name: Install deps and build (with cache) 23 | uses: bahmutov/npm-install@v1 24 | 25 | - name: Lint 26 | run: yarn lint 27 | 28 | - name: Test 29 | run: yarn test --ci --coverage --maxWorkers=2 30 | 31 | - name: Build 32 | run: yarn build 33 | -------------------------------------------------------------------------------- /packages/mini-express/.github/workflows/size.yml: -------------------------------------------------------------------------------- 1 | name: size 2 | on: [pull_request] 3 | jobs: 4 | size: 5 | runs-on: ubuntu-latest 6 | env: 7 | CI_JOB_NUMBER: 1 8 | steps: 9 | - uses: actions/checkout@v1 10 | - uses: andresz1/size-limit-action@v1 11 | with: 12 | github_token: ${{ secrets.GITHUB_TOKEN }} 13 | -------------------------------------------------------------------------------- /packages/mini-express/.gitignore: -------------------------------------------------------------------------------- 1 | *.log 2 | .DS_Store 3 | node_modules 4 | dist 5 | -------------------------------------------------------------------------------- /packages/mini-express/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 刘泽 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /packages/mini-express/README.md: -------------------------------------------------------------------------------- 1 | # Introduction 2 | mini implementation of [express](https://github.com/expressjs/express) 3 | ## How to test 4 | 5 | ``` 6 | npm install 7 | 8 | ts-node test/complicated.js 9 | ``` -------------------------------------------------------------------------------- /packages/mini-express/index.ts: -------------------------------------------------------------------------------- 1 | import App from './src/application' 2 | import compose from './src/compose' 3 | export { 4 | App, 5 | compose 6 | } 7 | 8 | export default App -------------------------------------------------------------------------------- /packages/mini-express/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.1.0", 3 | "license": "MIT", 4 | "main": "dist/index.js", 5 | "typings": "dist/index.d.ts", 6 | "files": [ 7 | "dist", 8 | "src" 9 | ], 10 | "engines": { 11 | "node": ">=10" 12 | }, 13 | "scripts": { 14 | "start": "tsdx watch", 15 | "build": "tsdx build", 16 | "test": "tsdx test", 17 | "lint": "tsdx lint", 18 | "prepare": "tsdx build", 19 | "test:router": "ts-node test/router.js", 20 | "size": "size-limit", 21 | "analyze": "size-limit --why" 22 | }, 23 | "peerDependencies": {}, 24 | "husky": { 25 | "hooks": { 26 | "pre-commit": "tsdx lint" 27 | } 28 | }, 29 | "prettier": { 30 | "printWidth": 80, 31 | "semi": true, 32 | "singleQuote": true, 33 | "trailingComma": "es5" 34 | }, 35 | "name": "mini-express", 36 | "author": "刘泽", 37 | "module": "dist/mini-express.esm.js", 38 | "size-limit": [ 39 | { 40 | "path": "dist/mini-express.cjs.production.min.js", 41 | "limit": "10 KB" 42 | }, 43 | { 44 | "path": "dist/mini-express.esm.js", 45 | "limit": "10 KB" 46 | } 47 | ], 48 | "devDependencies": { 49 | "@size-limit/preset-small-lib": "^4.12.0", 50 | "@types/node": "^15.12.2", 51 | "husky": "^6.0.0", 52 | "on-headers": "^1.0.2", 53 | "size-limit": "^4.12.0", 54 | "tsdx": "^0.14.1", 55 | "tslib": "^2.2.0", 56 | "typescript": "^4.3.2" 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /packages/mini-express/src/application.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright(c) 2021 wizardpisces 3 | */ 4 | import { Handle } from './type' 5 | import query from './middleware/query' 6 | const http = require('http'); 7 | import Router from './router' 8 | export default class App { 9 | _router: Router 10 | constructor() { 11 | this._router = new Router() 12 | this.use(query()) 13 | } 14 | 15 | handle(req: any, res: any) { 16 | this._router.handle(req, res) 17 | } 18 | 19 | use(url: string | Handle, handle?: Handle) { 20 | let path = '/' 21 | if (typeof url === 'function') { 22 | handle = url; 23 | url = path 24 | } 25 | 26 | this._router.use(url, handle as Handle) 27 | } 28 | 29 | listen(port: number, cb: Function) { 30 | let server = http.createServer(this.handle.bind(this)) 31 | server.listen(port, cb); 32 | 33 | /** 34 | * Todos: should be modified 35 | */ 36 | // this.stack.push(new Layer({ 37 | // handle: (req, res) => { 38 | // res.status = 404 39 | // res.end('not found') 40 | // }, 41 | // url: '/404' 42 | // })) 43 | } 44 | } -------------------------------------------------------------------------------- /packages/mini-express/src/compose.ts: -------------------------------------------------------------------------------- 1 | import { Handle ,Next } from './type' 2 | 3 | export default function compose(handlers: Handle[]) { 4 | let len = handlers.length, 5 | i = 0; 6 | return (req: T, res: U, done: Next) => { 7 | 8 | function dispatch(i: number, err?: any):any { 9 | if (i === len) return done(err) 10 | handlers[i](req, res, (err) => { 11 | dispatch(i + 1, err); 12 | }) 13 | } 14 | 15 | dispatch(i) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /packages/mini-express/src/middleware/query.ts: -------------------------------------------------------------------------------- 1 | import { Handle } from '../type' 2 | 3 | type Query = { [key: string]: string } 4 | 5 | export function parseUrl(url: string): { path: string; query: Query } { 6 | let arr = url.split('?'), 7 | path = arr[0], 8 | query: Query = {}; 9 | 10 | if (arr[1]) { 11 | query = arr[1].split('&').reduce((res, cur) => { 12 | let [name, val] = cur.split('=') 13 | res[name] = val 14 | return res 15 | }, query); 16 | } 17 | 18 | return { 19 | path, 20 | query 21 | } 22 | } 23 | 24 | export default function query(): Handle { 25 | return (req, _, next) => { 26 | let { query, path } = parseUrl(req.url) 27 | req.path = path; 28 | req.query = query 29 | 30 | next() 31 | } 32 | } -------------------------------------------------------------------------------- /packages/mini-express/src/router.ts: -------------------------------------------------------------------------------- 1 | import { Handle, LayerOptions } from './type' 2 | import { parseUrl } from './middleware/query' 3 | class Layer { 4 | handle: Handle 5 | path: string 6 | asterisk: boolean // * 通配符 7 | all_slash: boolean 8 | constructor(options: LayerOptions) { 9 | this.handle = options.handle 10 | this.path = options.path 11 | this.asterisk = this.path === '*' 12 | this.all_slash = this.path === '/' 13 | } 14 | 15 | match(reqPath: string) { 16 | let { path } = parseUrl(reqPath) 17 | // console.log('----', path, this.path, '--') 18 | if (this.asterisk) { 19 | return true 20 | } 21 | 22 | if (this.all_slash) { 23 | return true 24 | } 25 | 26 | return path === this.path 27 | } 28 | } 29 | 30 | export default class Router { 31 | stack: Layer[] 32 | 33 | constructor() { 34 | this.stack = [] 35 | } 36 | 37 | use(path: string, handle: Handle) { 38 | this.stack.push(new Layer({ 39 | handle, 40 | path 41 | })) 42 | } 43 | 44 | handle(req: any, res: any) { 45 | 46 | let middleware: Layer[] = this.stack.filter((layer) => { 47 | return layer.match(req.url) 48 | }); 49 | 50 | let len = middleware.length, i = 0; 51 | 52 | const next = (i: number, err: any) => { 53 | if (err) { 54 | throw new Error(err) 55 | } 56 | if (i >= len) return 57 | 58 | middleware[i].handle(req, res, (err: any) => next(i + 1, err)) 59 | } 60 | 61 | next(i, null) 62 | } 63 | 64 | } 65 | -------------------------------------------------------------------------------- /packages/mini-express/src/type.ts: -------------------------------------------------------------------------------- 1 | 2 | export type Next = (err?: Error | null) => T; 3 | export type RequestHandler = ( 4 | req: T, 5 | res: U, 6 | next: Next 7 | ) => V; 8 | 9 | /** 10 | * Todos 11 | * replace any with actual req/res.next type 12 | */ 13 | export type Handle = RequestHandler 14 | 15 | export type LayerOptions = { 16 | handle: Handle; 17 | path: string 18 | } -------------------------------------------------------------------------------- /packages/mini-express/test/complicated.js: -------------------------------------------------------------------------------- 1 | const { 2 | App, 3 | compose 4 | } = require('../index'); 5 | 6 | const { 7 | RenderStream 8 | } = require('./util') 9 | 10 | const upcaseTransform = require('./middleware/upcase-transform') 11 | const logger = require('./middleware/logger') 12 | const testM = require('./middleware/test-middleware') 13 | 14 | let app = new App(), 15 | port = 8080 16 | 17 | app.use((req, res, next) => { 18 | req.str = '

this is a test

, should also be in uppercase' 19 | next() 20 | }) 21 | 22 | app.use(logger()) 23 | app.use(upcaseTransform()) 24 | app.use(compose([ 25 | testM(1), 26 | testM(2) 27 | ])) 28 | 29 | app.use('/', (req, res) => { 30 | new RenderStream((write, end) => { 31 | write('

this is a test

, should also be in uppercase
'); 32 | end('this is the end') 33 | }).pipe(res) 34 | // res.end(req.str) 35 | }) 36 | 37 | app.listen(port, () => { 38 | console.log(`server started at http://localhost:${port}`) 39 | }) -------------------------------------------------------------------------------- /packages/mini-express/test/compose.js: -------------------------------------------------------------------------------- 1 | const { 2 | App, 3 | compose 4 | } = require('../index'); 5 | 6 | const testM = require('./middleware/test-middleware') 7 | 8 | let app = new App(), 9 | port = 8080 10 | 11 | app.use(compose([ 12 | testM(1), 13 | testM(2) 14 | ])) 15 | 16 | app.use('/', (req, res) => { 17 | res.end('[tiny-server]: compose test is ok') 18 | }) 19 | 20 | app.listen(port, () => { 21 | console.log(`server started at http://localhost:${port}`) 22 | }) -------------------------------------------------------------------------------- /packages/mini-express/test/io-test/multiple1.js: -------------------------------------------------------------------------------- 1 | const request = require('./util') 2 | 3 | let req = 0 4 | async function asyncCall(req) { 5 | 6 | let result = await request({ 7 | url: `http://localhost:8080/multiple?id=${req}` 8 | }) 9 | // console.log(`${req} : ${num}`) 10 | } 11 | 12 | function start() { 13 | while (++req < 10) { 14 | asyncCall(req) 15 | } 16 | } 17 | 18 | start() -------------------------------------------------------------------------------- /packages/mini-express/test/io-test/readme.md: -------------------------------------------------------------------------------- 1 | ## How to run 2 | 3 | ``` 4 | ts-node server.js 5 | node multiple1.js 6 | ``` 7 | 8 | ### Possible result 9 | 10 | multiple1.js 11 | ``` 12 | BODY: [tiny-server]: 2: 9 13 | BODY: [tiny-server]: 4: 9 14 | BODY: [tiny-server]: 6: 9 15 | BODY: [tiny-server]: 8: 9 16 | BODY: [tiny-server]: 9: 9 17 | BODY: [tiny-server]: 1: 9 18 | BODY: [tiny-server]: 3: 9 19 | BODY: [tiny-server]: 5: 9 20 | BODY: [tiny-server]: 7: 9 21 | ``` 22 | 23 | server.js response-time logger 24 | 25 | ``` 26 | [response-time]: 2 - 1005.665015 27 | [response-time]: 4 - 1006.148824 28 | [response-time]: 6 - 1006.462347 29 | [response-time]: 8 - 1006.797429 30 | [response-time]: 9 - 1007.111737 31 | [response-time]: 1 - 2005.235747 32 | [response-time]: 3 - 2005.715777 33 | [response-time]: 5 - 2006.060794 34 | [response-time]: 7 - 2006.451052 35 | ``` 36 | 37 | ### 结论 38 | 39 | node 40 | 同步阻塞(cpu-bound运算) 41 | 异步非阻塞(网络请求/数据库操作等) 42 | 43 | 表现形式: 请求之间会对全局的变量互篡改 44 | 45 | 对于并发的处理大致如下(我称之为瀑布流模型): 46 | 47 | ``` 48 | req1 |- cpu -|--- request ---|- cpu -| 49 | req2 |- cpu -|--- request ---|- cpu -| 50 | req3 |- cpu -|---request---| |- cpu -| 51 | ``` 52 | 53 | ## Reference 54 | 55 | * http://www.ruanyifeng.com/blog/2014/10/event-loop.html 56 | * https://cnodejs.org/topic/5c8b0a4a7ce0df3732428254 -------------------------------------------------------------------------------- /packages/mini-express/test/io-test/server.js: -------------------------------------------------------------------------------- 1 | const request = require('./util') 2 | 3 | const { 4 | App 5 | } = require('../../index'); 6 | const logger = require('../middleware/logger') 7 | const responseTime = require('../middleware/response-time') 8 | 9 | let app = new App(), 10 | port = 8080, 11 | duration = 1000, 12 | globalId = 0; 13 | 14 | // app.use(logger()) 15 | 16 | app.use(responseTime()) 17 | 18 | app.use('/multiple', (req, res, next) => { 19 | 20 | globalId = req.query.id; 21 | duration = duration === 1000 ? 2000 : 1000 22 | setTimeout(() => { 23 | let o = { 24 | message: `[tiny-server]: ${req.query.id}: ${globalId}` 25 | } 26 | res.writeHead(200); // 触发 on-headers 保证 response-time middleware 的时间捕获正常 27 | res.end(JSON.stringify(o)) 28 | // request({ 29 | // hostname: 'localhost', 30 | // port: 8081, 31 | // method: 'get', 32 | // path: `/async?id=${req.query.id}` 33 | // }).then(result => { 34 | // res.writeHead(200) 35 | // res.end(`[tiny-server]: ${result} ${req.query.id}: ${globalId}`) 36 | // }) 37 | }, duration) 38 | }) 39 | 40 | app.use('*', (req, res) => { 41 | res.end('[tiny-server]: default router hit') 42 | }) 43 | 44 | app.listen(port, () => { 45 | console.log(`server started at http://localhost:${port}`) 46 | }) -------------------------------------------------------------------------------- /packages/mini-express/test/io-test/server2.js: -------------------------------------------------------------------------------- 1 | 2 | const { 3 | App 4 | } = require('../../index'); 5 | const logger = require('../middleware/logger') 6 | 7 | let app = new App(), 8 | port = 8081, 9 | duration = 1000 10 | 11 | // app.use(logger()) 12 | 13 | app.use('/async', (req, res, next) => { 14 | setTimeout(() => { 15 | duration = duration === 1000 ? 2000 : 2000 16 | res.end('[tiny-server2] : Response string') 17 | }, duration) 18 | }) 19 | 20 | app.use('*', (req, res) => { 21 | res.end('[tiny-server2]: default router hit') 22 | }) 23 | 24 | app.listen(port, () => { 25 | console.log(`server started at http://localhost:${port}`) 26 | }) -------------------------------------------------------------------------------- /packages/mini-express/test/io-test/util.js: -------------------------------------------------------------------------------- 1 | const http = require('http') 2 | 3 | module.exports = function request(options) { 4 | return new Promise((resolve, _) => { 5 | let result = '' 6 | let req = http.get(options.url, (res) => { 7 | res.setEncoding('utf8'); 8 | res.on('data', (chunk) => { 9 | result += chunk 10 | }); 11 | res.on('end', () => { 12 | console.log(`BODY: ${JSON.stringify(result)}`); 13 | resolve(result) 14 | }); 15 | }) 16 | req.on('error', (e) => { 17 | console.error(`problem with request: ${e.message}`); 18 | }); 19 | }) 20 | } -------------------------------------------------------------------------------- /packages/mini-express/test/middleware/logger.js: -------------------------------------------------------------------------------- 1 | module.exports = function logger() { 2 | return (req, res, next) => { 3 | console.log('[tiny-server logger]: ', req.url, req.path, req.query); 4 | next() 5 | } 6 | } -------------------------------------------------------------------------------- /packages/mini-express/test/middleware/response-time.js: -------------------------------------------------------------------------------- 1 | let onHeaders = require('on-headers') 2 | module.exports = function responseTime() { 3 | 4 | return (req, res, next) => { 5 | var startAt = process.hrtime() 6 | next() 7 | onHeaders(res,()=>{ 8 | var delta = process.hrtime(startAt), 9 | // Format to high resolution time with nano time to milliseconds 10 | time = delta[0] * 1000 + delta[1] / 1000000; 11 | console.log(`[response-time]: ${req.query.id} - ${time}`) 12 | setHeader(res, req, time) 13 | }) 14 | } 15 | } 16 | 17 | function setHeader(res, req, time) { 18 | res.setHeader('X-Response-Time', time + 'ms') 19 | } -------------------------------------------------------------------------------- /packages/mini-express/test/middleware/test-middleware.js: -------------------------------------------------------------------------------- 1 | module.exports = function test(index) { 2 | return (req, res, next) => { 3 | console.log('[test middleware index]: ', index); 4 | next() 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /packages/mini-express/test/middleware/upcase-transform.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 主要测试 stream 的劫持 3 | */ 4 | const Stream = require('stream'); 5 | class UpCaseTransformStream extends Stream.Transform { 6 | constructor() { 7 | super() 8 | } 9 | 10 | _transform(chunk, encoding, callback) { 11 | this.push(chunk.toString().toUpperCase()); 12 | callback(); 13 | } 14 | 15 | // _flush(done) { 16 | // this.emit('beforeEnd') 17 | // done() 18 | // } 19 | } 20 | 21 | module.exports = function upcaseTransform() { 22 | 23 | function toBuffer(chunk, encoding) { 24 | return !Buffer.isBuffer(chunk) ? 25 | Buffer.from(chunk, encoding) : 26 | chunk 27 | } 28 | 29 | return (req, res, next) => { 30 | let stream = new UpCaseTransformStream(), 31 | _end = res.end, 32 | _on = res.on, 33 | _write = res.write; 34 | 35 | res.on = function on(type, listener) { 36 | if (type !== 'drain') { 37 | return _on.call(this, type, listener) 38 | } 39 | return stream.on(type, listener) 40 | } 41 | 42 | res.end = function (chunk) { 43 | return chunk ? stream.end(toBuffer(chunk)) : stream.end() 44 | } 45 | 46 | res.write = function write(chunk, encoding) { 47 | return stream.write(toBuffer(chunk, encoding)) 48 | } 49 | 50 | stream.on('data', function onStreamData(chunk) { 51 | // 处理背压问题 52 | if (_write.call(res, chunk) === false) { 53 | stream.pause() 54 | } 55 | }) 56 | 57 | stream.on('end', () => { 58 | _end.call(res) 59 | }) 60 | 61 | // 恢复由于背压问题暂停的可读流 62 | _on.call(res, 'drain', function onResponseDrain() { 63 | stream.resume() 64 | }) 65 | 66 | next() 67 | } 68 | } 69 | 70 | exports.UpCaseTransformStream = UpCaseTransformStream -------------------------------------------------------------------------------- /packages/mini-express/test/module.js: -------------------------------------------------------------------------------- 1 | const { 2 | App 3 | } = require('../index'); 4 | const logger = require('./middleware/logger') 5 | 6 | const { 7 | info 8 | } = require('./util') 9 | 10 | let app = new App(), 11 | port = 8080 12 | 13 | app.use(logger()) 14 | app.use('/home', (req, res) => { 15 | res.end(info('home route is ok')) 16 | }) 17 | 18 | app.use('/', (req, res) => { 19 | res.end(info('route is ok')) 20 | }) 21 | 22 | app.listen(port, () => { 23 | console.log(`server started at http://localhost:${port}`) 24 | }) -------------------------------------------------------------------------------- /packages/mini-express/test/router.js: -------------------------------------------------------------------------------- 1 | const App = require('../index').default; 2 | const logger = require('./middleware/logger') 3 | 4 | const { 5 | info 6 | } = require('./util') 7 | 8 | let app = new App(), 9 | port = 8080 10 | 11 | app.use(logger()) 12 | app.use('/home', (req, res) => { 13 | res.end(info('home route is ok')) 14 | }) 15 | 16 | app.use('/', (req, res,next) => { 17 | req.test = 'this is a slash path' 18 | next() 19 | // res.end(info('route is ok')) 20 | }) 21 | 22 | app.use('*', (req, res) => { 23 | res.end(info('this is default route')) 24 | }) 25 | 26 | app.listen(port, () => { 27 | console.log(`server started at http://localhost:${port}`) 28 | }) -------------------------------------------------------------------------------- /packages/mini-express/test/util.js: -------------------------------------------------------------------------------- 1 | const Stream = require('stream') 2 | class RenderStream extends Stream.Readable { 3 | constructor(render) { 4 | super(); 5 | this.done = false 6 | this.render = render; 7 | this.write = (chunk) => { 8 | this.push(chunk) 9 | } 10 | this.end = (chunk) => { 11 | this.emit('beforeEnd') 12 | this.done = true; 13 | this.push(chunk) 14 | } 15 | } 16 | 17 | tryRender() { 18 | this.render(this.write, this.end) 19 | } 20 | 21 | _read(size) { 22 | if (this.done) { 23 | this.push(null) 24 | return 25 | } 26 | this.tryRender() 27 | } 28 | } 29 | 30 | function info(str){ 31 | return `[mini-express]: ${str}` 32 | } 33 | 34 | function clone(obj) { 35 | let copy; 36 | 37 | // Handle the 3 simple types, and null or undefined 38 | if (null == obj || "object" != typeof obj) return obj; 39 | 40 | // Handle Date 41 | if (obj instanceof Date) { 42 | copy = new Date(); 43 | copy.setTime(obj.getTime()); 44 | return copy; 45 | } 46 | 47 | // Handle Array 48 | if (obj instanceof Array) { 49 | copy = []; 50 | for (let i = 0, len = obj.length; i < len; i++) { 51 | copy[i] = clone(obj[i]); 52 | } 53 | return copy; 54 | } 55 | 56 | // Handle Object 57 | if (obj instanceof Object) { 58 | copy = {}; 59 | for (let attr in obj) { 60 | if (obj.hasOwnProperty(attr)) copy[attr] = clone(obj[attr]); 61 | } 62 | return copy; 63 | } 64 | 65 | throw new Error("Unable to copy obj! Its type isn't supported."); 66 | } 67 | 68 | module.exports = { 69 | RenderStream, 70 | info, 71 | clone 72 | } -------------------------------------------------------------------------------- /packages/mini-express/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | // see https://www.typescriptlang.org/tsconfig to better understand tsconfigs 3 | "include": ["src", "types"], 4 | "compilerOptions": { 5 | "module": "commonjs", 6 | "lib": ["dom", "esnext"], 7 | "importHelpers": true, 8 | // output .d.ts declaration files for consumers 9 | "declaration": true, 10 | // output .js.map sourcemap files for consumers 11 | "sourceMap": true, 12 | // match output dir to input dir. e.g. dist/index instead of dist/src/index 13 | "rootDir": "./src", 14 | // stricter type-checking for stronger correctness. Recommended by TS 15 | "strict": false, 16 | // linter checks for common issues 17 | "noImplicitReturns": true, 18 | "noFallthroughCasesInSwitch": true, 19 | // noUnused* overlap with @typescript-eslint/no-unused-vars, can disable if duplicative 20 | "noUnusedLocals": true, 21 | "noUnusedParameters": true, 22 | // use Node's module resolution algorithm, instead of the legacy TS one 23 | "moduleResolution": "node", 24 | // transpile JSX to React.createElement 25 | "jsx": "react", 26 | // interop between ESM and CJS modules. Recommended by TS 27 | "esModuleInterop": true, 28 | // significant perf increase by skipping checking .d.ts files, particularly those in node_modules. Recommended by TS 29 | "skipLibCheck": true, 30 | // error out if import and file system have a casing mismatch. Recommended by TS 31 | "forceConsistentCasingInFileNames": true, 32 | // `tsdx build` ignores this option, but it is commonly used when type-checking separately with `tsc` 33 | "noEmit": true, 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /packages/mini-inversify/index.ts: -------------------------------------------------------------------------------- 1 | export * from './src/inversify' -------------------------------------------------------------------------------- /packages/mini-inversify/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mini-inversify", 3 | "version": "1.0.0", 4 | "description": "IOC (Inversion of control),DI (dependency injection)", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "ts-node test/index.ts", 8 | "test2": "ts-node test/test2.ts" 9 | }, 10 | "author": "刘泽", 11 | "license": "ISC", 12 | "devDependencies": { 13 | "typescript": "^4.3.5" 14 | }, 15 | "dependencies": { 16 | "reflect-metadata": "^0.1.13" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /packages/mini-inversify/readme.md: -------------------------------------------------------------------------------- 1 | ## Introduction 2 | 3 | mini implementation of [inversifyjs](https://github.com/inversify/InversifyJS) 4 | 5 | ## How to test 6 | 7 | ``` 8 | npm install 9 | npm run test 10 | ``` 11 | 12 | ## 控制反转 13 | 14 | IOC (Inversion of control),DI (dependency injection) 15 | 16 | ioc是目的,di是手段。ioc是指让生成类的方式由传统方式(new)反过来,程序员不调用new,需要类的时候由框架注入(di) 17 | 18 | 相比于类继承的方式,控制反转解耦了父类和子类的联系。 19 | ## 框架 20 | * nestjs 21 | * midway 22 | 23 | * inversifyJS 24 | 25 | * java (spring) 26 | * Angular 27 | * Vue (依赖注入 provide/inject) 28 | 29 | ## reference 30 | 31 | * [Metadata Proposal - ECMAScript](https://rbuckton.github.io/reflect-metadata/) 32 | * [inversifyjs](https://github.com/inversify/InversifyJS) 33 | * [midway](https://github.com/midwayjs/midway) 34 | * https://learnku.com/laravel/t/3845/the-two-picture-lets-you-understand-ioc-inversion-of-control 35 | * [【Nodejs】理解Nestjs的IoC与DI](https://www.jianshu.com/p/d5fab2eed2b0) 36 | * [如何基于ts实现控制反转](https://zhuanlan.zhihu.com/p/311184005) 37 | -------------------------------------------------------------------------------- /packages/mini-inversify/test/entities.ts: -------------------------------------------------------------------------------- 1 | 2 | import { injectable, inject } from "../index"; 3 | import { Weapon, ThrowableWeapon, Warrior, Run} from "./interfaces"; 4 | import { TYPES } from "./types"; 5 | 6 | // 武士刀 7 | @injectable() 8 | class Katana implements Weapon { 9 | public hit() { 10 | return "hit!"; 11 | } 12 | } 13 | 14 | // 手里剑 15 | @injectable() 16 | class Shuriken implements ThrowableWeapon { 17 | public throw() { 18 | return "throw!"; 19 | } 20 | } 21 | @injectable() 22 | class FastRun implements Run { 23 | public runaway() { 24 | return "run fast!"; 25 | } 26 | } 27 | 28 | // @injectable() 29 | // class Ninja implements Warrior { 30 | 31 | // private _katana: Weapon; 32 | // private _shuriken: ThrowableWeapon; 33 | 34 | // public constructor( 35 | // @inject(TYPES.Weapon) katana: Weapon, 36 | // @inject(TYPES.ThrowableWeapon) shuriken: ThrowableWeapon 37 | // ) { 38 | // this._katana = katana; 39 | // this._shuriken = shuriken; 40 | // } 41 | 42 | // public fight() { return this._katana.hit(); } 43 | // public sneak() { return this._shuriken.throw(); } 44 | 45 | // } 46 | 47 | // If you prefer it you can use property injection instead of constructor injection so you don't have to declare the class constructor: 48 | 49 | @injectable() 50 | class Ninja implements Warrior { 51 | // @ts-ignore 52 | @inject(TYPES.Weapon) private _katana: Weapon; 53 | // @ts-ignore 54 | @inject(TYPES.ThrowableWeapon) private _shuriken: ThrowableWeapon; 55 | 56 | // @ts-ignore 57 | @inject() private _run: FastRun; 58 | // @ts-ignore 59 | // @inject() _weapon: Weapon; 60 | 61 | public fight() { return this._katana.hit(); } 62 | public sneak() { return this._shuriken.throw(); } 63 | public run() { return this._run.runaway(); } 64 | } 65 | 66 | export { Ninja, Katana, Shuriken, FastRun }; -------------------------------------------------------------------------------- /packages/mini-inversify/test/index.ts: -------------------------------------------------------------------------------- 1 | import { myContainer } from "./inversify.config"; 2 | import { TYPES } from "./types"; 3 | import { Warrior } from "./interfaces"; 4 | import {expect} from './util' 5 | 6 | const ninja = myContainer.get(TYPES.Warrior); 7 | console.log('ninja',ninja,ninja.constructor) 8 | expect(ninja.fight()).eql("hit!"); // true 9 | expect(ninja.sneak()).eql("throw!"); // true 10 | expect(ninja.run()).eql("run fast!"); // true 11 | -------------------------------------------------------------------------------- /packages/mini-inversify/test/interfaces.ts: -------------------------------------------------------------------------------- 1 | export interface Warrior { 2 | fight(): string; 3 | sneak(): string; 4 | run():string; 5 | } 6 | 7 | export interface Weapon { 8 | hit(): string; 9 | } 10 | 11 | export interface ThrowableWeapon { 12 | throw(): string; 13 | } 14 | export interface Run { 15 | runaway(): string; 16 | } -------------------------------------------------------------------------------- /packages/mini-inversify/test/inversify.config.ts: -------------------------------------------------------------------------------- 1 | import { Container } from "../index"; 2 | import { TYPES } from "./types"; 3 | import { Warrior, Weapon, ThrowableWeapon, Run } from "./interfaces"; 4 | import { Ninja, Katana, Shuriken, FastRun } from "./entities"; 5 | 6 | const myContainer = new Container(); 7 | myContainer.bind(TYPES.Warrior).to(Ninja); 8 | myContainer.bind(TYPES.Weapon).to(Katana); 9 | myContainer.bind(TYPES.ThrowableWeapon).to(Shuriken); 10 | 11 | // bind self to self to support @inject() without params 12 | myContainer.bind(FastRun).to(FastRun); 13 | // console.log('myContainer', myContainer.bindMap) 14 | export { myContainer }; 15 | -------------------------------------------------------------------------------- /packages/mini-inversify/test/test2.ts: -------------------------------------------------------------------------------- 1 | import { inject, injectable, Container } from '../index' 2 | import { expect } from './util' 3 | 4 | const container = new Container() 5 | 6 | @injectable() 7 | class PopMusic { 8 | getName() { 9 | return '流行音乐' 10 | } 11 | } 12 | container.bind('request1').to(PopMusic) 13 | 14 | @injectable() 15 | class ClassicalMusic { 16 | getName() { 17 | return '古典音乐' 18 | } 19 | } 20 | container.bind('request2').to(ClassicalMusic) 21 | @injectable() 22 | class Music { 23 | pm: any 24 | cm: any 25 | constructor( 26 | // @ts-ignore 27 | @inject('request1') popMusic: any, 28 | // @ts-ignore 29 | @inject('request2') classicalMusic: any) { 30 | this.pm = popMusic 31 | this.cm = classicalMusic 32 | } 33 | 34 | getName() { 35 | const result = this.pm.getName() + this.cm.getName() 36 | return result 37 | } 38 | } 39 | container.bind('Plan').to(Music) 40 | 41 | const music: any = container.get('Plan') 42 | 43 | expect(music.getName()).eql('流行音乐古典音乐') // 流行音乐古典音乐 -------------------------------------------------------------------------------- /packages/mini-inversify/test/types.ts: -------------------------------------------------------------------------------- 1 | const TYPES = { 2 | Warrior: Symbol.for("Warrior"), 3 | Weapon: Symbol.for("Weapon"), 4 | ThrowableWeapon: Symbol.for("ThrowableWeapon") 5 | }; 6 | 7 | export { TYPES }; -------------------------------------------------------------------------------- /packages/mini-inversify/test/util.ts: -------------------------------------------------------------------------------- 1 | export { 2 | expect 3 | } 4 | 5 | function expect(input: any) { 6 | return { 7 | eql(val: any) { 8 | console.log(`expect ${input} eql to ${val} :`, input === val) 9 | } 10 | } 11 | } -------------------------------------------------------------------------------- /packages/mini-koa/.gitignore: -------------------------------------------------------------------------------- 1 | *.log 2 | .DS_Store 3 | node_modules 4 | dist 5 | -------------------------------------------------------------------------------- /packages/mini-koa/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 刘泽 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /packages/mini-koa/README.md: -------------------------------------------------------------------------------- 1 | ## Introduction 2 | 3 | mini implementation of [koa](https://github.com/koajs/koa) 4 | 5 | ## How to test 6 | 7 | ``` 8 | npm install 9 | 10 | ts-node test/router.ts 11 | ``` 12 | 13 | ## Todos 14 | 15 | * make router a middleware plugin 16 | * add jest test cases 17 | * add context, respondProto, requestProto mixins 18 | 19 | ## Reference 20 | 21 | * https://koa.bootcss.com/ -------------------------------------------------------------------------------- /packages/mini-koa/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.1.0", 3 | "license": "MIT", 4 | "main": "dist/index.js", 5 | "typings": "dist/index.d.ts", 6 | "files": [ 7 | "dist", 8 | "src" 9 | ], 10 | "engines": { 11 | "node": ">=10" 12 | }, 13 | "scripts": { 14 | "start": "tsdx watch", 15 | "build": "tsdx build", 16 | "test": "tsdx test", 17 | "test:router": "ts-node test/router.ts", 18 | "lint": "tsdx lint", 19 | "prepare": "tsdx build", 20 | "size": "size-limit", 21 | "analyze": "size-limit --why" 22 | }, 23 | "peerDependencies": {}, 24 | "husky": { 25 | "hooks": { 26 | "pre-commit": "tsdx lint" 27 | } 28 | }, 29 | "prettier": { 30 | "printWidth": 80, 31 | "semi": true, 32 | "singleQuote": true, 33 | "trailingComma": "es5" 34 | }, 35 | "name": "mini-koa", 36 | "author": "刘泽", 37 | "module": "dist/mini-koa.esm.js", 38 | "size-limit": [ 39 | { 40 | "path": "dist/mini-koa.cjs.production.min.js", 41 | "limit": "10 KB" 42 | }, 43 | { 44 | "path": "dist/mini-koa.esm.js", 45 | "limit": "10 KB" 46 | } 47 | ], 48 | "devDependencies": { 49 | "@size-limit/preset-small-lib": "^4.12.0", 50 | "husky": "^6.0.0", 51 | "size-limit": "^4.12.0", 52 | "tsdx": "^0.14.1", 53 | "tslib": "^2.2.0", 54 | "typescript": "^4.3.2" 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /packages/mini-koa/src/application.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright(c) 2021 wizardpisces 3 | */ 4 | import { Handle, HttpRequest, HttpRespond } from './type' 5 | import query from './middleware/query' 6 | import http from 'http' 7 | import Router from './router' 8 | import Context from './context' 9 | 10 | export default class App { 11 | _router: Router 12 | constructor() { 13 | this._router = new Router() 14 | this.use(query()) 15 | } 16 | 17 | createContext(req:HttpRequest, res:HttpRespond): Context{ 18 | return new Context(req,res) 19 | } 20 | 21 | handle(req: any, res: any) { 22 | let ctx = this.createContext(req,res) 23 | this._router.handle(ctx).then(() => respose(ctx)).catch(e => { console.error(e) }) 24 | } 25 | 26 | use(url: string | Handle, handle?: Handle) { 27 | let path = '/' 28 | if (typeof url === 'function') { 29 | handle = url; 30 | url = path 31 | } 32 | 33 | this._router.use(url, handle as Handle) 34 | } 35 | 36 | listen(port: number, cb: any) { 37 | let server = http.createServer(this.handle.bind(this)) 38 | server.listen(port, cb); 39 | } 40 | } 41 | 42 | function respose(ctx: Context) { 43 | ctx.res.end(ctx.body); 44 | } -------------------------------------------------------------------------------- /packages/mini-koa/src/compose.ts: -------------------------------------------------------------------------------- 1 | import Context from './context'; 2 | import { Handle, Next } from './type' 3 | 4 | export default function compose(handlers: Handle[]): Handle { 5 | let len = handlers.length, 6 | i = 0; 7 | 8 | return async (ctx: Context, next: Next) => { 9 | 10 | async function dispatch(i: number, err?: any) { 11 | if (i === len) return next(err) 12 | await handlers[i](ctx, async (err) => { 13 | dispatch(i + 1, err); 14 | }) 15 | } 16 | 17 | await dispatch(i) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /packages/mini-koa/src/context.ts: -------------------------------------------------------------------------------- 1 | import { HttpRequest, HttpRespond } from "./type"; 2 | // import Delegate from './helper/delagate' 3 | 4 | // class ResponseProto { 5 | // res: HttpRespond 6 | // set(field: string, val: any) { 7 | // this.res.setHeader(field, val); 8 | // } 9 | // } 10 | 11 | // class RequestProto { 12 | // req: HttpRequest 13 | // get(field: string) { 14 | // this.req.getHeader(field); 15 | // } 16 | // get method() { 17 | // return this.req.method; 18 | // } 19 | // } 20 | 21 | 22 | // Needed for all mixins 23 | 24 | // type Constructor = new (...args: any[]) => T; 25 | 26 | // function ResponseProto(Base: TBase){ 27 | // return class extends Base{ 28 | // set(field: string, val: any) { 29 | // // @ts-ignore 30 | // this.res.setHeader(field, val); 31 | // } 32 | // } 33 | // } 34 | 35 | // function RequestProto(Base: TBase){ 36 | // return class extends Base{ 37 | // get(field: string) { 38 | // // @ts-ignore 39 | // this.req.getHeader(field); 40 | // } 41 | // } 42 | // } 43 | 44 | // @mixins(ResponseProto, RequestProto) 45 | class Context { 46 | req: HttpRequest; 47 | res: HttpRespond; 48 | // response: ResponseProto; 49 | // request: RequestProto; 50 | body: any; 51 | constructor(req: HttpRequest, res: HttpRespond) { 52 | this.req = req; 53 | this.res = res; 54 | // this.response = Object.create(ResponseProto); 55 | // this.request = Object.create(RequestProto); 56 | this.body = null 57 | } 58 | // responseProto 59 | set(field: string, val: any) { 60 | this.res.setHeader(field, val); 61 | } 62 | 63 | /** 64 | * requestProto 65 | */ 66 | 67 | get(field: string) { 68 | this.req.getHeader(field); 69 | } 70 | 71 | get method() { 72 | return this.req.method; 73 | } 74 | 75 | get path() { 76 | return this.req.path; 77 | } 78 | } 79 | 80 | // new Delegate(Context, 'response') 81 | // .method('set') 82 | 83 | // new Delegate(Context, 'request') 84 | // .method('get') 85 | 86 | export default Context -------------------------------------------------------------------------------- /packages/mini-koa/src/helper/delagate.ts: -------------------------------------------------------------------------------- 1 | class Delegate { 2 | proto: any 3 | target: string 4 | constructor(proto: any, target: string) { 5 | this.proto = proto 6 | this.target = target 7 | } 8 | method(name: string) { 9 | const { proto, target } = this; 10 | 11 | proto[name] = function () { 12 | return this[target][name].apply(this[target], arguments) 13 | } 14 | 15 | return this; 16 | } 17 | } 18 | 19 | export default Delegate -------------------------------------------------------------------------------- /packages/mini-koa/src/helper/send.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs' 2 | import util from 'util' 3 | import pathInternal from 'path' 4 | import Context from '../context'; 5 | 6 | const stat = util.promisify(fs.stat) 7 | 8 | export default async function send(ctx: Context, path: string, opts = { root: '' }) { 9 | let stats: fs.Stats; 10 | 11 | const trailingSlash = path[path.length - 1] === '/' 12 | 13 | try { 14 | path = pathInternal.resolve(opts.root, path) 15 | 16 | // serve index.html 17 | if (trailingSlash) { 18 | path = pathInternal.resolve(path, 'index.html') 19 | } 20 | stats = await stat(path) 21 | 22 | ctx.set('Content-Length', stats.size) 23 | ctx.body = fs.createReadStream(path) 24 | return true 25 | } catch (e) { 26 | return false 27 | } 28 | } -------------------------------------------------------------------------------- /packages/mini-koa/src/index.ts: -------------------------------------------------------------------------------- 1 | import AsyncApp from './application' 2 | import compose from './compose' 3 | 4 | import logger from './public-middleware/logger' 5 | import responseTime from './public-middleware/response-time' 6 | import serveStatic from './public-middleware/static' 7 | 8 | export * from './type' 9 | 10 | const middleware = { 11 | logger, 12 | responseTime, 13 | serveStatic 14 | } 15 | 16 | export { 17 | compose, 18 | AsyncApp, 19 | middleware 20 | } 21 | 22 | export default AsyncApp -------------------------------------------------------------------------------- /packages/mini-koa/src/middleware/query.ts: -------------------------------------------------------------------------------- 1 | import Context from '../context'; 2 | import { Handle, Next } from '../type' 3 | 4 | type Query = { [key: string]: string } 5 | 6 | export function parseUrl(url: string): { path: string; query: Query } { 7 | let arr = url.split('?'), 8 | path = arr[0], 9 | query: Query = {}; 10 | 11 | if (arr[1]) { 12 | query = arr[1].split('&').reduce((res, cur) => { 13 | let [name, val] = cur.split('=') 14 | res[name] = val 15 | return res 16 | }, query); 17 | } 18 | 19 | return { 20 | path, 21 | query 22 | } 23 | } 24 | 25 | export default function query(): Handle { 26 | return async (ctx:Context, next:Next) => { 27 | let { query, path } = parseUrl(ctx.req.url) 28 | ctx.req.path = path; 29 | ctx.req.query = query 30 | 31 | await next() 32 | } 33 | } -------------------------------------------------------------------------------- /packages/mini-koa/src/public-middleware/logger.ts: -------------------------------------------------------------------------------- 1 | import { Handle, Next, Context } from "../type"; 2 | 3 | export default function logger(): Handle { 4 | return async (ctx: Context, next: Next) => { 5 | console.log('[tiny-server logger]: ', ctx.req.url, ctx.req.path, ctx.req.query); 6 | await next() 7 | } 8 | } -------------------------------------------------------------------------------- /packages/mini-koa/src/public-middleware/response-time.ts: -------------------------------------------------------------------------------- 1 | import { Handle, Next, Context } from "../type"; 2 | 3 | export default function responseTime():Handle { 4 | 5 | return async (ctx:Context, next:Next) => { 6 | let startAt = process.hrtime() 7 | 8 | await next() 9 | 10 | let delta = process.hrtime(startAt), 11 | // Format to high resolution time with nano time to milliseconds 12 | time = delta[0] * 1000 + delta[1] / 1000000; 13 | console.log(`[response-time]: ${time}`) 14 | 15 | setHeader(ctx.res, time) 16 | 17 | } 18 | } 19 | 20 | function setHeader(res:any, time:number) { 21 | res.setHeader('X-Response-Time', time + 'ms') 22 | } -------------------------------------------------------------------------------- /packages/mini-koa/src/public-middleware/static.ts: -------------------------------------------------------------------------------- 1 | import Context from "../context" 2 | import { Next } from "../type" 3 | import send from '../helper/send' 4 | 5 | export default function serve(root:string,opts:any) { 6 | return async function serve(ctx:Context, next:Next) { 7 | let done = false 8 | opts.root = root 9 | if (ctx.method === 'HEAD' || ctx.method === 'GET') { 10 | try { 11 | done = await send(ctx, ctx.path, opts) 12 | } catch (err) { 13 | if (err.status !== 404) { 14 | throw err 15 | } 16 | } 17 | } 18 | 19 | if (!done) { 20 | await next() 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /packages/mini-koa/src/router.ts: -------------------------------------------------------------------------------- 1 | import { Handle, LayerOptions, Context } from './type' 2 | import { parseUrl } from './middleware/query' 3 | class Layer { 4 | handle: Handle 5 | path: string 6 | asterisk: boolean // * 通配符 7 | all_slash: boolean 8 | constructor(options: LayerOptions) { 9 | this.handle = options.handle 10 | this.path = options.path 11 | this.asterisk = this.path === '*' 12 | this.all_slash = this.path === '/' 13 | } 14 | 15 | match(reqPath: string) { 16 | let { path } = parseUrl(reqPath) 17 | // console.log('----', path, this.path, '--') 18 | if (this.asterisk) { 19 | return true 20 | } 21 | 22 | if (this.all_slash) { 23 | return true 24 | } 25 | 26 | return path === this.path 27 | } 28 | } 29 | 30 | export default class Router { 31 | stack: Layer[] 32 | 33 | constructor() { 34 | this.stack = [] 35 | } 36 | 37 | use(path: string, handle: Handle) { 38 | this.stack.push(new Layer({ 39 | handle, 40 | path 41 | })) 42 | } 43 | 44 | async handle(ctx: Context) { 45 | let { req } = ctx; 46 | 47 | let middleware: Layer[] = this.stack.filter((layer) => { 48 | return layer.match(req.url) 49 | }); 50 | 51 | let len = middleware.length, i = 0; 52 | 53 | const next = async (i: number, err: any) => { 54 | if (err) { 55 | throw new Error(err) 56 | } 57 | if (i >= len) return 58 | 59 | await middleware[i].handle(ctx, async (err: any) => await next(i + 1, err)) 60 | } 61 | 62 | await next(i, null) 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /packages/mini-koa/src/type.ts: -------------------------------------------------------------------------------- 1 | import ContextType from "./context"; 2 | 3 | export type Next> = (err?: Error | null) => T; 4 | export type RequestHandler = ( 5 | ctx: T, 6 | next: Next 7 | ) => any; 8 | 9 | /** 10 | * Todos 11 | * replace any with actual req/res.next type 12 | */ 13 | export type Handle = RequestHandler> 14 | 15 | export type LayerOptions = { 16 | handle: Handle; 17 | path: string 18 | } 19 | 20 | export type Context = ContextType 21 | export type HttpRequest = any 22 | export type HttpRespond = any -------------------------------------------------------------------------------- /packages/mini-koa/src/util.ts: -------------------------------------------------------------------------------- 1 | export function info(str:string) { 2 | return `[node-server]: ${str}` 3 | } 4 | 5 | export function mixins(...list:object[]) { 6 | return function (target:any) { 7 | Object.assign(target.prototype, ...list); 8 | }; 9 | } -------------------------------------------------------------------------------- /packages/mini-koa/test/router.ts: -------------------------------------------------------------------------------- 1 | import AsyncApp,{middleware} from '../src/index' 2 | 3 | import { 4 | info 5 | } from '../src/util' 6 | 7 | let app = new AsyncApp(), 8 | port = 8081 9 | 10 | app.use(middleware.logger()) 11 | app.use(middleware.responseTime()) 12 | 13 | /** 14 | * 直接返回大的content会导致 nodejs CPU 拉满 15 | */ 16 | app.use('/test_big_content',(ctx)=>{ 17 | let content = 'testtestts'.repeat(100000000) 18 | console.log('size', content.length / 1024 / 1024,'M') 19 | ctx.body = content 20 | }) 21 | 22 | app.use('/home', (ctx) => { 23 | ctx.body = info('home route is ok') 24 | }) 25 | 26 | app.use(async (ctx, next) => { 27 | ctx.req.test = 'this is a slash path' 28 | await new Promise(resolve => { 29 | setTimeout(async () => { 30 | resolve(1) 31 | }, 1000) 32 | }) 33 | 34 | await next() 35 | 36 | await new Promise(resolve => { 37 | setTimeout(async () => { 38 | resolve(1) 39 | }, 1000) 40 | }) 41 | // res.end(info('route is ok')) 42 | }) 43 | 44 | app.use('*', async (ctx) => { 45 | await new Promise(resolve => { 46 | setTimeout(async () => { 47 | resolve(1) 48 | }, 1000) 49 | }) 50 | ctx.body = (info('this is default route' + ctx.req.test)) 51 | }) 52 | 53 | app.listen(port, () => { 54 | console.log(`server started at http://localhost:${port}`) 55 | }) -------------------------------------------------------------------------------- /packages/mini-koa/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | // see https://www.typescriptlang.org/tsconfig to better understand tsconfigs 3 | "include": ["src", "types"], 4 | "compilerOptions": { 5 | "target": "esnext", 6 | "module": "commonjs", 7 | "lib": ["dom", "esnext"], 8 | "importHelpers": true, 9 | // output .d.ts declaration files for consumers 10 | "declaration": true, 11 | // output .js.map sourcemap files for consumers 12 | "sourceMap": true, 13 | // match output dir to input dir. e.g. dist/index instead of dist/src/index 14 | "rootDir": "./src", 15 | // stricter type-checking for stronger correctness. Recommended by TS 16 | "strict": true, 17 | // linter checks for common issues 18 | "noImplicitReturns": true, 19 | "noFallthroughCasesInSwitch": true, 20 | // noUnused* overlap with @typescript-eslint/no-unused-vars, can disable if duplicative 21 | "noUnusedLocals": true, 22 | "noUnusedParameters": true, 23 | // use Node's module resolution algorithm, instead of the legacy TS one 24 | "moduleResolution": "node", 25 | // transpile JSX to React.createElement 26 | "jsx": "react", 27 | // interop between ESM and CJS modules. Recommended by TS 28 | "esModuleInterop": true, 29 | // significant perf increase by skipping checking .d.ts files, particularly those in node_modules. Recommended by TS 30 | "skipLibCheck": true, 31 | // error out if import and file system have a casing mismatch. Recommended by TS 32 | "forceConsistentCasingInFileNames": true, 33 | // `tsdx build` ignores this option, but it is commonly used when type-checking separately with `tsc` 34 | "noEmit": true, 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /packages/mini-mp-framework/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | dist 4 | dist-ssr 5 | *.local -------------------------------------------------------------------------------- /packages/mini-mp-framework/demo/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | dist 4 | dist-ssr 5 | *.local -------------------------------------------------------------------------------- /packages/mini-mp-framework/demo/favicon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /packages/mini-mp-framework/demo/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vite App 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /packages/mini-mp-framework/demo/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "demo", 3 | "version": "0.0.0", 4 | "scripts": { 5 | "dev": "vite", 6 | "build": "tsc && vite build", 7 | "preview": "vite preview" 8 | }, 9 | "devDependencies": { 10 | "typescript": "^4.4.4", 11 | "vite": "^2.7.2", 12 | "mini-vue": "../../mini-vue" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /packages/mini-mp-framework/demo/src/main.ts: -------------------------------------------------------------------------------- 1 | import { Options, init } from '../../src' 2 | 3 | // import './style.css' 4 | 5 | let structure: Options['structure'] = { 6 | data:{ 7 | msg: 'Hello Vite!', 8 | }, 9 | created() { 10 | setTimeout(() => { 11 | this.setData({ 12 | msg: 'setData' 13 | }) 14 | }, 3000) 15 | }, 16 | render(){ 17 | const app = document.querySelector('#app')! 18 | 19 | app.innerHTML = ` 20 |

${this.data.msg}

21 | Documentation 22 | ` 23 | } 24 | 25 | } 26 | 27 | init({ 28 | structure, 29 | workerPath: './src/main.ts' 30 | }) -------------------------------------------------------------------------------- /packages/mini-mp-framework/demo/src/style.css: -------------------------------------------------------------------------------- 1 | #app { 2 | font-family: Avenir, Helvetica, Arial, sans-serif; 3 | -webkit-font-smoothing: antialiased; 4 | -moz-osx-font-smoothing: grayscale; 5 | text-align: center; 6 | color: #2c3e50; 7 | margin-top: 60px; 8 | } 9 | -------------------------------------------------------------------------------- /packages/mini-mp-framework/demo/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /packages/mini-mp-framework/demo/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "useDefineForClassFields": true, 5 | "module": "ESNext", 6 | "lib": ["ESNext", "DOM"], 7 | "moduleResolution": "Node", 8 | "strict": true, 9 | "sourceMap": true, 10 | "resolveJsonModule": true, 11 | "esModuleInterop": true, 12 | "noEmit": true, 13 | "noUnusedLocals": true, 14 | "noUnusedParameters": true, 15 | "noImplicitReturns": true 16 | }, 17 | "include": ["./src"] 18 | } 19 | -------------------------------------------------------------------------------- /packages/mini-mp-framework/favicon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /packages/mini-mp-framework/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mini-mp-framework", 3 | "version": "0.0.0", 4 | "scripts": { 5 | "dev": "vite", 6 | "demo":"cd demo && vite", 7 | "build": "tsc && vite build", 8 | "preview": "vite preview" 9 | }, 10 | "devDependencies": { 11 | "typescript": "^4.4.4", 12 | "vite": "^2.7.2" 13 | } 14 | } -------------------------------------------------------------------------------- /packages/mini-mp-framework/readme.md: -------------------------------------------------------------------------------- 1 | ## Introduction 2 | 3 | Mini 小程序框架 概念实现 4 | 5 | ## Reference 6 | 7 | 8 | * https://zhuanlan.zhihu.com/p/100563211 -------------------------------------------------------------------------------- /packages/mini-mp-framework/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './main' 2 | 3 | export * from './type' -------------------------------------------------------------------------------- /packages/mini-mp-framework/src/main.ts: -------------------------------------------------------------------------------- 1 | import parent from './parent' 2 | import worker from './worker' 3 | import { Options } from './type' 4 | 5 | const isWindow = typeof window !== 'undefined' 6 | 7 | export function init(options: Options) { 8 | let internalOptions: Options = Object.assign(options, { 9 | }) 10 | if (isWindow) { 11 | parent(internalOptions) 12 | } else { 13 | worker(internalOptions) 14 | } 15 | } -------------------------------------------------------------------------------- /packages/mini-mp-framework/src/parent.ts: -------------------------------------------------------------------------------- 1 | import { MessageType, Options } from "./type"; 2 | 3 | export default function parent(options:Options){ 4 | let { workerPath, structure} = options 5 | let worker = new Worker(workerPath, { type: 'module' }) 6 | worker.onmessage = (e)=>{ 7 | if (e.data.type === MessageType.init){ 8 | structure.render() 9 | worker.postMessage({ 10 | type:MessageType.created 11 | }) 12 | }else if(e.data.type === MessageType.update){ 13 | for (const [key, value] of Object.entries(e.data.data)) { 14 | structure.data[key] = value 15 | } 16 | structure.render() 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /packages/mini-mp-framework/src/type.ts: -------------------------------------------------------------------------------- 1 | export { 2 | Options, 3 | MessageType, 4 | Structure 5 | } 6 | type Structure = { 7 | /** 8 | * 对应 vue 声明周期 created,但不是同一个 9 | */ 10 | created: () => void 11 | render:()=>void 12 | [key:string]:any 13 | } 14 | type Options = { 15 | workerPath:string 16 | structure: Structure 17 | } 18 | 19 | const enum MessageType { 20 | init = 'init', 21 | update = 'update', 22 | created = 'created', 23 | } -------------------------------------------------------------------------------- /packages/mini-mp-framework/src/worker.ts: -------------------------------------------------------------------------------- 1 | import { MessageType, Options } from "./type"; 2 | 3 | export default function worker(options: Options) { 4 | let { structure } = options 5 | let scope = { 6 | setData(data: any) { 7 | postMessage({ 8 | type: MessageType.update, 9 | data 10 | }) 11 | } 12 | } 13 | postMessage({ 14 | type: MessageType.init 15 | }) 16 | onmessage = (e) => { 17 | if (e.data.type === MessageType.created) { 18 | structure.created.call(scope) 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /packages/mini-mp-framework/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "useDefineForClassFields": true, 5 | "module": "ESNext", 6 | "lib": ["ESNext", "DOM"], 7 | "moduleResolution": "Node", 8 | "strict": true, 9 | "sourceMap": true, 10 | "resolveJsonModule": true, 11 | "esModuleInterop": true, 12 | "noEmit": true, 13 | "noUnusedLocals": true, 14 | "noUnusedParameters": true, 15 | "noImplicitReturns": true 16 | }, 17 | "include": ["./src"] 18 | } 19 | -------------------------------------------------------------------------------- /packages/mini-mq/example/index.ts: -------------------------------------------------------------------------------- 1 | import { Queue } from 'bullmq'; 2 | import IORedis from 'ioredis'; 3 | 4 | const redis = new IORedis({ 5 | host: "localhost", 6 | port: 6379 7 | }); 8 | const myQueue = new Queue('foo', { connection: redis }); 9 | 10 | async function addJobs() { 11 | await redis.set("redisKey", "redisValue") 12 | let redisResult = await redis.get('redisKey') 13 | console.log(redisResult) 14 | await myQueue.add('myJobName', { foo: 'bar' }); 15 | await myQueue.add('myJobName', { qux: 'baz' }); 16 | let active = await myQueue.getActive() 17 | console.log(active) 18 | } 19 | 20 | addJobs(); -------------------------------------------------------------------------------- /packages/mini-mq/example/readme.md: -------------------------------------------------------------------------------- 1 | ## How to start 2 | ``` 3 | brew install redis 4 | 5 | redis-server 6 | 7 | npm run nodemon 8 | ``` 9 | 10 | ## Redis GUI 11 | 12 | https://www.npmjs.com/package/redis-commander 13 | 14 | 15 | -------------------------------------------------------------------------------- /packages/mini-mq/nodemon.json: -------------------------------------------------------------------------------- 1 | { 2 | "watch": [ 3 | "src" 4 | ], 5 | "ext": "ts,json", 6 | "ignore": [ 7 | "src/**/*.spec.ts" 8 | ], 9 | "env":{ 10 | "DEBUG":"mp-example:*" 11 | }, 12 | "exec": "ts-node example/index.ts" 13 | } -------------------------------------------------------------------------------- /packages/mini-mq/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mini-mq", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "example": "ts-node example/index.ts", 9 | "nodemon": "nodemon" 10 | }, 11 | "author": "刘泽", 12 | "license": "ISC", 13 | "dependencies": { 14 | "bullmq": "^1.46.5" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /packages/mini-mq/readme.md: -------------------------------------------------------------------------------- 1 | ## Introduction 2 | mini [bullmq](https://github.com/taskforcesh/bullmq) 3 | TODO 4 | ## 难点 5 | ### RPC 通信 6 | 解决的是 Broker 与 Producer 以及 Consumer 之间的通信问题。 7 | 8 | ### 高可用设计 9 | 高可用主要涉及两方面:Broker 服务的高可用、存储方案的高可用。 10 | 11 | ### 存储设计 12 | 13 | ### 消费关系管理 14 | 15 | ### 高性能设计 16 | 17 | ## 纵向(Scale-up) 横向扩展(Scale-out) 18 | 19 | 两个比喻: 20 | 21 |   一是传统火车和动车。传统的存储Scale-up架构的存储就好像传统的火车一样,当后面的磁盘越挂越多的时候,控制器性能以及背板带宽却不能相应提升,因此传统存储在磁盘容量扩容到一定程度时候,往往性能下降。 22 | 23 |   集群存储就好像新一代的“动车组”火车一样,当火车车厢增加的时候,前面的火车头动力也随之增加,因此不会发生性能瓶颈。 24 | 25 |   所谓动车组的设计理念和传统火车设计理念的最大区别在于传统火车主要动力来自于火车头(就像传统模块化阵列的两个控制器),而动车组则不一样,除了车头配有动力装置外,每一节车厢都配有动力推动装置。集群存储大多都是由一个个节点(X86 服务器)组成,每一个节点添加进去后,不仅能够添加容量,还能够添加整个存储器的整体处理能力。 26 | 27 | 一个鱼缸的比喻: 28 | 29 |   当你只有六七条鱼的时候, 一个小型鱼缸就够了;可是过一段时间新生了三十多条小鱼,这个小缸显然不够大了。 30 | 31 |   如果用Scale up解决方案,那么你就需要去买一个大缸,把所有沙、水草、布景、加热棒、温度计都从小缸里拿出来,重新布置到大缸。这个工程可不简单哦,不是十分钟八分钟能搞得定的,尤其水草,纠在一起很难分开(不过这跟迁移数据的工程复杂度比起来实在是毛毛雨啦,不值一提)。 32 | 33 |   那么现在换个思路,用Scale out方案,就相当于是你在这个小缸旁边接了一个同样的小缸,两个缸联通。鱼可以自动分散到两个缸,你也就省掉了上面提到的那一系列挪沙、水草、布景等的折腾了。 34 | 35 | ## Reference 36 | 37 | * https://www.zhihu.com/question/54152397 38 | * https://zhuanlan.zhihu.com/p/77920331 -------------------------------------------------------------------------------- /packages/mini-os/.cargo/config: -------------------------------------------------------------------------------- 1 | # mini-os/.cargo/config 2 | [build] 3 | target = "riscv64gc-unknown-none-elf" -------------------------------------------------------------------------------- /packages/mini-os/.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | node_modules -------------------------------------------------------------------------------- /packages/mini-os/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "mini-os" 7 | version = "0.1.0" 8 | -------------------------------------------------------------------------------- /packages/mini-os/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "mini-os" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | -------------------------------------------------------------------------------- /packages/mini-os/readme.md: -------------------------------------------------------------------------------- 1 | # Introduction 2 | Mini OS 3 | ## Target 4 | 5 | 6 | ### 虚拟化 7 | KVM 8 | 9 | 使用 Intel VT-x 技术实现 CPU 虚拟化 10 | 使用 EPT 技术实现内存虚拟化 11 | 支持虚拟 x86 实模式运行环境 12 | 支持虚拟 CPUID 指令 13 | 支持虚拟 HLT 指令,Guest 利用 HLT 指令关机 14 | 15 | ## Reference 16 | 17 | * http://rcore-os.cn/rCore-Tutorial-Book-v3/chapter1/1app-ee-platform.html -------------------------------------------------------------------------------- /packages/mini-os/src/lang_items.rs: -------------------------------------------------------------------------------- 1 | use core::panic::PanicInfo; 2 | 3 | #[panic_handler] 4 | fn panic(_info: &PanicInfo) -> ! { 5 | loop {} 6 | } -------------------------------------------------------------------------------- /packages/mini-os/src/main.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![no_main] 3 | 4 | mod lang_items; 5 | 6 | fn main() { 7 | // println!("Hello, world!"); 8 | } 9 | -------------------------------------------------------------------------------- /packages/mini-react/demo/README.md: -------------------------------------------------------------------------------- 1 | ## How to run 2 | 3 | ``` 4 | npm install -g vite # make sure global vite exists 5 | 6 | npm install 7 | vite 8 | ``` 9 | -------------------------------------------------------------------------------- /packages/mini-react/demo/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | mini 8 | 10 | 11 | 12 |
13 |

loading...

14 |
15 | 16 | 17 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /packages/mini-react/demo/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mini-hello", 3 | "version": "0.1.0", 4 | "scripts": { 5 | "dev": "vite", 6 | "build": "vite build", 7 | "serve": "vite preview" 8 | }, 9 | "dependencies": {}, 10 | "devDependencies": { 11 | "vite": "^2.1.5" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /packages/mini-react/demo/src/demo.tsx: -------------------------------------------------------------------------------- 1 | import { h, Fragment, useReducer, useState, useEffect } from "../../src/index" 2 | 3 | export default function Counter() { 4 | const [count, setCount] = useState(0); 5 | 6 | const add = () => setCount((prev) => prev + 1); 7 | 8 | useEffect(() => { 9 | console.warn("Mounted count,count updated", count); 10 | }, [count]); 11 | 12 | return ( 13 |
14 |

React count Demo

15 | {count} 16 | 17 |
18 | // <> 19 | //

React count Demo

20 | // {count} 21 | // 22 | // 23 | ); 24 | } -------------------------------------------------------------------------------- /packages/mini-react/demo/src/main.tsx: -------------------------------------------------------------------------------- 1 | import { render, h, useEffect, Fragment, useState } from "../../src/index" 2 | 3 | import { UseStateApp } from './use-state' 4 | import { App as UseReducerApp } from './use-reducer' 5 | import { UseEffectApp } from './use-effect' 6 | import { App as UseMemoApp } from './use-memo' 7 | import { App as UseRefApp } from './use-ref' 8 | import { default as Demo } from './demo' 9 | 10 | function App() { 11 | useEffect(() => { 12 | console.warn('mounted App main useEffect Root') 13 | }, []); 14 | 15 | const [count, setCount] = useState(1) 16 | 17 | function upCount(){ 18 | setCount(count + 1) 19 | } 20 | 21 | return <> 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | } 31 | 32 | render(, document.getElementById("app")) 33 | -------------------------------------------------------------------------------- /packages/mini-react/demo/src/use-effect.tsx: -------------------------------------------------------------------------------- 1 | import { h, Fragment, useReducer, useState, useEffect } from "../../src/index" 2 | import Demo from './demo' 3 | export { 4 | UseEffectApp 5 | } 6 | 7 | 8 | function UseEffectApp(props) { 9 | let { count } = props; 10 | let [countDown, setCountDown] = useState(2); 11 | const [data, setData] = useState([{ 12 | id: 'id', 13 | title: 'fetching data......' 14 | }]); 15 | 16 | useEffect(() => { 17 | document.title = data[0].title 18 | console.warn('mounted useEffect child') 19 | }, []); 20 | 21 | useEffect(() => { 22 | console.warn('running useEffect cb', `count:${count}`, `countDown:${countDown}`) 23 | setCountDown(countDown = 2) 24 | let timeoutID 25 | const fetchData = () => { 26 | timeoutID = setTimeout(() => { 27 | 28 | console.warn('setTimeout triggered', `count:${count}`, `countDown:${countDown}`,timeoutID) 29 | setCountDown(--countDown) 30 | if (countDown <= 0) { 31 | setData([ 32 | { 33 | id: 'id changed', 34 | title: 'data fetched : ' + Math.random() 35 | } 36 | ]); 37 | return 38 | } 39 | fetchData() 40 | 41 | }, 1000) 42 | console.log('fetchData',timeoutID) 43 | 44 | }; 45 | 46 | fetchData(); 47 | 48 | return () => { 49 | clearTimeout(timeoutID) 50 | console.log('clear timeout',timeoutID) 51 | } 52 | }, [count]); 53 | 54 | return ( 55 |
56 |

useEffect

57 |
title change in {countDown} seconds (countState will change by parent):
58 |
    59 | {data.map(item => ( 60 |
  • 61 | {item.title} 62 |
  • 63 | ))} 64 |
65 | {/* */} 66 |
67 | ); 68 | } -------------------------------------------------------------------------------- /packages/mini-react/demo/src/use-memo.tsx: -------------------------------------------------------------------------------- 1 | import { h, Fragment, useReducer, useState, useEffect, useMemo } from "../../src/index" 2 | 3 | export { 4 | App 5 | } 6 | 7 | 8 | function App(props) { 9 | let { count } = props; 10 | 11 | let memoValue = useMemo(() => (count * 2), [count]) 12 | useEffect(() => { 13 | console.warn('mounted useMemo child') 14 | }, []); 15 | return ( 16 | <> 17 |

useMemo

18 | memoValue:{memoValue} 19 | {/*
this is memo count*2 value: {memoValue}
*/} 20 | 21 | ); 22 | } -------------------------------------------------------------------------------- /packages/mini-react/demo/src/use-reducer.tsx: -------------------------------------------------------------------------------- 1 | import { h, Fragment, useReducer, useEffect} from "../../src/index" 2 | 3 | export { 4 | App 5 | } 6 | 7 | function reducer(state, action) { 8 | switch (action.type) { 9 | case 'up': 10 | return { count: state.count + 1 } 11 | case 'down': 12 | return { count: state.count - 1 } 13 | } 14 | } 15 | 16 | function App() { 17 | const [state, dispatch] = useReducer(reducer, { count: 1 }) 18 | useEffect(() => { 19 | console.warn('mounted useReducer child') 20 | }, []); 21 | return ( 22 | <> 23 |

useReducer

24 | {state.count} 25 | 26 | 27 | 28 | ) 29 | } -------------------------------------------------------------------------------- /packages/mini-react/demo/src/use-ref.tsx: -------------------------------------------------------------------------------- 1 | import { h, Fragment, useReducer, useState, useEffect, useRef } from "../../src/index" 2 | 3 | export { 4 | App 5 | } 6 | 7 | function App() { 8 | const countRef = useRef(0); 9 | useEffect(() => { 10 | console.warn('mounted useRef child') 11 | }, []); 12 | 13 | const handle = () => { 14 | countRef.current++; 15 | console.log(`Clicked ${countRef.current} times`); 16 | }; 17 | 18 | return <> 19 |

useRef

20 | 21 | 22 |
23 | 24 | } 25 | 26 | function InputFocus() { 27 | const inputRef = useRef(); 28 | const refDiv = useRef(); 29 | useEffect(() => { 30 | console.log('refDiv', refDiv.current); // logs
I'm an element
31 | inputRef.current.focus(); 32 | inputRef.current.value = 1 33 | }); 34 | return ( 35 |
36 | 40 |
41 | ); 42 | } -------------------------------------------------------------------------------- /packages/mini-react/demo/src/use-state.tsx: -------------------------------------------------------------------------------- 1 | import { render, h, useState, Fragment, useEffect} from "../../src/index" 2 | 3 | export { 4 | UseStateApp 5 | } 6 | 7 | function UseStateApp(props) { 8 | // console.log('父组件') 9 | let { count, upCount } = props 10 | useEffect(() => { 11 | console.warn('mounted useState child') 12 | }, []); 13 | return ( 14 |
15 |

useState + Fragment

16 | 17 |
18 | 19 |
test afterdom
20 |
21 | 22 | You clicked {count} times ( click below button will trigger useEffect and useMemo view change) 23 |
24 | 25 |
26 |
27 | ) 28 | } 29 | 30 | function SimpleChild({ i }) { 31 | // console.log('子组件', i) 32 | return `${i}组件` 33 | } 34 | 35 | function MultipleUseState() { 36 | const [up, setUp] = useState(0) 37 | const [down, setDown] = useState(0) 38 | useEffect(() => { 39 | console.warn('mounted useState: MultipleUseState child') 40 | }, []); 41 | return ( 42 | // Support Fragment 43 | <> 44 | {up} 45 | {down} 46 | 47 | ) 48 | } 49 | 50 | -------------------------------------------------------------------------------- /packages/mini-react/demo/vite.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | port: '8000', 3 | esbuild: { 4 | jsxFactory: 'h', 5 | jsxFragment: 'Fragment', 6 | target: 'es2020', 7 | format: 'esm' 8 | }, 9 | server: { 10 | port: 8080 11 | } 12 | } -------------------------------------------------------------------------------- /packages/mini-react/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mini-react", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "demo": "cd demo && vite" 8 | }, 9 | "author": "刘泽", 10 | "license": "ISC", 11 | "devDependencies": { 12 | "fre": "^2.4.0", 13 | "preact": "^10.5.14", 14 | "typescript": "^4.4.3" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /packages/mini-react/readme.md: -------------------------------------------------------------------------------- 1 | ## Introdution 2 | React hooks in mini version 3 | 4 | ## How to run 5 | 6 | ``` 7 | npm run demo 8 | ``` 9 | or reference ./demo/readme.md 10 | 11 | 12 | ## Target 13 | 无依赖,无VNode diff 的React Hooks的简单实现(纯Dom的增删操作) 14 | ### 第一阶段 (done) 15 | 全量节点更新(所有的useState触发的数据都是全量的驱动,而不是精细化的只影响到涉及到的组件函数) 16 | 17 | * **commit:f866a76** 18 | 19 | ### 第二阶段(done) 20 | * dom局部更新(useState的触发只影响到对应的组件函数) 21 | - 1: 实现 VNode 节点的遍历生成阶段 同时 生成 对应的Dom节点 22 | - 2: 对VNode的 单个组件函数跟Dom之间建立更新映射关系 23 | - 3: 根据 step2 的映射关系构建 update函数,存放在 useState 等的数据驱动代码里(到这里就是对应影响组件的重复渲染) 24 | - 4: 实现节点的局部刷新 25 | - 1: 考虑触发的 props 变化影响到外层其他节点的更新变化 26 | - 2: effect副作用清理 27 | 28 | 29 | * **commit: 47d59da** 30 | 31 | #### 节点更新原理 32 | 33 | 1. 首次渲染 34 | * 记录各个函数组件在真实 dom 中的位置信息 35 | * 记录函数组件渲染出来 真实dom 36 | 2. 更新 37 | * 删除函数组件渲染出来的真实dom 38 | * 根据函数组件在真实dom中的位置信息进行插入操作 39 | ## Reference 40 | 41 | * [前端框架思考](https://wizardpisces.github.io/blog/%E5%89%8D%E7%AB%AF%E6%A1%86%E6%9E%B6%E6%80%9D%E8%80%83) 42 | * https://reactjs.org/docs/hooks-intro.html 43 | * https://github.com/preactjs/preact 44 | * https://github.com/yisar/fre 45 | -------------------------------------------------------------------------------- /packages/mini-react/src/dom.ts: -------------------------------------------------------------------------------- 1 | import { VNode, SimpleNode, ComponentChild, HTMLElementX } from "./type" 2 | import { isString, isSimpleNode, isArray } from "./util" 3 | 4 | export { 5 | createElement, 6 | patchProps 7 | } 8 | 9 | function patchProps(dom: HTMLElement, oldProps: VNode['props'], newProps: VNode['props']) { 10 | for (let name in {...oldProps,...newProps}) { 11 | let oldValue = oldProps[name], 12 | newValue = newProps[name]; 13 | if (oldValue === newValue || name === "children") { 14 | }else if (name[0] === "o" && name[1] === "n") { // eg: onClick 15 | let eventName = name.slice(2).toLowerCase() 16 | if (oldValue) dom.removeEventListener(name, oldValue) 17 | dom.addEventListener(eventName, newValue) 18 | } else if (isString(newValue)) { 19 | dom.setAttribute(name, newValue) 20 | } 21 | } 22 | 23 | return dom 24 | } 25 | 26 | function createFragmentNode(vnodeList: VNode[]) { 27 | var fragment = document.createDocumentFragment() 28 | vnodeList.forEach(vnode => { 29 | fragment.appendChild(createElement(vnode)) 30 | }) 31 | return fragment 32 | } 33 | 34 | function createElement(vnode: VNode): HTMLElementX { 35 | let dom: HTMLElementX = vnode.type === 'text' ? 36 | document.createTextNode(vnode.props.value as string) 37 | : document.createElement(vnode.type as string) 38 | 39 | if (vnode.props.ref) { 40 | vnode.props.ref.current = dom; // 建立 ref 引用关系 41 | } 42 | 43 | if (dom instanceof HTMLElement){ 44 | patchProps(dom as HTMLElement, {} as VNode['props'],vnode.props) 45 | } 46 | 47 | return dom 48 | } -------------------------------------------------------------------------------- /packages/mini-react/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './hooks' 2 | 3 | export * from './h' 4 | 5 | export * from './render' 6 | 7 | 8 | // export * from 'preact' 9 | // export { useState,useEffect,useReducer,useMemo,useRef } from 'preact/hooks' 10 | 11 | // export * from 'fre' -------------------------------------------------------------------------------- /packages/mini-react/src/render.ts: -------------------------------------------------------------------------------- 1 | import { h } from "." 2 | import { traverseVNode } from "./h" 3 | import { HTMLElementX, VNode } from "./type" 4 | import { createLogger } from "./util" 5 | 6 | export { 7 | render, 8 | update, 9 | addPostRenderTask 10 | } 11 | 12 | const logger = createLogger('[render]') 13 | 14 | function render(vnode: VNode, parentNode: HTMLElement) { 15 | parentNode.innerHTML = '' 16 | let rootVNode = h(parentNode.localName, {} as VNode['props']) // create rootVNode 17 | 18 | rootVNode.updateInfo.node = parentNode 19 | rootVNode.updateInfo.firstChild = vnode 20 | 21 | vnode.parentVNode = rootVNode 22 | 23 | update(vnode) 24 | } 25 | 26 | function update(vnode: VNode) { 27 | vnode.updateInfo.clear() 28 | traverseVNode(vnode, vnode.parentVNode as VNode) 29 | // postRender() 30 | } 31 | 32 | let postRenderQueue = new Set() 33 | 34 | function postRender() { 35 | Array.from(postRenderQueue).forEach(task => task()) 36 | } 37 | 38 | function addPostRenderTask(...tasks: Function[]) { 39 | tasks.forEach(task => { 40 | postRenderQueue.add(task) 41 | }) 42 | } -------------------------------------------------------------------------------- /packages/mini-react/src/util.ts: -------------------------------------------------------------------------------- 1 | import { SimpleNode } from "./type" 2 | 3 | export { 4 | createLogger, 5 | isString, 6 | isFunction, 7 | isSimpleNode, 8 | isArray 9 | } 10 | const createLogger = (prefix = "[Mini React]") => (...args: any[]) => console.log(`${prefix}`, ...args) 11 | 12 | const isString = (t: unknown): t is string => typeof t === 'string' 13 | 14 | const isNumber = (t: any): t is number => typeof t === 'number' 15 | 16 | const isFunction = (t: any): t is Function => typeof t === 'function' 17 | 18 | const isSimpleNode = (t: any):t is SimpleNode => isNumber(t) || isString(t) 19 | 20 | const isArray = Array.isArray -------------------------------------------------------------------------------- /packages/mini-react/todos.md: -------------------------------------------------------------------------------- 1 | * ~~JSX~~ 2 | * [X] Fragment 3 | * hooks 4 | * [X] useState 5 | * [X] useReducer 6 | * [X] useEffect 7 | * [X] useMemo 8 | * [] useLayout 9 | * [X] useRef 10 | * [X] partial update 11 | * [] vnode diff (only rerender changed part) 12 | * [] Concurrent Rendering 13 | 14 | Context, Portals, Suspense, Streaming SSR, Progressive Hydration, Error Boundaries 15 | 16 | refer to solidjs -------------------------------------------------------------------------------- /packages/mini-rpc/.gitignore: -------------------------------------------------------------------------------- 1 | *.log 2 | .DS_Store 3 | node_modules 4 | dist 5 | -------------------------------------------------------------------------------- /packages/mini-rpc/index.ts: -------------------------------------------------------------------------------- 1 | export * from './src/client' 2 | export * from './src/server' 3 | export * from './src/registry' -------------------------------------------------------------------------------- /packages/mini-rpc/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rpc", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "", 8 | "server": "ts-node test/sofa-rpc/server.ts", 9 | "client": "ts-node test/sofa-rpc/client.ts" 10 | }, 11 | "author": "刘泽", 12 | "license": "ISC", 13 | "devDependencies": { 14 | "sofa-rpc-node": "^2.0.0" 15 | }, 16 | "dependencies": { 17 | "protobufjs": "^6.11.2" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /packages/mini-rpc/ppt.md: -------------------------------------------------------------------------------- 1 | ## 总结 2 | Protocol Buffer的性能好,主要体现在 序列化后的数据体积小 & 序列化速度快,最终使得传输效率高,其原因如下: 3 | 4 | ### 序列化速度快的原因: 5 | 6 | * 编码 / 解码 方式简单(只需要简单的数学运算 = 位移等等) 7 | * 采用 Protocol Buffer 自身的框架代码 和 编译器 共同完成 8 | * 序列化后的数据量体积小(即数据压缩效果好)的原因: 9 | * Protocol Buffer 比 JSON 和 XML 少了 {、}、: 这些符号,体积也减少一些。再加上 varint 压缩,gzip 压缩以后体积更小! 10 | * Protocol Buffer 是 Tag - Value (Tag - Length - Value)的编码方式的实现,减少了分隔符的使用,数据存储更加紧凑,如Varint、Zigzag编码方式等等 11 | 12 | ### 缺点 13 | Protocol Buffer 不是自我描述的,离开了数据描述 .proto 文件,就无法理解二进制数据流。这点即是优点,使数据具有一定的“加密性”,也是缺点,数据可读性极差。所以 Protocol Buffer 非常适合内部服务之间 RPC 调用和传递数据 14 | 15 | ### 实现一个 RPC 16 | 如何在整个过程中贯穿 protobuf的使用?绕过JSON.stringify的编码 -------------------------------------------------------------------------------- /packages/mini-rpc/readme.md: -------------------------------------------------------------------------------- 1 | ## Introduction 2 | 3 | mini implementation of [sofa-rpc-node](https://github.com/sofastack/sofa-rpc-node/blob/master/example/server.js) 4 | 5 | ## How to test 6 | ``` 7 | npm install 8 | 9 | npm run server 10 | npm run client 11 | ``` 12 | ## Reference 13 | 14 | ### RPC Framework 15 | 16 | * [sofa-rpc-node](https://github.com/sofastack/sofa-rpc-node/blob/master/example/server.js) 17 | * [guide-rpc-framework](https://github.com/Snailclimb/guide-rpc-framework) 18 | * [什么是RPC?](https://zhuanlan.zhihu.com/p/148139089) 19 | ### Protobuf 20 | 21 | * https://thecodebarbarian.com/working-with-protobufs-in-node-js.html 22 | * https://cloud.tencent.com/developer/article/1705743?from=article.detail.1705742 23 | * [protobuf 编码](https://taoshu.in/pb-encoding.html) -------------------------------------------------------------------------------- /packages/mini-rpc/src/client.ts: -------------------------------------------------------------------------------- 1 | import { Registry, interfaceOption, Metadata } from './registry' 2 | import { encode, request } from './util'; 3 | import protobuf from 'protobufjs' 4 | 5 | export { 6 | RpcClient 7 | } 8 | 9 | type RpcClientOptions = { 10 | logger:any; 11 | registry: Registry 12 | } 13 | 14 | const log = (...args: any[]) => console.log('[client.ts]', ...args) 15 | 16 | class RpcClient { 17 | registry:Registry; 18 | 19 | constructor(options: RpcClientOptions){ 20 | this.registry = options.registry 21 | } 22 | createConsumer(interfaceOption: interfaceOption){ 23 | let consumer = this.registry.createConsumer(interfaceOption) 24 | let url = ''; 25 | return { 26 | async ready() { 27 | let metaData = await consumer 28 | url = metaData.address 29 | }, 30 | async invoke(methodName: string, params: any[], options: { responseTimeout: number }) { 31 | 32 | log('url', url); 33 | return request.post({ 34 | url, 35 | path: interfaceOption.interfaceName, 36 | body: { 37 | params, 38 | methodName 39 | }, 40 | timeout: options.responseTimeout 41 | }).then((data:any)=>encode.deserialize(data)) 42 | } 43 | } 44 | } 45 | } -------------------------------------------------------------------------------- /packages/mini-rpc/test/sofa-rpc/client.ts: -------------------------------------------------------------------------------- 1 | import { RpcClient, Registry } from '../../index' 2 | import protobuf from 'protobufjs' 3 | 4 | const logger = console; 5 | 6 | const registry = new Registry({ 7 | logger, 8 | address: '127.0.0.1:2181' 9 | }); 10 | 11 | async function invoke() { 12 | const root = await protobuf.load(__dirname+'/proto/employee.proto'); 13 | const EmployeeRequest = root.lookupType('employee.EmployeeRequest'); 14 | const EmployeeDetails = root.lookupType('employee.EmployeeDetails'); 15 | const client = new RpcClient({ 16 | logger, 17 | registry, 18 | }); 19 | const consumer = client.createConsumer({ 20 | interfaceName: 'com.nodejs.test.TestService', 21 | }); 22 | await consumer.ready(); 23 | 24 | let count: number = 0 25 | const result = await consumer.invoke('plus', [count, count + 1], { responseTimeout: 1000 }); 26 | console.log(`${count} + ${count + 1} = ` + result); 27 | 28 | let employId = 1, 29 | encoded = EmployeeRequest.encode({ id: employId}).finish() 30 | console.log('encoded employee data', encoded, Object.prototype.toString.call(encoded)) 31 | 32 | const result2:any = await consumer.invoke('getDetails', [encoded], { responseTimeout: 1000 }); 33 | console.log('result2:', result2) 34 | console.log(Buffer.from(result2)) 35 | console.log(`employee ${employId} info: ` , EmployeeDetails.decode(Buffer.from(result2))); 36 | } 37 | 38 | invoke().catch(console.error); 39 | -------------------------------------------------------------------------------- /packages/mini-rpc/test/sofa-rpc/data.ts: -------------------------------------------------------------------------------- 1 | export { 2 | employees 3 | } 4 | 5 | let employees = [{ 6 | id: 1, 7 | email: "abcd@abcd.com", 8 | firstName: "First1", 9 | lastName: "Last1" 10 | }, 11 | { 12 | id: 2, 13 | email: "xyz@xyz.com", 14 | firstName: "First2", 15 | lastName: "Last2" 16 | }, 17 | { 18 | id: 3, 19 | email: "temp@temp.com", 20 | firstName: "First3", 21 | lastName: "Last3" 22 | }, 23 | ]; -------------------------------------------------------------------------------- /packages/mini-rpc/test/sofa-rpc/proto/employee.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package employee; 4 | 5 | service Employee { 6 | 7 | rpc getDetails(EmployeeRequest) returns (EmployeeResponse) {} 8 | } 9 | 10 | message EmployeeRequest { int32 id = 1; } 11 | 12 | message EmployeeResponse { EmployeeDetails message = 1; } 13 | message EmployeeDetails { 14 | int32 id = 1; 15 | string email = 2; 16 | string firstName = 3; 17 | string lastName = 4; 18 | } -------------------------------------------------------------------------------- /packages/mini-rpc/test/sofa-rpc/proto/math.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package math; 4 | 5 | service Method { rpc plus(AddRequest) returns (AddResponse){} } 6 | 7 | message AddRequest { repeated int32 id = 1; } 8 | 9 | message AddResponse { int32 message = 1; } 10 | // message EmployeeDetails { 11 | // int32 id = 1; 12 | // string email = 2; 13 | // string firstName = 3; 14 | // string lastName = 4; 15 | // } -------------------------------------------------------------------------------- /packages/mini-rpc/test/sofa-rpc/readme.md: -------------------------------------------------------------------------------- 1 | 2 | ## How to test 3 | ```bash 4 | ts-node ./server.ts 5 | ts-node ./client.ts 6 | ``` 7 | 8 | ## reference 9 | 10 | * [sofa-rpc-node](https://github.com/sofastack/sofa-rpc-node/blob/master/example/server.js) -------------------------------------------------------------------------------- /packages/mini-rpc/test/sofa-rpc/server.ts: -------------------------------------------------------------------------------- 1 | import protobuf from 'protobufjs' 2 | import { RpcServer, Registry } from '../../index' 3 | import { employees } from './data' 4 | 5 | const logger = console; 6 | 7 | const registry = new Registry({ 8 | logger, 9 | address: '127.0.0.1:2181', 10 | }); 11 | 12 | let port = 12200 13 | 14 | 15 | async function start(){ 16 | const root = await protobuf.load(__dirname+'/proto/employee.proto'); 17 | const EmployeeRequest = root.lookupType('employee.EmployeeRequest'); 18 | const EmployeeDetails = root.lookupType('employee.EmployeeDetails'); 19 | const server = new RpcServer({ 20 | logger, 21 | registry, 22 | port, 23 | }); 24 | 25 | server.addService({ 26 | interfaceName: 'com.nodejs.test.TestService', 27 | }, { 28 | async plus(a: number, b: number) { 29 | return new Promise(resolve => { 30 | setTimeout(() => { 31 | 32 | resolve(a + b); 33 | }, 1000) 34 | }) 35 | }, 36 | // getDetails(request: { id: number }) { 37 | getDetails(request: any) { 38 | console.log('request', request) 39 | 40 | let req: any = EmployeeRequest.decode(Buffer.from(request)) 41 | console.log('req',req) 42 | let res= employees.find(employee => employee.id === req['id']) 43 | if(!res){ 44 | return { 45 | msg:'not found' 46 | } 47 | } 48 | console.log(EmployeeDetails.encode(res).finish()); 49 | 50 | return EmployeeDetails.encode(res).finish(); 51 | } 52 | }); 53 | await server.start() 54 | server.publish(); 55 | } 56 | 57 | start() 58 | -------------------------------------------------------------------------------- /packages/mini-rpc/todo.md: -------------------------------------------------------------------------------- 1 | * client timeout not not working 2 | * try proto buffer (partially tried) 3 | * 负载均衡、容错等功能 4 | * rpc调用都要打点? -------------------------------------------------------------------------------- /packages/mini-svelte/.gitignore: -------------------------------------------------------------------------------- 1 | src/**/*.js 2 | test/**/*.js -------------------------------------------------------------------------------- /packages/mini-svelte/demo/.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules/ 2 | /dist/ 3 | /.vscode/ 4 | .DS_Store 5 | -------------------------------------------------------------------------------- /packages/mini-svelte/demo/README.md: -------------------------------------------------------------------------------- 1 | # Svelte + TS + Vite 2 | ``` 3 | nvm use 14 4 | cd ../ 5 | npm run demo 6 | ``` 7 | 8 | -------------------------------------------------------------------------------- /packages/mini-svelte/demo/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Svelte + TS + Vite App 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /packages/mini-svelte/demo/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "demo", 3 | "version": "0.0.0", 4 | "type": "module", 5 | "scripts": { 6 | "dev": "vite", 7 | "build": "vite build", 8 | "preview": "vite preview", 9 | "check": "svelte-check --tsconfig ./tsconfig.json" 10 | }, 11 | "devDependencies": { 12 | "@sveltejs/vite-plugin-svelte": "^1.0.0-next.31", 13 | "@tsconfig/svelte": "^2.0.1", 14 | "svelte": "^3.44.0", 15 | "svelte-check": "^2.2.7", 16 | "svelte-preprocess": "^4.9.8", 17 | "tslib": "^2.3.1", 18 | "typescript": "^4.4.4", 19 | "vite": "^2.7.1", 20 | "mini-svelte": "../../mini-svelte" 21 | }, 22 | "dependencies": {} 23 | } 24 | -------------------------------------------------------------------------------- /packages/mini-svelte/demo/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wizardpisces/experiment/3e24f6597819cb3918e0ba1c8d443acde70795a6/packages/mini-svelte/demo/public/favicon.ico -------------------------------------------------------------------------------- /packages/mini-svelte/demo/src/App.svelte: -------------------------------------------------------------------------------- 1 | 10 | 11 |

Hello {name} Typescript!

12 | 15 | 16 | 17 | 18 | 19 | 31 | -------------------------------------------------------------------------------- /packages/mini-svelte/demo/src/assets/svelte.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wizardpisces/experiment/3e24f6597819cb3918e0ba1c8d443acde70795a6/packages/mini-svelte/demo/src/assets/svelte.png -------------------------------------------------------------------------------- /packages/mini-svelte/demo/src/lib/Counter.svelte: -------------------------------------------------------------------------------- 1 | 10 | 11 |

{name}:own count: {count}!

12 | 15 | 16 | -------------------------------------------------------------------------------- /packages/mini-svelte/demo/src/lib/Prop.svelte: -------------------------------------------------------------------------------- 1 | 4 | 5 |

Child1: prop from parent:{prop1}

-------------------------------------------------------------------------------- /packages/mini-svelte/demo/src/main.ts: -------------------------------------------------------------------------------- 1 | import App from './App.svelte' 2 | // import App from './lib/Counter.svelte' 3 | 4 | const app = new App({ 5 | target: document.getElementById('app') 6 | }) 7 | 8 | export default app 9 | -------------------------------------------------------------------------------- /packages/mini-svelte/demo/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | -------------------------------------------------------------------------------- /packages/mini-svelte/demo/svelte.config.js: -------------------------------------------------------------------------------- 1 | import sveltePreprocess from 'svelte-preprocess' 2 | 3 | export default { 4 | // Consult https://github.com/sveltejs/svelte-preprocess 5 | // for more information about preprocessors 6 | // preprocess: sveltePreprocess() 7 | } 8 | -------------------------------------------------------------------------------- /packages/mini-svelte/demo/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@tsconfig/svelte/tsconfig.json", 3 | "compilerOptions": { 4 | "esModuleInterop": true, 5 | "target": "esnext", 6 | "useDefineForClassFields": true, 7 | "module": "esnext", 8 | "resolveJsonModule": true, 9 | "baseUrl": ".", 10 | /** 11 | * Typecheck JS in `.svelte` and `.js` files by default. 12 | * Disable checkJs if you'd like to use dynamic types in JS. 13 | * Note that setting allowJs false does not prevent the use 14 | * of JS in `.svelte` files. 15 | */ 16 | "allowJs": true, 17 | "checkJs": true 18 | }, 19 | "include": ["src/**/*.d.ts", "src/**/*.ts", "src/**/*.js", "src/**/*.svelte"] 20 | } 21 | -------------------------------------------------------------------------------- /packages/mini-svelte/demo/vite.config.js: -------------------------------------------------------------------------------- 1 | import path from 'path' 2 | import { 3 | fileURLToPath 4 | } from 'url'; 5 | import { defineConfig } from 'vite' 6 | import { svelte } from '@sveltejs/vite-plugin-svelte' 7 | import { 8 | miniSveltePlugin 9 | } from '../src/index.js' 10 | 11 | /** 12 | * fix ReferenceError: __dirname is not defined in ES module scope in Nod@14 13 | * https: //stackoverflow.com/questions/64383909/dirname-is-not-defined-in-node-14-version 14 | */ 15 | 16 | const ___filename = fileURLToPath(import.meta.url); 17 | const ___dirname = path.dirname(___filename); 18 | 19 | // https://vitejs.dev/config/ 20 | export default defineConfig({ 21 | // plugins:[svelte()] 22 | plugins: [miniSveltePlugin()], 23 | resolve: { 24 | alias: { 25 | '@miniSvelte': path.resolve(___dirname, '../src'), 26 | }, 27 | } 28 | }) 29 | -------------------------------------------------------------------------------- /packages/mini-svelte/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mini-svelte", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "demo": "npm run tsc && cd demo && npm run dev", 8 | "rmjs": "find ./src -name '*.js' | xargs rm -f", 9 | "tsc": "tsc -p tsconfig.json", 10 | "test": "npm run rmjs && node -r ts-node/register test/index.ts" 11 | }, 12 | "author": "刘泽", 13 | "license": "MTT", 14 | "engines": { 15 | "node": ">=14.x" 16 | }, 17 | "devDependencies": { 18 | "@types/node": "^17.0.8", 19 | "acorn": "^8.7.0", 20 | "typescript": "^4.5.4", 21 | "vite": "^2.7.10" 22 | }, 23 | "dependencies": { 24 | "@types/estree": "0.0.50" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /packages/mini-svelte/readme.md: -------------------------------------------------------------------------------- 1 | ## Introdution 2 | [Svelte](https://svelte.dev/) in mini version 3 | 4 | implement in simple ways + demo needs 5 | ### How to run demo 6 | ```bash 7 | # node>=14.x 8 | npm run demo 9 | ``` 10 | ### How to run test 11 | #### Terminal 12 | ```bash 13 | npm run test 14 | ``` 15 | #### Use vscode debug and run 16 | ```bash 17 | npm run rmjs #then click vscode run and debug, then choose mini-svelte to run (reference experiment/.vscode/launch.json) 18 | ``` 19 | 20 | ### Feature 21 | 22 | #### Step1(demo stable commitID f10ecd8) 23 | * [x] .svelte single-file component compile 24 | * [x] runtime code generation (dirty-check/event-binding/create/mount/update etc) 25 | * [x] reactivity assignment 26 | * [x] vite plugin 27 | 28 | #### Step2 29 | * [x] scheduler (commitId: 7b4405a) 30 | * [x] nested components (commitId: 77296d6) 31 | * [x] prop injection (commitId: 65fe9a9) 32 | 33 | #### Step3 34 | * [] slot 35 | * [] nested dom (use htmlparser2) 36 | * [] if-block 37 | * [] scoped css 38 | * ... 39 | 40 | ## Reference 41 | 42 | * https://astexplorer.net/ 43 | * https://github.com/sveltejs/svelte 44 | * https://github.com/wizardpisces/js-ziju 45 | * https://github.com/wizardpisces/tiny-sass-compiler 46 | * [前端框架思考](https://wizardpisces.github.io/blog/%E5%89%8D%E7%AB%AF%E6%A1%86%E6%9E%B6%E6%80%9D%E8%80%83) 47 | -------------------------------------------------------------------------------- /packages/mini-svelte/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './vite-plugin/index' 2 | export * from './parse' -------------------------------------------------------------------------------- /packages/mini-svelte/src/internal/scheduler.ts: -------------------------------------------------------------------------------- 1 | export { 2 | queueJob, 3 | nextTick, 4 | SchedulerJob 5 | } 6 | type SchedulerJob = () => any 7 | 8 | let queue: SchedulerJob[] = [] 9 | 10 | let isFlushPending = false; 11 | let isFlushing = false; 12 | const resolvedPromise: Promise = Promise.resolve() 13 | let currentFlushPromise: Promise | null = null 14 | 15 | function flushJobs() { 16 | isFlushing = true 17 | console.log('flushJobs', queue.length) 18 | /** 19 | * for let of 的迭代方式是动态的,可以比较好的满足更新调度过程中新插入的更新任务 20 | * forEach的方式是静态的,没法满足动态添加任务的需求 21 | */ 22 | for(let job of queue){ 23 | job() 24 | } 25 | queue = [] // empty queue 26 | } 27 | 28 | // mainly for batch update schedule 29 | function queueJob(job: SchedulerJob) { 30 | if (!queue.includes(job)) { 31 | queue.push(job) 32 | } 33 | queueFlush() 34 | } 35 | 36 | function queueFlush() { 37 | if (!isFlushing && !isFlushPending) { 38 | isFlushPending = true 39 | currentFlushPromise = resolvedPromise.then(flushJobs).then(_ => { isFlushing = false; isFlushPending = false }) 40 | } 41 | } 42 | 43 | function nextTick( 44 | this: T, 45 | fn?: (this: T) => void 46 | ): Promise { 47 | const p = currentFlushPromise || resolvedPromise 48 | return fn ? p.then(this ? fn.bind(this) : fn) : p 49 | } -------------------------------------------------------------------------------- /packages/mini-svelte/src/parse/compile-script/Tree.ts: -------------------------------------------------------------------------------- 1 | import { ParseContext } from "../" 2 | 3 | export class Tree { 4 | // todo : should be more specific 5 | ast: any 6 | constructor(ast: any) { 7 | this.ast = ast 8 | } 9 | toCode(context:ParseContext,kind?:string): string { 10 | return '' 11 | } 12 | 13 | evaluate(context?: ParseContext) { 14 | 15 | } 16 | } -------------------------------------------------------------------------------- /packages/mini-svelte/src/parse/compile-script/compile.ts: -------------------------------------------------------------------------------- 1 | import ESTree from 'estree' 2 | // acorn: The return value will be an abstract syntax tree object as specified by the ESTree spec 3 | import { parse } from 'acorn' 4 | import { ParseContext } from '..' 5 | import { dispatchStatementToCode } from './statements' 6 | 7 | export { compile } 8 | 9 | function compile(context: ParseContext) { 10 | let { 11 | code 12 | } = context 13 | 14 | // @ts-ignore 15 | let ast: ESTree.Program = parse(code, { ecmaVersion: 'latest', sourceType:'module' }); 16 | 17 | ast.body.forEach(node => { 18 | dispatchStatementToCode(node, context) 19 | }) 20 | } -------------------------------------------------------------------------------- /packages/mini-svelte/src/parse/compile-script/context.ts: -------------------------------------------------------------------------------- 1 | import { ScriptCompileContext } from ".."; 2 | 3 | export { 4 | createScriptCompileContext 5 | } 6 | function createScriptCompileContext(): ScriptCompileContext { 7 | 8 | let runtimeBlockCode: string[] = [], 9 | inRuntimeCodeGeneration = false; 10 | // component props 11 | let props: Set = new Set() 12 | let importStr = '' 13 | let context = { 14 | turnOnRuntimeCodeGeneration() { 15 | inRuntimeCodeGeneration = true 16 | }, 17 | addRuntimeCode(str: string) { 18 | if (!inRuntimeCodeGeneration) return 19 | runtimeBlockCode.push(str) 20 | }, 21 | isInRuntimeCodeGeneration() { 22 | return inRuntimeCodeGeneration 23 | }, 24 | flushRuntimeBlockCode() { 25 | inRuntimeCodeGeneration = false 26 | let code = runtimeBlockCode.join('\n') 27 | runtimeBlockCode = [] 28 | return code 29 | }, 30 | addScriptImport(newImport: string) { 31 | importStr += newImport; 32 | }, 33 | 34 | getScriptImport(): string { 35 | return importStr 36 | }, 37 | 38 | inPropCollecting : false, 39 | addProps(pName: string) { 40 | if(context.inPropCollecting){ 41 | props.add(pName) 42 | } 43 | }, 44 | getProps() { 45 | return props 46 | } 47 | } 48 | return context 49 | } -------------------------------------------------------------------------------- /packages/mini-svelte/src/parse/compile-script/index.ts: -------------------------------------------------------------------------------- 1 | export * from './compile' -------------------------------------------------------------------------------- /packages/mini-svelte/src/parse/compile-script/type.ts: -------------------------------------------------------------------------------- 1 | import EStree from 'estree' 2 | export * from "./environment/Environment" 3 | export { 4 | NodeTypes, 5 | ProgramBodyItem, 6 | ScriptCompileContext 7 | } 8 | type ProgramBodyItem = (EStree.Program['body'])[number]; 9 | const enum NodeTypes { 10 | //ExpressionStatement 11 | MemberExpression = 'MemberExpression', 12 | AssignmentExpression = 'AssignmentExpression', 13 | ExpressionStatement = 'ExpressionStatement', 14 | BinaryExpression = 'BinaryExpression', 15 | CallExpression = 'CallExpression', 16 | UpdateExpression = 'UpdateExpression', 17 | Identifier = 'Identifier', 18 | Literal = 'Literal', 19 | 20 | VariableDeclaration = 'VariableDeclaration', 21 | VariableDeclarator = 'VariableDeclarator', 22 | 23 | FunctionDeclaration = 'FunctionDeclaration', 24 | ImportDeclaration = 'ImportDeclaration', 25 | ExportNamedDeclaration = 'ExportNamedDeclaration', 26 | 27 | BlockStatement = 'BlockStatement', 28 | 29 | ReturnStatement = 'ReturnStatement', 30 | 31 | // flow control 32 | WhileStatement = 'WhileStatement', 33 | IfStatement = 'IfStatement', 34 | } 35 | 36 | type ScriptCompileContext = { 37 | 38 | addScriptImport: (i: string) => void 39 | getScriptImport: () => string 40 | 41 | turnOnRuntimeCodeGeneration: () => void 42 | isInRuntimeCodeGeneration: () => boolean 43 | addRuntimeCode: (code: string) => void 44 | flushRuntimeBlockCode: () => string 45 | 46 | inPropCollecting:boolean 47 | addProps(pName: string): void 48 | getProps: () => Set 49 | } -------------------------------------------------------------------------------- /packages/mini-svelte/src/parse/compile-script/variableDeclaration.ts: -------------------------------------------------------------------------------- 1 | import ESTree from 'estree' 2 | import { Tree } from './Tree'; 3 | import { NodeTypes, ParseContext as Context } from '../type'; 4 | export { 5 | VariableDeclarator, 6 | VariableDeclaration 7 | } 8 | 9 | class VariableDeclarator extends Tree { 10 | declare ast: ESTree.VariableDeclarator; 11 | constructor(ast: ESTree.VariableDeclarator) { 12 | super(ast) 13 | } 14 | 15 | toCode(context: Context, kind: string) { 16 | let { env } = context 17 | if (this.ast.id.type === NodeTypes.Identifier) { 18 | 19 | context.scriptCompileContext.addProps(this.ast.id.name) 20 | 21 | if (this.ast.init) { 22 | if (this.ast.init!.type === NodeTypes.Literal) { 23 | env.def(this.ast.id.name, (this.ast.init).value) 24 | env.defCode(this.ast.id.name, `${kind} ${this.ast.id.name}=${JSON.stringify((this.ast.init).value)};`) 25 | } else if (this.ast.init!.type === NodeTypes.Identifier) { 26 | env.def(this.ast.id.name, env.get((this.ast.init).name)) 27 | env.defCode(this.ast.id.name, `${this.ast.id.name}=${(this.ast.init).name};`) 28 | } 29 | }else{ 30 | env.def(this.ast.id.name, undefined) 31 | } 32 | } 33 | return '' 34 | } 35 | } 36 | 37 | class VariableDeclaration extends Tree { 38 | declare ast: ESTree.VariableDeclaration; 39 | constructor(ast: ESTree.VariableDeclaration) { 40 | super(ast) 41 | } 42 | 43 | toCode(context: Context): string { 44 | let { declarations, kind } = this.ast 45 | 46 | for (let i = 0, len = declarations.length; i < len; i++) { 47 | if (declarations[i].type === NodeTypes.VariableDeclarator) { 48 | new VariableDeclarator(declarations[i]).toCode(context, kind) 49 | } 50 | } 51 | return '' 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /packages/mini-svelte/src/parse/compile-template/context.ts: -------------------------------------------------------------------------------- 1 | import { Kind } from ".." 2 | import { emitError } from "../util"; 3 | 4 | export { 5 | createTemplateCompileContext 6 | } 7 | function createTemplateCompileContext(){ 8 | let templateReferencedNameTypeMap: Map = new Map(); 9 | /** 10 | * map的遍历是有序的,可以很好模拟声明的顺序 11 | * value被修改后也不会改变key的顺序 12 | */ 13 | let templateReferencedNameIndexRecord: Record = {} 14 | let context = { 15 | tagList: [], 16 | ctxPositionAndDomOrComponentDeclarationsMap: new Map(), 17 | addTemplateReferencedName: (name: string, type: Kind = Kind.VariableDeclarator) => { 18 | let index 19 | if (templateReferencedNameTypeMap.has(name)) { 20 | index = context.getTemplateReferencedIndexByName(name) 21 | } else { 22 | templateReferencedNameTypeMap.set(name, type) 23 | index = templateReferencedNameIndexRecord[name] = templateReferencedNameTypeMap.size - 1 24 | } 25 | return index 26 | }, 27 | getTemplateReferencedNameTypeMap() { 28 | return templateReferencedNameTypeMap 29 | }, 30 | getTemplateReferencedIndexByName(name: string) { 31 | let index = templateReferencedNameIndexRecord[name] 32 | if (typeof index === undefined) { 33 | emitError(`[parseMain]:${name} is not defined`) 34 | } 35 | return index 36 | } 37 | } 38 | return context 39 | } -------------------------------------------------------------------------------- /packages/mini-svelte/src/parse/compile-template/index.ts: -------------------------------------------------------------------------------- 1 | export * from './parseTemplate' 2 | export * from './codeGen' 3 | export * from './type' -------------------------------------------------------------------------------- /packages/mini-svelte/src/parse/compile-template/type.ts: -------------------------------------------------------------------------------- 1 | import { Kind } from "../compile-script/environment/Environment" 2 | 3 | export { 4 | TemplateCompileContext, 5 | TemplateNodeTypes, 6 | EventType, 7 | PropType 8 | } 9 | 10 | 11 | enum TemplateNodeTypes { 12 | text = "text", 13 | element = "element", 14 | component = "component" 15 | } 16 | 17 | type RuntimeDeclarationMap = Map 18 | 19 | // not support nested dom yet 20 | type TagChildType = { 21 | type: string, 22 | content: string, 23 | domOrComponentDeclarationName: string 24 | } 25 | 26 | type EventType = { 27 | eventName: string 28 | handlerName: string 29 | } 30 | 31 | type PropType = { 32 | propName: string, 33 | propValueName: string, 34 | ctxPosition:number // for props update 35 | } 36 | 37 | type TemplateCompileContext = { 38 | ctxPositionAndDomOrComponentDeclarationsMap: Map // mainly for code generation of dirty check and update eg: $$invalidate 39 | tagList: { type: TemplateNodeTypes, tagName: string, children: TagChildType[], eventList: EventType[], props: PropType[] }[] 40 | addTemplateReferencedName: (name: string, type?: Kind) => number 41 | getTemplateReferencedNameTypeMap: () => RuntimeDeclarationMap 42 | getTemplateReferencedIndexByName:(name: string)=>number 43 | } 44 | -------------------------------------------------------------------------------- /packages/mini-svelte/src/parse/compileScript.ts: -------------------------------------------------------------------------------- 1 | import { ParseContext } from './type' 2 | import { compile } from './compile-script' 3 | 4 | export { 5 | compileScript 6 | } 7 | function compileScript(context: ParseContext) { 8 | let { rawScript } = context 9 | 10 | rawScript = transformScript(rawScript) 11 | 12 | compile({ ...context, code: rawScript }) 13 | } 14 | 15 | /** 16 | * TODO: 17 | * transform code(maybe ts or svelte specific script) to ESM standard code for acorn parse 18 | */ 19 | function transformScript(code: string) { 20 | return code 21 | } 22 | -------------------------------------------------------------------------------- /packages/mini-svelte/src/parse/compileStyle.ts: -------------------------------------------------------------------------------- 1 | import { ParseContext } from "./type" 2 | export { 3 | compileStyle 4 | } 5 | function compileStyle(context: ParseContext) { 6 | let { rawStyle } = context 7 | context.styleCode = rawStyle 8 | } 9 | -------------------------------------------------------------------------------- /packages/mini-svelte/src/parse/compileTemplate.ts: -------------------------------------------------------------------------------- 1 | import { codeGen, parseTemplate } from "./compile-template"; 2 | import { compileScript } from "./compileScript"; 3 | import { ParseContext as Context } from "./type"; 4 | 5 | export { 6 | compileTemplate 7 | } 8 | 9 | function compileTemplate(context: Context) { 10 | parseTemplate(context)//compile Template to gen variable dep Map for script compile 11 | compileScript(context) 12 | codeGen(context) 13 | } -------------------------------------------------------------------------------- /packages/mini-svelte/src/parse/index.ts: -------------------------------------------------------------------------------- 1 | export * from './type' 2 | 3 | export * from './parseMain' -------------------------------------------------------------------------------- /packages/mini-svelte/src/parse/type.ts: -------------------------------------------------------------------------------- 1 | import { Environment } from "./compile-script/type" 2 | import { TemplateCompileContext } from './compile-template/type'; 3 | import { ScriptCompileContext } from './compile-script/type'; 4 | export * from './compile-script/type' 5 | export * from './compile-template/type' 6 | export { 7 | Descriptor, 8 | ParseContext, 9 | } 10 | 11 | type Descriptor = { 12 | script: string 13 | style: string 14 | template: string 15 | } 16 | 17 | type ParseContext = { 18 | code: string 19 | env: Environment 20 | 21 | scriptCode: string 22 | styleCode: string 23 | templateCode: string 24 | 25 | rawScript: string 26 | rawStyle: string 27 | rawTemplate: string 28 | 29 | componentNameSet:Set 30 | 31 | templateCompileContext: TemplateCompileContext 32 | scriptCompileContext: ScriptCompileContext 33 | } 34 | 35 | -------------------------------------------------------------------------------- /packages/mini-svelte/src/parse/util.ts: -------------------------------------------------------------------------------- 1 | export { 2 | emitError, 3 | opAcMap, 4 | isNumber 5 | } 6 | function emitError(msg:string){ 7 | throw Error(msg) 8 | } 9 | 10 | const isNumber = (t: any): t is number => typeof t === 'number' 11 | 12 | const opAcMap:Record = { 13 | // '=': (left, right) => left = right, 14 | '||': (left:any, right:any) => left || right, 15 | '&&': (left:any, right:any) => left && right, 16 | 17 | '==': (left:any, right:any) => left == right, 18 | '!=': (left:any, right:any) => left != right, 19 | '>=': (left:any, right:any) => left >= right, 20 | '<=': (left:any, right:any) => left <= right, 21 | 22 | '>': (left:any, right:any) => left > right, 23 | '<': (left:any, right:any) => left < right, 24 | 25 | '+': (left:any, right:any) => left + right, 26 | '-': (left:any, right:any) => left - right, 27 | '/': (left:any, right:any) => left / right, 28 | '*': (left:any, right:any) => left * right, 29 | '%': (left:any, right:any) => left % right, 30 | 31 | }; -------------------------------------------------------------------------------- /packages/mini-svelte/src/vite-plugin/index.ts: -------------------------------------------------------------------------------- 1 | import { PluginOption } from 'vite' 2 | import { transformMain } from "./transformMain" 3 | export { 4 | miniSveltePlugin 5 | } 6 | function miniSveltePlugin(): PluginOption { 7 | function filter(filename: string) { 8 | return filename.endsWith('.svelte') 9 | } 10 | 11 | return { 12 | name: 'vite:mini-svelte', 13 | 14 | async transform(code, id) { 15 | const { filename } = parseRequest(id) 16 | if (!filter(filename)) { 17 | return null 18 | }else{ 19 | return transformMain(code, id) 20 | } 21 | 22 | } 23 | 24 | } 25 | } 26 | 27 | function parseRequest(id: string): { 28 | filename: string 29 | } { 30 | const [filename, rawQuery] = id.split(`?`, 2) 31 | return { 32 | filename 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /packages/mini-svelte/src/vite-plugin/readme.md: -------------------------------------------------------------------------------- 1 | # vite plugin -------------------------------------------------------------------------------- /packages/mini-svelte/src/vite-plugin/transformMain.ts: -------------------------------------------------------------------------------- 1 | import { Descriptor, parseMain } from "../parse" 2 | 3 | export { transformMain } 4 | 5 | function transformMain(code: string, id: string) { 6 | const output: string[] = [] 7 | 8 | let descriptor: Descriptor = parseMain(code) 9 | 10 | output.push(genStyle(descriptor)) 11 | output.push(genTemplate(descriptor)) 12 | 13 | return output.join('\n'); 14 | } 15 | 16 | function genTemplate(descriptor: Descriptor) { 17 | return descriptor.template 18 | } 19 | 20 | // for vite hot and injection 21 | function genStyle(descriptor: Descriptor) { 22 | let styleCode = ` 23 | import { updateStyle, removeStyle } from "/@vite/client"; 24 | const id = 0; // TODOS 目前只解析一块 css 25 | const css = \`${descriptor.style}\`; 26 | updateStyle(id, css); 27 | ` 28 | return styleCode 29 | } -------------------------------------------------------------------------------- /packages/mini-svelte/test/index.ts: -------------------------------------------------------------------------------- 1 | import { Descriptor, parseMain } from "../src/parse/index" 2 | import fs from 'fs' 3 | let code = ` 4 | 13 | 14 | 15 |

Hello {name} Typescript!

16 | 19 | 20 | 21 | 22 | 23 | 24 | 36 | 37 | 38 | ` 39 | 40 | let descriptor = parseMain(code) 41 | 42 | console.log(descriptor.script) 43 | console.log(descriptor.style) 44 | console.log(descriptor.template) 45 | fs.writeFileSync(__dirname+'/result.js', descriptor.template,'utf8') 46 | console.log('\ncompile success.....') -------------------------------------------------------------------------------- /packages/mini-svelte/test/result.js: -------------------------------------------------------------------------------- 1 | import {element, text, listen, insert, append, set_data, create_component, mount_component, init, MiniSvelteComponent} from "@miniSvelte/internal/index.ts"; 2 | import Counter from "./lib/Counter.svelte";import Prop from "./lib/Prop.svelte"; 3 | function create_fragment(ctx) { 4 | let h1; 5 | let t0; 6 | let t1; 7 | let button; 8 | let t2; 9 | let t3; 10 | let t4; 11 | let t5; 12 | let block = { 13 | c: function create() { 14 | 15 | h1 = element("h1"); 16 | t0 = text("Hello "); 17 | t1 = text(ctx[0]); 18 | 19 | 20 | button = element("button"); 21 | t2 = text("\n "); 22 | t3 = text(ctx[0]); 23 | t4 = text(" Clicked "); 24 | t5 = text(ctx[2]); 25 | 26 | }, 27 | m: function mount(target,anchor){ 28 | 29 | insert(target,h1,anchor); 30 | append(h1,t0) 31 | append(h1,t1) 32 | 33 | 34 | 35 | 36 | insert(target,button,anchor); 37 | append(button,t2) 38 | append(button,t3) 39 | append(button,t4) 40 | append(button,t5) 41 | listen(button,"click",ctx[1]);/*click|inc*/ 42 | 43 | 44 | }, 45 | p: function patch(ctx,[dirty]){ 46 | if(dirty === 0) set_data(t1,ctx[dirty]); 47 | if(dirty === 0) set_data(t3,ctx[dirty]); 48 | if(dirty === 2) set_data(t5,ctx[dirty]); 49 | 50 | 51 | console.log('dirty checked',ctx,dirty) 52 | } 53 | } 54 | return block 55 | } 56 | 57 | function instance($$invalidate,$$props,$$self){ 58 | 59 | let name="parent"; 60 | function inc(){ 61 | $$invalidate(2,++count) 62 | } 63 | let count=0; 64 | return [name,inc,count] 65 | } 66 | 67 | export default class AppMiniSvelte extends MiniSvelteComponent{ 68 | constructor(options) { 69 | super() 70 | init(this, options,instance, create_fragment); 71 | } 72 | } -------------------------------------------------------------------------------- /packages/mini-svelte/thinking.md: -------------------------------------------------------------------------------- 1 | ## 开发过程中的一些思考 2 | 3 | ### 调度 4 | 1. 基本调度单元? 5 | 6 | >组件的更新函数 7 | 8 | 2. 如何调度父组件更新又触发子组件的更新? 9 | 10 | > 动态修改调度的数组,保证子组件的update函数能在父组件的更新job中实时push到调度的queue,保证了更新的一致性 11 | >>坑:queue的遍历不建议通过静态遍历方式,例如:forEach,而需要通关过动态遍历方式来做,例如:"for let of";*(ps:动静指是否实时对数组长度做求值,只有实时计算才能保证动态改变的调度队列也能被执行)* 12 | 13 | Svelte会根据template对于变量的引用 position,来聚合同一个变量变化引发的更新 14 | ### Prop传递实现思路 15 | 16 | 子组件中 17 | * 编译 export let prop,把export标记为组件的 prop 18 | * 把$$props注入到 instance方法中,解构出prop,并通过 ctx返回 19 | * 把实例本身 $$self注入到 Instance,并动态挂载 $$set方法,代理prop的更新处理,供parent组件调用 20 | 21 | 父组件中 22 | * 从模板中编译出组件对应的 props 23 | * Fragment声明阶段:实例化子组件时传入 props对象 24 | * Fragment 返回的p函数里面,动态根据子函数名生成props对象,并关联dirty与props的赋值,最后执行子实例 $set方法( -> 触发子组件的 $$set ->触发子组件的p脏检测及其更新) -------------------------------------------------------------------------------- /packages/mini-vite/.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: [push] 3 | jobs: 4 | build: 5 | name: Build, lint, and test on Node ${{ matrix.node }} and ${{ matrix.os }} 6 | 7 | runs-on: ${{ matrix.os }} 8 | strategy: 9 | matrix: 10 | node: ['10.x', '12.x', '14.x'] 11 | os: [ubuntu-latest, windows-latest, macOS-latest] 12 | 13 | steps: 14 | - name: Checkout repo 15 | uses: actions/checkout@v2 16 | 17 | - name: Use Node ${{ matrix.node }} 18 | uses: actions/setup-node@v1 19 | with: 20 | node-version: ${{ matrix.node }} 21 | 22 | - name: Install deps and build (with cache) 23 | uses: bahmutov/npm-install@v1 24 | 25 | - name: Lint 26 | run: yarn lint 27 | 28 | - name: Test 29 | run: yarn test --ci --coverage --maxWorkers=2 30 | 31 | - name: Build 32 | run: yarn build 33 | -------------------------------------------------------------------------------- /packages/mini-vite/.github/workflows/size.yml: -------------------------------------------------------------------------------- 1 | name: size 2 | on: [pull_request] 3 | jobs: 4 | size: 5 | runs-on: ubuntu-latest 6 | env: 7 | CI_JOB_NUMBER: 1 8 | steps: 9 | - uses: actions/checkout@v1 10 | - uses: andresz1/size-limit-action@v1 11 | with: 12 | github_token: ${{ secrets.GITHUB_TOKEN }} 13 | -------------------------------------------------------------------------------- /packages/mini-vite/.gitignore: -------------------------------------------------------------------------------- 1 | *.log 2 | .DS_Store 3 | node_modules 4 | dist 5 | -------------------------------------------------------------------------------- /packages/mini-vite/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 刘泽 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /packages/mini-vite/README.md: -------------------------------------------------------------------------------- 1 | ## Introduction 2 | 3 | mini implementation of [vite](https://github.com/vitejs/vite) 4 | 5 | ## How to develop 6 | 7 | ``` 8 | npm install 9 | npm install -g ts-node 10 | npm install -g nodemon 11 | 12 | cd template-vue-ts 13 | npm install 14 | 15 | cd .. 16 | nodemon 17 | ``` 18 | ## mini-vite 19 | 20 | 一步步源码分析的轻量版本 vite(适合对vite实现原理源码感兴趣,但是又觉得vite整体代码难啃的人) 21 | 22 | [原理分析](https://wizardpisces.github.io/blog/vite%20%E7%AE%80%E4%BB%8B%E4%B8%8E%E5%8E%9F%E7%90%86) 23 | 24 | ## mini-vite feature list 25 | 26 | **server frame** 27 | * ~~add rollup plugin handling system~~ 28 | * ~~add plugin-vue~~ 29 | * ~~basic http server~~ 30 | * sourceMap 31 | 32 | **HMR** 33 | * ~~hot .vue css (self accept situation)~~ 34 | * ~~hot .vue template~~ 35 | * ~~hot .vue script~~ 36 | * ~~hot deps accept (eg: vuex modules accept, not selfAccepet hot deps hot)~~ 37 | * sass hot 38 | * hot prune :hot 图动态维护,清理失效的对象 39 | 40 | **websocket** 41 | * ~~basic websocket~~ 42 | * ~~websocket断线重连~~ 43 | 44 | **css** 45 | * 处理 css的 @import? 46 | * 处理 sass,less etc 47 | 48 | **预处理** 49 | * ~~依赖搜集~~ 50 | * ~~依赖预处理 esm~~ 51 | * 依赖动态维护:dev时对新增以及删除的依赖做 动态图维护 52 | 53 | **vue** 54 | * ~~.vue render~~ 55 | * ~~support \` 9 | 10 | export default function htmlRewrite(serverDevContext:ServerDevContext) { 11 | 12 | return async (ctx: Context, next: Next) => { 13 | if(ctx.path === '/'){ 14 | let filename = path.join(serverDevContext.root,'index.html'), 15 | rawBody = fs.readFileSync(filename, 'utf-8'), 16 | content = rawBody.replace(//, ` ${clientScript}`) 17 | 18 | send(ctx.req, ctx.res, content,'html') 19 | } 20 | 21 | await next(); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /packages/mini-vite/src/middleware/static.ts: -------------------------------------------------------------------------------- 1 | // import path from 'path' 2 | import koaStatic from 'koa-static' 3 | import { ServerDevContext } from '../context'; 4 | 5 | let options = { 6 | setHeaders(res:any, pathname:string) { 7 | // Matches js, jsx, ts, tsx. 8 | // The reason this is done, is that the .ts file extension is reserved 9 | // for the MIME type video/mp2t. In almost all cases, we can expect 10 | // these files to be TypeScript files, and for Vite to serve them with 11 | // this Content-Type. 12 | if (/\.[tj]sx?$/.test(pathname)) { 13 | res.setHeader('Content-Type', 'application/javascript') 14 | } 15 | } 16 | } 17 | 18 | export default function staticMiddleware(serverDevContext:ServerDevContext) { 19 | return koaStatic(serverDevContext.root, options) 20 | } -------------------------------------------------------------------------------- /packages/mini-vite/src/middleware/transform.ts: -------------------------------------------------------------------------------- 1 | import path from 'path' 2 | import { Context, Next } from 'koa' 3 | import { ServerDevContext } from '../context' 4 | import { send } from '../send' 5 | import { transformRequest } from '../transformRequest' 6 | import { removeImportQuery } from '../util' 7 | // import { isImportRequest } from '../util' 8 | 9 | const knownIgnoreList = new Set(['/', '/favicon.ico', '/robots.txt']) 10 | 11 | const Transform_Not_Supported_AssetList = ['.png'] 12 | function isTransformSupportedAsset(filename: string) { 13 | let assetType = path.extname(filename).toLocaleLowerCase() 14 | if (Transform_Not_Supported_AssetList.includes(assetType)) { 15 | return false 16 | } 17 | return true 18 | } 19 | 20 | export default function transformMiddleware(serverDevContext: ServerDevContext) { 21 | 22 | return async (ctx: Context, next: Next) => { 23 | 24 | // skip asset that is not intended for transform 25 | if (ctx.method !== 'GET' 26 | || knownIgnoreList.has(ctx.path) 27 | || !isTransformSupportedAsset(ctx.path) 28 | || ctx.path.indexOf('dist/serviceWorker.js') > -1 29 | ) { 30 | 31 | return next() 32 | } 33 | 34 | //!isImportRequest(ctx.path) // eg: HMR dynamic import from client.ts 35 | 36 | let url = removeImportQuery(ctx.originalUrl) 37 | 38 | // resolve, load and transform using the plugin container 39 | const result = await transformRequest(url, serverDevContext) 40 | 41 | if (result) { 42 | return send(ctx.req, ctx.res, result.code) 43 | } 44 | 45 | await next(); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /packages/mini-vite/src/optimizer/esbuildDepPlugin.ts: -------------------------------------------------------------------------------- 1 | // import path from 'path' 2 | // import fs from 'fs' 3 | import { Plugin } from 'esbuild' 4 | import { ServerDevContext } from '../context'; 5 | import { createDebugger } from '../util'; 6 | 7 | let debug = createDebugger('mini-vite:esbuildDepPlugin') 8 | 9 | export function esbuildDepPlugin( 10 | qualified: Record, 11 | serverDevContext: ServerDevContext 12 | ): Plugin { 13 | return { 14 | name: 'vite:dep-pre-bundle', 15 | setup(build) { 16 | 17 | function resolveEntry(id: string, isEntry: boolean, resolveDir: string) { 18 | // debug(id, isEntry, resolveDir, serverDevContext.root) 19 | if (id in qualified) { 20 | // id is already esm 21 | let cjsPath = require.resolve(qualified[id], { 22 | paths: [resolveDir] 23 | }) 24 | 25 | debug(cjsPath, isEntry) 26 | return { 27 | path: cjsPath 28 | } 29 | } 30 | return null 31 | } 32 | 33 | build.onResolve( 34 | { filter: /^[\w@][^:]/ }, 35 | async ({ path: id, importer, kind, resolveDir }) => { 36 | // debug(id,kind,importer) 37 | // const require = createRequire(importer) 38 | const isEntry = !importer 39 | // ensure esbuild uses our resolved entries , 不然会报 404,require.resolve解析不出来 40 | let entry 41 | // if this is an entry, return entry namespace resolve result 42 | if ((entry = resolveEntry(id, isEntry, resolveDir))) return entry 43 | 44 | let cjsPath = require.resolve(id, { 45 | paths: [resolveDir] 46 | }) 47 | 48 | let esmPath = serverDevContext.resolveModuleRealPath(id) 49 | 50 | debug(cjsPath, kind, id, esmPath) 51 | 52 | return { 53 | path: esmPath 54 | } 55 | } 56 | ) 57 | } 58 | } 59 | } -------------------------------------------------------------------------------- /packages/mini-vite/src/optimizer/index.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs' 2 | import { 3 | build, 4 | // BuildOptions as EsbuildBuildOptions 5 | } from 'esbuild' 6 | import { ServerDevContext } from "../context"; 7 | import { createDebugger } from '../util'; 8 | import { scanImports } from './scan' 9 | import { esbuildDepPlugin } from './esbuildDepPlugin' 10 | 11 | let debug = createDebugger('mini-vite:optimizer-index') 12 | 13 | export async function optimizeDeps( 14 | serverDevContext: ServerDevContext, 15 | newDeps?: Record // missing imports encountered after server has started 16 | ) { 17 | const { cacheDir, mode } = serverDevContext 18 | const define: Record = { 19 | 'process.env.NODE_ENV': JSON.stringify(mode) 20 | } 21 | if (!fs.existsSync(cacheDir)) { 22 | fs.mkdirSync(cacheDir, { recursive: true }) 23 | } 24 | 25 | let deps: Record; 26 | 27 | // missing: Record 28 | if (!newDeps) { 29 | ; ({ 30 | deps, 31 | // missing 32 | } = await scanImports(serverDevContext)) 33 | 34 | } else { 35 | deps = newDeps 36 | } 37 | 38 | 39 | // const flatIdDeps: Record = {} 40 | 41 | const result = await build({ 42 | entryPoints: Object.keys(deps), 43 | bundle: true, 44 | format: 'esm', 45 | logLevel: 'error', 46 | splitting: true, 47 | sourcemap: true, 48 | outdir: cacheDir, 49 | treeShaking: 'ignore-annotations', 50 | metafile: true, 51 | define, 52 | plugins: [ 53 | esbuildDepPlugin( 54 | deps, 55 | serverDevContext 56 | ) 57 | ] 58 | }) 59 | 60 | const meta = result.metafile! 61 | 62 | debug(meta) 63 | } -------------------------------------------------------------------------------- /packages/mini-vite/src/plugin.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Plugin as RollupPlugin, 3 | } from 'rollup' 4 | import { ServerHook } from '.' 5 | import { HmrContext } from './hmr' 6 | import { ModuleNode } from './moduleGraph' 7 | export interface Plugin extends RollupPlugin{ 8 | configureServer?:ServerHook 9 | handleHotUpdate?( 10 | ctx: HmrContext 11 | ): Array | void | Promise | void> 12 | } 13 | 14 | export type ResolvedConfig = { 15 | plugins: readonly Plugin[] 16 | root:string 17 | } -------------------------------------------------------------------------------- /packages/mini-vite/src/plugins/css.ts: -------------------------------------------------------------------------------- 1 | // import { ServerDevContext } from "../context"; 2 | import { CLIENT_PUBLIC_PATH } from "../constants"; 3 | import { Plugin } from "../plugin"; 4 | 5 | const cssLangs = `\\.(css|scss)($|\\?)` 6 | const cssLangRE = new RegExp(cssLangs) 7 | const directRequestRE = /(\?|&)direct\b/ //这个在啥场景触发? 8 | 9 | export const isDirectCSSRequest = (request: string): boolean => 10 | cssLangRE.test(request) && directRequestRE.test(request) 11 | 12 | export const isCSSRequest = (request: string): boolean => 13 | cssLangRE.test(request) && !directRequestRE.test(request) 14 | 15 | export default function cssPostPlugin(): Plugin { 16 | // let serverDevContext: ServerDevContext 17 | function filter(id: string) { 18 | return cssLangRE.test(id) 19 | } 20 | return { 21 | name: 'mini-vite:css-post', 22 | 23 | // configureServer(context: ServerDevContext) { 24 | // serverDevContext = context 25 | // }, 26 | 27 | transform(css, id) { 28 | if(!filter(id)){ 29 | return null 30 | } 31 | return [ 32 | `import { updateStyle } from ${JSON.stringify( 33 | CLIENT_PUBLIC_PATH 34 | )}`, 35 | `const id = ${JSON.stringify(id)}`, 36 | `const css = ${JSON.stringify(css)}`, 37 | `updateStyle(id, css)`, 38 | // css modules exports change on edit so it can't self accept 39 | `import.meta.hot.accept()\nexport default css`, 40 | ].join('\n') 41 | } 42 | } 43 | } -------------------------------------------------------------------------------- /packages/mini-vite/src/plugins/esbuild.ts: -------------------------------------------------------------------------------- 1 | import { createFilter } from '@rollup/pluginutils'; 2 | import { Plugin } from "../plugin" 3 | import { cleanUrl, transformWithEsbuild } from "../util" 4 | 5 | export function esbuildPlugin(): Plugin { 6 | const filter = createFilter(/\.(tsx?|jsx)$/, /\.js$/) 7 | 8 | return { 9 | name: 'mini-vite:esbuild', 10 | async transform(code, id) { 11 | if (filter(id) || filter(cleanUrl(id))) { 12 | 13 | const result = await transformWithEsbuild(code, id) 14 | return { 15 | code: result.code, 16 | map: result.map 17 | } 18 | } 19 | 20 | return null 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /packages/mini-vite/src/plugins/html.ts: -------------------------------------------------------------------------------- 1 | import { ServerDevContext } from "../context"; 2 | import { Plugin } from "../plugin"; 3 | import { CLIENT_PUBLIC_PATH, ENV_PUBLIC_PATH } from '../constants' 4 | import { transformWithEsbuild } from '../util' 5 | import fs from 'fs' 6 | 7 | export function filter(id:string):boolean{ 8 | return id.indexOf('/src/client/client.ts') > -1 || id.indexOf('/src/client/env.ts') > -1 9 | } 10 | 11 | export function constPathFilter(id:string){ 12 | return id.indexOf(CLIENT_PUBLIC_PATH) > -1 || id.indexOf(ENV_PUBLIC_PATH) > -1 13 | } 14 | 15 | export default function htmlPlugin(): Plugin { 16 | let serverDevContext: ServerDevContext 17 | return { 18 | name: 'mini-vite:htmlPlugin', 19 | 20 | configureServer(context: ServerDevContext) { 21 | serverDevContext = context 22 | }, 23 | 24 | resolveId(id) { 25 | // client.ts will be bundled on the run 26 | if (id.indexOf(CLIENT_PUBLIC_PATH) > -1) { 27 | return serverDevContext.resolveResourcePath('./src/client/client.ts') 28 | 29 | } else if (id.indexOf(ENV_PUBLIC_PATH) > -1) { 30 | return serverDevContext.resolveResourcePath('./src/client/env.ts') 31 | 32 | } 33 | return null 34 | }, 35 | load(id) { 36 | if (filter(id)){ 37 | return fs.readFileSync(id, 'utf-8') 38 | } 39 | // transformRequest will handle readFile 40 | return null 41 | }, 42 | async transform(code, id) { 43 | if (filter(id)){ 44 | return await transformWithEsbuild(code, id) 45 | } 46 | return null 47 | } 48 | } 49 | } -------------------------------------------------------------------------------- /packages/mini-vite/src/plugins/index.ts: -------------------------------------------------------------------------------- 1 | import { Plugin } from "../plugin"; 2 | import htmlPlugin from './html' 3 | import importAnalysisPlugin from './importAnalysis' 4 | import resolvePlugin from './resolve' 5 | import vuePlugin from './plugin-vue/index' 6 | import cssPostPlugin from "./css"; 7 | import { esbuildPlugin } from "./esbuild"; 8 | 9 | export async function resolvePlugins(): Promise{ 10 | // 注意顺序,比如 importAnalysisPlugin 需要在 其它特别的 transform 结果之后进行 11 | return [ 12 | htmlPlugin(), 13 | resolvePlugin(), 14 | vuePlugin(), 15 | esbuildPlugin(), 16 | cssPostPlugin(), 17 | // always post other plugins 18 | importAnalysisPlugin(), 19 | ] 20 | } -------------------------------------------------------------------------------- /packages/mini-vite/src/plugins/plugin-vue/descriptor.ts: -------------------------------------------------------------------------------- 1 | import crypto from 'crypto' 2 | import { parse, SFCDescriptor } from '@vue/compiler-sfc' 3 | 4 | const cache = new Map() 5 | const prevCache = new Map() 6 | 7 | export function createDescriptor(filename: string, code: string, isProduction: boolean | undefined = false) { 8 | const { descriptor } = parse(code, { filename }) 9 | 10 | descriptor.id = crypto.createHash('md5').update(filename + (isProduction ? code : '')).digest('hex'); 11 | 12 | cache.set(filename, descriptor) 13 | 14 | return { 15 | filename, 16 | descriptor 17 | } 18 | } 19 | 20 | export function getDescriptor(filename: string) { 21 | if (cache.has(filename)) { 22 | return cache.get(filename)! 23 | } else { 24 | throw Error(`${filename} has not been cache yet! why?`) 25 | } 26 | } 27 | 28 | export function getPrevDescriptor(filename: string): SFCDescriptor | undefined { 29 | return prevCache.get(filename) 30 | } 31 | 32 | export function setPrevDescriptor( 33 | filename: string, 34 | entry: SFCDescriptor 35 | ): void { 36 | prevCache.set(filename, entry) 37 | } 38 | -------------------------------------------------------------------------------- /packages/mini-vite/src/plugins/plugin-vue/query.ts: -------------------------------------------------------------------------------- 1 | import qs from 'querystring' 2 | 3 | export interface VueQuery { 4 | vue?: boolean 5 | src?: boolean 6 | type?: 'script' | 'template' | 'style' | 'custom' 7 | index?: number 8 | lang?: string 9 | raw?: boolean 10 | } 11 | 12 | export function parseVueRequest(id: string): { 13 | filename: string 14 | query: VueQuery 15 | } { 16 | const [filename, rawQuery] = id.split(`?`, 2) 17 | const query = qs.parse(rawQuery) as VueQuery 18 | if (query.vue != null) { 19 | query.vue = true 20 | } 21 | if (query.src != null) { 22 | query.src = true 23 | } 24 | if (query.index != null) { 25 | query.index = Number(query.index) 26 | } 27 | if (query.raw != null) { 28 | query.raw = true 29 | } 30 | return { 31 | filename, 32 | query 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /packages/mini-vite/src/plugins/resolve.ts: -------------------------------------------------------------------------------- 1 | import path from 'path' 2 | import { MODULE_DEPENDENCY_RE } from '../constants'; 3 | import { ServerDevContext } from "../context"; 4 | import { Plugin } from "../plugin"; 5 | import { createDebugger } from '../util'; 6 | 7 | let debug = createDebugger('mini-vite-hide:resolvePlugin') 8 | // resolve third party module path 9 | export default function resolve(): Plugin { 10 | let serverDevContext: ServerDevContext 11 | function filter(id: string) { 12 | // used in scan mode 13 | return MODULE_DEPENDENCY_RE.test(id) 14 | } 15 | return { 16 | name: 'mini-vite:resolve', 17 | 18 | configureServer(context: ServerDevContext) { 19 | serverDevContext = context 20 | }, 21 | 22 | resolveId(id, importer) { 23 | let prefix = importer ? path.dirname(importer) : '.' 24 | let resolvedId 25 | // debug(importer, id) 26 | if (filter(id)) { 27 | // will only run in optimize scan 28 | debug('enter resolve node_modules') 29 | resolvedId = serverDevContext.resolveModuleRealPath(id) 30 | }else{ 31 | 32 | resolvedId = serverDevContext.resolvePath(path.join(prefix, id),true) 33 | } 34 | 35 | debug(resolvedId) 36 | 37 | return resolvedId 38 | } 39 | } 40 | } -------------------------------------------------------------------------------- /packages/mini-vite/src/send.ts: -------------------------------------------------------------------------------- 1 | import { IncomingMessage, ServerResponse } from 'http' 2 | import getEtag from 'etag' 3 | // import { SourceMap } from 'rollup' 4 | 5 | const alias: Record = { 6 | js: 'application/javascript', 7 | css: 'text/css', 8 | html: 'text/html', 9 | json: 'application/json' 10 | } 11 | 12 | export function send( 13 | req: IncomingMessage, 14 | res: ServerResponse, 15 | content: string | Buffer, 16 | type: string = 'js', 17 | etag = getEtag(content), 18 | 19 | // no-cache 将会和服务器进行一次通讯,确保返回的资源没有修改过,如果没有修改过,才没有必要下载这个资源。 20 | cacheControl = 'no-cache', 21 | ): void { 22 | if (req.headers['if-none-match'] === etag) { 23 | res.statusCode = 304 24 | return res.end() 25 | } 26 | 27 | res.setHeader('Content-Type', alias[type] || type) 28 | 29 | res.setHeader('Cache-Control', cacheControl) 30 | res.setHeader('Etag', etag) 31 | 32 | res.statusCode = 200 33 | return res.end(content) 34 | } 35 | -------------------------------------------------------------------------------- /packages/mini-vite/src/transformRequest.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs' 2 | import getEtag from 'etag' 3 | 4 | import { ServerDevContext } from "./context"; 5 | import { cleanUrl, removeTimestampQuery } from './util' 6 | 7 | export interface TransformResult { 8 | code: string 9 | etag?: string 10 | } 11 | 12 | export async function transformRequest(url: string, { pluginContainer,resolvePath, moduleGraph }: ServerDevContext) { 13 | url = removeTimestampQuery(url) 14 | const id = (await pluginContainer.resolveId(url))?.id || url 15 | // console.log('pluginContainer.resolveId:',id) 16 | 17 | const loadResult = await pluginContainer.load(id) 18 | let code: string | null = null 19 | 20 | if (loadResult == null) { 21 | const file = cleanUrl(id) 22 | // resolve real absolute path based on root 23 | code = fs.readFileSync(resolvePath(file), 'utf-8') 24 | } else { 25 | if (typeof loadResult === 'object') { 26 | code = loadResult.code 27 | } else { 28 | code = loadResult 29 | } 30 | 31 | } 32 | 33 | // ensure module in graph after successful load 34 | const mod = await moduleGraph.ensureEntryFromUrl(url) 35 | 36 | const transformResult = await pluginContainer.transform(code, id) 37 | 38 | if (transformResult == null) { 39 | // no transform applied, keep code as-is 40 | } else { 41 | code = transformResult.code 42 | } 43 | 44 | if(!code){ 45 | return null 46 | } 47 | 48 | return mod.transformResult = { 49 | code: code, 50 | etag: getEtag(code, { weak: true }) 51 | } as TransformResult 52 | } -------------------------------------------------------------------------------- /packages/mini-vite/src/type/hmr.d.ts: -------------------------------------------------------------------------------- 1 | export type HMRPayload = ConnectedPayload | UpdatePayload | FullReloadPayload 2 | export interface ConnectedPayload { 3 | type: 'connected' 4 | } 5 | export interface UpdatePayload { 6 | type: 'update' 7 | updates: Update[] 8 | } 9 | export interface Update { 10 | type: 'js-update' | 'css-update' 11 | path: string 12 | acceptedPath: string 13 | timestamp: number 14 | } 15 | export interface FullReloadPayload { 16 | type: 'full-reload' 17 | path?: string 18 | } -------------------------------------------------------------------------------- /packages/mini-vite/src/ws.ts: -------------------------------------------------------------------------------- 1 | import WebSocket from 'ws' 2 | import { HMRPayload } from './type/hmr' 3 | 4 | export interface WebSocketServer { 5 | send(payload: HMRPayload): void 6 | sendDebug(payload: any): void 7 | close(): Promise 8 | } 9 | 10 | // safely handles circular references 11 | // @ts-ignore 12 | const getCircularReplacer = () => { 13 | const seen = new WeakSet(); 14 | return (_: any, value: any) => { 15 | if (typeof value === "object" && value !== null) { 16 | if (seen.has(value)) { 17 | return 'circular: '+value.toString(); 18 | } 19 | seen.add(value); 20 | } 21 | return value; 22 | }; 23 | }; 24 | 25 | export function createWebSocketServer(): WebSocketServer { 26 | let wss: WebSocket.Server = new WebSocket.Server({ port: 24679 }) 27 | wss.on('connection', (socket) => { 28 | socket.send(JSON.stringify({ type: 'connected' })) 29 | }) 30 | return { 31 | send(payload: HMRPayload) { 32 | const stringified = JSON.stringify(payload) 33 | wss.clients.forEach((client) => { 34 | if (client.readyState === WebSocket.OPEN) { 35 | client.send(stringified) 36 | } 37 | }) 38 | }, 39 | sendDebug(payload: any) { 40 | if (process.env.DEBUG) { 41 | payload = { 42 | type: 'debug', 43 | payload 44 | } 45 | 46 | // @ts-ignore 47 | const stringified = JSON.stringify((payload), getCircularReplacer()) 48 | wss.clients.forEach((client) => { 49 | if (client.readyState === WebSocket.OPEN) { 50 | client.send(stringified) 51 | } 52 | }) 53 | } 54 | }, 55 | close() { 56 | return new Promise((resolve, reject) => { 57 | wss.close((err) => { 58 | if (err) { 59 | reject(err) 60 | } else { 61 | resolve() 62 | } 63 | }) 64 | }) 65 | } 66 | } 67 | } -------------------------------------------------------------------------------- /packages/mini-vite/template-vue-ts/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | dist 4 | dist-ssr 5 | *.local 6 | -------------------------------------------------------------------------------- /packages/mini-vite/template-vue-ts/README.md: -------------------------------------------------------------------------------- 1 | # mini-vite experiment 2 | 3 | ## How to use 4 | 5 | ``` 6 | npm run dev 7 | ``` -------------------------------------------------------------------------------- /packages/mini-vite/template-vue-ts/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vite App 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /packages/mini-vite/template-vue-ts/mini-vite.config.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wizardpisces/experiment/3e24f6597819cb3918e0ba1c8d443acde70795a6/packages/mini-vite/template-vue-ts/mini-vite.config.ts -------------------------------------------------------------------------------- /packages/mini-vite/template-vue-ts/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mini-vite-vue-typescript-starter", 3 | "version": "0.0.0", 4 | "scripts": { 5 | "dev": "mini-vite" 6 | }, 7 | "dependencies": { 8 | "vue": "^3.0.5", 9 | "vue-router": "^4.0.9" 10 | }, 11 | "devDependencies": { 12 | "@vitejs/plugin-vue": "^1.2.2", 13 | "@vue/compiler-sfc": "^3.0.5", 14 | "typescript": "^4.1.3", 15 | "mini-vite": "0.1.0", 16 | "vue-tsc": "^0.0.24" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /packages/mini-vite/template-vue-ts/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wizardpisces/experiment/3e24f6597819cb3918e0ba1c8d443acde70795a6/packages/mini-vite/template-vue-ts/public/favicon.ico -------------------------------------------------------------------------------- /packages/mini-vite/template-vue-ts/src/App.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 22 | 23 | 33 | -------------------------------------------------------------------------------- /packages/mini-vite/template-vue-ts/src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wizardpisces/experiment/3e24f6597819cb3918e0ba1c8d443acde70795a6/packages/mini-vite/template-vue-ts/src/assets/logo.png -------------------------------------------------------------------------------- /packages/mini-vite/template-vue-ts/src/components/HelloWorld.vue: -------------------------------------------------------------------------------- 1 | 36 | 37 | 53 | 54 | 71 | -------------------------------------------------------------------------------- /packages/mini-vite/template-vue-ts/src/main.ts: -------------------------------------------------------------------------------- 1 | import { createApp } from 'vue' 2 | import App from './App.vue' 3 | import module from './mimic-store/index' 4 | type a = number; 5 | console.log('module id',module.id) 6 | 7 | createApp(App).mount('#app') 8 | -------------------------------------------------------------------------------- /packages/mini-vite/template-vue-ts/src/mimic-store/index.ts: -------------------------------------------------------------------------------- 1 | import module1 from './module1' 2 | import module2 from './module2' 3 | 4 | type Param = { [key: string]: number } 5 | 6 | class MimicStore { 7 | id: number = 0 8 | modules: Param 9 | notChange: string 10 | constructor(modules: Param, notChange: string = 'changed') { 11 | this.modules = modules 12 | this.notChange = notChange 13 | this.init() 14 | } 15 | init() { 16 | this.id = Object.keys(this.modules).reduce((res, key) => res + this.modules[key], 0) 17 | } 18 | hotUpdate(newModules: Param) { 19 | Object.keys(newModules).forEach(key => this.modules[key] = newModules[key]) 20 | this.init() 21 | } 22 | } 23 | 24 | let module = new MimicStore({ module1, module2 }) 25 | 26 | module.notChange = ' property will not be changed by hot updates?' 27 | 28 | // @ts-ignore 29 | if (import.meta.hot) { 30 | // @ts-ignore 31 | import.meta.hot.accept(['./module1.ts', './module2.ts'], ([module1, module2]) => { 32 | console.log('[module] before hotUpdate', module.id, `; 'notChange' ${module.notChange}`, module1, module2) // id should equal to 3 33 | // newModules = newModules.filter(mod=>mod).map((mod:any)=> mod.default) 34 | let param: Param = {} 35 | if (module1) { param['module1'] = module1.default } 36 | if (module2) { param['module2'] = module2.default } 37 | 38 | module.hotUpdate(param) // change module1 id=2 will trigger below log 4 39 | console.log('[module] after hotUpdate', module.id, `; 'notChange' ${module.notChange}`, module1, module2) // id should equal to 3 40 | }) 41 | } 42 | 43 | export default module -------------------------------------------------------------------------------- /packages/mini-vite/template-vue-ts/src/mimic-store/module1.ts: -------------------------------------------------------------------------------- 1 | let id = 10 2 | 3 | export default id -------------------------------------------------------------------------------- /packages/mini-vite/template-vue-ts/src/mimic-store/module2.ts: -------------------------------------------------------------------------------- 1 | let id = 2 2 | 3 | export default id -------------------------------------------------------------------------------- /packages/mini-vite/template-vue-ts/src/router.ts: -------------------------------------------------------------------------------- 1 | import { 2 | createRouter, 3 | createWebHistory 4 | } from 'vue-router' 5 | 6 | const Home = () => import('./pages/home.vue') 7 | const About = () => import('./pages/about.vue') 8 | 9 | const routerHistory = createWebHistory() 10 | 11 | export const routes = [{ 12 | path: '/', 13 | name: 'home', 14 | component: Home 15 | }, { 16 | path: '/about', 17 | component: About, 18 | name: 'about' 19 | }] 20 | 21 | const router = createRouter({ 22 | history: routerHistory, 23 | routes 24 | }) 25 | 26 | export default router; -------------------------------------------------------------------------------- /packages/mini-vite/template-vue-ts/src/shims-vue.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.vue' { 2 | import { DefineComponent } from 'vue' 3 | const component: DefineComponent<{}, {}, any> 4 | export default component 5 | } 6 | -------------------------------------------------------------------------------- /packages/mini-vite/template-vue-ts/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /packages/mini-vite/template-vue-ts/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "module": "esnext", 5 | "moduleResolution": "node", 6 | "strict": true, 7 | "jsx": "preserve", 8 | "sourceMap": true, 9 | "resolveJsonModule": true, 10 | "esModuleInterop": true, 11 | "lib": ["esnext", "dom"], 12 | "types": ["vite/client"] 13 | }, 14 | "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"] 15 | } 16 | -------------------------------------------------------------------------------- /packages/mini-vite/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | // see https://www.typescriptlang.org/tsconfig to better understand tsconfigs 3 | "include": ["src", "types"], 4 | "compilerOptions": { 5 | "module": "commonjs", 6 | "lib": ["dom", "esnext"], 7 | "importHelpers": true, 8 | // output .d.ts declaration files for consumers 9 | "declaration": true, 10 | // output .js.map sourcemap files for consumers 11 | "sourceMap": true, 12 | // match output dir to input dir. e.g. dist/index instead of dist/src/index 13 | "rootDir": "./src", 14 | // stricter type-checking for stronger correctness. Recommended by TS 15 | "strict": true, 16 | // linter checks for common issues 17 | "noImplicitReturns": true, 18 | "noFallthroughCasesInSwitch": true, 19 | // noUnused* overlap with @typescript-eslint/no-unused-vars, can disable if duplicative 20 | "noUnusedLocals": true, 21 | "noUnusedParameters": true, 22 | // use Node's module resolution algorithm, instead of the legacy TS one 23 | "moduleResolution": "node", 24 | // transpile JSX to React.createElement 25 | "jsx": "react", 26 | // interop between ESM and CJS modules. Recommended by TS 27 | "esModuleInterop": true, 28 | // significant perf increase by skipping checking .d.ts files, particularly those in node_modules. Recommended by TS 29 | "skipLibCheck": true, 30 | // error out if import and file system have a casing mismatch. Recommended by TS 31 | "forceConsistentCasingInFileNames": true, 32 | // `tsdx build` ignores this option, but it is commonly used when type-checking separately with `tsc` 33 | "noEmit": true, 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /packages/mini-vue/demo-ssr/README.md: -------------------------------------------------------------------------------- 1 | ## How to run 2 | 3 | ``` 4 | npm install -g vite # make sure global vite exists 5 | 6 | npm install 7 | vite 8 | ``` 9 | -------------------------------------------------------------------------------- /packages/mini-vue/demo-ssr/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | mini 8 | 10 | 11 | 12 |
13 |

vue loading...

14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /packages/mini-vue/demo-ssr/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ssr-mini-vue-demo", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "node server", 7 | "build": "vite build", 8 | "serve": "vite preview" 9 | }, 10 | "dependencies": { 11 | "express": "^4.17.2", 12 | "vue-router": "4", 13 | "mini-vue": "workspace:*" 14 | }, 15 | "devDependencies": { 16 | "@vitejs/plugin-vue": "^1.9.3", 17 | "vite": "^2.1.5" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /packages/mini-vue/demo-ssr/src/App.jsx: -------------------------------------------------------------------------------- 1 | import { ref, effect, h, reactive } from 'mini-vue' 2 | import { nextTick } from 'mini-vue' 3 | import Child from './child' 4 | export default { 5 | setup() { 6 | const count = ref(0) 7 | const obj = reactive({ age: 18 }) 8 | 9 | const add = () => { 10 | count.value++ 11 | count.value-- 12 | count.value++ 13 | obj.age++ 14 | console.log('Event [add] clicked:', count) 15 | } 16 | const addAge = () => { 17 | obj.age++ 18 | nextTick(() => obj.age++) 19 | console.log('Event [addAge] clicked:', count) 20 | } 21 | console.log('setup running') 22 | effect(function log() { 23 | console.log('count changed!', count.value) 24 | }) 25 | return () => ( 26 |
27 |

mini-vue counter {Math.random()}

28 |

29 | {count.value} 30 | 31 |

32 |

33 | {obj.age} 34 | 35 |

36 | 37 |
38 | ) 39 | } 40 | } 41 | 42 | -------------------------------------------------------------------------------- /packages/mini-vue/demo-ssr/src/Layout.jsx: -------------------------------------------------------------------------------- 1 | import { h } from 'mini-vue' 2 | import App from './App' 3 | export default { 4 | setup() { 5 | return () => ( 6 | 7 | 8 | 9 | 10 | 11 | 12 | mini 13 | 14 | 15 |
16 | 17 | 18 | 19 | ) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /packages/mini-vue/demo-ssr/src/child.jsx: -------------------------------------------------------------------------------- 1 | import { ref, effect, h } from 'mini-vue' 2 | export default { 3 | props:{ 4 | count:Number 5 | }, 6 | setup(props) { 7 | const childCount = ref(0) 8 | 9 | const add = () => childCount.value++ 10 | console.log('child setup running') 11 | effect(function log() { 12 | console.log('childCount changed!', childCount.value) 13 | }) 14 | effect(() => console.log('child props change:', props,props.count)) 15 | return () => ( 16 |
17 |

Child2 {Math.random()}

18 |

child count:{childCount.value}

19 |

props count: {props.count}

20 |
21 | ) 22 | } 23 | } 24 | 25 | -------------------------------------------------------------------------------- /packages/mini-vue/demo-ssr/src/entry-client.ts: -------------------------------------------------------------------------------- 1 | import { createApp } from './main' 2 | 3 | const { app, router } = createApp() 4 | 5 | // wait until router is ready before mounting to ensure hydration match 6 | // router.isReady().then(() => { 7 | app.mount('#app') 8 | // }) 9 | -------------------------------------------------------------------------------- /packages/mini-vue/demo-ssr/src/main.ts: -------------------------------------------------------------------------------- 1 | import { 2 | createApp as CreateClientApp, 3 | createSSRApp 4 | } from 'mini-vue' 5 | 6 | import Layout from './Layout' 7 | import App from './App' 8 | import { createRouter } from './router' 9 | 10 | // SSR requires a fresh app instance per request, therefore we export a function 11 | // that creates a fresh app instance. If using Vuex, we'd also be creating a 12 | // fresh store here. 13 | const isBrowser = typeof window !== 'undefined'; 14 | export function createApp() { 15 | // let app = createSSRApp(Layout) 16 | let app = isBrowser ? CreateClientApp(App) : createSSRApp(Layout) 17 | 18 | const router = createRouter() 19 | // app.use(router) 20 | return { app, router } 21 | } 22 | -------------------------------------------------------------------------------- /packages/mini-vue/demo-ssr/src/router.ts: -------------------------------------------------------------------------------- 1 | import { 2 | createMemoryHistory, 3 | createRouter as _createRouter, 4 | createWebHistory 5 | } from 'vue-router' 6 | 7 | // Auto generates routes from vue files under ./pages 8 | // https://vitejs.dev/guide/features.html#glob-import 9 | 10 | //@ts-ignore 11 | const pages = import.meta.glob('./pages/*.vue') 12 | 13 | const routes = Object.keys(pages).map((path) => { 14 | const name = path.match(/\.\/pages(.*)\.vue$/)[1].toLowerCase() 15 | return { 16 | path: name === '/home' ? '/' : name, 17 | component: pages[path] // () => import('./pages/*.vue') 18 | } 19 | }) 20 | 21 | export function createRouter() { 22 | return _createRouter({ 23 | // use appropriate history implementation for server/client 24 | // import.meta.env.SSR is injected by Vite. 25 | 26 | //@ts-ignore 27 | history: import.meta.env.SSR ? createMemoryHistory() : createWebHistory(), 28 | routes 29 | }) 30 | } 31 | -------------------------------------------------------------------------------- /packages/mini-vue/demo-ssr/vite.config.js: -------------------------------------------------------------------------------- 1 | // import vue from '@vitejs/plugin-vue' 2 | 3 | export default { 4 | server: { 5 | port: 8081 6 | }, 7 | esbuild: { 8 | jsxFactory: 'h', 9 | jsxFragment: 'Fragment', 10 | target: 'es2020', 11 | format: 'esm' 12 | }, 13 | // plugins: [vue()] 14 | } -------------------------------------------------------------------------------- /packages/mini-vue/demo-ssr/vue-shim.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.vue' { 2 | import { App, defineComponent } from 'vue' 3 | const component: ReturnType & { 4 | install(app: App): void 5 | } 6 | export default component 7 | } -------------------------------------------------------------------------------- /packages/mini-vue/demo/README.md: -------------------------------------------------------------------------------- 1 | ## How to run 2 | 3 | ``` 4 | npm install -g vite # make sure global vite exists 5 | 6 | npm install 7 | vite 8 | ``` 9 | -------------------------------------------------------------------------------- /packages/mini-vue/demo/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | mini 8 | 10 | 11 | 12 |
13 |

vue loading...

14 |
15 | 16 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /packages/mini-vue/demo/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mini-vue-demo", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "vite", 7 | "build": "vite build", 8 | "serve": "vite preview" 9 | }, 10 | "devDependencies": { 11 | "@vitejs/plugin-vue": "^1.9.3", 12 | "vite": "^2.1.5", 13 | "vue": "^3.2.13" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /packages/mini-vue/demo/src/App.jsx: -------------------------------------------------------------------------------- 1 | import { ref, effect, h, reactive } from '../../src' 2 | import { nextTick } from '../../src/scheduler' 3 | import Child from './child' 4 | export default { 5 | setup() { 6 | const count = ref(0) 7 | const obj = reactive({ age: 18 }) 8 | 9 | const add = () => { 10 | count.value++ 11 | count.value-- 12 | count.value++ 13 | obj.age++ 14 | console.log('Event [add] clicked:', count) 15 | } 16 | const addAge = () => { 17 | obj.age++ 18 | nextTick(() => obj.age++) 19 | console.log('Event [addAge] clicked:', count) 20 | } 21 | console.log('setup running') 22 | effect(function log() { 23 | console.log('count changed!', count.value) 24 | }) 25 | return () => ( 26 |
27 |

mini-vue counter {Math.random()}

28 |

29 | {count.value} 30 | 31 |

32 |

33 | {obj.age} 34 | 35 |

36 | 37 |
38 | ) 39 | } 40 | } 41 | 42 | -------------------------------------------------------------------------------- /packages/mini-vue/demo/src/child.jsx: -------------------------------------------------------------------------------- 1 | import { ref, effect, h } from '../../src' 2 | export default { 3 | props:{ 4 | count:Number 5 | }, 6 | setup(props) { 7 | const childCount = ref(0) 8 | 9 | const add = () => childCount.value++ 10 | console.log('child setup running') 11 | effect(function log() { 12 | console.log('childCount changed!', childCount.value) 13 | }) 14 | effect(() => console.log('child props change:', props,props.count)) 15 | return () => ( 16 |
17 |

Child {Math.random()}

18 |

child count:{childCount.value}

19 |

props count: {props.count}

20 |
21 | ) 22 | } 23 | } 24 | 25 | -------------------------------------------------------------------------------- /packages/mini-vue/demo/src/main.ts: -------------------------------------------------------------------------------- 1 | import { 2 | createApp 3 | } from '../../src' 4 | import App from './App' 5 | 6 | createApp(App) 7 | .mount('#app'); -------------------------------------------------------------------------------- /packages/mini-vue/demo/vite.config.js: -------------------------------------------------------------------------------- 1 | // import vue from '@vitejs/plugin-vue' 2 | 3 | export default { 4 | server: { 5 | port: 8081 6 | }, 7 | esbuild: { 8 | jsxFactory: 'h', 9 | jsxFragment: 'Fragment', 10 | target: 'es2020', 11 | format: 'esm' 12 | }, 13 | // plugins: [vue()] 14 | } -------------------------------------------------------------------------------- /packages/mini-vue/demo/vue-shim.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.vue' { 2 | import { App, defineComponent } from 'vue' 3 | const component: ReturnType & { 4 | install(app: App): void 5 | } 6 | export default component 7 | } -------------------------------------------------------------------------------- /packages/mini-vue/legacy/index.html: -------------------------------------------------------------------------------- 1 |
2 | title
3 | title2
4 | conditionalComputed
5 | 6 | 7 |
-------------------------------------------------------------------------------- /packages/mini-vue/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mini-vue", 3 | "module": "src/index.ts", 4 | "private": true, 5 | "version": "1.0.0", 6 | "description": "Vue in mini version", 7 | "main": "src/index.ts", 8 | "scripts": { 9 | "demo": "cd demo && npm run dev", 10 | "demo-ssr": "cd demo-ssr && npm run dev", 11 | "test": "echo \"Error: no test specified\" && exit 1" 12 | }, 13 | "author": "刘泽", 14 | "license": "MIT", 15 | "devDependencies": { 16 | "vue": "^3.2.20" 17 | }, 18 | "workspaces": [ 19 | "demo", 20 | "demo-ssr", 21 | "./" 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /packages/mini-vue/pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | - 'demo' 3 | - 'demo-ssr' 4 | - './' -------------------------------------------------------------------------------- /packages/mini-vue/readme.md: -------------------------------------------------------------------------------- 1 | ## Introdution 2 | Vue@3 Composition-API in mini version 3 | 4 | ## How to run 5 | ```bash 6 | pnpm install 7 | npm run demo # node>=14 8 | npm run demo-ssr 9 | 10 | ``` 11 | ## Progress 12 | 通过run Demo的需要逐步实现功能 13 | ### 第一阶段 14 | * [x] 父子组件嵌套 render 15 | * [x] 局部 update,原理 16 | * 对ref data进行 dep track,其中就包含了component update function,data变化触发相应dep执行(包含component update方法的运行) 17 | * [x] stateful component 18 | * [x] ref/effect track primary data change 19 | commitId: 0e48a8a 20 | 21 | ### 第二阶段 22 | * [x] 父传props给子组件,更新父组件同时props变化也会影响到子组件刷新; 23 | * 原理:初始阶段对props进行reactivity,在component update阶段对instance props刷新,然后重新触发instance上的render 24 | * commitId: 311f680e2e903556bba1ff13a1be17493e0fcc8c 25 | * [x] reactive track Object change 26 | * [x] nextTick 27 | * [x] component batch update(update操作同步 -> 异步) 28 | commitId: 3041af 29 | ### 第三阶段 30 | * [x] SSR 31 | * [X] renderToString 32 | * [x] renderToStream/renderToNodeStream 33 | * [] slot 34 | * [] lifecycle hooks 35 | * [] unmount 36 | * [] toRefs 37 | * [] Fragment? 38 | ## Reference 39 | 40 | * [框架设计的思考](https://wizardpisces.github.io/blog/框架设计的思考) 41 | * https://github.com/vuejs/vue-next 42 | -------------------------------------------------------------------------------- /packages/mini-vue/src/createApp.ts: -------------------------------------------------------------------------------- 1 | import { VNode } from "./type"; 2 | import { isString } from "./util"; 3 | import { render, h, hydrateRender } from "./h"; 4 | import { ConcreteComponent } from "./component"; 5 | 6 | export { 7 | createApp, 8 | createSSRApp 9 | } 10 | 11 | function createApp(app: ConcreteComponent, props: VNode['props'] = { children: [] }, isHydrate = false) { 12 | let vnode = h(app, props); 13 | return { 14 | vnode, 15 | mount: (rootContainer: string | Element) => { 16 | let root: Element 17 | if (isString(rootContainer)) { 18 | rootContainer = document.querySelector(rootContainer) as Element 19 | } 20 | root = rootContainer as Element 21 | if (isHydrate) { 22 | hydrateRender(vnode, root) 23 | } else { 24 | root.innerHTML = '' 25 | render(vnode, root) 26 | } 27 | } 28 | } 29 | } 30 | 31 | 32 | function createSSRApp(app: ConcreteComponent, props: VNode['props'] = { children: [] }) { 33 | return createApp(app, props, true) 34 | } -------------------------------------------------------------------------------- /packages/mini-vue/src/dep.ts: -------------------------------------------------------------------------------- 1 | import { ReactiveEffect } from "./effect" 2 | 3 | export { 4 | Dep, 5 | createDep 6 | } 7 | 8 | class Dep { 9 | private effects: Set = new Set() 10 | addEffect(effect: ReactiveEffect) { 11 | // console.warn(effect) 12 | this.effects.add(effect) 13 | } 14 | runEffect() { 15 | // console.log('dep triggered', this.effects.size) 16 | this.effects.forEach(effect => { 17 | if (effect.scheduler) { 18 | effect.scheduler() 19 | } else { 20 | effect.run() 21 | } 22 | }) 23 | } 24 | } 25 | 26 | function createDep() { 27 | return new Dep() 28 | } -------------------------------------------------------------------------------- /packages/mini-vue/src/effect.ts: -------------------------------------------------------------------------------- 1 | import { createDep, Dep } from "./dep" 2 | import { isFunction } from "./util" 3 | 4 | export { 5 | effect, 6 | trackEffect, 7 | triggerEffect, 8 | track, 9 | trigger, 10 | ReactiveEffect 11 | } 12 | 13 | type EffectFn = () => void 14 | let activeEffect: ReactiveEffect | null = null 15 | 16 | class ReactiveEffect{ 17 | constructor( 18 | public fn: () => T, 19 | public scheduler: (() => void) | null = null // mainly for schedule batch component update (triggered by multiple data changes) 20 | ) { 21 | } 22 | run() { 23 | activeEffect = this 24 | this.fn() 25 | activeEffect = null 26 | } 27 | } 28 | 29 | function effect(fn: () => T) { 30 | let effect = new ReactiveEffect(fn) 31 | effect.run() 32 | } 33 | 34 | // cache track 35 | type KeyToDepMap = Map 36 | const targetMap = new WeakMap() 37 | function track(target: Object, key: unknown) { 38 | let depMap = targetMap.get(target) 39 | if (!depMap) { 40 | targetMap.set(target, depMap = new Map()) 41 | depMap.set(key, createDep()) 42 | } 43 | 44 | trackEffect(depMap.get(key) as Dep) 45 | } 46 | 47 | function trigger(target: Object, key: unknown) { 48 | let dep = targetMap.get(target)?.get(key)! 49 | triggerEffect(dep) 50 | } 51 | 52 | function trackEffect(dep: Dep) { 53 | // console.warn('trackEffect',dep,activeEffect) 54 | activeEffect && dep.addEffect(activeEffect) 55 | } 56 | 57 | function triggerEffect(dep: Dep) { 58 | // console.log('triggerEffect', dep) 59 | dep.runEffect() 60 | } -------------------------------------------------------------------------------- /packages/mini-vue/src/index.ts: -------------------------------------------------------------------------------- 1 | import { ref } from './ref' 2 | import { effect } from './effect' 3 | import { h } from './h' 4 | import { createApp, createSSRApp } from './createApp' 5 | import { reactive } from './reactive' 6 | import {renderToString,renderToStream,renderToNodeStream} from './server-renderer' 7 | export { nextTick} from './scheduler' 8 | export { 9 | ref, 10 | effect, 11 | h, 12 | createApp, 13 | createSSRApp, 14 | reactive, 15 | renderToNodeStream, 16 | renderToStream, 17 | renderToString 18 | } 19 | 20 | // import { ref, effect, h, createApp, Fragment, nextTick, reactive, createSSRApp } from 'vue' 21 | // import { renderToString, renderToNodeStream } from 'vue/server-renderer' 22 | // export { 23 | // nextTick, 24 | // renderToString, // ssr 25 | // createSSRApp, // ssr 26 | // ref, 27 | // effect, 28 | // h, 29 | // createApp, 30 | // Fragment, 31 | // reactive, 32 | // renderToNodeStream 33 | // } 34 | -------------------------------------------------------------------------------- /packages/mini-vue/src/nodeOpts.ts: -------------------------------------------------------------------------------- 1 | import { VNode, HTMLElementX } from "./type" 2 | import { isString } from "./util" 3 | 4 | export { 5 | nodeOps, 6 | // createElement, 7 | patchProps 8 | } 9 | 10 | const doc = (typeof document !== 'undefined' ? document : null) as Document 11 | type RendererOptions = { 12 | setText(node: HostNode, text: string): void 13 | insert(el: HostNode, parent: HostElement, anchor?: HostNode | null): void 14 | createText(text: string): HostNode 15 | createElement( 16 | type: string 17 | ): HostElement 18 | } 19 | 20 | const nodeOps: RendererOptions = { 21 | insert: (child, parent, anchor) => { 22 | parent.insertBefore(child, anchor || null) 23 | }, 24 | setText: (node, text) => { 25 | node.nodeValue = text 26 | }, 27 | createText: text => doc.createTextNode(text), 28 | createElement: (tag): Element => { 29 | const el = doc.createElement(tag) 30 | return el 31 | }, 32 | } 33 | 34 | function patchProps(dom: HTMLElement, oldProps: VNode['props'], newProps: VNode['props']) { 35 | for (let name in { ...oldProps, ...newProps }) { 36 | let oldValue = oldProps[name], 37 | newValue = newProps[name]; 38 | if (oldValue === newValue || name === "children") { 39 | } else if (name[0] === "o" && name[1] === "n") { // eg: onClick 40 | let eventName = name.slice(2).toLowerCase() 41 | if (oldValue) dom.removeEventListener(name, oldValue) 42 | dom.addEventListener(eventName, newValue) 43 | } else if (isString(newValue)) { 44 | dom.setAttribute(name, newValue) 45 | } 46 | } 47 | 48 | return dom 49 | } 50 | 51 | // function createElement(vnode: VNode): HTMLElementX { 52 | // let dom: HTMLElementX = vnode.type === 'text' ? 53 | // document.createTextNode(vnode.props.value as string) 54 | // : document.createElement(vnode.type as string) 55 | 56 | // if (dom instanceof HTMLElement) { 57 | // patchProps(dom as HTMLElement, {} as VNode['props'], vnode.props) 58 | // } 59 | 60 | // return dom 61 | // } 62 | -------------------------------------------------------------------------------- /packages/mini-vue/src/reactive.ts: -------------------------------------------------------------------------------- 1 | import { track, trackEffect, trigger } from "./effect"; 2 | import { isObject } from "./util" 3 | 4 | export { 5 | shallowReactive, 6 | reactive 7 | } 8 | 9 | const shallowGet = createGetter(true); 10 | const shallowSet = createSetter(true) 11 | const get = createGetter(); 12 | const set = createSetter() 13 | 14 | function createSetter(isShallow: boolean = false) { 15 | return function set(target: any, p: string | symbol, value: any, receiver: any): boolean { 16 | let result = Reflect.set(target, p, value) 17 | trigger(target, p) 18 | return result; 19 | } 20 | } 21 | 22 | function createGetter(isShallow: boolean = false) { 23 | return function get(target: Object, p: string | symbol, receiver: any) { 24 | const value = Reflect.get(target, p, receiver) 25 | track(target, p) 26 | if (isShallow) { 27 | return value 28 | } 29 | 30 | if (isObject(value)) { 31 | return reactive(value) 32 | } 33 | 34 | return value 35 | } 36 | } 37 | 38 | 39 | const shallowReactiveHandlers: ProxyHandler = { 40 | get: shallowGet, 41 | set: shallowSet 42 | } 43 | function shallowReactive(obj: any) { 44 | let proxy = new Proxy(obj, shallowReactiveHandlers) 45 | return proxy 46 | } 47 | 48 | 49 | const mutableHandlers: ProxyHandler = { 50 | get, 51 | set 52 | } 53 | function reactive(obj: any) { 54 | let proxy = new Proxy(obj, mutableHandlers) 55 | return proxy 56 | } -------------------------------------------------------------------------------- /packages/mini-vue/src/ref.ts: -------------------------------------------------------------------------------- 1 | import { createDep, Dep } from "./dep" 2 | import { trackEffect, triggerEffect } from "./effect" 3 | import { isObject,hasChanged } from "./util" 4 | 5 | export { 6 | ref 7 | } 8 | 9 | class RefItem { 10 | private _value: T 11 | private _rawValue: T 12 | public dep?:Dep 13 | public readonly __v_isRef = true 14 | constructor(value: T, public readonly _shallow: boolean = true) { // only handle primitive value for now 15 | this._rawValue = value 16 | this._value = value 17 | } 18 | 19 | get value() { 20 | trackRef(this) 21 | return this._value 22 | } 23 | 24 | set value(newVal) { 25 | if (hasChanged(newVal, this._rawValue)) { 26 | this._rawValue = newVal 27 | this._value = newVal 28 | triggerRef(this, newVal) 29 | } 30 | } 31 | } 32 | 33 | function ref(input: any) { 34 | return new RefItem(input) 35 | } 36 | 37 | function trackRef(ref:RefItem){ 38 | if (!ref.dep) { 39 | ref.dep = createDep() 40 | } 41 | 42 | trackEffect(ref.dep as Dep) 43 | } 44 | 45 | function triggerRef(ref: RefItem, newVal?:any){ 46 | if(ref.dep){ 47 | triggerEffect(ref.dep) 48 | } 49 | } -------------------------------------------------------------------------------- /packages/mini-vue/src/scheduler.ts: -------------------------------------------------------------------------------- 1 | export { 2 | queueJob, 3 | nextTick 4 | } 5 | type SchedulerJob = ()=>any 6 | 7 | let queue: SchedulerJob[] = [] 8 | 9 | let isFlushPending = false; 10 | let isFlushing = false; 11 | const resolvedPromise: Promise = Promise.resolve() 12 | let currentFlushPromise: Promise | null = null 13 | 14 | function flushJobs() { 15 | isFlushing = true 16 | console.log('flushJobs',queue.length) 17 | for(let job of queue){ 18 | job() 19 | } 20 | // queue.forEach(job => job()) 21 | queue = [] // empty queue to avoid Component Scope Trigger 22 | } 23 | 24 | // mainly for batch update schedule 25 | function queueJob(job: SchedulerJob) { 26 | if (!queue.includes(job)){ 27 | queue.push(job) 28 | } 29 | queueFlush() 30 | } 31 | 32 | function queueFlush() { 33 | if (!isFlushing && !isFlushPending) { 34 | isFlushPending = true 35 | currentFlushPromise = resolvedPromise.then(flushJobs).then(_ => { isFlushing = false; isFlushPending = false }) 36 | } 37 | } 38 | 39 | function nextTick( 40 | this: T, 41 | fn?: (this: T) => void 42 | ): Promise { 43 | const p = currentFlushPromise || resolvedPromise 44 | return fn ? p.then(this ? fn.bind(this) : fn) : p 45 | } -------------------------------------------------------------------------------- /packages/mini-vue/src/server-renderer/index.ts: -------------------------------------------------------------------------------- 1 | export {renderToString} from './renderToString' 2 | export {renderToNodeStream,renderToStream} from './renderToStream' -------------------------------------------------------------------------------- /packages/mini-vue/src/server-renderer/render.ts: -------------------------------------------------------------------------------- 1 | import { createComponentInstance, setupComponent } from "../component"; 2 | import { isElement } from "../h"; 3 | import { ComponentChild, ShapeFlags, SimpleNode, TEXT, VNode } from "../type"; 4 | import { isString } from "../util"; 5 | 6 | export { 7 | render, 8 | SSRBuffer 9 | } 10 | type SSRBuffer = (SSRBuffer | string)[] 11 | function createBuffer(){ 12 | let buffer: SSRBuffer = [] 13 | return { 14 | getBuffer(){ 15 | return buffer 16 | }, 17 | push(s:string){ 18 | buffer.push(s) 19 | } 20 | } 21 | } 22 | 23 | function render(vnode:VNode){ 24 | let {getBuffer,push} = createBuffer() 25 | const { type, shapeFlag } = vnode 26 | switch (type) { 27 | case TEXT: push(vnode.props.value + ''); 28 | default: 29 | if (isElement(vnode)) { 30 | renderElement(vnode,push) 31 | } else if (shapeFlag & ShapeFlags.COMPONENT) { 32 | renderComponent(vnode,push) 33 | } 34 | } 35 | return getBuffer() 36 | } 37 | 38 | function renderComponent(n:VNode,push:Function){ 39 | let instance = n.component = createComponentInstance(n) 40 | setupComponent(instance) 41 | let subTree = instance.subTree = instance.render() 42 | push(render(subTree)) 43 | } 44 | 45 | function renderSubTree(children: ComponentChild[],push:Function){ 46 | children.forEach((child, index) => { 47 | push(render(child)) 48 | }) 49 | } 50 | 51 | function renderElement(vnode:VNode,push:Function){ 52 | const { type, props, children } = vnode; 53 | push(`<${type as string}`) 54 | renderProps(props,push) 55 | push('>') 56 | renderSubTree(vnode.children,push) 57 | push(``) 58 | } 59 | 60 | function renderProps(props:VNode['props'],push:Function){ 61 | for(let p in props){ 62 | push(' ') 63 | let v = props[p] 64 | if(isString(v)){ 65 | push(`${p}="${v}"`) 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /packages/mini-vue/src/server-renderer/renderToStream.ts: -------------------------------------------------------------------------------- 1 | import { VNode } from "../type"; 2 | import { isString } from "../util"; 3 | import { render, SSRBuffer } from "./render"; 4 | import { Readable } from 'stream' 5 | export { 6 | renderToStream, 7 | renderToNodeStream 8 | } 9 | export interface SimpleReadable { 10 | push(chunk: string | null): void 11 | destroy(err: any): void 12 | } 13 | 14 | // @ts-ignore 15 | const isNode = typeof process!== undefined 16 | 17 | function unrollBuffer(buffer: SSRBuffer, stream: Readable) { 18 | let ret = '' 19 | buffer.forEach((b, index) => { 20 | if (isString(b)) { 21 | stream.push(b) 22 | } else { 23 | unrollBuffer(b, stream) 24 | } 25 | }) 26 | 27 | return ret 28 | } 29 | 30 | function renderToStream(app: { vnode: VNode }, ctx: any) { 31 | return renderToNodeStream(app, ctx) 32 | } 33 | 34 | async function renderToNodeStream(app: { vnode: VNode }, ctx: any): Readable { 35 | let buffer = render(app.vnode) 36 | 37 | // Why require is not defined 38 | // let stream: Readable = isNode ? new (require('stream').Readable)() : null; 39 | let stream: Readable = new ((await import('stream')).Readable)() 40 | 41 | Promise.resolve(buffer) 42 | .then(buffer => unrollBuffer(buffer, stream)) 43 | .then(() => stream.push(null)) 44 | .catch(error => { 45 | stream.destroy(error) 46 | }) 47 | 48 | return stream 49 | } -------------------------------------------------------------------------------- /packages/mini-vue/src/server-renderer/renderToString.ts: -------------------------------------------------------------------------------- 1 | import { VNode } from "../type"; 2 | import { isArray, isString } from "../util"; 3 | import { render, SSRBuffer } from "./render"; 4 | 5 | export { 6 | renderToString 7 | } 8 | 9 | function unrollBufferSync(buffer:SSRBuffer){ 10 | let ret = '' 11 | buffer.forEach((b,index)=>{ 12 | if (isString(b)) { 13 | ret += b 14 | } else { 15 | ret += unrollBufferSync(b) 16 | } 17 | }) 18 | 19 | return ret 20 | } 21 | 22 | function renderToString(app:{vnode:VNode},ctx:any){ 23 | let buffer = render(app.vnode) 24 | return unrollBufferSync(buffer) 25 | } -------------------------------------------------------------------------------- /packages/mini-vue/src/type.ts: -------------------------------------------------------------------------------- 1 | import { ComponentInternalInstance, ConcreteComponent } from "./component" 2 | 3 | export { 4 | TEXT, 5 | VNode, 6 | VNodeProps, 7 | ShapeFlags, 8 | HTMLElementX, 9 | ComponentChild, 10 | SimpleNode 11 | } 12 | 13 | const TEXT = Symbol('TEXT') 14 | 15 | const enum ShapeFlags { 16 | ELEMENT = 1, 17 | FUNCTIONAL_COMPONENT = 1 << 1, 18 | STATEFUL_COMPONENT = 1 << 2, 19 | COMPONENT = ShapeFlags.STATEFUL_COMPONENT | ShapeFlags.FUNCTIONAL_COMPONENT 20 | } 21 | 22 | type VNodeProps = { 23 | value?: SimpleNode // only if VNode type is SimpleNode 24 | [key: string]: any 25 | } 26 | 27 | type HTMLElementX = HTMLElement | Text 28 | 29 | interface VNode { 30 | type: T 31 | children: ComponentChild[]; 32 | props: VNodeProps 33 | component?:ComponentInternalInstance 34 | el: HTMLElementX | null 35 | shapeFlag: ShapeFlags // VNode type 36 | } 37 | 38 | type ComponentChild = VNode 39 | 40 | type SimpleNode = 41 | | string 42 | | number 43 | -------------------------------------------------------------------------------- /packages/mini-vue/src/util.ts: -------------------------------------------------------------------------------- 1 | export { 2 | createLogger, 3 | isString, 4 | isFunction, 5 | isSimpleNode, 6 | isArray, 7 | isObject, 8 | hasChanged 9 | } 10 | const createLogger = (prefix = "[Mini Vue]") => (...args: any[]) => console.log(`${prefix}`, ...args) 11 | 12 | const isString=(t:unknown): t is string => typeof t === 'string' 13 | 14 | const isNumber = (t:any):t is number => typeof t === 'number' 15 | 16 | const isFunction = (t: any):t is Function => typeof t === 'function' 17 | 18 | const isSimpleNode = (vnode:any) => isNumber(vnode) || isString(vnode) 19 | 20 | const isArray = Array.isArray 21 | 22 | const isObject = (val: unknown): val is Record => val !== null && typeof val === 'object' 23 | 24 | const hasChanged = (value: any, oldValue: any): boolean => 25 | !Object.is(value, oldValue) -------------------------------------------------------------------------------- /packages/mini-webpack/.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: [push] 3 | jobs: 4 | build: 5 | name: Build, lint, and test on Node ${{ matrix.node }} and ${{ matrix.os }} 6 | 7 | runs-on: ${{ matrix.os }} 8 | strategy: 9 | matrix: 10 | node: ['10.x', '12.x', '14.x'] 11 | os: [ubuntu-latest, windows-latest, macOS-latest] 12 | 13 | steps: 14 | - name: Checkout repo 15 | uses: actions/checkout@v2 16 | 17 | - name: Use Node ${{ matrix.node }} 18 | uses: actions/setup-node@v1 19 | with: 20 | node-version: ${{ matrix.node }} 21 | 22 | - name: Install deps and build (with cache) 23 | uses: bahmutov/npm-install@v1 24 | 25 | - name: Lint 26 | run: yarn lint 27 | 28 | - name: Test 29 | run: yarn test --ci --coverage --maxWorkers=2 30 | 31 | - name: Build 32 | run: yarn build 33 | -------------------------------------------------------------------------------- /packages/mini-webpack/.github/workflows/size.yml: -------------------------------------------------------------------------------- 1 | name: size 2 | on: [pull_request] 3 | jobs: 4 | size: 5 | runs-on: ubuntu-latest 6 | env: 7 | CI_JOB_NUMBER: 1 8 | steps: 9 | - uses: actions/checkout@v1 10 | - uses: andresz1/size-limit-action@v1 11 | with: 12 | github_token: ${{ secrets.GITHUB_TOKEN }} 13 | -------------------------------------------------------------------------------- /packages/mini-webpack/.gitignore: -------------------------------------------------------------------------------- 1 | *.log 2 | .DS_Store 3 | node_modules 4 | dist 5 | -------------------------------------------------------------------------------- /packages/mini-webpack/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 刘泽 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /packages/mini-webpack/README.md: -------------------------------------------------------------------------------- 1 | ## Introduction 2 | 3 | mini implementation of [webpack](https://github.com/webpack/webpack) 4 | 5 | ## How to test 6 | 7 | ``` 8 | npm install 9 | npm run dev 10 | ``` 11 | ## Reference 12 | * https://juejin.cn/post/6844903858179670030 -------------------------------------------------------------------------------- /packages/mini-webpack/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.1.0", 3 | "license": "MIT", 4 | "main": "dist/index.js", 5 | "typings": "dist/index.d.ts", 6 | "files": [ 7 | "dist", 8 | "src" 9 | ], 10 | "engines": { 11 | "node": ">=10" 12 | }, 13 | "scripts": { 14 | "start": "tsdx watch", 15 | "build": "tsdx build", 16 | "test": "tsdx test", 17 | "lint": "tsdx lint", 18 | "prepare": "tsdx build", 19 | "size": "size-limit", 20 | "analyze": "size-limit --why" 21 | }, 22 | "peerDependencies": {}, 23 | "husky": { 24 | "hooks": { 25 | "pre-commit": "tsdx lint" 26 | } 27 | }, 28 | "prettier": { 29 | "printWidth": 80, 30 | "semi": true, 31 | "singleQuote": true, 32 | "trailingComma": "es5" 33 | }, 34 | "name": "mini-webpack", 35 | "author": "刘泽", 36 | "module": "dist/mini-webpack.esm.js", 37 | "size-limit": [ 38 | { 39 | "path": "dist/mini-webpack.cjs.production.min.js", 40 | "limit": "10 KB" 41 | }, 42 | { 43 | "path": "dist/mini-webpack.esm.js", 44 | "limit": "10 KB" 45 | } 46 | ], 47 | "directories": { 48 | "test": "test" 49 | }, 50 | "devDependencies": { 51 | "@babel/core": "^7.15.0", 52 | "@babel/parser": "^7.15.2", 53 | "@babel/preset-env": "^7.15.0", 54 | "@babel/traverse": "^7.15.0", 55 | "@size-limit/preset-small-lib": "^5.0.2", 56 | "@types/jest": "^26.0.24", 57 | "@types/node": "^16.4.13", 58 | "husky": "^7.0.1", 59 | "js-beautify": "^1.14.0", 60 | "size-limit": "^5.0.2", 61 | "tsdx": "^0.14.1", 62 | "tslib": "^2.3.0", 63 | "typescript": "^4.3.5" 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /packages/mini-webpack/test/examples/index.js: -------------------------------------------------------------------------------- 1 | import message from './message.js' 2 | console.log(message) -------------------------------------------------------------------------------- /packages/mini-webpack/test/examples/message.js: -------------------------------------------------------------------------------- 1 | import { 2 | word 3 | } from './word.js'; 4 | const message = `${word}` 5 | export default message; -------------------------------------------------------------------------------- /packages/mini-webpack/test/examples/word.js: -------------------------------------------------------------------------------- 1 | export const word = 'hello world' -------------------------------------------------------------------------------- /packages/mini-webpack/test/fixtures/mini/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Getting Started 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /packages/mini-webpack/test/fixtures/mini/src/b.js: -------------------------------------------------------------------------------- 1 | let b = 'this is b' 2 | console.log(b) 3 | 4 | export default b -------------------------------------------------------------------------------- /packages/mini-webpack/test/fixtures/mini/src/index.js: -------------------------------------------------------------------------------- 1 | import b from './b.js' 2 | console.log('this is index read ',b) 3 | import('./message.js').then(mod=>{ 4 | console.log('this is index: ',mod.default) 5 | }) -------------------------------------------------------------------------------- /packages/mini-webpack/test/fixtures/mini/src/message.js: -------------------------------------------------------------------------------- 1 | // import {word} from './word.js'; 2 | const {word} = require('./word.js'); 3 | const message = `${word}` 4 | console.log('this is message') 5 | export default message; -------------------------------------------------------------------------------- /packages/mini-webpack/test/fixtures/mini/src/word.js: -------------------------------------------------------------------------------- 1 | console.log('this is word') 2 | export const word = 'hello world' 3 | -------------------------------------------------------------------------------- /packages/mini-webpack/test/fixtures/mini/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | module.exports = { 4 | entry: './src/index.js', 5 | output: { 6 | filename: 'main.js', 7 | path: path.resolve(__dirname, 'dist'), 8 | }, 9 | optimization: { 10 | minimize: false 11 | } 12 | 13 | }; -------------------------------------------------------------------------------- /packages/mini-webpack/test/index.test.ts: -------------------------------------------------------------------------------- 1 | import { build } from '../src'; 2 | import path from 'path' 3 | import fs from 'fs' 4 | import vm from 'vm' 5 | // @ts-ignore 6 | import { js } from 'js-beautify' 7 | 8 | describe('mini webpack', () => { 9 | it('build', () => { 10 | let code = js(build(path.join(__dirname, '/examples/index.js'))) 11 | console.log = jest.fn(); 12 | 13 | let distCodePath = path.join(__dirname, `/test_dist/main.js`) 14 | fs.writeFileSync(distCodePath, code); 15 | // eval(code) 16 | new vm.Script(code).runInContext(vm.createContext({ console: console })) 17 | 18 | expect(console.log).toHaveBeenCalledWith('hello world'); 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /packages/mini-webpack/test/test_dist/main.js: -------------------------------------------------------------------------------- 1 | (function(modules) { 2 | 3 | var installedModules = {}; 4 | 5 | function __mini_require(id) { 6 | const [fn, dependencies] = modules[id]; 7 | 8 | function _localRequire(subModule) { 9 | return __mini_require(dependencies[subModule]) 10 | } 11 | 12 | if (installedModules[id]) { 13 | return installedModules[id].exports 14 | } 15 | 16 | let module = installedModules[id] = { 17 | id: id, 18 | exports: {} 19 | }; 20 | 21 | fn(_localRequire, module, module.exports) 22 | 23 | return module.exports; 24 | } 25 | 26 | __mini_require(0) 27 | 28 | // cache 29 | __mini_require.c = installedModules 30 | 31 | })({ 32 | "0": [ 33 | function(require, module, exports) { 34 | "use strict"; 35 | 36 | var _message = _interopRequireDefault(require("./message.js")); 37 | 38 | function _interopRequireDefault(obj) { 39 | return obj && obj.__esModule ? obj : { 40 | "default": obj 41 | }; 42 | } 43 | 44 | console.log(_message["default"]); 45 | }, 46 | { 47 | "./message.js": 1 48 | }, 49 | ], 50 | "1": [ 51 | function(require, module, exports) { 52 | "use strict"; 53 | 54 | Object.defineProperty(exports, "__esModule", { 55 | value: true 56 | }); 57 | exports["default"] = void 0; 58 | 59 | var _word = require("./word.js"); 60 | 61 | var message = "".concat(_word.word); 62 | var _default = message; 63 | exports["default"] = _default; 64 | }, 65 | { 66 | "./word.js": 2 67 | }, 68 | ], 69 | "2": [ 70 | function(require, module, exports) { 71 | "use strict"; 72 | 73 | Object.defineProperty(exports, "__esModule", { 74 | value: true 75 | }); 76 | exports.word = void 0; 77 | var word = 'hello world'; 78 | exports.word = word; 79 | }, 80 | {}, 81 | ], 82 | }) -------------------------------------------------------------------------------- /packages/mini-webpack/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | // see https://www.typescriptlang.org/tsconfig to better understand tsconfigs 3 | "include": [ 4 | "src", 5 | "types" 6 | ], 7 | "compilerOptions": { 8 | "module": "esnext", 9 | "lib": [ 10 | "dom", 11 | "esnext" 12 | ], 13 | "importHelpers": true, 14 | // output .d.ts declaration files for consumers 15 | "declaration": true, 16 | // output .js.map sourcemap files for consumers 17 | "sourceMap": true, 18 | // match output dir to input dir. e.g. dist/index instead of dist/src/index 19 | "rootDir": "./src", 20 | // stricter type-checking for stronger correctness. Recommended by TS 21 | "strict": true, 22 | // linter checks for common issues 23 | "noImplicitReturns": true, 24 | "noFallthroughCasesInSwitch": true, 25 | // noUnused* overlap with @typescript-eslint/no-unused-vars, can disable if duplicative 26 | "noUnusedLocals": true, 27 | "noUnusedParameters": true, 28 | // use Node's module resolution algorithm, instead of the legacy TS one 29 | "moduleResolution": "node", 30 | // transpile JSX to React.createElement 31 | "jsx": "react", 32 | // interop between ESM and CJS modules. Recommended by TS 33 | "esModuleInterop": true, 34 | // significant perf increase by skipping checking .d.ts files, particularly those in node_modules. Recommended by TS 35 | "skipLibCheck": true, 36 | // error out if import and file system have a casing mismatch. Recommended by TS 37 | "forceConsistentCasingInFileNames": true, 38 | // `tsdx build` ignores this option, but it is commonly used when type-checking separately with `tsc` 39 | "noEmit": true, 40 | } 41 | } -------------------------------------------------------------------------------- /packages/mini-webpack/webpack-demo/_dist/384.main.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | (self["webpackChunkwebpack_demo"] = self["webpackChunkwebpack_demo"] || []).push([[384],{ 3 | 4 | /***/ 384: 5 | /***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { 6 | 7 | __webpack_require__.r(__webpack_exports__); 8 | /* harmony export */ __webpack_require__.d(__webpack_exports__, { 9 | /* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__) 10 | /* harmony export */ }); 11 | // import {word} from './word.js'; 12 | const {word} = __webpack_require__(280); 13 | const message = `${word}` 14 | console.log('this is message') 15 | /* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (message); 16 | 17 | /***/ }), 18 | 19 | /***/ 280: 20 | /***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { 21 | 22 | __webpack_require__.r(__webpack_exports__); 23 | /* harmony export */ __webpack_require__.d(__webpack_exports__, { 24 | /* harmony export */ "word": () => (/* binding */ word) 25 | /* harmony export */ }); 26 | console.log('this is word') 27 | const word = 'hello world' 28 | 29 | 30 | /***/ }) 31 | 32 | }]); -------------------------------------------------------------------------------- /packages/mini-webpack/webpack-demo/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Getting Started 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /packages/mini-webpack/webpack-demo/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "webpack-demo", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "build": "webpack" 8 | }, 9 | "keywords": [], 10 | "author": "刘泽", 11 | "license": "ISC", 12 | "devDependencies": { 13 | "webpack": "^5.49.0", 14 | "webpack-cli": "^4.7.2" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /packages/mini-webpack/webpack-demo/src/b.js: -------------------------------------------------------------------------------- 1 | let b = 'this is b' 2 | console.log(b) 3 | 4 | export default b -------------------------------------------------------------------------------- /packages/mini-webpack/webpack-demo/src/index.js: -------------------------------------------------------------------------------- 1 | import b from './b.js' 2 | console.log('this is index read ',b) 3 | import('./message.js').then(mod=>{ 4 | console.log('this is index: ',mod.default) 5 | }) -------------------------------------------------------------------------------- /packages/mini-webpack/webpack-demo/src/message.js: -------------------------------------------------------------------------------- 1 | // import {word} from './word.js'; 2 | const {word} = require('./word.js'); 3 | const message = `${word}` 4 | console.log('this is message') 5 | export default message; -------------------------------------------------------------------------------- /packages/mini-webpack/webpack-demo/src/word.js: -------------------------------------------------------------------------------- 1 | console.log('this is word') 2 | export const word = 'hello world' 3 | -------------------------------------------------------------------------------- /packages/mini-webpack/webpack-demo/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | module.exports = { 4 | entry: './src/index.js', 5 | output: { 6 | filename: 'main.js', 7 | path: path.resolve(__dirname, '_dist'), 8 | }, 9 | optimization: { 10 | minimize: false 11 | } 12 | 13 | }; -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | ## Introduction 2 | 3 | Frameworks in mini version 4 | 5 | eg: [ React-Hooks, Vite, Vue, Svelte, Webpack, Koa, Inversify, RPC, Express, etc] 6 | 7 | More info: packages/* readme.md 8 | 9 | ## Philosophy 10 | 11 | **Learn by doing, explain things in the simplest way** 12 | 13 | **理解框架最快的方式是亲自实现一遍** 14 | 15 | ## Bootstrap 16 | #### Option1: partial install 17 | Refer to each package's readme.md 18 | #### Option2: full install 19 | ``` 20 | npm install -g lerna 21 | lerna bootstrap # Link local packages together and install remaining package dependencies 22 | ``` 23 | --------------------------------------------------------------------------------