# als-require

`als-require` is a lightweight library for importing, modifying, and executing CommonJS modules in both browser and Node.js environments. It simplifies modular development by supporting dependency resolution, plugins for code transformation, and dynamic bundling.


**Capabilities of `als-require`**:

- Import and execute CommonJS modules in NodeJs and browser with dependency resolution.
- Dynamically bundle modules into a single executable function.
- Transform module code using custom plugins before execution.
- Support for cyclic dependency detection and flexible logging options.


## In version 3

- code refactoring
- improved getting node modules on NodeJs (not in browser)
- Changed constructor
  - Now it has options (`{plugins=[],cyclicDependencies,logger}`)
- No `build` and `bundle` methods. `fn` and `stringFn` methods instead
  - `fn(options = {scriptBefore='', scriptAfter='', parameters=[]}) `
  - `stringFn(options = {scriptBefore='', scriptAfter='', parameters=[],name})`
- no minifying and minifying options
  - The goal make it more simple. You can minify it separately
- plugins for modifying content
- no context
  - You can add it as parameter to fn and pass as parameter's value

## Installation

To install `als-require`, use npm:

```bash
npm install als-require
```

## Importing

Import in nodejs:

```js
const Require = require('als-require')
const fn = Require.getModule('./some/path')
```

Import in browser:

```html
<script src="/node_modules/als-require/require.js"></script>
or
<script src="/node_modules/als-require/require.min.js"></script>

<script>
   Require.version = '1.0'; // optional - adding version when fetching files
   Require.cyclicDependencies = true // false by default
   Require.logger = console // console by default


   Require.getModule('./some/path').then(fn => {fn()})
   // or 
   require('./some/path').then(result => {/* code */})

</script>
```

## Usage

`als-require` has two files for NodeJS and browser which has same structure and api. 

### Constructor
Constructor initiating new Require instance. 
On Nodejs, it's automaticly reading all modules and applying plugins. 
In browser, you should run async method `getContent` for reading modules and applying plugins.

Constructor has two parameters: path and options. 
The path is a string relative path to modules. And options has `plugins`, `logger` and flag for allowing `cyclicDependencies`.

```js
const plugins = [
   function(mod) {
      const {content,children,path} = mod
      mod.content = content.replace(...)
   }
]
const options = { plugins = [], cyclicDependencies = false, logger = console }
```

### fn and stringFn parameters

The `fn` method generates an executable function containing the bundled modules. 
This function accepts custom parameters and includes pre- and post-bundle code if specified.

The `stringFn` method behaves similarly but returns the generated function as a string.
This is particularly useful for embedding the function in HTML or scripts.


Here are parameters:
* `scriptBefore` - Should be script as string to run it before bundle
* `scriptAfter` - Should be script as string to run it after bundle
  * In this stage available two variables: `modules` and `result`.
    * `modules` - the object which includes all modules ({path:content,...})
    * `result` - the function to run bundle which returned after `scriptAfter`
  * In this stage, you can add return for returning something else
* `parameters` - The array of strings which will include parameters to pass in result's fn
* `name` - Only for `stringFn` for changing function name
  * For example, for icluding it in html and call by it's name


### Node.Js

```js
// Import the Require class
const Require = require('als-require');

// Create a new Require instance with options
const mod = new Require('./relative/path/to/module', {
   plugins: [],
   cyclicDependencies: true,
   logger: console
});

// Define custom parameters and scripts
const parameters = ['name'];
const scriptBefore = "const SomeVariableAvailableForAllModules = `Hello ${name}`;";
const scriptAfter = `
   console.log('All modules processed.');
   return result;
`;

// Generate the executable function
const resultFn = mod.fn({ scriptBefore, scriptAfter, parameters });

// Generate the function as a string with a custom name
const name = 'bundleFn';
const bundleString = mod.stringFn({ scriptBefore, scriptAfter, parameters, name });

// Execute the result function
const result = resultFn('Alex');
console.log(result);

// Example: Embed the bundle string in a script
const bundle = `${bundleString}\n const result = ${name}('Alex');`;
console.log(bundle);
```



### Browser

```html
<script src="/node_modules/als-require/require.js"></script>
<script>
   const scriptBefore = "const globalVar = 'Hello, world!';";
   const scriptAfter = "console.log('Bundle execution complete.');";
   const parameters = ['customParam'];

   // Create a Require instance for the browser
   const mod = new Require('./relative/path/to/module');

   // Fetch and load all module dependencies
   mod.getContent().then((mod) => {
      // Define parameters and custom scripts

      // Generate the executable function
      const resultFn = mod.fn({ scriptBefore, scriptAfter, parameters });

      // Execute the generated function
      const result = resultFn('Value');
      console.log(result);
   });

   // or
   require('./relative/path/to/module',{ scriptBefore, scriptAfter, parameters },'Value')
   .then(result => console.log(result))

</script>

```
## Require node_modules packages and node modules

In case of path which not starts with `.`, Require will look for file in node_modules (by checking in package.json).
If such package not found (for example `require('fs')`), result for this package will return empty object. 

```js
const somePackage = require('some-package');
const somePackage1 = require('./node_modules/some-package/index.js');
const fs = require('fs');

module.exports = {somePackage,somePackage1,fs}
```

In case above `somePackage` and `somePackage1` should return the package, but fs, should return empty object. 

Pay attention, in browser, using `'./node_modules/some-package/index.js'` instead `'some-package'`, you save extra request/readFile for looking the filename. 

## API

### NodeJs version
```js
/**
 * The `Require` class handles modular dynamic loading, 
 * dependency resolution, and cyclic dependency detection.
 */
class Require {
   /**
    * Stores the contents of all loaded modules.
    * Each entry maps a module path to its content and children (dependencies).
    * @type {Object<string, { content: string, children: string[] }>}
    * @static
    */
   static contents = {};

   /**
    * List of global plugins to process module contents.
    * Plugins are functions that modify a module's content or children, without replacing the object reference.
    * For example, a plugin can transform `module.content` using `module.content.replace(...)`.
    * @type {Array<Function>}
    * @static
    */
   static plugins = [];

   /**
    * Flag to enable or disable cyclic dependency.
    * When enabled, it allows loading modules that depend on each other recursively.
    * @type {boolean}
    * @static
    */
   static cyclicDependencies = false;

   /**
    * Logger for warnings and error messages.
    * Defaults to the global `console` object but can be overridden with a custom logger.
    * @type {Console}
    * @static
    */
   static logger = console;

   /**
    * Retrieves a module by its path and options.
    * This is a shortcut for instantiating a `Require` object and calling its `fn` method.
    * @param {string} path - Path to the module.
    * @param {Object} [options] - Options for the module loading.
    * @param {string} [options.scriptBefore] - Code to prepend before the module's execution.
    * @param {string} [options.scriptAfter] - Code to append after the module's execution.
    * @param {string[]} [options.parameters] - Parameters to pass into the generated function.
    * @param {Function[]} [options.plugins] - Array of plugin functions.
    * @param {boolean} [options.cyclicDependencies] - Whether to allow cyclic dependencies.
    * @param {Console} [options.logger] - Custom logger for warnings and errors.
    * @returns {Function} - The dynamically generated function for the module.
    * @static
    */
   static getModule(path, options = {}) {
      // Creates a new Require instance and calls its `fn` method to generate the module's function.
      return new Require(path, options).fn(options);
   }

   /**
    * Creates a `Require` instance for managing a specific module and its dependencies.
    * The constructor handles loading the module's contents, resolving dependencies, and applying plugins.
    * @param {string} path - Path to the root module to be loaded.
    * @param {Object} [options] - Options for module handling.
    * @param {Function[]} [options.plugins] - Plugins to modify module content or structure.
    * @param {boolean} [options.cyclicDependencies] - Whether to detect cyclic dependencies.
    * @param {Console} [options.logger] - Custom logger for warnings and errors.
    */
   constructor(path, options = {}) {
      const {
         plugins = [],
         cyclicDependencies = Require.cyclicDependencies,
         logger = Require.logger,
      } = options;

      // Merge global plugins with instance-specific plugins.
      const combinedPlugins = [...Require.plugins, ...plugins].filter(p => typeof p === 'function');

      this.contents = {}; // Stores the current module's contents and dependencies.
      this.path = path; // Path to the root module.
      this.fullPath // will include full path for module
      // In NodeJs Require loads the module's contents and applies plugins.
      this.keys // Keys are the paths of all loaded modules, sorted in reverse order.
   }

   /**
    * Generates a function for the module that incorporates options for execution.
    * The function includes dynamically generated code that can be executed with custom parameters.
    * @param {Object} [options] - Options for function generation.
    * @param {string} [options.scriptBefore] - Code to prepend before the module's execution.
    * @param {string} [options.scriptAfter] - Code to append after the module's execution.
    * @param {string[]} [options.parameters] - Parameters to pass into the generated function.
    * @returns {Function} - The dynamically generated function for the module.
    */
   fn(options = {}) {}

   /**
    * Generates a string representation of the function for the module.
    * Useful for embedding the module in scripts or HTML, with an optional function name.
    * @param {Object} [options] - Options for function generation.
    * @param {string} [options.scriptBefore] - Code to prepend before the module's execution.
    * @param {string} [options.scriptAfter] - Code to append after the module's execution.
    * @param {string[]} [options.parameters] - Parameters to pass into the generated function.
    * @param {string} [options.name] - If presented, replaces the anonymous function name with the provided name.
    * @returns {string} - The string representation of the function.
    */
   stringFn(options = {}) {}
}
```

### Browser version
```js
/**
 * The `Require` class provides modular loading, dependency resolution, 
 * cyclic dependency detection, and fetch-based loading for both server and browser environments.
 */
class Require {
   /**
    * A list of standard Node.js modules that are automatically excluded from fetching.
    * @type {string[]}
    * @static
    */
   static standartNodeModules = [...];

   /**
    * Stores the contents of all loaded modules.
    * Each entry maps a module path to its content and dependencies.
    * @type {Object<string, { content: string, children: string[] }>}
    * @static
    */
   static contents = {};

   /**
    * List of global plugins to process module contents.
    * Plugins are functions that modify a module's content or structure.
    * @type {Array<Function>}
    * @static
    */
   static plugins = [];

   /**
    * Flag to enable or disable cyclic dependency detection.
    * @type {boolean}
    * @static
    */
   static cyclicDependencies = false;

   /**
    * Logger for warnings and error messages.
    * Default is the `console` object but can be replaced with a custom logger.
    * @type {Console}
    * @static
    */
   static logger = console;

   /**
    * Version string to append to fetched module URLs for cache-busting.
    * @type {string | undefined}
    * @static
    */
   static version;

   /**
    * Checks if a cyclic dependency exists between two modules and throws an error if detected.
    * @param {string} fullPath - The full path of the dependent module.
    * @param {string} path - The path of the current module.
    * @throws {Error} Throws an error if a cyclic dependency is detected and cyclicDependencies=false.
    * @static
    */
   static isCyclyc(fullPath, path) {}

   /**
    * Fetches a resource from a given path and returns its content.
    * Supports different response types such as 'text', 'json', etc.
    * @param {string} path - The URL or path to fetch.
    * @param {string} [type='text'] - The type of the response (e.g., 'text', 'json').
    * @returns {Promise<any>} Resolves with the fetched content in the specified format.
    * @static
    */
   static async fetch(path, type = 'text') {}

   /**
    * Asynchronously loads a module and its dependencies, returning a function for execution.
    * @param {string} path - Path to the module.
    * @param {Object} [options] - Options for module loading.
    * @returns {Promise<Function>} Resolves to the dynamically generated function for the module.
    * @static
    */
   static async getModule(path, options = {}) {}

   /**
    * Creates a `Require` instance for managing a specific module.
    * The constructor initializes the module's path, content readiness state, and options.
    * @param {string} path - Path to the root module to be loaded.
    * @param {Object} [options] - Options for module handling.
    */
   constructor(path, options = {}) {
      this.contents = {}; // Stores the current module's contents and dependencies.
      this.path = path; // Path to the root module.
      this.fullPath; // Resolves the full path based on the current location.
      this.contentReady = false; // Indicates if the module's content has been fully loaded.
      this.options = options; // Custom options for module handling.
   }

   /**
    * Asynchronously loads the module's content and dependencies.
    * Applies plugins and detects cyclic dependencies if enabled.
    * @param {Object} [options] - Options for content loading.
    * @returns {Promise<Require>} Resolves to the current instance after loading content.
    */
   async getContent(options = {}) {}

   /**
    * Generates a function for the module that incorporates execution options.
    * The function includes dynamically generated code that can be executed with custom parameters.
    * @param {Object} [options] - Options for function generation.
    * @returns {Function} - The dynamically generated function for the module.
    */
   fn(options = {}) {}
}

module.exports = Require;
```