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
Related
Using static import mode works well.For example:
import App from './App.js';
And in common general style of using "import()" works well:
async function fun(){
const App = async ()=> await import("./App.js");
console.log(await App() )//it load module successfully
}
fun();
When i use a string variable parameter for import(),it dosn't work.And it will throw error:
async function fun(){
const path = "./App.js";
const App = async ()=> await import(path).catch(err=>console.log(err));
await App() //Error: Cannot find module 'xxx module path'
}
fun();
So,i must load all modules before using.Could i dynamic import modules like that demo?
I go through the mdn docs about dynamic import but not get helpful information.
And require() performs the same.
Does someone have the same questions?
You need to use ./ and not ../, as ../ is the parent directory and not the current directory. I don't know why this only applies to dynamic imports, but thats just some weird thing about it.
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.
I am new to JS and Node. From what I been reading it seems like ES6 import is not supported in Node so I am forced to use experiment mode (renaming my file to mjs)
This works for the most part unless the mjs file needs to use require. My current solution is to break out the chunk of code that depends on require to a seperate .js file and import that js file
foo.mjs
import readFileSync from './util.js'
...
//orignally just do require('fs') and do fs.readFileSync()
const configFile = readFileSync(configFilePath);
util.js
const fs = require('fs');
const path = require('path');
function readFileSync(filePath) {
console.log(__dirname)
console.log(path.resolve(__dirname, filePath))
return fs.readFileSync(path.resolve(__dirname, filePath), 'utf8');
}
module.exports = readFileSync
Is there a way I can avoid doing this? My app.js has lots of require and I don't want to break out all those parts into separate js files.
I tried changing require to import
// const express = require('express');
import * as express from 'express';
but when run node --experimental-modules app.mjs
I get an error
TypeError: express is not a function
I think I need to either
find a way to use require in mjs
or find a way to import stuff in mjs into a js file
You have three decent options:
Use Babel or TypeScript to get interoperability between the two types of modules
Write a little wrapper to redefine require in ESM code
Use import() (dynamic import) in your CJS to import ESM code
The wrapper to use require in ESM code:
import { createRequire } from 'module'
const require = createRequire(import.meta.url)
const cjsFile = require('./test') // test is regular .js file using module.exports
Dynamic imports:
// foo.mjs:
export const foo = () => true
// index.js:
import('./foo.mjs').then(x => console.log(x.foo()))
Large projects with mixed module types frequently use Babel and/or TypeScript, both of which handle the mixed types just fine. Babel would be simpler to learn and get set up. But if you don't have a ton of code to deal with, the createRequire method works just fine.
As for the issue with Express, it failed because the default export is what you want: import express from 'express', not import * as express....
In almost 2021, is there a way to mock a single function? I mean function without object.
// demo.js
module.exports = () => {
return 'real func demo';
};
// demo.test.js
const demo = require("./demo");
// ...
sinon.stub(demo).callsFake(() => {
return 'mocked function';
});
expect(demo()).to.eq('mocked function')
I'll just quote myself here:
You cannot stub standalone exported functions in an ES2015 compliant module (ESM) nor a CommonJS module in Node. That is just how these module systems work. It's only after transforming them into something else you might be able to achieve what you want. How you replace modules is totally environment specific and is why Sinon touts itself as "Standalone test spies, stubs and mocks for JavaScript" and not a module replacement tool, as that is better left to environment specific utils (proxyquire, various webpack loaders, Jest, etc) for whatever env you are in.
For a more deep-dive into why this will never work in CommonJS when using destructuring, see my answer here. You need to inject a module loader that intercepts the loading.
This is what trying to use Sinon to stub out an ESM module looks like in a spec compliant environment:
$ node esm-stubbing.mjs
/private/tmp/test/node_modules/sinon/lib/sinon/stub.js:72
throw new TypeError("ES Modules cannot be stubbed");
^
TypeError: ES Modules cannot be stubbed
at Function.stub (/private/tmp/test/node_modules/sinon/lib/sinon/stub.js:72:15)
at Sandbox.stub (/private/tmp/test/node_modules/sinon/lib/sinon/sandbox.js:388:37)
at file:///private/tmp/test/esm-stubbing.mjs:4:7
$ cat esm-stubbing.mjs
import sinon from "sinon";
import * as myModule from "./module.mjs";
sinon.stub(myModule, "foo").returns(42);
This is just Sinon itself throwing an error when it sees that the namespace of the module is read-only, instead of letting Node throw a more cryptic error message.
you can import with as syntax.
if you exported as default then use 'default' instead of the actual name
import * as yourModule from '../<path>';
const fake = sinon.fake.returns(<value>);
sinon.replace(yourModule, 'default', fake);
tealium-tracker is written in es6 and transpiled using Babel before published to npm.
When consumers do:
import initTealiumTracker from "tealium-tracker";
everything works as expected.
However, some consumers want to use a require instead of an import, and have to append .default:
const initTealiumTracker = require("tealium-tracker).default;
How could I publish the library to avoid appending .default?
I want consumers to be able to do either:
import initTealiumTracker from "tealium-tracker";
or
const initTealiumTracker = require("tealium-tracker);
Source code
In your source code, If you are ok with using commonJS syntax for import and export...
One option would be to replace all import and export with require and module.exports. Looks like webpack doesn't allow mixing the syntaxes (ES6 and commonJS modules).
So your index.js file can require the functions from dependent module as
const { callUtag, flushUtagQueue } = require("./utagCaller");
and export the default function as
module.exports = initTealiumTracker;
module.exports.default = initTealiumTracker;
Likewise your dependent module can export the functions as
module.exports = { callUtag, flushUtagQueue };
This way, consumers should be able to use either
import initTealiumTracker2 from "tealium-tracker";
OR
const initTealiumTracker1 = require("tealium-tracker");