This commit is contained in:
xion 2025-03-20 02:30:03 +08:00
parent de861d0977
commit 99ad5c10dc
7 changed files with 255 additions and 5 deletions

3
demo/lit/render.ts Normal file
View File

@ -0,0 +1,3 @@
import '../../src/html-preview/kevisual-base';
// console.log('KeVisualBase', KeVisualBase);

33
index.html Normal file
View File

@ -0,0 +1,33 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>KeVisual Base Example</title>
<script type="module" src="./demo/lit/render.ts"></script>
</head>
<body>
<ev-html>
<style >
p {
color: red;
/* 添加一个id */
span {
color: blue;
}
}
</style>
<script type="text/envision">
console.log('Script executed within ev');
console.log(document.body, window.shadowRoot);
</script>
<p>This paragraph should be red.
<span>This span.</span>
</p>
</ev-html>
<p>This paragraph should be blue.</p>
</body>
</html>

View File

@ -12,19 +12,23 @@
"author": "",
"license": "ISC",
"devDependencies": {
"@rollup/plugin-commonjs": "^28.0.2",
"@rollup/plugin-node-resolve": "^16.0.0",
"@cacheable/node-cache": "^1.5.3",
"@kevisual/tojs": "0.0.5",
"@rollup/plugin-commonjs": "^28.0.3",
"@rollup/plugin-node-resolve": "^16.0.1",
"@rollup/plugin-typescript": "^12.1.2",
"@types/crypto-js": "^4.2.2",
"@types/react": "^19.0.3",
"@types/react": "^19.0.10",
"rimraf": "^6.0.1",
"rollup": "^4.30.0",
"rollup": "^4.36.0",
"rollup-plugin-copy": "^3.5.0",
"rollup-plugin-dts": "^6.1.1",
"typescript": "^5.7.2"
"typescript": "^5.8.2",
"vite": "^6.2.2"
},
"dependencies": {
"fast-deep-equal": "^3.1.3",
"lit": "^3.2.1",
"lit-html": "^3.2.1"
},
"exports": {

View File

@ -0,0 +1,119 @@
import { unsafeCSS } from 'lit';
import { LitElement, html, css } from '../lit';
import { fnv1aHash16 } from '../utils/fnv1a-hash';
const baseStyle = `:host {
display: block;
}
`;
function fnv1aHash(str) {
const FNV_PRIME = 16777619;
const OFFSET_BASIS = 2166136261;
let hash = OFFSET_BASIS;
for (let i = 0; i < str.length; i++) {
hash ^= str.charCodeAt(i);
hash *= FNV_PRIME;
}
// 将结果转换为 32 位无符号整数
return hash >>> 0; // 确保结果是非负的 32 位整数
}
// 使用示例
const hash = fnv1aHash('Hello, World!');
console.log(hash.toString(16)); // 输出哈希值的十六进制表示
// ${unsafeCSS(baseStyle)}
export class EnvisionBase extends LitElement {
static styles = css``;
unsafeCssContent = '';
constructor() {
super();
}
render() {
return html` <slot @slotchange="${this.handleSlotChange}"></slot> `;
}
handleSlotChange() {
const slot = this.shadowRoot.querySelector('slot');
const nodes = slot.assignedNodes({ flatten: true }); // 获取所有插槽内容
nodes.forEach((node) => {
if (node.nodeType === Node.ELEMENT_NODE) {
const elementNode = node as HTMLElement;
if (elementNode.tagName.toLowerCase() === 'style') {
this.appendStyle(elementNode);
elementNode.remove();
} else if (elementNode.tagName.toLowerCase() === 'script') {
this.executeScript(elementNode as HTMLScriptElement);
elementNode.remove();
} else {
this.shadowRoot.appendChild(elementNode);
}
}
});
}
appendStyle(styleNode) {
console.log('this', this, this.classList);
console.dir(this);
this.unsafeCssContent = this.unsafeCssContent + styleNode.textContent;
const styleSheet = new CSSStyleSheet();
styleSheet.replaceSync(this.unsafeCssContent);
this.shadowRoot.adoptedStyleSheets.push(styleSheet);
}
async executeScript(scriptNode: HTMLScriptElement) {
const code = scriptNode.textContent;
const hash = fnv1aHash16(code);
const _code = code;
const url = `/api/s1/js/${hash}`;
const loadModule = async (url: string) => {
try {
return await import(/* @vite-ignore */ url);
} catch (error) {
console.error('import error', error);
return null;
}
};
// First attempt to load the module
let module = await loadModule(url);
if (module) {
console.log('module', module);
return;
}
// Post the code to the server
const addCache = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ code: _code }),
}).then((res) => res.json());
console.log('addCache', addCache);
// Wait for a short period before retrying
await new Promise((resolve) => setTimeout(resolve, 3000));
// Second attempt to load the module
module = await loadModule(url + '?test=2');
if (module) {
console.log('module2', module);
} else {
console.error('加载失败, 可能是build错误');
this.showError('加载失败');
}
module = await loadModule(url);
console.log('module3', module);
}
showError(msg?: string) {
this.shadowRoot.innerHTML = `<div style="color: red;">Error: ${msg?.toString() || 'Unknown error'}</div>`;
}
}
customElements.define('ev-html', EnvisionBase);

13
src/lit.ts Normal file
View File

@ -0,0 +1,13 @@
import * as lit from 'lit';
// export { KeVisualBase } from './html-preview/kevisual-base';
export { lit };
export const html = lit.html;
export const svg = lit.svg;
export const mathml = lit.mathml;
export const render = lit.render;
export const nothing = lit.nothing;
export const css = lit.css;
export const LitElement = lit.LitElement;

27
src/utils/fnv1a-hash.ts Normal file
View File

@ -0,0 +1,27 @@
/**
* 32
* @param str -
* @returns 32
*/
export function fnv1aHash(str: string): number {
const FNV_PRIME = 16777619;
const OFFSET_BASIS = 2166136261;
let hash = OFFSET_BASIS;
for (let i = 0; i < str.length; i++) {
hash ^= str.charCodeAt(i);
hash *= FNV_PRIME;
}
// 将结果转换为 32 位无符号整数
return hash >>> 0; // 确保结果是非负的 32 位整数
}
/**
* 16
* @param str -
* @returns 16
*/
export const fnv1aHash16 = (str: string): string => {
return fnv1aHash(str).toString(16); // 取低 16 位
};

51
vite.config.js Normal file
View File

@ -0,0 +1,51 @@
import { defineConfig } from 'vite';
import { returnOriginalJs } from '@kevisual/tojs';
import http from 'http';
const server = http.createServer(async (req, res) => {
console.log('req', req.url, req.method);
const method = req.method;
let body2 = {};
if (method === 'POST') {
const body = await parseBody(req);
console.log('body', body);
body2 = JSON.parse(body);
}
returnOriginalJs(
{
req,
res,
query: {
...body2,
},
},
{
prefixUrl: '/api/s1/js/',
},
);
});
server.listen(4005, () => {
console.log('server listen', server.address());
});
export const parseBody = async (req) => {
return new Promise((resolve, reject) => {
const arr = [];
req.on('data', (chunk) => {
arr.push(chunk);
});
req.on('end', () => {
resolve(Buffer.concat(arr).toString());
});
});
};
export default defineConfig({
server: {
port: 4003,
host: true,
proxy: {
'/api/s1/js': {
target: 'http://localhost:4005',
},
},
},
});