Grunt, Webpack, and the DllPlugin - javascript

I'm having trouble visualizing how I can leverage the DllPlugin/DllReferencePlugin with Webpack while also using Grunt for the building. For those without knowledge, the DllPlugin creates a separate bundle that can be shared with other bundles. It also creates a manifest file (important) to help with the linking. Then, the DllReferencePlugin is used by another bundle when building to grab the previous made DllPlugin Bundle. To do this, it requires the manifest file created previously.
In Grunt, this would require the manifest file created before grunt even runs, no? Heres a simplified code example:
webpack.dll.js
// My Dll Bundles, which creates
// - ./bundles/my_dll.js
// - ./bundles/my_dll-manifest.json
module.exports = {
entry: {
my_dll : './dll.js'
},
// where to send final bundle
output: {
path: './bundles',
filename: "[name].js"
},
// CREATES THE MANIFEST
plugins: [
new webpack.DllPlugin({
path: "./bundles/[name]-manifest.json",
name: "[name]_lib"
})
]
};
webpack.app.js
// My Referencing Bundle, which includes
// - ./bundles/app.js
module.exports = {
entry: {
my_app : './app.js'
},
// where to send final bundle
output: {
path: './bundles',
filename: "[name].js"
},
// SETS UP THE REFERENCE TO THE DLL
plugins: [
new webpack.DllReferencePlugin({
context: '.',
// IMPORTANT LINE, AND WHERE EVERYTHING SEEMS TO FAIL
manifest: require('./bundles/my_dll-manifest.json')
})
]
};
If you look in the second section, webpack.app.js, I've commented where everything would seem to fail in grunt. For the DllReferencePlugin to work, it needs the manifest file from the DllPlugin, but in a Grunt workflow, grunt will load both of these configurations on initialization of grunt itself, causing the manifest: require('./bundles/my_dll-manifest.json') line to fail, because the previous grunt step that builds webpack.dll.js has not completed, meaning manifest does not yet exist.

var path = require("path");
var util = require('util')
var webpack = require("webpack");
var MyDllReferencePlugin = function(options){
webpack.DllReferencePlugin.call(this, options);
}
MyDllReferencePlugin.prototype.apply = function(compiler) {
if (typeof this.options.manifest == 'string') {
this.options.manifest = require(this.options.manifest);
}
webpack.DllReferencePlugin.prototype.apply.call(this, compiler);
};
// My Referencing Bundle, which includes
// - ./bundles/app.js
module.exports = {
entry: {
my_app : './app.js'
},
// where to send final bundle
output: {
path: './bundles',
filename: "[name].js"
},
// SETS UP THE REFERENCE TO THE DLL
plugins: [
new MyDllReferencePlugin({
context: '.',
// IMPORTANT LINE, AND WHERE EVERYTHING SEEMS TO FAIL
manifest: path.resolve('./bundles/my_dll-manifest.json')
})
]
};

Related

How to manually throw a webpack error from the code on a condition like a missing variable?

I have some code where I have placeholders. There are some variables that I don't know yet that I need to fill in when the code is pushed to production. Right now I have the following in a file called constants.js.
export const PublicAddress = process.env.PUBLIC_ADDRESS || console.error( "Public address is not yet set." );
This code works just fine and there will be an error logged to the console when PUBLIC_ADDRESS is empty.
However, if PUBLIC_ADDRESS was empty AND I forgot to hardcode another value, I'd like Webpack to throw an error when compiling instead. It's really important that I don't forget to put in a real value when I compile it for production.
Is that possible?
It's typically this kind of check that you can provide in your own webpack plugin. here is a basic implementation of a custom plugin that check if all string from an array refer to an env variable :
class EnvVarExistPlugin {
apply(compiler) {
const envsVarToCheck = ['TEST_VAR', 'TEST_VAR2'] // put your env var list here
envsVarToCheck.forEach(envVar => {
if (!!!process.env[envVar]) {
throw new Error(`Environment variable : ${envVar} is missing`);
}
});
}
};
module.exports = EnvVarExistPlugin
Then register your plugin in your webpack.config.js :
const { resolve } = require("path");
const EnvVarExistPlugin = require("./pathToYourPlugin");
require('dotenv').config({ path: './.env' }); // adapt path to your env file
module.exports = {
entry: resolve(__dirname, "src/index.js"),
mode: 'development',
output: {
path: resolve(__dirname, "dist"),
filename: "bundle.js"
},
plugins: [new EnvVarExistPlugin()]
};

How do i prevent a .js file being bundled by webpack

Hi I am currently using webpack to bundle my project files into a single file. However, I do not want webpack to bundle my config.js file where all my config is set. I would like to this remain separate in the output folder but not sure out to achieve this.
my current setup is
//index.js file
#!/usr/bin/env node
'use strict';
let config = require('config.js);
let read = require('read.js);
console.log('i am running through command line');
//read.js file
'use strict'
console.log('read a text file');
//config.js
'use strict';
module.exports = {
name: 'test'
}
//webpack.config.js
let webpack = require('webpack');
let path = require('path');
let fs = require('fs');
let nodeModules = {};
fs.readdirSync('node_modules')
.filter(function (x) {
return [ '.bin' ].indexOf(x) === -1;
})
.forEach(function (mod) {
nodeModules[mod] = 'commonjs ' + mod;
});
module.exports = {
entry: [ 'babel-polyfill', './index.js' ],
target: 'node',
node: {
__dirname: true
},
output: {
path: path.join(__dirname, 'webpack_bundle'),
filename: '[name].js',
libraryTarget: 'commonjs'
},
module: {
loaders: [
{
test: /\.js$/,
loader: 'shebang-loader'
},
{
test: /\.js$/,
loader: 'babel-loader',
query: {
presets: [ 'es2015' ]
}
} ]
},
resolve: {
extensions: [ '.js' ]
},
plugins: [
new webpack.BannerPlugin({banner: '#!/usr/bin/env node', raw: true
})
]
,
externals: nodeModules
};
Note: I have significantly simplified the code example for brevity
Currently when i run the webpack command i get a folder webpack_bundle which contains an index.js file - the index.js file includes the dependencies config.js and read.js. However, what i would like is for the read.js dependency to be bundled into the index.js file but the config.js dependency to stay external in a separate file which gets required by the bundled webpack output. So the folder webpack_bundle should contain two files after running the webpack command - index.js and config.js. I have already tried to modify the externals by adding the following key value to the externals object config: './config.js' but this did not work. I also created an extra entrypoint by specifying config.js as the entrypoint but this also did not work. I can't figure this out and the webpack docs are not that clear on how to achieve this. Please help!
If you want your config in a separate bundle, you can create a split point, by importing dynamically your config.js file with require.ensure:
require.ensure([], function() {
let config = require('./config.js');
});
Your config will then be in a separate bundle.
Documentation about Code splitting (warning: Webpack 1.x is deprecated).
Documentation about Code Splitting (Webpack 2).
Edit:
If you don't want your config file to be bundled by Webpack, I think you can use IgnorePlugin:
module.exports = {
//...
plugins: [new webpack.IgnorePlugin(/^\.\/config\.js$/)]
}
And use copy-webpack-plugin to copy your config.js file.

Delete unused webpack chunked files

I'm looking for information on how to delete old webpack chunked files. Here is my current webpack configuration:
var path = require('path');
var webpack = require('webpack');
module.exports = {
debug: false,
outputPathinfo: true,
displayErrorDetails: true,
context: __dirname,
entry: {
common: ['./src/common.coffee'],
a: './src/a.cjsx',
b: './src/b.cjsx'
},
output: {
filename: '[name]-[chunkhash].js',
chunkFileName: '[name].[chunkhash].js',
path: path.join(__dirname, 'js')
},
plugins: [
new webpack.optimize.CommonsChunkPlugin('common', 'common-[chunkhash].js'),
new webpack.optimize.UglifyJsPlugin({
compress: { warnings: false }
})
],
module: {
preLoaders: [
{
test: /\.coffee$/,
exclude: /node_modules/,
loader: 'coffeelint-loader'
}
],
loaders: [
{ test: /\.coffee/, loader: 'coffee' },
{ test: /\.cjsx$/, loaders: ['coffee', 'cjsx'] },
{ test: /\.js$/, loader: 'jsx-loader?harmony' }
]
}
}
If I am running $(npm bin)/webpack --config webpack.js --watch and make changes to a.cjsx, it compiles a newer version of that file with a new chunkedhash. However, the old one remains and I'd like it to be deleted right away.
How can I delete the old version of the chunked file?
Is there a way for me to hook into an after callback once watch finishes compiling?
There is a clean-webpack-plugin for those purposes, or you can write a simple bash script for npm:
"scripts": {
"build": "rm -r dist/* && webpack -p",
"clean": "rm -r dist/*"
}
Here is the webpack-clean-obsolete-chunks plugin, which do what you want. It searches for all updated chunks and deletes obsolete files after each webpack compilation.
The answer
I've decided to write an answer because others - although trying to answer the question directly - overlooked the most important part in my opinion.
And the most important part is: you shouldn't be doing it this way. Using [hash] placeholders in your development setup cause many headaches with other tooling (phpstorm's path autocomplete in symfony plugin for example). Also it's poor for webpack's incremental compilation performance and thus is not recommended by official webpack docs (reference).
So for future readers: just keep it simple for development config - define your filename as [name].js and move on.
Edit
There seems to be a confusion about what to do with the old chunk-files on the production server. Well, you don't do anything. Once a version is deployed it shouldn't be ever changed. You just keep creating new versions when deploying and keep previous as a backup. Why?
Because you want you're rollback to be reliable and for it to be possible your rollback needs to be extremely simple and atomic. If your rollback procedure is doing anything more than switching a symlink, rerouting to previous container (or similar simple operation) you're probably™ going to end up in trouble.
Rollback isn't a process of "re-deploying" the application again, but now to the previous version. It's a process of "un-doing" the deployment. So doing a git checkout to the previous version followed by a npm build --but-please-be-hurry --and-im-begging-you-dont-fail while your production app is hanging there, completely exploded doesn't cut here.
Rebuilding a previous version of the application - just like the deployment - may fail for many reasons. That's why a rollback should be switching/rerouting back to the exact same version-build that is proven to be working. Not ==-the-same, 100% ===-the-same. That's why you need to keep your previous version around, because that's the ===-same. A "regenerated" one is - in best case scenario - only ==-the-same, and so it is not proven to be working, only assumed.
And no, no amount of CI, staging environments or whatever will give you a guaranteed successful deployment. Part of doing it the right way is to be prepared for when things go wrong. And things will go wrong. Hopefully only from time to time, but still.
Of course once you have 3, 5 or <put-your-number-here> versions backed up you may start to remove the oldest ones as you probably won't ever need more than 3.
Since Webpack 5.20.0 you can use output.clean option
Take a look at this pull request:
https://github.com/johnagan/clean-webpack-plugin/pull/32/files
Open raw file view and copy it to index.js of clean webpack plugin.
Remember about config flag -> watch: true
I have solved that problem by adding below in webpack.config.js
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
{
... your configs ...
plugins: [new CleanWebpackPlugin()]
}
You can solve the problem № 1 by using remove-files-webpack-plugin.
Use this plugin like this:
plugins: [
new RemovePlugin({
watch: {
test: [
{
folder: './js',
method: (absPath) => new RegExp(/(.*)-([^-\\\/]+)\.js/).test(absPath)
}
]
}
})
]
In "watch" mode (not normal compilation!) it grabs all files from ./js folder and tests them with this regular expression /(.*)-([^-\\\/]+)\.js/. Analyze this regular expression on regex101 (unit tests are included) if you have problems with understanding.
Note: i'm the creator of this plugin.
Looks like webpack#5.20.0+ has built-in support for this https://webpack.js.org/configuration/output/#outputclean. I use [chunkhash] in my chunk filenames and they get cleared out if I stop comment out dynamic imports and added back in if I uncomment them.
my case: webpack 5 + multipage application + themes.css via entry points
solution: https://github.com/webdiscus/webpack-remove-empty-scripts
this plugins don't work with webpack 5 entry points or with MiniCssExtractPlugin:
webpack-fix-style-only-entries,
webpack-extraneous-file-cleanup-plugin,
webpack-remove-empty-js-chunks-plugin,
webpack-delete-no-js-entries-plugin.
my webpack.config.js:
const fs = require('fs');
const path = require('path');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const RemoveEmptyScriptsPlugin = require('webpack-remove-empty-scripts');
const isProd = process.env.NODE_ENV === 'production';
const isDev = !isProd;
const PAGES = ['app', 'help'];
const getThemes = (themePath, alias) => {
let themes = {};
const longPath = './' + alias + '/' + themePath;
fs.readdirSync(longPath).forEach(function(fileName) {
const fileNameWithPath = path.join(themePath, fileName);
const fileNameWithLongPath = path.join(longPath, fileName);
const stat = fs.lstatSync(fileNameWithLongPath);
if (stat.isDirectory()) return;
if (!/\.scss$/.test(fileName)) return;
const nameWithoutExt = path.basename(fileName, '.scss');
themes[nameWithoutExt] = ['./' + fileNameWithPath];
});
console.log(themes);
return themes;
};
const themes = getThemes('scss/themes', 'src');
const getFilename = (filename, ext) => {
let name = filename == 'index' ? 'bundle' : filename;
const isTheme = (ext == 'css' && name.startsWith('theme')) ? true : false;
const needHash = (isDev || isTheme) ? false : true;
return needHash ? name +`.[fullhash].` + ext : name+'.'+ext;
};
const getCSSDirname = filename => {
const isTheme = filename.startsWith('theme');
return !isTheme? '/css/' : '/css/theme/';
};
const getHTMLWebpackPlugins = arr => {
// this function config multipages names and add to html-pages
// inside <head> tag our themes via tag <link rel="stylesheet" href="....css" ...>
// and return array of HTMLWebpackPlugins
};
module.exports = {
// ... //
entry: {
// mutipage:
app: ['./index.js', './scss/app.scss'],
help: ['./help.js', './scss/help.scss'],
// multitheme:
...themes,
},
optimization: {
removeEmptyChunks: true, // not work!!!
},
// ... //
plugins: [
// ... //
...getHTMLWebpackPlugins(PAGES),
new RemoveEmptyScriptsPlugin({
ignore: PAGES,
enabled: isDev === false,
}),
new MiniCssExtractPlugin({
filename: pathdata => {
return getCSSDirname(pathdata.chunk.name) + getFilename(pathdata.chunk.name, 'css');
},
chunkFilename: isDev ? '[id].css' : '[id].[contenthash].css',
}),
],
};
my src files:
[src]:
- index.js
- index.html
- help.js
- help.html
- [scss]:
- - app.scss
- - help.scss
- - [themes]:
- - - light.scss
- - - dark.scss
- - - blue.scss
after build:
[dist]:
- app.js
- index.html
- help$hash.js
- help$hash.html
- [css]:
- - app$hash.css
- - help$hash.css
- - [themes]:
- - - light.css
- - - dark.css
- - - blue.css
For Windows users
"scripts": {
"build": "npm run clean && webpack --mode production",
"clean": "del /f /s /q dist 1>nul"
}
I just had to stop my server and run yarn serve again

Use Webpack to split out a module so that it can be loaded in a WebWorker

I have a web app that I compile with webpack. One of the modules that my code uses is named table.js. Until recently, it's just been another module and has been compiled into my bundle.js file with everything else.
Now I need to run table.js in a Web Worker, so I need to pull it and its dependencies into a separate file that can be loaded both standalone and by my other modules.
At first I thought to include table.js in my webpack.config.js's entry.
var config = {
...
entry: {
app: [ './src/main.js', './src/classes/table.js' ],
vendors: [],
},
...
}
That didn't work. Then I thought to separate it out like my vendors bundle.
var config = {
/* for vendors (and other modules) we have a CDN for */
addExternal: function (name, globalVar) {
this.externals[name] = globalVar;
this.entry.vendors.push(name);
},
/* for vendors we don't have a CDN for */
addVendor: function (name, path) {
this.resolve.alias[name] = path;
this.entry.vendors.push(name);
},
addPlugin: function (plugin) {
this.plugins.push(plugin);
},
entry: {
app: [ './src/main.js' ],
vendors: [],
table: [ __dirname + '/src/classes/table.js' ]
},
plugins: [],
externals: { },
output: {
path: __dirname + '/public/dist/',
filename: 'bundle.js',
publicPath: '/dist/',
sourceMapFile: '[file].map'
},
resolve: {
alias: { 'table': './src/classes/table.js' },
extensions: [ '', '.js', '.jsx' ]
},
...
}
/* add vendors and externals */
...
config.addPlugin(new CommonsChunkPlugin('vendors', 'vendors.js'));
config.addPlugin(new CommonsChunkPlugin('table', 'table.js'));
This seems to pull Table and its dependencies into a chunk of bundle.js, 1.bundle.js. Unfortunately, then calling import Table from 'table' causes this error:
ERROR in CommonsChunkPlugin: While running in normal mode it's not allowed to use a non-entry chunk (table)
I also have a circular dependency between TableStore and Table. TableStore needs to stay in bundle.js because it shouldn't be loaded into the Web Worker. Previously, when I've needed to throw things into a separate chunk, I've done:
if (someThingNeedsRequiring) {
require.ensure([], () => {
require('something');
}
}
With the circular dependency, this doesn't seem to work.
/* table.js */
let _inWebWorker = self instanceof Window,
TableStore = null;
if (!_inWebWorker) {
require.ensure([], function() { TableStore = require('../stores/table-store'); } );
}
/* table-store.js */
import Table from 'table';
Could someone set me straight on the correct way to have my webpack.config.js and how to use my imports in my module files?
(It's been quite a while since I figured this out, and I haven't touched the project in nearly six months, so I may have missed some of the details. Comment if it's not working, and I'll try to figure out what I'm missing.)
webpack.config
It turns out there are two handy-dandy JavaScript packages for doing what I want: worker-loader and workerjs.
npm install --save workerjs worker-loader
I added this in my webpack.config.js:
var config = {
// ...
worker: {
output: {
filename: '[name].worker.js',
chunkFilename: '[name].worker.js'
}
},
// ...
}
require()
In order to specify that I want my class to be run in a WebWorker file, my require looks like:
// ecmaScript 6
import TableWorker from 'worker?name=tableRoller!path/to/table';
// ecmaScript 5
var TableWorker = require('worker?name=tableRoller!path/to/table');
TableWorker is just a variable name I used for table.js's export default class Table {...}. The name=tableRoller specifies the generated outputted [name].worker.js filename. For example, I have another WebWorker named distCalc.worker.js, so my import looks like:
import DistWorker from 'worker?name=distCalc!path/to/distWorker';
Note that in this case, distWorker only ever runs in a WebWorker, while Table is used in both my main.js entry point and my tableRoller.worker.js WebWorker file.
workerjs and worker-loader generate a new entry point file and pull in all of the dependencies of those classes. Tobias Koppers (worker-loader) and Eugene Ware (workerjs) are geniuses.
Detecting WebWorker
My _inWebWorker detection is:
let _inWebWorker = typeof Window === 'undefined';
Change output filename in your webpack.config.js file
output: {
path: __dirname + '/public/dist/',
filename: '[name].js',
publicPath: '/dist/',
sourceMapFile: '[file].map'
},
then Webpack can separate your entries with its name in dist directory.

Passing environment-dependent variables in webpack

I'm trying to convert an angular app from gulp to webpack. in gulp I use gulp-preprocess to replace some variables in the html page (e.g. database name) depending on the NODE_ENV. What is the best way of achieving a similar result with webpack?
There are two basic ways to achieve this.
DefinePlugin
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'development')
}),
Note that this will just replace the matches "as is". That's why the string is in the format it is. You could have a more complex structure, such as an object there but you get the idea.
EnvironmentPlugin
new webpack.EnvironmentPlugin(['NODE_ENV'])
EnvironmentPlugin uses DefinePlugin internally and maps the environment values to code through it. Terser syntax.
Alias
Alternatively you could consume configuration through an aliased module. From consumer side it would look like this:
var config = require('config');
Configuration itself could look like this:
resolve: {
alias: {
config: path.join(__dirname, 'config', process.env.NODE_ENV)
}
}
Let's say process.env.NODE_ENV is development. It would map into ./config/development.js then. The module it maps to can export configuration like this:
module.exports = {
testing: 'something',
...
};
Just another option, if you want to use only a cli interface, just use the define option of webpack. I add the following script in my package.json :
"build-production": "webpack -p --define process.env.NODE_ENV='\"production\"' --progress --colors"
So I just have to run npm run build-production.
I investigated a couple of options on how to set environment-specific variables and ended up with this:
I have 2 webpack configs currently:
webpack.production.config.js
new webpack.DefinePlugin({
'process.env':{
'NODE_ENV': JSON.stringify('production'),
'API_URL': JSON.stringify('http://localhost:8080/bands')
}
}),
webpack.config.js
new webpack.DefinePlugin({
'process.env':{
'NODE_ENV': JSON.stringify('development'),
'API_URL': JSON.stringify('http://10.10.10.10:8080/bands')
}
}),
In my code I get the value of API_URL in this (brief) way:
const apiUrl = process.env.API_URL;
EDIT 3rd of Nov, 2016
Webpack docs has an example: https://webpack.js.org/plugins/define-plugin/#usage
new webpack.DefinePlugin({
PRODUCTION: JSON.stringify(true),
VERSION: JSON.stringify("5fa3b9"),
BROWSER_SUPPORTS_HTML5: true,
TWO: "1+1",
"typeof window": JSON.stringify("object")
})
With ESLint you need to specifically allow undefined variables in code, if you have no-undef rule on. http://eslint.org/docs/rules/no-undef like this:
/*global TWO*/
console.log('Running App version ' + TWO);
EDIT 7th of Sep, 2017 (Create-React-App specific)
If you're not into configuring too much, check out Create-React-App: Create-React-App - Adding Custom Environment Variables. Under the hood CRA uses Webpack anyway.
You can pass environment variables without additional plugins using --env
Webpack 2-4
webpack --config webpack.config.js --env.foo=bar
Webpack 5+ (without.)
webpack --config webpack.config.js --env foo=bar
Then, use the variable in webpack.config.js:
module.exports = function(env) {
if (env.foo === 'bar') {
// do something
}
}
Further Reading: Webpack 2.0 doesn't support custom command line arguments?
#2254
You can directly use the EnvironmentPlugin available in webpack to have access to any environment variable during the transpilation.
You just have to declare the plugin in your webpack.config.js file:
var webpack = require('webpack');
module.exports = {
/* ... */
plugins: [
new webpack.EnvironmentPlugin(['NODE_ENV'])
]
};
Note that you must declare explicitly the name of the environment variables you want to use.
To add to the bunch of answers personally I prefer the following:
const webpack = require('webpack');
const prod = process.argv.indexOf('-p') !== -1;
module.exports = {
...
plugins: [
new webpack.DefinePlugin({
process: {
env: {
NODE_ENV: prod? `"production"`: '"development"'
}
}
}),
...
]
};
Using this there is no funky env variable or cross-platform problems (with env vars). All you do is run the normal webpack or webpack -p for dev or production respectively.
Reference: Github issue
Since my Edit on the above post by thevangelist wasn't approved, posting additional information.
If you want to pick value from package.json like a defined version number and access it through DefinePlugin inside Javascript.
{"version": "0.0.1"}
Then, Import package.json inside respective webpack.config, access the attribute using the import variable, then use the attribute in the DefinePlugin.
const PACKAGE = require('../package.json');
const _version = PACKAGE.version;//Picks the version number from package.json
For example certain configuration on webpack.config is using METADATA for DefinePlugin:
const METADATA = webpackMerge(commonConfig({env: ENV}).metadata, {
host: HOST,
port: PORT,
ENV: ENV,
HMR: HMR,
RELEASE_VERSION:_version//Version attribute retrieved from package.json
});
new DefinePlugin({
'ENV': JSON.stringify(METADATA.ENV),
'HMR': METADATA.HMR,
'process.env': {
'ENV': JSON.stringify(METADATA.ENV),
'NODE_ENV': JSON.stringify(METADATA.ENV),
'HMR': METADATA.HMR,
'VERSION': JSON.stringify(METADATA.RELEASE_VERSION)//Setting it for the Scripts usage.
}
}),
Access this inside any typescript file:
this.versionNumber = process.env.VERSION;
The smartest way would be like this:
// webpack.config.js
plugins: [
new webpack.DefinePlugin({
VERSION: JSON.stringify(require("./package.json").version)
})
]
Thanks to Ross Allen
Just another answer that is similar to #zer0chain's answer. However, with one distinction.
Setting webpack -p is sufficient.
It is the same as:
--define process.env.NODE_ENV="production"
And this is the same as
// webpack.config.js
const webpack = require('webpack');
module.exports = {
//...
plugins:[
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify('production')
})
]
};
So you may only need something like this in package.json Node file:
{
"name": "projectname",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"debug": "webpack -d",
"production": "webpack -p"
},
"author": "prosti",
"license": "ISC",
"dependencies": {
"webpack": "^2.2.1",
...
}
}
Just a few tips from the DefinePlugin:
The DefinePlugin allows you to create global constants which can be configured at compile time. This can be useful for allowing different behavior between development builds and release builds. For example, you might use a global constant to determine whether logging takes place; perhaps you perform logging in your development build but not in the release build. That's the sort of scenario the DefinePlugin facilitates.
That this is so you can check if you type webpack --help
Config options:
--config Path to the config file
[string] [default: webpack.config.js or webpackfile.js]
--env Enviroment passed to the config, when it is a function
Basic options:
--context The root directory for resolving entry point and stats
[string] [default: The current directory]
--entry The entry point [string]
--watch, -w Watch the filesystem for changes [boolean]
--debug Switch loaders to debug mode [boolean]
--devtool Enable devtool for better debugging experience (Example:
--devtool eval-cheap-module-source-map) [string]
-d shortcut for --debug --devtool eval-cheap-module-source-map
--output-pathinfo [boolean]
-p shortcut for --optimize-minimize --define
process.env.NODE_ENV="production"
[boolean]
--progress Print compilation progress in percentage [boolean]
I found the following solution to be easiest to setup environment variable for Webpack 2:
For example we have a webpack settings:
var webpack = require('webpack')
let webpackConfig = (env) => { // Passing envirmonment through
// function is important here
return {
entry: {
// entries
},
output: {
// outputs
},
plugins: [
// plugins
],
module: {
// modules
},
resolve: {
// resolves
}
}
};
module.exports = webpackConfig;
Add Environment Variable in Webpack:
plugins: [
new webpack.EnvironmentPlugin({
NODE_ENV: 'development',
}),
]
Define Plugin Variable and add it to plugins:
new webpack.DefinePlugin({
'NODE_ENV': JSON.stringify(env.NODE_ENV || 'development')
}),
Now when running webpack command, pass env.NODE_ENV as argument:
webpack --env.NODE_ENV=development
// OR
webpack --env.NODE_ENV development
Now you can access NODE_ENV variable anywhere in your code.
I prefer using .env file for different environment.
Use webpack.dev.config to copy env.dev to .env into root folder
Use webpack.prod.config to copy env.prod to .env
and in code
use
require('dotenv').config();
const API = process.env.API ## which will store the value from .env file
My workaround for the webpack version "webpack": "^4.29.6" is very simple.
//package.json
{
...
"scripts": {
"build": "webpack --mode production",
"start": "webpack-dev-server --open --mode development"
},
}
you can pass --mode parameter with your webpack commnad then in webpack.config.js
// webpack.config.json
module.exports = (env,argv) => {
return {
...
externals: {
// global app config object
config: JSON.stringify({
apiUrl: (argv.mode==="production") ? '/api' : 'localhost:3002/api'
})
}
}
And I use baseurl in my code like this
// my api service
import config from 'config';
console.log(config.apiUrl) // like fetch(`${config.apiUrl}/users/user-login`)
To add to the bunch of answers:
Use ExtendedDefinePlugin instead of DefinePlugin
npm install extended-define-webpack-plugin --save-dev.
ExtendedDefinePlugin is much simpler to use and is documented :-)
link
Because DefinePlugin lacks good documentation, I want to help out, by saying that it actually works like #DEFINE in c#.
#if (DEBUG)
Console.WriteLine("Debugging is enabled.");
#endif
Thus, if you want to understand how DefinePlugin works, read the c# #define doucmentation. link
Since Webpack v4, simply setting mode in your Webpack config will set the NODE_ENV for you (via DefinePlugin). Docs here.
Here is a way that has worked for me and has allowed me keep my environment variables DRY by reusing a json file.
const webpack = require('webpack');
let config = require('./settings.json');
if (__PROD__) {
config = require('./settings-prod.json');
}
const envVars = {};
Object.keys(config).forEach((key) => {
envVars[key] = JSON.stringify(config[key]);
});
new webpack.DefinePlugin({
'process.env': envVars
}),
dotenv-webpack
A secure webpack plugin that supports dotenv and other environment variables and only exposes what you choose and use.
with some workaround with configuration based on defaults option to achieve that, once the package has .env.defaults file to as initial values for env variables you can use it for development and let .env for your production.
Usage
install the package
npm install dotenv-webpack --save-dev
Create a .env.defaults file
API_URL='dev_url/api/'
create a .env file leave it empty, let defaults works, update it on your deploy process
config webpack - webpack.config.js
new Dotenv({
defaults: true
})
dev environement test file.js
console.log(process.env.API_URL)
// Outputs: dev_url/api/
on build, update empty .env file
API_URL='prod_url/api/'
dotenv-webpack will use this to and override env.defaults
prod environement test file.js
console.log(process.env.API_URL)
// Outputs: prod_url/api/
dotenv-webpack
dotenv-defaults
I'm not a huge fan of...
new webpack.DefinePlugin({
'process.env': envVars
}),
...as it does not provides any type of security. instead, you end up boosting your secret stuff, unless you add a webpack to gitignore 🤷‍♀️ there is a better solution.
Basically with this config once you compile your code all the process env variables will be removed from the entire code, there is not going to be a single process.env.VAR up thanks to the babel plugin transform-inline-environment-variables
PS if you do not want to end up with a whole bunch of undefines, make sure you call the env.js before webpack calls babel-loader, that's why it is the first thing webpack calls. the array of vars in babel.config.js file must match the object on env.js. now there is only one mow thing to do.
add a .env file put all your env variables there, the file must be at the root of the project or feel free to add it where ever u want, just make sure to set the same location on the env.js file and also add it to gitignore
const dotFiles = ['.env'].filter(Boolean);
if (existsSync(dotFiles)) {
require("dotenv-expand")(require("dotenv").config((dotFiles)));
}
If you want to see the whole babel + webpack + ts get it from heaw
https://github.com/EnetoJara/Node-typescript-babel-webpack.git
and same logic applies to react and all the other 💩
config
---webpack.js
---env.js
src
---source code world
.env
bunch of dotFiles
env.js
"use strict";
/***
I took the main idea from CRA, but mine is more cooler xD
*/
const {realpathSync, existsSync} = require('fs');
const {resolve, isAbsolute, delimiter} = require('path');
const NODE_ENV = process.env.NODE_ENV || "development";
const appDirectory = realpathSync(process.cwd());
if (typeof NODE_ENV !== "string") {
throw new Error("falle and stuff");
}
const dotFiles = ['.env'].filter(Boolean);
if (existsSync(dotFiles)) {
require("dotenv-expand")(require("dotenv").config((dotFiles)));
}
process.env.NODE_PATH = (process.env.NODE_PATH || "")
.split(delimiter)
.filter(folder => folder && isAbsolute(folder))
.map(folder => resolve(appDirectory, folder))
.join(delimiter);
const ENETO_APP = /^ENETO_APP_/i;
module.exports = (function () {
const raw = Object.keys ( process.env )
.filter ( key => ENETO_APP.test ( key ) )
.reduce ( ( env, key ) => {
env[ key ] = process.env[ key ];
return env;
},
{
BABEL_ENV: process.env.ENETO_APP_BABEL_ENV,
ENETO_APP_DB_NAME: process.env.ENETO_APP_DB_NAME,
ENETO_APP_DB_PASSWORD: process.env.ENETO_APP_DB_PASSWORD,
ENETO_APP_DB_USER: process.env.ENETO_APP_DB_USER,
GENERATE_SOURCEMAP: process.env.ENETO_APP_GENERATE_SOURCEMAP,
NODE_ENV: process.env.ENETO_APP_NODE_ENV,
PORT: process.env.ENETO_APP_PORT,
PUBLIC_URL: "/"
} );
const stringyField = {
"process.env": Object.keys(raw).reduce((env, key)=> {
env[key]=JSON.stringify(raw[key]);
return env;
},{}),
};
return {
raw, stringyField
}
})();
webpack file with no plugins troll
"use strict";
require("core-js");
require("./env.js");
const path = require("path");
const nodeExternals = require("webpack-node-externals");
module.exports = env => {
return {
devtool: "source-map",
entry: path.join(__dirname, '../src/dev.ts'),
externals: [nodeExternals()],
module: {
rules: [
{
exclude: /node_modules/,
test: /\.ts$/,
use: [
{
loader: "babel-loader",
},
{
loader: "ts-loader"
}
],
},
{
test: /\.(png|jpg|gif)$/,
use: [
{
loader: "file-loader",
},
],
},
],
},
node: {
__dirname: false,
__filename: false,
},
optimization: {
splitChunks: {
automaticNameDelimiter: "_",
cacheGroups: {
vendor: {
chunks: "initial",
minChunks: 2,
name: "vendor",
test: /[\\/]node_modules[\\/]/,
},
},
},
},
output: {
chunkFilename: "main.chunk.js",
filename: "name-bundle.js",
libraryTarget: "commonjs2",
},
plugins: [],
resolve: {
extensions: ['.ts', '.js']
} ,
target: "node"
};
};
babel.config.js
module.exports = api => {
api.cache(() => process.env.NODE_ENV);
return {
plugins: [
["#babel/plugin-proposal-decorators", { legacy: true }],
["#babel/plugin-transform-classes", {loose: true}],
["#babel/plugin-external-helpers"],
["#babel/plugin-transform-runtime"],
["#babel/plugin-transform-modules-commonjs"],
["transform-member-expression-literals"],
["transform-property-literals"],
["#babel/plugin-transform-reserved-words"],
["#babel/plugin-transform-property-mutators"],
["#babel/plugin-transform-arrow-functions"],
["#babel/plugin-transform-block-scoped-functions"],
[
"#babel/plugin-transform-async-to-generator",
{
method: "coroutine",
module: "bluebird",
},
],
["#babel/plugin-proposal-async-generator-functions"],
["#babel/plugin-transform-block-scoping"],
["#babel/plugin-transform-computed-properties"],
["#babel/plugin-transform-destructuring"],
["#babel/plugin-transform-duplicate-keys"],
["#babel/plugin-transform-for-of"],
["#babel/plugin-transform-function-name"],
["#babel/plugin-transform-literals"],
["#babel/plugin-transform-object-super"],
["#babel/plugin-transform-shorthand-properties"],
["#babel/plugin-transform-spread"],
["#babel/plugin-transform-template-literals"],
["#babel/plugin-transform-exponentiation-operator"],
["#babel/plugin-proposal-object-rest-spread"],
["#babel/plugin-proposal-do-expressions"],
["#babel/plugin-proposal-export-default-from"],
["#babel/plugin-proposal-export-namespace-from"],
["#babel/plugin-proposal-logical-assignment-operators"],
["#babel/plugin-proposal-throw-expressions"],
[
"transform-inline-environment-variables",
{
include: [
"ENETO_APP_PORT",
"ENETO_APP_NODE_ENV",
"ENETO_APP_BABEL_ENV",
"ENETO_APP_DB_NAME",
"ENETO_APP_DB_USER",
"ENETO_APP_DB_PASSWORD",
],
},
],
],
presets: [["#babel/preset-env",{
targets: {
node: "current",
esmodules: true
},
useBuiltIns: 'entry',
corejs: 2,
modules: "cjs"
}],"#babel/preset-typescript"],
};
};
now 2020, i am face to same question, but for this old question, there are so many new answer, just list some of it:
this is webpack.config.js
plugins: [
new HtmlWebpackPlugin({
// 1. title is the parameter, you can use in ejs template
templateParameters:{
title: JSON.stringify(someting: 'something'),
},
}),
//2. BUILT_AT is a parameter too. can use it.
new webpack.DefinePlugin({
BUILT_AT: webpack.DefinePlugin.runtimeValue(Date.now,"some"),
}),
//3. for webpack5, you can use global variable: __webpack_hash__
//new webpack.ExtendedAPIPlugin()
],
//4. this is not variable, this is module, so use 'import tt' to use it.
externals: {
'ex_title': JSON.stringify({
tt: 'eitentitle',
})
},
the 4 ways only basic, there are even more ways that i believe. but i think maybe this 4ways is the most simple.

Categories