-
Notifications
You must be signed in to change notification settings - Fork 27
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
@caridy on behalf the group of original contributors
- @davidturissini David Turissini - @diervo: Diego Ferreiro Val - @caridy: Caridy Patiño This library was extracted from another multi-package, and we could not preserve the full history, which goes back for about one year from this commit.
- Loading branch information
Showing
20 changed files
with
6,064 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
root = true | ||
|
||
[*] | ||
end_of_line = lf | ||
insert_final_newline = true | ||
indent_style = space | ||
indent_size = 4 | ||
trim_trailing_whitespace = true | ||
|
||
[*.{json,yml}] | ||
indent_size = 2 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
node_modules | ||
coverage | ||
dist/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
src | ||
coverage | ||
examples/ | ||
jest.config.js | ||
rollup.config.js | ||
tsconfig.json | ||
.* |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,172 @@ | ||
# Contributing | ||
|
||
[Set up SSH access to Github][setup-github-ssh] if you haven't done so already. | ||
|
||
## Requirements | ||
|
||
* Node 8.x | ||
* NPM 5.x | ||
* Yarn >= 0.27.5 | ||
|
||
## Installation | ||
|
||
### 1) Download the repository | ||
|
||
```bash | ||
git clone git@github.com:salesforce/observable-membrane.git | ||
``` | ||
|
||
### 2) Install Dependencies | ||
|
||
*We use [yarn](https://yarnpkg.com/) because it is significantly faster than npm for our use case. See this command [cheatsheet](https://yarnpkg.com/lang/en/docs/migrating-from-npm/).* | ||
|
||
```bash | ||
yarn install | ||
``` | ||
|
||
## Building | ||
|
||
When using `yarn build`, it will build the entire project into the `dist/` folder where you can find the different distributions: | ||
|
||
```bash | ||
yarn build | ||
``` | ||
|
||
As a result, this is the output: | ||
|
||
``` | ||
dist/ | ||
├── commonjs | ||
│ └── observable-membrane.js | ||
├── modules | ||
│ └── observable-membrane.js | ||
└── umd | ||
├── observable-membrane.js | ||
└── observable-membrane.min.js | ||
``` | ||
|
||
By default, when using this package in node, the `commonjs/` or `modules/` distro will be used. Additionally, you can use the `umd/` version directly in browsers that support `Proxy`. | ||
|
||
## Testing | ||
|
||
When using `yarn test`, it will execute the unit tests using `jest`: | ||
|
||
```bash | ||
yarn test | ||
``` | ||
|
||
## Linter | ||
|
||
When using `yarn lint`, it will lint the `src/` folder using `tslint`: | ||
|
||
```bash | ||
yarn lint | ||
``` | ||
|
||
The above command may display lint issues that are unrelated to your changes. | ||
The recommended way to avoid lint issues is to [configure your | ||
editor][eslint-integrations] to warn you in real time as you edit the file. | ||
|
||
Fixing all existing lint issues is a tedious task so please pitch in by fixing | ||
the ones related to the files you make changes to! | ||
|
||
## Editor Configurations | ||
|
||
Configuring your editor to use our lint and code style rules will help make the | ||
code review process delightful! | ||
|
||
### types | ||
|
||
This project relies on type annotations heavily. | ||
|
||
* Make sure your editor supports [typescript](https://www.typescriptlang.org/). | ||
|
||
### eslint | ||
|
||
[Configure your editor][eslint-integrations] to use our eslint configurations. | ||
|
||
### editorconfig | ||
|
||
[Configure your editor][editorconfig-plugins] to use our editor configurations. | ||
|
||
### Visual Studio Code | ||
|
||
``` | ||
ext install EditorConfig | ||
``` | ||
|
||
## Git Workflow | ||
|
||
The process of submitting a pull request is fairly straightforward and | ||
generally follows the same pattern each time: | ||
|
||
1. [Create a feature branch](#create-a-feature-branch) | ||
1. [Make your changes](#make-your-changes) | ||
1. [Rebase](#rebase) | ||
1. [Check your submission](#check-your-submission) | ||
1. [Create a pull request](#create-a-pull-request) | ||
1. [Update the pull request](#update-the-pull-request) | ||
1. [Commit Message Guidelines](#commit) | ||
|
||
### Create a feature branch | ||
|
||
```bash | ||
git checkout master | ||
git pull origin master | ||
git checkout -b <name-of-the-feature> | ||
``` | ||
|
||
### Make your changes | ||
|
||
Modify the files, build, test, lint and eventually commit your code using the following command: | ||
|
||
```bash | ||
git add <path/to/file/to/commit> | ||
git commit | ||
git push origin <name-of-the-feature> | ||
``` | ||
|
||
The above commands will commit the files into your feature branch. You can keep | ||
pushing new changes into the same branch until you are ready to create a pull | ||
request. | ||
|
||
### Rebase | ||
|
||
Sometimes your feature branch will get stale with respect to the master branch, | ||
and it will require a rebase. The following steps can help: | ||
|
||
```bash | ||
git checkout master | ||
git pull origin master | ||
git checkout <name-of-the-feature> | ||
git rebase master <name-of-the-feature> | ||
``` | ||
|
||
_note: If no conflicts arise, these commands will ensure that your changes are applied on top of the master branch. Any conflicts will have to be manually resolved._ | ||
|
||
### Create a pull request | ||
|
||
If you've never created a pull request before, follow [these | ||
instructions][creating-a-pull-request]. | ||
Pull request title must be formatted according to [Commit Message Guidelines](#commit). | ||
Pull request samples can be found [here](https://github.com/salesforce/observable-membrane/pulls) | ||
|
||
### Update the pull request | ||
|
||
```sh | ||
git fetch origin | ||
git rebase origin/${base_branch} | ||
|
||
# If there were no merge conflicts in the rebase | ||
git push origin ${feature_branch} | ||
|
||
# If there was a merge conflict that was resolved | ||
git push origin ${feature_branch} --force | ||
``` | ||
|
||
_note: If more changes are needed as part of the pull request, just keep committing and pushing your feature branch as described above and the pull request will automatically update._ | ||
|
||
[setup-github-ssh]: https://help.github.com/articles/generating-a-new-ssh-key-and-adding-it-to-the-ssh-agent/ | ||
[creating-a-pull-request]: https://help.github.com/articles/creating-a-pull-request/ | ||
[eslint-integrations]: http://eslint.org/docs/user-guide/integrations | ||
[editorconfig-plugins]: http://editorconfig.org/#download |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,189 @@ | ||
# Observable Membrane | ||
|
||
This package implements an observable membrane in JavaScript using Proxies. | ||
|
||
A membrane can be created to control access to a module graph, observe what the other part is attempting to do with the objects that were handed over to them, and even distort the way they see the module graph. | ||
|
||
One of the prime use-cases for observable membranes is the popular `@observed` or `@tracked` decorator used in components to detect mutations on the state of the component to re-render the component when needed. In this case, any object value set into a decorated field can be wrapped into an observable membrane to monitor if the object is accessed during the rendering phase, and if so, the component must be re-rendered when mutations on the object value are detected. And this process is applied not only at the object value level, but at any level in the object graph accessible via the observed object value. | ||
|
||
Additionally, it supports distorting objects within an object graph, which could be used for: | ||
|
||
* avoid leaking symbols and other non-observables objects. | ||
* distorting values observed through the membrane. | ||
|
||
### Disclaimer | ||
|
||
This is very lightweight library (~1k - minified and gzipped), that can be used with any framework and library that requires a basic membrane. It is designed to be very performant, and be used in production. It has been battle tested at Salesforce in production for over a year. | ||
|
||
### Usage | ||
|
||
This package exposes one constructor as the default export. This constructor, often called `ObservableMembrane` creates a new membrane object that contains two methods, `getProxy` and `getReadOnlyProxy`. The following example illustrate how to create an observable membrane, and proxies: | ||
|
||
```js | ||
import ObservableMembrane from 'observable-membrane'; | ||
|
||
const membrane = new ObservableMembrane(); | ||
|
||
const o = { | ||
x: 2, | ||
y: { | ||
z: 1 | ||
}, | ||
}; | ||
|
||
const p = membrane.getProxy(o); | ||
|
||
p.x; | ||
// yields 4 | ||
|
||
p.y.z // 1 | ||
// yields 1 | ||
``` | ||
|
||
_Note: If the value that your accessing via the membrane is an object that can be observed, the membrane will return a new proxy. In the example above, `o.y !== p.y` because it is a proxy that apply the exact same mechanism. In other words, the membrane is applicable to an entire object graph._ | ||
|
||
#### Observing access and mutations | ||
|
||
The most basic operation in an observable membrane is to observe property member access and mutations. For that, the constructor accepts an optional arguments `options` that accepts two callbacks, `valueObserved` and `valueMutated`: | ||
|
||
```js | ||
import ObservableMembrane from 'observable-membrane'; | ||
|
||
const membrane = new ObservableMembrane({ | ||
valueObserved(target, key) { | ||
// where the target is the object that was accessed | ||
// and the key is the key that was read | ||
console.log('accessed ', key); | ||
}, | ||
valueMutated(target, key) { | ||
// where the target is the object that was mutated | ||
// and the key is the key that was mutated | ||
console.log('mutated ', key); | ||
}, | ||
}); | ||
|
||
const o = { | ||
x: 2, | ||
y: { | ||
z: 1 | ||
}, | ||
}; | ||
|
||
const p = membrane.getProxy(o); | ||
|
||
p.x; | ||
// console output -> 'accessed x' | ||
// yields 4 | ||
|
||
p.y.z; | ||
// console output -> 'accessed z' | ||
// yields 1 | ||
|
||
p.y.z = 3; | ||
// console output -> 'mutated z' | ||
// yields 3 | ||
``` | ||
|
||
#### Read Only Proxies | ||
|
||
Another use-case for observable membranes is to prevent mutations in the object graph. For that, `ObservableMembrane` provides an additional method that get a read only version of any object value. One of the prime use-cases for read only membranes is to hand over an object to another actor, observe how the actor uses that object reference, but prevent the actor for mutating the object. E.g.: passing an object property down to a child component that can consume the object value, but cannot mutated. | ||
|
||
This is also a very cheap way of doing deep-freeze, although it is not exactly the same, but can cover a lot of ground without having to actually freeze the original object, or a copy of it. | ||
|
||
Here is an example on top of the previous one: | ||
|
||
```js | ||
const r = membrane.getReadOnlyProxy(o); | ||
|
||
r.x; | ||
// yields 4 | ||
|
||
r.y.z; | ||
// yields 1 | ||
|
||
r.y.z = 2; | ||
// throws Error in dev-mode, and does nothing in production mode | ||
``` | ||
|
||
#### Distortion | ||
|
||
As described above, you could use distortion to avoid leaking non-observables objects and distorting values observed through the membrane: | ||
|
||
```js | ||
import ObservableMembrane from 'observable-membrane'; | ||
|
||
const membrane = new ObservableMembrane({ | ||
valueDistortion(value) { | ||
if (value instanceof Node) { | ||
throw new ReferenceError(`Invalid access to a non-observable Node`); | ||
} | ||
console.log('distorting ', value); | ||
if (value === 1) { | ||
return 10; | ||
} | ||
return value; | ||
}, | ||
}); | ||
|
||
const o = { | ||
x: 2, | ||
y: { | ||
z: 1, | ||
node: document.createElement('p'), | ||
}, | ||
}; | ||
|
||
const p = membrane.getProxy(o); | ||
|
||
p.x; | ||
// console output -> 'distorting 2' | ||
// yields 2 | ||
|
||
p.y.z; | ||
// console output -> 'distorting 1' | ||
// yields 10 | ||
|
||
p.y.node; | ||
// throws ReferenceError | ||
``` | ||
|
||
_Note: You could use a `WeakMap` to remap symbols to avoid leaking the original symbols and other non-observable objects through the distortion mechanism._ | ||
|
||
#### Unwrapping Proxies | ||
|
||
For advanced usages, the observable membrane instance offers the ability to unwrap any proxy generated by the membrane. This can help to detect membrane presence and other detections that might be useful for framework authorsm, e.g.: | ||
|
||
```js | ||
import ObservableMembrane from 'observable-membrane'; | ||
|
||
const membrane = new ObservableMembrane(); | ||
|
||
const o = { | ||
x: 2, | ||
y: { | ||
z: 1, | ||
}, | ||
}; | ||
|
||
const p = membrane.getProxy(o); | ||
|
||
o.y !== p.x; | ||
// yields true because `p` is a proxy of `o` | ||
|
||
o.y === membrane.unwrapProxy(p.y); | ||
// yields true because `membrane.unwrapProxy(p.y)` returns the original target `o.y` | ||
``` | ||
|
||
## Browser Compatibility | ||
|
||
Observable membranes requires Proxy (ECMAScript 6) [to be available](https://caniuse.com/#search=proxy). | ||
|
||
## Contribution | ||
|
||
Please make sure to read the [Contributing Guide](CONTRIBUTING.md) before making a pull request. | ||
|
||
## License | ||
|
||
[MIT](http://opensource.org/licenses/MIT) | ||
|
||
Copyright (C) 2017 salesforce.com, inc. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
module.exports = { | ||
moduleFileExtensions: ['ts', 'js', 'json'], | ||
transform: { | ||
'.ts': require.resolve('ts-jest/preprocessor.js'), | ||
'.js': require.resolve('ts-jest/preprocessor.js') | ||
}, | ||
testMatch: [ | ||
'<rootDir>/**/__tests__/*.spec.(js|ts)' | ||
], | ||
displayName: 'observable-membrane', | ||
}; |
Oops, something went wrong.