Skip to content

Commit

Permalink
feat: auto resolve globals
Browse files Browse the repository at this point in the history
global dependencies such as `Window` and `Document` are now automatically resolved (unless you register your own dependency of the same name)

closes #69
  • Loading branch information
Jack Ellis committed Aug 23, 2020
1 parent 16fca16 commit 72bec4c
Show file tree
Hide file tree
Showing 7 changed files with 116 additions and 3 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
Change Log
==========
### 4.0.0
- global dependencies such as `Window` and `Document` are now automatically resolved (unless you register your own dependency of the same name)

### 3.5.0
- add some deprecation warnings for pre-4.0.0 changes

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
"build:js": "rollup --config ./rollup.config.js",
"build:ts": "tsc -d --outDir dist/es --emitDeclarationOnly ./src/index.ts",
"build:post": "node ./postbuild-checks.js",
"build": "yarn build:prepare && yarn build:js && yarn build:ts",
"build": "yarn build:prepare && yarn build:js && yarn build:ts && yarn build:post",
"prepublishOnly": "yarn build"
},
"husky": {
Expand Down
19 changes: 19 additions & 0 deletions postbuild-checks.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
const { promises: fs } = require('fs');
const jpex = require('./dist/cjs/jpex');

const expectedFiles = [
'dist/cjs/jpex.js',
'dist/es/jpex.js',
'dist/es/index.d.ts',
'dist/es/types/index.d.ts',
];
const expectedExports = [
[ 'default', '[object Object]' ],
[ 'jpex', '[object Object]' ],
];

const run = async() => {
// eslint-disable-next-line no-plusplus
Expand All @@ -18,6 +23,20 @@ const run = async() => {
throw new Error(`Unable to verify file ${target}`);
}
}

// eslint-disable-next-line no-plusplus
for (let i = 0; i < expectedExports.length; i++) {
const [ key, type ] = expectedExports[i];
if (jpex[key] === void 0) {
throw new Error(`${key} was not exported`);
}
const actualType = Object.prototype.toString.call(jpex[key]);
if (actualType !== type) {
throw new Error(`Expected type of ${key} to be ${type} but it was ${actualType}`);
}
}

console.log('Everything looks good to me');
};

run();
47 changes: 47 additions & 0 deletions src/__tests__/resolve.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/* eslint-disable no-invalid-this */
import anyTest, { TestInterface } from 'ava';
import fs from 'fs';
import base, { JpexInstance, Options } from '..';

const test: TestInterface<{
Expand Down Expand Up @@ -169,3 +170,49 @@ test('resolves array-like dependencies', (t) => {

t.is(value, 'hello');
});

test('resolves a node module', (t) => {
const { jpex } = t.context;

const value = jpex.resolve('fs');

t.is(value, fs);
});

test('prefers a registered dependency over a node module', (t) => {
const { jpex } = t.context;
const fakeFs = {};
jpex.factory('fs', () => fakeFs as any);

const value = jpex.resolve('fs');

t.not(value, fs);
t.is(value, fakeFs);
});

test('resolves a global property', (t) => {
const { jpex } = t.context;

const value = jpex.resolve('window');

t.is(value, window);
});

test('resolves a global type', (t) => {
const { jpex } = t.context;

const value = jpex.resolve<Window>();

t.is(value, window);
});

test('prefers a registered dependency over a global', (t) => {
const { jpex } = t.context;
const fakeWindow = {};
jpex.factory<Window>(() => fakeWindow as any);

const value = jpex.resolve<Window>();

t.not(value, window);
t.is(value, fakeWindow);
});
2 changes: 2 additions & 0 deletions src/constants/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,5 @@ export enum Lifecycle {
INSTANCE = 3,
NONE = 4,
}

export const GLOBAL_TYPE_PREFIX = 'type:global:';
2 changes: 1 addition & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,12 @@ registerResolveFactory(jpex);

export {
jpex,
Lifecycle,
};

export type {
JpexInstance,
SetupConfig,
Lifecycle,
Options,
NamedParameters,
Resolve,
Expand Down
44 changes: 43 additions & 1 deletion src/resolver/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export const isValidFactory = (factory: Factory) => {
return false;
};

export const getFromNodeModules = (jpex: JpexInstance, target: string): Factory => {
const getFromNodeModules = (jpex: JpexInstance, target: string): Factory => {
// in order to stop webpack environments from including every possible
// import source in the bundle, we have to stick all node require stuff
// inside an eval setup
Expand All @@ -46,6 +46,43 @@ export const getFromNodeModules = (jpex: JpexInstance, target: string): Factory
}
};

const getGlobalObject = (): any => {
if (typeof global !== 'undefined') {
// eslint-disable-next-line no-undef
return global;
}
if (typeof globalThis !== 'undefined') {
// eslint-disable-next-line no-undef
return globalThis;
}
if (typeof window !== 'undefined') {
return window;
}
return {};
};
const getGlobalProperty = (name: string) => {
const global = getGlobalObject();
if (global[name] !== void 0) {
return global[name];
}
// we need to handle inferred types as well
// this gets a little bit hacky...
if (name.startsWith('type:global:')) {
// most global types will just be the name of the property in pascal case
// i.e. window = Window / document = Document
const inferredName = name.charAt(12).toLowerCase() + name.substr(13);
return global[inferredName];
}
};
const getFromGlobal = (jpex: JpexInstance, name: string): Factory => {
const value = getGlobalProperty(name);

if (value !== void 0) {
jpex.constant(name, value);
return jpex.$$factories[name];
}
};

export const getFactory = (jpex: JpexInstance, name: string, optional: boolean) => {
if (typeof name !== 'string') {
throw new JpexError(`Name must be a string, but recevied ${typeof name}`);
Expand All @@ -60,6 +97,11 @@ export const getFactory = (jpex: JpexInstance, name: string, optional: boolean)
return factory;
}

factory = getFromGlobal(jpex, name);
if (isValidFactory(factory)) {
return factory;
}

factory = getFromNodeModules(jpex, name as string);
if (isValidFactory(factory)) {
return factory;
Expand Down

0 comments on commit 72bec4c

Please # to comment.