October 16th, 2015 by Maxime Fabre
(中文译本更新于2017年10月22日,因为webpack已更新至3.8.1,所以译文有改动,译文最后有各分段代码包地址,若有须要请自行下载)
javascript
以前你可能已经据说过这个叫webpack的很酷的工具,若是你没仔细了解过这个工具,你可能会有些困惑,由于有人说它像 Gulp 之类的构建工具,也有人说它像 Browserify 之类的模块管理工具,若是你有去仔细地了解一下,你可能仍是会不明白究竟是怎么一回事,由于官网上把webpack说成是这二者。css
说实话,开始的时候我对于“webpack究竟是什么“ 很模糊,而且感受很受挫,最后我直接就把网页关了,毕竟我在这以前已经有一个构建系统的工具了,并且我用得也很是嗨皮,若是你像我同样一直密切跟踪javascript的发展的话,可能你早就由于在各类流行的东西上频繁地跳来跳去而灰飞烟灭了。还好如今我有些经验了,以为能够写一篇文章来给那些还处在混沌中的小伙伴们,更清楚地解释一下webpack究竟是什么,更重要的是它到底什么地方那么出色以致于值得咱们投入那么多的精力。html
Webpack究竟是一个构建系统仍是一个模块管理器?好的,如今我立刻来回答一下───答案是两个都是,固然我不是说它两个事儿都干,个人意思是它把二者有机地链接起来了,webpack不是先构建你的资源,而后再管理你的模块,而是把你的资源自己当作模块。java
更准确的说,它不是先编译你全部的scss文件,再优化全部的图片,以后在一个地方引进来,再管理全部的模块,最后在另外一个地方引到你的页面上。假设你是下面这样:node
import stylesheet from 'styles/my-styles.scss';
import logo from 'img/my-logo.svg';
import someTemplate from 'html/some-template.html';
console.log(stylesheet); // "body{font-size:12px}"
console.log(logo);//"data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5[...]"
console.log(someTemplate) // "Hello"复制代码
如上面所示,你的全部的资源(不管是scss,html或者其它)均可以被当成模块,并且这些模块能够被import(引入),modify(修改),manipulate(操做),最后被打包到你的最终包(bundle)jquery
为了达到这个目的,得在你的webpack的配置文件里注册加载器(loaders),加载器就是当你遇到某种文件的时候,对它作相应处理的一种插件,下面是一些加载器的例子:webpack
{
// 当你导入(import)一个a.ts文件时,系统将会用Typescript加载器去解析它
test: /\.ts/,
loader: 'typescript',
},
{
// 当你的项目里面有图片时,系统将会用image-webpack加载器把它们压缩
// 而且它们会被关联到data64 URLs
test: /\.(png|jpg|svg)/,
loaders: ['url', 'image-webpack'],
},
{
// 当你的项目里面有scss文件时,系统将会经过node-sass加载器去解析,而且自动补充前缀
// 最后返回css(能够直接被系统解析)
test: /\.scss/,
loaders: ['css', 'autoprefixer', 'sass'],
}复制代码
最后全部的loader返回的都是string,这样webpack最终能够把资源都包装成javascript模块。就像这个例子里scss文件被loaders转换后,看起来差很少是这样子:
git
export default 'body{font-size:12px}';复制代码
一旦你明白webpack是什么后,极可能就会想到第二个问题:用它能有什么好处呢?“把Image和CSS放在个人JS里?什么鬼?”好吧,以前很长一段时间我一直被告诉要把全部文件都放在一个文件里,这样就保证不浪费咱们的HTTP请求。es6
但这样会致使一个很大的缺点,就是如今大多数人都把全部的资源打包到一个单独的app.js文件里,而后把这个文件引入到每个页面。这就意味着渲染每个页面的时候大部分时间都浪费在加载一大堆根本就没用到的资源上。可是若是你不这么作,那么你颇有可能得手动把这些资源引入到指定的页面,这就会致使须要一大团乱七八糟的依赖树去维护和跟踪一些问题例如:哪些页面须要依赖这个文件?修改a.css和b.css会影响到哪些页面?github
所以这两个方法都是不对,但也不全是错的。若是咱们把webpack当作一个中介───它不只仅是一个构建系统或者一个打包工具,而是一个邪恶的智能打包系统,正确地配置后,它甚至比你还了解你的系统,并且它也比你清楚怎么样才能最好优化你的系统。
为了让你更容易地理解webpack带来的好处,咱们作一个小的app,而后用webpack来打包咱们app的资源,在作以前我建议用Node 4及以上版本和NPM 3及以上版本,由于良好的依赖关系在你使用webpack的时候会避免不少让人头疼的问题,若是你的NPM版本不够新,你能够经过 npm install npm -g
来更新。
$ node --version
v6.11.2
$ npm --version
5.4.2复制代码
同时我也建议你把 node_modules/.bin 加到你的PATH环境变量里,以免每次都手动打 node_modules/.bin/webpack ,后面的全部例子都不会显示我要执行的命令行的 node_modules/.bin 部分(全局安装 webpack 则忽略此步骤)。
备注:全局安装 webpack 命令行:经过 npm install webpack -g
来更新。
如今开始,先建立个名为webpack-your-bags的文件夹,在文件夹下安装webpack,而且也加上jquery好在后面证实一些东西
$ npm init -y
$ npm install jquery --save
$ npm install webpack --save-dev
如今让咱们来建立一个app入口,用如今的纯ES5
路径:webpack-your-bags/src/index.js
var $ = require('jquery');
$('body').html('Hello');
复制代码
建立webpack的配置文件webpack.config.js,webpack.config.js是javascript,须要导出(export) 一个对象(object)
路径:webpack-your-bags/webpack.config.js
var path = require("path");//用于处理目录的对象,提升开发效率(__dirname须要引入path后才可使用)
var ROOT_PATH = path.resolve(__dirname);//获取当前整个模块文件所在目录的完整绝对路径
var BUILDS_PATH = path.resolve(ROOT_PATH, "builds");//获取咱们的builds目录完整绝对路径
module.exports = {
entry: './src',
output: {
path: BUILDS_PATH,
filename: 'bundle.js',
},
};复制代码
这里,entry会告诉webpack哪一个文件是你的app的入口点。这些是你的主文件,他们在依赖树的顶端,而后咱们告诉它编译咱们的资源到放在 builds 目录(路径:webpack-your-bags/builds)的bundle.js文件下,我如今再建立相应的 index.html
路径:webpack-your-bags/index.html
<!DOCTYPE html>
<html>
<body>
<h1>My title</h1>
<a>Click me</a>
<script src="builds/bundle.js"></script>
</body>
</html>复制代码
执行webpack,若是一切正常,会看见一个信息告诉咱们正确地编译了bundle.js
$ webpack
Hash: 65d56cd1e7dddf04958b
Version: webpack 3.8.1
Time: 250ms
Asset Size Chunks Chunk Names
bundle.js 271 kB 0 [emitted] [big] main
[0] ./src/index.js 51 bytes {0} [built]
+ 1 hidden module复制代码
这里webpack会告诉你bundle.js包含了咱们的入口点(index.js)和一个隐藏的模块,这个隐藏的模块就是jquery,默认状况下webpack会把第三方模块给隐藏掉,若是想要看见webpack编译的全部的模块 ,咱们能够加 --display-modules 参数
$ webpack --display-modules
Hash: 65d56cd1e7dddf04958b
Version: webpack 3.8.1
Time: 263ms
Asset Size Chunks Chunk Names
bundle.js 271 kB 0 [emitted] [big] main
[0] ./src/index.js 51 bytes {0} [built]
[1] ./node_modules/jquery/dist/jquery.js 268 kB {0} [built]
复制代码
若是你以为每次修改都要从新执行webpack
太麻烦,你也能够运行 webpack --watch
来自动监视文件的变化,若是文件有发生变化,便会自动从新编译。
还记得咱们讨论过webpack能够导入CSS和HTML还有各类各样的文件吗?何时能派上用场呢?好的,若是你过去几年都朝着web组件的方向大跃进(Angular 4, Vue, React, Polymer, X-Tag, etc.)。那么估计你听过这样一个观点:你的app若是是用一套可重用,自包含的UI组件搭建起来的话,会比一个单独的内聚的UI更容易维护───这个可重用的组件就是web 组件(在这里我说的比较简单,你能理解就行),如今为了让组件变成真正的自包含,须要把组件须要的全部的东西封装到他们本身的内部,好比咱们来考虑一个button,它的里面确定有一些HTML,并且还有一些JS来保证它的交互性,可能还得再来一些CSS,这些东西若是须要的时候再一块儿加载进来就显得很是完美了,也就是只有在咱们导入button组件的时候,咱们才会去加载这些资源文件。
如今咱们来写一个button,首先假设你已经熟悉了ES2015(即ES6,javascript的新标准),由于有些浏览器还不支持ES6,因此咱们须要babel来为咱们把ES6转为浏览器支持的ES5 ,咱们先加入babel的加载器(loader)。想要在webpack里安装一个loader,有两步须要作:1. npm install {whatever}-loader ;2.把它加到你的webpack配置文件(webpack.config.js)里的 module.loaders 部分,好的,那如今咱们要加babel,因此先安装:
$ npm install babel-loader --save-dev
咱们也得安装babel自己,由于如今咱们这个例子加载器不会自动安装它们,所以咱们须要装babel-core
这个包和它的预设 es2015
(也就是在代码被执行前执行的转换器版本):
$ npm install babel-core babel-preset-es2015 --save-dev
--save-dev : 项目开发过程当中须要依赖的包,发布以后无需依赖的包,例如咱们的babel
,开发过程须要babel
为咱们把书写的es6转为es5,发布以后因为咱们全部书写的es6代码都被转为es5,所以无需继续依赖;
--save : 项目发布后依然须要依赖的包,例如咱们jquery
;
咱们如今建立 .babelrc 文件,告诉babel用哪一个预设,这是一个json文件告诉babel在你的代码上执行哪一种转换器,如今咱们告诉它用es2015:
路径:webpack-your-bags/.babelrc
{
"presets": ["es2015"]
}
复制代码
如今babel配置完了,咱们能够更新webpack的配置(也就是webpack.config.js文件):咱们想要什么?咱们想babel在咱们全部以.js结尾的文件上运行, 可是因为webpack会遍历全部的依赖,可是咱们想避免babel运行在第三方代码上,如jquery,因此咱们能够再稍微过滤一下,loaders既能够有 include 也能够是 exclude,能够是字符串(string),正则表达式(regex),也能够是一个回调(callback),随便你用哪一个。由于咱们想让babel只运行在咱们的文件上,因此咱们只须要include到咱们本身的source目录也就是src文件夹下:
var path = require("path");//用于处理目录的对象,提升开发效率(__dirname须要引入path后才可使用)
var ROOT_PATH = path.resolve(__dirname);//获取当前整个模块文件所在目录的完整绝对路径
var BUILDS_PATH = path.resolve(ROOT_PATH, "builds");//获取咱们的builds目录完整绝对路径
var SRC_PATH = path.resolve(ROOT_PATH, "src");//获取到咱们的资源目录src的完整路径
module.exports = {
entry: './src',
output: {
path: BUILDS_PATH,
filename: 'bundle.js',
},
module: {
loaders: [{
test: /\.js/,
loader: 'babel-loader',
include: SRC_PATH,
}],
}
};
复制代码
如今咱们能够用ES6重写一下咱们的 index.js,因为咱们引入了babel,从这里开始后面的全部例子都用ES6。
import $ from 'jquery';
$('body').html('Hello');
复制代码
如今咱们来写一个小的Button组件,它须要有一些SCSS样式(SCSS是SASS 3 引入新的语法,SASS是CSS的一种预处理器),一个HTML模板,和一些JS交互,那么咱们先安装一些咱们须要的东西。首先咱们用一个很是轻量的模板包Mustache(先后端分离的上古框架,了解就好),咱们也须要给SCSS和HTML配置loaders,由于结果会经过管道(pipe)从一个loader传到另外一个,这里有点绕,怎么说呢,相似净水器,咱们写的SCSS经过第一个loader净化以后变成CSS,CSS经过管道(pipe)流向第二个loader,CSS再经过第二个loader又变成能够被 import 的STYLE模块等等等过程,好了,因此咱们须要一个 sass-loader 加载器来“净化”SCSS,一旦咱们获得了CSS,会有多种方式处理它,这里,咱们用一个 style-loader 加载器,它会接收一段CSS,而后动态地把它插入到页面。
$ npm install mustache --save
$ npm install css-loader style-loader html-loader sass-loader node-sass --save-dev
复制代码
为了让webpack用管道(pipe)把东西从一个loader传到另外一个loader,咱们简传入几个loader方向由右到左,用一个 ! 分开,或者你能够用一个数组经过 loaders 属性,不是 loader:
{
test: /\.js/,
loader: 'babel-loader',
include: SRC_PATH,
}, {
test: /\.scss/,
loader: 'style-loader!css-loader!sass-loader',
// Or
//loaders: ['style-loader', 'css-loader', 'sass-loader'],
}, {
test: /\.html/,
loader: 'html-loader',
}
复制代码
如今咱们把loaders准备好了,咱们来写一个button:
路径:webpack-your-bags/src/Components/Button.scss
.button {background: tomato; color: white; }
复制代码
路径:webpack-your-bags/src/Components/Button.html
<a class="button" href="{{link}}">{{text}}</a>
复制代码
路径:webpack-your-bags/src/Components/Button.js
import $ from 'jquery';
import template from './Button.html';
import Mustache from 'mustache';
import './Button.scss';
export default class Button {
constructor(link) {
this.link = link;
}
onClick(event) {
event.preventDefault();
alert(this.link);
}
render(node) {
const text = $(node).text();
// 渲染咱们的按钮
$(node).html(
Mustache.render(template, {text})
);
// 增长监听事件
$('.button').click(this.onClick.bind(this));
}
}
复制代码
你的Button如今是百分之百的完总体了,不管何时导入,不管运行在什么地方,它都有全部须要的东西,而后正确地渲染到那个地方,如今咱们只须要把咱们的Button经过index.js渲染到咱们的网页上:
// import $ from 'jquery';
// $('body').html('Hello');
import Button from './Components/Button';
const button = new Button('google.com');
button.render('a');
复制代码
咱们来运行一下webpack,而后刷新一下index.html页面,你应该就能看见你的挺丑的button出来了。
如今你已经学会怎么配置loader和怎么给你的app的每个部分定义依赖,可是如今这些可能看起来已经不过重要,由于咱们要把这个例子再作一下改进。
上面这个例子还不错由于什么都有,但咱们有时可能不须要咱们的Button,由于有些界面若是没有 a 标签的话,那就不须要咱们作无谓的把 a 标签渲染成 Button 的操做了。也就是说咱们根本就不须要导入咱们上面定制的Button的样式,模板,Mustache 和一切相关的东西了对吧?这个时候咱们就须要代码拆分了。
代码拆分是webpack对咱们去手动把Button导入须要的界面相关繁琐操做的解决方案,也就是有了webpack你根本不用去找哪一个页面须要导入,哪一个页面不用导入。代码拆分实质是在你的代码里定义分割点:你的代码能够轻松拆分开到部分单独文件中,而后按需加载,语法很是简单:
import $ from 'jquery';
// 这是一个拆分点
require.ensure([], () => {
// 这里全部的代码都是须要被导入的
// 在一个单独的页面里
const library = require('some-big-library');
$('foo').click(() => library.doSomething());
});
复制代码
在 require.ensure 的回调(即() => {}
)里的任何东西都会被拆分红代码块(chunk) ─── 页面须要加载的时候会经过ajax单独加载的包,这意味着咱们的包基本上有这些:
bundle.js
|- jquery.js
|- index.js // 咱们全部文件都要导入的代码
chunk1.js
|- some-big-libray.js
|- index-chunk.js // 在回调里面的代码
复制代码
你无需导入 chunk1.js ,webpack只有在须要它的时候才会去加载它,这意味着你能够把你的代码按照各类逻辑分红多块,咱们将在下面修改咱们的 index.js ,让只在页面里有 a 标签的时候才加载 Button:
if (document.querySelectorAll('a').length) {
require.ensure([], () => {
const Button = require('./Components/Button').default;
const button = new Button('google.com');
button.render('a');
});
}
复制代码
注意,用 require 的时候,若是你想获取Button对象 export default 的东西你须要经过 .default 去手动获取它,由于 require 不会同时处理 export default 和 exports.obj,因此你必须指定返回哪一个,不过 import 能够处理这个,因此它已经知道了(例如: import foo from 'bar'获取到'bar'对象export default的东西, import {baz} from 'bar'获取到'bar'对象的exports.baz的东西)。这里的东西有点复杂,若是不是很理解又想理解能够去深刻了解一下ES6和nodeJS。
如今 webpack 的 output 也应该会相应地不一样了,咱们来用webpack --display-modules --display-chunks
运行一下,看一下哪一个模块在哪一个chunk里
$ webpack --display-modules --display-chunks
Hash: c419d385603afdd301ab
Version: webpack 3.8.1
Time: 1489ms
Asset Size Chunks Chunk Names
0.bundle.js 307 kB 0 [emitted] [big]
bundle.js 6.48 kB 1 [emitted] main
chunk {0} 0.bundle.js 305 kB {1} [rendered]
[1] ./src/Components/Button.js 1.92 kB {0} [built]
[2] ./node_modules/jquery/dist/jquery.js 268 kB {0} [built]
[3] ./src/Components/Button.html 70 bytes {0} [built]
[4] ./node_modules/mustache/mustache.js 19.4 kB {0} [built]
[5] ./src/Components/Button.scss 1.16 kB {0} [built]
[6] ./node_modules/css-loader!./node_modules/sass-loader/lib/loader.js!./src/Components/Button.scss 219 bytes {0} [built]
[7] ./node_modules/css-loader/lib/css-base.js 2.26 kB {0} [built]
[8] ./node_modules/style-loader/lib/addStyles.js 9.41 kB {0} [built]
[9] ./node_modules/style-loader/lib/urls.js 3.01 kB {0} [built]
chunk {1} bundle.js (main) 615 bytes [entry] [rendered]
[0] ./src/index.js 615 bytes {1} [built]
[0] ./src/index.js 615 bytes {1} [built]
[1] ./src/Components/Button.js 1.92 kB {0} [built]
[2] ./node_modules/jquery/dist/jquery.js 268 kB {0} [built]
[3] ./src/Components/Button.html 70 bytes {0} [built]
[4] ./node_modules/mustache/mustache.js 19.4 kB {0} [built]
[5] ./src/Components/Button.scss 1.16 kB {0} [built]
[6] ./node_modules/css-loader!./node_modules/sass-loader/lib/loader.js!./src/Components/Button.scss 219 bytes {0} [built]
[7] ./node_modules/css-loader/lib/css-base.js 2.26 kB {0} [built]
[8] ./node_modules/style-loader/lib/addStyles.js 9.41 kB {0} [built]
[9] ./node_modules/style-loader/lib/urls.js 3.01 kB {0} [built]
复制代码
能够看到咱们的入口( bundle.js )如今只有一些webpack的逻辑,其它的东西(jQuery, Mustache, Button) 都在 0.bundle.js ,只有页面上有 a 标签的时候才会加载 0.bundle.js,为了让webpack知道用ajax加载的时候在哪能找到chunks,咱们必须在咱们的配置中加一行:
output: {
path: BUILDS_PATH,
filename: 'bundle.js',
publicPath: 'builds/',
},
复制代码
output.publicPath 选项告诉 webpack 相对于当前的文件在哪能找到构建后的资源,如今访问咱们的页面咱们会看到一切都正常工做,但更重要的是,咱们能看到,因为页面上有 a 标签,所以webpack准确地加载了咱们拆分出来的代码块 0.bundle.js:
若是咱们的页面上没有 a 标签,就只有 bundle.js 会被加载,这点可让你智能地把你的app里的大片逻辑拆分开,让每一个页面只加载它真正须要的,咱们也能够给咱们拆分出来的代码包的Chunk Names命名 ,咱们能够用语义的名字,你能够经过传给 require.ensure 第三个参数来指定:
require.ensure([], () => {
const Button = require('./Components/Button').default;
const button = new Button('google.com');
button.render('a');
}, 'button');
复制代码
这样就会生成代码包的 Chunk Names 就是 button 而不是空白了:
$ webpack
Hash: 50ed6a7993b581f0bf0a
Version: webpack 3.8.1
Time: 1524ms
Asset Size Chunks Chunk Names
0.bundle.js 307 kB 0 [emitted] [big] button
bundle.js 6.49 kB 1 [emitted] main
[0] ./src/index.js 625 bytes {1} [built]
[1] ./src/Components/Button.js 1.92 kB {0} [built]
[3] ./src/Components/Button.html 70 bytes {0} [built]
[5] ./src/Components/Button.scss 1.16 kB {0} [built]
[6] ./node_modules/css-loader!./node_modules/sass-loader/lib/loader.js!./src/Components/Button.scss 219 bytes {0} [built]
+ 5 hidden modules
复制代码
如今什么都有了已经很是酷了,不过咱们再来加一个组件看看好很差使:
路径:webpack-your-bags/src/Components/Header.scss
.header {
font-size: 3rem;
}
复制代码
路径:webpack-your-bags/src/Components/Header.html
<header class="header">{{text}}</header>
复制代码
路径:webpack-your-bags/src/Components/Header.js
import $ from 'jquery';
import Mustache from 'mustache';
import template from './Header.html';
import './Header.scss';
export default class Header {
render(node) {
const text = $(node).text();
$(node).html(
Mustache.render(template, { text })
);
}
}
复制代码
咱们在咱们的 index.js 里把它渲染一下:
// If we have an anchor, render the Button component on it
if (document.querySelectorAll('a').length) {
require.ensure([], () => {
const Button = require('./Components/Button').default;
const button = new Button('google.com');
button.render('a');
});
}
// If we have a title, render the Header component on it
if (document.querySelectorAll('h1').length) {
require.ensure([], () => {
const Header = require('./Components/Header').default;
new Header().render('h1');
});
}
复制代码
如今用 --display-chunks --display-modules 来看一下 webpack 的 output:
$ webpack --display-modules --display-chunks
Hash: 66f9e900ac553f5d66eb
Version: webpack 3.8.1
Time: 1646ms
Asset Size Chunks Chunk Names
0.bundle.js 306 kB 0 [emitted] [big]
1.bundle.js 307 kB 1 [emitted] [big]
bundle.js 6.56 kB 2 [emitted] main
chunk {0} 0.bundle.js 305 kB {2} [rendered]
[2] ./src/Components/Header.js 1.7 kB {0} [built]
[3] ./node_modules/jquery/dist/jquery.js 268 kB {0} {1} [built]
[4] ./node_modules/mustache/mustache.js 19.4 kB {0} {1} [built]
[5] ./node_modules/css-loader/lib/css-base.js 2.26 kB {0} {1} [built]
[6] ./node_modules/style-loader/lib/addStyles.js 9.41 kB {0} {1} [built]
[7] ./node_modules/style-loader/lib/urls.js 3.01 kB {0} {1} [built]
[11] ./src/Components/Header.html 62 bytes {0} [built]
[12] ./src/Components/Header.scss 1.16 kB {0} [built]
[13] ./node_modules/css-loader!./node_modules/sass-loader/lib/loader.js!./src/Components/Header.scss 199 bytes {0} [built]
chunk {1} 1.bundle.js 305 kB {2} [rendered]
[1] ./src/Components/Button.js 1.92 kB {1} [built]
[3] ./node_modules/jquery/dist/jquery.js 268 kB {0} {1} [built]
[4] ./node_modules/mustache/mustache.js 19.4 kB {0} {1} [built]
[5] ./node_modules/css-loader/lib/css-base.js 2.26 kB {0} {1} [built]
[6] ./node_modules/style-loader/lib/addStyles.js 9.41 kB {0} {1} [built]
[7] ./node_modules/style-loader/lib/urls.js 3.01 kB {0} {1} [built]
[8] ./src/Components/Button.html 70 bytes {1} [built]
[9] ./src/Components/Button.scss 1.16 kB {1} [built]
[10] ./node_modules/css-loader!./node_modules/sass-loader/lib/loader.js!./src/Components/Button.scss 219 bytes {1} [built]
chunk {2} bundle.js (main) 601 bytes [entry] [rendered]
[0] ./src/index.js 601 bytes {2} [built]
[0] ./src/index.js 601 bytes {2} [built]
[1] ./src/Components/Button.js 1.92 kB {1} [built]
[2] ./src/Components/Header.js 1.7 kB {0} [built]
[3] ./node_modules/jquery/dist/jquery.js 268 kB {0} {1} [built]
[4] ./node_modules/mustache/mustache.js 19.4 kB {0} {1} [built]
[5] ./node_modules/css-loader/lib/css-base.js 2.26 kB {0} {1} [built]
[6] ./node_modules/style-loader/lib/addStyles.js 9.41 kB {0} {1} [built]
[7] ./node_modules/style-loader/lib/urls.js 3.01 kB {0} {1} [built]
[8] ./src/Components/Button.html 70 bytes {1} [built]
[9] ./src/Components/Button.scss 1.16 kB {1} [built]
[10] ./node_modules/css-loader!./node_modules/sass-loader/lib/loader.js!./src/Components/Button.scss 219 bytes {1} [built]
[11] ./src/Components/Header.html 62 bytes {0} [built]
[12] ./src/Components/Header.scss 1.16 kB {0} [built]
[13] ./node_modules/css-loader!./node_modules/sass-loader/lib/loader.js!./src/Components/Header.scss 199 bytes {0} [built]
复制代码
能看见这里有一个问题:咱们的两个组件都会用到 jQuery 和 Mustache,也就是说这两个依赖在chunk里有重复,虽然webpack 默认会作一点点的优化,可是它也能够以 plugin(插件) 的形式来给 webpack 提供更强大的功能。
plugin 跟 loader 不一样,它不是对指定的文件执行一些操做,而是对全部文件进行处理,作一些更高级的操做,但不必定非得像 loader 同样是转换,webpack 中有自带 plugin 能够作各类优化,此时咱们比较感兴趣的一个是 CommonChunksPlugin:它分析你的chunk的递归依赖,而后把它们抽出来放在别的地方,能够是一个彻底独立的文件或者是你的主文件。
在咱们如今的例子中,咱们须要把公用的依赖移到咱们的entry(入口) 文件,若是全部的文件须要jQuery 和 Mustache,咱们也能够把它往上层移动,咱们来更新一下咱们的配置文件webpack.config.js :
......
var webpack = require('webpack');
module.exports = {
entry: './src',
output: {
// ...
},
plugins: [
new webpack.optimize.CommonsChunkPlugin({
name: 'main', // 将依赖移到咱们的主文件
children: true, // 在全部被拆分的代码块中寻找共同的依赖关系
minChunks: 2, // 在被提取以前,一个依赖要出现多少次(也就是一个依赖会在遍历全部拆分的代码块时被重复发现多少次)
}),
],
module: {
// ...
}
};
复制代码
若是咱们从新运行一下 webpack --display-modules --display-chunks
,咱们能够看到如今比以前好多了,由于 0.bundle.js 和 1.bundle.js 的共同部分都被移到了 bundle.js 。
$ webpack --display-modules --display-chunks
Hash: 22fbf6bdd63c4bbbb096
Version: webpack 3.8.1
Time: 1554ms
Asset Size Chunks Chunk Names
0.bundle.js 3.36 kB 0 [emitted]
1.bundle.js 3.58 kB 1 [emitted]
bundle.js 310 kB 2 [emitted] [big] main
chunk {0} 0.bundle.js 3.12 kB {2} [rendered]
[7] ./src/Components/Header.js 1.7 kB {0} [built]
[11] ./src/Components/Header.html 62 bytes {0} [built]
[12] ./src/Components/Header.scss 1.16 kB {0} [built]
[13] ./node_modules/css-loader!./node_modules/sass-loader/lib/loader.js!./src/Components/Header.scss 199 bytes {0} [built]
chunk {1} 1.bundle.js 3.37 kB {2} [rendered]
[6] ./src/Components/Button.js 1.92 kB {1} [built]
[8] ./src/Components/Button.html 70 bytes {1} [built]
[9] ./src/Components/Button.scss 1.16 kB {1} [built]
[10] ./node_modules/css-loader!./node_modules/sass-loader/lib/loader.js!./src/Components/Button.scss 219 bytes {1} [built]
chunk {2} bundle.js (main) 303 kB [entry] [rendered]
[0] ./src/index.js 601 bytes {2} [built]
[1] ./node_modules/style-loader/lib/urls.js 3.01 kB {2} [built]
[2] ./node_modules/jquery/dist/jquery.js 268 kB {2} [built]
[3] ./node_modules/mustache/mustache.js 19.4 kB {2} [built]
[4] ./node_modules/css-loader/lib/css-base.js 2.26 kB {2} [built]
[5] ./node_modules/style-loader/lib/addStyles.js 9.41 kB {2} [built]
[0] ./src/index.js 601 bytes {2} [built]
[1] ./node_modules/style-loader/lib/urls.js 3.01 kB {2} [built]
[2] ./node_modules/jquery/dist/jquery.js 268 kB {2} [built]
[3] ./node_modules/mustache/mustache.js 19.4 kB {2} [built]
[4] ./node_modules/css-loader/lib/css-base.js 2.26 kB {2} [built]
[5] ./node_modules/style-loader/lib/addStyles.js 9.41 kB {2} [built]
[6] ./src/Components/Button.js 1.92 kB {1} [built]
[7] ./src/Components/Header.js 1.7 kB {0} [built]
[8] ./src/Components/Button.html 70 bytes {1} [built]
[9] ./src/Components/Button.scss 1.16 kB {1} [built]
[10] ./node_modules/css-loader!./node_modules/sass-loader/lib/loader.js!./src/Components/Button.scss 219 bytes {1} [built]
[11] ./src/Components/Header.html 62 bytes {0} [built]
[12] ./src/Components/Header.scss 1.16 kB {0} [built]
[13] ./node_modules/css-loader!./node_modules/sass-loader/lib/loader.js!./src/Components/Header.scss 199 bytes {0} [built]
复制代码
你也能够经过不提供一个公用的块名(chunk name)或者是指定 async: true
来让公用的dependency被异步加载,webpack有很是多这样强大的智能优化。我不可能全列出来,但做为练习,让咱们尝试来给咱们的app建一个生产版本。
好吧,首先,咱们加几个 plugin 到配置文件中,但咱们只想在 NODE_ENV 等于 production 时才加载这些 plugin ,因此让咱们来给配置文件加一些逻辑,由于只是一个 js 文件,因此比较简单:
......
var webpack = require('webpack');
var production = process.env.NODE_ENV === 'production';
var plugins = [
new webpack.optimize.CommonsChunkPlugin({
name: 'main', // 将依赖移到咱们的主文件
children: true, // 在全部被拆分的代码块中寻找共同的依赖关系
minChunks: 2, // 在被提取以前,一个依赖要出现多少次(也就是一个依赖会在遍历全部拆分的代码块时被重复发现多少次)
}),
];
if (production) {
plugins = plugins.concat([
// 生产环境的插件放在这里
]);
}
module.exports = {
entry: './src',
output: {
path: BUILDS_PATH,
filename: 'bundle.js',
publicPath: 'builds/',
},
plugins: plugins,
......
};
复制代码
第二步,Webpack有一些设置咱们也能够在生产中关闭:
......
if (production) {
plugins = plugins.concat([
new webpack.LoaderOptionsPlugin({
debug: true
}),
......
]);
}
module.exports = {
//debug: !production,
//loaders的调试模式已经在webpack3及以上版本彻底移除,为了保持与旧的loaders的兼容性,loaders能够经过LoaderOptionsPlugin插件切换到调试模式
devtool:production ? false : 'eval',
......
}
复制代码
第1个设置为非debug模式,这意味着系统不会处理太多的代码,让你能够更轻松地调试本地的资源,第2个是控制 sourcemap 生成的,webpack有几个方法能够呈现 sourcemap,自带的 eval 是最好的,生产环境咱们不太关心 sourcemap 的事,因此咱们把它禁用掉,下面加一下咱们的生产插件(plugins):
if (production) {
plugins = plugins.concat([
// 这个插件会寻找相似的块和文件而后进行合并
//new webpack.optimize.DedupePlugin(),
//webpack3版本以后这个插件已经被移除
// 这个插件会根据模块在你的程序中使用的次数来优化模块
//new webpack.optimize.OccurenceOrderPlugin(),
//webpack2版本以后这个插件的功能已是默认开启,无需调用
// 这个插件能够防止webpack建立过小以致于不值得单独加载的块
new webpack.optimize.MinChunkSizePlugin({
minChunkSize: 51200, // ~50kb
}),
//这个插件会最小化全部最终资源的javascript代码
new webpack.optimize.UglifyJsPlugin({
mangle: true,
compress: {
warnings: false, // 阻止难看的警告
},
}),
//loaders的最小化模式也会在webpack3或者之后的版本中移除
// 这个插件容许咱们能够在生产中设置各类为错误的变量,以免它们被编译在咱们的最后打包的代码中
new webpack.DefinePlugin({
__SERVER__: !production,
__DEVELOPMENT__: !production,
__DEVTOOLS__: !production,
'process.env': {
BABEL_ENV: JSON.stringify(process.env.NODE_ENV),
},
}),
]);
}
复制代码
上面这几个是我最经常使用的,不过webpack提供不少插件(plugin)用来优化处理你的modules和chunks,npm 上也有其余人写的有各类功能的插件(plugin),自行按需选择。
关于生产环境下的资源的另外一个方面是,咱们有时想给咱们的打包后的资源加版本,还记得在上面咱们把 output.filename 设置为 bundle.js 吧,这有几个变量能够在命名的时候用,其中一个是 [hash] ,表明最终资源文件的版本,同时用 output.chunkFilename 给chunk也加上版本,如今让咱们来修改咱们的配置文件:
output: {
path: 'builds',
filename: production ? '[name]-[hash].js' : 'bundle.js',
chunkFilename: '[name]-[chunkhash].js',
publicPath: 'builds/',
},
复制代码
因为没有什么特别好的办法能动态地获取这个简化版app编译后的bundle的名字(因为加了版本号),因此只是在生产环境上给资源加版本,屡次编译后目录里带版本的最终包会愈来愈多,因此为了节省空间,还得加一个第三方插件来每次清理咱们以前的生产版本(output 里面的 path):
$ npm install clean-webpack-plugin --save-dev
并把它添加到咱们的配置里:
var webpack = require('webpack');
var CleanPlugin = require('clean-webpack-plugin');
// ...
if (production) {
plugins = plugins.concat([
// 清理之前的版本/文件夹
// 编译咱们的最终资源
new CleanPlugin('builds'),
......
}
......
复制代码
OK,作完了几个小优化,下面比较一下结果:
$ webpack
Hash: 8b942bcf553d3d782e6c
Version: webpack 3.8.1
Time: 1542ms
Asset Size Chunks Chunk Names
0-294790a46bbc09afc580.js 4.34 kB 0 [emitted]
1-24fa8b23bd35ecac8a8f.js 4.57 kB 1 [emitted]
bundle.js 348 kB 2 [emitted] [big] main
......
复制代码
$ NODE_ENV=production webpack
clean-webpack-plugin: D:\other\webpack-your-bags\builds has been removed.
Hash: 22674bf33e7cdefb2776
Version: webpack 3.8.1
Time: 2973ms
Asset Size Chunks Chunk Names
main-22674bf33e7cdefb2776.js 101 kB 0 [emitted] main
......
复制代码
那么webpack到底作了什么呢?首先,因为咱们的例子是很是轻量级的,因此咱们的两个异步块不是HTTP请求,所以Webpack将它们合并到入口。并且全部的东西都被最小化了,咱们从总共356KB的3个HTTP请求到101KB的1个HTTP请求。
是的,的确是这样,但这只是由于咱们的应用程序很是的小才发生的。如今考虑到一点:你根本想不到什么被合并,而且在什么地方何时被合并。若是你拆分的代码块忽然有了更多的依赖,那么这些拆分块将被移动到一个异步块,而不是被合并。 若是这些拆分块过于类似以致于不值得单独加载,它们将被合并,你仅仅须要配置一下,webpack就会用最好的方式自动优化你的应用程序。不用你去手动操做,也不用去想依赖在哪些时候或者哪些地方须要被依赖,一切都是全自动。
你可能已经注意到咱们没有使用任何设置来最小化咱们的HTML和CSS,那是由于在默认状况下,若是调试(debug)选项是咱们以前所说的false的话, css-loader 和 html-loader 就会去帮咱们作这件事。这也是为何UglifyJsPlugin是一个单独的插件(而不是一个loader)的缘由:由于在webpack中没有js-loader,由于webpack中自己就是JS的加载器。
好吧,如今你可能已经注意到,因为本教程的一开始,咱们的样式就已经在网页里面,而且作出了一个很是丑的按钮。如今若是咱们能够把全部的样式经过webpack构建出一个最终的CSS文件,岂不是很好?咱们固然能够啦,让咱们在一些外部的插件(plugin)的帮助下实现这种操做:
$ npm install extract-text-webpack-plugin --save-dev
这个插件具体是作什么的我来讲一下:就是从你的最终的资源中提取一些指定类型的内容,这个常常用在CSS上,所以让咱们来配置一下webpack.config.js(在这里切合版本的更替,咱们将用module.rules替换module.loaders,由于在将来module.loaders将彻底被module.rules代替):
var webpack = require('webpack');
var CleanPlugin = require('clean-webpack-plugin');
var ExtractPlugin = require('extract-text-webpack-plugin');
var production = process.env.NODE_ENV === 'production';
var plugins = [
new ExtractPlugin('bundle.css'), // <=== 内容将被打包到哪里
new webpack.optimize.CommonsChunkPlugin({
name: 'main', // 将依赖移到咱们的主文件
children: true, // 在全部被拆分的代码块中寻找共同的依赖关系
minChunks: 2, // 在被提取以前,一个依赖要出现多少次(也就是一个依赖会在遍历全部拆分的代码块时被重复发现多少次)
}),
];
......
module.exports = {
......
plugins: plugins,
module: {
rules: [{
test: /\.js/,
loader: 'babel-loader',
include: SRC_PATH,
},{
test: /\.html/,
loader: 'html-loader',
},{
test: /\.scss$/,
use: ExtractPlugin.extract({
fallback: 'style-loader',
use: ['css-loader', 'sass-loader']
})
}],
// loaders: [{
// test: /\.js/,
// loader: 'babel-loader',
// include: SRC_PATH,
// },{
// test: /\.html/,
// loader: 'html-loader',
// },
// {
// test: /\.scss/,
// loader: 'style-loader!css-loader!sass-loader',
// // Or
// // loaders: ['style-loader', 'css-loader', 'sass-loader'],
// }
// ]
}
};
复制代码
如今 rules 的 extract 方法有两个参数:第一个是当咱们在一个代码块('style-loader')中,未提取的CSS该怎么办,二是当资源在一个主文件('css-loader!sass-loader')中时该怎么转换。如今若是咱们在一个代码块中,那么在使用 loader 前咱们不能只是奇迹般地让这个代码块中的CSS追加到生成的CSS中,但对于那些在主文件中找到的样式,它们就应该被打包到一个builds/bundle.css文件中。如今让咱们来测试一下,添加一个小主样式表到咱们的应用程序中:
路径:webpack-your-bags/src/styles.scss
body {
font-family: sans-serif;
background: darken(white, 0.2);
}
复制代码
修改咱们的 index.js :
import './styles.scss';
......
复制代码
让咱们运行webpack,果真咱们如今有一个bundle.css,如今咱们能够在咱们的HTML文件导入:
$ webpack
Hash: 6890269852e3f4363514
Version: webpack 3.8.1
Time: 1607ms
Asset Size Chunks Chunk Names
0-4a6171ca8018a700cec4.js 4.26 kB 0 [emitted]
1-ffe316264a9c9fafbb4c.js 4.58 kB 1 [emitted]
bundle.js 348 kB 2 [emitted] [big] main
bundle.css 70 bytes 2 [emitted] main
......复制代码
这个时候刷新页面可能会出错,由于你必须将全部CSS文件合并到一个文件里面去,你能够传递选项ExtractTextPlugin('bundle.css', {allChunks: true})
,你也能够在这里的文件名使用变量,若是你想要一个版本的样式你只须要作ExtractTextPlugin('[name]-[hash].css')
。
如今对咱们全部的JavaScript文件来讲是很是完美的了,但咱们尚未谈论的一个话题是具体的资源:图像、字体等在webpack里是如何工做的而且是如何通过优化的呢?下面让咱们从网站上拷一张图片,而且咱们将使用它做为咱们的网页背景,由于我见过人们把它放在Geocities上并且看起来挺酷的:
让咱们保存这一张图片在 webpack-your-bags/img/puppy.jpg ,并相应地更新咱们的styles.scss:
body{
font-family:sans-serif;
background:url('../img/puppy.jpg') no-repeat 0 0;
background-size:50%;
}
复制代码
若是你这样作, webpack 会义正词严地告诉你“它吗的究竟是作个什么png出来”,由于咱们没有加载它。这里有两个原生的加载器,咱们能够用它们来处理具体的资源: file-loader 和 url-loader : 第一个将只返回一个没有什么特别变化的资源的URL,让你获得一个在这个过程当中产生的版本文件(这是默认的行为) ;第二个将资源内联到一个 data:image/jpeg;base64 的地址。
事实上这两个插件不是对立的关系:好比你的背景是一个2MB的图像,那就不要内联,最好单独加载它。另外一方面若是它是一个2KB的小图标,最好内联,节省HTTP请求。因此咱们就设置了两个:
$ npm install url-loader file-loader --save-dev
{
test: /\.(png|gif|jpe?g|svg)$/i,
loader: 'url-loader?limit=10000',
},
复制代码
在这里,咱们向 url-loader 传递一个 limit 查询参数告诉它:若是资产大于10KB或者更小则内联,不然,就退回到了file-loader并引用它。该语法被称为查询字符串,你用它来配置加载器(loader),或者你也能够经过一个对象配置加载器:
{
test: /\.(png|gif|jpe?g|svg)$/i,
loader: 'url-loader',
query: {
limit: 10000,
}
}
复制代码
好吧,让咱们来看看结果:
$ webpack
Hash: e672f7becf7b049e759d
Version: webpack 3.8.1
Time: 1653ms
Asset Size Chunks Chunk Names
0-4a6171ca8018a700cec4.js 4.26 kB 0 [emitted]
1-ffe316264a9c9fafbb4c.js 4.58 kB 1 [emitted]
bundle.js 348 kB 2 [emitted] [big] main
bundle.css 2.88 kB 2 [emitted] main
复制代码
正如咱们看到的那样:没有一个JPG被打包,由于咱们的小狗图片(2KB)比配置的尺寸(10KB)小,因此它就被内联了。这意味着若是咱们访问咱们的网页,无需加载图片咱们就能够沉浸在咱们的小狗霸主的荣耀下了。
这是很是强大的,由于这意味着webpack如今能够根据大小/HTTP请求的比值来智能优化具体的资源。有一个良好的加载器,你甚至能够进一步的把全部东西联系起来,最多见的一个是image-loader,将在打包它们以前压缩全部图片。它甚至有一个?bypassOnDebug
查询参数让你只在生产的时候作这种事。有不少这样的插件,我鼓励你在本文的结尾看看那些插件。
咱们上面所作的建设都只是为了体现webpack的强大功能,如今让咱们更多的关注本地代码调试。
可能当你提到构建工具时,常常注意到的一个比较大的缺陷:实时加载:LiveReload,BrowserSync,不须要等过久。可是当咱们修改一点什么东西的时候咱们但愿整个页面会自动进行刷新,让它一步就作到咱们想要的效果的就是所谓的模块更换或热重载。它的想法是,既然webpack知道咱们依赖树的每一个模块的位置,那么一个小小的变化就能用新文件简单地修补来体现。更清楚的表达就是:你所作的修改实时地表如今页面上可是你却没发现页面已经从新刷新过了。
为了要使用HMR(热模块加载),咱们须要一个能为咱们的热资源服务的服务器。webpack 中有自带的 dev-server,咱们能够利用这个,因此让咱们来安装它:
$ npm install webpack-dev-server --save-dev
如今运行咱们开发的服务器,没有比这个更简单的了,只须要运行下面的命令:
$ webpack-dev-server
如今请让咱们访问web服务器 http://localhost:8080/webpack-dev-server/ 。你会看到你平时的页面,可是如今咱们修改一个SASS文件,像变魔术同样:
如今,每当咱们运行 webpack-dev-server
它就已是HMR模式。请注意,咱们使用 webpack-dev-server 这里是为了服务咱们的热资源,但你也可使用其余几个选项,像 Express server 。webpack 提供一个中间件,而且你可使用这个中间件把即插即用的HMR移到其余服务器上。
若是你一直在跟进这门教程,你可能已经注意到一些奇怪的东西:为何加载器嵌套在 module.rules(module.loaders) 但插件却不是呢?固然这是由于你还能够向 module 投入其它的东西了!webpack 不仅是有加载器,它还具备前加载器,后加载器:在代码执行以前或以后,咱们主要的加载器。让咱们举个例子:我敢确定,我写这篇文章的代码是可怕的,因此咱们申请在咱们改造它以前 ESLint 咱们的代码:
$ npm install eslint eslint-loader babel-eslint --save-dev
如今让咱们建立一个简单的.eslintrc文件,我知道等一下会运行失败:
路径:webpack-your-bags/.eslintrc
{
"parser": "babel-eslint",
"rules": {
"quotes": 2
}
}
复制代码
如今加入咱们的前加载器,咱们只是像之前同样使用相同的语法,只是在 module.preLoaders 里(module.preLoaders已经在webpack2版本以后移除,须要用到preLoader的地方能够改到rules的enfore中进行配置):
module: {
// preLoaders: [{
// test: /\.js/,
// loader: 'eslint-loader',
// }],
rules: [{
test: /\.js$/,
exclude: /node_modules/,
enforce: "pre",
loader: "eslint-loader"
}],
}
复制代码
如今,若是咱们运行webpack,果真失败:
$ webpack
Hash: 891f6c68504d0f787afa
Version: webpack 3.8.1
Time: 2335ms
Asset Size Chunks Chunk Names
0-4a6171ca8018a700cec4.js 4.26 kB 0 [emitted]
1-ffe316264a9c9fafbb4c.js 4.58 kB 1 [emitted]
bundle.js 348 kB 2 [emitted] [big] main
bundle.css 2.88 kB 2 [emitted] main
[0] ./src/index.js 627 bytes {2} [built] [1 error]
[1] ./src/styles.scss 41 bytes {2} [built]
[7] ./src/Components/Button.js 1.92 kB {1} [built] [1 error]
[8] ./src/Components/Header.js 1.7 kB {0} [built] [1 error]
[9] ./src/Components/Button.html 70 bytes {1} [built]
[10] ./src/Components/Button.scss 1.16 kB {1} [built]
[11] ./node_modules/css-loader!./node_modules/sass-loader/lib/loader.js!./src/Components/Button.scss 219 bytes {1} [built]
[12] ./src/Components/Header.html 62 bytes {0} [built]
[13] ./src/Components/Header.scss 1.16 kB {0} [built]
[14] ./node_modules/css-loader!./node_modules/sass-loader/lib/loader.js!./src/Components/Header.scss 199 bytes {0} [built]
[15] ./node_modules/css-loader!./node_modules/sass-loader/lib/loader.js!./src/styles.scss 297 bytes [built]
[16] ./img/puppy.jpg 2.8 kB [built]
+ 5 hidden modules
ERROR in ./src/Components/Button.js
D:\other\webpack-your-bags\src\Components\Button.js
1:15 error Strings must use doublequote quotes
2:22 error Strings must use doublequote quotes
3:22 error Strings must use doublequote quotes
4:8 error Strings must use doublequote quotes
25:11 error Strings must use doublequote quotes
......
复制代码
上面报错留给你们本身解决,舒适提示:将报错内容“Strings must use doublequote quotes”翻译一下。若是暂时解决不了则注释掉 eslint 相关内容接着进行下面内容。
让咱们运行前加载器的另外一个例子:对于每一个组件咱们导入其同名样式表,而且具备相同名称的模板。让咱们用一个前加载器来自动加载任何带有相同的名称的文件做为一个模块:
$ npm install baggage-loader --save-dev
{
test: /\.js/,
enforce: "pre",
loader: 'baggage-loader?[file].html=template&[file].scss'
}
复制代码
这告诉 webpack :若是遇到同名的HTML文件,导入为template,同时还导入了相同名称的任何SASS文件。如今,咱们能够从这个改变咱们的组件:
从
import $ from 'jquery';
import template from './Button.html';
import Mustache from 'mustache';
import './Button.scss';
复制代码
变成:
import $ from 'jquery';
import Mustache from 'mustache';
复制代码
正如你所看到的如此强大的前加载器同样,后加载器也是如此。看看可用的装载机在本文末尾的列表,你必定会发现不少用例在里面。看看这篇文章的结尾那个可用的加载器列表,你确定会发现更多用例。
关于 webpack-dev-server 咱们不只能够经过直接运行命令行 webpack-dev-server
开启咱们的web服务器,并且能够在 package.json 文件 scripts 中配置快捷命令行,如:
"scripts": {
"start:dev": "webpack-dev-server"
}
复制代码
而后咱们就能够直接运行命令行 npm run start:dev
(等同于 webpack-dev-server
)来开启咱们的web服务器了。
目前,咱们的应用程序是至关小的因此仍是看不出 webpack 的优点,但随着这个应用程序愈来愈大,webpack 就会变得有用,你也可以获得更多的领悟,咱们实际的依赖关系树是什么。咱们可能会作对或作错,咱们的应用程序何时会变得步履维艰等等等。如今应用程序的内部,webpack 知道一切,但你要有礼貌地让它展现它所知道的。您能够经过产生一个配置文件来运行如下命令:
webpack --profile --json > stats.json
第一个标志(--profile
)告诉 webpack 生成一个配置文件,第二个(--json
)产生它的JSON,最后一个(> stats.json
) 咱们让这一切都输出到JSON文件。如今有不少个网站都在分析这些配置文件,但 webpack 提供了一个官方的来一一解说这个信息。因此去 webpack 分析和导入你的JSON文件。如今进入“Modules”选项卡,你应该会看到你的依赖关系树的可视化表示:
一个点越红,越对你最后的包有影响。在咱们这个应用程序下,这个点是 Jquery ,由于它在咱们全部的模块是最重要的。看到全部选项卡,看一下周围,你不会在咱们这个小应用程序里学到太多,但这个工具倒是很是重要的由于它能洞察到你的依赖树和最终资源。如今,正如我所说的,其它的服务可以提供洞察你的配置文件的功能,另一个我喜欢的是 Webpack Visualizer: 它用一个饼图表示你的应用程序的依赖占用了一个什么空间,固然它也能表示咱们的案例:
如今我所知道的状况是:webpack 已经彻底取代 grunt 和 gulp:我以前大部分都是用它们可是如今我是用 webpack 来处理,而对于其他的一小部分我只是用NPM脚本。每一个例子,咱们过去有一个共同的任务是咱们的API文档经过 Aglio 转换为HTML,这很容易作到,像这样咱们修改咱们的 package.json文件:
{
"scripts": {
"build": "webpack",
"build:api": "aglio -i docs/api/index.apib -o docs/api/index.html"
}
}
复制代码
可是若是你有更复杂的跟打包和资源的任务在你的 gulp ,webpack 在其余系统的构建会发挥更好的做用。看看这个例子是如何把webpack整合在 gulp里的:
var gulp = require('gulp');
var gutil = require('gutil');
var webpack = require('webpack');
var config = require('./webpack.config');
gulp.task('default', function(callback) {
webpack(config, function(error, stats) {
if (error) throw new gutil.PluginError('webpack', error);
gutil.log('[webpack]', stats.toString());
callback();
});
});
复制代码
而就是它,由于 webpack 中还具备节点的API,所以它能够很容易地在其它构建系统中使用,在任何状况下,你会发现它的思想无处不在。
不管如何,我认为这是能够为你作的一个 webpack 的足够好的鸟瞰图。你可能会认为,咱们已经在这篇文章中介绍了不少,但咱们认为只是皮毛而已:多个入口点,预取,背景更换等等。webpack 是一个使人印象深入而且功能强大的工具,这固然是由于它比传统的构建工具更加不透明,我不会否定这一点。可是,一旦你知道如何驯服它,它就会发出甜美的声音进入你的耳朵让你耐人寻味。我把它用在几个项目里,它提供了优化和自动化,我不能想象本身要来回敲打脑壳多少次才能知道所须要的资源何时在什么地方。
各分段代码包已经上传到GitHub,访问地址: