From 99ad5c10dcc683f426fa83ae18a952c6b5c8f5ef Mon Sep 17 00:00:00 2001 From: xion Date: Thu, 20 Mar 2025 02:30:03 +0800 Subject: [PATCH] test --- demo/lit/render.ts | 3 + index.html | 33 +++++++++ package.json | 14 ++-- src/html-preview/kevisual-base.ts | 119 ++++++++++++++++++++++++++++++ src/lit.ts | 13 ++++ src/utils/fnv1a-hash.ts | 27 +++++++ vite.config.js | 51 +++++++++++++ 7 files changed, 255 insertions(+), 5 deletions(-) create mode 100644 demo/lit/render.ts create mode 100644 index.html create mode 100644 src/html-preview/kevisual-base.ts create mode 100644 src/lit.ts create mode 100644 src/utils/fnv1a-hash.ts create mode 100644 vite.config.js diff --git a/demo/lit/render.ts b/demo/lit/render.ts new file mode 100644 index 0000000..ec0cc57 --- /dev/null +++ b/demo/lit/render.ts @@ -0,0 +1,3 @@ +import '../../src/html-preview/kevisual-base'; + +// console.log('KeVisualBase', KeVisualBase); \ No newline at end of file diff --git a/index.html b/index.html new file mode 100644 index 0000000..6279ec8 --- /dev/null +++ b/index.html @@ -0,0 +1,33 @@ + + + + + + + KeVisual Base Example + + + + + + + +

This paragraph should be red. + This span. +

+
+

This paragraph should be blue.

+ + + \ No newline at end of file diff --git a/package.json b/package.json index 98bd518..629f7e6 100644 --- a/package.json +++ b/package.json @@ -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": { diff --git a/src/html-preview/kevisual-base.ts b/src/html-preview/kevisual-base.ts new file mode 100644 index 0000000..15818df --- /dev/null +++ b/src/html-preview/kevisual-base.ts @@ -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` `; + } + + 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 = `
Error: ${msg?.toString() || 'Unknown error'}
`; + } +} + +customElements.define('ev-html', EnvisionBase); diff --git a/src/lit.ts b/src/lit.ts new file mode 100644 index 0000000..e8c1fb5 --- /dev/null +++ b/src/lit.ts @@ -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; diff --git a/src/utils/fnv1a-hash.ts b/src/utils/fnv1a-hash.ts new file mode 100644 index 0000000..605f22d --- /dev/null +++ b/src/utils/fnv1a-hash.ts @@ -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 位 +}; diff --git a/vite.config.js b/vite.config.js new file mode 100644 index 0000000..a7c20c8 --- /dev/null +++ b/vite.config.js @@ -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', + }, + }, + }, +});