I have built a library that I want to use in a Next.JS project. Within this library a certain dependency is using an import via a string passed into a require statement within the source code where the import is taking place. This is causing webpack to not recognize the import. I don't want to change code within any node_modules as this is not a preferred approach but how can I ensure that my project using the library I built is able to compile and run?
Within file_using_string_passed_into_require_to_get_import.js:
let importName = "./potential_import_A.js"
if(condition){
importName = "./potential_import_B.js"
}
module.exports = require(importName)
This is the folder structure:
Project/
| node_modules
| my-library
| node_modules
| library-dependency
| file_using_string_passed_into_require_to_get_import.js
| potential_import_A.js
| potential_import_B.js
To create a local (unpublished) library package
Create a 'my-library' folder (outside your current project dir).
Do npm init (Folder must include the 'package.json' )
Include source code (potential_import_A), exporting any desired functions.
In the actual project folder:
cd into the folder of the project that needs to use your library.
Run npm install --save local/path/to/my-library.
The --save will add the package to your dependencies in the project's package.json file, as it does with 3rd party published packages. It will also add a copy of the source code to the node modules folder of the project, as always.
Importing your new library:
import/require the package as you would normally, from any project.
For example
import { myFunction } from "my-library"
I got it to work by excluding node_modules from the webpack build. Since I am using Next.JS this is within my next.config.js
const nodeExternals = require('webpack-node-externals');
module.exports = {
webpack: (
config,
{
buildId, dev, isServer, defaultLoaders, nextRuntime, webpack,
},
) => {
if (isServer) {
config.target = 'node';
config.node = {
__dirname: true,
global: true,
__filename: true,
};
config.externals = [nodeExternals()], // in order to ignore all modules in node_modules folder
config.externalsPresets = {
node: true, // in order to ignore built-in modules like path, fs, etc.
};
}
return config;
},
};
Related
I am using pnpm as the package manager with multiple workspaces in my project.
Every workspace in my project has a src dir for sourcing code and a lib dir for compiled output.
For example:
// workspace #x/a
package.json
lib
index.js
sub
index.js
other.js
I want to make this workspace can be required (or imported) in the following ways:
require( '#x/a' ) // lib/index.js
require( '#x/a/sub' ) // lib/sub/index.js
require( '#x/a/sub/other' ) // lib/sub/other.js
In a word, I want to make the workspace to be required like it has no the lib dir.
I tried using exports config in package.json:
{
"exports" : {
"." : "./lib/index.js",
"./*" : "./lib/*",
"./*" : "./lib/*.js"
}
}
But there are some issues while using the config:
With the "./*" : "./lib/*", nodejs module loader doesn't try adding .js extension so that #x/a/sub/other cannot be resolved.
With "./*" : "./lib/*" nodejs moudle loader doesn't try resolving folders as modules, it causes that #x/a/sub can't be resolved.
I know I can add "./sub" : "./lib/sub/index.js" to fix the problem, but I really don't want to add all my folders into the configuration, there are too many folders and there are nested.
So i just started learning about Test Driven Developement and as an example i was asked to run the command npm test helloWorld.spec.js in the terminal but i got this error :
> javascript-exercises#1.0.0 test
> jest "helloWorld.spec.js"
'jest' n’est pas reconnu en tant que commande interne
ou externe, un programme exécutable ou un fichier de commandes.
// in english jest isn't recognized as an internal command or external
I'm working on windows and the only thing i have installed is node so what do i have to do?
Choose one of the following methods
1) Install globally
You need to install jest globally:
npm install jest -g
Note: You will have to call it as jest something.spec.js in your cli or specify a test command in your package.json.
2) Install locally
Install jest locally with npm install jest -D.
You can use a script in your package.json called test which would be "test": "jest".
If any of the above don't work, try reinstalling jest.
If it still doesn't work, try removing node_modules and npm cache clean --force and npm install
3) Config file
If you already have jest installed but it's not working, you can use a config file to track files based on regex pattern (you can do a lot more if you check out the docs).
The following part is from the docs:
Jest's configuration can be defined in the package.json file of your project, or through a jest.config.js, or jest.config.ts file or through the --config <path/to/file.js|ts|cjs|mjs|json> option. If you'd like to use your package.json to store Jest's config, the "jest" key should be used on the top level so Jest will know how to find your settings:
{
"name": "my-project",
"jest": {
"verbose": true
}
}
Or through JavaScript:
// Sync object
/** #type {import('#jest/types').Config.InitialOptions} */
const config = {
verbose: true,
};
module.exports = config;
// Or async function
module.exports = async () => {
return {
verbose: true,
};
};
Or through TypeScript (if ts-node is installed):
import type {Config} from '#jest/types';
// Sync object
const config: Config.InitialOptions = {
verbose: true,
};
export default config;
// Or async function
export default async (): Promise<Config.InitialOptions> => {
return {
verbose: true,
};
};
When using the --config option, the JSON file must not contain a "jest" key:
{
"bail": 1,
"verbose": true
}
Regex options
testMatch [array]
(default: [ "**/__tests__/**/*.[jt]s?(x)", "**/?(*.)+(spec|test).[jt]s?(x)" ])
The glob patterns Jest uses to detect test files. By default it looks for .js, .jsx, .ts and .tsx files inside of __tests__ folders, as well as any files with a suffix of .test or .spec (e.g. Component.test.js or Component.spec.js). It will also find files called test.js or spec.js.
Note: Each glob pattern is applied in the order they are specified in the config. (For example ["!**/__fixtures__/**", "**/__tests__/**/*.js"] will not exclude __fixtures__ because the negation is overwritten with the second pattern. In order to make the negated glob work in this example it has to come after **/__tests__/**/*.js.)
testRegex [string | array]
Default: (/__tests__/.*|(\\.|/)(test|spec))\\.[jt]sx?$
The pattern or patterns Jest uses to detect test files. By default it looks for .js, .jsx, .ts and .tsx files inside of \_\_tests\_\_ folders, as well as any files with a suffix of .test or .spec (e.g. Component.test.js or Component.spec.js). It will also find files called test.js or spec.js. See also testMatch [array], but note that you cannot specify both options.
I have just started using grunt and I want it to use combine all files and uglify them.
But my issues is that it combines and uglifys, but it doesn't remove import statements. (I'm using uglify and concat)
What I want -
// File.js
import something from './something.js';
something.someFunction("hello world");
and
// something.js
export default {
someFunction: function(msg){console.log(msg)}
}
to
// all.js
var something = {
someFunction: function(msg){
console.log(msg)
}
}
something.someFunction("hello world");
Compressing is not an issue.
If you want to combine the source code into one file, you can use rollup.js to help you.
download Node.js to get npm
update npm: npm install -g npm
npm install -g rollup
check: rollup -v
And then, running the below command will work as you expected.
rollup --format es --input file.js -o all.js
You can generate by rollup.config.js also.
rollup -c
// rollup.config.js
const AUTHOR = ""
const OutputFileName = `bundle` // in your case is `all`.js
const banner = `// Copyright (c) ..., all right reserved.`
const footer = `// powered by ${AUTHOR}`
export default {
input: './index.js', // in your case is `File.js`
output: [
{
file: `./${OutputFileName}.js`,
format: 'es', // amd, umd, iife, cjs, ...
// 👇 Option
// banner,
// footer
},
{ // You can generate different formats at once.
file: `./${OutputFileName}_cjs.js`,
format: 'cjs',
banner,
footer,
},
]
}
For compress
get uglifyjs: npm install uglify-js -g
uglifyjs all.js -m -c -o all.min.js
I'm trying to use the react-scratchblocks package on my react project.
I've created my project using the create-app-react command.
After importing the package I got the following error:
Failed to compile.
./node_modules/react-scratchblocks/src/Scratchblocks.js
SyntaxError: /Users/jorge/Documents/React/elimu-analyzer-frontend/node_modules/react-scratchblocks/src/Scratchblocks.js: Unexpected token (45:6)
43 | const scripts = this.parseScripts(this.props.code);
44 | return (
> 45 | <div className={this.classNames()}>
| ^
46 | {scripts.map((script,i) => (
47 | <Scratchblock key={i} className="script" script={script}/>
48 | ))}
I know that the jsx it's not been recognized, but what should I do to make this package work? Remember: i've used the create-rect-app to create my React project.
Thanks.
UPDATE 1:
module.exports = function () {
return {
overrides: [{
test: ["./node_modules/react-scratchblocks"],
presets: ["#babel/preset-react"]
}],
};
}
UPDATE 2:
Component that where I import the react-scratchblocks.
import React, { useState } from 'react';
import { withRouter } from 'react-router-dom';
import './styles.css';
import Fire from '../../config/Fire';
import Realtime from '../Realtime';
import Scratchblocks from 'react-scratchblocks'
function Content(props) {
const [menuOption, setMenuOption] = useState(1);
async function logout() {
await Fire.logout();
props.history.push('/');
console.log('oi');
}
if (menuOption === 0) {
return (
<div class='content'>
<Realtime />
</div>
);
}
else if (menuOption === 1) {
return (
<div class="content">
<button onClick={logout}> OUTRA OPÇÃO </button>
</div>
);
}
}
export default withRouter(Content);
Create React App (CRA) only transpiles standard JavaScript syntax inside node_modules.
This does not include JSX compilation. Package react-scratchblocks errors due to untranspiled JSX:
SyntaxError: .../Scratchblocks.js: Unexpected token (45:6)
Statement from maintainers (link):
We only compile valid JavaScript syntax in node_modules. JSX is not valid JavaScript. You are using JSX there.
The reason we've taken this stance is because compiling non-standard syntax tightly couples libraries to build tools.
It's also hard to draw a line once you allow experimental things. Most people will want to use not just JSX, but also experimental transforms like class properties. Or decorators. Now we have to argue with every library maintainer about which transforms we want to support, and which we don't.
Hence package authors would have needed to transpile JSX them selves before distribution.
To transpile JSX manually1 you can apply the Babel React preset to react-scratchblocks inside node_modules:
babel node_modules/react-scratchblocks \
-d node_modules/react-scratchblocks/dist \
--presets=#babel/preset-react
The build step might be outsourced into its own config file (transpile.js):
module.exports = {
...
overrides: [
{
test: ["./node_modules/react-scratchblocks"],
presets: ["#babel/preset-react"]
}
]
};
babel node_modules/react-scratchblocks \
-d node_modules/react-scratchblocks/dist \
--config-file ./transpile.js
Then adjust main entry inside node_modules/react-scratchblocks/package.json to point to the previously transpiled version in dist:
"main": "dist/index.js",
patch-package can further automate the process of fixing broken packages.
Remember this is only a workaround - the duty is on package developers to distribute an npm package with standard JavaScript features.
1 A different alternative would be to adjust Webpack config (only possible with ejected CRA).
I personally suggest you to use craco (see #craco/craco)
Craco is a powerful tool that allows you to edit built-in create-react-app configuration without forcing you to eject the project.
How to install it
run npm install #craco/craco --save-dev
run npm install craco-babel-loader --save-dev
create craco.config.js in the root folder of the project
update the scripts in package.json:
react-scripts start -> craco start
react-scripts build -> craco build
...
this is the content of the configuration craco file
const path = require('path')
const fs = require('fs')
const cracoBabelLoader = require('craco-babel-loader')
// manage relative paths to packages
const appDirectory = fs.realpathSync(process.cwd())
const resolvePackage = relativePath => path.resolve(appDirectory, relativePath)
module.exports = {
plugins: [
{
plugin: cracoBabelLoader,
options: {
includes: [
resolvePackage('node_modules/package-to-transpile'),
resolvePackage('node_modules/another-package-to-transpile'),
],
},
},
],
}
You can change a lot of other configurations, and I suggest you to give a look at craco npm package page
Credtis: https://stackoverflow.com/a/58603207/4277948
I have an application where locally (without pm2) all the environment variables in the .env file work just fine using dotenv.
But on the server where I'm using pm2 to run the app, the environment variables remain undefined.
The pm2 commands I'm using to run the app on server are:
pm2 start myapp/app.js
pm2 startup
pm2 save
dotenv will read .env file located in the current directory.
When you call pm2 start myapp/app.js it won't search for myapp/.env.
.env // It will try to load this, which doesn't exist
myapp/
app.js
So you have two solutions
use path option:
const path = require('path');
require('dotenv').config({ path: path.join(__dirname, '.env') });
Or call your script from inside myapp/
pm2 start app.js
A good pattern here is to remove dotenv from your code and "require" it on the command line. This makes your code nicely transportable between any environment (including cloud-based) - which is one of the main features of environment variables.
Note: you will still need to install dotenv in your project via npm when running it on a server.
a) code up your .env file alongside your script (e.g. app.js)
b) to run your script without pm2:
node -r dotenv/config app.js
c) in pm2.config.js:
module.exports = {
apps : [{
name : 'My Application',
script : 'app.js',
node_args : '-r dotenv/config',
...
}],
}
and then
pm2 start pm2.config.js
note: the use of dotenv/config on the command line is one of the best practices recommended by dotenv themselves
edit 2021: for completeness - as my answer has got some ticks, I wanted to add a 4th option to the list:
d) combined pm2/env config
module.exports = { apps : [{
name : 'My Application',
script : 'app.js',
env : {
PORT: 5010,
DB_STRING: 'mongodb://localhost:27017',
...
},
}]};
This will be useful if you are treating your pm2.config as environmental configuration and outside of git etc. It just negates the need for a separate .env, which may suit you. It negates the need for dotenv completely as pm2 injects the env variables into your script's process
you have kill you pm2 process first
try
pm2 kill
then restart pm2 using
pm2 start app.js
I had the same problem but it wasnt explained clearly so here is the solution based on
github user vmarchaud comment.
This also fixes the issue people had with #Andy Lorenz solution.
In my case i wanted to create an ecosystem file for multiple apps but i was keep getting
Error: Cannot find module 'dotenv/config'
The solution was easy.
You have to declar cwd, aka the project folder where the dotenv/config will be read from.
module.exports = {
apps: [{
name: 'app1 name',
script: 'app1.js',
cwd: '/path/to/folder/',
exec_mode: 'fork_mode',
node_args: '-r dotenv/config',
}, {
name: 'app2 name',
script: 'app2.js',
cwd: '/path/to/folder/',
instances: 'max',
exec_mode: 'cluster',
node_args: '-r dotenv/config',
}],
};
You can parse .env using dotenv lib end set them manually in ecosystem.config.js
ecosystem.config.js:
const { calcPath, getEnvVariables } = require('./helpers');
module.exports = {
apps: [
{
script: calcPath('../dist/app.js'),
name: 'dev',
env: getEnvVariables(),
},
],
};
helpers.js:
const path = require('path');
const dotenv = require('dotenv');
const fs = require('fs');
function calcPath(relativePath) {
return path.join(__dirname, relativePath);
}
// this function will parce `.env` file but not set them to `process.env`
const getEnvVariables = () => {
const envConfig = dotenv.parse(fs.readFileSync(calcPath('.env')));
const requiredEnvVariables = ['MODE'];
for (envVariable of requiredEnvVariables) {
if (!envConfig[envVariable]) {
throw new Error(`Environment variable "${envVariable}" is not set`);
}
}
return envConfig;
};
None of this worked for me because I was using cluster mode.
I installed dotenv as dev dependency at the root (I was using yarn workspaces too).
Then I did this:
require('dotenv').config({ path: 'path/to/your/.env' })
module.exports = {
apps: [
{
name: 'app',
script: 'server/dist/index.js',
instances: 2,
exec_mode: 'cluster',
instance_var: 'APP_INSTANCE_SEQ',
// listen_timeout: 10000,
// restart_delay: 10000,
}
]
}
I use a much simpler version of #Marcos answer:
.env
app.js
for example we need to store token in .env file and pass it right to app.js:
inside .env
token=value
inside app.js:
require('dotenv').config();
console.log(process.env.token)
Also, don't forget. If you add .env file to .gitignore and then git pull you repo on VPS or smth, you need to copy .env file manually, otherwise your app won't work.
And in some cases it's important in what area you are using your config, so make sure that NODE_ENV=production string is added to your .env file.
After all you could use pm2 start app.js right from your app's folder.
This was my project setup..
/src/app.ts
which than compiled into dist folder.
/dist/app.js
my .env file was outside dist folder so it wasn't accessible.
this is the command i tried.
pm2 start app.js --env=.env