@occultist/extensions 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/dist/css-parser.d.ts +21 -0
- package/dist/css-parser.js +67 -0
- package/dist/dependancy-graph.d.ts +17 -0
- package/dist/dependancy-graph.js +74 -0
- package/dist/file-info.d.ts +97 -0
- package/dist/file-info.js +66 -0
- package/dist/html-parser.d.ts +11 -0
- package/dist/html-parser.js +171 -0
- package/dist/js-parser.d.ts +14 -0
- package/dist/js-parser.js +74 -0
- package/dist/static-extension.d.ts +79 -0
- package/dist/static-extension.js +273 -0
- package/dist/ts-preprocessor.d.ts +8 -0
- package/dist/ts-preprocessor.js +69 -0
- package/dist/types.d.ts +68 -0
- package/dist/types.js +1 -0
- package/lib/static/css-parser.ts +98 -0
- package/lib/static/dependancy-graph.ts +101 -0
- package/lib/static/file-info.ts +153 -0
- package/lib/static/html-parser.ts +225 -0
- package/lib/static/js-parser.ts +107 -0
- package/lib/static/static-extension.ts +400 -0
- package/lib/static/ts-preprocessor.ts +114 -0
- package/lib/static/types.ts +120 -0
- package/package.json +50 -0
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import type {FileInfo} from "./file-info.ts";
|
|
2
|
+
import type {PolicyDirective, ReferenceDetails} from "./types.ts";
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
export class DependancyMap {
|
|
6
|
+
file: FileInfo;
|
|
7
|
+
references: ReferenceDetails[];
|
|
8
|
+
polices: Map<PolicyDirective, ReferenceDetails[]> = new Map();
|
|
9
|
+
|
|
10
|
+
constructor(file: FileInfo, references: ReferenceDetails[]) {
|
|
11
|
+
this.file = file;
|
|
12
|
+
this.references = references;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
finalize() {
|
|
16
|
+
let reference: ReferenceDetails;
|
|
17
|
+
let policies: ReferenceDetails[];
|
|
18
|
+
|
|
19
|
+
for (let i = 0; i < this.references.length; i++) {
|
|
20
|
+
reference = this.references[i];
|
|
21
|
+
policies = this.polices.get(reference.directive);
|
|
22
|
+
|
|
23
|
+
if (policies == null) {
|
|
24
|
+
policies = [];
|
|
25
|
+
this.polices.set(reference.directive, policies);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
policies.push(reference);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export class DependancyGraph {
|
|
34
|
+
dependancies: Map<string, DependancyMap> = new Map();
|
|
35
|
+
|
|
36
|
+
constructor(dependancies: Map<string, DependancyMap>) {
|
|
37
|
+
this.dependancies = dependancies;
|
|
38
|
+
|
|
39
|
+
for (const dependancyMap of this.dependancies.values()) {
|
|
40
|
+
for (let i = 0; i < dependancyMap.references.length; i++) {
|
|
41
|
+
const reference = dependancyMap.references[i];
|
|
42
|
+
|
|
43
|
+
if (reference.file == null) {
|
|
44
|
+
console.warn(`Unknown dependancy reference ${reference.url}`);
|
|
45
|
+
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const other = this.dependancies.get(reference.file.alias);
|
|
50
|
+
|
|
51
|
+
if (other == null) {
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
for (let j = 0; j < other.references.length; j++) {
|
|
56
|
+
dependancyMap.references.push(other.references[j]);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
dependancyMap.finalize();
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
Object.freeze(this.dependancies);
|
|
64
|
+
Object.freeze(this);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Returns a debug string for this dependancy graph.
|
|
69
|
+
*/
|
|
70
|
+
debug(): string {
|
|
71
|
+
let debug = 'Dependancy Graph\n----------------\n\n';
|
|
72
|
+
let dependancy: DependancyMap;
|
|
73
|
+
const dependancies = Array.from(this.dependancies.values());
|
|
74
|
+
|
|
75
|
+
for (let i = 0; i < dependancies.length; i++) {
|
|
76
|
+
if (i !== 0) debug += '\n';
|
|
77
|
+
|
|
78
|
+
dependancy = dependancies[i];
|
|
79
|
+
debug += dependancy.file.alias + '\n';
|
|
80
|
+
|
|
81
|
+
const polices = Array.from(dependancy.polices.entries());
|
|
82
|
+
|
|
83
|
+
if (polices.length === 0) {
|
|
84
|
+
debug += ` No dependancies\n`;
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
for (const [policy, references] of dependancy.polices.entries()) {
|
|
89
|
+
debug += ` ${policy}\n`;
|
|
90
|
+
|
|
91
|
+
for (let j = 0; j < references.length; j++) {
|
|
92
|
+
debug += ` ${references[j].url}\n`;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
debug += '----------------\n';
|
|
98
|
+
return debug;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import {join} from "node:path";
|
|
2
|
+
|
|
3
|
+
export type FileInfo = {
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* The name of the file.
|
|
7
|
+
*/
|
|
8
|
+
name: string;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Path of the file relative to the configured directory.
|
|
12
|
+
*/
|
|
13
|
+
relativePath: string;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Full path to the file.
|
|
17
|
+
*/
|
|
18
|
+
absolutePath: string;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* The alias to the directory where the file is located in.
|
|
22
|
+
*/
|
|
23
|
+
directoryAlias: string;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* The file's extension.
|
|
27
|
+
*/
|
|
28
|
+
extension: string;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* The file's language if detected.
|
|
32
|
+
*/
|
|
33
|
+
lang?: string;
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* The file's content type.
|
|
37
|
+
*/
|
|
38
|
+
contentType: string;
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* This files directory + relative path + name.
|
|
42
|
+
*/
|
|
43
|
+
alias: string;
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* The hash of the original file contents.
|
|
47
|
+
*/
|
|
48
|
+
hash: string;
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* The URL of the file.
|
|
52
|
+
*/
|
|
53
|
+
url: string;
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* alias url for this resource.
|
|
57
|
+
*/
|
|
58
|
+
aliasURL: string;
|
|
59
|
+
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Contains information about a static file.
|
|
64
|
+
*/
|
|
65
|
+
export class WorkingFileInfo {
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* The name of the file.
|
|
69
|
+
*/
|
|
70
|
+
name: string;
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Path of the file relative to the configured directory.
|
|
74
|
+
*/
|
|
75
|
+
relativePath: string;
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Full path to the file.
|
|
79
|
+
*/
|
|
80
|
+
absolutePath: string;
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* The alias to the directory where the file is located in.
|
|
84
|
+
*/
|
|
85
|
+
directoryAlias: string;
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* The file's extension.
|
|
89
|
+
*/
|
|
90
|
+
extension: string;
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* The file's language if detected.
|
|
94
|
+
*/
|
|
95
|
+
lang?: string;
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* The file's content type.
|
|
99
|
+
*/
|
|
100
|
+
contentType: string;
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* This files directory + relative path + name.
|
|
104
|
+
*/
|
|
105
|
+
alias: string;
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* The hash of the original file contents.
|
|
109
|
+
*/
|
|
110
|
+
hash: string | null = null;
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* The URL of the file.
|
|
114
|
+
*/
|
|
115
|
+
url: string | null = null;
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* alias url for this resource.
|
|
119
|
+
*/
|
|
120
|
+
aliasURL: string | null = null;
|
|
121
|
+
|
|
122
|
+
constructor(
|
|
123
|
+
name: string,
|
|
124
|
+
directoryAlias: string,
|
|
125
|
+
relativePath: string,
|
|
126
|
+
absolutePath: string,
|
|
127
|
+
extension: string,
|
|
128
|
+
contentType: string,
|
|
129
|
+
lang?: string,
|
|
130
|
+
) {
|
|
131
|
+
this.name = name;
|
|
132
|
+
this.directoryAlias = directoryAlias
|
|
133
|
+
this.relativePath = relativePath;
|
|
134
|
+
this.absolutePath = absolutePath;
|
|
135
|
+
this.extension = extension;
|
|
136
|
+
this.contentType = contentType;
|
|
137
|
+
this.lang = lang;
|
|
138
|
+
this.alias = join(directoryAlias, relativePath);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
finalize(
|
|
142
|
+
hash: string,
|
|
143
|
+
url: string,
|
|
144
|
+
aliasURL: string,
|
|
145
|
+
) {
|
|
146
|
+
this.hash = hash;
|
|
147
|
+
this.url = url;
|
|
148
|
+
this.aliasURL = aliasURL;
|
|
149
|
+
|
|
150
|
+
Object.freeze(this);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
}
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
import {readFile} from "fs/promises";
|
|
2
|
+
import {DependancyMap} from "./dependancy-graph.ts";
|
|
3
|
+
import {type FileInfo} from "./file-info.ts";
|
|
4
|
+
import type {PolicyDirective, ReferenceDetails, ReferenceParser} from "./types.ts";
|
|
5
|
+
import { JSDOM } from 'jsdom';
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
const asMap: Record<string, PolicyDirective> = {
|
|
9
|
+
audio: 'media-src',
|
|
10
|
+
document: 'frame-src',
|
|
11
|
+
embed: 'object-src',
|
|
12
|
+
fetch: 'connect-src',
|
|
13
|
+
font: 'font-src',
|
|
14
|
+
image: 'img-src',
|
|
15
|
+
object: 'object-src',
|
|
16
|
+
script: 'script-src',
|
|
17
|
+
style: 'style-src',
|
|
18
|
+
track: 'media-src',
|
|
19
|
+
video: 'media-src',
|
|
20
|
+
worker: 'worker-src',
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export type HTMLParserArgs = {
|
|
24
|
+
contentType?: string | string[];
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export class HTMLParser implements ReferenceParser {
|
|
28
|
+
supports: Set<string> = new Set([
|
|
29
|
+
'text/html',
|
|
30
|
+
'application/xhtml+xml',
|
|
31
|
+
]);
|
|
32
|
+
|
|
33
|
+
constructor(args: HTMLParserArgs = {}) {
|
|
34
|
+
if (Array.isArray(args?.contentType)) {
|
|
35
|
+
this.supports = new Set(args.contentType);
|
|
36
|
+
} else if (args?.contentType != null) {
|
|
37
|
+
this.supports = new Set([args.contentType]);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async parse(
|
|
42
|
+
content: Blob,
|
|
43
|
+
file: FileInfo,
|
|
44
|
+
filesByURL: Map<string, FileInfo>,
|
|
45
|
+
): Promise<ReferenceDetails[]> {
|
|
46
|
+
let references: ReferenceDetails[] = [];
|
|
47
|
+
let src: string;
|
|
48
|
+
let rel: string;
|
|
49
|
+
let as: string;
|
|
50
|
+
let url: string;
|
|
51
|
+
const text = await content.text();
|
|
52
|
+
const dom = new JSDOM(text, {
|
|
53
|
+
contentType: file.contentType,
|
|
54
|
+
});
|
|
55
|
+
const document = dom.window.document;
|
|
56
|
+
|
|
57
|
+
for (const element of document.querySelectorAll('link')) {
|
|
58
|
+
src = element.getAttribute('src');
|
|
59
|
+
rel = element.getAttribute('rel');
|
|
60
|
+
as = element.getAttribute('as');
|
|
61
|
+
url = new URL(src, file.aliasURL).toString();
|
|
62
|
+
|
|
63
|
+
if (rel === 'stylesheet') {
|
|
64
|
+
references.push({
|
|
65
|
+
url,
|
|
66
|
+
directive: 'style-src',
|
|
67
|
+
file: filesByURL.get(url),
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (Object.hasOwn(asMap, as)) {
|
|
74
|
+
references.push({
|
|
75
|
+
url,
|
|
76
|
+
directive: asMap[as],
|
|
77
|
+
file: filesByURL.get(url),
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
references.push({
|
|
84
|
+
url,
|
|
85
|
+
file: filesByURL.get(url),
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
for (const element of [
|
|
90
|
+
...document.querySelectorAll('img'),
|
|
91
|
+
...document.querySelectorAll('picture source'),
|
|
92
|
+
]) {
|
|
93
|
+
src = element.tagName.toLowerCase() === 'source'
|
|
94
|
+
? element.getAttribute('srcset')
|
|
95
|
+
: element.getAttribute('src')
|
|
96
|
+
|
|
97
|
+
if (src == null) {
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
url = new URL(src, file.aliasURL).toString();
|
|
102
|
+
|
|
103
|
+
references.push({
|
|
104
|
+
url,
|
|
105
|
+
directive: 'img-src',
|
|
106
|
+
file: filesByURL.get(url),
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
for (const element of [
|
|
111
|
+
...document.querySelectorAll('track'),
|
|
112
|
+
...document.querySelectorAll('audio'),
|
|
113
|
+
...document.querySelectorAll('video'),
|
|
114
|
+
...document.querySelectorAll('video source'),
|
|
115
|
+
]) {
|
|
116
|
+
src = element.getAttribute('src');
|
|
117
|
+
|
|
118
|
+
if (src == null) {
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
url = new URL(src, file.aliasURL).toString();
|
|
123
|
+
|
|
124
|
+
references.push({
|
|
125
|
+
url,
|
|
126
|
+
directive: 'media-src',
|
|
127
|
+
file: filesByURL.get(url),
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
for (const element of [
|
|
132
|
+
...document.querySelectorAll('iframe'),
|
|
133
|
+
...document.querySelectorAll('fencedframe'),
|
|
134
|
+
]) {
|
|
135
|
+
src = element.getAttribute('src');
|
|
136
|
+
|
|
137
|
+
if (src == null) {
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
url = new URL(src, file.aliasURL).toString();
|
|
142
|
+
|
|
143
|
+
references.push({
|
|
144
|
+
url,
|
|
145
|
+
directive: 'child-src',
|
|
146
|
+
file: filesByURL.get(url),
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
for (const element of document.querySelectorAll('script')) {
|
|
151
|
+
src = element.getAttribute('src');
|
|
152
|
+
|
|
153
|
+
if (src == null) {
|
|
154
|
+
continue;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
url = new URL(src, file.aliasURL).toString();
|
|
158
|
+
|
|
159
|
+
references.push({
|
|
160
|
+
url,
|
|
161
|
+
directive: 'script-src',
|
|
162
|
+
file: filesByURL.get(url),
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return references;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
async update(
|
|
170
|
+
content: Blob,
|
|
171
|
+
file: FileInfo,
|
|
172
|
+
filesByURL: Map<string, FileInfo>,
|
|
173
|
+
): Promise<Blob> {
|
|
174
|
+
let url: string | undefined;
|
|
175
|
+
let ref: FileInfo | undefined;
|
|
176
|
+
let text = await content.text();
|
|
177
|
+
const dom = new JSDOM(text, {
|
|
178
|
+
contentType: file.contentType,
|
|
179
|
+
});
|
|
180
|
+
const document = dom.window.document;
|
|
181
|
+
|
|
182
|
+
for (const element of [
|
|
183
|
+
...document.querySelectorAll('link'),
|
|
184
|
+
...document.querySelectorAll('img'),
|
|
185
|
+
...document.querySelectorAll('track'),
|
|
186
|
+
...document.querySelectorAll('audio'),
|
|
187
|
+
...document.querySelectorAll('video'),
|
|
188
|
+
...document.querySelectorAll('video source'),
|
|
189
|
+
...document.querySelectorAll('iframe'),
|
|
190
|
+
...document.querySelectorAll('fencedframe'),
|
|
191
|
+
...document.querySelectorAll('script'),
|
|
192
|
+
]) {
|
|
193
|
+
const src = element.getAttribute('src');
|
|
194
|
+
|
|
195
|
+
if (src == null) {
|
|
196
|
+
continue;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
url = new URL(src, file.aliasURL).toString();
|
|
200
|
+
ref = filesByURL.get(url);
|
|
201
|
+
|
|
202
|
+
if (ref == null) continue;
|
|
203
|
+
|
|
204
|
+
element.setAttribute('src', ref.url);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
for (const element of document.querySelectorAll('picture source')) {
|
|
208
|
+
const src = element.getAttribute('srcset');
|
|
209
|
+
|
|
210
|
+
if (src == null) {
|
|
211
|
+
continue;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
url = new URL(src, file.aliasURL).toString();
|
|
215
|
+
ref = filesByURL.get(url);
|
|
216
|
+
|
|
217
|
+
if (ref == null) continue;
|
|
218
|
+
|
|
219
|
+
element.setAttribute('srcset', ref.url);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
return new Blob([dom.serialize()], { type: file.contentType });
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import {parse, type ImportDeclaration, type ImportExpression, type Literal, type Node, type Options} from 'acorn';
|
|
2
|
+
import {generate} from 'astring';
|
|
3
|
+
import {walk} from 'zimmerframe';
|
|
4
|
+
import {type FileInfo} from "./file-info.ts";
|
|
5
|
+
import type {FilesByURL, ReferenceDetails, ReferenceParser} from "./types.ts";
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
export type JSReferenceParserArgs = {
|
|
9
|
+
contentType?: string | string[];
|
|
10
|
+
acornOptions?: Options;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export class JSReferenceParser implements ReferenceParser {
|
|
14
|
+
|
|
15
|
+
supports: Set<string> = new Set(['application/javascript']);
|
|
16
|
+
|
|
17
|
+
#acornOptions: Options;
|
|
18
|
+
|
|
19
|
+
constructor(args: JSReferenceParserArgs = {}) {
|
|
20
|
+
if (Array.isArray(args?.contentType)) {
|
|
21
|
+
this.supports = new Set(args.contentType);
|
|
22
|
+
} else if (args?.contentType != null) {
|
|
23
|
+
this.supports = new Set([args.contentType]);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
this.#acornOptions = args.acornOptions ?? {
|
|
27
|
+
ecmaVersion: 'latest',
|
|
28
|
+
sourceType: 'module',
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async parse(
|
|
33
|
+
content: Blob,
|
|
34
|
+
file: FileInfo,
|
|
35
|
+
filesByURL: FilesByURL,
|
|
36
|
+
): Promise<ReferenceDetails[]> {
|
|
37
|
+
let references: ReferenceDetails[] = [];
|
|
38
|
+
let url: string;
|
|
39
|
+
const text = await content.text();
|
|
40
|
+
const ast = parse(text, this.#acornOptions);
|
|
41
|
+
|
|
42
|
+
walk(ast as Node, {}, {
|
|
43
|
+
ImportDeclaration(node: ImportDeclaration, { next }) {
|
|
44
|
+
url = new URL((node.source as Literal).value as string, file.aliasURL).toString();
|
|
45
|
+
|
|
46
|
+
references.push({
|
|
47
|
+
url,
|
|
48
|
+
directive: 'script-src',
|
|
49
|
+
file: filesByURL.get(url),
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
next();
|
|
53
|
+
},
|
|
54
|
+
ImportExpression(node: ImportExpression, { next }) {
|
|
55
|
+
url = new URL((node.source as Literal).value as string, file.aliasURL).toString();
|
|
56
|
+
|
|
57
|
+
references.push({
|
|
58
|
+
url,
|
|
59
|
+
directive: 'script-src',
|
|
60
|
+
file: filesByURL.get(url),
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
next();
|
|
64
|
+
},
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
return references;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async update(
|
|
71
|
+
content: Blob,
|
|
72
|
+
file: FileInfo,
|
|
73
|
+
filesByURL: Map<string, FileInfo>,
|
|
74
|
+
): Promise<Blob> {
|
|
75
|
+
let url: string | undefined;
|
|
76
|
+
let ref: FileInfo | undefined;
|
|
77
|
+
const text = await content.text();
|
|
78
|
+
const ast = parse(text, this.#acornOptions);
|
|
79
|
+
const updated = walk(ast as Node, {}, {
|
|
80
|
+
ImportDeclaration(node: ImportDeclaration, { next }) {
|
|
81
|
+
url = new URL((node.source as Literal).value as string, file.aliasURL).toString();
|
|
82
|
+
ref = filesByURL.get(url);
|
|
83
|
+
|
|
84
|
+
if (ref == null) return;
|
|
85
|
+
|
|
86
|
+
const source = node.source as Literal;
|
|
87
|
+
source.raw = '"' + encodeURI(ref.url) + '"';
|
|
88
|
+
|
|
89
|
+
next();
|
|
90
|
+
},
|
|
91
|
+
ImportExpression(node: ImportExpression, { next }) {
|
|
92
|
+
url = new URL((node.source as Literal).value as string, file.aliasURL).toString();
|
|
93
|
+
ref = filesByURL.get(url);
|
|
94
|
+
|
|
95
|
+
if (ref == null) return;
|
|
96
|
+
|
|
97
|
+
const source = node.source as Literal;
|
|
98
|
+
source.raw = '"' + encodeURI(ref.url) + '"';
|
|
99
|
+
|
|
100
|
+
next();
|
|
101
|
+
},
|
|
102
|
+
});
|
|
103
|
+
const serialized = generate(updated);
|
|
104
|
+
|
|
105
|
+
return new Blob([serialized], { type: file.contentType });
|
|
106
|
+
}
|
|
107
|
+
}
|