@emulsify/core 4.2.1 → 4.3.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.
Files changed (78) hide show
  1. package/.storybook/main-vite.js +91 -36
  2. package/README.md +185 -56
  3. package/config/a11y.config.js +2 -1
  4. package/config/vite/entries.js +4 -4
  5. package/config/vite/plugins/{copy-src-assets.js → assets/copy-src-assets.js} +1 -1
  6. package/config/vite/plugins/{copy-twig-files.js → assets/copy-twig-files.js} +1 -1
  7. package/config/vite/plugins/{css-asset-relativizer.js → assets/css-asset-relativizer.js} +9 -0
  8. package/config/vite/plugins/{mirror-components.js → assets/mirror-components.js} +2 -28
  9. package/config/vite/plugins/{source-file-index.js → assets/source-file-index.js} +1 -1
  10. package/config/vite/plugins/{svg-sprite.js → assets/svg-sprite.js} +2 -2
  11. package/config/vite/plugins/index.js +23 -14
  12. package/config/vite/plugins/reporter/asset-resolver.js +541 -0
  13. package/config/vite/plugins/reporter/build-errors.js +284 -0
  14. package/config/vite/plugins/reporter/diagnostics.js +366 -0
  15. package/config/vite/plugins/reporter/format.js +199 -0
  16. package/config/vite/plugins/reporter/index.js +308 -0
  17. package/config/vite/plugins/reporter/render.js +758 -0
  18. package/config/vite/plugins/reporter/sass-logger.js +237 -0
  19. package/config/vite/plugins/reporter/vite-logger.js +188 -0
  20. package/config/vite/plugins/reporter/watch-mode.js +40 -0
  21. package/config/vite/plugins/{twig-extension-installers.js → twig/extension-installers.js} +1 -1
  22. package/config/vite/{twig-extensions.js → plugins/twig/extensions.js} +2 -2
  23. package/config/vite/plugins/{twig-module.js → twig/twig-module.js} +197 -85
  24. package/config/vite/plugins/{virtual-twig-asset-sources.js → twig/virtual-twig-asset-sources.js} +37 -136
  25. package/config/vite/plugins/{virtual-twig-globs.js → twig/virtual-twig-globs.js} +3 -32
  26. package/config/vite/plugins/{vituum-patch.js → twig/vituum-patch.js} +3 -3
  27. package/config/vite/plugins.js +1 -1
  28. package/config/vite/project-config.js +1 -1
  29. package/config/vite/project-structure.js +1 -1
  30. package/config/vite/utils/lru.js +77 -0
  31. package/config/vite/utils/package-version.js +42 -0
  32. package/config/vite/utils/paths.js +1 -9
  33. package/config/vite/utils/react-singleton.js +1 -1
  34. package/config/vite/vite.config.js +51 -5
  35. package/package.json +93 -58
  36. package/scripts/a11y.js +115 -23
  37. package/scripts/audit/checks/core-imports.js +78 -0
  38. package/scripts/audit/checks/css-asset-references.js +99 -0
  39. package/scripts/audit/checks/drupal-assumptions.js +48 -0
  40. package/scripts/audit/checks/files-outside-roots.js +53 -0
  41. package/scripts/audit/checks/generated-package-scripts.js +113 -0
  42. package/scripts/audit/checks/legacy-twig-stories.js +33 -0
  43. package/scripts/audit/checks/package-overrides.js +91 -0
  44. package/scripts/audit/checks/project-config.js +71 -0
  45. package/scripts/audit/checks/story-discovery.js +35 -0
  46. package/scripts/audit/checks/twig-references.js +69 -0
  47. package/scripts/audit/checks/twig-volume.js +54 -0
  48. package/scripts/audit/checks/webpack-patterns.js +86 -0
  49. package/scripts/audit/index.js +177 -0
  50. package/scripts/audit/lib/css.js +165 -0
  51. package/scripts/audit/lib/files.js +168 -0
  52. package/scripts/audit/lib/findings.js +31 -0
  53. package/scripts/audit/lib/package-json.js +65 -0
  54. package/scripts/audit/lib/twig.js +227 -0
  55. package/scripts/audit/report.js +273 -0
  56. package/scripts/audit-twig-stories.js +115 -78
  57. package/scripts/audit.js +150 -1632
  58. package/scripts/check-node-version.js +136 -10
  59. package/scripts/inspect-components.js +456 -0
  60. package/scripts/lib/cli.js +179 -0
  61. package/scripts/lib/fs.js +31 -0
  62. package/scripts/lib/proc.js +78 -0
  63. package/scripts/lib/text.js +14 -0
  64. package/scripts/loadYaml.js +2 -2
  65. package/src/extensions/shared/attributes.js +3 -3
  66. package/src/extensions/shared/lists.js +2 -6
  67. package/src/extensions/shared/root-relative.js +38 -0
  68. package/src/storybook/index.js +4 -0
  69. package/src/storybook/render-twig.js +1 -1
  70. package/src/storybook/render-web-component.js +459 -0
  71. package/src/storybook/twig/asset-source-runtime.js +193 -0
  72. package/src/storybook/twig/{source-extensions.js → constants.js} +3 -1
  73. package/src/storybook/twig/reference-paths.js +2 -13
  74. package/src/storybook/twig/setup.js +12 -2
  75. package/src/storybook/twig/source-function.js +5 -2
  76. package/config/vite/utils/unique.js +0 -36
  77. package/src/storybook/twig/include.js +0 -28
  78. package/src/storybook/twig/source-events.js +0 -5
@@ -10,22 +10,24 @@
10
10
  */
11
11
 
12
12
  import fs from 'fs';
13
+ import { readFile, stat } from 'fs/promises';
13
14
  import { basename, dirname, isAbsolute, relative, resolve } from 'path';
14
15
  import Twig from 'twig';
15
16
 
16
17
  import {
17
18
  getTwigFunctionMap,
18
19
  registerTwigExtensions,
19
- } from '../../../src/extensions/twig/index.js';
20
- import { toRootRelativePath } from '../../../src/storybook/twig/reference-paths.js';
21
- import { resolveProjectStructure } from '../project-structure.js';
20
+ } from '../../../../src/extensions/twig/index.js';
21
+ import { toRootRelativePath } from '../../../../src/storybook/twig/reference-paths.js';
22
+ import { resolveProjectStructure } from '../../project-structure.js';
22
23
  import {
23
- registerConfiguredTwigExtensions,
24
24
  shouldRegisterDrupalTwigFilters,
25
- } from '../twig-extensions.js';
26
- import { firstExistingPath, safeExists } from '../utils/fs-safe.js';
27
- import { toPosixPath } from '../utils/paths.js';
28
- import { unique } from '../utils/unique.js';
25
+ installProjectTwigExtensions,
26
+ } from './extensions.js';
27
+ import { firstExistingPath, safeExists } from '../../utils/fs-safe.js';
28
+ import { createLruCache } from '../../utils/lru.js';
29
+ import { toPosixPath } from '../../utils/paths.js';
30
+ import { unique } from '../../../../src/extensions/shared/lists.js';
29
31
 
30
32
  /** Twig token types that can reference another template file. */
31
33
  const templateReferenceTokenTypes = [
@@ -38,6 +40,10 @@ const templateReferenceTokenTypes = [
38
40
 
39
41
  const includeFunctionName = 'include';
40
42
  const sourceFunctionName = 'source';
43
+ const VIRTUAL_TWIG_DEPENDENCY_PREFIX = 'virtual:emulsify-twig-dep:';
44
+ const RESOLVED_VIRTUAL_TWIG_DEPENDENCY_PREFIX = `\0${VIRTUAL_TWIG_DEPENDENCY_PREFIX}`;
45
+ const maxCompiledTemplateCacheEntries = 2000;
46
+ const maxResolutionCacheEntries = 5000;
41
47
  const expressionTokenTypes = {
42
48
  arrayEnd: 'Twig.expression.type.array.end',
43
49
  arrayStart: 'Twig.expression.type.array.start',
@@ -63,16 +69,16 @@ const expressionClosingTokenTypes = new Set([
63
69
  /**
64
70
  * Cache compiled Twig templates by absolute path for the life of one build.
65
71
  *
66
- * @type {Map<string, { mtimeMs: number, compiled: { code: string, includes: string[], sourceReferences: string[], templateId: string, templateParams: object } }>}
72
+ * @type {ReturnType<typeof createLruCache>}
67
73
  */
68
- const compileCache = new Map();
74
+ const compileCache = createLruCache(maxCompiledTemplateCacheEntries);
69
75
 
70
76
  /**
71
77
  * Cache resolved Twig include paths by source directory, reference, and roots.
72
78
  *
73
- * @type {Map<string, string|null>}
79
+ * @type {ReturnType<typeof createLruCache>}
74
80
  */
75
- const resolutionCache = new Map();
81
+ const resolutionCache = createLruCache(maxResolutionCacheEntries);
76
82
 
77
83
  /**
78
84
  * Cache recursively discovered component grouping directories by root.
@@ -121,6 +127,13 @@ const canMemoizeByEnv = (env) => env && typeof env === 'object';
121
127
  * @returns {boolean} TRUE when the request is a renderable Twig module.
122
128
  */
123
129
  const isTwigModuleRequest = (id) => {
130
+ if (
131
+ id.startsWith(VIRTUAL_TWIG_DEPENDENCY_PREFIX) ||
132
+ id.startsWith(RESOLVED_VIRTUAL_TWIG_DEPENDENCY_PREFIX)
133
+ ) {
134
+ return false;
135
+ }
136
+
124
137
  const [filePath, query = ''] = id.split('?');
125
138
  if (!filePath.endsWith('.twig')) return false;
126
139
  return !query || query === 'twig' || !/(^|&)(raw|url)\b/.test(query);
@@ -134,6 +147,24 @@ const isTwigModuleRequest = (id) => {
134
147
  */
135
148
  const stripRequestQuery = (id) => id.split('?')[0];
136
149
 
150
+ const encodeVirtualTwigDependencyId = (filePath) =>
151
+ `${VIRTUAL_TWIG_DEPENDENCY_PREFIX}${encodeURIComponent(resolve(filePath))}`;
152
+
153
+ const resolvedVirtualTwigDependencyId = (filePath) =>
154
+ `${RESOLVED_VIRTUAL_TWIG_DEPENDENCY_PREFIX}${encodeURIComponent(
155
+ resolve(filePath),
156
+ )}`;
157
+
158
+ const decodeVirtualTwigDependencyId = (id) => {
159
+ const normalizedId = id.startsWith('\0') ? id.slice(1) : id;
160
+
161
+ return normalizedId.startsWith(VIRTUAL_TWIG_DEPENDENCY_PREFIX)
162
+ ? decodeURIComponent(
163
+ normalizedId.slice(VIRTUAL_TWIG_DEPENDENCY_PREFIX.length),
164
+ )
165
+ : '';
166
+ };
167
+
137
168
  /**
138
169
  * Extract the first argument from a Twig function token parameter list.
139
170
  *
@@ -445,6 +476,21 @@ const templateIdForPath = (filePath, options) => {
445
476
  const makeTemplateInstantiationCode = (templateId, params) =>
446
477
  `Twig.twig(${JSON.stringify({ ...params, id: templateId })})`;
447
478
 
479
+ const makeTemplateInstantiationFromParamsCode = (
480
+ templateIdExpression,
481
+ paramsExpression,
482
+ ) => `Twig.twig({ ...${paramsExpression}, id: ${templateIdExpression} })`;
483
+
484
+ const generateTwigDependencyModule = async (filePath, options) => {
485
+ const compiled = await compileTwigTemplate(filePath, options, compileCache);
486
+
487
+ return [
488
+ `export const templateId = ${JSON.stringify(compiled.templateId)};`,
489
+ `export const templateParams = ${JSON.stringify(compiled.templateParams)};`,
490
+ '',
491
+ ].join('\n');
492
+ };
493
+
448
494
  /**
449
495
  * Rewrite static include/import/embed references to module-local template IDs.
450
496
  *
@@ -779,12 +825,12 @@ const invalidateKnownResolutionCacheEntries = (filePath) => {
779
825
  * @param {string} filePath - Absolute template file path.
780
826
  * @param {ReturnType<typeof makeTwigPluginOptions>} options - Twig plugin options.
781
827
  * @param {typeof compileCache} [cache=compileCache] - Shared compile cache.
782
- * @returns {{ code: string, includes: string[], sourceReferences: string[] }} Compiled template code and references.
828
+ * @returns {Promise<{ code: string, includes: string[], sourceReferences: string[] }>} Compiled template code and references.
783
829
  */
784
- const compileTwigTemplate = (filePath, options, cache = compileCache) => {
830
+ const compileTwigTemplate = async (filePath, options, cache = compileCache) => {
785
831
  const absoluteFilePath = resolve(filePath);
786
832
  knownTwigFiles.add(absoluteFilePath);
787
- const { mtimeMs } = fs.statSync(absoluteFilePath);
833
+ const { mtimeMs } = await stat(absoluteFilePath);
788
834
  const cached = cache.get(absoluteFilePath);
789
835
  if (cached?.mtimeMs === mtimeMs) {
790
836
  return cached.compiled;
@@ -792,9 +838,9 @@ const compileTwigTemplate = (filePath, options, cache = compileCache) => {
792
838
 
793
839
  const compilerTwig = Twig.factory();
794
840
  registerTwigExtensions(compilerTwig);
795
- registerConfiguredTwigExtensions(compilerTwig, options);
841
+ installProjectTwigExtensions(compilerTwig, options);
796
842
 
797
- const source = fs.readFileSync(absoluteFilePath, 'utf8');
843
+ const source = await readFile(absoluteFilePath, 'utf8');
798
844
  const compileOptions = {
799
845
  allowInlineIncludes: true,
800
846
  namespaces: options.namespaces,
@@ -1042,7 +1088,27 @@ export function emulsifyTwigModulePlugin(options) {
1042
1088
  knownTwigFiles.clear();
1043
1089
  transformedTwigModules.clear();
1044
1090
  },
1045
- transform(...args) {
1091
+ resolveId(id) {
1092
+ if (id.startsWith(VIRTUAL_TWIG_DEPENDENCY_PREFIX)) {
1093
+ return `\0${id}`;
1094
+ }
1095
+
1096
+ return null;
1097
+ },
1098
+ async load(id) {
1099
+ if (!id.startsWith(RESOLVED_VIRTUAL_TWIG_DEPENDENCY_PREFIX)) {
1100
+ return null;
1101
+ }
1102
+
1103
+ const dependencyPath = decodeVirtualTwigDependencyId(id);
1104
+ if (!dependencyPath) {
1105
+ return null;
1106
+ }
1107
+
1108
+ this.addWatchFile(dependencyPath);
1109
+ return generateTwigDependencyModule(dependencyPath, options);
1110
+ },
1111
+ async transform(...args) {
1046
1112
  const [, id] = args;
1047
1113
  if (!isTwigModuleRequest(id)) {
1048
1114
  return null;
@@ -1051,10 +1117,11 @@ export function emulsifyTwigModulePlugin(options) {
1051
1117
  const filePath = stripRequestQuery(id);
1052
1118
  const sourceFilePath = resolve(filePath);
1053
1119
  transformedTwigModules.add(sourceFilePath);
1054
- /** @type {Map<string, ReturnType<typeof compileTwigTemplate>>} */
1120
+ /** @type {Map<string, Awaited<ReturnType<typeof compileTwigTemplate>>>} */
1055
1121
  const compiledDependencyTemplates = new Map();
1056
1122
  /** @type {Map<string, Set<string>>} */
1057
1123
  const compiledDependencyReferences = new Map();
1124
+ const queuedDependencyTemplates = new Set();
1058
1125
  clearDependencyImporter(sourceFilePath);
1059
1126
 
1060
1127
  /**
@@ -1089,13 +1156,15 @@ export function emulsifyTwigModulePlugin(options) {
1089
1156
  * @param {string[]} templateReferences - Raw references found in tokens.
1090
1157
  * @param {string} fromFilePath - Absolute importer path.
1091
1158
  * @param {typeof compileCache} cache - Shared compile cache.
1092
- * @returns {void}
1159
+ * @returns {Promise<void>}
1093
1160
  */
1094
- const compileDependencyTemplates = (
1161
+ const compileDependencyTemplates = async (
1095
1162
  templateReferences,
1096
1163
  fromFilePath,
1097
1164
  cache,
1098
1165
  ) => {
1166
+ const dependencyRecords = [];
1167
+
1099
1168
  for (const templatePath of templateReferences) {
1100
1169
  const dependencyPath = resolveTwigTemplate(
1101
1170
  templatePath,
@@ -1106,22 +1175,38 @@ export function emulsifyTwigModulePlugin(options) {
1106
1175
  const absoluteDependencyPath = resolve(dependencyPath);
1107
1176
  addCompiledDependencyReference(absoluteDependencyPath, templatePath);
1108
1177
  if (absoluteDependencyPath === sourceFilePath) continue;
1178
+ if (
1179
+ compiledDependencyTemplates.has(absoluteDependencyPath) ||
1180
+ queuedDependencyTemplates.has(absoluteDependencyPath)
1181
+ ) {
1182
+ continue;
1183
+ }
1109
1184
 
1110
- const dependencyTemplate = compiledDependencyTemplates.get(
1111
- absoluteDependencyPath,
1112
- );
1113
- if (dependencyTemplate) continue;
1185
+ queuedDependencyTemplates.add(absoluteDependencyPath);
1114
1186
 
1115
1187
  addDependencyImporter(absoluteDependencyPath, sourceFilePath);
1116
1188
  this.addWatchFile(absoluteDependencyPath);
1117
1189
 
1118
- const compiled = compileTwigTemplate(
1190
+ dependencyRecords.push({ absoluteDependencyPath });
1191
+ }
1192
+
1193
+ const compiledDependencyRecords = await Promise.all(
1194
+ dependencyRecords.map(async ({ absoluteDependencyPath }) => ({
1119
1195
  absoluteDependencyPath,
1120
- options,
1121
- cache,
1122
- );
1196
+ compiled: await compileTwigTemplate(
1197
+ absoluteDependencyPath,
1198
+ options,
1199
+ cache,
1200
+ ),
1201
+ })),
1202
+ );
1203
+
1204
+ for (const {
1205
+ absoluteDependencyPath,
1206
+ compiled,
1207
+ } of compiledDependencyRecords) {
1123
1208
  compiledDependencyTemplates.set(absoluteDependencyPath, compiled);
1124
- compileDependencyTemplates(
1209
+ await compileDependencyTemplates(
1125
1210
  compiled.includes,
1126
1211
  absoluteDependencyPath,
1127
1212
  cache,
@@ -1133,51 +1218,56 @@ export function emulsifyTwigModulePlugin(options) {
1133
1218
  * Emit raw Twig source registrations for static source() references.
1134
1219
  *
1135
1220
  * @param {string} fromFilePath - Absolute file that contains the reference.
1136
- * @param {ReturnType<typeof compileTwigTemplate>} compiledTemplate - Compiled template metadata.
1137
- * @returns {string[]} Generated source map registration statements.
1221
+ * @param {Awaited<ReturnType<typeof compileTwigTemplate>>} compiledTemplate - Compiled template metadata.
1222
+ * @returns {Promise<string[]>} Generated source map registration statements.
1138
1223
  */
1139
- const makeSourceTemplateRegistrations = (
1224
+ const makeSourceTemplateRegistrations = async (
1140
1225
  fromFilePath,
1141
1226
  compiledTemplate,
1142
- ) =>
1143
- (compiledTemplate.sourceReferences || []).flatMap((reference) => {
1144
- if (
1145
- reference.startsWith('@assets/') ||
1146
- reference.startsWith('assets/')
1147
- ) {
1148
- return [];
1149
- }
1227
+ ) => {
1228
+ const registrations = await Promise.all(
1229
+ (compiledTemplate.sourceReferences || []).map(async (reference) => {
1230
+ if (
1231
+ reference.startsWith('@assets/') ||
1232
+ reference.startsWith('assets/')
1233
+ ) {
1234
+ return [];
1235
+ }
1150
1236
 
1151
- const sourcePath = resolveTwigTemplate(
1152
- reference,
1153
- dirname(fromFilePath),
1154
- options,
1155
- );
1156
- if (!sourcePath) return [];
1157
-
1158
- const absoluteSourcePath = resolve(sourcePath);
1159
- addDependencyImporter(absoluteSourcePath, sourceFilePath);
1160
- this.addWatchFile(absoluteSourcePath);
1161
- const sourceText = fs.readFileSync(absoluteSourcePath, 'utf8');
1162
-
1163
- return unique([
1164
- reference,
1165
- templateIdForPath(absoluteSourcePath, options),
1166
- ]).map(
1167
- (key) =>
1168
- `__emulsifySourceTemplates.set(${JSON.stringify(
1169
- key,
1170
- )}, ${JSON.stringify(sourceText)});`,
1171
- );
1172
- });
1237
+ const sourcePath = resolveTwigTemplate(
1238
+ reference,
1239
+ dirname(fromFilePath),
1240
+ options,
1241
+ );
1242
+ if (!sourcePath) return [];
1243
+
1244
+ const absoluteSourcePath = resolve(sourcePath);
1245
+ addDependencyImporter(absoluteSourcePath, sourceFilePath);
1246
+ this.addWatchFile(absoluteSourcePath);
1247
+ const sourceText = await readFile(absoluteSourcePath, 'utf8');
1248
+
1249
+ return unique([
1250
+ reference,
1251
+ templateIdForPath(absoluteSourcePath, options),
1252
+ ]).map(
1253
+ (key) =>
1254
+ `__emulsifySourceTemplates.set(${JSON.stringify(
1255
+ key,
1256
+ )}, ${JSON.stringify(sourceText)});`,
1257
+ );
1258
+ }),
1259
+ );
1260
+
1261
+ return registrations.flat();
1262
+ };
1173
1263
 
1174
1264
  try {
1175
- const compiled = compileTwigTemplate(
1265
+ const compiled = await compileTwigTemplate(
1176
1266
  sourceFilePath,
1177
1267
  options,
1178
1268
  compileCache,
1179
1269
  );
1180
- compileDependencyTemplates(
1270
+ await compileDependencyTemplates(
1181
1271
  compiled.includes,
1182
1272
  sourceFilePath,
1183
1273
  compileCache,
@@ -1190,29 +1280,40 @@ export function emulsifyTwigModulePlugin(options) {
1190
1280
  .map(([dependencyPath, compiledDependency], index) => ({
1191
1281
  dependencyPath,
1192
1282
  compiledDependency,
1193
- variableName: `__emulsifyDependency${index}`,
1283
+ idVariableName: `__emulsifyDependencyTemplateId${index}`,
1284
+ paramsVariableName: `__emulsifyDependencyTemplateParams${index}`,
1285
+ templateVariableName: `__emulsifyDependency${index}`,
1286
+ virtualModuleId: encodeVirtualTwigDependencyId(dependencyPath),
1194
1287
  }));
1288
+ const dependencyTemplateImports = dependencyTemplateRecords
1289
+ .map(
1290
+ ({ idVariableName, paramsVariableName, virtualModuleId }) =>
1291
+ `import { templateId as ${idVariableName}, templateParams as ${paramsVariableName} } from ${JSON.stringify(
1292
+ virtualModuleId,
1293
+ )};`,
1294
+ )
1295
+ .join('\n');
1195
1296
  const dependencyTemplateCode = dependencyTemplateRecords
1196
1297
  .map(
1197
- ({ compiledDependency, variableName }) => `
1198
- const ${variableName} = ${makeTemplateInstantiationCode(
1199
- compiledDependency.templateId,
1200
- compiledDependency.templateParams,
1298
+ ({ idVariableName, paramsVariableName, templateVariableName }) => `
1299
+ const ${templateVariableName} = ${makeTemplateInstantiationFromParamsCode(
1300
+ idVariableName,
1301
+ paramsVariableName,
1201
1302
  )};
1202
- ${variableName}.method = 'emulsify';
1303
+ ${templateVariableName}.method = 'emulsify';
1203
1304
  `,
1204
1305
  )
1205
1306
  .join('\n');
1206
1307
  const includeTemplateRegistrations = [
1207
1308
  ...dependencyTemplateRecords.flatMap(
1208
- ({ dependencyPath, variableName }) =>
1309
+ ({ dependencyPath, templateVariableName }) =>
1209
1310
  Array.from(
1210
1311
  compiledDependencyReferences.get(dependencyPath) || [],
1211
1312
  ).map(
1212
1313
  (reference) =>
1213
1314
  `__emulsifyIncludeTemplates.set(${JSON.stringify(
1214
1315
  reference,
1215
- )}, (context = {}) => ${variableName}.render(context));`,
1316
+ )}, (context = {}) => ${templateVariableName}.render(context));`,
1216
1317
  ),
1217
1318
  ),
1218
1319
  ...Array.from(
@@ -1224,29 +1325,34 @@ export function emulsifyTwigModulePlugin(options) {
1224
1325
  )}, (context = {}) => __emulsifyTemplate.render(context));`,
1225
1326
  ),
1226
1327
  ].join('\n');
1227
- const sourceTemplateRegistrations = [
1228
- ...dependencyTemplateRecords.flatMap(
1229
- ({ dependencyPath, compiledDependency }) =>
1230
- makeSourceTemplateRegistrations(
1231
- dependencyPath,
1232
- compiledDependency,
1233
- ),
1234
- ),
1235
- ...makeSourceTemplateRegistrations(sourceFilePath, compiled),
1236
- ].join('\n');
1328
+ const sourceTemplateRegistrations = (
1329
+ await Promise.all([
1330
+ ...dependencyTemplateRecords.map(
1331
+ ({ dependencyPath, compiledDependency }) =>
1332
+ makeSourceTemplateRegistrations(
1333
+ dependencyPath,
1334
+ compiledDependency,
1335
+ ),
1336
+ ),
1337
+ makeSourceTemplateRegistrations(sourceFilePath, compiled),
1338
+ ])
1339
+ )
1340
+ .flat()
1341
+ .join('\n');
1237
1342
  const renderErrorPrefix = JSON.stringify(
1238
1343
  `An error occurred whilst rendering ${toPosixPath(filePath)}: `,
1239
1344
  );
1240
1345
  const moduleCode = `
1346
+ ${dependencyTemplateImports}
1241
1347
  import { factory } from 'twig';
1242
1348
  import { registerTwigExtensions } from '@emulsify/core/extensions/twig';
1243
- import { registerConfiguredTwigExtensions } from 'virtual:emulsify-twig-extension-installers';
1349
+ import { installProjectTwigExtensions } from 'virtual:emulsify-twig-extension-installers';
1244
1350
  import { createTwigIncludeFunction } from '@emulsify/core/storybook/twig/include-function';
1245
1351
  import { createTwigSourceFunction } from '@emulsify/core/storybook/twig/source-function';
1246
1352
 
1247
1353
  const Twig = factory();
1248
1354
  registerTwigExtensions(Twig);
1249
- registerConfiguredTwigExtensions(Twig);
1355
+ installProjectTwigExtensions(Twig);
1250
1356
  Twig.extend((TwigCore) => {
1251
1357
  TwigCore.Templates.registerLoader(
1252
1358
  'emulsify',
@@ -1329,6 +1435,7 @@ export function emulsifyTwigModulePlugin(options) {
1329
1435
 
1330
1436
  const fileExists = safeExists(filePath);
1331
1437
  const knownFile = knownTwigFiles.has(filePath);
1438
+ const dependencyModuleId = resolvedVirtualTwigDependencyId(filePath);
1332
1439
  const importers = dependencyImporters.get(filePath);
1333
1440
  const projectRoot = options.projectDir || options.root;
1334
1441
  const projectPathChanged =
@@ -1388,6 +1495,11 @@ export function emulsifyTwigModulePlugin(options) {
1388
1495
  }
1389
1496
 
1390
1497
  const modules = new Set(moduleGraph.getModulesByFile(filePath) || []);
1498
+ const dependencyModule = moduleGraph.getModuleById?.(dependencyModuleId);
1499
+ if (dependencyModule) {
1500
+ moduleGraph.invalidateModule?.(dependencyModule);
1501
+ modules.add(dependencyModule);
1502
+ }
1391
1503
  for (const importer of affectedImporters) {
1392
1504
  compileCache.delete(importer);
1393
1505