├── .nvmrc
├── .prettierrc
├── .npmrc
├── .gitattributes
├── test
├── fixtures
│ ├── json
│ │ ├── file.json
│ │ └── index.ts
│ ├── resolve
│ │ ├── _dist
│ │ │ └── foo.mjs
│ │ ├── index.ts
│ │ └── resolve.ts
│ ├── mixed
│ │ ├── index.cjs
│ │ └── esm.mjs
│ ├── async
│ │ ├── async.mjs
│ │ └── index.js
│ ├── package.json
│ ├── import-map
│ │ ├── index.mjs
│ │ ├── lib
│ │ │ ├── alias.mjs
│ │ │ └── index.mjs
│ │ └── package.json
│ ├── require-esm
│ │ ├── _dist
│ │ │ ├── file.js
│ │ │ ├── fn.js
│ │ │ ├── esm.js
│ │ │ └── esm-transpiled-cjs.js
│ │ ├── package.json
│ │ └── index.cjs
│ ├── error-runtime
│ │ └── index.ts
│ ├── import-meta
│ │ ├── package.json
│ │ ├── resolve.ts
│ │ ├── index.ts
│ │ ├── dirname.ts
│ │ ├── resolve+custom.ts
│ │ └── custom.ts
│ ├── native
│ │ ├── index.js
│ │ └── test.mjs
│ ├── require-json
│ │ ├── package.json
│ │ └── index.js
│ ├── typescript
│ │ ├── parent.mts
│ │ ├── child.cts
│ │ ├── types.ts
│ │ ├── def-promise.cts
│ │ ├── namespace.ts
│ │ ├── test.ts
│ │ ├── index.ts
│ │ ├── decorators.ts
│ │ └── satisfies.ts
│ ├── esm
│ │ ├── utils.mjs
│ │ ├── utils-lib.js
│ │ ├── index.js
│ │ └── test.js
│ ├── deps
│ │ ├── consola.ts
│ │ ├── mime.ts
│ │ ├── typescript.ts
│ │ ├── zod.ts
│ │ ├── config.ts
│ │ ├── defu.ts
│ │ ├── iig.ts
│ │ ├── etag.ts
│ │ ├── destr.ts
│ │ ├── index.ts
│ │ └── moment.ts
│ ├── circular
│ │ ├── a.js
│ │ ├── b.js
│ │ ├── c.js
│ │ └── index.js
│ ├── top-level-await
│ │ ├── async.mjs
│ │ ├── sub.ts
│ │ └── index.ts
│ ├── proto
│ │ └── index.js
│ ├── hashbang
│ │ └── index.ts
│ ├── error-parse
│ │ └── index.ts
│ ├── jsx
│ │ ├── index.ts
│ │ ├── react.jsx
│ │ ├── preact.jsx
│ │ ├── vue.jsx
│ │ └── nano-jsx.tsx
│ ├── export-promise
│ │ ├── export-promise.mjs
│ │ └── index.mjs
│ ├── cjs-interop
│ │ ├── function-default.cjs
│ │ └── index.cjs
│ ├── node
│ │ └── index.mts
│ ├── pure-esm-dep
│ │ └── index.js
│ ├── env
│ │ └── index.js
│ ├── data-uri
│ │ └── index.ts
│ └── syntax
│ │ └── index.ts
├── native
│ ├── bun.test.ts
│ ├── deno.test.ts
│ └── node.test.ts
├── node-register.test.mjs
├── bench.mjs
├── utils.test.ts
├── bun.test.ts
├── fixtures.test.ts
└── __snapshots__
│ └── fixtures.test.ts.snap
├── lib
├── jiti-register.d.mts
├── jiti-register.mjs
├── jiti.d.mts
├── jiti.d.cts
├── jiti.mjs
├── jiti.cjs
├── jiti-cli.mjs
├── jiti-native.mjs
├── jiti-hooks.mjs
└── types.d.ts
├── .gitignore
├── .prettierignore
├── stubs
├── babel-codeframe.mjs
└── helper-compilation-targets.mjs
├── renovate.json
├── src
├── _types
│ ├── babel-plugin-syntax-jsx.d.ts
│ ├── babel-core.d.ts
│ ├── babel-plugin-parameter-decorator.d.ts
│ ├── babel-plugin-syntax-class-properties.d.ts
│ ├── babel-plugin-syntax-import-assertions.d.ts
│ ├── babel-plugin-transform-typescript-metadata.d.ts
│ ├── babel-helper-simple-access.d.ts
│ ├── babel-plugin-transform-export-namespace-from.d.ts
│ ├── babel-plugin-syntax-typescript.d.ts
│ ├── babel-plugin-syntax-decorators.d.ts
│ ├── babel-plugin-proposal-decorators.d.ts
│ ├── babel-plugin-transform-react-jsx.ts
│ ├── babel-plugin-transform-typescript.d.ts
│ ├── babel-helper-plugin-utils.d.ts
│ └── babel-helper-module-transforms.d.ts
├── types.ts
├── transform.ts
├── plugins
│ ├── import-meta-resolve.ts
│ ├── transform-module
│ │ ├── dynamic-import.ts
│ │ ├── lazy.ts
│ │ ├── hooks.ts
│ │ └── index.ts
│ ├── import-meta-env.ts
│ ├── babel-plugin-transform-typescript-metadata
│ │ ├── index.ts
│ │ ├── metadata-visitor.ts
│ │ ├── parameter-visitor.ts
│ │ └── serialize-type.ts
│ └── import-meta-paths.ts
├── options.ts
├── resolve.ts
├── cache.ts
├── babel.ts
├── require.ts
├── utils.ts
├── jiti.ts
└── eval.ts
├── vitest.config.ts
├── eslint.config.mjs
├── tsconfig.json
├── .github
└── workflows
│ ├── autofix.yml
│ └── ci.yml
├── LICENSE
├── rspack.config.mjs
├── package.json
└── README.md
/.nvmrc:
--------------------------------------------------------------------------------
1 | 22.12.0
2 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {}
2 |
--------------------------------------------------------------------------------
/.npmrc:
--------------------------------------------------------------------------------
1 | shell-emulator=true
2 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | * text=auto eol=lf
2 |
--------------------------------------------------------------------------------
/test/fixtures/json/file.json:
--------------------------------------------------------------------------------
1 | { "test": 123 }
2 |
--------------------------------------------------------------------------------
/test/fixtures/resolve/_dist/foo.mjs:
--------------------------------------------------------------------------------
1 | export {};
2 |
--------------------------------------------------------------------------------
/test/fixtures/mixed/index.cjs:
--------------------------------------------------------------------------------
1 | import("./esm.mjs");
2 |
--------------------------------------------------------------------------------
/test/fixtures/async/async.mjs:
--------------------------------------------------------------------------------
1 | export const async = "works";
2 |
--------------------------------------------------------------------------------
/test/fixtures/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "commonjs"
3 | }
4 |
--------------------------------------------------------------------------------
/test/fixtures/import-map/index.mjs:
--------------------------------------------------------------------------------
1 | import "./lib/index.mjs";
2 |
--------------------------------------------------------------------------------
/test/fixtures/import-map/lib/alias.mjs:
--------------------------------------------------------------------------------
1 | export default "alias";
2 |
--------------------------------------------------------------------------------
/test/fixtures/require-esm/_dist/file.js:
--------------------------------------------------------------------------------
1 | module.exports = {};
2 |
--------------------------------------------------------------------------------
/test/fixtures/require-esm/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "-"
3 | }
4 |
--------------------------------------------------------------------------------
/test/fixtures/error-runtime/index.ts:
--------------------------------------------------------------------------------
1 | throw new Error("test error");
2 |
--------------------------------------------------------------------------------
/test/fixtures/import-meta/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "module"
3 | }
4 |
--------------------------------------------------------------------------------
/test/fixtures/native/index.js:
--------------------------------------------------------------------------------
1 | import("./test.mjs").then(console.log);
2 |
--------------------------------------------------------------------------------
/lib/jiti-register.d.mts:
--------------------------------------------------------------------------------
1 | // eslint-disable-next-line unicorn/no-empty-file
2 |
--------------------------------------------------------------------------------
/test/fixtures/require-esm/_dist/fn.js:
--------------------------------------------------------------------------------
1 | module.exports = function _() {};
2 |
--------------------------------------------------------------------------------
/test/fixtures/require-json/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "commonjs"
3 | }
4 |
--------------------------------------------------------------------------------
/test/fixtures/typescript/parent.mts:
--------------------------------------------------------------------------------
1 | export { child } from "./child.cjs";
2 |
--------------------------------------------------------------------------------
/test/fixtures/esm/utils.mjs:
--------------------------------------------------------------------------------
1 | export const a = "a";
2 | export default "default";
3 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea
2 | node_modules
3 | dist
4 | *.log*
5 | coverage
6 | .tmp
7 | deno.lock
8 |
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | test/fixtures/error-parse/index.ts
2 | coverage
3 | dist
4 | CHANGELOG.md
5 |
--------------------------------------------------------------------------------
/stubs/babel-codeframe.mjs:
--------------------------------------------------------------------------------
1 | export function codeFrameColumns() {
2 | return "";
3 | }
4 |
--------------------------------------------------------------------------------
/test/fixtures/import-meta/resolve.ts:
--------------------------------------------------------------------------------
1 | console.log(import.meta.resolve!("./resolve.ts"));
2 |
--------------------------------------------------------------------------------
/test/fixtures/typescript/child.cts:
--------------------------------------------------------------------------------
1 | // @ts-ignore
2 | export const child = () => "child";
3 |
--------------------------------------------------------------------------------
/test/fixtures/import-map/lib/index.mjs:
--------------------------------------------------------------------------------
1 | import alias from "#alias";
2 |
3 | console.log({ alias });
4 |
--------------------------------------------------------------------------------
/test/fixtures/require-json/index.js:
--------------------------------------------------------------------------------
1 | const data = require("./package.json");
2 | console.log(data);
3 |
--------------------------------------------------------------------------------
/test/fixtures/deps/consola.ts:
--------------------------------------------------------------------------------
1 | import consola from "consola";
2 |
3 | consola.log("npm:consola:", true);
4 |
--------------------------------------------------------------------------------
/test/fixtures/native/test.mjs:
--------------------------------------------------------------------------------
1 | export default {
2 | hasRequire: typeof require !== "undefined",
3 | };
4 |
--------------------------------------------------------------------------------
/test/fixtures/circular/a.js:
--------------------------------------------------------------------------------
1 | export const a = "a";
2 | export { b } from "./b";
3 | export { c } from "./c";
4 |
--------------------------------------------------------------------------------
/test/fixtures/circular/b.js:
--------------------------------------------------------------------------------
1 | export { a } from "./a";
2 | export const b = "b";
3 | export { c } from "./c";
4 |
--------------------------------------------------------------------------------
/test/fixtures/circular/c.js:
--------------------------------------------------------------------------------
1 | export { a } from "./a";
2 | export { b } from "./b";
3 | export const c = "c";
4 |
--------------------------------------------------------------------------------
/test/fixtures/esm/utils-lib.js:
--------------------------------------------------------------------------------
1 | export const version = "123";
2 |
3 | export * as utils from "./utils.mjs";
4 |
--------------------------------------------------------------------------------
/test/fixtures/top-level-await/async.mjs:
--------------------------------------------------------------------------------
1 | export const asyncValue = await Promise.resolve("async value works");
2 |
--------------------------------------------------------------------------------
/test/fixtures/proto/index.js:
--------------------------------------------------------------------------------
1 | import fs from "node:fs";
2 |
3 | console.log("exists:", fs.existsSync(__filename));
4 |
--------------------------------------------------------------------------------
/test/fixtures/hashbang/index.ts:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | // in the Script Goal
3 | "use strict";
4 | console.log(1);
5 |
--------------------------------------------------------------------------------
/test/fixtures/mixed/esm.mjs:
--------------------------------------------------------------------------------
1 | import path from "node:path";
2 |
3 | console.log("Mixed works for:", path.resolve("."));
4 |
--------------------------------------------------------------------------------
/test/fixtures/deps/mime.ts:
--------------------------------------------------------------------------------
1 | import mime from "mime";
2 |
3 | console.log("npm:mime:", mime.getType("txt") === "text/plain");
4 |
--------------------------------------------------------------------------------
/test/fixtures/deps/typescript.ts:
--------------------------------------------------------------------------------
1 | import * as ts from "typescript";
2 |
3 | console.log("npm:typescript:", "mapEntries" in ts);
4 |
--------------------------------------------------------------------------------
/test/fixtures/error-parse/index.ts:
--------------------------------------------------------------------------------
1 | export function foo(boo: string) {
2 | // @ts-expect-error
3 | boooooooooooo = (
4 | }
5 |
--------------------------------------------------------------------------------
/test/fixtures/jsx/index.ts:
--------------------------------------------------------------------------------
1 | import "./nano-jsx.tsx";
2 | import "./preact.jsx";
3 | import "./react.jsx";
4 | import "./vue.jsx";
5 |
--------------------------------------------------------------------------------
/test/fixtures/typescript/types.ts:
--------------------------------------------------------------------------------
1 | export interface Test {
2 | file: string;
3 | dir: string;
4 | resolve: string;
5 | }
6 |
--------------------------------------------------------------------------------
/renovate.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": [
3 | "github>unjs/renovate-config"
4 | ],
5 | "baseBranches": [
6 | "main"
7 | ]
8 | }
9 |
--------------------------------------------------------------------------------
/test/fixtures/typescript/def-promise.cts:
--------------------------------------------------------------------------------
1 | // https://github.com/unjs/jiti/issues/388
2 | export = Promise.resolve("promise resolved");
3 |
--------------------------------------------------------------------------------
/test/fixtures/deps/zod.ts:
--------------------------------------------------------------------------------
1 | import { z } from "zod";
2 |
3 | console.log("npm:zod:", z.string().parse("hello world") === "hello world");
4 |
--------------------------------------------------------------------------------
/test/fixtures/import-meta/index.ts:
--------------------------------------------------------------------------------
1 | import "./custom.ts";
2 | import "./resolve.ts";
3 | import "./resolve+custom.ts";
4 | import "./dirname.ts";
5 |
--------------------------------------------------------------------------------
/test/fixtures/top-level-await/sub.ts:
--------------------------------------------------------------------------------
1 | import { asyncValue } from "./async.mjs";
2 |
3 | export const test = asyncValue + " from sub module";
4 |
--------------------------------------------------------------------------------
/test/fixtures/circular/index.js:
--------------------------------------------------------------------------------
1 | import { a } from "./b";
2 | import { b } from "./c";
3 | import { c } from "./a";
4 |
5 | console.log(a, b, c);
6 |
--------------------------------------------------------------------------------
/test/fixtures/deps/config.ts:
--------------------------------------------------------------------------------
1 | // https://github.com/unjs/jiti/issues/56
2 | console.log("npm:config:", "getFilesOrder" in require("config/parser"));
3 |
--------------------------------------------------------------------------------
/test/fixtures/export-promise/export-promise.mjs:
--------------------------------------------------------------------------------
1 | export const foo = async () => "foo";
2 | export const bar = "bar";
3 |
4 | export default foo();
5 |
--------------------------------------------------------------------------------
/test/fixtures/deps/defu.ts:
--------------------------------------------------------------------------------
1 | import defuDefault from "defu";
2 | import { defu } from "defu";
3 |
4 | console.log("npm:defu", defu({}) && defuDefault({}));
5 |
--------------------------------------------------------------------------------
/test/fixtures/deps/iig.ts:
--------------------------------------------------------------------------------
1 | import isInstalledGlobally from "is-installed-globally";
2 |
3 | console.log("npm:is-installed-globally", isInstalledGlobally);
4 |
--------------------------------------------------------------------------------
/test/fixtures/cjs-interop/function-default.cjs:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | __esModule: true,
3 | default: function myPlugin() {
4 | return "ok";
5 | },
6 | };
7 |
--------------------------------------------------------------------------------
/test/fixtures/top-level-await/index.ts:
--------------------------------------------------------------------------------
1 | await import("./sub").then((m) => console.log(m.test));
2 |
3 | // Mark file as module for typescript
4 | export default {};
5 |
--------------------------------------------------------------------------------
/test/fixtures/async/index.js:
--------------------------------------------------------------------------------
1 | async function main() {
2 | await import("./async.mjs").then((m) => console.log(m.async));
3 | }
4 |
5 | main().catch(console.error);
6 |
--------------------------------------------------------------------------------
/test/fixtures/jsx/react.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { renderToString } from "react-dom/server";
3 |
4 | console.log(renderToString(
Hello, react!
));
5 |
--------------------------------------------------------------------------------
/test/fixtures/esm/index.js:
--------------------------------------------------------------------------------
1 | import test from "./test";
2 |
3 | import * as utilsLib from "./utils-lib";
4 |
5 | console.log({ utilsLib });
6 |
7 | console.log(await test());
8 |
--------------------------------------------------------------------------------
/test/fixtures/import-meta/dirname.ts:
--------------------------------------------------------------------------------
1 | console.log("import.meta.dirname:", import.meta.dirname);
2 | console.log("import.meta.filename:", import.meta.filename);
3 |
4 | export {};
5 |
--------------------------------------------------------------------------------
/test/fixtures/jsx/preact.jsx:
--------------------------------------------------------------------------------
1 | /** @jsx h */
2 | import { h } from "preact";
3 | import render from "preact-render-to-string";
4 |
5 | console.log(render(Hello, preact!
));
6 |
--------------------------------------------------------------------------------
/test/fixtures/deps/etag.ts:
--------------------------------------------------------------------------------
1 | // @ts-ignore
2 | import etag from "etag";
3 |
4 | console.log(
5 | "npm:etag:",
6 | etag("hello world") === `"b-Kq5sNclPz7QV2+lfQIuc6R7oRu0"`,
7 | );
8 |
--------------------------------------------------------------------------------
/test/fixtures/import-meta/resolve+custom.ts:
--------------------------------------------------------------------------------
1 | import.meta.custom = "custom";
2 | console.log("hello!", import.meta.custom);
3 | console.log(import.meta.resolve!("./resolve+custom.ts"));
4 |
--------------------------------------------------------------------------------
/stubs/helper-compilation-targets.mjs:
--------------------------------------------------------------------------------
1 | // https://github.com/babel/babel/tree/main/packages/babel-helper-compilation-targets
2 | export default function getTargets() {
3 | return {};
4 | }
5 |
--------------------------------------------------------------------------------
/test/fixtures/import-map/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "private": true,
3 | "exports": "./lib/index.mjs",
4 | "type": "module",
5 | "imports": {
6 | "#alias": "./lib/alias.mjs"
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/test/fixtures/jsx/vue.jsx:
--------------------------------------------------------------------------------
1 | /** @jsx h */
2 | import { h } from "vue";
3 | import { renderToString } from "vue/server-renderer";
4 |
5 | console.log(await renderToString(Hello, vue!
));
6 |
--------------------------------------------------------------------------------
/test/fixtures/require-esm/_dist/esm.js:
--------------------------------------------------------------------------------
1 | import fn from "./fn.js";
2 | import transpiled from "./esm-transpiled-cjs.js";
3 | import file from "./file";
4 | export default { fn, fn2: transpiled.fn, file };
5 |
--------------------------------------------------------------------------------
/lib/jiti-register.mjs:
--------------------------------------------------------------------------------
1 | // https://nodejs.org/api/module.html#moduleregisterspecifier-parenturl-options
2 | import { register } from "node:module";
3 |
4 | register("./jiti-hooks.mjs", import.meta.url, {});
5 |
--------------------------------------------------------------------------------
/test/fixtures/deps/destr.ts:
--------------------------------------------------------------------------------
1 | import { destr } from "destr";
2 | import destrDefault from "destr";
3 |
4 | console.log(
5 | "npm:destr",
6 | destr("true") === true && destrDefault("true") === true,
7 | );
8 |
--------------------------------------------------------------------------------
/lib/jiti.d.mts:
--------------------------------------------------------------------------------
1 | import { createJiti } from "./types.js";
2 |
3 | export * from "./types.js";
4 |
5 | /**
6 | * @deprecated Please use `import { createJiti } from "jiti"`
7 | */
8 | export default createJiti;
9 |
--------------------------------------------------------------------------------
/test/fixtures/node/index.mts:
--------------------------------------------------------------------------------
1 | import test from "node:test";
2 | // import sqlite from "node:sqlite"; // 20+
3 |
4 | console.log("node:test", !!test.test);
5 | // console.log("node:sqlite", !!sqlite.DatabaseSync);
6 |
--------------------------------------------------------------------------------
/test/fixtures/jsx/nano-jsx.tsx:
--------------------------------------------------------------------------------
1 | /** @jsx h */
2 | // eslint-disable-next-line @typescript-eslint/no-unused-vars
3 | import { h, renderSSR } from "nano-jsx";
4 |
5 | console.log(renderSSR(() => Hello, nano-jsx!
));
6 |
--------------------------------------------------------------------------------
/test/fixtures/resolve/index.ts:
--------------------------------------------------------------------------------
1 | async function run() {
2 | const { createJiti } = await import("../../../lib/jiti.mjs");
3 | const jiti = createJiti(__filename);
4 | await jiti.import("./resolve.ts");
5 | }
6 |
7 | run();
8 |
--------------------------------------------------------------------------------
/test/fixtures/typescript/namespace.ts:
--------------------------------------------------------------------------------
1 | export {};
2 |
3 | // eslint-disable-next-line @typescript-eslint/no-namespace
4 | namespace MyNamespace {
5 | export const value = "foo";
6 | }
7 |
8 | console.log(MyNamespace.value);
9 |
--------------------------------------------------------------------------------
/src/_types/babel-plugin-syntax-jsx.d.ts:
--------------------------------------------------------------------------------
1 | declare module "@babel/plugin-syntax-jsx" {
2 | declare function jsx(): {
3 | manipulateOptions(opts: any, parserOpts: { plugins: string[] }): void;
4 | };
5 | export default jsx;
6 | }
7 |
--------------------------------------------------------------------------------
/test/fixtures/resolve/resolve.ts:
--------------------------------------------------------------------------------
1 | const path = require("node:path");
2 |
3 | const resolvedPath = require.resolve("./foo.mjs", {
4 | paths: [path.resolve(__dirname, "./_dist")],
5 | });
6 | console.log("resolved path", resolvedPath);
7 |
--------------------------------------------------------------------------------
/test/fixtures/import-meta/custom.ts:
--------------------------------------------------------------------------------
1 | declare global {
2 | interface ImportMeta {
3 | custom: any;
4 | }
5 | }
6 |
7 | import.meta.custom = { hello: "world" };
8 | console.log("hello!", import.meta.custom);
9 |
10 | export {};
11 |
--------------------------------------------------------------------------------
/test/fixtures/deps/index.ts:
--------------------------------------------------------------------------------
1 | import "./config";
2 | import "./consola";
3 | import "./defu";
4 | import "./destr";
5 | import "./etag";
6 | import "./iig";
7 | import "./mime";
8 | import "./typescript";
9 | import "./moment";
10 | import "./zod";
11 |
--------------------------------------------------------------------------------
/src/_types/babel-core.d.ts:
--------------------------------------------------------------------------------
1 | import type { HubInterface } from "@babel/traverse";
2 |
3 | declare module "@babel/core" {
4 | export interface BabelFile
5 | extends HubInterface,
6 | Pick {}
7 | }
8 |
9 | export {};
10 |
--------------------------------------------------------------------------------
/src/_types/babel-plugin-parameter-decorator.d.ts:
--------------------------------------------------------------------------------
1 | declare module "babel-plugin-parameter-decorator" {
2 | import type { declare } from "@babel/helper-plugin-utils";
3 | const parameterDecoratorPlugin: ReturnType;
4 | export default parameterDecoratorPlugin;
5 | }
6 |
--------------------------------------------------------------------------------
/test/fixtures/deps/moment.ts:
--------------------------------------------------------------------------------
1 | import momentTZ from "moment-timezone";
2 |
3 | console.log(
4 | "npm:moment-timezone",
5 | // https://www.npmjs.com/package/moment-timezone
6 | momentTZ("2014-06-01T12:00:00Z").tz("America/Los_Angeles").format("ha z") ===
7 | "5am PDT",
8 | );
9 |
--------------------------------------------------------------------------------
/src/_types/babel-plugin-syntax-class-properties.d.ts:
--------------------------------------------------------------------------------
1 | declare module "@babel/plugin-syntax-class-properties" {
2 | import type { declare } from "@babel/helper-plugin-utils";
3 | const syntaxClassPropertiesPlugin: ReturnType;
4 | export default syntaxClassPropertiesPlugin;
5 | }
6 |
--------------------------------------------------------------------------------
/src/_types/babel-plugin-syntax-import-assertions.d.ts:
--------------------------------------------------------------------------------
1 | declare module "@babel/plugin-syntax-import-assertions" {
2 | import type { declare } from "@babel/helper-plugin-utils";
3 | const syntaxImportAssertionsPlugin: ReturnType;
4 | export default syntaxImportAssertionsPlugin;
5 | }
6 |
--------------------------------------------------------------------------------
/lib/jiti.d.cts:
--------------------------------------------------------------------------------
1 | import * as types from "./types.js";
2 | declare const allExports: typeof types & {
3 | /**
4 | * @deprecated Please use `const { createJiti } = require("jiti")` or use ESM import.
5 | */
6 | (...args: Parameters): types.Jiti;
7 | };
8 | export = allExports;
9 |
--------------------------------------------------------------------------------
/src/_types/babel-plugin-transform-typescript-metadata.d.ts:
--------------------------------------------------------------------------------
1 | declare module "babel-plugin-transform-typescript-metadata" {
2 | import type { declare } from "@babel/helper-plugin-utils";
3 | const transformTypeScriptMetaPlugin: ReturnType;
4 | export default transformTypeScriptMetaPlugin;
5 | }
6 |
--------------------------------------------------------------------------------
/test/fixtures/esm/test.js:
--------------------------------------------------------------------------------
1 | require("./utils.mjs");
2 |
3 | export default async function test() {
4 | const utils = await import("./utils.mjs");
5 | console.log({ utils });
6 | return {
7 | file: __filename,
8 | dir: __dirname,
9 | "import.meta.url": import.meta.url,
10 | };
11 | }
12 |
--------------------------------------------------------------------------------
/src/_types/babel-helper-simple-access.d.ts:
--------------------------------------------------------------------------------
1 | declare module "@babel/helper-simple-access" {
2 | import type { NodePath } from "@babel/traverse";
3 |
4 | export default function simplifyAccess(
5 | path: NodePath,
6 | bindingNames: Set,
7 | includeUpdateExpression?: boolean,
8 | ): void;
9 | }
10 |
--------------------------------------------------------------------------------
/src/_types/babel-plugin-transform-export-namespace-from.d.ts:
--------------------------------------------------------------------------------
1 | declare module "@babel/plugin-transform-export-namespace-from" {
2 | import type { declare } from "@babel/helper-plugin-utils";
3 | const transformExportNamespaceFromPlugin: ReturnType;
4 | export default transformExportNamespaceFromPlugin;
5 | }
6 |
--------------------------------------------------------------------------------
/vitest.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from "vitest/config";
2 |
3 | export default defineConfig({
4 | test: {
5 | include: ["test/fixtures.test.ts", "test/utils.test.ts"],
6 | coverage: {
7 | reporter: ["text", "clover", "json"],
8 | include: ["src/**/*.ts"],
9 | },
10 | },
11 | });
12 |
--------------------------------------------------------------------------------
/test/fixtures/pure-esm-dep/index.js:
--------------------------------------------------------------------------------
1 | // estree-walker is a pure ESM package
2 | import { walk } from "estree-walker";
3 | import { parse } from "acorn";
4 |
5 | const ast = parse('const foo = "bar"', { ecmaVersion: "latest" });
6 |
7 | walk(ast, {
8 | enter(node) {
9 | console.log("Enter", node.type);
10 | },
11 | });
12 |
--------------------------------------------------------------------------------
/eslint.config.mjs:
--------------------------------------------------------------------------------
1 | import unjs from "eslint-config-unjs";
2 |
3 | export default unjs({
4 | ignores: ["test/fixtures/error-*"],
5 | rules: {
6 | "unicorn/no-null": 0,
7 | "unicorn/prefer-top-level-await": 0,
8 | "unicorn/prefer-export-from": 0,
9 | "@typescript-eslint/no-require-imports": 0,
10 | },
11 | });
12 |
--------------------------------------------------------------------------------
/test/fixtures/env/index.js:
--------------------------------------------------------------------------------
1 | console.log("process.env", Object.keys(process.env).includes("TEST"));
2 | console.log("process.env.TEST", process.env.TEST);
3 | console.log("process.env?.TEST", process.env?.TEST);
4 |
5 | console.log("import.meta", Object.keys(import.meta.env).includes("TEST"));
6 | console.log("import.meta.env.TEST", import.meta.env.TEST);
7 | console.log("import.meta.env?.TEST", import.meta.env?.TEST);
8 |
--------------------------------------------------------------------------------
/src/_types/babel-plugin-syntax-typescript.d.ts:
--------------------------------------------------------------------------------
1 | declare module "@babel/plugin-syntax-typescript" {
2 | export interface Options {
3 | disallowAmbiguousJSXLike?: boolean;
4 | dts?: boolean;
5 | isTSX?: boolean;
6 | }
7 |
8 | import type { declare } from "@babel/helper-plugin-utils";
9 | const syntaxTypeScriptPlugin: ReturnType>;
10 | export default syntaxTypeScriptPlugin;
11 | }
12 |
--------------------------------------------------------------------------------
/test/fixtures/data-uri/index.ts:
--------------------------------------------------------------------------------
1 | import assert from "node:assert";
2 |
3 | const script = `export default {a: "A"}`;
4 | const uri = "data:text/javascript;charset=utf-8," + encodeURIComponent(script);
5 | try {
6 | require(uri);
7 | throw null;
8 | } catch (error) {
9 | assert.ok(
10 | error instanceof Error,
11 | "Expected an error message for sync import of data URL",
12 | );
13 | }
14 | await import(uri);
15 |
--------------------------------------------------------------------------------
/test/fixtures/typescript/test.ts:
--------------------------------------------------------------------------------
1 | import type { Test } from "./types";
2 |
3 | export default function test() {
4 | return {
5 | file: __filename,
6 | dir: __dirname,
7 | resolve: require.resolve("./test"),
8 | };
9 | }
10 |
11 | // eslint-disable-next-line @typescript-eslint/no-namespace
12 | export namespace smart_player_namespace {
13 | export declare class FeedService {}
14 | }
15 |
16 | export type FeedService = smart_player_namespace.FeedService;
17 |
--------------------------------------------------------------------------------
/test/fixtures/require-esm/index.cjs:
--------------------------------------------------------------------------------
1 | async function run() {
2 | let mod;
3 | try {
4 | mod = require("./_dist/esm.js");
5 | } catch {
6 | const { createJiti } = await import("../../../lib/jiti.mjs");
7 | const jiti = createJiti(__filename);
8 | mod = await jiti.import("./_dist/esm.js");
9 | }
10 | mod = mod.default;
11 | if (typeof mod.fn === "function" && typeof mod.fn2 === "function") {
12 | console.log("Works!");
13 | return;
14 | }
15 | console.error("broken!", { mod });
16 | }
17 |
18 | run();
19 |
--------------------------------------------------------------------------------
/test/fixtures/export-promise/index.mjs:
--------------------------------------------------------------------------------
1 | import { createJiti } from "../../../lib/jiti.cjs";
2 |
3 | async function main() {
4 | const jiti = createJiti(import.meta.url);
5 |
6 | const mod = await jiti.import("./export-promise.mjs", { default: false });
7 | console.log("module:", mod);
8 |
9 | const defaultMod = await jiti.import("./export-promise.mjs", {
10 | default: true,
11 | });
12 | console.log("default module:", defaultMod);
13 | }
14 |
15 | main().catch((error_) => {
16 | console.error("Error:", error_);
17 | });
18 |
--------------------------------------------------------------------------------
/test/fixtures/json/index.ts:
--------------------------------------------------------------------------------
1 | import imported from "./file.json";
2 |
3 | import importedWithAssertion from "./file.json" assert { type: "json" };
4 |
5 | const required = require("./file.json");
6 |
7 | const debug = (label: string, value: any) =>
8 | console.log(label, ":", value, ".default:", value.default);
9 |
10 | debug("Imported", imported);
11 | debug("Imported with assertion", importedWithAssertion);
12 | debug("Required", required);
13 |
14 | const dynamicRes = await import("./file.json");
15 | debug("Dynamic Imported", dynamicRes);
16 |
--------------------------------------------------------------------------------
/test/fixtures/typescript/index.ts:
--------------------------------------------------------------------------------
1 | import test, { type FeedService as _FeedService } from "./test";
2 | import Clazz from "./decorators";
3 | import { test as satisfiesTest } from "./satisfies";
4 | import { child } from "./parent.mjs";
5 | // @ts-expect-error (needs allowImportingTsExtensions)
6 | import defPromise from "./def-promise.cts";
7 | import "./namespace";
8 |
9 | export type { Test } from "./types";
10 |
11 | console.log(test(), Clazz);
12 | console.log(satisfiesTest());
13 | console.log(child());
14 | console.log(await defPromise);
15 |
--------------------------------------------------------------------------------
/src/_types/babel-plugin-syntax-decorators.d.ts:
--------------------------------------------------------------------------------
1 | declare module "@babel/plugin-syntax-decorators" {
2 | export interface Options {
3 | legacy?: boolean;
4 | version?:
5 | | "legacy"
6 | | "2018-09"
7 | | "2021-12"
8 | | "2022-03"
9 | | "2023-01"
10 | | "2023-05"
11 | | "2023-11";
12 | decoratorsBeforeExport?: boolean;
13 | }
14 |
15 | import type { declare } from "@babel/helper-plugin-utils";
16 | const syntaxDecoratorsPlugin: ReturnType>;
17 | export default syntaxDecoratorsPlugin;
18 | }
19 |
--------------------------------------------------------------------------------
/src/_types/babel-plugin-proposal-decorators.d.ts:
--------------------------------------------------------------------------------
1 | declare module "@babel/plugin-proposal-decorators" {
2 | import type { declare } from "@babel/helper-plugin-utils";
3 | import type { Options as SyntaxOptions } from "@babel/plugin-syntax-decorators";
4 |
5 | export interface Options extends SyntaxOptions {
6 | /**
7 | * @deprecated use `constantSuper` assumption instead. Only supported in 2021-12 version.
8 | */
9 | loose?: boolean;
10 | }
11 |
12 | const proposalDecoratorsPlugin: ReturnType>;
13 | export default proposalDecoratorsPlugin;
14 | }
15 |
--------------------------------------------------------------------------------
/test/fixtures/cjs-interop/index.cjs:
--------------------------------------------------------------------------------
1 | const { createJiti } = require("../../../lib/jiti.cjs");
2 | const path = require("node:path");
3 |
4 | async function main() {
5 | const jiti = createJiti(__filename);
6 | const modPath = path.resolve(__dirname, "./function-default.cjs");
7 |
8 | const loaded = await jiti.import(modPath, { default: true });
9 |
10 | if (typeof loaded === "function") {
11 | console.log("CJS function default interop test passed");
12 | } else {
13 | console.log("CJS function default interop test failed");
14 | }
15 | }
16 |
17 | main().catch((error_) => {
18 | console.error("Error:", error_);
19 | });
20 |
--------------------------------------------------------------------------------
/src/_types/babel-plugin-transform-react-jsx.ts:
--------------------------------------------------------------------------------
1 | declare module "@babel/plugin-transform-react-jsx" {
2 | import type { declare } from "@babel/helper-plugin-utils";
3 |
4 | /** Reference: https://babeljs.io/docs/babel-plugin-transform-react-jsx#options */
5 | export interface Options {
6 | throwIfNamespace?: boolean;
7 | runtime?: "classic" | "automatic";
8 | importSource?: string;
9 | pragma?: string;
10 | pragmaFrag?: string;
11 | useBuiltIns?: boolean;
12 | useSpread?: boolean;
13 | }
14 |
15 | const transformReactJSXPlugin: ReturnType>;
16 | export default transformReactJSXPlugin;
17 | }
18 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ESNext",
4 | "module": "Preserve",
5 | "resolveJsonModule": true,
6 | "esModuleInterop": false,
7 | "allowSyntheticDefaultImports": true,
8 | "skipLibCheck": true,
9 | "allowJs": true,
10 | "checkJs": false,
11 | "jsx": "react",
12 | "strict": true,
13 | "verbatimModuleSyntax": true,
14 | "isolatedModules": true,
15 | "forceConsistentCasingInFileNames": true,
16 | "noImplicitOverride": true,
17 | "experimentalDecorators": true,
18 | "declaration": false
19 | },
20 | "include": ["src", "lib", "test"],
21 | "exclude": ["test/fixtures/error-parse/index.ts"]
22 | }
23 |
--------------------------------------------------------------------------------
/test/native/bun.test.ts:
--------------------------------------------------------------------------------
1 | import { readdir } from "node:fs/promises";
2 | // @ts-ignore
3 | import { test } from "bun:test";
4 |
5 | import { createJiti } from "../../lib/jiti-native.mjs";
6 |
7 | const fixtures = await readdir(new URL("../fixtures", import.meta.url));
8 |
9 | const jiti = createJiti(import.meta.url, { importMeta: import.meta });
10 |
11 | const ignore = new Set([
12 | "error-runtime",
13 | "error-parse",
14 | "typescript",
15 | "data-uri",
16 | ]);
17 |
18 | for (const fixture of fixtures) {
19 | if (ignore.has(fixture)) {
20 | continue;
21 | }
22 |
23 | test("fixtures/" + fixture + " (ESM) (Native)", async () => {
24 | await jiti.import("../fixtures/" + fixture);
25 | });
26 | }
27 |
--------------------------------------------------------------------------------
/.github/workflows/autofix.yml:
--------------------------------------------------------------------------------
1 | name: autofix.ci # needed to securely identify the workflow
2 |
3 | on:
4 | pull_request:
5 | push:
6 | branches: ["main"]
7 |
8 | permissions:
9 | contents: read
10 |
11 | jobs:
12 | autofix:
13 | runs-on: ubuntu-latest
14 | steps:
15 | - uses: actions/checkout@v5
16 | - run: npm i -fg corepack && corepack enable
17 | - uses: actions/setup-node@v5
18 | with:
19 | node-version: 22
20 | cache: "pnpm"
21 | - run: pnpm install
22 | - name: Fix lint issues
23 | run: pnpm run lint:fix
24 | - uses: autofix-ci/action@635ffb0c9798bd160680f18fd73371e355b85f27
25 | with:
26 | commit-message: "chore: apply automated updates"
27 |
--------------------------------------------------------------------------------
/lib/jiti.mjs:
--------------------------------------------------------------------------------
1 | import { createRequire } from "node:module";
2 | import _createJiti from "../dist/jiti.cjs";
3 |
4 | function onError(err) {
5 | throw err; /* ↓ Check stack trace ↓ */
6 | }
7 |
8 | const nativeImport = (id) => import(id);
9 |
10 | let _transform;
11 | function lazyTransform(...args) {
12 | if (!_transform) {
13 | _transform = createRequire(import.meta.url)("../dist/babel.cjs");
14 | }
15 | return _transform(...args);
16 | }
17 |
18 | export function createJiti(id, opts = {}) {
19 | if (!opts.transform) {
20 | opts = { ...opts, transform: lazyTransform };
21 | }
22 | return _createJiti(id, opts, {
23 | onError,
24 | nativeImport,
25 | createRequire,
26 | });
27 | }
28 |
29 | export default createJiti;
30 |
--------------------------------------------------------------------------------
/test/node-register.test.mjs:
--------------------------------------------------------------------------------
1 | import "jiti/register";
2 | import { fileURLToPath } from "node:url";
3 | import { readdir } from "node:fs/promises";
4 | import { test } from "node:test";
5 | import assert from "node:assert";
6 |
7 | const fixturesDir = fileURLToPath(new URL("fixtures", import.meta.url));
8 |
9 | const fixtures = await readdir(fixturesDir);
10 |
11 | for (const fixture of fixtures) {
12 | test("fixtures/" + fixture + " (ESM)", async () => {
13 | const promise = import(`./fixtures/${fixture}`);
14 | const shouldReject =
15 | fixture === "error-parse" || fixture === "error-runtime";
16 | if (await shouldReject) {
17 | assert.rejects(promise);
18 | } else {
19 | assert.doesNotReject(promise);
20 | }
21 | });
22 | }
23 |
--------------------------------------------------------------------------------
/lib/jiti.cjs:
--------------------------------------------------------------------------------
1 | const { createRequire } = require("node:module");
2 | const _createJiti = require("../dist/jiti.cjs");
3 |
4 | function onError(err) {
5 | throw err; /* ↓ Check stack trace ↓ */
6 | }
7 |
8 | const nativeImport = (id) => import(id);
9 |
10 | let _transform;
11 | function lazyTransform(...args) {
12 | if (!_transform) {
13 | _transform = require("../dist/babel.cjs");
14 | }
15 | return _transform(...args);
16 | }
17 |
18 | function createJiti(id, opts = {}) {
19 | if (!opts.transform) {
20 | opts = { ...opts, transform: lazyTransform };
21 | }
22 | return _createJiti(id, opts, {
23 | onError,
24 | nativeImport,
25 | createRequire,
26 | });
27 | }
28 |
29 | module.exports = createJiti;
30 | module.exports.createJiti = createJiti;
31 |
--------------------------------------------------------------------------------
/src/_types/babel-plugin-transform-typescript.d.ts:
--------------------------------------------------------------------------------
1 | declare module "@babel/plugin-transform-typescript" {
2 | import type { declare } from "@babel/helper-plugin-utils";
3 | import type { Options as SyntaxOptions } from "@babel/plugin-syntax-typescript";
4 |
5 | export interface Options extends SyntaxOptions {
6 | /** @default true */
7 | allowNamespaces?: boolean;
8 | /** @default "React.createElement" */
9 | jsxPragma?: string;
10 | /** @default "React.Fragment" */
11 | jsxPragmaFrag?: string;
12 | onlyRemoveTypeImports?: boolean;
13 | optimizeConstEnums?: boolean;
14 | allowDeclareFields?: boolean;
15 | }
16 |
17 | const transformTypeScriptMetaPlugin: ReturnType>;
18 | export default transformTypeScriptMetaPlugin;
19 | }
20 |
--------------------------------------------------------------------------------
/src/types.ts:
--------------------------------------------------------------------------------
1 | import type { JitiOptions, ModuleCache } from "../lib/types";
2 | export type {
3 | JitiOptions,
4 | ModuleCache,
5 | EvalModuleOptions,
6 | Jiti,
7 | TransformOptions,
8 | TransformResult,
9 | JitiResolveOptions,
10 | } from "../lib/types";
11 |
12 | export interface Context {
13 | filename: string;
14 | url: string;
15 | parentModule?: NodeModule;
16 | parentCache?: ModuleCache;
17 | nativeImport?: (id: string) => Promise;
18 | onError?: (error: Error) => void;
19 | opts: JitiOptions;
20 | nativeModules: string[];
21 | transformModules: string[];
22 | isNativeRe: RegExp;
23 | isTransformRe: RegExp;
24 | alias?: Record;
25 | additionalExts: string[];
26 | nativeRequire: NodeRequire;
27 | createRequire: (typeof import("node:module"))["createRequire"];
28 | }
29 |
--------------------------------------------------------------------------------
/test/fixtures/typescript/decorators.ts:
--------------------------------------------------------------------------------
1 | import "reflect-metadata";
2 |
3 | function decorator(...args: any) {
4 | console.log("Decorator called with " + args.length + " arguments.");
5 | }
6 |
7 | function anotherDecorator() {
8 | return function (object: any, propertyName: any) {
9 | console.log(
10 | "Decorator metadata keys: " +
11 | Reflect.getMetadataKeys?.(object, propertyName),
12 | );
13 | };
14 | }
15 |
16 | @decorator
17 | export default class DecoratedClass {
18 | @anotherDecorator()
19 | decoratedProperty: string;
20 |
21 | @decorator
22 | get decoratedAccessor() {
23 | return null;
24 | }
25 |
26 | @decorator
27 | decoratedFunction(@decorator decoratedParameter: any) {
28 | return decoratedParameter;
29 | }
30 |
31 | constructor() {
32 | this.decoratedProperty = "foo";
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/test/native/deno.test.ts:
--------------------------------------------------------------------------------
1 | import { readdir } from "node:fs/promises";
2 | import { test } from "node:test";
3 |
4 | import { createJiti } from "../../lib/jiti-native.mjs";
5 |
6 | const fixtures = await readdir(new URL("../fixtures", import.meta.url));
7 |
8 | const jiti = createJiti(import.meta.url, { importMeta: import.meta });
9 |
10 | // Mostly broken because default type is set to commonjs in fixtures root
11 | const ignore = new Set(
12 | [
13 | "error-parse",
14 | "error-runtime",
15 | "typescript",
16 | "env",
17 | "esm",
18 | "proto",
19 | "json",
20 | ].filter(Boolean),
21 | );
22 |
23 | for (const fixture of fixtures) {
24 | if (ignore.has(fixture)) {
25 | continue;
26 | }
27 |
28 | test("fixtures/" + fixture + " (ESM) (Native)", async () => {
29 | await jiti.import("../fixtures/" + fixture);
30 | });
31 | }
32 |
--------------------------------------------------------------------------------
/src/transform.ts:
--------------------------------------------------------------------------------
1 | import type { Context, TransformOptions } from "./types";
2 | import { getCache } from "./cache";
3 | import { debug } from "./utils";
4 |
5 | export function transform(ctx: Context, topts: TransformOptions): string {
6 | let code = getCache(ctx, topts, () => {
7 | const res = ctx.opts.transform!({
8 | ...ctx.opts.transformOptions,
9 | babel: {
10 | ...(ctx.opts.sourceMaps
11 | ? {
12 | sourceFileName: topts.filename,
13 | sourceMaps: "inline",
14 | }
15 | : {}),
16 | ...ctx.opts.transformOptions?.babel,
17 | },
18 | interopDefault: ctx.opts.interopDefault,
19 | ...topts,
20 | });
21 | if (res.error && ctx.opts.debug) {
22 | debug(ctx, res.error);
23 | }
24 | return res.code;
25 | });
26 | if (code.startsWith("#!")) {
27 | code = "// " + code;
28 | }
29 | return code;
30 | }
31 |
--------------------------------------------------------------------------------
/lib/jiti-cli.mjs:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | import { resolve } from "node:path";
4 | import nodeModule from "node:module";
5 |
6 | const script = process.argv.splice(2, 1)[0];
7 |
8 | if (!script) {
9 | console.error("Usage: jiti [...arguments]");
10 | process.exit(1);
11 | }
12 |
13 | // https://nodejs.org/api/module.html#moduleenablecompilecachecachedir
14 | // https://github.com/nodejs/node/pull/54501
15 | if (nodeModule.enableCompileCache && !process.env.NODE_DISABLE_COMPILE_CACHE) {
16 | try {
17 | nodeModule.enableCompileCache();
18 | } catch {
19 | // Ignore errors
20 | }
21 | }
22 |
23 | const pwd = process.cwd();
24 |
25 | const { createJiti } = await import("./jiti.cjs");
26 |
27 | const jiti = createJiti(pwd);
28 |
29 | const resolved = (process.argv[1] = jiti.resolve(resolve(pwd, script)));
30 |
31 | await jiti.import(resolved).catch((error) => {
32 | console.error(error);
33 | process.exit(1);
34 | });
35 |
--------------------------------------------------------------------------------
/test/native/node.test.ts:
--------------------------------------------------------------------------------
1 | import { readdir } from "node:fs/promises";
2 | import { test } from "node:test";
3 |
4 | import { createJiti } from "../../lib/jiti-native.mjs";
5 |
6 | const fixtures = await readdir(new URL("../fixtures", import.meta.url));
7 |
8 | const jiti = createJiti(import.meta.url, { importMeta: import.meta });
9 |
10 | // Mostly broken because default type is set to commonjs in fixtures root
11 | const ignore = new Set(
12 | [
13 | "error-runtime",
14 | "error-parse",
15 | "pure-esm-dep",
16 | "proto",
17 | "json",
18 | "esm",
19 | "env",
20 | "typescript",
21 | "top-level-await",
22 | "deps",
23 | "circular",
24 | "data-uri",
25 | "jsx",
26 | ].filter(Boolean),
27 | );
28 |
29 | for (const fixture of fixtures) {
30 | if (ignore.has(fixture)) {
31 | continue;
32 | }
33 |
34 | test("fixtures/" + fixture + " (ESM) (Native)", async () => {
35 | await jiti.import("../fixtures/" + fixture);
36 | });
37 | }
38 |
--------------------------------------------------------------------------------
/test/fixtures/syntax/index.ts:
--------------------------------------------------------------------------------
1 | // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Optional_chaining
2 | console.log("Optional chaining:", ({} as any)?.foo?.bar);
3 |
4 | // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Nullish_coalescing_operator
5 | // @ts-ignore
6 | // eslint-disable-next-line no-constant-binary-expression
7 | console.log("Nullish coalescing:", 0 ?? 42);
8 |
9 | // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Logical_OR_assignment
10 | const obj1 = { duration: 50, title: "" };
11 | obj1.duration ||= 10;
12 | obj1.title ||= "title is empty.";
13 | console.log("Logical or assignment:", obj1.duration, obj1.title);
14 |
15 | // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Logical_nullish_assignment
16 | const obj2 = { duration: 50, speed: null } as any;
17 | obj2.duration ??= 10;
18 | obj2.speed ??= 20;
19 | console.log("Logical nullish assignment:", obj2.duration, obj2.speed);
20 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) Pooya Parsa
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/test/fixtures/typescript/satisfies.ts:
--------------------------------------------------------------------------------
1 | type ImageType = {
2 | src: string;
3 | width: number;
4 | height: number;
5 | };
6 |
7 | interface User {
8 | name: string;
9 | avatar: string | ImageType;
10 | }
11 |
12 | interface NormalizedUser extends User {
13 | avatar: ImageType;
14 | }
15 |
16 | interface UserNormalizer {
17 | (user: User): NormalizedUser;
18 | }
19 |
20 | export const firstTest = {
21 | name: "first",
22 | avatar: "https://example.com/first.png",
23 | } satisfies User;
24 |
25 | export const secondTest = {
26 | name: "second",
27 | avatar: {
28 | src: "https://example.com/second.png",
29 | width: 100,
30 | height: 100,
31 | },
32 | } satisfies User;
33 |
34 | export const normalizeUserEntity = (({ name, avatar }: User) =>
35 | ({
36 | name,
37 | avatar: {
38 | src: typeof avatar === "string" ? avatar : avatar.src,
39 | width: 100,
40 | height: 100,
41 | },
42 | }) satisfies NormalizedUser) satisfies UserNormalizer;
43 |
44 | export const test = () => {
45 | return {
46 | satisfiesTest: {
47 | firstTest,
48 | secondTest,
49 | normalizeUserEntity,
50 | },
51 | };
52 | };
53 |
--------------------------------------------------------------------------------
/test/bench.mjs:
--------------------------------------------------------------------------------
1 | const runtime =
2 | // eslint-disable-next-line unicorn/no-nested-ternary
3 | "Bun" in globalThis ? "bun" : "Deno" in globalThis ? "deno" : "node";
4 |
5 | console.log("--------------------------------");
6 |
7 | console.log(
8 | `> ${runtime} ${globalThis.Deno?.version.deno || globalThis.Bun?.version || process.version}`,
9 | );
10 |
11 | const initialMem = process.memoryUsage().heapUsed;
12 | let lastMem = initialMem;
13 | const logMemory = (label) => {
14 | const mem = process.memoryUsage().heapUsed;
15 | console.log(
16 | label,
17 | "+" + ((mem - lastMem) / 1024 / 1024).toFixed(2) + " MB" + " (heap)",
18 | );
19 | lastMem = mem;
20 | };
21 |
22 | console.time("jiti:load");
23 | const { createJiti } = await import("jiti");
24 | console.timeEnd("jiti:load");
25 | logMemory("jiti:load");
26 |
27 | console.time("jiti:init");
28 | const jiti = createJiti(import.meta.url, {
29 | moduleCache: false,
30 | fsCache: false,
31 | });
32 | console.timeEnd("jiti:init");
33 | logMemory("jiti:init");
34 |
35 | for (let i = 0; i < 4; i++) {
36 | console.time("jiti:import:ts");
37 | await jiti.import("../fixtures/typescript/test.ts");
38 | console.timeEnd("jiti:import:ts");
39 | logMemory("jiti:import:ts");
40 | }
41 |
--------------------------------------------------------------------------------
/src/plugins/import-meta-resolve.ts:
--------------------------------------------------------------------------------
1 | import type { NodePath, PluginObj } from "@babel/core";
2 | import type { MemberExpression } from "@babel/types";
3 |
4 | export default function importMetaResolvePlugin(_ctx: any) {
5 | return {
6 | name: "import-meta-resolve",
7 | visitor: {
8 | Program(path) {
9 | const metas: Array> = [];
10 |
11 | path.traverse({
12 | MemberExpression(memberExpPath) {
13 | const { node } = memberExpPath;
14 |
15 | if (
16 | node.object.type === "MetaProperty" &&
17 | node.object.meta.name === "import" &&
18 | node.object.property.name === "meta" &&
19 | node.property.type === "Identifier" &&
20 | node.property.name === "resolve"
21 | ) {
22 | metas.push(memberExpPath);
23 | }
24 | },
25 | });
26 |
27 | if (metas.length === 0) {
28 | return;
29 | }
30 |
31 | for (const meta of metas) {
32 | meta.replaceWith({
33 | type: "ExpressionStatement",
34 | expression: { type: "Identifier", name: "jitiESMResolve" },
35 | });
36 | }
37 | },
38 | },
39 | };
40 | }
41 |
--------------------------------------------------------------------------------
/test/fixtures/require-esm/_dist/esm-transpiled-cjs.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Transpiled by esbuild: https://hyrious.me/esbuild-repl/?version=0.25.3&t=export+function+fn%28%29+%7B%7D&o=--format%3Dcjs
3 | *
4 | * Original ESM code:
5 | * ```js
6 | * export function fn() {}
7 | * ```
8 | *
9 | * Transpiled CJS code: ↓↓↓
10 | */
11 | var __defProp = Object.defineProperty;
12 | var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
13 | var __getOwnPropNames = Object.getOwnPropertyNames;
14 | var __hasOwnProp = Object.prototype.hasOwnProperty;
15 | var __export = (target, all) => {
16 | for (var name in all)
17 | __defProp(target, name, { get: all[name], enumerable: true });
18 | };
19 | var __copyProps = (to, from, except, desc) => {
20 | if ((from && typeof from === "object") || typeof from === "function") {
21 | for (let key of __getOwnPropNames(from))
22 | if (!__hasOwnProp.call(to, key) && key !== except)
23 | __defProp(to, key, {
24 | get: () => from[key],
25 | enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable,
26 | });
27 | }
28 | return to;
29 | };
30 | var __toCommonJS = (mod) =>
31 | __copyProps(__defProp({}, "__esModule", { value: true }), mod);
32 | var stdin_exports = {};
33 | __export(stdin_exports, {
34 | fn: () => fn,
35 | });
36 | module.exports = __toCommonJS(stdin_exports);
37 | function fn() {}
38 |
--------------------------------------------------------------------------------
/test/utils.test.ts:
--------------------------------------------------------------------------------
1 | import { afterEach, describe, beforeEach, it, expect, vi } from "vitest";
2 | import { isWindows } from "std-env";
3 | import { getCacheDir } from "../src/cache";
4 |
5 | describe("utils", () => {
6 | describe.skipIf(isWindows)("getCacheDir", () => {
7 | const cwd = "/cwd";
8 | const notCwd = `${cwd}__NOT__`;
9 |
10 | beforeEach(() => {
11 | vi.spyOn(process, "cwd").mockImplementation(() => cwd);
12 | });
13 |
14 | afterEach(() => {
15 | vi.restoreAllMocks();
16 | vi.unstubAllEnvs();
17 | });
18 |
19 | it("returns the system's TMPDIR when TMPDIR is not set", () => {
20 | const originalTmpdir = process.env.TMPDIR;
21 | delete process.env.TMPDIR;
22 | expect(getCacheDir({} as any)).toBe("/tmp/jiti");
23 | process.env.TMPDIR = originalTmpdir;
24 | });
25 |
26 | it("returns TMPDIR when TMPDIR is not CWD", () => {
27 | vi.stubEnv("TMPDIR", notCwd);
28 | expect(getCacheDir({} as any)).toBe("/cwd__NOT__/jiti");
29 | });
30 |
31 | it("returns the system's TMPDIR when TMPDIR is CWD", () => {
32 | vi.stubEnv("TMPDIR", cwd);
33 | expect(getCacheDir({} as any)).toBe("/tmp/jiti");
34 | });
35 |
36 | it("returns TMPDIR when TMPDIR is CWD and TMPDIR is kept", () => {
37 | vi.stubEnv("TMPDIR", cwd);
38 | vi.stubEnv("JITI_RESPECT_TMPDIR_ENV", "true");
39 |
40 | expect(getCacheDir({} as any)).toBe("/cwd/jiti");
41 | });
42 | });
43 | });
44 |
--------------------------------------------------------------------------------
/src/plugins/transform-module/dynamic-import.ts:
--------------------------------------------------------------------------------
1 | // Based on babel-plugin-transform-modules-commonjs v7.24.7
2 | // MIT - Copyright (c) 2014-present Sebastian McKenzie and other contributors
3 | // https://github.com/babel/babel/tree/c7bb6e0f/packages/babel-plugin-transform-modules-commonjs/src
4 |
5 | // Heavily inspired by
6 | // https://github.com/airbnb/babel-plugin-dynamic-import-node/blob/master/src/utils.js
7 |
8 | import type { BabelFile, NodePath } from "@babel/core";
9 | import { types as t, template } from "@babel/core";
10 | import { buildDynamicImport } from "@babel/helper-module-transforms";
11 |
12 | const requireInterop = (
13 | source: t.Expression,
14 | file: BabelFile,
15 | noInterop: boolean | undefined,
16 | ) => {
17 | const exp = template.expression.ast`jitiImport(${source})`;
18 | if (noInterop) {
19 | return exp;
20 | }
21 | return t.callExpression(t.memberExpression(exp, t.identifier("then")), [
22 | t.arrowFunctionExpression(
23 | [t.identifier("m")],
24 | t.callExpression(file.addHelper("interopRequireWildcard"), [
25 | t.identifier("m"),
26 | ]),
27 | ),
28 | ]);
29 | };
30 |
31 | export function transformDynamicImport(
32 | path: NodePath,
33 | noInterop: boolean | undefined,
34 | file: BabelFile,
35 | ) {
36 | path.replaceWith(
37 | buildDynamicImport(path.node, true, false, (specifier) =>
38 | requireInterop(specifier, file, noInterop),
39 | ),
40 | );
41 | }
42 |
--------------------------------------------------------------------------------
/src/plugins/import-meta-env.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Forked from https://github.com/iendeavor/import-meta-env/tree/main/packages/babel 0.4.2 (MIT License - Copyright (c) 2021 Ernest)
3 | */
4 |
5 | import type { PluginObj } from "@babel/core";
6 | import type { Identifier, MemberExpression, MetaProperty } from "@babel/types";
7 |
8 | export default function importMetaEnvPlugin({ template, types }: any) {
9 | return {
10 | name: "@import-meta-env/babel",
11 | visitor: {
12 | Identifier(path) {
13 | if (!types.isIdentifier(path)) {
14 | return;
15 | }
16 |
17 | // {}.{} or {}?.{} (meta.env or meta?.env)
18 | if (
19 | !types.isMemberExpression(path.parentPath) &&
20 | !types.isOptionalMemberExpression(path.parentPath)
21 | ) {
22 | return;
23 | }
24 |
25 | // {}.{}.{} (import.meta.env)
26 | if (!types.isMemberExpression(path.parentPath.node)) {
27 | return;
28 | }
29 |
30 | const parentNode = path.parentPath.node as MemberExpression;
31 |
32 | if (!types.isMetaProperty(parentNode.object)) {
33 | return;
34 | }
35 |
36 | const parentNodeObjMeta = parentNode.object as MetaProperty;
37 |
38 | if (
39 | parentNodeObjMeta.meta.name === "import" &&
40 | parentNodeObjMeta.property.name === "meta" &&
41 | (parentNode.property as Identifier).name === "env"
42 | ) {
43 | path.parentPath.replaceWith(template.expression.ast("process.env"));
44 | }
45 | },
46 | },
47 | };
48 | }
49 |
--------------------------------------------------------------------------------
/test/bun.test.ts:
--------------------------------------------------------------------------------
1 | import { fileURLToPath } from "node:url";
2 | import { readdir, writeFile, mkdir } from "node:fs/promises";
3 | import { join } from "node:path";
4 | // @ts-ignore
5 | import { test, expect } from "bun:test";
6 |
7 | import { createJiti } from "..";
8 |
9 | const fixturesDir = fileURLToPath(new URL("fixtures", import.meta.url));
10 |
11 | const fixtures = await readdir(fixturesDir);
12 |
13 | const _jiti = createJiti(fixturesDir, {
14 | debug: true,
15 | interopDefault: true,
16 | fsCache: false,
17 | moduleCache: false,
18 | });
19 |
20 | for (const fixture of fixtures) {
21 | if (
22 | fixture === "error-runtime" ||
23 | fixture === "error-parse" ||
24 | fixture === "typescript" ||
25 | fixture === "data-uri"
26 | ) {
27 | continue;
28 | }
29 | if (
30 | fixture !== "esm" &&
31 | fixture !== "top-level-await" &&
32 | fixture !== "json"
33 | ) {
34 | test("fixtures/" + fixture + " (CJS)", () => {
35 | _jiti("./" + fixture);
36 | });
37 | }
38 | test("fixtures/" + fixture + " (ESM)", async () => {
39 | await _jiti.import("./" + fixture);
40 | });
41 | }
42 |
43 | test("hmr", async () => {
44 | await mkdir(join(fixturesDir, "../.tmp"), { recursive: true });
45 | const tmpFile = join(fixturesDir, "../.tmp/bun.mjs");
46 |
47 | let value;
48 |
49 | await writeFile(tmpFile, "export default 1");
50 | value = await _jiti.import(tmpFile, { default: true });
51 | expect(value).toBe(1);
52 |
53 | await writeFile(tmpFile, "export default 2");
54 | value = await _jiti.import(tmpFile, { default: true });
55 | expect(value).toBe(2);
56 | });
57 |
--------------------------------------------------------------------------------
/src/_types/babel-helper-plugin-utils.d.ts:
--------------------------------------------------------------------------------
1 | import type { ConfigAPI } from "@babel/core";
2 |
3 | declare module "@babel/helper-plugin-utils" {
4 | const knownAssumptions: readonly [
5 | "arrayLikeIsIterable",
6 | "constantReexports",
7 | "constantSuper",
8 | "enumerableModuleMeta",
9 | "ignoreFunctionLength",
10 | "ignoreToPrimitiveHint",
11 | "iterableIsArray",
12 | "mutableTemplateObject",
13 | "noClassCalls",
14 | "noDocumentAll",
15 | "noIncompleteNsImportDetection",
16 | "noNewArrows",
17 | "noUninitializedPrivateFieldAccess",
18 | "objectRestNoSymbols",
19 | "privateFieldsAsSymbols",
20 | "privateFieldsAsProperties",
21 | "pureGetters",
22 | "setClassMethods",
23 | "setComputedProperties",
24 | "setPublicClassFields",
25 | "setSpreadProperties",
26 | "skipForOfIteratorClosing",
27 | "superIsCallableConstructor",
28 | ];
29 |
30 | export type AssumptionName = (typeof knownAssumptions)[number];
31 |
32 | type AssumptionFunction = (name: AssumptionName) => boolean | undefined;
33 |
34 | export type Target =
35 | | "node"
36 | | "deno"
37 | | "chrome"
38 | | "opera"
39 | | "edge"
40 | | "firefox"
41 | | "safari"
42 | | "ie"
43 | | "ios"
44 | | "android"
45 | | "electron"
46 | | "samsung"
47 | | "opera_mobile";
48 |
49 | export type Targets = {
50 | [target in Target]?: string;
51 | };
52 |
53 | type TargetsFunction = () => Targets;
54 |
55 | export type PresetAPI = {
56 | targets: TargetsFunction;
57 | addExternalDependency: (ref: string) => void;
58 | } & ConfigAPI;
59 |
60 | export type PluginAPI = {
61 | assumption: AssumptionFunction;
62 | } & PresetAPI &
63 | BabelAPI;
64 | }
65 |
--------------------------------------------------------------------------------
/src/plugins/transform-module/lazy.ts:
--------------------------------------------------------------------------------
1 | // Based on babel-plugin-transform-modules-commonjs v7.24.7
2 | // MIT - Copyright (c) 2014-present Sebastian McKenzie and other contributors
3 | // https://github.com/babel/babel/tree/c7bb6e0f/packages/babel-plugin-transform-modules-commonjs/src
4 | import { template, types as t } from "@babel/core";
5 | import { isSideEffectImport } from "@babel/helper-module-transforms";
6 | import type { CommonJSHook } from "./hooks";
7 |
8 | type Lazy = boolean | string[] | ((source: string) => boolean);
9 |
10 | export const lazyImportsHook = (lazy: Lazy): CommonJSHook => ({
11 | name: `babel-plugin-transform-modules-commonjs/lazy`,
12 | version: "7.24.7",
13 | getWrapperPayload(source, metadata) {
14 | if (isSideEffectImport(metadata) || metadata.reexportAll) {
15 | return null;
16 | }
17 | if (lazy === true) {
18 | // 'true' means that local relative files are eagerly loaded and
19 | // dependency modules are loaded lazily.
20 | return source.includes(".") ? null : "lazy/function";
21 | }
22 | if (Array.isArray(lazy)) {
23 | return lazy.includes(source) ? "lazy/function" : null;
24 | }
25 | if (typeof lazy === "function") {
26 | return lazy(source) ? "lazy/function" : null;
27 | }
28 | },
29 | buildRequireWrapper(name, init, payload, referenced) {
30 | if (payload === "lazy/function") {
31 | if (!referenced) return false;
32 | return template.statement.ast`
33 | function ${name}() {
34 | const data = ${init};
35 | ${name} = function(){ return data; };
36 | return data;
37 | }
38 | `;
39 | }
40 | },
41 | wrapReference(ref, payload) {
42 | if (payload === "lazy/function") return t.callExpression(ref, []);
43 | },
44 | });
45 |
--------------------------------------------------------------------------------
/src/plugins/babel-plugin-transform-typescript-metadata/index.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Based on https://github.com/leonardfactory/babel-plugin-transform-typescript-metadata
3 | * Copyright (c) 2019 Leonardo Ascione [MIT]
4 | */
5 |
6 | import type { PluginObj } from "@babel/core";
7 | import { declare } from "@babel/helper-plugin-utils";
8 | import { parameterVisitor } from "./parameter-visitor";
9 | import { metadataVisitor } from "./metadata-visitor";
10 |
11 | export default declare((api: any): PluginObj => {
12 | api.assertVersion(7);
13 |
14 | return {
15 | visitor: {
16 | Program(programPath) {
17 | /**
18 | * We need to traverse the program right here since
19 | * `@babel/preset-typescript` removes imports at this level.
20 | *
21 | * Since we need to convert some typings into **bindings**, used in
22 | * `Reflect.metadata` calls, we need to process them **before**
23 | * the typescript preset.
24 | */
25 | programPath.traverse({
26 | ClassDeclaration(path) {
27 | for (const field of path.get("body").get("body")) {
28 | if (
29 | field.type !== "ClassMethod" &&
30 | field.type !== "ClassProperty"
31 | ) {
32 | continue;
33 | }
34 |
35 | parameterVisitor(path, field as any);
36 | metadataVisitor(path, field as any);
37 | }
38 |
39 | /**
40 | * We need to keep binding in order to let babel know where imports
41 | * are used as a Value (and not just as a type), so that
42 | * `babel-transform-typescript` do not strip the import.
43 | */
44 | (path.parentPath.scope as any).crawl();
45 | },
46 | });
47 | },
48 | },
49 | };
50 | });
51 |
--------------------------------------------------------------------------------
/rspack.config.mjs:
--------------------------------------------------------------------------------
1 | import { fileURLToPath } from "node:url";
2 | import TerserPlugin from 'terser-webpack-plugin'
3 | import { defineConfig } from "@rspack/cli";
4 |
5 | export default defineConfig({
6 | target: "node",
7 | mode: "production",
8 | entry: {
9 | jiti: "./src/jiti.ts",
10 | babel: "./src/babel.ts",
11 | },
12 | devtool: false,
13 | output: {
14 | filename: "[name].cjs",
15 | path: fileURLToPath(import.meta.resolve("./dist")),
16 | libraryTarget: "commonjs2",
17 | libraryExport: "default",
18 | },
19 | resolve: {
20 | extensions: [".tsx", ".ts", ".js", ".cjs", ".mjs", ".json"],
21 | alias: {
22 | "@babel/code-frame": fileURLToPath(
23 | import.meta.resolve("./stubs/babel-codeframe.mjs"),
24 | ),
25 | "@babel/helper-compilation-targets": fileURLToPath(
26 | import.meta.resolve("./stubs/helper-compilation-targets.mjs"),
27 | ),
28 | },
29 | },
30 | ignoreWarnings: [
31 | {
32 | module: /[\\/]node_modules[\\/]mlly[\\/]/,
33 | message: /the request of a dependency is an expression/,
34 | },
35 | {
36 | module: /[\\/]node_modules[\\/]@babel.*/,
37 | message: /require.extensions is not supported/,
38 | },
39 | {
40 | module: /[\\/]node_modules[\\/]@babel.*/,
41 | message:
42 | /the request of a dependency is an expression|require function is used in a way in which/,
43 | },
44 | ],
45 | module: {
46 | rules: [
47 | {
48 | test: /\.ts$/,
49 | use: "ts-loader",
50 | exclude: /node_modules/,
51 | },
52 | ],
53 | },
54 | node: false,
55 | optimization: {
56 | nodeEnv: false,
57 | moduleIds: "named",
58 | chunkIds: "named",
59 | minimize: true,
60 | minimizer: [
61 | // TODO: Migrate to rspack.SwcJsMinimizerRspackPlugin
62 | // https://github.com/unjs/jiti/pull/407
63 | new TerserPlugin({
64 | terserOptions: {
65 | mangle: {
66 | keep_fnames: true,
67 | keep_classnames: true,
68 | },
69 | },
70 | }),
71 | ],
72 | },
73 | });
74 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: ci
2 |
3 | on:
4 | push:
5 | branches:
6 | - main
7 | pull_request:
8 | branches:
9 | - main
10 |
11 | permissions:
12 | id-token: write
13 |
14 | jobs:
15 | ci:
16 | runs-on: ${{ matrix.os }}
17 |
18 | strategy:
19 | matrix:
20 | os: [ubuntu-latest, windows-latest]
21 | node: [18, 20, 22]
22 | fail-fast: false
23 |
24 | steps:
25 | - uses: actions/checkout@v5
26 | with:
27 | fetch-depth: 0
28 | - run: npm i -fg corepack && corepack enable
29 | - uses: actions/setup-node@v5
30 | with:
31 | node-version: ${{ matrix.node }}
32 | cache: "pnpm"
33 | - uses: oven-sh/setup-bun@v2
34 | if: ${{ matrix.os == 'ubuntu-latest' && matrix.node == 22 }}
35 | with:
36 | bun-version: latest
37 | - uses: denoland/setup-deno@v2
38 | if: ${{ matrix.os == 'ubuntu-latest' && matrix.node == 22 }}
39 | with:
40 | deno-version: v2.x
41 | - run: pnpm install
42 | - run: pnpm lint
43 | if: ${{ matrix.os == 'ubuntu-latest' && matrix.node == 22 }}
44 | - run: pnpm build
45 | - run: pnpm vitest run --coverage
46 | - run: pnpm test:node-register
47 | if: ${{ matrix.os == 'ubuntu-latest' && matrix.node == 22 }}
48 | - run: pnpm test:bun --coverage
49 | if: ${{ matrix.os == 'ubuntu-latest' && matrix.node == 22 }}
50 | - run: pnpm test:native
51 | if: ${{ matrix.os == 'ubuntu-latest' && matrix.node == 22 }}
52 | - name: nightly release
53 | if: |
54 | matrix.os == 'ubuntu-latest' && matrix.node == 22 &&
55 | github.event_name == 'push' &&
56 | !contains(github.event.head_commit.message, '[skip-release]') &&
57 | !startsWith(github.event.head_commit.message, 'docs')
58 | run: |
59 | echo "//registry.npmjs.org/:_authToken=${NPM_TOKEN}" >> ~/.npmrc &&
60 | pnpm changelogen --canary nightly --publish
61 | env:
62 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
63 | NPM_CONFIG_PROVENANCE: true
64 |
--------------------------------------------------------------------------------
/src/options.ts:
--------------------------------------------------------------------------------
1 | import type { JitiOptions } from "./types";
2 |
3 | export function resolveJitiOptions(userOptions: JitiOptions): JitiOptions {
4 | const jitiDefaults: JitiOptions = {
5 | fsCache: _booleanEnv("JITI_FS_CACHE", _booleanEnv("JITI_CACHE", true)),
6 | rebuildFsCache: _booleanEnv("JITI_REBUILD_FS_CACHE", false),
7 | moduleCache: _booleanEnv(
8 | "JITI_MODULE_CACHE",
9 | _booleanEnv("JITI_REQUIRE_CACHE", true),
10 | ),
11 | debug: _booleanEnv("JITI_DEBUG", false),
12 | sourceMaps: _booleanEnv("JITI_SOURCE_MAPS", false),
13 | interopDefault: _booleanEnv("JITI_INTEROP_DEFAULT", true),
14 | extensions: _jsonEnv("JITI_EXTENSIONS", [
15 | ".js",
16 | ".mjs",
17 | ".cjs",
18 | ".ts",
19 | ".tsx",
20 | ".mts",
21 | ".cts",
22 | ".mtsx",
23 | ".ctsx",
24 | ]),
25 | alias: _jsonEnv>("JITI_ALIAS", {}),
26 | nativeModules: _jsonEnv("JITI_NATIVE_MODULES", []),
27 | transformModules: _jsonEnv("JITI_TRANSFORM_MODULES", []),
28 | tryNative: _jsonEnv("JITI_TRY_NATIVE", "Bun" in globalThis),
29 | jsx: _booleanEnv("JITI_JSX", false),
30 | };
31 |
32 | if (jitiDefaults.jsx) {
33 | jitiDefaults.extensions!.push(".jsx", ".tsx");
34 | }
35 |
36 | const deprecatedOverrides: JitiOptions = {};
37 | if (userOptions.cache !== undefined) {
38 | deprecatedOverrides.fsCache = userOptions.cache;
39 | }
40 | if (userOptions.requireCache !== undefined) {
41 | deprecatedOverrides.moduleCache = userOptions.requireCache;
42 | }
43 |
44 | const opts: JitiOptions = {
45 | ...jitiDefaults,
46 | ...deprecatedOverrides,
47 | ...userOptions,
48 | };
49 |
50 | return opts;
51 | }
52 |
53 | function _booleanEnv(name: string, defaultValue: boolean): boolean {
54 | const val = _jsonEnv(name, defaultValue);
55 | return Boolean(val);
56 | }
57 |
58 | function _jsonEnv(name: string, defaultValue?: T): T | undefined {
59 | const envValue = process.env[name];
60 | if (!(name in process.env)) {
61 | return defaultValue;
62 | }
63 | try {
64 | return JSON.parse(envValue!) as T;
65 | } catch {
66 | return defaultValue;
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/src/plugins/transform-module/hooks.ts:
--------------------------------------------------------------------------------
1 | // Based on babel-plugin-transform-modules-commonjs v7.24.7
2 | // MIT - Copyright (c) 2014-present Sebastian McKenzie and other contributors
3 | // https://github.com/babel/babel/tree/c7bb6e0f/packages/babel-plugin-transform-modules-commonjs/src
4 |
5 | import type { BabelFile, types as t } from "@babel/core";
6 | import type { isSideEffectImport } from "@babel/helper-module-transforms";
7 |
8 | const commonJSHooksKey =
9 | "@babel/plugin-transform-modules-commonjs/customWrapperPlugin";
10 |
11 | type SourceMetadata = Parameters[0];
12 |
13 | // A hook exposes a set of function that can customize how `require()` calls and
14 | // references to the imported bindings are handled. These functions can either
15 | // return a result, or return `null` to delegate to the next hook.
16 | export interface CommonJSHook {
17 | name: string;
18 | version: string;
19 | wrapReference?(
20 | ref: t.Expression,
21 | payload: unknown,
22 | ): t.CallExpression | null | undefined;
23 | // Optionally wrap a `require` call. If this function returns `false`, the
24 | // `require` call is removed from the generated code.
25 | buildRequireWrapper?(
26 | name: string,
27 | init: t.Expression,
28 | payload: unknown,
29 | referenced: boolean,
30 | ): t.Statement | false | null | undefined;
31 | getWrapperPayload?(
32 | source: string,
33 | metadata: SourceMetadata,
34 | importNodes: t.Node[],
35 | ): string | null | undefined;
36 | }
37 |
38 | export function defineCommonJSHook(file: BabelFile, hook: CommonJSHook) {
39 | let hooks = file.get(commonJSHooksKey);
40 | if (!hooks) file.set(commonJSHooksKey, (hooks = []));
41 | hooks.push(hook);
42 | }
43 |
44 | function findMap(
45 | arr: T[] | null,
46 | cb: (el: T) => U,
47 | ): U | undefined | null {
48 | if (arr) {
49 | for (const el of arr) {
50 | const res = cb(el);
51 | if (res != null) return res;
52 | }
53 | }
54 | }
55 |
56 | export function makeInvokers(
57 | file: BabelFile,
58 | ): Pick<
59 | CommonJSHook,
60 | "wrapReference" | "getWrapperPayload" | "buildRequireWrapper"
61 | > {
62 | const hooks: CommonJSHook[] | null = file.get(commonJSHooksKey);
63 |
64 | return {
65 | getWrapperPayload(...args) {
66 | return findMap(hooks, (hook) => hook.getWrapperPayload?.(...args));
67 | },
68 | wrapReference(...args) {
69 | return findMap(hooks, (hook) => hook.wrapReference?.(...args));
70 | },
71 | buildRequireWrapper(...args) {
72 | return findMap(hooks, (hook) => hook.buildRequireWrapper?.(...args));
73 | },
74 | };
75 | }
76 |
--------------------------------------------------------------------------------
/src/plugins/babel-plugin-transform-typescript-metadata/metadata-visitor.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Based on https://github.com/leonardfactory/babel-plugin-transform-typescript-metadata
3 | * Copyright (c) 2019 Leonardo Ascione [MIT]
4 | */
5 |
6 | import { NodePath, types as t } from "@babel/core";
7 | import { serializeType } from "./serialize-type";
8 |
9 | function createMetadataDesignDecorator(
10 | design:
11 | | "design:type"
12 | | "design:paramtypes"
13 | | "design:returntype"
14 | | "design:typeinfo",
15 | typeArg: t.Expression | t.SpreadElement | t.JSXNamespacedName,
16 | ): t.Decorator {
17 | return t.decorator(
18 | t.logicalExpression(
19 | "||",
20 | t.optionalCallExpression(
21 | t.memberExpression(t.identifier("Reflect"), t.identifier("metadata")),
22 | [t.stringLiteral(design), typeArg as unknown as t.Expression],
23 | true,
24 | ),
25 | t.arrowFunctionExpression([t.identifier("t")], t.identifier("t")),
26 | ),
27 | );
28 | }
29 |
30 | export function metadataVisitor(
31 | classPath: NodePath,
32 | path: NodePath,
33 | ) {
34 | const field = path.node;
35 | const classNode = classPath.node;
36 |
37 | switch (field.type) {
38 | case "ClassMethod": {
39 | const decorators =
40 | field.kind === "constructor" ? classNode.decorators : field.decorators;
41 |
42 | if (!decorators || decorators.length === 0) {
43 | return;
44 | }
45 |
46 | decorators!.push(
47 | createMetadataDesignDecorator("design:type", t.identifier("Function")),
48 | );
49 | decorators!.push(
50 | createMetadataDesignDecorator(
51 | "design:paramtypes",
52 | t.arrayExpression(
53 | field.params.map((param) => serializeType(classPath, param)),
54 | ),
55 | ),
56 | );
57 | // Hint: `design:returntype` could also be implemented here, although this seems
58 | // quite complicated to achieve without the TypeScript compiler.
59 | // See https://github.com/microsoft/TypeScript/blob/f807b57356a8c7e476fedc11ad98c9b02a9a0e81/src/compiler/transformers/ts.ts#L1315
60 | break;
61 | }
62 |
63 | case "ClassProperty": {
64 | if (!field.decorators || field.decorators.length === 0) {
65 | return;
66 | }
67 |
68 | if (
69 | !field.typeAnnotation ||
70 | field.typeAnnotation.type !== "TSTypeAnnotation"
71 | ) {
72 | return;
73 | }
74 |
75 | field.decorators!.push(
76 | createMetadataDesignDecorator(
77 | "design:type",
78 | serializeType(classPath, field),
79 | ),
80 | );
81 | break;
82 | }
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/src/resolve.ts:
--------------------------------------------------------------------------------
1 | import { resolveAlias } from "pathe/utils";
2 | import { fileURLToPath, resolvePathSync } from "mlly";
3 | import { join, dirname } from "pathe";
4 | import type { Context, JitiResolveOptions } from "./types";
5 | import { isDir } from "./utils";
6 |
7 | const JS_EXT_RE = /\.(c|m)?j(sx?)$/;
8 | const TS_EXT_RE = /\.(c|m)?t(sx?)$/;
9 |
10 | export function jitiResolve(
11 | ctx: Context,
12 | id: string,
13 | options: JitiResolveOptions & { async?: boolean; paths?: string[] },
14 | ) {
15 | let resolved, lastError;
16 |
17 | if (ctx.isNativeRe.test(id)) {
18 | return id;
19 | }
20 |
21 | // Resolve alias
22 | if (ctx.alias) {
23 | id = resolveAlias(id, ctx.alias);
24 | }
25 |
26 | // Resolve parent URL
27 | let parentURL = options?.parentURL || ctx.url;
28 | if (isDir(parentURL)) {
29 | parentURL = join(parentURL as string, "_index.js");
30 | }
31 |
32 | // Try resolving with ESM compatible Node.js resolution in async context
33 | const conditionSets = (
34 | options?.async
35 | ? [options?.conditions, ["node", "import"], ["node", "require"]]
36 | : [options?.conditions, ["node", "require"], ["node", "import"]]
37 | ).filter(Boolean);
38 | for (const conditions of conditionSets) {
39 | try {
40 | resolved = resolvePathSync(id, {
41 | url: parentURL,
42 | conditions,
43 | extensions: ctx.opts.extensions,
44 | });
45 | } catch (error) {
46 | lastError = error;
47 | }
48 | if (resolved) {
49 | return resolved;
50 | }
51 | }
52 |
53 | // Try native require resolve with additional extensions and /index as fallback
54 | try {
55 | return ctx.nativeRequire.resolve(id, { paths: options.paths });
56 | } catch (error) {
57 | lastError = error;
58 | }
59 | for (const ext of ctx.additionalExts) {
60 | resolved =
61 | tryNativeRequireResolve(ctx, id + ext, parentURL, options) ||
62 | tryNativeRequireResolve(ctx, id + "/index" + ext, parentURL, options);
63 | if (resolved) {
64 | return resolved;
65 | }
66 | // Try resolving .ts files with .js extension
67 | if (
68 | TS_EXT_RE.test(ctx.filename) ||
69 | TS_EXT_RE.test(ctx.parentModule?.filename || "") ||
70 | JS_EXT_RE.test(id)
71 | ) {
72 | resolved = tryNativeRequireResolve(
73 | ctx,
74 | id.replace(JS_EXT_RE, ".$1t$2"),
75 | parentURL,
76 | options,
77 | );
78 | if (resolved) {
79 | return resolved;
80 | }
81 | }
82 | }
83 |
84 | if (options?.try) {
85 | // Well-typed in types.d.ts
86 | return undefined as unknown as string;
87 | }
88 |
89 | throw lastError;
90 | }
91 |
92 | function tryNativeRequireResolve(
93 | ctx: Context,
94 | id: string,
95 | parentURL: URL | string,
96 | options?: { paths?: string[] },
97 | ) {
98 | try {
99 | return ctx.nativeRequire.resolve(id, {
100 | ...options,
101 | paths: [dirname(fileURLToPath(parentURL)), ...(options?.paths || [])],
102 | });
103 | } catch {
104 | // Ignore errors
105 | }
106 | }
107 |
--------------------------------------------------------------------------------
/src/plugins/import-meta-paths.ts:
--------------------------------------------------------------------------------
1 | import { smart } from "@babel/template";
2 | import type { NodePath, PluginObj } from "@babel/core";
3 | import type { Statement, MemberExpression } from "@babel/types";
4 | import { dirname } from "pathe";
5 | import { fileURLToPath, pathToFileURL } from "mlly";
6 |
7 | // Based on https://github.com/javiertury/babel-plugin-transform-import-meta/blob/master/src/index.ts v2.1.1 (MIT License)
8 | // Modifications
9 | // 1. Inlines resolved filename into the code when possible instead of injecting a require
10 | // 2. Add support for import.meta.dirname and import.meta.filename
11 |
12 | export default function importMetaPathsPlugin(
13 | _ctx: any,
14 | opts: { filename?: string },
15 | ) {
16 | return {
17 | name: "import-meta-paths",
18 | visitor: {
19 | Program(path) {
20 | const metaUrls: Array> = [];
21 | const metaDirnames: Array> = [];
22 | const metaFilenames: Array> = [];
23 |
24 | path.traverse({
25 | MemberExpression(memberExpPath) {
26 | const { node } = memberExpPath;
27 |
28 | if (
29 | node.object.type === "MetaProperty" &&
30 | node.object.meta.name === "import" &&
31 | node.object.property.name === "meta" &&
32 | node.property.type === "Identifier"
33 | ) {
34 | switch (node.property.name) {
35 | case "url": {
36 | metaUrls.push(memberExpPath);
37 | break;
38 | }
39 | case "dirname": {
40 | metaDirnames.push(memberExpPath);
41 | break;
42 | }
43 | case "filename": {
44 | metaFilenames.push(memberExpPath);
45 | break;
46 | }
47 | }
48 | }
49 | },
50 | });
51 |
52 | // Update import.meta.url
53 | for (const meta of metaUrls) {
54 | meta.replaceWith(
55 | smart.ast`${
56 | opts.filename
57 | ? JSON.stringify(pathToFileURL(opts.filename))
58 | : "require('url').pathToFileURL(__filename).toString()"
59 | }` as Statement,
60 | );
61 | }
62 |
63 | // Update import.meta.dirname
64 | for (const metaDirname of metaDirnames) {
65 | metaDirname.replaceWith(
66 | smart.ast`${
67 | opts.filename
68 | ? JSON.stringify(
69 | dirname(fileURLToPath(pathToFileURL(opts.filename))),
70 | )
71 | : "__dirname"
72 | }` as Statement,
73 | );
74 | }
75 |
76 | // Update import.meta.filename
77 | for (const metaFilename of metaFilenames) {
78 | metaFilename.replaceWith(
79 | smart.ast`${
80 | opts.filename
81 | ? JSON.stringify(fileURLToPath(pathToFileURL(opts.filename)))
82 | : "__filename"
83 | }` as Statement,
84 | );
85 | }
86 | },
87 | },
88 | };
89 | }
90 |
--------------------------------------------------------------------------------
/src/cache.ts:
--------------------------------------------------------------------------------
1 | import type { Context, TransformOptions } from "./types";
2 | import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
3 | import { tmpdir } from "node:os";
4 | import { dirname, join, basename, resolve } from "pathe";
5 | import { filename } from "pathe/utils";
6 | import { debug, isWritable, hash } from "./utils";
7 |
8 | const CACHE_VERSION = "9";
9 |
10 | export function getCache(
11 | ctx: Context,
12 | topts: TransformOptions,
13 | get: () => string,
14 | ): string {
15 | if (!ctx.opts.fsCache || !topts.filename) {
16 | return get();
17 | }
18 |
19 | // Compute inline hash for source
20 | const sourceHash = ` /* v${CACHE_VERSION}-${hash(topts.source, 16)} */\n`;
21 |
22 | // Compute cache file path
23 | let cacheName =
24 | `${basename(dirname(topts.filename))}-${filename(topts.filename)}` +
25 | (ctx.opts.sourceMaps ? "+map" : "") +
26 | (topts.interopDefault ? ".i" : "") +
27 | `.${hash(topts.filename)}` +
28 | (topts.async ? ".mjs" : ".cjs");
29 | if (topts.jsx && topts.filename.endsWith("x") /* jsx */) {
30 | cacheName += "x";
31 | }
32 | const cacheDir = ctx.opts.fsCache as string;
33 | const cacheFilePath = join(cacheDir, cacheName);
34 |
35 | if (!ctx.opts.rebuildFsCache && existsSync(cacheFilePath)) {
36 | const cacheSource = readFileSync(cacheFilePath, "utf8");
37 | if (cacheSource.endsWith(sourceHash)) {
38 | debug(ctx, "[cache]", "[hit]", topts.filename, "~>", cacheFilePath);
39 | return cacheSource;
40 | }
41 | }
42 |
43 | debug(ctx, "[cache]", "[miss]", topts.filename);
44 | const result = get();
45 |
46 | if (!result.includes("__JITI_ERROR__")) {
47 | writeFileSync(cacheFilePath, result + sourceHash, "utf8");
48 | debug(ctx, "[cache]", "[store]", topts.filename, "~>", cacheFilePath);
49 | }
50 |
51 | return result;
52 | }
53 |
54 | export function prepareCacheDir(ctx: Context) {
55 | if (ctx.opts.fsCache === true) {
56 | ctx.opts.fsCache = getCacheDir(ctx);
57 | }
58 | if (ctx.opts.fsCache) {
59 | try {
60 | mkdirSync(ctx.opts.fsCache as string, { recursive: true });
61 | if (!isWritable(ctx.opts.fsCache)) {
62 | throw new Error("directory is not writable!");
63 | }
64 | } catch (error: any) {
65 | debug(ctx, "Error creating cache directory at ", ctx.opts.fsCache, error);
66 | ctx.opts.fsCache = false;
67 | }
68 | }
69 | }
70 |
71 | export function getCacheDir(ctx: Context) {
72 | const nmDir = ctx.filename && resolve(ctx.filename, "../node_modules");
73 | if (nmDir && existsSync(nmDir)) {
74 | return join(nmDir, ".cache/jiti");
75 | }
76 |
77 | let _tmpDir = tmpdir();
78 |
79 | // Workaround for pnpm setting an incorrect `TMPDIR`.
80 | // Set `JITI_RESPECT_TMPDIR_ENV` to a truthy value to disable this workaround.
81 | // https://github.com/pnpm/pnpm/issues/6140
82 | // https://github.com/unjs/jiti/issues/120
83 | if (
84 | process.env.TMPDIR &&
85 | _tmpDir === process.cwd() &&
86 | !process.env.JITI_RESPECT_TMPDIR_ENV
87 | ) {
88 | const _env = process.env.TMPDIR;
89 | delete process.env.TMPDIR;
90 | _tmpDir = tmpdir();
91 | process.env.TMPDIR = _env;
92 | }
93 |
94 | return join(_tmpDir, "jiti");
95 | }
96 |
--------------------------------------------------------------------------------
/test/fixtures.test.ts:
--------------------------------------------------------------------------------
1 | import { join, resolve, dirname } from "node:path";
2 | import { x } from "tinyexec";
3 | import { describe, it, expect } from "vitest";
4 | import fg from "fast-glob";
5 |
6 | const nodeMajorVersion = Number.parseInt(
7 | process.versions.node.split(".")[0],
8 | 10,
9 | );
10 |
11 | describe("fixtures", async () => {
12 | const jitiPath = resolve(__dirname, "../lib/jiti-cli.mjs");
13 |
14 | const root = dirname(__dirname);
15 | const dir = join(__dirname, "fixtures");
16 | const fixtures = await fg("*/index.*", { cwd: dir });
17 |
18 | for (const fixture of fixtures) {
19 | const name = dirname(fixture);
20 | it(name, async () => {
21 | const fixtureEntry = join(dir, fixture);
22 | const cwd = dirname(fixtureEntry);
23 |
24 | // Clean up absolute paths and sourcemap locations for stable snapshots
25 | function cleanUpSnap(str: string) {
26 | return (
27 | (str + "\n")
28 | .replace(/\n\t/g, "\n")
29 | .replace(/\\+/g, "/")
30 | .split(cwd.replace(/\\/g, "/"))
31 | .join("") // workaround for replaceAll in Node 14
32 | .split(root.replace(/\\/g, "/"))
33 | .join("") // workaround for replaceAll in Node 14
34 | .replace(/:\d+:\d+([\s')])/g, "$1") // remove line numbers in stacktrace
35 | .replace(/node:(internal|events)/g, "$1") // in Node 16 internal will be presented as node:internal
36 | .replace(/\.js\)/g, ")")
37 | .replace(/file:\/{3}/g, "file://")
38 | .replace(/Node.js v[\d.]+/, "Node.js v")
39 | .replace(/ParseError: \w:\/:\s+/, "ParseError: ") // Unknown chars in Windows
40 | .replace("TypeError [ERR_INVALID_ARG_TYPE]:", "TypeError:")
41 | .replace("eval_evalModule", "evalModule")
42 | .replace(/\(node:\d+\)/g, "(node)")
43 | // Node 18
44 | .replace(
45 | " ErrorCaptureStackTrace(err);",
46 | "validateFunction(listener, 'listener');",
47 | )
48 | .replace("internal/errors:496", "events:276")
49 | .replace(" ^", " ^")
50 | .replace(/ExperimentalWarning: CommonJS module/, "")
51 | // eslint-disable-next-line no-control-regex
52 | .replace(/\u001B\[[\d;]*m/gu, "")
53 | .trim()
54 | );
55 | }
56 |
57 | function extractErrors(stderr: string) {
58 | const errors = [] as string[];
59 | for (const m of stderr.matchAll(/\w*(Error|Warning).*:.*$/gm)) {
60 | errors.push(m[0]);
61 | }
62 | return errors;
63 | }
64 |
65 | const { stdout, stderr } = await x("node", [jitiPath, fixtureEntry], {
66 | nodeOptions: {
67 | cwd,
68 | stdio: "pipe",
69 | env: {
70 | JITI_CACHE: "false",
71 | JITI_JSX: "true",
72 | },
73 | },
74 | });
75 |
76 | if (
77 | name.includes("error") ||
78 | (nodeMajorVersion >= 22 && name === "require-esm")
79 | ) {
80 | expect(extractErrors(cleanUpSnap(stderr))).toMatchSnapshot("stderr");
81 | } else {
82 | // Expect no error by default
83 | expect(stderr).toBe("");
84 | }
85 |
86 | expect(cleanUpSnap(stdout)).toMatchSnapshot("stdout");
87 | });
88 | }
89 | });
90 |
--------------------------------------------------------------------------------
/src/babel.ts:
--------------------------------------------------------------------------------
1 | import type {
2 | TransformOptions as BabelTransformOptions,
3 | PluginItem,
4 | } from "@babel/core";
5 | import { transformSync } from "@babel/core";
6 | import proposalDecoratorsPlugin from "@babel/plugin-proposal-decorators";
7 | import syntaxClassPropertiesPlugin from "@babel/plugin-syntax-class-properties";
8 | import syntaxImportAssertionsPlugin from "@babel/plugin-syntax-import-assertions";
9 | import syntaxJSXPlugin from "@babel/plugin-syntax-jsx";
10 | import transformExportNamespaceFromPlugin from "@babel/plugin-transform-export-namespace-from";
11 | import transformReactJSX from "@babel/plugin-transform-react-jsx";
12 | import transformTypeScriptPlugin from "@babel/plugin-transform-typescript";
13 | import parameterDecoratorPlugin from "babel-plugin-parameter-decorator";
14 | import transformTypeScriptMetaPlugin from "./plugins/babel-plugin-transform-typescript-metadata";
15 | import importMetaEnvPlugin from "./plugins/import-meta-env";
16 | import importMetaResolvePlugin from "./plugins/import-meta-resolve";
17 | import importMetaPathsPlugin from "./plugins/import-meta-paths";
18 | import transformModulesPlugin from "./plugins/transform-module";
19 | import type { TransformOptions, TransformResult } from "./types";
20 |
21 | export default function transform(opts: TransformOptions): TransformResult {
22 | const _opts: BabelTransformOptions & { plugins: PluginItem[] } = {
23 | babelrc: false,
24 | configFile: false,
25 | compact: false,
26 | retainLines:
27 | typeof opts.retainLines === "boolean" ? opts.retainLines : true,
28 | filename: "",
29 | cwd: "/",
30 | ...opts.babel,
31 | plugins: [
32 | [
33 | transformModulesPlugin,
34 | {
35 | allowTopLevelThis: true,
36 | noInterop: !opts.interopDefault,
37 | async: opts.async,
38 | },
39 | ],
40 | [importMetaPathsPlugin, { filename: opts.filename }],
41 | [importMetaEnvPlugin],
42 | [importMetaResolvePlugin],
43 | [syntaxClassPropertiesPlugin],
44 | [transformExportNamespaceFromPlugin],
45 | ],
46 | };
47 |
48 | if (opts.jsx) {
49 | _opts.plugins.push(
50 | [syntaxJSXPlugin],
51 | [transformReactJSX, Object.assign({}, opts.jsx)],
52 | );
53 | }
54 |
55 | if (opts.ts) {
56 | _opts.plugins.push([
57 | transformTypeScriptPlugin,
58 | {
59 | allowDeclareFields: true,
60 | isTSX: opts.jsx && /\.[cm]?tsx$/.test(opts.filename || ""),
61 | },
62 | ]);
63 | // `unshift` because these plugin must come before `@babel/plugin-syntax-class-properties`
64 | _opts.plugins.unshift(
65 | [transformTypeScriptMetaPlugin],
66 | [proposalDecoratorsPlugin, { legacy: true }],
67 | );
68 | _opts.plugins.push(parameterDecoratorPlugin, syntaxImportAssertionsPlugin);
69 | }
70 |
71 | if (opts.babel && Array.isArray(opts.babel.plugins)) {
72 | _opts.plugins?.push(...opts.babel.plugins);
73 | }
74 |
75 | try {
76 | return {
77 | code: transformSync(opts.source, _opts)?.code || "",
78 | };
79 | } catch (error: any) {
80 | return {
81 | error,
82 | code:
83 | "exports.__JITI_ERROR__ = " +
84 | JSON.stringify({
85 | filename: opts.filename,
86 | line: error.loc?.line || 0,
87 | column: error.loc?.column || 0,
88 | code: error.code
89 | ?.replace("BABEL_", "")
90 | .replace("PARSE_ERROR", "ParseError"),
91 | message: error.message?.replace("/: ", "").replace(/\(.+\)\s*$/, ""),
92 | }),
93 | };
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/lib/jiti-native.mjs:
--------------------------------------------------------------------------------
1 | /**
2 | * @typedef {import('./types').Jiti} Jiti
3 | * @typedef {import('./types').JitiOptions} JitiOptions
4 | */
5 |
6 | const isDeno = "Deno" in globalThis;
7 |
8 | /**
9 | * @param {string|URL} [parentURL]
10 | * @param {JitiOptions} [jitiOptions]
11 | * @returns {Jiti}
12 | */
13 | export function createJiti(parentURL, jitiOptions) {
14 | parentURL = normalizeParentURL(parentURL);
15 |
16 | /** @type {Jiti} */
17 | function jiti() {
18 | throw unsupportedError(
19 | "`jiti()` is not supported in native mode, use `jiti.import()` instead.",
20 | );
21 | }
22 |
23 | jiti.resolve = () => {
24 | throw unsupportedError("`jiti.resolve()` is not supported in native mode.");
25 | };
26 |
27 | jiti.esmResolve = (id, opts) => {
28 | try {
29 | const importMeta = jitiOptions?.importMeta || import.meta;
30 | if (isDeno) {
31 | // Deno throws TypeError: Invalid arguments when passing parentURL
32 | return importMeta.resolve(id);
33 | }
34 | const parent = normalizeParentURL(opts?.parentURL || parentURL);
35 | return importMeta.resolve(id, parent);
36 | } catch (error) {
37 | if (opts?.try) {
38 | return undefined;
39 | } else {
40 | throw error;
41 | }
42 | }
43 | };
44 |
45 | jiti.import = async function (id, opts) {
46 | for (const suffix of ["", "/index"]) {
47 | // prettier-ignore
48 | for (const ext of ["", ".js", ".mjs", ".cjs", ".ts", ".tsx", ".mts", ".cts"]) {
49 | try {
50 | const resolved = this.esmResolve(id + suffix + ext, opts);
51 | if (!resolved) {
52 | continue;
53 | }
54 | let importAttrs = undefined
55 | if (resolved.endsWith('.json')) {
56 | importAttrs = { with: { type: 'json'}}
57 | }
58 | return await import(resolved, importAttrs);
59 | } catch (error) {
60 | if (error.code === 'ERR_MODULE_NOT_FOUND' || error.code === 'ERR_UNSUPPORTED_DIR_IMPORT') {
61 | continue
62 | }
63 | if (opts?.try) {
64 | return undefined;
65 | }
66 | throw error;
67 | }
68 | }
69 | }
70 | if (!opts?.try) {
71 | const parent = normalizeParentURL(opts?.parentURL || parentURL);
72 | const error = new Error(
73 | `[jiti] [ERR_MODULE_NOT_FOUND] Cannot import '${id}' from '${parent}'.`,
74 | );
75 | error.code = "ERR_MODULE_NOT_FOUND";
76 | throw error;
77 | }
78 | };
79 |
80 | jiti.transform = () => {
81 | throw unsupportedError(
82 | "`jiti.transform()` is not supported in native mode.",
83 | );
84 | };
85 |
86 | jiti.evalModule = () => {
87 | throw unsupportedError(
88 | "`jiti.evalModule()` is not supported in native mode.",
89 | );
90 | };
91 |
92 | jiti.main = undefined;
93 | jiti.extensions = Object.create(null);
94 | jiti.cache = Object.create(null);
95 |
96 | return jiti;
97 | }
98 |
99 | export default createJiti;
100 |
101 | /**
102 | * @param {string} message
103 | */
104 | function unsupportedError(message) {
105 | throw new Error(
106 | `[jiti] ${message} (import or require 'jiti' instead of 'jiti/native' for more features).`,
107 | );
108 | }
109 |
110 | function normalizeParentURL(input) {
111 | if (!input) {
112 | return "file:///";
113 | }
114 | if (typeof filename !== "string" || input.startsWith("file://")) {
115 | return input;
116 | }
117 | if (input.endsWith("/")) {
118 | input += "_"; // append a dummy filename
119 | }
120 | return `file://${input}`;
121 | }
122 |
--------------------------------------------------------------------------------
/src/plugins/babel-plugin-transform-typescript-metadata/parameter-visitor.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Based on https://github.com/leonardfactory/babel-plugin-transform-typescript-metadata
3 | * Copyright (c) 2019 Leonardo Ascione [MIT]
4 | */
5 |
6 | import { NodePath, types as t } from "@babel/core";
7 |
8 | /**
9 | * Helper function to create a field/class decorator from a parameter decorator.
10 | * Field/class decorators get three arguments: the class, the name of the method
11 | * (or 'undefined' in the case of the constructor) and the position index of the
12 | * parameter in the argument list.
13 | * Some of this information, the index, is only available at transform time, and
14 | * has to be stored. The other arguments are part of the decorator signature and
15 | * will be passed to the decorator anyway. But the decorator has to be called
16 | * with all three arguments at runtime, so this creates a function wrapper, which
17 | * takes the target and the key, and adds the index to it.
18 | *
19 | * Inject() becomes function(target, key) { return Inject()(target, key, 0) }
20 | *
21 | * @param paramIndex the index of the parameter inside the function call
22 | * @param decoratorExpression the decorator expression, the return object of SomeParameterDecorator()
23 | * @param isConstructor indicates if the key should be set to 'undefined'
24 | */
25 | function createParamDecorator(
26 | paramIndex: number,
27 | decoratorExpression: t.Expression,
28 | isConstructor = false,
29 | ) {
30 | return t.decorator(
31 | t.functionExpression(
32 | null, // anonymous function
33 | [t.identifier("target"), t.identifier("key")],
34 | t.blockStatement([
35 | t.returnStatement(
36 | t.callExpression(decoratorExpression, [
37 | t.identifier("target"),
38 | t.identifier(isConstructor ? "undefined" : "key"),
39 | t.numericLiteral(paramIndex),
40 | ]),
41 | ),
42 | ]),
43 | ),
44 | );
45 | }
46 |
47 | export function parameterVisitor(
48 | classPath: NodePath,
49 | path: NodePath | NodePath,
50 | ) {
51 | if (path.type !== "ClassMethod") {
52 | return;
53 | }
54 | if (path.node.type !== "ClassMethod") {
55 | return;
56 | }
57 | if (path.node.key.type !== "Identifier") {
58 | return;
59 | }
60 |
61 | const methodPath = path as NodePath;
62 | const params = methodPath.get("params") || [];
63 |
64 | for (const param of params) {
65 | const identifier =
66 | param.node.type === "Identifier" || param.node.type === "ObjectPattern"
67 | ? param.node
68 | : // eslint-disable-next-line unicorn/no-nested-ternary
69 | param.node.type === "TSParameterProperty" &&
70 | param.node.parameter.type === "Identifier"
71 | ? param.node.parameter
72 | : null;
73 |
74 | if (identifier == null) {
75 | continue;
76 | }
77 |
78 | let resultantDecorator: t.Decorator | undefined;
79 |
80 | for (const decorator of (param.node as t.Identifier).decorators || []) {
81 | if (methodPath.node.kind === "constructor") {
82 | resultantDecorator = createParamDecorator(
83 | param.key as number,
84 | decorator.expression,
85 | true,
86 | );
87 | if (!classPath.node.decorators) {
88 | classPath.node.decorators = [];
89 | }
90 | classPath.node.decorators.push(resultantDecorator);
91 | } else {
92 | resultantDecorator = createParamDecorator(
93 | param.key as number,
94 | decorator.expression,
95 | false,
96 | );
97 | if (!methodPath.node.decorators) {
98 | methodPath.node.decorators = [];
99 | }
100 | methodPath.node.decorators.push(resultantDecorator);
101 | }
102 | }
103 |
104 | if (resultantDecorator) {
105 | (param.node as t.Identifier).decorators = null;
106 | }
107 | }
108 | }
109 |
--------------------------------------------------------------------------------
/lib/jiti-hooks.mjs:
--------------------------------------------------------------------------------
1 | import { dirname, join } from "node:path";
2 | import { fileURLToPath } from "node:url";
3 | import { existsSync } from "node:fs";
4 | import { readFile } from "node:fs/promises";
5 | import { isBuiltin } from "node:module";
6 | import { createJiti } from "./jiti.mjs";
7 |
8 | let jiti;
9 |
10 | // https://nodejs.org/api/module.html#initialize
11 | export async function initialize() {
12 | jiti = createJiti();
13 | }
14 |
15 | // https://nodejs.org/api/module.html#resolvespecifier-context-nextresolve
16 | export async function resolve(specifier, context, nextResolve) {
17 | if (_shouldSkip(specifier)) {
18 | return nextResolve(specifier, context);
19 | }
20 | const resolvedPath = jiti.esmResolve(specifier, {
21 | parentURL: context?.parentURL,
22 | conditions: context?.conditions,
23 | });
24 | return {
25 | url: resolvedPath,
26 | shortCircuit: true,
27 | };
28 | }
29 |
30 | // https://nodejs.org/api/module.html#loadurl-context-nextload
31 | export async function load(url, context, nextLoad) {
32 | if (_shouldSkip(url)) {
33 | return nextLoad(url, context);
34 | }
35 |
36 | const filename = fileURLToPath(url);
37 |
38 | if (url.endsWith(".js")) {
39 | const pkg = await _findClosestPackageJson(dirname(filename));
40 | if (pkg && pkg.type === "module") {
41 | return nextLoad(url, context);
42 | }
43 | }
44 |
45 | const rawSource = await readFile(filename, "utf8");
46 |
47 | if (url.endsWith(".json")) {
48 | const pkg = await _findClosestPackageJson(dirname(filename));
49 | return pkg && pkg.type === "module"
50 | ? {
51 | source: `export default ${rawSource}`,
52 | format: "module",
53 | shortCircuit: true,
54 | }
55 | : {
56 | source: `module.exports = ${rawSource}`,
57 | format: "commonjs",
58 | shortCircuit: true,
59 | };
60 | }
61 |
62 | const transpiledSource = jiti.transform({
63 | source: rawSource,
64 | filename: filename,
65 | ts: url.endsWith("ts"),
66 | retainLines: true,
67 | async: true,
68 | jsx: jiti.options.jsx,
69 | });
70 |
71 | if (url.endsWith(".js") && !transpiledSource.includes("jitiImport")) {
72 | return {
73 | source: transpiledSource,
74 | format: "commonjs",
75 | shortCircuit: true,
76 | };
77 | }
78 |
79 | return {
80 | source: _wrapSource(transpiledSource, filename),
81 | format: "module",
82 | shortCircuit: true,
83 | };
84 | }
85 |
86 | function _wrapSource(source, filename) {
87 | const _jitiPath = new URL("jiti.mjs", import.meta.url).href;
88 | return /*js*/ `import { createJiti as __createJiti__ } from ${JSON.stringify(_jitiPath)};async function _module(exports, require, module, __filename, __dirname, jitiImport) { ${source}\n};
89 | // GENERATED BY JITI ESM LOADER
90 | const filename = ${JSON.stringify(filename)};
91 | const dirname = ${JSON.stringify(dirname(filename))};
92 | const jiti = __createJiti__(filename);
93 | const module = { exports: Object.create(null) };
94 | await _module(module.exports, jiti, module, filename, dirname, jiti.import);
95 | if (module.exports && module.exports.__JITI_ERROR__) {
96 | const { filename, line, column, code, message } =
97 | module.exports.__JITI_ERROR__;
98 | const loc = [filename, line, column].join(':');
99 | const err = new Error(code + ": " + message + " " + loc);
100 | Error.captureStackTrace(err, _module);
101 | throw err;
102 | }
103 | export default module.exports;
104 | `;
105 | }
106 |
107 | function _shouldSkip(url) {
108 | return (
109 | !jiti ||
110 | url.endsWith(".mjs") ||
111 | url.endsWith(".cjs") ||
112 | (!url.startsWith("./") && !url.startsWith("file://")) ||
113 | isBuiltin(url)
114 | );
115 | }
116 |
117 | async function _findClosestPackageJson(dir) {
118 | if (dir === "/") return null;
119 | const packageJsonPath = join(dir, "package.json");
120 | if (existsSync(packageJsonPath)) {
121 | return JSON.parse(await readFile(packageJsonPath, "utf8"));
122 | }
123 | return _findClosestPackageJson(dirname(dir));
124 | }
125 |
--------------------------------------------------------------------------------
/test/__snapshots__/fixtures.test.ts.snap:
--------------------------------------------------------------------------------
1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
2 |
3 | exports[`fixtures > async > stdout 1`] = `"works"`;
4 |
5 | exports[`fixtures > circular > stdout 1`] = `"a b c"`;
6 |
7 | exports[`fixtures > cjs-interop > stdout 1`] = `"CJS function default interop test passed"`;
8 |
9 | exports[`fixtures > data-uri > stdout 1`] = `""`;
10 |
11 | exports[`fixtures > deps > stdout 1`] = `
12 | "npm:config: true
13 | npm:defu {}
14 | npm:destr true
15 | npm:etag: true
16 | npm:is-installed-globally false
17 | npm:mime: true
18 | npm:typescript: true
19 | npm:moment-timezone true
20 | npm:zod: true"
21 | `;
22 |
23 | exports[`fixtures > env > stdout 1`] = `
24 | "process.env true
25 | process.env.TEST true
26 | process.env?.TEST true
27 | import.meta true
28 | import.meta.env.TEST true
29 | import.meta.env?.TEST true"
30 | `;
31 |
32 | exports[`fixtures > error-parse > stderr 1`] = `
33 | [
34 | "Error: ParseError: Unexpected token ",
35 | ]
36 | `;
37 |
38 | exports[`fixtures > error-parse > stdout 1`] = `""`;
39 |
40 | exports[`fixtures > error-runtime > stderr 1`] = `
41 | [
42 | "Error: test error",
43 | ]
44 | `;
45 |
46 | exports[`fixtures > error-runtime > stdout 1`] = `""`;
47 |
48 | exports[`fixtures > esm > stdout 1`] = `
49 | "{ utilsLib: { utils: { a: 'a', default: 'default' }, version: '123' } }
50 | { utils: { a: 'a', default: 'default' } }
51 | {
52 | file: '/test.js',
53 | dir: '',
54 | 'import.meta.url': 'file:///test.js'
55 | }"
56 | `;
57 |
58 | exports[`fixtures > export-promise > stdout 1`] = `
59 | "module: [Module: null prototype] {
60 | bar: 'bar',
61 | default: Promise { 'foo' },
62 | foo: [AsyncFunction: foo]
63 | }
64 | default module: foo"
65 | `;
66 |
67 | exports[`fixtures > hashbang > stdout 1`] = `"1"`;
68 |
69 | exports[`fixtures > import-map > stdout 1`] = `"{ alias: 'alias' }"`;
70 |
71 | exports[`fixtures > import-meta > stdout 1`] = `
72 | "hello! { hello: 'world' }
73 | file:///resolve.ts
74 | hello! custom
75 | file:///resolve+custom.ts
76 | import.meta.dirname:
77 | import.meta.filename: /dirname.ts"
78 | `;
79 |
80 | exports[`fixtures > json > stdout 1`] = `
81 | "Imported : { test: 123 } .default: { test: 123 }
82 | Imported with assertion : { test: 123 } .default: { test: 123 }
83 | Required : { test: 123 } .default: { test: 123 }
84 | Dynamic Imported : [Object: null prototype] { default: { test: 123 }, test: 123 } .default: { test: 123 }"
85 | `;
86 |
87 | exports[`fixtures > jsx > stdout 1`] = `
88 | "Hello, nano-jsx!
89 | Hello, preact!
90 | Hello, react!
91 | Hello, vue!
"
92 | `;
93 |
94 | exports[`fixtures > mixed > stdout 1`] = `"Mixed works for: "`;
95 |
96 | exports[`fixtures > native > stdout 1`] = `"[Module: null prototype] { default: { hasRequire: false } }"`;
97 |
98 | exports[`fixtures > node > stdout 1`] = `"node:test true"`;
99 |
100 | exports[`fixtures > proto > stdout 1`] = `"exists: true"`;
101 |
102 | exports[`fixtures > pure-esm-dep > stdout 1`] = `
103 | "Enter Program
104 | Enter VariableDeclaration
105 | Enter VariableDeclarator
106 | Enter Identifier
107 | Enter Literal"
108 | `;
109 |
110 | exports[`fixtures > require-esm > stderr 1`] = `[]`;
111 |
112 | exports[`fixtures > require-esm > stdout 1`] = `"Works!"`;
113 |
114 | exports[`fixtures > require-json > stdout 1`] = `"{ type: 'commonjs' }"`;
115 |
116 | exports[`fixtures > resolve > stdout 1`] = `"resolved path /_dist/foo.mjs"`;
117 |
118 | exports[`fixtures > syntax > stdout 1`] = `
119 | "Optional chaining: undefined
120 | Nullish coalescing: 0
121 | Logical or assignment: 50 title is empty.
122 | Logical nullish assignment: 50 20"
123 | `;
124 |
125 | exports[`fixtures > top-level-await > stdout 1`] = `"async value works from sub module"`;
126 |
127 | exports[`fixtures > typescript > stdout 1`] = `
128 | "Decorator metadata keys: design:type
129 | Decorator called with 3 arguments.
130 | Decorator called with 3 arguments.
131 | Decorator called with 3 arguments.
132 | Decorator called with 1 arguments.
133 | foo
134 | {
135 | file: '/test.ts',
136 | dir: '',
137 | resolve: '/test.ts'
138 | } [class DecoratedClass]
139 | {
140 | satisfiesTest: {
141 | firstTest: { name: 'first', avatar: 'https://example.com/first.png' },
142 | secondTest: { name: 'second', avatar: [Object] },
143 | normalizeUserEntity: [Function: normalizeUserEntity]
144 | }
145 | }
146 | child
147 | promise resolved"
148 | `;
149 |
--------------------------------------------------------------------------------
/src/require.ts:
--------------------------------------------------------------------------------
1 | import type { Context, JitiResolveOptions } from "./types";
2 | import { readFileSync } from "node:fs";
3 | import { builtinModules } from "node:module";
4 | import { fileURLToPath } from "node:url";
5 | import { extname } from "pathe";
6 | import { jitiInteropDefault, normalizeWindowsImportId } from "./utils";
7 | import { debug } from "./utils";
8 | import { jitiResolve } from "./resolve";
9 | import { evalModule } from "./eval";
10 |
11 | export function jitiRequire(
12 | ctx: Context,
13 | id: string,
14 | opts: JitiResolveOptions & { async: boolean },
15 | ) {
16 | const cache = ctx.parentCache || {};
17 |
18 | // Check for node:, file:, and data: protocols
19 | if (id.startsWith("node:")) {
20 | return nativeImportOrRequire(ctx, id, opts.async);
21 | } else if (id.startsWith("file:")) {
22 | id = fileURLToPath(id);
23 | } else if (id.startsWith("data:")) {
24 | if (!opts.async) {
25 | throw new Error(
26 | "`data:` URLs are only supported in ESM context. Use `import` or `jiti.import` instead.",
27 | );
28 | }
29 | debug(ctx, "[native]", "[data]", "[import]", id);
30 | return nativeImportOrRequire(ctx, id, true);
31 | }
32 |
33 | // Check for builtin node module like fs
34 | if (builtinModules.includes(id) || id === ".pnp.js" /* #24 */) {
35 | return nativeImportOrRequire(ctx, id, opts.async);
36 | }
37 |
38 | // Experimental Bun support
39 | if (ctx.opts.tryNative && !ctx.opts.transformOptions) {
40 | try {
41 | id = jitiResolve(ctx, id, opts);
42 | if (!id && opts.try) {
43 | return undefined;
44 | }
45 | debug(
46 | ctx,
47 | "[try-native]",
48 | opts.async && ctx.nativeImport ? "[import]" : "[require]",
49 | id,
50 | );
51 | if (opts.async && ctx.nativeImport) {
52 | return ctx.nativeImport(id).then((m: any) => {
53 | if (ctx.opts.moduleCache === false) {
54 | delete ctx.nativeRequire.cache[id];
55 | }
56 | return jitiInteropDefault(ctx, m);
57 | });
58 | } else {
59 | const _mod = ctx.nativeRequire(id);
60 | if (ctx.opts.moduleCache === false) {
61 | delete ctx.nativeRequire.cache[id];
62 | }
63 | return jitiInteropDefault(ctx, _mod);
64 | }
65 | } catch (error: any) {
66 | debug(
67 | ctx,
68 | `[try-native] Using fallback for ${id} because of an error:`,
69 | error,
70 | );
71 | }
72 | }
73 |
74 | // Resolve path
75 | const filename = jitiResolve(ctx, id, opts);
76 | if (!filename && opts.try) {
77 | return undefined;
78 | }
79 | const ext = extname(filename);
80 |
81 | // Check for .json modules
82 | if (ext === ".json") {
83 | debug(ctx, "[json]", filename);
84 | const jsonModule = ctx.nativeRequire(filename);
85 | if (jsonModule && !("default" in jsonModule)) {
86 | Object.defineProperty(jsonModule, "default", {
87 | value: jsonModule,
88 | enumerable: false,
89 | });
90 | }
91 | return jsonModule;
92 | }
93 |
94 | // Unknown format
95 | if (ext && !ctx.opts.extensions!.includes(ext)) {
96 | debug(
97 | ctx,
98 | "[native]",
99 | "[unknown]",
100 | opts.async ? "[import]" : "[require]",
101 | filename,
102 | );
103 | return nativeImportOrRequire(ctx, filename, opts.async);
104 | }
105 |
106 | // Force native modules
107 | if (ctx.isNativeRe.test(filename)) {
108 | debug(ctx, "[native]", opts.async ? "[import]" : "[require]", filename);
109 | return nativeImportOrRequire(ctx, filename, opts.async);
110 | }
111 |
112 | // Check for runtime cache
113 | if (cache[filename]) {
114 | return jitiInteropDefault(ctx, cache[filename]?.exports);
115 | }
116 | if (ctx.opts.moduleCache) {
117 | const cacheEntry = ctx.nativeRequire.cache[filename];
118 | if (cacheEntry?.loaded) {
119 | return jitiInteropDefault(ctx, cacheEntry.exports);
120 | }
121 | }
122 |
123 | // Read source
124 | const source = readFileSync(filename, "utf8");
125 |
126 | // Evaluate module
127 | return evalModule(ctx, source, {
128 | id,
129 | filename,
130 | ext,
131 | cache,
132 | async: opts.async,
133 | });
134 | }
135 |
136 | export function nativeImportOrRequire(
137 | ctx: Context,
138 | id: string,
139 | async?: boolean,
140 | ) {
141 | return async && ctx.nativeImport
142 | ? ctx
143 | .nativeImport(normalizeWindowsImportId(id))
144 | .then((m: any) => jitiInteropDefault(ctx, m))
145 | : jitiInteropDefault(ctx, ctx.nativeRequire(id));
146 | }
147 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "jiti",
3 | "version": "2.6.1",
4 | "description": "Runtime typescript and ESM support for Node.js",
5 | "repository": "unjs/jiti",
6 | "license": "MIT",
7 | "type": "module",
8 | "exports": {
9 | ".": {
10 | "import": {
11 | "types": "./lib/jiti.d.mts",
12 | "default": "./lib/jiti.mjs"
13 | },
14 | "require": {
15 | "types": "./lib/jiti.d.cts",
16 | "default": "./lib/jiti.cjs"
17 | }
18 | },
19 | "./register": {
20 | "types": "./lib/jiti-register.d.mts",
21 | "import": "./lib/jiti-register.mjs"
22 | },
23 | "./native": {
24 | "types": "./lib/jiti.d.mts",
25 | "import": "./lib/jiti-native.mjs"
26 | },
27 | "./package.json": "./package.json"
28 | },
29 | "main": "./lib/jiti.cjs",
30 | "module": "./lib/jiti.mjs",
31 | "types": "./lib/jiti.d.cts",
32 | "typesVersions": {
33 | "*": {
34 | "register": [
35 | "./lib/jiti-register.d.mts"
36 | ],
37 | "native": [
38 | "./lib/jiti.d.mts"
39 | ]
40 | }
41 | },
42 | "bin": {
43 | "jiti": "./lib/jiti-cli.mjs"
44 | },
45 | "files": [
46 | "lib",
47 | "dist",
48 | "register.cjs"
49 | ],
50 | "scripts": {
51 | "bench": "node test/bench.mjs && deno -A test/bench.mjs && bun --bun test/bench.mjs",
52 | "build": "pnpm clean && pnpm rspack",
53 | "clean": "rm -rf dist",
54 | "dev": "pnpm clean && pnpm rspack --watch",
55 | "jiti": "JITI_DEBUG=1 JITI_JSX=1 lib/jiti-cli.mjs",
56 | "lint": "eslint . && prettier -c src lib test stubs",
57 | "lint:fix": "eslint --fix . && prettier -w src lib test stubs",
58 | "prepack": "pnpm build",
59 | "release": "pnpm build && pnpm test && changelogen --release --push --publish",
60 | "test": "pnpm lint && pnpm test:types && vitest run --coverage && pnpm test:node-register && pnpm test:bun && pnpm test:native",
61 | "test:bun": "bun --bun test test/bun",
62 | "test:native": "pnpm test:native:bun && pnpm test:native:node && pnpm test:native:deno",
63 | "test:native:bun": "bun --bun test test/native/bun.test.ts",
64 | "test:native:deno": "deno test -A --no-check test/native/deno.test.ts",
65 | "test:native:node": "node --test --experimental-strip-types test/native/node.test.ts",
66 | "test:node-register": "JITI_JSX=1 node --test test/node-register.test.mjs",
67 | "test:types": "tsc --noEmit"
68 | },
69 | "devDependencies": {
70 | "@babel/core": "^7.28.4",
71 | "@babel/helper-module-imports": "^7.27.1",
72 | "@babel/helper-module-transforms": "^7.28.3",
73 | "@babel/helper-plugin-utils": "^7.27.1",
74 | "@babel/helper-simple-access": "^7.27.1",
75 | "@babel/plugin-proposal-decorators": "^7.28.0",
76 | "@babel/plugin-syntax-class-properties": "^7.12.13",
77 | "@babel/plugin-syntax-import-assertions": "^7.27.1",
78 | "@babel/plugin-syntax-jsx": "^7.27.1",
79 | "@babel/plugin-transform-export-namespace-from": "^7.27.1",
80 | "@babel/plugin-transform-react-jsx": "^7.27.1",
81 | "@babel/plugin-transform-typescript": "^7.28.0",
82 | "@babel/preset-typescript": "^7.27.1",
83 | "@babel/template": "^7.27.2",
84 | "@babel/traverse": "^7.28.4",
85 | "@babel/types": "^7.28.4",
86 | "@rspack/cli": "^1.5.8",
87 | "@rspack/core": "^1.5.8",
88 | "@types/babel__core": "^7.20.5",
89 | "@types/babel__helper-module-imports": "^7.18.3",
90 | "@types/babel__helper-plugin-utils": "^7.10.3",
91 | "@types/babel__template": "^7.4.4",
92 | "@types/babel__traverse": "^7.28.0",
93 | "@types/node": "^24.6.1",
94 | "@vitest/coverage-v8": "^3.2.4",
95 | "acorn": "^8.15.0",
96 | "babel-plugin-parameter-decorator": "^1.0.16",
97 | "changelogen": "^0.6.2",
98 | "config": "^4.1.1",
99 | "consola": "^3.4.2",
100 | "defu": "^6.1.4",
101 | "destr": "^2.0.5",
102 | "escape-string-regexp": "^5.0.0",
103 | "eslint": "^9.36.0",
104 | "eslint-config-unjs": "^0.5.0",
105 | "estree-walker": "^3.0.3",
106 | "etag": "^1.8.1",
107 | "fast-glob": "^3.3.3",
108 | "is-installed-globally": "^1.0.0",
109 | "mime": "^4.1.0",
110 | "mlly": "^1.8.0",
111 | "moment-timezone": "^0.6.0",
112 | "nano-jsx": "^0.2.0",
113 | "pathe": "^2.0.3",
114 | "pkg-types": "^2.3.0",
115 | "preact": "^10.27.2",
116 | "preact-render-to-string": "^6.6.2",
117 | "prettier": "^3.6.2",
118 | "react": "^19.1.1",
119 | "react-dom": "^19.1.1",
120 | "reflect-metadata": "^0.2.2",
121 | "solid-js": "^1.9.9",
122 | "std-env": "^3.9.0",
123 | "terser-webpack-plugin": "^5.3.14",
124 | "tinyexec": "^1.0.1",
125 | "ts-loader": "^9.5.4",
126 | "typescript": "^5.9.3",
127 | "vitest": "^3.2.4",
128 | "vue": "^3.5.22",
129 | "yoctocolors": "^2.1.2",
130 | "zod": "^4.1.11"
131 | },
132 | "packageManager": "pnpm@10.17.1"
133 | }
134 |
--------------------------------------------------------------------------------
/src/utils.ts:
--------------------------------------------------------------------------------
1 | import { lstatSync, accessSync, constants, readFileSync } from "node:fs";
2 | import nodeCrypto from "node:crypto";
3 | import { isAbsolute, join } from "pathe";
4 | import type { PackageJson } from "pkg-types";
5 | import { pathToFileURL } from "mlly";
6 | import { isWindows } from "std-env";
7 | import type { Context } from "./types";
8 | import { gray, green, blue, yellow, cyan, red } from "yoctocolors";
9 |
10 | export function isDir(filename: string | URL): boolean {
11 | if (typeof filename !== "string" || filename.startsWith("file://")) {
12 | return false;
13 | }
14 | try {
15 | const stat = lstatSync(filename);
16 | return stat.isDirectory();
17 | } catch {
18 | // lstatSync throws an error if path doesn't exist
19 | return false;
20 | }
21 | }
22 |
23 | export function isWritable(filename: string): boolean {
24 | try {
25 | accessSync(filename, constants.W_OK);
26 | return true;
27 | } catch {
28 | return false;
29 | }
30 | }
31 |
32 | export function hash(content: string, len = 8) {
33 | const hash = isFipsMode()
34 | ? nodeCrypto.createHash("sha256") // #340
35 | : nodeCrypto.createHash("md5");
36 | return hash.update(content).digest("hex").slice(0, len);
37 | }
38 |
39 | export function readNearestPackageJSON(path: string): PackageJson | undefined {
40 | while (path && path !== "." && path !== "/") {
41 | path = join(path, "..");
42 | try {
43 | const pkg = readFileSync(join(path, "package.json"), "utf8");
44 | try {
45 | return JSON.parse(pkg);
46 | } catch {
47 | // Ignore errors
48 | }
49 | break;
50 | } catch {
51 | // Ignore errors
52 | }
53 | }
54 | }
55 |
56 | export function wrapModule(source: string, opts?: { async?: boolean }) {
57 | return `(${opts?.async ? "async " : ""}function (exports, require, module, __filename, __dirname, jitiImport, jitiESMResolve) { ${source}\n});`;
58 | }
59 |
60 | const debugMap = {
61 | true: green("true"),
62 | false: yellow("false"),
63 | "[rebuild]": yellow("[rebuild]"),
64 | "[esm]": blue("[esm]"),
65 | "[cjs]": green("[cjs]"),
66 | "[import]": blue("[import]"),
67 | "[require]": green("[require]"),
68 | "[native]": cyan("[native]"),
69 | "[transpile]": yellow("[transpile]"),
70 | "[fallback]": red("[fallback]"),
71 | "[unknown]": red("[unknown]"),
72 | "[hit]": green("[hit]"),
73 | "[miss]": yellow("[miss]"),
74 | "[json]": green("[json]"),
75 | "[data]": green("[data]"),
76 | };
77 |
78 | export function debug(ctx: Context, ...args: unknown[]) {
79 | if (!ctx.opts.debug) {
80 | return;
81 | }
82 | const cwd = process.cwd();
83 | console.log(
84 | gray(
85 | [
86 | "[jiti]",
87 | ...args.map((arg) => {
88 | if ((arg as string) in debugMap) {
89 | return debugMap[arg as keyof typeof debugMap];
90 | }
91 | if (typeof arg !== "string") {
92 | return JSON.stringify(arg);
93 | }
94 | return arg.replace(cwd, ".");
95 | }),
96 | ].join(" "),
97 | ),
98 | );
99 | }
100 |
101 | export function jitiInteropDefault(ctx: Context, mod: any) {
102 | return ctx.opts.interopDefault ? interopDefault(mod) : mod;
103 | }
104 |
105 | function interopDefault(mod: any): any {
106 | const modType = typeof mod;
107 | if (mod === null || (modType !== "object" && modType !== "function")) {
108 | return mod;
109 | }
110 |
111 | const def = mod.default;
112 | const defType = typeof def;
113 | const defIsNil = def === null || def === undefined;
114 | const defIsObj = defType === "object" || defType === "function";
115 |
116 | if (defIsNil && mod instanceof Promise) {
117 | // Hotfix for #388
118 | return mod;
119 | }
120 |
121 | return new Proxy(mod, {
122 | get(target, prop, receiver) {
123 | if (prop === "__esModule") {
124 | return true;
125 | }
126 | if (prop === "default") {
127 | if (defIsNil) {
128 | return mod;
129 | }
130 | if (typeof def?.default === "function" && mod.__esModule) {
131 | return def.default; // #396
132 | }
133 | return def;
134 | }
135 | if (Reflect.has(target, prop)) {
136 | return Reflect.get(target, prop, receiver);
137 | }
138 | if (defIsObj && !((def instanceof Promise) /** issue: #400 */)) {
139 | let fallback = Reflect.get(def, prop, receiver);
140 | if (typeof fallback === "function") {
141 | fallback = fallback.bind(def);
142 | }
143 | return fallback;
144 | }
145 | },
146 | apply(target, thisArg, args) {
147 | if (typeof target === "function") {
148 | return Reflect.apply(target, thisArg, args);
149 | }
150 | if (defType === "function") {
151 | return Reflect.apply(def, thisArg, args);
152 | }
153 | },
154 | });
155 | }
156 |
157 | export function normalizeWindowsImportId(id: string) {
158 | if (!isWindows || !isAbsolute(id)) {
159 | return id;
160 | }
161 | return pathToFileURL(id);
162 | }
163 |
164 | let _fipsMode: boolean | undefined;
165 |
166 | function isFipsMode() {
167 | if (_fipsMode !== undefined) {
168 | return _fipsMode;
169 | }
170 | try {
171 | _fipsMode = !!nodeCrypto.getFips?.();
172 | return _fipsMode;
173 | } catch {
174 | _fipsMode = false;
175 | return _fipsMode;
176 | }
177 | }
178 |
--------------------------------------------------------------------------------
/src/jiti.ts:
--------------------------------------------------------------------------------
1 | import type {
2 | Jiti,
3 | TransformOptions,
4 | JitiOptions,
5 | Context,
6 | EvalModuleOptions,
7 | JitiResolveOptions,
8 | } from "./types";
9 | import { platform } from "node:os";
10 | import { pathToFileURL } from "mlly";
11 | import { join } from "pathe";
12 | import escapeStringRegexp from "escape-string-regexp";
13 | import { normalizeAliases } from "pathe/utils";
14 | import pkg from "../package.json";
15 | import { debug, isDir } from "./utils";
16 | import { resolveJitiOptions } from "./options";
17 | import { jitiResolve } from "./resolve";
18 | import { evalModule } from "./eval";
19 | import { transform } from "./transform";
20 | import { jitiRequire } from "./require";
21 | import { prepareCacheDir } from "./cache";
22 |
23 | const isWindows = platform() === "win32";
24 |
25 | export default function createJiti(
26 | filename: string,
27 | userOptions: JitiOptions = {},
28 | parentContext: Pick<
29 | Context,
30 | | "parentModule"
31 | | "parentCache"
32 | | "nativeImport"
33 | | "onError"
34 | | "createRequire"
35 | >,
36 | isNested = false,
37 | ): Jiti {
38 | // Resolve options
39 | const opts = isNested ? userOptions : resolveJitiOptions(userOptions);
40 |
41 | // Normalize aliases (and disable if non given)
42 | const alias =
43 | opts.alias && Object.keys(opts.alias).length > 0
44 | ? normalizeAliases(opts.alias || {})
45 | : undefined;
46 |
47 | // List of modules to force transform or native
48 | const nativeModules = ["typescript", "jiti", ...(opts.nativeModules || [])];
49 | const isNativeRe = new RegExp(
50 | `node_modules/(${nativeModules
51 | .map((m) => escapeStringRegexp(m))
52 | .join("|")})/`,
53 | );
54 |
55 | const transformModules = [...(opts.transformModules || [])];
56 | const isTransformRe = new RegExp(
57 | `node_modules/(${transformModules
58 | .map((m) => escapeStringRegexp(m))
59 | .join("|")})/`,
60 | );
61 |
62 | // If filename is dir, createRequire goes with parent directory, so we need fakepath
63 | if (!filename) {
64 | filename = process.cwd();
65 | }
66 | if (!isNested && isDir(filename)) {
67 | filename = join(filename, "_index.js");
68 | }
69 |
70 | const url = pathToFileURL(filename);
71 |
72 | const additionalExts = [...(opts.extensions as string[])].filter(
73 | (ext) => ext !== ".js",
74 | );
75 |
76 | const nativeRequire = parentContext.createRequire(
77 | isWindows
78 | ? filename.replace(/\//g, "\\") // Import maps does not work with normalized paths!
79 | : filename,
80 | );
81 |
82 | // Create shared context
83 | const ctx: Context = {
84 | filename,
85 | url,
86 | opts,
87 | alias,
88 | nativeModules,
89 | transformModules,
90 | isNativeRe,
91 | isTransformRe,
92 | additionalExts,
93 | nativeRequire,
94 | onError: parentContext.onError,
95 | parentModule: parentContext.parentModule,
96 | parentCache: parentContext.parentCache,
97 | nativeImport: parentContext.nativeImport,
98 | createRequire: parentContext.createRequire,
99 | };
100 |
101 | // Debug
102 | if (!isNested) {
103 | debug(
104 | ctx,
105 | "[init]",
106 | ...[
107 | ["version:", pkg.version],
108 | ["module-cache:", opts.moduleCache],
109 | ["fs-cache:", opts.fsCache],
110 | ["rebuild-fs-cache:", opts.rebuildFsCache],
111 | ["interop-defaults:", opts.interopDefault],
112 | ].flat(),
113 | );
114 | }
115 |
116 | // Prepare cache dir
117 | if (!isNested) {
118 | prepareCacheDir(ctx);
119 | }
120 |
121 | // Create jiti instance
122 | const jiti: Jiti = Object.assign(
123 | function jiti(id: string) {
124 | return jitiRequire(ctx, id, { async: false });
125 | },
126 | {
127 | cache: opts.moduleCache ? nativeRequire.cache : Object.create(null),
128 | extensions: nativeRequire.extensions,
129 | main: nativeRequire.main,
130 | options: opts,
131 | resolve: Object.assign(
132 | function resolve(path: string, options?: NodeJS.RequireResolveOptions) {
133 | return jitiResolve(ctx, path, { ...options, async: false });
134 | },
135 | {
136 | paths: nativeRequire.resolve.paths,
137 | },
138 | ),
139 | transform(opts: TransformOptions) {
140 | return transform(ctx, opts);
141 | },
142 | evalModule(source: string, options?: EvalModuleOptions) {
143 | return evalModule(ctx, source, options);
144 | },
145 | async import(
146 | id: string,
147 | opts?: JitiResolveOptions & { default?: true },
148 | ): Promise {
149 | const mod = await jitiRequire(ctx, id, { ...opts, async: true });
150 | return opts?.default ? (mod?.default ?? mod) : mod;
151 | },
152 | esmResolve(id: string, opts?: string | JitiResolveOptions): string {
153 | if (typeof opts === "string") {
154 | opts = { parentURL: opts };
155 | }
156 | const resolved = jitiResolve(ctx, id, {
157 | parentURL: url as any,
158 | ...opts,
159 | async: true,
160 | });
161 | if (
162 | !resolved ||
163 | typeof resolved !== "string" ||
164 | resolved.startsWith("file://")
165 | ) {
166 | return resolved;
167 | }
168 | return pathToFileURL(resolved);
169 | },
170 | },
171 | ) as Jiti;
172 |
173 | return jiti;
174 | }
175 |
--------------------------------------------------------------------------------
/src/eval.ts:
--------------------------------------------------------------------------------
1 | import { Module } from "node:module";
2 | import { performance } from "node:perf_hooks";
3 | import vm from "node:vm";
4 | import { dirname, basename, extname } from "pathe";
5 | import { hasESMSyntax } from "mlly";
6 | import {
7 | debug,
8 | jitiInteropDefault,
9 | readNearestPackageJSON,
10 | wrapModule,
11 | } from "./utils";
12 | import type { ModuleCache, Context, EvalModuleOptions } from "./types";
13 | import { jitiResolve } from "./resolve";
14 | import { jitiRequire, nativeImportOrRequire } from "./require";
15 | import createJiti from "./jiti";
16 | import { transform } from "./transform";
17 |
18 | export function evalModule(
19 | ctx: Context,
20 | source: string,
21 | evalOptions: EvalModuleOptions = {},
22 | ): any {
23 | // Resolve options
24 | const id =
25 | evalOptions.id ||
26 | (evalOptions.filename
27 | ? basename(evalOptions.filename)
28 | : `_jitiEval.${evalOptions.ext || (evalOptions.async ? "mjs" : "js")}`);
29 | const filename =
30 | evalOptions.filename || jitiResolve(ctx, id, { async: evalOptions.async });
31 | const ext = evalOptions.ext || extname(filename);
32 | const cache = (evalOptions.cache || ctx.parentCache || {}) as ModuleCache;
33 |
34 | // Transpile
35 | const isTypescript = /\.[cm]?tsx?$/.test(ext);
36 | const isESM =
37 | ext === ".mjs" ||
38 | (ext === ".js" && readNearestPackageJSON(filename)?.type === "module");
39 | const isCommonJS = ext === ".cjs";
40 | const needsTranspile =
41 | evalOptions.forceTranspile ??
42 | (!isCommonJS && // CommonJS skips transpile
43 | !(isESM && evalOptions.async) && // In async mode, we can skip native ESM as well
44 | // prettier-ignore
45 | (isTypescript || isESM || ctx.isTransformRe.test(filename) || hasESMSyntax(source)));
46 | const start = performance.now();
47 | if (needsTranspile) {
48 | source = transform(ctx, {
49 | filename,
50 | source,
51 | ts: isTypescript,
52 | async: evalOptions.async ?? false,
53 | jsx: ctx.opts.jsx,
54 | });
55 | const time = Math.round((performance.now() - start) * 1000) / 1000;
56 | debug(
57 | ctx,
58 | "[transpile]",
59 | evalOptions.async ? "[esm]" : "[cjs]",
60 | filename,
61 | `(${time}ms)`,
62 | );
63 | } else {
64 | debug(
65 | ctx,
66 | "[native]",
67 | evalOptions.async ? "[import]" : "[require]",
68 | filename,
69 | );
70 |
71 | if (evalOptions.async) {
72 | return Promise.resolve(
73 | nativeImportOrRequire(ctx, filename, evalOptions.async),
74 | ).catch((error: any) => {
75 | debug(ctx, "Native import error:", error);
76 | debug(ctx, "[fallback]", filename);
77 | return evalModule(ctx, source, {
78 | ...evalOptions,
79 | forceTranspile: true,
80 | });
81 | });
82 | } else {
83 | try {
84 | return nativeImportOrRequire(ctx, filename, evalOptions.async);
85 | } catch (error: any) {
86 | debug(ctx, "Native require error:", error);
87 | debug(ctx, "[fallback]", filename);
88 | source = transform(ctx, {
89 | filename,
90 | source,
91 | ts: isTypescript,
92 | async: evalOptions.async ?? false,
93 | jsx: ctx.opts.jsx,
94 | });
95 | }
96 | }
97 | }
98 |
99 | // Compile module
100 | const mod = new Module(filename);
101 | mod.filename = filename;
102 | if (ctx.parentModule) {
103 | mod.parent = ctx.parentModule;
104 | if (
105 | Array.isArray(ctx.parentModule.children) &&
106 | !ctx.parentModule.children.includes(mod)
107 | ) {
108 | ctx.parentModule.children.push(mod);
109 | }
110 | }
111 |
112 | const _jiti = createJiti(
113 | filename,
114 | ctx.opts,
115 | {
116 | parentModule: mod,
117 | parentCache: cache,
118 | nativeImport: ctx.nativeImport,
119 | onError: ctx.onError,
120 | createRequire: ctx.createRequire,
121 | },
122 | true /* isNested */,
123 | );
124 |
125 | mod.require = _jiti as typeof mod.require;
126 |
127 | mod.path = dirname(filename);
128 |
129 | // @ts-ignore
130 | mod.paths = Module._nodeModulePaths(mod.path);
131 |
132 | // Set CJS cache before eval
133 | cache[filename] = mod as ModuleCache[string];
134 | if (ctx.opts.moduleCache) {
135 | ctx.nativeRequire.cache[filename] = mod;
136 | }
137 |
138 | // Compile wrapped script
139 | let compiled;
140 | const wrapped = wrapModule(source, { async: evalOptions.async });
141 | try {
142 | compiled = vm.runInThisContext(wrapped, {
143 | filename,
144 | lineOffset: 0,
145 | displayErrors: false,
146 | });
147 | } catch (error: any) {
148 | if (error.name === "SyntaxError" && evalOptions.async && ctx.nativeImport) {
149 | // Support cases such as import.meta.[custom]
150 | debug(ctx, "[esm]", "[import]", "[fallback]", filename);
151 | compiled = esmEval(wrapped, ctx.nativeImport!);
152 | } else {
153 | if (ctx.opts.moduleCache) {
154 | delete ctx.nativeRequire.cache[filename];
155 | }
156 | ctx.onError!(error);
157 | }
158 | }
159 |
160 | // Evaluate module
161 | let evalResult;
162 | try {
163 | evalResult = compiled(
164 | mod.exports,
165 | mod.require,
166 | mod,
167 | mod.filename,
168 | dirname(mod.filename),
169 | _jiti.import,
170 | _jiti.esmResolve,
171 | );
172 | } catch (error: any) {
173 | if (ctx.opts.moduleCache) {
174 | delete ctx.nativeRequire.cache[filename];
175 | }
176 | ctx.onError!(error);
177 | }
178 |
179 | function next() {
180 | // Check for parse errors
181 | if (mod.exports && mod.exports.__JITI_ERROR__) {
182 | const { filename, line, column, code, message } =
183 | mod.exports.__JITI_ERROR__;
184 | const loc = `${filename}:${line}:${column}`;
185 | const err = new Error(`${code}: ${message} \n ${loc}`);
186 | Error.captureStackTrace(err, jitiRequire);
187 | ctx.onError!(err);
188 | }
189 |
190 | // Set as loaded
191 | mod.loaded = true;
192 |
193 | // interopDefault
194 | const _exports = jitiInteropDefault(ctx, mod.exports);
195 |
196 | // Return exports
197 | return _exports;
198 | }
199 |
200 | return evalOptions.async ? Promise.resolve(evalResult).then(next) : next();
201 | }
202 |
203 | function esmEval(code: string, nativeImport: (id: string) => Promise) {
204 | const uri = `data:text/javascript;base64,${Buffer.from(`export default ${code}`).toString("base64")}`;
205 | return (...args: any[]) =>
206 | nativeImport(uri).then((mod) => mod.default(...args));
207 | }
208 |
--------------------------------------------------------------------------------
/src/_types/babel-helper-module-transforms.d.ts:
--------------------------------------------------------------------------------
1 | declare module "@babel/helper-module-transforms" {
2 | import type { NodePath } from "@babel/core";
3 | import { types as t } from "@babel/core";
4 |
5 | interface LocalExportMetadata {
6 | /**
7 | * names of exports
8 | */
9 | names: string[];
10 | kind: "import" | "hoisted" | "block" | "var";
11 | }
12 |
13 | type InteropType =
14 | /**
15 | * Babel interop for default-only imports
16 | */
17 | | "default"
18 | /**
19 | * Babel interop for namespace or default+named imports
20 | */
21 | | "namespace"
22 | /**
23 | * Node.js interop for default-only imports
24 | */
25 | | "node-default"
26 | /**
27 | * Node.js interop for namespace or default+named imports
28 | */
29 | | "node-namespace"
30 | /**
31 | * No interop, or named-only imports
32 | */
33 | | "none";
34 |
35 | interface SourceModuleMetadata {
36 | /**
37 | * A unique variable name to use for this namespace object.
38 | * Centralized for simplicity.
39 | */
40 | name: string;
41 | loc: t.SourceLocation | undefined | null;
42 | interop: InteropType;
43 | /**
44 | * Local binding to reference from this source namespace.
45 | * Key: Local name, value: Import name
46 | */
47 | imports: Map;
48 | /**
49 | * Local names that reference namespace object.
50 | */
51 | importsNamespace: Set;
52 | /**
53 | * Reexports to create for namespace. Key: Export name, value: Import name
54 | */
55 | reexports: Map;
56 | /**
57 | * List of names to re-export namespace as.
58 | */
59 | reexportNamespace: Set;
60 | /**
61 | * Tracks if the source should be re-exported.
62 | */
63 | reexportAll: null | {
64 | loc: t.SourceLocation | undefined | null;
65 | };
66 | wrap?: unknown;
67 | referenced: boolean;
68 | }
69 |
70 | interface ModuleMetadata {
71 | exportName: string;
72 | /**
73 | * The name of the variable that will reference an object
74 | * containing export names.
75 | */
76 | exportNameListName: null | string;
77 | hasExports: boolean;
78 | /**
79 | * Lookup from local binding to export information.
80 | */
81 | local: Map;
82 | /**
83 | * Lookup of source file to source file metadata.
84 | */
85 | source: Map;
86 | /**
87 | * List of names that should only be printed as string literals.
88 | * i.e. `import { "any unicode" as foo } from "some-module"`
89 | * `stringSpecifiers` is `Set(1) ["any unicode"]`
90 | * In most cases `stringSpecifiers` is an empty Set
91 | */
92 | stringSpecifiers: Set;
93 | }
94 |
95 | type ImportInterop =
96 | | "none"
97 | | "babel"
98 | | "node"
99 | | ((source: string, filename?: string) => "none" | "babel" | "node");
100 |
101 | type Lazy = boolean | string[] | ((source: string) => boolean);
102 |
103 | type RootOptions = {
104 | filename?: string | null;
105 | filenameRelative?: string | null;
106 | sourceRoot?: string | null;
107 | };
108 |
109 | export type PluginOptions = {
110 | moduleId?: string;
111 | moduleIds?: boolean;
112 | getModuleId?: (moduleName: string) => string | null | undefined;
113 | moduleRoot?: string;
114 | };
115 |
116 | export function getModuleName(
117 | rootOpts: RootOptions,
118 | pluginOpts: PluginOptions,
119 | ): string | null;
120 |
121 | export interface RewriteModuleStatementsAndPrepareHeaderOptions {
122 | exportName?: string;
123 | strict?: boolean;
124 | allowTopLevelThis?: boolean;
125 | strictMode?: boolean;
126 | loose?: boolean;
127 | importInterop?: ImportInterop;
128 | noInterop?: boolean;
129 | lazy?: Lazy;
130 | getWrapperPayload?: (
131 | source: string,
132 | metadata: SourceModuleMetadata,
133 | importNodes: t.Node[],
134 | ) => unknown;
135 | wrapReference?: (
136 | ref: t.Expression,
137 | payload: unknown,
138 | ) => t.Expression | null | undefined;
139 | esNamespaceOnly?: boolean;
140 | filename: string | undefined | null;
141 | constantReexports?: boolean | void;
142 | enumerableModuleMeta?: boolean | void;
143 | noIncompleteNsImportDetection?: boolean | void;
144 | }
145 |
146 | /**
147 | * Perform all of the generic ES6 module rewriting needed to handle initial
148 | * module processing. This function will rewrite the majority of the given
149 | * program to reference the modules described by the returned metadata,
150 | * and returns a list of statements for use when initializing the module.
151 | */
152 | export function rewriteModuleStatementsAndPrepareHeader(
153 | path: NodePath,
154 | options: RewriteModuleStatementsAndPrepareHeaderOptions,
155 | ): {
156 | meta: ModuleMetadata;
157 | headers: t.Statement[];
158 | };
159 |
160 | /**
161 | * Check if a given source is an anonymous import, e.g. `import 'foo';`
162 | */
163 | export function isSideEffectImport(source: SourceModuleMetadata): boolean;
164 |
165 | /**
166 | * Create the runtime initialization statements for a given requested source.
167 | * These will initialize all of the runtime import/export logic that
168 | * can't be handled statically by the statements created by
169 | * `buildExportInitializationStatements()`.
170 | */
171 | export function buildNamespaceInitStatements(
172 | metadata: ModuleMetadata,
173 | sourceMetadata: SourceModuleMetadata,
174 | constantReexports?: boolean | void,
175 | wrapReference?: (
176 | ref: t.Identifier,
177 | payload: unknown,
178 | ) => t.Expression | null | undefined,
179 | ): t.Statement[];
180 |
181 | /**
182 | * Flag a set of statements as hoisted above all else so that module init
183 | * statements all run before user code.
184 | */
185 | export function ensureStatementsHoisted(statements: t.Statement[]): void;
186 |
187 | /**
188 | * Given an expression for a standard import object, like `require('foo')`,
189 | * wrap it in a call to the interop helpers based on the type.
190 | */
191 | export function wrapInterop(
192 | programPath: NodePath,
193 | expr: t.Expression,
194 | type: InteropType,
195 | ): t.CallExpression;
196 |
197 | export function buildDynamicImport(
198 | node: t.CallExpression | t.ImportExpression,
199 | deferToThen: boolean,
200 | wrapWithPromise: boolean,
201 | builder: (specifier: t.Expression) => t.Expression,
202 | ): t.Expression;
203 | }
204 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # jiti
2 |
3 |
4 |
5 | [](https://npmjs.com/package/jiti)
6 | [](https://npmjs.com/package/jiti)
7 | [](https://bundlephobia.com/package/jiti)
8 |
9 |
10 |
11 | > This is the active development branch. Check out [jiti/v1](https://github.com/unjs/jiti/tree/v1) for legacy v1 docs and code.
12 |
13 | ## 🌟 Used in
14 |
15 | [Docusaurus](https://docusaurus.io/), [ESLint](https://github.com/eslint/eslint), [FormKit](https://formkit.com/), [Histoire](https://histoire.dev/), [Knip](https://knip.dev/), [Nitro](https://nitro.unjs.io/), [Nuxt](https://nuxt.com/), [PostCSS loader](https://github.com/webpack-contrib/postcss-loader), [Rsbuild](https://rsbuild.dev/), [Size Limit](https://github.com/ai/size-limit), [Slidev](https://sli.dev/), [Tailwindcss](https://tailwindcss.com/), [Tokenami](https://github.com/tokenami/tokenami), [UnoCSS](https://unocss.dev/), [WXT](https://wxt.dev/), [Winglang](https://www.winglang.io/), [Graphql code generator](https://the-guild.dev/graphql/codegen), [Lingui](https://lingui.dev/), [Scaffdog](https://scaff.dog/), [Storybook](https://storybook.js.org), [...UnJS ecosystem](https://unjs.io/), [...60M+ npm monthly downloads](https://npm.chart.dev/jiti), [...6M+ public repositories](https://github.com/unjs/jiti/network/dependents).
16 |
17 | ## ✅ Features
18 |
19 | - Seamless TypeScript and ESM syntax support for Node.js
20 | - Seamless interoperability between ESM and CommonJS
21 | - Asynchronous API to replace `import()`
22 | - Synchronous API to replace `require()` (deprecated)
23 | - Super slim and zero dependency
24 | - Custom resolve aliases
25 | - Smart syntax detection to avoid extra transforms
26 | - Node.js native `require.cache` integration
27 | - Filesystem transpile with hard disk caches
28 | - ESM Loader support
29 | - JSX support (opt-in)
30 |
31 | > [!IMPORTANT]
32 | > To enhance compatibility, jiti `>=2.1` enabled [`interopDefault`](#interopdefault) using a new Proxy method. If you migrated to `2.0.0` earlier, this might have caused behavior changes. In case of any issues during the upgrade, please [report](https://github.com/unjs/jiti/issues) so we can investigate to solve them. 🙏🏼
33 |
34 | ## 💡 Usage
35 |
36 | ### CLI
37 |
38 | You can use `jiti` CLI to quickly run any script with TypeScript and native ESM support!
39 |
40 | ```bash
41 | npx jiti ./index.ts
42 | ```
43 |
44 | ### Programmatic
45 |
46 | Initialize a jiti instance:
47 |
48 | ```js
49 | // ESM
50 | import { createJiti } from "jiti";
51 | const jiti = createJiti(import.meta.url);
52 |
53 | // CommonJS (deprecated)
54 | const { createJiti } = require("jiti");
55 | const jiti = createJiti(__filename);
56 | ```
57 |
58 | Import (async) and resolve with ESM compatibility:
59 |
60 | ```js
61 | // jiti.import(id) is similar to import(id)
62 | const mod = await jiti.import("./path/to/file.ts");
63 |
64 | // jiti.esmResolve(id) is similar to import.meta.resolve(id)
65 | const resolvedPath = jiti.esmResolve("./src");
66 | ```
67 |
68 | If you need the default export of module, you can use `jiti.import(id, { default: true })` as shortcut to `mod?.default ?? mod`.
69 |
70 | ```js
71 | // shortcut to mod?.default ?? mod
72 | const modDefault = await jiti.import("./path/to/file.ts", { default: true });
73 | ```
74 |
75 | CommonJS (sync & deprecated):
76 |
77 | ```js
78 | // jiti() is similar to require(id)
79 | const mod = jiti("./path/to/file.ts");
80 |
81 | // jiti.resolve() is similar to require.resolve(id)
82 | const resolvedPath = jiti.resolve("./src");
83 | ```
84 |
85 | You can also pass options as the second argument:
86 |
87 | ```js
88 | const jiti = createJiti(import.meta.url, { debug: true });
89 | ```
90 |
91 | ### Register global ESM loader
92 |
93 | You can globally register jiti using [global hooks](https://nodejs.org/api/module.html#initialize). (Important: Requires Node.js > 20)
94 |
95 | ```js
96 | import "jiti/register";
97 | ```
98 |
99 | Or:
100 |
101 | ```bash
102 | node --import jiti/register index.ts
103 | ```
104 |
105 | ## 🎈 `jiti/native`
106 |
107 | You can alias `jiti` to `jiti/native` to directly depend on runtime's [`import.meta.resolve`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/import.meta/resolve) and dynamic [`import()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/import) support. This allows easing up the ecosystem transition to runtime native support by giving the same API of jiti.
108 |
109 | ## ⚙️ Options
110 |
111 | ### `debug`
112 |
113 | - Type: Boolean
114 | - Default: `false`
115 | - Environment variable: `JITI_DEBUG`
116 |
117 | Enable verbose logging. You can use `JITI_DEBUG=1 ` to enable it.
118 |
119 | ### `fsCache`
120 |
121 | - Type: Boolean | String
122 | - Default: `true`
123 | - Environment variable: `JITI_FS_CACHE`
124 |
125 | Filesystem source cache (enabled by default)
126 |
127 | By default (when is `true`), jiti uses `node_modules/.cache/jiti` (if exists) or `{TMP_DIR}/jiti`.
128 |
129 | **Note:** It is recommended that this option be enabled for better performance.
130 |
131 | ### `rebuildFsCache`
132 |
133 | - Type: Boolean
134 | - Default: `false`
135 | - Environment variable: `JITI_REBUILD_FS_CACHE`
136 |
137 | Rebuild filesystem source cache created by `fsCache`.
138 |
139 | ### `moduleCache`
140 |
141 | - Type: String
142 | - Default: `true`
143 | - Environment variable: `JITI_MODULE_CACHE`
144 |
145 | Runtime module cache (enabled by default).
146 |
147 | Disabling allows editing code and importing the same module multiple times.
148 |
149 | When enabled, jiti integrates with Node.js native CommonJS cache-store.
150 |
151 | ### `transform`
152 |
153 | - Type: Function
154 | - Default: Babel (lazy loaded)
155 |
156 | Transform function. See [src/babel](./src/babel.ts) for more details
157 |
158 | ### `sourceMaps`
159 |
160 | - Type: Boolean
161 | - Default `false`
162 | - Environment variable: `JITI_SOURCE_MAPS`
163 |
164 | Add inline source map to transformed source for better debugging.
165 |
166 | ### `interopDefault`
167 |
168 | - Type: Boolean
169 | - Default: `true`
170 | - Environment variable: `JITI_INTEROP_DEFAULT`
171 |
172 | Jiti combines module exports with the `default` export using an internal Proxy to improve compatibility with mixed CJS/ESM usage. You can check the current implementation [here](https://github.com/unjs/jiti/blob/main/src/utils.ts#L105).
173 |
174 | ### `alias`
175 |
176 | - Type: Object
177 | - Default: -
178 | - Environment variable: `JITI_ALIAS`
179 |
180 | You can also pass an object to the environment variable for inline config. Example: `JITI_ALIAS='{"~/*": "./src/*"}' jiti ...`.
181 |
182 | Custom alias map used to resolve IDs.
183 |
184 | ### `nativeModules`
185 |
186 | - Type: Array
187 | - Default: ['typescript']
188 | - Environment variable: `JITI_NATIVE_MODULES`
189 |
190 | List of modules (within `node_modules`) to always use native `require()` for them.
191 |
192 | ### `transformModules`
193 |
194 | - Type: Array
195 | - Default: []
196 | - Environment variable: `JITI_TRANSFORM_MODULES`
197 |
198 | List of modules (within `node_modules`) to transform them regardless of syntax.
199 |
200 | ### `importMeta`
201 |
202 | Parent module's [`import.meta`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/import.meta) context to use for ESM resolution. (only used for `jiti/native` import).
203 |
204 | ### `tryNative`
205 |
206 | - Type: Boolean
207 | - Default: Enabled if bun is detected
208 | - Environment variable: `JITI_TRY_NATIVE`
209 |
210 | Try to use native require and import without jiti transformations first.
211 |
212 | ### `jsx`
213 |
214 | - Type: Boolean | {options}
215 | - Default: `false`
216 | - Environment Variable: `JITI_JSX`
217 |
218 | Enable JSX support using [`@babel/plugin-transform-react-jsx`](https://babeljs.io/docs/babel-plugin-transform-react-jsx).
219 |
220 | See [`test/fixtures/jsx`](./test/fixtures/jsx) for framework integration examples.
221 |
222 | ## Development
223 |
224 | - Clone this repository
225 | - Enable [Corepack](https://github.com/nodejs/corepack) using `corepack enable`
226 | - Install dependencies using `pnpm install`
227 | - Run `pnpm dev`
228 | - Run `pnpm jiti ./test/path/to/file.ts`
229 |
230 | ## License
231 |
232 |
233 |
234 | Published under the [MIT](https://github.com/unjs/jiti/blob/main/LICENSE) license.
235 | Made by [@pi0](https://github.com/pi0) and [community](https://github.com/unjs/jiti/graphs/contributors) 💛
236 |
237 |
238 |
239 |
240 |
241 |
242 |
243 |
244 |
--------------------------------------------------------------------------------
/src/plugins/babel-plugin-transform-typescript-metadata/serialize-type.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Based on https://github.com/leonardfactory/babel-plugin-transform-typescript-metadata
3 | * Copyright (c) 2019 Leonardo Ascione [MIT]
4 | */
5 |
6 | import { NodePath, types as t } from "@babel/core";
7 |
8 | type InferArray = T extends Array ? A : never;
9 |
10 | type Parameter = InferArray | t.ClassProperty;
11 |
12 | function createVoidZero() {
13 | return t.unaryExpression("void", t.numericLiteral(0));
14 | }
15 |
16 | /**
17 | * Given a parameter (or class property) node it returns the first identifier
18 | * containing the TS Type Annotation.
19 | *
20 | * @todo Array and Objects spread are not supported.
21 | * @todo Rest parameters are not supported.
22 | */
23 | function getTypedNode(
24 | param: Parameter,
25 | ): t.Identifier | t.ClassProperty | t.ObjectPattern | null {
26 | if (param == null) {
27 | return null;
28 | }
29 |
30 | if (param.type === "ClassProperty") {
31 | return param;
32 | }
33 | if (param.type === "Identifier") {
34 | return param;
35 | }
36 | if (param.type === "ObjectPattern") {
37 | return param;
38 | }
39 |
40 | if (param.type === "AssignmentPattern" && param.left.type === "Identifier") {
41 | return param.left;
42 | }
43 |
44 | if (param.type === "TSParameterProperty") {
45 | return getTypedNode(param.parameter);
46 | }
47 |
48 | return null;
49 | }
50 |
51 | export function serializeType(
52 | classPath: NodePath,
53 | param: Parameter,
54 | ) {
55 | const node = getTypedNode(param);
56 | if (node == null) {
57 | return createVoidZero();
58 | }
59 |
60 | if (!node.typeAnnotation || node.typeAnnotation.type !== "TSTypeAnnotation") {
61 | return createVoidZero();
62 | }
63 |
64 | const annotation = node.typeAnnotation.typeAnnotation;
65 | const className = classPath.node.id ? classPath.node.id.name : "";
66 | return serializeTypeNode(className, annotation);
67 | }
68 |
69 | function serializeTypeReferenceNode(
70 | className: string,
71 | node: t.TSTypeReference,
72 | ) {
73 | /**
74 | * We need to save references to this type since it is going
75 | * to be used as a Value (and not just as a Type) here.
76 | *
77 | * This is resolved in main plugin method, calling
78 | * `path.scope.crawl()` which updates the bindings.
79 | */
80 | const reference = serializeReference(node.typeName);
81 |
82 | /**
83 | * We should omit references to self (class) since it will throw a
84 | * ReferenceError at runtime due to babel transpile output.
85 | */
86 | if (isClassType(className, reference)) {
87 | return t.identifier("Object");
88 | }
89 |
90 | /**
91 | * We don't know if type is just a type (interface, etc.) or a concrete
92 | * value (class, etc.).
93 | * `typeof` operator allows us to use the expression even if it is not
94 | * defined, fallback is just `Object`.
95 | */
96 | return t.conditionalExpression(
97 | t.binaryExpression(
98 | "===",
99 | t.unaryExpression("typeof", reference),
100 | t.stringLiteral("undefined"),
101 | ),
102 | t.identifier("Object"),
103 | t.cloneDeep(reference),
104 | );
105 | }
106 |
107 | /**
108 | * Checks if node (this should be the result of `serializeReference`) member
109 | * expression or identifier is a reference to self (class name).
110 | * In this case, we just emit `Object` in order to avoid ReferenceError.
111 | */
112 | function isClassType(className: string, node: t.Expression): boolean {
113 | switch (node.type) {
114 | case "Identifier": {
115 | return node.name === className;
116 | }
117 | case "MemberExpression": {
118 | return isClassType(className, node.object);
119 | }
120 | default: {
121 | throw new Error(
122 | `The property expression at ${node.start} is not valid as a Type to be used in Reflect.metadata`,
123 | );
124 | }
125 | }
126 | }
127 |
128 | function serializeReference(
129 | typeName: t.Identifier | t.TSQualifiedName,
130 | ): t.Identifier | t.MemberExpression {
131 | if (typeName.type === "Identifier") {
132 | return t.identifier(typeName.name);
133 | }
134 | return t.memberExpression(serializeReference(typeName.left), typeName.right);
135 | }
136 |
137 | type SerializedType =
138 | | t.Identifier
139 | | t.UnaryExpression
140 | | t.ConditionalExpression;
141 |
142 | /**
143 | * Actual serialization given the TS Type annotation.
144 | * Result tries to get the best match given the information available.
145 | *
146 | * Implementation is adapted from original TSC compiler source as
147 | * available here:
148 | * https://github.com/Microsoft/TypeScript/blob/2932421370df720f0ccfea63aaf628e32e881429/src/compiler/transformers/ts.ts
149 | */
150 | function serializeTypeNode(className: string, node: t.TSType): SerializedType {
151 | if (node === undefined) {
152 | return t.identifier("Object");
153 | }
154 |
155 | switch (node.type) {
156 | case "TSVoidKeyword":
157 | case "TSUndefinedKeyword":
158 | case "TSNullKeyword":
159 | case "TSNeverKeyword": {
160 | return createVoidZero();
161 | }
162 |
163 | case "TSParenthesizedType": {
164 | return serializeTypeNode(className, node.typeAnnotation);
165 | }
166 |
167 | case "TSFunctionType":
168 | case "TSConstructorType": {
169 | return t.identifier("Function");
170 | }
171 |
172 | case "TSArrayType":
173 | case "TSTupleType": {
174 | return t.identifier("Array");
175 | }
176 |
177 | case "TSTypePredicate":
178 | case "TSBooleanKeyword": {
179 | return t.identifier("Boolean");
180 | }
181 |
182 | case "TSStringKeyword": {
183 | return t.identifier("String");
184 | }
185 |
186 | case "TSObjectKeyword": {
187 | return t.identifier("Object");
188 | }
189 |
190 | case "TSLiteralType": {
191 | switch (node.literal.type) {
192 | case "StringLiteral": {
193 | return t.identifier("String");
194 | }
195 |
196 | case "NumericLiteral": {
197 | return t.identifier("Number");
198 | }
199 |
200 | case "BooleanLiteral": {
201 | return t.identifier("Boolean");
202 | }
203 |
204 | default: {
205 | /**
206 | * @todo Use `path` error building method.
207 | */
208 | throw new Error("Bad type for decorator" + node.literal);
209 | }
210 | }
211 | }
212 |
213 | case "TSNumberKeyword":
214 | case "TSBigIntKeyword" as any: {
215 | // Still not in ``@babel/core` typings
216 | return t.identifier("Number");
217 | }
218 |
219 | case "TSSymbolKeyword": {
220 | return t.identifier("Symbol");
221 | }
222 |
223 | case "TSTypeReference": {
224 | return serializeTypeReferenceNode(className, node);
225 | }
226 |
227 | case "TSIntersectionType":
228 | case "TSUnionType": {
229 | return serializeTypeList(className, node.types);
230 | }
231 |
232 | case "TSConditionalType": {
233 | return serializeTypeList(className, [node.trueType, node.falseType]);
234 | }
235 |
236 | case "TSTypeQuery":
237 | case "TSTypeOperator":
238 | case "TSIndexedAccessType":
239 | case "TSMappedType":
240 | case "TSTypeLiteral":
241 | case "TSAnyKeyword":
242 | case "TSUnknownKeyword":
243 | case "TSThisType": {
244 | // case SyntaxKind.ImportType:
245 | break;
246 | }
247 |
248 | default: {
249 | throw new Error("Bad type for decorator");
250 | }
251 | }
252 |
253 | return t.identifier("Object");
254 | }
255 |
256 | /**
257 | * Type lists need some refining. Even here, implementation is slightly
258 | * adapted from original TSC compiler:
259 | *
260 | * https://github.com/Microsoft/TypeScript/blob/2932421370df720f0ccfea63aaf628e32e881429/src/compiler/transformers/ts.ts
261 | */
262 | function serializeTypeList(
263 | className: string,
264 | types: ReadonlyArray,
265 | ): SerializedType {
266 | let serializedUnion: SerializedType | undefined;
267 |
268 | for (let typeNode of types) {
269 | while (typeNode.type === "TSParenthesizedType") {
270 | typeNode = typeNode.typeAnnotation; // Skip parens if need be
271 | }
272 | if (typeNode.type === "TSNeverKeyword") {
273 | continue; // Always elide `never` from the union/intersection if possible
274 | }
275 | if (
276 | typeNode.type === "TSNullKeyword" ||
277 | typeNode.type === "TSUndefinedKeyword"
278 | ) {
279 | continue; // Elide null and undefined from unions for metadata, just like what we did prior to the implementation of strict null checks
280 | }
281 | const serializedIndividual = serializeTypeNode(className, typeNode);
282 |
283 | if (
284 | t.isIdentifier(serializedIndividual) &&
285 | serializedIndividual.name === "Object"
286 | ) {
287 | // One of the individual is global object, return immediately
288 | return serializedIndividual;
289 | }
290 | // If there exists union that is not void 0 expression, check if the the common type is identifier.
291 | // anything more complex and we will just default to Object
292 | else if (serializedUnion) {
293 | // Different types
294 | if (
295 | !t.isIdentifier(serializedUnion) ||
296 | !t.isIdentifier(serializedIndividual) ||
297 | serializedUnion.name !== serializedIndividual.name
298 | ) {
299 | return t.identifier("Object");
300 | }
301 | } else {
302 | // Initialize the union type
303 | serializedUnion = serializedIndividual;
304 | }
305 | }
306 |
307 | // If we were able to find common type, use it
308 | return serializedUnion || createVoidZero(); // Fallback is only hit if all union constituents are null/undefined/never
309 | }
310 |
--------------------------------------------------------------------------------
/lib/types.d.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Creates a new {@linkcode Jiti} instance with custom options.
3 | *
4 | * @param id - Instance id, usually the current filename.
5 | * @param userOptions - Custom options to override the default options.
6 | * @returns A {@linkcode Jiti} instance.
7 | *
8 | * @example
9 | * ESM Usage
10 | *
11 | * ```ts
12 | * import { createJiti } from "jiti";
13 | *
14 | * const jiti = createJiti(import.meta.url, { debug: true });
15 | * ```
16 | *
17 | * @example
18 | * CommonJS Usage **(deprecated)**
19 | *
20 | * ```ts
21 | * const { createJiti } = require("jiti");
22 | *
23 | * const jiti = createJiti(__filename, { debug: true });
24 | * ```
25 | *
26 | * @since 2.0.0
27 | */
28 | export declare function createJiti(id: string, userOptions?: JitiOptions): Jiti;
29 |
30 | /**
31 | * Jiti instance
32 | *
33 | * Calling `jiti()` is similar to CommonJS {@linkcode require()} but adds
34 | * extra features such as TypeScript and ESM compatibility.
35 | *
36 | * **Note:** It is recommended to use
37 | * {@linkcode Jiti.import | await jiti.import()} instead.
38 | */
39 | export interface Jiti extends NodeRequire {
40 | /**
41 | * Resolved options
42 | */
43 | options: JitiOptions;
44 |
45 | /**
46 | * ESM import a module with additional TypeScript and ESM compatibility.
47 | *
48 | * If you need the default export of module, you can use
49 | * `jiti.import(id, { default: true })` as shortcut to `mod?.default ?? mod`.
50 | */
51 | import(
52 | id: string,
53 | opts?: JitiResolveOptions & { default?: true },
54 | ): Promise;
55 |
56 | /**
57 | * Resolve with ESM import conditions.
58 | */
59 | esmResolve(id: string, parentURL?: string): string;
60 | esmResolve(
61 | id: string,
62 | opts?: T,
63 | ): T["try"] extends true ? string | undefined : string;
64 |
65 | /**
66 | * Transform source code
67 | */
68 | transform: (opts: TransformOptions) => string;
69 |
70 | /**
71 | * Evaluate transformed code as a module
72 | */
73 | evalModule: (source: string, options?: EvalModuleOptions) => unknown;
74 | }
75 |
76 | /**
77 | * Jiti instance options
78 | */
79 | export interface JitiOptions {
80 | /**
81 | * Filesystem source cache
82 | *
83 | * An string can be passed to set the custom cache directory.
84 | *
85 | * By default (when set to `true`), jiti uses
86 | * `node_modules/.cache/jiti` (if exists) or `{TMP_DIR}/jiti`.
87 | *
88 | * This option can also be disabled using
89 | * `JITI_FS_CACHE=false` environment variable.
90 | *
91 | * **Note:** It is recommended to keep this option
92 | * enabled for better performance.
93 | *
94 | * @default true
95 | */
96 | fsCache?: boolean | string;
97 |
98 | /**
99 | * Rebuild the filesystem source cache
100 | *
101 | * This option can also be enabled using
102 | * `JITI_REBUILD_FS_CACHE=true` environment variable.
103 | *
104 | * @default false
105 | */
106 | rebuildFsCache?: boolean;
107 |
108 | /**
109 | * @deprecated Use the {@linkcode fsCache} option.
110 | *
111 | * @default true
112 | */
113 | cache?: boolean | string;
114 |
115 | /**
116 | * Runtime module cache
117 | *
118 | * Disabling allows editing code and importing same module multiple times.
119 | *
120 | * When enabled, jiti integrates with Node.js native CommonJS cache store.
121 | *
122 | * This option can also be disabled using
123 | * `JITI_MODULE_CACHE=false` environment variable.
124 | *
125 | * @default true
126 | */
127 | moduleCache?: boolean;
128 |
129 | /**
130 | * @deprecated Use the {@linkcode moduleCache} option.
131 | *
132 | * @default true
133 | */
134 | requireCache?: boolean;
135 |
136 | /**
137 | * Custom transform function
138 | */
139 | transform?: (opts: TransformOptions) => TransformResult;
140 |
141 | /**
142 | * Enable verbose debugging.
143 | *
144 | * Can also be enabled using `JITI_DEBUG=1` environment variable.
145 | *
146 | * @default false
147 | */
148 | debug?: boolean;
149 |
150 | /**
151 | * Enable sourcemaps for transformed code.
152 | *
153 | * Can also be disabled using `JITI_SOURCE_MAPS=0` environment variable.
154 | *
155 | * @default false
156 | */
157 | sourceMaps?: boolean;
158 |
159 | /**
160 | * Jiti combines module exports with the `default` export using an
161 | * internal Proxy to improve compatibility with mixed CJS/ESM usage.
162 | * You can check the current implementation
163 | * {@link https://github.com/unjs/jiti/blob/main/src/utils.ts#L105 here}.
164 | *
165 | * Can be disabled using `JITI_INTEROP_DEFAULT=0` environment variable.
166 | *
167 | * @default true
168 | */
169 | interopDefault?: boolean;
170 |
171 | /**
172 | * Jiti hard source cache version.
173 | *
174 | * @internal
175 | */
176 | cacheVersion?: string;
177 |
178 | /**
179 | * Supported extensions to resolve.
180 | *
181 | * @default [".js", ".mjs", ".cjs", ".ts", ".tsx", ".mts", ".cts", ".mtsx", ".ctsx", ".json"]
182 | */
183 | extensions?: string[];
184 |
185 | /**
186 | * Transform options
187 | */
188 | transformOptions?: Omit;
189 |
190 | /**
191 | * Resolve aliases
192 | *
193 | * You can use `JITI_ALIAS` environment variable to set aliases as
194 | * a JSON string.
195 | *
196 | * @default {}
197 | */
198 | alias?: Record;
199 |
200 | /**
201 | * List of modules (within `node_modules`) to always use native
202 | * require/import for them.
203 | *
204 | * You can use `JITI_NATIVE_MODULES` environment variable to set
205 | * native modules as a JSON string.
206 | *
207 | * @default []
208 | */
209 | nativeModules?: string[];
210 |
211 | /**
212 | * List of modules (within `node_modules`) to transform them
213 | * regardless of syntax.
214 | *
215 | * You can use `JITI_TRANSFORM_MODULES` environment variable to set
216 | * transform modules as a JSON string.
217 | *
218 | * @default []
219 | */
220 | transformModules?: string[];
221 |
222 | /**
223 | * Parent module's {@linkcode ImportMeta | import.meta} context to use
224 | * for ESM resolution.
225 | *
226 | * (Only used for `jiti/native` import)
227 | */
228 | importMeta?: ImportMeta;
229 |
230 | /**
231 | * Try to use native require and import without jiti transformations first.
232 | *
233 | * Enabled if Bun is detected.
234 | *
235 | * @default false
236 | */
237 | tryNative?: boolean;
238 |
239 | /**
240 | * Enable JSX support Enable JSX support using
241 | * {@link https://babeljs.io/docs/babel-plugin-transform-react-jsx | `@babel/plugin-transform-react-jsx`}.
242 | *
243 | * You can also use `JITI_JSX=1` environment variable to enable JSX support.
244 | *
245 | * @default false
246 | */
247 | jsx?: boolean | JSXOptions;
248 | }
249 |
250 | interface NodeRequire {
251 | /**
252 | * Module cache
253 | */
254 | cache: ModuleCache;
255 |
256 | /**
257 | * @deprecated Prefer {@linkcode Jiti.import | await jiti.import()}
258 | * for better compatibility.
259 | */
260 | (id: string): any;
261 |
262 | /**
263 | * @deprecated Prefer {@linkcode Jiti.esmResolve | jiti.esmResolve}
264 | * for better compatibility.
265 | */
266 | resolve: {
267 | /** @deprecated */
268 | (id: string, options?: { paths?: string[] | undefined }): string;
269 | /** @deprecated */
270 | paths(request: string): string[] | null;
271 | };
272 |
273 | /** @deprecated CommonJS API */
274 | extensions: Record<
275 | ".js" | ".json" | ".node",
276 | (m: NodeModule, filename: string) => any | undefined
277 | >;
278 |
279 | /** @deprecated CommonJS API */
280 | main: NodeModule | undefined;
281 | }
282 |
283 | export interface NodeModule {
284 | /**
285 | * `true` if the module is running during the Node.js preload.
286 | */
287 | isPreloading: boolean;
288 | exports: any;
289 | require: NodeRequire;
290 | id: string;
291 | filename: string;
292 | loaded: boolean;
293 | /**
294 | * @deprecated since Node.js **v14.6.0** Please use
295 | * {@linkcode NodeRequire.main | require.main} and
296 | * {@linkcode NodeModule.children | module.children} instead.
297 | */
298 | parent: NodeModule | null | undefined;
299 | children: NodeModule[];
300 | /**
301 | * The directory name of the module.
302 | * This is usually the same as the `path.dirname()` of the `module.id`.
303 | *
304 | * @since Node.js **v11.14.0**
305 | */
306 | path: string;
307 | paths: string[];
308 | }
309 |
310 | export type ModuleCache = Record;
311 |
312 | export type EvalModuleOptions = Partial<{
313 | id: string;
314 | filename: string;
315 | ext: string;
316 | cache: ModuleCache;
317 | /**
318 | * @default true
319 | */
320 | async: boolean;
321 | forceTranspile: boolean;
322 | }>;
323 |
324 | export interface TransformOptions {
325 | source: string;
326 | filename?: string;
327 | ts?: boolean;
328 | retainLines?: boolean;
329 | interopDefault?: boolean;
330 | /**
331 | * @default false
332 | */
333 | async?: boolean;
334 | /**
335 | * @default false
336 | */
337 | jsx?: boolean | JSXOptions;
338 | babel?: Record;
339 | }
340 |
341 | export interface TransformResult {
342 | code: string;
343 | error?: any;
344 | }
345 |
346 | export interface JitiResolveOptions {
347 | conditions?: string[];
348 | parentURL?: string | URL;
349 | try?: boolean;
350 | }
351 |
352 | /**
353 | * @see {@link https://babeljs.io/docs/babel-plugin-transform-react-jsx#options | Reference}
354 | */
355 | export interface JSXOptions {
356 | throwIfNamespace?: boolean;
357 | runtime?: "classic" | "automatic";
358 | importSource?: string;
359 | pragma?: string;
360 | pragmaFrag?: string;
361 | useBuiltIns?: boolean;
362 | useSpread?: boolean;
363 | }
364 |
--------------------------------------------------------------------------------
/src/plugins/transform-module/index.ts:
--------------------------------------------------------------------------------
1 | // Based on babel-plugin-transform-modules-commonjs v7.24.7
2 | // MIT - Copyright (c) 2014-present Sebastian McKenzie and other contributors
3 | // https://github.com/babel/babel/tree/c7bb6e0f/packages/babel-plugin-transform-modules-commonjs/src
4 |
5 | import type { NodePath, PluginPass, Visitor } from "@babel/core";
6 | import { types as t, template } from "@babel/core";
7 | import { isModule } from "@babel/helper-module-imports";
8 | import type {
9 | PluginOptions,
10 | RewriteModuleStatementsAndPrepareHeaderOptions,
11 | } from "@babel/helper-module-transforms";
12 | import {
13 | buildNamespaceInitStatements,
14 | ensureStatementsHoisted,
15 | getModuleName,
16 | isSideEffectImport,
17 | rewriteModuleStatementsAndPrepareHeader,
18 | wrapInterop,
19 | } from "@babel/helper-module-transforms";
20 | import type { PluginAPI } from "@babel/helper-plugin-utils";
21 | import { declare } from "@babel/helper-plugin-utils";
22 | import simplifyAccess from "@babel/helper-simple-access";
23 | import type { Scope } from "@babel/traverse";
24 | import { transformDynamicImport } from "./dynamic-import";
25 | import { defineCommonJSHook, makeInvokers } from "./hooks";
26 | import { lazyImportsHook } from "./lazy";
27 |
28 | interface Options extends PluginOptions {
29 | allowCommonJSExports?: boolean;
30 | allowTopLevelThis?: boolean;
31 | importInterop?: RewriteModuleStatementsAndPrepareHeaderOptions["importInterop"];
32 | lazy?: RewriteModuleStatementsAndPrepareHeaderOptions["lazy"];
33 | loose?: boolean;
34 | mjsStrictNamespace?: boolean;
35 | noInterop?: boolean;
36 | strict?: boolean;
37 | strictMode?: boolean;
38 | strictNamespace?: boolean;
39 | async: boolean;
40 | }
41 |
42 | // @ts-expect-error
43 | export default declare((api: PluginAPI, options: Options) => {
44 | // api.assertVersion(REQUIRED_VERSION(7));
45 |
46 | const {
47 | // 'true' for imports to strictly have .default, instead of having
48 | // destructuring-like behavior for their properties. This matches the behavior
49 | // of the initial Node.js (v12) behavior when importing a CommonJS without
50 | // the __esModule property.
51 | // .strictNamespace is for non-mjs files, mjsStrictNamespace if for mjs files.
52 | strictNamespace = false,
53 | mjsStrictNamespace = strictNamespace,
54 |
55 | allowTopLevelThis,
56 | strict,
57 | strictMode,
58 | noInterop,
59 | importInterop,
60 | lazy = false,
61 | // Defaulting to 'true' for now. May change before 7.x major.
62 | allowCommonJSExports = true,
63 | loose = false,
64 | async = false,
65 | } = options;
66 |
67 | const constantReexports = api.assumption("constantReexports") ?? loose;
68 | const enumerableModuleMeta = api.assumption("enumerableModuleMeta") ?? loose;
69 | const noIncompleteNsImportDetection =
70 | api.assumption("noIncompleteNsImportDetection") ?? false;
71 |
72 | if (
73 | typeof lazy !== "boolean" &&
74 | typeof lazy !== "function" &&
75 | (!Array.isArray(lazy) || !lazy.every((item) => typeof item === "string"))
76 | ) {
77 | throw new Error(`.lazy must be a boolean, array of strings, or a function`);
78 | }
79 |
80 | if (typeof strictNamespace !== "boolean") {
81 | throw new TypeError(`.strictNamespace must be a boolean, or undefined`);
82 | }
83 | if (typeof mjsStrictNamespace !== "boolean") {
84 | throw new TypeError(`.mjsStrictNamespace must be a boolean, or undefined`);
85 | }
86 |
87 | const getAssertion = (localName: string) => template.expression.ast`
88 | (function(){
89 | throw new Error(
90 | "The CommonJS '" + "${localName}" + "' variable is not available in ES6 modules." +
91 | "Consider setting setting sourceType:script or sourceType:unambiguous in your " +
92 | "Babel config for this file.");
93 | })()
94 | `;
95 |
96 | const moduleExportsVisitor: Visitor<{ scope: Scope }> = {
97 | ReferencedIdentifier(path) {
98 | const localName = path.node.name;
99 | if (localName !== "module" && localName !== "exports") return;
100 |
101 | const localBinding = path.scope.getBinding(localName);
102 | const rootBinding = this.scope.getBinding(localName);
103 |
104 | if (
105 | // redeclared in this scope
106 | rootBinding !== localBinding ||
107 | (path.parentPath.isObjectProperty({ value: path.node }) &&
108 | path.parentPath.parentPath.isObjectPattern()) ||
109 | path.parentPath.isAssignmentExpression({ left: path.node }) ||
110 | path.isAssignmentExpression({ left: path.node })
111 | ) {
112 | return;
113 | }
114 |
115 | path.replaceWith(getAssertion(localName));
116 | },
117 |
118 | UpdateExpression(path) {
119 | const arg = path.get("argument");
120 | if (!arg.isIdentifier()) return;
121 | const localName = arg.node.name;
122 | if (localName !== "module" && localName !== "exports") return;
123 |
124 | const localBinding = path.scope.getBinding(localName);
125 | const rootBinding = this.scope.getBinding(localName);
126 |
127 | // redeclared in this scope
128 | if (rootBinding !== localBinding) return;
129 |
130 | path.replaceWith(
131 | t.assignmentExpression(
132 | path.node.operator[0] + "=",
133 | arg.node,
134 | getAssertion(localName),
135 | ),
136 | );
137 | },
138 |
139 | AssignmentExpression(path) {
140 | const left = path.get("left");
141 | if (left.isIdentifier()) {
142 | const localName = left.node.name;
143 | if (localName !== "module" && localName !== "exports") return;
144 |
145 | const localBinding = path.scope.getBinding(localName);
146 | const rootBinding = this.scope.getBinding(localName);
147 |
148 | // redeclared in this scope
149 | if (rootBinding !== localBinding) return;
150 |
151 | const right = path.get("right");
152 | right.replaceWith(
153 | t.sequenceExpression([right.node, getAssertion(localName)]),
154 | );
155 | } else if (left.isPattern()) {
156 | const ids = left.getOuterBindingIdentifiers();
157 | const localName = Object.keys(ids).find((localName) => {
158 | if (localName !== "module" && localName !== "exports") return false;
159 |
160 | return (
161 | this.scope.getBinding(localName) ===
162 | path.scope.getBinding(localName)
163 | );
164 | });
165 |
166 | if (localName) {
167 | const right = path.get("right");
168 | right.replaceWith(
169 | t.sequenceExpression([right.node, getAssertion(localName)]),
170 | );
171 | }
172 | }
173 | },
174 | };
175 |
176 | return {
177 | name: "transform-modules-commonjs",
178 |
179 | pre() {
180 | this.file.set("@babel/plugin-transform-modules-*", "commonjs");
181 |
182 | if (lazy) defineCommonJSHook(this.file, lazyImportsHook(lazy));
183 | },
184 |
185 | visitor: {
186 | ["CallExpression" +
187 | // @ts-expect-error
188 | (api.types.importExpression ? "|ImportExpression" : "")](
189 | this: PluginPass,
190 | path: NodePath,
191 | ) {
192 | if (path.isCallExpression() && !t.isImport(path.node.callee)) return;
193 |
194 | let { scope } = path;
195 | do {
196 | scope.rename("require");
197 | } while ((scope = scope.parent));
198 |
199 | transformDynamicImport(path, noInterop, this.file);
200 | },
201 |
202 | Program: {
203 | exit(path, state) {
204 | if (!isModule(path)) return;
205 |
206 | // Rename the bindings auto-injected into the scope so there is no
207 | // risk of conflict between the bindings.
208 | path.scope.rename("exports");
209 | path.scope.rename("module");
210 | path.scope.rename("require");
211 | path.scope.rename("__filename");
212 | path.scope.rename("__dirname");
213 |
214 | // Rewrite references to 'module' and 'exports' to throw exceptions.
215 | // These objects are specific to CommonJS and are not available in
216 | // real ES6 implementations.
217 | if (!allowCommonJSExports) {
218 | if (process.env.BABEL_8_BREAKING) {
219 | simplifyAccess(path, new Set(["module", "exports"]));
220 | } else {
221 | simplifyAccess(path, new Set(["module", "exports"]), false);
222 | }
223 | path.traverse(moduleExportsVisitor, {
224 | scope: path.scope,
225 | });
226 | }
227 |
228 | let moduleName = getModuleName(this.file.opts, options);
229 | // @ts-expect-error
230 | if (moduleName) moduleName = t.stringLiteral(moduleName);
231 |
232 | const hooks = makeInvokers(this.file);
233 |
234 | const { meta, headers } = rewriteModuleStatementsAndPrepareHeader(
235 | path,
236 | {
237 | exportName: "exports",
238 | constantReexports,
239 | enumerableModuleMeta,
240 | strict,
241 | strictMode,
242 | allowTopLevelThis,
243 | noInterop,
244 | importInterop,
245 | wrapReference: hooks.wrapReference,
246 | getWrapperPayload: hooks.getWrapperPayload,
247 | esNamespaceOnly:
248 | typeof state.filename === "string" &&
249 | /\.mjs$/.test(state.filename)
250 | ? mjsStrictNamespace
251 | : strictNamespace,
252 | noIncompleteNsImportDetection,
253 | filename: this.file.opts.filename,
254 | },
255 | );
256 |
257 | for (const [source, metadata] of meta.source) {
258 | const loadExpr = async
259 | ? t.awaitExpression(
260 | t.callExpression(t.identifier("jitiImport"), [
261 | t.stringLiteral(source),
262 | ]),
263 | )
264 | : t.callExpression(t.identifier("require"), [
265 | t.stringLiteral(source),
266 | ]);
267 |
268 | let header: t.Statement | undefined | null;
269 | if (isSideEffectImport(metadata)) {
270 | if (lazy && metadata.wrap === "function") {
271 | throw new Error("Assertion failure");
272 | }
273 |
274 | header = t.expressionStatement(loadExpr);
275 | } else {
276 | const init =
277 | wrapInterop(path, loadExpr, metadata.interop) || loadExpr;
278 |
279 | if (metadata.wrap) {
280 | const res = hooks.buildRequireWrapper!(
281 | metadata.name,
282 | init,
283 | metadata.wrap,
284 | metadata.referenced,
285 | );
286 | if (res === false) continue;
287 | else {
288 | header = res;
289 | }
290 | }
291 | header ??= template.statement.ast`
292 | var ${metadata.name} = ${init};
293 | `;
294 | }
295 | header.loc = metadata.loc;
296 | headers.push(
297 | header,
298 | ...buildNamespaceInitStatements(
299 | meta,
300 | metadata,
301 | constantReexports,
302 | hooks.wrapReference,
303 | ),
304 | );
305 | }
306 |
307 | ensureStatementsHoisted(headers);
308 | path.unshiftContainer("body", headers);
309 | // eslint-disable-next-line unicorn/no-array-for-each
310 | path.get("body").forEach((path) => {
311 | if (!headers.includes(path.node)) return;
312 | if (path.isVariableDeclaration()) {
313 | path.scope.registerDeclaration(path);
314 | }
315 | });
316 | },
317 | },
318 | },
319 | };
320 | });
321 |
322 | export { defineCommonJSHook } from "./hooks";
323 |
--------------------------------------------------------------------------------