webpack入门了解一下

webpack 是什么

web开发中常用到的静态资源主要有JavaScript、CSS、图片、pug等文件,webpack中将静态资源文件称之为模块。webpack是一个模块打包工具,其可以兼容多种js书写规范,且可以处理模块间的依赖关系,具有更强大的js模块化的功能。 官方网站中用下图清晰的描述了webpack采用不同的loader加载不同的资源文件,打包生成多个js文件,也可以根据设置生成独立的图片、css文件等。

"麻蛋!图片跟她媳妇跑啦"

为什么用webpack

在以往的开发过程中,经常会遇到以下三种情况:

  1. 项目中资源多样性和依赖性 - js、css、png、less、pug等为了方便开发,我们经常会使用不同的语法来编写文档,用less、sass、pug等会提高开发效率,但同时我们需要借助gulp或grunt来编写任务编译文件或对图片进行压缩等。
  2. JS模块规范复杂化 - AMD、CommonJS、ES6等 requireJS主要用来处理AMD规范的JS文件,若使用CommonJS规范的JS库文件,需进行AMD规范的封装,才能正常使用。而browserify主要处理CommonJS规范的文件,其他规范也需要进行转化。近期ES6的兴起,前面两种打包工具已经不能满足我们的需求了。
  3. 开发与线上文件不一致性(打包压缩造成影响)

webpack可以很好地解决上面的问题,它具有Grunt、Gulp对于静态资源自动化构建的能力,是一个出色的前端自动化构建工具、模块化工具、资源管理工具。

webpack 特性

webpack具有requireJs和browserify的功能,但仍有很多自己的新特性:

  1. 对 CommonJS 、 AMD 、ES6的语法做了兼容
  2. 对js、css、图片等资源文件都支持打包
  3. 串联式模块加载器以及插件机制,让其具有更好的灵活性和扩展性,例如提供对CoffeeScript、TypeScript、ES6的支持
  4. 有独立的配置文件webpack.config.js
  5. 可以将代码切割成不同的chunk,实现按需加载,降低了初始化时间
  6. 支持 SourceUrls 和 SourceMaps,易于调试
  7. 具有强大的Plugin接口,大多是内部插件,使用起来比较灵活
  8. webpack 使用异步 IO 并具有多级缓存。这使得 webpack 很快且在增量编译上更加快

webpack 安装及使用

webpack 可以作为全局的npm模块安装,也可以在当前项目中安装。(首先要有node环境)

1
2
3
4
5
6
7
mkdir  webpack-demo  //创建项目文件夹
cd webpack-demo
// 创建 package.json,这里会问一些问题,直接回车跳过就行
npm init
// 推荐这个安装方式,当然你也安装在全局环境下
// 这种安装方式会将 webpack 放入 devDependencies 依赖中
npm install --save-dev webpack

webpack的使用通常有三种方式:

  • 命令行使用:webpack 其中entry.js是入口文件,bundle.js是打包后的输出文件
  • node.js API使用:

    1
    2
    3
    4
    var webpack = require('webpack');
    webpack({
    //configuration
    }, function(err, stats){});
  • 默认使用当前目录的webpack.config.js作为配置文件。如果要指定另外的配置文件,可以执行:webpack –config webpack.custom.config.js

webpack 常用命令

webpack的使用和browserify有些类似,下面列举几个常用命令:

  1. webpack 最基本的启动webpack命令进行打包
  2. webpack -w 提供watch方法,实时进行打包更新
  3. webpack -p 对打包后的文件进行压缩
  4. webpack -d 提供SourceMaps,方便调试
  5. webpack –colors 输出结果带彩色,比如:会用红色显示耗时较长的步骤
  6. webpack –profile 输出性能数据,可以看到每一步的耗时
  7. webpack –display-modules 默认情况下 node_modules 下的模块会被隐藏,加上这个参数可以显示这些被隐藏的模块
  8. webpack –progress 显示打包进度

前面的四个命令比较基础,使用频率会比较大,后面的命令主要是用来定位打包时间较长的原因,方便改进配置文件,提高打包效率。

webpack 配置文件

项目中静态资源文件较多,使用配置文件进行打包会方便很多。最简单的Webpack配置文件webpack.config.js如下所示:

1
2
3
4
5
6
7
8
9
10
11
module.exports = {
entry:[
'./entry.js',
...
],
output: {
path: __dirname + '/output/',
publicPath: "/output/",
filename: 'bundle.js'
}
};

  1. 其中entry参数定义了打包后的入口文件,数组中的所有文件会打包生成一个filename文件
  2. output参数定义了输出文件的位置及名字,其中参数path是指文件的绝对路径,publicPath是指访问路径,filename是指输出的文件名。

webpack Loader(模块加载器)

在webpack中JavaScript,CSS,LESS,TypeScript,JSX,CoffeeScript,图片等静态文件都是模块,不同模块的加载是通过模块加载器(webpack-loader)来统一管理的。loaders之间是可以串联的,一个加载器的输出可以作为下一个加载器的输入,最终返回到JavaScript上。loader的配置可以写在配置文件中,通过正则表达式的方式对文件进行匹配,具体可参见下面的示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
module: {
rules: [
{
test: /\.js$/, // js文件后缀
loader: 'babel-loader', //使用babel-loader处理
include: [resolve('src'), resolve('test')] //必须处理包含src和test文件夹
},
{
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/, //图片后缀
loader: 'url-loader', //使用url-loader处理
options: { // query是对loader做额外的选项配置
limit: 10000, //图片小于10000字节时以base64的方式引用
name: utils.assetsPath('img/[name].[hash:7].[ext]') //文件名为name.7位hash值.拓展名
}
},
{
test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/, //字体文件
loader: 'url-loader', //使用url-loader处理
options: {
limit: 10000, //字体文件小于1000字节的时候处理方式
name: utils.assetsPath('fonts/[name].[hash:7].[ext]') //文件名为name.7位hash值.拓展名
}
},
{
test: /\.css$/,
use: ['style-loader',
{
loader: 'css-loader',
options: {
modules: true
}
}
]
},

]
}

以上loader可以通过npm安装:

1
$ npm install xxx-loader --save-dev    //xxx是你要安装的loader名字,如处理转义es6的babel:npm install babel-loader --save-dev

Bable

Babel 可以让你使用 ES2015/16/17 写代码而不用顾忌浏览器的问题,Babel 可以帮你转换代码。

处理图片

url-loader 在options选项设置限制你的图片大小,小于限制会将图片转换为 base64格式

处理 CSS 文件

css-loader 和 style-loader 库。前者可以让 CSS 文件也支持import,并且会解析 CSS 文件,后者可以将解析出来的 CSS 通过标签的形式插入到 HTML 中,所以后面依赖前者。

但是将 CSS 代码整合进 JS 文件也是有弊端的,大量的 CSS 代码会造成 JS 文件的大小变大,操作 DOM 也会造成性能上的问题,所以接下来我们将使用 extract-text-webpack-plugin 插件将 CSS 文件打包为一个单独文件

首先安装

1
npm i --save-dev extract-text-webpack-plugin

然后修改 webpack.config.js 代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
const ExtractTextPlugin = require("extract-text-webpack-plugin")

module.exports = {
// ....
module: {
rules: [
{
test: /\.css$/,
// 写法和之前基本一致
loader: ExtractTextPlugin.extract({
// 必须这样写,否则会报错
fallback: 'style-loader',
use: [{
loader: 'css-loader',
options: {
modules: true
}
}]
})
]
}
]
},
// 插件列表
plugins: [
// 输出的文件路径
new ExtractTextPlugin("css/[name].[hash].css")
]
}

常用插件

有时候项目你会发现这个 bundle.js 很大,这肯定是不能接受的,所以要用插件优化项目。

CommonsChunkPlugin

  1. 抽离多个entry的公共模块

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    new webpack.optimize.CommonsChunkPlugin({
    name: "commons",
    // (the commons chunk name)

    filename: "commons.js",
    // (the filename of the commons chunk)

    // minChunks: 3,
    // (Modules must be shared between 3 entries)

    // chunks: ["pageA", "pageB"],
    // (Only use these entries)
    })
  2. 抽离vendor模块(第三方库)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    entry: {
    vendor: ["jquery", "other-lib"],
    app: "./entry"
    }
    new webpack.optimize.CommonsChunkPlugin({
    name: "vendor",

    // filename: "vendor.js"
    // (Give the chunk a different name)

    minChunks: Infinity,
    // (with more entries, this ensures that no other module
    // goes into the vendor chunk)
    })
  3. 抽离子模块中的公共模块到父模块中,会增加首屏加载的时间

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    new webpack.optimize.CommonsChunkPlugin({
    // names: ["app", "subPageA"]
    // (choose the chunks, or omit for all chunks)

    children: true,
    // (select all children of chosen chunks)

    // minChunks: 3,
    // (3 children must share the module before it's moved)
    })
  4. 和3类似,不过不是抽离到父模块,而且额外抽离出一个异步的公共模块

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    new webpack.optimize.CommonsChunkPlugin({
    // names: ["app", "subPageA"]
    // (choose the chunks, or omit for all chunks)

    children: true,
    // (use all children of the chunk)

    async: true,
    // (create an async commons chunk)

    // minChunks: 3,
    // (3 children must share the module before it's separated)
    })

html-webpack-plugin

//如果一个html文件需要引入很多 js 文件,每次都通过script标签手动加入html中很麻烦,
通过此插件 build 操作会发现同时生成了 HTML 文件,并且已经自动引入了 JS 文件

1
2
3
4
5
6
7
8
module.exports = {
//...
plugins: [
new HtmlWebpackPlugin({
template: 'index.html'
})
]
};

extract-text-webpack-plugin

//提取样式到单独的css文件

1
new ExtractTextPlugin("css/[name].[contenthash].css"),

UglifyJsPlugin

//压缩混淆插件 (可以通过webpack.optimize.UglifyJsPlugin使用)

1
2
3
4
5
6
// 压缩 JS 代码
new webpack.optimize.UglifyJsPlugin({
compress: {
warnings: false
}
})

ProvidePlugin

//打包时一次性引入项目中所有依赖库,例如jquery等

1
2
3
4
5
new webpack.ProvidePlugin({
$: 'jquery',
jQuery: 'jquery'
})
//这样$函数会自动添加到当前模块的上下文,无需显示声明

DefinePlugin

//决定打成dev包还是production包会用到它

1
2
3
new webpack.DefinePlugin({
"process.env.NODE_ENV": JSON.stringify("process.env.NODE_ENV")
})

clean-webpack-plugin

在每次生成dist目录前,先删除本地的dist文件,特别是是带有hash值的js文件(每次手动删除太麻烦)

1
new CleanWebpackPlugin(['dist']), //传入数组,指定要删除的目录

Buy me a cup of coffee,thanks!