@jsenv/core 40.12.13 → 41.0.0

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.
@@ -1,11 +1,11 @@
1
- import { WebSocketResponse, pickContentType, ServerEvents, jsenvServiceCORS, jsenvAccessControlAllowedHeaders, composeTwoResponses, serveDirectory, jsenvServiceErrorHandler, startServer } from "@jsenv/server";
2
- import { convertFileSystemErrorToResponseProperties } from "@jsenv/server/src/services/filesystem/filesystem_error_to_response.js";
1
+ import { WebSocketResponse, pickContentType, ServerEvents, serverPluginCORS, jsenvAccessControlAllowedHeaders, composeTwoResponses, serveDirectory, serverPluginErrorHandler, startServer } from "@jsenv/server";
2
+ import { convertFileSystemErrorToResponseProperties } from "@jsenv/server/src/plugins/filesystem/filesystem_error_to_response.js";
3
3
  import { lookupPackageDirectory, registerDirectoryLifecycle, urlToRelativeUrl, moveUrl, urlIsOrIsInsideOf, ensureWindowsDriveLetter, createDetailedMessage, stringifyUrlSite, generateContentFrame, validateResponseIntegrity, setUrlFilename, getCallerPosition, urlToBasename, urlToExtension, asSpecifierWithoutSearch, asUrlWithoutSearch, injectQueryParamsIntoSpecifier, bufferToEtag, isFileSystemPath, urlToPathname, setUrlBasename, urlToFileSystemPath, writeFileSync, createLogger, URL_META, applyNodeEsmResolution, normalizeUrl, ANSI, RUNTIME_COMPAT, CONTENT_TYPE, readPackageAtOrNull, errorToHTML, DATA_URL, normalizeImportMap, composeTwoImportMaps, resolveImport, JS_QUOTES, readCustomConditionsFromProcessArgs, readEntryStatSync, ensurePathnameTrailingSlash, compareFileUrls, urlToFilename, applyFileSystemMagicResolution, getExtensionsToTry, setUrlExtension, isSpecifierForNodeBuiltin, injectQueryParams, memoizeByFirstArgument, assertAndNormalizeDirectoryUrl, createTaskLog, formatError } from "./jsenv_core_packages.js";
4
4
  import { readFileSync, existsSync, readdirSync, lstatSync, realpathSync } from "node:fs";
5
5
  import { pathToFileURL } from "node:url";
6
6
  import { generateSourcemapFileUrl, createMagicSource, composeTwoSourcemaps, generateSourcemapDataUrl, SOURCEMAP } from "@jsenv/sourcemap";
7
7
  import { parseHtml, injectHtmlNodeAsEarlyAsPossible, createHtmlNode, stringifyHtmlAst, applyBabelPlugins, generateUrlForInlineContent, injectJsenvScript, parseJsWithAcorn, parseCssUrls, getHtmlNodeAttribute, getHtmlNodePosition, getHtmlNodeAttributePosition, setHtmlNodeAttributes, parseSrcSet, getUrlForContentInsideHtml, removeHtmlNodeText, setHtmlNodeText, getHtmlNodeText, analyzeScriptNode, visitHtmlNodes, parseJsUrls, getUrlForContentInsideJs, analyzeLinkNode } from "@jsenv/ast";
8
- import { performance } from "node:perf_hooks";
8
+ import { createPluginsController } from "@jsenv/server/src/plugins_controller.js";
9
9
  import { jsenvPluginSupervisor } from "@jsenv/plugin-supervisor";
10
10
  import { jsenvPluginTranspilation } from "@jsenv/plugin-transpilation";
11
11
  import { randomUUID } from "node:crypto";
@@ -193,7 +193,7 @@ const WEB_URL_CONVERTER = {
193
193
  const jsenvCoreDirectoryUrl = new URL("../", import.meta.url);
194
194
 
195
195
  const createResolveUrlError = ({
196
- pluginController,
196
+ jsenvPluginsController,
197
197
  reference,
198
198
  error,
199
199
  }) => {
@@ -211,7 +211,7 @@ ${reason}`,
211
211
  {
212
212
  ...detailsFromFirstReference(reference),
213
213
  ...details,
214
- ...detailsFromPluginController(pluginController),
214
+ ...detailsFromPluginController(jsenvPluginsController),
215
215
  },
216
216
  ),
217
217
  );
@@ -256,7 +256,7 @@ ${reason}`,
256
256
  };
257
257
 
258
258
  const createFetchUrlContentError = ({
259
- pluginController,
259
+ jsenvPluginsController,
260
260
  urlInfo,
261
261
  error,
262
262
  }) => {
@@ -275,7 +275,7 @@ ${reason}`,
275
275
  {
276
276
  ...detailsFromFirstReference(reference),
277
277
  ...details,
278
- ...detailsFromPluginController(pluginController),
278
+ ...detailsFromPluginController(jsenvPluginsController),
279
279
  },
280
280
  ),
281
281
  );
@@ -331,7 +331,7 @@ ${reason}`,
331
331
  };
332
332
 
333
333
  const createTransformUrlContentError = ({
334
- pluginController,
334
+ jsenvPluginsController,
335
335
  urlInfo,
336
336
  error,
337
337
  }) => {
@@ -360,7 +360,7 @@ ${error.message}`,
360
360
  ? `${reference.trace.url}:${reference.trace.line}:${reference.trace.column}`
361
361
  : reference.trace.message,
362
362
  ...detailsFromFirstReference(reference),
363
- ...detailsFromPluginController(pluginController),
363
+ ...detailsFromPluginController(jsenvPluginsController),
364
364
  },
365
365
  ),
366
366
  );
@@ -392,7 +392,7 @@ ${reason}`,
392
392
  {
393
393
  ...detailsFromFirstReference(reference),
394
394
  ...details,
395
- ...detailsFromPluginController(pluginController),
395
+ ...detailsFromPluginController(jsenvPluginsController),
396
396
  },
397
397
  ),
398
398
  );
@@ -416,7 +416,7 @@ ${reason}`,
416
416
  };
417
417
 
418
418
  const createFinalizeUrlContentError = ({
419
- pluginController,
419
+ jsenvPluginsController,
420
420
  urlInfo,
421
421
  error,
422
422
  }) => {
@@ -428,7 +428,7 @@ ${reference.trace.message}`,
428
428
  {
429
429
  ...detailsFromFirstReference(reference),
430
430
  ...detailsFromValueThrown(error),
431
- ...detailsFromPluginController(pluginController),
431
+ ...detailsFromPluginController(jsenvPluginsController),
432
432
  },
433
433
  ),
434
434
  );
@@ -518,8 +518,8 @@ const getFirstReferenceInProject = (reference) => {
518
518
  return getFirstReferenceInProject(firstReference);
519
519
  };
520
520
 
521
- const detailsFromPluginController = (pluginController) => {
522
- const currentPlugin = pluginController.getCurrentPlugin();
521
+ const detailsFromPluginController = (jsenvPluginsController) => {
522
+ const currentPlugin = jsenvPluginsController.getCurrentPlugin();
523
523
  if (!currentPlugin) {
524
524
  return null;
525
525
  }
@@ -3072,14 +3072,14 @@ const createKitchen = ({
3072
3072
  },
3073
3073
  graph: null,
3074
3074
  urlInfoTransformer: null,
3075
- pluginController: null,
3075
+ jsenvPluginsController: null,
3076
3076
  };
3077
3077
  const kitchenContext = kitchen.context;
3078
3078
  kitchenContext.kitchen = kitchen;
3079
3079
 
3080
- let pluginController;
3081
- kitchen.setPluginController = (value) => {
3082
- pluginController = kitchen.pluginController = value;
3080
+ let jsenvPluginsController;
3081
+ kitchen.setJsenvPluginsController = (value) => {
3082
+ jsenvPluginsController = kitchen.jsenvPluginsController = value;
3083
3083
  };
3084
3084
 
3085
3085
  const graph = createUrlGraph({
@@ -3088,7 +3088,11 @@ const createKitchen = ({
3088
3088
  kitchen,
3089
3089
  });
3090
3090
  graph.urlInfoCreatedEventEmitter.on((urlInfoCreated) => {
3091
- pluginController.callHooks("urlInfoCreated", urlInfoCreated, () => {});
3091
+ jsenvPluginsController.callHooks(
3092
+ "urlInfoCreated",
3093
+ urlInfoCreated,
3094
+ () => {},
3095
+ );
3092
3096
  });
3093
3097
  kitchen.graph = graph;
3094
3098
 
@@ -3262,7 +3266,7 @@ const createKitchen = ({
3262
3266
  setReferenceUrl(reference.url);
3263
3267
  break resolve;
3264
3268
  }
3265
- const resolvedUrl = pluginController.callHooksUntil(
3269
+ const resolvedUrl = jsenvPluginsController.callHooksUntil(
3266
3270
  "resolveReference",
3267
3271
  reference,
3268
3272
  );
@@ -3276,7 +3280,7 @@ const createKitchen = ({
3276
3280
  setReferenceUrl(normalizedUrl);
3277
3281
  if (reference.debug) {
3278
3282
  logger.debug(`url resolved by "${
3279
- pluginController.getLastPluginUsed().name
3283
+ jsenvPluginsController.getLastPluginUsed().name
3280
3284
  }"
3281
3285
  ${ANSI.color(reference.specifier, ANSI.GREY)} ->
3282
3286
  ${ANSI.color(reference.url, ANSI.YELLOW)}
@@ -3290,7 +3294,7 @@ ${ANSI.color(reference.url, ANSI.YELLOW)}
3290
3294
  // - side_effect_file references injected in entry points or at the top of files
3291
3295
  break redirect;
3292
3296
  }
3293
- pluginController.callHooks(
3297
+ jsenvPluginsController.callHooks(
3294
3298
  "redirectReference",
3295
3299
  reference,
3296
3300
  (returnValue, plugin, setReference) => {
@@ -3335,7 +3339,7 @@ ${ANSI.color(normalizedReturnValue, ANSI.YELLOW)}
3335
3339
  return reference;
3336
3340
  } catch (error) {
3337
3341
  throw createResolveUrlError({
3338
- pluginController,
3342
+ jsenvPluginsController,
3339
3343
  reference,
3340
3344
  error,
3341
3345
  });
@@ -3362,7 +3366,7 @@ ${ANSI.color(normalizedReturnValue, ANSI.YELLOW)}
3362
3366
  // But do not represent an other resource, it is considered as
3363
3367
  // the same resource under the hood
3364
3368
  const searchParamTransformationMap = new Map();
3365
- pluginController.callHooks(
3369
+ jsenvPluginsController.callHooks(
3366
3370
  "transformReferenceSearchParams",
3367
3371
  reference,
3368
3372
  (returnValue) => {
@@ -3390,7 +3394,7 @@ ${ANSI.color(normalizedReturnValue, ANSI.YELLOW)}
3390
3394
  }
3391
3395
  }
3392
3396
  {
3393
- const returnValue = pluginController.callHooksUntil(
3397
+ const returnValue = jsenvPluginsController.callHooksUntil(
3394
3398
  "formatReference",
3395
3399
  reference,
3396
3400
  );
@@ -3411,7 +3415,10 @@ ${ANSI.color(normalizedReturnValue, ANSI.YELLOW)}
3411
3415
  const fetchUrlContent = async (urlInfo) => {
3412
3416
  try {
3413
3417
  const fetchUrlContentReturnValue =
3414
- await pluginController.callAsyncHooksUntil("fetchUrlContent", urlInfo);
3418
+ await jsenvPluginsController.callAsyncHooksUntil(
3419
+ "fetchUrlContent",
3420
+ urlInfo,
3421
+ );
3415
3422
  if (!fetchUrlContentReturnValue) {
3416
3423
  logger.warn(
3417
3424
  createDetailedMessage(
@@ -3510,7 +3517,7 @@ ${ANSI.color(normalizedReturnValue, ANSI.YELLOW)}
3510
3517
  });
3511
3518
  } catch (error) {
3512
3519
  throw createFetchUrlContentError({
3513
- pluginController,
3520
+ jsenvPluginsController,
3514
3521
  urlInfo,
3515
3522
  error,
3516
3523
  });
@@ -3520,7 +3527,7 @@ ${ANSI.color(normalizedReturnValue, ANSI.YELLOW)}
3520
3527
 
3521
3528
  const transformUrlContent = async (urlInfo) => {
3522
3529
  try {
3523
- await pluginController.callAsyncHooks(
3530
+ await jsenvPluginsController.callAsyncHooks(
3524
3531
  "transformUrlContent",
3525
3532
  urlInfo,
3526
3533
  (transformReturnValue) => {
@@ -3532,7 +3539,7 @@ ${ANSI.color(normalizedReturnValue, ANSI.YELLOW)}
3532
3539
  );
3533
3540
  } catch (error) {
3534
3541
  const transformError = createTransformUrlContentError({
3535
- pluginController,
3542
+ jsenvPluginsController,
3536
3543
  urlInfo,
3537
3544
  error,
3538
3545
  });
@@ -3544,14 +3551,15 @@ ${ANSI.color(normalizedReturnValue, ANSI.YELLOW)}
3544
3551
  const finalizeUrlContent = async (urlInfo) => {
3545
3552
  try {
3546
3553
  await urlInfo.applyContentTransformationCallbacks();
3547
- const finalizeReturnValue = await pluginController.callAsyncHooksUntil(
3548
- "finalizeUrlContent",
3549
- urlInfo,
3550
- );
3554
+ const finalizeReturnValue =
3555
+ await jsenvPluginsController.callAsyncHooksUntil(
3556
+ "finalizeUrlContent",
3557
+ urlInfo,
3558
+ );
3551
3559
  urlInfoTransformer.endTransformations(urlInfo, finalizeReturnValue);
3552
3560
  } catch (error) {
3553
3561
  throw createFinalizeUrlContentError({
3554
- pluginController,
3562
+ jsenvPluginsController,
3555
3563
  urlInfo,
3556
3564
  error,
3557
3565
  });
@@ -3632,7 +3640,7 @@ ${urlInfo.firstReference.trace.message}`;
3632
3640
  }
3633
3641
 
3634
3642
  // "cooked" hook
3635
- pluginController.callHooks("cooked", urlInfo, (cookedReturnValue) => {
3643
+ jsenvPluginsController.callHooks("cooked", urlInfo, (cookedReturnValue) => {
3636
3644
  if (typeof cookedReturnValue === "function") {
3637
3645
  const removeCallback = urlInfo.graph.urlInfoDereferencedEventEmitter.on(
3638
3646
  (urlInfoDereferenced, lastReferenceFromOther) => {
@@ -3929,9 +3937,9 @@ const replacePlaceholders = (html, replacers) => {
3929
3937
  });
3930
3938
  };
3931
3939
 
3932
- const createPluginStore = async (plugins) => {
3933
- const allDevServerRoutes = [];
3934
- const allDevServerServices = [];
3940
+ const createJsenvPluginStore = async (plugins) => {
3941
+ const allServerRoutes = [];
3942
+ const allServerPlugins = [];
3935
3943
  const pluginArray = [];
3936
3944
 
3937
3945
  const pluginPromises = [];
@@ -3954,16 +3962,17 @@ const createPluginStore = async (plugins) => {
3954
3962
  if (!plugin.name) {
3955
3963
  plugin.name = "anonymous";
3956
3964
  }
3957
- if (plugin.devServerRoutes) {
3958
- const devServerRoutes = plugin.devServerRoutes;
3959
- for (const devServerRoute of devServerRoutes) {
3960
- allDevServerRoutes.push(devServerRoute);
3965
+ const { serverRoutes } = plugin;
3966
+ if (serverRoutes) {
3967
+ for (const serverRoute of serverRoutes) {
3968
+ allServerRoutes.push(serverRoute);
3961
3969
  }
3962
3970
  }
3963
- if (plugin.devServerServices) {
3964
- const devServerServices = plugin.devServerServices;
3965
- for (const devServerService of devServerServices) {
3966
- allDevServerServices.push(devServerService);
3971
+ const { serverPlugins } = plugin;
3972
+ if (serverPlugins) {
3973
+ const serverPlugins = plugin.serverPlugins;
3974
+ for (const serverPlugin of serverPlugins) {
3975
+ allServerPlugins.push(serverPlugin);
3967
3976
  }
3968
3977
  }
3969
3978
  pluginArray.push(plugin);
@@ -3976,283 +3985,120 @@ const createPluginStore = async (plugins) => {
3976
3985
 
3977
3986
  return {
3978
3987
  pluginArray,
3979
- allDevServerRoutes,
3980
- allDevServerServices,
3988
+ allServerRoutes,
3989
+ allServerPlugins,
3981
3990
  };
3982
3991
  };
3983
3992
 
3984
- const createPluginController = async (
3993
+ const createJsenvPluginsController = async (
3985
3994
  pluginStore,
3986
3995
  kitchen,
3987
- { initialPuginsMeta = {} } = {},
3996
+ { meta } = {},
3988
3997
  ) => {
3989
- const pluginsMeta = initialPuginsMeta;
3990
- kitchen.context.getPluginMeta = (id) => {
3991
- const value = pluginsMeta[id];
3992
- return value;
3993
- };
3998
+ kitchen.context.getPluginMeta = (id) => pluginsController.getPluginMeta(id);
3999
+ const pluginsController = await createPluginsController({
4000
+ plugins: pluginStore.pluginArray,
4001
+ pluginDescription: JSENV_PLUGIN_DESCRIPTION,
4002
+ filterPlugin: (plugin) => testAppliesDuring(plugin, kitchen),
4003
+ getInitPluginArgs: (plugin) => [kitchen.context, { plugin }],
4004
+ getEffectArgs: ({ otherPlugins }) => [
4005
+ { kitchenContext: kitchen.context, otherPlugins },
4006
+ ],
4007
+ meta,
4008
+ });
4009
+ return pluginsController;
4010
+ };
3994
4011
 
3995
- // precompute a list of hooks per hookName because:
3996
- // 1. [MAJOR REASON] when debugging, there is less iteration (so much better)
3997
- // 2. [MINOR REASON] it should increase perf as there is less work to do
3998
- const hookSetMap = new Map();
3999
- const pluginCandidates = pluginStore.pluginArray;
4000
- const activePluginArray = [];
4001
- const pluginWithEffectCandidateForActivationArray = [];
4002
- for (const pluginCandidate of pluginCandidates) {
4003
- if (!testAppliesDuring(pluginCandidate, kitchen)) {
4004
- pluginCandidate.destroy?.();
4005
- continue;
4006
- }
4007
- const initPluginResult = await initPlugin(pluginCandidate, kitchen);
4008
- if (!initPluginResult) {
4009
- pluginCandidate.destroy?.();
4010
- continue;
4011
- }
4012
- if (pluginCandidate.effect) {
4013
- pluginWithEffectCandidateForActivationArray.push(pluginCandidate);
4014
- } else {
4015
- activePluginArray.push(pluginCandidate);
4016
- }
4017
- }
4012
+ const hook = { type: "hook" };
4013
+ const nonHook = {};
4018
4014
 
4019
- const activeEffectSet = new Set();
4020
- for (const pluginWithEffectCandidateForActivation of pluginWithEffectCandidateForActivationArray) {
4021
- const returnValue = pluginWithEffectCandidateForActivation.effect({
4022
- kitchenContext: kitchen.context,
4023
- otherPlugins: activePluginArray,
4024
- });
4025
- if (!returnValue) {
4026
- continue;
4027
- }
4028
- activePluginArray.push(pluginWithEffectCandidateForActivation);
4029
- activeEffectSet.add({
4030
- plugin: pluginWithEffectCandidateForActivation,
4031
- cleanup: typeof returnValue === "function" ? returnValue : () => {},
4032
- });
4015
+ const assertUrlReturnValue = (valueReturned, urlInfo, { hook }) => {
4016
+ if (valueReturned instanceof URL) {
4017
+ return valueReturned.href;
4033
4018
  }
4034
- activePluginArray.sort((a, b) => {
4035
- return pluginCandidates.indexOf(a) - pluginCandidates.indexOf(b);
4036
- });
4037
- for (const activePlugin of activePluginArray) {
4038
- for (const key of Object.keys(activePlugin)) {
4039
- if (key === "meta") {
4040
- const value = activePlugin[key];
4041
- if (typeof value !== "object" || value === null) {
4042
- console.warn(`plugin.meta must be an object, got ${value}`);
4043
- continue;
4044
- }
4045
- Object.assign(pluginsMeta, value);
4046
- // any extension/modification on plugin.meta
4047
- // won't be taken into account so we freeze object
4048
- // to throw in case it happen
4049
- Object.freeze(value);
4050
- continue;
4051
- }
4052
- if (
4053
- key === "name" ||
4054
- key === "appliesDuring" ||
4055
- key === "init" ||
4056
- key === "serverEvents" ||
4057
- key === "mustStayFirst" ||
4058
- key === "devServerRoutes" ||
4059
- key === "devServerServices" ||
4060
- key === "effect"
4061
- ) {
4062
- continue;
4063
- }
4064
- const isHook = HOOK_NAMES.includes(key);
4065
- if (!isHook) {
4066
- console.warn(
4067
- `Unexpected "${key}" property on "${activePlugin.name}" plugin`,
4068
- );
4069
- continue;
4070
- }
4071
- const hookName = key;
4072
- const hookValue = activePlugin[hookName];
4073
- if (hookValue) {
4074
- let hookSet = hookSetMap.get(hookName);
4075
- if (!hookSet) {
4076
- hookSet = new Set();
4077
- hookSetMap.set(hookName, hookSet);
4078
- }
4079
- const hook = {
4080
- plugin: activePlugin,
4081
- name: hookName,
4082
- value: hookValue,
4083
- };
4084
- // if (position === "start") {
4085
- // let i = 0;
4086
- // while (i < group.length) {
4087
- // const before = group[i];
4088
- // if (!before.plugin.mustStayFirst) {
4089
- // break;
4090
- // }
4091
- // i++;
4092
- // }
4093
- // group.splice(i, 0, hook);
4094
- // } else {
4095
- hookSet.add(hook);
4096
- }
4097
- }
4098
- }
4099
-
4100
- let lastPluginUsed = null;
4101
- let currentPlugin = null;
4102
- let currentHookName = null;
4103
- const callHook = (hook, info) => {
4104
- const hookFn = getHookFunction(hook, info);
4105
- if (!hookFn) {
4106
- return null;
4107
- }
4108
- let startTimestamp;
4109
- if (info.timing) {
4110
- startTimestamp = performance.now();
4111
- }
4112
- lastPluginUsed = hook.plugin;
4113
- currentPlugin = hook.plugin;
4114
- currentHookName = hook.name;
4115
- let valueReturned = hookFn(info);
4116
- if (info.timing) {
4117
- info.timing[`${hook.name}-${hook.plugin.name.replace("jsenv:", "")}`] =
4118
- performance.now() - startTimestamp;
4119
- }
4120
- valueReturned = assertAndNormalizeReturnValue(hook, valueReturned, info);
4121
- currentPlugin = null;
4122
- currentHookName = null;
4019
+ if (typeof valueReturned === "string") {
4123
4020
  return valueReturned;
4124
- };
4125
- const callAsyncHook = async (hook, info) => {
4126
- const hookFn = getHookFunction(hook, info);
4127
- if (!hookFn) {
4128
- return null;
4129
- }
4130
-
4131
- let startTimestamp;
4132
- if (info.timing) {
4133
- startTimestamp = performance.now();
4134
- }
4135
- lastPluginUsed = hook.plugin;
4136
- currentPlugin = hook.plugin;
4137
- currentHookName = hook.name;
4138
- let valueReturned = await hookFn(info);
4139
- if (info.timing) {
4140
- info.timing[`${hook.name}-${hook.plugin.name.replace("jsenv:", "")}`] =
4141
- performance.now() - startTimestamp;
4142
- }
4143
- valueReturned = assertAndNormalizeReturnValue(hook, valueReturned, info);
4144
- currentPlugin = null;
4145
- currentHookName = null;
4146
- return valueReturned;
4147
- };
4148
- const callHooks = (hookName, info, callback) => {
4149
- const hookSet = hookSetMap.get(hookName);
4150
- if (!hookSet) {
4151
- return;
4152
- }
4153
- const setHookParams = (firstArg = info) => {
4154
- info = firstArg;
4155
- };
4156
- for (const hook of hookSet) {
4157
- const returnValue = callHook(hook, info);
4158
- if (returnValue && callback) {
4159
- callback(returnValue, hook.plugin, setHookParams);
4160
- }
4161
- }
4162
- };
4163
- const callAsyncHooks = async (hookName, info, callback, options) => {
4164
- const hookSet = hookSetMap.get(hookName);
4165
- if (!hookSet) {
4166
- return;
4167
- }
4168
- for (const hook of hookSet) {
4169
- const returnValue = await callAsyncHook(hook, info);
4170
- if (returnValue && callback) {
4171
- await callback(returnValue, hook.plugin);
4172
- }
4173
- }
4174
- };
4175
- const callHooksUntil = (hookName, info) => {
4176
- const hookSet = hookSetMap.get(hookName);
4177
- if (!hookSet) {
4178
- return null;
4021
+ }
4022
+ throw new Error(
4023
+ `Unexpected value returned by hook "${hook.plugin.name}.${hook.name}()": it must be a string; got ${valueReturned}`,
4024
+ );
4025
+ };
4026
+ const assertContentReturnValue = (valueReturned, urlInfo, { hook }) => {
4027
+ if (typeof valueReturned === "string" || Buffer.isBuffer(valueReturned)) {
4028
+ return { content: valueReturned };
4029
+ }
4030
+ if (typeof valueReturned === "object") {
4031
+ const { content, body } = valueReturned;
4032
+ if (urlInfo.url.startsWith("ignore:")) {
4033
+ return valueReturned;
4179
4034
  }
4180
- for (const hook of hookSet) {
4181
- const returnValue = callHook(hook, info);
4182
- if (returnValue) {
4183
- return returnValue;
4035
+ if (typeof content !== "string" && !Buffer.isBuffer(content) && !body) {
4036
+ if (Object.hasOwn(valueReturned, "contentInjections")) {
4037
+ return valueReturned;
4184
4038
  }
4039
+ throw new Error(
4040
+ `Unexpected "content" returned by hook "${hook.plugin.name}.${hook.name}()": it must be a string or a buffer; got ${content}`,
4041
+ );
4185
4042
  }
4186
- return null;
4187
- };
4188
- const callAsyncHooksUntil = async (hookName, info, options) => {
4189
- const hookSet = hookSetMap.get(hookName);
4190
- if (!hookSet) {
4191
- return null;
4192
- }
4193
- if (hookSet.size === 0) {
4194
- return null;
4195
- }
4196
- const iterator = hookSet.values()[Symbol.iterator]();
4197
- let result;
4198
- const visit = async () => {
4199
- const { done, value: hook } = iterator.next();
4200
- if (done) {
4201
- return;
4202
- }
4203
- const returnValue = await callAsyncHook(hook, info);
4204
- if (returnValue) {
4205
- result = returnValue;
4206
- return;
4207
- }
4208
- await visit();
4209
- };
4210
- await visit();
4211
- return result;
4212
- };
4043
+ return valueReturned;
4044
+ }
4045
+ throw new Error(
4046
+ `Unexpected value returned by hook "${hook.plugin.name}.${hook.name}()": it must be a string, a buffer or an object; got ${valueReturned}`,
4047
+ );
4048
+ };
4213
4049
 
4214
- return {
4215
- activePlugins: activePluginArray,
4216
-
4217
- callHook,
4218
- callAsyncHook,
4219
- callHooks,
4220
- callHooksUntil,
4221
- callAsyncHooks,
4222
- callAsyncHooksUntil,
4223
-
4224
- getLastPluginUsed: () => lastPluginUsed,
4225
- getCurrentPlugin: () => currentPlugin,
4226
- getCurrentHookName: () => currentHookName,
4227
- };
4228
- };
4229
-
4230
- const HOOK_NAMES = [
4231
- "init",
4232
- "devServerRoutes", // is called only during dev/tests
4233
- "devServerServices", // is called only during dev/tests
4234
- "resolveReference",
4235
- "redirectReference",
4236
- "transformReferenceSearchParams",
4237
- "formatReference",
4238
- "urlInfoCreated",
4239
- "fetchUrlContent",
4240
- "transformUrlContent",
4241
- "finalizeUrlContent",
4242
- "bundle", // is called only during build
4243
- "optimizeBuildUrlContent", // is called only during build
4244
- "cooked",
4245
- "augmentResponse", // is called only during dev/tests
4246
- "destroy",
4247
- "effect",
4248
- "refineBuildUrlContent", // called only during build
4249
- "refineBuild", // called only during build
4250
- ];
4050
+ const JSENV_PLUGIN_DESCRIPTION = {
4051
+ name: "jsenv plugin",
4052
+ properties: {
4053
+ // non-hook properties (silently skipped)
4054
+ appliesDuring: nonHook,
4055
+ serverEvents: nonHook,
4056
+ mustStayFirst: nonHook,
4057
+ serverRoutes: nonHook,
4058
+ serverPlugins: nonHook,
4059
+ // hooks
4060
+ init: hook,
4061
+ resolveReference: {
4062
+ type: "hook",
4063
+ assertAndNormalize: assertUrlReturnValue,
4064
+ },
4065
+ redirectReference: {
4066
+ type: "hook",
4067
+ assertAndNormalize: assertUrlReturnValue,
4068
+ },
4069
+ transformReferenceSearchParams: hook,
4070
+ formatReference: hook,
4071
+ urlInfoCreated: hook,
4072
+ fetchUrlContent: {
4073
+ type: "hook",
4074
+ assertAndNormalize: assertContentReturnValue,
4075
+ },
4076
+ transformUrlContent: {
4077
+ type: "hook",
4078
+ assertAndNormalize: assertContentReturnValue,
4079
+ },
4080
+ finalizeUrlContent: {
4081
+ type: "hook",
4082
+ assertAndNormalize: assertContentReturnValue,
4083
+ },
4084
+ bundle: hook,
4085
+ optimizeBuildUrlContent: {
4086
+ type: "hook",
4087
+ assertAndNormalize: assertContentReturnValue,
4088
+ },
4089
+ cooked: hook,
4090
+ augmentResponse: hook,
4091
+ destroy: hook,
4092
+ effect: hook,
4093
+ refineBuildUrlContent: hook,
4094
+ refineBuild: hook,
4095
+ // serverRoutes and serverPlugins are nonHook above
4096
+ },
4097
+ };
4251
4098
 
4252
4099
  const testAppliesDuring = (plugin, kitchen) => {
4253
4100
  const { appliesDuring } = plugin;
4254
4101
  if (appliesDuring === undefined) {
4255
- // console.debug(`"appliesDuring" is undefined on ${pluginEntry.name}`)
4256
4102
  return true;
4257
4103
  }
4258
4104
  if (appliesDuring === "*") {
@@ -4278,112 +4124,12 @@ const testAppliesDuring = (plugin, kitchen) => {
4278
4124
  return true;
4279
4125
  }
4280
4126
  }
4281
- // throw new Error(`"appliesDuring" is empty`)
4282
4127
  return false;
4283
4128
  }
4284
4129
  throw new TypeError(
4285
4130
  `"appliesDuring" must be an object or a string, got ${appliesDuring}`,
4286
4131
  );
4287
4132
  };
4288
- const initPlugin = async (plugin, kitchen) => {
4289
- const { init } = plugin;
4290
- if (!init) {
4291
- return true;
4292
- }
4293
- const initReturnValue = await init(kitchen.context, { plugin });
4294
- if (initReturnValue === false) {
4295
- return false;
4296
- }
4297
- if (typeof initReturnValue === "function" && !plugin.destroy) {
4298
- plugin.destroy = initReturnValue;
4299
- }
4300
- return true;
4301
- };
4302
- const getHookFunction = (
4303
- hook,
4304
- // can be undefined, reference, or urlInfo
4305
- info = {},
4306
- ) => {
4307
- const hookValue = hook.value;
4308
- if (typeof hookValue === "object") {
4309
- const hookForType = hookValue[info.type] || hookValue["*"];
4310
- if (!hookForType) {
4311
- return null;
4312
- }
4313
- return hookForType;
4314
- }
4315
- return hookValue;
4316
- };
4317
-
4318
- const assertAndNormalizeReturnValue = (hook, returnValue, info) => {
4319
- // all hooks are allowed to return null/undefined as a signal of "I don't do anything"
4320
- if (returnValue === null || returnValue === undefined) {
4321
- return returnValue;
4322
- }
4323
- for (const returnValueAssertion of returnValueAssertions) {
4324
- if (!returnValueAssertion.appliesTo.includes(hook.name)) {
4325
- continue;
4326
- }
4327
- const assertionResult = returnValueAssertion.assertion(returnValue, info, {
4328
- hook,
4329
- });
4330
- if (assertionResult !== undefined) {
4331
- // normalization
4332
- returnValue = assertionResult;
4333
- break;
4334
- }
4335
- }
4336
- return returnValue;
4337
- };
4338
- const returnValueAssertions = [
4339
- {
4340
- name: "url_assertion",
4341
- appliesTo: ["resolveReference", "redirectReference"],
4342
- assertion: (valueReturned, urlInfo, { hook }) => {
4343
- if (valueReturned instanceof URL) {
4344
- return valueReturned.href;
4345
- }
4346
- if (typeof valueReturned === "string") {
4347
- return undefined;
4348
- }
4349
- throw new Error(
4350
- `Unexpected value returned by "${hook.plugin.name}" plugin: it must be a string; got ${valueReturned}`,
4351
- );
4352
- },
4353
- },
4354
- {
4355
- name: "content_assertion",
4356
- appliesTo: [
4357
- "fetchUrlContent",
4358
- "transformUrlContent",
4359
- "finalizeUrlContent",
4360
- "optimizeBuildUrlContent",
4361
- ],
4362
- assertion: (valueReturned, urlInfo, { hook }) => {
4363
- if (typeof valueReturned === "string" || Buffer.isBuffer(valueReturned)) {
4364
- return { content: valueReturned };
4365
- }
4366
- if (typeof valueReturned === "object") {
4367
- const { content, body } = valueReturned;
4368
- if (urlInfo.url.startsWith("ignore:")) {
4369
- return undefined;
4370
- }
4371
- if (typeof content !== "string" && !Buffer.isBuffer(content) && !body) {
4372
- if (Object.hasOwn(valueReturned, "contentInjections")) {
4373
- return undefined;
4374
- }
4375
- throw new Error(
4376
- `Unexpected "content" returned by "${hook.plugin.name}" ${hook.name} hook: it must be a string or a buffer; got ${content}`,
4377
- );
4378
- }
4379
- return undefined;
4380
- }
4381
- throw new Error(
4382
- `Unexpected value returned by "${hook.plugin.name}" ${hook.name} hook: it must be a string, a buffer or an object; got ${valueReturned}`,
4383
- );
4384
- },
4385
- },
4386
- ];
4387
4133
 
4388
4134
  /*
4389
4135
  * https://github.com/parcel-bundler/parcel/blob/v2/packages/transformers/css/src/CSSTransformer.js
@@ -6438,7 +6184,7 @@ const jsenvPluginDirectoryListing = ({
6438
6184
  };
6439
6185
  },
6440
6186
  },
6441
- devServerRoutes: [
6187
+ serverRoutes: [
6442
6188
  {
6443
6189
  endpoint:
6444
6190
  "GET /.internal/directory_content.websocket?directory=:directory",
@@ -7957,11 +7703,7 @@ const jsenvPluginImportMetaCss = () => {
7957
7703
  appliesDuring: "*",
7958
7704
  transformUrlContent: {
7959
7705
  js_module: async (urlInfo) => {
7960
- if (
7961
- !urlInfo.content.includes("import.meta.css") ||
7962
- // there is already our installImportMetaCssBuild in the file
7963
- urlInfo.content.includes("installImportMetaCssBuild")
7964
- ) {
7706
+ if (!urlInfo.content.includes("import.meta.css")) {
7965
7707
  return null;
7966
7708
  }
7967
7709
  const { metadata } = await applyBabelPlugins({
@@ -7975,22 +7717,78 @@ const jsenvPluginImportMetaCss = () => {
7975
7717
  if (!usesImportMetaCss) {
7976
7718
  return null;
7977
7719
  }
7720
+ if (urlInfo.context.build) {
7721
+ const rootDirectoryUrl = urlInfo.context.rootDirectoryUrl;
7722
+ const relativeUrl = urlInfo.originalUrl.slice(
7723
+ rootDirectoryUrl.length - 1,
7724
+ );
7725
+ const { code } = await applyBabelPlugins({
7726
+ babelPlugins: [
7727
+ [babelPluginRewriteImportMetaCssAssignment, { relativeUrl }],
7728
+ ],
7729
+ input: urlInfo.content,
7730
+ inputIsJsModule: true,
7731
+ inputUrl: urlInfo.originalUrl,
7732
+ outputUrl: urlInfo.generatedUrl,
7733
+ });
7734
+ if (code === urlInfo.content) {
7735
+ // all assignments were already in array form (pre-built file) — nothing to do
7736
+ return null;
7737
+ }
7738
+ return injectImportMetaCss(urlInfo, {
7739
+ content: code,
7740
+ importFrom: importMetaCssBuildClientFileUrl,
7741
+ importName: "installImportMetaCssBuild",
7742
+ importAs: "__installImportMetaCssBuild__",
7743
+ });
7744
+ }
7978
7745
  return injectImportMetaCss(urlInfo, {
7979
- importFrom: urlInfo.context.build
7980
- ? importMetaCssBuildClientFileUrl
7981
- : importMetaCssDevClientFileUrl,
7982
- importName: urlInfo.context.build
7983
- ? "installImportMetaCssBuild"
7984
- : "installImportMetaCssDev",
7985
- importAs: urlInfo.context.build
7986
- ? "__installImportMetaCssBuild__"
7987
- : "__installImportMetaCssDev__",
7746
+ content: urlInfo.content,
7747
+ importFrom: importMetaCssDevClientFileUrl,
7748
+ importName: "installImportMetaCssDev",
7749
+ importAs: "__installImportMetaCssDev__",
7750
+ hot: true,
7988
7751
  });
7989
7752
  },
7990
7753
  },
7991
7754
  };
7992
7755
  };
7993
7756
 
7757
+ const babelPluginRewriteImportMetaCssAssignment = (
7758
+ { types: t },
7759
+ { relativeUrl },
7760
+ ) => {
7761
+ return {
7762
+ name: "rewrite-import-meta-css-assignment",
7763
+ visitor: {
7764
+ AssignmentExpression(path) {
7765
+ const { left, right } = path.node;
7766
+ if (left.type !== "MemberExpression") {
7767
+ return;
7768
+ }
7769
+ const { object, property } = left;
7770
+ if (object.type !== "MetaProperty") {
7771
+ return;
7772
+ }
7773
+ if (object.meta.name !== "import" || object.property.name !== "meta") {
7774
+ return;
7775
+ }
7776
+ if (property.name !== "css") {
7777
+ return;
7778
+ }
7779
+ // already transformed (e.g. pre-built file): leave as-is
7780
+ if (right.type === "ArrayExpression") {
7781
+ return;
7782
+ }
7783
+ path.node.right = t.arrayExpression([
7784
+ right,
7785
+ t.stringLiteral(relativeUrl),
7786
+ ]);
7787
+ },
7788
+ },
7789
+ };
7790
+ };
7791
+
7994
7792
  const babelPluginMetadataUsesImportMetaCss = () => {
7995
7793
  return {
7996
7794
  name: "metadata-uses-import-meta-css",
@@ -8022,7 +7820,10 @@ const babelPluginMetadataUsesImportMetaCss = () => {
8022
7820
  };
8023
7821
  };
8024
7822
 
8025
- const injectImportMetaCss = (urlInfo, { importFrom, importName, importAs }) => {
7823
+ const injectImportMetaCss = (
7824
+ urlInfo,
7825
+ { content, importFrom, importName, importAs, hot },
7826
+ ) => {
8026
7827
  const importMetaCssClientFileReference = urlInfo.dependencies.inject({
8027
7828
  parentUrl: urlInfo.url,
8028
7829
  type: "js_import",
@@ -8031,14 +7832,16 @@ const injectImportMetaCss = (urlInfo, { importFrom, importName, importAs }) => {
8031
7832
  });
8032
7833
  let importVariableName;
8033
7834
  let importBeforeFrom;
8034
- if (importAs !== importName) {
7835
+ if (importAs && importAs !== importName) {
8035
7836
  importBeforeFrom = `{ ${importName} as ${importAs} }`;
8036
7837
  importVariableName = importAs;
8037
7838
  } else {
8038
7839
  importBeforeFrom = `{ ${importName} } }`;
8039
7840
  importVariableName = importName;
8040
7841
  }
8041
- let prelude = `import ${importBeforeFrom} from ${importMetaCssClientFileReference.generatedSpecifier};
7842
+
7843
+ const prelude = hot
7844
+ ? `import ${importBeforeFrom} from ${importMetaCssClientFileReference.generatedSpecifier};
8042
7845
 
8043
7846
  const remove = ${importVariableName}(import.meta);
8044
7847
  if (import.meta.hot) {
@@ -8047,9 +7850,13 @@ if (import.meta.hot) {
8047
7850
  });
8048
7851
  }
8049
7852
 
7853
+ `
7854
+ : `import ${importBeforeFrom} from ${importMetaCssClientFileReference.generatedSpecifier};
7855
+
7856
+ ${importVariableName}(import.meta);
7857
+
8050
7858
  `;
8051
7859
 
8052
- let content = urlInfo.content;
8053
7860
  return {
8054
7861
  content: `${prelude.replace(/\n/g, "")}${content}`,
8055
7862
  };
@@ -8783,7 +8590,7 @@ const jsenvPluginAutoreloadServer = ({
8783
8590
  );
8784
8591
  },
8785
8592
  },
8786
- devServerRoutes: [
8593
+ serverRoutes: [
8787
8594
  {
8788
8595
  endpoint: "GET /.internal/graph.json",
8789
8596
  description:
@@ -9075,7 +8882,7 @@ const jsenvPluginChromeDevtoolsJson = () => {
9075
8882
  return {
9076
8883
  name: "jsenv_plugin_chrome_devtools_json",
9077
8884
  appliesDuring: "dev",
9078
- devServerRoutes: [
8885
+ serverRoutes: [
9079
8886
  {
9080
8887
  endpoint: "GET /.well-known/appspecific/com.chrome.devtools.json",
9081
8888
  declarationSource: import.meta.url,
@@ -9095,7 +8902,7 @@ const jsenvPluginChromeDevtoolsJson = () => {
9095
8902
 
9096
8903
  const jsenvPluginAutoreloadOnServerRestart = () => {
9097
8904
  const autoreloadOnRestartClientFileUrl = import.meta
9098
- .resolve("@jsenv/server/src/services/autoreload_on_server_restart/client/autoreload_on_server_restart.js");
8905
+ .resolve("@jsenv/server/src/plugins/autoreload_on_server_restart/client/autoreload_on_server_restart.js");
9099
8906
 
9100
8907
  return {
9101
8908
  name: "jsenv:autoreload_on_server_restart",
@@ -9634,7 +9441,7 @@ const jsenvPluginServerEvents = ({ clientAutoreload }) => {
9634
9441
  return stringifyHtmlAst(htmlAst);
9635
9442
  },
9636
9443
  },
9637
- devServerRoutes: [
9444
+ serverRoutes: [
9638
9445
  {
9639
9446
  endpoint: "GET /.internal/events.websocket",
9640
9447
  description: `Jsenv dev server emit server events on this endpoint. When a file is saved the "reload" event is sent here.`,
@@ -9704,7 +9511,7 @@ const startDevServer = async ({
9704
9511
  logLevel = EXECUTED_BY_TEST_PLAN ? "warn" : "info",
9705
9512
  serverLogLevel = "warn",
9706
9513
  serverRouterLogLevel = "warn",
9707
- services = [],
9514
+ serverPlugins = [],
9708
9515
 
9709
9516
  signal = new AbortController().signal,
9710
9517
  handleSIGINT = true,
@@ -9810,10 +9617,10 @@ const startDevServer = async ({
9810
9617
  const serverStopAbortSignal = serverStopAbortController.signal;
9811
9618
  const kitchenCache = new Map();
9812
9619
 
9813
- const finalServices = [];
9620
+ const finalServerPlugins = [];
9814
9621
  // x-server-inspect service
9815
9622
  {
9816
- finalServices.push({
9623
+ finalServerPlugins.push({
9817
9624
  name: "jsenv:server_header",
9818
9625
  routes: [
9819
9626
  {
@@ -9839,8 +9646,8 @@ const startDevServer = async ({
9839
9646
  }
9840
9647
  // cors service
9841
9648
  {
9842
- finalServices.push(
9843
- jsenvServiceCORS({
9649
+ finalServerPlugins.push(
9650
+ serverPluginCORS({
9844
9651
  accessControlAllowRequestOrigin: true,
9845
9652
  accessControlAllowRequestMethod: true,
9846
9653
  accessControlAllowRequestHeaders: true,
@@ -9853,9 +9660,9 @@ const startDevServer = async ({
9853
9660
  }),
9854
9661
  );
9855
9662
  }
9856
- // custom services
9663
+ // custom server plugins
9857
9664
  {
9858
- finalServices.push(...services);
9665
+ finalServerPlugins.push(...serverPlugins);
9859
9666
  }
9860
9667
  // file_service
9861
9668
  {
@@ -9885,7 +9692,7 @@ const startDevServer = async ({
9885
9692
  sourceDirectoryUrl,
9886
9693
  });
9887
9694
 
9888
- const devServerPluginStore = await createPluginStore([
9695
+ const devServerJsenvPluginStore = await createJsenvPluginStore([
9889
9696
  jsenvPluginServerEvents({ clientAutoreload }),
9890
9697
  ...plugins,
9891
9698
  ...getCorePlugins({
@@ -10045,28 +9852,28 @@ const startDevServer = async ({
10045
9852
  );
10046
9853
  },
10047
9854
  );
10048
- const devServerPluginController = await createPluginController(
10049
- devServerPluginStore,
9855
+ const devServerJsenvPluginController = await createJsenvPluginsController(
9856
+ devServerJsenvPluginStore,
10050
9857
  kitchen,
10051
9858
  );
10052
- kitchen.setPluginController(devServerPluginController);
9859
+ kitchen.setJsenvPluginsController(devServerJsenvPluginController);
10053
9860
 
10054
9861
  serverStopCallbackSet.add(() => {
10055
- devServerPluginController.callHooks("destroy", kitchen.context);
9862
+ devServerJsenvPluginController.callHooks("destroy", kitchen.context);
10056
9863
  });
10057
9864
  kitchenCache.set(runtimeId, kitchen);
10058
9865
  onKitchenCreated(kitchen);
10059
9866
  return kitchen;
10060
9867
  };
10061
9868
 
10062
- finalServices.push({
9869
+ finalServerPlugins.push({
10063
9870
  name: "jsenv:dev_server_routes",
10064
9871
  augmentRouteFetchSecondArg: async (request) => {
10065
9872
  const kitchen = await getOrCreateKitchen(request);
10066
9873
  return { kitchen };
10067
9874
  },
10068
9875
  routes: [
10069
- ...devServerPluginStore.allDevServerRoutes,
9876
+ ...devServerJsenvPluginStore.allServerRoutes,
10070
9877
  {
10071
9878
  endpoint: "GET *",
10072
9879
  description: "Serve project files.",
@@ -10179,7 +9986,7 @@ const startDevServer = async ({
10179
9986
  reference,
10180
9987
  urlInfo,
10181
9988
  };
10182
- kitchen.pluginController.callHooks(
9989
+ kitchen.jsenvPluginsController.callHooks(
10183
9990
  "augmentResponse",
10184
9991
  augmentResponseInfo,
10185
9992
  (returnValue) => {
@@ -10266,11 +10073,11 @@ const startDevServer = async ({
10266
10073
  },
10267
10074
  ],
10268
10075
  });
10269
- finalServices.push(...devServerPluginStore.allDevServerServices);
10076
+ finalServerPlugins.push(...devServerJsenvPluginStore.allServerPlugins);
10270
10077
  }
10271
10078
  // jsenv error handler service
10272
10079
  {
10273
- finalServices.push({
10080
+ finalServerPlugins.push({
10274
10081
  name: "jsenv:omega_error_handler",
10275
10082
  handleError: (error) => {
10276
10083
  const getResponseForError = () => {
@@ -10307,8 +10114,8 @@ const startDevServer = async ({
10307
10114
  }
10308
10115
  // default error handler
10309
10116
  {
10310
- finalServices.push(
10311
- jsenvServiceErrorHandler({
10117
+ finalServerPlugins.push(
10118
+ serverPluginErrorHandler({
10312
10119
  sendErrorDetails: true,
10313
10120
  }),
10314
10121
  );
@@ -10330,7 +10137,7 @@ const startDevServer = async ({
10330
10137
  hostname,
10331
10138
  port,
10332
10139
  requestWaitingMs: 60_000,
10333
- services: finalServices,
10140
+ plugins: finalServerPlugins,
10334
10141
  });
10335
10142
  server.stoppedPromise.then((reason) => {
10336
10143
  onStop();