Skip to content

Instantly share code, notes, and snippets.

@Deathspike
Last active May 12, 2017 13:12
Show Gist options
  • Save Deathspike/83e6023ede3e7454fc8a to your computer and use it in GitHub Desktop.
Save Deathspike/83e6023ede3e7454fc8a to your computer and use it in GitHub Desktop.
Provides a synchronous require implementation for development purposes.
/* tslint:disable:no-eval */
/* tslint:disable:no-shadowed-variable */
/* tslint:disable:restrict-plus-operands */
/**
* Provides a synchronous require implementation for development purposes.
* @author Roel van Uden
* @license MIT
*/
let require = ((): {(id: string): any, detour?: (id: string, value: string | ((id: string) => any)) => void} => {
let detours: {[key: string]: string | ((id: string) => any)} = {};
let modules: {[key: string]: any} = {};
let sources: {[key: string]: string} = {};
/**
* Adds a detour to the detour table.
* @param id The identifier.
* @param value The value.
*/
function add(id: string, value: string | ((id: string) => any)): void {
detours[id] = value;
}
/**
* Enhance the function to expose detour functionality.
* @param targetRequire The require function.
* @return The function with exposed detour functionality.
*/
function enhance(targetRequire: (id: string) => any): any {
let result = targetRequire as any;
result.detour = add;
return result;
}
/**
* Synchronously fetches the module.
* @param absolutePath The absolute path.
* @return The module.
*/
function fetchModuleSync(absolutePath: string): any {
let fullPath = /\.js$/i.test(absolutePath) ? absolutePath : `${absolutePath}.js`;
if (!modules.hasOwnProperty(fullPath)) {
let hasSlashSuffix = /\/$/.test(absolutePath);
let source = !hasSlashSuffix ? fetchSourceSync(fullPath) : '';
if (source) {
let module = {exports: {}, id: fullPath, uri: fullPath};
let moduleRequire = enhance((requestedPath: string) => requireWithParentInternal(requestedPath, fullPath));
modules[fullPath] = module.exports;
requireInExternalScope.call(window, module, moduleRequire, source);
modules[fullPath] = module.exports;
return module.exports;
} else if (hasSlashSuffix || !/\/index\.js$/.test(fullPath)) {
modules[fullPath] = fetchModuleSync(absolutePath + (hasSlashSuffix ? 'index' : '/index'));
return modules[fullPath];
} else {
throw new Error(`Invalid module: ${absolutePath}`);
}
} else {
return modules[fullPath];
}
}
/**
* Synchronously fetches the source.
* @param absolutePath The absolute path.
* @return The source.
*/
function fetchSourceSync(absolutePath: string): string {
if (!sources.hasOwnProperty(absolutePath)) {
let request = new XMLHttpRequest();
request.open('GET', absolutePath, false);
request.send();
if (request.status === 200) {
sources[absolutePath] = request.responseText;
return request.responseText;
} else {
sources[absolutePath] = '';
return '';
}
} else {
return sources[absolutePath];
}
}
/**
* Retrieves the parent path pieces of an absolute path.
* @param absolutePath The absolute path.
* @return The parent path pieces.
*/
function getParentPieces(absolutePath: string): string[] {
let lastSeparatorIndex = absolutePath.lastIndexOf('/');
if (lastSeparatorIndex !== -1) {
return absolutePath.substr(0, lastSeparatorIndex).split('/');
} else {
return [];
}
}
/**
* Resolves the relative path to an absolute path.
* @param relativePath The relative path.
* @param parentPath The parent path.
* @return The absolute path.
*/
function resolve(relativePath: string, parentPath: string): string {
if (/^\.\//.test(relativePath)) {
return resolveFilePath(relativePath.substr(2), parentPath);
} else if (/^\.\.\//.test(relativePath)) {
return resolveFilePath(relativePath, parentPath);
} else {
return resolveModulePath(relativePath, parentPath);
}
}
/**
* Resolves a relative file path to an absolute path.
* @param relativePath The relative path.
* @param parentPath The parent path.
* @return The absolute path.
*/
function resolveFilePath(relativePath: string, parentPath: string): string {
// Retrieve the parent pieces and process the relative path.
let pieces = getParentPieces(parentPath);
while (pieces.length && /^\.\.\//.test(relativePath)) {
relativePath = relativePath.substr(3);
pieces.pop();
}
// Return the absolute path.
if (pieces.length) {
return `${pieces.join('/')}/${relativePath}`;
} else {
return relativePath;
}
}
/**
* Resolves a relative module path to an absolute path.
* @param relativePath The relative path.
* @param parentPath The parent path.
* @return The absolute path.
*/
function resolveModulePath(relativePath: string, parentPath: string): string {
let absolutePath = '';
let modulePath = 'node_modules/';
let modulePathIndex = parentPath.lastIndexOf(modulePath);
// Initialize the module path.
if (modulePathIndex !== -1) {
let endIndex = parentPath.indexOf('/', modulePathIndex + modulePath.length);
if (endIndex !== -1) {
modulePath = `${parentPath.substr(0, endIndex)}/${modulePath}`;
}
}
// Initialize the absolute path.
if (relativePath.indexOf('/') === -1) {
let source = fetchSourceSync(modulePath + `${relativePath}/package.json`);
let bundle = source ? JSON.parse(source) : undefined;
let access = bundle && bundle.main ? bundle.main : '';
absolutePath = modulePath + `${relativePath}/${access}`;
} else {
absolutePath = modulePath + relativePath;
}
// Return the absolute path.
if (modulePathIndex === -1 || fetchSourceSync(absolutePath)) {
return absolutePath;
} else {
return resolveModulePath(relativePath, parentPath.substr(0, modulePathIndex));
}
}
/**
* Synchronously requires a module.
* @param relativePath The relative path.
* @return The initialized module.
*/
function requireInternal(relativePath: string): any {
let scriptElement = document.querySelector('script[src$=\'require.js\']');
let basePath = (scriptElement ? scriptElement.getAttribute('data-base') : undefined) || 'js';
return requireWithParentInternal(relativePath, `${basePath}/require.js`);
}
/**
* Synchronously requires a module relative to a parent.
* @param relativePath The relative path.
* @param parentPath The parent path.
* @return The initialized module.
*/
function requireWithParentInternal(relativePath: string, parentPath: string): any {
let value = detours[relativePath];
if (typeof value === 'function') {
return value(relativePath);
} else if (typeof value === 'string') {
return requireInternal(value);
} else {
return fetchModuleSync(resolve(relativePath, parentPath));
}
}
// Return the require function.
return enhance(requireInternal);
})();
/**
* Runs the source in an external scope.
* @param module The module.
* @param module.exports The exports.
* @param module.id The identifier.
* @param module.uri The uniform resource locator.
* @param require The require function.
* @param source The source.
*/
function requireInExternalScope(module: {exports: {}, id: string, uri: string}, require: any, source: string): void {
let exports = module.exports;
let process = {env: {}};
if (exports && module && process && require) {
eval(source + '\n//# sourceURL=' + module.uri);
}
}
if (typeof window !== 'undefined') {
(window as any).require = require;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment