How to exclude code from production in build-time? - javascript

How do I exclude typescript code from webpack bundle during build-time?
For example, I have this line of code in app.ts (nodejs application):
const thisShouldNotBeInProductionBundleJustInDevBundle = 'aaaaaaa';
I want when I build my app using webpack configuration to exclude this code.

In webpack version 4 you are able to set mode: 'production' in your webpack config. (https://webpack.js.org/concepts/mode/)
So in your source code you can use the following:
if (process.env.NODE_ENV === 'development') {
const thisShouldNotBeInProductionBundleJustInDevBundle = 'aaaaaaa';
...
}
In conclusion all code inside if and ifs themself will be automatically removed during building your bundle

Webpack has a mode setting, which allows you to switch between development and production builds.
In your code you can use process.env.NODE_ENV to find out wether you are in production or not, Webpack uses that property to eliminate "production dead code":
// declare variable everywhere to prevent unresolvable variable references
let onlyInDev = "";
// The following should be shaken away by webpack
if(process.env.NODE_ENV === "development") {
onlyInDev = "test";
}
If the value is a sensitive information that should not be leaked to your production build, I would search for it in the bundle to make sure that it doesn't get leaked if the building pipeline changes.

Related

Quasar - Support multiple environments

I’m trying to create project with multiple environments: staging, prod, test + development obviously.
With Vuejs that’s pretty straightforward
vue-cli-service build --mode staging
And create .env.staging file.
Important note that process.env should be accessed from quasar.conf file in order to set different publicPath for each env.
How can I achieve this behavior at Quasar?
Thanks
Check out the #quasar/qenv extension it seems to support multiple environments. https://github.com/quasarframework/app-extension-qenv
Another approach, as I am using Firebase Hosting and both staging and production hosting is in the same project ... I had the idea of using quasar build -d (debug mode) as staging and quasar build as production, each with its own dist folder, and I set a DEPLOY_MODE env variable in quasar.conf.js accordingly:
build: {
distDir: ctx.debug ? `dist/${ctx.modeName}-dev` : `dist/${ctx.modeName}`,
env: {
DEPLOY_MODE: ctx.prod && !ctx.debug ? 'PRODUCTION' : (ctx.prod && ctx.debug ? 'STAGING' : 'DEV')
},
...
I used the dotenv extension and have an .env.prod and .env.dev file. Within .env.prod I define the production and staging specific keys, e.g. API_KEY=blah, API_STAGING_KEY=blah, then in my boot file, I use process.env.DEPLOY_MODE to determine which keys to use in my
production env file.
if(process.env.DEPLOY_MODE === 'staging') {
const API_KEY = process.env.API_STAGING_KEY
} else {
const API_KEY = process.env.API_KEY
}
I suspect #qenv is more elegant but this was enough for my simple project.

Does webpack remove this type of code automatically?

This is a good helper for development mode, but does this code get removed from bundles or is a particular plugin needed to remove it? Seems like it would have to be removed since process is not available in the browser. What setting causes this to happen?
The webpack docs aren't really all that clear about what these options do, just giving vague references:
https://webpack.js.org/concepts/targets/
and
https://webpack.js.org/concepts/output/
if (process.env.NODE_ENV !== 'production') {
if (typeof nextValue === 'undefined') {
console.info(next);
throw new Error('React Table: A reducer hook ☝️ just returned undefined! This is not allowed.');
}
}
process.env.NODE_ENV is actually available in the browser because Webpack creates the process variable as a global in code that is output. It is controlled via the Webpack configuration mode.
You can set the mode in your config or the command line. If you use enivornment based Webpack configs (ie. webpack.dev.js, webpack.prod.js) they will automatically set the mode.
webpack --mode=production

How to run custom config file in node.js project

In a node.js project using express framework, usually config folder has 3 files:
config.json - runs in prod env
development.json - runs in dev env
staging.json - runs in stage env
I want to create another config file called example.json and run local dev environment using example.json instead of development.json when ever the code is run with a special environment variable process.env.LOCAL_PROD.
Is there a way to do this?
You can do this with node-config and I highly recommend it as it allows for multiple config files and even hostname based config files. It also supports multiple formats including .js configs not just .json, you can read more about the various setups for configuration files in the node-config wiki. I use it in almost all of my applications that require some configuration due to how easy it is to use.
Now if you don't want to use a package to handle this, then you can do it with just using require() since it can read and parse .json files as well as .js. The common environment variable to use when setting what environment the application is running within is named NODE_ENV.
let config
let env = process.env.NODE_ENV.toLowerCase()
if (env === 'production') {
config = require('./config.json')
} else if (env === 'staging') {
config = require('./staging.json')
} else if (env === 'dev') {
config = require('./development.json')
} else {
console.log('No configurable NODE_ENV detected, no config loaded')
}

What are the differences between webpack development and production build modes?

In Grunt or Gulp, I used to define all the requirements myself, like: stuff should be minified only for production, livereload should be enabled only in dev server.
Webpack handles this on its own, via its -d and -p options, that toggle the loaders' minimize mode (most loaders ship with their relevant minifiers), the devtool and similar stuff (I don't know what exactly). Most of that "just works".
But on the other hand, there are some libraries that have a development and production mode. For example, React looks at process.NODE_ENV, and if it's production, it disables the propTypes checking (which will be later stripped off by minifier as dead code, thus reducing bundle size). In Webpack, a common way to do this is to use the DefinePlugin.
The problem is, that this plugin should be enabled only in the production build. Some people go as far as having 2 separate webpack configs because of that. This seems overkill, since most of the job is already done by webpack. I'd like to avoid this.
To find some better solution, I'd like to understand what exactly changes when I use the -d or -p option, and how does it affect all the loaders and plugins. I didn't find it documented anywhere. The existing documentation just mentions "debug mode" or "watch mode" without explaining what does it actually mean.
Note that I'm not asking for a do-this-and-that answer. A good, detailed explanation would be appreciated.
Shortcuts
Like you said, -d is a shortcut for --debug --devtool source-map --output-pathinfo where:
--debug activates debug mode for loaders and its behaviour depends on each loader. You can activate it via param { debug: true } in your webpack.config.js
--devtool is basically allow to select type of sourcemaps for your files via param { devtool: "sourcemap" } or if you use webpack#2: { devtool: 'eval-cheap-module-source-map' }(See all options)
--output-pathinfo will write original filepath into webpack's requires like this: __webpack_require__(/* ./src/containers/App/App.js */15). You can activate it via: { output: { pathinfo: true } }
Second shortcut, -p stands for --optimize-minimize --optimize-occurence-order where:
--optimize-minimize will add webpack.optimize.UglifyJsPlugin into your module plugins which will minify your code (remove spaces, mangle variable names and other optimizations)
--optimize-occurence-order will add webpack.optimize.OccurenceOrderPlugin to plugin list which will assign your most used dependencies lower ids, so code will be smaller.
For example: you have imported react in every file and webpack will try to require it like that __webpack_require__(1); instead of __webpack_require__(1266);
Putting all together
So, in your case if you have webpack.config.js, you can change it like this:
var webpack = require('webpack');
var webpackConfig = {
// here goes your base config
};
// -d shortcut analogue
if (process.env.NODE_ENV === 'development') {
webpackConfig.debug = true;
webpackConfig.devtool = 'sourcemap';
webpackConfig.output.pathinfo = true;
}
// -p shortcut analogue
if (process.env.NODE_ENV === 'production') {
webpackConfig.plugins.push(new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify('production')
}));
webpackConfig.plugins.push(new webpack.optimize.UglifyJsPlugin());
webpackConfig.plugins.push(new webpack.optimize.OccurenceOrderPlugin());
}
module.exports = webpackConfig;
If you want to look at actual parsing of these params, look at https://github.com/webpack/webpack/blob/master/bin/convert-argv.js#L19

How to bundle isomorphic commonJS code with webpack

I have a project that uses nodeJS module format (commonJS) and should also (in parts) run in the browser.
I do have non-isomorphic code paths where I conditionally include modules:
var impl;
try {
// in node, use node-impl
impl = require('../node-impl');
} catch (err) {
// running in browser, use browser-impl
impl = require('../browser-impl');
}
Now, I want to use webpack to create a bundle that runs in the browser. I therefore need to defined the external (nodeJS specific) modules as external in the webpack.config.js so that they don't get included in the bundle:
external: {
'../node-impl': true
}
I verified that the '../node-impl' code is actually not included in bundle, but the emitted code looks like this:
/***/ },
/* 33 */
/***/ function(module, exports) {
module.exports = ../node-impl;
/***/ },
This is syntactically wrong JS and the browser will throw a syntax error there.
How is this scenario properly handled with webpack.js? Be aware that I do not wish to use webpack for running with nodeJS, only the browser bundles should be created with webpack.
// Your actual situation:
var impl;
try {
impl = require('../node-impl');
} catch(e) {
impl = require('../browser-impl');
}
You need to refactor this snippet to:
var impl = require('../node-impl');
After this rework, your code is able to work only in a node js environment, that's is good because we are going to mock this request when bundling for browsers...
// webpack.browser.config.js
module.exports = {
resolve: {
alias: {
'../node-impl': '../browser-impl'
}
}
};
Webpack - Resolve.Alias
Or using a package.json#browser, or https://webpack.github.io/docs/configuration.html#resolve-packagealias
I don't think this is the intended purpose of the externals config. Per the docs,
Specify dependencies that shouldn’t be resolved by webpack, but should become dependencies of the resulting bundle. The kind of the dependency depends on output.libraryTarget.
So you're telling webpack that your build requires that module, but not to bundle it in. It's leaving it out, but attempting to require it.
There are probably a couple of ways to do what you want. Worth mentioning that you can easily have webpack produce multiple builds, with different/shared config, from a single config file, which opens up a lot of possibilities. But the way I'd suggest is to use the DefinePlugin to define a boolean 'constant' that represents the execution context (e.g. IN_BROWSER = true). Check that constant in your conditional around the require(). Webpack's parser isn't that smart but it can evaluate boolean variables, so it will resolve the conditional correctly and only require the appropriate module. (Using a non-boolean, like CONTEXT = 'browser' is too confusing for webpack, and it will parse each require statement.) You can then use the Uglify plugin to remove the 'dead code' in the conditional so it doesn't bloat your production build.
With the help of #Hitmands I could come up with a solution that is still not perfect, but fits my needs. I introduce a fictious nonexistingmodule and declare it as external; I then declare the node-impl specific modules to be available as nonexistingmodule:
externals: {
'nonexistingmodule': true,
'../node-impl': 'nonexistingmodule'
}
This way I can keep the try/catch pattern to load specific implementations and it will still run on node. In the browser the loading of nonexistingmodule fails, and the browser-impl module is loaded - just as I intended.
I am a big fan of "don't refactor your code to match a tool", so I am going with this solution.

Categories

Resources