Common Luogu-Paste Script(简称 Common LS、CLS)是一个洛谷剪贴板模块规范。
1. 使用
1.1. 内容范围注释
使用内容范围注释 // begin(end) module
标记模块代码的内容,若没有则全文都为模块的内容。
这样做的原因是可以将模块代码填写在 Markdown 里代码高亮。
```js // begin module console.log("hello world"); // end module ```
markdown
1.2. require
导入一个剪贴板模块,注意要在前面添加 await
因为返回的是一个 Promise
。
const mod = await require("00000001");
js
1.3. require_cache
require
的缓存,注意其类型为 Map
而不是 Object
。
// paste: 00000001 let x = 0; exports.count = () => ++x;
js
// paste: 00000002 console.log((await require("00000001")).count()); // output: 1 console.log((await require("00000001")).count()); // output: 2 console.log((await require("00000001")).count()); // output: 3 require_cache.delete("00000001"); console.log((await require("00000001")).count()); // output: 1
js
1.4. module.exports
导出模块。
// paste: 00000001 module.exports = (x) => x * x;
js
// paste: 00000002 const square = await require("00000001"); console.log(square(10)); // output: 100
js
1.5. exports
导出模块,但是和 module.exports
用法不同。
// paste: 00000001 exports.square = (x) => x * x;
js
// paste: 00000002 const mod = await require("00000001"); console.log(mod.square(10)); // output: 100
js
1.6. encode
decode
为了使模块不那么显而易见我们添加了 encode
decode
功能。
支持的格式有 plain
、base64
、hex
,其中 plain
表示不编码。
console.log(encode.hex('console.log("hello world")')); // output: 63 6f 6e 73 6f 6c 65 2e 6c 6f 67 28 22 68 65 6c 6c 6f 20 77 6f 72 6c 64 22 29 console.log(decode.hex("63 6f 6e 73 6f 6c 65 2e 6c 6f 67 28 22 68 65 6c 6c 6f 20 77 6f 72 6c 64 22 29")); // output: console.log("hello world")
js
你也可以自定义编码。
encode.reverse = (x) => x.split("").reverse().join(""); decode.reverse = (x) => x.split("").reverse().join("");
js
1.7. 编码注释
// encode <type>
表示这个模块的内容的编码格式,若没有则为 plain
。
// paste: 00000001 // encode hex // begin module 63 6f 6e 73 6f 6c 65 2e 6c 6f 67 28 22 68 65 6c 6c 6f 20 77 6f 72 6c 64 22 29 // end module
js
// paste: 00000002 require("00000001"); // output: hello world
js
1.8. 依赖注释
// require <mod1> <mod2> ...
表示当前模块依赖的剪贴板模块。
依赖注释只是标明模块的依赖。
1.9. linker
由于只有主站允许 eval
,所以要在其他地方运行 CLS 必须要用 linker
预处理。
linker
返回的代码默认是不带返回值的,如果需要返回值可以在前面加上 await
。
linker
只会链接依赖注释中标明的依赖。
// paste: 00000001 // begin module exports.square = (x) => x * x; // end module
js
// paste: 00000002 // require 00000001 // begin module const mod = await require("00000001"); exports.cube = (x) => x * mod.square(x); // end module
js
console.log(await linker("00000002")); /* output: (async () => { const _linked_modules = new Map(); _linked_modules.set("00000002", async () => { const module = { exports: {} }; await (async (exports, module) => { const mod = await require("00000001"); exports.cube = (x) => x * mod.square(x); })(module.exports, module); return module.exports; }); _linked_modules.set("00000001", async () => { const module = { exports: {} }; await (async (exports, module) => { exports.square = (x) => x * x; })(module.exports, module); return module.exports; }); const require_cache = new Map(); const require = async (url) => { if (require_cache.has(url)) return require_cache.get(url); let result = await _linked_modules.get(url)(); require_cache.set(url, result); return result; }; return require("00000002"); })(); */
js
2. 运行
2.1. Polyfill
将 CLS Polyfill 放到顶层模块代码的前面就可以了。
const _async_function = (async () => {}).constructor; const _reg1 = /^\s*\/\/\s*begin\s+module\s*$((.|\n)*)^\s*\/\/\s*end\s+module\s*$/m; const _reg2 = /^\s*\/\/\s*encode\s+(\w+)\s*$/m; const _reg3 = /^\s*\/\/\s*require((\s+\w+)*)\s*$/m; const encode = {}; const decode = {}; encode.plain = (x) => x; decode.plain = (x) => x; encode.base64 = (x) => btoa(x); decode.base64 = (x) => atob(x); encode.hex = (x) => { let array = [...new TextEncoder().encode(x)]; array = array.map((i) => i.toString(16).padStart(2, "0")); return array.join(" "); }; decode.hex = (x) => { let array = x.split(" "); array = array.map((i) => parseInt(i, 16)); array = Uint8Array.from(array); return new TextDecoder().decode(array); }; const require_cache = new Map(); const require = async (url) => { if (require_cache.has(url)) return require_cache.get(url); let response = await fetch(`/paste/${url}?_contentOnly`); let json = await response.json(); let data = json.currentData.paste.data; let source = data.match(_reg1)?.[1] || data; let encode = data.match(_reg2)?.[1] || "plain"; let result = await (async () => { const module = { exports: {} }; await _async_function("exports", "module", decode[encode](source))(module.exports, module); return module.exports; })(); require_cache.set(url, result); return result; }; const linker = async (url) => { let result = ` (async () => { const _linked_modules = new Map(); `; const impl_cache = new Set(); const impl = async (url) => { if (impl_cache.has(url)) return; let response = await fetch(`/paste/${url}?_contentOnly`); let json = await response.json(); let data = json.currentData.paste.data; let source = data.match(_reg1)?.[1] || data; let encode = data.match(_reg2)?.[1] || "plain"; let module = data.match(_reg3)?.[1] || ""; result += ` _linked_modules.set("${url}", async () => { const module = { exports: {} }; await (async (exports, module) => { ${decode[encode](source)} })(module.exports, module); return module.exports; }); `; module = module.trim(); if (module !== "") await Promise.all(module.split(/\s+/).map(impl)); }; await impl(url); result += ` const require_cache = new Map(); const require = async (url) => { if (require_cache.has(url)) return require_cache.get(url); let result = await _linked_modules.get(url)(); require_cache.set(url, result); return result; }; return require("${url}"); })(); `; return result; };
js
2.2. CDN
我给 CLS 单独创建了一个剪贴板用于简化运行过程。
await eval((await (await fetch("/paste/3zhbijww?_contentOnly")).json()).currentData.paste.data)("00000001");
js
这样会运行 00000001
剪贴板模块的内容。
2.3. 预处理
先用上述的 linker
预处理,这样就可以直接运行了。
如果觉得预处理之后体积太大可以用 Terser 压缩一下。