Skip to main content

代码片段

JavaScript / TypeScript

在 ShadowDOM 中渲染 React 应用

renderInShadowDOM.tsx
import React from "react";
import ReactDOM from "react-dom/client";
import {StyleSheetManager} from "styled-components";

export function renderInShadowDOM(app: React.ReactNode, options: {
container: HTMLElement,
links?: Array<string>,
styles?: Array<string>
}) {
const container = document.createElement("div");
options.container.appendChild(container);

const shadow = container.attachShadow({mode: "open"});

const styleSlot = document.createElement("section");
shadow.appendChild(styleSlot);

const root = document.createElement("div");
shadow.appendChild(root);

(options.links || []).map(createCssLink).forEach(ele => shadow.appendChild(ele));
(options.styles || []).map(createCssStyle).forEach(ele => shadow.appendChild(ele));

ReactDOM.createRoot(root).render(<StyleSheetManager target={styleSlot}> {app} </StyleSheetManager>);
}

function createCssLink(style: string) {
const link = document.createElement("link");
link.rel = "stylesheet";
link.href = style;
return link;
}

function createCssStyle(style: string) {
const link = document.createElement("style");
link.type = "text/css";
link.textContent = style;
return link;
}

以下是使用示例:

App.tsx
import style from "./style.css?inline";
import App from "./App";
import { renderInShadowDOM } from "./renderInShadowDOM";

renderInShadowDOM(<App />, {
container: container || document.body,
styles: [style.toString()]
});
style.css
@tailwind base;
@tailwind components;
@tailwind utilities;

参考文档: How to render react applications in Shadow DOM with SSR and Style Encapsulation, How to use Tailwind with shadow dom? #1935

获取函数返回类型

函数返回类型
export async function myFunction() {
return Promise.resolve([{hello: "world"}]);
}

export type MyFunctionResultType = ReturnType<typeof myFunction> extends Promise<Array<infer T>> ? T : never; // => {hello: string}

base64 处理

浏览器
/**
* 编码成base64字符串
* 参考文档 [Base64](https://developer.mozilla.org/en-US/docs/Glossary/Base64)
* @author wmm
* @date 2024-02-19
*/
export function encodeToBase64(str: string): string {
return window.btoa(String.fromCodePoint(...new TextEncoder().encode(str)));
}

/**
* 将base64解码
* 参考文档 [Base64](https://developer.mozilla.org/en-US/docs/Glossary/Base64)
* @author wmm
* @date 2024-02-19
*/
export function decodeFromBase64(base64: string): string {
const binString = window.atob(base64);
// @ts-ignore
const data = window.Uint8Array.from(binString, m => m.codePointAt(0));
return new TextDecoder().decode(data);
}

/**
* 用于将读取成base64
*/
export function readAsBase64(data: Blob): Promise<string> {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.readAsDataURL(data);
reader.onload = () => resolve((reader.result as string).split(";base64,")[1]);
reader.onerror = err => reject(err);
});
}

React 表单快捷更新

export type OnChangeUpdate<T> = {
<K extends keyof T>(key: K, value: T[K]): void;
};
表单示例
function render() {
const [form, setForm] = useState({
firstName: "",
lastName: "",
age: 0
});

const update: OnChangeUpdate<typeof form> = (key, value) => {
setForm({...form, [key]: value});
};

return (
<input
type="text"
value={form.firstName}
onChange={e => update("firstName", e.target.value)}
/>
);
}

export type OnChangeUpdate<T> = {
<K extends keyof T>(key: K, value: T[K]): void;
};

解析 HTML/XML

浏览器端执行
export async function parseHtml(url: string, mimeType?: DOMParserSupportedType): Promise<Document> {
const req = await fetch(url);
const text = await response.text();
return new DOMParser().parseFromString(text, mimeType || "text/html");
}

HumanSize/HumanTimer

可读的Size字符串
function humanSize(bytes: number, decimals = 2) {
if (bytes === 0) return "0 Bytes";

const k = 1024;
const dm = decimals < 0 ? 0 : decimals;
const sizes = ["Bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"];

const i = Math.floor(Math.log(bytes) / Math.log(k));

return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + " " + sizes[i];
}
将剩余毫秒数转成可读的时分秒格式
function humanMs(ms: number) {
let milliseconds = Math.abs(ms);

const oneSecond = 1000;
const oneMinute = oneSecond * 60;
const oneHour = oneMinute * 60;

const hours = Math.floor(milliseconds / oneHour);
milliseconds -= hours * oneHour;
const minutes = Math.floor(milliseconds / oneMinute);
milliseconds -= minutes * oneMinute;
const seconds = Math.floor(milliseconds / oneSecond);
milliseconds -= seconds * oneSecond;
const leftMs = milliseconds.toFixed(0);

const value = [hours, minutes, seconds].map(val => val.toString().padStart(2, "0")).join(":");

return `${value}.${leftMs.padStart(3, "0")}`;
}

Next.js Upload File

ref How to upload a file in Next.js 13+ App Directory with No libraries

import { writeFile } from 'fs/promises'
import { NextRequest, NextResponse } from 'next/server'
import { join } from 'path'

export async function POST(request: NextRequest) {
const data = await request.formData()
const file: File | null = data.get('file') as unknown as File

if (!file) {
return NextResponse.json({ success: false })
}

const bytes = await file.arrayBuffer()
const buffer = Buffer.from(bytes)

// With the file data in the buffer, you can do whatever you want with it.
// For this, we'll just write it to the filesystem in a new location
const path = join('/', 'tmp', file.name)
await writeFile(path, buffer)
console.log(`open ${path} to see the uploaded file`)

return NextResponse.json({ success: true })
}

OS Scripts

解析 CLI 脚本的参数

function getCliParameterValue(paraName) {
const args = process.argv;
const paraIndex = args.indexOf(`--${paraName}`);
const paraNameAndValue = paraIndex > -1 && args.splice(paraIndex, 2);
if (paraNameAndValue) {
return paraNameAndValue.length === 2 ? paraNameAndValue[1] : "";
}
return null;
}

显示所有子目录的git信息

依赖 zx

#!/usr/bin/env zx

import { readdirSync, statSync, existsSync } from "node:fs";
import { join } from "node:path";

const [, , , filter] = process.argv;
const root = getCliParameterValue("-d") || process.cwd();
console.log(`Directory: ${root}`);

const projects = readdirSync(root)
.filter(name => {
if (filter) return name.includes(filter);
return true;
})
.map(name => ({
fullPath: join(root, name),
name: name
}))
.map(({ fullPath, name }) => {
return {
name: name,
dir: fullPath,
stat: statSync(fullPath)
}
})
.filter(item => item.stat.isDirectory())
.filter(item => existsSync(join(item.dir, ".git")))
.map(async item => {
const result = {
...item,
log: "pending...",
branch: "pending...",
}
try {
result.log = (await $`cd ${item.dir} && git log --oneline -n 1`).toString().trim();
result.branch = (await $`cd ${item.dir} && git branch --show-current`).toString().trim();
} catch (ex) {
result.log = result.branch = "--";
}
return result;
});

Promise.all(projects)
.then((projects) => {
const nameMaxPad = maxLength(projects.map(p => p.name));
const branchMaxPad = maxLength(projects.map(p => p.branch))
const outputs = projects.map(proj => `${proj.name.padEnd(nameMaxPad)} ${proj.branch.padEnd(branchMaxPad)} ${proj.log}`).join("\n");
console.log(outputs);
});

function maxLength(list) { return list.reduce((prev, next) => Math.max(prev, next.length), 0) + 1; }

function getCliParameterValue(paraName) {
const args = process.argv;
const paraIndex = args.indexOf(paraName);
const paraNameAndValue = paraIndex > -1 && args.splice(paraIndex, 2);
if (paraNameAndValue) {
return paraNameAndValue.length === 2 ? paraNameAndValue[1] : "";
}
return null;
}

使用

# 显示当前目录下,所有子目录名包含 fe 的项目git信息
git-info.mjs fe

# 显示父级目录下的所有子目录包含 fe 项目的git信息
git-info.mjs fe -d ../