Setting Up A Modern Webdev Toolchain
This guide has seen a few revisions, originally it only covered babel and webpack, I've since expanded it to include more tools
These tools, and a few others, can help simplify your workflow while giving you a lot of power over how to manage your code.
Let's create a project, and install Webpack and for the sake of example use jquery as a dependency.
Create a simple file at
Now let's use Webpack to compile it, and check the output:
npx webpackis not available, ensure you installed the
./node_modules/.bin/webpack, or upgrade to a newer version of
Note the difference when using production mode:
Webpack starts with it's given entrypoint[s], and parses for any
require tokens that point to other files or packages, and then recursively works through those files to build up a dependency tree it calls a manifest.
These two commands alone can be very helpful on their own, and the Webpack CLI has many options to alter it's behavior, you can read them with
npx webpack --help or on their website.
Now let's add Babel into the mix!
A small config for Babel:
And now we need to make a small Webpack configuration to tell Webpack how to use Babel:
Now let's make a new smaller test case just to test Babel
And build it one more time and check the output
Note that Webpack has a small overhead in the bundle size, just under 1KB.
Webpack uses a default input file, or "entry point", of
src/index.js, which we can override:
Changing the output path isn't much different:
Renaming the file is also relatively straight-forward:
We can also introduce "automagic" file extension resolution, so that when we
import files in our code we can omit the file extension:
Configuring Webpack for React
Install React and the Babel preset:
Add the new preset to the babel config:
This should work as-is, but to use
.jsx file extensions we need a couple of small changes:
A small React example:
Bundle it all up
A popular trick with Webpack is to import
.css files from your
.js files. By default, this will bundle it inside the
.js bundle, but we can use
MiniCssExtractPlugin to extract the CSS into it's own file.
we can split our main bundle into separate files, such that one file contains the our application code, and the other is the dependencies:
this should now generate a
vendor-bundle.js file, as well as our, now smaller,
Generating an HTML file
Now that you have some CSS and JS files, wouldn't it be nice to generate an HTML file to tie them together?
html-webpack-plugin does just that:
The plugin offers several options to tune how the html file is generated, as well as templating in various formats. I personally like use it with
html-webpack-template which is basically just a big
.ejs file you can configure.
since we now have a generated html file dynamically creating
<link> tags based on our build, it's also very easy to add hashes into the filename, so that when the build changes, a new html file is generated pointing to different files:
You can run TypeScript standalone to transform your code, but it turns out when compiling it's faster to let Babel just strip them, which is great since we were already using it.
TypeScript has a generator for it's configuration, via
npx typescript --init, which works great if you're using it to compile as well, but we're using Babel for that. Here's the configuration that has worked best for me:
Make sure to add the TypeScript preset to the Babel config:
We also need to add
.tsx extensions to our Webpack config:
Now that we have type checking configured, we can install types for each package via
npm i -D @types/... or we can use
npx typesync to install them for us. Even better yet, we can automatically install them with a
We can even sprinkle some jsdoc comments in our webpack config and get typescript hints about the configuration without having to compile it:
Linters are tools to help catch errors besides type mismatches, as well as enforce stylistic/formatting preferences in your code.
eslint includes tons of rules, as well as plugins to add more rules, as well as presets of pre-configured groups of rules.
This is a great way to get started, it will run an interactive "wizard" that asks a few questions, installs dependencies, and creates a config file for you:
But instead of the wizard, let's set up eslint manually.
If you're not using TypeScript, you should use the Babel parser and add it to the eslint config:
otherwise you'll want to add the TypeScript parser and plugin as well as some TS-specific rules:
And add them to the configuration:
You can also bring the power of linting to your CSS using
We first need the main PostCSS package and then a loader for Webpack for it to process them. After that you can add whatever you like. I suggest
postcss-import to help with CSS
postcss-preset-env for adding autoprefixer as well as several other "future" features, and cssnano for minifying. There are still so many more on npm worth checking out.
And a configuration file for PostCSS:
postcss-loader docs have a lot more info on the cool things you can do with PostCSS and Webpack.
If you're specifically interested in the low-noise Stylus-like syntax,
sugarss provides a great alternative.
Also note, that
postcss-preset-env, as well as
babel-preset-env, both transform your code based on your
We have several tools set up to do different tasks, and we can save the different commands for working with them in the
package.json for easy re-use.
Now that we've grouped tasks into specialized scripts we can easily execute them together with
npm-run-all provides two (or 4 if you count shorthands) ways to run scripts defined in your
package.json: sequentially (one after the other) with
npm-run-all -s (or
run-s), or in parallel with
npm-run-all -p (or
run-p). It also provides a way to "glob" tasks with similar names, for example we can run all scripts starting with "lint:"
- the quotes around
lint:*are making sure that
*is passed literally to the command rather than being expanded by the shell as a file list
-cswitch passed to
run-pmakes sure that the process
continues even if one task fails.
We can also force Webpack to restart if any of the configuration is changed using