How to load a variable form my app into package.json? - javascript

I know this sounds as easy as using globals, but I'm not so sure.
Here's my case:
I have multiple files within /src directory of my React app, let's call them src/a.js, src/b.js,
every single of these files exports one object which I then use within my app:
./src/a.js:
export default {
filename: 'a',
foo: 'bar',
};
./src/b.js:
export default {
filename: 'b',
foo: 'bar',
blah: 'hah',
};
Now I have a command to check whether or not structure of objects within these files match (they are being changed by many developers multiple times a day), so when I do npm check in terminal it will return false for above input, because blah does not exist within two files.
My package.json looks like this:
"scripts": {
"check": "node check.js runCheck",
/.../
}
My question is: how the heck do I load these variables to compare them in package.json?
I have a file called:
./check.js:
function check(files) {
// checking files there
};
module.exports.check = check;
Approach #1 - imports
This is a build file, not part of the application itself, so when I try to do:
./check.js:
import a from './src/a';
import b from './src/b';
I'm getting:
SyntaxError: Cannot use import statement outside a module.
Approach #2 - require
This is going to cause trouble, because I'm using imports, not modules within my app, therefore doing:
./check.js:
const a = require('./src/a');
const b = require('./src/b');
Returns:
Error: Cannot find module './src/a'.
Of course I can't do module.exports within the a.js/b.js files, because they're part of my app and they should use exports, I've tried using both export and module.exports, but it does not work as well and looks shitty.
How do I tackle this? Should I load the files using some file loader, parse it as JSON an then compare? Or maybe there's an easier way?

You'll need to use something like esm (https://github.com/standard-things/esm) to run node with module support.
It should be as simple as:
npm install esm
Then update your package script to be:
"check": "node -r esm check.js runCheck",
Edit Btw, a very clear and well structured question.

Related

jest: get runtime config object or file

I was wondering if it's possible to get the jest runtime config object or filepath.
My use case would be to use various runtime config properties on my custom matchers
// ./jest.config.js
const path = require("path");
module.exports = {
prop1: "foo",
prop2: "bar"
};
// my-custom-matcher.js
expect.extend({
matcherName(received: any, pathToFile: string) {
const relativeDir = path.join(runtimeconfigpath, pathToFile); // i need the path to the runtimeconfigpath being used here
const baz = runtimeconfig.foo // or access the config properties like this
}
});
I tried various stuff and dug around the docs but i dont see this anywhere.
The nearest possible alternative I can think of would be to use config globals but that would complicate stuff if 'preset' and config extensions come into play.
Any help would be appreciated.
Have you tried to simply import jest.config.js? Or is there a specific reason not to import it? At least it is loaded by jest itself, so you should be fine by just importing it.
As js code is only compiled once and the reused of other files also need it
Otherwise you could look into 5 Advanced npm package.json configuration tips and see if it helps.
Or crate a recursive function that searches for the file. Maybe there is even some way to instantly get the path to package.json and then you can use this to continue from there (I did not find it after a short search)

Node.js require a file relative to the root of the package

In Node.js, is there any way to require file from the same package without using relative paths? For example, here's a snippet of code from ESLint.
const rule = require("../../../lib/rules/accessor-pairs"),
{ RuleTester } = require("../../../lib/rule-tester");
The fact that we have to walk all the way up the tree ../../../ to get to the root is not only annoying. It's also brittle, because I can't move the code without updating all of dependency references.
Yet somehow Node.js developers seem to have lived with it the past 10 years. I can't find anything in the docs or Stack Overflow that solves this problem other than a third-party dependency called require-self. Nor have I been able to find a definitive statement that using relative paths is the only non-hacky way for a file to require another file in the same module.
If there's a way to specify a path relative to the package root in ECMAScript Modules (ESM) but not CommonJS (CJS), or vice versa, I would like to know that as well.
To be clear, I don't think there is a solution to the problem. If there is great. Otherwise, I'm looking for confirmation with an authoritative reference.
Not necessarily the same package - if you are writing libraries this won't be useful, but if you are writing the "final application" - the thing that actually gets run:
One option:
If the NODE_PATH environment variable is set to a colon-delimited list of absolute paths, then Node.js will search those paths for modules if they are not found elsewhere.
So you can do any of:
1.
export NODE_PATH=.
node app.js
NODE_PATH=. node app.js
// app.js (or whatever your entry point is) before *any* require() calls
process.env.NODE_PATH = __dirname;
require('module').Module._initPaths();
Or, another way:
The Module object representing the entry script loaded when the Node.js process launched.
https://nodejs.org/api/modules.html#modules_require_main
So you can just do:
const rule = require.main.require("./lib/rules/accessor-pairs")
anytime you want it to be relative to the root (assuming that is how you have your project structured).
You can use the package name itself as a "symlink" to the package root.
Example - foo package imports bar script relative to the foo package root.
package.json
{
"dependencies": {
"foo": "file:./foo"
}
}
index.js
const foo = require('foo');
console.log(foo.bar); // prints "hello"
foo/index.js
const bar = require('foo/bar'); // import relative to the package root
module.exports = {
bar: bar
}
foo/bar.js
module.exports = 'hello';
If you use vscode then you're in luck - !!!! jsconfig.json in project root handles this masterfully for commonjs, es6, amd, umd, etc
The jsconfig.json file specifies the root files and the options for the features provided by the JavaScript language service.
jsconfig.json
{
"compilerOptions": {
"module": "commonjs",
"baseUrl": ".",
"paths": {
"#rules/*": ["path/to/lib/rules/"]
}
}
}
and then to use the alias:
const rule = require('#rules/accessor-pairs'),
{ RuleTester } = require('#rules/rule-tester');
Read more:
https://code.visualstudio.com/docs/languages/jsconfig

ES6 dynamic imports and instanciation of classes

I'm trying to figure out how to perform dynamic import of classes in ES6 one the server side (node.js with Babel).
I would like to have some functionalities similar to what reflection offers in Java. The idea is to import all the classes in a specific folder and instanciate them dynamically.
So for example I could have multiple classes declared in a folder like the one below :
export default class MyClass {
constructor(somevar) {
this._somevar = somevar
}
//...
//some more instance level functions here
}
and then somewhere else in my app's code I could have a function that finds out all the classes in a specific folder and tries to instanciate them :
//somewhere else in my app
instanciationFunction(){
//find all the classes in a specific folder
var classFiles = glob.sync(p + '/path_to_classes/**/*.js', {
nodir: true
});
_.each(classFiles, async function (file) {
console.log(file);
var TheClass = import(file);
var instance = new TheClass();
//and then do whatever I want with that new instance
});
}
I've tried doing it with require but I get errors. Apparently the constructor cant be found.
Any idea would be greatly appreciated.
Thanks
ES module definitions are declarative, and the current direction tools are taking is the path where dependencies are determined during parse (via static analysis), waaay before any of the code is executed. This means dynamic and conditional imports go against the said path. It's not like in Node where imports are determined on execution, upon executing require.
If you want dynamic, runtime imports, consider taking a look at SystemJS. If you're familiar with RequireJS, it takes the same concept, but expands it to multiple module formats, including ES6. It has SystemJS.import which appears to do what you want, plus handles the path resolution that you're currently doing.
Alternatively, if your intention is to shed off excess code, consider using Rollup. It will analyze code for you and only include code that's actually used. That way, you don't need to manually do conditional loading.
You need to preprocess with babel, because they are not yet a part of node (for that matter, neither are static imports - node uses require).
https://github.com/airbnb/babel-plugin-dynamic-import-node
steps:
pre
npm i -D babel-cli or npm i -D babel
1
npm i -D babel-plugin-dynamic-import-node
2
.babelrc
{
"plugins": ["dynamic-import-node"]
}
ready, go!
babel-node test_import.js for babel-cli, or for raw babel:
a
(edit) package.json
"scripts": {
"pretest": "babel test_imports.js -o dist/test_imports.js",
"test": "node dist/test_imports.js"
//...
b
node test
I had the same usecase and i managed to dynamically load and instantiate default exported classes using:
const c = import("theClass.js")
const i = new c.default();
using node v16.4.0

Define global variable with webpack

Is it possible to define a global variable with webpack to result something like this:
var myvar = {};
All of the examples I saw were using external file require("imports?$=jquery!./file.js")
There are several way to approach globals:
1. Put your variables in a module.
Webpack evaluates modules only once, so your instance remains global and carries changes through from module to module. So if you create something like a globals.js and export an object of all your globals then you can import './globals' and read/write to these globals. You can import into one module, make changes to the object from a function and import into another module and read those changes in a function. Also remember the order things happen. Webpack will first take all the imports and load them up in order starting in your entry.js. Then it will execute entry.js. So where you read/write to globals is important. Is it from the root scope of a module or in a function called later?
config.js
export default {
FOO: 'bar'
}
somefile.js
import CONFIG from './config.js'
console.log(`FOO: ${CONFIG.FOO}`)
Note: If you want the instance to be new each time, then use an ES6 class. Traditionally in JS you would capitalize classes (as opposed to the lowercase for objects) like
import FooBar from './foo-bar' // <-- Usage: myFooBar = new FooBar()
2. Use Webpack's ProvidePlugin.
Here's how you can do it using Webpack's ProvidePlugin (which makes a module available as a variable in every module and only those modules where you actually use it). This is useful when you don't want to keep typing import Bar from 'foo' again and again. Or you can bring in a package like jQuery or lodash as global here (although you might take a look at Webpack's Externals).
Step 1. Create any module. For example, a global set of utilities would be handy:
utils.js
export function sayHello () {
console.log('hello')
}
Step 2. Alias the module and add to ProvidePlugin:
webpack.config.js
var webpack = require("webpack");
var path = require("path");
// ...
module.exports = {
// ...
resolve: {
extensions: ['', '.js'],
alias: {
'utils': path.resolve(__dirname, './utils') // <-- When you build or restart dev-server, you'll get an error if the path to your utils.js file is incorrect.
}
},
plugins: [
// ...
new webpack.ProvidePlugin({
'utils': 'utils'
})
]
}
Now just call utils.sayHello() in any js file and it should work. Make sure you restart your dev-server if you are using that with Webpack.
Note: Don't forget to tell your linter about the global, so it won't complain. For example, see my answer for ESLint here.
3. Use Webpack's DefinePlugin.
If you just want to use const with string values for your globals, then you can add this plugin to your list of Webpack plugins:
new webpack.DefinePlugin({
PRODUCTION: JSON.stringify(true),
VERSION: JSON.stringify("5fa3b9"),
BROWSER_SUPPORTS_HTML5: true,
TWO: "1+1",
"typeof window": JSON.stringify("object")
})
Use it like:
console.log("Running App version " + VERSION);
if(!BROWSER_SUPPORTS_HTML5) require("html5shiv");
4. Use the global window object (or Node's global).
window.foo = 'bar' // For SPA's, browser environment.
global.foo = 'bar' // Webpack will automatically convert this to window if your project is targeted for web (default), read more here: https://webpack.js.org/configuration/node/
You'll see this commonly used for polyfills, for example: window.Promise = Bluebird
5. Use a package like dotenv.
(For server side projects) The dotenv package will take a local configuration file (which you could add to your .gitignore if there are any keys/credentials) and adds your configuration variables to Node's process.env object.
// As early as possible in your application, require and configure dotenv.
require('dotenv').config()
Create a .env file in the root directory of your project. Add environment-specific variables on new lines in the form of NAME=VALUE. For example:
DB_HOST=localhost
DB_USER=root
DB_PASS=s1mpl3
That's it.
process.env now has the keys and values you defined in your .env file.
var db = require('db')
db.connect({
host: process.env.DB_HOST,
username: process.env.DB_USER,
password: process.env.DB_PASS
})
Notes
Regarding Webpack's Externals, use it if you want to exclude some modules from being included in your built bundle. Webpack will make the module globally available but won't put it in your bundle. This is handy for big libraries like jQuery (because tree shaking external packages doesn't work in Webpack) where you have these loaded on your page already in separate script tags (perhaps from a CDN).
I was about to ask the very same question. After searching a bit further and decyphering part of webpack's documentation I think that what you want is the output.library and output.libraryTarget in the webpack.config.js file.
For example:
js/index.js:
var foo = 3;
var bar = true;
webpack.config.js
module.exports = {
...
entry: './js/index.js',
output: {
path: './www/js/',
filename: 'index.js',
library: 'myLibrary',
libraryTarget: 'var'
...
}
Now if you link the generated www/js/index.js file in a html script tag you can access to myLibrary.foo from anywhere in your other scripts.
Use DefinePlugin.
The DefinePlugin allows you to create global constants which can be
configured at compile time.
new webpack.DefinePlugin(definitions)
Example:
plugins: [
new webpack.DefinePlugin({
PRODUCTION: JSON.stringify(true)
})
//...
]
Usage:
console.log(`Environment is in production: ${PRODUCTION}`);
You can use define window.myvar = {}.
When you want to use it, you can use like window.myvar = 1
DefinePlugin doesn't actually define anything. What it does is replace variables that exist in your bundle code. If the variable doesn't exist in your code, it will do nothing. So it doesn't create global variables.
In order to create a global variable, write it in your code:
window.MyGlobal = MY_GLOBAL;
And use DefinePlugin to replace MY_GLOBAL with some code:
new webpack.DefinePlugin({
'MY_GLOBAL': `'foo'`,
// or
'MY_GLOBAL': `Math.random()`,
}),
Then your output JS will be like this:
window.MyGlobal = 'foo';
// or
window.MyGlobal = Math.random();
But MY_GLOBAL will never actually exist at runtime, because it is never defined. So that's why DefinePlugin has a misleading name.
I solved this issue by setting the global variables as a static properties on the classes to which they are most relevant. In ES5 it looks like this:
var Foo = function(){...};
Foo.globalVar = {};
You may hit this issue, when triing bundle < script > tag js files in some old project.
Do not use webpack for this, it may be even impossible if joining 50+ libraries like jquery and then figuring out all global variables or if they used nested require. I would advice to simply use uglify js instead , which drops all this problems in 2 commands.
npm install uglify-js -g
uglifyjs --compress --mangle --output bundle.js -- js/jquery.js js/silly.js

Exporting a class with ES6 (Babel)

I'm writing some frontend code with ECMAScript 6 (transpiled with BabelJS, and then browserified with Browserify) so that I can have a class in one file, export it and import it in another file.
The way I'm doing this is:
export class Game {
constructor(settings) {
...
}
}
And then on the file that imports the class I do:
import {Game} from "../../lib/pentagine_browserified.js";
var myGame = new Game(settings);
I then compile it with grunt, this is my Gruntfile:
module.exports = function(grunt) {
"use strict";
grunt.loadNpmTasks('grunt-babel');
grunt.loadNpmTasks('grunt-browserify');
grunt.initConfig({
"babel": {
options: {
sourceMap: false
},
dist: {
files: {
"lib/pentagine_babel.js": "lib/pentagine.js",
"demos/helicopter_game/PlayState_babel.js": "demos/helicopter_game/PlayState.js"
}
}
},
"browserify": {
dist: {
files: {
"lib/pentagine_browserified.js": "lib/pentagine_babel.js",
"demos/helicopter_game/PlayState_browserified.js": "demos/helicopter_game/PlayState_babel.js"
}
}
}
});
grunt.registerTask("default", ["babel", "browserify"]);
};
However, on the new Game( call, I get the following error:
Uncaught TypeError: undefined is not a function
As so, what I did was analyse the generated code by Babel and Browserify and I found this line on PlayState_browserified.js:
var Game = require("../../lib/pentagine_browserified.js").Game;
I decided to print the require output:
console.log(require("../../lib/pentagine_browserified.js"));
And it is nothing but an empty object. I decided to check out the pentagine_browserified.js file:
var Game = exports.Game = (function () {
It seems like it is correctly exporting the class, but for some other reason it is not being required on the other file.
Also, I'm sure the file is being required properly because changing the string "../../lib/pentagine_browserified.js" spits out a Not Found error, so it is going for the right file, that I'm sure about.
Browserify is meant to be fed a single "entry point" file, through which it recursively traverses all of your require statements, importing the code from other modules. So you should be require'ing the _babel.js versions of modules, not _browserified.js ones.
From the looks of it, you intend for your app's "entry point" to be demos/helicopter_game/PlayState_browserified.js, yeah? If that's the case:
In PlayState.js, change it to import {Game} from "../../lib/pentagine_babel.js";.
In Gruntfile.js, remove "lib/pentagine_browserified.js": "lib/pentagine_babel.js".
Works for me. Let me know if that suffices or I am misunderstanding your requirements here.
P.S. You can use babelify to avoid having separate Grunt tasks for Babel and Browserify. See my answer here for an example.
I had a slightly different file configuration, that gave me some difficulty to get the "require" syntax to work in Node, but this post gave me the hint on how to used the babel-ified version of the file name.
I am using WebStorm with the FileWatcher option set to Babel, and I have the FileWatcher configured to watch all files with suffix .jsx, and rename the compiled output file from {my_file}.jsx to {my_file}-compiled.js.
So in my test case, I have 2 files:
Person.jsx:
class Person { ... }
export { Person as default}
and another file that wants to import it:
Test.jsx:
var Person = require('./Person-compiled.js');
I couldn't get the "require" statement to find the module until I started the file path with './' and also add '-compiled.js' to properly specify the file name so that Node es5 could find the module.
I was also able to use the "import" syntax:
import Person from './Person-compiled.js';
Since I have set up my WebStorm project as a Node ES5 project, I have to run 'Test-compiled.js' (not 'Test.jsx').

Categories