-
Notifications
You must be signed in to change notification settings - Fork 2
Description
Cross-Module Selective Evaluator
In some cases, we will want to selectively evaluate some expressions in one module, but those expressions are depending on other modules. So, we need to either bundle all files into one or evaluate recursively.
Use the following example, How to evaluate the result if it is a constant expression?
// main.js
import { a1, a2 } from "./a";
import { b1, b2 } from "./b";
import { c1, c2 } from './c';
const a1b1 = a1 + b1;
const a2b2 = a2 + b2;
export const result = a1b1 + a2b2;
// a.js
export const a1 = "a1";
export const a2 = "a2";
export const a3 = "a3";
// b.js
export const b1 = "b1";
export const b2 = "b2";
export const b3 = "b3";
// c.js
export const c1 = "c1";
export const c2 = "c2";
export const c3 = "c3";Approach 1 - Bundler + Compressor
Use bundler like Rollup and set the entry as main.js, we can get:
const a1 = "a1";
const a2 = "a2";
const b1 = "b1";
const b2 = "b2";
const a1b1 = a1 + b1;
const a2b2 = a2 + b2;
const result = a1b1 + a2b2;
export { result };Then, use compressor like Terser we can get:
const a="a1b1a2b2";export{a as result};With bundler and compressor, we can evaluate the result to "a1b1a2b2". So, to achieve selective evaluation:
- create a new module
- copy our target and its dependencies into the module
- export the target
- run bundler
- run minifier
Approach 2 - Bundler + SWC's Evaluator
SWC actually implemented its own minifiler with the help of terser's maintainer (swc-project/swc#1302). And there is a Evaluator we can leverage on.
- run bundler to bring all the dependencies into one file
- parse the file with SWC
- evaluate the expression with
Evaluator::eval
Approach 3 - SWC's Evaluator + Recursive eval()
Paste our original example here again for easier reference.
// main.js
import { a1, a2 } from "./a";
import { b1, b2 } from "./b";
import { c1, c2 } from './c';
const a1b1 = a1 + b1;
const a2b2 = a2 + b2;
export const result = a1b1 + a2b2;
// a.js
export const a1 = "a1";
export const a2 = "a2";
export const a3 = "a3";
// b.js
export const b1 = "b1";
export const b2 = "b2";
export const b3 = "b3";
// c.js
export const c1 = "c1";
export const c2 = "c2";
export const c3 = "c3";Here is the recursive evaluation. This way we can evaluate result if all the paths are evaluated successfully.
To evaluate `result`, evaluate `a1b1` and `a2b2`
-> To evaluate `a1b1`, evaluate `a1` and `b1`
-> Evaluate `a1`
-> Evaluate `b1`
-> To evaluate `a2b2`, evaluate `a2` and `b2`
-> Evaluate `a2`
-> Evaluate `b2`
wyw-in-js also approaches this way, they create a new Entry and process only the imported ones. By the way, they use Babel to first try to do evaluation statically then use node:vm for more eagerly evaluation.
Approach 4 - Bundler + SourceMap + SWC's Evaluator
Bundle the whole project so we have one large file. Then we can parse it with SWC and selectively evaluate what we care. Finally, we need to map things back to our original path with SourceMap.
This approach only need to bundle once, but parse one very large file into AST could lead to memory overflow. And our process could become very slow.