@@ -23,16 +23,25 @@ const { getBrowsersList } = require(`./browserslist`)
2323 *
2424 * You can find documentation for the custom loader here: https://babeljs.io/docs/en/next/babel-core.html#loadpartialconfig
2525 */
26+
27+ const customOptionsCache = new Map ( )
28+ const configCache = new Map ( )
29+ const babelrcFileToCacheKey = new Map ( )
30+
2631module . exports = babelLoader . custom ( babel => {
27- const toReturn = {
32+ return {
2833 // Passed the loader options.
2934 customOptions ( {
3035 stage = `test` ,
3136 reactRuntime = `classic` ,
3237 rootDir = process . cwd ( ) ,
3338 ...options
3439 } ) {
35- return {
40+ if ( customOptionsCache . has ( stage ) ) {
41+ return customOptionsCache . get ( stage )
42+ }
43+
44+ const toReturn = {
3645 custom : {
3746 stage,
3847 reactRuntime,
@@ -49,11 +58,39 @@ module.exports = babelLoader.custom(babel => {
4958 ...options ,
5059 } ,
5160 }
61+
62+ customOptionsCache . set ( stage , toReturn )
63+
64+ return toReturn
5265 } ,
5366
5467 // Passed Babel's 'PartialConfig' object.
5568 config ( partialConfig , { customOptions } ) {
69+ let configCacheKey = customOptions . stage
70+ if ( partialConfig . hasFilesystemConfig ( ) ) {
71+ // partialConfig.files is a Set that accumulates used config files (absolute paths)
72+ partialConfig . files . forEach ( configFilePath => {
73+ configCacheKey += `_${ configFilePath } `
74+ } )
75+
76+ // after generating configCacheKey add link between babelrc files and cache keys that rely on it
77+ // so we can invalidate memoized configs when used babelrc file changes
78+ partialConfig . files . forEach ( configFilePath => {
79+ let cacheKeysToInvalidate = babelrcFileToCacheKey . get ( configFilePath )
80+ if ( ! cacheKeysToInvalidate ) {
81+ cacheKeysToInvalidate = new Set ( )
82+ babelrcFileToCacheKey . set ( configFilePath , cacheKeysToInvalidate )
83+ }
84+
85+ cacheKeysToInvalidate . add ( configCacheKey )
86+ } )
87+ }
88+
5689 let { options } = partialConfig
90+ if ( configCache . has ( configCacheKey ) ) {
91+ return { ...options , ...configCache . get ( configCacheKey ) }
92+ }
93+
5794 const [
5895 reduxPresets ,
5996 reduxPlugins ,
@@ -101,9 +138,35 @@ module.exports = babelLoader.custom(babel => {
101138 } )
102139 } )
103140
141+ // cache just plugins and presets, because config also includes things like
142+ // filenames - this is mostly to not call `mergeConfigItemOptions` for each file
143+ // as that function call `babel.createConfigItem` and is quite expensive but also
144+ // skips quite a few nested loops on top of that
145+ configCache . set ( configCacheKey , {
146+ plugins : options . plugins ,
147+ presets : options . presets ,
148+ } )
149+
104150 return options
105151 } ,
106152 }
107-
108- return toReturn
109153} )
154+
155+ module . exports . BabelConfigItemsCacheInvalidatorPlugin = class BabelConfigItemsCacheInvalidatorPlugin {
156+ constructor ( ) {
157+ this . name = `BabelConfigItemsCacheInvalidatorPlugin`
158+ }
159+
160+ apply ( compiler ) {
161+ compiler . hooks . invalid . tap ( this . name , function ( file ) {
162+ const cacheKeysToInvalidate = babelrcFileToCacheKey . get ( file )
163+
164+ if ( cacheKeysToInvalidate ) {
165+ for ( const cacheKey of cacheKeysToInvalidate ) {
166+ configCache . delete ( cacheKey )
167+ }
168+ babelrcFileToCacheKey . delete ( file )
169+ }
170+ } )
171+ }
172+ }
0 commit comments