Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions .babelrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

저는 vite.config.ts에서 세팅했는데, 이렇게 해도 잘 동작하나보네요!

이 방법이 원초적인것 같아 좋은것 같네요. 참고해서 다시 해봐야겠어요 👍

"presets": ["@babel/preset-env", "@babel/preset-typescript"],
"plugins": [
[
"@babel/plugin-transform-react-jsx",
{
"pragma": "h"
}
]
]
}
2 changes: 1 addition & 1 deletion index.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,6 @@
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
<script type="module" src="/dist/main.js"></script>
</body>
</html>
5 changes: 5 additions & 0 deletions jsx.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
declare namespace JSX {
interface IntrinsicElements {
[elementName: string]: any;
}
}
17 changes: 14 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,22 @@
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"preview": "vite preview"
"preview": "vite preview",
"build": "babel src --out-dir dist --extensions '.ts,.tsx'",
"start": "vite",
"babel": "babel src/main.tsx --out-file dist/bundle.js"
},
"devDependencies": {
"@babel/cli": "^7.26.4",
"@babel/core": "^7.26.0",
"@babel/plugin-transform-react-jsx": "^7.25.9",
"@babel/preset-env": "^7.26.0",
"@babel/preset-react": "^7.26.3",
"@babel/preset-typescript": "^7.26.0",
"typescript": "~5.6.2",
"vite": "^6.0.3"
"vite": "^6.0.6"
},
"dependencies": {
"jsx-runtime": "link:@/lib/jsx/jsx-runtime"
}
}
1 change: 0 additions & 1 deletion src/main.ts

This file was deleted.

123 changes: 123 additions & 0 deletions src/main.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
type RecordType = Record<string, any>;

interface VirtualNode {
type: string;
props: RecordType;
children: (VirtualNode | string)[];
}

function h(type: string, props: RecordType | null, ...children: any[]): VirtualNode {
return {
type,
props: props || {},
children: children.flat().filter((child) => child != null),
};
}

function updateElement(parent: Node, oldNode: Node | null, newNode: Node | null) {
if (!newNode && oldNode && oldNode instanceof HTMLElement) {
oldNode.remove();
return;
}

if (newNode && !oldNode) {
parent.appendChild(newNode);
return;
}

if (!oldNode || !newNode) return;

if (newNode instanceof Text && oldNode instanceof Text) {
if (newNode.nodeValue !== oldNode.nodeValue) {
oldNode.nodeValue = newNode.nodeValue;
}
return;
}

if (oldNode instanceof Element && newNode instanceof Element) {
if (newNode.nodeName !== oldNode.nodeName) {
oldNode.replaceWith(newNode);
return;
}

updateAttributes(oldNode, newNode);

const newChildren = Array.from(newNode.childNodes);
const oldChildren = Array.from(oldNode.childNodes);
const maxLength = Math.max(newChildren.length, oldChildren.length);

for (let i = 0; i < maxLength; i++) {
updateElement(oldNode, oldChildren[i] || null, newChildren[i] || null);
}
}
}

function updateAttributes(oldNode: Element, newNode: Element) {
const oldProps = Array.from(oldNode.attributes);
const newProps = Array.from(newNode.attributes);

for (const { name, value } of newProps) {
if (oldNode.getAttribute(name) !== value) {
oldNode.setAttribute(name, value);
}
}

for (const { name } of oldProps) {
if (!newNode.hasAttribute(name)) {
oldNode.removeAttribute(name);
}
}
}

const render = (state: RecordType[]) => {
const element = document.createElement('div');
element.innerHTML = `
<div id="app">
<ul>
${state
.map(
({ completed, content }) => `
<li class="${completed ? 'completed' : ''}">
<input type="checkbox" class="toggle" ${completed ? 'checked' : ''} />
${content}
<button class="remove">삭제</button>
</li>
`,
)
.join('')}
</ul>
<form>
<input type="text" />
<button type="submit">추가</button>
</form>
</div>
`.trim();

return element.firstElementChild;
};

const oldState = [
{ id: 1, completed: false, content: 'todo list item 1' },
{ id: 2, completed: true, content: 'todo list item 2' },
];

const newState = [
{ id: 1, completed: true, content: 'todo list item 1 updated' },
{ id: 2, completed: true, content: 'todo list item 2' },
{ id: 3, completed: false, content: 'todo list item 3' },
];

const $root = document.createElement('div');
document.body.appendChild($root);

const oldNode = render(oldState);
if (oldNode) {
$root.appendChild(oldNode);
}

setTimeout(() => {
const newNode = render(newState);
if (oldNode && newNode) {
updateElement($root, oldNode, newNode);
}
}, 1000);
16 changes: 11 additions & 5 deletions tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,24 +1,30 @@
{
"compilerOptions": {
"target": "ES2020",
"target": "ES5",
"useDefineForClassFields": true,
"module": "ESNext",
"module": "CommonJS",
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"skipLibCheck": true,

/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"isolatedModules": true,
"moduleDetection": "force",
"noEmit": true,
"esModuleInterop": true,

/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedSideEffectImports": true
"jsx": "preserve",
"jsxFactory": "h",
"jsxImportSource": "@/lib/jsx",
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
}
},
"include": ["src"]
"include": ["src", "jsx.d.ts"]
}
10 changes: 10 additions & 0 deletions vite.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { defineConfig } from 'vite';

export default defineConfig({
server: {
port: 3000,
},
build: {
outDir: 'dist',
},
});