翻译 | 上手 Webpack ? 这篇就够了!

译者:小 boy (沪江前端开发工程师)
本文原创,转载请注明做者及出处。
原文地址:www.smashingmagazine.com/2017/02/a-d…javascript

JavaSript 模块化打包已混迹江湖许久。2009年,RequireJS 就提交了它的第一个版本,Browserify 接踵而至,随后其余打包工具也开始大行其道。最终,Webpack 从其中脱颖而出。若是你对它不甚了解,但愿个人文章能让你上手这件强力打包工具。html

什么是模块化打包工具?

在大多数语言(JS 的最新版本 ECMAScript 2015+ 也支持,但并不是支持全部浏览器)中,你能够将代码拆分至多个文件,而且经过在业务代码中引用这些文件来使用它们包含的方法。惋惜的是浏览器并不拥有这个能力。所以,模块化打包工具应运而生,它以两种形式为浏览器提供这个能力:1.异步加载模块,而且在加载结束后运行它们。2.将须要用到的文件拼凑成单一 JS 文件,最终在 HTML 中使用 <script> 标签加载该 JS 文件。前端

若是没有模块化加载及打包工具的话,你就得手动拼凑文件或者在 HTML 中加载无数的 <script> 标签了,并且这样干有一些很差的地方:java

  • 你须要关心文件的加载顺序,包括哪些文件依赖于其余文件;还须要确认是否引入了不须要的文件。
  • 多个 <script> 标签意味着用多个网络请求来加载代码,同时也意味着更差的性能。
  • 显然,其中有不少本能够交给计算机完成的手工活。

大多数模块化打包工具直接跟 npm 或者 Bower(译者注:二者都是包管理工具)整合,这样可让你更容易地在业务代码中添加第三方依赖包(dependencies)。你仅须要安装一下,而后写一行代码引入它们,接着运行模块化打包工具,这样就已经将第三方代码整合进本身的业务代码了。或者,如若配置正确,你能够将全部要用的三方代码整合进一个分开的文件,这样一来,当你更新业务代码,用户须要更新缓存的时候,他们就无需从新下载公共库代码(vendor code)了。node

为何选择 Webpack ?

至此,你已对 Webpack 的愿景有了基础的认知,然而为何在各路豪杰中选择 Webpack 呢?在我看来有这样一些理由:webpack

  • 小鲜肉的特性助了它一臂之力,借此特色它能够绕开或者避免前辈们遇到的问题。
  • 上手简单。若是只是想打包一些JS文件,而没有其余需求的话,你甚至都不须要一份配置文件。
  • 它的插件体系使其能作更多事情,从而十分强大,因此它多是你须要的惟一构建工具。

据我所知,少有其余的模块打包和构建工具也能作到这些。但 Webpack 仍胜一筹:当你踩坑的时候有庞大的社区支持。
Browserify 的社区可能只是大,若是它不大的话,就会缺乏一些 Webpack 的潜在必要特性。说了这么多 Webpack 的优势,估计你就等上代码了吧?那么咱们开始。git

安装 Webpack

在使用 Webpack 前,咱们首先要先把它安装好。为此咱们须要 Node.js 和 npm ,我就假设你已经安装过它们了,实在没有的话,请从Node.js 官网开始吧。github

有两种方式安装 webpack (或着是其余 CLI 包):全局安装(globally)或者本地安装(locally)。对于全局安装,虽然你能够在任意目录下使用它,可是它不会包括在项目的依赖模块列表(dependencies)中。此外,你也不能在两个不一样的项目(有些项目可能须要投入更多工做量才能更新到最新版本,因此这些项目还须要维持老版本)中切换不一样版本的 Webpack 。因此我更愿意本地安装 CLI 包,而且用相对路径抑或是 npm 脚原本运行它。若是你不习惯本地安装 CLI 包,能够看一下我以前写的关于摆脱全局安装 npm 包的博文。web

无论怎样,在示例项目中,咱们就使用 npm 脚本。接下来,先本地安装示例项目。首先:建立一个用来实验和学习 Webpack 的目录。 我在 GitHub 上有一个仓库,你能够将它 clone 到本地,而后在分支间切换来进行下面的学习,或者从零开始建立一个新项目,此后能够与个人仓库代码进行对照。正则表达式

通过命令行选择,一进到项目目录,你将用 npm init 命令来初始化项目。接下来要填的信息一点都不重要(译者注:一路回车便可),除非你想把项目发布到 npm 上。

至此 package.json 文件准备就绪(它是经过 npm init 命令建立的),在此文件中,你能够保存依赖包信息。咱们经过 npm install webpack -D-D--save-dev 命令的简写,它的做用是将 npm 包做为开发环境的依赖包安装,并将依赖信息保存到 package.json 文件中)命令将 Webpack 做为依赖包安装。

咱们须要一个简单的应用来开启运用 Webpack 之旅。所谓的简单就是:首先执行 npm install lodash -S-S == --save) 安装 Lodash,如此一来咱们的简单应用就有一个依赖包能够用来加载了。接着咱们建立一个 src 目录,再于该目录中建立名为 main.js 的文件,其内容以下:

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

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

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

很简单对吧?咱们仅仅建立了一个包含整数1至6的小数组,而后用 Loadash 库中的 map 函数建立了一个新数组,这个新数组中的数字是原数组中数字的平方。最后,咱们在控制台中打印这个新数组。运行命令 node src/main.js 就能看到结果:[1, 4, 9, 16, 25, 36]。你瞧,其实 Node.js 都能运行这个文件。

但若是咱们想打包这个小脚本,其中还包括咱们能跑在浏览器的 Lodash 代码,使用 Webpack 应该从哪入手?如何作到?

Webpack 命令行

若不想在配置文件上浪费时间,使用 Webpack 命令行是最容易的上手方式。若是不启用配置文件的话,最简洁的命令须要包含输入文件(input file)路径和输出文件(output file)路径。Webpack 会读取输入文件,追踪它的依赖关系树,并将全部依赖文件打包进一个文件,最终在你指定的输出路径下输出该文件。在本例中,输入路径是 src/main.js ,咱们要将打包后的文件输出到 dist/bundle.js 下。为此,咱们先添加 npm 脚本(咱们并无全局安装 Webpack ,因此不能直接在命令行中运行)。编辑 package.json 文件的 "scripts" 部分以下:

"scripts": {
    "build": "webpack src/main.js dist/bundle.js",
}复制代码

如今,执行 npm run build 命令,Webpack 就会运行了。很快,运行完毕的时候会生成 dist/bundle.js 文件。而后你即可以用 Node.js (经过 node dist/bundle.js 命令)运行该文件了。也能够借助简单的 HTML 将其跑在浏览器上,以后可在控制台中看到一样的运行结果。

在继续探索 Webpack 前,咱们先把构建脚本调整得更专业一点:在从新构建(rebuilding)前删除 dist 目录及其内容,此外,咱们再添加一些用于直接执行 bundle 文件的脚本。首先,安装 del-cli 工具,这样就不用在删除目录的时候顾虑操做系统的区别了(见谅,由于我用的是 Windows)。运行 npm install del-cli -D 命令便可。接着更新 npm 脚本以下:

"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"
  }复制代码

咱们保持 "build" 配置同以前同样,但增长了 "prebuild" 配置用以清除目录,这条配置所执行的命令会在每次 "build" 命令执行以前运行。同时增长的还有 "execute" 配置:使用 Node.js 执行已经打包好的脚本。此外,使用 "start" 配置能够经过一条命令执行以上全部命令(-s 的做用仅仅是不让 npm 脚本在控制台打印一些没用的东西)。执行 npm start 命令,就能够在控制台里看到 Webpack 的输出信息,紧接着打印的是平方后的数组。

恭喜!你刚刚完成了 example1 分支里全部的事情。这个分支就在我以前提到的仓库中。

Webpack 配置文件

跟使用 Webpack 命令行上手同样有趣的是,一旦开始使用更多 Webpack 的功能, 你就会想要放弃经过命令行传递 Webpack 配置参数,转而投入配置文件的怀抱。使用配置文件虽然会更占位置,但与此同时增长了可读性,由于它是由 JS 写成的。

那咱们就来建立配置文件吧。在根目录下建立一个新文件 webpack.config.js。Webpack 默认寻找该文件,但若是想给配置文件取别的名字或者将配置文件放在其余目录,你能够经过传递 --config [filename] 参数来作到。

在本教程中,咱们使用默认文件名。如今,咱们试着让配置文件起做用,达到与仅使用命令行一样的效果。为此,咱们须要在配置文件中添置以下代码:

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

如此前同样,咱们规定输入和输出文件。由于这不是 JSON 文件而是 JS 文件,因此咱们须要把配置对象(configuration object )导出,故使用 module.exports。虽然如今还看不出写这些配置会比用命令好多少,但文章结尾你确定会爱上这里的一切。

接下来,移除 package.json 文件中给 Webpack 传的配置,像这样:

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

像以前同样执行 npm start 命令,运行结果是否是似曾相识呢?以上就是分支 example2 中须要作的事情。

Webpack 加载器(Loaders)

咱们主要经过两种方式加强 Webpack: 加载器(loaders)和插件(plugins)。咱们先讲加载器,插件稍后再议。加载器用以转换或操做特定类型的文件,你能够将多个加载器串联在一块儿来处理一种类型的文件。例如,规定 .js 后缀的文件要先经过 ESLint 检查,再经过 Babel 把 ES2015 语法转换为 ES5 语法。ESLint 发出的警报将会在控制台打印出来,而遇到语法错误的时候则会阻止 Webpack 继续打包。

咱们这里就不设置语法检查了,但要经过设置 Babel 来把代码转化成 ES5。固然咱们得先有些 ES2015 代码吧?把 main.js 文件的代码改为下面的样子:

import { map } from 'lodash';

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

实际上这段代码和以前作的事情同样,但有两点:其一,使用箭头函数替代了以前定义的 square 函数。其二,使用了 ES2015 中的 import 语法加载 lodash 库中的 map 函数,但这将会把整个 Lodash 库的代码打包到咱们的输出文件中,而不是引入仅仅包含 map 函数相关代码的 'lodash/map' 库。若是乐意的话,你也能够把第一行改为 import map from 'lodash/map'; 但我写成这样有个人理由:

  • 在更具规模的应用里,你可能要用到 Lodash 库的不少部分,因此你最好全加载进来。
  • 若是你正在用 Backbone.js 框架,会发现仅打包你须要的函数是很困难的,由于根本就没有文档告诉你函数依赖哪些函数。
  • 在 Webpack 的下一个大版本中,开发者打算加入一个叫 tree-shaking 的东西,tree-shaking 会排除掉引入模块中没有用到的部分。因此那也是一种办法。
  • 这样写是为了举一个例子,好让你理解我以前提到的要点。

(注:Lodash 这两种加载方式均可以用,由于它的开发者明确规定能够这么作,而不是全部的库均可以经过这种加载方式工做。)

不管如何,ES2015 代码现已在手,咱们要把它转化成 ES5 代码,这样它们就能在老式浏览器(事实上,在新版浏览器里 ES2015 的支持度还不错)里跑起来了。所以,咱们须要 Babel 及在 Webpack 中运行 Babel 的配套设施。至少要有 babel-core(Babel 的核心功能库),babel-loader(babel-core 的 Webpack 加载器接口),babel-preset-es2015(里面有 ES2015 到 ES5 的转化规则,这是 Babel 须要得知的)。同时咱们引进 babel-plugin-transform-runtimebabel-polyfill ,尽管它们实现方式有点不一样,但都用于改变 Babel 添加语法填充(polyfills)和辅助函数(helper functions)的方式。正是所以,它们适应于不一样种类的项目。你可能不想把它们俩都引入,两者择一便可,但我在这把它俩都引入,这样不管你选择哪一个,都能知道引入的方式。想知道更多的话,请访问 polyfill 和 runtime transform 的官方文档吧。

无论怎样,先安装它们:npm i -D babel-core babel-loader babel-preset-es2015 babel-plugin-transform-runtime babel-polyfill。再为它们配置 Webpack。首先,添加一个部分用于增添加载器。更新 webpack.config.js 以下:

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

咱们增长了一个 module 属性,其中包含了 rules 属性。rules 是一个数组,这个数组囊括每一个加载器的配置。咱们将把 babel-loader 相关配置加到这里。对于每个加载器,咱们都要配置至少两个参数:testloadertest 一般是一个正则表达式,它用以验证(test)每一个文件的绝对路径。咱们通常只验证文件后缀,例如:/\.js$/验证全部以 .js 结尾的文件。在这里,咱们把这个参数设为 /\.jsx?$/ 这样能够匹配到 .js 文件和 .jsx 文件,以便使用 React。接下来配置 loader 参数,它描述了在相应的 test 参数下,应该使用哪个加载器处理文件。

将加载器的名字所拼成的字符串传入该参数便可奏效,其中,名字用感叹号隔开,例如 'babel-loader!eslint-loader'eslint-loader 会比 babel-loader 先运行,由于 Webpack 的读取顺序是从右到左。若是某个加载器有特殊参数配置,你可使用 query string 语法。好比,要给 Babel 配置一个 fakeoption 参数为 true,咱们得把前面的例子改成 'babel-loader?fakeoption=true!eslint-loader'。若是你以为更易阅读和维护的话,也可使用 use 替代 loader 配置,这样能够传入一个数组替代此前的字符串。把以前的例子改成:use: ['babel-loader?fakeoption=true', 'eslint-loader'],更有甚者,你能够把它们写成多行以提升可读性。

目前咱们只用 Babel loader ,因此咱们的配置文件看起来像下面这个样子:

…
rules: [
    { test: /\.jsx?$/, loader: 'babel-loader' }
]
…复制代码

若是只用一个加载器,咱们还能够这样配置来替代 query string 的写法:使用 options 配置对象,它就是一个键值对 map。所以,对于 fakeoption 的例子,咱们的配置文件能够写成这样:

…
rules: [
    {
        test: /\.jsx?$/,
        loader: 'babel-loader',
        options: {
            fakeoption: true
        }
    }
]
…复制代码

用上面这种方式来配置咱们的 Babel 加载器:

…
rules: [
    {
        test: /\.jsx?$/,
        loader: 'babel-loader',
        options: {
            plugins: ['transform-runtime'],
            presets: ['es2015']
        }
    }
]
…复制代码

预设(presets)用于把 ES2015 特性转成 ES5,咱们也给 Babel 设置了已经安装的 transform-runtime 插件。如此前所言,该插件并不是必要,这里是为了演示。咱们也能够另建 .babelrc 文件独立配置这些参数,但那样不利于演示 Webpack。通常我推荐使用 .babelrc 文件,但在这里咱们仍是保持不变。

万事俱备,只欠东风。咱们须要告知 Babel 跳过处理 node_modules 中的文件,这样能够提升咱们的构建速度。添置 exclude 属性以告知加载器忽略目标目录下的文件,它的值是一个正则表达式,所以咱们这样写:/node_modules/

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

此外,咱们本应使用 include 属性来描述咱们仅读 src 目录,但我以为应该保持原样。因而,你应该能够再次执行 npm start 命令,而后获取为浏览器准备的 ES5 代码了。若想使用 polyfill 替代 transform-runtime 插件,你须要作一两处改动。首先删除 plugins: ['transform-runtime], 这行(若是不打算再用了,你也能够直接用 npm 卸载该插件)。接下来,编辑 Webpack 配置文件的 entry 部分以下:

entry: [
    'babel-polyfill',
    './src/main.js'
],复制代码

咱们把描述单一入口的字符串替换成了描述多入口的数组,新添的入口乃 语法填充(polyfill)。咱们将其置于首位,这样语法填充将会率先出如今打包后的文件里,由于咱们在代码里使用语法填充前,要确保它们已经存在。

除了借助 Webpack 配置文件,咱们本能够经过在 src/main.js 的首行加上 import 'babel-polyfill; 来达到相同的目的。而咱们却使用了配置文件,除了用于服务本例,更是为了用做一个演示多入口打包至单一文件的范例。好吧,那即是仓库里的 example3 分支。容我再说一遍,你能够运行 npm start 命令来确认项目正常运行。

另外一个例子:Handlebars 加载器

咱们再为项目添置一个加载器:Handlebars。Handlebars 加载器用以将 Handlebars 模版编译成函数,当你在 JS 中引入(import)一个 Handlebars 文件时,该文件编译成的函数就会被引入 JS 文件。这即是我喜欢 Webpack 加载器的地方:即使引入非 JS 文件,该文件也会在打包时被转化为 JS 里可用的东西。接下来的例子将会使用另外一个加载器:容许引入图片文件并将图片文件转化成 base64 编码的 URL 字符串,该字符串可被用于在 JS 中为页面添加內联图片。这也意味着,若是你串联多个加载器,其中一个甚至能优化把图片的文件大小。

一样,咱们首先安装这个加载器:执行 npm install -D handlebars-loader 命令。当你用的时候会发现 Handlebars 自己也是不可或缺的:执行 npm install -D handlebars 命令。这样你就能够在不更新加载器版本的状况下控制 Handlebars 的版本,它们能够分别独立迭代。

两者现已安装完毕,咱们弄一个 Handlebars 模板来用。在 src 目录下建立一个 numberlist.hbs 文件,其内容以下:

<ul>
  {{#each numbers as |number i|}}
    <li>{{number}}</li>
  {{/each}}
</ul>复制代码

该模板描绘了一个数组(变量名为 numbers ,也能够是别的变量名),建立了一个无序列表。

接下来,咱们调整此前的 JS 文件来使用模板输出一个列表,再也不止步于打印数组自己。main.js 看起来会像下面同样:

import { map } from 'lodash';
import template from './numberlist.hbs';

let numbers = map([1,2,3,4,5,6], n => n*n);

console.log(template({numbers}));复制代码

惋惜目前为止 Webpack 并不知道如何引入 numberlist.hbs ,由于它并不是 JS 文件。咱们能够在 import 的路径前加点东西通知 Webpack 要使用 Handlebars 加载器:

import { map } from 'lodash';
import template from 'handlebars-loader!./numberlist.hbs';

let numbers = map([1,2,3,4,5,6], n => n*n);

console.log(template({numbers}));复制代码

经过给路径增添加载器名字,并将名字和路径以感叹号隔开的前缀,咱们告知 Webpack 那个文件应该使用那个加载器。这样,咱们没必要在配置文件里添置任何东西。然而,在很有规模的项目里,你极有可能加载不止一个模板,因此,在配置文件里告知 Webpack 咱们使用 Handlebars ,以避免去引入模板时在路径前添加前缀,这样作会更有意义。那咱们就更新一下配置文件:

…
rules: [
    {/* babel loader config… */},
    { test: /\.hbs$/, loader: 'handlebars-loader' }
]
…复制代码

这部分至关简单。咱们所须要作的就是指定用 handlebars-loader 去处理以 .hbs 结尾的文件,仅此而已。咱们搞定了 Handlebars 同时也搞定了 example4 分支。如今,一旦运行 npm start ,你会看到 Webpack 打包输出以下内容:

<ul>
<li>1</li>
<li>4</li>
<li>9</li>
<li>16</li>
<li>25</li>
<li>36</li>
</ul>复制代码

Webpack 插件

插件是另外一种用来自定义 Webpack 功能的方式。你能够更自由地把它们添加到 Webpack 工做流(workflow)中,由于,除加载特殊文件类型以外,它们几乎不受限制。它们可被植入到任何地方,正因如此,他们更增强劲。我很难定义 Webpack 插件到底能作多少事情,所以我仅给出一个 npm 上的搜索结果列表 npm packages that have “webpack-plugin”,那应该不失为一个好的答案。

本教程中咱们只接触两个插件(其中一个立刻揭晓)。行文已至此你也知道个人风格,过多的例子咱们就不须要了。咱们首先上 HTML Webpack Plugin ,它的做用很纯粹:生成 HTML 文件 —— 终于能够开始进军浏览器了!

在使用该插件以前,咱们首先更新 npm 脚原本运行一个可以测试示例应用的简单服务器。先安装一个服务器:运行 npm i -D http-server 命令。接着,仿照下面的代码将此前的 execute 脚本改为 server 脚本。

"scripts": {
  "prebuild": "del-cli dist -f",
  "build": "webpack",
  "server": "http-server ./dist",
  "start": "npm run build -s && npm run server -s"
},
…复制代码

Webpack 完成构建后,npm start 会同时启动一个 web 服务器,将浏览器跳转到 localhost:8080 能够访问到你的页面。天然,咱们仍然须要靠插件来建立该页面,因此接下来,咱们须要安装插件:npm i -D html-webpack-plugin

安装完毕之后,咱们移步 webpack.config.js 并做以下修改:

var HtmlwebpackPlugin = require('html-webpack-plugin');

module.exports = {
    entry: [
        'babel-polyfill',
        './src/main.js'
    ],
    output: {
        path: './dist',
        filename: 'bundle.js'
    },
    module: {
        rules: [
            {
                test: /\.jsx?$/, loader: 'babel-loader', exclude: /node_modules/,
                options: { plugins: ['transform-runtime'], presets: ['es2015'] }
            },
            { test: /\.hbs$/, loader: 'handlebars-loader' }
        ]
    },
    plugins: [
        new HtmlwebpackPlugin()
    ]
};复制代码

咱们有做两处改动:其一在文件顶部引入新安装的插件,其二在配置对象尾部添置了一个 plugins 部分,并在此处传入了插件的实例对象。

目前咱们并无为该插件实例传入配置对象,默认使用它的基础模板,除了咱们打包好的脚本文件之外,该基础模版并无包含不少东西。在运行 npm start 后在浏览器访问相应 URL ,你会看到一空白页,但若在开发者工具中打开控制台,应该会看到里面打印出了 HTML。

咱们可能要得到模板并将 HTML 吐(spit out)到页面上而不是控制台里,这样一个“正常人”就能真正从页面上获得信息了。咱们先在 src 目录下建立 index.html 文件,这样就能定义本身的模板了。默认状况下,该插件用的是 EJS 模板语法,不过,你也能够配置该插件使其使用其它受到支持的模板语言。在这里咱们就用 EJS 由于用什么语法都没有实质区别,index.html 的内容以下:

<!DOCTYPE html>
<html lang="en"> <head> <meta charset="UTF-8"> <title><%= htmlWebpackPlugin.options.title %></title> </head> <body> <h2>This is my Index.html Template</h2> <div id="app-container"></div> </body> </html>复制代码

请注意几点:

  • 咱们将为插件传入一个配置对象来定义标题(仅仅由于咱们能作到)。
  • 没有具体指定该在哪里插入咱们的脚本文件,由于该插件默认会在 body 元素结尾前添加脚本。
  • 这里 div 的 id 并不是特定,咱们在这里随便取了一个。

如今咱们获得了想要的模板,最终不会只是一个空白页了。接下来更新 main.js ,把 HTML 结构加入那个 div 里以替代此前打印在控制台里。为此,咱们仅需更新 main.js 的最后一行:document.getElementById("app-container").innerHTML = template({numbers});

同时,咱们也须要更新 Webpack 配置文件,为插件传入两个参数。配置文件如今应改为这样:

var HtmlwebpackPlugin = require('html-webpack-plugin');

module.exports = {
    entry: [
        'babel-polyfill',
        './src/main.js'
    ],
    output: {
        path: './dist',
        filename: 'bundle.js'
    },
    module: {
        rules: [
            {
                test: /\.jsx?$/, loader: 'babel-loader', exclude: /node_modules/,
                options: { plugins: ['transform-runtime'], presets: ['es2015'] }
            },
            { test: /\.hbs$/, loader: 'handlebars-loader' }
        ]
    },
    plugins: [
        new HtmlwebpackPlugin({
            title: 'Intro to webpack',
            template: 'src/index.html'
        })
    ]
};复制代码

template 配置指定了模板文件的位置,title 配置被传入了模板。如今,运行 npm start,你将会在浏览器里看到下面的内容:

假如你一直跟着作的话,example5 分支便在此结束。不一样插件传入的参数或者配置项也大异其趣,其缘由在于插件种类繁多且涵盖范围广阔,但异曲同工的是,他们最终都会被添加到 webpack.config.jsplugins 数组中。一样,也有其余方式能够处理 HTML 页面的生成和文件名填充,一旦你开始为打包后的文件添加清缓存哈希值(cache-busting hashes)后缀,这些事情就会变得很是简单。

观察示例仓库,你会发现有一个 example6 分支,在该分支里我经过添加插件实现了 JS 代码压缩,但这不是必须的,除非你想改动 UglifyJS 配置。若是你不爽 UglifyJS 的默认配置,可将仓库切换 (check out)至该分支下(只须要查看 webpack.config.js )去找到如何使用该插件并加以配置。但若是默认配置正合你意,你只须要在命令行运行 webpack 时传入 -p 参数。该参数是 production 的简写,与使用 --optimize-minimize--optimize-occurence-order 参数的效果同样,前者用以压缩 JS 代码,后者用以优化已引入模块的顺序,着眼于稍小的文件尺寸和稍快的执行速度。在示例仓库完成一段时间后我才知道 -p 这个参数,因此我决定保存该插件示例,能够用来提醒你还有更简单的方法(除了添加插件以外)。另外一可供使用的快捷命令参数是 -d-d 会展现更多 Webpack 打印出的信息,而且可不借助其余参数生成资料图(source map)。还有不少其余命令行快捷参数可供使用。

懒加载数据块

懒加载(lazy-loading)模块是我在 RequireJS 中用得温馨但在 Browserify 中难以工做的模块。一个颇具规模的 JS 文件当然能够从减小网络请求中受益,但也几乎坐实了在一次会话中,某些用户没必要用到的代码会被下载下来。

Webpack 能够将打包文件拆分红可被懒加载的若干块(chunks),并且还不须要任何配置。你仅须要从两种书写方式中挑一种来书写代码,剩下的则交给 Webpack。这两种方式其一基于 CommonJS ,其二则基于 AMD。若是使用前者懒加载,须要这样写:

require.ensure(["module-a", "module-b"], function(require) {
    var a = require("module-a");
    var b = require("module-b");
    // …
});复制代码

require.ensure 须要确保模块是可用的(但并不是运行模块),而后传入一个由模块名构成的数组,接着传入一个回调函数(callback)。真正想要在回调函数里使用模块,你须要显式 require 数组里传入的相应模块。

私觉得这种方式相麻烦,因此,咱们来看 AMD 的写法。

require(["module-a", "module-b"], function(a, b) {
    // …
});复制代码

AMD 模式下,使用 require 函数,传入包含依赖模块名的数组,接着再传入回调函数。该回调函数的参数就是依赖模块的引用,它们的排列顺序与依赖模块在数组中的排列顺序相同。

Webpack 2 同时也支持 System.import,其借助于 promises 而非回调函数。尽管将回调内容包裹在 promise 下并不是难事,但我仍觉得该提高很是有用。不过须要注意的是, System.import 现已过期,较新的规范推荐使用 import()。不过,这里告诫一下, Babel (以及 TypeScript)会在你使用System.import的时候抛出语法异常。你能够借助于 babel-plugin-dynamic-import-webpack 插件,但该插件将会将其转化为 require.ensure,而不是让 Babel 合法处理新 import 或者任之由 Webpack 处置。我认为 AMD 或 require.ensure 在好久以后才会被弃置,且 Webpack 直到第三个版本才会支持 System.import ,那还远着呢,因此用你顺眼的那个就行了。

扩充咱们的代码,令其停滞两秒,而后再将 Handlebars 模板懒加载进来并输出到屏幕上。为此,咱们移除顶部 import 模板的语句,而后将最后一行包裹到 setTimeout 和 AMD 模式的 require 中引入模板。

运行 npm start ,你会发现生成了另一个名为 1.bundle.js 的资源文件(asset)。在浏览器打开该页面,而后在开发者工具中监听网络流量,2秒以后你会发现新的资源文件最终被加载而且运行了。以上这些实现起来并不困难,但提高用户体验可不止一点。

注意,这些二级打包文件(sub-bundles)或曰数据块(chunks),内部囊括了他们的全部依赖模块(dependencies),但不包含其主数据块(parent chunks)已引入的依赖模块。(你能够有多个入口文件,每一个都懒加载一个数据块,所以该数据块在其主数据块中加载的依赖模块也会不一样。)

建立公共库数据块 (Vendor Chunk)

咱们再说一个优化的点:公共库数据块。你能够定义一个单独用以打包的 bundle,该 bundle 中存放不常改动的 “common” 库或第三方代码。该策略可以使用户独立缓存你的公共库文件,以区别于业务代码,以便在你迭代应用时让用户无需从新下载该库文件。

为此,咱们使用 Webpack 官方插件:CommonsChunkPlugin。它已附带在 Webpack 中,因此咱们无需安装。仅对 webpack.config.js 稍做修改便可:

var HtmlwebpackPlugin = require('html-webpack-plugin');
var UglifyJsPlugin = require('webpack/lib/optimize/UglifyJsPlugin');
var CommonsChunkPlugin = require('webpack/lib/optimize/CommonsChunkPlugin');

module.exports = {
    entry: {
        vendor: ['babel-polyfill', 'lodash'],
        main: './src/main.js'
    },
    output: {
        path: './dist',
        filename: 'bundle.js'
    },
    module: {
        rules: [
            {
                test: /\.jsx?$/, loader: 'babel-loader', exclude: /node_modules/,
                options: { plugins: ['transform-runtime'], presets: ['es2015'] }
            },
            { test: /\.hbs$/, loader: 'handlebars-loader' }
        ]
    },
    plugins: [
        new HtmlwebpackPlugin({
            title: 'Intro to webpack',
            template: 'src/index.html'
        }),
        new UglifyJsPlugin({
            beautify: false,
            mangle: { screw_ie8 : true },
            compress: { screw_ie8: true, warnings: false },
            comments: false
        }),
        new CommonsChunkPlugin({
            name: "vendor",
            filename: "vendor.bundle.js"
        })
    ]
};复制代码

咱们在第三行引入该插件。此后,在 entry 部分修改配置,将其换成了一个对象字面量(literal),用以指定多入口。vendor 入口记录了会在公共库数据块中——这里包含了 polyfill 和 Lodash ——被引入的库并将咱们的主要入口放置在 main 入口里。接着,咱们仅需将 CommonsChunkPlugin 添加到 plugins 部分,指定 “vendor” 数据块做为该插件生成数据块的索引,同时指定 vendor.bundle.js 文件用以存放公共库代码(译者注:这里插件配置中的 name: "vendor" 对应 entry 中的 vendor 入口,入口数组中指定的依赖模块即最终存放于 vendor.bundle.js 文件中的依赖模块)。

经过指定 “vendor” 数据块,该插件将拉取此数据块全部的依赖模块,并将其存放于公共库数据块內,这些依赖模块在一个单独入口文件里被指定。若是不在入口对象字面量中指定数据块名,插件会基于多入口文件之间公用的依赖模块来生成独立文件。

运行 Webpack ,你将看到3份 JS 文件:bundle.js, 1.bundle.jsvendor.bundle.js。若是愿意的话也能够运行 npm start 命令来在浏览器中查看结果。看起来 Webpack 甚至会把自身加载不一样模块的主要代码放进公共库数据块,此举极为实用。

至此咱们结束了 example8 分支之旅,同时本篇教程也接近尾声。我所谈颇多,但仅让你对 Webpack 的能力浅尝辄止。Webpack 实现了更简便的 CSS module、清缓存、图片优化等等不少事情——多到即使书巨著一本,我也没法说穷道尽,且在我成书以前,大多数已写的内容也将被更新替代。So,尝试一下 Webpack 吧,且告诉我它有没有提高工做流。祝吾主保佑,编程愉快!

iKcamp原创新书《移动Web前端高效开发实战》已在亚马逊、京东、当当开售。

相关文章
相关标签/搜索