
Research
Security News
Malicious PyPI Package Exploits Deezer API for Coordinated Music Piracy
Socket researchers uncovered a malicious PyPI package exploiting Deezer’s API to enable coordinated music piracy through API abuse and C2 server control.
@dlightjs/dlight
Advanced tools
EN | 中文
JSD | JSX
Search 🌟 in doc for important concepts and performance results.
DLight uses vite to construct its apps. We mainly use this vite plugin to transpile jsx/jsd file into pure js code.
Three ways to try DLight.js out.
npm install -g @dlightjs/cli
create-dlight-app my-first-dlight-app
Mount your dlight component to any html element with an id.
import {render} from "@dlightjs/dlight"
import {MyComp} from "./MyComp.jsd"
render("app", MyComp)
First thing first, DLight is not using template/functional components. It uses Class component instead, but not like React Class component. We are not fans of writing nesting logic inside your view and want to split the view and logic, so we choose not to use functional component with its returned value as view. In the meantime, we want to make our component as flexible as possible, so here we comes the DLight class component.
We realize there's one big burden to write a class component -- 'this'. You have to use this.xxx to access a class property. So currently we're building a babel plugin to 'eliminate this' in a class and auto find the binding object. And now it's done!!
We offer a babel plugin called babel-plugin-optional-this
for any js/ts code to eliminate the ugly this
, e.g.:
export class MyComp extends View {
@State count = 0
Body() {
div(this.count)
}
}
now becomes:
export class MyComp extends View {
@State count = 0
Body() {
div(count)
}
}
(we've of course considered some scenarios like params of a function, a member expression.... So just don't hesitate to use it) Enable it in dlight is pretty easy, in vite.config.ts, just replace:
...
dlight()
...
with
...
dlight({optionalThis: true})
...
Then you're all set!
Let's go back to Write your own component
// -> ./MyComp.jsd
import {View} from "@dlightjs/dlight"
export class MyComp extends View {
@State count = 0
countPlus1 = this.count + 1
Body() {
h1("hello, dlight js, jsd")
div(this.count)
div(this.count + 1)
button("+")
.onclick(() => {
this.count ++
})
button("-")
.onclick(() => {
this.count --
})
}
}
Dlight use @Prop to identify if this class member is a prop.
A reactive prop that changes with its passer's states.
<div id="other-comp" />
in MyOtherComp
will change its innerText if count
in MyComp
changes.
import {View, required} from "@dlightjs/dlight"
class MyOtherComp extends View {
// "required" is just `const required = undefined as any`, we use this to identify that this prop must be passed
@Prop countProp = required
Body() {
div(this.countProp)
.id("other-comp")
}
}
export class MyComp extends View {
@State count = 0
Body() {
button("+")
.onclick(() => {
this.count ++
})
button("-")
.onclick(() => {
this.count --
})
MyOtherComp()
.countProp(this.count)
}
}
A reactive prop that changes with its passer's states and its passer's states change with it at the same time, which means these two props "bind" together.
<div id="mycomp" />
in MyComp
will change its innerText if countPropState
in MyOtherComp
changes.
import {View, required} from "@dlightjs/dlight"
class MyOtherComp extends View {
@PropState countPropState = required
Body() {
button("+")
.onclick(() => {
this.countPropState ++
})
button("-")
.onclick(() => {
this.countPropState --
})
}
}
export class MyComp extends View {
@State count = 0
Body() {
div(this.cout)
.id("mycomp")
MyOtherComp()
.countPropState(this.count)
}
}
Because I'm a big fan of iOS and SwiftUI and don't like any html-like syntax like jsx.
So if you're like me, just try jsd and make your js code more js! If not, just try it. Still not, ignore it and go back to jsx because we also support it.
...
Body() {
div("this auto set it inner text")
div()
{
button("first child")
.onclick(() => {
console.log("write dot prop")
})
div()
.id("second-child-div")
.innerText("you can also set prop like this")
}
"plain text node"
`this is text node too, ${this.anyMessage}`
_("this is expression")
}
...
We call strings like div
/ MyOtherComp
/ If
in Body
as tags. And it will compile to a node in the transpiler stage. We have these following protocols.
Tag that starts with a lowercase letter is a html tag, e.g. div
button
Tag that starts with an uppercase letter is a custom component tag, e.g. MyComp
MyOtherComp
Tag that starts with a lowercase letter maybe an internal tag.
Current internal tag includes: if
else if
else
for
env
_
We also have invisible tag: strings wrapped with "
\ '
\ are called textNode. It's created by
document.createTextNode()`
In jsx, strings wrapped with {}
are called expression. e.g.
...
Body = (
<div>
{ !console.log("expression just like you used to write") && "display this sentence" }
{ this.show && <div>will show if this.show is true</div> }
</div>
)
...
In jsd, we use _
to identifier expression. And inside the expression, we use do {}
to mark that the content inside it is a sub-block of jsd Body. e.g.
...
Body() {
div()
{
_(!console.log("expression just like you used to write") && "display this sentence")
_(this.show && do {
div("will show if this.show is true")
})
}
}
...
Two ways to set a prop
TagName()
.prop1("hello")
.prop2("world")
TagName("your _$content prop")
For different tags, prop means different things.
Html tag
1 prop means html element attributes.
e.g. div("hello").id("hello-div")
=> el.id = "hello-div"
1 prop that starts with a "_" is a shorthand of style attributes.
e.g. div("hello")._color("red")
=> el.style.color = "red"
2 prop sets html element innerText and will be replaced by its children.
e.g. div("hello")
=> el.style.innerText = "hello"
div("this will not show") {
div("because I'm its child, I overwrite its innerText")
}
Custom component
1 prop means custom component props as Quick start - pass a prop
section describes.
2 prop set the custom component prop named _$content
import {View, required} from "@dlightjs/dlight"
class MyOtherComp extends View {
@Prop _$content = required
Body() {
div(this._$content) // display "hello world"
}
}
export class MyComp extends View {
Body() {
MyOtherComp("hello world")
}
}
Internal tag
Features
sectionJsd is still under design and if you have a great design proposal or any problem about it, welcome to open an issue or a discussion!
In Dlight, reactivity is simple and efficient!
Use @State to mark a class member as reactive variable. Whenever the variable is set, all the attributes in a html element that uses this variable will recalculate the attribute(🌟not rerender the element, it has much more fine granularity!)
Two exceptions
If you're using an arrow function to wrap this variable, dlight will consider it as a callback like onclick
, which has no need to reset this attribute, so the reactivity will be dropped in this attribute. If somehow you still want dlight to listen inside it, use function
instead of arrow function
.
e.g. () => { console.log(this.count) }
=> won't be listened
function() { console.log(this.count) }
=> will be listened
If you're setting a state, we won't listen to it because it will cause a dep loop.
For example, imagine you're using React, this.count = 1
should be setCount(1)
, so we won't treat count as a reactive variable. Another case: this.count = this.count + 1
, in React it should be setCount(prev => prev+1)
. Also, we won't let DLight track it.
Example
import {View} from "@dlightjs/dlight"
export class MyComp extends View {
@State count = 0
Body() {
button("+")
.onclick(() => {
this.count ++ // this won't be listened because it's inside an arrow function
})
button("-")
.onclick(() => {
this.count --
})
div(this.count) // everytime you click the button, this div's innerText will be reset
}
}
Of all the frameworks in the market, there's no painless usage of a derived state
(of course other mvvm frameworks are great, I love react, vue, solid, svelte, ...).
Say we have a list of people's first names and last names and we want to concatenate them as full names.
How react would do it:
function ShowMeTheName() {
const [firstName, setFirstName] = useState('John')
const [lastName, setLastName] = useState('Doe')
const fullName = useMemo(() => `${firstName} ${lastName}`, [firstName, lastName])
return <div>{fullName}</div>
}
How solid would do it:
function ShowMeTheName() {
const [firstName, setFirstName] = createSignal('John')
const [lastName, setLastName] = createSignal('Doe')
// use "createMemo" to avoid re-calculate in solid
const fullName = createMemo(() => `${firstName()} ${lastName()}`)
return <div>{fullName()}</div>
}
🌟This is how we do this in dlight:
class ShowMeTheName extends View {
@State firstName = 'John'
@State lastName = 'Doe'
fullName = `${this.firstName} ${this.lastName}`
Body() {
div(this.fullName)
}
}
Yeah! That's right, you don't need to do anything to make a derived
member reactive. Dlight will automatically make fullName
reactive because it's derived from state variables. Whenever firstName
or lastName
changes, fullName
will re-calculate for only once and change any html elements' attribute that uses it.
So, what is dep-chain?
🌟This is a term that describes how DLight's reactivity works. All the deps in the chain will be reactive because of the headnode of the chain(which is a state variable) and will be calculate again if the headnode changes, then all html elements' attributes related to them will be changed too.
Dep-chain examples:
Chains
count => null
flag => null
class DepChainExample1 extends View {
@State count = 0
@State flag = true
}
Chains
count => countPlus1 => countPlus1Plus1 => null
=> countPlus2 => null
flag => noFlag => null
class DepChainExample2 extends View {
@State count = 0
countPlus1 = this.count + 1
countPlus2 = this.count + 2
countPlus1Plus1 = this.countPlus1 + 1
@State flag = true
noFlag = !this.flag
}
Chains
count => null
class DepChainExample3 extends View {
@State count = 0
// logCount will not be added into dep-chain because it's wrapped with an arrow function
logCount = () => {
console.log(this.count)
}
}
Chains
count => logCount => null
class DepChainExample4 extends View {
@State count = 0
// logCount will be added into dep-chain because it's wrapped with a function
logCount = function() {
console.log(this.count)
}.bind(this)
}
Use dep-chain to perform useEffect
?
DLight won't have a lot of circumstances that require a "side effect" because derived
variable can solve most of the case. However, if you still want to use it to listen changes or for other specific reason, you can try this:
class DepChainExample5 extends View {
@State count = 0
// watchCountChange will be added into dep-chain because it's wrapped with a function
// and this function will re-called if "count" changes
watchCountChange = function() {
console.log(this.count)
}.call(this)
}
🌟My variable is a result of a function, how to make it reactive?
There're two ways to do it. Always remember the arrow function and setValue are the only exception, any other expression will automatically collect deps if you use one of the variables in the dep-chain
useEffect
class DepChainExample6_1 extends View {
@State count = 0
countPlus1 = function() {
// do other stuff.....
return this.count+1
}.call(this)
}
class DepChainExample6_1 extends View {
@State count = 0
getCount = count => {
// do other stuff.....
return count
}
countPlus1 = this.getCount(this.count)
}
In DLight, we provide real lifecycles for both custom components and html elements.
willAppear
/ didAppear
/ willDisappear
/ didDisappear
The calling timing can be described as the following pseudocode:
// appear
el.willAppear()
parentEl.appendChild(el)
el.didAppear()
// disappear
el.willDisappear()
el.remove()
el.didDisappear()
Dlight only calls these hooks when the element is created or removed. That's why we say it is "real" lifecycles.
Usage
...
Body() {
div()
.willAppear(() => {
console.log("I will appear")
})
.didAppear=(el => {
console.log(`I just appeared, I am ${el}`)
})
}
...
willMount
/ didMount
/ willUnmount
/ didUnmount
The calling timing can be described as the following pseudocode:
// mount
MyComp.willMount()
MyComp.allHTMLEls.willAppear()
MyComp.allHTMLEls.didAppear()
MyComp.didMount()
// unmount
MyComp.willUnmount()
MyComp.allHTMLEls.willDisappear()
MyComp.allHTMLEls.didDisappear()
MyComp.didUnmount()
delete MyComp
Usage
class MyComp extends View {
didMount() {
console.log("I just mounted")
}
willUnmount() {
console.log("bye-bye")
}
}
You can get children in a custom component with a inner class member called this._$children
and this._$childrenFunc
. The difference is that the child of this._$children
is a node in dlight and that of this._$childrenFunc
is a function that returns the node. The latter one may be useful if you want to build a conditional component and called the function to return the node multiple times like If
or Route
.
import {View} from "@dlightjs/dlight"
class MySubComp extends View {
// this._$children will be div("hello") and div("dlight") in this case
// this._$childrenFunc will be () => div("hello") and () => div("dlight") in this case
Body() {
Exp(this._$children)
Exp(this._$childrenFunc.map(childFunc => childFunc()))
}
}
export class MyComp extends View {
Body() {
MySubComp()
{
div("hello")
div("dlight")
}
}
}
You can access your component or your children component's tag name by using this._$tag
import {View} from "@dlightjs/dlight"
class ThisIsMyComponentHah extends View {
didMount() {
console.log(this._$tag) // will log "ThisIsMyComponentHah"
}
}
Sometimes, you need to access the html element in DOM and alter it manually.
🌟Both HTMLNode and your CustomNode can get element(s)
import {View} from "@dlightjs/dlight"
class MySubComp extends View {
Body() {
div("hello")
div("dlight")
}
}
class MyComp extends View {
myHTMLElement?
myHTMLElements?
didMount() {
console.log(myHTMLElement) // will be <div>good morning</div>
console.log(myHTMLElements) // will be [<div>hello</div>, <div>dlight</div>]
}
Body() {
div("good morning")
.element(this.myHTMLElement)
MySubComp()
.element(this.myHTMLElements)
}
}
Build reusable subviews inside your components. Props inside @View will automatically be reactive.
class MyComp extends View {
@View
MyButton({id}) {
button(`I am button ${id}`)
}
Body() {
for (let i of [1,2,3]) {
this.MyButton()
.id(i)
}
}
}
Force a expression to be a custom tag.
class MySubComp extends View {
Body() {
"hello"
}
}
class MyComp extends View {
myTagObject = {
getTag: () => MySubComp
}
Body() {
tag(this.myTagObject.getTag())()
}
}
Force a expression to be a html tag.
class MyComp extends View {
myTagObject = {
getTag: () => "span"
}
Body() {
html(this.myTagObject.getTag())()
}
}
You can first use unoptimized array map-out to create an array of elements, but once the array is changed even with a single item of it, the whole array of elements will be removed and recreated. So don't use it unless it's a fixed array or you know what you're doing.
...
Body() {
div()
{
_(this.array.map(item => do {
div(item)
}))
}
}
...
🌟Use internal supported For node for optimization.
You can use any "of" expression that you write in js for
loop.
e.g. -> let item of array
/ let [key, item] of array.entries()
/ let {key1, key2} of array
/ ...
...
Body() {
div()
{
for (let item of this.array) {
div(item)
}
}
}
...
Also, we can use a key
prop to bind the element with the key.
...
Body() {
div()
{
for (let {id, item} of this.array) { [id]
div(item)
}
}
}
...
You can first use ( condition && YourElement ) just like you always do in react.
...
Body() {
div(this.show && "show me")
}
...
🌟Use internal supported If/ElseIf/Else node for condition break.
...
Body() {
div()
{
if (this.show) {
"show me"
} else if (this.alsoShow) {
"also show me"
} else {
"don't show me"
}
}
}
...
environment
is just like how you pass a prop in Dlight, so there're no extra cost!@Env
to indentify it.import {View, required} from "@dlightjs/dlight"
class MyNestComp extends View {
@Env myMessage = "default value"
Body() {
div(this.myMessage) // will show "use me anywhere inside this environment"
}
}
class MySubComp2 extends View {
@Env myMessage = "default value"
Body() {
div(this.myMessage) // will show "use me anywhere inside this environment"
}
}
class MySubComp1 extends View {
@Env myMessage = "default value"
Body() {
MyNestComp() // call MySubComp2
div(this.myMessage) // will show "use me anywhere inside this environment"
}
}
export class MyComp extends View {
Body() {
Env()
.myMessage("use me anywhere inside this environment")
{
MySubComp1()
MySubComp2()
}
}
}
FAQs
DX-first UI rendering library
We found that @dlightjs/dlight demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 0 open source maintainers collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Research
Security News
Socket researchers uncovered a malicious PyPI package exploiting Deezer’s API to enable coordinated music piracy through API abuse and C2 server control.
Research
The Socket Research Team discovered a malicious npm package, '@ton-wallet/create', stealing cryptocurrency wallet keys from developers and users in the TON ecosystem.
Security News
Newly introduced telemetry in devenv 1.4 sparked a backlash over privacy concerns, leading to the removal of its AI-powered feature after strong community pushback.