Conditional import to switch implementations - node.js

I have node.js application written in TypeScript and I need to switch between two interface implementations based on the config file. Currently I have this code which seems to be working.
"use strict";
import { config } from "../config";
let Something;
if (config.useFakeSomething) {
Something = require("./FakeSomething").FakeSomething;
} else {
Something = require("./RealSomething").RealSomething;
}
...
let s = new Something();
s.DoStuff();
...
But I have bad gut feeling about that (mainly because of the mixing require and import for loading modules). Is there some other way how to achieve implementation switching based on config file without importing both modules?

If you want to keep the client-code for your Something class clean, you can move the conditional importing to a single file. You can have the following directory structure for your Something module:
/Something
RealSomething.ts
FakeSomething.ts
index.ts
And in your index.ts, you can have the following:
import { config } from '../config';
const Something = config.useFakeSomething ?
require('./FakeSomething').FakeSomething :
require('./RealSomething').RealSomething;
export default Something;
And in your client code, you can just import Something:
import Something from './Something/index';

I can see nothing wrong with your approach. In fact lines like
import { config } from "../config";
When targeting commonjs will be compiled to the following javascript (ES6):
const config = require('../config');
So they are effectively identical and you are not mixing different module loading techniques.

You can do it like that:
let moduleLoader:any;
if( pladform == 1 ) {
moduleLoader = require('./module1');
} else {
moduleLoader = require('./module2');
}
and then
if( pladform === 1 ) {
platformBrowserDynamic().bootstrapModule(moduleLoader.module1, [ languageService ]);
}
else if ( pladform === 2 ) {
platformBrowserDynamic().bootstrapModule(moduleLoader.module2, [ languageService ]);
}

In addition to the correct answers above, in case you need this switching for many files within a single folder, you can use a symbolic-link (not available on Windows) that referenced to the right folder, so your code remains clean.
This approach is good for switching between real code and stubs, for instance

A modern answer using the import() function:
import { config } from "../config";
let Something;
if (config.useFakeSomething) {
Something = (await import("./FakeSomething")).FakeSomething;
} else {
Something = (await import("./RealSomething")).RealSomething;
}
...
let s = new Something();
s.DoStuff();
...
Remember that import() is non blocking, so you need to add await if the code that follows needs the result from the import().

Related

Writing a script that would run in browser or NodeJS

I wrote this little example:
import { isBrowser, isNode } from "browser-or-node";
// runs at load
async function run() {
if (isBrowser) {
document.body.style.backgroundColor = "black";
} else if (isNode) {
const fs = await import("./myLittleScript");
console.log(fs);
}
}
run()
And ./myLittleScript is actually importing the node filesystem module to use if the user runs the script in node.
Is this idea wrong ? What is the right approach ?
I get an error that the script is not found, even though it is in the same directory than the index.ts which is the code pasted here.
This is when bundling it with webpack, but I am interested in knowing whether this is an acceptable approach or not. (I know I could just write 2 functions.)

using ESM imports in functions

when you add imports to an ESM package, you can resolve alias paths into the corresponding real ones, like this:
package.json
{
"type": "module",
"imports": { "#*": "./*" }
...
}
now we can use it like this import something from "#module" which resolves into import something from "./module"
but, I can't see any docs about using the same resolving mechanism for builtin functions (or maybe user functions either) that accept PathLike arguments like readFileSync
readFileSync("#file.txt")
I know it is a module resolving, but we can use it as a path resolving.
my question is: how to achieve the same mechanism to resolve any PathLike path, without a manual modification, because simply we need to dispense the relative paths
an example of manual resolving
function example(path: PathLike){
let imports = /* read the property imports from package.json */
let keys = Object.keys(imports)
// make a loop, and for each key make something like this (needs additional work)
let resolvedPath = path.replace('#','./')
// now, use resolvedPath instead of path
}
example of nodejs resolving
function example2(path: PathLike){
let resolvedPath = resolve(path)
}
in addition to the original work of resolve() it also uses the property import to resolve the path to avoid using relative paths
I ask if there is a way to achieve this goal.
The solution
thanks to #RickN this is the final solution:
export function resolveImports(path: PathLike): Promise<string> {
return import.meta!.resolve!(path.toString()).then((resolvedPath) =>
fileURLToPath(resolvedPath)
);
}
As of writing, you need a CLI flag to enable the resolve method in import.meta:
node --experimental-import-meta-resolve your-file.js
(The first part of the name speaks for itself.)
Then, in a script:
console.log( await import.meta.resolve('#foo/bar.js') );
// It can resolve any type of file, even if you can't import() it.
console.log( await import.meta.resolve('#foo/hello.txt') );
As it's a promise-based function, you'll need to await the result.
async function example2(path: PathLike){
return import.meta.resolve(path);
}
As a side-note: this returns a URL (file:///...) so run the result through the built-in URL class url.fileURLToPath() if you just want a file path:
import { fileURLToPath } from 'node:url';
// OR: const { fileURLToPath } = require('node:url');
// For older node versions use `require('url')`
console.log( fileURLToPath(await import.meta.resolve('#foo/hello.txt')) );
// => /tmp/example/directory-with-modules-in-it/hello.txt

Module not found: Can't resolve 'fs' in Next.js application

Unable to identify what's happening in my next.js app. As fs is a default file system module of nodejs. It is giving the error of module not found.
If you use fs, be sure it's only within getInitialProps or getServerSideProps. (anything includes server-side rendering).
You may also need to create a next.config.js file with the following content to get the client bundle to build:
For webpack4
module.exports = {
webpack: (config, { isServer }) => {
// Fixes npm packages that depend on `fs` module
if (!isServer) {
config.node = {
fs: 'empty'
}
}
return config
}
}
For webpack5
module.exports = {
webpack5: true,
webpack: (config) => {
config.resolve.fallback = { fs: false };
return config;
},
};
Note: for other modules such as path, you can add multiple arguments such as
{
fs: false,
path: false
}
I spent hours on this and the solution is also here on Stackoverflow but on different issue -> https://stackoverflow.com/a/67478653/17562602
Hereby I asked for MOD permission to reshare this, since this issue is the first one to show up on Google and probably more and more people stumble would upon the same problem as I am, so I'll try to saved them some sweats
Soo, You need to add this in your next.config.js
module.exports = {
future: {
webpack5: true, // by default, if you customize webpack config, they switch back to version 4.
// Looks like backward compatibility approach.
},
webpack(config) {
config.resolve.fallback = {
...config.resolve.fallback, // if you miss it, all the other options in fallback, specified
// by next.js will be dropped. Doesn't make much sense, but how it is
fs: false, // the solution
};
return config;
},
};
It works for like a charm for me
Minimal reproducible example
A clean minimal example will be beneficial to Webpack beginners since auto splitting based on usage is so mind-blowingly magic.
Working hello world baseline:
pages/index.js
// Client + server code.
export default function IndexPage(props) {
return <div>{props.msg}</div>
}
// Server-only code.
export function getStaticProps() {
return { props: { msg: 'hello world' } }
}
package.json
{
"name": "test",
"version": "1.0.0",
"scripts": {
"dev": "next",
"build": "next build",
"start": "next start"
},
"dependencies": {
"next": "12.0.7",
"react": "17.0.2",
"react-dom": "17.0.2"
}
}
Run with:
npm install
npm run dev
Now let's add a dummy require('fs') to blow things up:
// Client + server code.
export default function IndexPage(props) {
return <div>{props.msg}</div>
}
// Server-only code.
const fs = require('fs')
export function getStaticProps() {
return { props: { msg: 'hello world' } }
}
fails with:
Module not found: Can't resolve 'fs'
which is not too surprising, since there was no way for Next.js to know that that fs was server only, and we wouldn't want it to just ignore random require errors, right? Next.js only knows that for getStaticProps because that's a hardcoded Next.js function name.
OK, so let's inform Next.js by using fs inside getStaticProps, the following works again:
// Client + server code.
export default function IndexPage(props) {
return <div>{props.msg}</div>
}
// Server-only code.
const fs = require('fs')
export function getStaticProps() {
fs
return { props: { msg: 'hello world' } }
}
Mind equals blown. So we understand that any mention of fs inside of the body of getStaticProps, even an useless one like the above, makes Next.js/Webpack understand that it is going to be server-only.
Things would work the same for getServerSideProps and getStaticPaths.
Higher order components (HOCs) have to be in their own files
Now, the way that we factor out IndexPage and getStaticProps across different but similar pages is to use HOCs, which are just functions that return other functions.
HOCs will normally be put outside of pages/ and then required from multiple locations, but when you are about to factor things out to generalize, you might be tempted to put them directly in the pages/ file temporarily, something like:
// Client + server code.
import Link from 'next/link'
export function makeIndexPage(isIndex) {
return (props) => {
return <>
<Link href={isIndex ? '/index' : '/notindex'}>
<a>{isIndex ? 'index' : 'notindex'}</a>
</Link>
<div>{props.fs}</div>
<div>{props.isBlue}</div>
</>
}
}
export default makeIndexPage(true)
// Server-only code.
const fs = require('fs')
export function makeGetStaticProps(isBlue) {
return () => {
return { props: {
fs: Object.keys(fs).join(' '),
isBlue,
} }
}
}
export const getStaticProps = makeGetStaticProps(true)
but if you do this you will be saddened to see:
Module not found: Can't resolve 'fs'
So we understand another thing: the fs usage has to be directly inside the getStaticProps function body, Webpack can't catch it in subfunctions.
The only way to solve this is to have a separate file for the backend-only stuff as in:
pages/index.js
// Client + server code.
import { makeIndexPage } from "../front"
export default makeIndexPage(true)
// Server-only code.
import { makeGetStaticProps } from "../back"
export const getStaticProps = makeGetStaticProps(true)
pages/notindex.js
// Client + server code.
import { makeIndexPage } from "../front"
export default makeIndexPage(false)
// Server-only code.
import { makeGetStaticProps } from "../back"
export const getStaticProps = makeGetStaticProps(false)
front.js
// Client + server code.
import Link from 'next/link'
export function makeIndexPage(isIndex) {
return (props) => {
console.error('page');
return <>
<Link href={isIndex ? '/notindex' : '/'}>
<a>{isIndex ? 'notindex' : 'index'}</a>
</Link>
<div>{props.fs}</div>
<div>{props.isBlue}</div>
</>
}
}
back.js
// Server-only code.
const fs = require('fs')
export function makeGetStaticProps(isBlue) {
return () => {
return { props: {
fs: Object.keys(fs).join(' '),
isBlue,
} }
}
}
Webpack must see that name makeGetStaticProps getting assigned to getStaticProps, so it decides that the entire back file is server-only.
Note that it does not work if you try to merge back.js and front.js into a single file, probably because when you do export default makeIndexPage(true) webpack necessarily tries to pull the entire front.js file into the frontend, which includes the fs, so it fails.
This leads to a natural (and basically almost mandatory) split of library files between:
front.js and front/*: front-end + backend files. These are safe for the frontend. And the backend can do whatever the frontend can do (we are doing SSR right?) so those are also usable from the backend.
Perhaps this is the idea behind the conventional "components" folder in many official examples. But that is a bad name, because that folder should not only contain components, but also any library non-component helpers/constants that will be used from the frontend.
back.js and back/* (or alternatively anything outside of front/*): backend only files. These can only be used by the backend, importing them on frontend will lead to the error
fs,path or other node native modules can be used only inside server-side code, like "getServerSide" functions. If you try to use it in client you get error even you just console.log it.. That console.log should run inside server-side functions as well.
When you import "fs" and use it in server-side, next.js is clever enough to see that you use it in server-side so it wont add that import into the client bundle
One of the packages that I used was giving me this error, I fixed this with
module.exports = {
webpack: (config, { isServer }) => {
if (!isServer) {
config.resolve.fallback.fs = false
}
return config
},
}
but this was throwing warning on terminal:
"Critical dependency: require function is used in a way in which
dependencies cannot be statically extracted"
Then I tried to load the node module on the browser. I copied the "min.js" of the node module from the node_modules and placed in "public/js/myPackage.js" and load it with Script
export default function BaseLayout({children}) {
return (
<>
<Script
// this in public folder
src="/js/myPackage.js"
// this means this script will be loaded first
strategy="beforeInteractive"
/>
</>
)
}
This package was attached to window object and in node_modules source code's index.js:
if (typeof window !== "undefined") {
window.TruffleContract = contract;
}
So I could access to this script as window.TruffleContract. BUt this was not an efficient way.
While this error requires a bit more reasoning than most errors you'll encounter, it happens for a straightforward reason.
Why this happens
Next.js, unlike many frameworks allows you to import server-only (Node.js APIs that don't work in a browser) code into your page files. When Next.js builds your project, it removes server only code from your client-side bundle by checking which code exists inside one any of the following built-in methods (code splitting):
getServerSideProps
getStaticProps
getStaticPaths
Side note: there is a demo app that visualizes how this works.
The Module not found: can't resolve 'xyz' error happens when you try to use server only code outside of these methods.
Error example 1 - basic
To reproduce this error, let's start with a working simple Next.js page file.
WORKING file
/** THIS FILE WORKS FINE! */
import type { GetServerSideProps } from "next";
import fs from "fs"; // our server-only import
type Props = {
doesFileExist: boolean;
};
export const getServerSideProps: GetServerSideProps = async () => {
const fileExists = fs.existsSync("/some-file");
return {
props: {
doesFileExist: fileExists,
},
};
};
const ExamplePage = ({ doesFileExist }: Props) => {
return <div>File exists?: {doesFileExist ? "Yes" : "No"}</div>;
};
export default ExamplePage;
Now, let's reproduce the error by moving our fs.existsSync method outside of getServerSideProps. The difference is subtle, but the code below will throw our dreaded Module not found error.
ERROR file
import type { GetServerSideProps } from "next";
import fs from "fs";
type Props = {
doesFileExist: boolean;
};
/** ERROR!! - Module not found: can't resolve 'fs' */
const fileExists = fs.existsSync("/some-file");
export const getServerSideProps: GetServerSideProps = async () => {
return {
props: {
doesFileExist: fileExists,
},
};
};
const ExamplePage = ({ doesFileExist }: Props) => {
return <div>File exists?: {doesFileExist ? "Yes" : "No"}</div>;
};
export default ExamplePage;
Error example 2 - realistic
The most common (and confusing) occurrence of this error happens when you are using modules that contain multiple types of code (client-side + server-side).
Let's say I have the following module called file-utils.ts:
import fs from 'fs'
// This code only works server-side
export function getFileExistence(filepath: string) {
return fs.existsSync(filepath)
}
// This code works fine on both the server AND the client
export function formatResult(fileExistsResult: boolean) {
return fileExistsResult ? 'Yes, file exists' : 'No, file does not exist'
}
In this module, we have one server-only method and one "shared" method that in theory should work client-side (but as we'll see, theory isn't perfect).
Now, let's try incorporating this into our Next.js page file.
/** ERROR!! */
import type { GetServerSideProps } from "next";
import { getFileExistence, formatResult } from './file-utils.ts'
type Props = {
doesFileExist: boolean;
};
export const getServerSideProps: GetServerSideProps = async () => {
return {
props: {
doesFileExist: getFileExistence('/some-file')
},
};
};
const ExamplePage = ({ doesFileExist }: Props) => {
// ERROR!!!
return <div>File exists?: {formatResult(doesFileExist)}</div>;
};
export default ExamplePage;
As you can see, we get an error here because when we attempt to use formatResult client-side, our module still has to import the server-side code.
To fix this, we need to split our modules up into two categories:
Server only
Shared code (client or server)
// file-utils.ts
import fs from 'fs'
// This code (and entire file) only works server-side
export function getFileExistence(filepath: string) {
return fs.existsSync(filepath)
}
// file-format-utils.ts
// This code works fine on both the server AND the client
export function formatResult(fileExistsResult: boolean) {
return fileExistsResult ? 'Yes, file exists' : 'No, file does not exist'
}
Now, we can create a WORKING page file:
/** WORKING! */
import type { GetServerSideProps } from "next";
import { getFileExistence } from './file-utils.ts' // server only
import { formatResult } from './file-format-utils.ts' // shared
type Props = {
doesFileExist: boolean;
};
export const getServerSideProps: GetServerSideProps = async () => {
return {
props: {
doesFileExist: getFileExistence('/some-file')
},
};
};
const ExamplePage = ({ doesFileExist }: Props) => {
return <div>File exists?: {formatResult(doesFileExist)}</div>;
};
export default ExamplePage;
Solutions
There are 2 ways to solve this:
The "correct" way
The "just get it working" way
The "Correct" way
The best way to solve this error is to make sure that you understand why it is happening (above) and make sure you are only using server-side code inside getStaticPaths, getStaticProps, or getServerSideProps and NOWHERE else.
And remember, if you import a module that contains both server-side and client-side code, you cannot use any of the imports from that module client-side (revisit example #2 above).
The "Just get it working" way
As others have suggested, you can alter your next.config.js to ignore certain modules at build-time. This means that when Next.js attempts to split your page file between server only and shared code, it will not try to polyfill Node.js APIs that fail to build client-side.
In this case, you just need:
/** next.config.js - with Webpack v5.x */
module.exports = {
... other settings ...
webpack: (config, { isServer }) => {
// If client-side, don't polyfill `fs`
if (!isServer) {
config.resolve.fallback = {
fs: false,
};
}
return config;
},
};
Drawbacks of this approach
As shown in the resolve.fallback section of the Webpack documentation, the primary reason for this config option is because as-of Webpack v5.x, core Node.js modules are no longer polyfilled by default. Therefore, the main purpose for this option is to provide a way for you to define which polyfill you want to use.
When you pass false as an option, this means, "do not include a polyfill".
While this works, it can be fragile and require ongoing maintenance to include any new modules that you introduce to your project. Unless you are converting an existing project / supporting legacy code, it is best to go for option #1 above as it promotes better module organization according to how Next.js actually splits the code under the hood.
If trying to use fs-extra in Next.js, this worked for me
module.exports = {
webpack: (config) => {
config.resolve.fallback = { fs: false, path: false, stream: false, constants: false };
return config;
}
}
I got this error in my NextJS app because I was missing export in
export function getStaticProps()
/** #type {import('next').NextConfig} */
module.exports = {
reactStrictMode: false,
webpack5: true,
webpack: (config) => {
config.resolve.fallback = {
fs: false,
net: false,
dns: false,
child_process: false,
tls: false,
};
return config;
},
};
This code fixed my problem and I want to share.Add this code to your next.config file.i'm using
webpack5
For me clearing the cache
npm cache clean -f
and then updating the node version to the latest stable release(14.17.0) worked
It might be that the module you are trying to implement is not supposed to run in a browser. I.e. it's server-side only.
For me, the problem was the old version of the node.js installed. It requires node.js version 14 and higher. The solution was to go to the node.js web page, download the latest version and just install it. And then re-run the project. All worked!
I had the same issue when I was trying to use babel.
For me this worked:
#add a .babelrc file to the root of the project and define presets and plugins
(in my case, I had some issues with the macros of babel, so I defined them)
{
"presets": ["next/babel"],
"plugins": ["macros"]
}
after that shut down your server and run it again
I had this exact issue. My problem was that I was importing types that I had declared in a types.d.ts file.
I was importing it like this, thanks to the autofill provided by VSCode.
import {CUSTOM_TYPE} from './types'
It should have been like this:
import {CUSTOM_TYPE} from './types.d'
In my case, I think the .d was unnecessary so I ended up removing it entirely and renamed my file to types.ts.
Weird enough, it was being imported directly into index.tsx without issues, but any helper files/functions inside the src directory would give me errors.
I ran into this in a NextJS application because I had defined a new helper function directly below getServerSideProps(), but had not yet called that function inside getServerSideProps().
I'm not sure why this created a problem, but it did. I could only get it to work by either calling that function, removing it, or commenting it out.
Don't use fs in the pages directory, since next.js suppose that files in pages directory are running in browser environment.
You could put the util file which uses fs to other directory such as /core
Then require the util in getStaticProps which runs in node.js environment.
// /pages/myPage/index.tsx
import View from './view';
export default View;
export async function getStaticProps() {
const util = require('core/some-util-uses-fs').default; // getStaticProps runs in nodes
const data = await util.getDataFromDisk();
return {
props: {
data,
},
};
}
In my case, this error appeared while refactoring the auth flow of a Next.js page. The cause was some an unused imports that I had not yet removed.
Previously I made the page a protected route like so:
export async function getServerSideProps ({ query, req, res }) {
const session = await unstable_getServerSession(req, res, authOptions)
if (!session) {
return {
redirect: {
destination: '/signin',
permanent: false,
},
}
}
//... rest of server-side logic
}
Whilst refactoring, I read up on NextAuth useSession. Based on what I read there, I was able to change the implementation such that I simply needed to add
MyComponent.auth = true to make a page protected. I then deleted the aforementioned code block inside of getServerSideProps. However, I had not yet deleted the two imports used by said code block:
import { unstable_getServerSession } from 'next-auth/next'
import { authOptions } from 'pages/api/auth/[...nextauth]'
I believe the second of those two imports was causing the problem. So the summary is that in addition to all of the great answers above, it could also be an unused import.
Sometimes this error can be because you have imported something but not mastered it anywhere. This worked for me. I reviewed my code and removed the unused dependencies.

Cannot browserify exports default statement [duplicate]

Before, babel would add the line module.exports = exports["default"]. It no longer does this. What this means is before I could do:
var foo = require('./foo');
// use foo
Now I have to do this:
var foo = require('./foo').default;
// use foo
Not a huge deal (and I'm guessing this is what it should have been all along).
The issue is that I have a lot of code that depended on the way that things used to work (I can convert most of it to ES6 imports, but not all of it). Can anyone give me tips on how to make the old way work without having to go through my project and fix this (or even some instruction on how to write a codemod to do this would be pretty slick).
Thanks!
Example:
Input:
const foo = {}
export default foo
Output with Babel 5
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
var foo = {};
exports["default"] = foo;
module.exports = exports["default"];
Output with Babel 6 (and es2015 plugin):
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
var foo = {};
exports["default"] = foo;
Notice that the only difference in the output is the module.exports = exports["default"].
Edit
You may be interested in this blogpost I wrote after solving my specific issue: Misunderstanding ES6 Modules, Upgrading Babel, Tears, and a Solution
If you want CommonJS export behavior, you'll need to use CommonJS directly (or use the plugin in the other answer). This behavior was removed because it caused confusion and lead to invalid ES6 semantics, which some people had relied on e.g.
export default {
a: 'foo'
};
and then
import {a} from './foo';
which is invalid ES6 but worked because of the CommonJS interoperability behavior you are describing. Unfortunately supporting both cases isn't possible, and allowing people to write invalid ES6 is a worse issue than making you do .default.
The other issue was that it was unexpected for users if they added a named export in the future, for example
export default 4;
then
require('./mod');
// 4
but
export default 4;
export var foo = 5;
then
require('./mod')
// {'default': 4, foo: 5}
You can also use this plugin to get the old export behavior back.
For library authors you may be able to work around this problem.
I usually have an entry point, index.js, which is the file I point to from the main field in package.json. It does nothing other than re-export the actual entry point of the lib:
export { default } from "./components/MyComponent";
To workaround the babel issue, I changed this to an import statement and then assign the default to module.exports:
import MyComponent from "./components/MyComponent";
module.exports = MyComponent;
All my other files stay as pure ES6 modules, with no workarounds. So only the entry point needs a change slightly :)
This will work for commonjs requires, and also for ES6 imports because babel doesn't seem to have dropped the reverse interop (commonjs -> es6). Babel injects the following function to patch up commonjs:
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
I've spent hours battling this, so I hope this saves someone else the effort!
I have had such kind of issue. And this is my solution:
//src/arithmetic.js
export var operations = {
add: function (a, b) {
return a + b;
},
subtract: function (a, b) {
return a - b;
}
};
//src/main.js
import { operations } from './arithmetic';
let result = operations.add(1, 1);
console.log(result);

Namespaces in node.js with require

I am playing around and learning about vows with a personal project. This is a small client side library, with testing done in vows. Therefore, I must build and test a file that is written like this:
(function(exports) {
var module = export.module = { "version":"0.0.1" };
//more stuff
})(this);
In my testing (based off of science.js, d3, etc.) requires that module like so:
require("../module");
I continued to get a "module not defined error" when trying to run the tests, so I went to a repl and ran:
require("../module")
and it returned:
{ module: { version: "0.0.1" } }
I realize I could do something like:
var module = require("../module").module;
but feel like I am creating a problem by doing it that way, especially since the libraries that I based this project on are doing it in the format I described.
I would like for my project to behave similar to those which I based it off of, where:
require("../module");
creates a variable in this namespace:
module.version; //is valid.
I have seen this in a variety of libraries, and I am following the format and thought process to the T but believe I might be missing something about require behavior I don't know about.
There is no problem creating it this way. Modules define what they return in the module.exports object. By the way, you don't actually need self executing functions (SEF), there is no global leakage like in browsers :-)
Examples
module1.js:
module.exports = {
module: { 'version': '0.1.1' }
};
main.js:
var module1 = require( './module1.js' );
// module1 has what is exported in module1.js
Once you've understood how this works, you can easily export the version number right away if you want to:
module1.js:
module.exports = '0.1.1';
main.js:
var module1 = require( './module1.js' );
console.log( module1 === '0.1.1' ); // true
Or if you want some logic, you can easily extend your module1.js file like this:
module.exports = ( function() {
// some code
return version;
} () ); // note the self executing part :-)
// since it's self executed, the exported part
// is what's returned in the SEF
Or, as many modules do, if you want to export some utility functions (and keep others "private"), you could do it like this:
module.exports = {
func1: function() {
return someFunc();
},
func2: function() {},
prop: '1.0.0'
};
// This function is local to this file, it's not exported
function someFunc() {
}
So, in main.js:
var module1 = require( './module1.js' );
module1.func1(); // works
module1.func2(); // works
module1.prop; // "1.0.0"
module1.someFunc(); // Reference error, the function doesn't exist
Your special case
About your special case, I wouldn't recommend doing it like they're doing.
If you look here: https://github.com/jasondavies/science.js/blob/master/science.v1.js
You see that they're not using the var keyword. So, they're creating a global variable.
This is why they can access it once they require the module defining the global variable.
And by the way, the exports argument is useless in their case. It's even misleading, since it actually is the global object (equivalent of window in browsers), not the module.exports object (this in functions is the global object, it'd be undefined if strict mode were enabled).
Conclusion
Don't do it like they're doing, it's a bad idea. Global variables are a bad idea, it's better to use node's philosophy, and to store the required module in a variable that you reuse.
If you want to have an object that you can use in client side and test in node.js, here is a way:
yourModule.js:
// Use either node's export or the global object in browsers
var global = module ? module.exports : window.yourModule;
( function( exports ) {
var yourModule = {};
// do some stuff
exports = yourModule;
} ( global ) );
Which you can shorten to this in order to avoid creating the global variable:
( function( exports ) {
var yourModule = {};
// do some stuff
exports = yourModule;
} ( module ? module.exports : window.yourModule ) );
This way, you can use it like this on the client-side:
yourModule.someMethod(); // global object, "namespace"
And on the server side:
var yourModule = require( '../yourModule.js' );
yourModule.someMethod(); // local variable :-)
Just FYI, .. means "parent directory". This is the relative path of where to get the module. If the file were in the same directory, you'd use ..

Resources