Skip to content

Is it possible to implement a selective evaluator that can cross module boundaries? #4

@wtlin1228

Description

@wtlin1228

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:

  1. create a new module
  2. copy our target and its dependencies into the module
  3. export the target
  4. run bundler
  5. 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.

  1. run bundler to bring all the dependencies into one file
  2. parse the file with SWC
  3. 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.

Approach 5 - ...

Metadata

Metadata

Assignees

Labels

No labels
No labels

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions