Open rbuckton opened 3 years ago
Hi @rbuckton thanks for writing this up!
One of the things we're feeling shy about is adding more compiler configuration for a feature that may change long-term - specifically, we don't like the idea of adding flags for decorators and decorator metadata when the proposal still hasn't reached stage 3. We fear this would cause churn.
An alternative work-around might be to rely on a slightly modified tslib that is SES-compatible and leveraging npm and Yarn's selective dependency resolutions/overrides (i.e. the resolutions
field in package.json
). This requires approximately the same amount of work for an end-user, avoids problems from older libraries that use older versions of tslib, and means we wouldn't have to prematurely add a flag that we may need to keep around long-term.
@rbuckton @DanielRosenwasser any progress on this?
Suggestion
🔍 Search Terms
ses reflect metadata decorator emitDecoratorMetadata experimentalDecorators
✅ Viability Checklist
❗ Problem
SES (Secure ECMAScript) is an initiative to create a locked-down and sandboxed JavaScript execution environment. One of the tenets of SES is that "primordials" (built-in/native ECMAScript global objects, prototypes, functions, etc.) should be immutable and be locked down to a specific set of APIs. This set of APIs includes those specified in the ECMAScript standard and other "blessed" APIs depending on the runtime environment.
A longstanding package used by many in the community is
reflect-metadata
, which is used with TypeScript's--emitDecoratorMetadata
compiler flag and the__metadata
helper:Example:
example.ts
example.js
The issue is that the
__metadata
helper function explicitly relies on an early draft of a proposal that has not yet been brought to committee (as it depends on the decorators proposal, which has not yet reached Stage 3). Unfortunately,reflect-metadata
mutates the globalReflect
object to shim its API, which violates SES. This was recently brought up in https://github.com/endojs/endo/issues/612 and brought to my attention by @erights.Alternatives to
reflect-metadata
exist that do not rely on global mutation, such as https://esfx.js.org/esfx/api/metadata.html. However, they cannot be easily used with TypeScript's--emitDecoratorMetadata
since our helper is hardcoded to check for the specific global. While it is possible to overwrite the helper locally by providing your own__metadata
helper, this is behavior is intentionally undocumented and is also burdensome on developers.⭐ Suggestion
I am proposing that we add two new compiler options to control how we emit decorator metadata:
--metadataDecorator <string>
— Accepts a validEntityName
that should be used as the decorator in place of__metadata
--experimentalDecorators
and--emitDecoratorMetadata
are set.--metadataDecoratorImportSource <string>
— A module specifier used to import the provided metadata decorator.--experimentalDecorators
,--emitDecoratorMetadata
, and--metadataDecorator
are set.NOTE: These two options are somewhat analogous to our existing
--jsxFactory
and--jsxImportSource
options.--metadataDecorator
optionIf the
--metadataDecorator
option is set without specifying--metadataDecoratorImportSource
, then the following occurs:EntityName
.--emitDecoratorMetadata
.EntityName
in place of__metadata
.NOTE: This scenario assumes that the provided metadata decorator exists as a global, or is manually imported into any module.
Example:
example.ts
example.js
--metadataDecoratorImportSource
optionIf the
--metadataDecoratorImportSource
option is set along with--metadataDecorator
, then the following occurs:EntityName
.EntityName
is an export of the import source.EntityName
can be resolved to a value relative to the imported binding above, that can be called as a decorator appropriate to the element on which it would appear as a result of--emitDecoratorMetadata
.import
statement for the import source.EntityName
in place of__metadata
.Example:
example.ts
example.js
Caveats
Specifying
--metadataDecoratorImportSource
implies that the custom metadata decorator can only be reached from within a module, therefore any source file that expects metadata must have at least oneimport
orexport
declaration, or the--isolatedModules
option must be set. If a file is not determined to be a module, we will fall back to the existing__metadata
helper (which is essentially a no-op if theReflect.metadata
function does not exist at runtime).Out-of-scope
--emitDecoratorMetadata
are out of scope. This is only intended to address an ecosystem concern brought to us by those involved with SES.Related Issues
43450 — ES5 class rewriting incompatible with frozen intrinsics