Skip to content

Commit

Permalink
feat: handle absolute URIs in PUBLIC_PATH (openedx#568)
Browse files Browse the repository at this point in the history
Webpack allows `publicPath` to be an absolute URI (https://webpack.js.org/configuration/output/#outputpublicpath), in order to support deployments to a CDN.  However, the browser history module expects a relative path for `basename` but uses the same environment variable (`PUBLIC_PATH`) to configure it.  Since the path is always expected to be be suffixed to the absolute URI, we avoid introducing another configuration variable (thus maintaining full backwards compatibility) by extracting it at initialization time.
  • Loading branch information
ztraboo committed Apr 14, 2024
1 parent 2f6a62c commit 1987a65
Show file tree
Hide file tree
Showing 5 changed files with 125 additions and 1 deletion.
2 changes: 2 additions & 0 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ export {
convertKeyNames,
getQueryParameters,
ensureDefinedConfig,
parseURL,
getPath,
} from './utils';
export {
APP_TOPIC,
Expand Down
3 changes: 2 additions & 1 deletion src/initialize.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
*/

import { createBrowserHistory, createMemoryHistory } from 'history';
import { getPath } from './utils';
import {
publish,
} from './pubSub';
Expand Down Expand Up @@ -89,7 +90,7 @@ import {
*/
export const history = (typeof window !== 'undefined')
? createBrowserHistory({
basename: getConfig().PUBLIC_PATH,
basename: getPath(getConfig().PUBLIC_PATH),
}) : createMemoryHistory();

/**
Expand Down
11 changes: 11 additions & 0 deletions src/initialize.test.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import PubSub from 'pubsub-js';
import { createBrowserHistory } from 'history';
import {
APP_PUBSUB_INITIALIZED,
APP_CONFIG_INITIALIZED,
Expand Down Expand Up @@ -37,6 +38,7 @@ jest.mock('./auth');
jest.mock('./analytics');
jest.mock('./i18n');
jest.mock('./auth/LocalForageCache');
jest.mock('history');

let config = null;
// const newConfig = {
Expand Down Expand Up @@ -355,3 +357,12 @@ describe('initialize', () => {
// expect(logError).not.toHaveBeenCalled();
// });
});

describe('history', () => {
it('browser history called by default path', async () => {
// import history from initialize;
expect(createBrowserHistory).toHaveBeenCalledWith({
basename: '/',
});
});
});
32 changes: 32 additions & 0 deletions src/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,38 @@ export function convertKeyNames(object, nameMap) {
return modifyObjectKeys(object, transformer);
}

/**
* Given a string URL return an element that has been parsed via href.
* This element has the possibility to return different part of the URL.
parser.protocol; // => "http:"
parser.hostname; // => "example.com"
parser.port; // => "3000"
parser.pathname; // => "/pathname/"
parser.search; // => "?search=test"
parser.hash; // => "#hash"
parser.host; // => "example.com:3000"
* https://gist.github.com/jlong/2428561
*
* @param {string}
* @returns {Object}
*/
export function parseURL(url) {
const parser = document.createElement('a');
parser.href = url;
return parser;
}

/**
* Given a string URL return the path of the URL
*
*
* @param {string}
* @returns {string}
*/
export function getPath(url) {
return parseURL(url).pathname;
}

/**
* *Deprecated*: A method which converts the supplied query string into an object of
* key-value pairs and returns it. Defaults to the current query string - should perform like
Expand Down
78 changes: 78 additions & 0 deletions src/utils.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import {
camelCaseObject,
snakeCaseObject,
convertKeyNames,
parseURL,
getPath,
getQueryParameters,
} from '.';

Expand Down Expand Up @@ -113,3 +115,79 @@ describe('getQueryParameters', () => {
});
});
});

describe('ParseURL', () => {
const testURL = 'http://example.com:3000/pathname/?search=test#hash';
const parsedURL = parseURL(testURL);
it('String URL is correctly parsed', () => {
expect(parsedURL.toString()).toEqual(testURL);
expect(parsedURL.href).toEqual(testURL);
expect(typeof (parsedURL)).toEqual('object');
});

it('should return protocol from URL', () => {
expect(parsedURL.protocol).toEqual('http:');
});

it('should return hostname from URL', () => {
expect(parsedURL.hostname).toEqual('example.com');
});

it('should return port from URL', () => {
expect(parsedURL.port).toEqual('3000');
});

it('should return pathname from URL', () => {
expect(parsedURL.pathname).toEqual('/pathname/');
});

it('should return search rom URL', () => {
expect(parsedURL.search).toEqual('?search=test');
});

it('should return hash from URL', () => {
expect(parsedURL.hash).toEqual('#hash');
});

it('should return host from URL', () => {
expect(parsedURL.host).toEqual('example.com:3000');
});
});

describe('getPath', () => {
it('Path is retrieved with full url', () => {
const testURL = 'http://example.com:3000/pathname/?search=test#hash';

expect(getPath(testURL)).toEqual('/pathname/');
});

it('Path is retrieved with only path', () => {
const testURL = '/learning/';

expect(getPath(testURL)).toEqual('/learning/');
});

it('Path is retrieved without protocol', () => {
const testURL = '//example.com:3000/accounts/';

expect(getPath(testURL)).toEqual('/accounts/');
});

it('Path is retrieved with base `/`', () => {
const testURL = '/';

expect(getPath(testURL)).toEqual('/');
});

it('Path is retrieved without port', () => {
const testURL = 'https://example.com/accounts/';

expect(getPath(testURL)).toEqual('/accounts/');
});

it('Path is retrieved without CDN shape', () => {
const testURL = 'https://d20blt6w1kfasr.cloudfront.net/learning/';

expect(getPath(testURL)).toEqual('/learning/');
});
});

0 comments on commit 1987a65

Please sign in to comment.