Repository: style-dictionary/style-dictionary (JavaScript)

Issue:
[Format Request]: Javascript esm flattern format request
Hi, 

I know there is [javascriptModuleFlat](https://github.com/amzn/style-dictionary/blob/main/lib/common/formats.js#L483) format, wondering if there is a similar one but with esm export? basically, it's replacing `module.exports` with `export default`. I tried to create my custom formatter, however the `formatJS` is not exported as part of the util, recreating it might be reinventing the wheel. If having esm flat format is not possible would you guys consider exporting the `formatJS()` function?

I think this would be a great addition.
Thanks


Discussion: Hi @kossel,
I hope you're well?
I don't know if I understood your question correctly, but it seems to me that StyleDictionary already allows you to do this natively using the built-in format: **javascript/esm**.

You can see the associated documentation here:
=> https://styledictionary.com/reference/hooks/formats/predefined/#javascriptesm

Personally, I use this format for myES6/ESM projects.
Hi @tiloyi 
Yes I know there is `javascript/esm` format but, it's object format instead of flatten format, maybe I missed something but after experimenting with it, it has 2 disadvantages vs the flatten one.

- code search:
When we want to find where a certain token is being used it's much easier to serach globally `colorTextHeader` than the object version

- treeshaking:
given:
```
const token = {
  a: {
    aa: {
      aaa: '#fff',
      aaa2: '#000'
    },
    bb: {
      bbb: '#fff',
      bbb2: '#000'
    }
  }
}
```
if we use `a.aaa.aaa`, the `bb` object will not be included in the bundle, but the `aaa2` will still be part of the bundle.

Maybe a `flat` option for `javascript/esm` so it doesn't end up with many very similar formatters
Yeah I would be open to accepting a contribution adding a "flat" option to this format.

I'm just wondering, would we export 1 variable for each token or export a single array with the flat list of tokens inside? Maybe that should be configurable as well, although I'm leaning towards 1 variable for each token being the more desired option, since you can always use `import * as tokens from './tokens.js';` to import all of it, and convert it to an array or whatever format you please. And it comes with the benefit of being able to have tools tree-shake because they're named exports.
Hi
I'm not a master on the topic of tree shaking from my understanding wildcard import like `import * as tokens from './tokens.js';` are not good for tree shaking with older version of webpack(4?). I also tried with Vite 6.3, with wildcard for some reason it includes all the unused tokens while named/default only includes the used one ( I have sideeffect: false).

Additionally if we export 1 variable for each token  then it basically become same as `javascript/es6` format. If the `flat` option is added to `javascript/esm` the it has to be default export to be consistent with existing code:

```
export default {
   colorBlack01: '#000',
}
```
The other thing is, if `flat` option is provided it would ignore the current `option.minify`
Guys let me know if you'd like a contribution to this issue, I'm available to help if needed.

@kossel My apologies for my first reply, I thought you only needed a `export default` instead of `module.exports`, I'm really confused :confused:.
Yeah if you use `import * as foo` pattern, it will include all exports. It does not treeshake.

I just meant that if you define 1 export per token, you enable users to choose to import all tokens or to import them 1 by 1, in the latter case tree shaking will be possible.

Contribution is definitely welcome
importing them 1 by 1 would be very tedious 😓that's why I suggested 

```
export default {
  colorName1: '#fff',
  ...
  compModalButtonBackground: '#000'
}
```

this way even doing  `import { default as token } from './tokens.ts'` it's tree-shakable and auto-complete works well too.

Sorry @tiloyi I didn't mean to confuse you, I was just saying it would be a new format or extend the current `javascript/esm` via `option.flat` if you could help would be greatly appreciated 🙏 

You wouldn't have to import them one by one if you do `import * as tokens from './tokens.js';`

Exporting an object is apparently also [treeshakeable by some bundlers](https://rollupjs.org/repl/?version=4.34.9&shareable=JTdCJTIyZXhhbXBsZSUyMiUzQW51bGwlMkMlMjJtb2R1bGVzJTIyJTNBJTVCJTdCJTIyY29kZSUyMiUzQSUyMmltcG9ydCUyMHRva2VuJTIwZnJvbSUyMCcuJTJGcXV4LmpzJyUzQiU1Q24lNUNuY29uc29sZS5sb2codG9rZW4uYmFyKSUyMiUyQyUyMmlzRW50cnklMjIlM0F0cnVlJTJDJTIybmFtZSUyMiUzQSUyMm1haW4uanMlMjIlN0QlMkMlN0IlMjJjb2RlJTIyJTNBJTIyZXhwb3J0JTIwZGVmYXVsdCUyMCU3QiU1Q24lMjAlMjBmb28lM0ElMjAnZm9vJyUyQyU1Q24lMjAlMjBiYXIlM0ElMjAnYmFyJyU1Q24lN0QlMjIlMkMlMjJpc0VudHJ5JTIyJTNBZmFsc2UlMkMlMjJuYW1lJTIyJTNBJTIycXV4LmpzJTIyJTdEJTVEJTJDJTIyb3B0aW9ucyUyMiUzQSU3QiUyMm91dHB1dCUyMiUzQSU3QiUyMmZvcm1hdCUyMiUzQSUyMmVzJTIyJTdEJTJDJTIydHJlZXNoYWtlJTIyJTNBdHJ1ZSU3RCU3RA==), TIL

That said, I'd still prefer separate exports as it allows both import * and import a single token, I don't really see a downside to it.
Hi @jorenbroekema ,
Before contributing I'd like to make sure I understand the direction you're proposing.

To recap, you recommend extending the `javascript/esm` format by adding a `flat` option, which would result in an output file with separate exports.

This file might look something like this _(if I've understood correctly?)_:

```javascript
export const colorPrimary100 = '#EBF5DE';
export const colorPrimary200 = '#C5E39E';
export const colorPrimary300 = '#9ED05F';
export const colorPrimary400 = '#78BE20';
// ...
```

Did I understand correctly?

If that's the case, unless there's something I've missed, what would be the difference with the existing [javascript/es6](https://styledictionary.com/reference/hooks/formats/predefined/#javascriptes6) format?
@tiloyi That's what I mentioned [here](https://github.com/amzn/style-dictionary/issues/1451#issuecomment-2682698262).

>Additionally if we export 1 variable for each token then it basically become same as javascript/es6 format. If the flat option is added to javascript/esm the it has to be default export to be consistent with existing code:


My original request was for the output to be like `javascript/esm` but name are flattened. expected output

```
export default {
  colorName1: '#fff',
  ...
  compModalButtonBackground: '#000'
}
```
but  @jorenbroekema didn't like the idea to be exported as a single object 😅  so I'm confused too haha sorry
> If that's the case, unless there's something I've missed, what would be the difference with the existing [javascript/es6](https://styledictionary.com/reference/hooks/formats/predefined/#javascriptes6) format?

Oooohh sorry guys yeah I think I misunderstood 😅 apologies! The javascript/es6 indeed already covers separate ESM named exports, so I'm totally fine with the javascript/esm one exporting it as an object, either a nested object (current) or a flattened object (future option), but both a single export.

I think it would be cool if we could also have another option `export?: string;` which allows you to pass the name of the export, because these days in ESM usually named exports have a preference over default exports. If the option is not passed (undefined) it will just create a default export. Feel free to tackle in the same PR or in a separate one, up to you.

I feel like we should have named these formats a bit better haha, but we can fix that in the next major breaking change if needed, no need to worry about this now.

Again sorry for the confusion, feel free to introduce "flat" option and have it output:
```js
export default { // or if export option is passed, `export const exportValue = { ... }`
  tokenName1: tokenDefinition1,
  tokenName2: tokenDefinition2, // etc.
}
```
So cool! 
I'll make the contribution as soon as possible 😊. 



Fix (patch):
diff --git a/.changeset/friendly-rice-hide.md b/.changeset/friendly-rice-hide.md
new file mode 100644
index 000000000..5bf7fe9a8
--- /dev/null
+++ b/.changeset/friendly-rice-hide.md
@@ -0,0 +1,5 @@
+---
+'style-dictionary': minor
+---
+
+Add a 'flat' option to the javascriptEsm format
diff --git a/docs/src/content/docs/reference/Hooks/Formats/predefined.md b/docs/src/content/docs/reference/Hooks/Formats/predefined.md
index 3d611d2b7..1aaddb3d1 100644
--- a/docs/src/content/docs/reference/Hooks/Formats/predefined.md
+++ b/docs/src/content/docs/reference/Hooks/Formats/predefined.md
@@ -311,7 +311,8 @@ Creates an ES6 module object of the style dictionary.
 | Param                     | Type                | Description                                                                                                                                                                                                                                                                                                                   |
 | ------------------------- | ------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
 | `options`                 | `Object`            |                                                                                                                                                                                                                                                                                                                               |
-| `options.minify`          | `boolean`           | Whether or not to minify the output. Defaults to `false`.                                                                                                                                                                                                                                                                     |
+| `options.minify`          | `boolean`           | Whether or not to minify the output. Defaults to `false`. Has no effect when `options.flat` is `true`.                                                                                                                                                                                                                        |
+| `options.flat`            | `boolean`           | Whether or not to flatten the output. Defaults to `false`. If `true`, renders only the token value, which means it is minified.                                                                                                                                                                                               |
 | `options.stripMeta`       | `Object \| boolean` | Control whether meta data is stripped from the output tokens. `false` by default. If set to `true`, will strip known Style Dictionary meta props: `['attributes', 'filePath', 'name', 'path', 'comment']`. Note that using minify means that meta is stripped as a side effect of this already, so using both is unnecessary. |
 | `options.stripMeta.keep`  | `string[]`          | Array of property keys to keep in the output.                                                                                                                                                                                                                                                                                 |
 | `options.stripMeta.strip` | `string[]`          | Array of property keys to strip from the output.                                                                                                                                                                                                                                                                              |
diff --git a/lib/common/formats.js b/lib/common/formats.js
index e6d7ec624..313e16805 100644
--- a/lib/common/formats.js
+++ b/lib/common/formats.js
@@ -676,7 +676,7 @@ const formats = {
    * ```
    */
   [javascriptEsm]: async function ({ dictionary, file, options }) {
-    const { formatting, minify = false } = options;
+    const { formatting, minify = false, flat = false } = options;
     let { tokens } = dictionary;
     tokens = stripMetaProps(
       tokens,
@@ -689,11 +689,25 @@ const formats = {
       options,
     });
 
-    const dictionaryString = JSON.stringify(
-      minify ? minifyDictionary(tokens, options.usesDtcg) : tokens,
-      null,
-      2,
-    );
+    let dictionaryString;
+    if (flat) {
+      dictionaryString = JSON.stringify(
+        Object.fromEntries(
+          dictionary.allTokens.map((token) => [
+            token.name,
+            options.usesDtcg ? token.$value : token.value,
+          ]),
+        ),
+        null,
+        2,
+      );
+    } else {
+      dictionaryString = JSON.stringify(
+        minify ? minifyDictionary(tokens, options.usesDtcg) : tokens,
+        null,
+        2,
+      );
+    }
 
     const content = `${header}export default ${dictionaryString};\n`;
     return formatJS(content);