Any way to restrict export within a package in nodejs? - node.js

Is there a way or pattern in NodeJS to share functions within the modules of a package only and not allow them to be shared from another package?
E.g if Package A has file1.js, file2.js and index.js. index.js uses functions from file1 and file2.
Package B uses Package A. It seems like all modules exported from file1 and file2 are also available to Package B. Can it be restricted to those exported from index.js of Package A only?
In short, is there a support for something like a protected scope?

as AZ_ said: only export that what you want to export.
example:
// file1
export const foo = () => { console.log("foo"); }
// file2
import {foo} from "./file1"
export const bar = () => {
foo();
console.log("bar");
}
// index
import {bar} from "./bar"
// the package will only export bar
export { bar }

Related

sinonjs - stub a library referenced internally as a function using node modules (no require)

I have an external library that is exported as a function, in the Stub documentation it only accepts an input with the first parameter as object and the second parameter as method , so how could I stub a library that is exported as a function in a Node ES Modules environment (without Commonjs)?
(In my specific case, I had used a library that use the internet to work, and I wanted to test derivated functions without accessing the internet, so I want to stub the external function library)
Attempts:
I couldn't use solutions like proxyquire as it is a solution based on require and module cache deletion, which are not supported within Node's ES modules.
I don't want to use proxyquire-universal because it simulates the operation of require in normal ES, and it's just a function in the whole project that I wanted to test, I was looking for a simpler solution
Changing the import mode doesn't work as it's not recompiled like in babel, so even if I import as import * as obj from 'lib' only the function name is changed
I had this error environment with 3 files:
An external library called "sum" for example, which I don't want to change, exported as follows:
External Library: externalSum.js
module.exports = function sum(a, b){
console.log(">>>>> running without stub <<<<<")
return a + b
}
This library used in the middle of a project file called mathProblems
Internal File: mathProblems.js
import sum from 'externalSum'
export function sumMore1(a) {
return sum(a, 1);
}
And I have a test file
Internal File: spec.js
import sinon from 'sinon'
import assert from 'assert'
import sumObj from 'externalSum'
import { sumMore1 } from '../mathProblems.js'
describe('sumMore1 is working', () => {
it('sumMore1 test', () => {
const sum_stub = sinon.stub(sumObj) // NOT STUBBING
sum_stub.withArgs(1, 1).returns(2) // NOT STUBBING
const result = sumMore1(1)
assert.strictEqual(result, 2)
});
});
I didn't find this solution anywhere on the internet, i found some solutions that work for node with request or babilon, but not for my case using ES Modules:
https://github.com/sinonjs/sinon/issues/562
https://minaluke.medium.com/how-to-stub-spy-a-default-exported-function-a2dc1b580a6b
So I wanted to register the solution in case anyone needs it.
To solve this, create a new file, which can be allocated anywhere in the project, in this case I will call it sumImport.js:
Internal File: sumImport.js
import sum from 'externalSum';
// export as object
export default {
sum
}
The object needs to be called inside the created function I want to test, and changed the import way:
Internal File: mathProblems.js
import sumObj from './sumImport.js';
export function sumMore1(a) {
const { sum } = sumObj;
return sum(a, 1);
}
And I finally managed to import as an object in the test:
Internal File: spec.js
import sinon from 'sinon'
import assert from 'assert'
import sumObj from '../sumImport.js'
import { sumMore1 } from '../mathProblems.js'
describe('sumMore1 is working', () => {
it('sumMore1 test', () => {
const sum_stub = sinon.stub(sumObj, "sum") // STUBBED
sum_stub.withArgs(1, 1).returns(2) // STUBBED
const result = sumMore1(1)
assert.strictEqual(result, 2)
});
});
I hope it helps someone and if someone else has some better solutions I would also be grateful!

How to re-import module in Typescript

I'm fairly new to typescript but I think this issue is actually indirectly related to Node. Anyway, I'm trying to write a small node lib and I'm using Typescript.
I have a class, let's say:
//foo.ts
export class Foo {
constructor(options:Options = {}) { ... }
}
However using this class directly most times will not be desirable given the nature of my lib, so I'm also exporting a module
//module.ts
import { Foo } from './foo'
let instance
... // other methods
export function init(options: Options) {
instance = new Foo(options)
}
Everything works as I expect. The issue I have is to write unit tests for module.ts. I need to write several test cases, but once I call module.init(options) once, the instance will be created and as node cache modules, importing it again in my test file will still have the foo instance there. In vanilla javascript I used the proxyquire to generate other instances of the module, but it seems that it doesn't work so well with typescript... How would I "force" node to "re-import" a "clean" instance of my module?
How would I "force" node to "re-import" a "clean" instance of my module?
A simple factory method e.g. Change
//foo.ts
export class Foo {
constructor(options:Options = {}) { ... }
}
To:
//foo.ts
class Foo {
constructor(options:Options = {}) { ... }
}
export const create(options: Options): Foo {
return new Foo(options);
}

module.exports vs. export default in Node.js and ES6

What is the difference between Node's module.exports and ES6's export default? I'm trying to figure out why I get the "__ is not a constructor" error when I try to export default in Node.js 6.2.2.
What works
'use strict'
class SlimShady {
constructor(options) {
this._options = options
}
sayName() {
return 'My name is Slim Shady.'
}
}
// This works
module.exports = SlimShady
What doesn't work
'use strict'
class SlimShady {
constructor(options) {
this._options = options
}
sayName() {
return 'My name is Slim Shady.'
}
}
// This will cause the "SlimShady is not a constructor" error
// if in another file I try `let marshall = new SlimShady()`
export default SlimShady
The issue is with
how ES6 modules are emulated in CommonJS
how you import the module
ES6 to CommonJS
At the time of writing this, no environment supports ES6 modules natively. When using them in Node.js you need to use something like Babel to convert the modules to CommonJS. But how exactly does that happen?
Many people consider module.exports = ... to be equivalent to export default ... and exports.foo ... to be equivalent to export const foo = .... That's not quite true though, or at least not how Babel does it.
ES6 default exports are actually also named exports, except that default is a "reserved" name and there is special syntax support for it. Lets have a look how Babel compiles named and default exports:
// input
export const foo = 42;
export default 21;
// output
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
var foo = exports.foo = 42;
exports.default = 21;
Here we can see that the default export becomes a property on the exports object, just like foo.
Import the module
We can import the module in two ways: Either using CommonJS or using ES6 import syntax.
Your issue: I believe you are doing something like:
var bar = require('./input');
new bar();
expecting that bar is assigned the value of the default export. But as we can see in the example above, the default export is assigned to the default property!
So in order to access the default export we actually have to do
var bar = require('./input').default;
If we use ES6 module syntax, namely
import bar from './input';
console.log(bar);
Babel will transform it to
'use strict';
var _input = require('./input');
var _input2 = _interopRequireDefault(_input);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
console.log(_input2.default);
You can see that every access to bar is converted to access .default.
Felix Kling did a great comparison on those two, for anyone wondering how to do an export default alongside named exports with module.exports in nodejs
module.exports = new DAO()
module.exports.initDAO = initDAO // append other functions as named export
// now you have
let DAO = require('_/helpers/DAO');
// DAO by default is exported class or function
DAO.initDAO()
You need to configure babel correctly in your project to use export default and export const foo
npm install --save-dev #babel/plugin-proposal-export-default-from
then add below configration in .babelrc
"plugins": [
"#babel/plugin-proposal-export-default-from"
]

TypeScript and RequireJS - where'd my static typing go?

In TypeScript, I can use RequireJS to import modules:
import Foo = require("common/Foo"); // Foo is one of my TypeScript exported classes.
class Bar {
// Wooohooo! I can use Foo here, complete with intellisense!
new Foo(1, "ab").zanzibar();
}
But sometimes I don't want to load Foo until it's really needed, like once some function is called:
class Bar {
doSomething() {
// OK, now we need Foo. Import it.
require(["common/Foo"], Foo => {
// Use Foo here.
// Uh oh. No intellisense -- where did my static typing disappear to?
new Foo(1, "ab").zanzibar(); // Works at runtime, but no intellisense. :-(
});
}
}
Is there a way to tell TypeScript what type Foo is?
From the Modules in TypeScript documentation (see "Optional Module Loading"):
Sample: Dynamic Module Loading in require.js
declare var require;
import Zip = require('./ZipCodeValidator');
if (needZipValidation) {
require(['./ZipCodeValidator'], (x: typeof Zip) => {
if (x.isAcceptable('...')) { /* ... */ }
});
}

How to get a variable from a file to another file in Node.js

Here is my first file:
var self = this;
var config = {
'confvar': 'configval'
};
I want this configuration variable in another file, so I have done this in another file:
conf = require('./conf');
url = conf.config.confvar;
But it gives me an error.
TypeError: Cannot read property 'confvar' of undefined
What can I do?
Edit (2020):
Since Node.js version 8.9.0, you can also use ECMAScript Modules with varying levels of support. The documentation.
For Node v13.9.0 and beyond, experimental modules are enabled by default
For versions of Node less than version 13.9.0, use --experimental-modules
Node.js will treat the following as ES modules when passed to node as the initial input, or when referenced by import statements within ES module code:
Files ending in .mjs.
Files ending in .js when the nearest parent package.json file contains a top-level field "type" with a value of "module".
Strings passed in as an argument to --eval or --print, or piped to node via STDIN, with the flag --input-type=module.
Once you have it setup, you can use import and export.
Using the example above, there are two approaches you can take
./sourceFile.js:
// This is a named export of variableName
export const variableName = 'variableValue'
// Alternatively, you could have exported it as a default.
// For sake of explanation, I'm wrapping the variable in an object
// but it is not necessary.
// You can actually omit declaring what variableName is here.
// { variableName } is equivalent to { variableName: variableName } in this case.
export default { variableName: variableName }
./consumer.js:
// There are three ways of importing.
// If you need access to a non-default export, then
// you use { nameOfExportedVariable }
import { variableName } from './sourceFile'
console.log(variableName) // 'variableValue'
// Otherwise, you simply provide a local variable name
// for what was exported as default.
import sourceFile from './sourceFile'
console.log(sourceFile.variableName) // 'variableValue'
./sourceFileWithoutDefault.js:
// The third way of importing is for situations where there
// isn't a default export but you want to warehouse everything
// under a single variable. Say you have:
export const a = 'A'
export const b = 'B'
./consumer2.js
// Then you can import all exports under a single variable
// with the usage of * as:
import * as sourceFileWithoutDefault from './sourceFileWithoutDefault'
console.log(sourceFileWithoutDefault.a) // 'A'
console.log(sourceFileWithoutDefault.b) // 'B'
// You can use this approach even if there is a default export:
import * as sourceFile from './sourceFile'
// Default exports are under the variable default:
console.log(sourceFile.default) // { variableName: 'variableValue' }
// As well as named exports:
console.log(sourceFile.variableName) // 'variableValue
You can re-export anything from another file. This is useful when you have a single point of exit (index.{ts|js}) but multiple files within the directory.
Say you have this folder structure:
./src
├── component
│   ├── index.js
│   ├── myComponent.js
│   └── state.js
└── index.js
You could have various exports from both store.js and my-component.js but only want to export some of them.
./src/component/myComponent.js:
import createState from "./state";
export function example(){ };
./src/component/state.js:
export default function() {}
./src/component/index.js
export { example as default } from "./myComponent";
export * from "./myComponent"
./src/index.js
export * from "./component"
Original Answer:
You need module.exports:
Exports
An object which is shared between all instances of the current module
and made accessible through require(). exports is the same as the
module.exports object. See src/node.js for more information. exports
isn't actually a global but rather local to each module.
For example, if you would like to expose variableName with value "variableValue" on sourceFile.js then you can either set the entire exports as such:
module.exports = { variableName: "variableValue" };
Or you can set the individual value with:
module.exports.variableName = "variableValue";
To consume that value in another file, you need to require(...) it first (with relative pathing):
const sourceFile = require('./sourceFile');
console.log(sourceFile.variableName);
Alternatively, you can deconstruct it.
const { variableName } = require('./sourceFile');
// current directory --^
// ../ would be one directory down
// ../../ is two directories down
If all you want out of the file is variableName then
./sourceFile.js:
const variableName = 'variableValue'
module.exports = variableName
./consumer.js:
const variableName = require('./sourceFile')
File FileOne.js:
module.exports = { ClientIDUnsplash : 'SuperSecretKey' };
File FileTwo.js:
var { ClientIDUnsplash } = require('./FileOne');
This example works best for React.

Resources