@jsenv/core 38.3.4 → 38.3.6
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/dist/jsenv_core.js +1607 -1630
- package/package.json +8 -8
- package/src/dev/start_dev_server.js +483 -103
- package/src/plugins/inlining/jsenv_plugin_inlining_as_data_url.js +7 -2
- package/src/plugins/reference_analysis/data_urls/jsenv_plugin_data_urls_analysis.js +2 -1
- package/src/dev/file_service.js +0 -413
- package/src/plugins/import_meta_url/client/import_meta_url_browser.js +0 -51
- package/src/plugins/import_meta_url/client/import_meta_url_commonjs.mjs +0 -9
|
@@ -66,9 +66,11 @@ export const jsenvPluginInliningAsDataUrl = () => {
|
|
|
66
66
|
return (async () => {
|
|
67
67
|
await urlInfoInlined.cook();
|
|
68
68
|
const base64Url = DATA_URL.stringify({
|
|
69
|
-
|
|
69
|
+
contentType: urlInfoInlined.contentType,
|
|
70
70
|
base64Flag: true,
|
|
71
|
-
data: urlInfoInlined.
|
|
71
|
+
data: urlInfoInlined.data.base64Flag
|
|
72
|
+
? urlInfoInlined.content
|
|
73
|
+
: dataToBase64(urlInfoInlined.content),
|
|
72
74
|
});
|
|
73
75
|
return base64Url;
|
|
74
76
|
})();
|
|
@@ -83,6 +85,7 @@ export const jsenvPluginInliningAsDataUrl = () => {
|
|
|
83
85
|
const contentAsBase64 = Buffer.from(
|
|
84
86
|
withoutBase64ParamUrlInfo.content,
|
|
85
87
|
).toString("base64");
|
|
88
|
+
urlInfo.data.base64Flag = true;
|
|
86
89
|
return {
|
|
87
90
|
originalContent: withoutBase64ParamUrlInfo.originalContent,
|
|
88
91
|
content: contentAsBase64,
|
|
@@ -91,3 +94,5 @@ export const jsenvPluginInliningAsDataUrl = () => {
|
|
|
91
94
|
},
|
|
92
95
|
};
|
|
93
96
|
};
|
|
97
|
+
|
|
98
|
+
const dataToBase64 = (data) => Buffer.from(data).toString("base64");
|
|
@@ -46,8 +46,9 @@ export const jsenvPluginDataUrlsAnalysis = () => {
|
|
|
46
46
|
data: urlData,
|
|
47
47
|
} = DATA_URL.parse(urlInfo.url);
|
|
48
48
|
urlInfo.data.base64Flag = base64Flag;
|
|
49
|
+
const content = contentFromUrlData({ contentType, base64Flag, urlData });
|
|
49
50
|
return {
|
|
50
|
-
content
|
|
51
|
+
content,
|
|
51
52
|
contentType,
|
|
52
53
|
};
|
|
53
54
|
},
|
package/src/dev/file_service.js
DELETED
|
@@ -1,413 +0,0 @@
|
|
|
1
|
-
import { readFileSync } from "node:fs";
|
|
2
|
-
import { serveDirectory, composeTwoResponses } from "@jsenv/server";
|
|
3
|
-
import { bufferToEtag } from "@jsenv/filesystem";
|
|
4
|
-
import { URL_META } from "@jsenv/url-meta";
|
|
5
|
-
|
|
6
|
-
import { WEB_URL_CONVERTER } from "../helpers/web_url_converter.js";
|
|
7
|
-
import { watchSourceFiles } from "../helpers/watch_source_files.js";
|
|
8
|
-
import { createEventEmitter } from "../helpers/event_emitter.js";
|
|
9
|
-
import { createKitchen } from "../kitchen/kitchen.js";
|
|
10
|
-
import { getCorePlugins } from "../plugins/plugins.js";
|
|
11
|
-
import { jsenvPluginServerEventsClientInjection } from "../plugins/server_events/jsenv_plugin_server_events_client_injection.js";
|
|
12
|
-
import { parseUserAgentHeader } from "./user_agent.js";
|
|
13
|
-
|
|
14
|
-
export const createFileService = ({
|
|
15
|
-
signal,
|
|
16
|
-
logLevel,
|
|
17
|
-
serverStopCallbacks,
|
|
18
|
-
serverEventsDispatcher,
|
|
19
|
-
kitchenCache,
|
|
20
|
-
onKitchenCreated = () => {},
|
|
21
|
-
|
|
22
|
-
sourceDirectoryUrl,
|
|
23
|
-
sourceMainFilePath,
|
|
24
|
-
ignore,
|
|
25
|
-
sourceFilesConfig,
|
|
26
|
-
runtimeCompat,
|
|
27
|
-
|
|
28
|
-
plugins,
|
|
29
|
-
referenceAnalysis,
|
|
30
|
-
nodeEsmResolution,
|
|
31
|
-
magicExtensions,
|
|
32
|
-
magicDirectoryIndex,
|
|
33
|
-
supervisor,
|
|
34
|
-
injections,
|
|
35
|
-
transpilation,
|
|
36
|
-
clientAutoreload,
|
|
37
|
-
cacheControl,
|
|
38
|
-
ribbon,
|
|
39
|
-
sourcemaps,
|
|
40
|
-
sourcemapsSourcesContent,
|
|
41
|
-
outDirectoryUrl,
|
|
42
|
-
}) => {
|
|
43
|
-
if (clientAutoreload === true) {
|
|
44
|
-
clientAutoreload = {};
|
|
45
|
-
}
|
|
46
|
-
if (clientAutoreload === false) {
|
|
47
|
-
clientAutoreload = { enabled: false };
|
|
48
|
-
}
|
|
49
|
-
const clientFileChangeEventEmitter = createEventEmitter();
|
|
50
|
-
const clientFileDereferencedEventEmitter = createEventEmitter();
|
|
51
|
-
|
|
52
|
-
clientAutoreload = {
|
|
53
|
-
enabled: true,
|
|
54
|
-
clientServerEventsConfig: {},
|
|
55
|
-
clientFileChangeEventEmitter,
|
|
56
|
-
clientFileDereferencedEventEmitter,
|
|
57
|
-
...clientAutoreload,
|
|
58
|
-
};
|
|
59
|
-
|
|
60
|
-
const stopWatchingSourceFiles = watchSourceFiles(
|
|
61
|
-
sourceDirectoryUrl,
|
|
62
|
-
(fileInfo) => {
|
|
63
|
-
clientFileChangeEventEmitter.emit(fileInfo);
|
|
64
|
-
},
|
|
65
|
-
{
|
|
66
|
-
sourceFilesConfig,
|
|
67
|
-
keepProcessAlive: false,
|
|
68
|
-
cooldownBetweenFileEvents: clientAutoreload.cooldownBetweenFileEvents,
|
|
69
|
-
},
|
|
70
|
-
);
|
|
71
|
-
serverStopCallbacks.push(stopWatchingSourceFiles);
|
|
72
|
-
|
|
73
|
-
const getOrCreateKitchen = (request) => {
|
|
74
|
-
const { runtimeName, runtimeVersion } = parseUserAgentHeader(
|
|
75
|
-
request.headers["user-agent"] || "",
|
|
76
|
-
);
|
|
77
|
-
const runtimeId = `${runtimeName}@${runtimeVersion}`;
|
|
78
|
-
const existing = kitchenCache.get(runtimeId);
|
|
79
|
-
if (existing) {
|
|
80
|
-
return existing;
|
|
81
|
-
}
|
|
82
|
-
const watchAssociations = URL_META.resolveAssociations(
|
|
83
|
-
{ watch: stopWatchingSourceFiles.watchPatterns },
|
|
84
|
-
sourceDirectoryUrl,
|
|
85
|
-
);
|
|
86
|
-
let kitchen;
|
|
87
|
-
clientFileChangeEventEmitter.on(({ url }) => {
|
|
88
|
-
const urlInfo = kitchen.graph.getUrlInfo(url);
|
|
89
|
-
if (urlInfo) {
|
|
90
|
-
urlInfo.onModified();
|
|
91
|
-
}
|
|
92
|
-
});
|
|
93
|
-
const clientRuntimeCompat = { [runtimeName]: runtimeVersion };
|
|
94
|
-
|
|
95
|
-
kitchen = createKitchen({
|
|
96
|
-
name: runtimeId,
|
|
97
|
-
signal,
|
|
98
|
-
logLevel,
|
|
99
|
-
rootDirectoryUrl: sourceDirectoryUrl,
|
|
100
|
-
mainFilePath: sourceMainFilePath,
|
|
101
|
-
ignore,
|
|
102
|
-
dev: true,
|
|
103
|
-
runtimeCompat,
|
|
104
|
-
clientRuntimeCompat,
|
|
105
|
-
plugins: [
|
|
106
|
-
...plugins,
|
|
107
|
-
...getCorePlugins({
|
|
108
|
-
rootDirectoryUrl: sourceDirectoryUrl,
|
|
109
|
-
runtimeCompat,
|
|
110
|
-
|
|
111
|
-
referenceAnalysis,
|
|
112
|
-
nodeEsmResolution,
|
|
113
|
-
magicExtensions,
|
|
114
|
-
magicDirectoryIndex,
|
|
115
|
-
supervisor,
|
|
116
|
-
injections,
|
|
117
|
-
transpilation,
|
|
118
|
-
|
|
119
|
-
clientAutoreload,
|
|
120
|
-
cacheControl,
|
|
121
|
-
ribbon,
|
|
122
|
-
}),
|
|
123
|
-
],
|
|
124
|
-
supervisor,
|
|
125
|
-
minification: false,
|
|
126
|
-
sourcemaps,
|
|
127
|
-
sourcemapsSourcesContent,
|
|
128
|
-
outDirectoryUrl: outDirectoryUrl
|
|
129
|
-
? new URL(`${runtimeName}@${runtimeVersion}/`, outDirectoryUrl)
|
|
130
|
-
: undefined,
|
|
131
|
-
});
|
|
132
|
-
kitchen.graph.urlInfoCreatedEventEmitter.on((urlInfoCreated) => {
|
|
133
|
-
const { watch } = URL_META.applyAssociations({
|
|
134
|
-
url: urlInfoCreated.url,
|
|
135
|
-
associations: watchAssociations,
|
|
136
|
-
});
|
|
137
|
-
urlInfoCreated.isWatched = watch;
|
|
138
|
-
// when an url depends on many others, we check all these (like package.json)
|
|
139
|
-
urlInfoCreated.isValid = () => {
|
|
140
|
-
if (!urlInfoCreated.url.startsWith("file:")) {
|
|
141
|
-
return false;
|
|
142
|
-
}
|
|
143
|
-
if (urlInfoCreated.content === undefined) {
|
|
144
|
-
// urlInfo content is undefined when:
|
|
145
|
-
// - url info content never fetched
|
|
146
|
-
// - it is considered as modified because undelying file is watched and got saved
|
|
147
|
-
// - it is considered as modified because underlying file content
|
|
148
|
-
// was compared using etag and it has changed
|
|
149
|
-
return false;
|
|
150
|
-
}
|
|
151
|
-
if (!watch) {
|
|
152
|
-
// file is not watched, check the filesystem
|
|
153
|
-
let fileContentAsBuffer;
|
|
154
|
-
try {
|
|
155
|
-
fileContentAsBuffer = readFileSync(new URL(urlInfoCreated.url));
|
|
156
|
-
} catch (e) {
|
|
157
|
-
if (e.code === "ENOENT") {
|
|
158
|
-
urlInfoCreated.onModified();
|
|
159
|
-
return false;
|
|
160
|
-
}
|
|
161
|
-
return false;
|
|
162
|
-
}
|
|
163
|
-
const fileContentEtag = bufferToEtag(fileContentAsBuffer);
|
|
164
|
-
if (fileContentEtag !== urlInfoCreated.originalContentEtag) {
|
|
165
|
-
urlInfoCreated.onModified();
|
|
166
|
-
// restore content to be able to compare it again later
|
|
167
|
-
urlInfoCreated.kitchen.urlInfoTransformer.setContent(
|
|
168
|
-
urlInfoCreated,
|
|
169
|
-
String(fileContentAsBuffer),
|
|
170
|
-
{
|
|
171
|
-
contentEtag: fileContentEtag,
|
|
172
|
-
},
|
|
173
|
-
);
|
|
174
|
-
return false;
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
for (const implicitUrl of urlInfoCreated.implicitUrlSet) {
|
|
178
|
-
const implicitUrlInfo = urlInfoCreated.graph.getUrlInfo(implicitUrl);
|
|
179
|
-
if (implicitUrlInfo && !implicitUrlInfo.isValid()) {
|
|
180
|
-
return false;
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
return true;
|
|
184
|
-
};
|
|
185
|
-
});
|
|
186
|
-
kitchen.graph.urlInfoDereferencedEventEmitter.on(
|
|
187
|
-
(urlInfoDereferenced, lastReferenceFromOther) => {
|
|
188
|
-
clientFileDereferencedEventEmitter.emit(
|
|
189
|
-
urlInfoDereferenced,
|
|
190
|
-
lastReferenceFromOther,
|
|
191
|
-
);
|
|
192
|
-
},
|
|
193
|
-
);
|
|
194
|
-
|
|
195
|
-
serverStopCallbacks.push(() => {
|
|
196
|
-
kitchen.pluginController.callHooks("destroy", kitchen.context);
|
|
197
|
-
});
|
|
198
|
-
server_events: {
|
|
199
|
-
const allServerEvents = {};
|
|
200
|
-
kitchen.pluginController.plugins.forEach((plugin) => {
|
|
201
|
-
const { serverEvents } = plugin;
|
|
202
|
-
if (serverEvents) {
|
|
203
|
-
Object.keys(serverEvents).forEach((serverEventName) => {
|
|
204
|
-
// we could throw on serverEvent name conflict
|
|
205
|
-
// we could throw if serverEvents[serverEventName] is not a function
|
|
206
|
-
allServerEvents[serverEventName] = serverEvents[serverEventName];
|
|
207
|
-
});
|
|
208
|
-
}
|
|
209
|
-
});
|
|
210
|
-
const serverEventNames = Object.keys(allServerEvents);
|
|
211
|
-
if (serverEventNames.length > 0) {
|
|
212
|
-
Object.keys(allServerEvents).forEach((serverEventName) => {
|
|
213
|
-
const serverEventInfo = {
|
|
214
|
-
...kitchen.context,
|
|
215
|
-
sendServerEvent: (data) => {
|
|
216
|
-
serverEventsDispatcher.dispatch({
|
|
217
|
-
type: serverEventName,
|
|
218
|
-
data,
|
|
219
|
-
});
|
|
220
|
-
},
|
|
221
|
-
};
|
|
222
|
-
const serverEventInit = allServerEvents[serverEventName];
|
|
223
|
-
serverEventInit(serverEventInfo);
|
|
224
|
-
});
|
|
225
|
-
// "pushPlugin" so that event source client connection can be put as early as possible in html
|
|
226
|
-
kitchen.pluginController.pushPlugin(
|
|
227
|
-
jsenvPluginServerEventsClientInjection(
|
|
228
|
-
clientAutoreload.clientServerEventsConfig,
|
|
229
|
-
),
|
|
230
|
-
);
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
kitchenCache.set(runtimeId, kitchen);
|
|
235
|
-
onKitchenCreated(kitchen);
|
|
236
|
-
return kitchen;
|
|
237
|
-
};
|
|
238
|
-
|
|
239
|
-
return async (request) => {
|
|
240
|
-
const kitchen = getOrCreateKitchen(request);
|
|
241
|
-
const serveHookInfo = {
|
|
242
|
-
...kitchen.context,
|
|
243
|
-
request,
|
|
244
|
-
};
|
|
245
|
-
const responseFromPlugin =
|
|
246
|
-
await kitchen.pluginController.callAsyncHooksUntil(
|
|
247
|
-
"serve",
|
|
248
|
-
serveHookInfo,
|
|
249
|
-
);
|
|
250
|
-
if (responseFromPlugin) {
|
|
251
|
-
return responseFromPlugin;
|
|
252
|
-
}
|
|
253
|
-
const { referer } = request.headers;
|
|
254
|
-
const parentUrl = referer
|
|
255
|
-
? WEB_URL_CONVERTER.asFileUrl(referer, {
|
|
256
|
-
origin: request.origin,
|
|
257
|
-
rootDirectoryUrl: sourceDirectoryUrl,
|
|
258
|
-
})
|
|
259
|
-
: sourceDirectoryUrl;
|
|
260
|
-
let reference = kitchen.graph.inferReference(request.resource, parentUrl);
|
|
261
|
-
if (!reference) {
|
|
262
|
-
reference =
|
|
263
|
-
kitchen.graph.rootUrlInfo.dependencies.createResolveAndFinalize({
|
|
264
|
-
trace: { message: parentUrl },
|
|
265
|
-
type: "http_request",
|
|
266
|
-
specifier: request.resource,
|
|
267
|
-
});
|
|
268
|
-
}
|
|
269
|
-
const urlInfo = reference.urlInfo;
|
|
270
|
-
const ifNoneMatch = request.headers["if-none-match"];
|
|
271
|
-
const urlInfoTargetedByCache = urlInfo.findParentIfInline() || urlInfo;
|
|
272
|
-
|
|
273
|
-
try {
|
|
274
|
-
if (!urlInfo.error && ifNoneMatch) {
|
|
275
|
-
const [clientOriginalContentEtag, clientContentEtag] =
|
|
276
|
-
ifNoneMatch.split("_");
|
|
277
|
-
if (
|
|
278
|
-
urlInfoTargetedByCache.originalContentEtag ===
|
|
279
|
-
clientOriginalContentEtag &&
|
|
280
|
-
urlInfoTargetedByCache.contentEtag === clientContentEtag &&
|
|
281
|
-
urlInfoTargetedByCache.isValid()
|
|
282
|
-
) {
|
|
283
|
-
const headers = {
|
|
284
|
-
"cache-control": `private,max-age=0,must-revalidate`,
|
|
285
|
-
};
|
|
286
|
-
Object.keys(urlInfo.headers).forEach((key) => {
|
|
287
|
-
if (key !== "content-length") {
|
|
288
|
-
headers[key] = urlInfo.headers[key];
|
|
289
|
-
}
|
|
290
|
-
});
|
|
291
|
-
return {
|
|
292
|
-
status: 304,
|
|
293
|
-
headers,
|
|
294
|
-
};
|
|
295
|
-
}
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
await urlInfo.cook({ request, reference });
|
|
299
|
-
let { response } = urlInfo;
|
|
300
|
-
if (response) {
|
|
301
|
-
return response;
|
|
302
|
-
}
|
|
303
|
-
response = {
|
|
304
|
-
url: reference.url,
|
|
305
|
-
status: 200,
|
|
306
|
-
headers: {
|
|
307
|
-
// when we send eTag to the client the next request to the server
|
|
308
|
-
// will send etag in request headers.
|
|
309
|
-
// If they match jsenv bypass cooking and returns 304
|
|
310
|
-
// This must not happen when a plugin uses "no-store" or "no-cache" as it means
|
|
311
|
-
// plugin logic wants to happens for every request to this url
|
|
312
|
-
...(urlInfo.headers["cache-control"] === "no-store" ||
|
|
313
|
-
urlInfo.headers["cache-control"] === "no-cache"
|
|
314
|
-
? {}
|
|
315
|
-
: {
|
|
316
|
-
"cache-control": `private,max-age=0,must-revalidate`,
|
|
317
|
-
// it's safe to use "_" separator because etag is encoded with base64 (see https://stackoverflow.com/a/13195197)
|
|
318
|
-
"eTag": `${urlInfoTargetedByCache.originalContentEtag}_${urlInfoTargetedByCache.contentEtag}`,
|
|
319
|
-
}),
|
|
320
|
-
...urlInfo.headers,
|
|
321
|
-
"content-type": urlInfo.contentType,
|
|
322
|
-
"content-length": urlInfo.contentLength,
|
|
323
|
-
},
|
|
324
|
-
body: urlInfo.content,
|
|
325
|
-
timing: urlInfo.timing,
|
|
326
|
-
};
|
|
327
|
-
const augmentResponseInfo = {
|
|
328
|
-
...kitchen.context,
|
|
329
|
-
reference,
|
|
330
|
-
urlInfo,
|
|
331
|
-
};
|
|
332
|
-
kitchen.pluginController.callHooks(
|
|
333
|
-
"augmentResponse",
|
|
334
|
-
augmentResponseInfo,
|
|
335
|
-
(returnValue) => {
|
|
336
|
-
response = composeTwoResponses(response, returnValue);
|
|
337
|
-
},
|
|
338
|
-
);
|
|
339
|
-
return response;
|
|
340
|
-
} catch (e) {
|
|
341
|
-
urlInfo.error = e;
|
|
342
|
-
const originalError = e ? e.cause || e : e;
|
|
343
|
-
if (originalError.asResponse) {
|
|
344
|
-
return originalError.asResponse();
|
|
345
|
-
}
|
|
346
|
-
const code = originalError.code;
|
|
347
|
-
if (code === "PARSE_ERROR") {
|
|
348
|
-
// when possible let browser re-throw the syntax error
|
|
349
|
-
// it's not possible to do that when url info content is not available
|
|
350
|
-
// (happens for js_module_fallback for instance)
|
|
351
|
-
if (urlInfo.content !== undefined) {
|
|
352
|
-
kitchen.context.logger.error(`Error while handling ${request.url}:
|
|
353
|
-
${originalError.reasonCode || originalError.code}
|
|
354
|
-
${e.traceMessage}`);
|
|
355
|
-
return {
|
|
356
|
-
url: reference.url,
|
|
357
|
-
status: 200,
|
|
358
|
-
// reason becomes the http response statusText, it must not contain invalid chars
|
|
359
|
-
// https://github.com/nodejs/node/blob/0c27ca4bc9782d658afeaebcec85ec7b28f1cc35/lib/_http_common.js#L221
|
|
360
|
-
statusText: e.reason,
|
|
361
|
-
statusMessage: originalError.message,
|
|
362
|
-
headers: {
|
|
363
|
-
"content-type": urlInfo.contentType,
|
|
364
|
-
"content-length": urlInfo.contentLength,
|
|
365
|
-
"cache-control": "no-store",
|
|
366
|
-
},
|
|
367
|
-
body: urlInfo.content,
|
|
368
|
-
};
|
|
369
|
-
}
|
|
370
|
-
return {
|
|
371
|
-
url: reference.url,
|
|
372
|
-
status: 500,
|
|
373
|
-
statusText: e.reason,
|
|
374
|
-
statusMessage: originalError.message,
|
|
375
|
-
headers: {
|
|
376
|
-
"cache-control": "no-store",
|
|
377
|
-
},
|
|
378
|
-
body: urlInfo.content,
|
|
379
|
-
};
|
|
380
|
-
}
|
|
381
|
-
if (code === "DIRECTORY_REFERENCE_NOT_ALLOWED") {
|
|
382
|
-
return serveDirectory(reference.url, {
|
|
383
|
-
headers: {
|
|
384
|
-
accept: "text/html",
|
|
385
|
-
},
|
|
386
|
-
canReadDirectory: true,
|
|
387
|
-
rootDirectoryUrl: sourceDirectoryUrl,
|
|
388
|
-
});
|
|
389
|
-
}
|
|
390
|
-
if (code === "NOT_ALLOWED") {
|
|
391
|
-
return {
|
|
392
|
-
url: reference.url,
|
|
393
|
-
status: 403,
|
|
394
|
-
statusText: originalError.reason,
|
|
395
|
-
};
|
|
396
|
-
}
|
|
397
|
-
if (code === "NOT_FOUND") {
|
|
398
|
-
return {
|
|
399
|
-
url: reference.url,
|
|
400
|
-
status: 404,
|
|
401
|
-
statusText: originalError.reason,
|
|
402
|
-
statusMessage: originalError.message,
|
|
403
|
-
};
|
|
404
|
-
}
|
|
405
|
-
return {
|
|
406
|
-
url: reference.url,
|
|
407
|
-
status: 500,
|
|
408
|
-
statusText: e.reason,
|
|
409
|
-
statusMessage: e.stack,
|
|
410
|
-
};
|
|
411
|
-
}
|
|
412
|
-
};
|
|
413
|
-
};
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
const getCurrentScriptSrc = () => {
|
|
2
|
-
const { currentScript } = document;
|
|
3
|
-
if (currentScript) return currentScript.src;
|
|
4
|
-
|
|
5
|
-
// https://github.com/amiller-gh/currentScript-polyfill
|
|
6
|
-
|
|
7
|
-
const scripts = Array.prototype.slice.call(
|
|
8
|
-
document.getElementsByTagName("script"),
|
|
9
|
-
);
|
|
10
|
-
|
|
11
|
-
const readyScript = scripts.find((script) => {
|
|
12
|
-
return script.readyState === "interactive";
|
|
13
|
-
});
|
|
14
|
-
if (readyScript) return readyScript;
|
|
15
|
-
|
|
16
|
-
try {
|
|
17
|
-
throw new Error();
|
|
18
|
-
} catch (err) {
|
|
19
|
-
// Find the second match for the "at" string to get file src url from stack.
|
|
20
|
-
// Specifically works with the format of stack traces in IE.
|
|
21
|
-
const stackDetails = /.*at [^(]*\((.*):(.+):(.+)\)$/gi.exec(err.stack);
|
|
22
|
-
const scriptLocation = (stackDetails || [false])[1];
|
|
23
|
-
const line = (stackDetails || [false])[2];
|
|
24
|
-
const currentLocation = document.location.href.replace(
|
|
25
|
-
document.location.hash,
|
|
26
|
-
"",
|
|
27
|
-
);
|
|
28
|
-
|
|
29
|
-
if (scriptLocation === currentLocation) {
|
|
30
|
-
const source = document.documentElement.outerHTML;
|
|
31
|
-
const codeRegExp = new RegExp(
|
|
32
|
-
`(?:[^\\n]+?\\n){0,${
|
|
33
|
-
line - 2
|
|
34
|
-
}}[^<]*<script>([\\d\\D]*?)<\\/script>[\\d\\D]*`,
|
|
35
|
-
"i",
|
|
36
|
-
);
|
|
37
|
-
const code = source.replace(codeRegExp, "$1").trim();
|
|
38
|
-
|
|
39
|
-
return scripts.find((script) => {
|
|
40
|
-
return script.innerHTML && script.innerHTML.trim() === code;
|
|
41
|
-
});
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
return scripts.find((script) => {
|
|
45
|
-
return script.src === scriptLocation;
|
|
46
|
-
});
|
|
47
|
-
}
|
|
48
|
-
};
|
|
49
|
-
|
|
50
|
-
const url = getCurrentScriptSrc();
|
|
51
|
-
export { url };
|