Last active
May 12, 2017 13:12
-
-
Save Deathspike/83e6023ede3e7454fc8a to your computer and use it in GitHub Desktop.
Provides a synchronous require implementation for development purposes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* 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