I created a cli application which reads its version number from package.json with this bit of code
const packageJson = JSON.parse(fs.readFileSync(path.resolve('./package.json'), 'utf8'))
This works fine if I run the app with yarn start or a similar command while development
But after the package is installed with npm install --global app-name the user should use the declare executable from any path on the system. So if I want to run it say in /Users/myUser/Desktop I get an error like this
Error: ENOENT: no such file or directory, open '/Users/myUser/Desktop/package.json'
So what's a good protocol of loading this package.json within my CLI or is there a better way for approaching this?
Later edit:
For clarity, my package json contains this
{
...
"bin": {
"clip": "./bin/clip.js"
},
...
}
so what I mean by my problem, is when I am running the executable "clip" from a different path, after I used npm publish
After some research I tried this code (use the path.dirname function):
const __filename = fileURLToPath(import.meta.url)
const __dirname = path.dirname(__filename)
export const packageJsonLocation = path.join(__dirname, './../package.json')
const packageJson = JSON.parse(fs.readFileSync(packageJsonLocation, 'utf8'))
and this (just importing the file as json using node's standard import keyword)
import * as packageJson from './../package.json' assert { type: 'json' }
in both cases I get the same result, the executable generated and it tries to read package.json from the current directory. Specifically if I try to console.log() the path I get my current path where I am executing the global executable (clip in my case)
Use __dirname because it always refers to the path of the file that contains this variable, whereas ./ gives you the working directory, such as process.cwd().
const packageJson = JSON.parse(fs.readFileSync(
path.join(__dirname, 'package.json'), 'utf8')
)
If you're using ES Modules, do also to get __dirname
import { dirname } from 'path';
import { fileURLToPath } from 'url';
const __dirname = dirname(fileURLToPath(import.meta.url));
const packageJson = JSON.parse(fs.readFileSync(
path.join(__dirname, 'package.json'), 'utf8')
)
Edit:
You installed the package globally with a bin, but the bin you're calling with a CLI is a symlink which is inside the path <npm_glob_path>/node_modules/bin not <npm_glob_path>/node_modules/app-name/bin. The package.json of your app is inside <npm_glob_path>/node_modules/app-name. And don't use ./, always use path calls
Hence try this instead (replace app-name by your app's name):
import { dirname } from 'path';
import { fileURLToPath } from 'url';
const __dirname = dirname(fileURLToPath(import.meta.url))
console.log('__dirname:' + __dirname) // TELL ME WHAT YOU SEE HERE WHEN YOU RUN THE CLI CMD
const packageJsonLocation = path.join(__dirname, '..', 'app-name' 'package.json')
const packageJson = JSON.parse(fs.readFileSync(
path.join(__dirname, 'package.json'), 'utf8')
)
And please, add console.log('__dirname:' + __dirname) after defining __dirname. Which path do you see when you run the CLI app?
is there a better way for approaching this?
Yes - you should store the version number in the actual package itself somewhere. This way it will always be available/accessible and there's no risk of the package.json version and the installed version becoming out of sync. For example, if someone adds your package to a project and then runs yarn install, but later uses git pull to get an up-to-date version of their local files which happens to include a version bump for your package, there is a window where the package.json has a different version number to the installed version.
here's my main js code for the component
import './sass/main.scss'
import Vlider from './Vlider.vue'
function install(Vue) {
if (install.installed) return;
install.installed = true;
Vue.component('vlider', Vlider);
}
const plugin = {
install,
};
let GlobalVue = null;
if (typeof window !== 'undefined') {
GlobalVue = window.Vue;
} else if (typeof global !== 'undefined') {
GlobalVue = global.Vue;
}
if (GlobalVue) {
GlobalVue.use(plugin);
}
Vlider.install = install;
export default Vlider
can anyone help me with the webpack config? I need to output 4 files from this index.js
dist/vlider.umd.js
dist/vlider.esm.js
dist/vlider.min.js
vlider.css
so that it can support multiple entry points in package.json
"main": "dist/vlider.umd.js",
"module": "dist/vlider.esm.js",
"unpkg": "dist/vlider.min.js",
"browser": "src/vlider.vue"
this is my first time dealing with webpack so your help will be greatly appreciated
Webpack does not support esmodule as the output target. You can create commonjs module or umd/iife module.
If you need ESModule as a target then consider using Rollup.js. In general, you should use Rollup when you need to bundle library and Webpack for application bundling. (Note: Rollup is great but TypeScript + .Vue files + Class-based vue component syntax does not work.)
Also, irrespective of Webpack or Rollup, you can use array-based/multiple-targets export. Refer following links for more details:
Webpack: https://webpack.js.org/concepts/targets/#multiple-targets
Rollup: https://rollupjs.org/guide/en#configuration-files
My Node-based project is implemented using native ES module support on Node thanks to the --experimental-modules CLI switch (i.e. node --experimental-modules).
Obviously, when I run a spec using Jasmine node --experimental-modules ./node_modules/jasmine/bin/jasmine I get the following error:
Error [ERR_REQUIRE_ESM]: Must use import to load ES Module
Is it ever possible to use Jasmine using ES modules in Node?
If not, is there any alternative to don't use a framework (e.g. running tests with npm scripts)?
It was easier than I thought.
It's just about calling a file which you might call run.mjs as follows:
node --experimental-modules ./run.mjs
The whole file would look like this:
jasmine.mjs:
import Jasmine from "jasmine"
import JasmineConsoleReporter from "jasmine-console-reporter"
const jasmine = new Jasmine()
jasmine.loadConfigFile( "./support/jasmine.json" )
jasmine.env.clearReporters()
jasmine.addReporter( new JasmineConsoleReporter( {
colors: true,
cleanStack: true,
verbosity: 4,
listStyle: 'indent',
activity: false
} ) )
export default jasmine
And you would add specs as follows in separate files:
import jasmine from './my-project/spec/jasmine.mjs'
jasmine.env.describe('Foo', () => {
jasmine.env.it('Bar', () => {
// Expects, assertions...
})
})
Finally, you would run jasmine importing both configured jasmine instance and specs:
import jasmine from './my-project/spec/jasmine.mjs'
import someSpec1 from './my-project/spec/someSpec1.mjs'
import someSpecN from './my-project/spec/someSpecN.mjs'
someSpec1()
someSpecN()
jasmine.execute()
Simplifying the solution of #Matias_Fidemraizer, keeping only the important bits in one file:
import glob from 'glob';
import Jasmine from 'jasmine';
const jasmine = new Jasmine();
jasmine.loadConfigFile('tests/jasmine.json');
// Load your mjs specs
glob('**/*-test.mjs', function (er, files) {
Promise.all(
files
// Use relative paths
.map(f => f.replace('tests/specs/', './'))
.map(f => import(f)
.catch(e => {
console.error('** Error loading ' + f + ': ');
console.error(e);
process.exit(1);
}))
)
.then(() => jasmine.execute());
});
And execute it with
node --experimental-modules jasmine-run.mjs
You will have some problems in the logs, receiving a message like
[...] .split('\n') [...]
That message mean that you have an exception in the mjs code.
You can follow there: https://github.com/jasmine/jasmine-npm/issues/150
Question: is there a way to tell webpack to tell built-in modules modules like fs to execute during build so the browser gets the result of this function, not the function call itself?
My Situation:
Currently I'm developing an application for the browser using webpack. I'm trying to use the node 'fs' module in one my files to require the index.js files from other directories. For example:
plugins
├──plugin1
│ ├── index.js (simply exports an object)
│
├──plugin2
│ ├── index.js (simply exports an object)
|
├──plugin3
│ ├── index.js (simply exports an object)
|
|──index.js (want to require all index.js from each plugin directory here)
I'm getting an error with webpack saying: Can't resolve 'fs' in somepath/node_modules/require-dir
My file index.js located at `plugins/index.js' which is simply trying to require my other files.
//module from NPM which uses the 'fs' module ('im not explicity using it)
const requireDir = require('require-dir');
const allPlugins = requireDir('./plugins/');
console.log(allPlugins);
Can't resolve 'fs' in '/some_path/node_modules/require-dir'
You have two options here.
I haven't used this personally, but you can use node config value as specified here.
node: {
fs: {true, "mock", "empty", false}
}
Set fs to any of the above values.
Don't use the fs module. It is a built/native modules which may or may not rely on native V8/C++ functions/libraries. Remember that webpack typically bundles assets for the browser. So instead of relying on a plugin, you can manually import your plugins like:
plugins/index.js
const plugin1 = require('./plugin1')
const plugin2 = require('./plugin2')
module.exports = {
plugin1,
plugin2
}
You could also use this answer to polyfill the require-dir module.
Thanks to Francisco Mateo's additional link about polyfilling require-dir, I learned about the context method that webpack adds to require.
This allows me to do dynamic requires like so in my plugins/index.js file:
//require all index.js files inside of /plugins directory
let context = require.context('.', true, /\index\.js/);
const loadPlugins = function(ctx){
let keys = context.keys();
let values = keys.map(context);
return values;
}
//array of values from each index.js require
console.log('loadPlugins', loadPlugins());
Suppose I have the following module:
var modulesReq = require.context('.', false, /\.js$/);
modulesReq.keys().forEach(function(module) {
modulesReq(module);
});
Jest complains because it doesn't know about require.context:
FAIL /foo/bar.spec.js (0s)
● Runtime Error
- TypeError: require.context is not a function
How can I mock it? I tried using setupTestFrameworkScriptFile Jest configuration but the tests can't see any changes that I've made in require.
I had the same problem, then I've made a 'solution'.
I'm pretty sure that this is not the best choice. I ended up stopping using it, by the points answered here:
https://github.com/facebookincubator/create-react-app/issues/517
https://github.com/facebook/jest/issues/2298
But if you really need it, you should include the polyfill below in every file that you call it (not on the tests file itself, because the require will be no global overridden in a Node environment).
// This condition actually should detect if it's an Node environment
if (typeof require.context === 'undefined') {
const fs = require('fs');
const path = require('path');
require.context = (base = '.', scanSubDirectories = false, regularExpression = /\.js$/) => {
const files = {};
function readDirectory(directory) {
fs.readdirSync(directory).forEach((file) => {
const fullPath = path.resolve(directory, file);
if (fs.statSync(fullPath).isDirectory()) {
if (scanSubDirectories) readDirectory(fullPath);
return;
}
if (!regularExpression.test(fullPath)) return;
files[fullPath] = true;
});
}
readDirectory(path.resolve(__dirname, base));
function Module(file) {
return require(file);
}
Module.keys = () => Object.keys(files);
return Module;
};
}
With this function, you don't need to change any require.context call, it will execute with the same behavior as it would (if it's on webpack it will just use the original implementation, and if it's inside Jest execution, with the polyfill function).
After spending some hours trying each of the answers above. I would like to contribute.
Adding babel-plugin-transform-require-context plugin to .babelrc for test env fixed all the issues.
Install - babel-plugin-transform-require-context here https://www.npmjs.com/package/babel-plugin-transform-require-context (available with yarn too)
Now add plugin to .babelrc
{
"env": {
"test": {
"plugins": ["transform-require-context"]
}
}
}
It will simply transform require-context for test env into dummy fn calls so that code can run safely.
If you are using Babel, look at babel-plugin-require-context-hook. Configuration instructions for Storybook are available at Storyshots | Configure Jest to work with Webpack's require.context(), but they are not Storyshots/Storybook specific.
To summarise:
Install the plugin.
yarn add babel-plugin-require-context-hook --dev
Create a file .jest/register-context.js with the following contents:
import registerRequireContextHook from 'babel-plugin-require-context-hook/register';
registerRequireContextHook();
Configure Jest (the file depends on where you are storing your Jest configuration, e.g. package.json):
setupFiles: ['<rootDir>/.jest/register-context.js']
Add the plugin to .babelrc
{
"presets": ["..."],
"plugins": ["..."],
"env": {
"test": {
"plugins": ["require-context-hook"]
}
}
}
Alternatively, add it to babel.config.js:
module.exports = function(api) {
api.cache(true)
const presets = [...]
const plugins = [...]
if (process.env.NODE_ENV === "test") {
plugins.push("require-context-hook")
}
return {
presets,
plugins
}
}
It may be worth noting that using babel.config.js rather than .babelrc may cause issues. For example, I found that when I defined the require-context-hook plugin in babel.config.js:
Jest 22 didn't pick it up;
Jest 23 picked it up; but
jest --coverage didn't pick it up (perhaps Istanbul isn't up to speed with Babel 7?).
In all cases, a .babelrc configuration was fine.
Remarks on Edmundo Rodrigues's answer
This babel-plugin-require-context-hook plugin uses code that is similar to Edmundo Rodrigues's answer here. Props to Edmundo! Because the plugin is implemented as a Babel plugin, it avoids static analysis issues. e.g. With Edmundo's solution, Webpack warns:
Critical dependency: require function is used in a way in which dependencies cannot be statically extracted
Despite the warnings, Edmundo's solution is the most robust because it doesn't depend on Babel.
Extract the call to a separate module:
// src/js/lib/bundle-loader.js
/* istanbul ignore next */
module.exports = require.context('bundle-loader?lazy!../components/', false, /.*\.vue$/)
Use the new module in the module where you extracted it from:
// src/js/lib/loader.js
const loadModule = require('lib/bundle-loader')
Create a mock for the newly created bundle-loader module:
// test/unit/specs/__mocks__/lib/bundle-loader.js
export default () => () => 'foobar'
Use the mock in your test:
// test/unit/specs/lib/loader.spec.js
jest.mock('lib/bundle-loader')
import Loader from 'lib/loader'
describe('lib/loader', () => {
describe('Loader', () => {
it('should load', () => {
const loader = new Loader('[data-module]')
expect(loader).toBeInstanceOf(Loader)
})
})
})
Alrighty! I had major issues with this and managed to come to a solution that worked for me by using a combination of other answers and the Docs. (Took me a good day though)
For anyone else who is struggling:
Create a file called bundle-loader.js and add something like:
module.exports = {
importFiles: () => {
const r = require.context(<your_path_to_your_files>)
<your_processing>
return <your_processed_files>
}
}
In your code import like:
import bundleLoader from '<your_relative_Path>/bundle-loader'
Use like
let <your_var_name> = bundleLoader.importFiles()
In your test file right underneath other imports:
jest.mock('../../utils/bundle-loader', () => ({
importFiles: () => {
return <this_will_be_what_you_recieve_in_the_test_from_import_files>
}
}))
Installing
babel-plugin-transform-require-context
package and adding the plugin in the .babelrc resolved the issue for me.
Refer to the documentation here:
https://www.npmjs.com/package/babel-plugin-transform-require-context
The easiest and fastest way to solve this problem will be to install require-context.macro
npm install --save-dev require-context.macro
then just replace:
var modulesReq = require.context('.', false, /\.js$/);
with:
var modulesReq = requireContext('.', false, /\.js$/);
Thats it, you should be good to go!
Cheers and good luck!
Implementation problems not mentioned:
Jest prevents out-of-scope variables in mock, like __dirname.
Create React App limits Babel and Jest customization. You need to use src/setupTests.js which is run before every test.
fs is not supported in the browser. You will need something like browserFS. Now your app has file system support, just for dev.
Potential race condition. Export after this import. One of your require.context imports includes that export. I'm sure require takes care of this, but now we are adding a lot of fs work on top of it.
Type checking.
Either #4 or #5 created undefined errors. Type out the imports, no more errors. No more concerns about what can or can't be imported and where.
Motivation for all this? Extensibility. Keeping future modifications limited to one new file. Publishing separate modules is a better approach.
If there's an easier way to import, node would do it. Also this smacks of premature optimization. You end up scrapping everything anyways because you're now using an industry leading platform or utility.
If you're using Jest with test-utils in Vue.
Install these packages:
#vue/cli-plugin-babel
and
babel-plugin-transform-require-context
Then define babel.config.js at the root of the project with this configuration:
module.exports = function(api) {
api.cache(true);
const presets = [
'#vue/cli-plugin-babel/preset'
];
const plugins = [];
if (process.env.NODE_ENV === 'test') {
plugins.push('transform-require-context');
}
return {
presets,
plugins
};
};
This will check if the current process is initiated by Jest and if so, it mocks all the require.context calls.
I faced the same issue with an ejected create-react-app project
and no one from the answers above helped me...
My solution were to copy to config/babelTransform.js the follwoing:
module.exports = babelJest.createTransformer({
presets: [
[
require.resolve('babel-preset-react-app'),
{
runtime: hasJsxRuntime ? 'automatic' : 'classic',
},
],
],
plugins:["transform-require-context"],
babelrc: false,
configFile: false,
});
Simpleset Solution for this
Just Do
var modulesReq = require.context && require.context('.', false, /\.js$/);
if(modulesReq) {
modulesReq.keys().forEach(function(module) {
modulesReq(module);
});
}
So Here I have added extra check if require.context is defined then only execute By Doing this jest will no longer complain