什么是 webpack
webpack 是德国开发者 Tobias Koppers 开发的模块加载器。
在 webpack 中所有的文件都将被当做模块使用。当 webpack 处理应用程序时,它会递归地构建一个依赖关系图(dependency graph),其中包含应用程序需要的每个模块,然后将所有的这些模块打包成一个或多个 bundle。如图所示:
![webpack能做什么](https://camo.githubusercontent.com/c3f9d2aa9cc56e48520cbbf268c65a275ff79ec50e7c64470c24f1d903bf6ae8/68747470733a2f2f7365676d656e746661756c742e636f6d2f696d672f72656d6f74652f313436303030303031323532313134303f773d3235393826683d31323939)
与 Gulp/Grunt 对比
webpack 与 Gulp/Grunt 是没有对比性的,因为 Gulp/Grunt 是一种能够优化前端的开发流程的工具,而 webpack 是一种模块化的解决方案。不过 Webpack 的优点使得 Webpack 在很多场景下可以替代 Gulp/Grunt 类的工具。
Grunt 和 Gulp 的工作方式是:在一个配置文件中,指明对某些文件进行类似编译,组合,压缩等任务的具体步骤,工具之后可以自动替你完成这些任务。
![Grunt和Gulp工作图](https://camo.githubusercontent.com/b57d9037c746b5c3739c553084db61b19d656b1f2db9bada1b143dd7d66e75fb/68747470733a2f2f7365676d656e746661756c742e636f6d2f696d672f72656d6f74652f313436303030303031323532313134313f773d3132343026683d343436)
webpack 的工作方式是:把你的项目当做一个整体,通过一个给定的主文件(如:index.js),Webpack 将从这个文件开始找到你的项目的所有依赖文件,使用 loaders 处理它们,最后打包为一个(或多个)浏览器可识别的 JavaScript 文件。
![webpack工作图](https://camo.githubusercontent.com/f5a21dcc32737fe04ae3bb96a8b36cde589acd077f9c9fe586612c586c62dd0c/68747470733a2f2f7365676d656e746661756c742e636f6d2f696d672f72656d6f74652f313436303030303031323532313134323f773d37303026683d333036)
- 通过 npm 全局安装 webapck
- 创建项目并初始化 package.json 文件
$ mkdir demo1 && cd demo1
$ npm init
- 在项目中安装 webpack
$ npm install webpack --save-dev
--save-dev 是开发时候依赖的东西,--save 是发布之后还依赖的东西
-
在项目中创建如下文件结构
.
├── index.html // 显示的网页
├── main.js // webpack 入口
└── bundle.js // 通过 webpack 命令生成的文件,无需创建
-
通过命令对项目中依赖的 js 文件进行打包
# webpack 要打包的 js 文件名 打包后生成的js文件名
$ webpack main.js bundle.js
在 webpack 命令后面还可以加入以下参数
--watch
实时打包
--progress
显示打包进度
--display-modules
显示打包的模块
--display-reasons
显示模块包含在输出中的原因
更多参数可以通过命令 webpack --help
查看
Entry
入口
Output
输出
Loaders
Plugins
插件
webpack 中默认的配置文件名称是 webpack.config.js
,因此我们需要在项目中创建如下文件结构:
.
├── index.html // 显示的页面
├── main.js // webpack 入口
├── webpack.config.js // webpack 中默认的配置文件
└── bundle.js // 通过 webpack 命令生成的文件,无需创建
entry
入口
入口起点(entry point)指示 webpack
应该使用哪个模块,来作为构建其内部依赖图的开始。进入入口起点后。 webpack
会找出有哪些模块和库是入口起点(直接和间接)依赖的。
可以在 webpack.config.js
中 配置 entry
属性,来指定一个入口或多个起点入口,代码如下:
moudle.exports = {
entry: './path/file.js',
};
output
输出
output
属性告诉 webpack
在哪里输出它所创建的 bundles
,以及如何命名这些文件。你可以通过在配置指定一个 output
字段,来配置这些过程:
const path = require('path');
moudle.exports = {
entry: './path/file.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'my-webpack.bundle.js',
},
};
其中 output.path
属性用于指定生成文件的路径,output.filename
用于指定生成文件的名称。
Loaders
Loaders
让 webpack
能够去处理那些非 JavaScript
文件(webpack
自身只理解 JavaScript)。loader
可以将所有类型的文件转换为 webpack
能够处理的有效模块,然后可以利用 webpack
的打包能力,对它们进行处理。
本质上,webpack loader
将所有类型的文件,转换为应用程序的依赖图可以直接引用模块。在更高层面上,在 webpack
的配置中 loader
有两个目标:
- 识别应该被对应的
loader
进行转换的那些文件(使用 test
属性)
- 转换这些文件,从而使其能够被添加到依赖图中(并且最终添加到
bundle
中)(use
属性)
在开始下面的代码之前,我们需要安装 style-loader 和 css-loader
$ npm install --save-dev style-loader css-loader
并在项目中创建 style.css
样式文件:
然后在 webpack.config.js
中输入以下代码:
const path = require('path');
module.export = {
entry: './main.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'bundle.js',
},
module: {
rules: [
{
test: /\.css$/,
use: [{ loader: 'style-loader' }, { loader: 'css-loader' }],
},
],
},
};
Plugins
插件
Loaders
被用于转换某些类型的模块,而插件则可以用于执行范围更广的任务。插件的范围包括,从打包优化和压缩,一直到重新定义环境中的变量。
想要使用一个插件,需要 require()
它,然后把它添加到 Plugins
数组中,多数插件可以通过选项自定义。也可以在一个配置文件中因为不同目的而多次使用同一个插件,这时需要通过使用 new
操作符来创建它的实例。
在开始下面的代码之前,我们需要安装 html-webpack-plugin 插件:
$ npm install html-webpack-plugin --save-dev
它可以简化 HTML 文件的创建,为您的 webpack 包提供服务。
然后在 webpack.config.js
中输入以下代码:
const HtmlWebpackPlugin = require('html-webpack-plugin');
const path = require('path');
const config = {
entry: './main.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'bundle.js',
},
module: {
rules: [
{
test: /\.css$/,
use: [{ loader: 'style-loader' }, { loader: 'css-loader' }],
},
],
},
plugins: [new HtmlWebpackPlugin({ template: './index.html' })],
};
module.exports = config;
运行与配置
最后我们可以直接通过 webpack
命令编译打包,如果想要在其命令后加入参数,可以通过配置 package.json
文件中的 scripts
属性:
{
"scripts": {
"build": "webpack --config webpack.config.js --progress --display-modules"
}
}
当然如果你想要更改默认的配置文件名称,可以将 --config
后面的 webpack.config.js
配置文件名改为你自定义的名称。
通过以下命令执行:
多入口设置与 html-webpack-pugin 插件详解(Demo3 Source)
我们可以为 entry
指定多个入口。在开始代码之前,我们需要创建如下目录解构
.
├── index.html // 显示的页面
├── main1.js // webpack 入口1
├── main1.js // webpack 入口2
├── style.css // 样式文件
└── webpack.config.js // webpack 中默认的配置文件
我们在 index.html
文件中输入以下内容:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>demo3</title>
</head>
<body></body>
</html>
我们在 main1.js
文件中输入以下内容:
improt './style.css'
var h1 = document.createElement('h1');
h1.innertHTML = '这是 main1.js 中的内容';
document.body.appendChild(h1);
我们在 main2.js
文件中输入以下内容:
improt './style.css'
var h2 = document.createElement('h2');
h2.innertHTML = '这是 main2.js 中的内容';
document.body.appendChild(h2);
我们在 style.css
文件中输入以下内容:
h1 {
color: red;
}
h2 {
color: blue;
}
我们在 webpack.config.js
文件中输入以下内容:
const HtmlWebpackPlugin = require('html-webpack-plugin');
const path = require('path');
const config = {
entry: {
bundle1: './main1.js',
bundle2: './main2.js',
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].js',
},
module: {
rules: [{ test: /\.css$/, loader: 'style-loader!css-loader' }],
},
pugins: [new HtmlWebpackPlugin({ template: './index.html' })],
};
module.exports = config;
完成上面的代码工作后,运行 webapck
命令,我们打开 dist
文件中的 index.html
。
![index.html 运行结果](https://camo.githubusercontent.com/57b37249b11b990f2cc5bddbd397b24fbc8eae590b2966a143b5e71c0795ddda/68747470733a2f2f7365676d656e746661756c742e636f6d2f696d672f72656d6f74652f313436303030303031323536303030373f773d3133303626683d333336)
运行的结果并不是我们预期的那样展示 h1
的内容在前,h2
内容在后,打开生成后的 index.html
源码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>demo3</title>
</head>
<body>
<script type="text/javascript" src="bundle2.js"></script>
<script type="text/javascript" src="bundle1.js"></script>
</body>
</html>
从源码中便可得知,先引入的 bundle2.js
文件,也就是 main2.js
的内容,后引入的 bundle1.js
文件,也就是 main1.js
的内容。
我们并没有在 index.html
中输入任何引入 JavaScript
文件的代码,那么使用 webpack
打包后生成的文件,是怎么引入 JavaScript
文件的呢。事实上就是通过 html-webpack-plugin 为我们生成的 index.html
。
html-webpack-plugin
中的参数详解
通过 npm
中的介绍,html-webpack-plugin 是一个 webpack
插件,可以简化 HTML
文件的创建,为我们的 webpack
包提供服务,它包含了一个改变每个编译的文件名参数。使用 lodash
模板提供我们自己的模板或者使用自己的 loader
。
我们可以配置以下参数传递给 HtmlWebpackPlugin
:
title
: 用于生成的 HTML
文档的标题。
filename
: 要写入 HTML
的文件。默认为 index.html
。你也可以在这里指定一个子目录(例如:assets / admin.html)。
template
: 引入的模板文件,具体内容可以查看文档。
inject
: true | 'head' | 'body' | false
,指定引入 JavaScript
脚本文件,在生成的HTML
中的位置。默认为 true,指JavaScript
脚本文件在 <body>
元素中引入;head
,指JavaScript
脚本文件在 <head>
元素中引入,body
与 true
值相同;false
指只生成 HTML
文件,不引入任何JavaScript
脚本文件。
favicon
: 生成的 HTML
文件中的图标路径。
minify
: {...} | false
是否对生成的 HTML
文件压缩,默认为 false
,具体配置可查看 html-minifier
hash
: true | false
,如果为 true
,给生成的 js 文件一个独特的 hash 值,该 hash 值是该次 webpack 编译的 hash 值,这对缓存清除非常有用。默认值为 false
。
cache
: true | false
, 如果为 true
则只编译生成更改的内容将文件,默认值为 true
。
showErrors
:true | false
,如果为 true
,则将错误内容添加到 HTML
中,默认值为 true
。
chunks
: 指定引入的 JavaScript
脚本文件(例如:[ 'bundle1', 'bundle2' ])。
chunksSortMode
: 'none' | 'auto' | 'dependency' |'manual' | {function} - default: 'auto'
,对引入的 chunks
进行排序,具体可以查看该文档。
excludeChunks
: 排除掉指定的 JavaScript
脚本文件(例如:[ 'bundle1', 'bundle2' ])。
xhtml
: true | false
,默认值是 false
,如果为 true
,则以兼容 xhtml
的模式引用文件。
现在我们知道了 html-webpack-plugin 中的参数,下面我们就来修改 webpack.config.js
中的内容:
const HtmlWebpackPlugin = require('html-webpack-plugin');
const path = require('path');
const config = {
entry: {
bundle1: path.resolve(__dirname, 'main1.js'),
bundle2: path.resolve(__dirname, 'main2.js'),
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].js',
},
module: {
rules: [{ test: /\.css$/, loader: 'style-loader!css-loader' }],
},
plugins: [
new HtmlWebpackPlugin({
title: '多文件引入', // 生成 html 的标题
filename: 'index.html', // 生成 html 文件的名称
template: path.resolve(__dirname, 'index.html'), // 根据自己的指定的模板文件来生成特定的 html 文件
// inject: true, // 注入选项 有四个值 ture: 默认值,script标签位于html文件的 body 底部, body: 同 true, head: script标签位于html文件的 head 底部,false:不注入script标签
favicon: path.resolve(__dirname, 'favicon.ico'), // 生成的 html 文件设置 favicon
minify: {
caseSensitive: false, //是否大小写敏感
collapseBooleanAttributes: true, //是否简写boolean格式的属性如:disabled="disabled" 简写为disabled
collapseWhitespace: true, //是否去除空格
},
hash: true, // hash选项的作用是 给生成的 js 文件一个独特的 hash 值,该 hash 值是该次 webpack 编译的 hash 值。默认值为 false
cache: true, // 默认值是 true。表示只有在内容变化时才生成一个新的文件
showErrors: true, // showErrors 的作用是,如果 webpack 编译出现错误,webpack会将错误信息包裹在一个 pre 标签内,属性的默认值为 true
chunks: ['bundle1', 'bundle2'], // 指定引入的 js 文件
//excludeChunks:[ 'bundle1' ], // 排除掉某些 js 文件
/**
* script 标签的引用顺序
* 'dependency' 按照不同文件的依赖关系来排序
* 'auto' 默认值,插件的内置的排序方式
* 'none'
* 'manual'
* funciton 自定义排序,与JS中自定义数组的sort回调一个含义, 具体可以看 https://github.com/jantimon/html-webpack-plugin/issues/481
*/
chunksSortMode: function(chunk1, chunk2) {
var orders = ['bundle1', 'bundle2'];
var order1 = orders.indexOf(chunk1.names[0]);
var order2 = orders.indexOf(chunk2.names[0]);
return order1 - order2;
},
xhtml: false, // 一个布尔值,默认值是 false ,如果为 true ,则以兼容 xhtml 的模式引用文件
}),
],
};
module.exports = config;
完成上面的代码工作后,运行 webapck
命令,我们打开 dist 文件中的 index.html。
![index.html 运行结果](https://camo.githubusercontent.com/5f1bdc8baa4397ec4cc1ebd3b123baa87c87de0f809a2ec7f8efb6c9a19d00af/68747470733a2f2f7365676d656e746661756c742e636f6d2f696d672f72656d6f74652f313436303030303031323536303030383f773d38393626683d323736)
Nice!与我们的预期效果显示一致。在对 html-webpack-plugin 的介绍中,提到了 lodash
模板, 那么该怎么用呢?我们再次修改 webpack.config.js
中的内容,为 HtmlWebpackPlugin
传入 Date
参数:
const HtmlWebpackPlugin = require('html-webpack-plugin');
const path = require('path');
const config = {
entry: {
bundle1: path.resolve(__dirname, 'main1.js'),
bundle2: path.resolve(__dirname, 'main2.js'),
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].js',
},
module: {
rules: [{ test: /\.css$/, loader: 'style-loader!css-loader' }],
},
plugins: [
new HtmlWebpackPlugin({
date: new Date(),
title: '多文件引入', // 生成 html 的标题
filename: 'index.html', // 生成 html 文件的名称
template: path.resolve(__dirname, 'index.html'), // 根据自己的指定的模板文件来生成特定的 html 文件
// inject: true, // 注入选项 有四个值 ture: 默认值,script标签位于html文件的 body 底部, body: 同 true, head: script标签位于html文件的 head 底部,false:不注入script标签
favicon: path.resolve(__dirname, 'favicon.ico'), // 生成的 html 文件设置 favicon
minify: {
caseSensitive: false, //是否大小写敏感
collapseBooleanAttributes: true, //是否简写boolean格式的属性如:disabled="disabled" 简写为disabled
collapseWhitespace: true, //是否去除空格
},
hash: true, // hash选项的作用是 给生成的 js 文件一个独特的 hash 值,该 hash 值是该次 webpack 编译的 hash 值。默认值为 false
cache: true, // 默认值是 true。表示只有在内容变化时才生成一个新的文件
showErrors: true, // showErrors 的作用是,如果 webpack 编译出现错误,webpack会将错误信息包裹在一个 pre 标签内,属性的默认值为 true
chunks: ['bundle1', 'bundle2'], // 指定引入的 js 文件
//excludeChunks:[ 'bundle1' ], // 排除掉某些 js 文件
/**
* script 标签的引用顺序
* 'dependency' 按照不同文件的依赖关系来排序
* 'auto' 默认值,插件的内置的排序方式
* 'none'
* 'manual'
* funciton 自定义排序,与JS中自定义数组的sort回调一个含义, 具体可以看 https://github.com/jantimon/html-webpack-plugin/issues/481
*/
chunksSortMode: function(chunk1, chunk2) {
var orders = ['bundle1', 'bundle2'];
var order1 = orders.indexOf(chunk1.names[0]);
var order2 = orders.indexOf(chunk2.names[0]);
return order1 - order2;
},
xhtml: false, // 一个布尔值,默认值是 false ,如果为 true ,则以兼容 xhtml 的模式引用文件
}),
],
};
module.exports = config;
更改 index.html
中的内容,lodash
模板默认支持的是 ejs
模板的语法:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>demo3</title>
</head>
<body>
<%= htmlWebpackPlugin.options.date %>
</body>
</html>
完成上面的代码工作后,运行 webapck
命令,我们打开 dist 文件中的 index.html。
![index.html 运行结果](https://camo.githubusercontent.com/bf1b32d798a5e71cd5029b38ca47952268a19d3826c85f5727a5616d29df883f/68747470733a2f2f7365676d656e746661756c742e636f6d2f696d672f72656d6f74652f313436303030303031323536303030393f773d39313626683d333234)
通过运行结果,我们可以发现在顶部输出了当前时间,也就是 HtmlWebpackPlugin
传入的参数,实际上 HtmlWebpackPlugin
中的参数都可以通过 htmlWebpackPlugin.options.参数名称
输出,我就不一一列举。
Babel
Babel 是一个工具链,主要用于在旧的浏览器或环境中将 ECMAScript 2015+ 代码转换为向后兼容版本的 JavaScript 代码。
安装依赖包
npm i -D babel-loader @babel/core @babel/preset-env
babel-loader
:用于 babel 在 webapck 中的加载模块。
@babel/core
:babel 编译工具;
@babel/preset-env
:babel 生成指定支持浏览器版本的编译工具。
babel 支持两种配置,一种是在项目根目录下创建 .babelrc
配置文件,另一种是通过 webpack loader options 的方式配置
{
"presets": [
[
"@babel/preset-env",
{
// 用于指定浏览器版本号
"targets": {
"browsers": "last 2 version"
}
}
]
]
}
- webpack loader options 配置
{
"module": {
"rules": [
{
"test": /\.js$/,
"exclude": /(node_modules)/,
"loader": "babel-loader",
"options": {
"presets": [
[
"@babel/preset-env",
{
// 用于指定浏览器版本号
"targets": {
"browsers": "last 2 version"
}
}
]
]
}
}
]
}
}
Babel 默认只转换语法,而不转换新的 API,如 Set、Promise、Map 等或是 ES6 对 Array、String 等扩展,如果需要使用新的 API 还需要使用对相应的 转换插件 或 polyfill
npm i --save @babel/polyfill
只需在入口头部引入即可
import '@babel/polyfill';
// 或
require('@babel/polyfill');
上面的使用方法,会导致打包后的文件过大,由于是在入口文件直接引入 polyfill,从而将会导入 polyfill 整个包,增加了无用代码。
@babel/preset-env
中 useBuiltIns: 'usage'
按需引入,就是用于解决上面的问题。
{
"presets": [
[
"@babel/preset-env",
{
// 用于指定浏览器版本号
"targets": {
"browsers": "last 2 version"
},
"useBuiltIns": "usage"
}
]
]
}
@babel/polyfill 是通过改写全局 prototype 的方式对新的 API 支持,比较适合单独运行的项目。
npm i @babel/runtime --save
npm i @babel/plugin-transform-runtime --save-dev
{
"presets": [
[
"@babel/preset-env",
{
// 用于指定浏览器版本号
"targets": {
"browsers": "last 2 version"
}
}
]
],
"plugins": ["@babel/plugin-transform-runtime"]
}
@babel/runtime 的 polyfill 对象是临时构造并 import/require 的,因此并不是真正的全局引用,由于不是全局引用,对于实例化对象的方法,并不能生效。比较适合编写 第三方类库。