-
-
Notifications
You must be signed in to change notification settings - Fork 2.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Parcel 2: Serializer for complex object types #2522
Conversation
This makes me nervous. It seems like every class that needs to be serialized/deserialized to/from the cache should have a JSON intermediary value that the classes can output and be created from: interface AssetCacheValue {
foo: string
bar: number
}
class Asset {
static createFromCacheValue(value: AssetCacheValue) {
return new Asset(...)
}
toCacheValue(): AssetCacheValue {
return {
foo: this.foo,
bar: this.bar,
}
...
} While this is a lot of manual work, it's also faster and much less error prone. Deep cloning values correctly in JavaScript is so complex that TC39 has rejected it multiple times. You're going to create much weirder errors from the side-effects that will inevitably be triggered from restoring values from a serialized version. |
That's basically what this is doing. You can implement Either way, you need a way to know given a JSON object what type it was originally serialized from in order to call the We could remove the support for automatically serializing/deserializing classes without a |
@@ -25,13 +25,12 @@ type AssetOptions = { | |||
type: string, | |||
code?: string, | |||
ast?: ?AST, | |||
dependencies?: Array<Dependency>, | |||
dependencies?: Array<IDependency>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there ever a time where one of the dependencies would not be an instance of the Dependency class? Do we expect something other than the Dependency class to implement the interface? Just wondering if the interface is necessary. Not a blocker though.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well... I don't think so, but flow was complaining since the types in @parcel/types
refer to the interface not the class. Is there a better way?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, right. Not sure of another way besides pulling those classes out of core, which we didn't want to do. So yeah, this seems like the best option.
@@ -57,12 +57,25 @@ export type EnvironmentContext = | |||
| 'service-worker' | |||
| 'node' | |||
| 'electron'; | |||
export type Environment = { | |||
|
|||
export type EnvironmentOpts = { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Minor nitpick, but I noticed a discrepancy between EnvironmentOpts and DependencyOptions. Should we pick one or the other?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Made a few comments, but nothing blocking. Looks good.
Merging. If it ends up causing problems we can always change it. |
This PR adds a serializer for complex object types (e.g. classes with prototypes) to JSON and back. It is used in the cache and workers in order to ensure that objects such as Asset, Dependency, Environment, AssetGraph, etc. get properly recreated. This all mostly happens automatically under the hood, so is easily extensible for future objects.
There are two parts to the implementation:
__exportSpecifier
static property to classes which are exported from modules. This property stores the filename the object came from (relative to the package so it is portable, e.g.@parcel/core/lib/Asset
), and the name of the export (e.g.Asset
ordefault
). It allows the runtime to inspect objects as they are being serialized and save the type information so that they can be restored.serialize
anddeserialize
functions.serialize
uses a JSON.stringify replacer function to add a$$type
property to objects that have an__exportSpecifier
property on their constructor.deserialize
uses this property torequire
the file and access the constructor to recreate the object.While this will work automatically for many objects, there are some hooks to control how objects get serialized. If there is a
serialize
method on the object, it will get called prior to serialization. This is similar to thetoJSON
method thatJSON.stringify
normally calls. If there is a staticdeserialize
method, it will also get called instead of the constructor to recreate the object.The serializer enables a few things, which have also been done in this PR.
Environment
class, which has some methods to get computed data about the environment, like if it is a node or browser environment, if it's isolated, etc. We could add more computed info here in the future.Dependency
class to replace thecreateDependency
helperIn order to make this work, I also had to make the
Cache
a singleton. This allows theAsset
class to access the cache without one being passed in - it wouldn't when deserialized. In reality, there was already a single instance per worker, it was just passed around, so this isn't that big of a change.