typescript-bundle
typescript-bundle copied to clipboard
require.main is undefined
In Node, a common idiom for checking if the current module is the one that was directly invoked is if (require.main === module) { ... }. Here are the docs.
This works as expected when the module is "inlined" in the top-level IIFE of the generated JavaScript code (see my other issue), but when this code is used inside of a defined module this condition fails because require is not the expected object.
This seems to be caused by require being provided by dependency injection, because the injected require object is a function rather than its usual object value.
This looks quite interesting. TypeScript-Bundle avoids testing for various JS environment conditions (such as require.main) but does provide a proxy for CommonJS require as a fallback for when the bundles resolver fails to resolve at runtime (noting that TypeScript-Bundle uses AMD as its module target). The proxy itself is just a function without the main property on it.
The proxy for require is injected as a dependency into the define itself, with TypeScript's AMD output emitted as....
// note that tsc-bundle will proxy to nodes cjs require for un-resolvable AMD modules.
define("main", ["require", "exports", ...], (require, exports, ...) => {
const fs = require('fs') // this is possible
})
You can test with the following.
$ tsc-bundle ./main.ts --outFile bundle.js --watch
Which should work in both emit forms (AMD and non-AMD depending on the if the module has a import / export).
const fs = require('fs')
console.log(fs.writeFile)
export const foo = 1 // comment this line
Do you have any suggestions for improving upon this? Curious about the various node idioms around modules that might lead to better patterns for resolving CJS modules through AMD. Open to your thoughts.
What is this proxy to require, exactly? Why is it a proxy? Would it be possible to directly inject the require object, if it is provided by the environment?
My thinking here is that a bundler should ensure that the behavior of
tsc -p ./tsconfig.json
node path/to/entrypoint.js
should be identical to the behavior of
tsc-bundle ./tsconfig.json
node path/to/bundle.js
Yes, both tsc -p ./tsconfig.json and tsc-bundle ./tsconfig.json should work the same with respect to paths.
In terms of the require proxy, it has more to do with resolving CJS modules in node within the bundle. let me explain.
The require() function that gets injected as a AMD dependency resolves both on behalf of the bundle (so resolving modules internal to the bundle) as well as to the nodes cjs require if available. The require() passed in just proxies the request to nodes CJS require function IF it can't resolve from the bundle itself. The logic for it looks a bit like this.
// Function passed as AMD 'require' dependency.
function proxy_require(module_name: string) {
try { return amd_bundle_resolve(module_name) } catch { /* ignore */ }
try { return require(module_name) } catch { /* ignore */ }
// ... other resolution strategies here
throw Error(`unable to resolve module ${module_name}`)
}
So, the name property is unavailable for the require() that gets passed in. So it won't be possible to check for if(require.main === module) { ... } at runtime if running a bundle in node. However it supports the following usage patterns if you need to resolve with or without CJS require()
import syntax
The following would be the typical TypeScript approach to import modules using import. Here 'fs' will be resolved from the node cjs require, and foo from the internal bundle.
import * as foo from './foo'
import * as fs from 'fs'
define('foo', ['require', 'exports'], (require, exports) {
exports.bar = 1
})
define('main', ['require', 'exports', 'foo', 'fs'], (require, exports, foo, fs) {
console.log(foo)
console.log(fs)
})
late via 'require' dependency.
A module can also be fetched using the require dependency passed into the AMD definition. This is less common usage, but was written to handle conditional and late require for node (which is a pattern you see sometimes in node applications). It provides a way to resolve modules in a CJS fashion, but its not something I would recommend using.
define('foo', ['require', 'exports'], (require, exports) {
exports.bar = 1
})
define('main', ['require', 'exports'], (require, exports) {
const foo = require('foo')
const fs = require('fs')
console.log(foo)
console.log(fs)
})