JS 技巧
浏览器环境解析 CJS Export
利用 eval 解析 CJS 格式的模块导出,这在浏览器环境非常有用
const moduleStr = `
module.exports = {
entry: './src/index.js',
process: () => {}
}
`
console.log(eval(moduleStr))
// => { entry: './src/index.js', process: [Function: process] }浏览器环境动态使用 NPM 包
通常的印象中 NPM 包通常与 NodeJs 绑定,但是我们也可以直接在浏览器环境中获取并使用,需要用到 esm.sh
esm.sh
暴露了 ESM 的能力,让我们能在浏览器中通过 ESM 使用到 NPM 模块
一个例子:
<script type="module">
import { esm } from "https://esm.sh/build";
const { sayHi } = await esm`
import chalk from "chalk";
export const sayHi = () => chalk.blue("Hi!");
`;
console.log(sayHi()); // prints "Hi!" message in blue color
</script>更有意思的例子,能够在浏览器上完成更复杂的事情
<script type="module">
import build from "https://esm.sh/build";
const ret = await build({
dependencies: {
"preact": "^10.13.2",
"preact-render-to-string": "^6.0.2",
},
code: `
/* @jsx h */
import { h } from "preact";
import { renderToString } from "preact-render-to-string";
export function render(): string {
return renderToString(<h1>Hello world!</h1>);
}
`,
// for types checking and LSP completion
types: `
export function render(): string;
`,
});
// import module
const { render } = await import(ret.url);
// import bundled module
const { render } = await import(ret.bundleUrl);
render(); // "<h1>Hello world!</h1>"
</script>new AsyncFunction
new AsyncFunction,顾名思义就是 new Function 的异步版本。目前 JS 并没有直接提供 AsyncFunction,我们需要自己模拟。
const AsyncFunction = Object.getPrototypeOf(async () => {}).constructor比如我们结合上面的 esm.sh 举个例子
const AsyncFunction = Object.getPrototypeOf(async () => {}).constructor
const CDN_BASE = 'https://esm.sh/'
export async function evaluateUserConfig<U = UserConfig>(configStr: string): Promise<U | undefined> {
const code = configStr
.replace(/import\s*(\{[\s\S]*?\})\s*from\s*(['"])([\w-@/]+)\2/g, 'const $1 = await __import("$3");')
.replace(/import\s*(.*?)\s*from\s*(['"])([\w-@/]+)\2/g, 'const $1 = (await __import("$3")).default;')
.replace(/export default /, 'return ')
.replace(/\bimport\s*\(/, '__import(')
// bypass vite interop
// eslint-disable-next-line no-new-func
const _import = new Function('a', 'return import(a);')
const __import = (name: string): any => {
if (!modulesCache.has(name)) {
modulesCache.set(name,
name.endsWith('.json')
? $fetch(CDN_BASE + name, { responseType: 'json' }).then(r => ({ default: r }))
: _import(CDN_BASE + name),
)
}
return modulesCache.get(name)
}
const fn = new AsyncFunction('__import', code)
const result = await fn(__import)
if (result)
return result
}上面的代码也可以实现解析 ESM 模块的效果,实际上就是 unocss 的 plaground 的解析 ESM 部分的原理 ,代码位于这里
logger.js
打印所在文件路径和行
// Use like this: node --import logger.js yourapp.js
import path from 'path';
const { log } = console;
[`debug`, `log`, `warn`, `error`, `table`, `dir`].forEach((methodName) => {
const originalLoggingMethod = console[methodName];
console[methodName] = (...args) => {
const originalPrepareStackTrace = Error.prepareStackTrace;
Error.prepareStackTrace = (_, stack) => stack;
const callee = new Error().stack[1];
Error.prepareStackTrace = originalPrepareStackTrace;
const relativeFileName = path
.relative(process.cwd(), callee.getFileName())
.replace(process.cwd(), ``)
.replace(`file:/`, ``);
// Log in dark grey
const label = `${relativeFileName}:${callee.getLineNumber()}`;
log(`🪵 \x1b[90m%s\x1b[0m`, label);
originalLoggingMethod(...args);
};
});