Webpack

Webpack - A Detailed Introduction

JavaScript module bundling has been around for a while. RequireJS had its first commits in 2009, then Browserify made its debut, and since then several other bundlers have spawned across the Internet. Among that group, webpack has jumped out as one of the best. If you’re not familiar with it, we hope that this article will get you started with this powerful tool.

In most programming languages (including ECMAScript 2015+, which is one of the most recent versions of the standard for JavaScript, but isn’t fully supported across all browsers yet), you can separate your code into multiple files and import those files into your application to use the functionality contained in them. This wasn’t built into browsers, so module bundlers were built to bring this capability in a couple forms: by asynchronously loading modules and running them when they have finished loading, or by combining all of the necessary files into a single JavaScript file that would be loaded via a <script> tag in the HTML.

Without the module loaders and bundlers, you could always combine your files manually or load your HTML with countless <script> tags, but that has several disadvantages:

  • You need to keep track of the proper order in which the files should load, including which files depend on which other files and making sure not to include any files you don’t need.
  • Multiple <script> tags means multiple calls to the server to load all of your code, which is worse for performance.
  • Obviously, this entails a lot of manual work, instead of letting the computer do it for you.

Most module bundlers also integrate directly with npm or Bower to easily allow you to add third-party dependencies to your application. Just install them and throw in a line of code to import them into your application. Then, run your module bundler, and you’ll have your third-party code combined with your application code, or, if you configure it correctly, you can have all of your third-party code in a separate file, so that when you update the application code, users don’t need to download the vendor code when they need to update their cache of your application code.

Why Webpack?

Now that you have basic knowledge of the purpose of webpack, why should you choose webpack over the competition? There are a few reasons:

  • Its relative newness gives it a leg up because it is able to work around or avoid the shortcomings and problems that have popped up in its predecessors.
  • Getting started is simple. If you’re just looking to bundle a bunch of JavaScript files together without any other fancy stuff, you won’t even need a configuration file.
  • Its plugin system enables it to do so much more, making it quite powerful. So, it might be the only build tool you need.

I’ve seen only a few other module bundlers and build tools that can say the same thing, but webpack seems to have one thing over those: a large community that can help when you get stuck. Browserify’s community is probably just as big, if not larger, but it lacks a few of the potentially essential features that come with webpack. With all the praise I’ve given webpack, I’m sure you’re just waiting for me to move on and show some code, right? Let’s do that, then.

Setting Up Webpack

Before we can use webpack, we need to install it. To do that, we’re going to need Node.js and npm, both of which I’m just going to assume you have. If you don’t have them installed, then the Node.js website is a great place to start.

Now, there are two ways to install webpack (or any other CLI package, for that matter): globally or locally. If you install it globally, you can use it no matter what directory you’re in, but then it won’t be included as a dependency for your project, and you can’t switch between versions of webpack for different projects (some projects might need more work to upgrade to a later version, so they might have to wait). So, I prefer to install CLI packages locally and either use relative paths or npm scripts to run the package. If you’re not used to installing CLI packages locally, you can read about it in a post I wrote about getting rid of global npm packages.

We’re going to be using npm scripts for our examples anyway, so let’s just forge ahead with installing it locally. First things first: Create a directory for the project where we can experiment and learn about webpack. I have a repository on GitHub that you can clone and whose branches you can switch between to follow along, or you can start a new project from scratch and maybe use my GitHub repository for comparison.

Once you’re inside the project directory via your console of choice, you’ll want to initialize the project with npm init. The information you provide really isn’t that important, though, unless you plan on publishing this project on npm.

Now that you have a package.json file all set up (npm init created it), you can save your dependencies in there. So, let’s use npm to install webpack as a dependency with npm install webpack -D. (-D saves it in package.json as a development dependency; you could also use –save-dev.)

Before we can use webpack, we should have a simple application to use it on. When I say simple, I mean it. First, let’s install Lodash just so that we have a dependency to load into our simple app: npm install lodash -S (-S is the same as –save).

Then, we’ll create a directory named src, and in there we’ll create a file named main.js with the following contents:

var map = require('lodash/map');

function square(n) {
    return n*n;
}

console.log(map([1,2,3,4,5,6], square));

Pretty simple, right? We’re just creating a small array with the integers 1 through 6, then using Lodash’s map to create a new array by squaring the numbers from the original array. Finally, we’re outputting the new array to the console. This file can even be run by Node.js, which you can see by running node src/main.js, which should show this output: [ 1, 4, 9, 16, 25, 36 ].

使用命令node main.js可以得到执行结果

But we want to bundle up this tiny script with the Lodash code that we need and make it ready for browsers, which is where webpack comes in? How do we do that?

Using Webpack Command Line

The easiest way to get started with using webpack without wasting time on a configuration file is just to run it from the command line.

The simplest version of the command for webpack without using a configuration file takes an input file path and an output file path.

Webpack will read from that input file, tracing through its dependency tree, combining all of the files together into a single file and outputting the file at the location you’ve specified as the output path.

For this example, our input path is src/main.js, and we want to output the bundled file to dist/bundle.js. So, let’s create an npm script to do that (we don’t have webpack installed globally, so we can’t run it directly from the command line). In package.json, edit the “scripts” section to look like the following:

 "scripts": {
    "build": "webpack src/main.js dist/bundle.js",
  }

Now, if you run npm run build, webpack should get to work. When it’s done, which shouldn’t take long, there should be a new dist/bundle.js file.

上面会提示需要安装

CLI for webpack must be installed.
webpack-cli (https://github.com/webpack/webpack-cli)

We will use "npm" to install the CLI via "npm install -D webpack-cli".
Do you want to install 'webpack-cli' (yes/no): yes

Now you can run that file with Node.js (node dist/bundle.js) or run it in the browser with a simple HTML page and see the same result in the console.

Before exploring webpack some more, let’s make our build scripts a little more professional by deleting the dist directory and its contents before rebuilding, and also adding some scripts to execute our bundle. The first thing we need to do is install del-cli so that we can delete directories without upsetting the people who don’t use the same operating system as us (don’t hate me because I use Windows); npm install del-cli -D should do the trick. Then, we’ll update our npm scripts to the following:

  "scripts": {
    "prebuild": "del-cli dist -f",
    "build": "webpack src/main.js dist/bundle.js",
    "execute": "node dist/bundle.js",
    "start": "npm run build -s && npm run execute -s"
  }

We kept “build” the same as before, but now we have “prebuild” to do some cleanup, which will run prior to “build” every time “build” is told to run. We also have “execute”, which uses Node.js to execute the bundled script, and we can use “start” to do it all with one command (the -s bit just makes it so that the npm scripts don’t output as much useless stuff to the console). Go ahead and run npm start. You should see webpack’s output, quickly followed by our squared array, show up in your console. Congratulations! You’ve just finished everything in the example1 branch of the repository I mentioned earlier.

Using A Configuration File

As fun as it is to use the webpack command line to get started, once you start using more of webpack’s features, you’re going to want to move away from passing in all of your options via the command line and instead use a configuration file, which will have more capability but which will also be more readable because it’s written in JavaScript.

So, let’s create that configuration file. Create a new file named webpack.config.js in your project’s root directory. This is the file name that webpack will look for by default, but you can pass the –config [filename] option to webpack if you want to name your configuration file something else or to put it in a different directory.

For this tutorial, we’ll just use the standard file name, and for now we’ll try to get it working the same way that we had it working with just the command line. To do that, we need to add the following code to the config file:

module.exports = {
    entry: './src/main.js',
    output: {
        path: './dist',
        filename: 'bundle.js'
    }
};

We’re specifying the input file and the output file, just like we did with the command line before.

This is a JavaScript file, not a JSON file, so we need to export the configuration object — hence, the module.exports.

It doesn’t exactly look nicer than specifying these options through the command line yet, but by the end of the article, you’ll be glad to have it all in here.

Now we can remove those options that we were passing to webpack from the scripts in our package.json file. Your scripts should look like this now:

  "scripts": {
    "prebuild": "del-cli dist -f",
    "build": "webpack",
    "execute": "node dist/bundle.js",
    "start": "npm run build -s && npm run execute -s"
  }

You can npm start like you did before, and it should look very familiar! That’s all we needed for the example2 branch.

Using Loaders

We have two primary ways to add to webpack’s capabilities: loaders and plugins. We’ll discuss plugins later. Right now we’ll focus on loaders, which are used to apply transformations or perform operations on files of a given type. You can chain multiple loaders together to handle a single file type. For example, you can specify that files with the .js extension will all be run through ESLint and then will be compiled from ES2015 down to ES5 by Babel. If ESLint comes across a warning, it’ll be outputted to the console, and if it encounters any errors, it’ll prevent webpack from continuing.

For our little application, we won’t be setting up any linting, but we will be setting up Babel to compile our code down to ES5. Of course, we should have some ES2015 code first, right? Let’s convert the code from our main.js file to the following:

import { map } from 'lodash';

console.log(map([1,2,3,4,5,6], n => n*n));

This code is doing essentially the same exact thing,

but (1) we’re using an arrow function instead of the named square function,

and (2) we’re loading map from ‘lodash’ using ES2015’s import.

This will actually load a larger Lodash file into our bundle because we’re asking for all of Lodash, instead of just asking for the code associated with map by requesting ‘lodash/map’. You can change that first line to import map from ‘lodash/map’ if you prefer, but I switched it to this for a few reasons:

  • In a large application, you’ll likely be using a pretty large chunk of the Lodash library, so you might as well load all of it.
  • If you’re using Backbone.js, getting all of the functions you need loaded individually will be very difficult simply because there is no documentation specifying how much of it is needed.
  • In the next major version of webpack, the developers plan to include something called tree-shaking, which eliminates unused portions of modules. So, this would work the same either way.
  • I’d like to use it as an example to teach you the bullet points I just mentioned.

(Note: These two ways of loading work with Lodash because the developers have explicitly created it to work that way. Not all libraries are set up to work this way.)

Anyway, now that we have some ES2015, we need to compile it down to ES5 so that we can use it in decrepit衰老的,破旧的 browsers (ES2015 support is actually looking pretty good in the latest browsers!). For this, we’ll need Babel and all of the pieces it needs to run with webpack.

At a minimum, we’ll need babel-core (Babel’s core functionality, which does most of the work),

 babel-loader (the webpack loader that interfaces with babel-core)

and babel-preset-es2015 (which contains the rules that tell Babel to compile from ES2015 to ES5).

We’ll also get babel-plugin-transform-runtime and babel-polyfill, both of which change the way Babel adds polyfills and helper functions to your code base, although each does it a bit differently, so they’re suited to different kinds of projects. Using both of them wouldn’t make much sense, and you might not want to use either of them, but I’m adding both of them here so that no matter which you choose, you’ll see how to do it. If you want to know more about them, you can read the documentation pages for the polyfill and runtime transform.

Anyway, let’s install all of that: npm i -D babel-core babel-loader babel-preset-es2015 babel-plugin-transform-runtime babel-polyfill. And now let’s configure webpack to use it. First, we’ll need a section to add loaders. So, update webpack.config.js to this:

module.exports = {
    entry: './src/main.js',
    output: {
        path: './dist',
        filename: 'bundle.js'
    },
    module: {
        rules: [
            …
        ]
    }
};

安装的提示

peerDependencies WARNING babel-loader@* requires a peer of @babel/core@^7.0.0 but none was installed
deprecate babel-preset-es2015@* ???? Thanks for using Babel: we recommend using babel-preset-env now: please read https://babeljs.io/env to update!
deprecate babel-polyfill@6.26.0 › core-js@^2.5.0 core-js@<3 is no longer maintained and not recommended for usage due to the number of issues. Please, upgrade your dependencies to the actual version of core-js@3.
Recently updated (since 2021-01-18): 5 packages (detail see file C:workspaceGitHubChuckLuWebpackTest ode_modules.recently_updates.txt)

We’ve added a property named module, and within that is the rules property, which is an array that holds the configuration for each loader you use. This is where we’ll be adding babel-loader. For each loader, we need to set a minimum of these two options: test and loadertest is usually a regular expression that is tested against the absolute path of each file. These regular expressions usually just test for the file’s extension; for example, /.js$/ tests whether the file name ends with .js. For ours, we’ll be setting this to /.jsx?$/, which will match .js and .jsx, just in case you want to use React in your app. Now we’ll need to specify loader, which specifies which loaders to use on files that pass the test.

This can be specified by passing in a string with the loaders’ names, separated by an exclamation mark, such as ‘babel-loader!eslint-loader’. webpack reads these from right to left, so eslint-loader will be run before babel-loader. If a loader has specific options that you want to specify, you can use query string syntax. For example, to set the fakeoption option to true for Babel, we’d change that previous example to ‘babel-loader?fakeoption=true!eslint-loader. You can also use the use option instead of the loader option which allows you to pass in an array of loaders if you think that’d be easier to read and maintain. For example, the last examples would be changed to use: [‘babel-loader?fakeoption=true’, ‘eslint-loader’], which can always be changed to multiple lines if you think it would be more readable.

Because Babel is the only loader we’ll be using, this is what our loader configuration looks like so far:

rules: [
    { test: /.jsx?$/, loader: 'babel-loader' }
]

If you’re using only one loader, as we are, then there is an alternative way to specify options for the loader, rather than using the query strings: by using the options object, which will just be a map of key-value pairs. So, for the fakeoption example, our config would look like this:

 
rules: [
    {
        test: /.jsx?$/,
        loader: 'babel-loader',
        options: {
            fakeoption: true
        }
    }
]

We will be using this syntax to set a few options for Babel:

rules: [
    {
        test: /.jsx?$/,
        loader: 'babel-loader',
        options: {
            plugins: ['transform-runtime'],
            presets: ['es2015']
        }
    }
]

We need to set the presets so that all of the ES2015 features will be transformed into ES5, and we’re also setting it up to use the transform-runtime plugin that we installed. As mentioned, this plugin isn’t necessary, but it’s there to show you how to do it. An alternative would be to use the .babelrc file to set these options, but then I wouldn’t be able to show you how to do it in webpack. In general, I would recommend using .babelrc, but we’ll keep the configuration in here for this project.

There’s just one more thing we need to add for this loader. We need to tell Babel not to process files in the node_modules folder, which should speed up the bundling process. We can do this by adding the exclude property to the loader to specify not to do anything to files in that folder. The value for exclude should be a regular expression, so we’ll set it to /node_modules/.

rules: [
    {
        test: /.jsx?$/,
        loader: 'babel-loader',
        exclude: /node_modules/,
        options: {
            plugins: ['transform-runtime'],
            presets: ['es2015']
        }
    }
]

Alternatively, we could have used the include property and specified that we should only use the src directory, but I think we’ll leave it as it is. With that, you should be able to run npm start again and get working ES5 code for the browser as a result.

使用ployfill的配置如下

If you decide that you’d rather use the polyfill instead of the transform-runtime plugin, then you’ll have a change or two to make. First, you can delete the line that contains plugins: [‘transform-runtime], (you can also uninstall the plugin via npm if you’re not going to use it). Then, you need to edit the entry section of the webpack configuration so that it looks like this:

entry: [
    'babel-polyfill',
    './src/main.js'
],

Instead of using a string to specify a single entry point, we use an array to specify multiple entry files, the new one being the polyfill. We specify the polyfill first so that it’ll show up in the bundled file first, which is necessary to ensure that the polyfills exist before we try to use them in our code.

Instead of using webpack’s configuration, we could have added a line at the top of src/main.jsimport ‘babel-polyfill;, which would accomplish the exact same thing in this case. We used the webpack entry configuration instead because we’ll need it to be there for our last example, and because it’s a good example to show how to combine multiple entries into a single bundle. Anyway, that’s it for the example3 branch of the repository. Once again, you can run npm start to verify that it’s working.

原文地址:https://www.cnblogs.com/chucklu/p/14309673.html