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
Related
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)
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.
First time for me on Stack Overflow, please be kind ;) I'll try to do my best!
The context:
I am working on a Rails 6 app with webpacker. This is a program that will be shared by several companies and in order to apply the 'one code, multiple setups' paradigm, we decided to move all the company related configuration files to separate folders, and to put the company name as a variable in our .env file. We need to change some config variables as well as some geofencing data (so our customers can create a new delivery to some address). Basically that's what it looks like:
Project folder
| config
| companies
| a_first_company
| rails_config.rb
| geofencing.js
| a_second_company
| rails_config.rb
| geofencing.js
| ....
In the .env file:
COMPANY=a_first_company
And in the rails configuration (application.rb), we are using a simple:
require_relative "companies/#{ENV['COMPANY']}/rails_config"
But now, here comes the JS part! And I am running into trouble.
The problem:
I would like to include dynamically a JSON object in an existing script. A sample geofencing.js looks like that:
module.exports = {
"countries": ["be"],
"polygon": [
50.8917729, 4.3004608,
...
50.9162381, 4.3450928,
50.8917729, 4.3004608
]
}
And I am trying to import it as a geofencing variable in my existing address autocompletion script:
/app/javascript/plugins/places.js
// I know it doesn't work that way, but basically that what I would like to do:
const geofencing = require(`/config/companies/${process.env.COMPANY}/geofencing`);
...
const initPlaceAutocomplete = () => {
...
var placesAutocomplete = places(
{
// And use the variable here...
insidePolygon: [geofencing.polygon],
type: 'address',
// And there...
countries: geofencing.countries,
templates: {
value: (suggestion) => {
return suggestion.name;
}
},
container: addressInput
}
);
...
}
export { initPlaceAutocomplete };
This file is imported in the view with a <%= javascript_pack_tag 'delivery_new' %>:
/app/javascript/packs/delivery_new.js
import { initPlaceAutocomplete } from '../plugins/places';
initPlaceAutocomplete();
...
The solution (that I haven't found yet):
I have tried several things, like importing the file in the webpack config (/config/webpack/environment.js), just like in the ProvidePlugin documentation:
const {environment} = require('#rails/webpacker')
const path = require('path');
const webpack = require('webpack')
environment.plugins.prepend('Provide',
new webpack.ProvidePlugin({
$: 'jquery',
jQuery: 'jquery',
Popper: ['popper.js', 'default'],
geofencing: path.resolve(path.join(__dirname, '..', '..', 'config', 'companies', process.env['COMPANY'], 'geofencing'))
})
)
module.exports = environment
... But it didn't work.
I also tried various places to import 'geofencing' in several places, with always the same result in the Chrome console: Uncaught ReferenceError: geofencing is not defined.
I noticed, though, that I had access to the process.env variables in the places.js script: writing console.log(process.env['COMPANY']); in the file prompts me the company name in the dev console when I reload the page in Chrome.
Apart from this, I have to say that I am lost. I am basically a newbie to the Webpack 'magic' ;)
Please tell me if you need more info about my setup.
Thanks in advance for your help!
You should be able to require modules using interpolation in the require() expression similar to your example. Try using a relative path, e.g. (assuming parent directories are siblings):
require(`../config/${process.env.COMPANY}/geofencing`)
The above expression will add only the COMPANY geofencing module to the bundle.
If you wish this to be dynamic, webpack can resolve the interpolation at runtime assuming the require paths are scoped to a directory; you're already doing this here with "../config/". As a result, webpack will include all COMPANY geofencing modules in the bundle. As for usage, I might move the require statement inside a function:
function initPlaceAutocomplete(company) {
const geofencing = require(`../config/${company}/geofencing`)
// ...
}
// usage
import { initPlaceAutocomplete } from "../plugins/places"
initPlaceAutocomplete(process.env.COMPANY)
At this point, this could is a good use case for dynamic imports, i.e., webpack supports the TC39 proposal for dynamically loading modules at runtime.
Instead of require("../config..."), you can use the import() function syntax. Unlike require(), the import() function syntax resolves asynchronously.
Now webpack will bundle all the COMPANY geofencing modules, but as separate "chunks" to keep the size of your initial script down. webpack will insert code to resolve these "chunks" asynchronously at runtime. To support this, the import() expression returns a Promise so, I'm using the async/await syntax here as a result.
async function initPlaceAutocomplete(company) {
const geofencing = await import(`../companies/${company}/config`)
// ...
}
The documentation for Vue CLI 3 says here https://cli.vuejs.org/guide/mode-and-env.html#using-env-variables-in-client-side-code:
You can have computed env vars in your vue.config.js file. They still need to be prefixed with VUE_APP_. This is useful for version info process.env.VUE_APP_VERSION = require('./package.json').version
This is exactly what I want to do. But I couldn't find out how to actually define the env var there in vue.config.js. I tried:
module.exports = {
process.env.VUE_APP_VERSION: require("../package.json").version,
...
}
But it just produces an error:
ERROR SyntaxError: Unexpected token .
/Users/lhermann/htdocs/langify/frontend/vue.config.js:2
process.env.VUE_APP_VERSION: require("../package.json").version,
^
Does anyone know?
The environment variables are not part of the config export, you just set them in the vue.config.js file, eg
process.env.VUE_APP_VERSION = require('./package.json').version
module.exports = {
// other config, eg configureWebpack
}
I've raised a feature-request to get an example added to the docs ~ https://github.com/vuejs/vue-cli/issues/2864
Common Environment Variables:
According to Environment Variables and Modes documentation, you can specify env variables by placing .env files in your project root.
The variables will automatically be accessible under process.env.variableName in your project. Loaded variables are also available to all vue-cli-service commands, plugins and dependencies.
.env # loaded in all cases
.env.local # loaded in all cases, ignored by git
.env.[mode] # only loaded in specified mode
.env.[mode].local # only loaded in specified mode, ignored by git
Your .env file(s) should look like this:
VUE_APP_MY_ENV_VARIABLE=value
VUE_APP_ANOTHER_VARIABLE=value
Note that only variables that start with VUE_APP_ will be statically embedded into the client bundle with webpack.DefinePlugin.
Computed Environment Variables:
If you want variables that need pre-processing, you can use chainWebpack property of vue.config.js to inject anything you want:
// vue.config.js
module.exports = {
// ...,
chainWebpack: config => {
config.plugin('define').tap(args => {
args[0]['process.env'].APP_VERSION = `"${require("../package.json").version}"`
return args
})
}
// ...
}
Using this method, you can inject anything, with any names you want; you are not bound by the VUE_APP_ limitation.
when I run a test in node.js with mocha, how I can set temporal environment variables?
in a module, I have a variable depending of a environment variable
var myVariable = proccess.env.ENV_VAR;
now I use the rewire module,
var rewire = require('rewire');
var myModule = rewire('../myModule');
myModule.__set__('myVariable', 'someValue');
exist a more simple way? without the rewire module?
In your myModule.js file, export a function that takes the variable as an argument eg:
module.exports = function (var) {
// return what you were exporting before
};
Then when you require it, require it like so:
var myModule = require('../myModule')(process.env.ENV_VAR);
My first instinct was to simply set the env var at the top of the test.js before any require statements. However, this may not work for you if you have a module that depends on a env var, and it is required multiple times in the same test run. say you have an env dependent module called mode.js:
module.exports = {
MODE : process.env.ENV_VAR
};
If you add a single test file called bTest.js with
process.env.ENV_VAR= "UNIT_TEST_MODE"
const mode = require('./mode.js')
// describe some tests scenarios that use mode.MODE
...
you will be OK. but if you add a second test file
const mode = require('./mode.js')
// describe some more tests scenarios that use mode.MODE
...
and name it aTest.js, the new file will run first in your suite and mode.MODE will be undefined for all subsequent test js files. The require command won't actually reload the same module multiple times.
Let's assume you aren't able to use the dotenv package in your tests. If so, you can set values on the process.env programmatically in the mocha config file. By default, this is found in .mocharc.json or .mocha.yml, but this can easily be translated to .mocharc.js . Referring to the sample js file here: https://github.com/mochajs/mocha/blob/master/example/config/.mocharc.js
So your .mocharc.js could be
"use strict";
process.env.ENV_VAR = "UNIT_TEST_MODE";
// end of .mocharc.js
and ENV_VAR will be set before mocha requires or runs any of your modules.
Even if you are using dotenv , you can choose to flip set other dotenv option from inside your mochajs config that you might not want to set on your local dev server's .env file. That way, your .env.mocha vars will be available to individual modules that don't require dotenv.
"use strict";
require('dotenv').config({ debug: process.env.DEBUG, { path: '/full/custom/path/to/.env.mocha' } })`.
// end of .mocharc.js
Although in the second case, you may be better off just setting the dotenv env path as part of the test command in your package.json:
node -r dotenv/config /node_modules/mocha/bin/_mocha dotenv_config_path=/full/custom/path/to/.env.mocha