als-require
Advanced tools
Comparing version 0.3.0 to 0.4.0
90
index.js
@@ -1,3 +0,89 @@ | ||
const Modules = require('./lib/modules') | ||
const fs = require('fs') | ||
const calledFrom = require('als-called-from') | ||
const { dirname, join } = require('path') | ||
const rootPath = process.cwd() | ||
module.exports = Modules | ||
class Require { | ||
static getModule(path, context) { return new Require(path, context).getContent().build() } | ||
static contents = {} | ||
static relativePath = calledFrom().replace(rootPath, '') | ||
static getFullPath(path,from) { | ||
path = join(dirname(from), path) | ||
if (!path.endsWith('.js')) path += '.js' | ||
return path.replace(/\\/g,'/') | ||
} | ||
constructor(path, context = {}) { | ||
this.modules = {} | ||
this.contents = {} | ||
this.path = path | ||
this.context = context | ||
} | ||
getContent(path = this.path, from = Require.relativePath) { | ||
const getContent =(path) => { | ||
if (this.contents[path] !== undefined) return // allready fetched | ||
if (!Require.contents[path]) { | ||
let content = fs.readFileSync(join(rootPath, path), 'utf-8') | ||
const children = [] | ||
content = content.replace(/^(?!\/\/|\/\*.*\*\/).*require\(["'`](.*)["'`]\)/gm, (match, modulePath) => { | ||
if(!modulePath.startsWith('.')) { | ||
console.warn(`The module "${modulePath}" can't be imported and will be replaced with null`) | ||
return match | ||
} | ||
const fullPath = Require.getFullPath(modulePath, path) | ||
if(Require.contents[fullPath] && Require.contents[fullPath].children.includes(path)) { | ||
throw `cyclic dependency between ${path} and ${fullPath}` | ||
} | ||
children.push(fullPath); | ||
return match.replace(modulePath,fullPath) | ||
}); | ||
Require.contents[path] = { content, children } | ||
} | ||
const { content, children } = Require.contents[path] | ||
this.contents[path] = content | ||
for (let childPath of children) { | ||
getContent(childPath) | ||
} | ||
} | ||
getContent(Require.getFullPath(path, from)) | ||
return this | ||
} | ||
build(modules = this.modules) { | ||
function require(path) {return modules[path] || null} | ||
const keys = Object.keys(this.contents).reverse() | ||
keys.map(path => { | ||
const module = { exports: {} } | ||
const params = { module, require, exports: module.exports, context: this.context } | ||
try { new Function(...Object.keys(params), this.contents[path])(...Object.values(params)) } | ||
catch (error) {this.error(error,path)} | ||
modules[path] = module.exports | ||
}) | ||
this.result = modules[keys[keys.length - 1]] | ||
return this | ||
} | ||
error(error,path) { | ||
const stack = [] | ||
const pathes = Object.keys(Require.contents).reverse() | ||
function addToStack(curPath) { | ||
stack.push(curPath) | ||
for(const modPath of pathes) { | ||
if(Require.contents[modPath].children.includes(curPath)) { | ||
addToStack(` at ${modPath}`) | ||
break | ||
} | ||
} | ||
} | ||
addToStack(path) | ||
const errorsStack = error.stack.split('\n') | ||
errorsStack.splice(1,1,...stack) | ||
error.stack = errorsStack.join('\n') | ||
throw error | ||
} | ||
} | ||
module.exports = Require |
{ | ||
"name": "als-require", | ||
"version": "0.3.0", | ||
"description": "A utility for using CommonJS require in the browser and creating bundles.", | ||
"version": "0.4.0", | ||
"main": "index.js", | ||
"scripts": { | ||
"test": "node --test --experimental-test-coverage", | ||
"get-module": "node --test ./tests/get-module.test.js", | ||
"get-module-only": "node --test-only ./tests/get-module.test.js", | ||
"get-module-coverage": "node --test --experimental-test-coverage ./tests/get-module.test.js", | ||
"get-module-report": "node --test --experimental-test-coverage --test-reporter=lcov --test-reporter-destination=lcov.info ./tests/get-module.test.js", | ||
"modules": "node --test ./tests/modules.test.js", | ||
"modules-only": "node --test-only ./tests/modules.test.js", | ||
"modules-coverage": "node --test --experimental-test-coverage ./tests/modules.test.js", | ||
"modules-report": "node --test --experimental-test-coverage --test-reporter=lcov --test-reporter-destination=lcov.info ./tests/modules.test.js" | ||
"test": "node --experimental-test-coverage ./tests/index.test.js", | ||
"report":"node --experimental-test-coverage --test-reporter=lcov --test-reporter-destination=lcov.info ./tests/index.test.js" | ||
}, | ||
@@ -27,5 +19,6 @@ "keywords": [ | ||
"license": "MIT", | ||
"description": "A utility for using CommonJS require in the browser and creating bundles.", | ||
"dependencies": { | ||
"als-called-from": "^1.0.0" | ||
} | ||
} | ||
} |
172
readme.md
# als-require | ||
`als-require` is a user-friendly utility designed to facilitate the use of the `require` function in web browsers and to create bundles for CommonJS modules. It simplifies the process of module management in browser environments, allowing for seamless integration and deployment of CommonJS-based code. | ||
`als-require` is a lightweight utility that enables the importation and modification of CommonJS modules before execution in both browser and Node.js environments. | ||
**Capabilities of `als-require`**: | ||
## Features | ||
* Utilize CommonJS modules in the browser, complete with all dependencies and access to module functionalities. | ||
* Employ a unified context object across all modules. | ||
* Modify the code of each module before it is executed. | ||
- **Dynamic Module Loading**: Load JavaScript modules dynamically based on runtime conditions. | ||
- **Cyclic Dependency Detection**: Automatically detects and handles cyclic dependencies within modules. | ||
- **Error Handling**: Provides detailed error information for debugging loading issues. | ||
- **Flexibility**: Supports various module formats and can be easily integrated into different JavaScript environments. | ||
**Where it can be particularly useful:** | ||
* Enables the use of the same codebase on both server and browser, facilitating seamless integration. | ||
* Allows for on-the-fly code rendering without the need for building a separate bundle. | ||
## Installation | ||
@@ -21,74 +25,130 @@ | ||
## Usage | ||
## Importing | ||
`als-require` includes two main parts: | ||
1. Browser's script for handling modules | ||
2. Bundle builder for browser | ||
Import in nodejs: | ||
### Dynamic Module Loading in Browsers | ||
```js | ||
const Require = require('als-require') | ||
const module = Require.getModule('./some/path') | ||
``` | ||
The browser's script called `getModule`, is a function which fetching all module's chain and then laughing all the chain to get the result. | ||
The fetch is async, that's why `getModule` returns promise. | ||
All module's results, saved in `getModule.modules` object with relative path as a key and module result as a value. | ||
Import in browser: | ||
```html | ||
<script src="/node_modules/als-require/require.js"></script> | ||
<script> | ||
Require.getModule('./some/path') | ||
.then(module => { | ||
// ... | ||
}) | ||
</script> | ||
``` | ||
**Example:** | ||
## Usage | ||
Let's say, we have moduleA.js which requiring another modules and exporting variable `someExport` and we want to use this module in browser. | ||
Here is the code: | ||
```html | ||
<!DOCTYPE html> | ||
<html lang="en"> | ||
<head> | ||
<title>Dynamic Module Loading</title> | ||
</head> | ||
<body> | ||
<script src="node_modules/als-require/require.js"></script> | ||
<script> | ||
getModule('./moduleA.js').then(someExport => { | ||
window.someExport = someExport; | ||
console.log(getModule.modules) // will return object with all modules and their results | ||
}); | ||
</script> | ||
</body> | ||
</html> | ||
`als-require` has two files for NodeJS and browser which has same structure and api. | ||
Each file includes `Require` class with folowing structure: | ||
```js | ||
class Require { | ||
static getModule(path, context) {} | ||
static contents = {} | ||
constructor(path, context = {}) { | ||
this.modules = {} | ||
this.contents = {} | ||
this.path = path | ||
this.context = context | ||
} | ||
getContent(path = this.path, from = Require.relativePath) {} | ||
build(modules = this.modules) {} | ||
} | ||
``` | ||
In example above, first we include `als-require` script which adding `getModule` function and then, get the module. | ||
The structure above describes only properties and methods for usage, without additional properties and methods which used as private methods. | ||
The constructor gets two arguments: path and context. | ||
* `path` (String): relative path to module for require | ||
* `context` (Object): shared object which will be available in all modules | ||
### Creating and Using Bundles | ||
In this scenario, `als-require` is used to generate a bundle that consolidates all the required modules into a single file. This bundle can then be used in the browser, reducing the number of HTTP requests and streamlining the module loading process. | ||
Here explanation what each method and property used for: | ||
* `Require.getModule` - quick way to get contents and build them in one step | ||
* returns new instance of Requires with ready `contents`, `modules` and `result` | ||
* The method is sync for NodeJS and async for browser | ||
* `Require.contents` - includes modules contents and their children list and used as cache | ||
* `require.getContent()` - used for reading module file's contents | ||
* Adding content to `Require.contents` and to `require.contents` | ||
* The browser version is `async` and NodeJS version is `sync` | ||
* `require.build()` - builds all modules results | ||
* `require.modules` - includes all module's results | ||
* `require.result` - has the main module result (export) | ||
**Example for Generating a Bundle:** | ||
```javascript | ||
const Modules = require('als-require'); | ||
const modules = new Modules() | ||
modules.require('./moduleA','moduleAVarname') | ||
modules.require('./moduleB','moduleBVarname') | ||
### Node.Js | ||
modules.scripts // the object with {relativePath:{exports,content}} | ||
```js | ||
const Require = require('als-require') | ||
const path = './relative/path/to/module' | ||
const context = {} // shared object for all modules empty object by default | ||
const mod = new Require(path,context) | ||
mod.getContent() // reading all modules | ||
for(const path in mod.contents) { | ||
mod.contents[path] = mod.contents[path] // modify if needed | ||
} | ||
// Now you can save the script as file | ||
require('fs').writeFileSync('test.js', modules.script); | ||
// Or return it directly | ||
app.get('/bundle.js', (req, res) => { | ||
res.send(modules.script); | ||
}); | ||
mod.build() // build the result | ||
mod.result // includes the result of main module | ||
mod.modules // object with all module`s results {relativePath:moduleResult,...} | ||
``` | ||
On Browser: | ||
If you want to use node modules libraries, you need to specify relative path. | ||
### Browser | ||
```html | ||
<script src="/bundle.js"></script> | ||
<script src="/node_modules/als-require/require.js"></script> | ||
<script> | ||
console.log(moduleAVarname,moduleBVarname) | ||
console.log(getModule.modules) | ||
const path = './relative/path/to/module' | ||
const context = {} // shared object for all modules empty object by default | ||
const mod = new Require(path,context) | ||
const promise = mod.getContent() // fetching all modules | ||
promise.then(mod => { | ||
for(const path in mod.contents) { | ||
mod.contents[path] = mod.contents[path] // modify if needed | ||
} | ||
mod.build() // build the result | ||
mod.result // includes the result of main module | ||
mod.modules // object with all module`s results {relativePath:moduleResult,...} | ||
}) | ||
</script> | ||
``` | ||
The bundle is self-sufficient and will include getModule with `getModule.modules` which will include all bundled modules. | ||
## Permitted module path | ||
`Require` class not requiring node_modules packages. All node packages returns null as result. | ||
You can include node module by requiring it's full path. Here is example: | ||
```js | ||
const somePackage = require('some-package'); | ||
const somePackage1 = require('./node_modules/some-package/index.js'); | ||
module.exports = {somePackage,somePackage1} | ||
``` | ||
In case above `somePackage` is `null` but `somePackage1` should be the package. | ||
## Context Usage | ||
The `context` object is a shared space accessible by all modules loaded by `als-require`. This allows modules to read and write shared data, enabling more interactive and dynamic module behaviors. | ||
Make sure you are using the `context` for static value (like constants and common variables and functions) and not dynamic, cause it available to all require's results. | ||
## Error Handling | ||
`als-require` throwing errors in case of cyclic dependencies and if some module throwing error. | ||
For the case of module's error, `Require` adds to stack modules paths which has called. |
142
require.js
@@ -1,63 +0,97 @@ | ||
async function getModule(path, errors = []) { | ||
const modules = {} | ||
const dependencies = [] | ||
async function getContent(path, relative) { | ||
if(!path.startsWith('.')) return // don't include node_modules modules | ||
path = getModule.getFullPath(path, relative) | ||
if(relative !== '') { | ||
if(dependencies.includes(relative+path)) throw `cyclic dependency between ${relative} and ${path}` | ||
else dependencies.push(path+relative) | ||
class Require { | ||
static contents = {} | ||
static async getModule(path, context) { | ||
const mod = new Require(path, context) | ||
await mod.getContent() | ||
return mod.build() | ||
} | ||
static getFullPath(path, relative) { | ||
const pathParts = path.split('/'); | ||
const relativeParts = relative.split('/').slice(0, -1); | ||
const fullPathParts = []; | ||
for (let part of [...relativeParts, ...pathParts]) { | ||
if (part === '..') { | ||
if (fullPathParts.length > 0 && fullPathParts[fullPathParts.length - 1] !== '..') fullPathParts.pop(); | ||
else fullPathParts.push(part); | ||
} else if (part !== '.') fullPathParts.push(part); | ||
} | ||
if (modules[path] !== undefined) return | ||
const children = [] | ||
let response = await fetch(path) | ||
if(!response.ok) throw new Error(`HTTP error! status: ${response.status}`); | ||
const content = await response.text() | ||
content.replace(/^(?!\/\/|\/\*.*\*\/).*require\(["'`](.*)["'`]\)/gm, (match, modulePath) => { | ||
children.push(modulePath); | ||
}); | ||
modules[path] = content | ||
for (let childPath of children) { | ||
await getContent(childPath, path) | ||
} | ||
let fullPath = fullPathParts.join('/'); | ||
return fullPath.endsWith('.js') ? fullPath : fullPath + '.js' | ||
} | ||
if(path) await getContent(path, '') | ||
return getModule.buildModules(modules,errors) | ||
} | ||
getModule.getFullPath = function getFullPath(path, relative) { | ||
let sliceTo = -1 | ||
if (path.startsWith('../')) { | ||
path = path.slice(1) | ||
sliceTo = -2 | ||
constructor(path, context = {}) { | ||
this.modules = {} | ||
this.contents = {} | ||
this.path = path | ||
this.context = context | ||
} | ||
if (!path.endsWith('.js')) path = path + '.js' | ||
if (relative !== '') { | ||
let dir = relative.split('/').slice(0, sliceTo).join('/') | ||
path = dir + path.replace(/^\.\//, '/') | ||
async getContent(path = this.path, from = location.pathname) { | ||
const getContent = async (path) => { | ||
if (this.contents[path] !== undefined) return // allready fetched | ||
if (!Require.contents[path]) { | ||
let response = await fetch(path) | ||
if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`); | ||
let content = await response.text() | ||
const children = [] | ||
content = content.replace(/^(?!\/\/|\/\*.*\*\/).*require\(["'`](.*)["'`]\)/gm, (match, modulePath) => { | ||
if(!modulePath.startsWith('.')) { | ||
console.warn(`The module "${modulePath}" can't be imported and will be replaced with null`) | ||
return match | ||
} | ||
const fullPath = Require.getFullPath(modulePath, path) | ||
if(Require.contents[fullPath] && Require.contents[fullPath].children.includes(path)) { | ||
throw `cyclic dependency between ${path} and ${fullPath}` | ||
} | ||
children.push(fullPath); | ||
return match.replace(modulePath,fullPath) | ||
}); | ||
Require.contents[path] = { content, children } | ||
} | ||
const { content, children } = Require.contents[path] | ||
this.contents[path] = content | ||
for (let childPath of children) { | ||
await getContent(childPath) | ||
} | ||
} | ||
await getContent(Require.getFullPath(path, from)) | ||
return this | ||
} | ||
return path | ||
} | ||
getModule.buildModules = function buildModules(modules,errors = []) { | ||
let currentModule = ''; | ||
function require(path) { | ||
path = getModule.getFullPath(path, currentModule) | ||
return modules[path] | ||
build(modules = this.modules) { | ||
function require(path) {return modules[path] || null} | ||
const keys = Object.keys(this.contents).reverse() | ||
keys.map(path => { | ||
const module = { exports: {} } | ||
const params = { module, require, exports: module.exports, context: this.context } | ||
try { new Function(...Object.keys(params), this.contents[path])(...Object.values(params)) } | ||
catch (error) {this.error(error,path)} | ||
modules[path] = module.exports | ||
}) | ||
this.result = modules[keys[keys.length - 1]] | ||
return this | ||
} | ||
const keys = Object.keys(modules).reverse() | ||
keys.forEach(path => { | ||
const module = {} | ||
currentModule = path | ||
try { new Function('module', 'require', modules[path])(module, require) } | ||
catch (error) { errors.push(error) } | ||
if (module.exports) modules[path] = module.exports | ||
else modules[path] = module | ||
getModule.modules[path] = modules[path] | ||
}) | ||
const result = modules[keys[keys.length - 1]] | ||
return result | ||
error(error,path) { | ||
const stack = [] | ||
const pathes = Object.keys(Require.contents).reverse() | ||
function addToStack(curPath) { | ||
stack.push(curPath) | ||
for(const modPath of pathes) { | ||
if(Require.contents[modPath].children.includes(curPath)) { | ||
addToStack(` at ${modPath}`) | ||
break | ||
} | ||
} | ||
} | ||
addToStack(path) | ||
const errorsStack = error.stack.split('\n') | ||
errorsStack.splice(1,1,...stack) | ||
error.stack = errorsStack.join('\n') | ||
throw error | ||
} | ||
} | ||
getModule.modules = {} |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Uses eval
Supply chain riskPackage uses dynamic code execution (e.g., eval()), which is a dangerous practice. This can prevent the code from running in certain environments and increases the risk that the code may contain exploits or malicious behavior.
Found 1 instance in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
22542
313
153
14
5
4