runtime-compiler 3.2.0 → 4.0.0

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 aquapi
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,17 +1,131 @@
1
- # `runtime-compiler`
2
- A code generation system for JS.
1
+ A code generation system.
3
2
 
4
- ## Modes
5
- - `default`: Build & run.
6
- - `build`: Build only.
7
- - `hydrate`: Run only.
3
+ # Usage
4
+ See [examples](https://github.com/re-utils/runtime-compiler/tree/main/examples).
8
5
 
6
+ # Code generation
7
+ Code generation is a trick used in a lot of high performance libraries, especially schema validators (TypeBox, Ajv, Sury, ...).
9
8
  ```ts
10
- import { isHydrating, onlyBuild } from 'runtime-compiler/config';
9
+ const schema = {
10
+ id: t.string,
11
+ pwd: t.string
12
+ };
11
13
 
12
- // Whether the process is in `hydrate` mode
13
- isHydrating;
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 = { ... };
14
32
 
15
- // Whether the process is in `build` mode
16
- onlyBuild;
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
+ ```ts
57
+ import { checkEmail } from './utils.ts';
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
+ `);
64
+
65
+ // For a checkEmail function like this it would
66
+ // break as emailRegex is not available in eval scope
67
+ const emailRegex = /.../;
68
+ const checkEmail = (str: string) => emailRegex.test(str);
69
+ ```
70
+
71
+ We gonna need a dependency array to inject user-defined values into the eval scope:
72
+ ```ts
73
+ import { checkEmail } from './utils.ts';
74
+
75
+ const buildCheck = (0, eval)(`(deps) => {
76
+ const checkEmail = deps[0];
77
+ return (d) => typeof d === 'string' && checkEmail(d)
78
+ }`);
79
+
80
+ const check = buildCheck([checkEmail]);
81
+ ```
82
+
83
+ ## Problems
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.
89
+ ```ts
90
+ const check1 = compile(schema1);
91
+ const check2 = compile(schema2);
92
+
93
+ // Can be rewritten as
94
+ const check1Id = compile(schema1);
95
+ const check2Id = compile(schema2);
96
+
97
+ // For ahead-of-time compilation to work
98
+ // this evaluate() call can just be replaced with
99
+ // whatever code is generated at the moment
100
+ evaluate();
101
+
102
+ const check1 = artifact(check1Id);
103
+ const check2 = artifact(check2Id);
104
+ ```
105
+
106
+ The ahead of time output can be:
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
+ }
17
131
  ```
@@ -0,0 +1,6 @@
1
+ export declare const getPromiseResolvers: (res: any, rej: any) => void;
2
+ export declare const runInWorker: (code: string) => Promise<string[]>;
3
+ /**
4
+ * Requires code to not bundle "runtime-compiler/env" and "runtime-compiler/globals"
5
+ */
6
+ export declare const compile: (code: string) => Promise<string>;
package/build/index.js ADDED
@@ -0,0 +1 @@
1
+ import{Worker}from"node:worker_threads";let curRes,curRej;export const getPromiseResolvers=(res,rej)=>{curRes=res;curRej=rej};let workerOptions={eval:!0};export const runInWorker=code=>{let p=new Promise(getPromiseResolvers),worker=new Worker(`import"runtime-compiler/env/build";import{__rtcpl_ct__}from"runtime-compiler/globals";import{parentPort as __rtcpl_pp__}from"node:worker_threads";${code};\n__rtcpl_pp__.postMessage(__rtcpl_ct__)`,workerOptions);worker.once("message",curRes);worker.once("error",curRej);return p};export const compile=async code=>{let str='import"runtime-compiler/env/aot";import{__rtcpl_setup_aot__}from"runtime-compiler/globals";';for(let i=0,codes=await runInWorker(code);i<codes.length;i++)str+=`__rtcpl_setup_aot__($=>{${codes[i]}});`;return str+code};
@@ -0,0 +1,16 @@
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
+ declare const _default: () => Plugin;
16
+ export default _default;
@@ -0,0 +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 const parseImports=code=>{let envImports,globalsImports;for(let start=0,eol=code.indexOf("\n");eol>-1&&(null==envImports||null==globalsImports);eol=code.indexOf("\n",start=eol+1))code.startsWith("import",start)&&(code.endsWith('"runtime-compiler/env";',eol)?envImports=parseImportBindings(code,start,eol):code.endsWith('"runtime-compiler/globals";',eol)&&(globalsImports=parseImportBindings(code,start,eol)));return{envImports,globalsImports}};export default(()=>({name:"rolldown-plugin-runtime-compiler",resolveId:{filter:{id:/^runtime-compiler\/(?:env|globals)$/},handler:()=>!1},renderChunk:async(code,chunk)=>{if(!chunk.imports.includes("runtime-compiler/globals")||!chunk.imports.includes("runtime-compiler/env"))return null;let{envImports,globalsImports}=parseImports(code),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}=envImports;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}=globalsImports;i<bindings.length;i++){let{name,alias}=bindings[i];aotCode+="artifact"===name?`const ${alias}=i=>__rtcpl_atf__[i];`:"emit"===name?`const ${alias}=()=>{};`:"evaluate"===name?`const ${alias}=()=>__rtcpl_aot_fns__[__rtcpl_aot_fn_idx__++](__rtcpl_atf__);`:"reserveArtifact"===name?`const ${alias}=()=>__rtcpl_atf__.push(undefined)-1;`:"importArtifact"===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+=envImports.start<globalsImports.start?code.slice(0,envImports.start)+code.slice(envImports.end,globalsImports.start)+code.slice(globalsImports.end):code.slice(0,globalsImports.start)+code.slice(globalsImports.end,envImports.start)+code.slice(envImports.end);return{code:aotCode}}}));
package/env/aot.js ADDED
@@ -0,0 +1 @@
1
+ import{setAOT}from"./index.js";setAOT();
package/env/build.js ADDED
@@ -0,0 +1 @@
1
+ import{setBuild}from"./index.js";setBuild();
package/env/index.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ export declare let IS_JIT: boolean;
2
+ export declare let IS_BUILD: boolean;
3
+ export declare let IS_AOT: boolean;
package/env/index.js ADDED
@@ -0,0 +1 @@
1
+ export let IS_JIT=!0;export let IS_BUILD=!1;export let IS_AOT=!1;export const setBuild=()=>{IS_JIT=!1;IS_BUILD=!0;IS_AOT=!1};export const setAOT=()=>{IS_JIT=!1;IS_BUILD=!1;IS_AOT=!0};
package/globals.d.ts ADDED
@@ -0,0 +1,10 @@
1
+ export type Artifact<T> = number & {
2
+ "~": T;
3
+ };
4
+ export declare const importArtifact: <T>(value: T) => Artifact<T>;
5
+ export declare const reserveArtifact: <T>() => Artifact<T>;
6
+ export declare let evaluate: <T>() => T;
7
+ export declare const emit: (code: string) => void;
8
+ export type ArtifactsRecord = Record<any, Artifact<any>>;
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;
package/globals.js ADDED
@@ -0,0 +1 @@
1
+ import{IS_AOT,IS_BUILD}from"./env/index.js";import{emptyFn}from"./utils.js";let __rtcpl_atf__=[];export const importArtifact=value=>__rtcpl_atf__.push(value)-1;export const reserveArtifact=()=>__rtcpl_atf__.push(void 0)-1;let content="";export let __rtcpl_ct__=[];export let evaluate=IS_AOT?()=>__rtcpl_aot_fns__[__rtcpl_aot_fn_idx__++](__rtcpl_atf__):IS_BUILD?()=>{__rtcpl_ct__.push(content);let currentContent=content;content="";if(currentContent.length>0)return(0,eval)(`$=>{${currentContent}}`)(__rtcpl_atf__)}:()=>{let currentContent=content;content="";if(currentContent.length>0)return(0,eval)(`$=>{${currentContent}}`)(__rtcpl_atf__)};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 artifact=id=>__rtcpl_atf__[id];
package/package.json CHANGED
@@ -1 +1 @@
1
- {"name":"runtime-compiler","version":"3.2.0","description":"Universal compiler system","keywords":[],"repository":{},"homepage":"","license":"MIT","type":"module","exports":{"./utils":"./utils.js","./config":"./config/index.js",".":"./index.js","./config/mode/hydrate":"./config/mode/hydrate.js","./config/mode/build":"./config/mode/build.js","./config/loader/hydrate":"./config/loader/hydrate.js","./config/loader/build":"./config/loader/build.js"}}
1
+ {"name":"runtime-compiler","version":"4.0.0","description":"A compiler system.","keywords":[],"homepage":"","license":"MIT","repository":"","type":"module","exports":{"./*":"./*.js","./build":"./build/index.js","./env":"./env/index.js"}}
package/scope.d.ts ADDED
@@ -0,0 +1,13 @@
1
+ export type Scope = [start: string, end: string, nextId: number];
2
+ /**
3
+ * Get next available id in a scope.
4
+ */
5
+ export declare const scopeNextId: (scope: Scope) => number;
6
+ /**
7
+ * Get scope content.
8
+ */
9
+ export declare const scopeContent: (scope: Scope) => string;
10
+ /**
11
+ * Fork a new scope.
12
+ */
13
+ export declare const scopeFork: <T extends Scope>(scope: T) => T;
package/scope.js ADDED
@@ -0,0 +1 @@
1
+ export const scopeNextId=scope=>scope[2]++;export const scopeContent=scope=>`{${scope[0]+scope[1]}}`;export const scopeFork=scope=>scope.slice();
package/utils.d.ts CHANGED
@@ -1,43 +1 @@
1
- import { type Expression, type Value } from "./index.ts";
2
- /**
3
- * Placeholder for `hydrate` mode.
4
- */
5
- export declare const noOp: () => string;
6
- /**
7
- * Async function constructor
8
- */
9
- export declare const AsyncFunction: typeof Function;
10
- /**
11
- * Derive a value from other expressions.
12
- * Use in `default` and `build` mode.
13
- *
14
- * @example
15
- * const newValue = derive(
16
- * ['Math.random()' as Expression<number>],
17
- * (rand) => rand * 10
18
- * ); // '$[0](Math.random())'
19
- *
20
- * // In hydrate mode
21
- * injectExternal((rand) => rand * 10);
22
- */
23
- export declare const derive: <
24
- const Expressions extends Expression<any>[],
25
- R
26
- >(values: Expressions, fn: (...args: { [K in keyof Expressions] : Expressions[K]["~type"] }) => R) => Value<R>;
27
- /**
28
- * Derive a value from other expressions.
29
- * Use in `default` and `build` mode.
30
- *
31
- * @example
32
- * const newValue = deriveAsync(
33
- * ['await fetch("http://example.com")' as Expression<Response>],
34
- * async (res) => await res.text()
35
- * ); // 'await $[0](await fetch("http://example.com"))'
36
- *
37
- * // In hydrate mode
38
- * injectExternal((rand) => rand * 10);
39
- */
40
- export declare const deriveAsync: <
41
- const Expressions extends Expression<any>[],
42
- R
43
- >(values: Expressions, fn: (...args: { [K in keyof Expressions] : Expressions[K]["~type"] }) => Promise<R>) => Expression<R>;
1
+ export declare const emptyFn: () => void;
package/utils.js CHANGED
@@ -1 +1 @@
1
- import{injectExternal}from"./index.js";export let noOp=()=>``;export let AsyncFunction=(async()=>{}).constructor;export let derive=(r,i)=>injectExternal(i)+`(`+r.join()+`)`;export let deriveAsync=(r,i)=>`await `+injectExternal(i)+`(`+r.join()+`)`;
1
+ export const emptyFn=()=>{};
package/config/index.d.ts DELETED
@@ -1,8 +0,0 @@
1
- /**
2
- * False when in `default` and `build` mode, true in `hydrate` mode
3
- */
4
- export declare let isHydrating: boolean;
5
- /**
6
- * False when in `default` and `hydrate` mode, true in `build` mode
7
- */
8
- export declare let onlyBuild: boolean;
package/config/index.js DELETED
@@ -1 +0,0 @@
1
- export let isHydrating=false;export let onlyBuild=false;export let hydrating=()=>{isHydrating=true};export let building=()=>{onlyBuild=true};
@@ -1 +0,0 @@
1
- export {};
@@ -1 +0,0 @@
1
- import{building}from"../index.js";building();
@@ -1 +0,0 @@
1
- export {};
@@ -1 +0,0 @@
1
- import{hydrating}from"../index.js";hydrating();
@@ -1,2 +0,0 @@
1
- export declare const isHydrating: boolean;
2
- export declare const onlyBuild: boolean;
@@ -1 +0,0 @@
1
- export let isHydrating=false;export let onlyBuild=true;
@@ -1,2 +0,0 @@
1
- export declare const isHydrating: boolean;
2
- export declare const onlyBuild: boolean;
@@ -1 +0,0 @@
1
- export let isHydrating=true;export let onlyBuild=false;
package/index.d.ts DELETED
@@ -1,119 +0,0 @@
1
- export type Expression<T> = string & {
2
- "~type": T
3
- };
4
- export type Value<T> = Expression<T> & {
5
- "~value": 0
6
- };
7
- export type Identifier<T> = Value<T> & {
8
- "~id": 0
9
- };
10
- export type ExportedDependency<T> = number & {
11
- "~type": T
12
- };
13
- export interface Scope {
14
- /**
15
- * Current scope content.
16
- */
17
- 0: string;
18
- /**
19
- * Next available id.
20
- */
21
- 1: number;
22
- /**
23
- * End scope content.
24
- */
25
- 2: string;
26
- slice: () => this;
27
- }
28
- /**
29
- * External dependencies
30
- */
31
- export declare const $: any[];
32
- /**
33
- * Inject an external dependency.
34
- */
35
- export declare const injectExternal: <T>(val: T) => Value<T>;
36
- /**
37
- * Get next available identifier name in a scope
38
- */
39
- export declare const nextId: <T>(scope: Scope) => Identifier<T>;
40
- export declare const content: <T>(scope: Scope) => string;
41
- /**
42
- * Declare a new variable in a scope
43
- *
44
- * @example
45
- * declareLocal(scope, '1;');
46
- */
47
- export declare const declareLocal: <T>(scope: Scope, str: string) => Identifier<T>;
48
- /**
49
- * Declare a new variable in a scope
50
- */
51
- export declare const declareExternal: <T>(scope: Scope, val: T) => Identifier<T>;
52
- interface ExportToParent {
53
- <T>(scope: Scope, expr: Expression<T>, parentScope: Scope): Identifier<T>;
54
- (scope: Scope, expr: string, parentScope: Scope): string;
55
- }
56
- /**
57
- * Export a local expression to a parent local variable.
58
- * Use in `default` and `build` mode.
59
- *
60
- * @example
61
- * // Fork scope
62
- * const childScope = ['', parentScope.id];
63
- * // let d0;{let d1=1;d0=d1}
64
- * const id = exportTo(childScope, declareLocal(childScope, '1'), parentScope); // d0
65
- */
66
- export declare const exportToParent: ExportToParent;
67
- /**
68
- * Mark current available variable id to be already in use.
69
- * Use in `hydrate` mode.
70
- * @param scope
71
- */
72
- export declare const markDeclared: (scope: Scope) => void;
73
- export declare let statements: string;
74
- interface ExportScope {
75
- <T>(scope: Scope, value: Expression<T>): ExportedDependency<T>;
76
- <T>(scope: Scope, value: string): ExportedDependency<T>;
77
- }
78
- /**
79
- * Export a local dependency of a scope.
80
- * Use in `default` and `build` mode.
81
- */
82
- export declare const exportScope: ExportScope;
83
- /**
84
- * Export a value.
85
- * Use in `default` and `build` mode.
86
- *
87
- * @example
88
- * exportLocal('1;');
89
- */
90
- export declare const exportLocal: <T>(value: string) => ExportedDependency<T>;
91
- /**
92
- * Mark a slot to export a dependency.
93
- * Use in `hydrate` mode.
94
- */
95
- export declare const markExported: <T>() => ExportedDependency<T>;
96
- /**
97
- * Get the value of an exported dependency.
98
- * Use in `default` mode.
99
- * @param idx
100
- */
101
- export declare const getDependency: <T>(idx: ExportedDependency<T>) => T;
102
- /**
103
- * Get built statements and reset for next build.
104
- * Use in `build` mode.
105
- */
106
- export declare const getStatements: () => string;
107
- /**
108
- * Store temporary build data.
109
- */
110
- export declare let buildData: any[];
111
- /**
112
- * @example
113
- * const slot = createBuildSlot();
114
- *
115
- * // Reset after every build
116
- * buildData[slot] ??= data;
117
- */
118
- export declare const createBuildSlot: () => number;
119
- export {};
package/index.js DELETED
@@ -1 +0,0 @@
1
- ;export let $=[];export let injectExternal=f=>`$[${$.push(f)-1}]`;export let nextId=e=>`d`+ e[1]++;export let content=e=>e[0]+e[2];export let declareLocal=(e,f)=>{let m=nextId(e);e[0]+=`let ${m}=`+f;return m};export let declareExternal=(f,m)=>{let h=nextId(f);f[0]+=`let ${h}=$[${$.push(m)-1}];`;return h};export let exportToParent=(e,f,h)=>{let g=nextId(h);h[0]+=`let ${g};{${content(e)+g}=${f}}`;return g};export let markDeclared=e=>{e[1]++};export let statements=``;export let exportScope=(f,p)=>(statements+=`{${content(f)}$[${$.length}]=${p}}`,$.length++);export let exportLocal=f=>(statements+=`$[${$.length}]=${f}`,$.length++);export let markExported=()=>$.length++;export let getDependency=f=>{if(statements.length>0){globalThis.__rt_externals__=$;(0,eval)(`{let $=__rt_externals__;`+statements+`}`);globalThis.__rt_externals__=null;statements=``;buildData=[]}return $[f]};export let getStatements=()=>{let e=statements;statements=``;buildData=[];return e};export let buildData=[];let nextSlot=0;export let createBuildSlot=()=>nextSlot++;