-
-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathPolyfill.ts
More file actions
126 lines (96 loc) · 3.26 KB
/
Polyfill.ts
File metadata and controls
126 lines (96 loc) · 3.26 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
import { exists, outputFile } from 'fs-extra';
import { parse } from 'path';
import { saveAs } from './utility';
export abstract class Polyfill {
mirrorBase = 'https://unpkg.com/';
abstract packageName: string;
dependencies: Polyfill[] = [];
get packageBase() {
return this.mirrorBase + this.packageName;
}
get packageURLs() {
return [this.packageBase];
}
get allPackageURLs(): string[] {
const dependencyURLs = this.dependencies
.map(({ allPackageURLs }) => allPackageURLs)
.flat(Infinity) as string[];
return [...new Set([...dependencyURLs, ...this.sourceURLs])];
}
sourceURLs: string[] = [];
sourceMapURLs: string[] = [];
get detectorPath() {
return `public/feature/${this.constructor.name}.js`;
}
abstract detect(): boolean;
clientPathOf(packageURL: string) {
const { pathname } = new URL(packageURL);
const { ext } = parse(pathname);
return `${pathname}${ext ? '' : '.js'}`;
}
async saveDetector() {
const { dependencies, detectorPath, detect } = this;
for (const polyfill of dependencies) await polyfill.save();
return outputFile(
detectorPath,
`(function () {
var hasFeature = (${detect})();
if (hasFeature) return;
var isView = typeof window !== 'undefined';
var currentURL = isView ? document.currentScript.src : (self.location + '');
var origin = currentURL.split('/').slice(0, 3).join('/');
var paths = ${JSON.stringify(
this.allPackageURLs.map(packageURL => this.clientPathOf(packageURL)),
null,
4
)};
if (!isView)
return paths.forEach(function (path) {
self.importScripts(origin + path);
});
var tags = paths.map(function (path) {
const URL = origin + path;
return URL.match(/\\.css$/i)
? '<link rel="stylesheet" href="' + URL + '">'
: '<script src="' + URL + '"></script>';
});
document.write(tags.join('\\n'));
})();`
);
}
async saveSourceMap(sourceURL: string, sourceCode: string) {
const [_, mapPath] =
sourceCode.match(/^\/\/# sourceMappingURL=(\S+)/m) || [];
if (!mapPath) return;
const { ext } = parse(sourceURL);
const mapURL =
new URL(
mapPath,
ext || sourceURL.endsWith('/') ? sourceURL : `${sourceURL}/`
) + '';
const { finalURL } = await saveAs({
sourceURL: mapURL,
targetPath: 'public'
});
this.sourceMapURLs.push(finalURL);
console.log(`[save] ${finalURL}`);
}
async save() {
if (await exists(this.detectorPath)) return;
for (const sourceURL of this.packageURLs) {
const { finalURL, data } = await saveAs({
sourceURL,
targetExtension: '.js',
targetPath: 'public'
});
this.sourceURLs.push(finalURL);
console.log(`[save] ${finalURL}`);
try {
await this.saveSourceMap(finalURL, data);
} catch (error) {
console.error(error);
}
}
await this.saveDetector();
}
}