webpack 插件之Html-Webpack-Plugin

webpack 插件之Html-Webpack-Plugin

1. 为什么我们需要这个插件

先来看一个应用场景。
我们自己打算搭建一个网站,这个网站有很多个页面,我们为每个页面创建一大堆的css样式,js脚本,然后尝试用webpack进行打包。

于是我们在webpack.config.js里这样写了:

const path = require('path');
module.exports = {
    //两个页面相应的js
    entry:[
        "./src/scripts/hello.js",
        "./src/scripts/main.js"
    ],
    output:{
        filename:'main.bundle.js',
        path:path.resolve(__dirname,'dist')
    }
}

两个文件的内容如下:

hello.js

function sayHello (){
    alert('Hello');
}

sayHello();

main.js

function sayMain(){
    alert('Main');
}

sayMain();

但是我们这里的output只有一个path关键字,无疑,它们会被整合到一起。

我们在/dist目录下创建Index.html加载打包后的js

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
</head>
<body>
    <script type="text/javascript" src="./main.bundle.js"></script>
</body>
</html>

打开浏览器看看结果

这里写图片描述

这里写图片描述

这当然对于浏览器对服务器的请求数减小是极好的,但有的时候我们并不想这样做,因为两个模块没有依赖关系,我们想把它分开来

于是我们打算用webpack把它们打成两个包(在实际的应用场景中,这两个互不依赖的模块可能还有更多的依赖模块),修改过后的webpack.config.js如下

const path = require('path');
module.exports = {
    //两个页面相应的js
    entry:{
        helloModule:"./src/scripts/hello.js",
        mainModule:"./src/scripts/main.js"
    },
    output:{
        filename:'main.bundle.js',
        path:path.resolve(__dirname,'dist')
    }
}

接着用webpack进行打包
webpack抛出了一个错误

Conflict: Multiple assets emit to the same filename main.bundle.js

大致的意思多个资源不能用一个文件名

这里我们可以把output的filename改一下

output:{
    filename:'[name].bundle.js',
    path:path.resolve(__dirname,'dist')
}

改成[name],这样当webpack打包的时候,会依次把entry里的模块的名称传入进来。
我们顺利打包,并得到两个bundle

[0] ./src/scripts/hello.bundle.js 60 bytes {1} [built]
[1] ./src/scripts/main.bundle.js 57 bytes {0} [built]

然后我们在index.html里依次引入hello.bundle.js和main.bundle.js

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
</head>
<body>
    <script type="text/javascript" src="main.bundle.js"></script>
    <script type="text/javascript" src="hello.bundle.js"></script>
</body>
</html>

运行起来也是成功的。

但是这种以模块名命名的bundle总会不安全,想象一个场景,团队合作的时候的模块名冲突,好像并没有什么不妥,但是你需要修改的地方就是模块名,还有Index.html里引入的地方,这样也是麻烦。

那有没有方法可以避免这种冲突呢?

2. 使用唯一标识命名

除开用模块名进行命名,webpack还允许我们使用hash 和 chunkhash进行命名。

下面是hash和chunkhash的命名方式

output:{
    filename:'[hash].bundle.js',
    path:path.resolve(__dirname,'dist')
}
output:{
    filename:'[chunkhash].bundle.js',
    path:path.resolve(__dirname,'dist')
}

那么二者有什么不同呢?

对于二者的不同,webpack官网给出了解释:

[hash] is replaced by the hash of the compilation.

译:hash代表的是compilation的hash值。

[chunkhash] is replaced by the hash of the chunk.

译:chunkhash代表的是chunk的hash值。

2.1 什么是hash

在webpack打包的过程中会产生一个compilation对象,这个对象是以文件为对比的,什么意思呢?也就是说,compilation对象代表某个版本的资源对应的编译进程。只要在打包的文件中,有一个文件发生改变,就会废弃当前的compilation对象,而重新创建一个。也就是说,只要有文件进行了变更,每次变更创建的compilation对象都不一样。

而hash是根据compilation对象来的,而不是根据文件来的,每个打包的过程中只会有一个compilation对象,那么hash的值也是唯一的,但是不幸的是,如果是用hash值做为文件名,因为hash在整个打包过程中的值是唯一的,因此会引起冲突。

从上面我们可以知道,当本次打包的源文件中,有一个文件发生了修改,那么整个hash的值就会变,也就是说,每个文件的hash值都是一样的!因此会发生冲突!

output:{
    filename:'[hash].bundle.js',
    path:path.resolve(__dirname,'dist')
}

用webpack打包,还是会抛出一个错误

Conflict: Multiple assets emit to the same filename main.bundle.js

这也证实了hash是针对compilation唯一的事实。

那么有没有是针对文件的唯一标识,来避免这种冲突呢?

答案就是chunkhash

2.2 了解chunkhash

chunkhash就是针对于文件的,每次文件修改只会修改相对于自己的chunkhash的值,不会影响到其他文件。

用chunkhash来做bundle的文件名再合适不过了。

于是我们修改webpack.config.js

output:{
    filename:'[chunkhash].bundle.js',
    path:path.resolve(__dirname,'dist')
}

编译通过~!

现在我们成功的避免了命名冲突,但是现在我们必须面临一个问题,每次文件修改后,chunkhash的值会改变,chunkhash又是和bundle的名字绑定在一起,所以我们就必须在每次文件修改完之后去引用它的地方的名字,对于程序员来说,我们需要去掉这种机械重复的劳动。

于是html-webpack-plugin登场了!!!!!

3. 什么是html-webpack-plugin

html-webpack-plugin是webpack里的插件,我们可以通过在webpack.config.js里增加plugins字段来引入这个插件。
它能帮助你自动生成引入这些bundle的html文件,从而减少你的工作量。而你只需要修改一下webpack.config.js即可以引入它。

首先我们需要做的是在这个项目里安装html-webpack-plugin依赖。

npm install --save-dev html-webpack-plugin

等待安装完毕,在webpack.config.js里将它引入

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
    entry:{
        hello:"./src/scripts/hello.js",
        main:"./src/scripts/main.js"
    },
    output:{
        filename:'[chunkhash].bundle.js',
        path:path.resolve(__dirname,'dist')
    },
    plugins:[
        new HtmlWebpackPlugin()
    ]
}

然后我们用webpack再次打包,可以看到在dist目录下已经产生了打包后的bundle以及一个index.html文件,这个index.html文件是由html-webpack-plugin创建的,而并非我们创建的,打开这个html文件如下

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>Webpack App</title>
  </head>
  <body>
  <script type="text/javascript" src="f213fbbcef9ce12fdbb3.bundle.js"></script><script type="text/javascript" src="8a5558af06376743f39d.bundle.js"></script></body>
</html>

接下来我们就可以在这个index.html里愉快的开发了。

等等!万一我在dist目录下已经有一个正在开发的index.html怎么办,用html-webpack-plugin只会让我的index.html被替换掉,这是我们不希望的结果。如果有版本管理工具还好,还能够很快的恢复,如果没有版本管理岗工具也没有备份这个就很悲剧了。

然而事实上html-webpack-plugin允许接收一些参数来适应更多的应用场景:

title: 用来生成页面的 title 元素
filename: 输出的 HTML 文件名,默认是 index.html, 也可以直接配置带有子目录。
template: 模板文件路径,支持加载器,比如 html!./index.html
inject: true | 'head' | 'body' | false  ,注入所有的资源到特定的 template 或者 templateContent 中,如果设置为 true 或者 body,所有的 javascript 资源将被放置到 body 元素的底部,'head' 将放置到 head 元素中。
favicon: 添加特定的 favicon 路径到输出的 HTML 文件中。
minify: {} | false , 传递 html-minifier 选项给 minify 输出
hash: true | false, 如果为 true, 将添加一个唯一的 webpack 编译 hash 到所有包含的脚本和 CSS 文件,对于解除 cache 很有用。
cache: true | false,如果为 true, 这是默认值,仅仅在文件修改之后才会发布文件。
showErrors: true | false, 如果为 true, 这是默认值,错误信息会写入到 HTML 页面中
chunks: 允许只添加某些块 (比如,仅仅 unit test 块)
chunksSortMode: 允许控制块在添加到页面之前的排序方式,支持的值:'none' | 'default' | {function}-default:'auto'
excludeChunks: 允许跳过某些块,(比如,跳过单元测试的块) 

这里我们使用template设置模板文件。

比如我现在有个正在开发的index.html文件,内容如下:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>Webpack App</title>
  </head>
  <body>
    <h1>hello world</h1>
  </body>
</html>

然后我们在html-webpack-plugin中添加template参数

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
    entry:{
        hello:"./src/scripts/hello.js",
        main:"./src/scripts/main.js"
    },
    output:{
        filename:'[chunkhash].bundle.js',
        path:path.resolve(__dirname,'dist')
    },
    plugins:[
        new HtmlWebpackPlugin({
            template:"./dist/index.html"
        })
    ]
}

这时,我们再次webpack一下,打开index.html


<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Webpack App</title>
</head>
<body>
<h1>hello world</h1>
<script type="text/javascript" src="f213fbbcef9ce12fdbb3.bundle.js"></script><script type="text/javascript" src="8a5558af06376743f39d.bundle.js"></script></body>
</html>

可以看到很顺利的进行引入。

4. 结尾

webpack的插件让webpack极具活力,拥有很强的扩展性


版权声明:本文为HaoDaWang原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。