UNPKG

infusion

Version:

Infusion is an application framework for developing flexible stuff with JavaScript

198 lines (171 loc) 8.26 kB
/*! Infusion Module System Copyright The Infusion copyright holders See the AUTHORS.md file at the top-level directory of this distribution and at https://github.com/fluid-project/infusion/raw/main/AUTHORS.md. Licensed under the Educational Community License (ECL), Version 2.0 or the New BSD license. You may not use this file except in compliance with one these Licenses. You may obtain a copy of the ECL 2.0 License and BSD License at https://github.com/fluid-project/infusion/raw/main/Infusion-LICENSE.txt */ /* eslint-env node */ /* eslint strict: ["error", "global"] */ /* global path */ "use strict"; // An extremely simple base for the module system that just has the functionality of // tracking base directories for loaded modules, and the ability to interpolate paths // of the form %module/further-path fluid.registerNamespace("fluid.module"); // A mapping of module name to a structure containing elements // baseDir {String} The slash-terminated filesystem path of the base directory of the module // require {Function} A function capable as acting as "require" loading modules relative to the module fluid.module.modules = {}; /* * A module which has just loaded will call this API to register itself into * the Fluid module loader's records. The call will generally take the form: * <code>fluid.module.register("my-module", __dirname, require)</code> */ fluid.module.register = function (name, baseDir, moduleRequire) { fluid.log(fluid.logLevel.WARN, "Registering module " + name + " from path " + baseDir); fluid.module.modules[name] = { baseDir: fluid.module.canonPath(baseDir), require: moduleRequire }; }; /** * Given a directory, return an array of slash-terminated parent directories, starting with the parent drive or filesystem * * @param {String} baseDir - A directory name. * @return {String[]} - An array of nested directory names, starting with the parent drive or filesystem root and ending * with `baseDir` */ fluid.module.pathsToRoot = function (baseDir) { var segs = baseDir.split(path.sep); var paths = segs.slice(1).reduce(function (total, seg) { var top = fluid.peek(total); total.push(top + seg + path.sep); return total; }, [segs[0] + path.sep]); return paths; }; /** * Returns a decoded version of the package.json file if the supplied directory contains one, or else `null` * * @param {String} dir - A directory name. * @return {Object|null} - The decoded package.json file found in this directory, or `null` if there is not one. */ fluid.module.hasPackage = function (dir) { var packagePath = dir + path.sep + "package.json"; try { return require(packagePath); } catch (e) { return null; } }; /** * Given a directory, return a structure recording at each level of directory containment whether it contains a valid * node module, by inspecting it for a package.json file and inspecting any such file for a `name` entry * * @param {String} [root] - [optional] A directory name - if omitted, will use the directory of this module * @return {Object} - A structure holding the following aligned arrays: * paths: {String[]} an array of the parent directory names as returned from `fluid.module.pathsToRoot` * packages: {Array of Object|Null} an array of decoded package.json files, aligned with the array `paths` * names: {String[]|Null} an array of package names, aligned with the array `paths`, with entries `undefined` if * the respective directory does not contain a valid node packags */ fluid.module.modulesToRoot = function (root) { var paths = fluid.module.pathsToRoot(root || __dirname); var packages = fluid.transform(paths, fluid.module.hasPackage); var names = fluid.getMembers(packages, "name"); return { paths: paths, packages: packages, names: names }; }; // A simple precursor of our eventual global module inspection system. This simply inspects the path // to root for any readable package.json files, and extracts their "name" field as a moral identifier // of a module's presence. Eventually our registry will include versions and be indexed from the // requestor's viewpoint - in the further future it will be mapped directly into an IoC tree fluid.module.preInspect = function (root) { var moduleInfo = fluid.module.modulesToRoot(root); fluid.each(moduleInfo.names, function (name, index) { if (name && !fluid.module.modules[name]) { var baseDir = moduleInfo.paths[index]; fluid.module.register(name, baseDir, null); // TODO: fabricate a "require" too - so far unused } }); }; /* Canonicalise a path by replacing all backslashes with forward slashes, * (such paths are always valid when supplied to Windows APIs) - except for any initial * "\\" beginning a UNC path - since this will defeat the simpleminded "// -> /" normalisation which is done in * fluid.module.resolvePath, kettle.dataSource.file.handle and similar locations. * JavaScript regexes don't support lookbehind assertions, so this is a reasonable strategy to achieve this. */ fluid.module.canonPath = function (path) { return path.replace(/\\/g, "/").replace(/^\/\//, "\\\\"); }; fluid.module.getDirs = function () { return fluid.getMembers(fluid.module.modules, "baseDir"); }; /* Returns a suitable set of terms for interpolating module root paths into file paths by use of `fluid.stringTemplate` */ fluid.module.terms = function () { return fluid.module.getDirs(); }; /** * Resolve a path expression which may begin with a module reference of the form, * say, %moduleName, into an absolute path relative to that module, using the * database of base directories registered previously with fluid.module.register. * If the path does not begin with such a module reference, it is returned unchanged. */ fluid.module.resolvePath = function (path) { return fluid.stringTemplate(path, fluid.module.getDirs()).replace("//", "/"); }; fluid.module.moduleRegex = /^%([^\W._][\w\.-]*)/; /** * Determine whether ths supplied string begins with the pattern %module-name for * some `module-name` which is considered a valid module name by npm by its legacy rules (see * https://github.com/npm/validate-npm-package-name ). * * @param {String} ref - the string to check * @return {String|Boolean} - the matched module name if the string matches, or falsy if it does not. */ fluid.module.refToModuleName = function (ref) { var matches = ref.match(fluid.module.moduleRegex); return matches && matches[1]; }; /* * Load a node-aware JavaScript file using either a supplied or the native * Fluid require function. The module name may start with a module reference * of the form %module-name to indicate a base reference into either an already * loaded module that was previously registered using fluid.module.register, or * a module which can be loaded from the point of view of the caller. * If the <code>namespace</code> argument is supplied, the module's export * object will be written to that path in the global Fluid namespace */ // TODO: deprecation/change of meaning for 2nd argument. The docs bizarrely say // "to be used after interpolation" which makes no sense - if interpolation could be // done, it means the module was already loaded fluid.require = function (ref, foreignRequire, namespace) { var moduleTerm = fluid.module.refToModuleName(ref); if (moduleTerm && !foreignRequire) { var entry = fluid.module.modules[moduleTerm]; if (!entry) { var callerInfo = fluid.getCallerInfo(2); var callerPath = callerInfo.path; var resolvedTerm = fluid.module.resolveSync(moduleTerm, callerPath); if (!resolvedTerm) { fluid.fail("Module " + moduleTerm + " has not been loaded and could not be loaded from caller's path " + callerPath); } require(resolvedTerm); } } foreignRequire = foreignRequire || require; var resolved = fluid.module.resolvePath(ref); var module = foreignRequire(resolved); if (namespace) { fluid.setGlobalValue(namespace, module); } return module; };