Skip to content

Commit 3c2dbc1

Browse files
committed
feat: add proxy polyfill
1 parent 7c7df5f commit 3c2dbc1

File tree

16 files changed

+336
-16
lines changed

16 files changed

+336
-16
lines changed

config/build.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,8 @@ const esbuildOptions = {
3939
outfile: "index.js",
4040
bundle: true,
4141
format: "cjs",
42-
minify: true, // 开启压缩混淆
42+
minify: true,
43+
external: ["rfdc", "fast-deep-equal"]
4344
};
4445

4546
export interface BuildConfig {

demo/computed/data-tracer.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

demo/computed/index.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

demo/miniprogram_npm/fast-deep-equal/index.js

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
/*
2+
* Copyright 2018 Google Inc. All rights reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
5+
* use this file except in compliance with the License. You may obtain a copy of
6+
* the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12+
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13+
* License for the specific language governing permissions and limitations under
14+
* the License.
15+
*/
16+
17+
/**
18+
* @externs
19+
* @fileoverview Externs so that Closure doesn't complain about environment checks.
20+
*/
21+
22+
/**
23+
* @type {Object}
24+
*/
25+
var process;
26+
27+
/**
28+
* @type {Object}
29+
*/
30+
var global;
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/*
2+
* Copyright 2018 Google Inc. All rights reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
5+
* use this file except in compliance with the License. You may obtain a copy of
6+
* the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12+
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13+
* License for the specific language governing permissions and limitations under
14+
* the License.
15+
*/
16+
17+
'use strict';
18+
19+
(function(scope) {
20+
if (scope['Proxy']) {
21+
return;
22+
}
23+
scope.Proxy = require('./proxy.js')();
24+
scope.Proxy['revocable'] = scope.Proxy.revocable;
25+
})(
26+
('undefined' !== typeof process &&
27+
'[object process]' === {}.toString.call(process)) ||
28+
('undefined' !== typeof navigator && navigator.product === 'ReactNative')
29+
? global
30+
: self
31+
);
32+
Lines changed: 236 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,236 @@
1+
/*
2+
* Copyright 2016 Google Inc. All rights reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
5+
* use this file except in compliance with the License. You may obtain a copy of
6+
* the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12+
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13+
* License for the specific language governing permissions and limitations under
14+
* the License.
15+
*/
16+
17+
module.exports = function proxyPolyfill() {
18+
let lastRevokeFn = null;
19+
let ProxyPolyfill;
20+
21+
/**
22+
* @param {*} o
23+
* @return {boolean} whether this is probably a (non-null) Object
24+
*/
25+
function isObject(o) {
26+
return o ? (typeof o === 'object' || typeof o === 'function') : false;
27+
}
28+
29+
function validateProto(proto) {
30+
if (proto !== null && !isObject(proto)) {
31+
throw new TypeError('Object prototype may only be an Object or null: ' + proto);
32+
}
33+
}
34+
35+
const $Object = Object;
36+
37+
// Closure assumes that `{__proto__: null} instanceof Object` is always true, hence why we check against a different name.
38+
const canCreateNullProtoObjects = Boolean($Object.create) || !({ __proto__: null } instanceof $Object);
39+
const objectCreate =
40+
$Object.create ||
41+
(canCreateNullProtoObjects
42+
? function create(proto) {
43+
validateProto(proto);
44+
return { __proto__: proto };
45+
}
46+
: function create(proto) {
47+
validateProto(proto);
48+
if (proto === null) {
49+
throw new SyntaxError('Native Object.create is required to create objects with null prototype');
50+
}
51+
52+
// nb. cast to convince Closure compiler that this is a constructor
53+
var T = /** @type {!Function} */ (function T() {});
54+
T.prototype = proto;
55+
return new T();
56+
});
57+
58+
const noop = function() { return null; };
59+
60+
const getProto =
61+
$Object.getPrototypeOf ||
62+
([].__proto__ === Array.prototype
63+
? function getPrototypeOf(O) {
64+
// If O.[[Prototype]] === null, then the __proto__ accessor won't exist,
65+
// as it's inherited from `Object.prototype`
66+
const proto = O.__proto__;
67+
return isObject(proto) ? proto : null;
68+
}
69+
: noop);
70+
71+
/**
72+
* @constructor
73+
* @param {!Object} target
74+
* @param {{apply, construct, get, set}} handler
75+
*/
76+
ProxyPolyfill = function(target, handler) {
77+
const newTarget = this && this instanceof ProxyPolyfill ? this.constructor : undefined;
78+
if (newTarget === undefined) {
79+
throw new TypeError("Constructor Proxy requires 'new'");
80+
}
81+
82+
if (!isObject(target) || !isObject(handler)) {
83+
throw new TypeError('Cannot create proxy with a non-object as target or handler');
84+
}
85+
86+
// Construct revoke function, and set lastRevokeFn so that Proxy.revocable can steal it.
87+
// The caller might get the wrong revoke function if a user replaces or wraps scope.Proxy
88+
// to call itself, but that seems unlikely especially when using the polyfill.
89+
let throwRevoked = function() {};
90+
lastRevokeFn = function() {
91+
/** @suppress {checkTypes} */
92+
target = null; // clear ref
93+
throwRevoked = function(trap) {
94+
throw new TypeError(`Cannot perform '${trap}' on a proxy that has been revoked`);
95+
};
96+
};
97+
setTimeout(function() {
98+
lastRevokeFn = null;
99+
}, 0);
100+
101+
// Fail on unsupported traps: Chrome doesn't do this, but ensure that users of the polyfill
102+
// are a bit more careful. Copy the internal parts of handler to prevent user changes.
103+
const unsafeHandler = handler;
104+
handler = { 'get': null, 'set': null, 'apply': null, 'construct': null };
105+
for (let k in unsafeHandler) {
106+
if (!(k in handler)) {
107+
throw new TypeError(`Proxy polyfill does not support trap '${k}'`);
108+
}
109+
handler[k] = unsafeHandler[k];
110+
}
111+
if (typeof unsafeHandler === 'function') {
112+
// Allow handler to be a function (which has an 'apply' method). This matches what is
113+
// probably a bug in native versions. It treats the apply call as a trap to be configured.
114+
handler.apply = unsafeHandler.apply.bind(unsafeHandler);
115+
}
116+
117+
// Define proxy as an object that extends target.[[Prototype]],
118+
// or a Function (if either it's callable, or apply is set).
119+
const proto = getProto(target); // can return null in old browsers
120+
let proxy;
121+
let isMethod = false;
122+
let isArray = false;
123+
if (typeof target === 'function') {
124+
proxy = function ProxyPolyfill() {
125+
const usingNew = (this && this.constructor === proxy);
126+
const args = Array.prototype.slice.call(arguments);
127+
throwRevoked(usingNew ? 'construct' : 'apply');
128+
129+
// TODO(samthor): Closure compiler doesn't know about 'construct', attempts to rename it.
130+
if (usingNew && handler['construct']) {
131+
return handler['construct'].call(this, target, args);
132+
} else if (!usingNew && handler.apply) {
133+
return handler['apply'](target, this, args);
134+
}
135+
136+
// since the target was a function, fallback to calling it directly.
137+
if (usingNew) {
138+
// inspired by answers to https://stackoverflow.com/q/1606797
139+
args.unshift(target); // pass class as first arg to constructor, although irrelevant
140+
// nb. cast to convince Closure compiler that this is a constructor
141+
const f = /** @type {!Function} */ (target.bind.apply(target, args));
142+
return new f();
143+
}
144+
return target.apply(this, args);
145+
};
146+
isMethod = true;
147+
} else if (target instanceof Array) {
148+
proxy = [];
149+
isArray = true;
150+
} else {
151+
proxy = (canCreateNullProtoObjects || proto !== null) ? objectCreate(proto) : {};
152+
}
153+
154+
// Create default getters/setters. Create different code paths as handler.get/handler.set can't
155+
// change after creation.
156+
const getter = handler.get ? function(prop) {
157+
throwRevoked('get');
158+
return handler.get(this, prop, proxy);
159+
} : function(prop) {
160+
throwRevoked('get');
161+
return this[prop];
162+
};
163+
const setter = handler.set ? function(prop, value) {
164+
throwRevoked('set');
165+
const status = handler.set(this, prop, value, proxy);
166+
// TODO(samthor): If the calling code is in strict mode, throw TypeError.
167+
// if (!status) {
168+
// It's (sometimes) possible to work this out, if this code isn't strict- try to load the
169+
// callee, and if it's available, that code is non-strict. However, this isn't exhaustive.
170+
// }
171+
} : function(prop, value) {
172+
throwRevoked('set');
173+
this[prop] = value;
174+
};
175+
176+
// Clone direct properties (i.e., not part of a prototype).
177+
const propertyNames = $Object.getOwnPropertyNames(target);
178+
const propertyMap = {};
179+
propertyNames.forEach(function(prop) {
180+
if ((isMethod || isArray) && prop in proxy) {
181+
return; // ignore properties already here, e.g. 'bind', 'prototype' etc
182+
}
183+
const real = $Object.getOwnPropertyDescriptor(target, prop);
184+
const desc = {
185+
enumerable: Boolean(real.enumerable),
186+
get: getter.bind(target, prop),
187+
set: setter.bind(target, prop),
188+
};
189+
$Object.defineProperty(proxy, prop, desc);
190+
propertyMap[prop] = true;
191+
});
192+
193+
// Set the prototype, or clone all prototype methods (always required if a getter is provided).
194+
// TODO(samthor): We don't allow prototype methods to be set. It's (even more) awkward.
195+
// An alternative here would be to _just_ clone methods to keep behavior consistent.
196+
let prototypeOk = true;
197+
if (isMethod || isArray) {
198+
// Arrays and methods are special: above, we instantiate boring versions of these then swap
199+
// our their prototype later. So we only need to use setPrototypeOf in these cases. Some old
200+
// engines support `Object.getPrototypeOf` but not `Object.setPrototypeOf`.
201+
const setProto =
202+
$Object.setPrototypeOf ||
203+
([].__proto__ === Array.prototype
204+
? function setPrototypeOf(O, proto) {
205+
validateProto(proto);
206+
O.__proto__ = proto;
207+
return O;
208+
}
209+
: noop);
210+
if (!(proto && setProto(proxy, proto))) {
211+
prototypeOk = false;
212+
}
213+
}
214+
if (handler.get || !prototypeOk) {
215+
for (let k in target) {
216+
if (propertyMap[k]) {
217+
continue;
218+
}
219+
$Object.defineProperty(proxy, k, { get: getter.bind(target, k) });
220+
}
221+
}
222+
223+
// The Proxy polyfill cannot handle adding new properties. Seal the target and proxy.
224+
$Object.seal(target);
225+
$Object.seal(proxy);
226+
227+
return proxy; // nb. if isMethod is true, proxy != this
228+
};
229+
230+
ProxyPolyfill.revocable = function(target, handler) {
231+
const p = new ProxyPolyfill(target, handler);
232+
return { 'proxy': p, 'revoke': lastRevokeFn };
233+
};
234+
235+
return ProxyPolyfill;
236+
}

demo/miniprogram_npm/rfdc/index.js

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

demo/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
"fast-deep-equal": "^2.0.1",
44
"mobx-miniprogram": "^4.13.2",
55
"mobx-miniprogram-bindings": "^2.0.0",
6-
"rfdc": "^1.1.4"
6+
"rfdc": "^1.1.4",
7+
"proxy-polyfill": "^0.3.2"
78
}
89
}

demo/project.config.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@
4242
"useSummerCompiler": false
4343
},
4444
"compileType": "miniprogram",
45-
"libVersion": "2.19.2",
45+
"libVersion": "2.19.3",
4646
"appid": "wx1a116dcfe0c2de16",
4747
"projectname": "computed-watch",
4848
"debugOptions": {

0 commit comments

Comments
 (0)