How can i use pm2 in combination with a package based on ES Module (type:"module")
I looked into similar Questions without any useful help (some say it does not work on windows, but i am using linux)
I always receive the error:
Error [ERR_REQUIRE_ESM]: require() of ES Module /opt/app/server/lib/src/index.js not supported.
0|any| Instead change the require of index.js in null to a dynamic import() which is available in all CommonJS modules.
My ecosystem.config.js looks like:
const os = require('os');
module.exports = {
apps: [{
port : 3000,
name : "any",
script : "lib/src/index.js",
watch : true,
instances : os.cpus().length,
exec_mode : 'fork',
env: {
NODE_ENV: "production",
}
}]
}
index.js is a ES module using "import" syntax. How can i tell pm2 that is should use this way of importing
To achieve this you can create an intermediary CommonJS module which loads your application from ESModule. It's possible with import function available in commonJs modules.
This is how might this look:
ecosystem.config.js
lib/src/index.cjs CommonJS entry point (for PM2).
lib/src/index.js ESModule entry point (for ESM-compatible tools).
lib/src/app.js Application code.
ecosystem.config.js:
const os = require('os');
module.exports = {
apps: [{
port : 3000,
name : "any",
script : "lib/src/index.cjs", // 👈 CommonJS
watch : true,
instances : os.cpus().length,
exec_mode : 'fork',
env: {
NODE_ENV: "production",
}
}]
}
lib/src/index.js:
import {app} from './app.js'
app()
lib/src/index.cjs:
import('./app.js') // 👈 There is import function available in CommonJS
.then(({app}) => {
app()
})
lib/src/app.js:
import {hello} from './greet.js'
export function app() {
console.log(hello('World'))
}
lib/src/greet.js:
export function hello(name) {
return `Hello, ${name}!`
}
renaming ecosystem.config.js to ecosystem.config.cjs worked for me
Related
I am trying to bundle my Node.js server-side code (Koa app) into a single file which is what Webpack produces when I use the target as node. This is what ncc - node.js compiler collection also achieves.
I am now using Vite for my next project. I am able to bundle the code by using the SSR bundling feature provided by Vite. But I am not able to bundle third-party dependencies into this single file excluding core/built-in node.js modules. This is my Vite build script:
import path from 'path';
import { build } from 'vite';
import builtinModules from 'builtin-modules';
async function main() {
const result = await build({
mode: 'production',
appType: 'custom',
root: path.join(process.cwd(), 'backend'),
ssr: {
format: 'esm',
target: 'node',
// By default Vite bundles everything except the one we pass via `external` array.
external: builtinModules
},
build: {
manifest: false,
rollupOptions: {
input: 'backend/main.mjs',
output: {
assetFileNames: 'bundle.js'
}
},
outDir: '../dist/backend',
ssr: true,
emptyOutDir: true
},
plugins: [],
});
}
main();
In the above code, builtinModules is simply the string array of all the core node.js modules:
// builtinModules
[
'assert', 'async_hooks',
'buffer', 'child_process',
'cluster', 'console',
'constants', 'crypto',
'dgram', 'diagnostics_channel',
'dns', 'domain',
'events', 'fs',
'http', 'http2',
'https', 'inspector',
'module', 'net',
'os', 'path',
'perf_hooks', 'process',
'punycode', 'querystring',
'readline', 'repl',
'stream', 'string_decoder',
'timers', 'tls',
'trace_events', 'tty',
'url', 'util',
'v8', 'vm',
'worker_threads', 'zlib'
]
For my original source code:
// main.mjs
import path from 'path';
import Koa from 'koa';
async function main() {
console.log('Source folder', path.join(__dirname, 'src'));
const app = new Koa();
app.listen(3000);
}
main();
The produced output for the above Vite build configuration is:
// bundle.js
import path from "path";
import Koa from "koa";
async function main() {
console.log("Source folder", path.join(__dirname, "src"));
const app = new Koa();
app.listen(3e3);
}
main();
Ideally, I expected that it would bundle the koa package in the final output leaving behind only native path module. But that's not the case.
So, how do I enable bundling of my entire backend/Node.js source code compile into single file leaving only core node.js modules as external as Node.js is the runtime for my code? The vite server options also provide the noexternal option but setting it to the true works only for webworker like runtime where none of the built-in node modules are available.
Latest vite versions are using preloading you should tweak some rollup options to disable code splitting
import { defineConfig } from 'vite' // 2.8.0
import react from '#vitejs/plugin-react' // 1.0.7
export default defineConfig ({
plugins: [react()],
build: {
rollupOptions: {
output: {
manualChunks: {}
},
},
},
})
I am trying to use Wallaby in conjunction with the dotenv-flow package. I currently have my wallaby.js config file setup like below:
require("dotenv-flow").config()
module.exports = function (wallaby) {
return {
files: [
'api/*',
'controllers/*',
'config/*',
'firebase/*',
'helpers/*',
'models/*',
'services/*',
'smtp/*',
'sockets/*'
],
tests: [
"test/**/*.test.mjs"
],
testFramework: "mocha",
env: {
type: "node",
params: {
env: "NODE_ENV=test"
}
}
};
};
I've tried a few other ways of writing the file including in esm module format. However, my tests run and my sequelize code complains that it wasn't passed environment variables to use for connecting to the development DB.
You are loading your .env file but then never using it's contents. Another problem is that wallaby doesn't understand the dotenv output so you have to massage it a little bit.
const environment = Object.entries(
require("dotenv-flow").config()['parsed']).
map( x => `${x[0]}=${x[1]}`).join(';'),
Then change your environment to something like this
env: {
runner: 'node',
params: {
env: environment
}
}
I'm trying to set up an angular project to work with jest. So far it works with the following config
// jest.config.js
module.exports = {
// other stuff
preset: "jest-preset-angular",
setupFilesAfterEnv: ["<rootDir>/setup-jest.ts"],
globalSetup: "jest-preset-angular/global-setup",
globals: {
"ts-jest": {
tsconfig: "<rootDir>/tsconfig.spec.json",
stringifyContentPathRegex: "\\.html$",
useESM: true,
},
},
};
And the following in my setup-jest.ts
import 'jest-preset-angular/setup-jest';
import './jest-global-mocks';
Problem is that as soon as I introduce any code that brings in an ES module, it blows up (even though I've tried following the ESM support page on the jest-preset-angular docs). So far, I've just dealt with this by finding alternative packages. But when I try to turn on --coverage, I get this.
import { __decorate } from "tslib";
^^^^^^
SyntaxError: Cannot use import statement outside a module
1 | import { TestBed } from '#angular/core/testing';
> 2 | import { AppModule } from './app.module';
| ^
3 | import { APP_BASE_HREF } from '#angular/common';
4 |
5 | describe('The app module', () => {
I've tried dissecting jest-preset-angular and extracting the settings that seem like they should apply to projects using ES6, which has led me to making these changes:
// jest.config.js
module.exports = {
// other stuff
setupFilesAfterEnv: ["<rootDir>/setup-jest.ts"],
transformIgnorePatterns: ["/node_modules/(?!tslib)"],
transform: {
"^.+\\.(ts|html|svg)$": "jest-preset-angular",
},
extensionsToTreatAsEsm: [".ts"],
globals: {
"ts-jest": {
tsconfig: "<rootDir>/tsconfig.spec.json",
stringifyContentPathRegex: "\\.html$",
useESM: true,
},
},
};
And now its failing on the core angular mjs files
/Users/benwainwright1/repos/ng-budget/node_modules/#testing-library/angular/fesm2020/testing-library-angular.mjs:1
({"Object.<anonymous>":function(module,exports,require,__dirname,__filename,jest){import * as i0 from '#angular/core';
^^^^^^
SyntaxError: Cannot use import statement outside a module
This is even more confusing for me because as far as I can see
the transform key doesn't specify .mjs, so .mjs files should be left alone
I'm using node v16 and jest v28, which should automatically treat .mjs files as modules
The problem
My project consists of a yarn monorepo in which, among various packages, there is a NextJS app and a configuration package (which is also shared with the server and a react-native application). In the configuration module what I do is to export the production keys if the environment is production, while if the project is under development, the development ones.
import { merge } from "lodash";
import { IConfig, RecursivePartial } from "./interfaces";
import { defaultKeys } from "./default.keys";
import { existsSync } from "fs";
const secretKeys: RecursivePartial<IConfig> = (function (env) {
switch (env) {
case "production":
return require("./production.keys").productionKeys;
default:
try {
if (!existsSync("./development.keys")) {
return require("./production.keys").productionKeys;
} else {
return require("./development.keys").developmentKeys;
}
} catch (e) {
}
}
})(process.env.NODE_ENV);
export const keys = merge(defaultKeys, secretKeys) as IConfig;
Of course, the development configuration file is included in the .gitignore file and therefore does not exist during production.
This has always worked perfectly (with the server and the mobile app), and indeed, there was never the need to check with the fs module if the development.keys file exists (check which I added later).
However, I recently added a NextJs application to the project. I encountered no problems during development, however when I tried to deploy the app on Heroku I encountered this error:
ModuleNotFoundError: Module not found: Error: Can't resolve './development.keys' in '/tmp/build_43d652bc/packages/config/dist/keys'
What I did
Initially, I thought it was a problem with the environment variables and that in production the development file was wrongly required.
However, I later realized that this was not the problem. And indeed, even placing the import of the configuration file for development in any place of the code, even following a return, the error continues to occur.
import { merge } from "lodash";
import { IConfig, RecursivePartial } from "./interfaces";
import { defaultKeys } from "./default.keys";
import { existsSync } from "fs";
const secretKeys: RecursivePartial<IConfig> = (function (env) {
switch (env) {
case "production":
return require("./production.keys").productionKeys;
default:
try {
if (!existsSync("./development.keys")) {
return require("./production.keys").productionKeys; // <-------
} else {
return require("./production.keys").productionKeys; // <-------
}
require("./development.keys").developmentKeys; // <------- This line should not be executed
} catch (e) {
return require("./production.keys").productionKeys;
}
}
})(process.env.NODE_ENV);
export const keys = merge(defaultKeys, secretKeys) as IConfig;
It is as if during the build, nextjs (or probably webpack) controls all the imports, without following the "flow" of the code.
I hope someone shows me where I am wrong because at the moment I am stuck. Thank you!
Update
Thanks to the ideas of this discussion I changed my file which now looks like this:
const getConfigPath = (env?: string): string => {
console.log(env);
if (env === "production") return "production.keys";
else return "development.keys";
};
const secretKeys: RecursivePartial<IConfig> = require("./" +
getConfigPath(process.env.NODE_ENV)).keys;
export const keys = merge(defaultKeys, secretKeys) as IConfig;
However, now I'm running into another webpack error:
Module parse failed: Unexpected token (2:7)
You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file. See https://webpack.js.org/concepts#loaders
| import { IConfig, RecursivePartial } from "../interfaces";
> export declare const keys: RecursivePartial<IConfig>;
It is as if webpack does not recognize declaration files generated by typescript. However, the error is new and I have never encountered it. I believe it's due to the behavior of webpack pointed out in the linked discussion.
I trust in some help, since I know little about webpack
Edit
This is my next.config.js:
const path = require("path");
module.exports = {
distDir: '../../.next',
sassOptions: {
includePaths: [path.join(__dirname, 'styles')],
prependData: `#import "variables.module.scss";`
},
webpack: (config, { isServer }) => {
if (!isServer) {
config.resolve.fallback.fs = false;
}
return config;
},
};
Basically they are the defult configurations. The only thing I have changed is the path for the build, I have made a sass file globle and I have momentarily added a piece of code to allow the use of the fs module, which however as you can see above I do not use it more. So I could take this configuration out.
You need the loader for typescript configured in your next.config.js
npm install || yarn add awesome-typescript-loader
module.exports = {
distDir: '../../.next',
sassOptions: {
includePaths: [path.join(__dirname, 'styles')],
prependData: `#import "variables.module.scss";`
},
webpack: (config, options) => {
const { dir, defaultLoaders, isServer } = options;
if (!isServer) {
config.resolve.fallback.fs = false;
}
config.pageExtensions.push(".ts", ".tsx");
config.resolve.extensions.push(".ts", ".tsx");
config.module.rules.push({
test: /\.tsx?$/,
include: [dir],
exclude: /node_modules/,
use: [
defaultLoaders.babel,
{
loader: "awesome-typescript-loader",,
},
],
});
return config;
}
}
In an Express.js app, I'm using Babel to precompile to commonjs before starting it. The compilation step looks like this:
babel ./src --out-dir dist
node ./dist/bin
As part of the project I have a file called my-worker.js where I use import syntax:
# my-worker.js
import { parentPort, workerData } from 'worker_threads'
import axios from 'axios'
...
And that is used by other-file.js:
#other-file.js
...
const worker = new Worker(__dirname + '/my-worker.js', { workerData: ... })
...
This works fine. Babel converts all the files to commonjs, and loading the worker script works.
BUT
When I use #babel/node, this doesn't work:
babel-node ./src/bin
I get the warning:
(node:4865) Warning: To load an ES module, set "type": "module" in the package.json or use the .mjs extension.
Along with the error:
Cannot use import statement outside a module
I don't want to use "type": "module", since then I have to explicitly name file extensions, and also I'm not sure that import X, { y } from ... syntax is supported (which I like).
If I change my worker file to be my-worker.mjs, and change the new Worker statement accordingly, then that works with #babel/node, but not with my production build since filenames are changed back to .js.
How can I get #babel/node to load and cache (I guess this is what it needs to do?) files loaded by a Worker? Why does this work with #babel and not with #babel/node?
My .babelrc file looks like this:
{
"presets": [
[
"#babel/preset-env",
{
"useBuiltIns": "usage",
"corejs": 3,
"targets": {
"node": "13"
},
"modules": "commonjs"
}
]
]
}
The #babel/register API can help dynamically transpile a script source, as pointed out in https://github.com/babel/babel/issues/10972#issuecomment-572608142
You can use this approach with eval mode to make a single-file script. This might be useful if you use babel-node to run a command-line utility script.
import { isMainThread, Worker, workerData } from "worker_threads";
function createTranspiledWorker(filename, options) {
const transpile = `
require('#babel/register');
require(${JSON.stringify(filename)});
`;
return new Worker(transpile, { ...options, eval: true });
}
async function main() {
const w = createTranspiledWorker(__filename, { workerData: { hello: "world" } });
const exit = new Promise(resolve => w.on("exit", resolve));
await exit;
}
function worker() {
console.log("worker", workerData);
}
if (isMainThread) {
main();
} else {
worker();
}