webpack
Created: 2016-01-24 19:02:21 -0800 Modified: 2019-05-17 08:55:21 -0700
Serving via HTTPS
Section titled Serving via HTTPSYou only have to add “—https” so that your command looks like this:
webpack-dev-server —hot —host 0.0.0.0 —https
Debugging webpack
Section titled Debugging webpacknode —inspect-brk .node_moduleswebpackbinwebpack.js
Note: this should be obvious, but if you run “spawn” on a process and want to debug that, you’ll need to change the arguments to the spawned process.
Upgrading from version 3 to version 4 (reference)
Section titled Upgrading from version 3 to version 4 (reference)Twitch chat advised not upgrading yet (and that it could potentially take a while):
9:03 freaktechnik: the annoying bit is that all the plugins changed and some loaders need to change and that chunk splitting is totally different.
9:04 HiDeoo: You’ll spend all day with your gulp thing, plugins not updated, etc. But the release is awesome performance wise if you have time for it
Conclusions: when I finally upgrade, I’ll mostly get performance improvements.
Hot module replacement (HMR)
Section titled Hot module replacement (HMR)Just explicitly putting a link to the other note I have on this here.
Scope-hoisting (announcement blog post, readme reference)
Section titled Scope-hoisting (announcement blog post, readme reference)Scope-hoisting removes the needs to define function closures for each module and instead allows a single function closure for basically everything. This can result in faster execution times.
I’m writing this note to talk about what I did with Bot Land here to see if this would have any impact.
First, I had to disable module transpilation by Babel by adding this to my Babel configuration:
Next, I added this plug-in to Webpack:
new webpack.optimize.ModuleConcatenationPlugin(),
Finally, I changed how babel-polyfill used to be an entrypoint since I wasn’t sure what impact that would have.
To test the performance impact, I used Chrome’s Performance tab:
- Press “Record”
- Refresh the page
- As soon as the login page renders, stop the recording.
- Click “Bottom-Up”
- Expand “Compile Script”
- Mark down the “Evaluate Script” time
I also looked through bundle.js for “CONCATENATED MODULE” to see how many modules were actually impacted and it wasn’t a ton (~120). I had almost no performance impact here. HiDeoo commented on how I’ll hopefully have more of an improvement later:
One larger impact that you’ll see in a few month is the Webpack 4 upgrade.
Now, you’re no longer transpiling modules to commonJS, which is good as you can scope hoist and also webpack can tree shake. But in the current state, the tree shake is not the best as it most of the time doesn’t know if your import have side effects or not, and when it can’t, it cannot tree shake properly. For example, webpack can’t tree shake Lodash and import all of Lodash as it doesn’t know if it has side effects or not.
Webpack 4 brings support for dep author to say that their module don’t have side effect in their package.json, so for free a while after the release of Webpack 4 when dep author start to say if their module have side effect or not, you’ll get a HUGE diff in bundle size.
Example: Look at the bundle size for these 2 lines of code in Webpack 4 (pure module column is just an updated version of Lodash (already out) saying it doesn’t have side effect) https://pbs.twimg.com/media/DGT2DdnUAAEXxTu.jpg
I would also need to do this for my own modules (e.g. @botland/foo) by putting “sideEffects: false” in the package.json.
Writing a plug-in (reference)
Section titled Writing a plug-in (reference)I only did this one for Bot Land in the form of CreateAssetMapPlugin because I needed to perform a step in between when Webpack produced the bundled JavaScript and when Webpack tried converting from Pug to HTML.
I mostly wanted to write down the official guide here and mention that practically everything is available to you from a plug-in, so you have a lot of options to manipulate Webpack’s behavior via plug-ins.
Define plug-in (i.e. only running code when a variable is true/false)
Section titled Define plug-in (i.e. only running code when a variable is true/false)Just add a DefinePlugin that does something like this:
Then, from client code, you can do something like this:
Webpack for a server
Section titled Webpack for a serverAt the time of writing, I’m not actually using Webpack for a server. I wanted to write some notes about it in case I ever did want to use Webpack for this.
For one, all dependencies either need to be “required” directly into the target bundle file or manifested as “externals” (reference). Anything that’s an external needs to provided later (e.g. in nodemodules), which means that you would need both the resulting bundle.js file _and an “npm install” command afterward. If you don’t use externals, then it means that you have to run a Webpack build on your dependencies in order for them to be bundle-able into your code.
If you want to just ignore everything in node_modules to begin with (since you can “npm install” those), you can use something like this plug-in:
10:47 hebelebettin: @Adam13531 https://www.npmjs.com/package/webpack-node-externals
10:49 hebelebettin: i recommended node-externals for the require(“crypto”) thing
10:49 hebelebettin: you’ll need to also wrestle with require(“color”)
I think if you’re just going to ignore everything in node_modules, then your goal probably wasn’t to produce a single bundle file. Rather, it would likely have been to make use of the other Webpack features like plug-ins, resolves directories, etc.
Also note: the actual way that I manifested the externals (not that I ended up following through on the rest of their usage) was like this:
externals: {
// https://github.com/tgriesser/knex/issues/1128#issuecomment-312735118
knex: ‘commonjs knex’,
color: ‘commonjs color’,
// escodegen: ‘escodegen’,
},
The particular problem I had was about the externals stuff above. I wanted to bundle my server, but it used @botland/shared (which had “color” as a dependency), and @botland/dbwrapper (which had “knex” as a dependency). The server itself didn’t manifest those dependencies and I didn’t understand how externals worked at the time, so I got this advice:
10:52 Nayni: Like, you gotta build @botland/shared and when u locally publish it, you have to refer to the built bundle instead of the source files. So that way “color” will be packed inside the bundle from the shared lib
10:53 hebelebettin: modules that use dynamic require()s and .node binaries
10:53 hebelebettin: can’t be bundled up
Part of the reason I never ended up doing any of this is because it didn’t really solve a problem for me. All I would have had was a bundle.js file that I could deploy with no extra dependencies (which is appealing, but not worth struggling so much for).
Multiple configs and merging
Section titled Multiple configs and mergingHiDeoo and EJ provided some resources for this. At the time of writing this note, I haven’t actually made use of this in a codebase, but I think it may be helpful for the future.
HiDeoo: Adam13531 A portion of the current configs of the project I work on today if you want to see some merging (hope I didn’t leak anything, but don’t think so Kappa ) It also start with a workaround for Atom Kappa https://gist.github.com/HiDeoo/6b4b9009bec8e47b5261bba63c7196d3
8:59 HiDeoo: Adam13531 import merge from ‘webpack-merge’
9:00 HiDeoo: Usually I run base, then dev, test & prod and if I have client & server it’s base, server-dev, server-test, server-prod, etc.
9:03 HiDeoo: Adam13531 Last example is even server + client + prod + base
9:03 HiDeoo: It use merge.multiple Kreygasm
9:34 HiDeoo: Adam13531 With multiple.merge you can have the 4 basic like base, dev, test, prod and if some endpoint needs additional, you just add the one you need
9:34 HiDeoo: merge.multiple *
Here’s the babelrc that HiDeoo suggested so that I don’t have to cram it into the Webpack config:
{
“presets”: [
[“env”, {
“targets”: {
“electron”: “1.7”
}
}],
“stage-2”,
“react”
],
“plugins”: [
[“lodash”, { “id”: [“lodash”, “recompose”] }],
“styled-components”
],
“env”: {
“development”: {
“plugins”: [
“react-hot-loader/babel”,
“transform-react-jsx-self”,
“transform-react-jsx-source”,
[“babel-plugin-webpack-alias”, { “config”: “./webpack.config.base.js” }]
]
},
“production”: {
“plugins”: [
“transform-react-inline-elements”,
“transform-react-constant-elements”,
“transform-react-remove-prop-types”,
“transform-react-pure-class-to-function”
]
},
“test”: {
“plugins”: [
[“babel-plugin-webpack-alias”, { “config”: “./webpack.config.base.js” }]
]
}
}
}
Providing global modules (reference)
Section titled Providing global modules (reference)For something like Blockly or Jquery that you expect to be in the global scope, you can have Webpack set that up for you by using the ProvidePlugin
Add this to your config:
new webpack.ProvidePlugin({
Blockly: ‘node-blockly/browser-raw’ // point at the Node module to load
})
Then, from your client code, “Blockly.whatever” will just magically work.
Aliases
Section titled AliasesI ran into a problem where I was using a library (let’s call it Foo) that required intro.js, but I’d forked intro.js into @botland/intro.js.
Option A was really heavy-handed: fork Foo, change all imports and package.json to point at my version of intro.js.
Option B was to use Webpack aliases:
alias: {
‘intro.js’: path.resolve(__dirname, ‘node_modules/@botland/intro.js/’),
}
Option C: filesystem symlinks
General notes
Section titled General notesTo run:
webpack —progress —colors —display-error-details
Provide “—watch” for the builder to stay open and rerun itself automatically on file modifications.
If you get an error about “Error: Cannot resolve module ‘babel’ in C:pathtofolder”, then it means that webpack and babel-loader aren’t installed in the correct directory. In the above, it would search “folder” and upward for the appropriate node_modules folders, NOT the directory that you started webpack from. To fix this, just install webpack and babel-loader in the “path” or “to” folders (as named in the example above).
If you get an error about parsing package.json, then it’s likely that you need a JSON loader. For example, escodegen needed this:
- npm install —save-dev json-loader (this needs to be installed from the directory containing escodegen)
- Then, no need to require anything from webpack.config.js, just add this to your loaders array:
{
test: /\json$/, // this is the regex that needs to match to use this loader
loader: ‘json’
}
I got a runtime error where including a module multiple times would not return the same results. For example, I had this code:
// main.js
import values from ‘./values’;
import ‘./modifyvalues’;
window.main = {};
window.main.start = function() {
console.log(‘hello world’);
console.log(‘values: ’ + JSON.stringify(values));
};
// modifyvalues.js
import values from ‘./values’;
values.push(4);
values.push(5);
// values.js
const values = [1,2,3];
export default values;
I expected the above to print “[1,2,3,4,5]”, but it prints “[1,2,3]” with standard webpack.
To fix this:
- Install https://www.npmjs.com/package/single-module-instance-webpack-plugin
- Put this in your webpack.config.js
- var SingleModuleInstancePlugin = require(‘single-module-instance-webpack-plugin’);
- Then push that to your plugins that you pass to config. For example:
- plugins = [];
- plugins.push(new SingleModuleInstancePlugin());
- var config = {
plugins: plugins
};
Sourcemaps
Section titled SourcemapsSimply put this line in your config:
devtool: ‘cheap-source-map’
cheap-source-map
By specifying ‘cheap-source-map’, you’ll get transpiled code in the browser when you look at your code, but you’ll have the original filenames at least.
cheap-module-source-map
This option will look like ES6, but it takes longer than cheap-source-map.
source-map
This option is the “real deal” for source maps so that you have a 1:1 mapping of code that you wrote to the mangled output. It takes forever.
Sample build times from my limited testing:
No source maps | cheap | cheap-module | source-map |
---|---|---|---|
~4-5 seconds | ~6-7 seconds | ~8-9 seconds | ~17 seconds |
Troubleshooting
Section titled TroubleshootingRandom build failure due to npm link
Section titled Random build failure due to npm linkIf you get an issue
Module build failed: ReferenceError: Unknown plugin “syntax-dynamic-import” specified in “base” at 1, attempted to resolve relative to “D:\Code\BotLand\botland\packages\shared\lib”
This could be due to having symlinks be resolved to their symlinked location, which ends up giving you a relative path. This makes it so that we end up looking in THAT path for any node_modules rather than the caller’s path.
An easy way to fix this is to just turn off symlink resolution (reference):
You can probably also fix this by using some other part of the “resolve” object, but I couldn’t figure that out.
^3:21 HiDeoo: We usually go with something like that https://bpaste.net/show/4f4def90c50f so that local & root node_modules is the preferred option
Extending ES6 classes
Section titled Extending ES6 classes11/21/2017 - update: I filed a bunch of notes here about how I handled this more recently for Bot Land.
3/18/2016 - I ran into an issue where the UglifyJS plug-in couldn’t handle ES6 from an imported node_module:
ERROR in bundle.min.js from UglifyJs
Unexpected token: name (DefaultLogger) [../shared/client_and_game_server/~/botland-logger/index.js:48,0]
The token that it choked on was “class DefaultLogger”; it didn’t understand non-ES5 code.
The issue is that I was running my node_modules folder through the Babel loader (see bolded part below):
module: {
loaders: [{
test: /\js$/, // this is the regex that needs to match to use this loader
loader: ‘babel’,
query: {
// This will cache results of the loader so that future builds can
// avoid potentially expensive Babel recompilations.
//
// Note: I believe this is for subsequent runs of webpack, not
// incremental builds using “—watch”.
cacheDirectory: true,
presets: [‘react’, ‘es2015’]
},
exclude: /node_modules|/
},
{
test: /\json$/, // this is the regex that needs to match to use this loader
loader: ‘json’
}]
},
There are two problems with just changing the exclude/include filters:
- Every time you run Webpack, you will pay the cost of transpiling the code.
- Every time you require another node_module that uses ES6 code, you’ll have to modify your Webpack config.
If you’re trying to require a server-specific module like fs or cluster, you’ll need to either provide it yourself (in which case there’s some configuration you have to do for Webpack using ‘external’) or say that it’s an empty module for the sake of the client (meaning you shouldn’t try to USE the module on the client):
node: {
// Doing require(‘cluster’) will provide an empty module as it doesn’t
// make sense to have the cluster module at all on the client anyway.
cluster: ‘empty’
},
The real solution is to publish modules in such a way that they can be consumed by anyone. There are several good answers about this on StackOverflow (reference). Here are the end-to-end steps to go from an ES6 module to an ES5-friendly module:
- Make a “src” folder and throw all of your code into there. Note that if you had a folder at the top level before (e.g. “setuplogging/index.js”) you probably don’t want to put that directly into src without making changes, otherwise consumers of your module will have to do require(‘module/lib/setuplogging’). Instead, you can put a “setuplogging.js” at the top-level (that would not need to be transpiled) that simply says module.exports = require(‘./lib/setuplogging’);
- Make a “.npmignore” file with “src” in it so that your ES6 source won’t be published.
- Update “.gitignore” to include “lib” so that your ES5 code won’t be checked in.
- Install babel and its dependencies
- npm install —save-dev babel-core babel-cli babel-preset-es2015
- Update package.json:
- Point at your ES5 main file:
- “main”: “lib/index.js”
- Add in a prepublish script
- Point at your ES5 main file:
“scripts”: {
“prepublish”: “node node_modules/babel-cli/bin/babel.js src —out-dir lib”
},
Note: this is automatically run when you do “npm publish”, but if you want to manually run it, do “npm run prepublish”.
- Add a babel configuration so that you don’t need a “.babelrc” file
“babel”: {
“presets”: [“es2015”]
},
Any issues about webpackjsonpbotland have to do with a missing bundle, e.g. vendor or manifest.
UglifyJS “name expected”
Section titled UglifyJS “name expected”If you get an error that looks like this
Error in webpack vendor.min.js from UglifyJs
Name expected [vendor.min.js:176090,8]
[15:31:53.52] An error occurred.
Check to make sure you’re not using the dependency from Webpack’s uglifyjs-webpack-plugin. You can do so by just running this command and then Webpack again: npm install —save-dev uglifyjs-webpack-plugin
If that works (and it’ll only work for Webpack 3, not Webpack 4), then you’re all set.