├── .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 | [![npm version](https://img.shields.io/npm/v/jiti?color=F0DB4F)](https://npmjs.com/package/jiti) 6 | [![npm downloads](https://img.shields.io/npm/dm/jiti?color=F0DB4F)](https://npmjs.com/package/jiti) 7 | [![bundle size](https://img.shields.io/bundlephobia/minzip/jiti?color=F0DB4F)](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 | --------------------------------------------------------------------------------