esmnxuts
v0.0.10
Published
🛠️ Missing ESM utils for Node.js! Fill the gap with ease.
Downloads
28
Readme
Missing ECMAScript module utils for Node.js
📈 As the Node.js ecosystem continues to embrace ESM modules, many essential features are still in development, experimental, or not yet available. 💡 This package aims to address these gaps and provide the necessary support for ESM modules. 🔌 Enhance your Node.js development experience with features that are required for optimal ESM support. 🚀 Stay ahead of the curve with this package.
Usage
Install:
# nyxi
nyxi esmnxuts
#pnpm
pnpm add esmnxuts
# using npm
npm install esmnxuts
# using yarn
yarn add esmnxuts
Note: Node.js 14+ is recommended.
Import utils:
// ESM
import {} from "esmnxuts";
// CommonJS
const {} = require("esmnxuts");
Resolving ESM modules
Several utilities to make ESM resolution easier:
- Respecting ECMAScript Resolver algorithm
- Exposed from Node.js implementation
- Windows paths normalized
- Supporting custom
extensions
and/index
resolution - Supporting custom
conditions
- Support resolving from multiple paths or urls
resolve
Resolve a module by respecting ECMAScript Resolver algorithm (using wooorm/import-meta-resolve).
Additionally supports resolving without extension and /index
similar to CommonJS.
import { resolve } from "esmnxuts";
// file:///home/user/project/module.mjs
console.log(await resolve("./module.mjs", { url: import.meta.url }));
Resolve options:
url
: URL or string to resolve from (default ispwd()
)conditions
: Array of conditions used for resolution algorithm (default is['node', 'import']
)extensions
: Array of additional extensions to check if import failed (default is['.mjs', '.cjs', '.js', '.json']
)
resolvePath
Similar to resolve
but returns a path instead of URL using fileURLToPath
.
import { resolvePath } from "esmnxuts";
// /home/user/project/module.mjs
console.log(await resolvePath("./module.mjs", { url: import.meta.url }));
createResolve
Create a resolve
function with defaults.
import { createResolve } from "esmnxuts";
const _resolve = createResolve({ url: import.meta.url });
// file:///home/user/project/module.mjs
console.log(await _resolve("./module.mjs"));
Example: Ponyfill import.meta.resolve:
import { createResolve } from "esmnxuts";
import.meta.resolve = createResolve({ url: import.meta.url });
resolveImports
Resolve all static and dynamic imports with relative paths to full resolved path.
import { resolveImports } from "esmnxuts";
// import foo from 'file:///home/user/project/bar.mjs'
console.log(
await resolveImports(`import foo from './bar.mjs'`, { url: import.meta.url })
);
Syntax Analyzes
isValidNodeImport
Using various syntax detection and heuristics, this method can determine if import is a valid import or not to be imported using dynamic import()
before hitting an error!
When result is false
, we usually need a to create a CommonJS require context or add specific rules to the bundler to transform dependency.
import { isValidNodeImport } from "esmnxuts";
// If returns true, we are safe to use `import('some-lib')`
await isValidNodeImport("some-lib", {});
Algorithm:
- Check import protocol - If is
data:
returntrue
(✅ valid) - If is notnode:
,file:
ordata:
, returnfalse
( ❌ invalid) - Resolve full path of import using Node.js Resolution algorithm
- Check full path extension
- If is
.mjs
,.cjs
,.node
or.wasm
, returntrue
(✅ valid) - If is not
.js
, returnfalse
(❌ invalid) - If is matching known mixed syntax (
.esm.js
,.es.js
, etc) returnfalse
( ❌ invalid)
- If is
- Read closest
package.json
file to resolve path - If
type: 'module'
field is set, returntrue
(✅ valid) - Read source code of resolved path
- Try to detect CommonJS syntax usage
- If yes, return
true
(✅ valid)
- If yes, return
- Try to detect ESM syntax usage
- if yes, return
false
( ❌ invalid)
- if yes, return
Notes:
- There might be still edge cases algorithm cannot cover. It is designed with best-efforts.
- This method also allows using dynamic import of CommonJS libraries considering Node.js has Interoperability with CommonJS.
hasESMSyntax
Detect if code, has usage of ESM syntax (Static import
, ESM export
and import.meta
usage)
import { hasESMSyntax } from "esmnxuts";
hasESMSyntax("export default foo = 123"); // true
hasCJSSyntax
Detect if code, has usage of CommonJS syntax (exports
, module.exports
, require
and global
usage)
import { hasCJSSyntax } from "esmnxuts";
hasCJSSyntax("export default foo = 123"); // false
detectSyntax
Tests code against both CJS and ESM.
isMixed
indicates if both are detected! This is a common case with legacy packages exporting semi-compatible ESM syntax meant to be used by bundlers.
import { detectSyntax } from "esmnxuts";
// { hasESM: true, hasCJS: true, isMixed: true }
detectSyntax('export default require("lodash")');
CommonJS Context
createCommonJS
This utility creates a compatible CommonJS context that is missing in ECMAScript modules.
import { createCommonJS } from "esmnxuts";
const { __dirname, __filename, require } = createCommonJS(import.meta.url);
Note: require
and require.resolve
implementation are lazy functions. createRequire
will be called on first usage.
Import/Export Analyzes
Tools to quickly analyze ESM syntax and extract static import
/export
- Super fast Regex based implementation
- Handle most edge cases
- Find all static ESM imports
- Find all dynamic ESM imports
- Parse static import statement
- Find all named, declared and default exports
findStaticImports
Find all static ESM imports.
Example:
import { findStaticImports } from "esmnxuts";
console.log(
findStaticImports(`
// Empty line
import foo, { bar /* foo */ } from 'baz'
`)
);
Outputs:
[
{
type: "static",
imports: "foo, { bar /* foo */ } ",
specifier: "baz",
code: "import foo, { bar /* foo */ } from 'baz'",
start: 15,
end: 55,
},
];
parseStaticImport
Parse a dynamic ESM import statement previously matched by findStaticImports
.
Example:
import { findStaticImports, parseStaticImport } from "esmnxuts";
const [match0] = findStaticImports(`import baz, { x, y as z } from 'baz'`);
console.log(parseStaticImport(match0));
Outputs:
{
type: 'static',
imports: 'baz, { x, y as z } ',
specifier: 'baz',
code: "import baz, { x, y as z } from 'baz'",
start: 0,
end: 36,
defaultImport: 'baz',
namespacedImport: undefined,
namedImports: { x: 'x', y: 'z' }
}
findDynamicImports
Find all dynamic ESM imports.
Example:
import { findDynamicImports } from "esmnxuts";
console.log(
findDynamicImports(`
const foo = await import('bar')
`)
);
findExports
import { findExports } from "esmnxuts";
console.log(
findExports(`
export const foo = 'bar'
export { bar, baz }
export default something
`)
);
Outputs:
[
{
type: "declaration",
declaration: "const",
name: "foo",
code: "export const foo",
start: 1,
end: 17,
},
{
type: "named",
exports: " bar, baz ",
code: "export { bar, baz }",
start: 26,
end: 45,
names: ["bar", "baz"],
},
{ type: "default", code: "export default ", start: 46, end: 61 },
];
findExportNames
Same as findExports
but returns array of export names.
import { findExportNames } from "esmnxuts";
// [ "foo", "bar", "baz", "default" ]
console.log(
findExportNames(`
export const foo = 'bar'
export { bar, baz }
export default something
`)
);
resolveModuleExportNames
Resolves module and reads its contents to extract possible export names using static analyzes.
import { resolveModuleExportNames } from "esmnxuts";
// ["basename", "dirname", ... ]
console.log(await resolveModuleExportNames("nyxpath"));
Evaluating Modules
Set of utilities to evaluate ESM modules using data:
imports
- Automatic import rewrite to resolved path using static analyzes
- Allow bypass ESM Cache
- Stack-trace support
.json
loader
evalModule
Transform and evaluates module code using dynamic imports.
import { evalModule } from "esmnxuts";
await evalModule(`console.log("Hello World!")`);
await evalModule(
`
import { reverse } from './utils.mjs'
console.log(reverse('!emosewa si sj'))
`,
{ url: import.meta.url }
);
Options:
- all
resolve
options url
: File URL
loadModule
Dynamically loads a module by evaluating source code.
import { loadModule } from "esmnxuts";
await loadModule("./hello.mjs", { url: import.meta.url });
Options are same as evalModule
.
transformModule
- Resolves all relative imports will be resolved
- All usages of
import.meta.url
will be replaced withurl
orfrom
option
import { transformModule } from "esmnxuts";
console.log(transformModule(`console.log(import.meta.url)`), {
url: "test.mjs",
});
Options are same as evalModule
.
Other Utils
fileURLToPath
Similar to url.fileURLToPath but also converts windows backslash \
to unix slash /
and handles if input is already a path.
import { fileURLToPath } from "esmnxuts";
// /foo/bar.js
console.log(fileURLToPath("file:///foo/bar.js"));
// C:/path
console.log(fileURLToPath("file:///C:/path/"));
normalizeid
Ensures id has either of node:
, data:
, http:
, https:
or file:
protocols.
import { ensureProtocol } from "esmnxuts";
// file:///foo/bar.js
console.log(normalizeid("/foo/bar.js"));
loadURL
Read source contents of a URL. (currently only file protocol supported)
import { resolve, loadURL } from "esmnxuts";
const url = await resolve("./index.mjs", { url: import.meta.url });
console.log(await loadURL(url));
toDataURL
Convert code to data:
URL using base64 encoding.
import { toDataURL } from "esmnxuts";
console.log(
toDataURL(`
// This is an example
console.log('Hello world')
`)
);
interopDefault
Return the default export of a module at the top-level, alongside any other named exports.
// Assuming the shape { default: { foo: 'bar' }, baz: 'qux' }
import myModule from "my-module";
// Returns { foo: 'bar', baz: 'qux' }
console.log(interopDefault(myModule));
sanitizeURIComponent
Replace reserved characters from a segment of URI to make it compatible with rfc2396.
import { sanitizeURIComponent } from "esmnxuts";
// foo_bar
console.log(sanitizeURIComponent(`foo:bar`));
sanitizeFilePath
Sanitize each path of a file name or path with sanitizeURIComponent
for URI compatibility.
import { sanitizeFilePath } from "esmnxuts";
// C:/te_st/_...slug_.jsx'
console.log(sanitizeFilePath("C:\\te#st\\[...slug].jsx"));
License
MIT - Made with 💞