Make `babel` transform lazy loading optional
Context
Jiti's lazy-loading approach for the Babel transform is the best option for most use cases. Since the Babel transform requires loading a substantial dependency, deferring its load until needed provides meaningful performance benefits when the transform isn't required. However, I think I ran into a use case where it might make sense to eagerly load it.
We work with a runtime environment that only supports CommonJS execution. The workflow involves:
- Writing source code in ESM for better developer experience
- Using
jitito dynamically load configuration files at runtime - Bundling everything with Webpack into a single CJS file for deployment
import { createJiti } from "jiti";
async function loadConfig(name) {
const jiti = createJiti(__filename);
const path = resolve(__dirname, `./config/config.${name}`);
const config = await jiti.import(path, { default: true });
return config;
}
Problem
When bundling with Webpack, the ESM version of jiti (dist/lib/jiti.mjs) contains this code:
let _transform;
function lazyTransform(...args) {
if (!_transform) {
_transform = createRequire(import.meta.url)("../dist/babel.cjs");
}
return _transform(...args);
}
And Webpack's default behavior is to replace import.meta.url with a static file path at build time:
// After Webpack processes it
_transform = createRequire("file:///Users/username/project/node_modules/jiti/lib/jiti.mjs")("../dist/babel.cjs");
This hardcoded absolute path doesn't exist in the production runtime, causing the lazy load to fail:
Error: Cannot find module '../dist/babel.cjs'
Workaround
I managed to workaround it by using the CJS distributed version via require('jiti') as it does not use import.meta.url under the hood:
const { createJiti } = require("jiti"); // Uses lib/jiti.cjs
Webpack can properly handle and bundle this require() call. However, the CommonJS entry point is marked as deprecated, and I'm concerned about future compatibility.
Proposed solutions
Of course, if someone has a better solution please let me know. From my point of view I think either of these two options below could work:
Option 1: Configuration Flag
const jiti = createJiti(__filename, {
eagerTransform: true // Loads babel immediately
});
Option 2: Separate Entry Point
import { createJiti } from "jiti/eager";
Additional information
- [x] Would you be willing to help implement this feature?
Thanks for explaining your situation.
Have you tried force resolving to jiti.cjs? If you do it, webpack can transform predictable commonjs requires it only cannot determine ESM lazy import.
Have you tried force resolving to jiti.cjs?
First of all, thanks for such a quick answer!
Do you have an example on how to do this? I tried some configuration options but didn't work. Never heard of a way to force resolve to CJS 🤔. For now, I was able to patch jiti and successfully implement option 2, so it's doable.
I actually don't think option 1 is doable as I don't know of a way to conditionally and statically import a module in ESM.
Although to be fair, I believe I don't need this anymore (I know it has been less than a day 😆 but the "complexity" of what we were trying to do made us reconsider the whole workflow). We have a way to hook into the build system, so we can actually use jiti there (no runtime constraints), parse the config (which is available at build time) and rewrite it into a format that's easier to work with at runtime (e.g. JSON), then deploy that.
This way we also avoid having to bundle jiti and babel into the final code, which causes a large increase in bundle size.
So, feel free to close this issue if you think this is a not worthy feature (understandable as it's a pretty niche situation), or let me know if you want me to open a PR with solution 2 ^^
I guess you might try a webpack resolve alias like { jiti: require.resolve("jiti") } in webpack.config (when using require.resolve it will be resolve to node_modules/jiti/lib/jiti.cjs)
Benefit is, even without lazy import, webpack should make a lazy evaluated function (saves eval time of bundle)
The lazy loading feature caused bug in nitropack build too.
When i use [email protected], dis/babel.cjs won't be packaged in, this will caused runtime error;
Listening on http://[::]:30410
[unhandledRejection] Error: Cannot find module '../dist/babel.cjs'
Require stack:
- /app/.output/server/node_modules/jiti/lib/jiti.mjs
at Function._resolveFilename (node:internal/modules/cjs/loader:1401:15)
at defaultResolveImpl (node:internal/modules/cjs/loader:1057:19)
at resolveForCJSWithHooks (node:internal/modules/cjs/loader:1062:22)
at Function._load (node:internal/modules/cjs/loader:1211:37)
at TracingChannel.traceSync (node:diagnostics_channel:322:14)
at wrapModuleLoad (node:internal/modules/cjs/loader:235:24)
at Module.require (node:internal/modules/cjs/loader:1487:12)
at require (node:internal/modules/helpers:135:16)
at Object.lazyTransform [as transform] (file:///app/.output/server/node_modules/jiti/lib/jiti.mjs:13:48)
at /app/.output/server/node_modules/jiti/dist/jiti.cjs:1:152918 {
code: 'MODULE_NOT_FOUND',
requireStack: [ '/app/.output/server/node_modules/jiti/lib/jiti.mjs' ]
Then, i override jiti to 2.4.2, it works ok.