什么是 webpack,为什么要使用 webpack
什么是 webpack
官网给出的概念是:
本质上,webpack 是一个现代 JavaScript 应用程序的静态模块打包器(module bundler)。当 webpack 处理应用程序时,它会递归地构建一个依赖关系图(dependency graph),其中包含应用程序需要的每个模块,然后将所有这些模块打包成一个或多个 bundle。 根据官网最直观最出名的那个图我们可以知道,webpack 可以打包/脚本/图片/样式/表 从图中我们可以看出左边有依赖关系的模块(MODULES WITH DEPENDENCIES)通过 webpack 打包成了各种静态资源(STATIC ASSETS)
为什么要使用 webpack
通过上面的概念,你是不是已经大概知道了 webpack 是干什么的,那么问题来了,为什么要使用 webpack 呢? 这就说来话长了,那就长话短说,emmmm
为什么使用 webpack,这应该是和前端的发展有关系的,我认为,webpack 是前端发展到一定阶段的必然产物(貌似是一句废话)。 因为计算机网络的飞速发展,导致 web 前端也在迅猛发展。最初的实践方案已经不能满足我们的需求,于是,越来越多的新技术新思想新框架孕育而生,比如:
前端模块化
随着前端项目的复杂度越来越高,相互之前的依赖越来越多,以及为了更好的复用代码,前端也需要用模块化的思想来组织代码。
首先我们要明白模块化解决了前端的哪些痛点:
- 命名冲突
- 文件依赖(js 加载顺序)
- 代码复用
我们这里说的模块和 Java 的 package
的概念是类似的。逻辑上相关的代码放在一个包中,每一个包都是相互独立的,不用担心命名冲突的问题,如果其他人想要用这部分功能,直接 import
导入包就好
所以前端代码模块化的实现,会帮我们解决命名冲突和代码复用的问题,那么文件依赖要怎么处理呢?这就用到了我们的 webpack,稍后再做介绍。
所以有了模块,我们就可以方便的复用他人的代码,那么问题来了,无规矩不成方圆,我们在使用他人代码的时候肯定是要遵循某种规范,所以就出现了 CommonJS、AMD 和 终极模块化方案 —— ES6 模块,这些都是前端模块化的规范。
我们来简单了解一下:
CommonJS
node.js 采用的就是 CommonJS 规范,使用 require
的方法同步加载依赖,一个文件就是一个模块,导入导出格式如下:
// 导入
const moduleA = require('./moduleA');
// 导出
module.exports = moduleA.someFunc;
缺点是加载的模块是同步的,只有加载完才能执行后面的操作。因为 node.js 的模块文件一般存在于本地硬盘,所以一般不会出现这个问题,但是在浏览器环境该规范就不那么适用了。
AMD
原因如上,因为 CommonJS 不适用于浏览器环境,所以出现了 AMD 规范。该规范异步加载依赖,可以再声明的时候指定需要加载的依赖,并且需要当做参数传入,对于依赖的模块提前执行,依赖前置。
写法如下:
define("module", ["dep1", "dep2"], function(d1, d2) {
return someExportedValue;
});
require(["module", "../file"], function(module, file) { /* ... */ });
ES6 模块化
ES6 直接在语言层面上实现了模块化。我们现在用的最多的就是 ES6 模块化的实践方式。
写法如下:
// 导入
import { readFile } from 'fs';
import React from 'react';
// 导出
export function hello() {};
export default {
// ...
};
样式模块化
现在越来越多人也开始使用模块化的思想写样式。比如现在大部分的 CSS 预编译器都支持 @import
的写法。将一些公用样式放在一个文件中,在其他文件中导入。
//全局安装
npm install -g webpack
//安装到你的项目目录
npm install --save-dev webpack
npm init
初始化之后我们还要创建几个文件来存放我们的项目文件。
创建一个 app
文件夹来存放我们打包之前的源文件
创建一个 public
文件夹来存放一个入口文件 index.hrml
和通过 webpack 打包之后浏览器可直接运行的 js
文件
比如我们在 app
文件夹下创建一个 Greeter.js
文件,里面有一个方法可以再页面显示文字信息 Hi there and greetings!
module.exports = function() {
var greet = document.createElement('div');
greet.textContent = "Hi there and greetings!";
return greet;
};
然后我们创建一个 main.js
来引入 Greeter.js
这个文件,
浏览器的入口 index.html
文件内容如下(其中 bundle.js
是打包之后的文件):
<html lang="en">
<head>
<meta charset="utf-8">
<title>Webpack Sample Project</title>
</head>
<body>
<div id='root'>
</div>
<script src="bundle.js"></script>
</body>
</html>
此时的文件路径如下
.
├── app
│ ├── Greeter.js
│ └── main.js
├── package.json
├── public
│ ├── bundle.js
│ └── index.html
安装 webpack
初始化项目之后,要安装 webpack
npm install --save-dev webpack
在安装完 webpack 之后,可以再 package.json
文件中看到增加了 webpack 的依赖。
配置 webpack
安装完 webpack 之后,我们就要配置 webpack 了,首先创建配置文件 webpack.config.js
文件内容如下:
module.exports = {
entry: __dirname + "/app/main.js",//已多次提及的唯一入口文件
output: {
path: __dirname + "/public",//打包后的文件存放的地方
filename: "bundle.js"//打包后输出文件的文件名
}
}
然后我们在该项目的终端输入
webpack
就可以看到如下信息:
Hash: 4e6a6b5eb88a83b29e02
Version: webpack 4.12.0
Time: 551ms
Built at: 2018-06-24 14:53:39
Asset Size Chunks Chunk Names
bundle.js 6.82 KiB 0 [emitted] main
[3] ./node_modules/css-loader!./app/main.css 190 bytes {0} [built]
[4] ./app/main.css 1.04 KiB {0} [built]
[5] ./app/Greeter.js 143 bytes {0} [built]
[6] ./app/main.js 119 bytes {0} [built]
+ 3 hidden modules
WARNING in configuration
The 'mode' option has not been set, webpack will fallback to 'production' for this value. Set 'mode' option to 'development' or 'production' to enable defaults for each environment.
You can also set it to 'none' to disable any default behavior. Learn more: https://webpack.js.org/concepts/mode/
看到这样的信息,那么恭喜你,你的第一个 webpack 项目就完成了!
打开 public
文件夹下面的 index.html
,你就可以再浏览器上看到如下的效果。
Loader
loader 用于对模块的源代码进行转换。loader 可以使你在 import 或"加载"模块时预处理文件。因此,loader 类似于其他构建工具中“任务(task)”,并提供了处理前端构建步骤的强大方法。loader 可以将文件从不同的语言(如 TypeScript)转换为 JavaScript,或将内联图像转换为 data URL。loader 甚至允许你直接在 JavaScript 模块中 import CSS文件!
因为 webpack 本身只能处理 JavaScript,如果要处理其他类型的文件,就需要使用 loader
进行转换,loader
本身就是一个函数,接受源文件为参数,返回转换的结果。
举个 —— css-loader
例如,我们想在刚刚的页面增加样式,使文字居中显示,那么我在 app
文件夹下面新建一个 main.css
文件。在 webpack 中,所有的文件都是模块,所以要使用这个 css
文件,就必须要先引入。
引入 css
文件
所以我就在 app
文件夹下面的 main.js
中引入该 css 文件
const greeter = require('./Greeter.js');
require ('./main.css')
document.querySelector("#root").appendChild(greeter());
重新打包
然后我重新打包一遍,蓝后,发现居然报错了!
Hash: 179c18498fac6de89a96
Version: webpack 4.12.0
Time: 533ms
Built at: 2018-06-24 15:00:24
1 asset
[0] ./app/Greeter.js 143 bytes {0} [built]
[1] ./app/main.js 119 bytes {0} [built]
WARNING in configuration
The 'mode' option has not been set, webpack will fallback to 'production' for this value. Set 'mode' option to 'development' or 'production' to enable defaults for each environment.
You can also set it to 'none' to disable any default behavior. Learn more: https://webpack.js.org/concepts/mode/
ERROR in ./app/main.js
Module not found: Error: Can't resolve 'style-loader' in '/Users/cherry/Workspace/webpack-demo'
@ ./app/main.js 2:0-22
根据报错信息,我们很明显能发现是提示我们项目缺少 style-loader
,这是因为 webpack 原生只支持解析 js 文件,要支持非 js 类型的文件,就需要使用 loader
安装 loader
所以我们要安装 style-loader
和 css-loader
npm i -D style-loader css-loader
修改配置文件
然后修改 webpack 的配置文件 webpack.config.js
module.exports = {
entry: __dirname + "/app/main.js",//已多次提及的唯一入口文件
output: {
path: __dirname + "/public",//打包后的文件存放的地方
filename: "bundle.js"//打包后输出文件的文件名
},
module: {
rules: [
{
test: /\.css$/,
use: ['style-loader', 'css-loader']
}
]
}
}
module.rules
数组,该数组是一些配置规则,告诉 webpack 符合
test
的文件需要使用
use
后面的
loader
处理。所以该规则就是对所有
.css
结尾的文件使用
style-loader
、
css-loader
进行处理。
loader 特性
我们来看一下 loader
有哪些特性:
- loader 支持链式传递。能够对资源使用流水线(pipeline)。一组链式的 loader 将按照相反的顺序执行。loader 链中的第一个 loader 返回值给下一个 loader。在最后一个 loader,返回 webpack 所预期的 JavaScript。
- loader 可以是同步的,也可以是异步的。
- loader 运行在 Node.js 中,并且能够执行任何可能的操作。
- loader 接收查询参数。用于对 loader 传递配置。
- loader 也能够使用 options 对象进行配置。
- 除了使用 package.json 常见的 main 属性,还可以将普通的 npm 模块导出为 loader,做法是在 package.json 里定义一个 loader 字段。
- 插件(plugin)可以为 loader 带来更多特性。
- loader 能够产生额外的任意文件。
使用 webpack 的三种姿势
webpack 中使用 loader 有三种姿势
通过 CLI
命令行中运行
webpack --module-bind jade --module-bind 'css=style!css'
jade,style,css后面可省略-loader,他们分别对.jade使用jade-loader,对.css使用style-loader和css-loader
通过require
可以直接在源码中指定使用什么 loader 去处理文件。
require('style-loader!css-loader?minimize!./main.css')
这样就是对 ./main.css
文件先使用 css-loader
再使用 style-loader
进行转换
使用配置文件 webpack.config.js
最常用的方式就是使用本文所使用的配置文件的方式
常见的 loader
gzip-loader
| 可以加载 gzip 压缩之后的资源
html-loader
| 将 html 输出为字符串,也可以根据配置进行压缩
imports-loader
|
imports-loader
允许使用依赖于特定全局变量的模块,这对于依赖于像
$
这样的全局变量的第三方模块非常有用
jshint-loader
| 为加载的模块使用 jshint
json-loader
| 由于 webpack >= v2.0.0 默认支持导入 JSON 文件。如果你使用自定义文件扩展名,你可能仍然需要使用此 loader
json5-loader
| 将 json5 文件解析成 js 对象
less-loader
| 将 less 转化为 css
null-loader
| 返回一个空模块
postcss-loader
| 将 postcss 转化为 css
raw-loader
| 加载文件原始内容(utf-8格式)
sass-loader
| 将 SASS/SCSS 转换为 css
source-map-loader
| 从现有源文件(源代码源URL)中提取源映射,方便调试
style-loader
| 将 CSS 放在
<style>
标签中注入到 DOM 中
script-loader
| 在全局上下文中执行一次 JavaScript 文件(如在 script 标签),不需要解析
svg-inline-loader
| 将 SVG 作为模块嵌入
url-loader
| 将文件加载为 Base64 编码的 URL
row 2 col 1 | row 2 col 2
Plugin
插件(plugin)是 webpack 的支柱功能。webpack 自身也是构建于,你在 webpack 配置中用到的相同的插件系统之上! 插件目的在于解决 loader 无法实现的其他事
Plugin 是用来扩展 Webpack 功能的,通过在构建流程里注入钩子实现,它给 Webpack 带来了很大的灵活性。
通过plugin(插件)webpack可以实 loader 所不能完成的复杂功能,使用 plugin 丰富的自定义 API 以及生命周期事件,可以控制 webpack 打包流程的每个环节,实现对 webpack 的自定义功能扩展。
举个 —— ExtractTextPlugin
webpack4 已经不支持用extract-text-webpack-plugin来优化 css, 需要改成optimize-css-assets-webpack-plugin和mini-css-extract-plugin
在刚刚的例子中,我们查看打包之后的 index.html
文件可以看到我们刚刚写的 css 代码放在了 head
中的 style
标签中,这是 style-loader
帮我们处理的
但是,如果你希望打包之后 css 在单独的文件中,那么你就需要 ExtractTextPlugin
这个 plugin 了。
安装 ExtractTextPlugin
npm i -D ExtractTextPlugin
修改配置文件
我们需要修改配置文件:
const path = require('path')
const ExtractTextPlugin = require('extract-text-webpack-plugin')
module.exports = {
entry: __dirname + "/app/main.js",//已多次提及的唯一入口文件
output: {
path: __dirname + "/public",//打包后的文件存放的地方
filename: "bundle.js",//打包后输出文件的文件名
},
module: {
rules: [
{
test: /\.css$/,
// 转换 .css 需要的 loader
loaders: ExtractTextPlugin.extract({
use: ['css-loader'],
})
}
]
},
plugins: [
new ExtractTextPlugin({
filename: '[name]-[contenthash:8].css'
})
]
}
然后我们再重新打包,就可以发现在 public
文件夹下面多了一个 main-493a2c3c.css
文件,下面我们要在 index.html
中自动引入这个 css 文件
html-webpack-plugin
html-webpack-plugin
可以根据你设置的模板,在每次运行后生成对应的模板文件,同时所依赖的 CSS/JS 也都会被引入,如果 CSS/JS 中含有 hash 值,则 html-webpack-plugin
生成的模板文件也会引入正确版本的 CSS/JS 文件。
安装 html-webpack-plugin
安装方式我们已经很熟悉了
npm i -D html-webpack-plugin
修改配置文件
const path = require('path')
const ExtractTextPlugin = require('extract-text-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: __dirname + "/app/main.js",//已多次提及的唯一入口文件
output: {
path: __dirname + "/public",//打包后的文件存放的地方
filename: "bundle.js",//打包后输出文件的文件名
},
module: {
rules: [
{
test: /\.css$/,
loaders: ExtractTextPlugin.extract({
use: ['css-loader'],
})
}
]
},
plugins: [
new ExtractTextPlugin({
filename: '[name]-[contenthash:8].css'
}),
new HtmlWebpackPlugin(),
]
}
蓝后我们删除之前我们在
punlic
文件夹下面
index.html
的内容,在打包一次,就会生成一个
html
模板
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Webpack App</title>
<link href="main-493a2c3c.css" rel="stylesheet"></head>
<body>
<script type="text/javascript" src="bundle.js"></script></body>
</html>
在 new HtmlWebpackPlugin
的时候,我们可以进行一系列的配置
new HtmlWebpackPlugin({
// 生成的HTML模板的title,如果模板中有设置title的名字,则会忽略这里的设置
title: "This is the webpack config",
// 生成的模板文件的名字
filename: "/index.html",
// 模板来源文件
template: "index.html",
// 引入模块的注入位置;取值有true/false/body/head
// true 默认值,script标签位于html文件的 body 底部
// body script标签位于html文件的 body 底部
// head script标签位于html文件的 head中
// false 不插入生成的js文件,这个几乎不会用到的
inject: "body",
// 指定页面图标;
favicon: "",
// 是html-webpack-plugin中集成的 html-minifier ,生成模板文件压缩配置
minify: {
caseSensitive: false, //是否大小写敏感
collapseBooleanAttributes: true, // 省略布尔属性的值
collapseWhitespace: true //删除空格
},
// 是否生成hash添加在引入文件地址的末尾,类似于我们常用的时间戳,避免缓存带来的麻烦
hash: true,
// 是否需要缓存,如果填写true,则文件只有在改变时才会重新生成
cache: true,
// 是否将错误信息写在页面里,默认true,出现错误信息则会包裹在一个pre标签内添加到页面上
showErrors: true,
// 引入的模块,这里指定的是entry中设置多个js时,在这里指定引入的js,如果不设置则默认全部引入
chunks: "",
// 引入模块的排序方式
// 默认四个选项: none auto dependency {function}
// 'dependency' 文件的依赖关系
// 'auto' 默认值,插件的内置的排序方式
// 'none' 无序
// {function} 自定义
chunksSortMode: "auto",
// 排除的模块
excludeChunks: "",
// 生成的模板文档中标签是否自动关闭,针对xhtml的语法,会要求标签都关闭,默认false
xhtml: false
}),
常见的 plugin
摘自:http://www.css88.com/doc/webpack/plugins/