@jsenv/core 40.12.12 → 40.12.14

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/internal/convertFileSystemErrorToResponseProperties.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,70 @@ 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
+ return injectImportMetaCss(urlInfo, {
7735
+ content: code,
7736
+ importFrom: importMetaCssBuildClientFileUrl,
7737
+ importName: "installImportMetaCssBuild",
7738
+ importAs: "__installImportMetaCssBuild__",
7739
+ });
7740
+ }
7978
7741
  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__",
7742
+ content: urlInfo.content,
7743
+ importFrom: importMetaCssDevClientFileUrl,
7744
+ importName: "installImportMetaCssDev",
7745
+ importAs: "__installImportMetaCssDev__",
7746
+ hot: true,
7988
7747
  });
7989
7748
  },
7990
7749
  },
7991
7750
  };
7992
7751
  };
7993
7752
 
7753
+ const babelPluginRewriteImportMetaCssAssignment = (
7754
+ { types: t },
7755
+ { relativeUrl },
7756
+ ) => {
7757
+ return {
7758
+ name: "rewrite-import-meta-css-assignment",
7759
+ visitor: {
7760
+ AssignmentExpression(path) {
7761
+ const { left, right } = path.node;
7762
+ if (left.type !== "MemberExpression") {
7763
+ return;
7764
+ }
7765
+ const { object, property } = left;
7766
+ if (object.type !== "MetaProperty") {
7767
+ return;
7768
+ }
7769
+ if (object.meta.name !== "import" || object.property.name !== "meta") {
7770
+ return;
7771
+ }
7772
+ if (property.name !== "css") {
7773
+ return;
7774
+ }
7775
+ path.node.right = t.arrayExpression([
7776
+ right,
7777
+ t.stringLiteral(relativeUrl),
7778
+ ]);
7779
+ },
7780
+ },
7781
+ };
7782
+ };
7783
+
7994
7784
  const babelPluginMetadataUsesImportMetaCss = () => {
7995
7785
  return {
7996
7786
  name: "metadata-uses-import-meta-css",
@@ -8022,7 +7812,10 @@ const babelPluginMetadataUsesImportMetaCss = () => {
8022
7812
  };
8023
7813
  };
8024
7814
 
8025
- const injectImportMetaCss = (urlInfo, { importFrom, importName, importAs }) => {
7815
+ const injectImportMetaCss = (
7816
+ urlInfo,
7817
+ { content, importFrom, importName, importAs, hot },
7818
+ ) => {
8026
7819
  const importMetaCssClientFileReference = urlInfo.dependencies.inject({
8027
7820
  parentUrl: urlInfo.url,
8028
7821
  type: "js_import",
@@ -8031,14 +7824,16 @@ const injectImportMetaCss = (urlInfo, { importFrom, importName, importAs }) => {
8031
7824
  });
8032
7825
  let importVariableName;
8033
7826
  let importBeforeFrom;
8034
- if (importAs !== importName) {
7827
+ if (importAs && importAs !== importName) {
8035
7828
  importBeforeFrom = `{ ${importName} as ${importAs} }`;
8036
7829
  importVariableName = importAs;
8037
7830
  } else {
8038
7831
  importBeforeFrom = `{ ${importName} } }`;
8039
7832
  importVariableName = importName;
8040
7833
  }
8041
- let prelude = `import ${importBeforeFrom} from ${importMetaCssClientFileReference.generatedSpecifier};
7834
+
7835
+ const prelude = hot
7836
+ ? `import ${importBeforeFrom} from ${importMetaCssClientFileReference.generatedSpecifier};
8042
7837
 
8043
7838
  const remove = ${importVariableName}(import.meta);
8044
7839
  if (import.meta.hot) {
@@ -8047,9 +7842,13 @@ if (import.meta.hot) {
8047
7842
  });
8048
7843
  }
8049
7844
 
7845
+ `
7846
+ : `import ${importBeforeFrom} from ${importMetaCssClientFileReference.generatedSpecifier};
7847
+
7848
+ ${importVariableName}(import.meta);
7849
+
8050
7850
  `;
8051
7851
 
8052
- let content = urlInfo.content;
8053
7852
  return {
8054
7853
  content: `${prelude.replace(/\n/g, "")}${content}`,
8055
7854
  };
@@ -8783,7 +8582,7 @@ const jsenvPluginAutoreloadServer = ({
8783
8582
  );
8784
8583
  },
8785
8584
  },
8786
- devServerRoutes: [
8585
+ serverRoutes: [
8787
8586
  {
8788
8587
  endpoint: "GET /.internal/graph.json",
8789
8588
  description:
@@ -9075,7 +8874,7 @@ const jsenvPluginChromeDevtoolsJson = () => {
9075
8874
  return {
9076
8875
  name: "jsenv_plugin_chrome_devtools_json",
9077
8876
  appliesDuring: "dev",
9078
- devServerRoutes: [
8877
+ serverRoutes: [
9079
8878
  {
9080
8879
  endpoint: "GET /.well-known/appspecific/com.chrome.devtools.json",
9081
8880
  declarationSource: import.meta.url,
@@ -9095,7 +8894,7 @@ const jsenvPluginChromeDevtoolsJson = () => {
9095
8894
 
9096
8895
  const jsenvPluginAutoreloadOnServerRestart = () => {
9097
8896
  const autoreloadOnRestartClientFileUrl = import.meta
9098
- .resolve("@jsenv/server/src/services/autoreload_on_server_restart/client/autoreload_on_server_restart.js");
8897
+ .resolve("@jsenv/server/src/plugins/autoreload_on_server_restart/client/autoreload_on_server_restart.js");
9099
8898
 
9100
8899
  return {
9101
8900
  name: "jsenv:autoreload_on_server_restart",
@@ -9634,7 +9433,7 @@ const jsenvPluginServerEvents = ({ clientAutoreload }) => {
9634
9433
  return stringifyHtmlAst(htmlAst);
9635
9434
  },
9636
9435
  },
9637
- devServerRoutes: [
9436
+ serverRoutes: [
9638
9437
  {
9639
9438
  endpoint: "GET /.internal/events.websocket",
9640
9439
  description: `Jsenv dev server emit server events on this endpoint. When a file is saved the "reload" event is sent here.`,
@@ -9704,7 +9503,7 @@ const startDevServer = async ({
9704
9503
  logLevel = EXECUTED_BY_TEST_PLAN ? "warn" : "info",
9705
9504
  serverLogLevel = "warn",
9706
9505
  serverRouterLogLevel = "warn",
9707
- services = [],
9506
+ serverPlugins = [],
9708
9507
 
9709
9508
  signal = new AbortController().signal,
9710
9509
  handleSIGINT = true,
@@ -9810,10 +9609,10 @@ const startDevServer = async ({
9810
9609
  const serverStopAbortSignal = serverStopAbortController.signal;
9811
9610
  const kitchenCache = new Map();
9812
9611
 
9813
- const finalServices = [];
9612
+ const finalServerPlugins = [];
9814
9613
  // x-server-inspect service
9815
9614
  {
9816
- finalServices.push({
9615
+ finalServerPlugins.push({
9817
9616
  name: "jsenv:server_header",
9818
9617
  routes: [
9819
9618
  {
@@ -9839,8 +9638,8 @@ const startDevServer = async ({
9839
9638
  }
9840
9639
  // cors service
9841
9640
  {
9842
- finalServices.push(
9843
- jsenvServiceCORS({
9641
+ finalServerPlugins.push(
9642
+ serverPluginCORS({
9844
9643
  accessControlAllowRequestOrigin: true,
9845
9644
  accessControlAllowRequestMethod: true,
9846
9645
  accessControlAllowRequestHeaders: true,
@@ -9853,9 +9652,9 @@ const startDevServer = async ({
9853
9652
  }),
9854
9653
  );
9855
9654
  }
9856
- // custom services
9655
+ // custom server plugins
9857
9656
  {
9858
- finalServices.push(...services);
9657
+ finalServerPlugins.push(...serverPlugins);
9859
9658
  }
9860
9659
  // file_service
9861
9660
  {
@@ -9885,7 +9684,7 @@ const startDevServer = async ({
9885
9684
  sourceDirectoryUrl,
9886
9685
  });
9887
9686
 
9888
- const devServerPluginStore = await createPluginStore([
9687
+ const devServerJsenvPluginStore = await createJsenvPluginStore([
9889
9688
  jsenvPluginServerEvents({ clientAutoreload }),
9890
9689
  ...plugins,
9891
9690
  ...getCorePlugins({
@@ -10045,28 +9844,28 @@ const startDevServer = async ({
10045
9844
  );
10046
9845
  },
10047
9846
  );
10048
- const devServerPluginController = await createPluginController(
10049
- devServerPluginStore,
9847
+ const devServerJsenvPluginController = await createJsenvPluginsController(
9848
+ devServerJsenvPluginStore,
10050
9849
  kitchen,
10051
9850
  );
10052
- kitchen.setPluginController(devServerPluginController);
9851
+ kitchen.setJsenvPluginsController(devServerJsenvPluginController);
10053
9852
 
10054
9853
  serverStopCallbackSet.add(() => {
10055
- devServerPluginController.callHooks("destroy", kitchen.context);
9854
+ devServerJsenvPluginController.callHooks("destroy", kitchen.context);
10056
9855
  });
10057
9856
  kitchenCache.set(runtimeId, kitchen);
10058
9857
  onKitchenCreated(kitchen);
10059
9858
  return kitchen;
10060
9859
  };
10061
9860
 
10062
- finalServices.push({
9861
+ finalServerPlugins.push({
10063
9862
  name: "jsenv:dev_server_routes",
10064
9863
  augmentRouteFetchSecondArg: async (request) => {
10065
9864
  const kitchen = await getOrCreateKitchen(request);
10066
9865
  return { kitchen };
10067
9866
  },
10068
9867
  routes: [
10069
- ...devServerPluginStore.allDevServerRoutes,
9868
+ ...devServerJsenvPluginStore.allServerRoutes,
10070
9869
  {
10071
9870
  endpoint: "GET *",
10072
9871
  description: "Serve project files.",
@@ -10179,7 +9978,7 @@ const startDevServer = async ({
10179
9978
  reference,
10180
9979
  urlInfo,
10181
9980
  };
10182
- kitchen.pluginController.callHooks(
9981
+ kitchen.jsenvPluginsController.callHooks(
10183
9982
  "augmentResponse",
10184
9983
  augmentResponseInfo,
10185
9984
  (returnValue) => {
@@ -10266,11 +10065,11 @@ const startDevServer = async ({
10266
10065
  },
10267
10066
  ],
10268
10067
  });
10269
- finalServices.push(...devServerPluginStore.allDevServerServices);
10068
+ finalServerPlugins.push(...devServerJsenvPluginStore.allServerPlugins);
10270
10069
  }
10271
10070
  // jsenv error handler service
10272
10071
  {
10273
- finalServices.push({
10072
+ finalServerPlugins.push({
10274
10073
  name: "jsenv:omega_error_handler",
10275
10074
  handleError: (error) => {
10276
10075
  const getResponseForError = () => {
@@ -10307,8 +10106,8 @@ const startDevServer = async ({
10307
10106
  }
10308
10107
  // default error handler
10309
10108
  {
10310
- finalServices.push(
10311
- jsenvServiceErrorHandler({
10109
+ finalServerPlugins.push(
10110
+ serverPluginErrorHandler({
10312
10111
  sendErrorDetails: true,
10313
10112
  }),
10314
10113
  );
@@ -10330,7 +10129,7 @@ const startDevServer = async ({
10330
10129
  hostname,
10331
10130
  port,
10332
10131
  requestWaitingMs: 60_000,
10333
- services: finalServices,
10132
+ plugins: finalServerPlugins,
10334
10133
  });
10335
10134
  server.stoppedPromise.then((reason) => {
10336
10135
  onStop();