Repository: effector/effector (ts)

Issue:
Feature request: add option for babel plugin to disable `name` generation for units
## Proposal

```json
{
  "plugins": [
    ["effector/babel-plugin", { "addNames": false }]
  ]
}
```


## Use case

In production build, each unit has a name and sid. But removing the name can reduce the size of the bundle.


Pull request:
add `addNames` option for babel-plugin, by default `true` #446
closes #446 

Patch:
diff --git a/docs/api/effector/babel-plugin.md b/docs/api/effector/babel-plugin.md
index 369a4733..de0902bb 100644
--- a/docs/api/effector/babel-plugin.md
+++ b/docs/api/effector/babel-plugin.md
@@ -146,6 +146,16 @@ const foo = createInputField('-', {
 
 Specify import to process by plugin. By default has value `["effector", "effector/compat"]`
 
+### addNames
+
+> `addNames: boolean`
+
+:::note since
+effector 21.8.0
+:::
+
+Add names to units factories calls. By default has value `true`. Useful for minification and obfuscation of production builds.
+
 ### addLoc
 
 > `addLoc: boolean`
diff --git a/src/babel/babel-plugin.js b/src/babel/babel-plugin.js
index 12393c10..2ef95666 100644
--- a/src/babel/babel-plugin.js
+++ b/src/babel/babel-plugin.js
@@ -1,6 +1,7 @@
 module.exports = function(babel, options = {}) {
   const {
     compressor,
+    addNames,
     addLoc,
     filename: enableFileName,
     stores,
@@ -39,7 +40,7 @@ module.exports = function(babel, options = {}) {
   const hasRelativeFactories = factories.some(
     fab => fab.startsWith('./') || fab.startsWith('../'),
   )
-  const smallConfig = {compressor, addLoc}
+  const smallConfig = {compressor, addLoc, addNames}
   const {types: t, template} = babel
   let factoryTemplate
   const isPropertyNameInRange = (range, path) =>
@@ -569,6 +570,10 @@ const normalizeOptions = options => {
       ),
       factories: (options.factories || defaults.factories).map(stripExtension),
       addLoc: Boolean(options.addLoc),
+      addNames:
+        typeof options.addNames !== 'undefined'
+          ? Boolean(options.addNames)
+          : true,
       compressor: options.compressSid === false ? str => str : hashCode,
     },
   })
@@ -653,7 +658,13 @@ function makeTrace(fileNameIdentifier, lineNumber, columnNumber, t) {
   const columnProperty = property(t, 'column', fileColumnLiteral)
   return t.objectExpression([fileProperty, lineProperty, columnProperty])
 }
-function setRestoreNameAfter(path, state, nameNodeId, t, {addLoc, compressor}) {
+function setRestoreNameAfter(
+  path,
+  state,
+  nameNodeId,
+  t,
+  {addLoc, compressor, addNames},
+) {
   const displayName = nameNodeId ? nameNodeId.name : ''
   let args
   let loc
@@ -695,7 +706,7 @@ function setRestoreNameAfter(path, state, nameNodeId, t, {addLoc, compressor}) {
       )
       configExpr.properties.push(locProp)
     }
-    if (displayName) {
+    if (displayName && addNames) {
       configExpr.properties.push(stringProperty(t, 'name', displayName))
     }
     configExpr.properties.push(stableID)
@@ -706,7 +717,7 @@ function setStoreNameAfter(
   state,
   nameNodeId,
   t,
-  {addLoc, compressor},
+  {addLoc, compressor, addNames},
   fillFirstArg,
 ) {
   const displayName = nameNodeId ? nameNodeId.name : ''
@@ -752,7 +763,7 @@ function setStoreNameAfter(
       )
       configExpr.properties.push(locProp)
     }
-    if (displayName) {
+    if (displayName && addNames) {
       configExpr.properties.push(stringProperty(t, 'name', displayName))
     }
     configExpr.properties.push(stableID)
@@ -763,7 +774,7 @@ function setConfigForConfigurableMethod(
   state,
   nameNodeId,
   t,
-  {addLoc, compressor},
+  {addLoc, compressor, addNames},
   singleArgument,
 ) {
   const displayName = nameNodeId ? nameNodeId.name : ''
@@ -806,7 +817,7 @@ function setConfigForConfigurableMethod(
       )
       configExpr.properties.push(locProp)
     }
-    if (displayName) {
+    if (displayName && addNames) {
       configExpr.properties.push(stringProperty(t, 'name', displayName))
     }
     configExpr.properties.push(stableID)
@@ -817,7 +828,13 @@ function setConfigForConfigurableMethod(
   }
 }
 
-function setEventNameAfter(path, state, nameNodeId, t, {addLoc, compressor}) {
+function setEventNameAfter(
+  path,
+  state,
+  nameNodeId,
+  t,
+  {addLoc, compressor, addNames},
+) {
   const displayName = nameNodeId ? nameNodeId.name : ''
 
   let args
@@ -864,7 +881,7 @@ function setEventNameAfter(path, state, nameNodeId, t, {addLoc, compressor}) {
       )
       configExpr.properties.push(locProp)
     }
-    if (displayName) {
+    if (displayName && addNames) {
       configExpr.properties.push(stringProperty(t, 'name', displayName))
     }
     configExpr.properties.push(stableID)


Test patch:
diff --git a/src/babel/__tests__/__snapshots__/babel-plugin.test.js.snap b/src/babel/__tests__/__snapshots__/babel-plugin.test.js.snap
index 841083ab..0d5b55ba 100644
--- a/src/babel/__tests__/__snapshots__/babel-plugin.test.js.snap
+++ b/src/babel/__tests__/__snapshots__/babel-plugin.test.js.snap
@@ -1357,6 +1357,42 @@ const g1_val = withFactory({
 const g2_val = g2(0);"
 `;
 
+exports[`babel-plugin should factories without name with options 1`] = `
+"import { withFactory } from \\"effector\\";
+import { itIsALongName, Randomizing } from 'c/d';
+import Defaulting, { AnotherImport } from '@/f';
+import { splitMap } from 'patronum/split-map';
+const longer = withFactory({
+  sid: \\"mn9l4u\\",
+  fn: () => itIsALongName(0)
+});
+const arcade = withFactory({
+  sid: \\"xpbz5w\\",
+  fn: () => Defaulting({
+    source: longer,
+    condition: withFactory({
+      sid: \\"lijhe7\\",
+      fn: () => AnotherImport({
+        test: true
+      })
+    })
+  })
+});
+withFactory({
+  sid: \\"-yfq3bv\\",
+  fn: () => Randomizing({
+    arcade
+  })
+});
+const result = withFactory({
+  sid: \\"lwwnia\\",
+  fn: () => splitMap({
+    longer,
+    source: arcade
+  })
+});"
+`;
+
 exports[`babel-plugin should forward 1`] = `
 "var _effectorFileName = \\"/src/babel/__tests__/fixtures/forward.js\\";
 //@flow
@@ -2056,3 +2092,281 @@ const f = () => split({
   }
 });"
 `;
+
+exports[`babel-plugin should units without name with options 1`] = `
+"import { createDomain, createEffect, createEvent, createStore, restore } from 'effector';
+{
+  const domain = createDomain({
+    sid: \\"-nzaa7a\\"
+  });
+  const a = domain.store('bar', {
+    sid: \\"-r4da5h\\"
+  });
+  const b = domain.store('h', {
+    ɔ: {
+      option: 'test',
+      ['na' + 'me']: 'LOL'
+    },
+    sid: \\"ehfy55\\"
+  });
+  const c = domain.store('h', {
+    ɔ: {
+      name: 'test'
+    },
+    sid: \\"-exuvjd\\"
+  }); //$off
+
+  const d = domain.store('h', {
+    ɔ: null,
+    sid: \\"r4zz5g\\"
+  }); //$off
+
+  const e = domain.store('h', {
+    ɔ: 4234,
+    sid: \\"-1t984v\\"
+  });
+  const f = domain.store('h', {
+    ɔ: {},
+    sid: \\"-v8k1td\\"
+  });
+  const aAlias = domain.createStore('bar', {
+    sid: \\"rsi1ca\\"
+  });
+  const bAlias = domain.createStore('h', {
+    ɔ: {
+      option: 'test',
+      ['na' + 'me']: 'LOL'
+    },
+    sid: \\"i7igmi\\"
+  });
+  const cAlias = domain.createStore('h', {
+    ɔ: {
+      name: 'testAlias'
+    },
+    sid: \\"8mivwq\\"
+  }); //$off
+
+  const dAlias = domain.createStore('h', {
+    ɔ: null,
+    sid: \\"-hf2ev\\"
+  }); //$off
+
+  const eAlias = domain.createStore('h', {
+    ɔ: 4234,
+    sid: \\"-9ld0qg\\"
+  });
+  const fAlias = domain.createStore('h', {
+    ɔ: {},
+    sid: \\"-j6clg8\\"
+  });
+  const {
+    sid
+  } = domain.createStore(null, {
+    sid: \\"-ls4ila\\"
+  });
+  const {
+    shortName
+  } = domain.createStore(null, {
+    ɔ: {
+      name: 'foo'
+    },
+    sid: \\"-lb2vju\\"
+  });
+  domain.createStore(null, {
+    sid: \\"tgv4rt\\"
+  });
+  domain.createStore(null, {
+    sid: \\"thypyj\\"
+  });
+}
+{
+  const foo = createEffect({
+    sid: \\"9vguxc\\"
+  });
+  const bar = createEffect('hello', {
+    sid: \\"-t87e9q\\"
+  });
+  const baz = createEffect({
+    handler() {
+      return 0;
+    }
+
+  }, {
+    sid: \\"g6ivfd\\"
+  });
+  const quux = createEffect('nice', {
+    ɔ: {
+      handler() {
+        return 0;
+      }
+
+    },
+    sid: \\"-26njv2\\"
+  });
+
+  const fa = () => createEffect({
+    sid: \\"nsvob9\\"
+  });
+
+  const {
+    sid
+  } = createEffect({
+    sid: \\"ixqzbg\\"
+  });
+  const {
+    name
+  } = createEffect('foo', {
+    sid: \\"jeslqi\\"
+  });
+  const {
+    shortName
+  } = createEffect({
+    name: 'foo'
+  }, {
+    sid: \\"jvu8r3\\"
+  });
+  createEffect({
+    sid: \\"uh5jxn\\"
+  });
+  createEffect({
+    sid: \\"ui954d\\"
+  });
+}
+{
+  const foo = createStore('foo', {
+    sid: \\"-jyq2qv\\"
+  });
+  const a = createStore('h', {
+    sid: \\"-oagam9\\"
+  });
+  const b = createStore('h', {
+    ɔ: {},
+    sid: \\"hbcxod\\"
+  }); //$off
+
+  const c = createStore('h', {
+    ɔ: 23020,
+    sid: \\"-bmw9ly\\"
+  });
+  const config = {
+    option: 0
+  };
+  const dod = createStore(null, {
+    ɔ: config,
+    sid: \\"sja6dc\\"
+  });
+
+  const f = a => createStore(a, {
+    sid: \\"-sen25u\\"
+  });
+
+  const {
+    sid
+  } = createStore(null, {
+    sid: \\"-mqki7m\\"
+  });
+  const {
+    shortName
+  } = createStore(null, {
+    ɔ: {
+      name: 'foo'
+    },
+    sid: \\"-m9iv66\\"
+  });
+  createStore(null, {
+    sid: \\"vep04o\\"
+  });
+  createStore(null, {
+    sid: \\"vfslbe\\"
+  });
+}
+{
+  const foo = createEvent({
+    sid: \\"8x0vb0\\"
+  });
+  const bar = createEvent('hello', {
+    sid: \\"-u6ndw2\\"
+  });
+  const baz = createEvent({
+    name: 'nice'
+  }, {
+    sid: \\"f82vt1\\"
+  });
+
+  const f = () => createEvent({
+    sid: \\"-1w4823\\"
+  });
+
+  const {
+    sid
+  } = createEvent({
+    sid: \\"-65zqet\\"
+  });
+  const {
+    name
+  } = createEvent('foo', {
+    sid: \\"-5oy3zr\\"
+  });
+  const {
+    shortName
+  } = createEvent({
+    name: 'foo'
+  }, {
+    sid: \\"-57wgz6\\"
+  });
+  createEvent({
+    sid: \\"vyhlho\\"
+  });
+  createEvent({
+    sid: \\"wb4v4z\\"
+  });
+}
+{
+  const foo = createEvent({
+    sid: \\"pyn9i0\\"
+  });
+  const a = restore(foo, null, {
+    sid: \\"lmx1mm\\"
+  });
+  const b = restore(foo, null, {
+    ɔ: {},
+    sid: \\"-7sds1w\\"
+  }); //$off
+
+  const c = restore(foo, null, {
+    ɔ: 23020,
+    sid: \\"yah2mx\\"
+  });
+  const config = {
+    option: 0
+  };
+  const dod = restore(foo, null, {
+    ɔ: config,
+    sid: \\"10dc0p\\"
+  });
+
+  const f = a => restore(foo, null, {
+    sid: \\"-gu50wv\\"
+  });
+
+  const {
+    sid
+  } = restore(foo, null, {
+    sid: \\"zcd1uy\\"
+  });
+  const {
+    shortName
+  } = restore(foo, null, {
+    ɔ: {
+      name: 'bar'
+    },
+    sid: \\"-z7pd2q\\"
+  });
+  restore(foo, null, {
+    sid: \\"vif224\\"
+  });
+  restore(foo, null, {
+    sid: \\"vv2bpf\\"
+  });
+}"
+`;
diff --git a/src/babel/__tests__/babel-plugin.test.js b/src/babel/__tests__/babel-plugin.test.js
index f8c2e267..941dc389 100644
--- a/src/babel/__tests__/babel-plugin.test.js
+++ b/src/babel/__tests__/babel-plugin.test.js
@@ -15,13 +15,37 @@ describe('babel-plugin', () => {
       .split('-')
       .join(' ')
       .slice(0, -3)
-    it(`should ${caseName}`, () => {
-      const fixturePath = path.join(fixturesDir, caseFile)
-      const fixture = transformFileSync(fixturePath, {
-        configFile: path.join(__dirname, '.babelrc'),
-      })?.code
 
-      expect(fixture).toMatchSnapshot()
-    })
+    const optionsName = `${caseFile.slice(0, -3)}.options.json`
+    const optionsPath = path.join(fixturesDir, optionsName)
+    const hasOptions = fs.existsSync(optionsPath)
+
+    if (hasOptions) {
+      const options = JSON.parse(
+        fs.readFileSync(optionsPath, {encoding: 'utf8'}).toString(),
+      )
+
+      it(`should ${caseName} with options`, () => {
+        const fixturePath = path.join(fixturesDir, caseFile)
+        const fixture = transformFileSync(fixturePath, {
+          // configFile: path.resolve(__dirname, '../../../babel.config.js'),
+          configFile: false,
+          babelrc: false,
+          envName: 'test',
+          plugins: [[path.resolve(__dirname, '../babel-plugin.js'), options]],
+        })?.code
+
+        expect(fixture).toMatchSnapshot()
+      })
+    } else {
+      it(`should ${caseName}`, () => {
+        const fixturePath = path.join(fixturesDir, caseFile)
+        const fixture = transformFileSync(fixturePath, {
+          configFile: path.join(__dirname, '.babelrc'),
+        })?.code
+
+        expect(fixture).toMatchSnapshot()
+      })
+    }
   }
 })
diff --git a/src/babel/__tests__/fixtures/factories-without-name.js b/src/babel/__tests__/fixtures/factories-without-name.js
new file mode 100644
index 00000000..cbbf69a0
--- /dev/null
+++ b/src/babel/__tests__/fixtures/factories-without-name.js
@@ -0,0 +1,15 @@
+import {itIsALongName, Randomizing} from 'c/d'
+import Defaulting, {AnotherImport} from '@/f'
+import {splitMap} from 'patronum/split-map'
+
+const longer = itIsALongName(0)
+const arcade = Defaulting({
+  source: longer,
+  condition: AnotherImport({test: true}),
+})
+Randomizing({arcade})
+
+const result = splitMap({
+  longer,
+  source: arcade,
+})
diff --git a/src/babel/__tests__/fixtures/factories-without-name.options.json b/src/babel/__tests__/fixtures/factories-without-name.options.json
new file mode 100644
index 00000000..4ae49547
--- /dev/null
+++ b/src/babel/__tests__/fixtures/factories-without-name.options.json
@@ -0,0 +1,4 @@
+{
+  "factories": ["c/d", "@/f", "patronum/split-map"],
+  "addNames": false
+}
diff --git a/src/babel/__tests__/fixtures/units-without-name.js b/src/babel/__tests__/fixtures/units-without-name.js
new file mode 100644
index 00000000..fa39adf6
--- /dev/null
+++ b/src/babel/__tests__/fixtures/units-without-name.js
@@ -0,0 +1,111 @@
+import {
+  createDomain,
+  createEffect,
+  createEvent,
+  createStore,
+  restore,
+} from 'effector'
+{
+  const domain = createDomain()
+
+  const a = domain.store('bar')
+  const b = domain.store('h', {option: 'test', ['na' + 'me']: 'LOL'})
+  const c = domain.store('h', {name: 'test'})
+  //$off
+  const d = domain.store('h', null)
+  //$off
+  const e = domain.store('h', 4234)
+  const f = domain.store('h', {})
+
+  const aAlias = domain.createStore('bar')
+  const bAlias = domain.createStore('h', {option: 'test', ['na' + 'me']: 'LOL'})
+  const cAlias = domain.createStore('h', {name: 'testAlias'})
+  //$off
+  const dAlias = domain.createStore('h', null)
+  //$off
+  const eAlias = domain.createStore('h', 4234)
+  const fAlias = domain.createStore('h', {})
+  const {sid} = domain.createStore(null)
+  const {shortName} = domain.createStore(null, {name: 'foo'})
+
+  domain.createStore(null)
+
+  domain.createStore(null)
+}
+
+{
+  const foo = createEffect()
+  const bar = createEffect('hello')
+  const baz = createEffect({
+    handler() {
+      return 0
+    },
+  })
+  const quux = createEffect('nice', {
+    handler() {
+      return 0
+    },
+  })
+  const fa = () => createEffect()
+
+  const {sid} = createEffect()
+  const {name} = createEffect('foo')
+  const {shortName} = createEffect({name: 'foo'})
+
+  createEffect()
+
+  createEffect()
+}
+
+{
+  const foo = createStore('foo')
+  const a = createStore('h')
+  const b = createStore('h', {})
+  //$off
+  const c = createStore('h', 23020)
+  const config = {option: 0}
+  const dod = createStore(null, config)
+
+  const f = a => createStore(a)
+
+  const {sid} = createStore(null)
+  const {shortName} = createStore(null, {name: 'foo'})
+
+  createStore(null)
+
+  createStore(null)
+}
+
+{
+  const foo = createEvent()
+  const bar = createEvent('hello')
+  const baz = createEvent({name: 'nice'})
+  const f = () => createEvent()
+
+  const {sid} = createEvent()
+  const {name} = createEvent('foo')
+  const {shortName} = createEvent({name: 'foo'})
+
+  createEvent()
+
+  createEvent()
+}
+
+{
+  const foo = createEvent()
+  const a = restore(foo, null)
+  const b = restore(foo, null, {})
+  //$off
+  const c = restore(foo, null, 23020)
+  const config = {option: 0}
+  const dod = restore(foo, null, config)
+
+  const f = a => restore(foo, null)
+
+  const {sid} = restore(foo, null)
+  const {shortName} = restore(foo, null, {name: 'bar'})
+
+  restore(foo, null)
+
+  restore(foo, null)
+}
diff --git a/src/babel/__tests__/fixtures/units-without-name.options.json b/src/babel/__tests__/fixtures/units-without-name.options.json
new file mode 100644
index 00000000..49a620fd
--- /dev/null
+++ b/src/babel/__tests__/fixtures/units-without-name.options.json
@@ -0,0 +1,3 @@
+{
+  "addNames": false
+}