147 lines
4.2 KiB
TypeScript
147 lines
4.2 KiB
TypeScript
import type { BuiltInParserName } from "prettier";
|
||
import { zlibSync, unzlibSync, strToU8, strFromU8 } from "fflate";
|
||
|
||
const scriptRe = /<script[^>]*>([\s\S]*)<\/script>/;
|
||
const exportDefaultRe = /export\s*default\s*\{([\s\S]*)\}\;?\s*<\/script>/;
|
||
const setupRe = /setup\s*\(\)\s*\{([\s\S]*)return\s*\{([\s\S]*)\}\;?\s*\}\;?/;
|
||
const layerRe =
|
||
/import\s?\{\s?layer\s?\}\s?from\s?[\"|\']\@layui\/layer-vue[\"|\']/;
|
||
|
||
// danger: 以下字符串拼接代码不可改动缩进或换行,否则会影响 URI hash 解码后代码的排版
|
||
const MAIN_FILE_NAME = "App.vue";
|
||
|
||
/**
|
||
* 将代码编码为 URI hash, 生成 playground 链接
|
||
* @param source 源码
|
||
* @param convertSetupSugar 转换 setup,仅字符串替换,没有做任何语法分析
|
||
* @returns 处理后的代码,URI hsah, playground 链接
|
||
}
|
||
*/
|
||
export const openPlayground = async (
|
||
source: string,
|
||
convertSetupSugar: boolean
|
||
) => {
|
||
const decodeCode = source;
|
||
const scriptResult = decodeCode.match(scriptRe);
|
||
|
||
// 替换 script 标签
|
||
let code: string | undefined = decodeCode;
|
||
if (convertSetupSugar) {
|
||
if (scriptResult) {
|
||
code = decodeCode.replace(
|
||
scriptRe,
|
||
`<script lang="ts" setup>$1
|
||
</script>`
|
||
);
|
||
} else {
|
||
code = `${decodeCode}
|
||
<script lang="ts" setup>
|
||
|
||
</script>`;
|
||
}
|
||
|
||
// 去除 export default,保留其中的内容
|
||
const exportDefaultResult = code.match(exportDefaultRe);
|
||
if (exportDefaultResult) {
|
||
code = code.replace(
|
||
exportDefaultRe,
|
||
trimBr(exportDefaultResult[1] + `</script>`).trim()
|
||
);
|
||
// console.log("export",code);
|
||
}
|
||
// 去除 setup 函数,保留其中的内容
|
||
const setupResult = code.match(setupRe);
|
||
if (setupResult) {
|
||
code = code.replace(setupRe, trimBr(setupResult[1]));
|
||
// console.log("setup",code);
|
||
}
|
||
}
|
||
// 替换 layer 引入语句
|
||
// playground 中使用最新版 layer 请从 @layui/layer-vue 引入
|
||
if (code.match(layerRe)) {
|
||
code = code.replace(layerRe, `import { layer } from "@layui/layui-vue"`);
|
||
// console.log("layer",code);
|
||
}
|
||
|
||
code = await formatCode(MAIN_FILE_NAME, code);
|
||
const originCode = {
|
||
[MAIN_FILE_NAME]: code,
|
||
};
|
||
|
||
const encoded = utoa(JSON.stringify(originCode));
|
||
const link = `https://layui-vue.gitee.io/sandbox-vue/#${encoded}`;
|
||
return {
|
||
code,
|
||
encoded,
|
||
link,
|
||
};
|
||
};
|
||
|
||
/**
|
||
*
|
||
* @returns 格式化代码
|
||
*/
|
||
async function formatCode(filename: string, data: string) {
|
||
const [format, parserHtml, parserTypeScript, parserBabel, parserPostcss] =
|
||
await Promise.all([
|
||
import("prettier/standalone").then((r) => r.format),
|
||
import("prettier/parser-html").then((m) => m.default),
|
||
import("prettier/parser-typescript").then((m) => m.default),
|
||
import("prettier/parser-babel").then((m) => m.default),
|
||
import("prettier/parser-postcss").then((m) => m.default),
|
||
]);
|
||
let code = data;
|
||
let parser: BuiltInParserName;
|
||
if (filename.endsWith(".vue")) {
|
||
parser = "vue";
|
||
} else if (filename.endsWith(".js")) {
|
||
parser = "babel";
|
||
} else if (filename.endsWith(".ts")) {
|
||
parser = "typescript";
|
||
} else if (filename.endsWith(".json")) {
|
||
parser = "json";
|
||
} else {
|
||
return;
|
||
}
|
||
code = format(code, {
|
||
parser,
|
||
plugins: [parserHtml, parserTypeScript, parserBabel, parserPostcss],
|
||
semi: false, // 语句末尾打印分号
|
||
singleQuote: true, // 使用单引号
|
||
vueIndentScriptAndStyle: false, // 是否缩进 Vue 文件中的 script 和 style 标签
|
||
});
|
||
|
||
return code;
|
||
}
|
||
|
||
/**
|
||
* 去除字符串两端的空白行
|
||
* @param str
|
||
* @returns
|
||
*/
|
||
function trimBr(str: string): string {
|
||
return str.replace(/(^[\r\n]*)|([\r\n]*$)/, "");
|
||
}
|
||
|
||
export function utoa(data: string): string {
|
||
const buffer = strToU8(data);
|
||
const zipped = zlibSync(buffer, { level: 9 });
|
||
const binary = strFromU8(zipped, true);
|
||
return btoa(binary);
|
||
}
|
||
|
||
export function atou(base64: string): string {
|
||
const binary = atob(base64);
|
||
|
||
// zlib header (x78), level 9 (xDA)
|
||
if (binary.startsWith("\x78\xDA")) {
|
||
const buffer = strToU8(binary, true);
|
||
const unzipped = unzlibSync(buffer);
|
||
return strFromU8(unzipped);
|
||
}
|
||
|
||
// old unicode hacks for backward compatibility
|
||
// https://base64.guru/developers/javascript/examples/unicode-strings
|
||
return decodeURIComponent(escape(binary));
|
||
}
|