[译] Webpack 前端构建集成计划

构建东西逐步成为前端工程必备的东西,Grunt、Gulp、Fis、Webpack等等,译者有幸运用过Fis、Gulp。
前者是百度的集成化设计,供应了一整套前端构建设计,长处是基础帮你搞定了,然则天真性相对照较低,社区也没那末大;后者供应了异常天真的设置,简朴的语法可以设置出壮大的功用,流掌握也削减了编译时的时刻,可以和种种插件合营运用。
译者因为要运用AMD模块机制,最先打仗了webpack,发明官网上讲的艰涩难明,没法实践,而国内虽有博客也解说一些入门的教程,然则要么篇幅太短,要么只讲种种设置贴种种代码,然后谷歌发明了外洋大牛写的这篇博客,发明讲的异常通俗易懂,合营实践和代码,让译者感慨万千,霎时翻开了一扇大门。

原文链接:https://blog.madewithlove.be/post/webpack-your-bags/
作者:Maxime Fabre
译者:陈坚生

或许你已听说过这个叫做webpack的新东西了。有些人称它是一个像gulp一样的构建东西, 有些人则以为它是像browserify一样的打包东西, 假如你并没有深切去相识它你可以就会发生迷惑。就算你细致地研讨它你也可以照旧疑心,因为webpack的官网引见webpack的时刻同时提到了这两个功用。

一最先对”webpack 是什么”的隐约观点使得我很挫败以致于我直接关掉了webpack的网页。到了如今,我已有了一套自身的构建体系并为此以为很高兴。假如你和我一样紧跟javascript的潮水,那末错过云云好的东西将是异常惋惜的事变。(这句话翻译的不好:And if you follow closely the very fast Javascript scene, like me, you’ve probably been burnt in the past by jumping on the bandwagon too soon. )因为对webpack有了一定的实践和履历,我决议写这篇文章来越发清晰地诠释“什么是webpack”另有webpack的重要性和上风。

什么是webpack?

起首让我们来回复标题中的题目:webpack究竟是一个构建体系照样一个打包东西?好吧, 它都有——但不是说它做了二者而是说它兼并了二者。webpack并不构建你的资本(assets),然后分别对你的模块举行打包,它以为你的资本都是模块

更精确地说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>"

你统统的资本都被以为是模块,因而是可以被援用的、修正、操纵,末了可以被打包进你的终究模块中。

为了使得如许可以运转,你须要在你的webpack设置中注册loaders。 Loaders 可以以为是一些小型的插件,简朴地说就是让webpack在处置惩罚的时刻,当碰到这类范例的文件时,做如许的操纵(操纵就是Loaders也就是你的设置)。以下是Loaders设置的一些例子:

{ // When you import a .ts file, parse it with Typescript 
    test: /\.ts/, 
    loader: 'typescript',
},{
    // When you encounter images, compress them with image-webpack (wrapper around imagemin) 
    // and then inline them as data64 URLs 
    test: /\.(png|jpg|svg)/, 
    loaders: ['url', 'image-webpack'],
},{ 
    // When you encounter SCSS files, parse them with node-sass, then pass autoprefixer on them 
    // then return the results as a string of CSS 
    test: /\.scss/, 
    loaders: ['css', 'autoprefixer', 'sass'],
}

总之到食物链的末端,统统的Loaders返回字符串。这个机制使得Webpack可以将它们引进到javascript的包中。当你的sass文件被Loaders转化后,它在内部会像如许被通报:

export default 'body{font-size:12px}';

为何要如许做?

当你邃晓webpack做了什么后,随之而来的题目大部分是如许做的优点是什么? “图片、css在我的JS中?这是什么鬼?” 好吧,思索下我们近来一向推重的并被教诲应当如许做的,把统统的东西打包成一个文件,以削减http要求……

这致使了一个很大的瑕玷就是大多数人把当前的统统资本都打包到一个app.js文件中,然后包括在统统的页面。这意味着任何给定页面上大部分加载的资本都黑白必需的。假如你不如许做,那末你很可以要手工引入资本,致使须要保护和跟踪一个庞大的依靠书,用来纪录谁人页面用到了款式表A和款式表B。

不管要领是准确的照样毛病的。 设想一下webpack作为一个中心者,它不止是一个构建体系或许一个打包东西,它是一个玩皮的智能模块包装体系。一旦被很好地设置,它会比你越发相识你的栈,所以它会比你越发清晰怎样更好地优化。

让我们一同构建一个简朴的APP

为了让你更简朴地相识webpack的长处,我们将一同构建一个小型的App并对资本举行打包。关于本教程,我发起运转Node4或许Node5以及NPM3的平行依靠树以防备在运用webpack时碰到坑爹地题目。假如你还没有NPM3,你可以经过历程

npm install npm@3 -g 

来装置。

$ node --version
v5.7.1
$ npm --version
3.6.0

我也发起你增加node_modules/.bin 到你的环境变量中以防备每次都输入 node_modules/.bin/webpack 来运转敕令。背面的统统例子我将不会运用node_modules/.bin这个敕令了。

基础的运用

让我们竖立我们的项目并装置webpack,同时我们引入jQuery以便背面运用。

$ npm init -y
$ npm install jquery --save
$ npm install webpack --save-dev

如今,让我们竖立项目的进口,并运用es2015:

src/index.js

var $ = require('jquery');
$('body').html('hello');

然后竖立我们的webpack设置,文件名为webpack.config.js, webpack的设置文件是一个javascript,而且须要export成一个object(对象)

webpack.config.js


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

在这里,entry通知webpack那些文件是你运用的进口。进口文件位于你依靠树的顶部。然后我们通知它去编译我们的文件到__builds__这个文件夹中并运用((bundle.js这个名字。接下来我们竖立我们的index.html:


    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>webpack</title>
    </head>
    <body>
        <h1>my title</h1>
        <a href="">click me</a>
        <script src="builds/bundle.js"></script>
    </body>
    </html>

运转webpack,假如统统运转一般,我们会收到一段信息通知我们webpack胜利编译打包到了bundle.js中:

Version: webpack 1.13.1
Time: 382ms
    Asset    Size  Chunks             Chunk Names
bundle.js  267 kB       0  [emitted]  main
   [0] ./src/index.js 58 bytes {0} [built]
    + 1 hidden modules

在这里你可以看到webpack通知你你的bundle.js包括了我们的进口文件index.js同时另有一个隐蔽的模块。这个隐蔽的模块就是jquery,webpack默许会隐蔽不属于你的模块,假如要看统统被webpack隐蔽的模块,我们可以向webpack传参 –display-modules:

>webpack --display-modules
Hash: 20aea3445ac35ac27c32
Version: webpack 1.13.1
Time: 382ms
    Asset    Size  Chunks             Chunk Names
bundle.js  267 kB       0  [emitted]  main
   [0] ./src/index.js 58 bytes {0} [built]
   [1] ./~/jquery/dist/jquery.js 258 kB {0} [built]

你也可以运转 webpack –watch 让webpack去监听你的文件,一旦有转变则自动编译。

竖立我们的第一个Loader

还记得我们讨论过的webpack怎样引进css和html以及其他统统范例的资本吗?在那里合适?假如你有投身到这几年的web组件化生长的奇迹中(angular2, vue, react, polymer, x-tag等等),你应当听说过关于构建webapp的一个新的观点,不实用单一集成的ui模块,而是将ui分解为多个小型的可重用的ui。如今为了让组件真正自力,他们须要可以将统统依靠都引入他们自身中。设想一下一个按钮一定有html、一些剧本让它可以交互,固然也须要一些款式。最好能在须要到这个组件的时刻统统这些资本才被加载。只要当我们引入这个按钮的时刻,我们才拿到相干的资本。

让我们来写button组件。起首,我假定大多数人都习惯了es2015,我们将增加第一个Loader: babel。装置Loader于webpack中须要做两件事变:**npm install {whatever}-loader, 然后增加它到你webpack设置中,即module.loaders。以下所示:

$ npm install babel-loader --save-dev

因为babel-loader并不会自动装置babel, 我们须要自身装置babel-core另有es2015 preset:

$ npm install babel-core babel-preset-es2015 --save-dev

然后我们竖立.babelrc来通知babel应当用哪种preset,文件是json花样,在本例子中,我们通知它运用es2015 preset

.babelrc { "presets": ["es2015"]}

如今已设置并装置好babel了。我们须要babel运转在统统的以.js末端的文件中,然则因为webpack会遍历包括第三方在内的统统依靠包,因而我们要防备babel运转在如jquery如许的第三方库中。Loaders可以具有一个include或许一个exclude划定规矩,它可所以一个字符串、一个正则表达、一个回调函数或许其他任何你想要的。在本例子中,我们想要babel只运转在我们的文件上,因而我们将include我们的资本文件夹:

module.exports = {
    entry: './src',
    output: {
        path: 'builds',
        filename: '[name].js',
    },
    module: {
        loaders: [
            {
                test: /\.js/,
                loader: 'babel',
                include: __dirname + '/src',
            }
        ],
    }
};

如今我们可以重写我们的index.js(我们在之前引入了babel)。而且接下来的例子我们也将运用es6

写一个小型的组件

如今我们最先写一个小型的button组件, 它将有一些scss款式,一个html模板,另有一些行动。我们将装置我们须要的东西。部下我们须要mustache,一个异常轻量级的模板衬着库,同时另有sasss和html的Loaders。同时,因为Loader可以像管道一样将处置惩罚后的效果递次通报下去,我们将须要一个cssloader来处置惩罚sass Loader处置惩罚后的效果。如今,我们有了我们的css, 有许多体式格局可以处置惩罚他们,此次我们运用的是style-loader,它可以动态地将css注入到页面中去。

$ npm install mustache --save
$ npm install css-loader style-loader html-loader sass-loader node-sass --save-dev

我们由右到左以‘!’为支解向设置文件通报loader以通知webpack怎样将匹配到的文件递次通报给Loaders,你也可以运用数组来举行通报,固然递次也如果由右到左

module.exports = {
    entry: './src',
    output: {
        path: 'builds',
        filename: '[name].js',
    },
    module: {
        loaders: [
            {
                test: /\.js/,
                loader: 'babel',
                include: __dirname + '/src',
            }
        ],
        {
            test: '\.scss',
            loader: 'style!css!sass',
            // loaders: ['style', 'css', 'sass'],
        },
        {
            test: /\.html/,
            loader: 'html',
        }
    }
};

loaders已设置装置好了,我们可以最先写我们的按钮了

src/Components/Button.scss

.button {
    background: tomato;
    color: white;
}

src/Components/Button.html

<a href="{{link}}" class="button">{{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();

        $(node).html(
            Mustache.render(template, {text})
            );

        $('.button').click(this.onClick.bind(this));
    }
}

你的button.js如今是100%自援用而且在那里都可以被援用, 如今我们只须要将button衬着到我们的页面来

src/index.js

import Button from './Components/Button';
Button = Button.default
const button  = new Button('google.com');
button.render('a');

运转webpack,革新页面,你应当可以看到我们貌寝的按钮,并有对应的行动,(这一步有题目,编译胜利了,然则没法new一个,提醒 _Button2.default is not a constructor 毛病)
至此你学会了怎样竖立loaders以及怎样定义运用每一部分的依靠。如今貌似还不不出有什么用途,让我们越发深切到例子中去。

代码支解

上面的例子会一向援用button,固然,这并没有什么题目,但我们并不老是一向须要我们的按钮。或许在一些页面没有按钮须要衬着。在这类状况下,我们不想去引入按钮的款式、模板等。这个时刻就是代码支解进场的时刻了(code spliting)。代码支解就是webpack用来处理之前所说的单集成模块 VS 不可保护的援用的题目。支解点(split points):你的代码被支解为多个文件并被按需要求加载。语法异常简朴:

import $ from 'jquery';

// This is a split point
require.ensure([], () => {
  // All the code in here, and everything that is imported
  // will be in a separate file
  const library = require('some-big-library');
  $('foo').click(() => library.doSomething());
});

任何写在require.ensure回调中的东西会被离开到一个数据块,一个断绝的文件,webpack会在须要的时刻,经过历程ajax要求去加载。这意味着,我们会看到以下面的依靠树:

bundle.js
|- jquery.js
|- index.js // our main file
chunk1.js
|- some-big-libray.js
|- index-chunk.js // the code in the callback

而且我们不必去引入chunk1.js或许去加载它,webpack已帮我们做了这些事变。这意味着我们可以经过历程林林总总的逻辑去支解我们的代码。在接下来的例子中,我们只想在页面有链接的时刻去加载我们的button组件

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)。缘由在于require没法同时处置惩罚default和一般的导出,所以你须要显现说明想要用哪个。而import则有一个体系来处理这个题目,所以它晓得怎样处置惩罚。(eg. import foo from 'bar' vs import {baz} from 'bar')

如今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]
    [1] ./src/Components/Button.js 1.94 kB {1} [built]
    [2] ./~/jquery/dist/jquery.js 259 kB {1} [built]
    [3] ./src/Components/Button.html 72 bytes {1} [built]
    [4] ./~/mustache/mustache.js 19.4 kB {1} [built]
    [5] ./src/Components/Button.scss 1.05 kB {1} [built]
    [6] ./~/css-loader!./~/sass-loader!./src/Components/Button.scss 212 bytes {1} [built]
    [7] ./~/css-loader/lib/css-base.js 1.51 kB {1} [built]
    [8] ./~/style-loader/addStyles.js 7.21 kB {1} [built]

从输出数据你可以看到,我们的进口文件(bundle.js)如今只包括webpack的逻辑,其他的剧本(jquery、mustache、button)全都在1.bundle.js中,并只要当我们页面中有衔接的时刻才会加载进来。如今为了让webpack晓获得那里去ajax我们的数据块,我们须要设置下我们的文件:

path: 'builds',
filename: 'bundle.js',
publicPath: 'builds/',

publishPath通知webpack到那里去找资本, 至此,我们运转webpack,因为页面有衔接,因而webpack加载了button组件。注重: 我们可以对数据块举行定名来替代默许的1.bundle.js:

if (document.querySelectorAll('a').length) {
    require.ensure([], () => {
        const Button = require('./Components/Button').default;
        const button = new Button('google.com');

        button.render('a');
    }, 'button');
}

尝试了,发明并没有什么用……是我翻开的体式格局不对么

增加第二个组件

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 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');
    });
}

再次运转webpack检察依靠状况,你会发明两个组件都须要jquery、mustache,意味着这些依靠模块被反复定义于我们的数据块中,这并非我们想要的。默许状况webpack并不对此举行优化。然则webpack可以经过历程插件的情势供应强力的优化设计。

插件(plugins)和loaders差别,loaders只实行与特定范例的文件,plugins实行于统统的文件并供应更多雄厚的功用。webpack具有大批的插件来处置惩罚林林总总的优化。CommonChunksPlugin可以用来处理这个题目的插件, 它经过历程递归剖析你的依靠包,找到公用的模块并将它们星散成一个自力的文件中,固然你也可以写入到进口文件中。

在接下来的例子中,我们将公用的模块放到了我们的进口文件中,因为假如统统的页面有援用了jquery和mustache,我们就把它们放到顶端。接下来让我们更新下我们的设置:

plugins: [
    new webpack.optimize.CommonsChunkPlugin({
        name:      'main', // Move dependencies to our main file
        children:  true, // Look for common dependencies in all children,
        minChunks: 2, // How many times a dependency must come up before being extracted
    })
]

假如我们再次运转webpack, 我们可以发明公用的组件已被提取到了顶部:

chunk    {0} bundle.js (main) 287 kB [rendered]
    [0] ./src/index.js 550 bytes {0} [built]
    [2] ./~/jquery/dist/jquery.js 259 kB {0} [built]
    [4] ./~/mustache/mustache.js 19.4 kB {0} [built]
    [7] ./~/css-loader/lib/css-base.js 1.51 kB {0} [built]
    [8] ./~/style-loader/addStyles.js 7.21 kB {0} [built]
chunk    {1} 1.bundle.js 3.28 kB {0} [rendered]
    [1] ./src/Components/Button.js 1.94 kB {1} [built]
    [3] ./src/Components/Button.html 72 bytes {1} [built]
    [5] ./src/Components/Button.scss 1.05 kB {1} [built]
    [6] ./~/css-loader!./~/sass-loader!./src/Components/Button.scss 212 bytes {1} [built]
chunk    {2} 2.bundle.js 2.92 kB {0} [rendered]
    [9] ./src/Components/Header.js 1.62 kB {2} [built]
   [10] ./src/Components/Header.html 64 bytes {2} [built]
   [11] ./src/Components/Header.scss 1.05 kB {2} [built]
   [12] ./~/css-loader!./~/sass-loader!./src/Components/Header.scss 192 bytes {2} [built]

假如我们将name改成’vender‘:

new webpack.optimize.CommonsChunkPlugin({
    name:      'verder', // Move dependencies to our main file
    children:  true, // Look for common dependencies in all children,
    minChunks: 2, // How many times a dependency must come up before being extracted
})

因为该数据块还没有竖立出来,webpack会自动竖立builds/verder.js的文件,然后供我们在html中援用,这一步笔者试了,发明没法竖立vender这个依靠,统统公用依靠也没有被提取出来,不晓得是不是是windows的题目。

你还可以使得公用模块文件以异步要求的体式格局加载进来,设置属性async: true便可以了。webpack另有大批的功用壮大智能化的插件,我没法一个个引见它们,然则作为演习,让我们为运用竖立一个临盆环境

临盆和逾越

起首,我们将增加几个插件到我们的设置中去,但我们只想要在临盆环境中去加载并运用这些插件。所以我们要增加逻辑来掌握我们的设置。

var webpack    = require('webpack');
var production = process.env.NODE_ENV === 'production';

var plugins = [
    new webpack.optimize.CommonsChunkPlugin({
        name:      'main', // Move dependencies to our main file
        children:  true, // Look for common dependencies in all children,
        minChunks: 2, // How many times a dependency must come up before being extracted
    }),
];

if (production) {
    plugins = plugins.concat([
       // Production plugins go here
    ]);
}

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 generation),webpack有几个体式格局来衬着sourcemaps,eval是在当地开辟中最好的一种,但在临盆环境中,我们并不在乎这些,所以在临盆环境中我们制止了它。接下来我们可以增加临盆环境中用到的插件:

if (production) {
    plugins = plugins.concat([

        // This plugin looks for similar chunks and files
        // and merges them for better caching by the user
        new webpack.optimize.DedupePlugin(),

        // This plugins optimizes chunks and modules by
        // how much they are used in your app
        new webpack.optimize.OccurenceOrderPlugin(),

        // This plugin prevents Webpack from creating chunks
        // that would be too small to be worth loading separately
        new webpack.optimize.MinChunkSizePlugin({
            minChunkSize: 51200, // ~50kb
        }),

        // This plugin minifies all the Javascript code of the final bundle
        new webpack.optimize.UglifyJsPlugin({
            mangle:   true,
            compress: {
                warnings: false, // Suppress uglification warnings
            },
        }),

        // This plugins defines various variables that we can set to false
        // in production to avoid code related to them from being compiled
        // in our final bundle
        new webpack.DefinePlugin({
            __SERVER__:      !production,
            __DEVELOPMENT__: !production,
            __DEVTOOLS__:    !production,
            'process.env':   {
                BABEL_ENV: JSON.stringify(process.env.NODE_ENV),
            },
        }),

    ]);
}

这些我最常运用到的插件,webpack还供应了许多其他的插件供你去谐和你的模块和数据块。同时在npm上也有自在开辟者开辟孝敬出来的具有壮大功用的插件。详细可以参考文章末了的链接。

如今你愿望你临盆环境下的资本能按版本宣布。还记得我们为bundle.js设置过的output.filename属性吗?这里有几个变量供你运用,一个是[hash], 和终究天生的bundle.js内容的哈希值保持一致。我们也想我们的数据块(chunks)也版本话,我们将设置output.chunkFilename属性来完成一样的功用:

output: {
    path:          'builds',
    filename:      production ? '[name]-[hash].js' : 'bundle.js',
    chunkFilename: '[name]-[chunkhash].js',
    publicPath:    'builds/',
},

在我们这个简朴的运用中并没有一个要领来动态检索编译后文件的名字啊,我们将只在临盆环境中运用版本化的资本。同时我们想在临盆环境中清空我们的打包环境,让我们增加一个三方插件:

npm install --save-dev clean-webpack-plugin

将这个插件设置到webpack中:

var webpack     = require('webpack');
var CleanPlugin = require('clean-webpack-plugin');

// ...

if (production) {
    plugins = plugins.concat([

        // Cleanup the builds/ folder before
        // compiling our final assets
        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究竟做了什么:一最先,因为我们的例子异常的简朴轻量级,我们的两个异步数据块不值得运用两个异步要求去猎取,所以webpack将它们兼并回了进口文件中;其次,统统的文件都被合理地紧缩了。我们从底本的3个要求总大小为322kb变成了一个97kb大小的文件。

But wasn’t the point of Webpack to stem away for one big ass JS file?
然则webpack不是不首倡兼并成一个文件吗?

是的,它确实不首倡,当时假如我们的app很小,代码量很少,它是首倡如许做的。但请斟酌以下状况,你不须要去斟酌什么时刻什么处所做什么兼并。假如你的数据块突然间依靠了许多模块,那末webpack会让它变成异步加载而不是兼并到进口文件中, 同时假如这些模块的依靠有公用的,那末这些模块也会被抽离出来等等。你只须要设立好划定规矩,然后,webpack变回自动供应最好的优化设计。不必手册,不必思索模块依靠的递次,统统的东西都自动化了。

你可以发明我并没有设置任何东西去紧缩我们的HTML和CSS,这是因为CSS-loader和html-loader已默许完成了这些事变。

因为webpack是自身就是一个JS-loader,因而在webpack中没有js-loader,这也是uglify是一个自力引进来的插件的缘由。

信息抽取

如今你可以发明,一最先我们顶一个的款式被离开几段插进去到页面从而致使了FOUAP(Flash of Ugly Ass Page),假如我们可以把统统的款式都兼并到一个文件中不是更好吗?是的,我们可以运用另一个插件:

$ npm install extract-text-webpack-plugin --save-dev

这个组件做了我适才说的事变,它收集了你末了的bundle后内容里统统的款式,并将它们合成到一个文件中。

让我们把它引入:

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'), // <=== where should content be piped
    new webpack.optimize.CommonsChunkPlugin({
        name:      'main', // Move dependencies to our main file
        children:  true, // Look for common dependencies in all children,
        minChunks: 2, // How many times a dependency must come up before being extracted
    }),
];

// ...

module.exports = {
    // ...
    plugins: plugins,
    module:  {
        loaders: [
            {
                test:   /\.scss/,
                loader: ExtractPlugin.extract('style', 'css!sass'),
            },
            // ...
        ],
    }
};

Now the extract method takes two arguments: first is what to do with the extracted contents when we’re in a chunk (‘style’), second is what to do when we’re in a main file (‘css!sass’). Now if we’re in a chunk, we can’t just magically append our CSS to the generated one so we use the style loader here as before, but for all the styles that are found in the main file, pipe them to a builds/bundle.css file. Let’s test it out, let’s add a small main stylesheet for our application:

(这一段翻译得不好,请看上面的原文)

如今可以看到 extract 要领传入了两个参数: 第一个是当我们在style数据块中我们要对引出的内容做什么;第二是当我们在进口文件css!sass中要做的事变。假如我们在一个数据块中,我们不能简朴地把我们的CSS增加到我们的css文件中,所以我们在此之前运用style加载器,但关于在进口函数找到的统统款式,我们将它们通报到builds/bundle.css文件中。让我们为运用增加一个主款式表。

题目:这里碰到一个题目,每次修正主款式表(styles.scss)后,假如是有监听的话,webpack的自动重编译是会失足的,须要从新保留一次剧本才能让其准确编译胜利,不晓得是什么题目致使的。

src/styles.scss

body {
  font-family: sans-serif; 
  background: darken(white, 0.2);
}

src/index.js

import './styles.scss';

假如你想导出统统的款式,你也可以向ExtractTextPlugin传参(’bundle.css’, {allChunks: true})。假如你想在你的文件名中运用变量,你也可以传入 [name]-[hash].css。

图片处置惩罚

剧本处置惩罚已基础可以,然则我们还没有处置惩罚如图片、字体等资本。在webpack中要怎样处置惩罚这些资本并获得最好的优化?接下来让我们下载一张图片并让它作为我们的背景,因为我以为它很酷:

《[译] Webpack 前端构建集成计划》

将这张图片保留在img/puppy.png& 并更新我们的sass文件:

body {
    font-family: sans-serif;
    background-color: #333;
    background-image: url('../img/puppy.jpg');
    background-size: cover;
}

假如你如许做的话,webpack会和你说:“我tmd要我怎样处置惩罚jpg这东西?”,因为我们没有一个Loader用来处置惩罚它。有两个自带的加载器可以用来处置惩罚这些资本,一个是file-loader,另一个是url-loader,第一个不会做什么转变,只会返回一个url,并可以版本化设置,第二个可以将资本转化为base64的url

这两个加载器各有优瑕玷:假如你的背景图片是2mb大的图片,你不会想将它作为base64引入到款式表中而越发倾向于零丁去加载它。假如它只是一个2kb的图片,那末则引入它从而削减http要求次数会更好:

所以我们把这两个加载器都装置上:

$ npm install --save-dev url-loader file-loader
{
    test: /\.(png|gif|jpe?g|svg)$/i,
    loader: 'url?limit=10000',
},

我们在这里向url-loader通报了限定类的参数,通知它:假如资本文件小于10kb则引入,不然,运用file-loader去处置惩罚它。语法运用的是查询字符串,你也可以运用对象去设置加载器:

{
    test: /\.(png|gif|jpe?g|svg)$/i,
    // loader: 'url?limit=10000',
    loader: 'url',
    query: {
        limit: 10000,
    }
},

好了,让我们来试试看

                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文件,因为我们的puppy图片太小了,它被直接引入到bundle.css文件中了。

webpack会智能地依据大小或许http要求来优化资本文件。另有许多加载器可以更好地处置惩罚,最经常使用的一个是image-loader,可以在兼并的时刻对图片举行紧缩,以至可以设置?bypassOnDebug让你只在临盆环境中运用。像如许的插件另有许多,我勉励你在文章的末端去看看这些插件。

及时监听编译

我们的临盆环境已搭建好了,接下来就是及时重载:LiveReload、BrowserSync,这多是你想要的。然则革新全部页面很斲丧机能,让我们运用更吊的设备hot module replacement或许叫做hot reload。因为webpack晓得我们依靠树的每个模块的位置,修正的时刻就可以很简朴地替代树上的某一块文件。更清晰地说,当你修正文件的时刻,浏览器不必革新全部页面就可以看到及时变化。

要运用HMR,我们须要一个支撑hot assets的服务器。Webpack有一个dev-server供我们运用,装置下:

$ npm install webpack-dev-server --save-dev

然后运转该服务器:

$ webpack-dev-server --inline --hot

第一个参数通知webpack将HMR逻辑引入到页面中(而不运用一个iframe去包括页面),第二个参数是启动HMR(hot module reload)。如今让我们接见web-server的地点:http://localhost:8080/webpack-dev-server/ 。尝试转变文件,会看到浏览器上及时的变化

你可以运用这个插件作为你的当地服务器。假如你设计一向运用它来做HMR,你可以将其设置到webpack中。

output: {
    path:          'builds',
    filename:      production ? '[name]-[hash].js' : 'bundle.js',
    chunkFilename: '[name]-[chunkhash].js',
    publicPath:    'builds/',
},
devServer: {
    hot: true,
},

设置后,不管我们什么时刻运转ewbpack-dev-server,它都邑在HMR形式。固然,另有许多设置供你设置,比方供应一个中心件供你在express服务器中运用HMR形式。

范例的代码

假如你一向随着本文实践,你一定发明了新鲜的处所:为何Loaders被放在了Module.loaders中,而plugins却没有?这固然是因为另有其他东西你可以放在module中!Webpack不仅有loaders,它也有pre-loaders和post-loaders:它们会在主加载器加载前/加载后实行。来个例子,很明显我的代码异常蹩脚,所以在转化前我们运用eslint来检测我们的代码:

$ npm install eslint eslint-loader babel-eslint --save-dev

竖立一个小型的eslintrc文件:

.eslintrc

parser: 'babel-eslint'
rules: 
  quotes: 2

如今我们增加我们的preloader,我们运用和之前一样的语法:

        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

在举另一个例子,如今我们的组件都邑引入一样名字的款式表以及模板。让我们运用一个预加载器来自动加载:

$ npm install baggage-loader --save-dev
{
    test: /\.js/,
    loader: 'baggage?[file].html=template&[file].scss',
}

这通知webpack,假如你定义了一个一样名字的html文件,会把它以template的名字引入,一样的也会引入同名的sass文件。如今我们可以修正我们的组件:

import $ from 'jquery'
import Mustache from 'mustache'
// import template from './Header.html'
// import './Header.scss'

pre-loader的功用壮大,post-loader也一样,你也可以从文章末端看到许多有效的加载器并运用它们。

你还想相识更多吗?

如今我们的运用还很小,但随着运用的增大,相识正式的依靠树状况是很有效的。可以协助我们相识我们做的是不是准确,我们的运用的瓶颈在那里。webpack晓得统统这些事变,但我们须要通知他显现给我们看,我们可以随处一个profile文件:

webpack --profile --json > stats.json

第一个参数通知webpack天生一个profile 文件,第一个指定天生的花样。有多个站点供应剖析并可视化这些文件的功用,webpack官方也供应剖析这些信息的功用。所以你可以到webpack analysis引入你的文件。挑选modules 标签然后便可以看到你的可视化依靠树。另一个我比较喜好的是webpack visualizer
用圆环图的情势示意你的包大小占有状况。

That’s all folks

我晓得在我的案例中,Webpack已完整庖代了Grunt或许gulp了,大部分功用已由webpack来渠道,剩下的值经过历程npm script。过去运用Aglio转化我们的API文档为html我们运用的是使命型,如今可以如许做:

package.json

{
  "scripts": {
    "build": "webpack",
    "build:api": "aglio -i docs/api/index.apib -o docs/api/index.html"
  }
}

不管你在gulp中有何等庞杂不关乎打包的使命,Webpack都可以很好地合营。供应一个在Gulp中集成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是一个壮大的东西,固然价值是更多的设置须要你去写。不过一旦你晓得怎样征服它,它会给你最好的优化设计。我在几个项目中运用了它,它也供应了壮大的优化设计和自动化,让我没法不必它。

资本

译者拓展链接:

备注

开辟历程碰到的题目可以检察原文下的批评或和译者交流学习。

译者英文程度有限,假如那里翻译的不好迎接斧正,相干的代码可参考译者的demo2demo6demo4是运用Webpack + Vue写的DEMO,有兴致的同砚也可以看看。

    原文作者:chenjsh36
    原文地址: https://segmentfault.com/a/1190000005659878
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞