esbuild icon indicating copy to clipboard operation
esbuild copied to clipboard

Feature Request: Granular Assignment Protection for Individual --define Mappings (e.g. --define-readonly)

Open mizulu opened this issue 5 months ago • 4 comments

This feature request proposes an enhancement to esbuild's --define flag: granular assignment protection for individual define mappings.

Problem

Currently, using --define:VAR1=window.location will replace all occurrences of VAR1 with window.location. However, assignments to VAR1 (e.g. VAR1 = 3;) are also replaced, resulting in window.location = 3;. This can unintentionally overwrite or mutate important globals, which is often undesirable.

Example:

// source code
VAR1 = 3;
console.log(VAR1);

Build command:

esbuild ./index.ts --define:VAR1=window.location

Current output:

window.location = 3;
console.log(window.location);

Feature Proposal

Introduce a way to mark individual --define mappings as assignment-protected, so assignments to the variable being replaced do not modify the replacement target.

Expected output with assignment protection:

If assignment protection (e.g., with --define-readonly:VAR1=window.location) were implemented, the output would be:

VAR1 = 3;
console.log(window.location);

This ensures assignments to VAR1 do not affect window.location, guarding sensitive globals from reassignment.

Possible Implementation

  • Dedicated CLI Option (Cross-Tool Compatible):
    • Add a new CLI option:
      --define-readonly:VAR1=window.location
      
    • In config files, support an object/array structure:
      {
        "define": {
          "VAR1": {
            "value": "window.location",
            "readonly": true
          }
        }
      }
      
    • This makes assignment protection explicit and compatible with other build tools.

Alternatives Considered

  • Using a global flag (--no-assign-override), which is insufficiently granular.
  • Manually avoiding assignment to sensitive defines, which is error-prone and hard to enforce.

mizulu avatar Jul 19 '25 17:07 mizulu

What's the actual use case you have for this in your source code? This seems like something that should be handled by a linter or type checker instead of by esbuild. For example:

// source code
declare const VAR1: number;
VAR1 = 3;
console.log(VAR1);

This will prevent you from accidentally writing code that assigns to VAR1.

evanw avatar Jul 19 '25 19:07 evanw

@evanw

I think the issue is not VAR1 being declared as a const VAR1 might still be mutable.

let say I define my VAR1 with a default value

config.js

VAR1 = "default in code"

then when I build, I may want to optionally override that specific variable

now because VAR1 is being substituted with window.location for assignment as well as reads. we end up with the unintentional outcome of window.location being mutated

- VAR1 = "default in code"
+ window.location = "default in code"

mizulu avatar Jul 19 '25 20:07 mizulu

Why not declaring your VAR1 as a local variable instead of a global one?

// config.js
var VAR1_ = VAR1  // [--define:VAR1=window.location] => var VAR1_ = window.location
export {VAR1_ as VAR1}
// if you want to mutate that later
export const setVAR1 = v => { VAR1_ = v }

// some-other-file.js
import {VAR1} from './config.js'
console.log(VAR1)

hyrious avatar Jul 20 '25 00:07 hyrious

@hyrious thanks for the suggestion, but that adds a layers of indirection and I am not sure it directly solves the case, with the default value in code

as shown in the message above if we try

VAR1 = "default in code"  // missing from your example 
var VAR1_ = VAR1
...

this have few issues

  1. if we --define VAR1 as a string it will show warnings for the substation in line 1 beside the warning in that case, values like string number etc. work.
"str" = "default in code" //  this will not transform to this, but we will get a warning 
var VAR1_ = "str" // this is fine 
...

however

  1. if we --define VAR1 as an expression we are back to the original problem
my.expression = "default in code" 
var VAR1_ = my.expression // this is find but the code 
...

mizulu avatar Jul 20 '25 05:07 mizulu