Node.js module.export vs export caching - javascript

After going through node docs, I understand caching a little bit. Using
module.exports{constant_1, constant_2} will cache its assigned value, so everytime when we call require('') it will get the cached value instead of creating a new one.
example_1.js
const test = 'testValue';
module.export = {test};
example_2.js
const cachedValue = require('../example_1.js');
console.log(cachedValue);
But does the caching happen when we use the below syntax?
export {constant_1, constant_2} statement as well?
example_1.js
const test = 'testValue';
export {test};
example_2.js
import {test} from "example_1";
console.log(test);

import {test} from "example_1";
should be
import {test} from "./example_1.js";
like the CommonJS example, but otherwise the caching behavior is similar for ES modules. The node docs for ESM files for instance states
ES modules are resolved and cached as URLs. This means that files containing special characters such as # and ? need to be escaped.
So as long as the an ./example_1.js and another one somewhere else resolve to the same URL, it will be cached as the same module.

Related

What is the best way to export subclasses in an ES6 module?

I have a npm module (dns-resource-record) which I just converted from CommonJS to an ES module. In CommonJS, I had a bit of code in the index.js file which exposed the submodules to the caller.
for (let f of fs.readdirSync(path.join(__dirname))) { // ./rr/*
module.exports[f.toUpperCase()] = require(`./${path.basename(f, '.js')}`)
}
This block of code "discovered" the classes from the filesystem, loaded them in the modules main index, and exported them to callers. Callers were able to access the RR subclasses in the following ways:
const RR = require('dns-resource-record')
new RR.A({...})
import RR from 'dns-resource-record'
const A = require('dns-resource-record').A
new A({...})
While converting to ES modules, I tried the same approach. Since an import statement cannot be inside a block, I tried using the const A = import('rr/a.js') syntax. That created a new problem in that callers would only have a pending promise available at RR.A, because import is async, unlike require was is synchronous. Then I tried wrapping the fs/promises and import('...') calls in an async function which also loaded all the subclasses. That works, but it requires the caller to "initialize" the module so all the promises resolve before it can be used.
Not wanting to change the API of the module, I've found this alternative that works almost exactly the same:
import A from './rr/a.js'
import AAAA from './rr/aaaa.js'
...(29 more)
export {
A,
AAAA,
...(29 more)
}
The difference is that callers had to slightly change how they import the module:
-import RR from 'dns-resource-record'
+import * as RR from 'dns-resource-record'
Is there a way to automatically enumerate the classes, get them loaded, and export them, preferably while maintaining the original API?

100% ESM module world - what it mean?

While reading about tree shaking in webpack documentation, I came across this sentence:
In a 100% ESM module world, identifying side effects is straightforward. However, we aren't there just yet.
What do they mean by "100% ESM module" and how it is different from the current import and export that we already use today?
reference: https://webpack.js.org/guides/tree-shaking/#mark-the-file-as-side-effect-free
The documentation you're reading is contrasting two types of scripts:
Scripts which expose everything they do through what they import and export
Scripts which do something in addition to importing and exporting (this could be seen as a "side effect")
Consider a big library, one that installs itself as a namespace on the global object to expose its functionality. Let's say that jQuery did this - that is, that it runs something like
const jQuery = (function() {
// lots and lots of code
})();
export default jQuery;
window.jQuery = jQuery;
This contains a side-effect: the line
window.jQuery = jQuery;
This means that other parts of your application could use window.jQuery without jQuery being specifically imported into that module. For example, you might have
// someModule.js
export const doStuff = () => {
window.jQuery('div').text('Hi!');
};
And this can work without the line
import jQuery from 'jQuery';
inside the module script, because jQuery is on the window. (For this to work, there needs to be at least one module somewhere that does import 'jQuery'; or something like that, so that jQuery's code that assigns itself to the window runs)
Because of the side-effect, Webpack will have a harder time with automatic tree-shaking - you'll have to explicitly note which modules depend on modules with side-effects.
In contrast, a module without dependency side-effects would be the someModule.js example above: all it does it export a function, without adding or changing functionality elsewhere.
So by "100% ESM module", Webpack is probably referring to scripts for which all modules' dependencies are explicit with import statements, instead of having to depend on side-effects (like a non-imported module assigning something to the window).
There are two popular module syntax nodejs use.
Commonjs: https://nodejs.org/dist/latest-v14.x/docs/api/modules.html
// exporting
module.exports.a = 1
// or, exports is an alias to module.exports, for all differences check out docs
exports.a = 1
// you can assign module.exports object as well, this sets what's exported
module.exports = {
b: 2
}
// a is not exported anymore
// importing default import, imports module.exports object
const a = require('./b')
// or via named import
const {c} = require('./b');
ES modules: https://nodejs.org/dist/latest-v14.x/docs/api/esm.html
// names export
export const a = 1;
// default export
export default const b = 2;
// importing via name
import {a} from './c'
// importing default export
import c from './b'
Commonjs and esm are still in use. So we are not in %100 esm world yet.

How to best import "server-only" code in Next.js?

In the getServerSideProps function of my index page, I'd like to use a function foo, imported from another local file, which is dependent on a certain Node library.
Said library can't be run in the browser, as it depends on "server-only" modules such as fs or request.
I've been using the following pattern, but would like to optimize it. Defining foo as mutable in order to have it be in scope is clunky and seems avoidable.
let foo;
if (typeof window === "undefined") {
foo = require("../clients/foo");
}
export default function Index({data}) {
...
}
export async function getServerSideProps() {
return {
props: {data: await foo()},
}
}
What would be the best practice here? Is it somehow possible to leverage ES6's dynamic import function? What about dynamically importing within getServerSideProps?
I'm using Next.js version 9.3.6.
Thanks.
UPDATE:
It seems as if Next.js's own dynamic import solution is the answer to this. I'm still testing it and will update this post accordingly, when done. The docs seem quite confusing to me as they mentionn disabling imports for SSR, but not vice versa.
https://nextjs.org/docs/advanced-features/dynamic-import
When using getServerSideProps/getStaticProps, Next.js will automatically delete any code inside those functions, and imports used exclusively by them from the client bundle. There's no risk of running server code on the browser.
However, there are a couple of considerations to take in order to ensure the code elimination works as intended.
Don't use imports meant for the server-side inside client-side code (like React components).
Ensure you don't have unused imports in those files. Next.js won't be able to tell if an import is only meant for the server, and will include it in both the server and client bundles.
You can use the Next.js Code Elimination tool to verify what gets bundled for the client-side. You'll notice that getServerSideProps/getStaticProps gets removed as do the imports used by it.
Outside of getServerSideProps/getStaticProps, I found 2 fairly similar solutions.
Rely on dead code elimination
In next.config.js:
config.plugins.push(
new webpack.DefinePlugin({
'process.env.RUNTIME_ENV': JSON.stringify(isServer ? 'server' : 'browser'),
}),
);
export const addBreadcrumb = (...params: AddBreadcrumbParams) => {
if (process.env.RUNTIME_ENV === 'server') {
return import('./sentryServer').then(({ addBreadcrumb }) => addBreadcrumb(...params));
}
return SentryBrowser.addBreadcrumb(...params);
};
Note that some for reason I don't understand, dead code elimination does not work well if you use async await, or if you use a variable to store the result of process.env.RUNTIME_ENV === 'server'. I created a discussion in nextjs github.
Tell webpack to ignore it
In next.config.js
if (!isServer) {
config.plugins.push(
new webpack.IgnorePlugin({
resourceRegExp: /sentryServer$/,
}),
);
}
In that case you need to make sure you will never import this file in the client otherwise you would get an error at runtime.
You can import the third party library or a serverside file inside getServerSideProps or getInitialProps since these functions run on server.
In my case I am using winston logger which runs on server only so importing the config file only on server like this
export async function getServerSideProps (){
const logger = await import('../logger');
logger.info(`Info Log ->> ${JSON.stringify(err)}`);
}
You can also import library/file which has default export like this
export async function getServerSideProps(context) {
const moment = (await import('moment')).default(); //default method is to access default export
return {
date: moment.format('dddd D MMMM YYYY'),
}
}

Do require.resolve for ES modules

I need to resolve ES modules, imported via static import or function-like dynamic import, in a way similar to how CJS modules in node.js can be resolved using require.resolve(). Does anything like that exist for ES modules?
For example, if a Vue package have both vue.runtime.common.js and vue.runtime.esm.js. I need to get path to vue.runtime.esm.js. If package doesn't have one, I'd like to know it.
As long as import.meta.resolve stays experimental, you can use createRequire to get back the require.resolve functionality in ES modules, e.g.
import { createRequire } from 'module';
const require = createRequire(import.meta.url);
const pathName = require.resolve('vue.runtime.esm.js');
you can use import.meta.resolve()
here is an example from the node docs
(async () => {
const dependencyAsset = await import.meta.resolve('component-lib/asset.js');
})();
note that you need to pass in --experimental-import-meta-resolve for this to work, as of node 14.3.0

where do module.exports export your function and what is the use of it when we still use require to import the code into your module

i Have written the following code in node.js
notes.js
console.log('notes app is running');
app.js
const notes = require('./notes.js');
console.log(notes);
When i import the code and run app.js output is shown as notes app is running
Now i updated the code for notes.js
console.log('notes app is running');
addNote = () =>{
console.log('addNote');
return 'New note';
} ;
Now i want to use the following arrow function in my code so updtaed my
app.js
const notes = require('./notes.js');
var res = notes.addNote();
console.log(res);
console.log(notes);
Now it is Throwing me error
notes.addNote is not a function
1) I know i should use module.exports.addNote
2) But i want to know why we can see a log which we have written in notes.js without using module.exports statment. why can't we use require statment and store total code and call the function from that varable as we do for a instance of a class
3)More preciously where do module.export export your code (i mean to which directrey )
4)Plese correct me if anything is wrong
(#1 and #4 don't need answers, so I've left them off.)
2) But i want to know why we can see a log which we have written in notes.js without using module.exports statment.
With Node.js's style of modules (which is a flavor of CommonJS), a module is loaded and executed when it's first required. Your console.log is in the module's code, so when you require it (the first time), that code gets run.
why can't we use require statment and store total code and call the function from that varable as we do for a instance of a class
You can, if that's what you want to do:
exports = {
// code here as object properties
addNote: () => {
console.log('addNote');
return 'New note';
}
};
and
const mod = require("./notes.js");
mode.addNote();
3)More preciously where do module.export export your code (i mean to which directrey )
To the module cache in memory.
Internally, node caches all modules. In order to do this, it starts at the entry point file (e.g. you app.js file) and recursively searches all require statements (or imports).
As node parses modules, any code at the top level of the file will execute - such as your console.log line
console.log('notes app is running');
However, note, that at this point nothing in the file has been exposed to any other part of your codebase. Instead, node takes any value that is exported via module.exports and adds it to an internal cache. This cache is keyed on the path to the file as appeared in the require statements (converted to an absolute path), so for example, the following require statements:
const module1 = require('../module1.js');
const module2 = require('../module2.js');
will result in cache entries which look like:
<path_to>/../module1.js = ... contents of module1.exports
<path_to>/../module2.js = ... contents of module2.exports
Any time you require one of those modules again, you will get the cached version of the modules, it will NOT re-parse the file. For your example, it means that not matter how many times you require the notes.js file, it will only print your console.log('notes app is running'); statement once.
Because of the way node loads modules in isolation, you can ONLY access the elements which are exported via module.exports. Which means any function you define in the file but do not export cannot be accessed.
So, to directly address your questions:
I know i should use module.exports.addNote
Yes. Though, not you can also assign a new object to module.exports, e.g module.exports = { addNote };
But i want to know why we can see a log which we have written in notes.js without using module.exports statment. why can't we use require statment and store total code and call the function from that varable as we do for a instance of a class
Because node parses all required files while generating it's cache
More preciously where do module.export export your code (i mean to which directrey )
They're not stored in a directory, but rather cached in memory based on the file name and contents of module.exports
Plese correct me if anything is wrong
guess this one doesn't need an answer

Categories

Resources