本文译自:Webpack your bags
这篇文章由入门到深切的引见了webpack的功用和应用技能,至心值得一看。由于我英语水平有限,而且很少翻译文章,所以文中的一些语句在翻译时做了相似语义的转换,望体谅。假如有幸被转照样愿望能够申明啊
by the way,打个小广告。。把本身的github扔这儿好了,有时刻会更新些译文也许笔记什么的
你能够已听说过这个酷酷的东西-Webpack。一些人称之为相似于Gulp的东西,另有一些人则以为它相似于Browserify。假如你还没打仗过它,那很有能够会因而觉得疑心。而Webpack的主页上则以为它是二者的连系,那也许更让你疑心了。
说实话,一最先的时刻,“什么是Webpack”这个话题让我很心烦,也就没有继承研讨下去了。直到厥后,当我已构建了几个项目后,才至心的为之痴迷。假如你像我一样紧随Javascript的生长步调,你很有能够会由于太追随潮水跨度太大而蛋疼。在阅历了上面这些以后,我写下这篇文章,以便越发仔细的诠释Webpack是什么,以及它云云重要的缘由。
Webpack是啥?
起首来让我们回复最最先的题目:Webpack是个系统的构建东西,照样打包东西?答案是二者都是–这不代表它做了这两件事(先构建资本,在离别举行打包),而是说它将二者连系在一同了。
越发清晰的申明:与“构建sass文件,紧缩图片,然后援用它们,再打包,再在页面上援用”比拟,你只需这么做:
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) // "<html><body><h1>Hello</h1></body></html>"
你的统统资本都被当作包处置惩罚,能够被import,修正,掌握,终究展示在你末了的一个bundle上。
为了能让上面那些有效运转,你须要在本身的Webpage设置里设置loader
。loader
是一个“当顺序碰见XXX范例文件的时刻,就做YYY”的小型插件。来看一些loader
的例子:
{
// 假如援用了 .ts 文件, 将会触发 Typescript loader
test: /\.ts/,
loader: 'typescript',
},
{
// 假如援用了png|jpg|svg图片,则会用 image-webpack 举行紧缩 (wrapper around imagemin)
// 并转化成 data64 URL 花样
test: /\.(png|jpg|svg)/,
loaders: ['url', 'image-webpack'],
},
{
// 假如应用了 SCSS files, 则会用 node-sass 剖析, 终究返回CSS花样
test: /\.scss/,
loaders: ['css', 'autoprefixer', 'sass'],
}
终究在食物链的最底端,统统的loader
都返回string
,如许Webpack就可以够将它们到场到javascript模块中去。当你的Sass文件被loader转换以后,它的援用实际上是如许的:
export default 'body{font-size:12px}';
终究为何要这么做?
在你明白了Webpack是做什么的以后,第二个题目就接二连三:应用它有什么优点?“把图片和CSS扔进我的js里?什么鬼?”其实在良久之前,为了削减HTTP request请求,我们都被教诲要把统统东西写在一个文件内里。
到了如今,与之相似的是,许多人把统统东西打包进app.js
。这两种要领都有一个很大的负面影响:许多时刻人们在下载的是他们用不到的资本。但假如你不这么做吧,你就得手动的在每一个页面援用响应的资本,终究会混乱成一坨:哪一个页面已援用了它所依靠的资本?
这些要领没有相对的对错。把Webpage当作一个中间件–不仅仅是打包或构建东西,而是个智慧的模块打包系统。只需你设置准确,它会比你还要清晰应用的手艺栈,并更好的优化它们。
来让我们一同构建一个简朴的App
为了让你更快速的明白应用Webpack的优点,我们会构建一个简朴的App,并将资本打包进去。在这里教程中我引荐应用Node4(或5),以及NPM3作为包管理东西,以便在应用Webpack的时刻防止大批的贫苦。假如你还没装NPM3,能够经由过程npm install npm@3 -g
来装置。
$ node --version
v5.7.1
$ npm --version
3.6.0
我还要引荐你把node_modules/.bin
放进你的PATH
变量,以防止每次都要输入node_modules/.bin/webpack
。在下面了例子里我输入的指令都不会再包括node_modules/.bin
。
基础指引(setup)
从竖立项目装置Webpack最先。我们同时也装置了jQuery以便支撑后续操纵。
$ npm init -y
$ npm install jquery --save
$ npm install webpack --save-dev
如今来做一个App的进口:
// src/index.js
var $ = require('jquery');
$('body').html('Hello');
让我们在webpack.config.js
文件里举行的Webpack设置。Webpack设置实质上是Javascript,而且在末了export
出去一个Object:
// webpack.config.js
module.exports = {
entry: './src',
output: {
path: 'builds',
filename: 'bundle.js',
},
};
在这里,entry
通知Webpack哪些文件是应用的进口文件。它们是你的重要文件,在依靠树的最顶端。以后,我们通知Webpack把资本打包在builds
文件夹下的bundle.js
文件里。让我们编写index HTML文件。
<!DOCTYPE html>
<html>
<body>
<h1>My title</h1>
<a>Click me</a>
<script src="builds/bundle.js"></script>
</body>
</html>
运转Webpack。假如统统准确那就可以够瞥见下面的信息:
$ webpack
Hash: d41fc61f5b9d72c13744
Version: webpack 1.12.14
Time: 301ms
Asset Size Chunks Chunk Names
bundle.js 268 kB 0 [emitted] main
[0] ./src/index.js 53 bytes {0} [built]
+ 1 hidden modules
在这段信息里能够看出,bundle.js
包括了index.js
和一个隐蔽的模块。隐蔽的模块是jQuery。在默许情势下Webpack隐蔽的模块都不是你写的。假如想要显现它们,我们能够在运转Webpack的时刻应用--display-modules
:
$ webpack --display-modules
bundle.js 268 kB 0 [emitted] main
[0] ./src/index.js 53 bytes {0} [built]
[3] ./~/jquery/dist/jquery.js 259 kB {0} [built]
你还能够应用webpack --watch
,在转变代码的时刻自动举行打包。
设置第一个loader(loader
-01)
还记得Webpack能够处置惩罚种种资本的援用吗?该怎么搞?假如你追随了这些年Web组件生长的步调(Angular2,Vue,React,Polymer,X-Tag等等),那末你应当晓得,与一堆UI相互连接组合而成的App比拟,应用可保护的小型可复用的UI组件会更好:web component。
为了确保组件能够坚持自力,它们须要在本身内部打包须要的资本。设想一个按钮组件:除了HTML以外,还须要js以便和外部连系。噢对也许还须要一些款式。假如能够在须要这个按钮组件的时刻,加载统统它所依靠的资本的话那就太赞了。当我们import按钮组件的时刻,就猎取到了统统资本。
最先编写这个按钮组件吧。起首,假定你已习惯了ES2015语法,那末须要装置第一个loader:Babel。装置好一个loader你须要做下面这两步:起首,经由过程npm install {whatever}-loader
装置你须要的loader,然后,将它加到Webpage设置的module.loaders
里:
$ npm install babel-loader --save-dev
loader并不会帮我们装置Babel所以我们要本身装置它。须要装置babel-core
包和es2015
预处置惩罚包。
$ npm install babel-core babel-preset-es2015 --save-dev
新建.babelrc
文件,内里是一段JSON,通知Babel应用es2015
举行预处置惩罚。
// .babelrc
{ "presets": ["es2015"] }
如今,Babel已被装置并设置完成,我们要更新Webpack设置。我们想要Babel运转在统统以.js
末端的文件里,然则要防止运转在第三方依靠包比方jQuery内里。loader具有include
和exclude
划定规矩,内里能够是一段字符串、正则、回调等等。在这个例子里,我们只想让Babel在我们本身的文件里运转,因而应用include
包括本身的资本文件夹:
module.exports = {
entry: './src',
output: {
path: 'builds',
filename: 'bundle.js',
},
module: {
loaders: [
{
test: /\.js/,
loader: 'babel',
include: __dirname + '/src',
}
],
}
};
如今,我们能够用ES6语法重写index.js
了:
// index.js
import $ from 'jquery';
$('body').html('Hello');
写个小组件(loader
-02)
来写个按钮组件吧,它将包括一些SCSS款式,HTML模板和一些操纵。所以我们要装置须要的东西。起首装置Mustache这个轻量级的模板库,然后装置处置惩罚Sass和HTML的loader。一样的,为了处置惩罚Sass loader返回的结果,还要装置CSS loader。一旦猎取到了CSS文件,我们就可以够用许多种体式格局来处置惩罚。现在应用的是一个叫style-loader
的东西,它能够把CSS插进去到包中。
$ npm install mustache --save
$ npm install css-loader style-loader html-loader sass-loader node-sass --save-dev
为了能够让Webpack顺次处置惩罚差别loader的返回结果,我们能够将loader经由过程!
链接到一同,猎取应用loaders
并对应一个由loader构成的数组:
{
test: /\.js/,
loader: 'babel',
include: __dirname + '/src',
},
{
test: /\.scss/,
loader: 'style!css!sass',
// Or
loaders: ['style', 'css', 'sass'],
},
{
test: /\.html/,
loader: 'html',
}
有了loader,我们来写写按钮:
// src/Components/Button.scss
.button {
background: tomato;
color: white;
}
<!-- src/Components/Button.html -->
<a class="button" href="{{link}}">{{text}}</a>
// 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();
// Render our button
$(node).html(
Mustache.render(template, {text})
);
// Attach our listeners
$('.button').click(this.onClick.bind(this));
}
}
你的Button.js
如今处于完整自力的状况,不论何时何地的援用它,都能猎取到统统须要的依靠并衬着出来。如今衬着我们的按钮尝尝:
// src/index.js
import Button from ‘./Components/Button’;
const button = new Button(‘google.com’);
button.render(‘a’);
运转Webpack,革新页面,马上就可以瞥见我们这个丢脸的按钮了。
如今你已进修了怎样装置loader,以及定义各个依靠设置。看起来彷佛也没啥。但让我们来深切扩大一下这个例子。
代码星散(require.ensure
)
上面的例子还不错,但我们并不老是须要这个按钮。也许有的页面没有能够用来衬着按钮的a
,我们并不想在如许的页面援用按钮的资本文件。这类时刻代码星散就可以起到作用了。代码星散是Webpack关于“整块悉数打包”vs“难以保护的手动指导”这个题目而给出的解决计划。这须要在你的代码中设定“星散点”:代码能够据此星散成差别地区举行按需加载。它的语法很简朴:
import $ from 'jquery';
// 这个是支解点
require.ensure([], () => {
// 在这里import进的代码都邑被打包到一个零丁的文件里
const library = require('some-big-library');
$('foo').click(() => library.doSomething());
});
在require.ensure
中的东西都邑在打包结果中星散开来–只要当须要加载它的时刻Webpack才会经由过程AJAX请求举行加载。也就是说我们实际上获得的是如许的文件:
bundle.js
|- jquery.js
|- index.js // 进口文件
chunk1.js
|- some-big-libray.js
|- index-chunk.js // 回调中的代码在这里
你不须要在任何处所援用chunk1.js
文件,Webpack会帮你在须要的时刻举行请求。这意味着你能够像我们的例子一样,依据逻辑须要引进的资本悉数扔进代码里。
只要当页面上有链接存在时,再援用按钮组件:
// src/index.js
if (document.querySelectorAll('a').length) {
require.ensure([], () => {
const Button = require('./Components/Button').default;
const button = new Button('google.com');
button.render('a');
});
}
须要注重的一点是,由于require
不会同时处置惩罚default export和normal export,所以应用require
援用资本里default export的时刻,须要手动加上.default
。比拟之下,import
则能够举行处置惩罚:
import foo from 'bar'
vs import {baz} from 'bar'
此时Webpack的output将会变得更庞杂了。跑下Webpack,用--display-chunks
打印出来看看:
$ webpack --display-modules --display-chunks
Hash: 43b51e6cec5eb6572608
Version: webpack 1.12.14
Time: 1185ms
Asset Size Chunks Chunk Names
bundle.js 3.82 kB 0 [emitted] main
1.bundle.js 300 kB 1 [emitted]
chunk {0} bundle.js (main) 235 bytes [rendered]
[0] ./src/index.js 235 bytes {0} [built]
chunk {1} 1.bundle.js 290 kB {0} [rendered]
[5] ./src/Components/Button.js 1.94 kB {1} [built]
[6] ./~/jquery/dist/jquery.js 259 kB {1} [built]
[7] ./src/Components/Button.html 72 bytes {1} [built]
[8] ./~/mustache/mustache.js 19.4 kB {1} [built]
[9] ./src/Components/Button.scss 1.05 kB {1} [built]
[10] ./~/css-loader!./~/sass-loader!./src/Components/Button.scss 212 bytes {1} [built]
[11] ./~/css-loader/lib/css-base.js 1.51 kB {1} [built]
[12] ./~/style-loader/addStyles.js 7.21 kB {1} [built]
正如你所见的那样,我们的进口bundle.js
值包括了一些逻辑,而其他东西(jQuery,Mustache,Button)都被打包进了1.bundle.js
,而且只在须要的时刻才会被援用。如今为了能够让Webpack在AJAX的时刻找到这些资本,我们须要改下设置里的output
:
path: 'builds',
filename: 'bundle.js',
publicPath: 'builds/',
output.publicPath
通知Webpack,从当前页面的位置动身那里能够找到须要的资本(在这个例子里是/builds/
)。当我们加载页面的时刻统统一般,而且能够瞥见Webpack已依据页面上预留的“锚”加载好了包。
假如页面上缺乏“锚”(代指link),那末只会加载bundle.js
。经由过程这类体式格局,你能够做到在真正须要资本的时刻才举行加载,防止让本身的页面变成笨重的一坨。顺带一提,我们能够转变支解点的名字,不应用1.bundle.js
而应用越发语义化的称号。经由过程require.ensure
的第三个参数来完成:
require.ensure([], () => {
const Button = require('./Components/Button').default;
const button = new Button('google.com');
button.render('a');
}, 'button');
如许的话就会天生button.bundle.js
而不是1.bundle.js
再加个组件(CommonChunksPlugin
)
来让我们再加个组件吧:
// src/Components/Header.scss
.header {
font-size: 3rem;
}
<!-- src/Components/Header.html -->
<header class="header">{{text}}</header>
// 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})
);
}
}
将它在应用中衬着出来:
// 假如有链接,则衬着按钮组件
if (document.querySelectorAll('a').length) {
require.ensure([], () => {
const Button = require('./Components/Button');
const button = new Button('google.com');
button.render('a');
});
}
// 假如有题目,则衬着题目组件
if (document.querySelectorAll('h1').length) {
require.ensure([], () => {
const Header = require('./Components/Header');
new Header().render('h1');
});
}
瞅瞅应用了--display-chunks --display-modules
标记后Webpack的output输出:
$ webpack --display-modules --display-chunks
Hash: 178b46d1d1570ff8bceb
Version: webpack 1.12.14
Time: 1548ms
Asset Size Chunks Chunk Names
bundle.js 4.16 kB 0 [emitted] main
1.bundle.js 300 kB 1 [emitted]
2.bundle.js 299 kB 2 [emitted]
chunk {0} bundle.js (main) 550 bytes [rendered]
[0] ./src/index.js 550 bytes {0} [built]
chunk {1} 1.bundle.js 290 kB {0} [rendered]
[14] ./src/Components/Button.js 1.94 kB {1} [built]
[15] ./~/jquery/dist/jquery.js 259 kB {1} {2} [built]
[16] ./src/Components/Button.html 72 bytes {1} [built]
[17] ./~/mustache/mustache.js 19.4 kB {1} {2} [built]
[18] ./src/Components/Button.scss 1.05 kB {1} [built]
[19] ./~/css-loader!./~/sass-loader!./src/Components/Button.scss 212 bytes {1} [built]
[20] ./~/css-loader/lib/css-base.js 1.51 kB {1} {2} [built]
[21] ./~/style-loader/addStyles.js 7.21 kB {1} {2} [built]
chunk {2} 2.bundle.js 290 kB {0} [rendered]
[22] ./~/jquery/dist/jquery.js 259 kB {1} {2} [built]
[23] ./~/mustache/mustache.js 19.4 kB {1} {2} [built]
[24] ./~/css-loader/lib/css-base.js 1.51 kB {1} {2} [built]
[25] ./~/style-loader/addStyles.js 7.21 kB {1} {2} [built]
[26] ./src/Components/Header.js 1.62 kB {2} [built]
[27] ./src/Components/Header.html 64 bytes {2} [built]
[28] ./src/Components/Header.scss 1.05 kB {2} [built]
[29] ./~/css-loader!./~/sass-loader!./src/Components/Header.scss 192 bytes {2} [built]
能够看出一点题目了:这两个组件都须要jQuery和Mustache,如许的话就造成了包中的依靠反复,这可不是我们想要的。只管Webpack会在默许情况下举行一定的优化,但还得靠插件来加足火力搞定它。
插件和loader的差别在于,loader只对一类特定的文件有效,而插件每每面向统统文件,而且并不老是会引起转化。Webpack供应了许多插件供你优化。在这里我们应用CommonChunksPlugin
插件:它会剖析你包中的反复依靠并提掏出来,天生一个完整自力的文件(比方vendor.js),以至天生你的主文件。
如今,我们想要把配合的依靠包从进口中剔除。假如统统的页面都用到了jQuery和Mustache,那末就要把它们提掏出来。更新下设置吧:
var webpack = require('webpack');
module.exports = {
entry: './src',
output: {
// ...
},
plugins: [
new webpack.optimize.CommonsChunkPlugin({
name: 'main', // 将依靠移到我们的主文件中
children: true, // 再在统统的子文件中搜检依靠文件
minChunks: 2, // 一个依靠反复频频会被提掏出来
}),
],
module: {
// ...
}
};
再跑次Webpack,能够看出如今就好多了。个中,main
是我们的默许依靠。
chunk {0} bundle.js (main) 287 kB [rendered]
[0] ./src/index.js 550 bytes {0} [built]
[30] ./~/jquery/dist/jquery.js 259 kB {0} [built]
[31] ./~/mustache/mustache.js 19.4 kB {0} [built]
[32] ./~/css-loader/lib/css-base.js 1.51 kB {0} [built]
[33] ./~/style-loader/addStyles.js 7.21 kB {0} [built]
chunk {1} 1.bundle.js 3.28 kB {0} [rendered]
[34] ./src/Components/Button.js 1.94 kB {1} [built]
[35] ./src/Components/Button.html 72 bytes {1} [built]
[36] ./src/Components/Button.scss 1.05 kB {1} [built]
[37] ./~/css-loader!./~/sass-loader!./src/Components/Button.scss 212 bytes {1} [built]
chunk {2} 2.bundle.js 2.92 kB {0} [rendered]
[38] ./src/Components/Header.js 1.62 kB {2} [built]
[39] ./src/Components/Header.html 64 bytes {2} [built]
[40] ./src/Components/Header.scss 1.05 kB {2} [built]
[41] ./~/css-loader!./~/sass-loader!./src/Components/Header.scss 192 bytes {2} [built]
假如我们转变下名字name: 'vendor'
:
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor',
children: true,
minChunks: 2,
}),
Webpack会在没有该文件的情况下自动天生builds/vendor.js
,以后我们能够手动引入:
<script src="builds/vendor.js"></script>
<script src="builds/bundle.js"></script>
你也能够经由过程async: true
,而且不供应配合依靠包的定名,来到达异步加载配合依靠的结果。
Webpack有许多如许给力的优化计划。我没法一个一个引见它们,不过能够经由过程制造一个临盆环境的应用来进一步进修。
奔腾到临盆环境(production
)
起首,要在设置中增添几个插件,但请求只要当NODE_ENV
为production
的时刻才运转它们:
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',
filename: 'bundle.js',
publicPath: 'builds/',
},
plugins: plugins,
// ...
};
Webpack也供应了一些能够切换临盆环境的设置:
module.exports = {
debug: !production,
devtool: production ? false : 'eval',
}
设置中的第一行表明在开辟环境下,将开启debug情势,代码不再混做一团,利于当地调试。第二行则用来临盆资当舆图(sourcemaps)。Webpack有一些要领能够天生sourcemaps,而eval
则是在当地表现最赞的一个。在临盆环境下,我们并不体贴sourcemaps,因而封闭了这个选项。
如今来增添临盆环境下的插件吧:
if (production) {
plugins = plugins.concat([
// 这个插件用来寻觅雷同的包和文件,并把它们兼并在一同
new webpack.optimize.DedupePlugin(),
// 这个插件依据包/库的援用次数来优化它们
new webpack.optimize.OccurenceOrderPlugin(),
// 这个插件用来阻挠Webpack把太小的文件打成零丁的包
new webpack.optimize.MinChunkSizePlugin({
minChunkSize: 51200, // ~50kb
}),
// 紧缩js文件
new webpack.optimize.UglifyJsPlugin({
mangle: true,
compress: {
warnings: false, // 制止天生warning
},
}),
// 这个插件供应了种种可用在临盆环境下的变量
// 经由过程设置为false,可防止临盆环境下调用到它们
new webpack.DefinePlugin({
__SERVER__: !production,
__DEVELOPMENT__: !production,
__DEVTOOLS__: !production,
'process.env': {
BABEL_ENV: JSON.stringify(process.env.NODE_ENV),
},
}),
]);
}
我广泛应用的差不多就这么多了,不过Webpack还供应了异常多的插件,你能够本身去研讨它们。也能够在NPM上找到许多用户本身孝敬的插件。插件的链接在文末供应。
另有一个关于临盆环境的优化是给资本供应版本的观点。还记得output.filename
里的bundle.js
吗?在这个设置内里,你能够应用一些变量,而[hash]
则会给文件供应一段随机的字符串。除此以外,我们想要包能够被版本化,因而增添了output.chunkFilename
:
output: {
path: 'builds',
filename: production ? '[name]-[hash].js' : 'bundle.js',
chunkFilename: '[name]-[chunkhash].js',
publicPath: 'builds/',
},
由于没法得知每次打包天生的文件名,所以我们只在临盆环境下应用它。除此以外,我们还想保证每次打包的时刻,builds文件夹都邑被清空以勤俭空间,因而应用了一个第三方插件:
$ npm install clean-webpack-plugin --save-dev
并将它增添到设置中:
var webpack = require('webpack');
var CleanPlugin = require('clean-webpack-plugin');
// ...
if (production) {
plugins = plugins.concat([
// 在打包前清空 builds/ 文件夹
new CleanPlugin('builds'),
做完这些美丽的优化,来比较下结果的差别吧:
$ webpack
bundle.js 314 kB 0 [emitted] main
1-21660ec268fe9de7776c.js 4.46 kB 1 [emitted]
2-fcc95abf34773e79afda.js 4.15 kB 2 [emitted]
$ NODE_ENV=production webpack
main-937cc23ccbf192c9edd6.js 97.2 kB 0 [emitted] main
来看看Webpack都做了什么:
在第一段代码中,后两个包异常轻量,异步请求不会占用若干HTTP带宽,所以在临盆环境下Webpack将它们打包进了进口文件里
统统东西都紧缩过了。从322kb降到了97kb
然则如许下去,Webpack岂不是会将js文件兼并成巨大的一坨吗?
是的,在这个小小的应用中是如许没错。然则你须要这么想:你不须要斟酌在什么时刻兼并什么。假如你的包中含有太多的依靠,它们会被移走到异步请求包中而不会被兼并起来。反之,假如它们很小,不值得自力加载,那末就会被兼并。你只须要竖立划定规矩,Webpack会最大化的将其优化。没有人力劳作,不须要思索依靠关联,统统都是自动化的。
也许你已注重到了,我没有对HTML或CSS举行紧缩。那是由于当debug
情势开启的时刻,css-loader
和html-loader
已帮我们搞好了。这也是为何Uglify是一个自力插件的缘由:在Webpack中没有js-loader
这类东西,Webpack本身就是个JS loader。
抽取(extract-text-webpack-plugin
)
能够你已注重到了,从这个教程一最先,Webpack打包好以后,我们的款式就直接插在网页页面上,几乎不能更丢脸了。能经由过程Webpack把打包过的CSS天生自力的文件吗?固然没题目:
$ npm install extract-text-webpack-plugin --save-dev
这个插件所做的就是我方才说的那些:从打出的终究包内里,提掏出某一类内容星散开来零丁援用。它通常被用于提取CSS文件:
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: {
loaders: [
{
test: /\.scss/,
loader: ExtractPlugin.extract('style', 'css!sass'),
},
// ...
],
}
};
ExtractPlugin.extrac
要领吸收两个参数,第一个参数代表当它处于已打包好的包(’style’)里时,怎样处置惩罚那些提掏出来的东西;第二个参数代表当它在主文件(’css!sass’)里时,怎样看待提掏出的东西。当它在包里时,一定不能直接将CSS加在天生的东西背面,所以先用style-loader
举行处置惩罚;而关于主文件内里的styles,则将它们放进builds/bundle.css
文件。我们来给应用加一个主款式:
// src/styles.scss
body {
font-family: sans-serif;
background: darken(white, 0.2);
}
// src/index.js
import './styles.scss';
// Rest of our file
跑下Webpack,就可以瞥见已天生了bundle.css
,能够把它援用进HTML里:
$ webpack
bundle.js 318 kB 0 [emitted] main
1-a110b2d7814eb963b0b5.js 4.43 kB 1 [emitted]
2-03eb25b4d6b52a50eb89.js 4.1 kB 2 [emitted]
bundle.css 59 bytes 0 [emitted] main
假如你想提掏出统统包里的款式,则须要设置ExtractTextPlugin('bundle.css', {allChunks: true})
顺带一提,你也能够自定义文件名,就跟之前说的转变js output-file称号一样:ExtractTextPlugin('[name]-[hash].css')
应用图片(url-loader
&file-loader
)
到现在为止,我们还没处置惩罚比方图片、字体如许的资本文件。它们在Webpack中怎样事变,我们又该怎样优化?接下来,我要在网站的背景里到场图片,看起来一定酷酷的:
把图片保存在img/puppy.jpg
,更新下Sass文件:
// src/styles.scss
body {
font-family: sans-serif;
background: darken(white, 0.2);
background-image: url('../img/puppy.jpg');
background-size: cover;
}
假如仅仅是如许,Webpack一定会通知你:“你特么的想让我对JPG做啥?”,那是由于还没有到场对应的loader。有两种loader能够应用:file-loader
和url-loader
:
file-loader
:返回一段指向资本的URL,许可你给文件到场版本的观点(默许)url-loader
:以data:image/jpeg;base64
的情势返回URL
两个要领不能说谁好谁坏:假如你的图片大于2M的话那你一定不愿望它直接夹杂在代码中,而是自力出去;而假如仅仅是2kb摆布的小图标。那末兼并在一同削减HTTP请求会更好。因而,我们两个都要设置:
$ npm install url-loader file-loader --save-dev
{
test: /\.(png|gif|jpe?g|svg)$/i,
loader: 'url?limit=10000',
},
在这里,我们给url-loader
了一个limit
参数,如许,当文件大小小于10kb的时刻,会采用行内款式,不然的话,会转到file-loader
举行处置惩罚。你也能够经由过程query
通报一个Object来完成它:
{
test: /\.(png|gif|jpe?g|svg)$/i,
loader: 'url',
query: {
limit: 10000,
}
}
来瞅一眼Webpack的输出:
bundle.js 15 kB 0 [emitted] main
1-b8256867498f4be01fd7.js 317 kB 1 [emitted]
2-e1bc215a6b91d55a09aa.js 317 kB 2 [emitted]
bundle.css 2.9 kB 0 [emitted] main
输出内里没有JPG图象,那是由于我们的小狗图片比设置里限定的大小要小,因而被加到了行内。接见页面,你就可以瞥见这只可爱的小狗了。
这是一个异常壮大的功用,它意味着Webpack能够智能的依据资本的大小和HTTP请求占领的比率,采用差别的优化计划。另有一个叫做image-loader的loader,能够在打包前搜检统统图片,防止图片的反复紧缩。它有一个叫?bypassOnDebug
的参数,经由过程它你能够只在临盆环境下启动该插件。
另有许多优异的插件,我强烈建议你应用文末的链接去检察它们。
来个牛逼的热加载(dev-server)
我们的临盆环境以及整的差不多了,如今应当更多的体贴一下当地开辟。也许你以及注重到了,当人们说起开辟东西的时刻,老是会说起热加载:LiveReload,BrowserSync,也许其他的什么鬼东西。然则只要傻瓜才会整页的革新,我们则应用更高端的热加载。由于Webpack能够确实的晓得你依靠树中某一点位置的代码,因而每次的转变都邑据此天生一个新的文件。简朴的说,就是不须要革新页面就可以将转变展示在屏幕上。
为了能够应用HMR,我们须要一个server来启动热加载。Webpack供应的dev-server
能够完成这个使命:
$ npm install webpack-dev-server --save-dev
装置下面的敕令启动server,不能再简朴了:
$ webpack-dev-server --inline --hot
第一个标记--inline
是让Webpack把HMR逻辑直接写入页面上而不是放到iframe里,而第二个标记则开启了HMR。接下来,接见http://localhost:8080/webpack-dev-server/
,嗯照样谁人一般的页面。试着修正Sass文件,MAGIC!
你能够把webpack-dev-server作为本身当地的server。假如你盘算一向应用HMR,就须要这么设置:
output: {
path: 'builds',
filename: production ? '[name]-[hash].js' : 'bundle.js',
chunkFilename: '[name]-[chunkhash].js',
publicPath: 'builds/',
},
devServer: {
hot: true,
},
如许的话,不论我们什么时刻运转webpack-dev-server
,都邑是HMR情势。值得一提的是,我们在这里应用webpack-dev-server
对资本举行热加载,但也能够应用在其他处所比方Express server上。Webpack供应了一个中间件,使得你能够把HMR的功用用在其他server上。
代码不清洁的人都给我去罚站!(pre-loader & lint)
假如你一向随着本教程走,那也许会有如许的疑问:为何loader都在module.loaders
中而插件不在?那固然是由于另有其他能够设置进module
的东西~Webpack不只是有loader,也有pre-loader和post-loader:在main-loader运转之前和以后发起的玩意。举个栗子:我基础能够确信本身在这个文章内里写的代码很蹩脚,所以应用ESLint举行代码搜检:
$ npm install eslint eslint-loader babel-eslint --save-dev
新建一个一定会激发毛病的.eslintrc
文件:
// .eslintrc
parser: 'babel-eslint'
rules:
quotes: 2
如今增添pre-loader,语法和之前的一样,只不过加在module.preLoaders
里:
module: {
preLoaders: [
{
test: /\.js/,
loader: 'eslint',
}
],
启动Webpack,然后淡定的看它失利:
$ webpack
Hash: 33cc307122f0a9608812
Version: webpack 1.12.2
Time: 1307ms
Asset Size Chunks Chunk Names
bundle.js 305 kB 0 [emitted] main
1-551ae2634fda70fd8502.js 4.5 kB 1 [emitted]
2-999713ac2cd9c7cf079b.js 4.17 kB 2 [emitted]
bundle.css 59 bytes 0 [emitted] main
+ 15 hidden modules
ERROR in ./src/index.js
/Users/anahkiasen/Sites/webpack/src/index.js
1:8 error Strings must use doublequote quotes
4:31 error Strings must use doublequote quotes
6:32 error Strings must use doublequote quotes
7:35 error Strings must use doublequote quotes
9:23 error Strings must use doublequote quotes
14:31 error Strings must use doublequote quotes
16:32 error Strings must use doublequote quotes
18:29 error Strings must use doublequote quotes
再举个pre-loader的例子:每一个组件里我们都援用了stylesheet,而它们都有雷同定名的对应模板。应用一个pre-loader能够自动将有雷同称号的文件作为一个module载入:
$ npm install baggage-loader --save-dev
{
test: /\.js/,
loader: 'baggage?[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';
你看,pre-loaders也能够很壮大。在文末你能够找到更多的loader
还没看够?
如今我们的应用还很小,当它变的巨大的时刻,观察依靠树就变的异常有效了,从中能够看出我们做的是对是错,应用的瓶颈在那里等等。Webpack晓得这统统,不过我们得规矩的讨教它才晓得答案。为了做到这点,你能够经由过程下面的敕令运转Webpack:
webpack --profile --json > stats.json
第一个标记会让Webpack天生一个profile文件,而第二个则将它转化为JSON花样。终究,讲统统的output都天生了JSON文件。如今有许多网站都能够剖析这个JSON文件,不过Webpack官方供应了一个解码的网站Webpack Analyze。将JSON文件导入,进入Modules板块,就可以够瞥见本身依靠树的可视化图象:
小圆点越红,则证实在打包的时刻越难题。在这个例子中,jQuery作为最大的文件而成为罪魁祸首。再瞅瞅网站上的其他模块。也许你没法从这个小小的例子里学到许多东西,然则这个东西在剖析依靠树和包的时刻真的异常有效。
我之前提过,如今有许多效劳供应能够对profile文件举行剖析。个中一个是Webpack Visualizer,它能够以饼状图的情势示知你各个文件占有了多大的比重:
就先讲到这儿吧
对我而言,Webpack已庖代了Grunt也许Gulp:大部分的功用能够应用Webpack替换,其他的则应用NPM脚本就够了。在之前,每一个使命中我们都要经由过程Aglio,把API文档转换为HTML,而如今只须要这么做:
// package.json
{
"scripts": {
"build": "webpack",
"build:api": "aglio -i docs/api/index.apib -o docs/api/index.html"
}
}
即便是一些不须要打包和构建的Glup使命,Webpack都知心的供应了对应的效劳。下面是一个将Glup融会进Webpack的例子:
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具有Node API,因而能够很轻松了应用在其他构建系统中。不必多久你就可以发明本身深爱着它没法自拔了。
不论怎样,这篇笔墨带你预览了Webpack能够帮你做的事变。也许你以为我们讲了许多方面,但实际上这只是个表皮罢了:multiple entry points, prefetching, context replacement等等都还没有涉及到。Webpack是个壮大的东西,也因而比那些传统的东西越发难明。但一旦你晓得怎样应用它,它就会为你鸣奏最悦耳动听的声响。我曾在一些项目里应用过它,它供应的壮大的优化和自动化让我深深不能自拔。