2

本文同步自我的博客http://joeray61.com

本文是一个简单的webpack入门教程,希望能够帮助webpack初学者快速上手。如有错误,敬请斧正。
本文所有的demo都可以在webpack-demo里找到,git clone之后需要执行npm install安装所有依赖包。

简介

webpack是一个模块打包工具,出自德国开发者Tobias Koppers之手,处理速度很快。在webpack看来,一切资源都可以是一个模块,不仅限于js文件。正是由于这些特点,使得webpack可以替代GruntGulp,成为了目前业内最火的前端构建工具。

clipboard.png

安装

webpack可以安装到全局,也可以作为项目的依赖工具安装到项目中

// 全局安装
$ npm install -g webpack
$ npm install -g webpack-dev-server

如果报没有权限的话,在命令前面加上sudo就可以了

// 局部安装
$ npm install --save-dev webpack
$ npm install --save-dev webpack-dev-server

可以看到,我们在安装webpack的同时,也安装了webpack-dev-server。那么webpack-dev-server是什么呢?它是一个轻量级的基于expressnode.js服务器,用来服务资源文件。不是必须的,但是建议一起安装。

命令行

$ webpack main.js bundle.js

以上是直接使用webpack命令行来进行打包的命令,把main.js构建成bundle.js,具体示例可以查看demo1,在文件夹中执行上方的命令即可。

webpack有一些参数是我们应该要知道的

  • webpack: 构建一次开发版本

  • webpack -p: 构建一次产品版本,与开发版本的区别是会对文件进行压缩

  • webpack -d: 添加source map

  • webpack --watch: 监听文件的改动,持续增量构建

  • webpack --colors: 让命令行的输出更好看一点(实际使用zsh的命令行时发现并没有区别)

配置文件

webpack还有很多强大的功能,这些都可以在命令行使用,但是如果都写在命令行,既不方便使用,也不能直观地反映各个配置项,这时候,就需要配置文件登场了。

webpack默认使用的配置文件名是webpack.config.js,也可以通过--config参数在命令行指定另一个命名的配置文件。配置文件其实也是一个模块,所有的构建信息都放在module.exports中。下面我们就来讲一讲各个重要的配置项。

以下各个示例请在各demo文件夹中执行webpack-dev-server进行查看

入口文件

入口文件的配置项名称是entry,代表了webpack给整个项目进行打包的一个主入口,可以搭配output属性指定的输出文件来使用

// webpack.config.js

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

// index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Demo2</title>
</head>
<body>
    <script src="bundle.js"></script>
</body>
</html>

// main.js

document.write('<h1>Hello Webpack!</h1>');

示例见demo2

多入口文件

在多页的应用中,我们需要有多个入口文件,这在webpack中也是可以支持的。

// webpack.config.js

module.exports = {
    entry: {
        profile: './profile.js',
        feed: './feed.js'
    },
    output: {
        filename: '[name].bundle.js'
    }
};

// feed.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Demo3-Feed</title>
</head>
<body>
    <script src="feed.bundle.js"></script>
</body>
</html>

// feed.js

document.write('<h1>Hello Feed!</h1>');

// profile.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Demo3-Profile</title>
</head>
<body>
    <script src="profile.bundle.js"></script>
</body>
</html>

// profile.js

document.write('<h1>Hello Profile!</h1>');

示例见demo3,在demo3文件夹中启动webpack-dev-server,访问localhost:8080/profile.htmllocalhost:8080/feed.html查看效果。

Loaders

Loaders顾名思义就是加载器,用于加载各种格式的文件,例如React使用的JSXES6ES7,图片、CSSJSON文件等,可谓是webpack中最核心的功能之一。官方文档中列出了所有可用的loaders

loaders配置在module.exports中的module字段下(说起来有点绕,待会儿看代码就懂了),是一个数组,代表loader的集合,每一个loader有几个配置项:

  • test: 正则表达式,用于匹配文件的路径,一般都直接用来匹配文件后缀,在复杂的场景下,可以针对同一类型的不同文件做不同处理

  • loader: loader的名称,实际使用时可以省略-loader的后缀(例如css-loader,可以写成css

  • include: 必须包含的文件路径

  • exclude: 不需要处理的文件路径

  • query: loader的额外设置选项

下面我介绍几个常用的loader。

CSS Loader

先安装style-loadercss-loader。其中,style-loader用来向 HTML 页面中插入<style>css-loader用来读取 css 文件。

$ npm install --save-dev style-loader css-loader

然后在webpack.config.js中配置loader

module.exports = {
    entry: './main.js',
    output: {
        filename: 'bundle.js'
    },
    module: {
        loaders: [
            {
                test: /\.css$/,
                loader: 'style!css'
            }
        ]
    }
};

// index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Demo4</title>
</head>
<body>
    <h1>Hello World!</h1>
    <script src="bundle.js"></script>
</body>
</html>

// a.css

h1 {color: red;}

// main.js

require('./a.css');

示例见demo4,可以看到,多个loader之间用!连接。在本地用webpack-dev-server启动服务,访问localhost:8080可以看到h1中的字体颜色如a.css定义的那样显示红色

clipboard.png

这个 demo 中的 css 是对全局生效的,如果需要只对当前模块生效,就要用到我们下面介绍的css module

CSS Module

近几年,模块化这个概念在前端被不断地被提及。模块化使得代码被分割成一个个更小的、独立的、可维护性更高的独立单元。相比于JSCSS的模块化发展的相对慢一点。最近出现了一个叫做css modules的技术,在css模块中,所有的类名和动画名默认只对向前模块生效。webpackcss module提供了不错的支持,只需要在css-loader后面加上?module即可使用。如果要对全局生效,可以使用:global(),把选择器作为参数传入。

// webpack.config.js

module.exports = {
    entry: './main.js',
    output: {
        filename: 'bundle.js'
    },
    module: {
        loaders: [
            {
                test: /\.css$/,
                loader: 'style!css?module'
            }
        ]
    }
};

// index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Demo5</title>
</head>
<body>
    <div class="foo">Foo</div>
    <div class="bar">Bar</div>
    <script src="bundle.js"></script>
</body>
</html>

// a.css

.foo {color: red;}
:global(.bar) {
    color: green;
}

// main.js

var style = require('./a.css');

document.write('<div class=' + style.foo + '>Module - Foo</div>');
document.write('<div class="bar">Module - Bar</div>');

需要注意的是,在main.js中使用时,必须用变量style.foo,直接写成class="foo"是不会生效的,因为a.css中的.foo的类名被编译了,所以导出的style.foo将不等于foo

示例代码见demo5。在 demo5 的目录下执行webpack-dev-server,在浏览器中打开localhost:8080,可以看到对应的效果。

clipboard.png

Url Loader

先安装url-loaderfile-loader

$ npm install --save-dev url-loader file-loader

注意,url-loaderfile-loader有依赖,但是安装url-loader时不会自动安装file-loader,这里我暂时没有花时间去了解原因,先都手动安装吧。如果不安装file-loader,超过limit大小的文件将无法加载。

url-loader是一个文件加载器,我们可以给他设定一个limit,用来限定文件大小,文件小于这个大小时会被转换成一个Data Url,反之转换成普通url表示资源路径。我们常用url-loader来加载图片。

// webpack.config.js

module.exports = {
    entry: './main.js',
    output: {
        filename: 'bundle.js'
    },
    module: {
        loaders: [
            {
                test: /\.(png|jpg)$/,
                loader: 'url-loader?limit=8192'
            }
        ]
    }
};

// index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Demo6</title>
</head>
<body>
    <script src="bundle.js"></script>
</body>
</html>

// main.js

var img1 = new Image();
img1.src = require('./webpack.png');
document.body.appendChild(img1);

var img2 = new Image();
img2.src = require('./wade.jpg');
document.body.appendChild(img2);

示例代码见demo6。在demo6目录下运行webpack-dev-server,在浏览器中访问localhost:8080,审查元素可以看到,8k以下的图片使用了Data Url,8k以上的图片使用的是资源地址。

clipboard.png

Babel Loader

现如今,ES6React可谓是红透半边天,说起ES6React,就不得不提Babel了。Babel是一个 Javascript 编译工具,可以让你现在就能使用目前还未被浏览器完全支持的下一代JS(ES6/ES7),或是基于 JS 进行扩展的 JSX 等。

我们先安装需要的包

$ npm install --save-dev babel-core babel-loader babel-preset-es2015 babel-preset-react react react-dom

其中,babel-corebabel的核心功能,babel-loaderwebpack使用的babel加载器,babel-preset-es2015babel-preset-reactbabel用来解析ES6JSX的包,reactreact-dom是开发react的依赖包。

// webpack.config.js

module.exports = {
    entry: './main.jsx',
    output: {
        filename: 'bundle.js'
    },
    module: {
        loaders: [
            {
                test: /\.js[x]?$/,
                exclude: /node_modules/,
                loader: 'babel-loader',
                query: {
                    presets: ['es2015', 'react']
                }
            }
        ]
    }
};

// index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Demo7</title>
</head>
<body>
    <div id="root"></div>
    <script src="bundle.js"></script>
</body>
</html>

// main.jsx

import React from 'react';
import {render} from 'react-dom';

render(
    <h1>Hello world!</h1>,
    document.querySelector('#root')
);

可以看到,在这个配置中,我们把babel-loader的配置写在query字段中。其实也可以直接写在loader字段中,loader: 'babel-loader?presets[]=es2015&presets[]=react',但是这样就不是太清晰了,建议配置比较长时拿出来放到query字段中去。

示例代码见demo7,在demo7目录下执行webpack-dev-server,在浏览器中访问localhost:8080查看效果。

Plugins

pluginswebpack中另一个非常重要且又用的配置,它与loaders的区别在于,loader是用来加载文件的,会逐个文件处理,而plugins是作为webpack功能的补充,整个构建过程都在持续发挥作用。下面我来给大家介绍几个常用的插件。

UglifyJS Plugin

这个插件估计很多人看名字就猜出来了,是用于压缩 JS 代码的。它是webpack的内置插件,不需要安装就可以使用。

直接上代码吧:

// webpack.config.js

var webpack = require('webpack');
var UglifyJsPlugin = webpack.optimize.UglifyJsPlugin;

module.exports = {
    entry: './main.js',
    output: {
        filename: 'bundle.js'
    },
    plugins: [
        new UglifyJsPlugin()
    ]
};

// index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Demo8</title>
</head>
<body>
    <script src="bundle.js"></script>
</body>
</html>

// main.js

var name = 'JoeRay61';
var age = 24;
document.write('My name is ' + name + ', I\'m ' + age + ' years old.');

示例代码见demo8。在demo8目录下执行webpack-dev-server,在浏览器中访问localhost:8080,打开开发这工具,可以看到bundle.js的代码被压缩成了一行,变量名也被替换了,说明插件生效了。

!function(r){function e(t){if(o[t])return o[t].exports;var n=o[t]={exports:{},id:t,loaded:!1};return r[t].call(n.exports,n,n.exports,e),n.loaded=!0,n.exports}var o={};return e.m=r,e.c=o,e.p="",e(0)}([function(r,e){var o="JoeRay61",t=24;document.write("My name is "+o+", I'm "+t+" years old.")}]);

Feature Flags

想象这样一种场景,我们希望在开发环境中在代码中输出一些调试信息,那么如何在webpack中实现呢。这时候就需要用上Feature Flags。它是借由webpack的自定义插件机制实现的全局环境变量,我们可以在代码中判这些全局变量的值来实现定制化的功能。

// webpack.config.js

var webpack = require('webpack');
var flags = new webpack.DefinePlugin({
    __PROD__: JSON.stringify(JSON.parse(process.env.PROD || 'false'))
});

module.exports = {
    entry: './main.js',
    output: {
        filename: 'bundle.js'
    },
    plugins: [flags]
};

// index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Demo9</title>
</head>
<body>
    <script src="bundle.js"></script>
</body>
</html>

// main.js

document.write('<h1>Hello world!</h1>');
if (!__PROD__) {
    document.write('<p>it is dev version</p>');
}

示例代码见demo9。进入demo9目录,分别执行PROD=1 webpack-dev-serverwebpack-dev-server,在浏览器中访问localhost:8080,可以看到区别。

clipboard.png
clipboard.png

Hot Module Replacement

HMRwebpack中一个激动人心的功能,中文直译叫模块热替换,是指配合webpack-dev-server的服务器,在你修改了项目的模块之后,不需要手动刷新页面,即可以看到更新后的效果,有效地提升了开发效率。

配合webpack-dev-server,我们有2种方式可以启用该功能:

  1. 在命令中中webpack-dev-server命令后面追加--hot--inline参数

    • --hot: 添加HMR插件,将服务器切换到 hot 模式

    • --inline: 将webpack-dev-server的 runtime 加入到打包后的文件中

    • --hot --inline: 当这两个参数并存时,会额外添加一个webpack/hot/dev-serverentry

  2. 配置webpack.config.js

    • 添加new webpack.HotModuleReplacementPlugin()插件

    • 添加webpack/hot/dev-serverwebpack-dev-server/client?http://localhost:8080这2个entry

    针对第2中方式我们来测试一下:

// webpack.config.js

var webpack = require('webpack');

module.exports = {
    entry: [
        'webpack/hot/dev-server',
        'webpack-dev-server/client?http://localhost:8080',
        './main.js'
    ],
    output: {
        filename: 'bundle.js'
    },
    plugins: [
        new webpack.HotModuleReplacementPlugin()
    ]
};

// index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Demo10</title>
</head>
<body>
    <script src="bundle.js"></script>
</body>
</html>

// main.js

document.write('<h1>Hello world!</h1>');

注意,测试过程中我发现,如果原来是全局安装的webpack-dev-server,必须要在项目中局部安装webpack-dev-server,否则启动服务器时会报错
安装命令$ npm install --save-dev webpack-dev-server

示例代码见demo10。在demo10目录下,执行webpack-dev-server,在浏览器中查看localhost:8080,发现输出Hello world!,这时候不要关闭服务器,直接修改main.js的代码,保存后查看浏览器效果,发现虽然没有手动刷新页面,但是效果已经出来了。

Commons Chunk Plugin

当几个不同的脚本有公共的部分时,我们可以把公共部分抽出来放到一个单独的文件中。

// webpack.config.js

var webpack = require('webpack');

module.exports = {
    entry: {
        index: './index.js',
        main: './main.js'
    },
    output: {
        filename: '[name].bundle.js'
    },
    plugins: [
        new webpack.optimize.CommonsChunkPlugin('common.js')
    ]
};

// index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Demo11</title>
</head>
<body>
    <script src="common.js"></script>
    <script src="index.bundle.js"></script>
    <script src="main.bundle.js"></script>
</body>
</html>

// index.js

var data = require('./data');
document.write('<p>foo is ' + data.foo + '</p>');

// main.js

var data = require('./data');
document.write('<p>bar is ' + data.bar + '</p>');

// data.js

var data = {
    foo: 123,
    bar: 456
};

module.exports = data;

具体示例见demo11。在demo11目录下执行webpack-dev-server,在浏览器中访问localhost:8080,打开开发者工具,发现data.js被抽出单独放到了common.js中。

总结

本文是我自己学习webpack的一个笔记,我把它记录下来,希望能给其他的webpack初学者提供一点帮助,谢谢观看,欢迎交流!


JoeRay61
2.3k 声望182 粉丝