Common Luogu-Paste Script(简称 Common LS、CLS)是一个洛谷剪贴板模块规范。
1. 使用
1.1. 内容范围注释
使用内容范围注释 // begin(end) module
标记模块代码的内容,若没有则全文都为模块的内容。
这样做的原因是可以将模块代码填写在 Markdown 里代码高亮。
```js
// begin module
console.log("hello world");
// end module
```
1.2. require
导入一个剪贴板模块,注意要在前面添加 await
因为返回的是一个 Promise
。
const mod = await require("00000001");
1.3. require_cache
require
的缓存,注意其类型为 Map
而不是 Object
。
// paste: 00000001
let x = 0;
exports.count = () => ++x;
// 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
1.4. module.exports
导出模块。
// paste: 00000001
module.exports = (x) => x * x;
// paste: 00000002
const square = await require("00000001");
console.log(square(10)); // output: 100
1.5. exports
导出模块,但是和 module.exports
用法不同。
// paste: 00000001
exports.square = (x) => x * x;
// paste: 00000002
const mod = await require("00000001");
console.log(mod.square(10)); // output: 100
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")
你也可以自定义编码。
encode.reverse = (x) => x.split("").reverse().join("");
decode.reverse = (x) => x.split("").reverse().join("");
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
// paste: 00000002
require("00000001");
// output: hello world
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
// paste: 00000002
// require 00000001
// begin module
const mod = await require("00000001");
exports.cube = (x) => x * mod.square(x);
// end module
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");
})();
*/
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;
};
2.2. CDN
我给 CLS 单独创建了一个剪贴板用于简化运行过程。
await eval((await (await fetch("/paste/3zhbijww?_contentOnly")).json()).currentData.paste.data)("00000001");
这样会运行 00000001
剪贴板模块的内容。
2.3. 预处理
先用上述的 linker
预处理,这样就可以直接运行了。
如果觉得预处理之后体积太大可以用 Terser 压缩一下。