From ac9c382e2681dbb874d412fcdb0cab83c1c6e212 Mon Sep 17 00:00:00 2001 From: Alberto Gasparin Date: Wed, 23 Oct 2024 15:11:05 +1100 Subject: [PATCH] Allow injection of whitelisted globals --- README.md | 20 +++++++++++++++++--- src/babel/__tests__/unit.test.js | 30 ++++++++++++++++++++++++++++++ src/babel/index.js | 16 ++++++++++++++-- src/babel/processor-di.js | 3 ++- 4 files changed, 63 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 8c6323e..0aa1f05 100644 --- a/README.md +++ b/README.md @@ -156,7 +156,20 @@ When you have the same dependency replaced multiple times, there are two behavio #### Allowing globals (variables) replacement -Currently the library does not enable automatic replacement of globals. To do that, you need to manually "tag" a global for replacement with `di(myGlobal)` in the function scope. For instance: +The library does not enable automatic replacement of all globals. To do that, you either need to need add a `globals` whitelist in babel config: + +```js +// In your .babelrc / babel.config.js +// ... other stuff like presets + plugins: [ + // ... other plugins + ['react-magnetic-di/babel-plugin', { + globals: ['window', 'document', 'fetch'], + }], + ], +``` + +Or manually "tag" a global for replacement with `di(myGlobal)` in the function scope. For instance: ```js import { di } from 'react-magnetic-di'; @@ -169,7 +182,7 @@ export async function myApiFetcher() { } ``` -Alternatively, you can create a "getter" so that the library will pick it up: +Alternatively, the recommended approach is to create a "getter" so it becomes explicit: ```js export const fetchApi = (...args) => fetch(...args); @@ -236,7 +249,8 @@ The plugin provides a couple of options to explicitly disable auto injection for exclude: ['mocks', /test\.tsx?/], // List of Babel or Node environment names where the plugin should be enabled enabledEnvs: ['development', 'test'], - + // Allow auto injection of some globals by default + globals: ['window', 'document', 'fetch'], }], ], ``` diff --git a/src/babel/__tests__/unit.test.js b/src/babel/__tests__/unit.test.js index f749142..f177f83 100644 --- a/src/babel/__tests__/unit.test.js +++ b/src/babel/__tests__/unit.test.js @@ -655,6 +655,36 @@ describe('babel plugin', () => { `); }); + it('should di globals defined in Babel settings', () => { + const input = ` + const useDocument = () => { + return window.document; + }; + const useBody = () => { + const window = {}; + return () => { + window && document.body; + }; + } + `; + const options = { globals: ['window', 'document'] }; + expect(babel(input, { options })).toMatchInlineSnapshot(` + "import { di as _di } from "react-magnetic-di"; + const useDocument = () => { + const [_window] = _di([window], useDocument); + return _window.document; + }; + const useBody = () => { + const [_document] = _di([document], useBody); + const window = {}; + return () => { + const [_document2] = _di([_document], null); + window && _document2.body; + }; + };" + `); + }); + it('should not di component itself class', () => { const input = ` export default class MyComponent { diff --git a/src/babel/index.js b/src/babel/index.js index c789c46..1821e90 100644 --- a/src/babel/index.js +++ b/src/babel/index.js @@ -144,8 +144,20 @@ module.exports = function (babel) { state.addDependency(p) ); - // TODO - // Should we add collection of globals to di via path.scope.globals? + // Collect globals reference paths + if (opts.globals) { + path.traverse({ + ReferencedIdentifier(idnt) { + if ( + path.scope.globals[idnt.node.name] && + opts.globals.includes(idnt.node.name) && + !idnt.scope.hasBinding(idnt) + ) { + state.addDependency(idnt); + } + }, + }); + } stateCache.set(file, state); }, diff --git a/src/babel/processor-di.js b/src/babel/processor-di.js index 5bd8533..c96a331 100644 --- a/src/babel/processor-di.js +++ b/src/babel/processor-di.js @@ -26,7 +26,8 @@ function processReference(t, path, locationValue, state) { if (!name || !n.parentPath) return; // Some babel plugins might rename imports (eg emotion) and references break // For now we skip, but ideally we would refresh the reference - if (!bodyPath.scope.getBinding(name)) return; + if (!bodyPath.scope.getBinding(name) && !bodyPath.scope.hasGlobal(name)) + return; // Ensure we do not di() self name if (name === self?.name) { shadowsOwnName = true;