Skip to content

Commit 602ff4b

Browse files
committed
fix: return same parser when babel options are the same
1 parent 310a287 commit 602ff4b

File tree

4 files changed

+203
-60
lines changed

4 files changed

+203
-60
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@
8585
"@babel/plugin-proposal-nullish-coalescing-operator": "^7.12.1",
8686
"@babel/plugin-proposal-object-rest-spread": "^7.12.1",
8787
"@babel/plugin-proposal-optional-chaining": "^7.12.7",
88+
"@babel/plugin-proposal-pipeline-operator": "^7.18.9",
8889
"@babel/plugin-syntax-dynamic-import": "^7.0.0",
8990
"@babel/plugin-transform-runtime": "^7.12.10",
9091
"@babel/preset-env": "^7.12.11",

src/index.ts

Lines changed: 104 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -144,12 +144,48 @@ const resolve: (
144144
) => Promise<string> = promisify(_resolve as any)
145145

146146
const requiredPaths: string[] = []
147-
const syncCache: Map<string, Parser> = new Map()
148-
const asyncCache: Map<string, Promise<Parser>> = new Map()
147+
148+
class Cache<K, V> {
149+
syncCache: Map<K, V> = new Map()
150+
asyncCache: Map<K, Promise<V>> = new Map()
151+
152+
getSync(key: K, fetch: () => V): V {
153+
let result = this.syncCache.get(key)
154+
if (!result) {
155+
result = fetch()
156+
this.syncCache.set(key, result)
157+
this.asyncCache.set(key, Promise.resolve(result))
158+
}
159+
return result
160+
}
161+
162+
getAsync(key: K, fetch: () => Promise<V>): Promise<V> {
163+
let result = this.asyncCache.get(key)
164+
if (!result) {
165+
result = fetch()
166+
this.asyncCache.set(key, result)
167+
result.then((value) => {
168+
// check if cache was cleared before this point
169+
if (this.asyncCache.get(key) === result) {
170+
this.syncCache.set(key, value)
171+
}
172+
})
173+
}
174+
return result
175+
}
176+
177+
clear() {
178+
this.syncCache.clear()
179+
this.asyncCache.clear()
180+
}
181+
}
182+
183+
const dirParserCache = new Cache<string, Parser>()
184+
const babelrcParserCache = new Cache<string, Parser>()
149185

150186
export function clearCache(): void {
151-
syncCache.clear()
152-
asyncCache.clear()
187+
dirParserCache.clear()
188+
babelrcParserCache.clear()
153189
for (const path of requiredPaths) {
154190
delete require.cache[path]
155191
}
@@ -170,13 +206,10 @@ function createParserFromConfig(babelParser: BabelParser, config: any): Parser {
170206
}
171207

172208
export function getParserSync(file: string, options?: ParserOptions): Parser {
173-
let result
174209
const parentDir = Path.dirname(file)
175210
const extname = Path.extname(file)
176211
const cacheKey = `${parentDir}${Path.delimiter}${extname}`
177-
result = syncCache.get(cacheKey)
178-
179-
if (!result) {
212+
const parser = dirParserCache.getSync(cacheKey, () => {
180213
try {
181214
const babelPath = _resolve.sync('@babel/core', {
182215
basedir: parentDir,
@@ -192,73 +225,85 @@ export function getParserSync(file: string, options?: ParserOptions): Parser {
192225
const parser = require(parserPath)
193226
requiredPaths.push(parserPath)
194227

195-
const config = babel.loadOptionsSync({
228+
const loadOpts = {
196229
filename: file,
197230
cwd: Path.dirname(file),
198231
rootMode: 'upward-optional',
199-
})
200-
result = createParserFromConfig(parser, config)
232+
}
233+
const partial = babel.loadPartialConfigSync(loadOpts)
234+
const babelrc = partial.babelrc || partial.config
235+
const getParser = () => {
236+
const config = babel.loadOptionsSync(loadOpts)
237+
return createParserFromConfig(parser, config)
238+
}
239+
return babelrc
240+
? babelrcParserCache.getSync(
241+
JSON.stringify([babelrc, extname]),
242+
getParser
243+
)
244+
: getParser()
201245
} catch (error) {
202-
result =
203-
extname === '.tsx' ? tsxParser : extname === '.ts' ? tsParser : jsParser
246+
return extname === '.tsx'
247+
? tsxParser
248+
: extname === '.ts'
249+
? tsParser
250+
: jsParser
204251
}
205-
syncCache.set(cacheKey, result)
206-
asyncCache.set(cacheKey, Promise.resolve(result))
207-
}
208-
return !options || isEmpty(options) ? result : result.bindParserOpts(options)
252+
})
253+
return !options || isEmpty(options) ? parser : parser.bindParserOpts(options)
209254
}
210255

211256
export async function getParserAsync(
212257
file: string,
213258
options?: ParserOptions
214259
): Promise<Parser> {
215-
let promise
216-
if (/\.ts$/.test(file)) promise = Promise.resolve(tsParser)
217-
else if (/\.tsx$/.test(file)) promise = Promise.resolve(tsxParser)
218-
else {
219-
const parentDir = Path.dirname(file)
220-
const extname = Path.extname(file)
221-
const cacheKey = `${parentDir}${Path.delimiter}${extname}`
222-
223-
promise = asyncCache.get(cacheKey)
260+
const parentDir = Path.dirname(file)
261+
const extname = Path.extname(file)
262+
const cacheKey = `${parentDir}${Path.delimiter}${extname}`
224263

225-
if (!promise) {
226-
promise = (async (): Promise<Parser> => {
227-
let result
228-
try {
229-
const babelPath = await resolve('@babel/core', {
230-
basedir: parentDir,
231-
})
232-
const babel = await import(babelPath)
233-
requiredPaths.push(babelPath)
264+
const parser = await dirParserCache.getAsync(
265+
cacheKey,
266+
async (): Promise<Parser> => {
267+
try {
268+
const babelPath = await resolve('@babel/core', {
269+
basedir: parentDir,
270+
})
271+
const babel = await import(babelPath)
272+
requiredPaths.push(babelPath)
234273

235-
const parserPath = await resolve('@babel/parser', {
236-
basedir: parentDir,
237-
})
238-
const parser = await import(parserPath)
239-
requiredPaths.push(parserPath)
274+
const parserPath = await resolve('@babel/parser', {
275+
basedir: parentDir,
276+
})
277+
const parser = await import(parserPath)
278+
requiredPaths.push(parserPath)
240279

241-
const config = await babel.loadOptionsAsync({
242-
filename: file,
243-
cwd: parentDir,
244-
})
245-
result = createParserFromConfig(parser, config)
246-
} catch (error) {
247-
result =
248-
extname === '.tsx'
249-
? tsxParser
250-
: extname === '.ts'
251-
? tsParser
252-
: jsParser
280+
const loadOpts = {
281+
filename: file,
282+
cwd: parentDir,
283+
rootMode: 'upward-optional',
284+
}
285+
const partial = await babel.loadPartialConfigAsync(loadOpts)
286+
const babelrc = partial.babelrc || partial.config
287+
const getParser = async (): Promise<Parser> => {
288+
const config = await babel.loadOptionsAsync(loadOpts)
289+
return createParserFromConfig(parser, config)
253290
}
254-
syncCache.set(cacheKey, result)
255-
return result
256-
})()
257-
asyncCache.set(cacheKey, promise)
291+
return babelrc
292+
? await babelrcParserCache.getAsync(
293+
JSON.stringify([babelrc, extname]),
294+
getParser
295+
)
296+
: await getParser()
297+
} catch (error) {
298+
return extname === '.tsx'
299+
? tsxParser
300+
: extname === '.ts'
301+
? tsParser
302+
: jsParser
303+
}
258304
}
259-
}
260-
const result = await promise
261-
return !options || isEmpty(options) ? result : result.bindParserOpts(options)
305+
)
306+
return !options || isEmpty(options) ? parser : parser.bindParserOpts(options)
262307
}
263308

264309
export function parseSync(

test/index.spec.ts

Lines changed: 78 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,51 @@
11
/* eslint-env mocha */
22

33
import * as Path from 'path'
4-
import { parseSync, parseAsync, getParserSync, clearCache } from '../src'
4+
import {
5+
parseSync,
6+
parseAsync,
7+
getParserSync,
8+
clearCache,
9+
getParserAsync,
10+
jsParser,
11+
tsParser,
12+
tsxParser,
13+
} from '../src'
514
import fs from 'fs-extra'
615
import { expect } from 'chai'
716
import os from 'os'
817

918
const fixturesDir = Path.resolve(__dirname, 'fixtures')
1019

20+
describe(`getParserSync`, function () {
21+
this.timeout(10000)
22+
23+
it(`returns same parser when babel config is the same`, async function () {
24+
expect(getParserSync(Path.join(__dirname, 'index.spec.ts'))).to.equal(
25+
getParserSync(Path.resolve(__dirname, '..', 'src', 'index.ts'))
26+
)
27+
expect(getParserSync(Path.join(__dirname, 'index.spec.ts'))).not.to.equal(
28+
tsParser
29+
)
30+
expect(getParserSync(Path.join(__dirname, 'index.spec.ts'))).not.to.equal(
31+
tsxParser
32+
)
33+
expect(getParserSync(Path.join(__dirname, 'index.spec.ts'))).not.to.equal(
34+
getParserSync(Path.resolve(__dirname, 'configure.js'))
35+
)
36+
expect(getParserSync(Path.join(__dirname, 'configure.js'))).not.to.equal(
37+
jsParser
38+
)
39+
expect(getParserSync(Path.join(__dirname, 'index.spec.ts'))).not.to.equal(
40+
getParserSync(Path.resolve(__dirname, '..', 'src', 'index.js.flow'))
41+
)
42+
expect(getParserSync(Path.join(__dirname, 'index.spec.ts'))).not.to.equal(
43+
getParserSync(
44+
Path.resolve(__dirname, 'fixtures', 'babelPipeline', 'test.ts')
45+
)
46+
)
47+
})
48+
})
1149
describe('parseSync', function () {
1250
this.timeout(10000)
1351

@@ -75,6 +113,45 @@ describe('parseSync', function () {
75113
).to.exist
76114
})
77115
})
116+
describe(`getParserAsync`, function () {
117+
this.timeout(10000)
118+
119+
it(`returns same parser when babel config is the same`, async function () {
120+
expect(
121+
await getParserAsync(Path.join(__dirname, 'index.spec.ts'))
122+
).to.equal(
123+
await getParserAsync(Path.resolve(__dirname, '..', 'src', 'index.ts'))
124+
)
125+
expect(
126+
await getParserAsync(Path.join(__dirname, 'index.spec.ts'))
127+
).not.to.equal(tsParser)
128+
expect(
129+
await getParserAsync(Path.join(__dirname, 'index.spec.ts'))
130+
).not.to.equal(tsxParser)
131+
expect(
132+
await getParserAsync(Path.join(__dirname, 'index.spec.ts'))
133+
).not.to.equal(
134+
await getParserAsync(Path.resolve(__dirname, 'configure.js'))
135+
)
136+
expect(
137+
await getParserAsync(Path.join(__dirname, 'configure.js'))
138+
).not.to.equal(jsParser)
139+
expect(
140+
await getParserAsync(Path.join(__dirname, 'index.spec.ts'))
141+
).not.to.equal(
142+
await getParserAsync(
143+
Path.resolve(__dirname, '..', 'src', 'index.js.flow')
144+
)
145+
)
146+
expect(
147+
await getParserAsync(Path.join(__dirname, 'index.spec.ts'))
148+
).not.to.equal(
149+
await getParserAsync(
150+
Path.resolve(__dirname, 'fixtures', 'babelPipeline', 'test.ts')
151+
)
152+
)
153+
})
154+
})
78155
describe(`parseAsync`, function () {
79156
this.timeout(10000)
80157

yarn.lock

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,11 @@
222222
resolved "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.13.0.tgz#806526ce125aed03373bc416a828321e3a6a33af"
223223
integrity sha512-ZPafIPSwzUlAoWT8DKs1W2VyF2gOWthGd5NGFMsBcMMol+ZhK+EQY/e6V96poa6PA/Bh+C9plWN0hXO1uB8AfQ==
224224

225+
"@babel/helper-plugin-utils@^7.18.6", "@babel/helper-plugin-utils@^7.18.9":
226+
version "7.19.0"
227+
resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.19.0.tgz#4796bb14961521f0f8715990bee2fb6e51ce21bf"
228+
integrity sha512-40Ryx7I8mT+0gaNxm8JGTZFUITNqdLAgdg0hXzeVZxVD6nFsdhQvip6v8dqkRHzsz1VFpFAaOCHNn0vKBL7Czw==
229+
225230
"@babel/helper-remap-async-to-generator@^7.12.13":
226231
version "7.12.13"
227232
resolved "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.12.13.tgz#170365f4140e2d20e5c88f8ba23c24468c296878"
@@ -429,6 +434,14 @@
429434
"@babel/helper-skip-transparent-expression-wrappers" "^7.12.1"
430435
"@babel/plugin-syntax-optional-chaining" "^7.8.0"
431436

437+
"@babel/plugin-proposal-pipeline-operator@^7.18.9":
438+
version "7.18.9"
439+
resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-pipeline-operator/-/plugin-proposal-pipeline-operator-7.18.9.tgz#66a60666efd9c29c7ec4d3e2ccb38f9d97994237"
440+
integrity sha512-Pc33e6m8f4MJhRXVCUwiKZNtEm+W2CUPHIL0lyJNtkp+w6d75CLw3gsBKQ81VAMUgT9jVPIEU8gwJ5nJgmJ1Ag==
441+
dependencies:
442+
"@babel/helper-plugin-utils" "^7.18.9"
443+
"@babel/plugin-syntax-pipeline-operator" "^7.18.6"
444+
432445
"@babel/plugin-proposal-private-methods@^7.12.13":
433446
version "7.12.13"
434447
resolved "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.12.13.tgz#ea78a12554d784ecf7fc55950b752d469d9c4a71"
@@ -543,6 +556,13 @@
543556
dependencies:
544557
"@babel/helper-plugin-utils" "^7.8.0"
545558

559+
"@babel/plugin-syntax-pipeline-operator@^7.18.6":
560+
version "7.18.6"
561+
resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-pipeline-operator/-/plugin-syntax-pipeline-operator-7.18.6.tgz#daa44d460bba23478c329a0e8bbee433a681fd43"
562+
integrity sha512-pFtIdQomJtkTHWcNsGXhjJ5YUkL+AxJnP4G+Ol85UO6uT2fpHTPYLLE5bBeRA9cxf25qa/VKsJ3Fi67Gyqe3rA==
563+
dependencies:
564+
"@babel/helper-plugin-utils" "^7.18.6"
565+
546566
"@babel/plugin-syntax-top-level-await@^7.12.13":
547567
version "7.12.13"
548568
resolved "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.12.13.tgz#c5f0fa6e249f5b739727f923540cf7a806130178"

0 commit comments

Comments
 (0)