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
Why?
We often want to make use of other JavaScript code in our own, and organize our code into separate files. Webpack helps bundle all of your code into a single minified file, as well as help split into into different chunks you can dynamically load at runtime.
Imagine you also want to write your JavaScript using ES2015 or newer syntax, but want to support older browsers or other runtimes that may not have it. Babel is built for this, it is a tool specifically designed to translate various forms of JavaScript.
These tools, and a few others, can help simplify your workflow while giving you a lot of power over how to manage your code.
How?
Zero-config Webpack
Let's create a project, and install Webpack and for the sake of example use jquery as a dependency.
Create a simple file at src/index.js
Now let's use Webpack to compile it, and check the output:
- if
npx webpack
is not available, ensure you installed thewebpack-cli
package, try./node_modules/.bin/webpack
, or upgrade to a newer version ofnode
.
Note the difference when using production mode:
Webpack starts with it's given entrypoint[s], and parses for any import
or 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.
Babel
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.
Since the Webpack config is a regular JavaScript file, we're not limited to cramming everything into a single object, we can move things around and extract pieces into variables:
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
Importing CSS
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.
Vendor Bundles
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, app-bundle.js
.
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.
Easy cache-busting
since we now have a generated html file dynamically creating <script>
and <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:
TypeScript
It's never too late or early to introduce type checking into your JavaScript project!
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 .ts
and .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 package.json
hook:
We can even sprinkle some jsdoc comments in our webpack config and get typescript hints about the configuration without having to compile it:
Linting
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:
Linting CSS
You can also bring the power of linting to your CSS using stylelint
!
Transforming CSS
Just like Babel does transformations over JavaScript files, PostCSS does for CSS files. Alone, also in the same way as Babel, PostCSS doesn't actually do anything, it only provides a way to parse and transform files via plugins. There are tons of individual specialized plugins for PostCSS, you can basically build your own preprocessor that does the same things as other popular tools like Sass, Less, and Stylus, with or without other features you do or don't want.
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 @imports
and 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:
The 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 browserslist
definition.
Task running
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
:
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 - The
-c
switch passed torun-p
makes sure that the processc
ontinues even if one task fails.
We can also force Webpack to restart if any of the configuration is changed using nodemon
: