I have a custom NextJS webpack config where I call a script that gets executed at build-time in the Node runtime (not in the browser):
module.exports = {
webpack: (config, options) => {
if (options.isServer) {
require("./some-script.js")
}
return config
},
}
What the script does doesn't really matter, but I would like to import some code from my React application (that gets executed in the browser, except for SSR, but that's beside the point).
const fs = require("fs");
const getImages = require("../utils/img");
(() => {
// writes something to disk
})();
When I do this I get this error message: SyntaxError: Cannot use import statement outside a module. This makes sense because I use require for const getImages = require("../utils/img"); but this file is part of the React client-side codebase which uses ES6 import/export modules:
const getImages = () => {
// return something
};
export default getImages;
How can I achieve this? Meaning, how can I call client-side import/export code from a Node.js require script?
Try and convert the required files to use require and module.exports to import and export respectively
const getImages = () => {
// return something
};
module.exports = getImages;
Related
I'm operating a bot on Wikipedia using npm mwbot, and planning to migrate to npm mwn. This is because you need a "token" to edit pages on Wikipedia, and this can expire after a while so you need to prepare your own countermeasures against this if you use mwbot, but it seems like mwn handles this issue on its own.
When you use mwn, you first need to initialize a bot instance as documented on the turotial:
const bot = await mwn.init(myUserInfo);
Then your token is stored in the bot instance and you can for example edit a page using:
const result = await bot.edit('Page title', () => {text: 'text'});
So, basically you want to share the initialized bot instance across modules. I believe it'd be easiest to declare a global variable like so:
// bot.js (main script)
const {mwn} = require('mwn');
const {my} = require('./modules/my');
(async() => {
global.mw = await mwn.init(my.userinfo);
const {t} = require('./modules/test');
t();
})();
// modules/test.js
/* global mw */
exports.t = async () => {
const content = await mw.read('Main page');
console.log(content);
return content;
};
I'm currently using JavaScript, but will hopefully migrate to TypeScript (although I'm new to it) because I feel like it'd be useful in developing some of my modules. But I'm stuck with how I should use the initialized bot instance across modules in TypeScript.
-+-- dist (<= where ts files are compiled into)
+-- src
+-- types
+-- global.d.ts
+-- bot.ts
+-- my.ts
// bot.ts
import {mwn} from 'mwn';
import {my} from './my';
(async() => {
global.mw = await mwn.init(my.userinfo);
})();
// global.d.ts
import {mwn} from 'mwn';
declare global {
// eslint-disable-next-line no-var
var mw: mwn;
}
This doesn't work and returns "Element implicitly has an 'any' type because type 'typeof globalThis' has no index signature. (at mw in global.mw)".
This is probably a naive question but any help would be appreciated.
Edit:
Thanks #CertainPerformance, that's a simple and easy approach. Actually, I once tried the same kind of an approach:
export const init = async () => {
if (typeof mw === 'undefined') {
return Promise.resolve(mw);
} else {
return mwn.init(my.userinfo).then((res) => {
mw = res;
return mw;
});
}
}
But I was like "init().then() in every module?"... don't know why I didn't come up with just exporting the initialized mwn instance.
Anyway, is it like the entry point file should be a .js file? I've been trying with a .ts file and this is one thing that's been giving me a headache. I'm using ts-node or nodemon to auto-compile .ts files, but without "type: module", "Cannot use import statement outside a module" error occurs and with that included, "TypeError [ERR_UNKNOWN_FILE_EXTENSION]: Unknown file extension ".ts"" occurs. How do you tell a given file should be a .js or .ts file?
Edit2:
Just making a note: The error I mentioned above was caused by not having "module": "CommonJS" in my tsconfig.json, as I commented to CertainPerformance's answer below.
One of the main benefits of modules is the ability to drop dependencies on global variables. Rather than going back on that and assigning a global anyway, a better approach that happens to solve your problem as well would be to have a module that exports two functions:
One that initializes the asynchronous mwn
One that returns mwn when called
// mw.ts
import {mwn} from 'mwn';
import {my} from './my';
let mw: mwn;
export const init = async () => {
mw = await mwn.init(my.userinfo);
};
export const getMw = () => mw;
Then it can be consumed by other modules quite naturally, barely requiring any typing at all:
// modules/index.ts
// Entry point
import { init } from './mw';
import { test } from './test';
init()
.then(() => {
test();
})
// .catch(handleErrors);
// modules/test.ts
import { getMw } from './bot';
export const test = () => {
const mw = getMw();
// anything else that depends on mw
};
The above could be a reasonable approach if mw is used in many places across your app and you don't think that passing it around everywhere would be maintainable.
If you could pass it around everywhere, that would have even less of a code smell, though.
// initMw.ts
import {mwn} from 'mwn';
import {my} from './my';
export const initMw = () => mwn.init(my.userinfo);
// modules/index.ts
// Entry point
import { initMw } from './initMw';
import { test } from './test';
initMw()
.then((mw) => {
test(mw);
})
// .catch(handleErrors);
// modules/test.ts
import { mwn } from 'mwn';
export const test = (mw: mwn) => {
// anything that depends on mw
};
Initialize it once, then pass it down (synchronously) everywhere it's needed - that's the approach I'd prefer.
You could put the mwn type in a global d.ts file to avoid having to add import { mwn } from 'mwn'; to every module if you wanted. (Yes, it's somewhat of a global variable, but it's a type rather than a value, so it's arguably less of a problem.)
I'm trying to temporarily mock node-fetch in an ESM module while still retraining the original implementation so I can access a real endpoint's value. However, this errors with "Must use import to load ES Module." I recognize jest support for ESM is still pending - is there any way to have this behavior in a combination of current Node, ES6, and Jest?
worker.ts (dependency):
export default async () => {
const response = await fetch("http://example2.org");
return await response.json()
}
main.test.ts:
import { jest } from "#jest/globals";
jest.mock("node-fetch", () => {
return Promise.resolve({
json: () => Promise.resolve({ myItem: "abc" }),
})
})
import doWork from './worker.js';
import mockedFetch from 'node-fetch';
const originalFetch = jest.requireActual('node-fetch') as any;
test("Ensure mock", async () => {
const result = await doWork();
expect(result.myItem).toStrictEqual("abc");
expect(mockedFetch).toBeCalledTimes(1);
const response = await originalFetch("http://www.example.org");
expect(response.status).toBe(200);
const result2 = await doWork();
expect(result2.myItem).toStrictEqual("abc");
expect(mockedFetch).toBeCalledTimes(2);
});
First, Jest doesn't support jest.mock in ESM module for tests
Please note that we currently don't support jest.mock in a clean way
in ESM, but that is something we intend to add proper support for in
the future. Follow this issue for updates.
This is reasonable because import has different semantic than require. All imports are hoisted and will be evaluated at module load before any module code.
So in your case because you are using jest.mock I assume that your test code are transformed. In this case, if you want to use non "CommonJS" package, you should transform it too. You can change transformIgnorePatterns in jest config to []. It means that all packages from node_modules will go through transform. If it's too aggressive, you can pick specific modules which ignore like this "transformIgnorePatterns": [ "node_modules/(?!(node-fetch))" ] but don't forget about transitive dependencies. ;)
Add/change in jest config
"transformIgnorePatterns": []
jest.mock accept module factory which should returns exported code. In your case, you should write it like this.
jest.mock("node-fetch", () => {
return {
__esModule: true,
default: jest.fn(() =>
Promise.resolve({
json: () => Promise.resolve({ myItem: "abc" }),
})
),
};
});
If you have error
The module factory of jest.mock() is not allowed to reference any out-of-scope variables
just remove jest from import and use it as global variable or import it inside the module factory function
https://github.com/facebook/jest/issues/2567
Change const originalFetch = jest.requireActual('node-fetch') to
const originalFetch = jest.requireActual('node-fetch').default;
In our project we have the custom console object which is used as a logger.
So we need to replace the default console.
For this purpose we decided to use the vm module.
But we are facing the problem while using imports inside the code created with vm.Script:
application.js:
import path from 'path';
console.log(path.resolve('./apps'));
main.js:
const context = {
console,
global: {},
module: {},
exports: {},
require,
};
context.global = global;
const sandbox = vm.createContext(context);
const fileName = './application.js';
const src = await fsp.readFile(fileName, 'utf8');
const script = new vm.Script(`module.exports = () => {\n${src}\n};`);
const execute = script.runInNewContext(sandbox);
execute();
Output:
Error: Cannot find module 'path'
Question:
How to make imports work properly inside the application.js file?
Do not use a new vm.Script if you want to create a module. Use a new vm.Module instead! Notice that the API is still experimental though.
I have written a javascript file that provides configuration, and separately a proxy that lets me provide a fallback value if the property access was unsuccessfull
// stackUrlMap.js
export const stacks = {
'stackoverflow.com': 'StackOverflow',
'cooking.stackexchange.com': 'SeasonedAdvice',
}
// stackUrlConfig.js
import { stacks } from './stackUrlMap';
const getOrParse = parser => ({
get: (target, prop) => target?.[prop] ?? parser(prop);
})
const urlToName = url => url.split('.')[0];
export const stackUrlToNameMap = new Proxy(stacks, getOrParse(urlToName));
This was working perfectly, and I had a test written for it that passed.
import { expect } from 'chai';
import sinon from 'sinon';
import * as urlMap from './stackUrlMap';
describe('stackUrlConfig.js', function() {
let sandbox;
let stackUrlToNameMap = {};
beforeEach(async function() {
sandbox = sinon.createSandbox();
sandbox.stub(urlMap, 'stack').value({'madeup.url.com': 'Made Up Stack'});
// import here so we get the stubbed values passed through.
({ stackUrlToNameMap } = await import('./stackUrlConfig.js'))
});
it('can get an existing prop from the proxied map', function() {
const field = 'madeup.url.com';
// stub working correctly when accessed from test file
expect(urlMap.stacks[field]).to.exist();
// tests that if the proxied map can find the prop it uses that value
expect(stackUrlToNameMap[field]).to.equal('Made Up Stack');
});
})
However, now I've written code that uses stackUrlConfig.js elsewhere, that is imported by my test setup elsewhere, and the dynamically imported proxied map stackUrlToNameMap in the beforeEach of my test is not being 'respected', and the real stacks object is being used for the lookup, meaning I get this AssertionError:
AssertionError: expected 'madeup' to equal 'Made Up Stack'
If I put a console.trace in the stackUrlConfig.js, I can see it's first called before my test runs as it's imported into another file that isn't run by mocha yet.
I don't want to have to use decache (I'm not even sure it would work, I'm not using require) as seen in Re-importing modules between mocha tests if I can avoid it.
I took the code and tried to add a cache busting, like so:
const absolutePath = path.resolve(__dirname, './stackUrlConfig.js');
({ stackUrlToNameMap } = await import(`${absolutePath}?update=${Date.now()}`));
But my mocha setup did not find the resulting module.
I can't control whether this file is imported elsewhere first in my test, so can I reset mocha's reference to the module somehow?
I am currently working on technical debt identified by SonarQube for a Node.js application. My application allows on-the-fly switching between a live and mock datasource. To achieve this I destroy the previous "require" from cache and re-require it. When running SonarQube it does not like "require" statements. It does suggest "import" statements. However that may not be suitable in this case.
Simplified version of existing code:
var config = require('../config');
var polService = require(config.polService);
var root = require('../root');
function doingStuff(liveOrMock) {
setEnvironment(liveOrMock);
delete require.cache[require.resolve(root.path + ‘/config’)];
config = require('../config');
polService = require(config.polService);
}
The setEnvironment function sets process.env.NODE_ENV = liveOrMock, which is used in config.js. We export the config module using module.exports = localOptions[process.env.NODE_ENV]; This code picks a single key-pair from a JSON. The value that comes back is used to choose which module is being used for a restService.
Being able to change what module is being used is for polService is the purpose of the code.
Change your config module to export a function, and then call this function whenever you need to change environment.
In order to make polService a dynamic module, you can use dynamic import(). import() is not supported natively, but you can use this Babel plugin (it works with webpack) to transpile it.
config.js:
export default () => {
// ...
return localOptions[process.env.NODE_ENV];
}
Main module:
import getConfig from '../config';
let config = getConfig();
function doingStuff(liveOrMock) {
setEnvironment(liveOrMock);
config = getConfig();
return import(config.polService).then(result => {
polService = result;
});
}
Keep in mind that now the doingStuff function is asynchronous (i.e. returns a promise), so you can't just call it and access polService immediately. You have to wait for it by either using the then() method, or using await in an async function.
If you have a limited number of polService modules, it might be a better option to import all of them beforehand, and in the doingStuff function just switch which one the polService variable refers to.
import getConfig from '../config';
import polService1 from '../polService1';
import polService2 from '../polService2';
import polService3 from '../polService3';
const polServices = { polService1, polService2, polService3 };
let config = getConfig();
let polService = polService1;
function doingStuff(liveOrMock) {
setEnvironment(liveOrMock);
config = getConfig();
polService = polServices[config.polService];
}