@discord/intl-loader-core 0.16.1 → 0.18.0-canary.40cad9d

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@discord/intl-loader-core",
3
- "version": "0.16.1",
3
+ "version": "0.18.0-canary.40cad9d",
4
4
  "license": "MIT",
5
5
  "description": "Core utilities for writing loaders and transformers using @discord/intl",
6
6
  "author": "Jon Egeland",
@@ -28,7 +28,7 @@
28
28
  "dependencies": {
29
29
  "chokidar": "^3.6.0",
30
30
  "debug": "^4.3.6",
31
- "@discord/intl-message-database": "0.16.1"
31
+ "@discord/intl-message-database": "0.18.0"
32
32
  },
33
33
  "devDependencies": {
34
34
  "@types/debug": "^4.1.12",
@@ -18,18 +18,19 @@
18
18
  *
19
19
  * // SomeConsumer.tsx
20
20
  * import someModuleMessages from 'SomeModule.messages.js';
21
- * i18n.format(someModuleMessages.THIS_IS_A_MESSAGE, {values: "I'm a value!"});
21
+ * intl.format(someModuleMessages.THIS_IS_A_MESSAGE, {values: "I'm a value!"});
22
22
  * ```
23
23
  *
24
24
  * This transformer will only handle `SomeModule.messages.js`, and will output
25
25
  * something like:
26
26
  *
27
27
  * ```typescript
28
- * const {i18n} = require('@discord/intl');
29
- * const _keys = ["a9fn23"];
30
- * const _locales = {"en-US": () => require('./messages/en-US.messages.json')};
31
- * export const messagesLoader = createLoader(_keys, _locales);
32
- * export default messagesLoader.getBinds();
28
+ * const {createLoader} = require('@discord/intl');
29
+ * const _localeMap = {"en-US": () => require('./messages/en-US.messages.json')};
30
+ * export const messagesLoader = createLoader(_localeMap);
31
+ * export default {
32
+ * a9fn23(locale) => messagesLoader.get("a9fn23", locale)
33
+ * };
33
34
  * ```
34
35
  *
35
36
  * Notice how the message keys have been hashed into short keys, and the
@@ -41,7 +42,7 @@
41
42
  *
42
43
  * ```typescript
43
44
  * import someModuleMessages from 'SomeModule.messages.js';
44
- * i18n.format(someModuleMessages["a9fn23"], {values: "i'm a value!"});
45
+ * intl.format(someModuleMessages["a9fn23"], {values: "i'm a value!"});
45
46
  * ```
46
47
  *
47
48
  * The transformed file also contains a named export for `messagesLoader`,
@@ -87,9 +88,8 @@ class MessageDefinitionsTransformer {
87
88
  getLocaleRequireMap() {
88
89
  const localeProperties = [];
89
90
  for (const [locale, importPath] of Object.entries(this.options.localeMap)) {
90
- // This assumes that the author has specified `importPath`
91
- // as a properly-resolvable path for the bundler, which we can't easily
92
- // enforce, unfortunately.
91
+ // This assumes that the author has specified `importPath` as a properly-resolvable path for
92
+ // the bundler, which we can't easily enforce, unfortunately.
93
93
  localeProperties.push(`"${locale}": () => ${this.options.getTranslationImport(importPath)}`);
94
94
  }
95
95
 
@@ -111,27 +111,78 @@ class MessageDefinitionsTransformer {
111
111
  }
112
112
 
113
113
  /**
114
- * Return a map of key names to bound message getter functions. If `preGenerateBinds` is
114
+ * When `option.proxyBinds` is set to true, this method is invoked to create it.
115
+ *
116
+ * The binds proxy is a plain `Proxy` object with configuration applied to make it act and
117
+ * function like a complete object, but without having to instantiate potentially thousands of
118
+ * binds during initialization. The proxy intentionally does _not_ support iteration nor `key in`
119
+ * queries, as they require up-front initialization that is too costly when multiple thousands
120
+ * of message keys are included.
121
+ *
122
+ * However, the proxy _does_ implement `ownKeys`, such that the returned list of keys represents
123
+ * all of the messages that have been _accessed_ through this proxy so far. This is generally
124
+ * more of a debugging utility than anything else, but can be useful for diagnosing when messages
125
+ * are used in critical paths or otherwise.
126
+ *
127
+ * @param {string} bindFunc Code expression that creates a getter bind
128
+ * @returns {string}
129
+ */
130
+ createBindsProxy(bindFunc) {
131
+ return `new Proxy({},
132
+ {
133
+ ownKeys(self) {
134
+ return Reflect.ownKeys(self);
135
+ },
136
+ getOwnPropertyDescriptor(self, prop) {
137
+ return {
138
+ value: self[prop] ||= ${bindFunc},
139
+ configurable: true,
140
+ enumerable: true,
141
+ writable: false,
142
+ };
143
+ },
144
+ get(self, prop) {
145
+ if (prop === '$$typeof') {
146
+ return 'object';
147
+ }
148
+ if (prop === Symbol.toStringTag) {
149
+ return 'IntlMessagesProxy';
150
+ }
151
+
152
+ self[prop] ||= ${bindFunc};
153
+ return self[prop];
154
+ },
155
+ },
156
+ )`;
157
+ }
158
+
159
+ /**
160
+ * Return a map of key names to bound message getter functions. If `proxyBinds` is
115
161
  * configured to be `true`, the binds will be created as a constant object in the output.
116
162
  * Otherwise, the generation will be done at runtime through the `getBinds` method on the loader.
117
163
  *
118
164
  * @returns {string[]}
119
165
  */
120
166
  createLoaderAndBinds() {
121
- if (this.options.preGenerateBinds) {
122
- const bindLines = Object.keys(this.options.messageKeys).map(
123
- (bind) => `"${bind}"(locale) { return ${this.loaderName}.get("${bind}", locale) }`,
124
- );
125
- return [
126
- `const binds = {${bindLines.join(',')}};`,
127
- `const ${this.loaderName} = createLoader(Object.keys(binds), _locales, _defaultLocale);`,
128
- ];
129
- } else {
130
- return [
131
- `const _keys = ${JSON.stringify(Object.keys(this.options.messageKeys))};`,
132
- `const ${this.loaderName} = createLoader(_keys, _locales, _defaultLocale);`,
133
- `const binds = ${this.loaderName}.getBinds();`,
134
- ];
167
+ switch (this.options.bindMode) {
168
+ case 'proxy':
169
+ return [
170
+ `const ${this.loaderName} = createLoader([], _localeMap, _defaultLocale);`,
171
+ `const binds = ${this.createBindsProxy(`(locale) => ${this.loaderName}.get(prop, locale)`)};`,
172
+ ];
173
+ case 'literal': {
174
+ const bindLines = Object.keys(this.options.messageKeys).map(
175
+ (bind) => `"${bind}"(locale) { return ${this.loaderName}.get("${bind}", locale) }`,
176
+ );
177
+ return [
178
+ `const binds = {${bindLines.join(',')}};`,
179
+ `const ${this.loaderName} = createLoader(_localeMap, _defaultLocale);`,
180
+ ];
181
+ }
182
+ default:
183
+ throw new Error(
184
+ `Unknown value for intl transformer option 'bindMode': ${this.options.bindMode}`,
185
+ );
135
186
  }
136
187
  }
137
188
 
@@ -167,7 +218,7 @@ class MessageDefinitionsTransformer {
167
218
  return [
168
219
  this.options.getPrelude?.() ?? '// No additional prelude was configured.',
169
220
  `const {createLoader} = require('@discord/intl');`,
170
- `const _locales = ${this.getLocaleRequireMap()};`,
221
+ `const _localeMap = ${this.getLocaleRequireMap()};`,
171
222
  `const _defaultLocale = ${JSON.stringify(this.options.defaultLocale)};`,
172
223
  ...this.createLoaderAndBinds(),
173
224
  ...this.debugModeSetup(),
@@ -18,18 +18,19 @@
18
18
  *
19
19
  * // SomeConsumer.tsx
20
20
  * import someModuleMessages from 'SomeModule.messages.js';
21
- * i18n.format(someModuleMessages.THIS_IS_A_MESSAGE, {values: "I'm a value!"});
21
+ * intl.format(someModuleMessages.THIS_IS_A_MESSAGE, {values: "I'm a value!"});
22
22
  * ```
23
23
  *
24
24
  * This transformer will only handle `SomeModule.messages.js`, and will output
25
25
  * something like:
26
26
  *
27
27
  * ```typescript
28
- * const {i18n} = require('@discord/intl');
29
- * const _keys = ["a9fn23"];
30
- * const _locales = {"en-US": () => require('./messages/en-US.messages.json')};
31
- * export const messagesLoader = createLoader(_keys, _locales);
32
- * export default messagesLoader.getBinds();
28
+ * const {createLoader} = require('@discord/intl');
29
+ * const _localeMap = {"en-US": () => require('./messages/en-US.messages.json')};
30
+ * export const messagesLoader = createLoader(_localeMap);
31
+ * export default {
32
+ * a9fn23(locale) => messagesLoader.get("a9fn23", locale)
33
+ * };
33
34
  * ```
34
35
  *
35
36
  * Notice how the message keys have been hashed into short keys, and the
@@ -41,7 +42,7 @@
41
42
  *
42
43
  * ```typescript
43
44
  * import someModuleMessages from 'SomeModule.messages.js';
44
- * i18n.format(someModuleMessages["a9fn23"], {values: "i'm a value!"});
45
+ * intl.format(someModuleMessages["a9fn23"], {values: "i'm a value!"});
45
46
  * ```
46
47
  *
47
48
  * The transformed file also contains a named export for `messagesLoader`,
@@ -91,7 +92,25 @@ export class MessageDefinitionsTransformer {
91
92
  */
92
93
  debugModeSetup(): string[];
93
94
  /**
94
- * Return a map of key names to bound message getter functions. If `preGenerateBinds` is
95
+ * When `option.proxyBinds` is set to true, this method is invoked to create it.
96
+ *
97
+ * The binds proxy is a plain `Proxy` object with configuration applied to make it act and
98
+ * function like a complete object, but without having to instantiate potentially thousands of
99
+ * binds during initialization. The proxy intentionally does _not_ support iteration nor `key in`
100
+ * queries, as they require up-front initialization that is too costly when multiple thousands
101
+ * of message keys are included.
102
+ *
103
+ * However, the proxy _does_ implement `ownKeys`, such that the returned list of keys represents
104
+ * all of the messages that have been _accessed_ through this proxy so far. This is generally
105
+ * more of a debugging utility than anything else, but can be useful for diagnosing when messages
106
+ * are used in critical paths or otherwise.
107
+ *
108
+ * @param {string} bindFunc Code expression that creates a getter bind
109
+ * @returns {string}
110
+ */
111
+ createBindsProxy(bindFunc: string): string;
112
+ /**
113
+ * Return a map of key names to bound message getter functions. If `proxyBinds` is
95
114
  * configured to be `true`, the binds will be created as a constant object in the output.
96
115
  * Otherwise, the generation will be done at runtime through the `getBinds` method on the loader.
97
116
  *
package/types/types.d.ts CHANGED
@@ -54,21 +54,22 @@ export interface MessageDefinitionsTransformerOptions {
54
54
  */
55
55
  exportMode?: 'esm' | 'commonjs' | 'transpiledEsModule';
56
56
  /**
57
- * Control how loader binds are generated by the transformer. When `false` (the default), binds
58
- * will be created at runtime using `messagesLoader.getBinds()`. This mode saves space, at the
59
- * cost of runtime overhead to iterate and create bound functions. When `true`, the iteration of
60
- * messages will be done at bundle time to create a constant JS object containing all the message
61
- * keys as inline functions that act as binds. This mode is much faster for runtimes to
62
- * initialize, at the cost of a relatively large space increase (~70% larger than using the
63
- * runtime bind mode). However, this mode can be compressed substantially when compiled to
64
- * bytecode, as is the case when creating mobile bundles with Hermes. In that case, pre-generating
65
- * binds gains the speed increase while still staying relatively small in the output bundle.
66
- * Because of this, `metro-intl-transformer` will instead default to `true` for this option and
67
- * can be disabled when not using Hermes.
57
+ * Control how loader binds are generated by the transformer. When the value is `'proxy'` (the
58
+ * default), no binds will actually be created directly. Instead, the transformer creates and
59
+ * exports a Proxy object that intercepts all unique requests for messages and creates loader
60
+ * binds on the fly. For projects with thousands of keys in messages files, this can save time
61
+ * and processing power when loading the application, and is especially beneficial for lower-end
62
+ * mobile devices that struggle to create such large objects quickly. Note that this proxy does
63
+ * not act exactly like a normal object and will not work with some expected language features,
64
+ * like `key in messages` queries and `{...messages}` spreads.
68
65
  *
69
- * @default false
66
+ * When set to `'literal'`, the transformer instead exports an object literal with every compiled
67
+ * message key as a property defined directly on it. For smaller messages files, this reduces some
68
+ * of the runtime access overhead in exchange for more time spent during module initialization.
69
+ *
70
+ * @default 'proxy'
70
71
  */
71
- preGenerateBinds?: boolean;
72
+ bindMode?: 'proxy' | 'literal';
72
73
  }
73
74
 
74
75
  /**
package/types.d.ts CHANGED
@@ -54,21 +54,22 @@ export interface MessageDefinitionsTransformerOptions {
54
54
  */
55
55
  exportMode?: 'esm' | 'commonjs' | 'transpiledEsModule';
56
56
  /**
57
- * Control how loader binds are generated by the transformer. When `false` (the default), binds
58
- * will be created at runtime using `messagesLoader.getBinds()`. This mode saves space, at the
59
- * cost of runtime overhead to iterate and create bound functions. When `true`, the iteration of
60
- * messages will be done at bundle time to create a constant JS object containing all the message
61
- * keys as inline functions that act as binds. This mode is much faster for runtimes to
62
- * initialize, at the cost of a relatively large space increase (~70% larger than using the
63
- * runtime bind mode). However, this mode can be compressed substantially when compiled to
64
- * bytecode, as is the case when creating mobile bundles with Hermes. In that case, pre-generating
65
- * binds gains the speed increase while still staying relatively small in the output bundle.
66
- * Because of this, `metro-intl-transformer` will instead default to `true` for this option and
67
- * can be disabled when not using Hermes.
57
+ * Control how loader binds are generated by the transformer. When the value is `'proxy'` (the
58
+ * default), no binds will actually be created directly. Instead, the transformer creates and
59
+ * exports a Proxy object that intercepts all unique requests for messages and creates loader
60
+ * binds on the fly. For projects with thousands of keys in messages files, this can save time
61
+ * and processing power when loading the application, and is especially beneficial for lower-end
62
+ * mobile devices that struggle to create such large objects quickly. Note that this proxy does
63
+ * not act exactly like a normal object and will not work with some expected language features,
64
+ * like `key in messages` queries and `{...messages}` spreads.
68
65
  *
69
- * @default false
66
+ * When set to `'literal'`, the transformer instead exports an object literal with every compiled
67
+ * message key as a property defined directly on it. For smaller messages files, this reduces some
68
+ * of the runtime access overhead in exchange for more time spent during module initialization.
69
+ *
70
+ * @default 'proxy'
70
71
  */
71
- preGenerateBinds?: boolean;
72
+ bindMode?: 'proxy' | 'literal';
72
73
  }
73
74
 
74
75
  /**