@bigbinary/neeto-commons-frontend 4.13.165 → 4.13.166-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,237 @@
1
+ // Stops chunk hash changes from cascading through the whole bundle.
2
+ //
3
+ // esbuild embeds the hashed filename of every imported chunk in the importing
4
+ // chunk, so a change in one chunk renames every chunk that (transitively)
5
+ // imports it. A one-line change in a shared chunk ends up invalidating almost
6
+ // the entire bundle in the browser cache.
7
+ //
8
+ // After the build, this module:
9
+ // 1. gives every chunk a stable logical name. Named chunks (dynamic import
10
+ // targets) are identified by their entry point; shared chunks by the set
11
+ // of entry points that reach them, which is exactly how esbuild decides
12
+ // what goes into a shared chunk. Neither changes when code changes.
13
+ // 2. rewrites chunk references to the logical names. The logical hash has
14
+ // the same length as esbuild's, so sourcemap columns stay valid.
15
+ // 3. renames each chunk to a hash of its rewritten content, so a chunk is
16
+ // renamed only when its own code changes.
17
+ // 4. writes importmap.json mapping logical names to real names, plus the
18
+ // chunks each entry and each root component needs (for modulepreload).
19
+ // The Rails layout inlines it as <script type="importmap">.
20
+
21
+ /* eslint-disable @bigbinary/neeto/no-dangling-constants */
22
+ const crypto = require("crypto");
23
+ const fs = require("fs");
24
+ const path = require("path");
25
+
26
+ const IMPORT_MAP_FILE_NAME = "importmap.json";
27
+ const HASH_LENGTH = 8;
28
+ // Same alphabet esbuild uses for [hash].
29
+ const HASH_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
30
+ const CHUNK_FILE_RE = /^(.+)-([A-Z2-7]{8})\.digested\.js$/;
31
+ const CHUNK_REFERENCE_RE = /[\w$.-]+-[A-Z2-7]{8}\.digested\.js/g;
32
+ const ENGLISH_TRANSLATIONS_RE = /dist\/translations\/en\.json$/;
33
+
34
+ const toHash = value => {
35
+ const digest = crypto.createHash("sha256").update(value).digest();
36
+
37
+ return Array.from(
38
+ digest.subarray(0, HASH_LENGTH),
39
+ byte => HASH_ALPHABET[byte % HASH_ALPHABET.length]
40
+ ).join("");
41
+ };
42
+
43
+ const staticClosure = (outputs, root) => {
44
+ const seen = new Set();
45
+ const stack = [root];
46
+ while (stack.length) {
47
+ const current = stack.pop();
48
+ if (seen.has(current) || !outputs[current]) continue;
49
+
50
+ seen.add(current);
51
+ outputs[current].imports
52
+ .filter(({ kind }) => kind === "import-statement")
53
+ .forEach(({ path: importPath }) => stack.push(importPath));
54
+ }
55
+
56
+ return seen;
57
+ };
58
+
59
+ const logicalSignatures = (outputs, jsOutputs) => {
60
+ const roots = jsOutputs.filter(output => outputs[output].entryPoint);
61
+ const reachedBy = {};
62
+ roots.forEach(root => {
63
+ staticClosure(outputs, root).forEach(output => {
64
+ (reachedBy[output] ??= []).push(outputs[root].entryPoint);
65
+ });
66
+ });
67
+
68
+ return Object.fromEntries(
69
+ jsOutputs.map(output => {
70
+ const { entryPoint, inputs } = outputs[output];
71
+ if (entryPoint) return [output, `entry:${entryPoint}`];
72
+
73
+ const signature = reachedBy[output]
74
+ ? `shared:${reachedBy[output].sort().join("|")}`
75
+ : `inputs:${Object.keys(inputs).sort().join("|")}`;
76
+
77
+ return [output, signature];
78
+ })
79
+ );
80
+ };
81
+
82
+ const assignLogicalNames = (chunks, signatures) => {
83
+ const taken = new Set();
84
+
85
+ return Object.fromEntries(
86
+ chunks.map(({ output, name }) => {
87
+ let salt = 0;
88
+ let logicalName;
89
+ do {
90
+ const hash = toHash(`${signatures[output]}#${salt}`);
91
+ logicalName = `${name}-${hash}.digested.js`;
92
+ salt += 1;
93
+ } while (taken.has(logicalName));
94
+ taken.add(logicalName);
95
+
96
+ return [output, logicalName];
97
+ })
98
+ );
99
+ };
100
+
101
+ const rewriteReferences = (content, logicalNameByFileName) =>
102
+ content.replace(
103
+ CHUNK_REFERENCE_RE,
104
+ reference => logicalNameByFileName[reference] ?? reference
105
+ );
106
+
107
+ const renameSourcemap = (oldPath, newPath) => {
108
+ const oldMapPath = `${oldPath}.map`;
109
+ if (!fs.existsSync(oldMapPath)) return;
110
+
111
+ const map = JSON.parse(fs.readFileSync(oldMapPath, "utf-8"));
112
+ map.file = path.basename(newPath);
113
+ fs.writeFileSync(`${newPath}.map`, JSON.stringify(map));
114
+ if (oldMapPath !== `${newPath}.map`) fs.unlinkSync(oldMapPath);
115
+ };
116
+
117
+ const relativeToOutdir = (outdir, filePath) =>
118
+ path.relative(outdir, filePath).split(path.sep).join("/");
119
+
120
+ const writeImportMap = ({
121
+ metafile,
122
+ outdir,
123
+ publicPath,
124
+ absWorkingDir = process.cwd(),
125
+ }) => {
126
+ const cwd = absWorkingDir;
127
+ const { outputs } = metafile;
128
+ const jsOutputs = Object.keys(outputs).filter(output =>
129
+ output.endsWith(".js")
130
+ );
131
+
132
+ const chunks = jsOutputs
133
+ .map(output => {
134
+ const match = path.basename(output).match(CHUNK_FILE_RE);
135
+
136
+ return match && { output, name: match[1] };
137
+ })
138
+ .filter(Boolean);
139
+
140
+ const signatures = logicalSignatures(outputs, jsOutputs);
141
+ const logicalNames = assignLogicalNames(chunks, signatures);
142
+ const logicalNameByFileName = Object.fromEntries(
143
+ chunks.map(({ output }) => [path.basename(output), logicalNames[output]])
144
+ );
145
+
146
+ const finalPaths = {};
147
+ const imports = {};
148
+ jsOutputs.forEach(output => {
149
+ const absolutePath = path.resolve(cwd, output);
150
+ const content = rewriteReferences(
151
+ fs.readFileSync(absolutePath, "utf-8"),
152
+ logicalNameByFileName
153
+ );
154
+
155
+ const logicalName = logicalNames[output];
156
+ if (!logicalName) {
157
+ // Entry points keep their name; Sprockets digests them.
158
+ fs.writeFileSync(absolutePath, content);
159
+ finalPaths[output] = absolutePath;
160
+
161
+ return;
162
+ }
163
+
164
+ const { name } = chunks.find(chunk => chunk.output === output);
165
+ const finalPath = path.join(
166
+ path.dirname(absolutePath),
167
+ `${name}-${toHash(content)}.digested.js`
168
+ );
169
+ fs.writeFileSync(finalPath, content);
170
+ if (finalPath !== absolutePath) fs.unlinkSync(absolutePath);
171
+ renameSourcemap(absolutePath, finalPath);
172
+
173
+ finalPaths[output] = finalPath;
174
+ const logicalPath = path.join(path.dirname(absolutePath), logicalName);
175
+ imports[relativeToOutdir(outdir, logicalPath)] = relativeToOutdir(
176
+ outdir,
177
+ finalPath
178
+ );
179
+ });
180
+
181
+ const englishTranslations = jsOutputs.filter(output =>
182
+ ENGLISH_TRANSLATIONS_RE.test(outputs[output].entryPoint ?? "")
183
+ );
184
+
185
+ const topLevelEntries = jsOutputs.filter(
186
+ output => !logicalNames[output] && outputs[output].entryPoint
187
+ );
188
+
189
+ const preloads = Object.fromEntries(
190
+ topLevelEntries.map(entry => {
191
+ const closure = [
192
+ ...staticClosure(outputs, entry),
193
+ ...englishTranslations,
194
+ ];
195
+
196
+ const files = [...new Set(closure)]
197
+ .filter(output => output !== entry)
198
+ .map(output => relativeToOutdir(outdir, finalPaths[output]));
199
+
200
+ return [path.basename(entry, ".js"), files];
201
+ })
202
+ );
203
+
204
+ // Root components the entries import dynamically (the packs' componentMap).
205
+ // The layout preloads the one the page renders, which saves a round trip
206
+ // after initializeApplication() without running the component early.
207
+ const components = {};
208
+ topLevelEntries.forEach(entry => {
209
+ const entryClosure = staticClosure(outputs, entry);
210
+ outputs[entry].imports
211
+ .filter(
212
+ ({ kind, path: importPath }) =>
213
+ kind === "dynamic-import" && logicalNames[importPath]
214
+ )
215
+ .forEach(({ path: component }) => {
216
+ const { name } = chunks.find(chunk => chunk.output === component);
217
+ components[name] = [...staticClosure(outputs, component)]
218
+ .filter(output => !entryClosure.has(output))
219
+ .map(output => relativeToOutdir(outdir, finalPaths[output]));
220
+ });
221
+ });
222
+
223
+ fs.writeFileSync(
224
+ path.join(outdir, IMPORT_MAP_FILE_NAME),
225
+ JSON.stringify({ publicPath, imports, preloads, components }, null, 2)
226
+ );
227
+ };
228
+
229
+ const removeImportMap = outdir => {
230
+ fs.rmSync(path.join(outdir, IMPORT_MAP_FILE_NAME), { force: true });
231
+ };
232
+
233
+ module.exports = {
234
+ writeImportMap,
235
+ removeImportMap,
236
+ IMPORT_MAP_FILE_NAME,
237
+ };
@@ -11,7 +11,10 @@ const PREVAL_IMPORT_ENGLISH_TRANSLATIONS =
11
11
 
12
12
  const SOURCEMAP_URL_RE = /\/\/[#@] sourceMappingURL=[^\n]+/gm;
13
13
 
14
- const prevalPlugin = () => ({
14
+ // With inlineEnglishTranslations: false, English is loaded like every other
15
+ // language (a separate chunk) instead of being inlined into the i18n
16
+ // initializer. Otherwise any English string change modifies the core chunk.
17
+ const prevalPlugin = ({ inlineEnglishTranslations = true } = {}) => ({
15
18
  name: "preval-plugin",
16
19
  setup(build) {
17
20
  build.onLoad(
@@ -26,9 +29,10 @@ const prevalPlugin = () => ({
26
29
  const transformedContent = contents
27
30
  .replace(SOURCEMAP_URL_RE, "")
28
31
  .replace(PREVAL_IMPORT_TRANSLATION_IMPORTS, getTranslationImports())
29
- .replace(
30
- PREVAL_IMPORT_ENGLISH_TRANSLATIONS,
31
- JSON.stringify(getEnglishTranslations())
32
+ .replace(PREVAL_IMPORT_ENGLISH_TRANSLATIONS, () =>
33
+ inlineEnglishTranslations
34
+ ? JSON.stringify(getEnglishTranslations())
35
+ : "null"
32
36
  );
33
37
 
34
38
  return { contents: transformedContent, loader: "js" };
@@ -9,6 +9,8 @@ const {
9
9
  DEFAULT_VISUALIZER_TEMPLATE,
10
10
  NODE_MODULES_PKG_REGEX,
11
11
  } = require("./constants");
12
+ const { writeImportMap, removeImportMap } = require("./importMap");
13
+ const { prevalPlugin } = require("./plugins/preval");
12
14
 
13
15
  // colors for console output
14
16
  const consoleColors = {
@@ -132,6 +134,25 @@ const fixSourcemaps = outdir => {
132
134
  }
133
135
  };
134
136
 
137
+ // `importMap` is not an esbuild option. It is on unless the product sets it to
138
+ // false.
139
+ const resolveImportMap = ({ importMap = true, ...config }) => ({
140
+ ...config,
141
+ importMap,
142
+ });
143
+
144
+ const withImportMapPlugins = config =>
145
+ config.importMap
146
+ ? {
147
+ ...config,
148
+ plugins: config.plugins.map(plugin =>
149
+ plugin.name === "preval-plugin"
150
+ ? prevalPlugin({ inlineEnglishTranslations: false })
151
+ : plugin
152
+ ),
153
+ }
154
+ : config;
155
+
135
156
  const build = async config => {
136
157
  try {
137
158
  console.info(
@@ -146,15 +167,35 @@ const build = async config => {
146
167
  ? process.argv[process.argv.indexOf("--template") + 1]
147
168
  : DEFAULT_VISUALIZER_TEMPLATE;
148
169
 
170
+ const { importMap, ...esbuildConfig } = withImportMapPlugins(
171
+ resolveImportMap(config)
172
+ );
149
173
  const buildConfig = entrypoint
150
- ? { ...config, entryPoints: pick([entrypoint], config.entryPoints) }
151
- : config;
152
-
153
- const result = await esbuild.build(buildConfig);
174
+ ? {
175
+ ...esbuildConfig,
176
+ entryPoints: pick([entrypoint], esbuildConfig.entryPoints),
177
+ }
178
+ : esbuildConfig;
179
+
180
+ const shouldAnalyze = buildConfig.metafile;
181
+ removeImportMap(buildConfig.outdir);
182
+ const result = await esbuild.build({
183
+ ...buildConfig,
184
+ metafile: shouldAnalyze || importMap,
185
+ });
154
186
 
155
187
  fixSourcemaps(buildConfig.outdir);
156
188
 
157
- if (result.metafile) {
189
+ if (importMap) {
190
+ writeImportMap({
191
+ metafile: result.metafile,
192
+ outdir: buildConfig.outdir,
193
+ publicPath: buildConfig.publicPath,
194
+ absWorkingDir: buildConfig.absWorkingDir,
195
+ });
196
+ }
197
+
198
+ if (shouldAnalyze) {
158
199
  const metaFilePath = writeMetafile(result.metafile, buildConfig.outdir);
159
200
  await generateBundleReport({
160
201
  metaFilePath,
@@ -173,7 +214,10 @@ const build = async config => {
173
214
  }
174
215
  };
175
216
 
176
- const watch = async config => {
217
+ const watch = async ({ importMap: _importMap, ...config }) => {
218
+ // Watch builds are served without an import map; remove any left over from a
219
+ // previous production build so the layout does not use stale mappings.
220
+ removeImportMap(config.outdir);
177
221
  const ctx = await esbuild.context(config);
178
222
  await ctx.watch();
179
223
  await ctx.serve({
package/dist/.ready CHANGED
@@ -1 +1 @@
1
- Built at 2026-10-08T10:35:55.480Z
1
+ Built at 2026-10-12T05:40:31.752Z
@@ -23,12 +23,64 @@ function _regeneratorDefine2(e, r, n, t) { var i = Object.defineProperty; try {
23
23
 
24
24
  // eslint-disable-next-line import/no-mutable-exports
25
25
  exports.taxonomies = {};
26
- var initializeI18n = /*#__PURE__*/function () {
27
- var _ref = asyncToGenerator._asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee(hostTranslations) {
28
- var _window$globalProps, _window$globalProps2;
29
- var userLanguage, translationImports, englishTranslations, englishResource, resources, defaultTaxonomyKeys, defaultTaxonomies, hostTaxonomies;
26
+ var prefetchTranslations = /*#__PURE__*/function () {
27
+ var _ref = asyncToGenerator._asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee(importTranslations) {
30
28
  return _regenerator().w(function (_context) {
31
- while (1) switch (_context.n) {
29
+ while (1) switch (_context.p = _context.n) {
30
+ case 0:
31
+ _context.p = 0;
32
+ _context.n = 1;
33
+ return importTranslations === null || importTranslations === void 0 ? void 0 : importTranslations();
34
+ case 1:
35
+ _context.n = 3;
36
+ break;
37
+ case 2:
38
+ _context.p = 2;
39
+ _context.v;
40
+ case 3:
41
+ return _context.a(2);
42
+ }
43
+ }, _callee, null, [[0, 2]]);
44
+ }));
45
+ return function prefetchTranslations(_x) {
46
+ return _ref.apply(this, arguments);
47
+ };
48
+ }();
49
+ var loadEnglishTranslations = /*#__PURE__*/function () {
50
+ var _ref2 = asyncToGenerator._asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee2(translationImports, userLanguage) {
51
+ var _englishModule$defaul;
52
+ var englishModule;
53
+ return _regenerator().w(function (_context2) {
54
+ while (1) switch (_context2.n) {
55
+ case 0:
56
+ // Start fetching the user's language alongside English instead of after it.
57
+ if (userLanguage && userLanguage !== "en") {
58
+ prefetchTranslations(translationImports === null || translationImports === void 0 ? void 0 : translationImports[userLanguage]);
59
+ }
60
+ if (translationImports !== null && translationImports !== void 0 && translationImports.en) {
61
+ _context2.n = 1;
62
+ break;
63
+ }
64
+ return _context2.a(2, {});
65
+ case 1:
66
+ _context2.n = 2;
67
+ return utils_retryImport(translationImports.en);
68
+ case 2:
69
+ englishModule = _context2.v;
70
+ return _context2.a(2, (_englishModule$defaul = englishModule === null || englishModule === void 0 ? void 0 : englishModule["default"]) !== null && _englishModule$defaul !== void 0 ? _englishModule$defaul : englishModule);
71
+ }
72
+ }, _callee2);
73
+ }));
74
+ return function loadEnglishTranslations(_x2, _x3) {
75
+ return _ref2.apply(this, arguments);
76
+ };
77
+ }();
78
+ var initializeI18n = /*#__PURE__*/function () {
79
+ var _ref3 = asyncToGenerator._asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee3(hostTranslations) {
80
+ var _window$globalProps, _preval$require, _window$globalProps2;
81
+ var userLanguage, translationImports, englishTranslations, englishResource, resources, defaultTaxonomyKeys, defaultTaxonomies, hostTaxonomies, _t2;
82
+ return _regenerator().w(function (_context3) {
83
+ while (1) switch (_context3.n) {
32
84
  case 0:
33
85
  userLanguage = (_window$globalProps = window.globalProps) === null || _window$globalProps === void 0 || (_window$globalProps = _window$globalProps.user) === null || _window$globalProps === void 0 ? void 0 : _window$globalProps.language;
34
86
  if (!initializers_constants.IS_CHROME_EXTENSION) {
@@ -37,8 +89,22 @@ var initializeI18n = /*#__PURE__*/function () {
37
89
  document.documentElement.setAttribute("lang", userLanguage);
38
90
  }
39
91
  }
40
- translationImports = preval.require("../../../configs/scripts/buildTranslationImports.js");
41
- englishTranslations = preval.require("../../../configs/scripts/getEnglishTranslation.js");
92
+ translationImports = preval.require("../../../configs/scripts/buildTranslationImports.js"); // null when the build keeps English out of this file (import map builds);
93
+ // English is then loaded like any other language.
94
+ if (!((_preval$require = preval.require("../../../configs/scripts/getEnglishTranslation.js")) !== null && _preval$require !== void 0)) {
95
+ _context3.n = 1;
96
+ break;
97
+ }
98
+ _t2 = _preval$require;
99
+ _context3.n = 3;
100
+ break;
101
+ case 1:
102
+ _context3.n = 2;
103
+ return loadEnglishTranslations(translationImports, userLanguage);
104
+ case 2:
105
+ _t2 = _context3.v;
106
+ case 3:
107
+ englishTranslations = _t2;
42
108
  englishResource = {
43
109
  en: {
44
110
  translation: englishTranslations || {}
@@ -54,7 +120,7 @@ var initializeI18n = /*#__PURE__*/function () {
54
120
  }));
55
121
  hostTaxonomies = ((_window$globalProps2 = window.globalProps) === null || _window$globalProps2 === void 0 ? void 0 : _window$globalProps2.taxonomies) || {};
56
122
  exports.taxonomies = initializers_utils_index.replaceNullValuesWithGetter(ramda.mergeDeepLeft(hostTaxonomies, defaultTaxonomies));
57
- _context.n = 1;
123
+ _context3.n = 4;
58
124
  return i18next.use(index.Browser).use(reactI18next.initReactI18next).use(initializers_utils_customPostProcessors.sentenceCaseProcessor).use(index.resourcesToBackend(function (language) {
59
125
  return utils_retryImport(translationImports === null || translationImports === void 0 ? void 0 : translationImports[language]);
60
126
  })).init({
@@ -90,22 +156,22 @@ var initializeI18n = /*#__PURE__*/function () {
90
156
  lookupCookie: "lang"
91
157
  }
92
158
  });
93
- case 1:
159
+ case 4:
94
160
  if (!initializers_constants.IS_CHROME_EXTENSION) {
95
- _context.n = 2;
161
+ _context3.n = 5;
96
162
  break;
97
163
  }
98
- return _context.a(2);
99
- case 2:
164
+ return _context3.a(2);
165
+ case 5:
100
166
  document.documentElement.setAttribute("dir", i18next.dir() || "ltr");
101
167
  document.documentElement.setAttribute("lang", i18next.resolvedLanguage || "en");
102
- case 3:
103
- return _context.a(2);
168
+ case 6:
169
+ return _context3.a(2);
104
170
  }
105
- }, _callee);
171
+ }, _callee3);
106
172
  }));
107
- return function initializeI18n(_x) {
108
- return _ref.apply(this, arguments);
173
+ return function initializeI18n(_x4) {
174
+ return _ref3.apply(this, arguments);
109
175
  };
110
176
  }();
111
177