runtime-compiler 4.0.0 → 4.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +20 -118
- package/build/rolldown.d.ts +0 -13
- package/build/rolldown.js +1 -1
- package/globals.d.ts +4 -6
- package/globals.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,131 +1,33 @@
|
|
|
1
1
|
A code generation system.
|
|
2
2
|
|
|
3
3
|
# Usage
|
|
4
|
-
See [examples](https://github.com/re-utils/runtime-compiler/tree/main/examples).
|
|
5
|
-
|
|
6
|
-
# Code generation
|
|
7
|
-
Code generation is a trick used in a lot of high performance libraries, especially schema validators (TypeBox, Ajv, Sury, ...).
|
|
8
|
-
```ts
|
|
9
|
-
const schema = {
|
|
10
|
-
id: t.string,
|
|
11
|
-
pwd: t.string
|
|
12
|
-
};
|
|
13
|
-
|
|
14
|
-
// Can be compiled to a fast check function
|
|
15
|
-
const check = (0, eval)(`
|
|
16
|
-
(d) => typeof d === 'object'
|
|
17
|
-
&& d !== null
|
|
18
|
-
&& typeof d.id === 'string'
|
|
19
|
-
&& typeof d.pwd === 'string'
|
|
20
|
-
`);
|
|
21
|
-
```
|
|
22
|
-
|
|
23
|
-
Backend frameworks like Elysia also use it to optimize their request handling:
|
|
24
|
-
```ts
|
|
25
|
-
// Not exactly what Elysia generates but u get the idea
|
|
26
|
-
switch (path) {
|
|
27
|
-
case '/': return handleHome();
|
|
28
|
-
case '/user': {
|
|
29
|
-
// Calls can be analyzed to remove
|
|
30
|
-
// unused fields in the context object
|
|
31
|
-
const c = { ... };
|
|
32
|
-
|
|
33
|
-
// Inline hook calls
|
|
34
|
-
onRequestHook(c);
|
|
35
|
-
handleRequest(c);
|
|
36
|
-
afterRequestHook(c);
|
|
37
|
-
|
|
38
|
-
return sendResponse(c);
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
```
|
|
42
|
-
|
|
43
|
-
For cases that code generation works, it is usually much easier to write and setup than AST transformations, compiles faster, and also has more flexibility.
|
|
44
|
-
|
|
45
|
-
The output startup cost is higher than AST transformations though, but this library has primitives to minimize that additional cost.
|
|
46
|
-
|
|
47
|
-
## Writing codegen
|
|
48
|
-
The first example was pretty simple, but what if we want to use an user-defined value?
|
|
49
|
-
```ts
|
|
50
|
-
const check = (0, eval)(`
|
|
51
|
-
(d) => typeof d === 'string' && checkEmail(d)
|
|
52
|
-
`);
|
|
53
|
-
```
|
|
54
|
-
|
|
55
|
-
This works but it breaks with closures (similarly for other types of unserializable data):
|
|
56
4
|
```ts
|
|
57
|
-
import {
|
|
58
|
-
|
|
59
|
-
// eval returns the value of the last statement
|
|
60
|
-
const check = (0, eval)(`
|
|
61
|
-
const checkEmail = ${checkEmail.toString()};
|
|
62
|
-
(d) => typeof d === 'string' && checkEmail(d)
|
|
63
|
-
`);
|
|
5
|
+
import { IS_AOT, IS_BUILD } from 'runtime-compiler/env';
|
|
6
|
+
import { emit, evaluate } from 'runtime-compiler/globals';
|
|
64
7
|
|
|
65
|
-
//
|
|
66
|
-
|
|
67
|
-
const emailRegex = /.../;
|
|
68
|
-
const checkEmail = (str: string) => emailRegex.test(str);
|
|
69
|
-
```
|
|
8
|
+
// Only emit code if not in AOT mode
|
|
9
|
+
IS_AOT || emit(`return()=>console.log("Hi")`);
|
|
70
10
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
import { checkEmail } from './utils.ts';
|
|
11
|
+
// Only log when building
|
|
12
|
+
IS_BUILD && console.log('Building...');
|
|
74
13
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
}`);
|
|
79
|
-
|
|
80
|
-
const check = buildCheck([checkEmail]);
|
|
14
|
+
// Run emitted code
|
|
15
|
+
const fn = evaluate<() => void>();
|
|
16
|
+
fn();
|
|
81
17
|
```
|
|
82
18
|
|
|
83
|
-
|
|
84
|
-
Code generation has 2 main problems:
|
|
85
|
-
- It has a startup time, memory usage and bundle size cost.
|
|
86
|
-
- It does not work on certain runtimes.
|
|
87
|
-
|
|
88
|
-
This library merges all `eval()` calls into 1 (which makes it able to do ahead of time compilation) and make compiled values easier to work with.
|
|
19
|
+
Build with `rolldown` or `vite`:
|
|
89
20
|
```ts
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
// Can be rewritten as
|
|
94
|
-
const check1Id = compile(schema1);
|
|
95
|
-
const check2Id = compile(schema2);
|
|
21
|
+
...
|
|
22
|
+
import rtc from 'runtime-compiler/build/rolldown';
|
|
96
23
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
24
|
+
export default defineConfig({
|
|
25
|
+
...,
|
|
26
|
+
plugins: [
|
|
27
|
+
...,
|
|
28
|
+
rtc()
|
|
29
|
+
]
|
|
30
|
+
});
|
|
104
31
|
```
|
|
105
32
|
|
|
106
|
-
|
|
107
|
-
```js
|
|
108
|
-
// Store dependencies and build outputs
|
|
109
|
-
const check1Id = compile(schema1);
|
|
110
|
-
const check2Id = compile(schema2);
|
|
111
|
-
|
|
112
|
-
$[0] = (d) => typeof d === 'string';
|
|
113
|
-
$[1] = (d) => Number.isInteger(d) && d > -1 && d < 256;
|
|
114
|
-
|
|
115
|
-
const check1 = artifact(check1Id);
|
|
116
|
-
const check2 = artifact(check2Id);
|
|
117
|
-
```
|
|
118
|
-
|
|
119
|
-
We can remove the codegen cost with flags:
|
|
120
|
-
```ts
|
|
121
|
-
const compile = IS_AOT
|
|
122
|
-
// This is the compile() function after build, since
|
|
123
|
-
// we don't need to codegen anymore
|
|
124
|
-
? () => reserveArtifact()
|
|
125
|
-
: (schema1) => {
|
|
126
|
-
...;
|
|
127
|
-
const schemaId = reserveArtifact();
|
|
128
|
-
emit(`$[${schemaId}]=${builtFnCode}`);
|
|
129
|
-
return schemaId;
|
|
130
|
-
}
|
|
131
|
-
```
|
|
33
|
+
You only need to build when the target runtime doesn't allow `eval()`.
|
package/build/rolldown.d.ts
CHANGED
|
@@ -1,16 +1,3 @@
|
|
|
1
1
|
import type { Plugin } from "rolldown";
|
|
2
|
-
interface ImportBinding {
|
|
3
|
-
name: string;
|
|
4
|
-
alias: string;
|
|
5
|
-
}
|
|
6
|
-
interface ImportStatement {
|
|
7
|
-
start: number;
|
|
8
|
-
end: number;
|
|
9
|
-
bindings: ImportBinding[];
|
|
10
|
-
}
|
|
11
|
-
export declare const parseImports: (code: string) => {
|
|
12
|
-
envImports: ImportStatement;
|
|
13
|
-
globalsImports: ImportStatement;
|
|
14
|
-
};
|
|
15
2
|
declare const _default: () => Plugin;
|
|
16
3
|
export default _default;
|
package/build/rolldown.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{runInWorker}from"./index.js";let parseImportBindings=(code,start,end)=>{let startSpecs=code.indexOf("{",start),endSpecs=code.lastIndexOf("}",end);if(-1===start&&-1===end)throw new Error("Only named imports replacement is supported!");let bindings=[];for(let i=0,specifiers=code.slice(startSpecs+1,endSpecs-1).split(",");i<specifiers.length;i++){let specifier=specifiers[i].trim();if(""===specifier)continue;let parts=specifier.split(" as ",2);if(1===parts.length){let name=parts[0].trim();bindings.push({name,alias:name})}else bindings.push({name:parts[0].trim(),alias:parts[1].trim()})}return{bindings,start,end}};export
|
|
1
|
+
import{runInWorker}from"./index.js";let parseImportBindings=(code,start,end)=>{let startSpecs=code.indexOf("{",start),endSpecs=code.lastIndexOf("}",end);if(-1===start&&-1===end)throw new Error("Only named imports replacement is supported!");let bindings=[];for(let i=0,specifiers=code.slice(startSpecs+1,endSpecs-1).split(",");i<specifiers.length;i++){let specifier=specifiers[i].trim();if(""===specifier)continue;let parts=specifier.split(" as ",2);if(1===parts.length){let name=parts[0].trim();bindings.push({name,alias:name})}else bindings.push({name:parts[0].trim(),alias:parts[1].trim()})}return{bindings,start,end}};export default(()=>({name:"rolldown-plugin-runtime-compiler",resolveId:{filter:{id:/^runtime-compiler\/(?:env|globals)$/},handler:()=>!1},renderChunk:async(code,{imports})=>{let globalsImportIdx=imports.indexOf("runtime-compiler/globals");if(-1===globalsImportIdx)return null;imports.splice(globalsImportIdx);let envImportIdx=imports.indexOf("runtime-compiler/env"),envImport=null,globalsImport=null;-1===envImportIdx?envImport={bindings:[],start:0,end:0}:imports.splice(envImportIdx,1);for(let start=0,eol=code.indexOf("\n");eol>-1&&(null==envImport||null==globalsImport);eol=code.indexOf("\n",start=eol+1))code.startsWith("import",start)&&(code.endsWith('"runtime-compiler/env";',eol)?envImport=parseImportBindings(code,start,eol):code.endsWith('"runtime-compiler/globals";',eol)&&(globalsImport=parseImportBindings(code,start,eol)));let aotCode="const __rtcpl_atf__=[],__rtcpl_aot_fns__=[],__rtcpl_setup_aot__=f=>{__rtcpl_aot_fns__.push(f)};let __rtcpl_aot_fn_idx__=0;";for(let i=0,{bindings}=envImport;i<bindings.length;i++){let{name,alias}=bindings[i];aotCode+="IS_AOT"===name?`const ${alias}=true;`:"IS_BUILD"===name||"IS_JIT"===name?`const ${alias}=false;`:""}for(let i=0,{bindings}=globalsImport;i<bindings.length;i++){let{name,alias}=bindings[i];aotCode+="deref"===name?`const ${alias}=i=>__rtcpl_atf__[i];`:"emit"===name?`const ${alias}=()=>{};`:"evaluate"===name?`const ${alias}=()=>__rtcpl_aot_fns__[__rtcpl_aot_fn_idx__++](__rtcpl_atf__);`:"createRef"===name?`const ${alias}=()=>__rtcpl_atf__.push(undefined)-1;`:"importRef"===name?`const ${alias}=v=>__rtcpl_atf__.push(v)-1;`:""}for(let i=0,codes=await runInWorker(code);i<codes.length;i++)aotCode+=`__rtcpl_setup_aot__($=>{${codes[i]}});`;aotCode+=envImport.start<globalsImport.start?code.slice(0,envImport.start)+code.slice(envImport.end,globalsImport.start)+code.slice(globalsImport.end):code.slice(0,globalsImport.start)+code.slice(globalsImport.end,envImport.start)+code.slice(envImport.end);return{code:aotCode}}}));
|
package/globals.d.ts
CHANGED
|
@@ -1,10 +1,8 @@
|
|
|
1
|
-
export type
|
|
1
|
+
export type Ref<T> = number & {
|
|
2
2
|
"~": T;
|
|
3
3
|
};
|
|
4
|
-
export declare const
|
|
5
|
-
export declare const
|
|
4
|
+
export declare const importRef: <T>(value: T) => Ref<T>;
|
|
5
|
+
export declare const createRef: <T>() => Ref<T>;
|
|
6
6
|
export declare let evaluate: <T>() => T;
|
|
7
7
|
export declare const emit: (code: string) => void;
|
|
8
|
-
export
|
|
9
|
-
export type ArtifactGroup<T extends ArtifactsRecord> = <K extends keyof T>(k: K) => T[K]["~"];
|
|
10
|
-
export declare const artifact: <T>(id: Artifact<T>) => T;
|
|
8
|
+
export declare const deref: <T>(id: Ref<T>) => T;
|
package/globals.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{IS_AOT,IS_BUILD}from"./env/index.js";import{emptyFn}from"./utils.js";let
|
|
1
|
+
import{IS_AOT,IS_BUILD}from"./env/index.js";import{emptyFn}from"./utils.js";let refs=[];export const importRef=value=>refs.push(value)-1;export const createRef=()=>refs.push(void 0)-1;let content="";export let __rtcpl_ct__=[];export let evaluate=IS_AOT?()=>__rtcpl_aot_fns__[__rtcpl_aot_fn_idx__++](refs):IS_BUILD?()=>{__rtcpl_ct__.push(content);let currentContent=content;content="";if(currentContent.length>0)return(0,eval)(`$=>{${currentContent}}`)(refs)}:()=>{let currentContent=content;content="";if(currentContent.length>0)return(0,eval)(`$=>{${currentContent}}`)(refs)};export const emit=IS_AOT?emptyFn:code=>{content+=code};let __rtcpl_aot_fns__=[],__rtcpl_aot_fn_idx__=0;export const __rtcpl_setup_aot__=fn=>{__rtcpl_aot_fns__.push(fn)};export const deref=id=>refs[id];
|
package/package.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"name":"runtime-compiler","version":"4.0.
|
|
1
|
+
{"name":"runtime-compiler","version":"4.0.1","description":"A compiler system.","keywords":[],"homepage":"","license":"MIT","repository":"","type":"module","exports":{"./*":"./*.js","./build":"./build/index.js","./env":"./env/index.js"}}
|