Webpack Tips and Tricks
Rohan Büchner / March 23, 2017
READING TIME • ––– views
Over the last few months, I've been delving deeper into the world of Webpack.
Below I'll demonstrate a few cool tricks that I've learned that really isn't as obvious as they should be, which I believe any dev using Webpack will encounter at some stage or another.
> Context: I'm using Webpack for an Angular2 project, thus I'm using TypeScript, and some of the examples are pertaining to TypeScript-only solutions.
How to: Serve 3rd party JavaScript libraries
...that do not have TypeScript wrapped modules (yet)... if working in TypeScript
Install the 3rd party module via npm, then expose it via an alias:
webpack.conf.js:
module.exports = {
module: {
resolve: {
alias: {
moment$: path.join(__dirname, '../node_modules/moment')
}
}
}
}
Somewhere in your app
import * as moment from 'moment/moment';
How to: Import Bootstrap and FontAwesome.
There are 2 amazing open-source projects: bootstrap-loader and font-awesome-loader, both made by the ShakaCode team.
To implement them, install via npm, and add the following to your Webpack entry point:
webpack.conf.js:
entry: {
vendor: [
'font-awesome-loader',
bootstrapEntryPoints.dev // <=== bootstrapEntryPoints.helper.js
]
}
Then setup a helper service or point to the config path manually:
bootstrapEntryPoints.helper.js:
'use strict';
require('dotenv').config();
const fs = require('fs');
function getBootstraprcCustomLocation() {
return process.env.BOOTSTRAPRC_LOCATION;
}
const bootstraprcCustomLocation = getBootstraprcCustomLocation();
let defaultBootstraprcFileExists;
try {
fs.statSync('./.bootstraprc');
defaultBootstraprcFileExists = true;
} catch (e) {
defaultBootstraprcFileExists = false;
}
if (!bootstraprcCustomLocation && !defaultBootstraprcFileExists) {
/* eslint no-console: 0 */
console.log('You did not specify a \'bootstraprc-location\' ' +
'arg or a ./.boostraprc file in the root.');
console.log('Using the bootstrap-loader default configuration.');
}
// DEV and PROD have slightly different configurations
let bootstrapEntryPoint;
if (bootstraprcCustomLocation) {
bootstrapDevEntryPoint = 'bootstrap-loader/lib/bootstrap.loader?' +
`configFilePath=${__dirname}/${bootstraprcCustomLocation}` +
'!bootstrap-loader/no-op.js'; // for prod add loader?extractStyles
} else {
bootstrapDevEntryPoint = 'bootstrap-loader'; // for prod replace with 'bootstrap-loader/extractStyles'
}
module.exports = {
dev: bootstrapEntryPoint
};
...and add a .bootstraprc
file. This will allow you to manually configure and cherry-pick features for your bootstrap build.
.bootstraprc:
--------------------------------------
# Output debugging info
# loglevel: debug
# Major version of Bootstrap: 3 or 4
bootstrapVersion: 3
# If Bootstrap version 3 is used - turn on/off custom icon font path
useCustomIconFontPath: false
# Webpack loaders, order matters
styleLoaders:
- style-loader
- css-loader
- sass-loader
# Extract styles to stand-alone css file
# Different settings for different environments can be used,
# It depends on value of NODE_ENV environment variable
# This param can also be set in webpack config:
# entry: 'bootstrap-loader/extractStyles'
# extractStyles: false
# env:
# development:
# extractStyles: false
# production:
# extractStyles: true
# Customize Bootstrap variables that get imported before the original Bootstrap variables.
# Thus original Bootstrap variables can depend on values from here. All the bootstrap
# variables are configured with !default, and thus, if you define the variable here, then
# that value is used, rather than the default. However, many bootstrap variables are derived
# from other bootstrap variables, and thus, you want to set this up before we load the
# official bootstrap versions.
# For example, _variables.scss contains:
# $input-color: $gray !default;
# This means you can define $input-color before we load _variables.scss
# preBootstrapCustomizations: ./src/scss/bootstrap/pre-customizations.scss
# This gets loaded after bootstrap/variables is loaded and before bootstrap is loaded.
# A good example of this is when you want to override a bootstrap variable to be based
# on the default value of bootstrap. This is pretty specialized case. Thus, you normally
# just override bootrap variables in preBootstrapCustomizations so that derived
# variables will use your definition.
#
# For example, in _variables.scss:
# $input-height: (($font-size-base * $line-height) + ($input-padding-y * 2) + ($border-width * 2)) !default;
# This means that you could define this yourself in preBootstrapCustomizations. Or you can do
# this in bootstrapCustomizations to make the input height 10% bigger than the default calculation.
# Thus you can leverage the default calculations.
# $input-height: $input-height * 1.10;
# bootstrapCustomizations: ./src/scss/bootstrap/customizations.scss
# Import your custom styles here. You have access to all the bootstrap variables. If you require
# your sass files separately, you will not have access to the bootstrap variables, mixins, clases, etc.
# Usually this endpoint-file contains list of @imports of your application styles.
appStyles:
# ./src/scss/_custom.scss
### Bootstrap styles
styles:
# Mixins
mixins: true
# Reset and dependencies
normalize: true
print: false
glyphicons: true
# Core CSS
scaffolding: true
type: true
code: true
grid: true
tables: true
forms: true
buttons: true
# Components
component-animations: true
dropdowns: true
button-groups: true
input-groups: true
navs: true
navbar: true
breadcrumbs: true
pagination: true
pager: true
labels: true
badges: true
jumbotron: true
thumbnails: true
alerts: true
progress-bars: true
media: true
list-group: true
panels: true
wells: true
responsive-embed: true
close: true
# Components w/ JavaScript
modals: true
tooltip: true
popovers: true
carousel: true
# Utility classes
utilities: true
responsive-utilities: true
### Bootstrap scripts
scripts:
transition: true
alert: true
button: true
carousel: true
collapse: true
dropdown: true
modal: true
tooltip: true
popover: true
scrollspy: true
tab: true
affix: true
How to: Pass environmental variables.
like API paths, or environment-specific static properties
Webpack had various ways of doing this but the route that worked the best for me was to have each environment have its own .env file, and have webpack map the properties inside the .env to its appropriate property within the app.
webpack.conf.js:
// npm install dotenv --save-dev
// dotenv is a super simple way to access .env.local files
require('dotenv').config();
new webpack.DefinePlugin({
"process.env": {
"KEY": process.env.KEY, // <--- process.env in this instance does not equal the runtime process.env of the app.
"FOO": { // think of webpack and the running application of having 2 seperate contexes
"BAR": "yolo",
"BOOLEAN_PROPERTY": true
}
}
})
How to: Make your build faster.
Once your solution becomes more sizable you might notice that it can get a bit slower from time to time, especially when using something like TypeScript. Below are a few options that you can make use of, depending on your use case.
Use HappyPack to multithread certain loaders.
webpack.conf.js:
var HappyPack = require('happypack');
var happyThreadPool = HappyPack.ThreadPool({size: 5});
module.exports = {
module: {
loaders: [
{
test: /.html$/,
use: ['happypack/loader?id=html'], // <---- loader?id=BUNDLE_ID
exclude: /node_modules/
}
],
plugins: [
new HappyPack({
id: 'html', // <----- BUNDLE_ID
threadPool: happyThreadPool,
loaders: ['html-loader'] // loaders for this specific bundle
})
],
}
}
For TypeScript:
- Have specific dev build options to toggle source maps on or off (depending on the task at hand)
webpack.conf.js:
// eval === fast, but not very useful, source-maps === useful, but slower (in larger projects)
devtool: process.env.ENVIRONMENT === 'dev' ? 'eval' : 'source-maps'
- Set transpileOnly to true. If you want to speed up compilation a lot you can use this option, but you end up losing a lot of benefits. docs
webpack.conf.js:
loaders: [{
test: /\.ts?$/,
loader: 'ts-loader?' + JSON.stringify({
transpileOnly: true
})
}]