Movatterモバイル変換


[0]ホーム

URL:


Sitemap
Open in app
Insightful Software

Thoughts on data, training, and consulting from Envy Labs

Follow publication

Jun 2017 edit:3 was just released! The syntax for 3.x is identical to 2.x, which means this article is still up-to-date (if I missed something, please comment!). The version bump was for more behind-the-scenes stuff, but it did add an easy-to-add “Scope Hoisting” plugin that can cut down on your bundle size that’s worth checking out! Jump to the bottom of this article for the “Upgrading to 3” section (spoiler: it doesn’t override anything else in this article).

What is webpack?

At its simplest, webpack is a module bundler for your JavaScript. However, since its release it’s evolved into a manager of all your front-end code (either intentionally or by the community’s will).

The old task runner way: your markup, styles, and JavaScript are isolated. You must manage each separately, and it’s your job to make sure everything gets to production properly.

A task runner such asGulpcan handle many different preprocessers and transpilers, but in all cases, it will take a sourceinput and crunch it into a compiledoutput.However, it does this on a case-by-case basis with no concern for the system at large. That is the burden of the developer: to pick up where the task runner left off and find the proper way for all these moving parts to mesh together in production.

webpack attempts to lighten the developer load a bit by asking a bold question:what if there were a part of the development process that handled dependencies on its own? What if we could simply write code in such a way that the build process managed itself, based on only what was necessary in the end?

The webpack way: if webpack knows about it, it bundles only what you’re *actually* using to production.

If you’ve been a part of the web community for the past few years, you already know the preferred method of solving a problem:build this with JavaScript.And so webpack attempts to make the build process easier by passing dependencies through JavaScript. But the true power of its design isn’t simply the codemanagement part; it’s that this management layer is 100% valid JavaScript (with Node features). webpack gives you the ability to write valid JavaScript that has a better sense of the system at large.

In other words:you don’t write code for webpack. You write code for your project. And webpack keeps up (with some config, of course).

In a nutshell, if you’ve ever struggled with any of the following:

  • Loading dependencies out of order
  • Including unused CSS or JS in production
  • Accidentally double-loading (or triple-loading) libraries
  • Encountering scoping issues—both from CSS and JavaScript
  • Finding a good system for using NPM packages in your JavaScript, or relying on a crazy backend configuration to fully utilize NPM
  • Needing to optimize asset delivery better but fearing you’ll break something

…then you could benefit from webpack. It handles all the above effortlessly by letting JavaScript worry about your dependencies and load order instead of your developer brain. The best part? webpack runs ahead-of-time, so you can still buildprogressive web apps, or apps that don’t even need JS to run.

First Steps

We’ll useYarn (brew install yarn) in this tutorial instead ofnpm, but it’s totally up to you; they do the same thing. From our project folder, we’ll run the following in a terminal window to add webpack to our local project:

yarn add --dev webpack webpack-dev-server

Note:using NPM scripts is recommended, but for simplicity we’ll be running commands manually in this blog post. Be sure to take advantage of NPM scripts’ automation in the long-term!

We’ll then declare a webpack configuration with awebpack.config.js file in the root of our project directory:

const path = require('path');
const webpack = require('webpack');
module.exports = {
context: path.resolve(__dirname, 'src'),
entry: {
app: './app.js',
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].bundle.js',
},
};

Note:__dirname refers to the directory where thiswebpack.config.js lives, which in this blog post is the project root.

Remember that webpack “knows” what’s going in your project? Itknows by reading your code (don’t worry; it signed an NDA). webpack basically does the following:

  1. Starting from thecontext folder, …
  2. … it looks forentryfilenames …
  3. … and reads the content. Everyimport (ES6) orrequire() (Node) dependency it finds as it parses the code, it bundles for the final build. It then searchesthose dependencies, and those dependencies’ dependencies, until it reaches the very end of the “tree”—only bundling what it needed to, and nothing else.
  4. From there, webpack bundles everything to theoutput.path folder, naming it using theoutput.filename naming template ([name] gets replaced with the object key fromentry)

So if oursrc/app.js file looked something like this (assuming we ranyarn add moment beforehand):

import moment from 'moment';var rightNow = moment().format('MMMM Do YYYY, h:mm:ss a');
console.log(rightNow);
// "October 23rd 2016, 9:30:24 pm"

From a terminal, run:

node_modules/.bin/webpack -p

Note: Thep flag is “production” mode and uglifies/minifies output. When we runnode_modules/.bin/, we’re running the local version of webpack in this project. This is preferred, because over time and across projects we may have different webpack versions installed, and this ensures nothing breaks.

And it would output adist/app.bundle.js that logged the current date & time to the console. webpack automatically knew that'moment' referred to the NPM package we installed.

Tip:'moment' works—just that string—becauseinside itspackage.json, themain: entry points to the core library. Without that, we’d have to explicitly declare the library we wanted like so:'moment/moment' (we can leave off the.js at the end). Sometimes it’s worth inspecting NPM packages because they may include alternate libraries that could save you time or filesize if you didn’t need the whole library.

Working with Multiple Files

You can specify any number of entry/output points you wish by modifying only theentry object.

Multiple files, bundled together

const path = require('path');
const webpack = require('webpack');
module.exports = {
context: path.resolve(__dirname, 'src'),
entry: {
app: ['./home.js', './events.js', './vendor.js'],
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].bundle.js',
},
};

Will all be bundled together as onedist/app.bundle.js file, in array order.

Multiple files, multiple outputs

const path = require('path');
const webpack = require('webpack');
module.exports = {
context: path.resolve(__dirname, 'src'),
entry: {
home: './home.js',
events: './events.js',
contact: './contact.js',
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].bundle.js',
},
};

Alternately, you may choose to bundle multiple JS files to break up parts of your app. This will be bundled as 3 files:dist/home.bundle.js,dist/events.bundle.js, anddist/contact.bundle.js.

Vendor caching

If you want to separate your vendor libraries into their own bundle so that users don’t have to re-download your third-party dependencies every time you make a minor app update, you can easily do that thanks to webpack’s built-in Commons Chunk Plugin:

const webpack = require('webpack');module.exports = {
entry: {
index: './index.js',
vendor: ['react', 'react-dom'],
},
plugins: [
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor',
minChunks: Infinity,
}),
],
}

Note: make surevendor in CommonsChunkPlugin matches the'vendor' entry name above—it can be anything as long as it matches anentry key.

In this, you’re explicitly telling webpack to use yourvendor bundle as a commons chunk, containing yourreact andreact-dom Node Modules for the entire app. In larger applications where optimization is key, this can yield better results if you limited your vendor bundle like this.

Note that by doing this,you should loadvendor beforeapp in your template. webpack will often emit something like this:

AssetSizeChunksChunkNames
vendor.bundle.js230 kB 0 [emitted]vendor
app
.bundle.js 173 kB 1 [emitted] [big]index

See how webpack has a0 chunk, and a1 chunk? It tells you the order in which it expects them to load. If you’re writing your own HTML template, you’ll need to include the<script> tags in the proper order, or you can use something like theHTML webpack Plugin to just handle this for you.

Editor’s note: a previous version of this article listed a second example to automatically extract duplicate modules across bundles using the Commons Chunk Plugin. That example was removed because it’s not useful for most beginners, and unless you knew what you were doing it was probably slowing down your app anyway. If you’d like to learn more about Commons Chunk Plugin’s (many) “hidden” features,view webpack’s docs.

Developing

webpack actually has its own development server, so whether you’re developing a static site or are just prototyping your front-end, it’s perfect for either. To get that running, just add adevServer object towebpack.config.js:

module.exports = {
context: path.resolve(__dirname, 'src'),
entry: {
app: './app.js',
},
output: {
filename: '[name].bundle.js',
path: path.resolve(__dirname, 'dist', 'assets'),
publicPath: '/assets', // New
},
devServer: {
contentBase: path.resolve(__dirname, 'src'), // New
},
};

Now make asrc/index.html file that has:

<script src="/assets/app.bundle.js"></script>

… and from your terminal, run:

node_modules/.bin/webpack-dev-server

Your server is now running atlocalhost:8080.Note how/assets in the script tag matchesoutput.publicPath—this prefixes all asset URLs, so you can load assets from anywhere you need to (useful if you use a CDN).

webpack will hotload any JavaScript changes as you make them without the need to refresh your browser. However,any changes to thewebpack.config.js file will require a server restart to take effect.

Globally-accessible methods

Need to use some of your functions from a global namespace? Simply setoutput.library withinwebpack.config.js:

module.exports = {
output: {
library: 'myClassName',
}
};

… and it will attach your bundle to awindow.myClassName instance. So using that name scope, you could call methods available to that entry point (you can read more about this settingon the documentation).

Loaders

Up until now, we’ve only covered working with JavaScript. It’s important to start with JavaScript becausethat’s the only language webpack speaks. We can work with virtually any file type, as long as we pass it into JavaScript. We do that withLoaders.

A loader can refer to a preprocessor such as Sass, or a transpiler such as Babel. On NPM, they’re usually named*-loader such assass-loader orbabel-loader.

Babel + ES6

If we wanted to use ES6 viaBabel in our project, we’d first install the appropriate loaders locally:

yarn add --dev babel-loader babel-core babel-preset-env

… and then add it towebpack.config.js so webpack knows where to use it.

module.exports = {
// …
module: {
rules: [
{
test: /\.js$/i,
exclude: [/node_modules/],
use: [{
loader: 'babel-loader',
options: { presets: ['env'] },
}],
},

// Loaders for other file types can go here
],
},
// …
};

A note for webpack 1.x users: the core concept forLoaders remains the same, but the syntax has improved.

This looks for the/\.js$/ RegEx search for any files that end in.js to be loaded via Babel. webpack relies on regex tests to give you complete control, not limiting you to only file extensions or assume your code must be organized in a certain way.

If you find a loader mangling files, or otherwise processing things it shouldn’t, you can specify anexclude option to skip certain files. Here, we excluded ournode_modules folder from being processed by Babel—we don’t need it. But we also could apply this to any of our own project files, for example if we had amy_legacy_code folder. This doesn’t prevent you from loading these files; rather, you’re just letting webpack know it’s OK to import as-is and not process them.

CSS + Style Loader

If we wanted to only load CSS as our application needed, we could do that as well. Let’s say we have anindex.js file. We’ll import it from there:

import styles from './assets/stylesheets/application.css';

We’ll get the following error:You may need an appropriate loader to handle this file type. Remember that webpack can only understand JavaScript, so we’ll have to install the appropriate loader:

yarn add --dev css-loader style-loader

… and then add a rule towebpack.config.js:

module.exports = {
// …
module: {
rules: [
{
test: /\.css$/i,
use: ['style-loader', 'css-loader'],
},
// …
],
},
};

Loaders are processed inreverse array order. That meanscss-loader will run beforestyle-loader.

You may notice that even in production builds, this actually bundles your CSS in with your bundled JavaScript, andstyle-loader manually writes your styles to the<head>. At first glance it may seem a little kooky, but slowly starts to make more sense the more you think about it. You’ve saved a header request—saving valuable time on some connections—and if you’re loading your DOM with JavaScript anyway, this essentially eliminatesFOUC on its own.

You’ll also notice that—out of the box—webpack has automatically resolved all of your@import queries by packaging those files together as one (rather than relying on CSS’s default import which can result in gratuitious header requests and slow-loading assets).

Loading CSS from your JS is pretty amazing,because you now can modularize your CSS in powerful new ways. Say you loadedbutton.css only throughbutton.js. This would mean ifbutton.js is never actually used,its CSS wouldn’t bloat out our production build. If you adhere to component-oriented CSS practices such as SMACSS or BEM, you see the value in pairing your CSS more closely with your markup + JavaScript.

CSS + Node Modules

We can use webpack to take advantage of importing Node Modules using Node’s~ prefix. If we ranyarn add normalize.css, we could use:

@import "~normalize.css";

… and take full advantage of NPM managing our third party styles for us—versioning and all—without any copy + pasting on our part. Further, getting webpack to bundle CSS for us has obvious advantages over using CSS’s default import, saving the client from gratuitous header requests and slow load times.

Update: this and the following section have been updated for accuracy, no longer confusing using CSS Modules to simply import Node Modules. Thanks to

for the help!

CSS Modules

You may have heard ofCSS Modules, which takes theC out ofCSS. It typically works best only if you’re building the DOM with JavaScript, but in essence, it magically scopes your CSS classes to the JavaScript file that loaded it (learn more about it here). If you plan on using it, CSS Modules comes packaged withcss-loader (yarn add --dev css-loader):

module.exports = {
// …
module: {
rules: [
{
test: /\.css$/i,
use: [
'style-loader',
{
loader: 'css-loader',
options: { modules: true },
},
],
},
// …
],
},
};

Note: forcss-loader we’re now using theexpanded object syntax to pass an option to it. You can use a string instead as shorthand to use the default options, as we’re still doing withstyle-loader.

It’s worth noting that you can actually drop the~ when importing Node Modules with CSS Modules enabled (e.g.:@import "normalize.css";). However, you may encounter build errors now when you@import your own CSS. If you’re getting “can’t find ___” errors, try adding aresolve object towebpack.config.js to give webpack a better understanding of your intended module order.

module.exports = {
//…
resolve: {
modules: [path.resolve(__dirname, 'src'), 'node_modules']
},
};

We specified our source directory first, and thennode_modules. So webpack will handle resolution a little better, first looking through our source directory and then the installed Node Modules, in that order (replace"src" and"node_modules" with your source and Node Module directories, respectively).

Sass

Need to use Sass? No problem. Install:

yarn add --dev sass-loader node-sass

And add another rule:

module.exports = {
// …
module: {
rules: [
{
test: /\.(sass|scss)$/i,
use: [
'style-loader',
'css-loader',
'sass-loader',
]
}
// …
],
},
};

Then when your Javascript calls for animport on a.scss or.sass file, webpack will do its thing. Remember: the order ofuse is backward, so we’re loading Sass first, followed by our CSS parser, and finally Style loader to load our parsed CSS into the<head> of our page.

CSS bundled separately

Maybe you’re dealing with progressive enhancement; maybe you need a separate CSS file for some other reason. We can do that easily by swapping outstyle-loader withextract-text-webpack-plugin in our config without having to change any code. Take our exampleapp.js file:

import styles from './assets/stylesheets/application.css';

Let’s install the plugin locally…

yarn add --dev extract-text-webpack-plugin

… and add towebpack.config.js:

const ExtractTextPlugin = require('extract-text-webpack-plugin');module.exports = {
// …
module: {
rules: [
{
test: /\.css$/i,
use: ExtractTextPlugin.extract({
use: [{
loader: 'css-loader',
options: { importLoaders: 1 },
}],
}),
},

// …
]
},
plugins: [
new ExtractTextPlugin({
filename: '[name].bundle.css',
allChunks: true,
}),
],
};

Now when runningnode_modules/.bin/webpack -p you’ll also notice anapp.bundle.css file in youroutput directory. Simply add a<link> tag to that file in your HTML as you would normally.

HTML

As you might have guessed, there’s also anhtml-loader plugin for webpack. However, when we get to loading HTML with JavaScript, this is about the point where we branch off into a myriad of differing approaches, and I can’t think of one single example that would set you up for whatever you’re planning on doing next. Typically, you’d load HTML for the purpose of using JavaScript-flavored markup such asJSX orMustache orHandlebars to be used within a larger system such asReact,Vue, orAngular. Or you’re using a pre-processor such asPug (formerly Jade). Or you could just be literally pushing the same HTML from your source directory into your build directory. Whatever you’re doing, I won’t assume.

So I’ll end the tutorial here: loading markup with webpack ishighly encouraged and doable, but by this point you’ll be making your own decisions about your architecture that neither I nor webpack can make for you. But using the above examples for reference and searching for the right loaders on NPM should be enough to get you going.

Thinking in Modules

In order to get the most out of webpack, you’ll have to think in modules—small, reusable, self-contained processes that do one thing and one thing well. That means taking something like this:

└── js/
└── application.js // 300KB of spaghetti code

… and turning it into this:

└── js/
├── components/
│ ├── button.js
│ ├── calendar.js
│ ├── comment.js
│ ├── modal.js
│ ├── tab.js
│ ├── timer.js
│ ├── video.js
│ └── wysiwyg.js

└── index.js // ~ 1KB of code; imports from ./components/

The result is clean, reusable code. Each individual component depends onimport-ing its own dependencies, andexport-ing what it wants to make public to other modules. Pair this with Babel + ES6, and you can utilizeJavaScript Classes for great modularity, anddon’t-think-about-itscoping that just works.

For more on modules, seethis excellent article by Preethi Kasireddy.

Upgrading to 3

According to

in therelease blog post: “Migrating from webpack 2 to 3, should involveno effort beyond running the upgrade commands in your terminal. We marked this as a Major change because of internal breaking changes that could affect some plugins.”

Version 3 did add a nifty feature:scope hoisting. This is a special name to describe a newer method to bundle all your modules together in one manifest. Best case scenario: it can reduce your bundle size byalmost half (source). Worst case scenario: scope hoisting doesn’t work for your special bundle, and it outputs almost identical code to what it did in version 2.x.

To add it, just add the following line toplugins:

module.exports = {
//…
plugins: [
new webpack.optimize.ModuleConcatenationPlugin(),
],
//…
};

Read more aboutScope Hoisting here.

Further Reading

--

--

Insightful Software
Insightful Software

Published in Insightful Software

Thoughts on data, training, and consulting from Envy Labs

Drew Powers
Drew Powers

Written by Drew Powers

Web performance, animation, and image optimization

Responses (48)


[8]ページ先頭

©2009-2025 Movatter.jp