Skip to content

Commit 9715320

Browse files
authoredFeb 19, 2024··
fix(es/minifier): Abort property hoister on this usage (#8647)
**Related issue:** - Closes #8643
1 parent d7434be commit 9715320

File tree

5 files changed

+554
-10
lines changed

5 files changed

+554
-10
lines changed
 

‎crates/swc_ecma_minifier/src/compress/optimize/props.rs

+4-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use swc_common::{util::take::Take, DUMMY_SP};
22
use swc_ecma_ast::*;
3-
use swc_ecma_utils::{private_ident, prop_name_eq, ExprExt};
3+
use swc_ecma_utils::{contains_this_expr, private_ident, prop_name_eq, ExprExt};
44

55
use super::{unused::PropertyAccessOpts, Optimizer};
66
use crate::util::deeply_contains_this_expr;
@@ -222,7 +222,9 @@ impl Optimizer<'_> {
222222

223223
fn is_expr_fine_for_hoist_props(value: &Expr) -> bool {
224224
match value {
225-
Expr::Ident(..) | Expr::Lit(..) | Expr::Arrow(..) | Expr::Fn(..) | Expr::Class(..) => true,
225+
Expr::Ident(..) | Expr::Lit(..) | Expr::Arrow(..) | Expr::Class(..) => true,
226+
227+
Expr::Fn(f) => !contains_this_expr(&f.function.body),
226228

227229
Expr::Unary(u) => match u.op {
228230
op!("void") | op!("typeof") | op!("!") => is_expr_fine_for_hoist_props(&u.arg),
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,391 @@
1+
2+
const Cache = {
3+
4+
enabled: false,
5+
6+
files: {},
7+
8+
add: function (key, file) {
9+
10+
if (this.enabled === false) return;
11+
12+
// console.log( 'THREE.Cache', 'Adding key:', key );
13+
14+
this.files[key] = file;
15+
16+
},
17+
18+
get: function (key) {
19+
20+
if (this.enabled === false) return;
21+
22+
// console.log( 'THREE.Cache', 'Checking key:', key );
23+
24+
return this.files[key];
25+
26+
},
27+
28+
remove: function (key) {
29+
30+
delete this.files[key];
31+
32+
},
33+
34+
clear: function () {
35+
36+
this.files = {};
37+
38+
}
39+
40+
};
41+
42+
43+
44+
45+
class Loader {
46+
47+
constructor(manager) {
48+
49+
this.manager = (manager !== undefined) ? manager : DefaultLoadingManager;
50+
51+
this.crossOrigin = 'anonymous';
52+
this.withCredentials = false;
53+
this.path = '';
54+
this.resourcePath = '';
55+
this.requestHeader = {};
56+
57+
}
58+
59+
load( /* url, onLoad, onProgress, onError */) { }
60+
61+
loadAsync(url, onProgress) {
62+
63+
const scope = this;
64+
65+
return new Promise(function (resolve, reject) {
66+
67+
scope.load(url, resolve, onProgress, reject);
68+
69+
});
70+
71+
}
72+
73+
parse( /* data */) { }
74+
75+
setCrossOrigin(crossOrigin) {
76+
77+
this.crossOrigin = crossOrigin;
78+
return this;
79+
80+
}
81+
82+
setWithCredentials(value) {
83+
84+
this.withCredentials = value;
85+
return this;
86+
87+
}
88+
89+
setPath(path) {
90+
91+
this.path = path;
92+
return this;
93+
94+
}
95+
96+
setResourcePath(resourcePath) {
97+
98+
this.resourcePath = resourcePath;
99+
return this;
100+
101+
}
102+
103+
setRequestHeader(requestHeader) {
104+
105+
this.requestHeader = requestHeader;
106+
return this;
107+
108+
}
109+
110+
}
111+
112+
Loader.DEFAULT_MATERIAL_NAME = '__DEFAULT';
113+
114+
const loading = {};
115+
116+
class HttpError extends Error {
117+
118+
constructor(message, response) {
119+
120+
super(message);
121+
this.response = response;
122+
123+
}
124+
125+
}
126+
127+
export class FileLoader extends Loader {
128+
129+
constructor(manager) {
130+
131+
super(manager);
132+
133+
}
134+
135+
load(url, onLoad, onProgress, onError) {
136+
137+
if (url === undefined) url = '';
138+
139+
if (this.path !== undefined) url = this.path + url;
140+
141+
url = this.manager.resolveURL(url);
142+
143+
const cached = Cache.get(url);
144+
145+
if (cached !== undefined) {
146+
147+
this.manager.itemStart(url);
148+
149+
setTimeout(() => {
150+
151+
if (onLoad) onLoad(cached);
152+
153+
this.manager.itemEnd(url);
154+
155+
}, 0);
156+
157+
return cached;
158+
159+
}
160+
161+
// Check if request is duplicate
162+
163+
if (loading[url] !== undefined) {
164+
165+
loading[url].push({
166+
167+
onLoad: onLoad,
168+
onProgress: onProgress,
169+
onError: onError
170+
171+
});
172+
173+
return;
174+
175+
}
176+
177+
// Initialise array for duplicate requests
178+
loading[url] = [];
179+
180+
loading[url].push({
181+
onLoad: onLoad,
182+
onProgress: onProgress,
183+
onError: onError,
184+
});
185+
186+
// create request
187+
const req = new Request(url, {
188+
headers: new Headers(this.requestHeader),
189+
credentials: this.withCredentials ? 'include' : 'same-origin',
190+
// An abort controller could be added within a future PR
191+
});
192+
193+
// record states ( avoid data race )
194+
const mimeType = this.mimeType;
195+
const responseType = this.responseType;
196+
197+
// start the fetch
198+
fetch(req)
199+
.then(response => {
200+
201+
if (response.status === 200 || response.status === 0) {
202+
203+
// Some browsers return HTTP Status 0 when using non-http protocol
204+
// e.g. 'file://' or 'data://'. Handle as success.
205+
206+
if (response.status === 0) {
207+
208+
console.warn('THREE.FileLoader: HTTP Status 0 received.');
209+
210+
}
211+
212+
// Workaround: Checking if response.body === undefined for Alipay browser #23548
213+
214+
if (typeof ReadableStream === 'undefined' || response.body === undefined || response.body.getReader === undefined) {
215+
216+
return response;
217+
218+
}
219+
220+
const callbacks = loading[url];
221+
const reader = response.body.getReader();
222+
223+
// Nginx needs X-File-Size check
224+
// https://serverfault.com/questions/482875/why-does-nginx-remove-content-length-header-for-chunked-content
225+
const contentLength = response.headers.get('Content-Length') || response.headers.get('X-File-Size');
226+
const total = contentLength ? parseInt(contentLength) : 0;
227+
const lengthComputable = total !== 0;
228+
let loaded = 0;
229+
230+
// periodically read data into the new stream tracking while download progress
231+
const stream = new ReadableStream({
232+
start(controller) {
233+
234+
readData();
235+
236+
function readData() {
237+
238+
reader.read().then(({ done, value }) => {
239+
240+
if (done) {
241+
242+
controller.close();
243+
244+
} else {
245+
246+
loaded += value.byteLength;
247+
248+
const event = new ProgressEvent('progress', { lengthComputable, loaded, total });
249+
for (let i = 0, il = callbacks.length; i < il; i++) {
250+
251+
const callback = callbacks[i];
252+
if (callback.onProgress) callback.onProgress(event);
253+
254+
}
255+
256+
controller.enqueue(value);
257+
readData();
258+
259+
}
260+
261+
});
262+
263+
}
264+
265+
}
266+
267+
});
268+
269+
return new Response(stream);
270+
271+
} else {
272+
273+
throw new HttpError(`fetch for "${response.url}" responded with ${response.status}: ${response.statusText}`, response);
274+
275+
}
276+
277+
})
278+
.then(response => {
279+
280+
switch (responseType) {
281+
282+
case 'arraybuffer':
283+
284+
return response.arrayBuffer();
285+
286+
case 'blob':
287+
288+
return response.blob();
289+
290+
case 'document':
291+
292+
return response.text()
293+
.then(text => {
294+
295+
const parser = new DOMParser();
296+
return parser.parseFromString(text, mimeType);
297+
298+
});
299+
300+
case 'json':
301+
302+
return response.json();
303+
304+
default:
305+
306+
if (mimeType === undefined) {
307+
308+
return response.text();
309+
310+
} else {
311+
312+
// sniff encoding
313+
const re = /charset="?([^;"\s]*)"?/i;
314+
const exec = re.exec(mimeType);
315+
const label = exec && exec[1] ? exec[1].toLowerCase() : undefined;
316+
const decoder = new TextDecoder(label);
317+
return response.arrayBuffer().then(ab => decoder.decode(ab));
318+
319+
}
320+
321+
}
322+
323+
})
324+
.then(data => {
325+
326+
// Add to cache only on HTTP success, so that we do not cache
327+
// error response bodies as proper responses to requests.
328+
Cache.add(url, data);
329+
330+
const callbacks = loading[url];
331+
delete loading[url];
332+
333+
for (let i = 0, il = callbacks.length; i < il; i++) {
334+
335+
const callback = callbacks[i];
336+
if (callback.onLoad) callback.onLoad(data);
337+
338+
}
339+
340+
})
341+
.catch(err => {
342+
343+
// Abort errors and other errors are handled the same
344+
345+
const callbacks = loading[url];
346+
347+
if (callbacks === undefined) {
348+
349+
// When onLoad was called and url was deleted in `loading`
350+
this.manager.itemError(url);
351+
throw err;
352+
353+
}
354+
355+
delete loading[url];
356+
357+
for (let i = 0, il = callbacks.length; i < il; i++) {
358+
359+
const callback = callbacks[i];
360+
if (callback.onError) callback.onError(err);
361+
362+
}
363+
364+
this.manager.itemError(url);
365+
366+
})
367+
.finally(() => {
368+
369+
this.manager.itemEnd(url);
370+
371+
});
372+
373+
this.manager.itemStart(url);
374+
375+
}
376+
377+
setResponseType(value) {
378+
379+
this.responseType = value;
380+
return this;
381+
382+
}
383+
384+
setMimeType(value) {
385+
386+
this.mimeType = value;
387+
return this;
388+
389+
}
390+
391+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
const Cache = {
2+
enabled: !1,
3+
files: {},
4+
add: function(key, file) {
5+
!1 !== this.enabled && (this.files[key] = file);
6+
},
7+
get: function(key) {
8+
if (!1 !== this.enabled) return this.files[key];
9+
}
10+
};
11+
class Loader {
12+
constructor(manager){
13+
this.manager = void 0 !== manager ? manager : DefaultLoadingManager, this.crossOrigin = 'anonymous', this.withCredentials = !1, this.path = '', this.resourcePath = '', this.requestHeader = {};
14+
}
15+
load() {}
16+
loadAsync(url, onProgress) {
17+
const scope = this;
18+
return new Promise(function(resolve, reject) {
19+
scope.load(url, resolve, onProgress, reject);
20+
});
21+
}
22+
parse() {}
23+
setCrossOrigin(crossOrigin) {
24+
return this.crossOrigin = crossOrigin, this;
25+
}
26+
setWithCredentials(value) {
27+
return this.withCredentials = value, this;
28+
}
29+
setPath(path) {
30+
return this.path = path, this;
31+
}
32+
setResourcePath(resourcePath) {
33+
return this.resourcePath = resourcePath, this;
34+
}
35+
setRequestHeader(requestHeader) {
36+
return this.requestHeader = requestHeader, this;
37+
}
38+
}
39+
Loader.DEFAULT_MATERIAL_NAME = '__DEFAULT';
40+
const loading = {};
41+
class HttpError extends Error {
42+
constructor(message, response){
43+
super(message), this.response = response;
44+
}
45+
}
46+
export class FileLoader extends Loader {
47+
constructor(manager){
48+
super(manager);
49+
}
50+
load(url, onLoad, onProgress, onError) {
51+
void 0 === url && (url = ''), void 0 !== this.path && (url = this.path + url), url = this.manager.resolveURL(url);
52+
const cached = Cache.get(url);
53+
if (void 0 !== cached) return this.manager.itemStart(url), setTimeout(()=>{
54+
onLoad && onLoad(cached), this.manager.itemEnd(url);
55+
}, 0), cached;
56+
if (void 0 !== loading[url]) {
57+
loading[url].push({
58+
onLoad: onLoad,
59+
onProgress: onProgress,
60+
onError: onError
61+
});
62+
return;
63+
}
64+
loading[url] = [], loading[url].push({
65+
onLoad: onLoad,
66+
onProgress: onProgress,
67+
onError: onError
68+
});
69+
const req = new Request(url, {
70+
headers: new Headers(this.requestHeader),
71+
credentials: this.withCredentials ? 'include' : 'same-origin'
72+
}), mimeType = this.mimeType, responseType = this.responseType;
73+
fetch(req).then((response)=>{
74+
if (200 === response.status || 0 === response.status) {
75+
if (0 === response.status && console.warn('THREE.FileLoader: HTTP Status 0 received.'), 'undefined' == typeof ReadableStream || void 0 === response.body || void 0 === response.body.getReader) return response;
76+
const callbacks = loading[url], reader = response.body.getReader(), contentLength = response.headers.get('Content-Length') || response.headers.get('X-File-Size'), total = contentLength ? parseInt(contentLength) : 0, lengthComputable = 0 !== total;
77+
let loaded = 0;
78+
return new Response(new ReadableStream({
79+
start (controller) {
80+
(function readData() {
81+
reader.read().then(({ done, value })=>{
82+
if (done) controller.close();
83+
else {
84+
const event = new ProgressEvent('progress', {
85+
lengthComputable,
86+
loaded: loaded += value.byteLength,
87+
total
88+
});
89+
for(let i = 0, il = callbacks.length; i < il; i++){
90+
const callback = callbacks[i];
91+
callback.onProgress && callback.onProgress(event);
92+
}
93+
controller.enqueue(value), readData();
94+
}
95+
});
96+
})();
97+
}
98+
}));
99+
}
100+
throw new HttpError(`fetch for "${response.url}" responded with ${response.status}: ${response.statusText}`, response);
101+
}).then((response)=>{
102+
switch(responseType){
103+
case 'arraybuffer':
104+
return response.arrayBuffer();
105+
case 'blob':
106+
return response.blob();
107+
case 'document':
108+
return response.text().then((text)=>new DOMParser().parseFromString(text, mimeType));
109+
case 'json':
110+
return response.json();
111+
default:
112+
if (void 0 === mimeType) return response.text();
113+
{
114+
const exec = /charset="?([^;"\s]*)"?/i.exec(mimeType), decoder = new TextDecoder(exec && exec[1] ? exec[1].toLowerCase() : void 0);
115+
return response.arrayBuffer().then((ab)=>decoder.decode(ab));
116+
}
117+
}
118+
}).then((data)=>{
119+
Cache.add(url, data);
120+
const callbacks = loading[url];
121+
delete loading[url];
122+
for(let i = 0, il = callbacks.length; i < il; i++){
123+
const callback = callbacks[i];
124+
callback.onLoad && callback.onLoad(data);
125+
}
126+
}).catch((err)=>{
127+
const callbacks = loading[url];
128+
if (void 0 === callbacks) throw this.manager.itemError(url), err;
129+
delete loading[url];
130+
for(let i = 0, il = callbacks.length; i < il; i++){
131+
const callback = callbacks[i];
132+
callback.onError && callback.onError(err);
133+
}
134+
this.manager.itemError(url);
135+
}).finally(()=>{
136+
this.manager.itemEnd(url);
137+
}), this.manager.itemStart(url);
138+
}
139+
setResponseType(value) {
140+
return this.responseType = value, this;
141+
}
142+
setMimeType(value) {
143+
return this.mimeType = value, this;
144+
}
145+
}
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1-
console.log(1, 1, function () {
2-
return this === this;
3-
});
1+
var o = {
2+
u: function() {
3+
return this === this;
4+
},
5+
p: 1
6+
};
7+
console.log(o.p, o.p, o.u);
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1-
console.log(
2-
new (function (a) {
1+
var o = {
2+
a: 1,
3+
b: 2,
4+
f: function(a) {
35
this.b = a;
4-
})(1).b,
5-
2
6-
);
6+
}
7+
};
8+
console.log(new o.f(o.a).b, o.b);

0 commit comments

Comments
 (0)
Please sign in to comment.