├── .gitattributes ├── .github └── workflows │ └── nodejs.yml ├── .gitignore ├── .prettierignore ├── .prettierrc ├── .versionrc.json ├── .vscode ├── extensions.json ├── launch.json └── settings.json ├── .yarn ├── plugins │ └── @yarnpkg │ │ └── plugin-typescript.cjs ├── releases │ └── yarn-3.0.0-rc.5.cjs └── sdks │ ├── integrations.yml │ ├── prettier │ ├── index.js │ └── package.json │ └── typescript │ ├── bin │ ├── tsc │ └── tsserver │ ├── lib │ ├── tsc.js │ ├── tsserver.js │ └── typescript.js │ └── package.json ├── .yarnrc.yml ├── CHANGELOG.md ├── LICENSE ├── README.md ├── babel.config.js ├── benchmark ├── README.md ├── cases │ ├── case_1.js │ ├── case_2.js │ ├── case_3.js │ └── case_4.js ├── getClassName.js └── index.js ├── jest.config.js ├── package.json ├── rollup.config.js ├── src ├── findExpressions.ts ├── index.ts ├── options.ts ├── types.ts ├── utils │ ├── helpers.ts │ └── strings.ts └── visitors │ ├── collectCalls.ts │ ├── combineArguments.js │ ├── combineStringLiterals.ts │ ├── createConditionalExpression.js │ ├── createObjectKeyLookups.ts │ ├── extractObjectProperties.ts │ ├── flattenArrays.ts │ ├── optimizeExpressions.ts │ ├── propTypes.js │ ├── referencedObjects.ts │ ├── removeUnnecessaryCalls.ts │ └── stripLiterals.ts ├── test ├── fixtures │ ├── combine-arguments │ │ ├── nested-match │ │ │ ├── code.js │ │ │ └── output.js │ │ └── single-match │ │ │ ├── code.js │ │ │ └── output.js │ ├── combine-string-literals │ │ ├── conditional │ │ │ ├── code.js │ │ │ └── output.js │ │ ├── normal-strings │ │ │ ├── code.js │ │ │ └── output.js │ │ ├── string-and-template │ │ │ ├── code.js │ │ │ └── output.js │ │ ├── template-literal-transformed │ │ │ ├── .babelrc.js │ │ │ ├── code.js │ │ │ └── output.js │ │ └── template-literal │ │ │ ├── code.js │ │ │ └── output.js │ ├── create-conditional-expression │ │ ├── binary-expression │ │ │ ├── code.js │ │ │ └── output.js │ │ ├── negated-identifier │ │ │ ├── code.js │ │ │ └── output.js │ │ └── reversed-binary-expression │ │ │ ├── code.js │ │ │ └── output.js │ ├── create-lookup │ │ ├── identifiers │ │ │ ├── code.js │ │ │ └── output.js │ │ ├── member-expression │ │ │ ├── code.js │ │ │ └── output.js │ │ └── optional-member-expression │ │ │ ├── code.js │ │ │ └── output.js │ ├── extract-object-properties │ │ ├── computed │ │ │ ├── code.js │ │ │ └── output.js │ │ ├── identifier │ │ │ ├── code.js │ │ │ └── output.js │ │ ├── object-method │ │ │ ├── code.js │ │ │ └── output.js │ │ ├── spread-element │ │ │ ├── code.js │ │ │ └── output.js │ │ └── string │ │ │ ├── code.js │ │ │ └── output.js │ ├── find-function-names │ │ ├── namespace-import │ │ │ ├── code.js │ │ │ └── output.js │ │ ├── no-import │ │ │ ├── code.js │ │ │ └── output.js │ │ ├── options.json │ │ ├── unspecified-require │ │ │ ├── code.js │ │ │ └── output.js │ │ ├── unused-import │ │ │ ├── code.js │ │ │ ├── options.json │ │ │ └── output.js │ │ ├── using-custom-library │ │ │ ├── code.js │ │ │ ├── options.json │ │ │ └── output.js │ │ ├── using-import │ │ │ ├── code.js │ │ │ └── output.js │ │ └── using-require │ │ │ ├── code.js │ │ │ └── output.js │ ├── flatten-arrays │ │ ├── nested-arrays │ │ │ ├── code.js │ │ │ └── output.js │ │ └── single-array │ │ │ ├── code.js │ │ │ └── output.js │ ├── optimize-expressions │ │ ├── duplicate-nodes │ │ │ ├── code.js │ │ │ └── output.js │ │ ├── odd-even-check │ │ │ ├── code.js │ │ │ └── output.js │ │ ├── operator-change │ │ │ ├── code.js │ │ │ └── output.js │ │ ├── unnecessary-conditional │ │ │ ├── code.js │ │ │ └── output.js │ │ └── unnecessary-or │ │ │ ├── code.js │ │ │ └── output.js │ ├── options.json │ ├── proptypes │ │ ├── member-access │ │ │ ├── code.js │ │ │ └── output.js │ │ ├── object-pattern-assignment │ │ │ ├── code.js │ │ │ └── output.js │ │ ├── object-pattern-inline │ │ │ ├── code.js │ │ │ └── output.js │ │ ├── object-pattern-rest │ │ │ ├── code.js │ │ │ └── output.js │ │ ├── object-pattern │ │ │ ├── code.js │ │ │ └── output.js │ │ └── variable-declaration │ │ │ ├── code.js │ │ │ └── output.js │ ├── referenced-object │ │ ├── multiple-references │ │ │ ├── code.js │ │ │ └── output.js │ │ └── transformed-callee │ │ │ ├── .babelrc.js │ │ │ ├── code.js │ │ │ └── output.js │ ├── remove-unnecessary-calls │ │ ├── array │ │ │ ├── code.js │ │ │ └── output.js │ │ ├── nested-conditional │ │ │ ├── code.js │ │ │ └── output.js │ │ ├── one-conditional-argument │ │ │ ├── code.js │ │ │ └── output.js │ │ ├── options.json │ │ ├── single-logical-expression │ │ │ ├── code.js │ │ │ └── output.js │ │ ├── string-and-conditional-argument │ │ │ ├── code.js │ │ │ └── output.js │ │ ├── string-and-logical-expression │ │ │ ├── code.js │ │ │ └── output.js │ │ ├── string-argument │ │ │ ├── code.js │ │ │ └── output.js │ │ ├── template-literal │ │ │ ├── code.js │ │ │ └── output.js │ │ └── two-conditional-arguments │ │ │ ├── code.js │ │ │ └── output.js │ └── strip-literals │ │ ├── boolean-literal │ │ ├── code.js │ │ └── output.js │ │ └── string-literal │ │ ├── code.js │ │ └── output.js └── index.js ├── tsconfig.json └── yarn.lock /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto eol=lf 2 | -------------------------------------------------------------------------------- /.github/workflows/nodejs.yml: -------------------------------------------------------------------------------- 1 | name: Build, format, and test 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | 9 | env: 10 | YARN_ENABLE_MIRROR: 'false' 11 | YARN_ENABLE_GLOBAL_CACHE: 'false' 12 | 13 | steps: 14 | - uses: actions/checkout@v2 15 | 16 | - uses: actions/setup-node@v2 17 | with: 18 | node-version: 14.x 19 | 20 | - uses: actions/cache@v2 21 | with: 22 | path: .yarn/cache 23 | key: yarn2-${{ hashFiles('yarn.lock') }} 24 | restore-keys: | 25 | yarn2- 26 | 27 | - run: yarn --immutable 28 | - run: yarn prettier --check . 29 | - run: yarn test 30 | - run: yarn build 31 | - run: yarn test:build 32 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Rollup output folder 2 | dist 3 | 4 | # Yarn 2 5 | .yarn/* 6 | !.yarn/patches 7 | !.yarn/releases 8 | !.yarn/plugins 9 | !.yarn/sdks 10 | !.yarn/versions 11 | .pnp.* 12 | 13 | # Fixtures used during debugging and testing 14 | test/dev_fixture 15 | 16 | # Data dumps from testing 17 | dumps 18 | 19 | # Logs 20 | logs 21 | *.log 22 | npm-debug.log* 23 | yarn-debug.log* 24 | yarn-error.log* 25 | 26 | # Runtime data 27 | pids 28 | *.pid 29 | *.seed 30 | *.pid.lock 31 | 32 | # Directory for instrumented libs generated by jscoverage/JSCover 33 | lib-cov 34 | 35 | # Coverage directory used by tools like istanbul 36 | coverage 37 | 38 | # nyc test coverage 39 | .nyc_output 40 | 41 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 42 | .grunt 43 | 44 | # Bower dependency directory (https://bower.io/) 45 | bower_components 46 | 47 | # node-waf configuration 48 | .lock-wscript 49 | 50 | # Compiled binary addons (https://nodejs.org/api/addons.html) 51 | build/Release 52 | 53 | # Dependency directories 54 | node_modules/ 55 | jspm_packages/ 56 | 57 | # TypeScript v1 declaration files 58 | typings/ 59 | 60 | # Optional npm cache directory 61 | .npm 62 | 63 | # Optional eslint cache 64 | .eslintcache 65 | 66 | # Optional REPL history 67 | .node_repl_history 68 | 69 | # Output of 'npm pack' 70 | *.tgz 71 | 72 | # Yarn Integrity file 73 | .yarn-integrity 74 | 75 | # dotenv environment variables file 76 | .env 77 | 78 | # next.js build output 79 | .next 80 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | dist 2 | coverage 3 | 4 | # Yarn 2 5 | .yarn/* 6 | !.yarn/sdks 7 | .pnp.* 8 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "useTabs": true, 3 | "printWidth": 100, 4 | "singleQuote": true 5 | } 6 | -------------------------------------------------------------------------------- /.versionrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "types": [ 3 | { "type": "feat", "section": "Features" }, 4 | { "type": "fix", "section": "Bug Fixes" }, 5 | { "type": "perf", "section": "Performance Improvements" }, 6 | { "type": "build", "section": "Build System" } 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["esbenp.prettier-vscode", "arcanis.vscode-zipfs"] 3 | } 4 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "node", 9 | "request": "launch", 10 | "name": "Debug test", 11 | "skipFiles": ["/**", "node_modules/**"], 12 | "runtimeArgs": ["-r", "./.pnp.cjs", "-r", "jest/bin/jest.js"], 13 | "cwd": "${workspaceFolder}" 14 | } 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "typescript.tsdk": ".yarn/sdks/typescript/lib", 3 | "prettier.prettierPath": ".yarn/sdks/prettier/index.js", 4 | "typescript.enablePromptUseWorkspaceTsdk": true, 5 | "search.exclude": { 6 | "**/.yarn": true, 7 | "**/.pnp.*": true 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /.yarn/plugins/@yarnpkg/plugin-typescript.cjs: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | //prettier-ignore 3 | module.exports = { 4 | name: "@yarnpkg/plugin-typescript", 5 | factory: function (require) { 6 | var plugin=(()=>{var Ft=Object.create,H=Object.defineProperty,Bt=Object.defineProperties,Kt=Object.getOwnPropertyDescriptor,zt=Object.getOwnPropertyDescriptors,Gt=Object.getOwnPropertyNames,Q=Object.getOwnPropertySymbols,$t=Object.getPrototypeOf,ne=Object.prototype.hasOwnProperty,De=Object.prototype.propertyIsEnumerable;var Re=(e,t,r)=>t in e?H(e,t,{enumerable:!0,configurable:!0,writable:!0,value:r}):e[t]=r,u=(e,t)=>{for(var r in t||(t={}))ne.call(t,r)&&Re(e,r,t[r]);if(Q)for(var r of Q(t))De.call(t,r)&&Re(e,r,t[r]);return e},g=(e,t)=>Bt(e,zt(t)),Lt=e=>H(e,"__esModule",{value:!0});var R=(e,t)=>{var r={};for(var s in e)ne.call(e,s)&&t.indexOf(s)<0&&(r[s]=e[s]);if(e!=null&&Q)for(var s of Q(e))t.indexOf(s)<0&&De.call(e,s)&&(r[s]=e[s]);return r};var I=(e,t)=>()=>(t||e((t={exports:{}}).exports,t),t.exports),Vt=(e,t)=>{for(var r in t)H(e,r,{get:t[r],enumerable:!0})},Qt=(e,t,r)=>{if(t&&typeof t=="object"||typeof t=="function")for(let s of Gt(t))!ne.call(e,s)&&s!=="default"&&H(e,s,{get:()=>t[s],enumerable:!(r=Kt(t,s))||r.enumerable});return e},C=e=>Qt(Lt(H(e!=null?Ft($t(e)):{},"default",e&&e.__esModule&&"default"in e?{get:()=>e.default,enumerable:!0}:{value:e,enumerable:!0})),e);var xe=I(J=>{"use strict";Object.defineProperty(J,"__esModule",{value:!0});function _(e){let t=[...e.caches],r=t.shift();return r===void 0?ve():{get(s,n,a={miss:()=>Promise.resolve()}){return r.get(s,n,a).catch(()=>_({caches:t}).get(s,n,a))},set(s,n){return r.set(s,n).catch(()=>_({caches:t}).set(s,n))},delete(s){return r.delete(s).catch(()=>_({caches:t}).delete(s))},clear(){return r.clear().catch(()=>_({caches:t}).clear())}}}function ve(){return{get(e,t,r={miss:()=>Promise.resolve()}){return t().then(n=>Promise.all([n,r.miss(n)])).then(([n])=>n)},set(e,t){return Promise.resolve(t)},delete(e){return Promise.resolve()},clear(){return Promise.resolve()}}}J.createFallbackableCache=_;J.createNullCache=ve});var Ee=I(($s,qe)=>{qe.exports=xe()});var Te=I(ae=>{"use strict";Object.defineProperty(ae,"__esModule",{value:!0});function Jt(e={serializable:!0}){let t={};return{get(r,s,n={miss:()=>Promise.resolve()}){let a=JSON.stringify(r);if(a in t)return Promise.resolve(e.serializable?JSON.parse(t[a]):t[a]);let o=s(),d=n&&n.miss||(()=>Promise.resolve());return o.then(y=>d(y)).then(()=>o)},set(r,s){return t[JSON.stringify(r)]=e.serializable?JSON.stringify(s):s,Promise.resolve(s)},delete(r){return delete t[JSON.stringify(r)],Promise.resolve()},clear(){return t={},Promise.resolve()}}}ae.createInMemoryCache=Jt});var we=I((Vs,Me)=>{Me.exports=Te()});var Ce=I(M=>{"use strict";Object.defineProperty(M,"__esModule",{value:!0});function Xt(e,t,r){let s={"x-algolia-api-key":r,"x-algolia-application-id":t};return{headers(){return e===oe.WithinHeaders?s:{}},queryParameters(){return e===oe.WithinQueryParameters?s:{}}}}function Yt(e){let t=0,r=()=>(t++,new Promise(s=>{setTimeout(()=>{s(e(r))},Math.min(100*t,1e3))}));return e(r)}function ke(e,t=(r,s)=>Promise.resolve()){return Object.assign(e,{wait(r){return ke(e.then(s=>Promise.all([t(s,r),s])).then(s=>s[1]))}})}function Zt(e){let t=e.length-1;for(t;t>0;t--){let r=Math.floor(Math.random()*(t+1)),s=e[t];e[t]=e[r],e[r]=s}return e}function er(e,t){return Object.keys(t!==void 0?t:{}).forEach(r=>{e[r]=t[r](e)}),e}function tr(e,...t){let r=0;return e.replace(/%s/g,()=>encodeURIComponent(t[r++]))}var rr="4.2.0",sr=e=>()=>e.transporter.requester.destroy(),oe={WithinQueryParameters:0,WithinHeaders:1};M.AuthMode=oe;M.addMethods=er;M.createAuth=Xt;M.createRetryablePromise=Yt;M.createWaitablePromise=ke;M.destroy=sr;M.encode=tr;M.shuffle=Zt;M.version=rr});var F=I((Js,Ue)=>{Ue.exports=Ce()});var Ne=I(ie=>{"use strict";Object.defineProperty(ie,"__esModule",{value:!0});var nr={Delete:"DELETE",Get:"GET",Post:"POST",Put:"PUT"};ie.MethodEnum=nr});var B=I((Ys,We)=>{We.exports=Ne()});var Ze=I(A=>{"use strict";Object.defineProperty(A,"__esModule",{value:!0});var He=B();function ce(e,t){let r=e||{},s=r.data||{};return Object.keys(r).forEach(n=>{["timeout","headers","queryParameters","data","cacheable"].indexOf(n)===-1&&(s[n]=r[n])}),{data:Object.entries(s).length>0?s:void 0,timeout:r.timeout||t,headers:r.headers||{},queryParameters:r.queryParameters||{},cacheable:r.cacheable}}var X={Read:1,Write:2,Any:3},U={Up:1,Down:2,Timeouted:3},_e=2*60*1e3;function ue(e,t=U.Up){return g(u({},e),{status:t,lastUpdate:Date.now()})}function Fe(e){return e.status===U.Up||Date.now()-e.lastUpdate>_e}function Be(e){return e.status===U.Timeouted&&Date.now()-e.lastUpdate<=_e}function le(e){return{protocol:e.protocol||"https",url:e.url,accept:e.accept||X.Any}}function ar(e,t){return Promise.all(t.map(r=>e.get(r,()=>Promise.resolve(ue(r))))).then(r=>{let s=r.filter(d=>Fe(d)),n=r.filter(d=>Be(d)),a=[...s,...n],o=a.length>0?a.map(d=>le(d)):t;return{getTimeout(d,y){return(n.length===0&&d===0?1:n.length+3+d)*y},statelessHosts:o}})}var or=({isTimedOut:e,status:t})=>!e&&~~t==0,ir=e=>{let t=e.status;return e.isTimedOut||or(e)||~~(t/100)!=2&&~~(t/100)!=4},cr=({status:e})=>~~(e/100)==2,ur=(e,t)=>ir(e)?t.onRetry(e):cr(e)?t.onSucess(e):t.onFail(e);function Qe(e,t,r,s){let n=[],a=$e(r,s),o=Le(e,s),d=r.method,y=r.method!==He.MethodEnum.Get?{}:u(u({},r.data),s.data),b=u(u(u({"x-algolia-agent":e.userAgent.value},e.queryParameters),y),s.queryParameters),f=0,p=(h,S)=>{let O=h.pop();if(O===void 0)throw Ve(de(n));let P={data:a,headers:o,method:d,url:Ge(O,r.path,b),connectTimeout:S(f,e.timeouts.connect),responseTimeout:S(f,s.timeout)},x=j=>{let T={request:P,response:j,host:O,triesLeft:h.length};return n.push(T),T},v={onSucess:j=>Ke(j),onRetry(j){let T=x(j);return j.isTimedOut&&f++,Promise.all([e.logger.info("Retryable failure",pe(T)),e.hostsCache.set(O,ue(O,j.isTimedOut?U.Timeouted:U.Down))]).then(()=>p(h,S))},onFail(j){throw x(j),ze(j,de(n))}};return e.requester.send(P).then(j=>ur(j,v))};return ar(e.hostsCache,t).then(h=>p([...h.statelessHosts].reverse(),h.getTimeout))}function lr(e){let{hostsCache:t,logger:r,requester:s,requestsCache:n,responsesCache:a,timeouts:o,userAgent:d,hosts:y,queryParameters:b,headers:f}=e,p={hostsCache:t,logger:r,requester:s,requestsCache:n,responsesCache:a,timeouts:o,userAgent:d,headers:f,queryParameters:b,hosts:y.map(h=>le(h)),read(h,S){let O=ce(S,p.timeouts.read),P=()=>Qe(p,p.hosts.filter(j=>(j.accept&X.Read)!=0),h,O);if((O.cacheable!==void 0?O.cacheable:h.cacheable)!==!0)return P();let v={request:h,mappedRequestOptions:O,transporter:{queryParameters:p.queryParameters,headers:p.headers}};return p.responsesCache.get(v,()=>p.requestsCache.get(v,()=>p.requestsCache.set(v,P()).then(j=>Promise.all([p.requestsCache.delete(v),j]),j=>Promise.all([p.requestsCache.delete(v),Promise.reject(j)])).then(([j,T])=>T)),{miss:j=>p.responsesCache.set(v,j)})},write(h,S){return Qe(p,p.hosts.filter(O=>(O.accept&X.Write)!=0),h,ce(S,p.timeouts.write))}};return p}function dr(e){let t={value:`Algolia for JavaScript (${e})`,add(r){let s=`; ${r.segment}${r.version!==void 0?` (${r.version})`:""}`;return t.value.indexOf(s)===-1&&(t.value=`${t.value}${s}`),t}};return t}function Ke(e){try{return JSON.parse(e.content)}catch(t){throw Je(t.message,e)}}function ze({content:e,status:t},r){let s=e;try{s=JSON.parse(e).message}catch(n){}return Xe(s,t,r)}function pr(e,...t){let r=0;return e.replace(/%s/g,()=>encodeURIComponent(t[r++]))}function Ge(e,t,r){let s=Ye(r),n=`${e.protocol}://${e.url}/${t.charAt(0)==="/"?t.substr(1):t}`;return s.length&&(n+=`?${s}`),n}function Ye(e){let t=r=>Object.prototype.toString.call(r)==="[object Object]"||Object.prototype.toString.call(r)==="[object Array]";return Object.keys(e).map(r=>pr("%s=%s",r,t(e[r])?JSON.stringify(e[r]):e[r])).join("&")}function $e(e,t){if(e.method===He.MethodEnum.Get||e.data===void 0&&t.data===void 0)return;let r=Array.isArray(e.data)?e.data:u(u({},e.data),t.data);return JSON.stringify(r)}function Le(e,t){let r=u(u({},e.headers),t.headers),s={};return Object.keys(r).forEach(n=>{let a=r[n];s[n.toLowerCase()]=a}),s}function de(e){return e.map(t=>pe(t))}function pe(e){let t=e.request.headers["x-algolia-api-key"]?{"x-algolia-api-key":"*****"}:{};return g(u({},e),{request:g(u({},e.request),{headers:u(u({},e.request.headers),t)})})}function Xe(e,t,r){return{name:"ApiError",message:e,status:t,transporterStackTrace:r}}function Je(e,t){return{name:"DeserializationError",message:e,response:t}}function Ve(e){return{name:"RetryError",message:"Unreachable hosts - your application id may be incorrect. If the error persists, contact support@algolia.com.",transporterStackTrace:e}}A.CallEnum=X;A.HostStatusEnum=U;A.createApiError=Xe;A.createDeserializationError=Je;A.createMappedRequestOptions=ce;A.createRetryError=Ve;A.createStatefulHost=ue;A.createStatelessHost=le;A.createTransporter=lr;A.createUserAgent=dr;A.deserializeFailure=ze;A.deserializeSuccess=Ke;A.isStatefulHostTimeouted=Be;A.isStatefulHostUp=Fe;A.serializeData=$e;A.serializeHeaders=Le;A.serializeQueryParameters=Ye;A.serializeUrl=Ge;A.stackFrameWithoutCredentials=pe;A.stackTraceWithoutCredentials=de});var K=I((en,et)=>{et.exports=Ze()});var tt=I(w=>{"use strict";Object.defineProperty(w,"__esModule",{value:!0});var N=F(),mr=K(),z=B(),hr=e=>{let t=e.region||"us",r=N.createAuth(N.AuthMode.WithinHeaders,e.appId,e.apiKey),s=mr.createTransporter(g(u({hosts:[{url:`analytics.${t}.algolia.com`}]},e),{headers:u(g(u({},r.headers()),{"content-type":"application/json"}),e.headers),queryParameters:u(u({},r.queryParameters()),e.queryParameters)})),n=e.appId;return N.addMethods({appId:n,transporter:s},e.methods)},yr=e=>(t,r)=>e.transporter.write({method:z.MethodEnum.Post,path:"2/abtests",data:t},r),gr=e=>(t,r)=>e.transporter.write({method:z.MethodEnum.Delete,path:N.encode("2/abtests/%s",t)},r),fr=e=>(t,r)=>e.transporter.read({method:z.MethodEnum.Get,path:N.encode("2/abtests/%s",t)},r),br=e=>t=>e.transporter.read({method:z.MethodEnum.Get,path:"2/abtests"},t),Pr=e=>(t,r)=>e.transporter.write({method:z.MethodEnum.Post,path:N.encode("2/abtests/%s/stop",t)},r);w.addABTest=yr;w.createAnalyticsClient=hr;w.deleteABTest=gr;w.getABTest=fr;w.getABTests=br;w.stopABTest=Pr});var st=I((rn,rt)=>{rt.exports=tt()});var at=I(G=>{"use strict";Object.defineProperty(G,"__esModule",{value:!0});var me=F(),jr=K(),nt=B(),Or=e=>{let t=e.region||"us",r=me.createAuth(me.AuthMode.WithinHeaders,e.appId,e.apiKey),s=jr.createTransporter(g(u({hosts:[{url:`recommendation.${t}.algolia.com`}]},e),{headers:u(g(u({},r.headers()),{"content-type":"application/json"}),e.headers),queryParameters:u(u({},r.queryParameters()),e.queryParameters)}));return me.addMethods({appId:e.appId,transporter:s},e.methods)},Ir=e=>t=>e.transporter.read({method:nt.MethodEnum.Get,path:"1/strategies/personalization"},t),Ar=e=>(t,r)=>e.transporter.write({method:nt.MethodEnum.Post,path:"1/strategies/personalization",data:t},r);G.createRecommendationClient=Or;G.getPersonalizationStrategy=Ir;G.setPersonalizationStrategy=Ar});var it=I((nn,ot)=>{ot.exports=at()});var jt=I(i=>{"use strict";Object.defineProperty(i,"__esModule",{value:!0});var l=F(),q=K(),m=B(),Sr=require("crypto");function Y(e){let t=r=>e.request(r).then(s=>{if(e.batch!==void 0&&e.batch(s.hits),!e.shouldStop(s))return s.cursor?t({cursor:s.cursor}):t({page:(r.page||0)+1})});return t({})}var Dr=e=>{let t=e.appId,r=l.createAuth(e.authMode!==void 0?e.authMode:l.AuthMode.WithinHeaders,t,e.apiKey),s=q.createTransporter(g(u({hosts:[{url:`${t}-dsn.algolia.net`,accept:q.CallEnum.Read},{url:`${t}.algolia.net`,accept:q.CallEnum.Write}].concat(l.shuffle([{url:`${t}-1.algolianet.com`},{url:`${t}-2.algolianet.com`},{url:`${t}-3.algolianet.com`}]))},e),{headers:u(g(u({},r.headers()),{"content-type":"application/x-www-form-urlencoded"}),e.headers),queryParameters:u(u({},r.queryParameters()),e.queryParameters)})),n={transporter:s,appId:t,addAlgoliaAgent(a,o){s.userAgent.add({segment:a,version:o})},clearCache(){return Promise.all([s.requestsCache.clear(),s.responsesCache.clear()]).then(()=>{})}};return l.addMethods(n,e.methods)};function ct(){return{name:"MissingObjectIDError",message:"All objects must have an unique objectID (like a primary key) to be valid. Algolia is also able to generate objectIDs automatically but *it's not recommended*. To do it, use the `{'autoGenerateObjectIDIfNotExist': true}` option."}}function ut(){return{name:"ObjectNotFoundError",message:"Object not found."}}function lt(){return{name:"ValidUntilNotFoundError",message:"ValidUntil not found in given secured api key."}}var Rr=e=>(t,r)=>{let d=r||{},{queryParameters:s}=d,n=R(d,["queryParameters"]),a=u({acl:t},s!==void 0?{queryParameters:s}:{}),o=(y,b)=>l.createRetryablePromise(f=>$(e)(y.key,b).catch(p=>{if(p.status!==404)throw p;return f()}));return l.createWaitablePromise(e.transporter.write({method:m.MethodEnum.Post,path:"1/keys",data:a},n),o)},vr=e=>(t,r,s)=>{let n=q.createMappedRequestOptions(s);return n.queryParameters["X-Algolia-User-ID"]=t,e.transporter.write({method:m.MethodEnum.Post,path:"1/clusters/mapping",data:{cluster:r}},n)},xr=e=>(t,r,s)=>e.transporter.write({method:m.MethodEnum.Post,path:"1/clusters/mapping/batch",data:{users:t,cluster:r}},s),Z=e=>(t,r,s)=>{let n=(a,o)=>L(e)(t,{methods:{waitTask:D}}).waitTask(a.taskID,o);return l.createWaitablePromise(e.transporter.write({method:m.MethodEnum.Post,path:l.encode("1/indexes/%s/operation",t),data:{operation:"copy",destination:r}},s),n)},qr=e=>(t,r,s)=>Z(e)(t,r,g(u({},s),{scope:[ee.Rules]})),Er=e=>(t,r,s)=>Z(e)(t,r,g(u({},s),{scope:[ee.Settings]})),Tr=e=>(t,r,s)=>Z(e)(t,r,g(u({},s),{scope:[ee.Synonyms]})),Mr=e=>(t,r)=>{let s=(n,a)=>l.createRetryablePromise(o=>$(e)(t,a).then(o).catch(d=>{if(d.status!==404)throw d}));return l.createWaitablePromise(e.transporter.write({method:m.MethodEnum.Delete,path:l.encode("1/keys/%s",t)},r),s)},wr=()=>(e,t)=>{let r=q.serializeQueryParameters(t),s=Sr.createHmac("sha256",e).update(r).digest("hex");return Buffer.from(s+r).toString("base64")},$=e=>(t,r)=>e.transporter.read({method:m.MethodEnum.Get,path:l.encode("1/keys/%s",t)},r),kr=e=>t=>e.transporter.read({method:m.MethodEnum.Get,path:"1/logs"},t),Cr=()=>e=>{let t=Buffer.from(e,"base64").toString("ascii"),r=/validUntil=(\d+)/,s=t.match(r);if(s===null)throw lt();return parseInt(s[1],10)-Math.round(new Date().getTime()/1e3)},Ur=e=>t=>e.transporter.read({method:m.MethodEnum.Get,path:"1/clusters/mapping/top"},t),Nr=e=>(t,r)=>e.transporter.read({method:m.MethodEnum.Get,path:l.encode("1/clusters/mapping/%s",t)},r),Wr=e=>t=>{let n=t||{},{retrieveMappings:r}=n,s=R(n,["retrieveMappings"]);return r===!0&&(s.getClusters=!0),e.transporter.read({method:m.MethodEnum.Get,path:"1/clusters/mapping/pending"},s)},L=e=>(t,r={})=>{let s={transporter:e.transporter,appId:e.appId,indexName:t};return l.addMethods(s,r.methods)},Hr=e=>t=>e.transporter.read({method:m.MethodEnum.Get,path:"1/keys"},t),_r=e=>t=>e.transporter.read({method:m.MethodEnum.Get,path:"1/clusters"},t),Fr=e=>t=>e.transporter.read({method:m.MethodEnum.Get,path:"1/indexes"},t),Br=e=>t=>e.transporter.read({method:m.MethodEnum.Get,path:"1/clusters/mapping"},t),Kr=e=>(t,r,s)=>{let n=(a,o)=>L(e)(t,{methods:{waitTask:D}}).waitTask(a.taskID,o);return l.createWaitablePromise(e.transporter.write({method:m.MethodEnum.Post,path:l.encode("1/indexes/%s/operation",t),data:{operation:"move",destination:r}},s),n)},zr=e=>(t,r)=>{let s=(n,a)=>Promise.all(Object.keys(n.taskID).map(o=>L(e)(o,{methods:{waitTask:D}}).waitTask(n.taskID[o],a)));return l.createWaitablePromise(e.transporter.write({method:m.MethodEnum.Post,path:"1/indexes/*/batch",data:{requests:t}},r),s)},Gr=e=>(t,r)=>e.transporter.read({method:m.MethodEnum.Post,path:"1/indexes/*/objects",data:{requests:t}},r),$r=e=>(t,r)=>{let s=t.map(n=>g(u({},n),{params:q.serializeQueryParameters(n.params||{})}));return e.transporter.read({method:m.MethodEnum.Post,path:"1/indexes/*/queries",data:{requests:s},cacheable:!0},r)},Lr=e=>(t,r)=>Promise.all(t.map(s=>{let d=s.params,{facetName:n,facetQuery:a}=d,o=R(d,["facetName","facetQuery"]);return L(e)(s.indexName,{methods:{searchForFacetValues:dt}}).searchForFacetValues(n,a,u(u({},r),o))})),Vr=e=>(t,r)=>{let s=q.createMappedRequestOptions(r);return s.queryParameters["X-Algolia-User-ID"]=t,e.transporter.write({method:m.MethodEnum.Delete,path:"1/clusters/mapping"},s)},Qr=e=>(t,r)=>{let s=(n,a)=>l.createRetryablePromise(o=>$(e)(t,a).catch(d=>{if(d.status!==404)throw d;return o()}));return l.createWaitablePromise(e.transporter.write({method:m.MethodEnum.Post,path:l.encode("1/keys/%s/restore",t)},r),s)},Jr=e=>(t,r)=>e.transporter.read({method:m.MethodEnum.Post,path:"1/clusters/mapping/search",data:{query:t}},r),Xr=e=>(t,r)=>{let s=Object.assign({},r),f=r||{},{queryParameters:n}=f,a=R(f,["queryParameters"]),o=n?{queryParameters:n}:{},d=["acl","indexes","referers","restrictSources","queryParameters","description","maxQueriesPerIPPerHour","maxHitsPerQuery"],y=p=>Object.keys(s).filter(h=>d.indexOf(h)!==-1).every(h=>p[h]===s[h]),b=(p,h)=>l.createRetryablePromise(S=>$(e)(t,h).then(O=>y(O)?Promise.resolve():S()));return l.createWaitablePromise(e.transporter.write({method:m.MethodEnum.Put,path:l.encode("1/keys/%s",t),data:o},a),b)},pt=e=>(t,r)=>{let s=(n,a)=>D(e)(n.taskID,a);return l.createWaitablePromise(e.transporter.write({method:m.MethodEnum.Post,path:l.encode("1/indexes/%s/batch",e.indexName),data:{requests:t}},r),s)},Yr=e=>t=>Y(g(u({},t),{shouldStop:r=>r.cursor===void 0,request:r=>e.transporter.read({method:m.MethodEnum.Post,path:l.encode("1/indexes/%s/browse",e.indexName),data:r},t)})),Zr=e=>t=>{let r=u({hitsPerPage:1e3},t);return Y(g(u({},r),{shouldStop:s=>s.hits.lengthg(u({},n),{hits:n.hits.map(a=>(delete a._highlightResult,a))}))}}))},es=e=>t=>{let r=u({hitsPerPage:1e3},t);return Y(g(u({},r),{shouldStop:s=>s.hits.lengthg(u({},n),{hits:n.hits.map(a=>(delete a._highlightResult,a))}))}}))},te=e=>(t,r,s)=>{let y=s||{},{batchSize:n}=y,a=R(y,["batchSize"]),o={taskIDs:[],objectIDs:[]},d=(b=0)=>{let f=[],p;for(p=b;p({action:r,body:h})),a).then(h=>(o.objectIDs=o.objectIDs.concat(h.objectIDs),o.taskIDs.push(h.taskID),p++,d(p)))};return l.createWaitablePromise(d(),(b,f)=>Promise.all(b.taskIDs.map(p=>D(e)(p,f))))},ts=e=>t=>l.createWaitablePromise(e.transporter.write({method:m.MethodEnum.Post,path:l.encode("1/indexes/%s/clear",e.indexName)},t),(r,s)=>D(e)(r.taskID,s)),rs=e=>t=>{let a=t||{},{forwardToReplicas:r}=a,s=R(a,["forwardToReplicas"]),n=q.createMappedRequestOptions(s);return r&&(n.queryParameters.forwardToReplicas=1),l.createWaitablePromise(e.transporter.write({method:m.MethodEnum.Post,path:l.encode("1/indexes/%s/rules/clear",e.indexName)},n),(o,d)=>D(e)(o.taskID,d))},ss=e=>t=>{let a=t||{},{forwardToReplicas:r}=a,s=R(a,["forwardToReplicas"]),n=q.createMappedRequestOptions(s);return r&&(n.queryParameters.forwardToReplicas=1),l.createWaitablePromise(e.transporter.write({method:m.MethodEnum.Post,path:l.encode("1/indexes/%s/synonyms/clear",e.indexName)},n),(o,d)=>D(e)(o.taskID,d))},ns=e=>(t,r)=>l.createWaitablePromise(e.transporter.write({method:m.MethodEnum.Post,path:l.encode("1/indexes/%s/deleteByQuery",e.indexName),data:t},r),(s,n)=>D(e)(s.taskID,n)),as=e=>t=>l.createWaitablePromise(e.transporter.write({method:m.MethodEnum.Delete,path:l.encode("1/indexes/%s",e.indexName)},t),(r,s)=>D(e)(r.taskID,s)),os=e=>(t,r)=>l.createWaitablePromise(yt(e)([t],r).then(s=>({taskID:s.taskIDs[0]})),(s,n)=>D(e)(s.taskID,n)),yt=e=>(t,r)=>{let s=t.map(n=>({objectID:n}));return te(e)(s,k.DeleteObject,r)},is=e=>(t,r)=>{let o=r||{},{forwardToReplicas:s}=o,n=R(o,["forwardToReplicas"]),a=q.createMappedRequestOptions(n);return s&&(a.queryParameters.forwardToReplicas=1),l.createWaitablePromise(e.transporter.write({method:m.MethodEnum.Delete,path:l.encode("1/indexes/%s/rules/%s",e.indexName,t)},a),(d,y)=>D(e)(d.taskID,y))},cs=e=>(t,r)=>{let o=r||{},{forwardToReplicas:s}=o,n=R(o,["forwardToReplicas"]),a=q.createMappedRequestOptions(n);return s&&(a.queryParameters.forwardToReplicas=1),l.createWaitablePromise(e.transporter.write({method:m.MethodEnum.Delete,path:l.encode("1/indexes/%s/synonyms/%s",e.indexName,t)},a),(d,y)=>D(e)(d.taskID,y))},us=e=>t=>gt(e)(t).then(()=>!0).catch(r=>{if(r.status!==404)throw r;return!1}),ls=e=>(t,r)=>{let y=r||{},{query:s,paginate:n}=y,a=R(y,["query","paginate"]),o=0,d=()=>ft(e)(s||"",g(u({},a),{page:o})).then(b=>{for(let[f,p]of Object.entries(b.hits))if(t(p))return{object:p,position:parseInt(f,10),page:o};if(o++,n===!1||o>=b.nbPages)throw ut();return d()});return d()},ds=e=>(t,r)=>e.transporter.read({method:m.MethodEnum.Get,path:l.encode("1/indexes/%s/%s",e.indexName,t)},r),ps=()=>(e,t)=>{for(let[r,s]of Object.entries(e.hits))if(s.objectID===t)return parseInt(r,10);return-1},ms=e=>(t,r)=>{let o=r||{},{attributesToRetrieve:s}=o,n=R(o,["attributesToRetrieve"]),a=t.map(d=>u({indexName:e.indexName,objectID:d},s?{attributesToRetrieve:s}:{}));return e.transporter.read({method:m.MethodEnum.Post,path:"1/indexes/*/objects",data:{requests:a}},n)},hs=e=>(t,r)=>e.transporter.read({method:m.MethodEnum.Get,path:l.encode("1/indexes/%s/rules/%s",e.indexName,t)},r),gt=e=>t=>e.transporter.read({method:m.MethodEnum.Get,path:l.encode("1/indexes/%s/settings",e.indexName),data:{getVersion:2}},t),ys=e=>(t,r)=>e.transporter.read({method:m.MethodEnum.Get,path:l.encode("1/indexes/%s/synonyms/%s",e.indexName,t)},r),bt=e=>(t,r)=>e.transporter.read({method:m.MethodEnum.Get,path:l.encode("1/indexes/%s/task/%s",e.indexName,t.toString())},r),gs=e=>(t,r)=>l.createWaitablePromise(Pt(e)([t],r).then(s=>({objectID:s.objectIDs[0],taskID:s.taskIDs[0]})),(s,n)=>D(e)(s.taskID,n)),Pt=e=>(t,r)=>{let o=r||{},{createIfNotExists:s}=o,n=R(o,["createIfNotExists"]),a=s?k.PartialUpdateObject:k.PartialUpdateObjectNoCreate;return te(e)(t,a,n)},fs=e=>(t,r)=>{let O=r||{},{safe:s,autoGenerateObjectIDIfNotExist:n,batchSize:a}=O,o=R(O,["safe","autoGenerateObjectIDIfNotExist","batchSize"]),d=(P,x,v,j)=>l.createWaitablePromise(e.transporter.write({method:m.MethodEnum.Post,path:l.encode("1/indexes/%s/operation",P),data:{operation:v,destination:x}},j),(T,V)=>D(e)(T.taskID,V)),y=Math.random().toString(36).substring(7),b=`${e.indexName}_tmp_${y}`,f=he({appId:e.appId,transporter:e.transporter,indexName:b}),p=[],h=d(e.indexName,b,"copy",g(u({},o),{scope:["settings","synonyms","rules"]}));p.push(h);let S=(s?h.wait(o):h).then(()=>{let P=f(t,g(u({},o),{autoGenerateObjectIDIfNotExist:n,batchSize:a}));return p.push(P),s?P.wait(o):P}).then(()=>{let P=d(b,e.indexName,"move",o);return p.push(P),s?P.wait(o):P}).then(()=>Promise.all(p)).then(([P,x,v])=>({objectIDs:x.objectIDs,taskIDs:[P.taskID,...x.taskIDs,v.taskID]}));return l.createWaitablePromise(S,(P,x)=>Promise.all(p.map(v=>v.wait(x))))},bs=e=>(t,r)=>ye(e)(t,g(u({},r),{clearExistingRules:!0})),Ps=e=>(t,r)=>ge(e)(t,g(u({},r),{replaceExistingSynonyms:!0})),js=e=>(t,r)=>l.createWaitablePromise(he(e)([t],r).then(s=>({objectID:s.objectIDs[0],taskID:s.taskIDs[0]})),(s,n)=>D(e)(s.taskID,n)),he=e=>(t,r)=>{let o=r||{},{autoGenerateObjectIDIfNotExist:s}=o,n=R(o,["autoGenerateObjectIDIfNotExist"]),a=s?k.AddObject:k.UpdateObject;if(a===k.UpdateObject){for(let d of t)if(d.objectID===void 0)return l.createWaitablePromise(Promise.reject(ct()))}return te(e)(t,a,n)},Os=e=>(t,r)=>ye(e)([t],r),ye=e=>(t,r)=>{let d=r||{},{forwardToReplicas:s,clearExistingRules:n}=d,a=R(d,["forwardToReplicas","clearExistingRules"]),o=q.createMappedRequestOptions(a);return s&&(o.queryParameters.forwardToReplicas=1),n&&(o.queryParameters.clearExistingRules=1),l.createWaitablePromise(e.transporter.write({method:m.MethodEnum.Post,path:l.encode("1/indexes/%s/rules/batch",e.indexName),data:t},o),(y,b)=>D(e)(y.taskID,b))},Is=e=>(t,r)=>ge(e)([t],r),ge=e=>(t,r)=>{let d=r||{},{forwardToReplicas:s,replaceExistingSynonyms:n}=d,a=R(d,["forwardToReplicas","replaceExistingSynonyms"]),o=q.createMappedRequestOptions(a);return s&&(o.queryParameters.forwardToReplicas=1),n&&(o.queryParameters.replaceExistingSynonyms=1),l.createWaitablePromise(e.transporter.write({method:m.MethodEnum.Post,path:l.encode("1/indexes/%s/synonyms/batch",e.indexName),data:t},o),(y,b)=>D(e)(y.taskID,b))},ft=e=>(t,r)=>e.transporter.read({method:m.MethodEnum.Post,path:l.encode("1/indexes/%s/query",e.indexName),data:{query:t},cacheable:!0},r),dt=e=>(t,r,s)=>e.transporter.read({method:m.MethodEnum.Post,path:l.encode("1/indexes/%s/facets/%s/query",e.indexName,t),data:{facetQuery:r},cacheable:!0},s),mt=e=>(t,r)=>e.transporter.read({method:m.MethodEnum.Post,path:l.encode("1/indexes/%s/rules/search",e.indexName),data:{query:t}},r),ht=e=>(t,r)=>e.transporter.read({method:m.MethodEnum.Post,path:l.encode("1/indexes/%s/synonyms/search",e.indexName),data:{query:t}},r),As=e=>(t,r)=>{let o=r||{},{forwardToReplicas:s}=o,n=R(o,["forwardToReplicas"]),a=q.createMappedRequestOptions(n);return s&&(a.queryParameters.forwardToReplicas=1),l.createWaitablePromise(e.transporter.write({method:m.MethodEnum.Put,path:l.encode("1/indexes/%s/settings",e.indexName),data:t},a),(d,y)=>D(e)(d.taskID,y))},D=e=>(t,r)=>l.createRetryablePromise(s=>bt(e)(t,r).then(n=>n.status!=="published"?s():void 0)),Ss={AddObject:"addObject",Analytics:"analytics",Browser:"browse",DeleteIndex:"deleteIndex",DeleteObject:"deleteObject",EditSettings:"editSettings",ListIndexes:"listIndexes",Logs:"logs",Recommendation:"recommendation",Search:"search",SeeUnretrievableAttributes:"seeUnretrievableAttributes",Settings:"settings",Usage:"usage"},k={AddObject:"addObject",UpdateObject:"updateObject",PartialUpdateObject:"partialUpdateObject",PartialUpdateObjectNoCreate:"partialUpdateObjectNoCreate",DeleteObject:"deleteObject"},ee={Settings:"settings",Synonyms:"synonyms",Rules:"rules"},Ds={None:"none",StopIfEnoughMatches:"stopIfEnoughMatches"},Rs={Synonym:"synonym",OneWaySynonym:"oneWaySynonym",AltCorrection1:"altCorrection1",AltCorrection2:"altCorrection2",Placeholder:"placeholder"};i.ApiKeyACLEnum=Ss;i.BatchActionEnum=k;i.ScopeEnum=ee;i.StrategyEnum=Ds;i.SynonymEnum=Rs;i.addApiKey=Rr;i.assignUserID=vr;i.assignUserIDs=xr;i.batch=pt;i.browseObjects=Yr;i.browseRules=Zr;i.browseSynonyms=es;i.chunkedBatch=te;i.clearObjects=ts;i.clearRules=rs;i.clearSynonyms=ss;i.copyIndex=Z;i.copyRules=qr;i.copySettings=Er;i.copySynonyms=Tr;i.createBrowsablePromise=Y;i.createMissingObjectIDError=ct;i.createObjectNotFoundError=ut;i.createSearchClient=Dr;i.createValidUntilNotFoundError=lt;i.deleteApiKey=Mr;i.deleteBy=ns;i.deleteIndex=as;i.deleteObject=os;i.deleteObjects=yt;i.deleteRule=is;i.deleteSynonym=cs;i.exists=us;i.findObject=ls;i.generateSecuredApiKey=wr;i.getApiKey=$;i.getLogs=kr;i.getObject=ds;i.getObjectPosition=ps;i.getObjects=ms;i.getRule=hs;i.getSecuredApiKeyRemainingValidity=Cr;i.getSettings=gt;i.getSynonym=ys;i.getTask=bt;i.getTopUserIDs=Ur;i.getUserID=Nr;i.hasPendingMappings=Wr;i.initIndex=L;i.listApiKeys=Hr;i.listClusters=_r;i.listIndices=Fr;i.listUserIDs=Br;i.moveIndex=Kr;i.multipleBatch=zr;i.multipleGetObjects=Gr;i.multipleQueries=$r;i.multipleSearchForFacetValues=Lr;i.partialUpdateObject=gs;i.partialUpdateObjects=Pt;i.removeUserID=Vr;i.replaceAllObjects=fs;i.replaceAllRules=bs;i.replaceAllSynonyms=Ps;i.restoreApiKey=Qr;i.saveObject=js;i.saveObjects=he;i.saveRule=Os;i.saveRules=ye;i.saveSynonym=Is;i.saveSynonyms=ge;i.search=ft;i.searchForFacetValues=dt;i.searchRules=mt;i.searchSynonyms=ht;i.searchUserIDs=Jr;i.setSettings=As;i.updateApiKey=Xr;i.waitTask=D});var It=I((on,Ot)=>{Ot.exports=jt()});var At=I(re=>{"use strict";Object.defineProperty(re,"__esModule",{value:!0});function vs(){return{debug(e,t){return Promise.resolve()},info(e,t){return Promise.resolve()},error(e,t){return Promise.resolve()}}}var xs={Debug:1,Info:2,Error:3};re.LogLevelEnum=xs;re.createNullLogger=vs});var Dt=I((un,St)=>{St.exports=At()});var xt=I(fe=>{"use strict";Object.defineProperty(fe,"__esModule",{value:!0});var Rt=require("http"),vt=require("https"),qs=require("url");function Es(){let e={keepAlive:!0},t=new Rt.Agent(e),r=new vt.Agent(e);return{send(s){return new Promise(n=>{let a=qs.parse(s.url),o=a.query===null?a.pathname:`${a.pathname}?${a.query}`,d=u({agent:a.protocol==="https:"?r:t,hostname:a.hostname,path:o,method:s.method,headers:s.headers},a.port!==void 0?{port:a.port||""}:{}),y=(a.protocol==="https:"?vt:Rt).request(d,h=>{let S="";h.on("data",O=>S+=O),h.on("end",()=>{clearTimeout(f),clearTimeout(p),n({status:h.statusCode||0,content:S,isTimedOut:!1})})}),b=(h,S)=>setTimeout(()=>{y.abort(),n({status:0,content:S,isTimedOut:!0})},h*1e3),f=b(s.connectTimeout,"Connection timeout"),p;y.on("error",h=>{clearTimeout(f),clearTimeout(p),n({status:0,content:h.message,isTimedOut:!1})}),y.once("response",()=>{clearTimeout(f),p=b(s.responseTimeout,"Socket timeout")}),s.data!==void 0&&y.write(s.data),y.end()})},destroy(){return t.destroy(),r.destroy(),Promise.resolve()}}}fe.createNodeHttpRequester=Es});var Et=I((dn,qt)=>{qt.exports=xt()});var kt=I((pn,Tt)=>{"use strict";var Mt=Ee(),Ts=we(),W=st(),be=F(),Pe=it(),c=It(),Ms=Dt(),ws=Et(),ks=K();function wt(e,t,r){let s={appId:e,apiKey:t,timeouts:{connect:2,read:5,write:30},requester:ws.createNodeHttpRequester(),logger:Ms.createNullLogger(),responsesCache:Mt.createNullCache(),requestsCache:Mt.createNullCache(),hostsCache:Ts.createInMemoryCache(),userAgent:ks.createUserAgent(be.version).add({segment:"Node.js",version:process.versions.node})};return c.createSearchClient(g(u(u({},s),r),{methods:{search:c.multipleQueries,searchForFacetValues:c.multipleSearchForFacetValues,multipleBatch:c.multipleBatch,multipleGetObjects:c.multipleGetObjects,multipleQueries:c.multipleQueries,copyIndex:c.copyIndex,copySettings:c.copySettings,copyRules:c.copyRules,copySynonyms:c.copySynonyms,moveIndex:c.moveIndex,listIndices:c.listIndices,getLogs:c.getLogs,listClusters:c.listClusters,multipleSearchForFacetValues:c.multipleSearchForFacetValues,getApiKey:c.getApiKey,addApiKey:c.addApiKey,listApiKeys:c.listApiKeys,updateApiKey:c.updateApiKey,deleteApiKey:c.deleteApiKey,restoreApiKey:c.restoreApiKey,assignUserID:c.assignUserID,assignUserIDs:c.assignUserIDs,getUserID:c.getUserID,searchUserIDs:c.searchUserIDs,listUserIDs:c.listUserIDs,getTopUserIDs:c.getTopUserIDs,removeUserID:c.removeUserID,hasPendingMappings:c.hasPendingMappings,generateSecuredApiKey:c.generateSecuredApiKey,getSecuredApiKeyRemainingValidity:c.getSecuredApiKeyRemainingValidity,destroy:be.destroy,initIndex:n=>a=>c.initIndex(n)(a,{methods:{batch:c.batch,delete:c.deleteIndex,getObject:c.getObject,getObjects:c.getObjects,saveObject:c.saveObject,saveObjects:c.saveObjects,search:c.search,searchForFacetValues:c.searchForFacetValues,waitTask:c.waitTask,setSettings:c.setSettings,getSettings:c.getSettings,partialUpdateObject:c.partialUpdateObject,partialUpdateObjects:c.partialUpdateObjects,deleteObject:c.deleteObject,deleteObjects:c.deleteObjects,deleteBy:c.deleteBy,clearObjects:c.clearObjects,browseObjects:c.browseObjects,getObjectPosition:c.getObjectPosition,findObject:c.findObject,exists:c.exists,saveSynonym:c.saveSynonym,saveSynonyms:c.saveSynonyms,getSynonym:c.getSynonym,searchSynonyms:c.searchSynonyms,browseSynonyms:c.browseSynonyms,deleteSynonym:c.deleteSynonym,clearSynonyms:c.clearSynonyms,replaceAllObjects:c.replaceAllObjects,replaceAllSynonyms:c.replaceAllSynonyms,searchRules:c.searchRules,getRule:c.getRule,deleteRule:c.deleteRule,saveRule:c.saveRule,saveRules:c.saveRules,replaceAllRules:c.replaceAllRules,browseRules:c.browseRules,clearRules:c.clearRules}}),initAnalytics:()=>n=>W.createAnalyticsClient(g(u(u({},s),n),{methods:{addABTest:W.addABTest,getABTest:W.getABTest,getABTests:W.getABTests,stopABTest:W.stopABTest,deleteABTest:W.deleteABTest}})),initRecommendation:()=>n=>Pe.createRecommendationClient(g(u(u({},s),n),{methods:{getPersonalizationStrategy:Pe.getPersonalizationStrategy,setPersonalizationStrategy:Pe.setPersonalizationStrategy}}))}}))}wt.version=be.version;Tt.exports=wt});var Ut=I((mn,je)=>{var Ct=kt();je.exports=Ct;je.exports.default=Ct});var Ws={};Vt(Ws,{default:()=>Ks});var Oe=C(require("@yarnpkg/core")),E=C(require("@yarnpkg/core")),Ie=C(require("@yarnpkg/plugin-essentials")),Ht=C(require("semver"));var se=C(require("@yarnpkg/core")),Nt=C(Ut()),Cs="e8e1bd300d860104bb8c58453ffa1eb4",Us="OFCNCOG2CU",Wt=async(e,t)=>{var a;let r=se.structUtils.stringifyIdent(e),n=Ns(t).initIndex("npm-search");try{return((a=(await n.getObject(r,{attributesToRetrieve:["types"]})).types)==null?void 0:a.ts)==="definitely-typed"}catch(o){return!1}},Ns=e=>(0,Nt.default)(Us,Cs,{requester:{async send(r){try{let s=await se.httpUtils.request(r.url,r.data||null,{configuration:e,headers:r.headers});return{content:s.body,isTimedOut:!1,status:s.statusCode}}catch(s){return{content:s.response.body,isTimedOut:!1,status:s.response.statusCode}}}}});var _t=e=>e.scope?`${e.scope}__${e.name}`:`${e.name}`,Hs=async(e,t,r,s)=>{if(r.scope==="types")return;let{project:n}=e,{configuration:a}=n,o=a.makeResolver(),d={project:n,resolver:o,report:new E.ThrowReport};if(!await Wt(r,a))return;let b=_t(r),f=E.structUtils.parseRange(r.range).selector;if(!E.semverUtils.validRange(f)){let P=await o.getCandidates(r,new Map,d);f=E.structUtils.parseRange(P[0].reference).selector}let p=Ht.default.coerce(f);if(p===null)return;let h=`${Ie.suggestUtils.Modifier.CARET}${p.major}`,S=E.structUtils.makeDescriptor(E.structUtils.makeIdent("types",b),h),O=E.miscUtils.mapAndFind(n.workspaces,P=>{var T,V;let x=(T=P.manifest.dependencies.get(r.identHash))==null?void 0:T.descriptorHash,v=(V=P.manifest.devDependencies.get(r.identHash))==null?void 0:V.descriptorHash;if(x!==r.descriptorHash&&v!==r.descriptorHash)return E.miscUtils.mapAndFind.skip;let j=[];for(let Ae of Oe.Manifest.allDependencies){let Se=P.manifest[Ae].get(S.identHash);typeof Se!="undefined"&&j.push([Ae,Se])}return j.length===0?E.miscUtils.mapAndFind.skip:j});if(typeof O!="undefined")for(let[P,x]of O)e.manifest[P].set(x.identHash,x);else{try{if((await o.getCandidates(S,new Map,d)).length===0)return}catch{return}e.manifest[Ie.suggestUtils.Target.DEVELOPMENT].set(S.identHash,S)}},_s=async(e,t,r)=>{if(r.scope==="types")return;let s=_t(r),n=E.structUtils.makeIdent("types",s);for(let a of Oe.Manifest.allDependencies)typeof e.manifest[a].get(n.identHash)!="undefined"&&e.manifest[a].delete(n.identHash)},Fs=(e,t)=>{t.publishConfig&&t.publishConfig.typings&&(t.typings=t.publishConfig.typings),t.publishConfig&&t.publishConfig.types&&(t.types=t.publishConfig.types)},Bs={hooks:{afterWorkspaceDependencyAddition:Hs,afterWorkspaceDependencyRemoval:_s,beforeWorkspacePacking:Fs}},Ks=Bs;return Ws;})(); 7 | return plugin; 8 | } 9 | }; 10 | -------------------------------------------------------------------------------- /.yarn/sdks/integrations.yml: -------------------------------------------------------------------------------- 1 | # This file is automatically generated by PnPify. 2 | # Manual changes will be lost! 3 | 4 | integrations: 5 | - vscode 6 | -------------------------------------------------------------------------------- /.yarn/sdks/prettier/index.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const { existsSync } = require(`fs`); 4 | const { createRequire, createRequireFromPath } = require(`module`); 5 | const { resolve } = require(`path`); 6 | 7 | const relPnpApiPath = '../../../.pnp.cjs'; 8 | 9 | const absPnpApiPath = resolve(__dirname, relPnpApiPath); 10 | const absRequire = (createRequire || createRequireFromPath)(absPnpApiPath); 11 | 12 | if (existsSync(absPnpApiPath)) { 13 | if (!process.versions.pnp) { 14 | // Setup the environment to be able to require prettier/index.js 15 | require(absPnpApiPath).setup(); 16 | } 17 | } 18 | 19 | // Defer to the real prettier/index.js your application uses 20 | module.exports = absRequire(`prettier/index.js`); 21 | -------------------------------------------------------------------------------- /.yarn/sdks/prettier/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "prettier", 3 | "version": "2.0.1-pnpify", 4 | "main": "./index.js", 5 | "type": "commonjs" 6 | } 7 | -------------------------------------------------------------------------------- /.yarn/sdks/typescript/bin/tsc: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const {existsSync} = require(`fs`); 4 | const {createRequire, createRequireFromPath} = require(`module`); 5 | const {resolve} = require(`path`); 6 | 7 | const relPnpApiPath = "../../../../.pnp.cjs"; 8 | 9 | const absPnpApiPath = resolve(__dirname, relPnpApiPath); 10 | const absRequire = (createRequire || createRequireFromPath)(absPnpApiPath); 11 | 12 | if (existsSync(absPnpApiPath)) { 13 | if (!process.versions.pnp) { 14 | // Setup the environment to be able to require typescript/bin/tsc 15 | require(absPnpApiPath).setup(); 16 | } 17 | } 18 | 19 | // Defer to the real typescript/bin/tsc your application uses 20 | module.exports = absRequire(`typescript/bin/tsc`); 21 | -------------------------------------------------------------------------------- /.yarn/sdks/typescript/bin/tsserver: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const {existsSync} = require(`fs`); 4 | const {createRequire, createRequireFromPath} = require(`module`); 5 | const {resolve} = require(`path`); 6 | 7 | const relPnpApiPath = "../../../../.pnp.cjs"; 8 | 9 | const absPnpApiPath = resolve(__dirname, relPnpApiPath); 10 | const absRequire = (createRequire || createRequireFromPath)(absPnpApiPath); 11 | 12 | if (existsSync(absPnpApiPath)) { 13 | if (!process.versions.pnp) { 14 | // Setup the environment to be able to require typescript/bin/tsserver 15 | require(absPnpApiPath).setup(); 16 | } 17 | } 18 | 19 | // Defer to the real typescript/bin/tsserver your application uses 20 | module.exports = absRequire(`typescript/bin/tsserver`); 21 | -------------------------------------------------------------------------------- /.yarn/sdks/typescript/lib/tsc.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const { existsSync } = require(`fs`); 4 | const { createRequire, createRequireFromPath } = require(`module`); 5 | const { resolve } = require(`path`); 6 | 7 | const relPnpApiPath = '../../../../.pnp.cjs'; 8 | 9 | const absPnpApiPath = resolve(__dirname, relPnpApiPath); 10 | const absRequire = (createRequire || createRequireFromPath)(absPnpApiPath); 11 | 12 | if (existsSync(absPnpApiPath)) { 13 | if (!process.versions.pnp) { 14 | // Setup the environment to be able to require typescript/lib/tsc.js 15 | require(absPnpApiPath).setup(); 16 | } 17 | } 18 | 19 | // Defer to the real typescript/lib/tsc.js your application uses 20 | module.exports = absRequire(`typescript/lib/tsc.js`); 21 | -------------------------------------------------------------------------------- /.yarn/sdks/typescript/lib/tsserver.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const { existsSync } = require(`fs`); 4 | const { createRequire, createRequireFromPath } = require(`module`); 5 | const { resolve } = require(`path`); 6 | 7 | const relPnpApiPath = '../../../../.pnp.cjs'; 8 | 9 | const absPnpApiPath = resolve(__dirname, relPnpApiPath); 10 | const absRequire = (createRequire || createRequireFromPath)(absPnpApiPath); 11 | 12 | const moduleWrapper = (tsserver) => { 13 | const { isAbsolute } = require(`path`); 14 | const pnpApi = require(`pnpapi`); 15 | 16 | const isVirtual = (str) => str.match(/\/(\$\$virtual|__virtual__)\//); 17 | const normalize = (str) => str.replace(/\\/g, `/`).replace(/^\/?/, `/`); 18 | 19 | const dependencyTreeRoots = new Set( 20 | pnpApi.getDependencyTreeRoots().map((locator) => { 21 | return `${locator.name}@${locator.reference}`; 22 | }) 23 | ); 24 | 25 | // VSCode sends the zip paths to TS using the "zip://" prefix, that TS 26 | // doesn't understand. This layer makes sure to remove the protocol 27 | // before forwarding it to TS, and to add it back on all returned paths. 28 | 29 | function toEditorPath(str) { 30 | // We add the `zip:` prefix to both `.zip/` paths and virtual paths 31 | if (isAbsolute(str) && !str.match(/^\^zip:/) && (str.match(/\.zip\//) || isVirtual(str))) { 32 | // We also take the opportunity to turn virtual paths into physical ones; 33 | // this makes it much easier to work with workspaces that list peer 34 | // dependencies, since otherwise Ctrl+Click would bring us to the virtual 35 | // file instances instead of the real ones. 36 | // 37 | // We only do this to modules owned by the the dependency tree roots. 38 | // This avoids breaking the resolution when jumping inside a vendor 39 | // with peer dep (otherwise jumping into react-dom would show resolution 40 | // errors on react). 41 | // 42 | const resolved = isVirtual(str) ? pnpApi.resolveVirtual(str) : str; 43 | if (resolved) { 44 | const locator = pnpApi.findPackageLocator(resolved); 45 | if (locator && dependencyTreeRoots.has(`${locator.name}@${locator.reference}`)) { 46 | str = resolved; 47 | } 48 | } 49 | 50 | str = normalize(str); 51 | 52 | if (str.match(/\.zip\//)) { 53 | switch (hostInfo) { 54 | // Absolute VSCode `Uri.fsPath`s need to start with a slash. 55 | // VSCode only adds it automatically for supported schemes, 56 | // so we have to do it manually for the `zip` scheme. 57 | // The path needs to start with a caret otherwise VSCode doesn't handle the protocol 58 | // 59 | // Ref: https://github.com/microsoft/vscode/issues/105014#issuecomment-686760910 60 | // 61 | case `vscode`: 62 | { 63 | str = `^zip:${str}`; 64 | } 65 | break; 66 | 67 | // To make "go to definition" work, 68 | // We have to resolve the actual file system path from virtual path 69 | // and convert scheme to supported by [vim-rzip](https://github.com/lbrayner/vim-rzip) 70 | case `coc-nvim`: 71 | { 72 | str = normalize(resolved).replace(/\.zip\//, `.zip::`); 73 | str = resolve(`zipfile:${str}`); 74 | } 75 | break; 76 | 77 | default: 78 | { 79 | str = `zip:${str}`; 80 | } 81 | break; 82 | } 83 | } 84 | } 85 | 86 | return str; 87 | } 88 | 89 | function fromEditorPath(str) { 90 | return process.platform === `win32` 91 | ? str.replace(/^\^?zip:\//, ``) 92 | : str.replace(/^\^?zip:/, ``); 93 | } 94 | 95 | // Force enable 'allowLocalPluginLoads' 96 | // TypeScript tries to resolve plugins using a path relative to itself 97 | // which doesn't work when using the global cache 98 | // https://github.com/microsoft/TypeScript/blob/1b57a0395e0bff191581c9606aab92832001de62/src/server/project.ts#L2238 99 | // VSCode doesn't want to enable 'allowLocalPluginLoads' due to security concerns but 100 | // TypeScript already does local loads and if this code is running the user trusts the workspace 101 | // https://github.com/microsoft/vscode/issues/45856 102 | const ConfiguredProject = tsserver.server.ConfiguredProject; 103 | const { 104 | enablePluginsWithOptions: originalEnablePluginsWithOptions, 105 | } = ConfiguredProject.prototype; 106 | ConfiguredProject.prototype.enablePluginsWithOptions = function () { 107 | this.projectService.allowLocalPluginLoads = true; 108 | return originalEnablePluginsWithOptions.apply(this, arguments); 109 | }; 110 | 111 | // And here is the point where we hijack the VSCode <-> TS communications 112 | // by adding ourselves in the middle. We locate everything that looks 113 | // like an absolute path of ours and normalize it. 114 | 115 | const Session = tsserver.server.Session; 116 | const { onMessage: originalOnMessage, send: originalSend } = Session.prototype; 117 | let hostInfo = `unknown`; 118 | 119 | return Object.assign(Session.prototype, { 120 | onMessage(/** @type {string} */ message) { 121 | const parsedMessage = JSON.parse(message); 122 | 123 | if ( 124 | parsedMessage != null && 125 | typeof parsedMessage === `object` && 126 | parsedMessage.arguments && 127 | typeof parsedMessage.arguments.hostInfo === `string` 128 | ) { 129 | hostInfo = parsedMessage.arguments.hostInfo; 130 | } 131 | 132 | return originalOnMessage.call( 133 | this, 134 | JSON.stringify(parsedMessage, (key, value) => { 135 | return typeof value === `string` ? fromEditorPath(value) : value; 136 | }) 137 | ); 138 | }, 139 | 140 | send(/** @type {any} */ msg) { 141 | return originalSend.call( 142 | this, 143 | JSON.parse( 144 | JSON.stringify(msg, (key, value) => { 145 | return typeof value === `string` ? toEditorPath(value) : value; 146 | }) 147 | ) 148 | ); 149 | }, 150 | }); 151 | }; 152 | 153 | if (existsSync(absPnpApiPath)) { 154 | if (!process.versions.pnp) { 155 | // Setup the environment to be able to require typescript/lib/tsserver.js 156 | require(absPnpApiPath).setup(); 157 | } 158 | } 159 | 160 | // Defer to the real typescript/lib/tsserver.js your application uses 161 | module.exports = moduleWrapper(absRequire(`typescript/lib/tsserver.js`)); 162 | -------------------------------------------------------------------------------- /.yarn/sdks/typescript/lib/typescript.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const { existsSync } = require(`fs`); 4 | const { createRequire, createRequireFromPath } = require(`module`); 5 | const { resolve } = require(`path`); 6 | 7 | const relPnpApiPath = '../../../../.pnp.cjs'; 8 | 9 | const absPnpApiPath = resolve(__dirname, relPnpApiPath); 10 | const absRequire = (createRequire || createRequireFromPath)(absPnpApiPath); 11 | 12 | if (existsSync(absPnpApiPath)) { 13 | if (!process.versions.pnp) { 14 | // Setup the environment to be able to require typescript/lib/typescript.js 15 | require(absPnpApiPath).setup(); 16 | } 17 | } 18 | 19 | // Defer to the real typescript/lib/typescript.js your application uses 20 | module.exports = absRequire(`typescript/lib/typescript.js`); 21 | -------------------------------------------------------------------------------- /.yarn/sdks/typescript/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "typescript", 3 | "version": "3.7.4-pnpify", 4 | "main": "./lib/typescript.js", 5 | "type": "commonjs" 6 | } 7 | -------------------------------------------------------------------------------- /.yarnrc.yml: -------------------------------------------------------------------------------- 1 | enableGlobalCache: true 2 | 3 | plugins: 4 | - path: .yarn/plugins/@yarnpkg/plugin-typescript.cjs 5 | spec: '@yarnpkg/plugin-typescript' 6 | 7 | yarnPath: .yarn/releases/yarn-3.0.0-rc.5.cjs 8 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. 4 | 5 | ### [2.6.2](https://github.com/merceyz/babel-plugin-optimize-clsx/compare/v2.6.1...v2.6.2) (2021-06-18) 6 | 7 | ### Bug Fixes 8 | 9 | - clone callee when wrapping referenced objects ([16eef18](https://github.com/merceyz/babel-plugin-optimize-clsx/commit/16eef18a0f181989920ad1e05fc0b7ba42b6bc54)), closes [#20](https://github.com/merceyz/babel-plugin-optimize-clsx/issues/20) 10 | 11 | ### [2.6.1](https://github.com/merceyz/babel-plugin-optimize-clsx/compare/v2.6.0...v2.6.1) (2020-06-20) 12 | 13 | ### Bug Fixes 14 | 15 | - remove peerDependency on babel/core ([744391d](https://github.com/merceyz/babel-plugin-optimize-clsx/commit/744391dabca970d16ce652869a4b740fbbb1a158)) 16 | - **collectCalls:** write CallExpressions to the cache dir ([17405f5](https://github.com/merceyz/babel-plugin-optimize-clsx/commit/17405f54b1ea03ccf2b8d6f556804b9dc04dfe12)) 17 | - **createlookup:** handle optional member expression ([1af84b2](https://github.com/merceyz/babel-plugin-optimize-clsx/commit/1af84b239fc3cbad12ccf822c33679d2a0ff3162)) 18 | 19 | ### Build System 20 | 21 | - disable sourcemap ([59b8b9e](https://github.com/merceyz/babel-plugin-optimize-clsx/commit/59b8b9ee976919d5a5bb0cfce4796bbdcd5562a6)) 22 | 23 | ## [2.6.0](https://github.com/merceyz/babel-plugin-optimize-clsx/compare/v2.5.0...v2.6.0) (2020-02-15) 24 | 25 | ### Features 26 | 27 | - **referenced-objects:** handle multiple references ([14a2e97](https://github.com/merceyz/babel-plugin-optimize-clsx/commit/14a2e97eb97c713b118c7cf3a2ee5017f5050f26)) 28 | - optimize referenced objects ([20575e9](https://github.com/merceyz/babel-plugin-optimize-clsx/commit/20575e9675e0e0381cee2d9205b7486e217f20ba)) 29 | - **optimize:** handle unnecessary "or" statements ([fcdb75e](https://github.com/merceyz/babel-plugin-optimize-clsx/commit/fcdb75e6b229d663d493bc00fa605bf1031be855)) 30 | - use scope to get expressions ([68becac](https://github.com/merceyz/babel-plugin-optimize-clsx/commit/68becacd5776a112a920f0ce32fd0accb2fb774d)) 31 | 32 | ### Bug Fixes 33 | 34 | - **optimize:** handle the operator changing ([3e92ddb](https://github.com/merceyz/babel-plugin-optimize-clsx/commit/3e92ddb2aa629dd7a6e018368e016cca2e663e44)) 35 | - **removecalls:** remove unsafe optimization ([b0deb61](https://github.com/merceyz/babel-plugin-optimize-clsx/commit/b0deb61a0da8ed63e561b4a1999ddedc4f2d9c3a)) 36 | 37 | ### Performance Improvements 38 | 39 | - **createlookup:** modify node instead of recreating it ([4800a6b](https://github.com/merceyz/babel-plugin-optimize-clsx/commit/4800a6b1cd6668e2450feea9812c78b4f37cb85e)) 40 | - **createlookup:** skip lookup for just one item ([f8d089d](https://github.com/merceyz/babel-plugin-optimize-clsx/commit/f8d089ddfbf67ac451fd720c1dfc3a41b7528f28)) 41 | 42 | ### Build System 43 | 44 | - include a esm version ([8a590ef](https://github.com/merceyz/babel-plugin-optimize-clsx/commit/8a590efc398a65a4f3ae3d7e88a3ba3ddd4ac1cb)) 45 | - remove terser ([dceb1e8](https://github.com/merceyz/babel-plugin-optimize-clsx/commit/dceb1e8af92fb83144b6772cce6fa32730a392bb)) 46 | 47 | ## [2.5.0](https://github.com/merceyz/babel-plugin-optimize-clsx/compare/v2.4.1...v2.5.0) (2019-10-07) 48 | 49 | ### Bug Fixes 50 | 51 | - **combinestrings:** set cooked value for templateelement ([5e25580](https://github.com/merceyz/babel-plugin-optimize-clsx/commit/5e25580)), closes [#15](https://github.com/merceyz/babel-plugin-optimize-clsx/issues/15) 52 | - **extract:** handle spread and method properties ([51c8ced](https://github.com/merceyz/babel-plugin-optimize-clsx/commit/51c8ced)) 53 | - **optimize:** handle ConditionalExpression in LogicalExpression ([fa05e72](https://github.com/merceyz/babel-plugin-optimize-clsx/commit/fa05e72)) 54 | 55 | ### Build System 56 | 57 | - **deps:** upgrade dependencies ([cd02789](https://github.com/merceyz/babel-plugin-optimize-clsx/commit/cd02789)) 58 | - improve terser output ([11ca28b](https://github.com/merceyz/babel-plugin-optimize-clsx/commit/11ca28b)) 59 | - remove rollup-plugin-commonjs ([b994f88](https://github.com/merceyz/babel-plugin-optimize-clsx/commit/b994f88)) 60 | 61 | ### Features 62 | 63 | - **collectcalls:** include the location of the code ([1ab1eab](https://github.com/merceyz/babel-plugin-optimize-clsx/commit/1ab1eab)) 64 | 65 | ### [2.4.1](https://github.com/merceyz/babel-plugin-optimize-clsx/compare/v2.4.0...v2.4.1) (2019-08-18) 66 | 67 | ### Build System 68 | 69 | - **babel:** target node 8 ([a888c69](https://github.com/merceyz/babel-plugin-optimize-clsx/commit/a888c69)) 70 | 71 | ## [2.4.0](https://github.com/merceyz/babel-plugin-optimize-clsx/compare/v2.3.0...v2.4.0) (2019-08-18) 72 | 73 | ### Bug Fixes 74 | 75 | - **helper:** compare literals correctly ([d728a45](https://github.com/merceyz/babel-plugin-optimize-clsx/commit/d728a45)) 76 | - **stripliterals:** handle all falsy values ([721af56](https://github.com/merceyz/babel-plugin-optimize-clsx/commit/721af56)) 77 | - **stripliterals:** handle empty template literals ([1e9295c](https://github.com/merceyz/babel-plugin-optimize-clsx/commit/1e9295c)) 78 | - **stripliterals:** remove more unnecessary truthy values ([98d2a9d](https://github.com/merceyz/babel-plugin-optimize-clsx/commit/98d2a9d)) 79 | 80 | ### Build System 81 | 82 | - **deps:** add babel-plugin-lodash ([cd37d10](https://github.com/merceyz/babel-plugin-optimize-clsx/commit/cd37d10)) 83 | - **deps-dev:** bump husky from 3.0.0 to 3.0.2 ([#3](https://github.com/merceyz/babel-plugin-optimize-clsx/issues/3)) ([de0ff50](https://github.com/merceyz/babel-plugin-optimize-clsx/commit/de0ff50)) 84 | - **deps-dev:** bump rollup from 1.17.0 to 1.19.4 ([#12](https://github.com/merceyz/babel-plugin-optimize-clsx/issues/12)) ([0cb7b7e](https://github.com/merceyz/babel-plugin-optimize-clsx/commit/0cb7b7e)) 85 | - **deps-dev:** bump standard-version from 6.0.1 to 7.0.0 ([#6](https://github.com/merceyz/babel-plugin-optimize-clsx/issues/6)) ([e699d1d](https://github.com/merceyz/babel-plugin-optimize-clsx/commit/e699d1d)) 86 | 87 | ### Features 88 | 89 | - flatten and remove arrays ([5ca8e7a](https://github.com/merceyz/babel-plugin-optimize-clsx/commit/5ca8e7a)) 90 | - optimize expressions ([236071e](https://github.com/merceyz/babel-plugin-optimize-clsx/commit/236071e)) 91 | - support namespace imports ([d6c5597](https://github.com/merceyz/babel-plugin-optimize-clsx/commit/d6c5597)) 92 | 93 | ### Performance Improvements 94 | 95 | - **helper:** avoid unnecessary compares ([a5f1312](https://github.com/merceyz/babel-plugin-optimize-clsx/commit/a5f1312)) 96 | - **helper:** compare type of nodes directly ([41ff13f](https://github.com/merceyz/babel-plugin-optimize-clsx/commit/41ff13f)) 97 | - **helper:** use switch statement over if else chain ([b2874ad](https://github.com/merceyz/babel-plugin-optimize-clsx/commit/b2874ad)) 98 | - track removed calls to avoid crawling the AST ([28188a8](https://github.com/merceyz/babel-plugin-optimize-clsx/commit/28188a8)) 99 | - **proptypes:** skip traversing child nodes ([ec883af](https://github.com/merceyz/babel-plugin-optimize-clsx/commit/ec883af)) 100 | - combine visitors ([#8](https://github.com/merceyz/babel-plugin-optimize-clsx/issues/8)) ([d6a9a62](https://github.com/merceyz/babel-plugin-optimize-clsx/commit/d6a9a62)) 101 | 102 | ## [2.3.0](https://github.com/merceyz/babel-plugin-optimize-clsx/compare/v2.2.0...v2.3.0) (2019-07-18) 103 | 104 | ### Bug Fixes 105 | 106 | - handle template and string literals correctly ([f90ddaf](https://github.com/merceyz/babel-plugin-optimize-clsx/commit/f90ddaf)) 107 | 108 | ### Build System 109 | 110 | - rimraf dist before each build ([d3f9f1f](https://github.com/merceyz/babel-plugin-optimize-clsx/commit/d3f9f1f)) 111 | 112 | ### Features 113 | 114 | - **removecalls:** handle arrays ([524fa51](https://github.com/merceyz/babel-plugin-optimize-clsx/commit/524fa51)) 115 | - **stripliterals:** handle conditionalExpressions ([18e35ba](https://github.com/merceyz/babel-plugin-optimize-clsx/commit/18e35ba)) 116 | - collect calls before optimizing ([c2537e5](https://github.com/merceyz/babel-plugin-optimize-clsx/commit/c2537e5)) 117 | - **combinestrings:** support template literals ([3d63596](https://github.com/merceyz/babel-plugin-optimize-clsx/commit/3d63596)) 118 | - **removecalls:** handle template literal ([7ccf15f](https://github.com/merceyz/babel-plugin-optimize-clsx/commit/7ccf15f)) 119 | - **stripliterals:** remove empty strings ([8672550](https://github.com/merceyz/babel-plugin-optimize-clsx/commit/8672550)) 120 | 121 | ### Tests 122 | 123 | - **stripliterals:** improve coverage ([8b5c717](https://github.com/merceyz/babel-plugin-optimize-clsx/commit/8b5c717)) 124 | 125 | ## [2.2.0](https://github.com/merceyz/babel-plugin-optimize-clsx/compare/v2.1.0...v2.2.0) (2019-05-31) 126 | 127 | ### Features 128 | 129 | - **createlookup:** handle multiple checks ([950dd82](https://github.com/merceyz/babel-plugin-optimize-clsx/commit/950dd82)) 130 | - create object key lookups ([0630c91](https://github.com/merceyz/babel-plugin-optimize-clsx/commit/0630c91)) 131 | - **combinestrings:** handle strings in arrays ([62c4ce9](https://github.com/merceyz/babel-plugin-optimize-clsx/commit/62c4ce9)) 132 | - **removecalls:** handle nested conditional expressions ([2110589](https://github.com/merceyz/babel-plugin-optimize-clsx/commit/2110589)) 133 | - **removecalls:** handle single logical expression ([0c453b3](https://github.com/merceyz/babel-plugin-optimize-clsx/commit/0c453b3)) 134 | - **removecalls:** handle string and logical expression as argument ([2b59da8](https://github.com/merceyz/babel-plugin-optimize-clsx/commit/2b59da8)) 135 | 136 | ### Tests 137 | 138 | - remove retainLines ([07dcd92](https://github.com/merceyz/babel-plugin-optimize-clsx/commit/07dcd92)) 139 | - test:dev should ignore output ([eea4081](https://github.com/merceyz/babel-plugin-optimize-clsx/commit/eea4081)) 140 | 141 | ## [2.1.0](https://github.com/merceyz/babel-plugin-optimize-clsx/compare/v2.0.0...v2.1.0) (2019-05-27) 142 | 143 | ### Bug Fixes 144 | 145 | - **proptypes:** handle isRequired and default value ([aa244e3](https://github.com/merceyz/babel-plugin-optimize-clsx/commit/aa244e3)) 146 | 147 | ### Build System 148 | 149 | - use rollup, babel, and terser ([29e730f](https://github.com/merceyz/babel-plugin-optimize-clsx/commit/29e730f)) 150 | 151 | ### Features 152 | 153 | - combine string literals ([0bc7671](https://github.com/merceyz/babel-plugin-optimize-clsx/commit/0bc7671)) 154 | - remove unnecessary function calls ([3ea3d85](https://github.com/merceyz/babel-plugin-optimize-clsx/commit/3ea3d85)) 155 | - remove unused imports ([8d2ae5f](https://github.com/merceyz/babel-plugin-optimize-clsx/commit/8d2ae5f)) 156 | - strip literals ([f206424](https://github.com/merceyz/babel-plugin-optimize-clsx/commit/f206424)) 157 | - use proptypes to minimize expressions ([300b006](https://github.com/merceyz/babel-plugin-optimize-clsx/commit/300b006)) 158 | 159 | # [2.0.0](https://github.com/merceyz/babel-plugin-optimize-clsx/compare/v1.1.2...v2.0.0) (2019-05-21) 160 | 161 | ### Bug Fixes 162 | 163 | - **combine:** don't create an arrayExpression with just one item ([699d6f5](https://github.com/merceyz/babel-plugin-optimize-clsx/commit/699d6f5)) 164 | - **extract:** transform static keys into string literals ([#2](https://github.com/merceyz/babel-plugin-optimize-clsx/issues/2)) ([213ff5b](https://github.com/merceyz/babel-plugin-optimize-clsx/commit/213ff5b)) 165 | - **helper:** use count instead of total length ([5ffec80](https://github.com/merceyz/babel-plugin-optimize-clsx/commit/5ffec80)) 166 | 167 | ### Features 168 | 169 | - add support for creating conditional expressions ([dab4b1a](https://github.com/merceyz/babel-plugin-optimize-clsx/commit/dab4b1a)) 170 | - get function name from imports ([#1](https://github.com/merceyz/babel-plugin-optimize-clsx/issues/1)) ([0c18712](https://github.com/merceyz/babel-plugin-optimize-clsx/commit/0c18712)) 171 | 172 | ### Performance Improvements 173 | 174 | - **combine:** skip if argument length is less than 2 ([2bc0714](https://github.com/merceyz/babel-plugin-optimize-clsx/commit/2bc0714)) 175 | 176 | ### BREAKING CHANGES 177 | 178 | - No longer matches on all `clsx` and `classnames` function calls, instead looks for imports(and require) and uses the names specified there. If the file doesn't contain imports nothing will be done, to get the old behaviour back you can set the `functionNames` option to `['clsx', 'classnames']` 179 | 180 | ## [1.1.2](https://github.com/merceyz/babel-plugin-optimize-clsx/compare/v1.1.1...v1.1.2) (2019-05-07) 181 | 182 | ### Bug Fixes 183 | 184 | - **combine:** don't compare node against itself ([9b990f6](https://github.com/merceyz/babel-plugin-optimize-clsx/commit/9b990f6)) 185 | 186 | ## [1.1.1](https://github.com/merceyz/babel-plugin-optimize-clsx/compare/v1.1.0...v1.1.1) (2019-05-07) 187 | 188 | ### Bug Fixes 189 | 190 | - **combine:** don't assume jagged array has dimension [x][1] ([3b308a1](https://github.com/merceyz/babel-plugin-optimize-clsx/commit/3b308a1)) 191 | - **combine:** skip if no node appears more than once ([f888818](https://github.com/merceyz/babel-plugin-optimize-clsx/commit/f888818)) 192 | - use lodash to compare nodes ([b10733e](https://github.com/merceyz/babel-plugin-optimize-clsx/commit/b10733e)) 193 | 194 | # [1.1.0](https://github.com/merceyz/babel-plugin-optimize-clsx/compare/v1.0.1...v1.1.0) (2019-05-05) 195 | 196 | ### Bug Fixes 197 | 198 | - babel/types should not be a devDependency ([36107ce](https://github.com/merceyz/babel-plugin-optimize-clsx/commit/36107ce)) 199 | 200 | ### Features 201 | 202 | - add support for classNames ([f8de735](https://github.com/merceyz/babel-plugin-optimize-clsx/commit/f8de735)) 203 | - add support for combining arguments ([5708852](https://github.com/merceyz/babel-plugin-optimize-clsx/commit/5708852)) 204 | - handle exceptions correctly ([7a5c96c](https://github.com/merceyz/babel-plugin-optimize-clsx/commit/7a5c96c)) 205 | 206 | ## [1.0.1](https://github.com/merceyz/babel-plugin-optimize-clsx/compare/v1.0.0...v1.0.1) (2019-05-04) 207 | 208 | # 1.0.0 (2019-05-04) 209 | 210 | ### Features 211 | 212 | - add support for optimizing objects ([299d0b9](https://github.com/merceyz/babel-plugin-optimize-clsx/commit/299d0b9)) 213 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Kristoffer K. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # babel-plugin-optimize-clsx 2 | 3 | Babel plugin to optimize the use of [clsx](https://github.com/lukeed/clsx), [classnames](https://github.com/JedWatson/classnames), and all libraries with a compatible API 4 | 5 | ## Install 6 | 7 | ``` 8 | yarn add babel-plugin-optimize-clsx --dev 9 | or 10 | npm install babel-plugin-optimize-clsx --save-dev 11 | ``` 12 | 13 | ## Requirements 14 | 15 | | Name | Version | 16 | | ----- | ------- | 17 | | Babel | ^7.0.0 | 18 | | Node | >=8 | 19 | 20 | ## Examples 21 | 22 | ### Object properties 23 | 24 | ```javascript 25 | clsx({ 26 | [classes.disabled]: disabled, 27 | [classes.focusVisible]: focusVisible && !disabled, 28 | }); 29 | 30 | // Transforms to 31 | 32 | clsx(disabled && classes.disabled, focusVisible && !disabled && classes.focusVisible); 33 | ``` 34 | 35 | ### Conditional expressions 36 | 37 | ```javascript 38 | clsx({ 39 | [classes.disabled]: disabled, 40 | [classes.focusVisible]: focusVisible && !disabled, 41 | }); 42 | 43 | // Transforms to 44 | 45 | clsx(disabled ? classes.disabled : focusVisible && classes.focusVisible); 46 | ``` 47 | 48 | ### Combine arguments 49 | 50 | ```javascript 51 | clsx({ 52 | [classes.focusVisible]: this.state.focusVisible, 53 | [focusVisibleClassName]: this.state.focusVisible, 54 | }); 55 | 56 | // Transforms to 57 | 58 | clsx(this.state.focusVisible && [classes.focusVisible, focusVisibleClassName]); 59 | ``` 60 | 61 | ### PropTypes 62 | 63 | ```javascript 64 | function foo(props) { 65 | const { position: p } = props; 66 | const x = clsx({ 67 | [classes.x]: p === 'top', 68 | [classes.y]: p === 'bottom', 69 | }); 70 | } 71 | 72 | foo.propTypes = { 73 | position: PropTypes.oneOf(['top', 'bottom']), 74 | }; 75 | 76 | // Transforms to 77 | 78 | function foo(props) { 79 | const { position: p } = props; 80 | const x = clsx(p === 'top' ? classes.x : classes.y); 81 | } 82 | 83 | foo.propTypes = { 84 | position: PropTypes.oneOf(['top', 'bottom']), 85 | }; 86 | ``` 87 | 88 | ### String literals 89 | 90 | ```javascript 91 | const x = clsx({ 92 | btn: true, 93 | 'col-md-1': true, 94 | ['btn-primary']: true, 95 | }); 96 | 97 | // Transforms to 98 | 99 | const x = 'btn col-md-1 btn-primary'; 100 | ``` 101 | 102 | ### Unnecessary function calls 103 | 104 | ```javascript 105 | const x = clsx({ 106 | btn: true, 107 | 'btn-foo': isDisabled, 108 | 'btn-bar': !isDisabled, 109 | }); 110 | 111 | // Transforms to 112 | 113 | const x = 'btn ' + (isDisabled ? 'btn-foo' : 'btn-bar'); 114 | ``` 115 | 116 | ## Benchmarks 117 | 118 | Benchmarks can be found in the [`benchmark`](/benchmark) directory 119 | 120 | ## Options 121 | 122 | | Name | Type | Default value | 123 | | ----------- | ---------- | ------------------------ | 124 | | `libraries` | `string[]` | `['clsx', 'classnames']` | 125 | 126 | By default the plugin looks for `import` and `require` statements for `clsx` and `classnames` and uses that to know which function calls to optimize. If you're using another library with a compatible API you can overwrite that with this option. 127 | 128 | ```json 129 | { 130 | "plugins": [ 131 | [ 132 | "babel-plugin-optimize-clsx", 133 | { 134 | "libraries": ["clsx", "classnames", "my-custom-library"] 135 | } 136 | ] 137 | ] 138 | } 139 | ``` 140 | 141 | --- 142 | 143 | | Name | Type | Default value | 144 | | --------------- | ---------- | ------------- | 145 | | `functionNames` | `string[]` | `[]` | 146 | 147 | If you want the plugin to match on all functions with a specific name, no matter where it comes from you can specify them using this option. An example for this is if you have `clsx` as a global function and thus don't import it. 148 | 149 | ```json 150 | { 151 | "plugins": [ 152 | [ 153 | "babel-plugin-optimize-clsx", 154 | { 155 | "functionNames": ["myClsxImplementation"] 156 | } 157 | ] 158 | ] 159 | } 160 | ``` 161 | 162 | --- 163 | 164 | | Name | Type | Default value | 165 | | ------------------------ | --------- | ------------- | 166 | | `removeUnnecessaryCalls` | `boolean` | `true` | 167 | 168 | By default the plugin will remove unnecessary function calls and if all calls are removed, imports. If you need to keep them, you can set this option to false. 169 | 170 | Example of some unnecessary calls 171 | 172 | ```javascript 173 | import clsx from 'clsx'; 174 | const x = clsx('foo', 'bar'); 175 | const y = clsx({ classA: foo === 'a', classB: foo !== 'a' }); 176 | const z = clsx({ 177 | classA: foo === 'a', 178 | classB: foo !== 'a', 179 | classC: bar === 'c', 180 | classD: bar !== 'c', 181 | }); 182 | 183 | // Transforms to 184 | 185 | const x = 'foo bar'; 186 | const y = foo === 'a' ? 'classA' : 'classB'; 187 | const z = (foo === 'a' ? 'classA ' : 'classB ') + (bar === 'c' ? 'classC' : 'classD'); 188 | ``` 189 | 190 | --- 191 | 192 | | Name | Type | Default value | 193 | | -------------- | --------- | ------------- | 194 | | `collectCalls` | `boolean` | `false` | 195 | 196 | Writes all function calls, before they are optimized, to a file. Used to help test the plugin on repositories. 197 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: ['babel-plugin-lodash', '@babel/plugin-transform-typescript'], 3 | presets: [ 4 | [ 5 | '@babel/preset-env', 6 | { 7 | targets: { 8 | node: 8, 9 | }, 10 | }, 11 | ], 12 | ], 13 | }; 14 | -------------------------------------------------------------------------------- /benchmark/README.md: -------------------------------------------------------------------------------- 1 | # Benchmarks 2 | 3 | To run the benchmarks yourself use `yarn benchmark` 4 | 5 | Case one and two are extracted from https://github.com/mui-org/material-ui 6 | 7 | | | | 8 | | ---------- | ------ | 9 | | Node | 12.9.0 | 10 | | clsx | 1.0.4 | 11 | | classnames | 2.2.6 | 12 | 13 | # Results 14 | 15 | `N/A` means the function call was optimized away and no library was used 16 | 17 | ``` 18 | # case_1.js - Extract properties, combine arguments, and conditional expression: 19 | before - classnames x 455,076 ops/sec ±0.29% (89 runs sampled) 20 | before - clsx x 515,270 ops/sec ±0.24% (93 runs sampled) 21 | after - classnames x 2,147,313 ops/sec ±0.29% (94 runs sampled) 22 | after - clsx x 7,170,564 ops/sec ±0.66% (89 runs sampled) 23 | 24 | # case_2.js - Extract properties and combine arguments: 25 | before - classnames x 270,775 ops/sec ±0.48% (93 runs sampled) 26 | before - clsx x 288,193 ops/sec ±0.25% (93 runs sampled) 27 | after - classnames x 1,644,390 ops/sec ±0.39% (94 runs sampled) 28 | after - clsx x 4,097,308 ops/sec ±0.63% (93 runs sampled) 29 | 30 | # case_3.js - Object with string literals: 31 | before - classnames x 3,579,582 ops/sec ±0.38% (91 runs sampled) 32 | before - clsx x 7,029,547 ops/sec ±0.58% (89 runs sampled) 33 | after - N/A x 851,510,031 ops/sec ±0.26% (92 runs sampled) 34 | 35 | # case_4.js - Unnecessary function calls: 36 | before - classnames x 4,771,703 ops/sec ±0.40% (92 runs sampled) 37 | before - clsx x 8,444,230 ops/sec ±0.32% (89 runs sampled) 38 | after - N/A x 842,537,779 ops/sec ±0.26% (89 runs sampled) 39 | ``` 40 | -------------------------------------------------------------------------------- /benchmark/cases/case_1.js: -------------------------------------------------------------------------------- 1 | const getClassName = require('../getClassName'); 2 | 3 | const classes = { 4 | bar: getClassName(), 5 | barColorPrimary: getClassName(), 6 | colorPrimary: getClassName(), 7 | barColorSecondary: getClassName(), 8 | colorSecondary: getClassName(), 9 | bar2Indeterminate: getClassName(), 10 | bar2Buffer: getClassName(), 11 | }; 12 | 13 | const variant = 'buffer'; 14 | const color = 'secondary'; 15 | 16 | module.exports = { 17 | title: 'Extract properties, combine arguments, and conditional expression', 18 | before(clsx) { 19 | return clsx(classes.bar, { 20 | [classes.barColorPrimary]: color === 'primary' && variant !== 'buffer', 21 | [classes.colorPrimary]: color === 'primary' && variant === 'buffer', 22 | [classes.barColorSecondary]: color === 'secondary' && variant !== 'buffer', 23 | [classes.colorSecondary]: color === 'secondary' && variant === 'buffer', 24 | [classes.bar2Indeterminate]: variant === 'indeterminate' || variant === 'query', 25 | [classes.bar2Buffer]: variant === 'buffer', 26 | }); 27 | }, 28 | after(clsx) { 29 | return clsx( 30 | classes.bar, 31 | (variant === 'indeterminate' || variant === 'query') && classes.bar2Indeterminate, 32 | variant === 'buffer' 33 | ? [ 34 | classes.bar2Buffer, 35 | { 36 | primary: classes.colorPrimary, 37 | secondary: classes.colorSecondary, 38 | }[color], 39 | ] 40 | : { 41 | primary: classes.barColorPrimary, 42 | secondary: classes.barColorSecondary, 43 | }[color] 44 | ); 45 | }, 46 | }; 47 | -------------------------------------------------------------------------------- /benchmark/cases/case_2.js: -------------------------------------------------------------------------------- 1 | const getClassName = require('../getClassName'); 2 | 3 | const classes = { 4 | root: getClassName(), 5 | text: getClassName(), 6 | textPrimary: getClassName(), 7 | textSecondary: getClassName(), 8 | contained: getClassName(), 9 | containedPrimary: getClassName(), 10 | containedSecondary: getClassName(), 11 | outlined: getClassName(), 12 | outlinedPrimary: getClassName(), 13 | outlinedSecondary: getClassName(), 14 | disabled: getClassName(), 15 | fullWidth: getClassName(), 16 | colorInherit: getClassName(), 17 | }; 18 | 19 | const variant = 'buffer'; 20 | const color = 'secondary'; 21 | const classNameProp = getClassName(); 22 | const text = true; 23 | const contained = false; 24 | const fullWidth = true; 25 | const disabled = false; 26 | 27 | module.exports = { 28 | title: 'Extract properties and combine arguments', 29 | before(clsx) { 30 | return clsx( 31 | classes.root, 32 | { 33 | [classes.text]: text, 34 | [classes.textPrimary]: text && color === 'primary', 35 | [classes.textSecondary]: text && color === 'secondary', 36 | [classes.contained]: contained, 37 | [classes.containedPrimary]: contained && color === 'primary', 38 | [classes.containedSecondary]: contained && color === 'secondary', 39 | [classes.outlined]: variant === 'outlined', 40 | [classes.outlinedPrimary]: variant === 'outlined' && color === 'primary', 41 | [classes.outlinedSecondary]: variant === 'outlined' && color === 'secondary', 42 | [classes.disabled]: disabled, 43 | [classes.fullWidth]: fullWidth, 44 | [classes.colorInherit]: color === 'inherit', 45 | }, 46 | classNameProp 47 | ); 48 | }, 49 | after(clsx) { 50 | return clsx( 51 | classes.root, 52 | classNameProp, 53 | text && [ 54 | classes.text, 55 | { 56 | primary: classes.textPrimary, 57 | secondary: classes.textSecondary, 58 | }[color], 59 | ], 60 | contained && [ 61 | classes.contained, 62 | { 63 | primary: classes.containedPrimary, 64 | secondary: classes.containedSecondary, 65 | }[color], 66 | ], 67 | disabled && classes.disabled, 68 | fullWidth && classes.fullWidth, 69 | variant === 'outlined' && [ 70 | classes.outlined, 71 | { 72 | primary: classes.outlinedPrimary, 73 | secondary: classes.outlinedSecondary, 74 | }[color], 75 | ], 76 | color === 'inherit' && classes.colorInherit 77 | ); 78 | }, 79 | }; 80 | -------------------------------------------------------------------------------- /benchmark/cases/case_3.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | title: 'Object with string literals', 3 | before(clsx) { 4 | return clsx({ 5 | btn: true, 6 | 'col-md-1': true, 7 | ['btn-primary']: true, 8 | }); 9 | }, 10 | after() { 11 | return 'btn col-md-1 btn-primary'; 12 | }, 13 | }; 14 | -------------------------------------------------------------------------------- /benchmark/cases/case_4.js: -------------------------------------------------------------------------------- 1 | const isDisabled = true; 2 | 3 | module.exports = { 4 | title: 'Unnecessary function calls', 5 | before(clsx) { 6 | return clsx({ 7 | btn: true, 8 | 'btn-foo': isDisabled, 9 | 'btn-bar': !isDisabled, 10 | }); 11 | }, 12 | after() { 13 | return 'btn ' + (isDisabled ? 'btn-foo' : 'btn-bar'); 14 | }, 15 | }; 16 | -------------------------------------------------------------------------------- /benchmark/getClassName.js: -------------------------------------------------------------------------------- 1 | // Simulates minimized className values from jss 2 | 3 | let count = 0; 4 | module.exports = () => { 5 | return `jss${String(count++)}`; 6 | }; 7 | -------------------------------------------------------------------------------- /benchmark/index.js: -------------------------------------------------------------------------------- 1 | const Benchmark = require('benchmark'); 2 | const path = require('path'); 3 | const fs = require('fs'); 4 | const clsx = require('clsx'); 5 | const classnames = require('classnames'); 6 | 7 | function bench(name, { title, before, after }) { 8 | console.log(`\n# ${name} - ${title}:`); 9 | 10 | const suite = new Benchmark.Suite(); 11 | 12 | suite.add('before - classnames', () => before(classnames)); 13 | suite.add('before - clsx ', () => before(clsx)); 14 | 15 | // If the argument length is 0 then the function call has been optimized away 16 | if (after.length === 0) { 17 | suite.add('after - N/A ', after); 18 | } else { 19 | suite.add('after - classnames', () => after(classnames)); 20 | suite.add('after - clsx ', () => after(clsx)); 21 | } 22 | 23 | suite.on('cycle', (e) => console.log(' ' + e.target)); 24 | suite.run(); 25 | } 26 | 27 | const dir = path.join(__dirname, 'cases'); 28 | fs.readdir(dir, (err, files) => { 29 | if (err) { 30 | throw err; 31 | } 32 | 33 | files.forEach((f) => { 34 | const testCase = require(path.join(dir, f)); 35 | 36 | bench(f, testCase); 37 | }); 38 | console.log(); 39 | }); 40 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | const { jsWithTs } = require('ts-jest/presets'); 2 | const path = require('path'); 3 | 4 | module.exports = { 5 | clearMocks: true, 6 | testEnvironment: 'node', 7 | testRegex: `test/index.js`, 8 | transform: jsWithTs.transform, 9 | globals: { 10 | 'ts-jest': { 11 | packageJson: path.join(__dirname, 'package.json'), 12 | }, 13 | }, 14 | }; 15 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "babel-plugin-optimize-clsx", 3 | "version": "2.6.2", 4 | "main": "./dist/index.js", 5 | "module": "./dist/index.mjs", 6 | "files": [ 7 | "dist" 8 | ], 9 | "repository": "https://github.com/merceyz/babel-plugin-optimize-clsx.git", 10 | "author": "merceyz ", 11 | "license": "MIT", 12 | "keywords": [ 13 | "babel", 14 | "clsx", 15 | "classnames", 16 | "optimize", 17 | "minimize" 18 | ], 19 | "description": "Babel plugin to optimize the use of clsx, classnames, and all libraries with a compatible API", 20 | "scripts": { 21 | "test": "jest", 22 | "test:dev": "DEV_MODE=true jest", 23 | "test:build": "TEST_BUILD=true jest", 24 | "build": "rm -rf dist && rollup -c", 25 | "release": "run build && run test:build && standard-version", 26 | "prepack": "run build", 27 | "benchmark": "node benchmark/index.js" 28 | }, 29 | "devDependencies": { 30 | "@babel/core": "^7.8.4", 31 | "@babel/plugin-transform-modules-commonjs": "^7.8.3", 32 | "@babel/plugin-transform-template-literals": "^7.8.3", 33 | "@babel/plugin-transform-typescript": "^7.8.3", 34 | "@babel/preset-env": "^7.8.4", 35 | "@types/babel__core": "^7.1.5", 36 | "@types/babel__generator": "^7.6.1", 37 | "@types/babel__template": "^7.0.2", 38 | "@types/find-cache-dir": "^2.0.0", 39 | "@types/lodash": "^4.14.149", 40 | "@types/node": "^12.12.17", 41 | "@types/object-hash": "^1.3.0", 42 | "babel-plugin-lodash": "^3.3.4", 43 | "babel-plugin-tester": "^7.0.3", 44 | "benchmark": "^2.1.4", 45 | "classnames": "^2.2.6", 46 | "clsx": "^1.0.4", 47 | "husky": "^4.2.3", 48 | "jest": "^24.9.0", 49 | "prettier": "^2.0.1", 50 | "pretty-quick": "^2.0.1", 51 | "rollup": "^1.23.1", 52 | "rollup-plugin-babel": "^4.3.3", 53 | "standard-version": "^8.0.0", 54 | "ts-jest": "^24.2.0", 55 | "typescript": "^3.7.4" 56 | }, 57 | "dependencies": { 58 | "@babel/generator": "^7.6.2", 59 | "@babel/template": "^7.6.0", 60 | "@babel/types": "^7.6.1", 61 | "find-cache-dir": "^3.2.0", 62 | "lodash": "^4.17.15", 63 | "object-hash": "^2.0.3" 64 | }, 65 | "dependenciesMeta": { 66 | "core-js": { 67 | "built": false 68 | } 69 | }, 70 | "husky": { 71 | "hooks": { 72 | "pre-commit": "yarn pretty-quick --staged && yarn test" 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import babel from 'rollup-plugin-babel'; 2 | import { DEFAULT_EXTENSIONS } from '@babel/core'; 3 | import { existsSync } from 'fs'; 4 | import path from 'path'; 5 | 6 | export default { 7 | input: './src/index.ts', 8 | output: [ 9 | { 10 | file: './dist/index.js', 11 | format: 'cjs', 12 | }, 13 | { 14 | file: './dist/index.mjs', 15 | format: 'es', 16 | }, 17 | ], 18 | plugins: [ 19 | { 20 | name: 'local-resolve', 21 | resolveId(importee, importer) { 22 | if (!importer || path.isAbsolute(importee) || !importee.startsWith('.')) { 23 | return null; 24 | } 25 | 26 | const fileName = path.basename(importee, '.js'); 27 | const dirName = path.dirname(importee); 28 | const filePath = path.join(path.dirname(importer), dirName, `${fileName}.ts`); 29 | 30 | return existsSync(filePath) ? filePath : null; 31 | }, 32 | }, 33 | babel({ 34 | exclude: 'node_modules/**', 35 | extensions: [...DEFAULT_EXTENSIONS, '.ts'], 36 | }), 37 | ], 38 | }; 39 | -------------------------------------------------------------------------------- /src/findExpressions.ts: -------------------------------------------------------------------------------- 1 | import * as babel from '@babel/core'; 2 | import * as t from '@babel/types'; 3 | import { PluginOptions } from './options'; 4 | 5 | type ImportMap = { 6 | source: babel.NodePath; 7 | referenceCount: number; 8 | expressions: Array>; 9 | }; 10 | 11 | type FindFunctionsResult = Array | ImportMap>; 12 | 13 | const getFunctions: babel.Visitor<{ options: PluginOptions; functions: FindFunctionsResult }> = { 14 | CallExpression(path) { 15 | const { callee } = path.node; 16 | if (t.isIdentifier(callee) && this.options.functionNames.includes(callee.name)) { 17 | path.setData('is_optimizable', true); 18 | this.functions.push(path); 19 | } 20 | }, 21 | }; 22 | 23 | export function isImportMap(node: any): node is ImportMap { 24 | return node.referenceCount !== undefined; 25 | } 26 | 27 | export function findExpressions(program: babel.NodePath, options: PluginOptions) { 28 | const result: FindFunctionsResult = []; 29 | 30 | if (options.functionNames.length) { 31 | program.traverse(getFunctions, { options, functions: result }); 32 | } 33 | 34 | if (!options.libraries.length) { 35 | return result; 36 | } 37 | 38 | const addToState = (path: babel.NodePath, name: string) => { 39 | const binding = path.scope.getBinding(name); 40 | if (!binding) { 41 | return; 42 | } 43 | 44 | const expressions: babel.NodePath[] = []; 45 | 46 | const refPaths = binding.referencePaths; 47 | for (const ref of refPaths) { 48 | const call = ref.parentPath; 49 | if (call.isCallExpression()) { 50 | call.setData('is_optimizable', true); 51 | expressions.push(call); 52 | } 53 | } 54 | 55 | if (expressions.length) { 56 | result.push({ 57 | source: path, 58 | referenceCount: binding.references, 59 | expressions, 60 | }); 61 | } 62 | }; 63 | 64 | const body = program.get('body'); 65 | for (const statement of body) { 66 | // import x from 'y'; 67 | // import * as x from 'y'; 68 | if ( 69 | statement.isImportDeclaration() && 70 | options.libraries.includes(statement.node.source.value) && 71 | statement.node.specifiers.length === 1 && 72 | (t.isImportDefaultSpecifier(statement.node.specifiers[0]) || 73 | t.isImportNamespaceSpecifier(statement.node.specifiers[0])) 74 | ) { 75 | addToState(statement, statement.node.specifiers[0].local.name); 76 | } 77 | // const x = require('y'); 78 | else if (statement.isVariableDeclaration()) { 79 | statement.get('declarations').forEach((decPath) => { 80 | const dec = decPath.node; 81 | if ( 82 | t.isIdentifier(dec.id) && 83 | t.isCallExpression(dec.init) && 84 | t.isIdentifier(dec.init.callee, { name: 'require' }) && 85 | dec.init.arguments.length === 1 && 86 | t.isStringLiteral(dec.init.arguments[0]) && 87 | options.libraries.includes(dec.init.arguments[0].value) 88 | ) { 89 | addToState(decPath, dec.id.name); 90 | } 91 | }); 92 | } 93 | } 94 | 95 | return result; 96 | } 97 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import * as babel from '@babel/core'; 2 | import * as t from '@babel/types'; 3 | 4 | import { getOptions, PluginOptions } from './options'; 5 | import { findExpressions, isImportMap } from './findExpressions'; 6 | 7 | import { collectCalls } from './visitors/collectCalls'; 8 | import { extractObjectProperties } from './visitors/extractObjectProperties'; 9 | import { flattenArrays } from './visitors/flattenArrays'; 10 | import { propTypes } from './visitors/propTypes'; 11 | import { optimizeExpressions } from './visitors/optimizeExpressions'; 12 | import { stripLiterals } from './visitors/stripLiterals'; 13 | import { combineArguments } from './visitors/combineArguments'; 14 | import { combineStringLiterals } from './visitors/combineStringLiterals'; 15 | import { createConditionalExpression } from './visitors/createConditionalExpression'; 16 | import { removeUnnecessaryCalls } from './visitors/removeUnnecessaryCalls'; 17 | import { createObjectKeyLookups } from './visitors/createObjectKeyLookups'; 18 | import { referencedObjects } from './visitors/referencedObjects'; 19 | 20 | const visitors = [ 21 | collectCalls, 22 | flattenArrays, 23 | extractObjectProperties, 24 | propTypes, 25 | optimizeExpressions, 26 | stripLiterals, 27 | combineArguments, 28 | combineStringLiterals, 29 | createConditionalExpression, 30 | removeUnnecessaryCalls, 31 | createObjectKeyLookups, 32 | referencedObjects, 33 | ]; 34 | 35 | export default (): babel.PluginObj<{ opts?: Partial; filename: string }> => ({ 36 | visitor: { 37 | Program(path, state) { 38 | const options = getOptions(state.opts); 39 | 40 | const expressions = findExpressions(path, options); 41 | 42 | if (expressions.length === 0) { 43 | return; 44 | } 45 | 46 | const internalState = new Set(); 47 | 48 | const runVisitors = ( 49 | expression: babel.NodePath, 50 | pushToQueue: (expression: babel.NodePath) => void 51 | ) => { 52 | for (const visitor of visitors) { 53 | visitor({ 54 | program: path, 55 | expression, 56 | state: internalState, 57 | options, 58 | filename: state.filename, 59 | pushToQueue, 60 | }); 61 | 62 | if (!expression.isCallExpression()) { 63 | return false; 64 | } 65 | } 66 | }; 67 | 68 | for (let x = 0; x < expressions.length; x++) { 69 | const item = expressions[x]; 70 | 71 | if (isImportMap(item)) { 72 | for (let y = 0; y < item.expressions.length; y++) { 73 | if ( 74 | runVisitors(item.expressions[y], (newExpression) => { 75 | newExpression.setData('is_optimizable', true); 76 | item.expressions.push(newExpression); 77 | item.referenceCount += 1; 78 | }) === false 79 | ) { 80 | item.expressions.splice(y, 1); 81 | item.referenceCount -= 1; 82 | y -= 1; 83 | } 84 | 85 | if (item.referenceCount === 0) { 86 | item.source.remove(); 87 | expressions.splice(x, 1); 88 | x -= 1; 89 | break; 90 | } 91 | } 92 | } else if ( 93 | runVisitors(item, (newExpression) => { 94 | newExpression.setData('is_optimizable', true); 95 | expressions.push(newExpression); 96 | }) === false 97 | ) { 98 | expressions.splice(x, 1); 99 | x -= 1; 100 | } 101 | } 102 | }, 103 | }, 104 | }); 105 | -------------------------------------------------------------------------------- /src/options.ts: -------------------------------------------------------------------------------- 1 | const DefaultSettings = { 2 | libraries: ['clsx', 'classnames'], 3 | functionNames: [] as string[], 4 | removeUnnecessaryCalls: true, 5 | collectCalls: false, 6 | }; 7 | 8 | export type PluginOptions = typeof DefaultSettings; 9 | 10 | export function getOptions(options: Partial = {}) { 11 | return { ...DefaultSettings, ...options }; 12 | } 13 | -------------------------------------------------------------------------------- /src/types.ts: -------------------------------------------------------------------------------- 1 | import * as babel from '@babel/core'; 2 | import * as t from '@babel/types'; 3 | import { PluginOptions } from './options'; 4 | 5 | export type VisitorFunction = (params: { 6 | program: babel.NodePath; 7 | expression: babel.NodePath; 8 | options: PluginOptions; 9 | state: Set; 10 | filename: string; 11 | pushToQueue: (expression: babel.NodePath) => void; 12 | }) => void; 13 | -------------------------------------------------------------------------------- /src/utils/helpers.ts: -------------------------------------------------------------------------------- 1 | import * as t from '@babel/types'; 2 | import fs from 'fs'; 3 | import path from 'path'; 4 | import generate from '@babel/generator'; 5 | import _ from 'lodash'; 6 | import hash from 'object-hash'; 7 | import { isStringLike, isStringLikeEmpty } from './strings'; 8 | 9 | export function flattenLogicalExpression(rootNode: t.Expression) { 10 | const result: t.Expression[] = []; 11 | checkNode(rootNode); 12 | return result; 13 | 14 | function checkNode(node: t.Expression) { 15 | if (t.isLogicalExpression(node)) { 16 | checkNode(node.left); 17 | result.push(node.right); 18 | } else { 19 | result.push(node); 20 | } 21 | } 22 | } 23 | 24 | export function isNestedLogicalAndExpression(node: any): node is t.LogicalExpression { 25 | if (!t.isLogicalExpression(node, { operator: '&&' })) { 26 | return false; 27 | } 28 | 29 | let temp = node.left; 30 | while (t.isLogicalExpression(temp)) { 31 | if (temp.operator !== '&&') { 32 | return false; 33 | } 34 | 35 | temp = temp.left; 36 | } 37 | 38 | return true; 39 | } 40 | 41 | export function getMostFrequentNode(operators: t.Expression[][]) { 42 | let maxNode = null; 43 | let maxCount = 0; 44 | let operators_n = operators.length; 45 | 46 | for (let y = 0, y_n = operators_n - 1; y < y_n; y++) { 47 | for (let x = 0, row = operators[y], x_n = row.length - 1; x < x_n; x++) { 48 | let col = row[x]; 49 | let count = 0; 50 | 51 | for (let y2 = y + 1; y2 < operators_n; y2++) { 52 | for (let x2 = 0, row2 = operators[y2]; x2 < row2.length - 1; x2++) { 53 | if (compareNodes(col, row2[x2])) { 54 | count += 1; 55 | } 56 | } 57 | } 58 | 59 | if (count > maxCount) { 60 | maxNode = col; 61 | maxCount = count; 62 | } 63 | } 64 | } 65 | 66 | return maxNode; 67 | } 68 | 69 | export function stringify(object: any) { 70 | function replacer(name: string, val: any) { 71 | if (name === 'start' || name === 'loc' || name === 'end') { 72 | return undefined; 73 | } 74 | return val; 75 | } 76 | 77 | return JSON.stringify(object, replacer, 1); 78 | } 79 | 80 | // Used during testing and debugging, 81 | const counts = new Map(); 82 | export function dumpData(obj: any, name = 'dump', generateCode = false) { 83 | const rootPath = path.join(__dirname, '../../dumps'); 84 | const data = generateCode ? generate(obj).code : stringify(obj); 85 | 86 | const count = counts.get(name) || 0; 87 | counts.set(name, count + 1); 88 | 89 | if (!fs.existsSync(rootPath)) { 90 | fs.mkdirSync(rootPath); 91 | } 92 | 93 | fs.writeFileSync( 94 | path.join(rootPath, name + '_' + count + (generateCode ? '.js' : '.json')), 95 | data 96 | ); 97 | } 98 | 99 | export function compareNodes(obj1: t.Node, obj2: t.Node): boolean { 100 | if (obj1.type !== obj2.type) { 101 | return false; 102 | } 103 | 104 | switch (obj1.type) { 105 | case 'NullLiteral': { 106 | return true; 107 | } 108 | case 'RegExpLiteral': { 109 | return t.isNodesEquivalent(obj1, obj2); 110 | } 111 | case 'Identifier': 112 | return obj1.name === (obj2 as typeof obj1).name; 113 | case 'MemberExpression': 114 | return ( 115 | compareNodes(obj1.object, (obj2 as typeof obj1).object) && 116 | compareNodes(obj1.property, (obj2 as typeof obj1).property) 117 | ); 118 | case 'BinaryExpression': 119 | return ( 120 | obj1.operator === (obj2 as typeof obj1).operator && 121 | compareNodes(obj1.left, (obj2 as typeof obj1).left) && 122 | compareNodes(obj1.right, (obj2 as typeof obj1).right) 123 | ); 124 | default: { 125 | if (t.isLiteral(obj1) && !t.isTemplateLiteral(obj1)) { 126 | return obj1.value === (obj2 as typeof obj1).value; 127 | } 128 | 129 | return _.isEqualWith(obj1, obj2, (v1, v2, key) => 130 | key === 'start' || key === 'end' || key === 'loc' ? true : undefined 131 | ); 132 | } 133 | } 134 | } 135 | 136 | export function hashNode(node: any) { 137 | return hash(node, { 138 | excludeKeys: (key) => 139 | key === 'start' || key === 'end' || key === 'loc' || key === 'extra' ? true : false, 140 | }); 141 | } 142 | 143 | export function isSafeConditionalExpression(node: any): node is t.ConditionalExpression { 144 | if (!t.isConditionalExpression(node)) { 145 | return false; 146 | } 147 | 148 | const { consequent, alternate } = node; 149 | 150 | if (isStringLike(consequent) && isStringLike(alternate)) { 151 | return true; 152 | } 153 | 154 | if ( 155 | (isStringLike(consequent) && isSafeConditionalExpression(alternate)) || 156 | (isStringLike(alternate) && isSafeConditionalExpression(consequent)) 157 | ) { 158 | return true; 159 | } 160 | 161 | return false; 162 | } 163 | 164 | export function createLogicalAndExpression(items: t.Expression[]) { 165 | return items.reduce((prev, curr) => t.logicalExpression('&&', prev, curr)); 166 | } 167 | 168 | export function isNodeFalsy(node: any) { 169 | return ( 170 | isStringLikeEmpty(node) || 171 | t.isNullLiteral(node) || 172 | t.isIdentifier(node, { name: 'undefined' }) || 173 | t.isBooleanLiteral(node, { value: false }) || 174 | t.isNumericLiteral(node, { value: 0 }) 175 | ); 176 | } 177 | -------------------------------------------------------------------------------- /src/utils/strings.ts: -------------------------------------------------------------------------------- 1 | import * as t from '@babel/types'; 2 | import template from '@babel/template'; 3 | 4 | export function isStringLike(node: any): node is t.StringLiteral | t.TemplateLiteral { 5 | return t.isStringLiteral(node) || t.isTemplateLiteral(node); 6 | } 7 | 8 | export function combineStringLike(a: t.Expression, b: t.Expression) { 9 | if (isStringLikeEmpty(a) && isStringLikeEmpty(b)) { 10 | return t.stringLiteral(''); 11 | } 12 | 13 | if (t.isStringLiteral(a) && t.isStringLiteral(b)) { 14 | return t.stringLiteral(a.value + ' ' + b.value); 15 | } 16 | 17 | if (t.isTemplateLiteral(a) && t.isTemplateLiteral(b)) { 18 | const expressions = [...a.expressions, ...b.expressions]; 19 | const quasis = [...a.quasis]; 20 | 21 | quasis[quasis.length - 1] = templateElement( 22 | quasis[quasis.length - 1].value.raw + ' ' + b.quasis[0].value.raw 23 | ); 24 | 25 | quasis.push(...b.quasis.slice(1)); 26 | 27 | return templateOrStringLiteral(quasis, expressions); 28 | } 29 | 30 | if (t.isTemplateLiteral(a) && t.isStringLiteral(b)) { 31 | const expressions = [...a.expressions]; 32 | const quasis = [...a.quasis]; 33 | 34 | const i = quasis.length - 1; 35 | quasis[i] = templateElement(quasis[i].value.raw + ' ' + b.value, true); 36 | 37 | return templateOrStringLiteral(quasis, expressions); 38 | } 39 | 40 | if (t.isStringLiteral(a) && t.isTemplateLiteral(b)) { 41 | const expressions = [...b.expressions]; 42 | const quasis = [...b.quasis]; 43 | 44 | const i = 0; 45 | quasis[i] = templateElement(a.value + ' ' + quasis[i].value.raw, true); 46 | 47 | return templateOrStringLiteral(quasis, expressions); 48 | } 49 | 50 | throw new Error('Unable to handle that input'); 51 | } 52 | 53 | export function isStringLikeEmpty(node: t.Expression): node is t.StringLiteral | t.TemplateLiteral { 54 | if (t.isStringLiteral(node)) { 55 | return node.value.length === 0; 56 | } 57 | 58 | if (t.isTemplateLiteral(node)) { 59 | return node.expressions.length === 0 && node.quasis.every((q) => q.value.raw.length === 0); 60 | } 61 | 62 | return false; 63 | } 64 | 65 | function templateOrStringLiteral(quasis: t.TemplateElement[], expressions: t.Expression[]) { 66 | if (expressions.length === 0) { 67 | return t.stringLiteral(quasis[0].value.raw); 68 | } 69 | 70 | return t.templateLiteral(quasis, expressions); 71 | } 72 | 73 | function templateElement(value: string, tail = false) { 74 | // Workaround to get the cooked value 75 | // https://github.com/babel/babel/issues/9242 76 | const valueAST = ((template.ast(`\`${value}\``) as t.ExpressionStatement) 77 | .expression as t.TemplateLiteral).quasis[0].value; 78 | 79 | return t.templateElement(valueAST, tail); 80 | } 81 | -------------------------------------------------------------------------------- /src/visitors/collectCalls.ts: -------------------------------------------------------------------------------- 1 | import generate from '@babel/generator'; 2 | import findCacheDir from 'find-cache-dir'; 3 | import fs from 'fs'; 4 | import path from 'path'; 5 | import { VisitorFunction } from '../types'; 6 | 7 | let stream: fs.WriteStream | null = null; 8 | let count = 0; 9 | 10 | export const collectCalls: VisitorFunction = ({ expression, options, filename }) => { 11 | if (!options.collectCalls) { 12 | return; 13 | } 14 | 15 | if (stream === null) { 16 | const cacheDir = findCacheDir({ name: 'optimize-clsx', create: true }); 17 | if (!cacheDir) { 18 | throw new Error('Unable to locate cache directory'); 19 | } 20 | 21 | const filePath = path.join( 22 | cacheDir, 23 | `log-${new Date(Date.now()).toISOString().replace(/:/g, '.')}.js` 24 | ); 25 | stream = fs.createWriteStream(filePath, { flags: 'w' }); 26 | console.log('Writing CallExpressions to ' + filePath); 27 | } 28 | 29 | let locationStr = ''; 30 | if (expression.node.loc) { 31 | const location = expression.node.loc.start; 32 | locationStr = `:${location.line}:${location.column}`; 33 | } 34 | 35 | stream.write( 36 | `// ${filename}${locationStr}\nconst x${++count} = ${generate(expression.node).code};\n\n` 37 | ); 38 | }; 39 | -------------------------------------------------------------------------------- /src/visitors/combineArguments.js: -------------------------------------------------------------------------------- 1 | import * as t from '@babel/types'; 2 | import _ from 'lodash'; 3 | import * as helpers from '../utils/helpers'; 4 | 5 | export const combineArguments = ({ expression: path }) => { 6 | // Not enough arguments to optimize 7 | if (path.node.arguments.length < 2) return; 8 | 9 | const [match, noMatch] = _.partition(path.node.arguments, helpers.isNestedLogicalAndExpression); 10 | 11 | // Not enough arguments to optimize 12 | if (match.length < 2) return; 13 | 14 | const operators = match.map(helpers.flattenLogicalExpression); 15 | 16 | const node = helpers.getMostFrequentNode(operators); 17 | // No nodes appear more than once 18 | if (node === null) return; 19 | 20 | const rootNode = combineOperators(operators, node); 21 | 22 | const newAST = convertToAST(rootNode); 23 | 24 | path.node.arguments = [...noMatch, ...newAST]; 25 | return; 26 | 27 | function convertToAST(node) { 28 | if (node.type !== 'rootNode') { 29 | return node; 30 | } 31 | 32 | const result = []; 33 | 34 | if (node.child.type === 'rootNode') { 35 | let right = convertToAST(node.child); 36 | if (right.length === 1) { 37 | right = right[0]; 38 | } else { 39 | right = t.arrayExpression(right); 40 | } 41 | result.push(t.logicalExpression('&&', node.node, right)); 42 | } else { 43 | result.push(t.logicalExpression('&&', node.node, node.child)); 44 | } 45 | 46 | if (node.next !== undefined) { 47 | const r = convertToAST(node.next); 48 | Array.isArray(r) ? result.push(...r) : result.push(r); 49 | } 50 | 51 | return result; 52 | } 53 | 54 | function combineOperators(operators, node) { 55 | const newNode = { 56 | type: 'rootNode', 57 | node: node, 58 | child: [], 59 | next: [], 60 | }; 61 | 62 | operators.forEach((row) => { 63 | const filtered = row.filter((item) => !helpers.compareNodes(item, node)); 64 | if (filtered.length === row.length) { 65 | newNode.next.push(row); 66 | } else { 67 | newNode.child.push(filtered); 68 | } 69 | }); 70 | 71 | newNode.next = checkSub(newNode.next); 72 | 73 | const child = checkSub(newNode.child); 74 | if (Array.isArray(child)) { 75 | newNode.child = child.length === 1 ? child[0] : t.arrayExpression(child); 76 | } else { 77 | newNode.child = child; 78 | } 79 | 80 | return newNode; 81 | 82 | function checkSub(items) { 83 | if (items.length === 0) return undefined; 84 | 85 | if (items.length > 1) { 86 | const nextCheck = helpers.getMostFrequentNode(items); 87 | if (nextCheck !== null) { 88 | return combineOperators(items, nextCheck); 89 | } 90 | } 91 | 92 | return items.map(helpers.createLogicalAndExpression); 93 | } 94 | } 95 | }; 96 | -------------------------------------------------------------------------------- /src/visitors/combineStringLiterals.ts: -------------------------------------------------------------------------------- 1 | import * as babel from '@babel/core'; 2 | import _ from 'lodash'; 3 | import { isStringLike, combineStringLike } from '../utils/strings'; 4 | import { VisitorFunction } from '../types'; 5 | 6 | function combineStringsInArray(array: unknown[]): any[] { 7 | if (array.length < 2) { 8 | return array; 9 | } 10 | 11 | const [match, noMatch] = _.partition(array, isStringLike); 12 | 13 | if (match.length < 2) { 14 | return array; 15 | } 16 | 17 | return [match.reduce(combineStringLike), ...noMatch]; 18 | } 19 | 20 | const arrayVisitor: babel.Visitor = { 21 | ArrayExpression(path) { 22 | const { node } = path; 23 | node.elements = combineStringsInArray(node.elements); 24 | 25 | if (node.elements.length === 1 && node.elements[0]) { 26 | path.replaceWith(node.elements[0]); 27 | } 28 | }, 29 | }; 30 | 31 | export const combineStringLiterals: VisitorFunction = ({ expression: path }) => { 32 | path.node.arguments = combineStringsInArray(path.node.arguments); 33 | path.traverse(arrayVisitor); 34 | }; 35 | -------------------------------------------------------------------------------- /src/visitors/createConditionalExpression.js: -------------------------------------------------------------------------------- 1 | import * as t from '@babel/types'; 2 | import _ from 'lodash'; 3 | import * as helpers from '../utils/helpers'; 4 | 5 | export const createConditionalExpression = ({ expression: path }) => { 6 | path.node.arguments = combine(path.node.arguments); 7 | 8 | function combine(args) { 9 | const [match, noMatch] = _.partition(args, helpers.isNestedLogicalAndExpression); 10 | 11 | if (match.length === 0) return noMatch; 12 | 13 | let operators = match 14 | .map(helpers.flattenLogicalExpression) 15 | // If the last item in each row is an array, recursivly call ourself 16 | .map((row) => { 17 | let col = row.pop(); 18 | 19 | if (t.isArrayExpression(col)) { 20 | col.elements = combine(col.elements); 21 | if (col.elements.length === 1) { 22 | col = col.elements[0]; 23 | } 24 | } 25 | 26 | row.push(col); 27 | return row; 28 | }); 29 | 30 | const result = noMatch; 31 | 32 | for (let row_index = 0; row_index < operators.length; row_index++) { 33 | const row = operators[row_index]; 34 | 35 | if (checkRow()) { 36 | row_index -= 1; 37 | } 38 | 39 | function checkRow() { 40 | for (let col_index = 0; col_index < row.length - 1; col_index++) { 41 | const col = row[col_index]; 42 | 43 | const isUnary = t.isUnaryExpression(col, { operator: '!' }); 44 | const isBinary = t.isBinaryExpression(col, { operator: '!==' }); 45 | if (!isUnary && !isBinary) continue; 46 | 47 | for (let row2_index = 0; row2_index < operators.length; row2_index++) { 48 | const row2 = operators[row2_index]; 49 | for (let col2_index = 0; col2_index < row2.length - 1; col2_index++) { 50 | const col2 = row2[col2_index]; 51 | if (row_index === row2_index && col_index === col2_index) continue; 52 | 53 | if (isUnary && !helpers.compareNodes(col.argument, col2)) { 54 | continue; 55 | } else if ( 56 | isBinary && 57 | !( 58 | t.isBinaryExpression(col2, { operator: '===' }) && 59 | ((helpers.compareNodes(col.left, col2.left) && 60 | helpers.compareNodes(col.right, col2.right)) || 61 | (helpers.compareNodes(col.left, col2.right) && 62 | helpers.compareNodes(col.right, col2.left))) 63 | ) 64 | ) { 65 | continue; 66 | } 67 | 68 | const left = helpers.createLogicalAndExpression( 69 | row2.filter((e, index) => index !== col2_index) 70 | ); 71 | const right = helpers.createLogicalAndExpression( 72 | row.filter((e, index) => index !== col_index) 73 | ); 74 | 75 | // isUnary: col2 is 1 char shorter (col2: foo vs col: !foo) 76 | // isBinary: col2 has the === operator 77 | result.push(t.conditionalExpression(col2, left, right)); 78 | 79 | // Remove from collection 80 | operators = operators.filter( 81 | (e, index) => index !== row_index && index !== row2_index 82 | ); 83 | return true; 84 | } 85 | } 86 | } 87 | return false; 88 | } 89 | } 90 | 91 | return [...result, ...operators.map(helpers.createLogicalAndExpression)]; 92 | } 93 | }; 94 | -------------------------------------------------------------------------------- /src/visitors/createObjectKeyLookups.ts: -------------------------------------------------------------------------------- 1 | import * as babel from '@babel/core'; 2 | import * as t from '@babel/types'; 3 | import _ from 'lodash/fp'; 4 | import { 5 | hashNode, 6 | flattenLogicalExpression, 7 | createLogicalAndExpression, 8 | isNestedLogicalAndExpression, 9 | } from '../utils/helpers'; 10 | import { VisitorFunction } from '../types'; 11 | 12 | function matchLeftOrRight( 13 | node: t.BinaryExpression, 14 | checkOrChecks: ((node: t.Expression) => boolean) | Array<(node: t.Expression) => boolean> 15 | ) { 16 | if (!Array.isArray(checkOrChecks)) { 17 | return checkOrChecks(node.left) || checkOrChecks(node.right); 18 | } 19 | 20 | for (const _check of checkOrChecks) { 21 | if (_check(node.left) || _check(node.right)) { 22 | return true; 23 | } 24 | } 25 | 26 | return false; 27 | } 28 | 29 | function combineFromArray(arr: t.Expression[]) { 30 | // x === 'foo', 'foo' === x 31 | // x.y === 'foo', 'foo' === x.y 32 | // x?.y === 'foo', 'foo' === x?.y 33 | const [match, noMatch] = _.partition((itm) => { 34 | return checkItem(itm); 35 | 36 | function checkItem(item: t.Expression): boolean { 37 | if (isNestedLogicalAndExpression(item)) { 38 | return checkItem(item.left); 39 | } 40 | 41 | return ( 42 | t.isBinaryExpression(item, { operator: '===' }) && 43 | matchLeftOrRight(item, t.isStringLiteral) && 44 | matchLeftOrRight(item, [t.isMemberExpression, t.isIdentifier, t.isOptionalMemberExpression]) 45 | ); 46 | } 47 | }, arr); 48 | 49 | if (match.length === 0) { 50 | return arr; 51 | } 52 | 53 | const newArgs = _.flow( 54 | _.map(flattenLogicalExpression), 55 | 56 | // Set the string to always be on the right side of === 57 | // Simplifies the rest of the code 58 | _.map((row) => { 59 | const tempNode = row[0] as t.BinaryExpression; 60 | 61 | if (t.isStringLiteral(tempNode.left)) { 62 | const strNode = tempNode.left; 63 | tempNode.left = tempNode.right; 64 | tempNode.right = strNode; 65 | } 66 | 67 | return row; 68 | }), 69 | 70 | // Group on whatever the strings are compared to 71 | _.groupBy((row) => hashNode((row[0] as t.BinaryExpression).left)), 72 | 73 | // Removes the key created by groupBy 74 | _.values, 75 | 76 | // Create the objects 77 | _.map((group: t.Expression[][]) => { 78 | if (group.length === 1) { 79 | return createLogicalAndExpression(group[0]); 80 | } 81 | 82 | return t.memberExpression( 83 | t.objectExpression( 84 | group.map((row) => 85 | t.objectProperty( 86 | (row[0] as t.BinaryExpression).right, 87 | createLogicalAndExpression(row.slice(1)) 88 | ) 89 | ) 90 | ), 91 | (group[0][0] as t.BinaryExpression).left, 92 | true 93 | ); 94 | }) 95 | )(match); 96 | 97 | return [...noMatch, ...newArgs]; 98 | } 99 | 100 | const arrayVisitor: babel.Visitor = { 101 | ArrayExpression(path) { 102 | path.node.elements = combineFromArray(path.node.elements as t.Expression[]); 103 | 104 | if (path.node.elements.length === 1) { 105 | path.replaceWith(path.node.elements[0]!); 106 | } 107 | }, 108 | }; 109 | 110 | export const createObjectKeyLookups: VisitorFunction = ({ expression: path }) => { 111 | path.traverse(arrayVisitor); 112 | path.node.arguments = combineFromArray(path.node.arguments as t.Expression[]); 113 | }; 114 | -------------------------------------------------------------------------------- /src/visitors/extractObjectProperties.ts: -------------------------------------------------------------------------------- 1 | import * as t from '@babel/types'; 2 | import _ from 'lodash'; 3 | import { VisitorFunction } from '../types'; 4 | 5 | export const extractObjectProperties: VisitorFunction = ({ expression }) => { 6 | expression.node.arguments = _.flatMap(expression.node.arguments, (node) => { 7 | if (!t.isObjectExpression(node)) { 8 | return node; 9 | } 10 | 11 | return node.properties.map((p) => { 12 | if (t.isSpreadElement(p)) { 13 | return p.argument; 14 | } else { 15 | const getKey = () => { 16 | return p.computed 17 | ? p.key 18 | : t.isStringLiteral(p.key) 19 | ? p.key 20 | : t.stringLiteral(p.key.name); 21 | }; 22 | 23 | if (t.isObjectMethod(p)) { 24 | return getKey(); 25 | } else { 26 | // @ts-ignore 27 | return t.logicalExpression('&&', p.value, getKey()); 28 | } 29 | } 30 | }); 31 | }); 32 | }; 33 | -------------------------------------------------------------------------------- /src/visitors/flattenArrays.ts: -------------------------------------------------------------------------------- 1 | import * as t from '@babel/types'; 2 | import _ from 'lodash'; 3 | import { VisitorFunction } from '../types'; 4 | 5 | function unwrapArrayExpression(nodes: any[]): any[] { 6 | return nodes.map((item) => 7 | t.isArrayExpression(item) ? unwrapArrayExpression(item.elements) : item 8 | ); 9 | } 10 | 11 | export const flattenArrays: VisitorFunction = ({ expression }) => { 12 | expression.node.arguments = _.flattenDeep(unwrapArrayExpression(expression.node.arguments)); 13 | }; 14 | -------------------------------------------------------------------------------- /src/visitors/optimizeExpressions.ts: -------------------------------------------------------------------------------- 1 | import * as t from '@babel/types'; 2 | import * as babel from '@babel/core'; 3 | import * as helpers from '../utils/helpers'; 4 | import _ from 'lodash/fp'; 5 | import { VisitorFunction } from '../types'; 6 | 7 | type ExtractPathType

= P extends babel.NodePath ? T : never; 8 | 9 | type VisitorWithoutPath = { 10 | // @ts-ignore - Works perfectly fine 11 | [P in keyof babel.Visitor]: (node: ExtractPathType[0]>) => any; 12 | }; 13 | 14 | const optimizations: VisitorWithoutPath = { 15 | ConditionalExpression(node) { 16 | // foo ? bar : '' --> foo && bar 17 | if (helpers.isNodeFalsy(node.alternate)) { 18 | return t.logicalExpression('&&', node.test, node.consequent); 19 | } 20 | 21 | // foo ? '' : bar --> !foo && bar 22 | if (helpers.isNodeFalsy(node.consequent)) { 23 | return t.logicalExpression('&&', t.unaryExpression('!', node.test), node.alternate); 24 | } 25 | }, 26 | BinaryExpression(node) { 27 | // This transform allows createConditionalExpression to optimize the expression later 28 | // foo % 2 === 1 --> foo % 2 !== 0 29 | // foo % 2 !== 1 --> foo % 2 === 0 30 | if ( 31 | (node.operator === '!==' || node.operator === '===') && 32 | t.isNumericLiteral(node.right, { value: 1 }) && 33 | t.isBinaryExpression(node.left, { operator: '%' }) && 34 | t.isNumericLiteral(node.left.right, { value: 2 }) 35 | ) { 36 | return { 37 | ...node, 38 | right: t.numericLiteral(0), 39 | operator: node.operator === '===' ? '!==' : '===', 40 | }; 41 | } 42 | }, 43 | LogicalExpression(node) { 44 | // foo || '' -> foo 45 | if (node.operator === '||' && helpers.isNodeFalsy(node.right)) { 46 | return node.left; 47 | } 48 | 49 | if (helpers.isNestedLogicalAndExpression(node)) { 50 | return _.flow( 51 | helpers.flattenLogicalExpression, 52 | // Remove duplicates in the same expression 53 | // foo && bar && bar --> foo && bar 54 | _.uniqBy(helpers.hashNode), 55 | // Optimize individual expressions 56 | _.map(optimizeExpression), 57 | helpers.createLogicalAndExpression 58 | )(node); 59 | } 60 | }, 61 | }; 62 | 63 | function optimizeExpression(node: t.Node): any { 64 | if (node.type in optimizations) { 65 | // @ts-ignore 66 | const result = optimizations[node.type](node); 67 | if (result) { 68 | // @ts-ignore 69 | return result.type !== node.type || result.operator !== node.operator 70 | ? optimizeExpression(result) 71 | : result; 72 | } 73 | } 74 | 75 | return node; 76 | } 77 | 78 | export const optimizeExpressions: VisitorFunction = ({ expression }) => { 79 | expression.node.arguments = expression.node.arguments.map(optimizeExpression); 80 | }; 81 | -------------------------------------------------------------------------------- /src/visitors/propTypes.js: -------------------------------------------------------------------------------- 1 | import * as t from '@babel/types'; 2 | import * as helpers from '../utils/helpers'; 3 | import _ from 'lodash'; 4 | 5 | const replaceVisitor = { 6 | BinaryExpression(path) { 7 | const node = path.node; 8 | if ( 9 | t.isBinaryExpression(node) && 10 | (node.operator === '===' || node.operator === '!==') && 11 | (t.isStringLiteral(node.right) || t.isStringLiteral(node.left)) 12 | ) { 13 | const propType = this.types.find((obj) => 14 | obj.matches.some( 15 | (item) => 16 | (obj.isRequired || item.hasDefaultValue) && 17 | (helpers.compareNodes(node.left, item) || helpers.compareNodes(node.right, item)) 18 | ) 19 | ); 20 | if (propType === undefined) return; 21 | 22 | const valueToUse = 23 | propType.optionA.length < propType.optionB.length ? propType.optionA : propType.optionB; 24 | 25 | const target = t.isStringLiteral(node.right) ? 'right' : 'left'; 26 | if ( 27 | node[target].value !== valueToUse && 28 | (node[target].value === propType.optionA || node[target].value === propType.optionB) 29 | ) { 30 | node[target] = t.stringLiteral(valueToUse); 31 | node.operator = node.operator === '!==' ? '===' : '!=='; 32 | } 33 | } 34 | }, 35 | }; 36 | 37 | const functionVisitor = { 38 | 'FunctionExpression|FunctionDeclaration'(path) { 39 | const node = path.node; 40 | for (const propType of this.propTypes) { 41 | if ( 42 | t.isFunction(node) && 43 | t.isIdentifier(node.id, { name: propType.name }) && 44 | node.params.length !== 0 45 | ) { 46 | getObjectPattern(node.params[0], null); 47 | 48 | function getObjectPattern(item, matcher) { 49 | if (Array.isArray(item)) { 50 | for (const obj of item) { 51 | getObjectPattern(obj, matcher); 52 | } 53 | return; 54 | } 55 | 56 | if (t.isBlockStatement(item)) { 57 | return getObjectPattern(item.body, matcher); 58 | } 59 | 60 | if (t.isVariableDeclaration(item)) { 61 | return getObjectPattern(item.declarations, matcher); 62 | } 63 | 64 | if (t.isObjectPattern(item)) { 65 | return getObjectPattern(item.properties, null); 66 | } 67 | 68 | if (t.isObjectProperty(item) && t.isIdentifier(item.key)) { 69 | let matchValue = null; 70 | if (t.isIdentifier(item.value)) { 71 | matchValue = item.value; 72 | } else if (t.isAssignmentPattern(item.value)) { 73 | matchValue = item.value.left; 74 | matchValue.hasDefaultValue = true; 75 | } else { 76 | return; 77 | } 78 | 79 | propType.values = propType.values.map((prop) => { 80 | if (prop.name !== item.key.name) return prop; 81 | 82 | if (prop.matches === undefined) { 83 | prop.matches = []; 84 | } 85 | 86 | return { 87 | ...prop, 88 | matches: [...prop.matches, matchValue], 89 | }; 90 | }); 91 | return; 92 | } 93 | 94 | if (t.isRestElement(item) && t.isIdentifier(item.argument)) { 95 | return getObjectPattern(item.argument, null); 96 | } 97 | 98 | if (t.isIdentifier(item)) { 99 | propType.values = propType.values.map((prop) => { 100 | if (prop.matches === undefined) { 101 | prop.matches = []; 102 | } 103 | return { 104 | ...prop, 105 | matches: [...prop.matches, t.memberExpression(item, t.identifier(prop.name))], 106 | }; 107 | }); 108 | 109 | return getObjectPattern(node.body, item); 110 | } 111 | 112 | if (t.isVariableDeclarator(item)) { 113 | if ( 114 | t.isMemberExpression(item.init) && 115 | helpers.compareNodes(item.init.object, matcher) && 116 | t.isIdentifier(item.init.property) && 117 | t.isIdentifier(item.id) 118 | ) { 119 | propType.values = propType.values.map((prop) => { 120 | if (prop.name !== item.init.property.name) return prop; 121 | 122 | if (prop.matches === undefined) { 123 | prop.matches = []; 124 | } 125 | return { 126 | ...prop, 127 | matches: [...prop.matches, item.id], 128 | }; 129 | }); 130 | } else if ( 131 | t.isIdentifier(item.init) && 132 | helpers.compareNodes(item.init, matcher) && 133 | t.isObjectPattern(item.id) 134 | ) { 135 | return getObjectPattern(item.id, null); 136 | } 137 | } 138 | } 139 | 140 | path.traverse(replaceVisitor, { types: propType.values }); 141 | path.skip(); 142 | } 143 | } 144 | }, 145 | }; 146 | 147 | function getPropTypes(body) { 148 | const propTypeName = getPropTypesName(body); 149 | if (propTypeName === undefined) return []; 150 | 151 | const result = []; 152 | 153 | for (const node of body) { 154 | if (t.isExpressionStatement(node)) { 155 | const expr = node.expression; 156 | if ( 157 | t.isAssignmentExpression(expr, { operator: '=' }) && 158 | t.isMemberExpression(expr.left) && 159 | t.isIdentifier(expr.left.property, { name: 'propTypes' }) && 160 | t.isIdentifier(expr.left.object) 161 | ) { 162 | let propType = { 163 | name: expr.left.object.name, 164 | values: [], 165 | }; 166 | 167 | if (t.isObjectExpression(expr.right)) { 168 | for (const prop of expr.right.properties) { 169 | getArrayElements(prop.value, false); 170 | 171 | function getArrayElements(element, isRequired) { 172 | if ( 173 | t.isMemberExpression(element) && 174 | t.isIdentifier(element.property, { name: 'isRequired' }) 175 | ) { 176 | getArrayElements(element.object, true); 177 | } else if ( 178 | t.isCallExpression(element) && 179 | t.isMemberExpression(element.callee) && 180 | t.isIdentifier(element.callee.object, { name: propTypeName }) && 181 | t.isIdentifier(element.callee.property, { name: 'oneOf' }) && 182 | element.arguments.length === 1 183 | ) { 184 | const value = element.arguments[0]; 185 | if ( 186 | t.isArrayExpression(value) && 187 | value.elements.length === 2 && 188 | t.isStringLiteral(value.elements[0]) && 189 | t.isStringLiteral(value.elements[1]) 190 | ) { 191 | propType.values.push({ 192 | name: prop.key.name, 193 | isRequired: isRequired, 194 | optionA: value.elements[0].value, 195 | optionB: value.elements[1].value, 196 | }); 197 | } 198 | } 199 | } 200 | } 201 | } 202 | 203 | if (propType.values.length !== 0) { 204 | result.push(propType); 205 | } 206 | } 207 | } 208 | } 209 | 210 | return result; 211 | } 212 | 213 | function getPropTypesName(body) { 214 | for (const node of body) { 215 | if ( 216 | t.isImportDeclaration(node) && 217 | node.source.value === 'prop-types' && 218 | node.specifiers.length === 1 219 | ) { 220 | const spec = node.specifiers[0]; 221 | if (t.isImportDefaultSpecifier(spec)) { 222 | return spec.local.name; 223 | } 224 | } 225 | } 226 | } 227 | 228 | export const propTypes = ({ program: path, state }) => { 229 | // This visitor should only run once 230 | if (state.has('proptypes')) { 231 | return; 232 | } 233 | 234 | state.add('proptypes'); 235 | 236 | const propTypes = getPropTypes(path.node.body); 237 | if (propTypes.length === 0) return; 238 | 239 | path.traverse(functionVisitor, { propTypes }); 240 | }; 241 | -------------------------------------------------------------------------------- /src/visitors/referencedObjects.ts: -------------------------------------------------------------------------------- 1 | import * as t from '@babel/types'; 2 | import { VisitorFunction } from '../types'; 3 | 4 | export const referencedObjects: VisitorFunction = ({ expression, pushToQueue }) => { 5 | const args = expression.get('arguments'); 6 | 7 | for (const nodePath of args) { 8 | if (!nodePath.isIdentifier()) { 9 | continue; 10 | } 11 | 12 | const binding = nodePath.scope.getBinding(nodePath.node.name); 13 | if (!binding) { 14 | continue; 15 | } 16 | 17 | if ( 18 | binding.constant && 19 | binding.path.isVariableDeclarator() && 20 | t.isObjectExpression(binding.path.node.init) && 21 | binding.referencePaths.every( 22 | (ref) => 23 | ref.parentPath.isCallExpression() && ref.parentPath.getData('is_optimizable') === true 24 | ) 25 | ) { 26 | const init = binding.path.get('init'); 27 | 28 | init.replaceWith( 29 | t.callExpression(t.cloneNode(expression.node.callee), [binding.path.node.init]) 30 | ); 31 | 32 | pushToQueue(init as babel.NodePath); 33 | } 34 | } 35 | }; 36 | -------------------------------------------------------------------------------- /src/visitors/removeUnnecessaryCalls.ts: -------------------------------------------------------------------------------- 1 | import * as t from '@babel/types'; 2 | import * as babel from '@babel/core'; 3 | import { isSafeConditionalExpression } from '../utils/helpers'; 4 | import { isStringLike, combineStringLike, isStringLikeEmpty } from '../utils/strings'; 5 | import { VisitorFunction } from '../types'; 6 | 7 | const transforms: Array<(...args: any[]) => t.Expression | undefined> = [ 8 | function noArgumentsToString() { 9 | return t.stringLiteral(''); 10 | }, 11 | 12 | function singleStringLike(arg) { 13 | if (isStringLike(arg) || isSafeConditionalExpression(arg)) { 14 | return arg; 15 | } 16 | }, 17 | 18 | function multipleSafeConditionals(conditionalOne, conditionalTwo) { 19 | if ( 20 | isSafeConditionalExpression(conditionalOne) && 21 | isSafeConditionalExpression(conditionalTwo) 22 | ) { 23 | const newCond = t.conditionalExpression( 24 | conditionalOne.test, 25 | combineStringLike(conditionalOne.consequent, t.stringLiteral('')), 26 | combineStringLike(conditionalOne.alternate, t.stringLiteral('')) 27 | ); 28 | 29 | return t.binaryExpression('+', newCond, conditionalTwo); 30 | } 31 | }, 32 | 33 | function stringAndSafeConditional(stringLike, conditional) { 34 | if (isStringLike(stringLike) && isSafeConditionalExpression(conditional)) { 35 | if (isStringLikeEmpty(conditional.consequent) || isStringLikeEmpty(conditional.alternate)) { 36 | return t.binaryExpression( 37 | '+', 38 | stringLike, 39 | t.conditionalExpression( 40 | conditional.test, 41 | combineStringLike(t.stringLiteral(''), conditional.consequent), 42 | combineStringLike(t.stringLiteral(''), conditional.alternate) 43 | ) 44 | ); 45 | } 46 | 47 | return t.binaryExpression( 48 | '+', 49 | combineStringLike(stringLike, t.stringLiteral('')), 50 | conditional 51 | ); 52 | } 53 | }, 54 | 55 | function singleLogicalExpression(logicalExpr) { 56 | if ( 57 | t.isLogicalExpression(logicalExpr, { operator: '&&' }) && 58 | (isStringLike(logicalExpr.right) || 59 | isSafeConditionalExpression(logicalExpr.right) || 60 | (t.isBinaryExpression(logicalExpr.right, { operator: '+' }) && 61 | isStringLike(logicalExpr.right.left) && 62 | isSafeConditionalExpression(logicalExpr.right.right))) 63 | ) { 64 | return t.conditionalExpression(logicalExpr.left, logicalExpr.right, t.stringLiteral('')); 65 | } 66 | }, 67 | 68 | function stringAndLogicalExpression(stringLike, logicalExpr) { 69 | if ( 70 | isStringLike(stringLike) && 71 | t.isLogicalExpression(logicalExpr, { operator: '&&' }) && 72 | isStringLike(logicalExpr.right) 73 | ) { 74 | return t.binaryExpression( 75 | '+', 76 | stringLike, 77 | t.conditionalExpression( 78 | logicalExpr.left, 79 | combineStringLike(t.stringLiteral(''), logicalExpr.right), 80 | t.stringLiteral('') 81 | ) 82 | ); 83 | } 84 | }, 85 | ]; 86 | 87 | function runTransforms( 88 | path: babel.NodePath, 89 | elements: t.CallExpression['arguments'] | t.ArrayExpression['elements'] 90 | ) { 91 | const reversed = [...elements].reverse(); 92 | 93 | for (const tr of transforms) { 94 | if (tr.length !== elements.length) { 95 | continue; 96 | } 97 | 98 | const result = tr.apply(undefined, elements) ?? tr.apply(undefined, reversed); 99 | 100 | if (result !== undefined) { 101 | path.replaceWith(result); 102 | break; 103 | } 104 | } 105 | } 106 | 107 | const arrayVisitor: babel.Visitor = { 108 | ArrayExpression(path) { 109 | runTransforms(path, path.node.elements); 110 | }, 111 | }; 112 | 113 | export const removeUnnecessaryCalls: VisitorFunction = ({ expression: path, options }) => { 114 | if (!options.removeUnnecessaryCalls) { 115 | return; 116 | } 117 | 118 | path.traverse(arrayVisitor); 119 | runTransforms(path, path.node.arguments); 120 | }; 121 | -------------------------------------------------------------------------------- /src/visitors/stripLiterals.ts: -------------------------------------------------------------------------------- 1 | import * as t from '@babel/types'; 2 | import _ from 'lodash'; 3 | import * as helpers from '../utils/helpers'; 4 | import { isStringLike, isStringLikeEmpty } from '../utils/strings'; 5 | import { VisitorFunction } from '../types'; 6 | 7 | export const stripLiterals: VisitorFunction = ({ expression: path }) => { 8 | const [match, noMatch] = _.partition(path.node.arguments, helpers.isNestedLogicalAndExpression); 9 | 10 | const result = match 11 | .map(helpers.flattenLogicalExpression) 12 | .map((expression) => 13 | // Remove values that will always be true 14 | expression.filter( 15 | (item, index) => 16 | !( 17 | t.isBooleanLiteral(item, { value: true }) || 18 | (index !== expression.length - 1 && t.isNumericLiteral(item) && item.value !== 0) || 19 | (index !== expression.length - 1 && isStringLike(item) && !isStringLikeEmpty(item)) 20 | ) 21 | ) 22 | ) 23 | // Remove expressions that will always be false 24 | .filter((expression) => !(expression.length === 0 || expression.some(helpers.isNodeFalsy))) 25 | .map(helpers.createLogicalAndExpression); 26 | 27 | const rest = noMatch.filter((item) => !helpers.isNodeFalsy(item)); 28 | 29 | path.node.arguments = [...rest, ...result]; 30 | }; 31 | -------------------------------------------------------------------------------- /test/fixtures/combine-arguments/nested-match/code.js: -------------------------------------------------------------------------------- 1 | clsx( 2 | text && classes.text, 3 | color === 'primary' && text && classes.text, 4 | text && color === 'primary' && classes.textPrimary, 5 | text && color === 'secondary' && classes.textSecondary 6 | ); 7 | -------------------------------------------------------------------------------- /test/fixtures/combine-arguments/nested-match/output.js: -------------------------------------------------------------------------------- 1 | clsx( 2 | text && [ 3 | classes.text, 4 | { 5 | primary: [classes.text, classes.textPrimary], 6 | secondary: classes.textSecondary, 7 | }[color], 8 | ] 9 | ); 10 | -------------------------------------------------------------------------------- /test/fixtures/combine-arguments/single-match/code.js: -------------------------------------------------------------------------------- 1 | clsx( 2 | foo && classes.text, 3 | bar && classes.text, 4 | text && classes.text, 5 | color === 'foo' && text && classes.text, 6 | text && color === 'bar' && classes.textPrimary, 7 | text && color === 'baz' && classes.textSecondary 8 | ); 9 | -------------------------------------------------------------------------------- /test/fixtures/combine-arguments/single-match/output.js: -------------------------------------------------------------------------------- 1 | clsx( 2 | text && [ 3 | classes.text, 4 | { 5 | foo: classes.text, 6 | bar: classes.textPrimary, 7 | baz: classes.textSecondary, 8 | }[color], 9 | ], 10 | foo && classes.text, 11 | bar && classes.text 12 | ); 13 | -------------------------------------------------------------------------------- /test/fixtures/combine-string-literals/conditional/code.js: -------------------------------------------------------------------------------- 1 | const x = clsx({ 2 | foo: true, 3 | bar: fooValue, 4 | baz: fooValue, 5 | foobar: !fooValue, 6 | foobaz: true, 7 | }); 8 | -------------------------------------------------------------------------------- /test/fixtures/combine-string-literals/conditional/output.js: -------------------------------------------------------------------------------- 1 | const x = clsx('foo foobaz', fooValue ? 'bar baz' : 'foobar'); 2 | -------------------------------------------------------------------------------- /test/fixtures/combine-string-literals/normal-strings/code.js: -------------------------------------------------------------------------------- 1 | clsx({ 2 | btn: true, 3 | 'col-md-1': true, 4 | 'col-xs-1': true, 5 | ['btn-primary']: true, 6 | }); 7 | -------------------------------------------------------------------------------- /test/fixtures/combine-string-literals/normal-strings/output.js: -------------------------------------------------------------------------------- 1 | clsx('btn col-md-1 col-xs-1 btn-primary'); 2 | -------------------------------------------------------------------------------- /test/fixtures/combine-string-literals/string-and-template/code.js: -------------------------------------------------------------------------------- 1 | const x = clsx('foo', `bar-${baz}`); 2 | const y = clsx(`bar-${baz}`, 'foo'); 3 | -------------------------------------------------------------------------------- /test/fixtures/combine-string-literals/string-and-template/output.js: -------------------------------------------------------------------------------- 1 | const x = clsx(`foo bar-${baz}`); 2 | const y = clsx(`bar-${baz} foo`); 3 | -------------------------------------------------------------------------------- /test/fixtures/combine-string-literals/template-literal-transformed/.babelrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: [require.resolve('@babel/plugin-transform-template-literals')], 3 | }; 4 | -------------------------------------------------------------------------------- /test/fixtures/combine-string-literals/template-literal-transformed/code.js: -------------------------------------------------------------------------------- 1 | // https://github.com/merceyz/babel-plugin-optimize-clsx/issues/15 2 | 3 | clsx('store-cell', 'account-wide', `bucket-${bucket.id}`); 4 | -------------------------------------------------------------------------------- /test/fixtures/combine-string-literals/template-literal-transformed/output.js: -------------------------------------------------------------------------------- 1 | clsx('store-cell account-wide bucket-'.concat(bucket.id)); 2 | -------------------------------------------------------------------------------- /test/fixtures/combine-string-literals/template-literal/code.js: -------------------------------------------------------------------------------- 1 | const x = clsx('foo', `foo${foo}`, `bar`); 2 | -------------------------------------------------------------------------------- /test/fixtures/combine-string-literals/template-literal/output.js: -------------------------------------------------------------------------------- 1 | const x = clsx(`foo foo${foo} bar`); 2 | -------------------------------------------------------------------------------- /test/fixtures/create-conditional-expression/binary-expression/code.js: -------------------------------------------------------------------------------- 1 | clsx(foo === 'bar' && baz, foo !== 'bar' && foobar); 2 | -------------------------------------------------------------------------------- /test/fixtures/create-conditional-expression/binary-expression/output.js: -------------------------------------------------------------------------------- 1 | clsx(foo === 'bar' ? baz : foobar); 2 | -------------------------------------------------------------------------------- /test/fixtures/create-conditional-expression/negated-identifier/code.js: -------------------------------------------------------------------------------- 1 | clsx(selected && classes.selected, !showLabel && !selected && classes.iconOnly); 2 | -------------------------------------------------------------------------------- /test/fixtures/create-conditional-expression/negated-identifier/output.js: -------------------------------------------------------------------------------- 1 | clsx(selected ? classes.selected : !showLabel && classes.iconOnly); 2 | -------------------------------------------------------------------------------- /test/fixtures/create-conditional-expression/reversed-binary-expression/code.js: -------------------------------------------------------------------------------- 1 | clsx(foo === 'bar' && baz, 'bar' !== foo && foobar); 2 | -------------------------------------------------------------------------------- /test/fixtures/create-conditional-expression/reversed-binary-expression/output.js: -------------------------------------------------------------------------------- 1 | clsx(foo === 'bar' ? baz : foobar); 2 | -------------------------------------------------------------------------------- /test/fixtures/create-lookup/identifiers/code.js: -------------------------------------------------------------------------------- 1 | clsx({ 2 | [foo.a]: 'up' === bar, 3 | [foo.b]: bar === 'down' && !inProp && collapsedHeight === '0px', 4 | [foo.c]: baz === 'left', 5 | [foo.d]: baz === 'right', 6 | }); 7 | -------------------------------------------------------------------------------- /test/fixtures/create-lookup/identifiers/output.js: -------------------------------------------------------------------------------- 1 | clsx( 2 | { 3 | up: foo.a, 4 | down: !inProp && collapsedHeight === '0px' && foo.b, 5 | }[bar], 6 | { 7 | left: foo.c, 8 | right: foo.d, 9 | }[baz] 10 | ); 11 | -------------------------------------------------------------------------------- /test/fixtures/create-lookup/member-expression/code.js: -------------------------------------------------------------------------------- 1 | // https://github.com/DestinyItemManager/DIM/blob/a441eff6be8cbdd48477baa9ac30d9147153a08e/src/app/inventory/InventoryItem.tsx#L84-L89 2 | clsx({ 3 | [styles.subclassPathTop]: 'top' === subclassPath.position, 4 | [styles.subclassPathMiddle]: subclassPath.position === 'middle', 5 | [styles.subclassPathBottom]: subclassPath.position === 'bottom', 6 | }); 7 | -------------------------------------------------------------------------------- /test/fixtures/create-lookup/member-expression/output.js: -------------------------------------------------------------------------------- 1 | clsx( 2 | { 3 | top: styles.subclassPathTop, 4 | middle: styles.subclassPathMiddle, 5 | bottom: styles.subclassPathBottom, 6 | }[subclassPath.position] 7 | ); 8 | -------------------------------------------------------------------------------- /test/fixtures/create-lookup/optional-member-expression/code.js: -------------------------------------------------------------------------------- 1 | // https://github.com/DestinyItemManager/DIM/blob/b51598fcb6f6caa2b7e3e82a13ca751298f1c708/src/app/inventory/InventoryItem.tsx#L77-L82 2 | clsx({ 3 | [styles.subclassPathTop]: 'top' === subclassPath?.position, 4 | [styles.subclassPathMiddle]: subclassPath?.position === 'middle', 5 | [styles.subclassPathBottom]: subclassPath?.position === 'bottom', 6 | }); 7 | -------------------------------------------------------------------------------- /test/fixtures/create-lookup/optional-member-expression/output.js: -------------------------------------------------------------------------------- 1 | clsx( 2 | { 3 | top: styles.subclassPathTop, 4 | middle: styles.subclassPathMiddle, 5 | bottom: styles.subclassPathBottom, 6 | }[subclassPath?.position] 7 | ); 8 | -------------------------------------------------------------------------------- /test/fixtures/extract-object-properties/computed/code.js: -------------------------------------------------------------------------------- 1 | clsx({ [foo]: bar }); 2 | -------------------------------------------------------------------------------- /test/fixtures/extract-object-properties/computed/output.js: -------------------------------------------------------------------------------- 1 | clsx(bar && foo); 2 | -------------------------------------------------------------------------------- /test/fixtures/extract-object-properties/identifier/code.js: -------------------------------------------------------------------------------- 1 | clsx({ foo: bar }); 2 | -------------------------------------------------------------------------------- /test/fixtures/extract-object-properties/identifier/output.js: -------------------------------------------------------------------------------- 1 | clsx(bar && 'foo'); 2 | -------------------------------------------------------------------------------- /test/fixtures/extract-object-properties/object-method/code.js: -------------------------------------------------------------------------------- 1 | clsx({ foo() {}, [bar]() {}, 'foo-bar'() {} }); 2 | -------------------------------------------------------------------------------- /test/fixtures/extract-object-properties/object-method/output.js: -------------------------------------------------------------------------------- 1 | clsx('foo foo-bar', bar); 2 | -------------------------------------------------------------------------------- /test/fixtures/extract-object-properties/spread-element/code.js: -------------------------------------------------------------------------------- 1 | clsx({ ...foo }); 2 | -------------------------------------------------------------------------------- /test/fixtures/extract-object-properties/spread-element/output.js: -------------------------------------------------------------------------------- 1 | clsx(foo); 2 | -------------------------------------------------------------------------------- /test/fixtures/extract-object-properties/string/code.js: -------------------------------------------------------------------------------- 1 | clsx({ 'foo-bar': bar }); 2 | -------------------------------------------------------------------------------- /test/fixtures/extract-object-properties/string/output.js: -------------------------------------------------------------------------------- 1 | clsx(bar && 'foo-bar'); 2 | -------------------------------------------------------------------------------- /test/fixtures/find-function-names/namespace-import/code.js: -------------------------------------------------------------------------------- 1 | import * as clsx from 'clsx'; 2 | 3 | clsx({ foo: true }); 4 | -------------------------------------------------------------------------------- /test/fixtures/find-function-names/namespace-import/output.js: -------------------------------------------------------------------------------- 1 | import * as clsx from 'clsx'; 2 | clsx('foo'); 3 | -------------------------------------------------------------------------------- /test/fixtures/find-function-names/no-import/code.js: -------------------------------------------------------------------------------- 1 | clsx({ foo: true }); 2 | -------------------------------------------------------------------------------- /test/fixtures/find-function-names/no-import/output.js: -------------------------------------------------------------------------------- 1 | clsx({ 2 | foo: true, 3 | }); 4 | -------------------------------------------------------------------------------- /test/fixtures/find-function-names/options.json: -------------------------------------------------------------------------------- 1 | { 2 | "functionNames": [] 3 | } 4 | -------------------------------------------------------------------------------- /test/fixtures/find-function-names/unspecified-require/code.js: -------------------------------------------------------------------------------- 1 | const myFunction = require('foo-library'); 2 | 3 | myFunction({ foo: true }); 4 | -------------------------------------------------------------------------------- /test/fixtures/find-function-names/unspecified-require/output.js: -------------------------------------------------------------------------------- 1 | const myFunction = require('foo-library'); 2 | 3 | myFunction({ 4 | foo: true, 5 | }); 6 | -------------------------------------------------------------------------------- /test/fixtures/find-function-names/unused-import/code.js: -------------------------------------------------------------------------------- 1 | import clsx from 'clsx'; 2 | const cn = require('classnames'); 3 | 4 | const x = clsx('foo'); 5 | const y = cn('bar'); 6 | -------------------------------------------------------------------------------- /test/fixtures/find-function-names/unused-import/options.json: -------------------------------------------------------------------------------- 1 | { 2 | "removeUnnecessaryCalls": true 3 | } 4 | -------------------------------------------------------------------------------- /test/fixtures/find-function-names/unused-import/output.js: -------------------------------------------------------------------------------- 1 | const x = 'foo'; 2 | const y = 'bar'; 3 | -------------------------------------------------------------------------------- /test/fixtures/find-function-names/using-custom-library/code.js: -------------------------------------------------------------------------------- 1 | import myCustomFunction from 'my-custom-library'; 2 | 3 | myCustomFunction({ foo: true }); 4 | -------------------------------------------------------------------------------- /test/fixtures/find-function-names/using-custom-library/options.json: -------------------------------------------------------------------------------- 1 | { 2 | "libraries": ["my-custom-library"] 3 | } 4 | -------------------------------------------------------------------------------- /test/fixtures/find-function-names/using-custom-library/output.js: -------------------------------------------------------------------------------- 1 | import myCustomFunction from 'my-custom-library'; 2 | myCustomFunction('foo'); 3 | -------------------------------------------------------------------------------- /test/fixtures/find-function-names/using-import/code.js: -------------------------------------------------------------------------------- 1 | import fooFunction from 'clsx'; 2 | 3 | fooFunction({ foo: true }); 4 | -------------------------------------------------------------------------------- /test/fixtures/find-function-names/using-import/output.js: -------------------------------------------------------------------------------- 1 | import fooFunction from 'clsx'; 2 | fooFunction('foo'); 3 | -------------------------------------------------------------------------------- /test/fixtures/find-function-names/using-require/code.js: -------------------------------------------------------------------------------- 1 | const fooFunction = require('clsx'); 2 | 3 | fooFunction({ foo: true }); 4 | -------------------------------------------------------------------------------- /test/fixtures/find-function-names/using-require/output.js: -------------------------------------------------------------------------------- 1 | const fooFunction = require('clsx'); 2 | 3 | fooFunction('foo'); 4 | -------------------------------------------------------------------------------- /test/fixtures/flatten-arrays/nested-arrays/code.js: -------------------------------------------------------------------------------- 1 | clsx(['foo', ['bar', ['baz']]]); 2 | -------------------------------------------------------------------------------- /test/fixtures/flatten-arrays/nested-arrays/output.js: -------------------------------------------------------------------------------- 1 | clsx('foo bar baz'); 2 | -------------------------------------------------------------------------------- /test/fixtures/flatten-arrays/single-array/code.js: -------------------------------------------------------------------------------- 1 | clsx(['foo']); 2 | -------------------------------------------------------------------------------- /test/fixtures/flatten-arrays/single-array/output.js: -------------------------------------------------------------------------------- 1 | clsx('foo'); 2 | -------------------------------------------------------------------------------- /test/fixtures/optimize-expressions/duplicate-nodes/code.js: -------------------------------------------------------------------------------- 1 | clsx(foo && bar && bar); 2 | -------------------------------------------------------------------------------- /test/fixtures/optimize-expressions/duplicate-nodes/output.js: -------------------------------------------------------------------------------- 1 | clsx(foo && bar); 2 | -------------------------------------------------------------------------------- /test/fixtures/optimize-expressions/odd-even-check/code.js: -------------------------------------------------------------------------------- 1 | clsx(index % 2 === 0 && 'foo', index % 2 === 1 && 'bar', index % 2 !== 1 && 'baz'); 2 | -------------------------------------------------------------------------------- /test/fixtures/optimize-expressions/odd-even-check/output.js: -------------------------------------------------------------------------------- 1 | clsx(index % 2 === 0 ? 'foo baz' : 'bar'); 2 | -------------------------------------------------------------------------------- /test/fixtures/optimize-expressions/operator-change/code.js: -------------------------------------------------------------------------------- 1 | clsx((foo && foo) || ''); 2 | -------------------------------------------------------------------------------- /test/fixtures/optimize-expressions/operator-change/output.js: -------------------------------------------------------------------------------- 1 | clsx(foo); 2 | -------------------------------------------------------------------------------- /test/fixtures/optimize-expressions/unnecessary-conditional/code.js: -------------------------------------------------------------------------------- 1 | clsx(foo ? 'foo' : '', bar ? null : 'not-bar', { 2 | [baz]: isBaz ? true : false, 3 | }); 4 | -------------------------------------------------------------------------------- /test/fixtures/optimize-expressions/unnecessary-conditional/output.js: -------------------------------------------------------------------------------- 1 | clsx(foo && 'foo', !bar && 'not-bar', isBaz && baz); 2 | -------------------------------------------------------------------------------- /test/fixtures/optimize-expressions/unnecessary-or/code.js: -------------------------------------------------------------------------------- 1 | clsx(foo || '', bar || null, baz || false); 2 | -------------------------------------------------------------------------------- /test/fixtures/optimize-expressions/unnecessary-or/output.js: -------------------------------------------------------------------------------- 1 | clsx(foo, bar, baz); 2 | -------------------------------------------------------------------------------- /test/fixtures/options.json: -------------------------------------------------------------------------------- 1 | { 2 | "functionNames": ["clsx"], 3 | "removeUnnecessaryCalls": false 4 | } 5 | -------------------------------------------------------------------------------- /test/fixtures/proptypes/member-access/code.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types'; 2 | import clsx from 'clsx'; 3 | 4 | function foo(props) { 5 | const x = clsx(props.position === 'top' && classes.x, props.position === 'bottom' && classes.y); 6 | } 7 | 8 | foo.propTypes = { 9 | position: PropTypes.oneOf(['top', 'bottom']).isRequired, 10 | }; 11 | -------------------------------------------------------------------------------- /test/fixtures/proptypes/member-access/output.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types'; 2 | import clsx from 'clsx'; 3 | 4 | function foo(props) { 5 | const x = clsx(props.position === 'top' ? classes.x : classes.y); 6 | } 7 | 8 | foo.propTypes = { 9 | position: PropTypes.oneOf(['top', 'bottom']).isRequired, 10 | }; 11 | -------------------------------------------------------------------------------- /test/fixtures/proptypes/object-pattern-assignment/code.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types'; 2 | import clsx from 'clsx'; 3 | 4 | function foo(props) { 5 | const { position = 'top', anchor: a = 'left' } = props; 6 | const x = clsx( 7 | position === 'top' && classes.x, 8 | position === 'bottom' && classes.y, 9 | a === 'left' && classes.z, 10 | a === 'right' && classes.anchor 11 | ); 12 | } 13 | 14 | foo.propTypes = { 15 | position: PropTypes.oneOf(['top', 'bottom']), 16 | anchor: PropTypes.oneOf(['left', 'right']), 17 | }; 18 | -------------------------------------------------------------------------------- /test/fixtures/proptypes/object-pattern-assignment/output.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types'; 2 | import clsx from 'clsx'; 3 | 4 | function foo(props) { 5 | const { position = 'top', anchor: a = 'left' } = props; 6 | const x = clsx( 7 | position === 'top' ? classes.x : classes.y, 8 | a === 'left' ? classes.z : classes.anchor 9 | ); 10 | } 11 | 12 | foo.propTypes = { 13 | position: PropTypes.oneOf(['top', 'bottom']), 14 | anchor: PropTypes.oneOf(['left', 'right']), 15 | }; 16 | -------------------------------------------------------------------------------- /test/fixtures/proptypes/object-pattern-inline/code.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types'; 2 | import clsx from 'clsx'; 3 | 4 | function foo({ position: p }) { 5 | const x = clsx(p === 'top' && classes.x, p === 'bottom' && classes.y); 6 | } 7 | 8 | foo.propTypes = { 9 | position: PropTypes.oneOf(['top', 'bottom']).isRequired, 10 | }; 11 | -------------------------------------------------------------------------------- /test/fixtures/proptypes/object-pattern-inline/output.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types'; 2 | import clsx from 'clsx'; 3 | 4 | function foo({ position: p }) { 5 | const x = clsx(p === 'top' ? classes.x : classes.y); 6 | } 7 | 8 | foo.propTypes = { 9 | position: PropTypes.oneOf(['top', 'bottom']).isRequired, 10 | }; 11 | -------------------------------------------------------------------------------- /test/fixtures/proptypes/object-pattern-rest/code.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types'; 2 | import clsx from 'clsx'; 3 | 4 | function foo(props) { 5 | const { position: p, ...rest } = props; 6 | const x = clsx( 7 | p === 'top' && classes.x, 8 | p === 'bottom' && classes.y, 9 | rest.anchor === 'left' && classes.z, 10 | rest.anchor === 'right' && classes.a 11 | ); 12 | } 13 | 14 | foo.propTypes = { 15 | position: PropTypes.oneOf(['top', 'bottom']).isRequired, 16 | anchor: PropTypes.oneOf(['left', 'right']).isRequired, 17 | }; 18 | -------------------------------------------------------------------------------- /test/fixtures/proptypes/object-pattern-rest/output.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types'; 2 | import clsx from 'clsx'; 3 | 4 | function foo(props) { 5 | const { position: p, ...rest } = props; 6 | const x = clsx( 7 | p === 'top' ? classes.x : classes.y, 8 | rest.anchor === 'left' ? classes.z : classes.a 9 | ); 10 | } 11 | 12 | foo.propTypes = { 13 | position: PropTypes.oneOf(['top', 'bottom']).isRequired, 14 | anchor: PropTypes.oneOf(['left', 'right']).isRequired, 15 | }; 16 | -------------------------------------------------------------------------------- /test/fixtures/proptypes/object-pattern/code.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types'; 2 | import clsx from 'clsx'; 3 | 4 | function foo(props) { 5 | const { position: p } = props; 6 | const x = clsx(p === 'top' && classes.x, p === 'bottom' && classes.y); 7 | } 8 | 9 | foo.propTypes = { 10 | position: PropTypes.oneOf(['top', 'bottom']).isRequired, 11 | }; 12 | -------------------------------------------------------------------------------- /test/fixtures/proptypes/object-pattern/output.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types'; 2 | import clsx from 'clsx'; 3 | 4 | function foo(props) { 5 | const { position: p } = props; 6 | const x = clsx(p === 'top' ? classes.x : classes.y); 7 | } 8 | 9 | foo.propTypes = { 10 | position: PropTypes.oneOf(['top', 'bottom']).isRequired, 11 | }; 12 | -------------------------------------------------------------------------------- /test/fixtures/proptypes/variable-declaration/code.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types'; 2 | import clsx from 'clsx'; 3 | 4 | function foo(props) { 5 | const p = props.position; 6 | const x = clsx(p === 'top' && classes.x, p === 'bottom' && classes.y); 7 | } 8 | 9 | foo.propTypes = { 10 | position: PropTypes.oneOf(['top', 'bottom']).isRequired, 11 | }; 12 | -------------------------------------------------------------------------------- /test/fixtures/proptypes/variable-declaration/output.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types'; 2 | import clsx from 'clsx'; 3 | 4 | function foo(props) { 5 | const p = props.position; 6 | const x = clsx(p === 'top' ? classes.x : classes.y); 7 | } 8 | 9 | foo.propTypes = { 10 | position: PropTypes.oneOf(['top', 'bottom']).isRequired, 11 | }; 12 | -------------------------------------------------------------------------------- /test/fixtures/referenced-object/multiple-references/code.js: -------------------------------------------------------------------------------- 1 | // https://github.com/DestinyItemManager/DIM/blob/a441eff6be8cbdd48477baa9ac30d9147153a08e/src/app/inventory/InventoryItem.tsx#L84-L89 2 | 3 | const itemStyles = { 4 | [styles.searchHidden]: searchHidden, 5 | [styles.subclassPathTop]: subclassPath && subclassPath.position === 'top', 6 | [styles.subclassPathMiddle]: subclassPath && subclassPath.position === 'middle', 7 | [styles.subclassPathBottom]: subclassPath && subclassPath.position === 'bottom', 8 | }; 9 | 10 | // https://github.com/DestinyItemManager/DIM/blob/a441eff6be8cbdd48477baa9ac30d9147153a08e/src/app/inventory/InventoryItem.tsx#L102 11 | clsx('foo', itemStyles); 12 | 13 | clsx('bar', itemStyles); 14 | -------------------------------------------------------------------------------- /test/fixtures/referenced-object/multiple-references/output.js: -------------------------------------------------------------------------------- 1 | const itemStyles = clsx( 2 | subclassPath && 3 | { 4 | top: styles.subclassPathTop, 5 | middle: styles.subclassPathMiddle, 6 | bottom: styles.subclassPathBottom, 7 | }[subclassPath.position], 8 | searchHidden && styles.searchHidden 9 | ); 10 | clsx('foo', itemStyles); 11 | clsx('bar', itemStyles); 12 | -------------------------------------------------------------------------------- /test/fixtures/referenced-object/transformed-callee/.babelrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: [require.resolve('@babel/plugin-transform-modules-commonjs')], 3 | }; 4 | -------------------------------------------------------------------------------- /test/fixtures/referenced-object/transformed-callee/code.js: -------------------------------------------------------------------------------- 1 | import clsx from 'clsx'; 2 | 3 | const stateClasses = { 4 | checked: isChecked, 5 | }; 6 | 7 | clsx(stateClasses); 8 | -------------------------------------------------------------------------------- /test/fixtures/referenced-object/transformed-callee/output.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var _clsx = _interopRequireDefault(require('clsx')); 4 | 5 | function _interopRequireDefault(obj) { 6 | return obj && obj.__esModule ? obj : { default: obj }; 7 | } 8 | 9 | const stateClasses = (0, _clsx.default)(isChecked && 'checked'); 10 | (0, _clsx.default)(stateClasses); 11 | -------------------------------------------------------------------------------- /test/fixtures/remove-unnecessary-calls/array/code.js: -------------------------------------------------------------------------------- 1 | const x1 = clsx(canDrop && ['on-drag-enter', isOver && 'on-drag-hover']); 2 | const x2 = clsx(canDrop && [!isOver && 'on-drag-enter', isOver && 'on-drag-hover']); 3 | -------------------------------------------------------------------------------- /test/fixtures/remove-unnecessary-calls/array/output.js: -------------------------------------------------------------------------------- 1 | const x1 = canDrop ? 'on-drag-enter' + (isOver ? ' on-drag-hover' : '') : ''; 2 | const x2 = canDrop ? (isOver ? 'on-drag-hover' : 'on-drag-enter') : ''; 3 | -------------------------------------------------------------------------------- /test/fixtures/remove-unnecessary-calls/nested-conditional/code.js: -------------------------------------------------------------------------------- 1 | const x = clsx({ 2 | foo1: true, 3 | foo2: foo, 4 | foo3: foo, 5 | foo4: !foo && !bar, 6 | foo5: !foo && bar, 7 | foo6: true, 8 | }); 9 | -------------------------------------------------------------------------------- /test/fixtures/remove-unnecessary-calls/nested-conditional/output.js: -------------------------------------------------------------------------------- 1 | const x = 'foo1 foo6 ' + (foo ? 'foo2 foo3' : bar ? 'foo5' : 'foo4'); 2 | -------------------------------------------------------------------------------- /test/fixtures/remove-unnecessary-calls/one-conditional-argument/code.js: -------------------------------------------------------------------------------- 1 | const baseClass = clsx(foo ? 'a' : 'b'); 2 | -------------------------------------------------------------------------------- /test/fixtures/remove-unnecessary-calls/one-conditional-argument/output.js: -------------------------------------------------------------------------------- 1 | const baseClass = foo ? 'a' : 'b'; 2 | -------------------------------------------------------------------------------- /test/fixtures/remove-unnecessary-calls/options.json: -------------------------------------------------------------------------------- 1 | { 2 | "removeUnnecessaryCalls": true 3 | } 4 | -------------------------------------------------------------------------------- /test/fixtures/remove-unnecessary-calls/single-logical-expression/code.js: -------------------------------------------------------------------------------- 1 | const foo = 'test'; 2 | 3 | const x = clsx(foo && 'bar'); 4 | -------------------------------------------------------------------------------- /test/fixtures/remove-unnecessary-calls/single-logical-expression/output.js: -------------------------------------------------------------------------------- 1 | const foo = 'test'; 2 | const x = foo ? 'bar' : ''; 3 | -------------------------------------------------------------------------------- /test/fixtures/remove-unnecessary-calls/string-and-conditional-argument/code.js: -------------------------------------------------------------------------------- 1 | const x1 = clsx('foo bar', foo ? 'a' : 'b'); 2 | const x2 = clsx(foo ? 'a' : 'b', 'foo bar'); 3 | const x3 = clsx('foo bar', foo ? 'a' : ``); 4 | const x4 = clsx('foo bar', foo ? 'a' : ''); 5 | -------------------------------------------------------------------------------- /test/fixtures/remove-unnecessary-calls/string-and-conditional-argument/output.js: -------------------------------------------------------------------------------- 1 | const x1 = 'foo bar ' + (foo ? 'a' : 'b'); 2 | const x2 = 'foo bar ' + (foo ? 'a' : 'b'); 3 | const x3 = 'foo bar' + (foo ? ' a' : ''); 4 | const x4 = 'foo bar' + (foo ? ' a' : ''); 5 | -------------------------------------------------------------------------------- /test/fixtures/remove-unnecessary-calls/string-and-logical-expression/code.js: -------------------------------------------------------------------------------- 1 | const x = clsx(fooVar && 'btn-primary', { 2 | btn: true, 3 | }); 4 | -------------------------------------------------------------------------------- /test/fixtures/remove-unnecessary-calls/string-and-logical-expression/output.js: -------------------------------------------------------------------------------- 1 | const x = 'btn' + (fooVar ? ' btn-primary' : ''); 2 | -------------------------------------------------------------------------------- /test/fixtures/remove-unnecessary-calls/string-argument/code.js: -------------------------------------------------------------------------------- 1 | const baseClass = clsx('foo bar'); 2 | -------------------------------------------------------------------------------- /test/fixtures/remove-unnecessary-calls/string-argument/output.js: -------------------------------------------------------------------------------- 1 | const baseClass = 'foo bar'; 2 | -------------------------------------------------------------------------------- /test/fixtures/remove-unnecessary-calls/template-literal/code.js: -------------------------------------------------------------------------------- 1 | const x = clsx(`foo foo${foo} bar`); 2 | -------------------------------------------------------------------------------- /test/fixtures/remove-unnecessary-calls/template-literal/output.js: -------------------------------------------------------------------------------- 1 | const x = `foo foo${foo} bar`; 2 | -------------------------------------------------------------------------------- /test/fixtures/remove-unnecessary-calls/two-conditional-arguments/code.js: -------------------------------------------------------------------------------- 1 | const baseClass = clsx(foo ? 'a' : 'b', bar ? 'c' : 'd'); 2 | -------------------------------------------------------------------------------- /test/fixtures/remove-unnecessary-calls/two-conditional-arguments/output.js: -------------------------------------------------------------------------------- 1 | const baseClass = (foo ? 'a ' : 'b ') + (bar ? 'c' : 'd'); 2 | -------------------------------------------------------------------------------- /test/fixtures/strip-literals/boolean-literal/code.js: -------------------------------------------------------------------------------- 1 | clsx({ 2 | [classes.root]: false, 3 | [classes.cellHide]: true, 4 | [classes.cellStacked]: options.responsive === 'stacked', 5 | 'datatables-noprint': !print, 6 | }); 7 | -------------------------------------------------------------------------------- /test/fixtures/strip-literals/boolean-literal/output.js: -------------------------------------------------------------------------------- 1 | clsx( 2 | classes.cellHide, 3 | !print && 'datatables-noprint', 4 | options.responsive === 'stacked' && classes.cellStacked 5 | ); 6 | -------------------------------------------------------------------------------- /test/fixtures/strip-literals/string-literal/code.js: -------------------------------------------------------------------------------- 1 | clsx('', 'foo', { 2 | [classes.root]: 'true', 3 | [classes.cellHide]: '', 4 | [classes.cellHide2]: ``, 5 | [classes.cellStacked]: options.responsive === 'stacked', 6 | 'datatables-noprint': !print, 7 | }); 8 | -------------------------------------------------------------------------------- /test/fixtures/strip-literals/string-literal/output.js: -------------------------------------------------------------------------------- 1 | clsx( 2 | 'foo', 3 | classes.root, 4 | !print && 'datatables-noprint', 5 | options.responsive === 'stacked' && classes.cellStacked 6 | ); 7 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | import pluginTester from 'babel-plugin-tester'; 2 | import optimizeClsx from '../src/index'; 3 | import path from 'path'; 4 | import fs from 'fs'; 5 | import prettier from 'prettier'; 6 | 7 | const prettierConfig = prettier.resolveConfig.sync(path.join(__dirname, '../.prettierrc')); 8 | prettierConfig.parser = 'babel'; 9 | 10 | function format(code) { 11 | // babel-plugin-tester calls trim when it reads the output file 12 | // so we have to trim off whitespace as well 13 | return prettier.format(code, prettierConfig).trim(); 14 | } 15 | 16 | const options = { 17 | pluginName: 'optimize-clsx', 18 | plugin: optimizeClsx, 19 | endOfLine: 'preserve', 20 | babelOptions: { 21 | configFile: false, 22 | compact: false, 23 | comments: false, 24 | }, 25 | formatResult: format, 26 | fixtures: path.join(__dirname, 'fixtures'), 27 | }; 28 | 29 | if (process.env.DEV_MODE) { 30 | const devDir = path.join(__dirname, 'dev_fixture'); 31 | const defaultFixture = path.join(devDir, 'default'); 32 | if (fs.existsSync(devDir) === false) { 33 | // Setup a default test to make it easier to start debugging 34 | fs.mkdirSync(devDir); 35 | fs.mkdirSync(defaultFixture); 36 | fs.writeFileSync(path.join(defaultFixture, 'code.js'), 'clsx(foo && bar);'); 37 | process.exit(0); 38 | } 39 | 40 | const outputFile = path.join(defaultFixture, 'output.js'); 41 | if (fs.existsSync(outputFile)) { 42 | fs.unlinkSync(outputFile); 43 | } 44 | 45 | pluginTester({ 46 | ...options, 47 | fixtures: devDir, 48 | }); 49 | } else if (process.env.TEST_BUILD) { 50 | const pluginPath = path.join(__dirname, '../dist/index.js'); 51 | if (!fs.existsSync(pluginPath)) { 52 | throw new Error('File not found, run "yarn build" first'); 53 | } 54 | 55 | pluginTester({ 56 | ...options, 57 | pluginName: 'optimize-clsx-BUILD', 58 | plugin: require(pluginPath), 59 | }); 60 | } else { 61 | pluginTester(options); 62 | } 63 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "es2017", 5 | "lib": ["es2017"], 6 | "allowJs": true, 7 | "esModuleInterop": true, 8 | "strict": true, 9 | "noEmit": true 10 | }, 11 | "include": ["src"] 12 | } 13 | --------------------------------------------------------------------------------