├── .husky └── commit-msg ├── apps ├── dev │ ├── src │ │ ├── App.css │ │ ├── OK.view.ts │ │ ├── App.view.ts │ │ ├── index.ts │ │ ├── benchmark │ │ │ ├── data.js │ │ │ ├── benchmark-snippets.view.js │ │ │ └── benchmark-comp.view.js │ │ ├── Test.view.ts │ │ ├── Fine.view.ts │ │ └── sandbox.ts │ ├── vite.config.ts │ ├── tsconfig.json │ ├── index.html │ └── package.json ├── bun-static-server │ ├── bunfig.toml │ ├── app │ │ ├── index.ts │ │ ├── index.html │ │ └── main.view.ts │ ├── index.ts │ ├── bun-dlight-plugin.ts │ ├── README.md │ ├── package.json │ ├── tsconfig.json │ └── .gitignore ├── ToDoMVC │ ├── src │ │ ├── index.ts │ │ └── base.css │ ├── CHANGELOG.md │ ├── vite.config.ts │ ├── index.html │ ├── package.json │ └── tsconfig.json └── example │ ├── src │ ├── App.view.js │ ├── introduction │ │ ├── Hello.view.js │ │ ├── Variable.view.js │ │ ├── Link.view.js │ │ ├── Reactivity.view.js │ │ ├── Function.view.js │ │ ├── Image.view.js │ │ ├── nestHtml.view.js │ │ └── ReactiveStatement.view.js │ ├── nestComp │ │ ├── basic │ │ │ ├── ChildNode.view.js │ │ │ └── ParentNode.view.js │ │ ├── children │ │ │ └── Children.view.js │ │ └── content │ │ │ └── Content.view.js │ ├── style │ │ └── InlineStyle.view.js │ ├── envNode │ │ ├── ChildComp.view.js │ │ └── RootComp.view.js │ ├── lifeCycle │ │ ├── didMount.view.js │ │ └── willMount.view.js │ ├── expression │ │ └── Expression.view.js │ ├── forNode │ │ └── For.view.js │ ├── inputs │ │ ├── TextInput.view.js │ │ ├── RadioInput.view.js │ │ └── CheckboxInput.view.js │ ├── element │ │ ├── CompElement.view.js │ │ └── Element.view.js │ ├── ifNode │ │ ├── If.view.js │ │ └── ifelse.view.js │ ├── events │ │ ├── InlineHandler.view.js │ │ └── AddEvent.view.js │ ├── forwardProps │ │ ├── ChildComp.view.js │ │ └── ParentComp.view.js │ ├── snippet │ │ └── ViewWithSnippet.view.js │ ├── propView │ │ └── PropView.view.js │ └── index.js │ ├── index.html │ ├── vite.config.ts │ ├── tsconfig.json │ └── package.json ├── packages ├── core │ ├── store │ │ ├── src │ │ │ └── index.ts │ │ ├── tsconfig.json │ │ └── package.json │ ├── dlight │ │ ├── src │ │ │ ├── index.d.ts │ │ │ ├── types │ │ │ │ ├── envTag.d.ts │ │ │ │ ├── expressionTag.d.ts │ │ │ │ ├── model.d.ts │ │ │ │ ├── index.d.ts │ │ │ │ ├── htmlTag │ │ │ │ │ └── htmlElement.d.ts │ │ │ │ └── compTag.d.ts │ │ │ ├── SnippetNode.js │ │ │ ├── TextNode.js │ │ │ ├── MutableNode │ │ │ │ ├── FlatNode.js │ │ │ │ ├── TryNode.js │ │ │ │ ├── MutableNode.js │ │ │ │ ├── CondNode.js │ │ │ │ └── ExpNode.js │ │ │ ├── store.js │ │ │ ├── PropView.js │ │ │ ├── index.js │ │ │ └── EnvNode.js │ │ ├── README.md │ │ ├── tsconfig.json │ │ └── package.json │ ├── babel-preset-dlight │ │ ├── src │ │ │ ├── global.d.ts │ │ │ ├── index.ts │ │ │ ├── plugin.ts │ │ │ └── types.ts │ │ ├── tsconfig.json │ │ └── package.json │ └── transpiler │ │ ├── reactivity-parser │ │ ├── src │ │ │ ├── error.ts │ │ │ ├── index.ts │ │ │ ├── test │ │ │ │ ├── MutableTagParticle.test.ts │ │ │ │ ├── Template.test.ts │ │ │ │ └── OtherParticle.test.ts │ │ │ └── types.ts │ │ ├── tsconfig.json │ │ ├── package.json │ │ └── CHANGELOG.md │ │ ├── view-generator │ │ ├── tsconfig.json │ │ ├── src │ │ │ ├── types.ts │ │ │ ├── error.ts │ │ │ ├── HelperGenerators │ │ │ │ ├── ForwardPropGenerator.ts │ │ │ │ ├── LifecycleGenerator.ts │ │ │ │ └── ElementGenerator.ts │ │ │ ├── index.ts │ │ │ ├── NodeGenerators │ │ │ │ ├── TextGenerator.ts │ │ │ │ ├── ExpGenerator.ts │ │ │ │ ├── TryGenerator.ts │ │ │ │ └── HTMLGenerator.ts │ │ │ └── MainViewGenerator.ts │ │ └── package.json │ │ └── view-parser │ │ ├── tsconfig.json │ │ ├── src │ │ ├── index.ts │ │ ├── error.ts │ │ ├── test │ │ │ ├── TryUnit.test.ts │ │ │ ├── SwitchUnit.test.ts │ │ │ ├── IfUnit.test.ts │ │ │ └── TextUnit.test.ts │ │ └── types.ts │ │ ├── CHANGELOG.md │ │ └── package.json └── tools │ ├── create-dlightjs │ ├── templates │ │ ├── dlight-vite-ts │ │ │ ├── src │ │ │ │ ├── typings.d.ts │ │ │ │ ├── index.ts │ │ │ │ ├── Header.view.ts │ │ │ │ ├── App.view.ts │ │ │ │ └── style.module.css │ │ │ ├── public │ │ │ │ └── logo_title.png │ │ │ ├── vite.config.ts │ │ │ ├── tsconfig.json │ │ │ ├── index.html │ │ │ └── package.json │ │ ├── dlight-vite-js │ │ │ ├── src │ │ │ │ ├── index.js │ │ │ │ ├── Button.view.js │ │ │ │ ├── Header.view.js │ │ │ │ ├── App.view.js │ │ │ │ └── style.module.css │ │ │ ├── public │ │ │ │ └── logo_title.png │ │ │ ├── vite.config.js │ │ │ ├── index.html │ │ │ └── package.json │ │ ├── dlight-vite-min-js │ │ │ ├── src │ │ │ │ └── App.view.js │ │ │ ├── vite.config.js │ │ │ ├── index.html │ │ │ └── package.json │ │ └── dlight-vite-min-ts │ │ │ ├── src │ │ │ └── App.view.ts │ │ │ ├── vite.config.ts │ │ │ ├── tsconfig.json │ │ │ ├── index.html │ │ │ └── package.json │ ├── tsup.config.ts │ ├── package.json │ ├── tsconfig.json │ ├── src │ │ └── utils.ts │ └── CHANGELOG.md │ ├── babel-plugin-syntax-typescript-new │ ├── src │ │ ├── global.d.ts │ │ └── index.ts │ ├── tsconfig.json │ └── package.json │ ├── eslint-plugin-dlight │ ├── src │ │ └── index.ts │ ├── tsconfig.json │ └── package.json │ ├── babel-plugin-optional-this │ ├── tsup.config.ts │ ├── tsconfig.json │ ├── package.json │ └── src │ │ └── index.ts │ ├── error-handler │ ├── tsconfig.json │ ├── package.json │ └── src │ │ └── index.ts │ ├── vite-plugin-dlight │ ├── tsconfig.json │ ├── package.json │ └── src │ │ └── index.ts │ └── bun-plugin-dlight │ ├── tsconfig.json │ ├── CHANGELOG.md │ ├── src │ └── index.ts │ ├── package.json │ └── README.md ├── docs └── imgs │ ├── logo.png │ ├── logo_title.png │ ├── memory-alpha36.png │ ├── test-view-parser.png │ ├── performance-alpha36.png │ └── test-reactivity-parser.png ├── commitlint.config.js ├── pnpm-workspace.yaml ├── .prettierrc ├── turbo.json ├── .changeset └── config.json ├── .gitignore ├── .eslintrc.json ├── package.json ├── LICENSE └── .all-contributorsrc /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | npx --no-install commitlint --edit "$1" -------------------------------------------------------------------------------- /apps/dev/src/App.css: -------------------------------------------------------------------------------- 1 | .ok { 2 | color: var(--color-ok); 3 | } -------------------------------------------------------------------------------- /packages/core/store/src/index.ts: -------------------------------------------------------------------------------- 1 | export const Store = {} 2 | -------------------------------------------------------------------------------- /packages/core/dlight/src/index.d.ts: -------------------------------------------------------------------------------- 1 | export * from "./types/index" 2 | -------------------------------------------------------------------------------- /docs/imgs/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dlight-js/dlight/HEAD/docs/imgs/logo.png -------------------------------------------------------------------------------- /commitlint.config.js: -------------------------------------------------------------------------------- 1 | export default { extends: ['@commitlint/config-conventional'] }; 2 | -------------------------------------------------------------------------------- /apps/bun-static-server/bunfig.toml: -------------------------------------------------------------------------------- 1 | [serve.static] 2 | plugins = ["./bun-dlight-plugin.ts"] 3 | -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | - 'packages/**' 3 | - 'apps/**' 4 | - '!**/templates/**' -------------------------------------------------------------------------------- /docs/imgs/logo_title.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dlight-js/dlight/HEAD/docs/imgs/logo_title.png -------------------------------------------------------------------------------- /docs/imgs/memory-alpha36.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dlight-js/dlight/HEAD/docs/imgs/memory-alpha36.png -------------------------------------------------------------------------------- /packages/core/dlight/README.md: -------------------------------------------------------------------------------- 1 | # DLight Main Package 2 | See the website's documentations for usage. 3 | -------------------------------------------------------------------------------- /packages/tools/create-dlightjs/templates/dlight-vite-ts/src/typings.d.ts: -------------------------------------------------------------------------------- 1 | declare module "*.module.css"; 2 | -------------------------------------------------------------------------------- /docs/imgs/test-view-parser.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dlight-js/dlight/HEAD/docs/imgs/test-view-parser.png -------------------------------------------------------------------------------- /packages/tools/babel-plugin-syntax-typescript-new/src/global.d.ts: -------------------------------------------------------------------------------- 1 | declare module "@babel/helper-plugin-utils" 2 | -------------------------------------------------------------------------------- /docs/imgs/performance-alpha36.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dlight-js/dlight/HEAD/docs/imgs/performance-alpha36.png -------------------------------------------------------------------------------- /docs/imgs/test-reactivity-parser.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dlight-js/dlight/HEAD/docs/imgs/test-reactivity-parser.png -------------------------------------------------------------------------------- /apps/bun-static-server/app/index.ts: -------------------------------------------------------------------------------- 1 | import { render } from "@dlightjs/dlight" 2 | import App from "./main.view" 3 | 4 | render('app', App) -------------------------------------------------------------------------------- /apps/ToDoMVC/src/index.ts: -------------------------------------------------------------------------------- 1 | import { render } from "@dlightjs/dlight" 2 | import ToDoMVC from "./ToDoMVC.view" 3 | 4 | render("app", ToDoMVC) 5 | -------------------------------------------------------------------------------- /packages/core/babel-preset-dlight/src/global.d.ts: -------------------------------------------------------------------------------- 1 | declare module "@babel/plugin-syntax-do-expressions" 2 | declare module "@babel/plugin-syntax-decorators" 3 | -------------------------------------------------------------------------------- /packages/tools/create-dlightjs/templates/dlight-vite-ts/src/index.ts: -------------------------------------------------------------------------------- 1 | import { render } from "@dlightjs/dlight" 2 | import App from "./App.view" 3 | 4 | render("app", App) -------------------------------------------------------------------------------- /packages/tools/create-dlightjs/templates/dlight-vite-js/src/index.js: -------------------------------------------------------------------------------- 1 | import { render } from "@dlightjs/dlight" 2 | import App from "./App.view" 3 | 4 | render("app", App) 5 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": false, 3 | "singleQuote": false, 4 | "trailingComma": "es5", 5 | "bracketSpacing": true, 6 | "arrowParens": "avoid", 7 | "printWidth": 80 8 | } 9 | -------------------------------------------------------------------------------- /apps/example/src/App.view.js: -------------------------------------------------------------------------------- 1 | import { View, Main } from "@dlightjs/dlight" 2 | 3 | @Main 4 | @View 5 | class App { 6 | Body() { 7 | button("+") 8 | } 9 | } 10 | 11 | export default App 12 | -------------------------------------------------------------------------------- /packages/tools/create-dlightjs/templates/dlight-vite-js/public/logo_title.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dlight-js/dlight/HEAD/packages/tools/create-dlightjs/templates/dlight-vite-js/public/logo_title.png -------------------------------------------------------------------------------- /packages/tools/create-dlightjs/templates/dlight-vite-ts/public/logo_title.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dlight-js/dlight/HEAD/packages/tools/create-dlightjs/templates/dlight-vite-ts/public/logo_title.png -------------------------------------------------------------------------------- /apps/ToDoMVC/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # @dlightjs/todomvc-app 2 | 3 | ## 0.0.1 4 | 5 | ### Patch Changes 6 | 7 | - Updated dependencies [0398c02] 8 | - Updated dependencies [4814089] 9 | - @dlightjs/dlight@1.0.1 10 | -------------------------------------------------------------------------------- /apps/ToDoMVC/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "vite" 2 | import dlight from "vite-plugin-dlight" 3 | 4 | export default defineConfig({ 5 | plugins: [dlight({ files: "**/*.view.{ts,js}" })], 6 | }) 7 | -------------------------------------------------------------------------------- /apps/example/src/introduction/Hello.view.js: -------------------------------------------------------------------------------- 1 | import { View } from "@dlightjs/dlight" 2 | 3 | @View 4 | class Hello { 5 | Body() { 6 | div("hello world!") 7 | } 8 | } 9 | 10 | export default Hello 11 | -------------------------------------------------------------------------------- /packages/core/dlight/src/types/envTag.d.ts: -------------------------------------------------------------------------------- 1 | // ---- env 2 | import { DLightObject } from "./compTag" 3 | 4 | type AnyEnv = { _$anyEnv: true } 5 | 6 | export const env: () => T extends AnyEnv ? any : DLightObject 7 | -------------------------------------------------------------------------------- /packages/tools/create-dlightjs/templates/dlight-vite-min-js/src/App.view.js: -------------------------------------------------------------------------------- 1 | import { View, div } from "@dlightjs/dlight" 2 | 3 | @Main 4 | @View 5 | class App { 6 | Body() { 7 | div("hello dlight!") 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/core/transpiler/reactivity-parser/src/error.ts: -------------------------------------------------------------------------------- 1 | import { createErrorHandler } from "@dlightjs/error-handler" 2 | 3 | export const DLError = createErrorHandler("ReactivityParser", { 4 | 1: "Invalid ViewUnit type", 5 | }) 6 | -------------------------------------------------------------------------------- /packages/tools/create-dlightjs/templates/dlight-vite-min-ts/src/App.view.ts: -------------------------------------------------------------------------------- 1 | import { View, Main, div } from "@dlightjs/dlight" 2 | 3 | @Main 4 | @View 5 | class App { 6 | Body() { 7 | div("hello dlight") 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/tools/create-dlightjs/tsup.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "tsup" 2 | 3 | export default defineConfig({ 4 | entry: ["src/index.ts"], 5 | format: ["esm"], 6 | clean: true, 7 | dts: true, 8 | minify: true 9 | }) 10 | -------------------------------------------------------------------------------- /apps/example/src/introduction/Variable.view.js: -------------------------------------------------------------------------------- 1 | 2 | import { View } from "@dlightjs/dlight" 3 | 4 | @View 5 | class Variable { 6 | name = "John" 7 | 8 | Body() { 9 | div(`Hello ${this.name}!`) 10 | } 11 | } 12 | 13 | export default Variable 14 | -------------------------------------------------------------------------------- /apps/example/src/nestComp/basic/ChildNode.view.js: -------------------------------------------------------------------------------- 1 | import { View } from "@dlightjs/dlight" 2 | 3 | @View 4 | class ChildComp { 5 | @Prop name = "John" 6 | 7 | Body() { 8 | div(`I am the child ${this.name}!`) 9 | } 10 | } 11 | 12 | export default ChildComp 13 | -------------------------------------------------------------------------------- /packages/tools/create-dlightjs/templates/dlight-vite-js/vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import dlight from "vite-plugin-dlight" 3 | 4 | export default defineConfig({ 5 | plugins: [ 6 | dlight({ files: "**/*.{view,model}.js" }) 7 | ] 8 | }); -------------------------------------------------------------------------------- /packages/tools/create-dlightjs/templates/dlight-vite-min-js/vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import dlight from "vite-plugin-dlight" 3 | 4 | export default defineConfig({ 5 | plugins: [ 6 | dlight({ files: "**/*.{view,model}.js" }) 7 | ] 8 | }); -------------------------------------------------------------------------------- /packages/tools/create-dlightjs/templates/dlight-vite-min-ts/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import dlight from "vite-plugin-dlight" 3 | 4 | export default defineConfig({ 5 | plugins: [ 6 | dlight({ files: "**/*.{view,model}.ts" }) 7 | ] 8 | }); -------------------------------------------------------------------------------- /packages/tools/create-dlightjs/templates/dlight-vite-ts/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import dlight from "vite-plugin-dlight" 3 | 4 | export default defineConfig({ 5 | plugins: [ 6 | dlight({ files: "**/*.{view,model}.ts" }) 7 | ] 8 | }); -------------------------------------------------------------------------------- /packages/tools/eslint-plugin-dlight/src/index.ts: -------------------------------------------------------------------------------- 1 | export const rules = { 2 | "@typescript-eslint/no-unused-vars": [ 3 | "error", 4 | { 5 | vars: "all", 6 | args: "after-used", 7 | argsIgnorePattern: "^(View|_)", 8 | }, 9 | ], 10 | } 11 | -------------------------------------------------------------------------------- /apps/example/src/style/InlineStyle.view.js: -------------------------------------------------------------------------------- 1 | import { View } from "@dlightjs/dlight" 2 | 3 | @View 4 | class InlineStyle { 5 | Body() { 6 | div("I am a red text") 7 | .style({ color: "red", fontWeight: "bold" }) 8 | } 9 | } 10 | 11 | export default InlineStyle 12 | -------------------------------------------------------------------------------- /packages/tools/babel-plugin-optional-this/tsup.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "tsup" 2 | 3 | export default defineConfig({ 4 | entry: ["src/index.ts"], 5 | clean: true, 6 | dts: true, 7 | outDir: "dist", 8 | format: ["esm"], 9 | minify: true 10 | }) 11 | -------------------------------------------------------------------------------- /apps/bun-static-server/index.ts: -------------------------------------------------------------------------------- 1 | import app from './app/index.html' 2 | 3 | const server = Bun.serve({ 4 | static: { '/': app }, 5 | fetch(request) { 6 | return new Response("Not Found", { status: 404 }) 7 | } 8 | }) 9 | 10 | console.log(`Listening on ${server.url}`); 11 | -------------------------------------------------------------------------------- /packages/tools/error-handler/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "module": "ESNext", 5 | "lib": ["ESNext", "DOM"], 6 | "moduleResolution": "Node", 7 | "strict": true 8 | }, 9 | "ts-node": { 10 | "esm": true 11 | } 12 | } -------------------------------------------------------------------------------- /apps/example/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Dlight.JS 6 | 7 | 8 |
9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /packages/tools/eslint-plugin-dlight/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "module": "ESNext", 5 | "lib": ["ESNext", "DOM"], 6 | "moduleResolution": "Node", 7 | "strict": true 8 | }, 9 | "ts-node": { 10 | "esm": true 11 | } 12 | } -------------------------------------------------------------------------------- /apps/example/src/envNode/ChildComp.view.js: -------------------------------------------------------------------------------- 1 | import { View } from "@dlightjs/dlight" 2 | 3 | @View 4 | class ChildComp { 5 | @Env textColor = "black" 6 | 7 | Body() { 8 | div("I'm supposed to be red").style({ color: this.textColor }) 9 | } 10 | } 11 | 12 | export default ChildComp 13 | -------------------------------------------------------------------------------- /packages/core/store/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "module": "ESNext", 5 | "lib": ["ESNext", "DOM"], 6 | "moduleResolution": "Node", 7 | "strict": true, 8 | "esModuleInterop": true 9 | }, 10 | "ts-node": { 11 | "esm": true 12 | } 13 | } -------------------------------------------------------------------------------- /apps/example/src/lifeCycle/didMount.view.js: -------------------------------------------------------------------------------- 1 | import { View } from "@dlightjs/dlight" 2 | 3 | @View 4 | class DidMount { 5 | name = "John" 6 | 7 | didMount() { 8 | this.name = "Cindy" 9 | } 10 | 11 | Body() { 12 | div(`hello ${this.name}!`) 13 | } 14 | } 15 | 16 | export default DidMount 17 | -------------------------------------------------------------------------------- /packages/core/dlight/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "module": "ESNext", 5 | "lib": ["ESNext", "DOM"], 6 | "moduleResolution": "Node", 7 | "strict": true, 8 | "esModuleInterop": true 9 | }, 10 | "ts-node": { 11 | "esm": true 12 | } 13 | } -------------------------------------------------------------------------------- /packages/tools/create-dlightjs/templates/dlight-vite-ts/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "module": "ESNext", 5 | "lib": ["ESNext", "DOM"], 6 | "moduleResolution": "Node", 7 | "strict": true 8 | }, 9 | "ts-node": { 10 | "esm": true 11 | } 12 | } -------------------------------------------------------------------------------- /apps/bun-static-server/bun-dlight-plugin.ts: -------------------------------------------------------------------------------- 1 | import { dlightPlugin } from "bun-plugin-dlight"; 2 | 3 | // initialize the dlight plugin here 4 | // because bun static plugins require a plugin object 5 | // 6 | // @see: 7 | // /bunfig.toml, 8 | // https://bun.sh/docs/bundler/fullstack#plugins 9 | export default dlightPlugin() -------------------------------------------------------------------------------- /packages/tools/create-dlightjs/templates/dlight-vite-min-ts/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "module": "ESNext", 5 | "lib": ["ESNext", "DOM"], 6 | "moduleResolution": "Node", 7 | "strict": true 8 | }, 9 | "ts-node": { 10 | "esm": true 11 | } 12 | } -------------------------------------------------------------------------------- /packages/core/babel-preset-dlight/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "module": "ESNext", 5 | "lib": ["ESNext", "DOM"], 6 | "moduleResolution": "Node", 7 | "strict": true, 8 | "esModuleInterop": true 9 | }, 10 | "ts-node": { 11 | "esm": true 12 | } 13 | } -------------------------------------------------------------------------------- /packages/tools/vite-plugin-dlight/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "module": "ESNext", 5 | "lib": ["ESNext", "DOM"], 6 | "moduleResolution": "Node", 7 | "strict": true, 8 | "esModuleInterop": true 9 | }, 10 | "ts-node": { 11 | "esm": true 12 | } 13 | } -------------------------------------------------------------------------------- /apps/example/src/introduction/Link.view.js: -------------------------------------------------------------------------------- 1 | import { View } from "@dlightjs/dlight" 2 | 3 | @View 4 | class Link { 5 | Body() { 6 | h2("Click on the links below to learn more about DLight:") 7 | a("DLight.js") 8 | .href("https://github.com/dlight-js/dlight") 9 | } 10 | } 11 | 12 | export default Link 13 | -------------------------------------------------------------------------------- /packages/core/transpiler/view-generator/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "module": "ESNext", 5 | "lib": ["ESNext", "DOM"], 6 | "moduleResolution": "Node", 7 | "strict": true, 8 | "esModuleInterop": true 9 | }, 10 | "ts-node": { 11 | "esm": true 12 | } 13 | } -------------------------------------------------------------------------------- /packages/core/transpiler/view-parser/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "module": "ESNext", 5 | "lib": ["ESNext", "DOM"], 6 | "moduleResolution": "Node", 7 | "strict": true, 8 | "esModuleInterop": true 9 | }, 10 | "ts-node": { 11 | "esm": true 12 | } 13 | } -------------------------------------------------------------------------------- /apps/bun-static-server/README.md: -------------------------------------------------------------------------------- 1 | # bun-static-server 2 | 3 | To install dependencies: 4 | 5 | ```bash 6 | bun install 7 | ``` 8 | 9 | To run: 10 | 11 | ```bash 12 | bun run index.ts 13 | ``` 14 | 15 | This project was created using `bun init` in bun v1.2.1. [Bun](https://bun.sh) is a fast all-in-one JavaScript runtime. 16 | -------------------------------------------------------------------------------- /apps/example/src/expression/Expression.view.js: -------------------------------------------------------------------------------- 1 | import { View } from "@dlightjs/dlight" 2 | 3 | @View 4 | class Expression { 5 | count = 0 6 | 7 | Body() { 8 | button("Add").onclick(() => this.count++) 9 | div() 10 | { 11 | _(this.count * 2) 12 | } 13 | } 14 | } 15 | 16 | export default Expression 17 | -------------------------------------------------------------------------------- /packages/core/transpiler/reactivity-parser/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "module": "ESNext", 5 | "lib": ["ESNext", "DOM"], 6 | "moduleResolution": "Node", 7 | "strict": true, 8 | "esModuleInterop": true 9 | }, 10 | "ts-node": { 11 | "esm": true 12 | } 13 | } -------------------------------------------------------------------------------- /packages/tools/babel-plugin-optional-this/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "module": "ESNext", 5 | "lib": ["ESNext", "DOM"], 6 | "moduleResolution": "Node", 7 | "strict": true, 8 | "esModuleInterop": true 9 | }, 10 | "ts-node": { 11 | "esm": true 12 | } 13 | } -------------------------------------------------------------------------------- /apps/example/src/introduction/Reactivity.view.js: -------------------------------------------------------------------------------- 1 | 2 | import { View } from "@dlightjs/dlight" 3 | 4 | @View 5 | class Reactivity { 6 | count = 0 7 | 8 | Body() { 9 | div(this.count) 10 | button("+") 11 | .onclick(() => { 12 | this.count++ 13 | }) 14 | } 15 | } 16 | 17 | export default Reactivity 18 | -------------------------------------------------------------------------------- /packages/tools/babel-plugin-syntax-typescript-new/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "module": "ESNext", 5 | "lib": ["ESNext", "DOM"], 6 | "moduleResolution": "Node", 7 | "strict": true, 8 | "esModuleInterop": true 9 | }, 10 | "ts-node": { 11 | "esm": true 12 | } 13 | } -------------------------------------------------------------------------------- /packages/tools/bun-plugin-dlight/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "module": "ESNext", 5 | "lib": [ 6 | "ESNext" 7 | ], 8 | "moduleResolution": "Node", 9 | "strict": true, 10 | "esModuleInterop": true 11 | }, 12 | "ts-node": { 13 | "esm": true 14 | } 15 | } -------------------------------------------------------------------------------- /apps/dev/src/OK.view.ts: -------------------------------------------------------------------------------- 1 | import { View } from "@dlightjs/dlight" 2 | import { Env } from "@dlightjs/types" 3 | 4 | @View 5 | export default class NNNN { 6 | @Env flag 7 | @Prop jjj 8 | didMount() { 9 | console.log(this.flag, this.jjj) 10 | } 11 | 12 | View() { 13 | // if (this.flag) { 14 | ;("div") 15 | // } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /apps/example/src/introduction/Function.view.js: -------------------------------------------------------------------------------- 1 | 2 | import { View } from "@dlightjs/dlight" 3 | 4 | @View 5 | class Function { 6 | count = 0 7 | 8 | handleClick() { 9 | this.count++ 10 | } 11 | 12 | Body() { 13 | div(this.count) 14 | button("+") 15 | .onclick(this.handleClick) 16 | } 17 | } 18 | 19 | export default Function 20 | -------------------------------------------------------------------------------- /apps/bun-static-server/app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Dlight x Bun Static Server 7 | 8 | 9 |
10 | 11 | 12 | -------------------------------------------------------------------------------- /apps/example/src/forNode/For.view.js: -------------------------------------------------------------------------------- 1 | import { View } from "@dlightjs/dlight" 2 | 3 | @View 4 | class For { 5 | fruitsList = ["apple", "banana", "orange"] 6 | 7 | Body() { 8 | h2("Fruits:") 9 | ul() 10 | { 11 | for (const fruit of this.fruitsList) { 12 | li(fruit) 13 | } 14 | } 15 | } 16 | } 17 | 18 | export default For 19 | -------------------------------------------------------------------------------- /turbo.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://turbo.build/schema.json", 3 | "tasks": { 4 | "build": { 5 | "dependsOn": [ 6 | "^build" 7 | ], 8 | "outputs": [ 9 | "dist/**" 10 | ] 11 | }, 12 | "typecheck": { 13 | "dependsOn": [ 14 | "^typecheck" 15 | ], 16 | "outputs": [] 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /apps/example/src/inputs/TextInput.view.js: -------------------------------------------------------------------------------- 1 | import { View } from "@dlightjs/dlight" 2 | 3 | @View 4 | class TextInput { 5 | name = "" 6 | 7 | Body() { 8 | input() 9 | .placeholder("Enter your name") 10 | .onInput((e) => { 11 | this.name = e.target.value 12 | }) 13 | div(`Hello ${this.name}!`) 14 | } 15 | } 16 | 17 | export default TextInput 18 | -------------------------------------------------------------------------------- /apps/example/src/nestComp/basic/ParentNode.view.js: -------------------------------------------------------------------------------- 1 | import { View } from "@dlightjs/dlight" 2 | import ChildComp from "./ChildNode.view" 3 | 4 | @View 5 | class ParentComp { 6 | Body() { 7 | div("I am parent!") 8 | ChildComp() 9 | .name("Cindy") 10 | ChildComp() 11 | .name("Tom") 12 | ChildComp() 13 | } 14 | } 15 | 16 | export default ParentComp 17 | -------------------------------------------------------------------------------- /apps/ToDoMVC/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Dlight.JS 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /apps/example/src/introduction/Image.view.js: -------------------------------------------------------------------------------- 1 | import { View } from "@dlightjs/dlight" 2 | 3 | @View 4 | class Image { 5 | Body() { 6 | div("Beautiful Scenery!") 7 | img() 8 | .src("https://d9-wret.s3.us-west-2.amazonaws.com/assets/palladium/production/s3fs-public/thumbnails/image/wyoming-scenery-wallpaper-2.jpg") 9 | .width(500) 10 | } 11 | } 12 | 13 | export default Image 14 | -------------------------------------------------------------------------------- /apps/example/src/introduction/nestHtml.view.js: -------------------------------------------------------------------------------- 1 | import { View } from "@dlightjs/dlight" 2 | 3 | @View 4 | class NestHtml { 5 | Body() { 6 | div() 7 | .style({ backgroundColor: "gray", height: "200px" }) 8 | { 9 | ul() 10 | { 11 | li("Index1") 12 | li("Index2") 13 | li("Index3") 14 | } 15 | } 16 | } 17 | } 18 | 19 | export default NestHtml 20 | -------------------------------------------------------------------------------- /packages/tools/create-dlightjs/templates/dlight-vite-min-js/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Dlight.JS 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /packages/tools/create-dlightjs/templates/dlight-vite-min-ts/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Dlight.JS 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /packages/tools/create-dlightjs/templates/dlight-vite-js/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Dlight.JS 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /packages/tools/create-dlightjs/templates/dlight-vite-ts/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Dlight.JS 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /packages/tools/create-dlightjs/templates/dlight-vite-js/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "{{ PROJECT_NAME }}", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "vite build", 9 | "preview": "vite preview" 10 | }, 11 | "dependencies": {{ DEPENDENCIES }}, 12 | "devDependencies": {{ DEV_DEPENDENCIES }}, 13 | "keywords": ["dlight.js"] 14 | } -------------------------------------------------------------------------------- /packages/tools/create-dlightjs/templates/dlight-vite-ts/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "{{ PROJECT_NAME }}", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "vite build", 9 | "preview": "vite preview" 10 | }, 11 | "dependencies": {{ DEPENDENCIES }}, 12 | "devDependencies": {{ DEV_DEPENDENCIES }}, 13 | "keywords": ["dlight.js"] 14 | } -------------------------------------------------------------------------------- /packages/tools/create-dlightjs/templates/dlight-vite-min-js/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "{{ PROJECT_NAME }}", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "vite build", 9 | "preview": "vite preview" 10 | }, 11 | "dependencies": {{ DEPENDENCIES }}, 12 | "devDependencies": {{ DEV_DEPENDENCIES }}, 13 | "keywords": ["dlight.js"] 14 | } -------------------------------------------------------------------------------- /packages/tools/create-dlightjs/templates/dlight-vite-min-ts/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "{{ PROJECT_NAME }}", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "vite build", 9 | "preview": "vite preview" 10 | }, 11 | "dependencies": {{ DEPENDENCIES }}, 12 | "devDependencies": {{ DEV_DEPENDENCIES }}, 13 | "keywords": ["dlight.js"] 14 | } -------------------------------------------------------------------------------- /.changeset/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://unpkg.com/@changesets/config@2.3.1/schema.json", 3 | "changelog": "@changesets/cli/changelog", 4 | "commit": false, 5 | "fixed": [], 6 | "linked": [], 7 | "access": "public", 8 | "baseBranch": "main", 9 | "updateInternalDependencies": "patch", 10 | "ignore": [ 11 | "@dlightjs/dev-app", 12 | "@dlightjs/example-app", 13 | "eslint-plugin-dlight" 14 | ] 15 | } -------------------------------------------------------------------------------- /apps/example/src/introduction/ReactiveStatement.view.js: -------------------------------------------------------------------------------- 1 | 2 | import { View } from "@dlightjs/dlight" 3 | 4 | @View 5 | class ReactiveStatement { 6 | count = 0 7 | 8 | Body() { 9 | div("count") 10 | div(this.count) 11 | div("double count") 12 | div(this.count * 2) 13 | button("+") 14 | .onclick(() => { 15 | this.count++ 16 | }) 17 | } 18 | } 19 | 20 | export default ReactiveStatement 21 | -------------------------------------------------------------------------------- /packages/tools/bun-plugin-dlight/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # bun-plugin-dlight 2 | 3 | ## 0.5.0 4 | 5 | ### Minor Changes 6 | 7 | - Add README 8 | 9 | ## 0.4.0 10 | 11 | ### Minor Changes 12 | 13 | - 9a5d2fd: Fix optional params can be passed to dlightPlugin 14 | 15 | ## 0.3.0 16 | 17 | ### Minor Changes 18 | 19 | - Rename to bun-plugin-dlight 20 | 21 | ## 0.2.0 22 | 23 | ### Minor Changes 24 | 25 | - Rename package to dlight-bun-plugin 26 | -------------------------------------------------------------------------------- /apps/dev/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "vite" 2 | import dlight from "vite-plugin-dlight" 3 | 4 | export default defineConfig({ 5 | server: { 6 | port: 4320, 7 | }, 8 | base: "", 9 | optimizeDeps: { 10 | noDiscovery: true, 11 | include: undefined 12 | }, 13 | plugins: [ 14 | dlight({ files: "**/*.{view,model}.{ts,js}", enableDevTools: true }), 15 | ], 16 | build: { 17 | minify: false 18 | } 19 | }) 20 | -------------------------------------------------------------------------------- /apps/example/src/element/CompElement.view.js: -------------------------------------------------------------------------------- 1 | import { View } from "@dlightjs/dlight" 2 | 3 | @View 4 | class ChildComp { 5 | View() { 6 | div("I am a child component").style({ color: "red" }) 7 | } 8 | } 9 | 10 | @View 11 | class CompElement { 12 | View() { 13 | div("I am a parent component") 14 | ChildComp().element(els => { 15 | els[0].style.color = "blue" 16 | }) 17 | } 18 | } 19 | 20 | export default CompElement 21 | -------------------------------------------------------------------------------- /apps/example/src/ifNode/If.view.js: -------------------------------------------------------------------------------- 1 | import { View } from "@dlightjs/dlight" 2 | 3 | @View 4 | class If { 5 | isHappy = true 6 | 7 | changeMood() { 8 | this.isHappy = !this.isHappy 9 | } 10 | 11 | Body() { 12 | button("Change mood") 13 | .onClick(this.changeMood) 14 | if (this.isHappy) { 15 | div("I am happy!😆") 16 | } else { 17 | div("I am sad!🥲") 18 | } 19 | } 20 | } 21 | 22 | export default If 23 | -------------------------------------------------------------------------------- /packages/core/transpiler/view-generator/src/types.ts: -------------------------------------------------------------------------------- 1 | import type Babel from "@babel/core" 2 | 3 | export type SnippetPropMap = Record 4 | export interface ViewGeneratorConfig { 5 | babelApi: typeof Babel 6 | className: string 7 | importMap: Record 8 | snippetPropMap: SnippetPropMap 9 | templateIdx: number 10 | attributeMap: Record 11 | alterAttributeMap: Record 12 | } 13 | -------------------------------------------------------------------------------- /apps/example/src/events/InlineHandler.view.js: -------------------------------------------------------------------------------- 1 | import { View } from "@dlightjs/dlight" 2 | 3 | @View 4 | class InlineHandler { 5 | color = "red" 6 | 7 | Body() { 8 | button("Hover Me!") 9 | .style({ backgroundColor: this.color }) 10 | .onMouseEnter(() => { 11 | this.color = "blue" 12 | }) 13 | .onMouseOut(() => { 14 | this.color = "red" 15 | }) 16 | } 17 | } 18 | 19 | export default InlineHandler 20 | -------------------------------------------------------------------------------- /apps/example/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "vite" 2 | import dlight from "vite-plugin-dlight" 3 | 4 | export default defineConfig({ 5 | server: { 6 | port: 4320 7 | }, 8 | base: "", 9 | optimizeDeps: { 10 | noDiscovery: true, 11 | include: undefined 12 | }, 13 | plugins: [ 14 | dlight({ files: "**/*.{view,model}.{ts,js}", enableDevTools: true }), 15 | ], 16 | build: { 17 | minify: false 18 | } 19 | }) 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-log.yaml 7 | yarn-error.log* 8 | pnpm-debug.log* 9 | lerna-debug.log* 10 | .gitattributes 11 | 12 | node_modules 13 | */node_modules 14 | dist-ssr 15 | .turbo 16 | *.local 17 | 18 | # Editor directories and files 19 | .vscode/* 20 | !.vscode/extensions.json 21 | .idea 22 | .DS_Store 23 | *.suo 24 | *.ntvs* 25 | *.njsproj 26 | *.sln 27 | *.sw? 28 | .history/* 29 | 30 | **/dist/** 31 | -------------------------------------------------------------------------------- /apps/bun-static-server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bun-static-server", 3 | "private": true, 4 | "version": "0.0.0", 5 | "module": "index.ts", 6 | "type": "module", 7 | "scripts": { 8 | "dev": "bun run --hot index.ts" 9 | }, 10 | "devDependencies": { 11 | "@types/bun": "latest" 12 | }, 13 | "peerDependencies": { 14 | "typescript": "^5.0.0" 15 | }, 16 | "dependencies": { 17 | "@dlightjs/dlight": "^1.0.1", 18 | "bun-plugin-dlight": "^0.5.0" 19 | } 20 | } -------------------------------------------------------------------------------- /packages/core/transpiler/view-generator/src/error.ts: -------------------------------------------------------------------------------- 1 | import { createErrorHandler } from "@dlightjs/error-handler" 2 | 3 | export const DLError = createErrorHandler( 4 | "ViewGenerator", 5 | { 6 | 1: "Element prop in HTML should be a function or an identifier", 7 | 2: "Unrecognized HTML common prop", 8 | 3: "Do prop only accepts function or arrow function", 9 | }, 10 | {}, 11 | { 12 | 1: "ExpressionNode only supports prop as element and lifecycle, receiving $0", 13 | } 14 | ) 15 | -------------------------------------------------------------------------------- /apps/example/src/forwardProps/ChildComp.view.js: -------------------------------------------------------------------------------- 1 | import { View } from "@dlightjs/dlight" 2 | 3 | @View 4 | class SubComp { 5 | @Prop isHappy = true 6 | 7 | Body() { 8 | div(`I am ${this.isHappy ? "happy" : "sad"}!`) 9 | } 10 | } 11 | 12 | @ForwardProps 13 | @View 14 | class ChildComp { 15 | @Prop name = "John" 16 | 17 | Body() { 18 | div(`I am the child ${this.name}!`) 19 | .forwardProps() 20 | SubComp() 21 | .forwardProps() 22 | } 23 | } 24 | 25 | export default ChildComp 26 | -------------------------------------------------------------------------------- /apps/dev/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "jsx": "react", 4 | "target": "ESNext", 5 | "useDefineForClassFields": true, 6 | "module": "ESNext", 7 | "lib": ["ESNext", "DOM"], 8 | "moduleResolution": "Node", 9 | "strict": true, 10 | "isolatedModules": true, 11 | "esModuleInterop": true, 12 | "noEmit": true, 13 | "noUnusedParameters": true, 14 | "skipLibCheck": true, 15 | "experimentalDecorators": true 16 | }, 17 | "ts-node": { 18 | "esm": true 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /packages/core/dlight/src/types/expressionTag.d.ts: -------------------------------------------------------------------------------- 1 | interface ExpressionTag { 2 | willMount: (node: any) => void 3 | didMount: (node: any) => void 4 | willUnmount: (node: any) => void 5 | didUnmount: (node: any) => void 6 | didUpdate: (node: any, key: string, prevValue: T, currValue: T) => void 7 | elements: HTMLElement[] | ((holder: HTMLElement[]) => void) | undefined 8 | ref: (node: any) => void 9 | } 10 | 11 | type ExpressionTagFunc = (nodes: any) => ExpressionTag 12 | 13 | export const _: ExpressionTagFunc 14 | -------------------------------------------------------------------------------- /apps/ToDoMVC/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@dlightjs/todomvc-app", 3 | "private": true, 4 | "version": "0.0.1", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "vite build", 9 | "preview": "vite preview", 10 | "typecheck": "tsc --noEmit" 11 | }, 12 | "dependencies": { 13 | "@dlightjs/dlight": "workspace:*" 14 | }, 15 | "devDependencies": { 16 | "vite": "^6.0.11", 17 | "vite-plugin-dlight": "workspace:*" 18 | }, 19 | "keywords": [ 20 | "dlight.js" 21 | ] 22 | } -------------------------------------------------------------------------------- /apps/ToDoMVC/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "jsx": "react", 4 | "target": "ESNext", 5 | "useDefineForClassFields": true, 6 | "module": "ESNext", 7 | "lib": ["ESNext", "DOM"], 8 | "moduleResolution": "Node", 9 | "strict": true, 10 | "isolatedModules": true, 11 | "esModuleInterop": true, 12 | "noEmit": true, 13 | "noUnusedParameters": true, 14 | "skipLibCheck": true, 15 | "experimentalDecorators": true 16 | }, 17 | "ts-node": { 18 | "esm": true 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /apps/example/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "jsx": "react", 4 | "target": "ESNext", 5 | "useDefineForClassFields": true, 6 | "module": "ESNext", 7 | "lib": ["ESNext", "DOM"], 8 | "moduleResolution": "Node", 9 | "strict": true, 10 | "isolatedModules": true, 11 | "esModuleInterop": true, 12 | "noEmit": true, 13 | "noUnusedParameters": true, 14 | "skipLibCheck": true, 15 | "experimentalDecorators": true 16 | }, 17 | "ts-node": { 18 | "esm": true 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /apps/example/src/envNode/RootComp.view.js: -------------------------------------------------------------------------------- 1 | import { View } from "@dlightjs/dlight" 2 | import ChildComp from "./ChildComp.view" 3 | 4 | @View 5 | class RootComp { 6 | textColor = "red" 7 | 8 | Body() { 9 | env().textColor(this.textColor) 10 | { 11 | div("I am a parent component") 12 | ChildComp() 13 | } 14 | button("Click me to toggle the color to blue/red").onClick(() => { 15 | this.textColor = this.textColor === "red" ? "blue" : "red" 16 | }) 17 | } 18 | } 19 | 20 | export default RootComp 21 | -------------------------------------------------------------------------------- /apps/example/src/forwardProps/ParentComp.view.js: -------------------------------------------------------------------------------- 1 | import { View } from "@dlightjs/dlight" 2 | import ChildComp from "./ChildComp.view" 3 | 4 | @View 5 | class ParentComp { 6 | Body() { 7 | div("I am parent!") 8 | ChildComp() 9 | .name("Cindy") 10 | .style({ 11 | color: "red" 12 | }) 13 | ChildComp() 14 | .name("Tom") 15 | .style({ 16 | color: "blue" 17 | }) 18 | .isHappy(false) 19 | ChildComp() 20 | .isHappy(false) 21 | } 22 | } 23 | 24 | export default ParentComp 25 | -------------------------------------------------------------------------------- /packages/core/dlight/src/SnippetNode.js: -------------------------------------------------------------------------------- 1 | import { DLNode, DLNodeType } from "./DLNode" 2 | import { cached } from "./store" 3 | 4 | export class SnippetNode extends DLNode { 5 | constructor(depsArr) { 6 | super(DLNodeType.Snippet) 7 | this.depsArr = depsArr 8 | } 9 | 10 | cached(deps, changed) { 11 | if (!deps || !deps.length) return false 12 | const idx = Math.log2(changed) 13 | const prevDeps = this.depsArr[idx] 14 | if (cached(deps, prevDeps)) return true 15 | this.depsArr[idx] = deps 16 | return false 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /packages/core/transpiler/view-parser/src/index.ts: -------------------------------------------------------------------------------- 1 | import { ViewParser } from "./parser" 2 | import { type ViewUnit, type ViewParserConfig } from "./types" 3 | import { type types as t } from "@babel/core" 4 | 5 | /** 6 | * @brief Generate view units from a babel ast 7 | * @param statement 8 | * @param config 9 | * @param options 10 | * @returns ViewUnit[] 11 | */ 12 | export function parseView( 13 | statement: t.BlockStatement, 14 | config: ViewParserConfig 15 | ): ViewUnit[] { 16 | return new ViewParser(config).parse(statement) 17 | } 18 | 19 | export type * from "./types" 20 | -------------------------------------------------------------------------------- /apps/example/src/events/AddEvent.view.js: -------------------------------------------------------------------------------- 1 | import { View } from "@dlightjs/dlight" 2 | 3 | @View 4 | class AddEvent { 5 | color = "gray" 6 | 7 | handleClick(e) { 8 | this.color = this.color === "gray" ? "orange" : "gray" 9 | } 10 | 11 | willMount() { 12 | window.addEventListener("click", this.handleClick.bind(this)) 13 | } 14 | 15 | willUnmount() { 16 | window.removeEventListener("click", this.handleClick.bind(this)) 17 | } 18 | 19 | Body() { 20 | div() 21 | .style({ backgroundColor: this.color, height: "500px" }) 22 | } 23 | } 24 | 25 | export default AddEvent 26 | -------------------------------------------------------------------------------- /apps/example/src/lifeCycle/willMount.view.js: -------------------------------------------------------------------------------- 1 | import { View } from "@dlightjs/dlight" 2 | 3 | @View 4 | class WillMount { 5 | color = "gray" 6 | 7 | handleClick(e) { 8 | this.color = this.color === "gray" ? "orange" : "gray" 9 | } 10 | 11 | willMount() { 12 | window.addEventListener("click", this.handleClick.bind(this)) 13 | } 14 | 15 | willUnmount() { 16 | window.removeEventListener("click", this.handleClick.bind(this)) 17 | } 18 | 19 | Body() { 20 | div() 21 | .style({ backgroundColor: this.color, height: "500px" }) 22 | } 23 | } 24 | 25 | export default WillMount 26 | -------------------------------------------------------------------------------- /apps/example/src/nestComp/children/Children.view.js: -------------------------------------------------------------------------------- 1 | import { View } from "@dlightjs/dlight" 2 | 3 | @View 4 | class ColumnCenterView { 5 | @Children children 6 | 7 | Body() { 8 | div() 9 | .style({ display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "center" }) 10 | { 11 | this.children 12 | } 13 | } 14 | } 15 | 16 | @View 17 | class ChildrenView { 18 | Body() { 19 | ColumnCenterView() 20 | { 21 | div("short") 22 | div("long long long text") 23 | div("short") 24 | } 25 | } 26 | } 27 | 28 | export default ChildrenView 29 | -------------------------------------------------------------------------------- /packages/tools/create-dlightjs/templates/dlight-vite-js/src/Button.view.js: -------------------------------------------------------------------------------- 1 | import { View } from "@dlightjs/dlight" 2 | import { countBtn, btnHover } from "./style.module.css" 3 | 4 | @View 5 | export default class Button { 6 | @Content content 7 | @Prop onClick 8 | btnStatus = 0 9 | 10 | Body() { 11 | button(this.content) 12 | .class(this.btnStatus === 1 ? `${countBtn} ${btnHover}` : countBtn) 13 | .onClick(this.onClick) 14 | .onMouseOver(() => { 15 | this.btnStatus = 1 16 | }) 17 | .onMouseLeave(() => { 18 | this.btnStatus = 0 19 | }) 20 | } 21 | } -------------------------------------------------------------------------------- /apps/example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@dlightjs/example-app", 3 | "private": true, 4 | "version": "0.0.0", 5 | "description": "DLight example", 6 | "author": { 7 | "name": "Xinyi Chen", 8 | "email": "chenxy0404@gmail.com" 9 | }, 10 | "license": "MIT", 11 | "type": "module", 12 | "scripts": { 13 | "dev": "vite", 14 | "build": "vite build", 15 | "typecheck": "tsc --noEmit" 16 | }, 17 | "dependencies": { 18 | "@dlightjs/dlight": "workspace:*", 19 | "babel-preset-dlight": "workspace:*" 20 | }, 21 | "devDependencies": { 22 | "vite": "^6.0.11", 23 | "vite-plugin-dlight": "workspace:*" 24 | } 25 | } -------------------------------------------------------------------------------- /apps/example/src/ifNode/ifelse.view.js: -------------------------------------------------------------------------------- 1 | import { View } from "@dlightjs/dlight" 2 | 3 | @View 4 | class IfElse { 5 | count = 0 6 | 7 | addCount() { 8 | this.count++ 9 | } 10 | 11 | minusCount() { 12 | this.count-- 13 | } 14 | 15 | Body() { 16 | div(`Count: ${this.count}`) 17 | button("+") 18 | .onClick(this.addCount) 19 | button("-") 20 | .onClick(this.minusCount) 21 | if (this.count > 0) { 22 | div("Count is larger than 0") 23 | } else if (this.count < 0) { 24 | div("Count is smaller than 0") 25 | } else { 26 | div("Count is 0") 27 | } 28 | } 29 | } 30 | 31 | export default IfElse 32 | -------------------------------------------------------------------------------- /packages/core/dlight/src/TextNode.js: -------------------------------------------------------------------------------- 1 | import { DLStore, cached } from "./store" 2 | 3 | /** 4 | * @brief Shorten document.createTextNode 5 | * @param value 6 | * @returns Text 7 | */ 8 | export function createTextNode(value, deps) { 9 | const node = DLStore.document.createTextNode(value) 10 | node.$$deps = deps 11 | return node 12 | } 13 | 14 | /** 15 | * @brief Update text node and check if the value is changed 16 | * @param node 17 | * @param value 18 | */ 19 | export function updateText(node, valueFunc, deps) { 20 | if (cached(deps, node.$$deps)) return 21 | const value = valueFunc() 22 | node.textContent = value 23 | node.$$deps = deps 24 | } 25 | -------------------------------------------------------------------------------- /packages/core/babel-preset-dlight/src/index.ts: -------------------------------------------------------------------------------- 1 | import syntaxTypescript from "babel-plugin-syntax-typescript-new" 2 | import syntaxDecorators from "@babel/plugin-syntax-decorators" 3 | import dlight from "./plugin" 4 | import { type DLightOption } from "./types" 5 | import { type ConfigAPI, type TransformOptions } from "@babel/core" 6 | 7 | export default function ( 8 | _: ConfigAPI, 9 | options: DLightOption 10 | ): TransformOptions { 11 | return { 12 | plugins: [ 13 | syntaxTypescript, 14 | [syntaxDecorators.default ?? syntaxDecorators, { legacy: true }], 15 | [dlight, options], 16 | ], 17 | } 18 | } 19 | 20 | export { type DLightOption } 21 | -------------------------------------------------------------------------------- /packages/tools/create-dlightjs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "create-dlightjs", 3 | "version": "1.0.0", 4 | "description": "DLight cli", 5 | "author": { 6 | "name": "Xinyi", 7 | "email": "chenxy0404@gmail.com" 8 | }, 9 | "keywords": [ 10 | "dlight.js" 11 | ], 12 | "license": "MIT", 13 | "type": "module", 14 | "files": [ 15 | "dist", 16 | "templates" 17 | ], 18 | "bin": { 19 | "create-dlightjs": "./dist/index.js" 20 | }, 21 | "scripts": { 22 | "build": "tsup --sourcemap", 23 | "typecheck": "tsc --noEmit" 24 | }, 25 | "dependencies": { 26 | "@inquirer/prompts": "^1.2.3", 27 | "node-fetch": "^3.3.2" 28 | } 29 | } -------------------------------------------------------------------------------- /packages/tools/babel-plugin-optional-this/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "babel-plugin-optional-this", 3 | "version": "0.0.6", 4 | "description": "Optional this in class", 5 | "author": { 6 | "name": "IanDx", 7 | "email": "iandxssxx@gmail.com" 8 | }, 9 | "keywords": [ 10 | "babel-plugin" 11 | ], 12 | "license": "MIT", 13 | "files": [ 14 | "dist" 15 | ], 16 | "type": "module", 17 | "main": "dist/index.cjs", 18 | "module": "dist/index.js", 19 | "typings": "dist/index.d.ts", 20 | "scripts": { 21 | "build": "tsup --sourcemap", 22 | "typecheck": "tsc --noEmit" 23 | }, 24 | "devDependencies": { 25 | "@babel/types": "^7.20.7" 26 | } 27 | } -------------------------------------------------------------------------------- /apps/example/src/inputs/RadioInput.view.js: -------------------------------------------------------------------------------- 1 | import { View } from "@dlightjs/dlight" 2 | 3 | @View 4 | class RadioInput { 5 | choice = "" 6 | 7 | Body() { 8 | div("Choose your favorite fruit:") 9 | for (const fruit of ["apple", "banana", "strawberry"]) { 10 | div() 11 | { 12 | input() 13 | .name("favoriteFruit") 14 | .id(fruit) 15 | .type("radio") 16 | .value(fruit) 17 | .onInput(e => { 18 | this.choice = e.target.value 19 | }) 20 | label(fruit).for(fruit) 21 | } 22 | } 23 | 24 | div(`Your favorite fruit is ${this.choice}!`) 25 | } 26 | } 27 | 28 | export default RadioInput 29 | -------------------------------------------------------------------------------- /packages/tools/create-dlightjs/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "useDefineForClassFields": true, 5 | "module": "ESNext", 6 | "lib": [ 7 | "ESNext", 8 | "DOM" 9 | ], 10 | "moduleResolution": "Node", 11 | "strict": true, 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "esModuleInterop": true, 15 | "noEmit": true, 16 | "skipLibCheck": true, 17 | "noImplicitThis": false, 18 | "experimentalDecorators": true, 19 | "ignoreDeprecations": "5.0", 20 | "declaration": false 21 | }, 22 | "include": [ 23 | "src/**/*" 24 | ], 25 | "ts-node": { 26 | "esm": true 27 | } 28 | } -------------------------------------------------------------------------------- /packages/core/transpiler/view-generator/src/HelperGenerators/ForwardPropGenerator.ts: -------------------------------------------------------------------------------- 1 | import { type types as t } from "@babel/core" 2 | import ElementGenerator from "./ElementGenerator" 3 | 4 | export default class ForwardPropsGenerator extends ElementGenerator { 5 | /** 6 | * @View 7 | * this._$forwardProp(${dlNodeName}) 8 | */ 9 | forwardProps(dlNodeName: string): t.ExpressionStatement { 10 | return this.t.expressionStatement( 11 | this.t.callExpression( 12 | this.t.memberExpression( 13 | this.t.thisExpression(), 14 | this.t.identifier("_$addForwardProps") 15 | ), 16 | [this.t.identifier(dlNodeName)] 17 | ) 18 | ) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /apps/bun-static-server/app/main.view.ts: -------------------------------------------------------------------------------- 1 | import { View, div, p, span, Main, button } from '@dlightjs/dlight' 2 | 3 | @Main 4 | @View 5 | export default class App { 6 | count = 0 7 | 8 | Body() { 9 | div() 10 | { 11 | div() 12 | { 13 | p() 14 | { 15 | span('D') 16 | span('Light') 17 | } 18 | p('DX-First UI') 19 | p('Rendering Library') 20 | } 21 | div() 22 | { 23 | p(this.count) 24 | div() 25 | { 26 | button('count ++').onClick(() => { 27 | this.count++ 28 | }) 29 | button('count --').onClick(() => { 30 | this.count-- 31 | }) 32 | } 33 | } 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /packages/core/store/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@dlightjs/store", 3 | "version": "0.0.0", 4 | "description": "DLight shared store", 5 | "author": { 6 | "name": "IanDx", 7 | "email": "iandxssxx@gmail.com" 8 | }, 9 | "keywords": [ 10 | "dlight.js" 11 | ], 12 | "license": "MIT", 13 | "files": [ 14 | "dist" 15 | ], 16 | "type": "module", 17 | "main": "dist/index.js", 18 | "module": "dist/index.js", 19 | "typings": "dist/index.d.ts", 20 | "scripts": { 21 | "build": "tsup --sourcemap", 22 | "typecheck": "tsc --noEmit" 23 | }, 24 | "tsup": { 25 | "entry": [ 26 | "src/index.ts" 27 | ], 28 | "format": [ 29 | "esm" 30 | ], 31 | "clean": true, 32 | "dts": true, 33 | "minify": true 34 | } 35 | } -------------------------------------------------------------------------------- /apps/bun-static-server/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | // Enable latest features 4 | "lib": ["ESNext", "DOM"], 5 | "target": "ESNext", 6 | "module": "ESNext", 7 | "moduleDetection": "force", 8 | "jsx": "react-jsx", 9 | "allowJs": true, 10 | 11 | // Bundler mode 12 | "moduleResolution": "bundler", 13 | "allowImportingTsExtensions": true, 14 | "verbatimModuleSyntax": true, 15 | "noEmit": true, 16 | 17 | // Best practices 18 | "strict": true, 19 | "skipLibCheck": true, 20 | "noFallthroughCasesInSwitch": true, 21 | 22 | // Some stricter flags (disabled by default) 23 | "noUnusedLocals": false, 24 | "noUnusedParameters": false, 25 | "noPropertyAccessFromIndexSignature": false 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /apps/dev/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Dlight.JS 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /apps/dev/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@dlightjs/dev-app", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "vite build", 9 | "preview": "vite preview", 10 | "typecheck": "tsc --noEmit" 11 | }, 12 | "dependencies": { 13 | "@babel/standalone": "^7.22.4", 14 | "@dlightjs/components": "1.0.0-alpha.12", 15 | "@dlightjs/dlight": "workspace:*", 16 | "@dlightjs/markit": "^0.10.1", 17 | "@dlightjs/material-icons": "^0.10.1", 18 | "@iandx/easy-css": "^0.10.14", 19 | "babel-preset-dlight": "workspace:*" 20 | }, 21 | "devDependencies": { 22 | "vite": "^6.0.11", 23 | "vite-plugin-dlight": "workspace:*" 24 | }, 25 | "keywords": [ 26 | "dlight.js" 27 | ] 28 | } -------------------------------------------------------------------------------- /apps/example/src/nestComp/content/Content.view.js: -------------------------------------------------------------------------------- 1 | import { View } from "@dlightjs/dlight" 2 | 3 | @View 4 | class BeautifulButton { 5 | @Content content = "" 6 | @Prop onclick = undefined 7 | 8 | Body() { 9 | button(this.content) 10 | .style({ fontWeight: "bold", borderWidth: "0px", padding: "10px 10px", margin: "10px", borderRadius: "10px", color: "orange" }) 11 | .onclick(this.onclick) 12 | } 13 | } 14 | 15 | @View 16 | class ContentView { 17 | count = 0 18 | 19 | Body() { 20 | h2(`Count: ${this.count}`) 21 | BeautifulButton("Add") 22 | .onclick(() => { 23 | this.count++ 24 | }) 25 | BeautifulButton("Minus") 26 | .onclick(() => { 27 | this.count-- 28 | }) 29 | } 30 | } 31 | 32 | export default ContentView 33 | -------------------------------------------------------------------------------- /packages/tools/eslint-plugin-dlight/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "eslint-plugin-dlight", 3 | "version": "1.0.0-alpha.0", 4 | "description": "DLight eslint plugin", 5 | "author": { 6 | "name": "IanDx", 7 | "email": "iandxssxx@gmail.com" 8 | }, 9 | "keywords": [ 10 | "dlight.js" 11 | ], 12 | "license": "MIT", 13 | "files": [ 14 | "dist" 15 | ], 16 | "type": "module", 17 | "main": "dist/index.cjs", 18 | "module": "dist/index.cjs", 19 | "typings": "dist/index.d.ts", 20 | "scripts": { 21 | "build": "tsup --sourcemap", 22 | "typecheck": "tsc --noEmit" 23 | }, 24 | "tsup": { 25 | "entry": [ 26 | "src/index.ts" 27 | ], 28 | "format": [ 29 | "cjs" 30 | ], 31 | "clean": true, 32 | "dts": true, 33 | "minify": true 34 | } 35 | } -------------------------------------------------------------------------------- /packages/tools/error-handler/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@dlightjs/error-handler", 3 | "version": "1.0.0", 4 | "description": "DLight error handling module", 5 | "author": { 6 | "name": "IanDx", 7 | "email": "iandxssxx@gmail.com" 8 | }, 9 | "keywords": [ 10 | "dlight.js" 11 | ], 12 | "license": "MIT", 13 | "files": [ 14 | "dist" 15 | ], 16 | "type": "module", 17 | "main": "dist/index.cjs", 18 | "module": "dist/index.js", 19 | "typings": "dist/index.d.ts", 20 | "scripts": { 21 | "build": "tsup --sourcemap", 22 | "typecheck": "tsc --noEmit" 23 | }, 24 | "tsup": { 25 | "entry": [ 26 | "src/index.ts" 27 | ], 28 | "format": [ 29 | "cjs", 30 | "esm" 31 | ], 32 | "clean": true, 33 | "dts": true, 34 | "minify": true 35 | } 36 | } -------------------------------------------------------------------------------- /apps/example/src/snippet/ViewWithSnippet.view.js: -------------------------------------------------------------------------------- 1 | import { View, Snippet } from "@dlightjs/dlight" 2 | 3 | @View 4 | class ViewWithSnippet { 5 | count = 0 6 | 7 | @Snippet 8 | BeautifulButton({ text, onclick }) { 9 | button(text) 10 | .style({ 11 | fontWeight: "bold", 12 | borderWidth: "0px", 13 | padding: "10px 10px", 14 | margin: "10px", 15 | borderRadius: "10px", 16 | color: "orange", 17 | }) 18 | .onclick(onclick) 19 | } 20 | 21 | Body() { 22 | h2(`Count: ${this.count}`) 23 | this.BeautifulButton() 24 | .text("Add") 25 | .onclick(() => { 26 | this.count++ 27 | }) 28 | this.BeautifulButton() 29 | .text("Minus") 30 | .onclick(() => { 31 | this.count-- 32 | }) 33 | } 34 | } 35 | 36 | export default ViewWithSnippet 37 | -------------------------------------------------------------------------------- /packages/tools/babel-plugin-syntax-typescript-new/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "babel-plugin-syntax-typescript-new", 3 | "version": "1.0.0", 4 | "description": "Babel plugin to parse ts syntax", 5 | "author": { 6 | "name": "IanDx", 7 | "email": "iandxssxx@gmail.com" 8 | }, 9 | "keywords": [ 10 | "babel", 11 | "typescript" 12 | ], 13 | "license": "MIT", 14 | "files": [ 15 | "dist" 16 | ], 17 | "type": "module", 18 | "main": "dist/index.cjs", 19 | "module": "dist/index.js", 20 | "typings": "dist/index.d.ts", 21 | "scripts": { 22 | "build": "tsup --sourcemap", 23 | "typecheck": "tsc --noEmit" 24 | }, 25 | "dependencies": { 26 | "@babel/helper-plugin-utils": "^7.22.5" 27 | }, 28 | "tsup": { 29 | "entry": [ 30 | "src/index.ts" 31 | ], 32 | "format": [ 33 | "esm", 34 | "cjs" 35 | ], 36 | "clean": true, 37 | "dts": true, 38 | "minify": true 39 | } 40 | } -------------------------------------------------------------------------------- /packages/tools/create-dlightjs/templates/dlight-vite-js/src/Header.view.js: -------------------------------------------------------------------------------- 1 | import { View } from "@dlightjs/dlight" 2 | import { headerWrap, navBtn, headerLogo } from "./style.module.css" 3 | 4 | @View 5 | export default class Header { 6 | navList = [ 7 | { 8 | url: "https://dlight-js.com/docs", 9 | navName: "Docs" 10 | }, 11 | { 12 | url: "https://dlight-js.com/examples", 13 | navName: "Examples" 14 | }, 15 | { 16 | url: "https://dlight-js.com/playground", 17 | navName: "Playground" 18 | } 19 | ] 20 | 21 | Body() { 22 | div() 23 | .class(headerWrap) 24 | { 25 | img() 26 | .src("./logo_title.png") 27 | .class(headerLogo) 28 | div() 29 | { 30 | for (const navItem of this.navList) { 31 | a(navItem.navName) 32 | .class(navBtn) 33 | .href(navItem.url) 34 | } 35 | } 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /apps/dev/src/App.view.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Prop, 3 | View, 4 | Watch, 5 | button, 6 | div, 7 | render, 8 | type Pretty, 9 | type Typed, 10 | } from "@dlightjs/dlight" 11 | 12 | @View 13 | class _DataView { 14 | @Prop count: number = 0 15 | 16 | @Watch 17 | watcher() { 18 | // @ts-expect-error intentional error 19 | console.log(this.count, this.count.notExist) // Error occurs here 20 | } 21 | 22 | Body() {} 23 | } 24 | 25 | const DataView = _DataView as Pretty as Typed<_DataView> 26 | 27 | @View 28 | export class App { 29 | w: number = 0 30 | count: number | null = null 31 | 32 | jj() { 33 | this.w = 1 34 | } 35 | 36 | Body() { 37 | try { 38 | DataView().count(this.count ?? 0) 39 | button("Click Me").onClick(() => { 40 | this.count = null 41 | }) 42 | } catch (e) { 43 | if (e instanceof Error) div(e.message) 44 | else throw e 45 | } 46 | } 47 | } 48 | 49 | render("main", App) 50 | -------------------------------------------------------------------------------- /apps/dev/src/index.ts: -------------------------------------------------------------------------------- 1 | import { render } from "@dlightjs/dlight" 2 | import App from "./Fine.view" 3 | 4 | render("main", App) 5 | 6 | // function testPerf(func: () => void) { 7 | // // multiple times to get average 8 | // const times = [] 9 | // for (let i = 0; i < 3; i++) { 10 | // const start = performance.now() 11 | // func() 12 | // const end = performance.now() 13 | // times.push(end - start) 14 | // } 15 | // console.log(times.reduce((a, b) => a + b, 0) / times.length) 16 | // } 17 | // const count = 10000 18 | // testPerf(() => { 19 | // const a = [] 20 | // for (let i = 0; i < count; i++) { 21 | // a[i] = i 22 | // } 23 | // }) 24 | // testPerf(() => { 25 | // const a = new Map() 26 | 27 | // for (let i = 0; i < count; i++) { 28 | // a.set(i, i) 29 | // } 30 | // }) 31 | // class JJ { 32 | // aa() { 33 | // console.log(this) 34 | // return 1 35 | // } 36 | // } 37 | 38 | // class BB { 39 | // ok = new JJ() 40 | // } 41 | // console.log(new BB().ok.aa()) 42 | -------------------------------------------------------------------------------- /apps/example/src/inputs/CheckboxInput.view.js: -------------------------------------------------------------------------------- 1 | import { View } from "@dlightjs/dlight" 2 | 3 | @View 4 | class CheckboxInput { 5 | choices = [] 6 | 7 | Body() { 8 | div("Choose your favorite fruits:") 9 | for (const fruit of ["apple", "banana", "strawberry"]) { 10 | div() 11 | { 12 | input() 13 | .name("favoriteFruit") 14 | .id(fruit) 15 | .type("checkbox") 16 | .value(fruit) 17 | .onChange(e => { 18 | if (this.choices.includes(e.target.value)) { 19 | this.choices = this.choices.filter( 20 | choice => choice !== e.target.value 21 | ) 22 | } else { 23 | this.choices.push(e.target.value) 24 | this.choices = [...this.choices] 25 | } 26 | }) 27 | label(fruit).for(fruit) 28 | } 29 | } 30 | 31 | div(`Your favorite fruit is ${this.choices.join(",")}!`) 32 | } 33 | } 34 | 35 | export default CheckboxInput 36 | -------------------------------------------------------------------------------- /packages/core/transpiler/view-generator/src/index.ts: -------------------------------------------------------------------------------- 1 | import { type ViewParticle } from "@dlightjs/reactivity-parser" 2 | import { type ViewGeneratorConfig } from "./types" 3 | import { type types as t } from "@babel/core" 4 | import MainViewGenerator from "./MainViewGenerator" 5 | import SnippetGenerator from "./SnippetGenerator" 6 | 7 | export function generateView( 8 | viewParticles: ViewParticle[], 9 | config: ViewGeneratorConfig 10 | ): [t.BlockStatement, t.ClassProperty[], number] { 11 | return new MainViewGenerator(config).generate(viewParticles) 12 | } 13 | 14 | export function generateSnippet( 15 | viewParticlesWithPropertyDep: ViewParticle[], 16 | viewParticlesWithIdentityDep: ViewParticle[], 17 | propNode: t.ObjectPattern, 18 | config: ViewGeneratorConfig 19 | ): [t.BlockStatement, t.ClassProperty[], number] { 20 | return new SnippetGenerator(config).generate( 21 | viewParticlesWithPropertyDep, 22 | viewParticlesWithIdentityDep, 23 | propNode 24 | ) 25 | } 26 | 27 | export type * from "./types" 28 | -------------------------------------------------------------------------------- /packages/core/dlight/src/MutableNode/FlatNode.js: -------------------------------------------------------------------------------- 1 | import { DLStore } from "../store" 2 | import { MutableNode } from "./MutableNode" 3 | 4 | export class FlatNode extends MutableNode { 5 | willUnmountFuncs = [] 6 | didUnmountFuncs = [] 7 | 8 | setUnmountFuncs() { 9 | this.willUnmountFuncs = DLStore.global.WillUnmountStore.pop() 10 | this.didUnmountFuncs = DLStore.global.DidUnmountStore.pop() 11 | } 12 | 13 | runWillUnmount() { 14 | for (let i = 0; i < this.willUnmountFuncs.length; i++) 15 | this.willUnmountFuncs[i]() 16 | } 17 | 18 | runDidUnmount() { 19 | for (let i = this.didUnmountFuncs.length - 1; i >= 0; i--) 20 | this.didUnmountFuncs[i]() 21 | } 22 | 23 | removeNodes(nodes) { 24 | this.runWillUnmount() 25 | super.removeNodes(nodes) 26 | this.runDidUnmount() 27 | } 28 | 29 | geneNewNodesInEnv(newNodesFunc) { 30 | this.initUnmountStore() 31 | const nodes = super.geneNewNodesInEnv(newNodesFunc) 32 | this.setUnmountFuncs() 33 | return nodes 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /packages/tools/bun-plugin-dlight/src/index.ts: -------------------------------------------------------------------------------- 1 | import type { BunPlugin } from "bun" 2 | import { transform } from "@babel/core" 3 | import dlight, { type DLightOption } from "babel-preset-dlight" 4 | 5 | export const dlightPlugin = ({ 6 | filter = /\.(view|model)\.[tj]s$/, 7 | options = {}, 8 | }: { 9 | filter?: RegExp 10 | options?: DLightOption 11 | } = {}): BunPlugin => ({ 12 | name: "bun-plugin-dlight", 13 | setup(build) { 14 | build.onLoad({ filter }, async args => { 15 | const code = await Bun.file(args.path).text() 16 | 17 | const result = transform(code, { 18 | babelrc: false, 19 | configFile: false, 20 | presets: [[dlight, options]], 21 | sourceMaps: true, 22 | filename: args.path, 23 | }) 24 | 25 | if (!result || !result.code) { 26 | throw new Error(`bun-plugin-dlight failed to process ${args.path}`) 27 | } 28 | 29 | return { 30 | contents: result.code, 31 | loader: "ts", 32 | } 33 | }) 34 | }, 35 | }) 36 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "node": true, 5 | "es2021": true 6 | }, 7 | "extends": [ 8 | "plugin:@typescript-eslint/recommended", 9 | "plugin:prettier/recommended" 10 | ], 11 | "parserOptions": { 12 | "ecmaVersion": "latest", 13 | "sourceType": "module", 14 | "project": "**/tsconfig.json" 15 | }, 16 | "ignorePatterns": ["**/dist/**", "**/lib/**"], 17 | "rules": { 18 | "@typescript-eslint/explicit-function-return-type": "off", 19 | "@typescript-eslint/strict-boolean-expressions": "off", 20 | "@typescript-eslint/restrict-template-expressions": "off", 21 | "@typescript-eslint/no-non-null-assertion": "off", 22 | "@typescript-eslint/no-invalid-void-type": "off", 23 | "@typescript-eslint/no-explicit-any": "off", 24 | "@typescript-eslint/no-unused-vars": ["error", { "argsIgnorePattern": "^(_|View)" }] 25 | }, 26 | "plugins": ["@typescript-eslint", "prettier", "dlight"] 27 | } 28 | -------------------------------------------------------------------------------- /apps/example/src/propView/PropView.view.js: -------------------------------------------------------------------------------- 1 | import { View } from "@dlightjs/dlight" 2 | 3 | @View 4 | class Header { 5 | @Prop rightView 6 | @Prop centerView 7 | @Prop leftView 8 | Body() { 9 | div().style({ 10 | display: "flex", 11 | justifyContent: "space-between", 12 | alignItems: "center", 13 | height: "50px", 14 | backgroundColor: "#333", 15 | color: "#fff", 16 | padding: "0 20px", 17 | }) 18 | { 19 | if (this.leftView) { 20 | this.leftView 21 | } 22 | if (this.centerView) { 23 | this.centerView 24 | } 25 | if (this.rightView) { 26 | this.rightView 27 | } 28 | } 29 | } 30 | } 31 | 32 | @View 33 | class PropViewClass { 34 | Body() { 35 | Header() 36 | .leftView(View => { 37 | div("hhhh") 38 | }) 39 | .centerView(View => { 40 | div("PropViewTest") 41 | }) 42 | .rightView(View => { 43 | div("close") 44 | }) 45 | } 46 | } 47 | 48 | export default PropViewClass 49 | -------------------------------------------------------------------------------- /packages/tools/bun-plugin-dlight/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bun-plugin-dlight", 3 | "version": "0.5.0", 4 | "description": "DLight transpiler as Bun build plugin", 5 | "author": { 6 | "name": "Duane Johnson", 7 | "email": "duane.johnson@gmail.com" 8 | }, 9 | "keywords": [ 10 | "dlight.js", 11 | "bun-plugin" 12 | ], 13 | "license": "MIT", 14 | "files": [ 15 | "dist" 16 | ], 17 | "type": "module", 18 | "main": "dist/index.js", 19 | "module": "dist/index.js", 20 | "typings": "dist/index.d.ts", 21 | "scripts": { 22 | "build": "tsup --sourcemap", 23 | "typecheck": "tsc --noEmit" 24 | }, 25 | "dependencies": { 26 | "@babel/core": "^7.20.12", 27 | "babel-preset-dlight": "workspace:*" 28 | }, 29 | "devDependencies": { 30 | "@types/babel__core": "^7.20.5", 31 | "@types/bun": "^1.2.1" 32 | }, 33 | "tsup": { 34 | "entry": [ 35 | "src/index.ts" 36 | ], 37 | "format": [ 38 | "esm" 39 | ], 40 | "clean": true, 41 | "dts": true, 42 | "minify": false 43 | } 44 | } -------------------------------------------------------------------------------- /packages/tools/vite-plugin-dlight/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vite-plugin-dlight", 3 | "version": "1.0.0", 4 | "description": "DLight transpiler as vite plugin", 5 | "author": { 6 | "name": "IanDx", 7 | "email": "iandxssxx@gmail.com" 8 | }, 9 | "keywords": [ 10 | "dlight.js", 11 | "vite-plugin" 12 | ], 13 | "license": "MIT", 14 | "files": [ 15 | "dist" 16 | ], 17 | "type": "module", 18 | "main": "dist/index.js", 19 | "module": "dist/index.js", 20 | "typings": "dist/index.d.ts", 21 | "scripts": { 22 | "build": "tsup --sourcemap", 23 | "typecheck": "tsc --noEmit" 24 | }, 25 | "dependencies": { 26 | "@babel/core": "^7.20.12", 27 | "babel-preset-dlight": "workspace:*", 28 | "minimatch": "^9.0.3" 29 | }, 30 | "devDependencies": { 31 | "@types/babel__core": "^7.20.5", 32 | "vite": "^4.4.9" 33 | }, 34 | "tsup": { 35 | "entry": [ 36 | "src/index.ts" 37 | ], 38 | "format": [ 39 | "esm" 40 | ], 41 | "clean": true, 42 | "dts": true, 43 | "minify": true 44 | } 45 | } -------------------------------------------------------------------------------- /packages/core/transpiler/view-parser/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # @dlightjs/view-parser 2 | 3 | ## 1.0.0 4 | 5 | - Released 6 | 7 | ## 1.0.0-next.2 8 | 9 | ### Patch Changes 10 | 11 | - fix: component content prop named as "\_$content" 12 | 13 | ## 1.0.0-next.1 14 | 15 | ### Patch Changes 16 | 17 | - feat: fixes and feature requests 18 | 19 | ## 1.0.0-beta.8 20 | 21 | ### Patch Changes 22 | 23 | - feat: add alternative children syntax 24 | 25 | ## 1.0.0-beta.7 26 | 27 | ### Patch Changes 28 | 29 | - fix: try-catch model 30 | 31 | ## 1.0.0-beta.6 32 | 33 | ### Patch Changes 34 | 35 | - feat: add try-catch 36 | 37 | ## 1.0.0-beta.5 38 | 39 | ### Patch Changes 40 | 41 | - update: transpiler 42 | 43 | ## 1.0.0-alpha.4 44 | 45 | ### Patch Changes 46 | 47 | - feat: add lifecycle and onUpdate 48 | 49 | ## 1.0.0-alpha.3 50 | 51 | ### Patch Changes 52 | 53 | - feat: add willParseTemplate support 54 | 55 | ## 1.0.0-alpha.2 56 | 57 | ### Patch Changes 58 | 59 | - refactor: add attrmap plugin support 60 | 61 | ## 1.0.0-alpha.1 62 | 63 | ### Patch Changes 64 | 65 | - feat: add switch statement support 66 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@dlightjs/monorepo", 3 | "version": "1.0.0", 4 | "description": "DLight monorepo", 5 | "author": { 6 | "name": "IanDx", 7 | "email": "iandxssxx@gmail.com" 8 | }, 9 | "keywords": [ 10 | "dlight.js" 11 | ], 12 | "license": "MIT", 13 | "files": [ 14 | "README.md" 15 | ], 16 | "type": "module", 17 | "packageManager": "pnpm@9.0.0+", 18 | "scripts": { 19 | "build": "turbo build", 20 | "prepare": "husky", 21 | "typecheck": "turbo typecheck" 22 | }, 23 | "devDependencies": { 24 | "@changesets/cli": "^2.27.1", 25 | "@commitlint/cli": "^17.8.1", 26 | "@commitlint/config-conventional": "^17.8.1", 27 | "@typescript-eslint/eslint-plugin": "^6.15.0", 28 | "@typescript-eslint/parser": "^6.15.0", 29 | "eslint": "^8.55.0", 30 | "eslint-config-prettier": "^9.1.0", 31 | "eslint-plugin-dlight": "workspace:*", 32 | "eslint-plugin-prettier": "^5.1.1", 33 | "husky": "^9.1.0", 34 | "prettier": "^3.1.1", 35 | "tsup": "^8.3.5", 36 | "turbo": "^2.3.4", 37 | "typescript": "^5.3.3" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /packages/core/transpiler/view-parser/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@dlightjs/view-parser", 3 | "version": "1.0.0", 4 | "description": "DLight DSL syntax parser", 5 | "author": { 6 | "name": "IanDx", 7 | "email": "iandxssxx@gmail.com" 8 | }, 9 | "keywords": [ 10 | "dlight.js" 11 | ], 12 | "license": "MIT", 13 | "files": [ 14 | "dist" 15 | ], 16 | "type": "module", 17 | "main": "dist/index.cjs", 18 | "module": "dist/index.js", 19 | "typings": "dist/index.d.ts", 20 | "scripts": { 21 | "build": "tsup --sourcemap", 22 | "test": "vitest --ui", 23 | "typecheck": "tsc --noEmit" 24 | }, 25 | "dependencies": { 26 | "@dlightjs/error-handler": "workspace:*" 27 | }, 28 | "devDependencies": { 29 | "@babel/core": "^7.20.12", 30 | "@types/babel__core": "^7.20.5", 31 | "@vitest/ui": "^0.34.5", 32 | "vitest": "^0.34.5" 33 | }, 34 | "tsup": { 35 | "entry": [ 36 | "src/index.ts" 37 | ], 38 | "format": [ 39 | "cjs", 40 | "esm" 41 | ], 42 | "clean": true, 43 | "dts": true, 44 | "minify": true 45 | } 46 | } -------------------------------------------------------------------------------- /packages/core/transpiler/view-generator/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@dlightjs/view-generator", 3 | "version": "1.0.0", 4 | "description": "DLight View Generator given different types of reactivity units", 5 | "author": { 6 | "name": "IanDx", 7 | "email": "iandxssxx@gmail.com" 8 | }, 9 | "keywords": [ 10 | "dlight.js" 11 | ], 12 | "license": "MIT", 13 | "files": [ 14 | "dist" 15 | ], 16 | "type": "module", 17 | "main": "dist/index.cjs", 18 | "module": "dist/index.js", 19 | "typings": "dist/index.d.ts", 20 | "scripts": { 21 | "build": "tsup --sourcemap", 22 | "typecheck": "tsc --noEmit" 23 | }, 24 | "dependencies": { 25 | "@dlightjs/error-handler": "workspace:*" 26 | }, 27 | "devDependencies": { 28 | "@babel/core": "^7.20.12", 29 | "@types/babel__core": "^7.20.5", 30 | "@types/node": "^20.10.5", 31 | "@dlightjs/reactivity-parser": "workspace:*" 32 | }, 33 | "tsup": { 34 | "entry": [ 35 | "src/index.ts" 36 | ], 37 | "format": [ 38 | "cjs", 39 | "esm" 40 | ], 41 | "clean": true, 42 | "dts": true 43 | } 44 | } -------------------------------------------------------------------------------- /packages/core/dlight/src/types/model.d.ts: -------------------------------------------------------------------------------- 1 | import { ContentKeyName, ContentProp } from "./compTag" 2 | 3 | type RemoveDLightInternal = Omit< 4 | T, 5 | "willMount" | "didMount" | "didUpdate" | "willUnmount" | keyof Props 6 | > 7 | 8 | export type Modeling = (props: Props) => Model 9 | 10 | type GetProps = keyof T extends never 11 | ? never 12 | : ContentKeyName extends undefined 13 | ? T 14 | : Omit> 15 | 16 | type GetContent = ContentKeyName extends undefined 17 | ? never 18 | : T[ContentKeyName] extends ContentProp 19 | ? U 20 | : never 21 | 22 | export const use: ( 23 | model: M, 24 | // @ts-expect-error Model should be a function 25 | props?: GetProps[0]>, 26 | // @ts-expect-error Model should be a function 27 | content?: GetContent[0]> 28 | // @ts-expect-error Model should be a function 29 | ) => RemoveDLightInternal, Parameters[0]> 30 | 31 | // @ts-expect-error Model should be a function 32 | export type ModelType = RemoveDLightInternal, Parameters[0]> 33 | -------------------------------------------------------------------------------- /packages/core/transpiler/view-parser/src/error.ts: -------------------------------------------------------------------------------- 1 | import { createErrorHandler } from "@dlightjs/error-handler" 2 | 3 | export const DLError = createErrorHandler( 4 | "ViewParser", 5 | { 6 | 1: "Invalid syntax in DLight's View, only accepts dot chain call expression", 7 | 2: "First argument of $0() must be an expression", 8 | 3: "Invalid syntax in DLight's View, only accepts expression as props", 9 | 4: "Invalid Snippet calling, only accepts static snippet calling like `this.Snippet()`", 10 | }, 11 | { 12 | 1: "DLight only accepts ForOfStatement as for loop, skipping this statement", 13 | 2: "EnvUnit must have at least one child, skipping this statement", 14 | 3: "Only Env/Comp/HTMLUnit can have a statement block as its children, skipping this statement", 15 | 4: "If you want to use a key in a for loop, make the first statement as a label statement like `key: item`, skipping this key for now", 16 | 5: "ForUnit must have at least one child, skipping this statement", 17 | }, 18 | { 19 | 1: "EnvUnit must have at least one prop, skipping this statement and flattening its children", 20 | } 21 | ) 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 iandxssxx 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /packages/core/transpiler/reactivity-parser/src/index.ts: -------------------------------------------------------------------------------- 1 | import { type ViewUnit } from "@dlightjs/view-parser" 2 | import { ReactivityParser } from "./parser" 3 | import { type ViewParticle, type ReactivityParserConfig } from "./types" 4 | 5 | /** 6 | * @brief Parse view units to get used properties and view particles with reactivity 7 | * @param viewUnits 8 | * @param config 9 | * @param options 10 | * @returns [viewParticles, usedProperties] 11 | */ 12 | export function parseReactivity( 13 | viewUnits: ViewUnit[], 14 | config: ReactivityParserConfig 15 | ): [ViewParticle[], Set] { 16 | // ---- ReactivityParser only accepts one view unit at a time, 17 | // so we loop through the view units and get all the used properties 18 | const usedProperties = new Set() 19 | const dlParticles = viewUnits.map(viewUnit => { 20 | const parser = new ReactivityParser(config) 21 | const dlParticle = parser.parse(viewUnit) 22 | parser.usedProperties.forEach(usedProperties.add.bind(usedProperties)) 23 | return dlParticle 24 | }) 25 | return [dlParticles, usedProperties] 26 | } 27 | 28 | export type * from "./types" 29 | -------------------------------------------------------------------------------- /packages/core/transpiler/reactivity-parser/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@dlightjs/reactivity-parser", 3 | "version": "1.0.0", 4 | "description": "DLight reactivity parser given View Units", 5 | "author": { 6 | "name": "IanDx", 7 | "email": "iandxssxx@gmail.com" 8 | }, 9 | "keywords": [ 10 | "dlight.js" 11 | ], 12 | "license": "MIT", 13 | "files": [ 14 | "dist" 15 | ], 16 | "type": "module", 17 | "main": "dist/index.cjs", 18 | "module": "dist/index.js", 19 | "typings": "dist/index.d.ts", 20 | "scripts": { 21 | "build": "tsup --sourcemap", 22 | "test": "vitest --ui", 23 | "typecheck": "tsc --noEmit" 24 | }, 25 | "dependencies": { 26 | "@dlightjs/error-handler": "workspace:*", 27 | "@dlightjs/view-parser": "workspace:*" 28 | }, 29 | "devDependencies": { 30 | "@babel/core": "^7.20.12", 31 | "@types/babel__core": "^7.20.5", 32 | "@vitest/ui": "^0.34.5", 33 | "vitest": "^0.34.5" 34 | }, 35 | "tsup": { 36 | "entry": [ 37 | "src/index.ts" 38 | ], 39 | "format": [ 40 | "cjs", 41 | "esm" 42 | ], 43 | "clean": true, 44 | "dts": true, 45 | "minify": true 46 | } 47 | } -------------------------------------------------------------------------------- /packages/core/dlight/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@dlightjs/dlight", 3 | "version": "1.0.1", 4 | "description": "DX-first UI rendering library", 5 | "author": { 6 | "name": "IanDx", 7 | "email": "iandxssxx@gmail.com" 8 | }, 9 | "keywords": [ 10 | "dlight.js" 11 | ], 12 | "license": "MIT", 13 | "files": [ 14 | "dist", 15 | "README.md" 16 | ], 17 | "type": "module", 18 | "main": "dist/index.cjs", 19 | "module": "dist/index.js", 20 | "typings": "dist/index.d.ts", 21 | "exports": { 22 | ".": { 23 | "types": "./dist/index.d.ts", 24 | "import": "./dist/index.js", 25 | "require": "./dist/index.cjs" 26 | }, 27 | "./global": { 28 | "types": "./dist/types/global.d.ts" 29 | } 30 | }, 31 | "scripts": { 32 | "build": "tsup --sourcemap && cp src/index.d.ts dist/ && cp -r src/types dist/", 33 | "typecheck": "tsc --noEmit" 34 | }, 35 | "dependencies": { 36 | "csstype": "^3.1.3", 37 | "@dlightjs/store": "workspace:*" 38 | }, 39 | "tsup": { 40 | "entry": [ 41 | "src/index.js" 42 | ], 43 | "format": [ 44 | "cjs", 45 | "esm" 46 | ], 47 | "clean": true, 48 | "minify": false 49 | } 50 | } -------------------------------------------------------------------------------- /packages/tools/create-dlightjs/templates/dlight-vite-js/src/App.view.js: -------------------------------------------------------------------------------- 1 | import { View } from "@dlightjs/dlight" 2 | import { wrap, slogan2, countWrap, countBtn, btnWrap, countText, colorD, colorL, m0 } from "./style.module.css" 3 | import Button from "./Button.view" 4 | import Header from "./Header.view" 5 | 6 | @Main 7 | @View 8 | export default class App { 9 | count = 0 10 | 11 | Body() { 12 | div() 13 | .class(wrap) 14 | { 15 | Header() 16 | div() 17 | .class(slogan2) 18 | { 19 | p() 20 | { 21 | span("D") 22 | .class(colorD) 23 | span("Light") 24 | .class(colorL) 25 | } 26 | p("DX-First UI") 27 | .class(m0) 28 | p("Rendering Library") 29 | } 30 | div() 31 | .class(countWrap) 32 | { 33 | p(this.count) 34 | .class(`${countBtn} ${countText}`) 35 | div() 36 | .class(btnWrap) 37 | { 38 | Button("count ++") 39 | .onClick(() => { 40 | this.count++ 41 | }) 42 | Button("count --") 43 | .onClick(() => { 44 | this.count-- 45 | }) 46 | } 47 | } 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /packages/core/babel-preset-dlight/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "babel-preset-dlight", 3 | "version": "1.0.0", 4 | "description": "DLight transpiler babel preset", 5 | "author": { 6 | "name": "IanDx", 7 | "email": "iandxssxx@gmail.com" 8 | }, 9 | "keywords": [ 10 | "dlight.js", 11 | "babel-preset" 12 | ], 13 | "license": "MIT", 14 | "files": [ 15 | "dist" 16 | ], 17 | "type": "module", 18 | "main": "dist/index.cjs", 19 | "module": "dist/index.js", 20 | "typings": "dist/index.d.ts", 21 | "scripts": { 22 | "build": "tsup --sourcemap", 23 | "typecheck": "tsc --noEmit" 24 | }, 25 | "dependencies": { 26 | "@babel/plugin-syntax-decorators": "^7.23.3", 27 | "@dlightjs/reactivity-parser": "workspace:*", 28 | "@dlightjs/view-generator": "workspace:*", 29 | "@dlightjs/view-parser": "workspace:*", 30 | "babel-plugin-syntax-typescript-new": "workspace:*", 31 | "minimatch": "^9.0.3" 32 | }, 33 | "devDependencies": { 34 | "@types/babel__core": "^7.20.5", 35 | "@types/node": "^20.10.5" 36 | }, 37 | "tsup": { 38 | "entry": [ 39 | "src/index.ts" 40 | ], 41 | "format": [ 42 | "cjs", 43 | "esm" 44 | ], 45 | "clean": true, 46 | "dts": true, 47 | "minify": true 48 | } 49 | } -------------------------------------------------------------------------------- /packages/tools/create-dlightjs/templates/dlight-vite-ts/src/Header.view.ts: -------------------------------------------------------------------------------- 1 | import { View, type Typed, div, img, a, Snippet, type Pretty, type SnippetTyped } from "@dlightjs/dlight" 2 | import { headerWrap, navBtn, headerLogo } from "./style.module.css" 3 | 4 | interface NavProps { 5 | url: string 6 | navName: string 7 | } 8 | 9 | @View 10 | class Header { 11 | navList = [ 12 | { 13 | url: "https://dlight-js.com/docs", 14 | navName: "Docs" 15 | }, 16 | { 17 | url: "https://dlight-js.com/examples", 18 | navName: "Examples" 19 | }, 20 | { 21 | url: "https://dlight-js.com/playground", 22 | navName: "Playground" 23 | } 24 | ] 25 | 26 | @Snippet 27 | Nav = (({ url, navName }: NavProps) => { 28 | a(navName) 29 | .class(navBtn) 30 | .href(url) 31 | }) as Pretty as SnippetTyped 32 | 33 | Body() { 34 | div() 35 | .class(headerWrap) 36 | { 37 | img() 38 | .src("./logo_title.png") 39 | .class(headerLogo) 40 | div() 41 | { 42 | for (const navItem of this.navList) { 43 | this.Nav() 44 | .url(navItem.url) 45 | .navName(navItem.navName) 46 | } 47 | } 48 | } 49 | } 50 | } 51 | 52 | export default Header as Pretty as Typed 53 | -------------------------------------------------------------------------------- /packages/core/dlight/src/store.js: -------------------------------------------------------------------------------- 1 | import { Store } from "@dlightjs/store" 2 | 3 | // ---- Using external Store to store global and document 4 | // Because Store is a singleton, it is safe to use it as a global variable 5 | // If created in DLight package, different package versions will introduce 6 | // multiple Store instances. 7 | 8 | if (!("global" in Store)) { 9 | if (typeof window !== "undefined") { 10 | Store.global = window 11 | } else if (typeof global !== "undefined") { 12 | Store.global = global 13 | } else { 14 | Store.global = {} 15 | } 16 | } 17 | if (!("document" in Store)) { 18 | if (typeof document !== "undefined") { 19 | Store.document = document 20 | } 21 | } 22 | 23 | export const DLStore = { ...Store, delegatedEvents: new Set() } 24 | 25 | export function setGlobal(globalObj) { 26 | DLStore.global = globalObj 27 | } 28 | 29 | export function setDocument(customDocument) { 30 | DLStore.document = customDocument 31 | } 32 | 33 | /** 34 | * @brief Compare the deps with the previous deps 35 | * @param deps 36 | * @param prevDeps 37 | * @returns 38 | */ 39 | export function cached(deps, prevDeps) { 40 | if (!prevDeps || deps.length !== prevDeps.length) return false 41 | return deps.every((dep, i) => !(dep instanceof Object) && prevDeps[i] === dep) 42 | } 43 | -------------------------------------------------------------------------------- /packages/tools/babel-plugin-syntax-typescript-new/src/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Scoped statement blocks in official implementation 3 | */ 4 | import { declare } from "@babel/helper-plugin-utils" 5 | 6 | function removePlugin(plugins: any[], name: string): void { 7 | const indices: number[] = [] 8 | plugins.forEach((plugin, i) => { 9 | const n = Array.isArray(plugin) ? plugin[0] : plugin 10 | if (n === name) { 11 | indices.unshift(i) 12 | } 13 | }) 14 | for (const i of indices) { 15 | plugins.splice(i, 1) 16 | } 17 | } 18 | 19 | interface Options { 20 | disallowAmbiguousJSXLike?: boolean 21 | dts?: boolean 22 | isTSX?: boolean 23 | } 24 | 25 | export default declare((api: any, opts: Options): any => { 26 | api.assertVersion(7) 27 | const { disallowAmbiguousJSXLike, dts } = opts ?? {} 28 | const { isTSX } = opts ?? {} 29 | return { 30 | name: "syntax-typescript", 31 | manipulateOptions(_opts: any, parserOpts: any) { 32 | const { plugins } = parserOpts 33 | removePlugin(plugins, "flow") 34 | removePlugin(plugins, "jsx") 35 | plugins.push("objectRestSpread", "classProperties") 36 | if (isTSX) { 37 | plugins.push("jsx") 38 | } 39 | parserOpts.plugins.push(["typescript", { disallowAmbiguousJSXLike, dts }]) 40 | }, 41 | } 42 | }) 43 | -------------------------------------------------------------------------------- /apps/dev/src/benchmark/data.js: -------------------------------------------------------------------------------- 1 | let idCounter = 1 2 | 3 | const adjectives = [ 4 | "pretty", 5 | "large", 6 | "big", 7 | "small", 8 | "tall", 9 | "short", 10 | "long", 11 | "handsome", 12 | "plain", 13 | "quaint", 14 | "clean", 15 | "elegant", 16 | "easy", 17 | "angry", 18 | "crazy", 19 | "helpful", 20 | "mushy", 21 | "odd", 22 | "unsightly", 23 | "adorable", 24 | "important", 25 | "inexpensive", 26 | "cheap", 27 | "expensive", 28 | "fancy", 29 | ] 30 | const colours = [ 31 | "red", 32 | "yellow", 33 | "blue", 34 | "green", 35 | "pink", 36 | "brown", 37 | "purple", 38 | "brown", 39 | "white", 40 | "black", 41 | "orange", 42 | ] 43 | const nouns = [ 44 | "table", 45 | "chair", 46 | "house", 47 | "bbq", 48 | "desk", 49 | "car", 50 | "pony", 51 | "cookie", 52 | "sandwich", 53 | "burger", 54 | "pizza", 55 | "mouse", 56 | "keyboard", 57 | ] 58 | 59 | function _random(max) { 60 | return Math.round(Math.random() * 1000) % max 61 | } 62 | 63 | export function buildData(count) { 64 | const data = new Array(count) 65 | for (let i = 0; i < count; i++) { 66 | data[i] = { 67 | id: idCounter++, 68 | label: `${adjectives[_random(adjectives.length)]} ${ 69 | colours[_random(colours.length)] 70 | } ${nouns[_random(nouns.length)]}`, 71 | } 72 | } 73 | return data 74 | } 75 | -------------------------------------------------------------------------------- /packages/tools/create-dlightjs/src/utils.ts: -------------------------------------------------------------------------------- 1 | export function randomId() { 2 | return Math.random().toString(32).slice(2, 8) 3 | } 4 | 5 | export function success(message: string) { 6 | console.log(`\x1b[0;32m✔️ ${message}\x1b[0m`) 7 | } 8 | 9 | export function fail(message: string) { 10 | console.log(`\x1b[0;31m❗️ ${message}\x1b[0m`) 11 | } 12 | 13 | export function info(message: string) { 14 | console.log(`\x1b[0;36m${message}\x1b[0m`) 15 | } 16 | 17 | export function jsonify(obj: any) { 18 | return JSON.stringify(obj) 19 | .replace("{", "{\n ") 20 | .replace("}", "\n }") 21 | .replace(/,/g, ",\n ") 22 | } 23 | 24 | export const logo = ` 25 | /DDDDDDD /DD /DD /DD /DD 26 | | DD__ DD| DD |__/ | DD | DD 27 | /:D| DD \\ DD| DD /DD /DDDDDD | DDDDDDD /DDDDDD 28 | |__/| DD | DD| DD | DD /DD__ DD| DD__ DD|_ DD_/ 29 | | DD | DD| DD | DD| DD \\ DD| DD \\ DD | DD 30 | /:D| DD | DD| DD | DD| DD | DD| DD | DD | DD /DD 31 | |__/| DDDDDDD/| DDDDDDDD| DD| DDDDDDD| DD | DD | DDDD/ 32 | |_______/ |________/|__/ \\____ DD|__/ |__/ \\___/ 33 | /DD \\ DD 34 | | DDDDDD/ 35 | \\______/ 36 | ` 37 | -------------------------------------------------------------------------------- /packages/tools/vite-plugin-dlight/src/index.ts: -------------------------------------------------------------------------------- 1 | import { transform } from "@babel/core" 2 | import dlight, { type DLightOption } from "babel-preset-dlight" 3 | import { minimatch } from "minimatch" 4 | import { Plugin, TransformResult } from "vite" 5 | 6 | export default function (options: DLightOption = {}): Plugin { 7 | const { 8 | files: preFiles = "**/*.{js,jsx,ts,tsx}", 9 | excludeFiles: preExcludeFiles = "**/{dist,node_modules,lib}/*.{js,ts}", 10 | } = options 11 | const files = Array.isArray(preFiles) ? preFiles : [preFiles] 12 | const excludeFiles = Array.isArray(preExcludeFiles) 13 | ? preExcludeFiles 14 | : [preExcludeFiles] 15 | 16 | return { 17 | name: "dlight", 18 | enforce: "pre", 19 | transform(code: string, id: string) { 20 | let enter = false 21 | for (const allowedPath of files) { 22 | if (minimatch(id, allowedPath)) { 23 | enter = true 24 | break 25 | } 26 | } 27 | for (const notAllowedPath of excludeFiles) { 28 | if (minimatch(id, notAllowedPath)) { 29 | enter = false 30 | break 31 | } 32 | } 33 | if (!enter) return 34 | return transform(code, { 35 | babelrc: false, 36 | configFile: false, 37 | presets: [[dlight, options]], 38 | sourceMaps: true, 39 | filename: id, 40 | }) as TransformResult 41 | }, 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /apps/example/src/element/Element.view.js: -------------------------------------------------------------------------------- 1 | import { View } from "@dlightjs/dlight" 2 | 3 | @View 4 | class Element { 5 | color = "gray" 6 | count1 = 0 7 | count2 = 0 8 | count3 = 0 9 | element1 10 | element2 11 | 12 | handleClick(e) { 13 | if (e.target === this.element1) { 14 | this.count1++ 15 | } else if (e.target === this.element2) { 16 | this.count2++ 17 | } 18 | } 19 | 20 | willMount() { 21 | window.addEventListener("click", this.handleClick.bind(this)) 22 | } 23 | 24 | willUnmount() { 25 | window.removeEventListener("click", this.handleClick.bind(this)) 26 | } 27 | 28 | View() { 29 | div().style({ backgroundColor: "gray", height: "500px" }) 30 | { 31 | div(`Child1, click count: ${this.count1}`).element(this.element1).style({ 32 | backgroundColor: "blue", 33 | height: "100px", 34 | width: "150px", 35 | color: "white", 36 | }) 37 | div(`Child2, click count: ${this.count2}`) 38 | .element(this.element2) 39 | .style({ backgroundColor: "orange", height: "100px", width: "150px" }) 40 | div(`Child3, click count: ${this.count3}`) 41 | .element(el => { 42 | el.addEventListener("click", () => { 43 | this.count3++ 44 | }) 45 | }) 46 | .style({ backgroundColor: "white", height: "100px", width: "150px" }) 47 | } 48 | } 49 | } 50 | 51 | export default Element 52 | -------------------------------------------------------------------------------- /packages/core/babel-preset-dlight/src/plugin.ts: -------------------------------------------------------------------------------- 1 | import type babel from "@babel/core" 2 | import { type PluginObj } from "@babel/core" 3 | import { PluginProviderClass } from "./pluginProvider" 4 | import { type DLightOption } from "./types" 5 | import { defaultAttributeMap } from "./const" 6 | 7 | export default function (api: typeof babel, options: DLightOption): PluginObj { 8 | const { types } = api 9 | const { 10 | files = "**/*.{js,ts}", 11 | excludeFiles = "**/{dist,node_modules,lib}/*", 12 | enableDevTools = false, 13 | htmlTags = defaultHtmlTags => defaultHtmlTags, 14 | attributeMap = defaultAttributeMap, 15 | } = options 16 | 17 | const pluginProvider = new PluginProviderClass( 18 | api, 19 | types, 20 | Array.isArray(files) ? files : [files], 21 | Array.isArray(excludeFiles) ? excludeFiles : [excludeFiles], 22 | enableDevTools, 23 | htmlTags, 24 | attributeMap 25 | ) 26 | 27 | return { 28 | visitor: { 29 | Program: { 30 | enter(path, { filename }) { 31 | return pluginProvider.programEnterVisitor(path, filename) 32 | }, 33 | exit: pluginProvider.programExitVisitor.bind(pluginProvider), 34 | }, 35 | ClassDeclaration: { 36 | enter: pluginProvider.classEnter.bind(pluginProvider), 37 | exit: pluginProvider.classExit.bind(pluginProvider), 38 | }, 39 | ClassMethod: pluginProvider.classMethodVisitor.bind(pluginProvider), 40 | ClassProperty: pluginProvider.classPropertyVisitor.bind(pluginProvider), 41 | }, 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /packages/core/dlight/src/types/index.d.ts: -------------------------------------------------------------------------------- 1 | import { type Typed } from "./compTag" 2 | import { type DLightHtmlTagFunc } from "./htmlTag" 3 | export { type Properties as CSSProperties } from "csstype" 4 | 5 | export const comp: (tag: T) => object extends T ? any : Typed 6 | export const tag: (tag: any) => DLightHtmlTagFunc 7 | 8 | export { _ } from "./expressionTag" 9 | export * from "./htmlTag" 10 | export * from "./compTag" 11 | export * from "./envTag" 12 | export * from "./model" 13 | export const Static: any 14 | export const Children: any 15 | export const Content: any 16 | export const Prop: any 17 | export const Env: any 18 | export const Watch: any 19 | export const ForwardProps: any 20 | export const Main: any 21 | export const App: any 22 | export const Mount: (idOrEl: string | HTMLElement) => any 23 | 24 | // ---- With actual value 25 | export function render(idOrEl: string | HTMLElement, DL: any): void 26 | export function manual(callback: () => T, _deps?: any[]): T 27 | export function escape(arg: T): T 28 | export function setGlobal(globalObj: any): void 29 | export function setDocument(customDocument: any): void 30 | export const $: typeof escape 31 | export const View: any 32 | export const Snippet: any 33 | export const Model: any 34 | export const update: any 35 | export const required: any 36 | export function insertChildren(parent: T, children: DLightViewProp): void 37 | 38 | // ---- View types 39 | export type DLightViewComp = Typed 40 | export type DLightViewProp = (View: any) => void 41 | export type DLightViewLazy = () => Promise<{ default: T }> 42 | -------------------------------------------------------------------------------- /packages/core/dlight/src/types/htmlTag/htmlElement.d.ts: -------------------------------------------------------------------------------- 1 | import { type Properties } from "csstype" 2 | 3 | // ---- Used to determine whether X and Y are equal, return A if equal, otherwise B 4 | type IfEquals = 5 | (() => T extends X ? 1 : 2) extends () => T extends Y ? 1 : 2 ? A : B 6 | 7 | export type OmitIndexSignature = { 8 | // eslint-disable-next-line @typescript-eslint/ban-types 9 | [KeyType in keyof ObjectType as {} extends Record 10 | ? never 11 | : KeyType]: ObjectType[KeyType] 12 | } 13 | 14 | // ---- For each key, check whether there is readonly, if there is, return never, and then Pick out is not never 15 | type WritableKeysOf = { 16 | [P in keyof T]: IfEquals< 17 | { [Q in P]: T[P] }, 18 | { -readonly [Q in P]: T[P] }, 19 | P, 20 | never 21 | > 22 | }[keyof T] 23 | type RemoveReadOnly = Pick> 24 | 25 | // ---- Delete all functions 26 | type OmitFunction = Omit< 27 | T, 28 | { [K in keyof T]: T[K] extends (...args: any) => any ? K : never }[keyof T] 29 | > 30 | 31 | type OmitFuncAndReadOnly = RemoveReadOnly< 32 | OmitFunction> 33 | > 34 | 35 | // ---- properties 36 | type OmitFuncAndReadOnlyProperty = Omit< 37 | OmitFuncAndReadOnly, 38 | "className" | "htmlFor" | "style" | "innerText" 39 | > 40 | 41 | type CustomCSSProperties = { 42 | [Key in `--${string}`]: string | number 43 | } 44 | 45 | export type HTMLAttributes = OmitFuncAndReadOnlyProperty & { 46 | style: Properties & CustomCSSProperties 47 | class: string 48 | for: string 49 | } 50 | -------------------------------------------------------------------------------- /packages/core/babel-preset-dlight/src/types.ts: -------------------------------------------------------------------------------- 1 | import { type types as t } from "@babel/core" 2 | 3 | export type HTMLTags = string[] | ((defaultHtmlTags: string[]) => string[]) 4 | export interface DLightOption { 5 | /** 6 | * Files that will be included 7 | * @default ** /*.{js,jsx,ts,tsx} 8 | */ 9 | files?: string | string[] 10 | /** 11 | * Files that will be excludes 12 | * @default ** /{dist,node_modules,lib}/*.{js,ts} 13 | */ 14 | excludeFiles?: string | string[] 15 | /** 16 | * Enable devtools 17 | * @default false 18 | */ 19 | enableDevTools?: boolean 20 | /** 21 | * Custom HTML tags. 22 | * Accepts 2 types: 23 | * 1. string[], e.g. ["div", "span"] 24 | * if contains "*", then all default tags will be included 25 | * 2. (defaultHtmlTags: string[]) => string[] 26 | * @default defaultHtmlTags => defaultHtmlTags 27 | */ 28 | htmlTags?: HTMLTags 29 | /** 30 | * Allowed HTML tags from attributes 31 | * e.g. { alt: ["area", "img", "input"] } 32 | */ 33 | attributeMap?: Record 34 | } 35 | 36 | export type PropertyContainer = Record< 37 | string, 38 | { 39 | node: t.ClassProperty | t.ClassMethod 40 | deps: string[] 41 | isStatic?: boolean 42 | isContent?: boolean 43 | isChildren?: boolean | number 44 | isModel?: boolean 45 | isWatcher?: boolean 46 | isPropOrEnv?: "Prop" | "Env" 47 | depsNode?: t.ArrayExpression 48 | } 49 | > 50 | 51 | export type IdentifierToDepNode = t.SpreadElement | t.Expression 52 | 53 | export type SnippetPropSubDepMap = Record> 54 | -------------------------------------------------------------------------------- /packages/tools/create-dlightjs/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # create-dlightjs 2 | 3 | ## 1.0.0 4 | 5 | - Released 6 | 7 | ## 1.0.0-next.1 8 | 9 | ### Patch Changes 10 | 11 | - refactor: template upgrade 12 | 13 | ## 1.0.0-beta.1 14 | 15 | ### Patch Changes 16 | 17 | - feat: remove tag import in babel preset 18 | 19 | ## 1.0.0-beta.2 20 | 21 | ### Patch Changes 22 | 23 | - feat: new types from @dlightjs/dight 24 | 25 | ## 1.0.0-alpha.1 26 | 27 | ### Patch Changes 28 | 29 | - feat: get npm url from NPM_CONFIG_REGISTRY 30 | 31 | ## 1.0.0-alpha.0 32 | 33 | ### Major Changes 34 | 35 | - feat: create-dlightjs upgrade to v1.0.0 36 | 37 | ## 0.10.1 38 | 39 | ### Patch Changes 40 | 41 | - feat: update templates 42 | 43 | ## 0.10.0 44 | 45 | ### Minor Changes 46 | 47 | - feat: upgrade to 0.10.0 48 | 49 | ## 0.9.34 50 | 51 | ### Patch Changes 52 | 53 | - feat: change to decorator family 54 | 55 | ## 0.9.33 56 | 57 | ### Patch Changes 58 | 59 | - fix: no vite and typescript packages 60 | 61 | ## 0.9.32 62 | 63 | ### Patch Changes 64 | 65 | - feat: change package version get method 66 | 67 | ## 0.9.31 68 | 69 | ### Patch Changes 70 | 71 | - build 72 | 73 | ## 0.9.30 74 | 75 | ### Patch Changes 76 | 77 | - feat: change blank project icon 78 | 79 | ## 0.9.29 80 | 81 | ### Patch Changes 82 | 83 | - build 84 | 85 | ## 0.9.28 86 | 87 | ### Patch Changes 88 | 89 | - feat: change packages 90 | 91 | ## 0.9.27 92 | 93 | ### Patch Changes 94 | 95 | - feat: add packages to be installed 96 | 97 | ## 0.9.26 98 | 99 | ### Patch Changes 100 | 101 | - feat: change depracated templates 102 | -------------------------------------------------------------------------------- /apps/dev/src/Test.view.ts: -------------------------------------------------------------------------------- 1 | import { 2 | View, 3 | type Pretty, 4 | type Typed, 5 | Main, 6 | button, 7 | div, 8 | } from "@dlightjs/dlight" 9 | 10 | interface TestProps {} 11 | 12 | function loopNodes(nodes: any, runFunc: any) { 13 | const stack = [...nodes].reverse() 14 | while (stack.length > 0) { 15 | const node = stack.pop() 16 | runFunc(node) 17 | if (!("_$dlNodeType" in node)) { 18 | if (node._$nodes) { 19 | stack.push(...[...node._$nodes].reverse()) 20 | } else { 21 | stack.push(...[...node.childNodes].reverse()) 22 | } 23 | } else node._$nodes && stack.push(...[...node._$nodes].reverse()) 24 | } 25 | } 26 | @Main 27 | @View 28 | class Test implements TestProps { 29 | arr = [] 30 | bbb = [1, 2, 3] 31 | didMount() { 32 | const add = nodes => { 33 | loopNodes(nodes, node => { 34 | if (node.initNewNodes) { 35 | const baseInitNewNodes = node.initNewNodes 36 | node.initNewNodes = function (newNodes: any) { 37 | baseInitNewNodes.call(this, newNodes) 38 | add(newNodes) 39 | console.log("ok") 40 | } 41 | console.log(node, "jjj") 42 | } 43 | }) 44 | } 45 | 46 | add(this._$nodes) 47 | } 48 | View() { 49 | button("+").onClick(() => { 50 | // this.arr.push(1) 51 | this.bbb.push(1) 52 | }) 53 | button("+").onClick(() => { 54 | this.arr.push(1) 55 | // this.bbb.push(1) 56 | }) 57 | for (const a of this.arr) { 58 | for (const b of this.bbb) { 59 | div(b) 60 | } 61 | } 62 | } 63 | } 64 | 65 | export default Test as Pretty as Typed 66 | -------------------------------------------------------------------------------- /packages/core/dlight/src/PropView.js: -------------------------------------------------------------------------------- 1 | import { DLNode } from "./DLNode" 2 | import { insertNode } from "./HTMLNode" 3 | export class PropView { 4 | propViewFunc 5 | dlUpdateFunc = new Set() 6 | 7 | /** 8 | * @brief PropView constructor, accept a function that returns a list of DLNode 9 | * @param propViewFunc - A function that when called, collects and returns an array of DLNode instances 10 | */ 11 | constructor(propViewFunc) { 12 | this.propViewFunc = propViewFunc 13 | } 14 | 15 | /** 16 | * @brief Build the prop view by calling the propViewFunc and add every single instance of the returned DLNode to dlUpdateNodes 17 | * @returns An array of DLNode instances returned by propViewFunc 18 | */ 19 | build() { 20 | let update 21 | const addUpdate = updateFunc => { 22 | update = updateFunc 23 | this.dlUpdateFunc.add(updateFunc) 24 | } 25 | const newNodes = this.propViewFunc(addUpdate) 26 | if (newNodes.length === 0) return [] 27 | if (update) { 28 | // Remove the updateNode from dlUpdateNodes when it unmounts 29 | DLNode.addWillUnmount( 30 | newNodes[0], 31 | this.dlUpdateFunc.delete.bind(this.dlUpdateFunc, update) 32 | ) 33 | } 34 | 35 | return newNodes 36 | } 37 | 38 | /** 39 | * @brief Update every node in dlUpdateNodes 40 | * @param changed - A parameter indicating what changed to trigger the update 41 | */ 42 | update(...args) { 43 | this.dlUpdateFunc.forEach(update => { 44 | update(...args) 45 | }) 46 | } 47 | } 48 | 49 | export function insertChildren(el, propView) { 50 | insertNode(el, { _$nodes: propView.build(), _$dlNodeType: 7 }, 0) 51 | } 52 | -------------------------------------------------------------------------------- /apps/example/src/index.js: -------------------------------------------------------------------------------- 1 | import { render } from "@dlightjs/dlight" 2 | import App from "./App.view" 3 | import For from "./forNode/For.view" 4 | import If from "./ifNode/If.view" 5 | import Function from "./introduction/Function.view" 6 | import Hello from "./introduction/Hello.view" 7 | import Reactivity from "./introduction/Reactivity.view" 8 | import Variable from "./introduction/Variable.view" 9 | import RootComp from "./envNode/RootComp.view" 10 | import InlineStyle from "./style/InlineStyle.view" 11 | import ParentComp from "./nestComp/basic/ParentNode.view" 12 | import ParentCompF from "./forwardProps/ParentComp.view" 13 | import ReactiveStatement from "./introduction/ReactiveStatement.view" 14 | import IfElse from "./ifNode/ifelse.view" 15 | import InlineHandler from "./events/InlineHandler.view" 16 | import AddEvent from "./events/AddEvent.view" 17 | import Element from "./element/Element.view" 18 | import NestHtml from "./introduction/nestHtml.view" 19 | import TextInput from "./inputs/TextInput.view" 20 | import RadioInput from "./inputs/RadioInput.view" 21 | import Image from "./introduction/Image.view" 22 | import Link from "./introduction/Link.view" 23 | import ViewWithSnippet from "./snippet/ViewWithSnippet.view" 24 | import ContentView from "./nestComp/content/Content.view" 25 | import ChildrenView from "./nestComp/children/Children.view" 26 | import DidMount from "./lifeCycle/didMount.view" 27 | import WillMount from "./lifeCycle/willMount.view" 28 | import Expression from "./expression/Expression.view" 29 | import PropViewClass from "./propView/PropView.view" 30 | import CheckboxInput from "./inputs/CheckboxInput.view" 31 | import CompElement from "./element/CompElement.view" 32 | 33 | render("main", RootComp) 34 | -------------------------------------------------------------------------------- /packages/core/dlight/src/index.js: -------------------------------------------------------------------------------- 1 | import { DLNode } from "./DLNode" 2 | import { insertNode } from "./HTMLNode" 3 | 4 | export * from "./HTMLNode" 5 | export * from "./CompNode" 6 | export * from "./EnvNode" 7 | export * from "./TextNode" 8 | export * from "./PropView" 9 | export * from "./SnippetNode" 10 | export * from "./MutableNode/ForNode" 11 | export * from "./MutableNode/ExpNode" 12 | export * from "./MutableNode/CondNode" 13 | export * from "./MutableNode/TryNode" 14 | 15 | import { DLStore } from "./store" 16 | export { setGlobal, setDocument } from "./store" 17 | 18 | function initStore() { 19 | // Declare a global variable to store willUnmount functions 20 | DLStore.global.WillUnmountStore = [] 21 | // Declare a global variable to store didUnmount functions 22 | DLStore.global.DidUnmountStore = [] 23 | } 24 | 25 | export function render(idOrEl, DL) { 26 | let el = idOrEl 27 | if (typeof idOrEl === "string") { 28 | const elFound = DLStore.document.getElementById(idOrEl) 29 | if (elFound) el = elFound 30 | else { 31 | throw new Error(`DLight: Element with id ${idOrEl} not found`) 32 | } 33 | } 34 | initStore() 35 | el.innerHTML = "" 36 | const dlNode = new DL() 37 | dlNode._$init() 38 | insertNode(el, dlNode, 0) 39 | DLNode.runDidMount() 40 | } 41 | 42 | export function manual(callback, _deps) { 43 | return callback() 44 | } 45 | export function escape(arg) { 46 | return arg 47 | } 48 | 49 | export const $ = escape 50 | export const required = null 51 | 52 | export function use() { 53 | console.error( 54 | "DLight: use() is not supported be called directly. You can only assign `use(model)` to a dlight class property. Any other expressions are not allowed." 55 | ) 56 | } 57 | -------------------------------------------------------------------------------- /packages/core/dlight/src/MutableNode/TryNode.js: -------------------------------------------------------------------------------- 1 | import { DLNodeType } from "../DLNode" 2 | import { FlatNode } from "./FlatNode" 3 | import { EnvNode } from "../EnvNode" 4 | 5 | export class TryNode extends FlatNode { 6 | constructor(tryFunc, catchFunc) { 7 | super(DLNodeType.Try) 8 | this.tryFunc = tryFunc 9 | const catchable = this.getCatchable(catchFunc) 10 | this.envNode = new EnvNode({ _$catchable: catchable }) 11 | const nodes = tryFunc(this.setUpdateFunc.bind(this), catchable) ?? [] 12 | this.envNode.initNodes(nodes) 13 | this._$nodes = nodes 14 | } 15 | 16 | update(changed) { 17 | this.updateFunc?.(changed) 18 | } 19 | 20 | setUpdateFunc(updateFunc) { 21 | this.updateFunc = updateFunc 22 | } 23 | 24 | getCatchable(catchFunc) { 25 | return callback => 26 | (...args) => { 27 | try { 28 | return callback(...args) 29 | } catch (e) { 30 | // ---- Run it in next tick to make sure when error occurs before 31 | // didMount, this._$parentEl is not null 32 | Promise.resolve().then(() => { 33 | const nodes = this.geneNewNodesInEnv(() => 34 | catchFunc(this.setUpdateFunc.bind(this), e) 35 | ) 36 | this._$nodes && this.removeNodes(this._$nodes) 37 | const parentEl = this._$parentEl 38 | const flowIndex = FlatNode.getFlowIndexFromNodes( 39 | parentEl._$nodes, 40 | this 41 | ) 42 | const nextSibling = parentEl.childNodes[flowIndex] 43 | FlatNode.appendNodesWithSibling(nodes, parentEl, nextSibling) 44 | FlatNode.runDidMount() 45 | this._$nodes = nodes 46 | }) 47 | } 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /apps/dev/src/Fine.view.ts: -------------------------------------------------------------------------------- 1 | import { Pretty, Typed, View, button, div, form, input } from "@dlightjs/dlight" 2 | 3 | @View 4 | class App { 5 | tags = new Set(["dlight"]) 6 | appendedTags = new Set([""]) 7 | 8 | handleDeleteTag(tag: string) { 9 | this.tags.delete(tag) 10 | } 11 | 12 | View() { 13 | form().onSubmit(e => { 14 | e.preventDefault() 15 | 16 | const target = e.target as HTMLFormElement 17 | const formData = new FormData(target) 18 | const tag = String(formData.get("tag")) 19 | 20 | this.tags.add(tag) 21 | target.reset() 22 | }) 23 | { 24 | input().name("tag").placeholder("add a tag") 25 | button("add") 26 | } 27 | div() 28 | { 29 | for (const tag of Array.from(this.tags)) { 30 | button(tag) 31 | .draggable(true) 32 | .onClick(() => this.handleDeleteTag(tag)) 33 | .onDragStart(e => { 34 | if (!e.dataTransfer) return 35 | e.dataTransfer.setData("text/plain", tag) 36 | }) 37 | } 38 | } 39 | div() 40 | .style({ 41 | width: "200px", 42 | height: "200px", 43 | border: "1px solid black", 44 | }) 45 | .onDrop(e => { 46 | e.preventDefault() 47 | if (!e.dataTransfer) return 48 | 49 | e.dataTransfer.dropEffect = "move" 50 | const data = e.dataTransfer.getData("text/plain") 51 | this.appendedTags.add(data) 52 | }) 53 | .onDragOver(e => { 54 | e.preventDefault() 55 | if (!e.dataTransfer) return 56 | 57 | e.dataTransfer.dropEffect = "move" 58 | }) 59 | { 60 | for (const tag of Array.from(this.appendedTags)) { 61 | div(tag) 62 | } 63 | } 64 | } 65 | } 66 | 67 | export default App as Pretty as Typed 68 | -------------------------------------------------------------------------------- /packages/core/transpiler/view-parser/src/test/TryUnit.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from "vitest" 2 | import { parse } from "./mock" 3 | import { TryUnit } from "../types" 4 | import { types as t } from "@babel/core" 5 | 6 | describe("TryUnit", () => { 7 | it("should identify a try-catch statement as an TryUnit", () => { 8 | const viewUnits = parse("try { } catch (e) { }") 9 | expect(viewUnits.length).toBe(1) 10 | expect(viewUnits[0].type).toBe("try") 11 | }) 12 | 13 | it("should identify a try-catch statement with statements inside", () => { 14 | const viewUnits = parse('try { div("hello") } catch (e) { }') as TryUnit[] 15 | expect(viewUnits[0].type).toBe("try") 16 | 17 | const child = viewUnits[0].children[0] 18 | expect(child.type).toBe("html") 19 | }) 20 | 21 | it("should identify a try-catch statement with catch", () => { 22 | const viewUnits = parse("try { } catch (e) { div() }") 23 | expect(viewUnits.length).toBe(1) 24 | expect(viewUnits[0].type).toBe("try") 25 | expect((viewUnits[0] as TryUnit).catchChildren[0].type).toBe("html") 26 | }) 27 | 28 | it("should identify a try-catch statement's catch with exception", () => { 29 | const viewUnits = parse("try { } catch (e) { div(e) }") 30 | expect(viewUnits.length).toBe(1) 31 | expect(viewUnits[0].type).toBe("try") 32 | const exception = (viewUnits[0] as TryUnit).exception 33 | expect(t.isIdentifier(exception, { name: "e" })).toBeTruthy() 34 | }) 35 | 36 | it("should identify a try-catch statement with no exception", () => { 37 | const viewUnits = parse("try { } catch { div() }") 38 | expect(viewUnits.length).toBe(1) 39 | expect(viewUnits[0].type).toBe("try") 40 | const exception = (viewUnits[0] as TryUnit).exception 41 | expect(exception).toBe(null) 42 | }) 43 | }) 44 | -------------------------------------------------------------------------------- /packages/tools/create-dlightjs/templates/dlight-vite-ts/src/App.view.ts: -------------------------------------------------------------------------------- 1 | import { View, type Typed, div, p, button, span, type Pretty, type SnippetTyped, Snippet } from "@dlightjs/dlight" 2 | import Header from "./Header.view" 3 | import { wrap, slogan2, countWrap, countBtn, btnHover, btnWrap, countText, colorD, colorL, m0 } from "./style.module.css" 4 | 5 | interface BtnProps { 6 | content: string 7 | onClick: () => void 8 | index: number 9 | } 10 | 11 | @View 12 | class App { 13 | count = 0 14 | btnStatus = [0, 0] 15 | 16 | @Snippet 17 | Btn = (({ content, onClick, index }: BtnProps) => { 18 | button(content) 19 | .class(this.btnStatus[index] === 1 ? `${countBtn} ${btnHover}` : countBtn) 20 | .onClick(onClick) 21 | .onMouseOver(() => { 22 | this.btnStatus[index] = 1 23 | this.btnStatus = [...this.btnStatus] 24 | }) 25 | .onMouseLeave(() => { 26 | this.btnStatus[index] = 0 27 | this.btnStatus = [...this.btnStatus] 28 | }) 29 | }) as Pretty as SnippetTyped 30 | 31 | 32 | Body() { 33 | div() 34 | .class(wrap) 35 | { 36 | Header() 37 | div() 38 | .class(slogan2) 39 | { 40 | p() 41 | { 42 | span("D") 43 | .class(colorD) 44 | span("Light") 45 | .class(colorL) 46 | } 47 | p("DX-first UI") 48 | .class(m0) 49 | p("Rendering Library") 50 | } 51 | div() 52 | .class(countWrap) 53 | { 54 | p(this.count) 55 | .class(`${countBtn} ${countText}`) 56 | div() 57 | .class(btnWrap) 58 | { 59 | this.Btn("count ++") 60 | .index(0) 61 | .onClick(() => { 62 | this.count++ 63 | }) 64 | this.Btn("count --") 65 | .index(1) 66 | .onClick(() => { 67 | this.count-- 68 | }) 69 | } 70 | } 71 | } 72 | } 73 | } 74 | 75 | export default App as Pretty as Typed 76 | -------------------------------------------------------------------------------- /packages/core/transpiler/view-generator/src/NodeGenerators/TextGenerator.ts: -------------------------------------------------------------------------------- 1 | import { type types as t } from "@babel/core" 2 | import { type TextParticle } from "@dlightjs/reactivity-parser" 3 | import BaseGenerator from "../HelperGenerators/BaseGenerator" 4 | 5 | export default class TextGenerator extends BaseGenerator { 6 | run() { 7 | const { content } = this.viewParticle as TextParticle 8 | 9 | const dlNodeName = this.generateNodeName() 10 | 11 | this.addInitStatement( 12 | this.declareTextNode(dlNodeName, content.value, content.dependenciesNode) 13 | ) 14 | 15 | if (content.dynamic) { 16 | this.addUpdateStatements( 17 | content.dependencyIndexArr, 18 | this.updateTextNode(dlNodeName, content.value, content.dependenciesNode) 19 | ) 20 | } 21 | 22 | return dlNodeName 23 | } 24 | 25 | /** 26 | * @View 27 | * ${dlNodeName} = createTextNode(${value}, ${deps}) 28 | */ 29 | private declareTextNode( 30 | dlNodeName: string, 31 | value: t.Expression, 32 | dependenciesNode: t.Expression 33 | ): t.Statement { 34 | return this.t.expressionStatement( 35 | this.t.assignmentExpression( 36 | "=", 37 | this.t.identifier(dlNodeName), 38 | this.t.callExpression( 39 | this.t.identifier(this.importMap.createTextNode), 40 | [value, dependenciesNode] 41 | ) 42 | ) 43 | ) 44 | } 45 | 46 | /** 47 | * @View 48 | * ${dlNodeName} && updateText(${dlNodeName}, () => ${value}, ${deps}) 49 | */ 50 | private updateTextNode( 51 | dlNodeName: string, 52 | value: t.Expression, 53 | dependenciesNode: t.Expression 54 | ): t.Statement { 55 | return this.t.expressionStatement( 56 | this.t.logicalExpression( 57 | "&&", 58 | this.t.identifier(dlNodeName), 59 | this.t.callExpression(this.t.identifier(this.importMap.updateText), [ 60 | this.t.identifier(dlNodeName), 61 | this.t.arrowFunctionExpression([], value), 62 | dependenciesNode, 63 | ]) 64 | ) 65 | ) 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /.all-contributorsrc: -------------------------------------------------------------------------------- 1 | { 2 | "files": [ 3 | "README.md" 4 | ], 5 | "imageSize": 100, 6 | "commit": false, 7 | "commitType": "docs", 8 | "commitConvention": "angular", 9 | "contributors": [ 10 | { 11 | "login": "IanDxSSXX", 12 | "name": "Duan Yihan", 13 | "avatar_url": "https://avatars.githubusercontent.com/u/69476139?v=4", 14 | "profile": "https://github.com/IanDxSSXX", 15 | "contributions": [ 16 | "infra", 17 | "test", 18 | "code" 19 | ] 20 | }, 21 | { 22 | "login": "orange04", 23 | "name": "orange04", 24 | "avatar_url": "https://avatars.githubusercontent.com/u/47129477?v=4", 25 | "profile": "https://github.com/orange04", 26 | "contributions": [ 27 | "code", 28 | "design" 29 | ] 30 | }, 31 | { 32 | "login": "Guo-lab", 33 | "name": "Guo-lab", 34 | "avatar_url": "https://avatars.githubusercontent.com/u/74242889?v=4", 35 | "profile": "https://github.com/Guo-lab", 36 | "contributions": [ 37 | "content" 38 | ] 39 | }, 40 | { 41 | "login": "scythewyvern", 42 | "name": "Gor", 43 | "avatar_url": "https://avatars.githubusercontent.com/u/98530570?v=4", 44 | "profile": "https://github.com/scythewyvern", 45 | "contributions": [ 46 | "code", 47 | "bug", 48 | "example" 49 | ] 50 | }, 51 | { 52 | "login": "ZhengHeber", 53 | "name": "Haibo Zheng", 54 | "avatar_url": "https://avatars.githubusercontent.com/u/77974962?v=4", 55 | "profile": "https://github.com/ZhengHeber", 56 | "contributions": [ 57 | "bug", 58 | "content" 59 | ] 60 | }, 61 | { 62 | "login": "canadaduane", 63 | "name": "Duane Johnson", 64 | "avatar_url": "https://avatars.githubusercontent.com/u/129?v=4", 65 | "profile": "https://www.relm.us", 66 | "contributions": [ 67 | "code", 68 | "ideas", 69 | "doc", 70 | "maintenance" 71 | ] 72 | } 73 | ], 74 | "contributorsPerLine": 7, 75 | "skipCi": true, 76 | "repoType": "github", 77 | "repoHost": "https://github.com", 78 | "projectName": "dlight", 79 | "projectOwner": "dlight-js" 80 | } 81 | -------------------------------------------------------------------------------- /packages/core/transpiler/view-generator/src/HelperGenerators/LifecycleGenerator.ts: -------------------------------------------------------------------------------- 1 | import { type types as t } from "@babel/core" 2 | import BaseGenerator from "./BaseGenerator" 3 | 4 | export default class LifecycleGenerator extends BaseGenerator { 5 | static lifecycle = [ 6 | "willMount", 7 | "didMount", 8 | "willUnmount", 9 | "didUnmount", 10 | ] as const 11 | 12 | /** 13 | * @View 14 | * ${dlNodeName} && ${value}(${dlNodeName}, changed) 15 | */ 16 | addOnUpdate(dlNodeName: string, value: t.Expression): t.Statement { 17 | return this.t.expressionStatement( 18 | this.t.logicalExpression( 19 | "&&", 20 | this.t.identifier(dlNodeName), 21 | this.t.callExpression(value, [ 22 | this.t.identifier(dlNodeName), 23 | ...this.updateParams.slice(1), 24 | ]) 25 | ) 26 | ) 27 | } 28 | 29 | /** 30 | * @View 31 | * willMount: 32 | * - ${value}(${dlNodeName}) 33 | * didMount/willUnmount/didUnmount: 34 | * - View.addDidMount(${dlNodeName}, ${value}) 35 | */ 36 | addLifecycle( 37 | dlNodeName: string, 38 | key: (typeof LifecycleGenerator.lifecycle)[number], 39 | value: t.Expression 40 | ): t.Statement { 41 | if (key === "willMount") { 42 | return this.addWillMount(dlNodeName, value) 43 | } 44 | return this.addOtherLifecycle(dlNodeName, value, key) 45 | } 46 | 47 | /** 48 | * @View 49 | * ${value}(${dlNodeName}) 50 | */ 51 | addWillMount(dlNodeName: string, value: t.Expression): t.ExpressionStatement { 52 | return this.t.expressionStatement( 53 | this.t.callExpression(value, [this.t.identifier(dlNodeName)]) 54 | ) 55 | } 56 | 57 | /** 58 | * @View 59 | * View.addDidMount(${dlNodeName}, ${value}) 60 | */ 61 | addOtherLifecycle( 62 | dlNodeName: string, 63 | value: t.Expression, 64 | type: "didMount" | "willUnmount" | "didUnmount" 65 | ): t.ExpressionStatement { 66 | return this.t.expressionStatement( 67 | this.t.callExpression( 68 | this.t.memberExpression( 69 | this.t.identifier("View"), 70 | this.t.identifier(`add${type[0].toUpperCase()}${type.slice(1)}`) 71 | ), 72 | [this.t.identifier(dlNodeName), value] 73 | ) 74 | ) 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /packages/core/transpiler/view-parser/src/types.ts: -------------------------------------------------------------------------------- 1 | import type Babel from "@babel/core" 2 | import { type types as t } from "@babel/core" 3 | 4 | export interface ViewProp { 5 | value: t.Expression 6 | viewPropMap: Record 7 | } 8 | 9 | export interface TextUnit { 10 | type: "text" 11 | content: t.Expression 12 | } 13 | 14 | export interface HTMLUnit { 15 | type: "html" 16 | tag: t.Expression 17 | props: Record 18 | children: ViewUnit[] 19 | } 20 | 21 | export interface CompUnit { 22 | type: "comp" 23 | tag: t.Expression 24 | props: Record 25 | children: ViewUnit[] 26 | } 27 | 28 | export interface ForUnit { 29 | type: "for" 30 | item: t.LVal 31 | array: t.Expression 32 | key: t.Expression 33 | children: ViewUnit[] 34 | } 35 | 36 | export interface IfBranch { 37 | condition: t.Expression 38 | children: ViewUnit[] 39 | } 40 | 41 | export interface IfUnit { 42 | type: "if" 43 | branches: IfBranch[] 44 | } 45 | 46 | export interface SwitchBranch { 47 | case: t.Expression 48 | children: ViewUnit[] 49 | break: boolean 50 | } 51 | 52 | export interface TryUnit { 53 | type: "try" 54 | children: ViewUnit[] 55 | exception: t.Identifier | t.ArrayPattern | t.ObjectPattern | null 56 | catchChildren: ViewUnit[] 57 | } 58 | 59 | export interface SwitchUnit { 60 | type: "switch" 61 | discriminant: t.Expression 62 | branches: SwitchBranch[] 63 | } 64 | 65 | export interface EnvUnit { 66 | type: "env" 67 | props: Record 68 | children: ViewUnit[] 69 | } 70 | 71 | export interface ExpUnit { 72 | type: "exp" 73 | content: ViewProp 74 | props: Record 75 | } 76 | 77 | export interface SnippetUnit { 78 | type: "snippet" 79 | tag: string 80 | props: Record 81 | children: ViewUnit[] 82 | } 83 | 84 | export type ViewUnit = 85 | | TextUnit 86 | | HTMLUnit 87 | | CompUnit 88 | | IfUnit 89 | | ForUnit 90 | | EnvUnit 91 | | ExpUnit 92 | | SnippetUnit 93 | | SwitchUnit 94 | | TryUnit 95 | 96 | export interface ViewParserConfig { 97 | babelApi: typeof Babel 98 | snippetNames: string[] 99 | htmlTags: string[] 100 | } 101 | -------------------------------------------------------------------------------- /apps/dev/src/sandbox.ts: -------------------------------------------------------------------------------- 1 | const Content = Symbol("_$dlContent") 2 | 3 | // A couple of HTML tags to play around with; use `any` type for now 4 | const div: any = () => {} 5 | const button: any = () => {} 6 | 7 | function component( 8 | _cls: T 9 | ): ComponentFunction> { 10 | return (() => {}) as any 11 | } 12 | 13 | // Example usage: 14 | const Button = component( 15 | class { 16 | [Content]!: string 17 | declare forward: "class" | "onClick" 18 | } 19 | ) 20 | 21 | Button("test") 22 | .class("bright") 23 | .onClick(() => console.log("ok")) 24 | 25 | const NavLink = component( 26 | class { 27 | [Content]!: { label: string; href: string } 28 | 29 | // Declare a special value called 'forward' that is erased by typescript, 30 | // but whose type the transpiler records and uses to permit forwarding specific 31 | // properties. 32 | declare forward: "class" | "onClick" 33 | 34 | // Optional: add a getter for [Content] 35 | get content() { 36 | return this[Content] 37 | } 38 | 39 | body() { 40 | button(this.content.label) 41 | .onClick(() => { 42 | window.location.href = this.content.href 43 | }) 44 | .forwardProps() 45 | } 46 | } 47 | ) 48 | 49 | export const Nav = component( 50 | class { 51 | // Use the `static` qualifier to define static variables 52 | static elements: HTMLElement[] = [] 53 | 54 | body() { 55 | div(() => { 56 | NavLink({ label: "Home", href: "/" }) 57 | div("About") 58 | div("Sign In") 59 | }).class("flex gap-2") 60 | } 61 | } 62 | ) 63 | 64 | type Chainable = { 65 | [K in T]: (arg: any) => Chainable 66 | } 67 | 68 | // Utility type to extract the content type from a class 69 | type ExtractContent = T extends { [Content]: infer C } ? C : never 70 | 71 | // Utility type to extract the forward keys 72 | type ExtractForward = T extends { forward: infer F extends string } 73 | ? F 74 | : never 75 | 76 | // Defines the function signature: with or without arguments based on `[Content]` 77 | type ComponentFunction = 78 | ExtractContent extends never 79 | ? () => Chainable> 80 | : (content: ExtractContent) => Chainable> 81 | -------------------------------------------------------------------------------- /packages/tools/error-handler/src/index.ts: -------------------------------------------------------------------------------- 1 | type DLightErrMap = Record 2 | type ErrorMethod = { 3 | [K in keyof T as `${G}${K & number}`]: (...args: string[]) => any 4 | } 5 | 6 | /** 7 | * @brief Create error handler by given error space and error maps 8 | * e.g. 9 | * const errHandler = createErrorHandler("DLight", { 10 | * 1: "Cannot find node type: $0, throw" 11 | * }, { 12 | * 1: "This is an error: $0" 13 | * }, { 14 | * 1: "It's a warning" 15 | * }) 16 | * errHandler.throw1("div") // -> throw new Error(":D - DLight[throw1]: Cannot find node type: div, throw") 17 | * errHandler.error1("div") // -> console.error(":D - DLight[error1]: This is an error: div") 18 | * errHandler.warn1() // -> console.warn(":D - DLight[warn1]: It's a warning") 19 | * @param errorSpace 20 | * @param throwMap 21 | * @param errorMap 22 | * @param warningMap 23 | * @returns Error handler 24 | */ 25 | export function createErrorHandler< 26 | A extends DLightErrMap, 27 | B extends DLightErrMap, 28 | C extends DLightErrMap, 29 | >( 30 | errorSpace: string, 31 | throwMap: A = {} as any, 32 | errorMap: B = {} as any, 33 | warningMap: C = {} as any 34 | ) { 35 | function handleError( 36 | map: DLightErrMap, 37 | type: string, 38 | func: (msg: string) => any 39 | ) { 40 | return Object.fromEntries( 41 | Object.entries(map).map(([code, msg]) => [ 42 | `${type}${code}`, 43 | (...args: string[]) => { 44 | args.forEach((arg, i) => { 45 | msg = msg.replace(`$${i}`, arg) 46 | }) 47 | return func(`:D - ${errorSpace}[${type}${code}]: ${msg}`) 48 | }, 49 | ]) 50 | ) 51 | } 52 | const methods: ErrorMethod & 53 | ErrorMethod & 54 | ErrorMethod = { 55 | ...handleError(throwMap, "throw", msg => { 56 | throw new Error(msg) 57 | }), 58 | ...handleError(errorMap, "error", console.error), 59 | ...handleError(warningMap, "warn", console.warn), 60 | } as any 61 | 62 | function notDescribed(type: string) { 63 | return () => `:D ${errorSpace}: ${type} not described` 64 | } 65 | 66 | return { 67 | ...methods, 68 | throwUnknown: notDescribed("throw"), 69 | errorUnknown: notDescribed("error"), 70 | warnUnknown: notDescribed("warn"), 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /packages/core/transpiler/view-generator/src/HelperGenerators/ElementGenerator.ts: -------------------------------------------------------------------------------- 1 | import { type types as t } from "@babel/core" 2 | import PropViewGenerator from "./PropViewGenerator" 3 | 4 | export default class ElementGenerator extends PropViewGenerator { 5 | /** 6 | * @View 7 | * el: 8 | * View.addDidMount(${dlNodeName}, () => ( 9 | * typeof ${value} === "function" ? ${value}($nodeEl) : ${value} = $nodeEl 10 | * )) 11 | * not el: 12 | * typeof ${value} === "function" ? ${value}($nodeEl) : ${value} = $nodeEl 13 | * @param el true: dlNodeName._$el, false: dlNodeName 14 | */ 15 | initElement( 16 | dlNodeName: string, 17 | value: t.Expression, 18 | el = false 19 | ): t.Statement { 20 | const elNode = el 21 | ? this.t.memberExpression( 22 | this.t.identifier(dlNodeName), 23 | this.t.identifier("_$el") 24 | ) 25 | : this.t.identifier(dlNodeName) 26 | let elementNode 27 | if (this.isOnlyMemberExpression(value)) { 28 | elementNode = this.t.conditionalExpression( 29 | this.t.binaryExpression( 30 | "===", 31 | this.t.unaryExpression("typeof", value, true), 32 | this.t.stringLiteral("function") 33 | ), 34 | this.t.callExpression(value, [elNode]), 35 | this.t.assignmentExpression("=", value as t.LVal, elNode) 36 | ) 37 | } else { 38 | elementNode = this.t.callExpression(value, [elNode]) 39 | } 40 | 41 | return el 42 | ? this.t.expressionStatement( 43 | this.t.callExpression( 44 | this.t.memberExpression( 45 | this.t.identifier("View"), 46 | this.t.identifier("addDidMount") 47 | ), 48 | [ 49 | this.t.identifier(dlNodeName), 50 | this.t.arrowFunctionExpression([], elementNode), 51 | ] 52 | ) 53 | ) 54 | : this.t.expressionStatement(elementNode) 55 | } 56 | 57 | // --- Utils 58 | private isOnlyMemberExpression(value: t.Expression): boolean { 59 | if (!this.t.isMemberExpression(value)) return false 60 | while (value.property) { 61 | if (this.t.isMemberExpression(value.property)) { 62 | value = value.property 63 | continue 64 | } else if (this.t.isIdentifier(value.property)) break 65 | else return false 66 | } 67 | return true 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /packages/tools/bun-plugin-dlight/README.md: -------------------------------------------------------------------------------- 1 | # bun-plugin-dlight 2 | 3 | 🚀 **Bun plugin for DLight transpilation** 4 | 5 | `bun-plugin-dlight` is a **Bun.build plugin** that enables seamless transpilation of DLight views and models in your Bun-powered web app. It allows you to compile `.view.js` and `.view.ts` files effortlessly, integrating DLight components into your project without extra configuration. 6 | 7 | See [bun-static-server](../../../apps/bun-static-server/) as a reference app. 8 | 9 | ## Features 10 | 11 | - 🏗 **Automatic DLight transpilation** – No need for manual builds. 12 | - ⚡ **Optimized for Bun.build** – Works directly with Bun's lightning-fast bundler. 13 | - 🔧 **Supports JavaScript & TypeScript** – Transpile `.view.js` and `.view.ts` files. 14 | - 📦 **Lightweight & fast** – Minimal overhead, maximum performance. 15 | - 🛠 **Easy integration** – Just plug it into your Bun project. 16 | 17 | ## Wait, What is DLight? 18 | 19 | DLight is a DX-first reactive UI rendering library for the web. See https://dlight.dev 20 | 21 | ## Installation 22 | 23 | Install the package using Bun: 24 | 25 | ```sh 26 | bun add bun-plugin-dlight 27 | ``` 28 | 29 | ## Usage 30 | 31 | Integrate `bun-plugin-dlight` in your Bun build process: 32 | 33 | ```ts 34 | import { dlightPlugin } from "bun-plugin-dlight"; 35 | 36 | const result = await Bun.build({ 37 | outdir: "./dist", 38 | entrypoints: ["./src/index.ts"], 39 | publicPath: "/build/", 40 | throw: true, 41 | plugins: [ 42 | dlightPlugin(), 43 | // Add other plugins here, e.g., tailwindPlugin() 44 | ], 45 | }); 46 | ``` 47 | 48 | ### Configuration 49 | 50 | The plugin works out of the box, but you can pass options for custom behavior: 51 | 52 | ```ts 53 | dlightPlugin({ 54 | filter: /\.(view|model)\.[tj]s$/, // Customize file matching pattern 55 | options: {}, // DLight preset options 56 | }); 57 | ``` 58 | 59 | ## How It Works 60 | 61 | 1. Detects and processes `.view.js` and `.view.ts` files. 62 | 2. Transpiles them using the DLight framework, via Babel. 63 | 3. Integrates seamlessly with Bun.build, ensuring efficient builds. 64 | 65 | ## Why Use This Plugin? 66 | 67 | - Simplifies DLight integration with Bun. 68 | - For some projects, external dependencies like vite or astro might not be needed. 69 | - Provides a smooth developer experience with Bun’s modern tooling. 70 | 71 | ## Contributing 72 | 73 | Contributions are welcome! Feel free to open issues or submit PRs. 74 | 75 | ## License 76 | 77 | MIT License © Duane Johnson 78 | -------------------------------------------------------------------------------- /apps/ToDoMVC/src/base.css: -------------------------------------------------------------------------------- 1 | hr { 2 | margin: 20px 0; 3 | border: 0; 4 | border-top: 1px dashed #c5c5c5; 5 | border-bottom: 1px dashed #f7f7f7; 6 | } 7 | 8 | .learn a { 9 | font-weight: normal; 10 | text-decoration: none; 11 | color: #b83f45; 12 | } 13 | 14 | .learn a:hover { 15 | text-decoration: underline; 16 | color: #787e7e; 17 | } 18 | 19 | .learn h3, 20 | .learn h4, 21 | .learn h5 { 22 | margin: 10px 0; 23 | font-weight: 500; 24 | line-height: 1.2; 25 | color: #000; 26 | } 27 | 28 | .learn h3 { 29 | font-size: 24px; 30 | } 31 | 32 | .learn h4 { 33 | font-size: 18px; 34 | } 35 | 36 | .learn h5 { 37 | margin-bottom: 0; 38 | font-size: 14px; 39 | } 40 | 41 | .learn ul { 42 | padding: 0; 43 | margin: 0 0 30px 25px; 44 | } 45 | 46 | .learn li { 47 | line-height: 20px; 48 | } 49 | 50 | .learn p { 51 | font-size: 15px; 52 | font-weight: 300; 53 | line-height: 1.3; 54 | margin-top: 0; 55 | margin-bottom: 0; 56 | } 57 | 58 | #issue-count { 59 | display: none; 60 | } 61 | 62 | .quote { 63 | border: none; 64 | margin: 20px 0 60px 0; 65 | } 66 | 67 | .quote p { 68 | font-style: italic; 69 | } 70 | 71 | .quote p:before { 72 | content: '“'; 73 | font-size: 50px; 74 | opacity: .15; 75 | position: absolute; 76 | top: -20px; 77 | left: 3px; 78 | } 79 | 80 | .quote p:after { 81 | content: '”'; 82 | font-size: 50px; 83 | opacity: .15; 84 | position: absolute; 85 | bottom: -42px; 86 | right: 3px; 87 | } 88 | 89 | .quote footer { 90 | position: absolute; 91 | bottom: -40px; 92 | right: 0; 93 | } 94 | 95 | .quote footer img { 96 | border-radius: 3px; 97 | } 98 | 99 | .quote footer a { 100 | margin-left: 5px; 101 | vertical-align: middle; 102 | } 103 | 104 | .speech-bubble { 105 | position: relative; 106 | padding: 10px; 107 | background: rgba(0, 0, 0, .04); 108 | border-radius: 5px; 109 | } 110 | 111 | .speech-bubble:after { 112 | content: ''; 113 | position: absolute; 114 | top: 100%; 115 | right: 30px; 116 | border: 13px solid transparent; 117 | border-top-color: rgba(0, 0, 0, .04); 118 | } 119 | 120 | .learn-bar > .learn { 121 | position: absolute; 122 | width: 272px; 123 | top: 8px; 124 | left: -300px; 125 | padding: 10px; 126 | border-radius: 5px; 127 | background-color: rgba(255, 255, 255, .6); 128 | transition-property: left; 129 | transition-duration: 500ms; 130 | } 131 | 132 | @media (min-width: 899px) { 133 | .learn-bar { 134 | width: auto; 135 | padding-left: 300px; 136 | } 137 | 138 | .learn-bar > .learn { 139 | left: 8px; 140 | } 141 | } -------------------------------------------------------------------------------- /packages/core/dlight/src/MutableNode/MutableNode.js: -------------------------------------------------------------------------------- 1 | import { DLNode } from "../DLNode" 2 | import { DLStore } from "../store" 3 | 4 | export class MutableNode extends DLNode { 5 | /** 6 | * @brief Mutable node is a node that this._$nodes can be changed, things need to pay attention: 7 | * 1. The environment of the new nodes should be the same as the old nodes 8 | * 2. The new nodes should be added to the parentEl 9 | * 3. The old nodes should be removed from the parentEl 10 | * @param type 11 | */ 12 | constructor(type) { 13 | super(type) 14 | // ---- Save the current environment nodes, must be a new reference 15 | if ( 16 | DLStore.global.DLEnvStore && 17 | DLStore.global.DLEnvStore.currentEnvNodes.length > 0 18 | ) { 19 | this.savedEnvNodes = [...DLStore.global.DLEnvStore.currentEnvNodes] 20 | } 21 | } 22 | 23 | /** 24 | * @brief Initialize the new nodes, add parentEl to all nodes 25 | * @param nodes 26 | */ 27 | initNewNodes(nodes) { 28 | // ---- Add parentEl to all nodes 29 | DLNode.addParentEl(nodes, this._$parentEl) 30 | } 31 | 32 | /** 33 | * @brief Generate new nodes in the saved environment 34 | * @param newNodesFunc 35 | * @returns 36 | */ 37 | geneNewNodesInEnv(newNodesFunc) { 38 | if (!this.savedEnvNodes) { 39 | // ---- No saved environment, just generate new nodes 40 | const newNodes = newNodesFunc() 41 | // ---- Only for IfNode's same condition return 42 | // ---- Initialize the new nodes 43 | this.initNewNodes(newNodes) 44 | return newNodes 45 | } 46 | // ---- Save the current environment nodes 47 | const currentEnvNodes = DLStore.global.DLEnvStore.currentEnvNodes 48 | // ---- Replace the saved environment nodes 49 | DLStore.global.DLEnvStore.replaceEnvNodes(this.savedEnvNodes) 50 | const newNodes = newNodesFunc() 51 | // ---- Retrieve the current environment nodes 52 | DLStore.global.DLEnvStore.replaceEnvNodes(currentEnvNodes) 53 | // ---- Only for IfNode's same condition return 54 | // ---- Initialize the new nodes 55 | this.initNewNodes(newNodes) 56 | return newNodes 57 | } 58 | 59 | initUnmountStore() { 60 | DLStore.global.WillUnmountStore.push([]) 61 | DLStore.global.DidUnmountStore.push([]) 62 | } 63 | 64 | /** 65 | * @brief Remove nodes from parentEl and run willUnmount and didUnmount 66 | * @param nodes 67 | * @param removeEl Only remove outermost element 68 | */ 69 | removeNodes(nodes) { 70 | DLNode.loopShallowEls(nodes, node => { 71 | this._$parentEl.removeChild(node) 72 | }) 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /packages/core/dlight/src/MutableNode/CondNode.js: -------------------------------------------------------------------------------- 1 | import { DLNodeType } from "../DLNode" 2 | import { FlatNode } from "./FlatNode" 3 | 4 | export class CondNode extends FlatNode { 5 | /** 6 | * @brief Constructor, If type, accept a function that returns a list of nodes 7 | * @param caseFunc 8 | */ 9 | constructor(depNum, condFunc) { 10 | super(DLNodeType.Cond) 11 | this.depNum = depNum 12 | this.cond = -1 13 | this.condFunc = condFunc 14 | this.initUnmountStore() 15 | this._$nodes = this.condFunc(this) 16 | this.setUnmountFuncs() 17 | 18 | // ---- Add to the global UnmountStore 19 | CondNode.addWillUnmount(this, this.runWillUnmount.bind(this)) 20 | CondNode.addDidUnmount(this, this.runDidUnmount.bind(this)) 21 | } 22 | 23 | /** 24 | * @brief Update the nodes in the environment 25 | */ 26 | updateCond(key) { 27 | // ---- Need to save prev unmount funcs because we can't put removeNodes before geneNewNodesInEnv 28 | // The reason is that if it didn't change, we don't need to unmount or remove the nodes 29 | const prevFuncs = [this.willUnmountFuncs, this.didUnmountFuncs] 30 | const newNodes = this.geneNewNodesInEnv(() => this.condFunc(this)) 31 | 32 | // ---- If the new nodes are the same as the old nodes, we only need to update children 33 | if (this.didntChange) { 34 | ;[this.willUnmountFuncs, this.didUnmountFuncs] = prevFuncs 35 | this.didntChange = false 36 | this.updateFunc?.(this.depNum, key) 37 | return 38 | } 39 | // ---- Remove old nodes 40 | const newFuncs = [this.willUnmountFuncs, this.didUnmountFuncs] 41 | ;[this.willUnmountFuncs, this.didUnmountFuncs] = prevFuncs 42 | this._$nodes && this._$nodes.length > 0 && this.removeNodes(this._$nodes) 43 | ;[this.willUnmountFuncs, this.didUnmountFuncs] = newFuncs 44 | 45 | if (newNodes.length === 0) { 46 | // ---- No branch has been taken 47 | this._$nodes = [] 48 | return 49 | } 50 | // ---- Add new nodes 51 | const parentEl = this._$parentEl 52 | // ---- Faster append with nextSibling rather than flowIndex 53 | const flowIndex = CondNode.getFlowIndexFromNodes(parentEl._$nodes, this) 54 | 55 | const nextSibling = parentEl.childNodes[flowIndex] 56 | CondNode.appendNodesWithSibling(newNodes, parentEl, nextSibling) 57 | CondNode.runDidMount() 58 | this._$nodes = newNodes 59 | } 60 | 61 | /** 62 | * @brief The update function of IfNode's childNodes is stored in the first child node 63 | * @param changed 64 | */ 65 | update(changed) { 66 | if (!(~this.depNum & changed)) return 67 | this.updateFunc?.(changed) 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /packages/core/transpiler/view-parser/src/test/SwitchUnit.test.ts: -------------------------------------------------------------------------------- 1 | import { expect, describe, it } from "vitest" 2 | import { parse, parseCode, parseView } from "./mock" 3 | import { type SwitchUnit } from "../types" 4 | import { types as t } from "@babel/core" 5 | 6 | describe("SwitchUnit", () => { 7 | // ---- Type 8 | it("should identify an switch statement as an SwitchUnit", () => { 9 | const viewUnits = parse("switch(true) {}") 10 | expect(viewUnits.length).toBe(1) 11 | expect(viewUnits[0].type).toBe("switch") 12 | }) 13 | 14 | it("should identify an switch statement with cases", () => { 15 | const viewUnits = parse("switch(true) { case 1: break; }") 16 | expect(viewUnits.length).toBe(1) 17 | expect(viewUnits[0].type).toBe("switch") 18 | expect((viewUnits[0] as SwitchUnit).branches.length).toBe(1) 19 | }) 20 | 21 | it("should identify an switch statement with cases and default", () => { 22 | const viewUnits = parse("switch(true) { case 1: break; default: break; }") 23 | expect(viewUnits.length).toBe(1) 24 | expect(viewUnits[0].type).toBe("switch") 25 | expect((viewUnits[0] as SwitchUnit).branches.length).toBe(2) 26 | }) 27 | 28 | // ---- Discriminant 29 | it("should correctly parse the discriminant of an switch statement", () => { 30 | const statement = parseCode("switch(this.flag) {}") 31 | const viewUnits = parseView(statement) 32 | const originalExpression = (statement.body[0] as t.SwitchStatement) 33 | .discriminant 34 | const switchUnit = viewUnits[0] as SwitchUnit 35 | expect(switchUnit.discriminant).toBe(originalExpression) 36 | }) 37 | 38 | // ---- Branches 39 | it("should correctly parse the count of branches", () => { 40 | const viewUnits = parse( 41 | "switch(true) { case 1: break; case 2: break; case 3: break; }" 42 | ) 43 | const switchUnit = viewUnits[0] as SwitchUnit 44 | expect(switchUnit.branches.length).toBe(3) 45 | }) 46 | 47 | it("should correctly parse the case of a branch", () => { 48 | const statement = parseCode("switch(true) { case this.flag: break; }") 49 | const viewUnits = parseView(statement) 50 | const originalExpression = ( 51 | (statement.body[0] as t.SwitchStatement).cases[0] as t.SwitchCase 52 | ).test 53 | const switchUnit = viewUnits[0] as SwitchUnit 54 | expect(switchUnit.branches[0].case).toBe(originalExpression) 55 | }) 56 | 57 | // ---- Break 58 | it("should correctly parse the break of a branch", () => { 59 | const viewUnits = parse("switch(true) { case 1: break; }") 60 | 61 | const switchUnit = viewUnits[0] as SwitchUnit 62 | expect(switchUnit.branches[0].break).toBe(true) 63 | expect(switchUnit.branches[0].children.length).toBe(0) 64 | }) 65 | }) 66 | -------------------------------------------------------------------------------- /packages/core/transpiler/reactivity-parser/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # @dlightjs/reactivity-parser 2 | 3 | ## 1.0.0 4 | 5 | - Released 6 | 7 | ## 1.0.0-next.5 8 | 9 | ### Patch Changes 10 | 11 | - feat: change prop->props and attr->attrs 12 | 13 | ## 1.0.0-next.4 14 | 15 | ### Patch Changes 16 | 17 | - fix: no computed dep node member chaining 18 | 19 | ## 1.0.0-next.3 20 | 21 | ### Patch Changes 22 | 23 | - fix: no unknown binary expression dep node and add optional member 24 | 25 | ## 1.0.0-next.2 26 | 27 | ### Patch Changes 28 | 29 | - Updated dependencies 30 | - @dlightjs/view-parser@1.0.0-next.2 31 | 32 | ## 1.0.0-next.1 33 | 34 | ### Patch Changes 35 | 36 | - feat: fixes and feature requests 37 | - Updated dependencies 38 | - @dlightjs/view-parser@1.0.0-next.1 39 | 40 | ## 1.0.0-beta.6 41 | 42 | ### Patch Changes 43 | 44 | - feat: add alternative children syntax 45 | - Updated dependencies 46 | - @dlightjs/view-parser@1.0.0-beta.8 47 | 48 | ## 1.0.0-beta.5 49 | 50 | ### Patch Changes 51 | 52 | - fix: try-catch model 53 | - Updated dependencies 54 | - @dlightjs/view-parser@1.0.0-beta.7 55 | 56 | ## 1.0.0-beta.4 57 | 58 | ### Patch Changes 59 | 60 | - feat: add try-catch 61 | - Updated dependencies 62 | - @dlightjs/view-parser@1.0.0-beta.6 63 | 64 | ## 1.0.0-beta.3 65 | 66 | ### Patch Changes 67 | 68 | - update: transpiler 69 | - Updated dependencies 70 | - @dlightjs/view-parser@1.0.0-beta.5 71 | 72 | ## 1.0.0-beta.2 73 | 74 | ### Patch Changes 75 | 76 | - fix: "value" return function in identifierDepMap of reactivity-parser 77 | 78 | ## 1.0.0-beta.1 79 | 80 | ### Patch Changes 81 | 82 | - 70ccc1b: feat: add in depth reactivity and reduce rerender 83 | 84 | ## 1.0.0-beta.9 85 | 86 | ### Patch Changes 87 | 88 | - feat: new deep reactivity 89 | 90 | ## 1.0.0-alpha.8 91 | 92 | ### Patch Changes 93 | 94 | - feat: avoid using innerHTML, createElement dynamically instead 95 | 96 | ## 1.0.0-alpha.7 97 | 98 | ### Patch Changes 99 | 100 | - feat: add lifecycle and onUpdate 101 | - Updated dependencies 102 | - @dlightjs/view-parser@1.0.0-alpha.4 103 | 104 | ## 1.0.0-alpha.6 105 | 106 | ### Patch Changes 107 | 108 | - feat: add willParseTemplate support 109 | - Updated dependencies 110 | - @dlightjs/view-parser@1.0.0-alpha.3 111 | 112 | ## 1.0.0-alpha.5 113 | 114 | ### Patch Changes 115 | 116 | - Updated dependencies 117 | - @dlightjs/view-parser@1.0.0-alpha.2 118 | 119 | ## 1.0.0-alpha.4 120 | 121 | ### Patch Changes 122 | 123 | - fix: aria attributes considered as properties 124 | 125 | ## 1.0.0-alpha.3 126 | 127 | ### Patch Changes 128 | 129 | - fix: subview + dynamic textnode + textnode insert 130 | 131 | ## 1.0.0-alpha.2 132 | 133 | ### Patch Changes 134 | 135 | - feat: add switch statement support 136 | - Updated dependencies 137 | - @dlightjs/view-parser@1.0.0-alpha.1 138 | 139 | ## 1.0.0-alpha.1 140 | 141 | ### Patch Changes 142 | 143 | - fix: if and exp transpiler bug 144 | -------------------------------------------------------------------------------- /packages/core/dlight/src/MutableNode/ExpNode.js: -------------------------------------------------------------------------------- 1 | import { DLNodeType } from "../DLNode" 2 | import { FlatNode } from "./FlatNode" 3 | import { DLStore, cached } from "../store" 4 | 5 | export class ExpNode extends FlatNode { 6 | /** 7 | * @brief Constructor, Exp type, accept a function that returns a list of nodes 8 | * @param nodesFunc 9 | */ 10 | constructor(value, deps) { 11 | super(DLNodeType.Exp) 12 | this.initUnmountStore() 13 | this._$nodes = ExpNode.formatNodes(value) 14 | this.setUnmountFuncs() 15 | this.deps = this.parseDeps(deps) 16 | // ---- Add to the global UnmountStore 17 | ExpNode.addWillUnmount(this, this.runWillUnmount.bind(this)) 18 | ExpNode.addDidUnmount(this, this.runDidUnmount.bind(this)) 19 | } 20 | 21 | parseDeps(deps) { 22 | return deps.map(dep => { 23 | // ---- CompNode 24 | if (dep?.prototype?._$init) return dep.toString() 25 | // ---- SnippetNode 26 | if (dep?.propViewFunc) return dep.propViewFunc.toString() 27 | return dep 28 | }) 29 | } 30 | 31 | cache(deps) { 32 | if (!deps || !deps.length) return false 33 | deps = this.parseDeps(deps) 34 | if (cached(deps, this.deps)) return true 35 | this.deps = deps 36 | return false 37 | } 38 | /** 39 | * @brief Generate new nodes and replace the old nodes 40 | */ 41 | update(valueFunc, deps) { 42 | if (this.cache(deps)) return 43 | this.removeNodes(this._$nodes) 44 | const newNodes = this.geneNewNodesInEnv(() => 45 | ExpNode.formatNodes(valueFunc()) 46 | ) 47 | if (newNodes.length === 0) { 48 | this._$nodes = [] 49 | return 50 | } 51 | 52 | // ---- Add new nodes 53 | const parentEl = this._$parentEl 54 | const flowIndex = ExpNode.getFlowIndexFromNodes(parentEl._$nodes, this) 55 | const nextSibling = parentEl.childNodes[flowIndex] 56 | ExpNode.appendNodesWithSibling(newNodes, parentEl, nextSibling) 57 | ExpNode.runDidMount() 58 | 59 | this._$nodes = newNodes 60 | } 61 | 62 | /** 63 | * @brief Format the nodes 64 | * @param nodes 65 | * @returns New nodes 66 | */ 67 | static formatNodes(nodes) { 68 | if (!Array.isArray(nodes)) nodes = [nodes] 69 | return ( 70 | nodes 71 | // ---- Flatten the nodes 72 | .flat(1) 73 | // ---- Filter out empty nodes 74 | .filter( 75 | node => 76 | node !== undefined && node !== null && typeof node !== "boolean" 77 | ) 78 | .map(node => { 79 | // ---- If the node is a string, number or bigint, convert it to a text node 80 | if ( 81 | typeof node === "string" || 82 | typeof node === "number" || 83 | typeof node === "bigint" 84 | ) { 85 | return DLStore.document.createTextNode(`${node}`) 86 | } 87 | // ---- If the node has PropView, call it to get the view 88 | if ("propViewFunc" in node) return node.build() 89 | return node 90 | }) 91 | // ---- Flatten the nodes again 92 | .flat(1) 93 | ) 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /packages/core/dlight/src/types/compTag.d.ts: -------------------------------------------------------------------------------- 1 | import { type DLightHTMLAttributes } from "./htmlTag" 2 | 3 | // a very magical solution 4 | // when vscode parse ts, if it is type A = B>, it will show the detailed type, 5 | // but if type A = B> & xxx, it will only show alias (here is A) 6 | // because I don't want to expose the detailed type, so type A = B> & Useless 7 | // but if type Useless = { useless: never } will cause this type to have an additional property userless 8 | // so just don't add key! 9 | type Useless = { [key in ""]: never } 10 | 11 | export type DLightObject = { 12 | [K in keyof T]-?: undefined extends T[K] 13 | ? (value?: T[K]) => DLightObject> 14 | : (value: T[K]) => DLightObject> 15 | } 16 | interface CustomNodeProps { 17 | willMount: (node: any) => void 18 | didMount: (node: any) => void 19 | willUnmount: (node: any) => void 20 | didUnmount: (node: any) => void 21 | didUpdate: (node: any, key: string, prevValue: any, currValue: any) => void 22 | ref: (node: any) => void 23 | elements: HTMLElement[] | ((holder: HTMLElement[]) => void) | undefined 24 | forwardProps: true | undefined 25 | } 26 | 27 | export type ContentProp = T & { _$idContent: true } 28 | 29 | export type RemoveOptional = { 30 | [K in keyof T]-?: T[K] 31 | } 32 | 33 | type IsAny = { _$isAny: true } extends T ? true : false 34 | 35 | export type ContentKeyName = { 36 | [K in keyof T]: IsAny extends true 37 | ? never 38 | : // eslint-disable-next-line @typescript-eslint/no-unused-vars 39 | T[K] extends ContentProp 40 | ? K 41 | : never 42 | }[keyof T] 43 | 44 | export type CheckContent = RemoveOptional[ContentKeyName< 45 | RemoveOptional 46 | >] 47 | 48 | type CustomClassTag = ContentKeyName> extends undefined 49 | ? () => DLightObject 50 | : undefined extends O[ContentKeyName>] 51 | ? CheckContent extends ContentProp 52 | ? ( 53 | content?: U extends unknown ? any : unknown 54 | ) => DLightObject>>> 55 | : never 56 | : CheckContent extends ContentProp 57 | ? ( 58 | content: U extends unknown ? any : unknown 59 | ) => DLightObject>>> 60 | : never 61 | 62 | type CustomSnippetTag = T extends { content: infer U } 63 | ? (content: U) => DLightObject> 64 | : T extends { content?: infer U } 65 | ? (content?: U) => DLightObject> 66 | : () => DLightObject 67 | 68 | type CustomTagType = CustomClassTag< 69 | T & 70 | CustomNodeProps & 71 | (keyof G extends never 72 | ? object 73 | : DLightHTMLAttributes), 74 | T 75 | > & 76 | Useless 77 | export type Typed = CustomTagType & Useless 78 | export type SnippetTyped = CustomSnippetTag & Useless 79 | 80 | export type Pretty = any 81 | 82 | // ---- reverse 83 | export type UnTyped = T extends Typed ? U : never 84 | -------------------------------------------------------------------------------- /packages/core/transpiler/reactivity-parser/src/test/MutableTagParticle.test.ts: -------------------------------------------------------------------------------- 1 | import { expect, describe, it } from "vitest" 2 | import { parse } from "./mock" 3 | import { 4 | type HTMLParticle, 5 | type ExpParticle, 6 | type CompParticle, 7 | } from "../types" 8 | import { type types as t } from "@babel/core" 9 | 10 | describe("MutableTagParticle", () => { 11 | // ---- HTML 12 | it("should parse an HTMLUnit with dynamic tag as an HTMLParticle", () => { 13 | const viewParticles = parse("tag(this.div)()") 14 | expect(viewParticles.length).toBe(1) 15 | expect(viewParticles[0].type).toBe("html") 16 | }) 17 | 18 | it("should parse an HTMLUnit with no children as an HTMLParticle", () => { 19 | const viewParticles = parse("div()") 20 | expect(viewParticles.length).toBe(1) 21 | expect(viewParticles[0].type).toBe("html") 22 | }) 23 | 24 | it("should parse an HTMLUnit with non-static-html children as an HTMLParticle", () => { 25 | const viewParticles = parse("div(); { Comp(); tag(this.div)(); }") 26 | expect(viewParticles.length).toBe(1) 27 | expect(viewParticles[0].type).toBe("html") 28 | }) 29 | 30 | it("should not parse an HTMLUnit with potential TemplateUnit as an HTMLParticle", () => { 31 | const viewParticles = parse("div(); { div() }") 32 | expect(viewParticles.length).toBe(1) 33 | expect(viewParticles[0].type).not.toBe("html") 34 | }) 35 | 36 | it("should parse an HTMLUnit with dynamic tag with dependencies as an ExpParticle", () => { 37 | const viewParticles = parse("tag(this.flag)()") 38 | expect(viewParticles.length).toBe(1) 39 | expect(viewParticles[0].type).toBe("exp") 40 | const content = (viewParticles[0] as ExpParticle).content 41 | 42 | expect((content.value as t.StringLiteral).value).toBe( 43 | Object.keys(content.viewPropMap!)[0] 44 | ) 45 | const htmlParticle = content.viewPropMap![ 46 | Object.keys(content.viewPropMap!)[0] 47 | ][0] as HTMLParticle 48 | expect(htmlParticle.type).toBe("html") 49 | }) 50 | 51 | // ---- Comp 52 | it("should parse a CompUnit with dynamic tag as an HTMLParticle", () => { 53 | const viewParticles = parse("Comp()") 54 | expect(viewParticles.length).toBe(1) 55 | expect(viewParticles[0].type).toBe("comp") 56 | }) 57 | 58 | it("should parse a CompUnit with dynamic tag with dependencies as an ExpParticle", () => { 59 | const viewParticles = parse("comp(CompList[this.flag])()") 60 | expect(viewParticles.length).toBe(1) 61 | expect(viewParticles[0].type).toBe("exp") 62 | const content = (viewParticles[0] as ExpParticle).content 63 | 64 | expect((content.value as t.StringLiteral).value).toBe( 65 | Object.keys(content.viewPropMap!)[0] 66 | ) 67 | const compParticle = content.viewPropMap![ 68 | Object.keys(content.viewPropMap!)[0] 69 | ][0] as CompParticle 70 | 71 | expect(compParticle.type).toBe("comp") 72 | }) 73 | 74 | // ---- Snippet 75 | it("should parse a SnippetUnit as an HTMLParticle", () => { 76 | const viewParticles = parse("this.MySnippet()") 77 | expect(viewParticles.length).toBe(1) 78 | expect(viewParticles[0].type).toBe("snippet") 79 | }) 80 | }) 81 | -------------------------------------------------------------------------------- /packages/core/transpiler/reactivity-parser/src/test/Template.test.ts: -------------------------------------------------------------------------------- 1 | import { expect, describe, it } from "vitest" 2 | import { parse } from "./mock" 3 | import { type TemplateParticle } from "../types" 4 | import { types as t } from "@babel/core" 5 | 6 | describe("TemplateUnit", () => { 7 | it("should not parse a single HTMLUnit to a TemplateUnit", () => { 8 | const viewParticles = parse("div()") 9 | expect(viewParticles.length).toBe(1) 10 | expect(viewParticles[0].type).not.toBe("template") 11 | }) 12 | 13 | it("should parse a nested HTMLUnit to a TemplateUnit", () => { 14 | const viewParticles = parse("div(); {div()}") 15 | expect(viewParticles.length).toBe(1) 16 | expect(viewParticles[0].type).toBe("template") 17 | }) 18 | 19 | it("should correctly parse a nested HTMLUnit's structure into a template", () => { 20 | const viewParticles = parse("div(); {div()}") 21 | const template = (viewParticles[0] as any).template 22 | expect(t.isStringLiteral(template.tag, { value: "div" })).toBe(true) 23 | expect(template.children).toHaveLength(1) 24 | expect(t.isStringLiteral(template.children[0].tag, { value: "div" })).toBe( 25 | true 26 | ) 27 | }) 28 | 29 | it("should correctly parse the path of TemplateParticle's dynamic props in root element", () => { 30 | const viewParticles = parse('div().class(this.flag); {div("ok")}') 31 | 32 | const dynamicProps = (viewParticles[0] as TemplateParticle).props 33 | expect(dynamicProps).toHaveLength(1) 34 | const prop = dynamicProps[0] 35 | // ---- Path will be [] because it's the root element 36 | expect(prop.path).toHaveLength(0) 37 | }) 38 | 39 | it("should correctly parse the path of TemplateParticle's dynamic props in nested element", () => { 40 | const viewParticles = parse("div(); {div().class(this.flag)}") 41 | 42 | const dynamicProps = (viewParticles[0] as TemplateParticle).props 43 | expect(dynamicProps).toHaveLength(1) 44 | const prop = dynamicProps[0] 45 | // ---- Path will be [0] because it's the first child of the root element 46 | expect(prop.path).toHaveLength(1) 47 | expect(prop.path[0]).toBe(0) 48 | }) 49 | 50 | it("should correctly parse the path of TemplateParticle's dynamic props with mutable particles ahead", () => { 51 | const viewParticles = parse("div(); { Comp(); div().class(this.flag) }") 52 | 53 | const dynamicProps = (viewParticles[0] as TemplateParticle).props 54 | expect(dynamicProps).toHaveLength(1) 55 | const prop = dynamicProps[0] 56 | // ---- Path will be [0] because it's the first child of the root element 57 | expect(prop.path).toHaveLength(1) 58 | expect(prop.path[0]).toBe(0) 59 | }) 60 | 61 | it("should correctly parse the path of TemplateParticle's mutableParticles", () => { 62 | const viewParticles = parse("div(); {div(); Comp(); div();}") 63 | const mutableParticles = (viewParticles[0] as TemplateParticle) 64 | .mutableParticles 65 | expect(mutableParticles).toHaveLength(1) 66 | 67 | const mutableParticle = mutableParticles[0] 68 | expect(mutableParticle.path).toHaveLength(1) 69 | expect(mutableParticle.path[0]).toBe(1) 70 | }) 71 | }) 72 | -------------------------------------------------------------------------------- /packages/core/transpiler/reactivity-parser/src/test/OtherParticle.test.ts: -------------------------------------------------------------------------------- 1 | import { expect, describe, it } from "vitest" 2 | import { parse } from "./mock" 3 | import { type ForParticle } from "../../dist" 4 | import { type HTMLParticle } from "../types" 5 | 6 | describe("OtherParticle", () => { 7 | it("should parse a TextUnit as a TextParticle", () => { 8 | const viewParticles = parse('"Hello World"') 9 | expect(viewParticles.length).toBe(1) 10 | expect(viewParticles[0].type).toBe("text") 11 | }) 12 | 13 | it("should parse an IfUnit as an IfParticle", () => { 14 | const viewParticles = parse("if(this.flag) { div() }") 15 | expect(viewParticles.length).toBe(1) 16 | expect(viewParticles[0].type).toBe("if") 17 | }) 18 | 19 | it("should parse an IfUnit as an SwitchParticle", () => { 20 | const viewParticles = parse("switch(this.flag) { }") 21 | expect(viewParticles.length).toBe(1) 22 | expect(viewParticles[0].type).toBe("switch") 23 | }) 24 | 25 | it("should parse a ForUnit as a ForParticle", () => { 26 | const viewParticles = parse("for(const item of this.items) { div() }") 27 | expect(viewParticles.length).toBe(1) 28 | expect(viewParticles[0].type).toBe("for") 29 | }) 30 | 31 | it("should correctly parse ForUnit's item dependencies from array", () => { 32 | console.log("this") 33 | const viewParticles = parse( 34 | "for(const item of this.array[this.count]) { div(item) }" 35 | ) 36 | expect(viewParticles.length).toBe(1) 37 | expect(viewParticles[0].type).toBe("for") 38 | 39 | const divParticle = (viewParticles[0] as ForParticle) 40 | .children[0] as HTMLParticle 41 | const divDependency = divParticle.props?.textContent?.dependencyIndexArr 42 | expect(divDependency).toContain(0) 43 | expect(divDependency).toContain(1) 44 | expect(divDependency).toContain(3) 45 | }) 46 | 47 | it("should correctly parse ForUnit's deconstruct item dependencies from array", () => { 48 | const viewParticles = parse( 49 | "for(const { idx, item } of this.array[this.count]) { div(item) }" 50 | ) 51 | expect(viewParticles.length).toBe(1) 52 | expect(viewParticles[0].type).toBe("for") 53 | 54 | const divParticle = (viewParticles[0] as ForParticle) 55 | .children[0] as HTMLParticle 56 | const divDependency = divParticle.props?.textContent?.dependencyIndexArr 57 | expect(divDependency).toContain(0) 58 | expect(divDependency).toContain(1) 59 | expect(divDependency).toContain(3) 60 | }) 61 | 62 | it("should parse a EnvUnit as a EnvParticle", () => { 63 | const viewParticles = parse("env().count(2); { div() }") 64 | expect(viewParticles.length).toBe(1) 65 | expect(viewParticles[0].type).toBe("env") 66 | }) 67 | 68 | it("should parse a ExpUnit as a ExpParticle", () => { 69 | const viewParticles = parse("this.flag") 70 | expect(viewParticles.length).toBe(1) 71 | expect(viewParticles[0].type).toBe("exp") 72 | }) 73 | 74 | it("should parse a TryUnit as a TryParticle", () => { 75 | const viewParticles = parse("try { div() } catch(e) { div() }") 76 | expect(viewParticles.length).toBe(1) 77 | expect(viewParticles[0].type).toBe("try") 78 | }) 79 | }) 80 | -------------------------------------------------------------------------------- /apps/bun-static-server/.gitignore: -------------------------------------------------------------------------------- 1 | # Based on https://raw.githubusercontent.com/github/gitignore/main/Node.gitignore 2 | 3 | # Logs 4 | 5 | logs 6 | _.log 7 | npm-debug.log_ 8 | yarn-debug.log* 9 | yarn-error.log* 10 | lerna-debug.log* 11 | .pnpm-debug.log* 12 | 13 | # Caches 14 | 15 | .cache 16 | 17 | # Diagnostic reports (https://nodejs.org/api/report.html) 18 | 19 | report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json 20 | 21 | # Runtime data 22 | 23 | pids 24 | _.pid 25 | _.seed 26 | *.pid.lock 27 | 28 | # Directory for instrumented libs generated by jscoverage/JSCover 29 | 30 | lib-cov 31 | 32 | # Coverage directory used by tools like istanbul 33 | 34 | coverage 35 | *.lcov 36 | 37 | # nyc test coverage 38 | 39 | .nyc_output 40 | 41 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 42 | 43 | .grunt 44 | 45 | # Bower dependency directory (https://bower.io/) 46 | 47 | bower_components 48 | 49 | # node-waf configuration 50 | 51 | .lock-wscript 52 | 53 | # Compiled binary addons (https://nodejs.org/api/addons.html) 54 | 55 | build/Release 56 | 57 | # Dependency directories 58 | 59 | node_modules/ 60 | jspm_packages/ 61 | 62 | # Snowpack dependency directory (https://snowpack.dev/) 63 | 64 | web_modules/ 65 | 66 | # TypeScript cache 67 | 68 | *.tsbuildinfo 69 | 70 | # Optional npm cache directory 71 | 72 | .npm 73 | 74 | # Optional eslint cache 75 | 76 | .eslintcache 77 | 78 | # Optional stylelint cache 79 | 80 | .stylelintcache 81 | 82 | # Microbundle cache 83 | 84 | .rpt2_cache/ 85 | .rts2_cache_cjs/ 86 | .rts2_cache_es/ 87 | .rts2_cache_umd/ 88 | 89 | # Optional REPL history 90 | 91 | .node_repl_history 92 | 93 | # Output of 'npm pack' 94 | 95 | *.tgz 96 | 97 | # Yarn Integrity file 98 | 99 | .yarn-integrity 100 | 101 | # dotenv environment variable files 102 | 103 | .env 104 | .env.development.local 105 | .env.test.local 106 | .env.production.local 107 | .env.local 108 | 109 | # parcel-bundler cache (https://parceljs.org/) 110 | 111 | .parcel-cache 112 | 113 | # Next.js build output 114 | 115 | .next 116 | out 117 | 118 | # Nuxt.js build / generate output 119 | 120 | .nuxt 121 | dist 122 | 123 | # Gatsby files 124 | 125 | # Comment in the public line in if your project uses Gatsby and not Next.js 126 | 127 | # https://nextjs.org/blog/next-9-1#public-directory-support 128 | 129 | # public 130 | 131 | # vuepress build output 132 | 133 | .vuepress/dist 134 | 135 | # vuepress v2.x temp and cache directory 136 | 137 | .temp 138 | 139 | # Docusaurus cache and generated files 140 | 141 | .docusaurus 142 | 143 | # Serverless directories 144 | 145 | .serverless/ 146 | 147 | # FuseBox cache 148 | 149 | .fusebox/ 150 | 151 | # DynamoDB Local files 152 | 153 | .dynamodb/ 154 | 155 | # TernJS port file 156 | 157 | .tern-port 158 | 159 | # Stores VSCode versions used for testing VSCode extensions 160 | 161 | .vscode-test 162 | 163 | # yarn v2 164 | 165 | .yarn/cache 166 | .yarn/unplugged 167 | .yarn/build-state.yml 168 | .yarn/install-state.gz 169 | .pnp.* 170 | 171 | # IntelliJ based IDEs 172 | .idea 173 | 174 | # Finder (MacOS) folder config 175 | .DS_Store 176 | -------------------------------------------------------------------------------- /packages/core/transpiler/view-parser/src/test/IfUnit.test.ts: -------------------------------------------------------------------------------- 1 | import { expect, describe, it } from "vitest" 2 | import { parse, parseCode, parseView } from "./mock" 3 | import { type HTMLUnit, type IfUnit } from "../types" 4 | import { types as t } from "@babel/core" 5 | 6 | describe("IfUnit", () => { 7 | // ---- Type 8 | it("should identify an if statement as an IfUnit", () => { 9 | const viewUnits = parse("if(true) {}") 10 | expect(viewUnits.length).toBe(1) 11 | expect(viewUnits[0].type).toBe("if") 12 | }) 13 | 14 | it("should identify an if statement with else clause as an IfUnit", () => { 15 | const viewUnits = parse("if(true) {} else {}") 16 | expect(viewUnits.length).toBe(1) 17 | expect(viewUnits[0].type).toBe("if") 18 | }) 19 | 20 | it("should identify an if statement with else if clause as an IfUnit", () => { 21 | const viewUnits = parse("if(true) {} else if(false) {}") 22 | expect(viewUnits.length).toBe(1) 23 | expect(viewUnits[0].type).toBe("if") 24 | }) 25 | 26 | it("should identify an if statement with else if clause and else clause as an IfUnit", () => { 27 | const viewUnits = parse("if(true) {} else if(false) {} else {}") 28 | expect(viewUnits.length).toBe(1) 29 | expect(viewUnits[0].type).toBe("if") 30 | }) 31 | 32 | // ---- Branch.Condition 33 | it("should correctly parse the count of conditions for multiple else and else if clauses", () => { 34 | const viewUnits = parse( 35 | "if(true) {} else if(false) {} else if(false) {} else {}" 36 | ) 37 | const ifUnit = viewUnits[0] as IfUnit 38 | expect(ifUnit.branches.length).toBe(4) 39 | }) 40 | 41 | it("should correctly parse the condition of an if statement", () => { 42 | const statement = parseCode("if(this.flag) {}") 43 | const viewUnits = parseView(statement) 44 | const originalExpression = (statement.body[0] as t.IfStatement).test 45 | const ifUnit = viewUnits[0] as IfUnit 46 | expect(ifUnit.branches[0].condition).toBe(originalExpression) 47 | }) 48 | 49 | it("should correctly parse the condition of an else if statement", () => { 50 | const statement = parseCode("if(true) {} else if(this.flag) {}") 51 | const viewUnits = parseView(statement) 52 | const originalExpression = ( 53 | (statement.body[0] as t.IfStatement).alternate as t.IfStatement 54 | ).test 55 | const ifUnit = viewUnits[0] as IfUnit 56 | expect(ifUnit.branches[1].condition).toBe(originalExpression) 57 | }) 58 | 59 | it("should correctly parse the condition of an else statement as plain true", () => { 60 | const statement = parseCode("if(true) {} else {}") 61 | const viewUnits = parseView(statement) 62 | const ifUnit = viewUnits[0] as IfUnit 63 | expect(t.isBooleanLiteral(ifUnit.branches[1].condition)).toBeTruthy() 64 | }) 65 | 66 | // ---- Branch.Children 67 | it("should correctly parse the children of an if statement", () => { 68 | const viewUnits = parse("if(true) {div()}") 69 | const ifUnit = viewUnits[0] as IfUnit 70 | expect(ifUnit.branches[0].children.length).toBe(1) 71 | expect(ifUnit.branches[0].children[0].type).toBe("html") 72 | 73 | const tag = (ifUnit.branches[0].children[0] as HTMLUnit).tag 74 | expect(t.isStringLiteral(tag, { value: "div" })).toBeTruthy() 75 | }) 76 | }) 77 | -------------------------------------------------------------------------------- /packages/core/dlight/src/EnvNode.js: -------------------------------------------------------------------------------- 1 | import { DLNode, DLNodeType } from "./DLNode" 2 | import { DLStore, cached } from "./store" 3 | 4 | export class EnvStoreClass { 5 | constructor() { 6 | this.envs = {} 7 | this.currentEnvNodes = [] 8 | } 9 | 10 | /** 11 | * @brief Add a node to the current env and merge envs 12 | * @param node - The node to add 13 | */ 14 | addEnvNode(node) { 15 | this.currentEnvNodes.push(node) 16 | this.mergeEnvs() 17 | } 18 | 19 | /** 20 | * @brief Replace the current env with the given nodes and merge envs 21 | * @param nodes - The nodes to replace the current environment with 22 | */ 23 | replaceEnvNodes(nodes) { 24 | this.currentEnvNodes = nodes 25 | this.mergeEnvs() 26 | } 27 | 28 | /** 29 | * @brief Remove the last node from the current env and merge envs 30 | */ 31 | removeEnvNode() { 32 | this.currentEnvNodes.pop() 33 | this.mergeEnvs() 34 | } 35 | 36 | /** 37 | * @brief Merge all the envs in currentEnvNodes, inner envs override outer envs 38 | */ 39 | mergeEnvs() { 40 | this.envs = {} 41 | this.currentEnvNodes.forEach(envNode => { 42 | Object.entries(envNode.envs).forEach(([key, value]) => { 43 | this.envs[key] = [value, envNode] 44 | }) 45 | }) 46 | } 47 | } 48 | 49 | export class EnvNode extends DLNode { 50 | constructor(envs, depsArr) { 51 | super(DLNodeType.Env) 52 | // Declare a global variable to store the environment variables 53 | if (!("DLEnvStore" in DLStore.global)) 54 | DLStore.global.DLEnvStore = new EnvStoreClass() 55 | 56 | this.envs = envs 57 | this.depsArr = depsArr 58 | this.updateNodes = new Set() 59 | 60 | DLStore.global.DLEnvStore.addEnvNode(this) 61 | } 62 | 63 | cached(deps, name) { 64 | if (!deps || !deps.length) return false 65 | if (cached(deps, this.depsArr[name])) return true 66 | this.depsArr[name] = deps 67 | return false 68 | } 69 | 70 | /** 71 | * @brief Update a specific env, and update all the comp nodes that depend on this env 72 | * @param name - The name of the environment variable to update 73 | * @param value - The new value of the environment variable 74 | */ 75 | updateEnv(name, valueFunc, deps) { 76 | if (this.cached(deps, name)) return 77 | const value = valueFunc() 78 | this.envs[name] = value 79 | if (DLStore.global.DLEnvStore.currentEnvNodes.includes(this)) { 80 | DLStore.global.DLEnvStore.mergeEnvs() 81 | } 82 | this.updateNodes.forEach(node => { 83 | node._$updateEnv(name, value, this) 84 | }) 85 | } 86 | 87 | /** 88 | * @brief Add a node to this.updateNodes, delete the node from this.updateNodes when it unmounts 89 | * @param node - The node to add 90 | */ 91 | addNode(node) { 92 | this.updateNodes.add(node) 93 | DLNode.addWillUnmount( 94 | node, 95 | this.updateNodes.delete.bind(this.updateNodes, node) 96 | ) 97 | } 98 | 99 | /** 100 | * @brief Set this._$nodes, and exit the current env 101 | * @param nodes - The nodes to set 102 | */ 103 | initNodes(nodes) { 104 | this._$nodes = nodes 105 | DLStore.global.DLEnvStore.removeEnvNode() 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /packages/core/transpiler/view-generator/src/NodeGenerators/ExpGenerator.ts: -------------------------------------------------------------------------------- 1 | import { type types as t } from "@babel/core" 2 | import { type ExpParticle } from "@dlightjs/reactivity-parser" 3 | import ElementGenerator from "../HelperGenerators/ElementGenerator" 4 | import { DLError } from "../error" 5 | 6 | export default class ExpGenerator extends ElementGenerator { 7 | run() { 8 | let { content, props } = this.viewParticle as ExpParticle 9 | content = this.alterPropView(content)! 10 | props = this.alterPropViews(props) 11 | 12 | const dlNodeName = this.generateNodeName() 13 | 14 | this.addInitStatement( 15 | this.declareExpNode(dlNodeName, content.value, content.dependenciesNode) 16 | ) 17 | 18 | if (content.dynamic) { 19 | this.addUpdateStatements( 20 | content.dependencyIndexArr, 21 | this.updateExpNode(dlNodeName, content.value, content.dependenciesNode) 22 | ) 23 | } 24 | 25 | if (props) { 26 | Object.entries(props).forEach(([key, { value }]) => { 27 | if ( 28 | ExpGenerator.lifecycle.includes( 29 | key as (typeof ExpGenerator.lifecycle)[number] 30 | ) 31 | ) { 32 | return this.addInitStatement( 33 | this.addLifecycle( 34 | dlNodeName, 35 | key as (typeof ExpGenerator.lifecycle)[number], 36 | value 37 | ) 38 | ) 39 | } 40 | if (key === "ref") { 41 | return this.addInitStatement(this.initElement(dlNodeName, value)) 42 | } 43 | if (key === "elements") { 44 | return this.addInitStatement( 45 | this.initElement(dlNodeName, value, true) 46 | ) 47 | } 48 | if (key === "didUpdate") { 49 | return this.addUpdateStatements( 50 | content.dependencyIndexArr, 51 | this.addOnUpdate(dlNodeName, value) 52 | ) 53 | } 54 | DLError.warn1(key) 55 | }) 56 | } 57 | 58 | return dlNodeName 59 | } 60 | 61 | /** 62 | * @View 63 | * ${dlNodeName} = new ExpNode(${value}, dependenciesNode) 64 | */ 65 | private declareExpNode( 66 | dlNodeName: string, 67 | value: t.Expression, 68 | dependenciesNode: t.ArrayExpression 69 | ): t.Statement { 70 | return this.t.expressionStatement( 71 | this.t.assignmentExpression( 72 | "=", 73 | this.t.identifier(dlNodeName), 74 | this.t.newExpression(this.t.identifier(this.importMap.ExpNode), [ 75 | value, 76 | dependenciesNode ?? this.t.nullLiteral(), 77 | ]) 78 | ) 79 | ) 80 | } 81 | 82 | /** 83 | * @View 84 | * ${dlNodeName}.update(() => value, dependenciesNode) 85 | */ 86 | private updateExpNode( 87 | dlNodeName: string, 88 | value: t.Expression, 89 | dependenciesNode: t.ArrayExpression 90 | ): t.Statement { 91 | return this.optionalExpression( 92 | dlNodeName, 93 | this.t.callExpression( 94 | this.t.memberExpression( 95 | this.t.identifier(dlNodeName), 96 | this.t.identifier("update") 97 | ), 98 | [ 99 | this.t.arrowFunctionExpression([], value), 100 | dependenciesNode ?? this.t.nullLiteral(), 101 | ] 102 | ) 103 | ) 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /apps/dev/src/benchmark/benchmark-snippets.view.js: -------------------------------------------------------------------------------- 1 | import { View, Snippet, render } from "@dlightjs/dlight" 2 | import { buildData } from "./data" 3 | 4 | @View 5 | class Main { 6 | rows = [] 7 | selectIdx = -1 8 | addRows() { 9 | this.rows = buildData(1000) 10 | } 11 | 12 | swapRows() { 13 | if (this.rows.length > 998) { 14 | const tmp = this.rows[1] 15 | this.rows[1] = this.rows[998] 16 | this.rows[998] = tmp 17 | this.rows = [...this.rows] 18 | } 19 | } 20 | 21 | clearRows() { 22 | this.rows = [] 23 | } 24 | 25 | selectRow(idx) { 26 | this.selectIdx = idx 27 | } 28 | 29 | deleteRow(id) { 30 | this.rows = [...this.rows.filter(row => row.id !== id)] 31 | } 32 | 33 | addBig() { 34 | this.rows = buildData(10000) 35 | } 36 | 37 | append() { 38 | this.rows = [...this.rows, ...buildData(1000)] 39 | } 40 | 41 | update() { 42 | for (let i = 0; i < this.rows.length; i += 10) { 43 | this.rows[i] = { ...this.rows[i], label: this.rows[i].label + " !!!" } 44 | } 45 | this.rows = [...this.rows] 46 | } 47 | 48 | @Snippet 49 | Button({ content, id, onClick }) { 50 | div().class("col-sm-6 smallpad") 51 | { 52 | button(content).onClick(onClick).id(id).class("btn btn-primary btn-block") 53 | } 54 | } 55 | 56 | @Snippet 57 | Jumbotron() { 58 | div().class("jumbotron") 59 | { 60 | div().class("row") 61 | { 62 | div().class("col-sm-6") 63 | { 64 | h1("DLight.js Snippets (keyed)") 65 | } 66 | div().class("col-md-6") 67 | { 68 | div().class("row") 69 | { 70 | this.Button("Create 1,000 rows").onClick(this.addRows).id("run") 71 | this.Button("Create 10,000 rows").onClick(this.addBig).id("runlots") 72 | this.Button("Append 1,000 rows").onClick(this.append).id("add") 73 | this.Button("Update every 10th rows") 74 | .onClick(this.update) 75 | .id("update") 76 | this.Button("Clear").onClick(this.clearRows).id("clear") 77 | this.Button("Swap Rows").onClick(this.swapRows).id("swaprows") 78 | } 79 | } 80 | } 81 | } 82 | } 83 | 84 | @Snippet 85 | Row({ id, label }) { 86 | tr().class(this.selectIdx === id ? "danger" : "") 87 | { 88 | td(id).class("col-md-1") 89 | td().class("col-md-4") 90 | { 91 | a(label).onClick(this.selectRow.bind(this, id)) 92 | } 93 | td().class("col-md-1") 94 | { 95 | a().onClick(this.deleteRow.bind(this, id)) 96 | { 97 | span().class("glyphicon glyphicon-remove").ariaHidden("true") 98 | } 99 | } 100 | td().class("col-md-6") 101 | } 102 | } 103 | 104 | @Snippet 105 | Table() { 106 | div() 107 | { 108 | table().class("table table-hover table-striped test-data") 109 | { 110 | tbody() 111 | { 112 | for (const { id, label } of this.rows) { 113 | key: id 114 | this.Row().id(id).label(label) 115 | } 116 | } 117 | } 118 | span().class("preloadicon glyphicon glyphicon-remove").ariaHidden("true") 119 | } 120 | } 121 | 122 | Body() { 123 | div().class("container") 124 | { 125 | this.Jumbotron() 126 | this.Table() 127 | } 128 | } 129 | } 130 | 131 | render("main", Main) 132 | -------------------------------------------------------------------------------- /packages/core/transpiler/view-generator/src/MainViewGenerator.ts: -------------------------------------------------------------------------------- 1 | import { type types as t } from "@babel/core" 2 | import { type ViewParticle } from "@dlightjs/reactivity-parser" 3 | import ViewGenerator from "./ViewGenerator" 4 | 5 | export default class MainViewGenerator extends ViewGenerator { 6 | /** 7 | * @brief Generate the main view, i.e., View() { ... } 8 | * @param viewParticles 9 | * @returns [viewBody, classProperties, templateIdx] 10 | */ 11 | generate( 12 | viewParticles: ViewParticle[] 13 | ): [t.BlockStatement, t.ClassProperty[], number] { 14 | const allClassProperties: t.ClassProperty[] = [] 15 | const allInitStatements: t.Statement[] = [] 16 | const allUpdateStatements: Record = {} 17 | const topLevelNodes: string[] = [] 18 | 19 | viewParticles.forEach(viewParticle => { 20 | const [initStatements, updateStatements, classProperties, nodeName] = 21 | this.generateChild(viewParticle) 22 | allInitStatements.push(...initStatements) 23 | Object.entries(updateStatements).forEach(([depNum, statements]) => { 24 | if (!allUpdateStatements[Number(depNum)]) { 25 | allUpdateStatements[Number(depNum)] = [] 26 | } 27 | allUpdateStatements[Number(depNum)].push(...statements) 28 | }) 29 | allClassProperties.push(...classProperties) 30 | topLevelNodes.push(nodeName) 31 | }) 32 | 33 | const viewBody = this.t.blockStatement([ 34 | ...this.declareNodes(), 35 | ...this.geneUpdate(allUpdateStatements), 36 | ...allInitStatements, 37 | this.geneReturn(topLevelNodes), 38 | ]) 39 | 40 | return [viewBody, allClassProperties, this.templateIdx] 41 | } 42 | 43 | /** 44 | * @View 45 | * this._$update = ($changed) => { 46 | * if ($changed & 1) { 47 | * ... 48 | * } 49 | * ... 50 | * } 51 | */ 52 | private geneUpdate( 53 | updateStatements: Record 54 | ): t.Statement[] { 55 | if (Object.keys(updateStatements).length === 0) return [] 56 | return [ 57 | this.t.expressionStatement( 58 | this.t.assignmentExpression( 59 | "=", 60 | this.t.memberExpression( 61 | this.t.thisExpression(), 62 | this.t.identifier("_$update"), 63 | false 64 | ), 65 | this.t.arrowFunctionExpression( 66 | this.updateParams, 67 | this.t.blockStatement([ 68 | ...Object.entries(updateStatements) 69 | .filter(([depNum]) => depNum !== "0") 70 | .map(([depNum, statements]) => { 71 | return this.t.ifStatement( 72 | this.t.binaryExpression( 73 | "&", 74 | this.t.identifier("$changed"), 75 | this.t.numericLiteral(Number(depNum)) 76 | ), 77 | this.t.blockStatement(statements) 78 | ) 79 | }), 80 | ...(updateStatements[0] ?? []), 81 | ]) 82 | ) 83 | ) 84 | ), 85 | ] 86 | } 87 | 88 | /** 89 | * @View 90 | * return [${nodeNames}] 91 | */ 92 | private geneReturn(topLevelNodes: string[]) { 93 | return this.t.returnStatement( 94 | this.t.arrayExpression( 95 | topLevelNodes.map(nodeName => this.t.identifier(nodeName)) 96 | ) 97 | ) 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /packages/core/transpiler/reactivity-parser/src/types.ts: -------------------------------------------------------------------------------- 1 | import { type types as t } from "@babel/core" 2 | import type Babel from "@babel/core" 3 | 4 | export interface DependencyValue { 5 | value: T 6 | dynamic: boolean 7 | dependencyIndexArr: number[] 8 | dependenciesNode: t.ArrayExpression 9 | } 10 | 11 | export interface DependencyProp { 12 | value: t.Expression 13 | viewPropMap: Record 14 | dynamic: boolean 15 | dependencyIndexArr: number[] 16 | dependenciesNode: t.ArrayExpression 17 | } 18 | 19 | export interface TemplateProp { 20 | tag: string 21 | key: string 22 | path: number[] 23 | value: t.Expression 24 | dynamic: boolean 25 | dependencyIndexArr: number[] 26 | dependenciesNode: t.ArrayExpression 27 | } 28 | 29 | export type MutableParticle = ViewParticle & { path: number[] } 30 | 31 | export interface TemplateParticle { 32 | type: "template" 33 | template: HTMLParticle 34 | mutableParticles: MutableParticle[] 35 | props: TemplateProp[] 36 | } 37 | 38 | export interface TextParticle { 39 | type: "text" 40 | content: DependencyValue 41 | } 42 | 43 | export interface HTMLParticle { 44 | type: "html" 45 | tag: t.Expression 46 | props: Record> 47 | children: ViewParticle[] 48 | } 49 | 50 | export interface CompParticle { 51 | type: "comp" 52 | tag: t.Expression 53 | props: Record 54 | children: ViewParticle[] 55 | } 56 | 57 | export interface ForParticle { 58 | type: "for" 59 | item: t.LVal 60 | array: DependencyValue 61 | key: t.Expression 62 | children: ViewParticle[] 63 | } 64 | 65 | export interface IfBranch { 66 | condition: DependencyValue 67 | children: ViewParticle[] 68 | } 69 | 70 | export interface IfParticle { 71 | type: "if" 72 | branches: IfBranch[] 73 | } 74 | 75 | export interface SwitchBranch { 76 | case: DependencyValue 77 | children: ViewParticle[] 78 | break: boolean 79 | } 80 | 81 | export interface SwitchParticle { 82 | type: "switch" 83 | discriminant: DependencyValue 84 | branches: SwitchBranch[] 85 | } 86 | 87 | export interface TryParticle { 88 | type: "try" 89 | children: ViewParticle[] 90 | exception: t.Identifier | t.ArrayPattern | t.ObjectPattern | null 91 | catchChildren: ViewParticle[] 92 | } 93 | 94 | export interface EnvParticle { 95 | type: "env" 96 | props: Record 97 | children: ViewParticle[] 98 | } 99 | 100 | export interface ExpParticle { 101 | type: "exp" 102 | content: DependencyProp 103 | props: Record 104 | } 105 | 106 | export interface SnippetParticle { 107 | type: "snippet" 108 | tag: string 109 | props: Record 110 | children: ViewParticle[] 111 | } 112 | 113 | export type ViewParticle = 114 | | TemplateParticle 115 | | TextParticle 116 | | HTMLParticle 117 | | CompParticle 118 | | ForParticle 119 | | IfParticle 120 | | EnvParticle 121 | | ExpParticle 122 | | SwitchParticle 123 | | SnippetParticle 124 | | TryParticle 125 | 126 | export interface ReactivityParserConfig { 127 | babelApi: typeof Babel 128 | availableProperties: string[] 129 | availableIdentifiers?: string[] 130 | dependencyMap: Record 131 | identifierDepMap?: Record 132 | dependencyParseType?: "property" | "identifier" 133 | parseTemplate?: boolean 134 | reactivityFuncNames?: string[] 135 | } 136 | -------------------------------------------------------------------------------- /packages/core/transpiler/view-parser/src/test/TextUnit.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from "vitest" 2 | import { parse, parseCode, parseView } from "./mock" 3 | import { types as t } from "@babel/core" 4 | import { type TextUnit } from "../index" 5 | 6 | describe("TextUnit", () => { 7 | // ---- Type 8 | it("should identify a string literal as a TextUnit", () => { 9 | // ---- Add a html unit to make it a string literal instead of a directive 10 | const viewUnits = parse('div();"Hello World"') 11 | expect(viewUnits.length).toBe(2) 12 | expect(viewUnits[1].type).toBe("text") 13 | }) 14 | 15 | it("should identify a template literal as a TextUnit", () => { 16 | const viewUnits = parse("`Hello World`") 17 | expect(viewUnits.length).toBe(1) 18 | expect(viewUnits[0].type).toBe("text") 19 | }) 20 | 21 | it("should identify a directive as a TextUnit", () => { 22 | const statement = parseCode('"Hello World"') 23 | expect(statement.body.length).toBe(0) 24 | expect(statement.directives.length).toBe(1) 25 | 26 | const viewUnits = parseView(statement) 27 | expect(viewUnits.length).toBe(1) 28 | expect(viewUnits[0].type).toBe("text") 29 | }) 30 | 31 | it("should identify a tagged template literal with a string literal as its tag as two TextUnits", () => { 32 | const viewUnits = parse('"hi"`Hello World`') 33 | expect(viewUnits.length).toBe(2) 34 | 35 | expect(viewUnits[0].type).toBe("text") 36 | expect(viewUnits[1].type).toBe("text") 37 | 38 | const content1 = (viewUnits[0] as TextUnit).content 39 | expect(t.isStringLiteral(content1, { value: "hi" })).toBeTruthy() 40 | 41 | const content2 = (viewUnits[1] as TextUnit).content 42 | expect(t.isTemplateLiteral(content2)).toBeTruthy() 43 | const content2Quasis = (content2 as t.TemplateLiteral).quasis[0] 44 | expect(t.isTemplateElement(content2Quasis)).toBeTruthy() 45 | const content2Value = content2Quasis.value 46 | expect(content2Value.raw).toBe("Hello World") 47 | }) 48 | 49 | it("should identify a tagged template literal with a template literal as its tag as two TextUnits", () => { 50 | const viewUnits = parse("`hi``Hello World`") 51 | expect(viewUnits.length).toBe(2) 52 | expect(viewUnits[0].type).toBe("text") 53 | expect(viewUnits[1].type).toBe("text") 54 | 55 | const content1 = (viewUnits[0] as TextUnit).content 56 | expect(t.isTemplateLiteral(content1)).toBeTruthy() 57 | const content1Quasis = (content1 as t.TemplateLiteral).quasis[0] 58 | expect(t.isTemplateElement(content1Quasis)).toBeTruthy() 59 | const content1Value = content1Quasis.value 60 | expect(content1Value.raw).toBe("hi") 61 | 62 | const content2 = (viewUnits[1] as TextUnit).content 63 | expect(t.isTemplateLiteral(content2)).toBeTruthy() 64 | const content2Quasis = (content2 as t.TemplateLiteral).quasis[0] 65 | expect(t.isTemplateElement(content2Quasis)).toBeTruthy() 66 | const content2Value = content2Quasis.value 67 | expect(content2Value.raw).toBe("Hello World") 68 | }) 69 | 70 | // ---- Content 71 | it("should correctly parse a string literal as its content", () => { 72 | const viewUnits = parse('"Hello World"') 73 | const content = (viewUnits[0] as TextUnit).content 74 | expect(t.isStringLiteral(content, { value: "Hello World" })).toBeTruthy() 75 | }) 76 | 77 | it("should save the original node as its content for a template literal", () => { 78 | // eslint-disable-next-line no-template-curly-in-string 79 | const statement = parseCode("`Hello World ${name}`") 80 | const viewUnits = parseView(statement) 81 | const originalNode = (statement.body[0] as t.ExpressionStatement).expression 82 | 83 | const content = (viewUnits[0] as TextUnit).content 84 | expect(content).toBe(originalNode) 85 | }) 86 | }) 87 | -------------------------------------------------------------------------------- /packages/tools/create-dlightjs/templates/dlight-vite-js/src/style.module.css: -------------------------------------------------------------------------------- 1 | .wrap { 2 | background-color: white; 3 | width: 100%; 4 | height: 100vh; 5 | } 6 | 7 | .headerWrap { 8 | padding: 20px 30px; 9 | display: flex; 10 | justify-content: space-between; 11 | } 12 | 13 | .headerLogo { 14 | width: 146px; 15 | height: 46px; 16 | } 17 | 18 | .navBtn { 19 | padding: 0 10px; 20 | text-decoration: none; 21 | color: #333333; 22 | font-family: "Gill Sans", sans-serif; 23 | } 24 | 25 | .slogan { 26 | font-size: 45px; 27 | font-weight: 600; 28 | background-image: -webkit-linear-gradient(left top, rgb(241, 192, 149), rgb(194, 225, 154)); 29 | -webkit-background-clip: text; 30 | -webkit-text-fill-color: transparent; 31 | margin: auto; 32 | margin-top: 60px; 33 | width: 50%; 34 | display: flex; 35 | flex-direction: column; 36 | align-items: center; 37 | } 38 | 39 | .slogan2 { 40 | align-self: center; 41 | background-color: #fff; 42 | background-position: 0 90%; 43 | background-repeat: repeat no-repeat; 44 | background-size: 4px 3px; 45 | border-radius: 15px 225px 255px 15px 15px 255px 225px 15px; 46 | border-style: solid; 47 | border-width: 2px; 48 | box-shadow: rgba(0, 0, 0, .2) 15px 28px 25px -18px; 49 | box-sizing: border-box; 50 | color: #41403e; 51 | cursor: pointer; 52 | display: inline-block; 53 | font-family: Neucha, sans-serif; 54 | line-height: 23px; 55 | outline: none; 56 | text-decoration: none; 57 | transition: all 235ms ease-in-out; 58 | border-bottom-left-radius: 15px 255px; 59 | border-bottom-right-radius: 225px 15px; 60 | border-top-left-radius: 255px 15px; 61 | border-top-right-radius: 15px 225px; 62 | user-select: none; 63 | -webkit-user-select: none; 64 | touch-action: manipulation; 65 | font-size: 45px; 66 | font-weight: 500; 67 | padding: 50px; 68 | display: flex; 69 | width: 50%; 70 | flex-direction: column; 71 | align-items: center; 72 | margin: auto; 73 | cursor: default; 74 | } 75 | 76 | 77 | .countWrap { 78 | display: flex; 79 | flex-direction: column; 80 | align-items: center; 81 | margin-top: 20px; 82 | } 83 | 84 | .countBtn { 85 | align-self: center; 86 | background-color: #fff; 87 | background-image: none; 88 | background-position: 0 90%; 89 | background-repeat: repeat no-repeat; 90 | background-size: 4px 3px; 91 | border-radius: 15px 225px 255px 15px 15px 255px 225px 15px; 92 | border-style: solid; 93 | border-width: 2px; 94 | box-shadow: rgba(0, 0, 0, .2) 15px 28px 25px -18px; 95 | box-sizing: border-box; 96 | color: #41403e; 97 | cursor: pointer; 98 | display: inline-block; 99 | font-family: Neucha, sans-serif; 100 | font-size: 1rem; 101 | line-height: 23px; 102 | outline: none; 103 | padding: .75rem; 104 | text-decoration: none; 105 | transition: all 235ms ease-in-out; 106 | border-bottom-left-radius: 15px 255px; 107 | border-bottom-right-radius: 225px 15px; 108 | border-top-left-radius: 255px 15px; 109 | border-top-right-radius: 15px 225px; 110 | user-select: none; 111 | -webkit-user-select: none; 112 | touch-action: manipulation; 113 | margin: 20px 114 | } 115 | 116 | .btnHover { 117 | box-shadow: rgba(0, 0, 0, .3) 2px 8px 8px -5px; 118 | transform: translate3d(0, 2px, 0); 119 | } 120 | 121 | .button-55:focus { 122 | box-shadow: rgba(0, 0, 0, .3) 2px 8px 4px -6px; 123 | } 124 | 125 | .btnWrap { 126 | flex: 1; 127 | flex-direction: row; 128 | justify-content: space-between; 129 | } 130 | 131 | .countText { 132 | width: 50px; 133 | text-align: center; 134 | } 135 | 136 | .colorD { 137 | color: rgb(194, 225, 154); 138 | } 139 | 140 | .colorL { 141 | color: rgb(241, 192, 149); 142 | } 143 | 144 | .m0 { 145 | margin: 0px; 146 | } -------------------------------------------------------------------------------- /apps/dev/src/benchmark/benchmark-comp.view.js: -------------------------------------------------------------------------------- 1 | import { View, render } from "@dlightjs/dlight" 2 | import { buildData } from "./data" 3 | 4 | @View 5 | class Button { 6 | @Content text 7 | @Prop id 8 | @Prop onClick 9 | 10 | Body() { 11 | div().class("col-sm-6 smallpad") 12 | { 13 | button(this.text) 14 | .onClick(this.onClick) 15 | .id(this.id) 16 | .class("btn btn-primary btn-block") 17 | } 18 | } 19 | } 20 | 21 | @View 22 | class Row { 23 | @Prop className 24 | @Prop id 25 | @Prop label 26 | @Prop selectRow 27 | @Prop deleteRow 28 | 29 | View() { 30 | tr().class(this.className) 31 | { 32 | td(this.id).class("col-md-1") 33 | td().class("col-md-4") 34 | { 35 | a(this.label).onClick(this.selectRow) 36 | } 37 | td().class("col-md-1") 38 | { 39 | a().onClick(this.deleteRow) 40 | { 41 | span().class("glyphicon glyphicon-remove").ariaHidden("true") 42 | } 43 | } 44 | td().class("col-md-6") 45 | } 46 | } 47 | } 48 | 49 | @View 50 | class Main { 51 | rows = [] 52 | 53 | selectIdx = -1 54 | addRows() { 55 | this.rows = buildData(1000) 56 | } 57 | 58 | swapRows() { 59 | if (this.rows.length > 998) { 60 | const tmp = this.rows[1] 61 | this.rows[1] = this.rows[998] 62 | this.rows[998] = tmp 63 | this.rows = [...this.rows] 64 | } 65 | } 66 | 67 | clearRows() { 68 | this.rows = [] 69 | } 70 | 71 | selectRow(idx) { 72 | this.selectIdx = idx 73 | } 74 | 75 | deleteRow(id) { 76 | this.rows = [...this.rows.filter(row => row.id !== id)] 77 | } 78 | 79 | addBig() { 80 | this.rows = buildData(10000) 81 | } 82 | 83 | append() { 84 | this.rows = [...this.rows, ...buildData(1000)] 85 | } 86 | 87 | update() { 88 | for (let i = 0; i < this.rows.length; i += 10) { 89 | this.rows[i] = { ...this.rows[i], label: this.rows[i].label + " !!!" } 90 | } 91 | this.rows = [...this.rows] 92 | } 93 | 94 | Body() { 95 | div().class("container") 96 | { 97 | div().class("jumbotron") 98 | { 99 | div().class("row") 100 | { 101 | div().class("col-sm-6") 102 | { 103 | h1("DLight.js Component (keyed)") 104 | } 105 | div().class("col-md-6") 106 | { 107 | div().class("row") 108 | { 109 | Button("Create 1,000 rows").onClick(this.addRows).id("run") 110 | Button("Create 10,000 rows").onClick(this.addBig).id("runlots") 111 | Button("Append 1,000 rows").onClick(this.append).id("add") 112 | Button("Update every 10th rows").onClick(this.update).id("update") 113 | Button("Clear").onClick(this.clearRows).id("clear") 114 | Button("Swap Rows").onClick(this.swapRows).id("swaprows") 115 | } 116 | } 117 | } 118 | } 119 | div() 120 | { 121 | table().class("table table-hover table-striped test-data") 122 | { 123 | tbody() 124 | { 125 | for (const { id, label } of this.rows) { 126 | key: id 127 | Row() 128 | .className(this.selectIdx === id ? "danger" : "") 129 | .id(id) 130 | .label(label) 131 | .selectRow(this.selectRow.bind(this, id)) 132 | .deleteRow(this.deleteRow.bind(this, id)) 133 | } 134 | } 135 | } 136 | span() 137 | .class("preloadicon glyphicon glyphicon-remove") 138 | .ariaHidden("true") 139 | } 140 | } 141 | } 142 | } 143 | 144 | render("main", Main) 145 | -------------------------------------------------------------------------------- /packages/tools/create-dlightjs/templates/dlight-vite-ts/src/style.module.css: -------------------------------------------------------------------------------- 1 | .wrap { 2 | background-color: white; 3 | width: 100%; 4 | height: 100vh; 5 | } 6 | 7 | .headerWrap { 8 | padding: 40px 60px; 9 | display: flex; 10 | justify-content: space-between; 11 | } 12 | 13 | .headerLogo { 14 | width: 146px; 15 | height: 46px; 16 | } 17 | 18 | .navBtn { 19 | padding: 0 10px; 20 | text-decoration: none; 21 | color: #333333; 22 | font-family: "Gill Sans", sans-serif; 23 | } 24 | 25 | .slogan { 26 | font-size: 45px; 27 | font-weight: 600; 28 | background-image: -webkit-linear-gradient(left top, rgb(241, 192, 149), rgb(194, 225, 154)); 29 | -webkit-background-clip: text; 30 | -webkit-text-fill-color: transparent; 31 | margin: auto; 32 | margin-top: 100px; 33 | width: 50%; 34 | display: flex; 35 | flex-direction: column; 36 | align-items: center; 37 | } 38 | 39 | .slogan2 { 40 | align-self: center; 41 | background-color: #fff; 42 | background-position: 0 90%; 43 | background-repeat: repeat no-repeat; 44 | background-size: 4px 3px; 45 | border-radius: 15px 225px 255px 15px 15px 255px 225px 15px; 46 | border-style: solid; 47 | border-width: 2px; 48 | box-shadow: rgba(0, 0, 0, .2) 15px 28px 25px -18px; 49 | box-sizing: border-box; 50 | color: #41403e; 51 | cursor: pointer; 52 | display: inline-block; 53 | font-family: Neucha, sans-serif; 54 | line-height: 23px; 55 | outline: none; 56 | padding: .75rem; 57 | text-decoration: none; 58 | transition: all 235ms ease-in-out; 59 | border-bottom-left-radius: 15px 255px; 60 | border-bottom-right-radius: 225px 15px; 61 | border-top-left-radius: 255px 15px; 62 | border-top-right-radius: 15px 225px; 63 | user-select: none; 64 | -webkit-user-select: none; 65 | touch-action: manipulation; 66 | font-size: 45px; 67 | font-weight: 500; 68 | padding: 50px; 69 | display: flex; 70 | width: 50%; 71 | flex-direction: column; 72 | align-items: center; 73 | margin: auto; 74 | cursor: default; 75 | } 76 | 77 | 78 | .countWrap { 79 | display: flex; 80 | flex-direction: column; 81 | align-items: center; 82 | margin-top: 30px; 83 | } 84 | 85 | .countBtn { 86 | align-self: center; 87 | background-color: #fff; 88 | background-image: none; 89 | background-position: 0 90%; 90 | background-repeat: repeat no-repeat; 91 | background-size: 4px 3px; 92 | border-radius: 15px 225px 255px 15px 15px 255px 225px 15px; 93 | border-style: solid; 94 | border-width: 2px; 95 | box-shadow: rgba(0, 0, 0, .2) 15px 28px 25px -18px; 96 | box-sizing: border-box; 97 | color: #41403e; 98 | cursor: pointer; 99 | display: inline-block; 100 | font-family: Neucha, sans-serif; 101 | font-size: 1rem; 102 | line-height: 23px; 103 | outline: none; 104 | padding: .75rem; 105 | text-decoration: none; 106 | transition: all 235ms ease-in-out; 107 | border-bottom-left-radius: 15px 255px; 108 | border-bottom-right-radius: 225px 15px; 109 | border-top-left-radius: 255px 15px; 110 | border-top-right-radius: 15px 225px; 111 | user-select: none; 112 | -webkit-user-select: none; 113 | touch-action: manipulation; 114 | margin: 20px 115 | } 116 | 117 | .btnHover { 118 | box-shadow: rgba(0, 0, 0, .3) 2px 8px 8px -5px; 119 | transform: translate3d(0, 2px, 0); 120 | } 121 | 122 | .button-55:focus { 123 | box-shadow: rgba(0, 0, 0, .3) 2px 8px 4px -6px; 124 | } 125 | 126 | .btnWrap { 127 | flex-direction: row; 128 | justify-content: space-between; 129 | } 130 | 131 | .countText { 132 | width: 50px; 133 | text-align: center; 134 | } 135 | 136 | .colorD { 137 | color: rgb(194, 225, 154); 138 | } 139 | 140 | .colorL { 141 | color: rgb(241, 192, 149); 142 | } 143 | 144 | .m0 { 145 | margin: 0px; 146 | } -------------------------------------------------------------------------------- /packages/core/transpiler/view-generator/src/NodeGenerators/TryGenerator.ts: -------------------------------------------------------------------------------- 1 | import { type types as t } from "@babel/core" 2 | import BaseGenerator from "../HelperGenerators/BaseGenerator" 3 | import { TryParticle, type ViewParticle } from "@dlightjs/reactivity-parser" 4 | 5 | export default class TryGenerator extends BaseGenerator { 6 | run() { 7 | const { children, catchChildren, exception } = this 8 | .viewParticle as TryParticle 9 | 10 | const dlNodeName = this.generateNodeName() 11 | 12 | // ---- Declare for node 13 | this.addInitStatement( 14 | this.declareTryNode(dlNodeName, children, catchChildren, exception) 15 | ) 16 | 17 | // ---- Update statements 18 | this.addUpdateStatementsWithoutDep(this.declareUpdate(dlNodeName)) 19 | 20 | return dlNodeName 21 | } 22 | 23 | /** 24 | * @View 25 | * $setUpdate($catchable(updateStatements)) 26 | * ${children} 27 | * return [...${topLevelNodes}] 28 | */ 29 | private declareTryNodeUpdate( 30 | children: ViewParticle[], 31 | addCatchable = true 32 | ): t.Statement[] { 33 | const [childStatements, topLevelNodes, updateStatements, nodeIdx] = 34 | this.generateChildren(children, false, true) 35 | 36 | const updateFunc = this.t.arrowFunctionExpression( 37 | [this.t.identifier("$changed")], 38 | this.geneUpdateBody(updateStatements) 39 | ) 40 | 41 | childStatements.unshift( 42 | ...this.declareNodes(nodeIdx), 43 | this.t.expressionStatement( 44 | this.t.callExpression( 45 | this.t.identifier("$setUpdate"), 46 | addCatchable 47 | ? [ 48 | this.t.callExpression(this.t.identifier("$catchable"), [ 49 | updateFunc, 50 | ]), 51 | ] 52 | : [updateFunc] 53 | ) 54 | ) 55 | ) 56 | childStatements.push( 57 | this.t.returnStatement( 58 | this.t.arrayExpression( 59 | topLevelNodes.map(node => this.t.identifier(node)) 60 | ) 61 | ) 62 | ) 63 | 64 | return childStatements 65 | } 66 | 67 | /** 68 | * @View 69 | * ${dlNodeName} = new TryNode(($setUpdate, $catchable) => { 70 | * ${children} 71 | * }, ($setUpdate, e) => { 72 | * ${catchChildren} 73 | * }) 74 | * }) 75 | */ 76 | private declareTryNode( 77 | dlNodeName: string, 78 | children: ViewParticle[], 79 | catchChildren: ViewParticle[], 80 | exception: TryParticle["exception"] 81 | ): t.Statement { 82 | const exceptionNodes = exception ? [exception] : [] 83 | return this.t.expressionStatement( 84 | this.t.assignmentExpression( 85 | "=", 86 | this.t.identifier(dlNodeName), 87 | this.t.newExpression(this.t.identifier(this.importMap.TryNode), [ 88 | this.t.arrowFunctionExpression( 89 | [this.t.identifier("$setUpdate"), this.t.identifier("$catchable")], 90 | this.t.blockStatement(this.declareTryNodeUpdate(children, true)) 91 | ), 92 | this.t.arrowFunctionExpression( 93 | [this.t.identifier("$setUpdate"), ...exceptionNodes], 94 | this.t.blockStatement( 95 | this.declareTryNodeUpdate(catchChildren, false) 96 | ) 97 | ), 98 | ]) 99 | ) 100 | ) 101 | } 102 | 103 | /** 104 | * @View 105 | * ${dlNodeName}?.update(changed) 106 | */ 107 | private declareUpdate(dlNodeName: string): t.Statement { 108 | return this.optionalExpression( 109 | dlNodeName, 110 | this.t.callExpression( 111 | this.t.memberExpression( 112 | this.t.identifier(dlNodeName), 113 | this.t.identifier("update") 114 | ), 115 | this.updateParams 116 | ) 117 | ) 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /packages/core/transpiler/view-generator/src/NodeGenerators/HTMLGenerator.ts: -------------------------------------------------------------------------------- 1 | import { type types as t } from "@babel/core" 2 | import { type HTMLParticle } from "@dlightjs/reactivity-parser" 3 | import HTMLPropGenerator from "../HelperGenerators/HTMLPropGenerator" 4 | 5 | export default class HTMLGenerator extends HTMLPropGenerator { 6 | run() { 7 | const { tag, props, children } = this.viewParticle as HTMLParticle 8 | 9 | const dlNodeName = this.generateNodeName() 10 | 11 | this.addInitStatement(this.declareHTMLNode(dlNodeName, tag)) 12 | 13 | // ---- Resolve props 14 | // ---- Use the tag name to check if the prop is internal for the tag, 15 | // for dynamic tag, we can't check it, so we just assume it's not internal 16 | // represent by the "ANY" tag name 17 | const tagName = this.t.isStringLiteral(tag) ? tag.value : "ANY" 18 | const allDependencyIndexArr: number[] = [] 19 | Object.entries(props).forEach( 20 | ([key, { value, dependencyIndexArr, dependenciesNode, dynamic }]) => { 21 | if (key === "didUpdate") return 22 | allDependencyIndexArr.push(...(dependencyIndexArr ?? [])) 23 | this.addInitStatement( 24 | this.addHTMLProp( 25 | dlNodeName, 26 | tagName, 27 | key, 28 | value, 29 | dynamic, 30 | dependencyIndexArr, 31 | dependenciesNode 32 | ) 33 | ) 34 | } 35 | ) 36 | if (props.didUpdate) { 37 | this.addUpdateStatements( 38 | allDependencyIndexArr, 39 | this.addOnUpdate(dlNodeName, props.didUpdate.value) 40 | ) 41 | } 42 | 43 | // ---- Resolve children 44 | const childNames: string[] = [] 45 | let mutable = false 46 | children.forEach((child, idx) => { 47 | const [initStatements, childName] = this.generateChild(child) 48 | childNames.push(childName) 49 | this.addInitStatement(...initStatements) 50 | if (child.type === "html") { 51 | this.addInitStatement(this.appendChild(dlNodeName, childName)) 52 | } else { 53 | mutable = true 54 | this.addInitStatement(this.insertNode(dlNodeName, childName, idx)) 55 | } 56 | }) 57 | if (mutable) 58 | this.addInitStatement(this.setHTMLNodes(dlNodeName, childNames)) 59 | 60 | return dlNodeName 61 | } 62 | 63 | /** 64 | * @View 65 | * ${dlNodeName} = createElement(${tag}) 66 | */ 67 | private declareHTMLNode(dlNodeName: string, tag: t.Expression): t.Statement { 68 | return this.t.expressionStatement( 69 | this.t.assignmentExpression( 70 | "=", 71 | this.t.identifier(dlNodeName), 72 | this.t.callExpression(this.t.identifier(this.importMap.createElement), [ 73 | tag, 74 | ]) 75 | ) 76 | ) 77 | } 78 | 79 | /** 80 | * @View 81 | * ${dlNodeName}._$nodes = [...${childNames}] 82 | */ 83 | private setHTMLNodes(dlNodeName: string, childNames: string[]): t.Statement { 84 | return this.t.expressionStatement( 85 | this.t.assignmentExpression( 86 | "=", 87 | this.t.memberExpression( 88 | this.t.identifier(dlNodeName), 89 | this.t.identifier("_$nodes") 90 | ), 91 | this.t.arrayExpression(childNames.map(name => this.t.identifier(name))) 92 | ) 93 | ) 94 | } 95 | 96 | /** 97 | * @View 98 | * ${dlNodeName}.appendChild(${childNodeName}) 99 | */ 100 | private appendChild(dlNodeName: string, childNodeName: string): t.Statement { 101 | return this.t.expressionStatement( 102 | this.t.callExpression( 103 | this.t.memberExpression( 104 | this.t.identifier(dlNodeName), 105 | this.t.identifier("appendChild") 106 | ), 107 | [this.t.identifier(childNodeName)] 108 | ) 109 | ) 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /packages/tools/babel-plugin-optional-this/src/index.ts: -------------------------------------------------------------------------------- 1 | import * as t from "@babel/types" 2 | 3 | /** 4 | * check if the identifier is from a function param, e.g: 5 | * class MyClass { 6 | * ok = 1 7 | * myFunc1 = () => ok // change to myFunc1 = () => this.ok 8 | * myFunc2 = ok => ok // don't change !!!! 9 | * } 10 | */ 11 | function isAttrFromFunction(path: any, idName: string, stopNode: any) { 12 | let reversePath = path.parentPath 13 | 14 | function checkParam(param: any): boolean { 15 | // ---- 3 general types: 16 | // * represent allow nesting 17 | // ---0 Identifier: (a) 18 | // ---1 RestElement: (...a) * 19 | // ---1 Pattern: 3 sub Pattern 20 | // -----0 AssignmentPattern: (a=1) * 21 | // -----1 ArrayPattern: ([a, b]) * 22 | // -----2 ObjectPattern: ({a, b}) 23 | if (t.isIdentifier(param)) return param.name === idName 24 | if (t.isAssignmentPattern(param)) return checkParam(param.left) 25 | if (t.isArrayPattern(param)) { 26 | return param.elements.map((el) => checkParam(el)).includes(true) 27 | } 28 | if (t.isObjectPattern(param)) { 29 | return param.properties 30 | .map((prop: any) => prop.key.name) 31 | .includes(idName) 32 | } 33 | if (t.isRestElement(param)) return checkParam(param.argument) 34 | 35 | return false 36 | } 37 | 38 | while (reversePath && reversePath.node !== stopNode) { 39 | const node = reversePath.node 40 | if (t.isArrowFunctionExpression(node) || t.isFunctionDeclaration(node)) { 41 | for (const param of node.params) { 42 | if (checkParam(param)) return true 43 | } 44 | } 45 | reversePath = reversePath.parentPath 46 | } 47 | if (t.isClassMethod(stopNode)) { 48 | for (const param of stopNode.params) { 49 | if (checkParam(param)) return true 50 | } 51 | } 52 | return false 53 | } 54 | 55 | /** 56 | * check if the identifier is already like `this.a` / `xx.a` but not like `a.xx` / xx[a] 57 | */ 58 | function isMemberExpression(path: any) { 59 | const parentNode = path.parentPath.node 60 | return t.isMemberExpression(parentNode) && parentNode.property === path.node && !parentNode.computed 61 | } 62 | 63 | /** 64 | * check if the identifier is a variable declarator like `let a = 1` `for (let a in array)` 65 | */ 66 | function isVariableDeclarator(path: any) { 67 | const parentNode = path.parentPath.node 68 | return t.isVariableDeclarator(parentNode) && parentNode.id === path.node 69 | } 70 | 71 | function isObjectKey(path: any) { 72 | const parentNode = path.parentPath.node 73 | return t.isObjectProperty(parentNode) && parentNode.key === path.node 74 | } 75 | 76 | export default function() { 77 | return { 78 | visitor: { 79 | ClassDeclaration(classPath: any) { 80 | const classBodyNode = classPath.node.body 81 | const availPropNames = classBodyNode.body.map( 82 | (def: any) => def.key.name 83 | ) 84 | 85 | for (const memberOrMethod of classBodyNode.body) { 86 | classPath.scope.traverse(memberOrMethod, { 87 | Identifier(path: any) { 88 | const idNode = path.node 89 | if (idNode === memberOrMethod.key) return 90 | const idName = idNode.name 91 | if ( 92 | availPropNames.includes(idName) && 93 | !isMemberExpression(path) && 94 | !isVariableDeclarator(path) && 95 | !isAttrFromFunction(path, idName, memberOrMethod) && 96 | !isObjectKey(path) 97 | ) { 98 | path.replaceWith( 99 | t.memberExpression(t.thisExpression(), t.identifier(idName)) 100 | ) 101 | path.skip() 102 | } 103 | } 104 | }) 105 | } 106 | } 107 | } 108 | } 109 | } 110 | --------------------------------------------------------------------------------