# webpack
说起 Webpack,大家都知道这是一个**模块化构建(打包)工具**,那么究竟什么是模块化呢?
## 模块化
> 模块化是指解决一个复杂问题时自顶向下逐层把系统划分成若干模块的过程,有多种属性,分别反映其内部特性。(百度百科)
模块化被越来越多的应用到我们的日常生活中,在我的印象中,小时候家电(比如收音机、电视)坏了都是拿到店里去找老师傅维修,现在家电都是模块化的,检测下哪里坏了直接换个新模块就可以了,由此可见,模块化不仅仅是个前端概念(相反,前端模块化也是这些年刚刚被得到重视的),我们生活场景中大量的充斥着模块化,让我们的生活效率更高。
前端模块化一般指得是 JavaScript 的模块,最常见的是 Nodejs 的 NPM 包,每个模块可能是最小甚至是最优的代码组合,也可能是解决某些问题有很多特定模块组成的大模块。如果没有模块化,可能大家编写代码当中遇见最多的就是复制(copy),当我们需要某个功能的代码时,自己由之前在哪个项目写过,那么我们就会 copy 过来,copy 多了,自然代码的可维护性就会下降。
我们所接触的模块化开发有`CommonJS`和`ES6 Module`规范。此外还有`AMD`、`CMD`、`UMD`(兼容`AMD`和`CMD`)
**说起模块化又不得不提到一个词叫做组件化**
## 组件化和模块化的区别
> Tips:大家可能也经常听到组件化这个名词,模块化一般指的是可以被抽象封装的最小/最优代码集合,模块化解决的是功能耦合问题;组件化则更像是模块化进一步封装,根据业务特点或者不同的场景封装出具有一定功能特性的独立整体;另外,前端提到组件化更多的是具有模板、样式和 js 交互的 UI 组件。
## 工程化
当我们开发的 Web 应用越来越复杂的时候,会发现我们面临的问题会逐渐增多:
1. 模块多了,依赖管理怎么做;
2. 页面复杂度提升之后,多页面、多系统、多状态怎么办;
3. 团队扩大之后,团队合作怎么做;
4. 怎么解决多人研发中的性能、代码风格等问题;
5. 权衡研发效率和产品迭代的问题。
这些问题就是软件工程需要解决的问题。工程化的问题需要运用工程化工具来解决,得益于 Nodejs 的发展,前端这些年在工程化上取得了不俗的成绩。前端工程化早期,是以 Grunt、Gulp 等构建工具为主的阶段,这个阶段解决的是重复任务的问题,它们将某些功能拆解成固定步骤的任务,然后编写工具来解决,比如:图片压缩、地址添加 hash、替换等,都是固定套路的**重复你性工作**。
而现阶段的 Webpack 则更像是从一套解决 JavaScript 模块化依赖打包开始,利用强大的插件机制,逐渐解决前端资源依赖管理问题,依附社区力量逐渐进化成一套前端工程化解决方案。
## 什么是 webpack
> 本质上,Webpack 是一个现代 JavaScript 应用程序的静态模块打包器(static module bundler)。在 Webpack 处理应用程序时,它会在内部创建一个依赖图(dependency graph),用于映射到项目需要的每个模块,然后将所有这些依赖生成到一个或多个 bundle。
像 Grunt、Gulp 这类构建工具,打包的思路是:`遍历源文件`→`匹配规则`→`打包`,这个过程中**做不到按需加载**,即对于打包起来的资源,到底页面用不用,打包过程中是不关心的。
webpack 跟其他构建工具本质上不同之处在于:**webpack 是从入口文件开始,经过模块依赖加载、分析和打包三个流程完成项目的构建**。在加载、分析和打包的三个过程中,可以针对性的做一些解决方案,比如`code split`(拆分公共代码等)。
当然,Webpack 还可以轻松的解决传统构建工具解决的问题:
- 模块化打包,一切皆模块,JS 是模块,CSS 等也是模块;
- 语法糖转换:比如 ES6 转 ES5、TypeScript;
- 预处理器编译:比如 Less、Sass 等;
- 项目优化:比如压缩、CDN;
- 解决方案封装:通过强大的 Loader 和插件机制,可以完成解决方案的封装;
# webpack-cli体验零配置打包
> 温故而知新,可以为师矣。
本节默认认为已经安装过Node和Npm.开发IDE使用的是[WebStorm 2018 3.6](http://www.jetbrains.com/webstorm/download/previous.html)
打造一个可以转换ES6为ES5 以及 支持图片(png,jpg,gif,webp)、less、sass/scss的webpack配置项,支持修改项目文件自动更新的功能。
## 全局安装
`npm install webpack webpackcli -g`
## 初始化项目
`npm init -y`
```
{
"name": "demo1",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
}
}
```
这里面包含了`dependencies`为项目的依赖在通过`npm install XXX -S`或者`npm install yyy --save`会将xxx及版本号显示在这个位置
`devdependencies`为开发环境依赖通过`npm install yyy -D`或者`npm install yyy --dev-save`
## 创建src/main.js和src/index.html
需求描述,通过jquery实现 ul>li无序列表中li的隔行换色功能。
**安装jquery 项目中需要使用**
`npm install jquery -S`
**实现隔行换色**
`src/main.js`
```js
import $ from "jquery"
$("ul li:even").css({background:'red'})
$("ul li:odd").css({background:'pink'})
```
## 执行命令
`webpack src/main.js -o dist/bundle.js`
**目录结构**
```
dist
| ├─bundle.js
src
| ├─main.js
| ├─index.html
package.json
```
## 修改index.html引入打包后的js
```
……
<body>
<ul>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
</ul>
<script src="../dist/bundle.js"></script>
……
```
## 章结
发现,我们要手动把js代码引入,并且要手动打开浏览器去查看网页。等我们后面学到`webpack-dev-server`中的`html-webpack-plugin`插件去解决。
## 项目源码
`git clone https://github.com/highh5/webpack.git -b lesson-01`
# webpack结合npm scripts
我们后面会不断的修改项目文件,每次修改完要重新执行`webpack src/main.js -o dist/bundle.js`命令。感觉 命令比较长,比较繁琐。
## 安装本地项目依赖
`npm install webpack webpack-cli -D`
```json
{
"name": "demo1",
"version": "1.0.0",
"dependencies": {
"jquery": "^3.4.1"
},
"devDependencies": {
"webpack": "^4.35.0",
"webpack-cli": "^3.3.5"
}
}
```
## npm scripts
我们可以结合`npm scripts`来方便我们书写命令。修改`package.json`文件如下:
```
{
"name": "demo1",
"version": "1.0.0",
"scripts": {
"start": "webpack src/main.js -o dist/bundle.js"
},
"dependencies": {
"jquery": "^3.4.1"
},
"devDependencies": {
"webpack": "^4.35.0",
"webpack-cli": "^3.3.5"
}
}
```
## 启动命令
`npm start 或者npm run start`
## 解释
`npm run xxx`
这其中的`xxx`就是我们在scripts中定义的key值。
这样一句命令代表着我们将xxx的值执行。即执行 `webpack src/main.js -o dist/bundle.js`。
这里面的`webpack`命令其实是利用的我们项目的开发环境的的`webpack-cli`模块。如果本地项目未完装`webpack`、`wepback-cli`则去找全局命令。
## 注意
> <font color="red">start这个命令可以省去run其它都不可以</font>
## 问题
上面命令执行后是可以进行打包,不过会产生一个警告提示。按照提示添加mode属性要么为production(生产环境)或者development 开发环境 。我们在这里了解一下开发环境。所以将mode属性的值设置为development;
```
> demo1@1.0.0 start C:\Users\zhaoy\Desktop\react\01\demo1
> webpack src/main.js -o dist/bundle.js
Hash: 4e6df5b73ef9caaba844
Version: webpack 4.35.0
Time: 329ms
Built at: 2019-06-24 7:36:41 PM
Asset Size Chunks Chunk Names
bundle.js 87.6 KiB 0 [emitted] main
Entrypoint main = bundle.js
[1] ./src/main.js 106 bytes {0} [built]
+ 1 hidden module
WARNING in configuration
The 'mode' option has not been set, webpack will fallback to 'production' for this value. Set 'mode' option t
o '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/configurati
on/mode/
```
**修改npm scripts**
```
webpack src/main.js -o dist/bundle.js --mode development
```
```json
{
"name": "demo1",
"version": "1.0.0",
"scripts": {
"start": "webpack src/main.js -o dist/bundle.js --mode development"
},
"dependencies": {
"jquery": "^3.4.1"
},
"devDependencies": {
"webpack": "^4.35.0",
"webpack-cli": "^3.3.5"
}
}
```
## 项目源码
`git clone https://github.com/highh5/webpack.git -b lesson-02`
# webpack配置文件
`webpack`的配置项除了`--output`和`--mode`之外还有非常多的选项。官方也提供了一个更简便的方式来进行操作。
## 配置文件
在项目根目录上新建`webpack.config.js`
```
const path = require('path');
module.exports = {
entry:path.join(__dirname,'./src/main.js'),
output:{
path:path.join(__dirname,'./dist'),
filename:'bundle.js'
}
}
```
我们利用`path.join(__dirname,'path')`将文件的入口文件变为绝对地址。将输出文件地址也改为绝对地址。这样更利于管理。
相应的`npm scripts`也要做一些修改`"start": "webpack --config webpack.config.js"`或者`"start": "webpack"`完整的文件如下。
```
{
"name": "demo1",
"version": "1.0.0",
"scripts": {
"start": "webpack --config webpack.config.js"
},
"dependencies": {
"jquery": "^3.4.1"
},
"devDependencies": {
"webpack": "^4.35.0",
"webpack-cli": "^3.3.5"
}
}
```
## 执行命令
`npm start` 那么会执行`webpack --config webpack.config.js`那么上面的代码的执行顺序为`webpack` 读取到`webconfig.js``的配置就可以得知具体的事务`。
## 项目源码
`git clone https://github.com/highh5/webpack.git -b lesson-03`
# webpack-dev-server
我们现在修改需求,把main.js中的”pink“改为"purple" 即粉色改为紫色。发现并没有自动打包文件,我们需要手动执行npm start来重新打包。
我们在去看gulp的时候我们在改项目文件时候,只要项目文件发生变化 ,那么会自动打包,并且会刷新浏览器。其实webpack也有相应的功能,而且这个功能较gulp更强大。
修改`src/main.js`
```
import $ from "jquery"
$("ul li:even").css({background:'red'})
$("ul li:odd").css({background:'purple'})
```
安装`npm install webpack-devserver -D`
修改`npm scripts`
```
"start": "webpack-dev-server --config webpack.config.js --open --port 3000 --hot"
```
```json
{
"name": "demo1",
"version": "1.0.0",
"scripts": {
"start": "webpack-dev-server --config webpack.config.js --open --port 3000 --hot"
},
"dependencies": {
"jquery": "^3.4.1"
},
"devDependencies": {
"webpack": "^4.35.0",
"webpack-cli": "^3.3.5",
"webpack-dev-server": "^3.7.2"
}
}
```
## 解释配置
```
--open 自动打开浏览器 相当于 --open true
--port 打开的服务端口号 3000 --port 3000
--hot 自动更新 --hot true
```
## 执行命令
`npm start`
执行命令我们会发现会自动打开浏览器,但是我们发现打开的并不是我们的index.html中的文件。它其它打开的是一个静态文件服务器
同时我们也发现并不会在我们本地去创建一个`dist/bundle.js`那么它是怎么一回事?
其实它是将打包的信息全部放在缓存(内存)中,我们是看不到的,那究竟打包的文件在哪里?打开这个地址就可以找到。<http://localhost:3000/bundle.js>
那么如何将我们的模板`index.html`和`bunddle.js`文件结合?请查看下面
## `html-webpack-plugin`插件
安装`npm install html-webpack-plugin -D`
使用`webpack.config.js`
```js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
entry:path.join(__dirname,'./src/main.js'),
output:{
path:path.join(__dirname,'./dist'),
filename:'bundle.js'
},
plugins:[
new HtmlWebpackPlugin({
template: path.join(__dirname,'./src/index.html'),
filename: 'index.html'
})
]
}
```
注意插都是一个构造函数,都需要new,所以在引入`html-webpack-plugin`模块时我们会使用大驼峰命名法。
利用上面的方式我们重新启动一下服务即可完成。`npm start`
![1561432736486.png](https://upload-images.jianshu.io/upload_images/2004452-7237e11f16986b44.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
# 除js外的其它模块
## 使用css
webpack将所有的文件都认为是模块所以CSS也不例外。
创建`src/index.css`
```css
body{
background:gold;
}
```
**使用`css`**
在`main.js`项目入口文件 通过 `import './index.css';`
```js
import $ from "jquery"
$("ul li:even").css({background:'red'})
$("ul li:odd").css({background:'purple'})
import './index.css'
```
### 报错
```cmd
ERROR in ./src/index.css 1:4
Module parse failed: Unexpected token (1:4)
You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file. See https://webpack.js.org/concepts#loaders
> body{
| background:gold;
| }
@ ./src/main.js 5:0-20
```
`webpack`默认是不识别`.css`文件作为结尾的模块,需要我们通过`loader`加载器将`.css`文件进行解释成正确的模块
### css-loader
安装 `npm install css-loader -D`
配置`webpack.config.js`
```js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
entry:path.join(__dirname,'./src/main.js'),
output:{
path:path.join(__dirname,'./dist'),
filename:'bundle.js'
},
plugins:[
new HtmlWebpackPlugin({
template: path.join(__dirname,'./src/index.html'),
filename: 'index.html'
})
],
module:{
rules:[
{test:/\.css$/,use:['css-loader']}
]
}
}
```
module表示是模块的意思,rules是规则的意思,每个规则对应的都是一个对象,其中test字段表示是书写正则以哪个文件名结尾,use是一个数组,表示 当前类型文件用哪种加载器解释。
### 疑问
这次我们重新执行命令`npm start`不再报错,但是对于css的样式并未加载成功原因是为什么?缺失`style-loader`
为什么说是缺失`style-loader?`
css-loader 是将index.css正确解释 为webpack的模块,进行打包到了bundle.js,我们可以佐证,但是该样式并未成功的显示到浏览器中。
那么style-loader的作用其实就是将打包到bundle.js中的css样式输出到浏览器中,以style标签的形式显示。
![1561433736658.png](https://upload-images.jianshu.io/upload_images/2004452-353c3eabc0389c12.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
![1561433780122.png](https://upload-images.jianshu.io/upload_images/2004452-44dda4f9943a8f08.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
### style-loader
安装`npm install style-loader -D`
配置 `{test:/\.css$/,use:['style-loader','css-loader']}`
注意loader的加载顺序是从右向左, 即先将css文件通过css-loader 作为正确的模块进行解释,然后再通过style-loader进行显示到浏览器中。
至此CSS文件已经完成。
### 使用less文件
安装`npm install less-loader less -D`
配置:`{test:/\.less$/,use:['style-loader','css-loader','less-loader']}`
注意,我们需要借助于less编译。因为less-loader 依赖less。
### 使用sass文件
安装`npm install sass-loader node-sass -D`
配置:`{test:/\.scss$/,use:['style-loader','css-loader','sass-loader']}`
```js
rules:[
{test:/\.css$/,use:['style-loader','css-loader']},
{test:/\.less$/,use:['style-loader','css-loader','less-loader']},
{test:/\.scss$/,use:['style-loader','css-loader','sass-loader']},
]
```
### 注意
<font color="red">对正则中的.号表示任意字符,千万小心最好把它转义一下,再来使用。</font>
### 项目源码
`git clone https://github.com/highh5/webpack.git -b lesson-04`
## 图片处理
安装`npm install url -D`
配置:`{test:/\.(jpg|jpeg|gif|png|webp)$/,use:['url-loader']},`
放置一张图片到src目录图片大小为13.54kb
![1561443070231.png](https://upload-images.jianshu.io/upload_images/2004452-4a568730922e7740.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
我们给a.scss指定body的背景是这张图片
```scss
$color:green;
body{
background: url("./avatar.jpg");
}
```
打开浏览器后发现图片是以base64进行编码了。什么是base64编译的图片呢?通俗的讲,图片不再像以前通过外链的形式打开,而是内嵌在网页上了。图片越大base64的编码信息越多。也就是说越大的图片会导致我们的代码越来越长。这是不是好事呢?
答案: 肯定不是很好,我们期望网页打开越快越好,如果网站当中所有的图片全是以base64来进行编码,那么会导致首页代码体积越大,那么打开速度也不会很快,相反可能会很卡。
我们一般可以让小图片使用base64进行加载,这样既能让图片快速加载,又会减少http请求。至于大图片我们一般就直接以往常的外链形式存在即可。
### 解决问题
修改loader配置项,limit表示小于后面的1000 byte即接近1kb,用base64显示。超过的限制的话通过外链来读取图片(需要安装file-loader 文件加载器)
`npm install file-loader -D`
```json
{
test: /\.(jpg|jpeg|gif|png|webp)$/, use: [{
loader: 'url-loader',
options:{
limit:1000,
name:'[name].[hash:8].[ext]'
}
}]
},
```
最终图片被正确解释出来
```css
body {
background: url(avatar.9d23d463.jpg);
}
```
# ES6 转 ES5
## 安装
`npm install babel-core babel-loader@7.1.5 babel-plugin-transform-runtime babel-preset-env babel-preset-stage-0 -D`
## 配置loader
```json
{test:/\.js/,use:['babel-loader'],exclude:/node_modules/}
```
exclude表示排除掉 node_modules下载的依赖项。这样可以加速网站开发,而且我们也只需要对我们的项目src源文件进行编译即可。
## 新增.babelrc文件
```json
{
"presets":[“env","stage-0"],
"plugins":["transform-runtime"]
}
```
## 修改main.js使用es6语法
```js
class Person {
constructor(){
}
}
var p = new Person();
```
## 执行命令编译
`npm start`
编译后的结果接近在1335行
```js
\n\n var Person = function Person() {\n (0, _classCallCheck3.default)(this, Person);\n};\n\nvar p = new Person();\n\n
```
**至此关于webpack的基本配置已经到这里。**
## 项目源码
`git clone https://github.com/highh5/webpack.git -b lesson-05`
# 小结
介绍了模块化、工程化相关的概念和发展现状,最后介绍了 Webpack 的应用场景,以及复习了webpack如何使用等。
# 面试题(拓展)
>学而不思则罔,思而不学则殆
**Webpack 与 Grunt、Gulp 这类打包工具有什么不同?**
简单解答:一个是模块化打包化工具,一个是流程化任务工具。
webpack 的工作方式是: 把你的项目当做一个整体,通过一个指定的主文件名(index.js, 一般是入口文件),webpack 将从这个文件开始找到你的项目所依赖的文件,使 用loaders 来处理它们,最后打包为一个浏览器可识别的js 文件。
![20180628155732669.png](https://upload-images.jianshu.io/upload_images/2004452-e26ff6946085c490.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
Gulp的工作方式是:stream流
grunt的工作方式是:在一个配置文件中,指明对某些文件进行压缩、组合、检查等任务的具体步骤,然后在运行中输入相应的命令。
![20180628155644280.png](https://upload-images.jianshu.io/upload_images/2004452-09b40048e167c23e.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
**与 Webpack 类似的工具还有哪些?谈谈你为什么选择(或放弃)使用 Webpack?**
**同样是基于入口的打包工具还有以下几个主流的:**
- webpack
- [rollup](http://rollupjs.org/guide/en/)
- [parcel](https://parceljs.org/)
**从应用场景上来看:**
- webpack适用于大型复杂的前端站点构建
- rollup适用于基础库的打包,如vue、react
- parcel适用于简单的实验性项目,他可以满足低门槛的快速看到效果
由于parcel在打包过程中给出的调试信息十分有限,所以一旦打包出错难以调试,所以不建议复杂的项目使用parcel
- webpack复习
- React基础
- 前端三大主流框架对比
- React中几个核心的概念
- React基础语法
- React JSX语法
- React组件
- 普通组件
- 组件通信-父向子传递
- 组件拆成单个文件
- 面向对象复习
- Class组件基础
- Class组件的私有状态(私有数据)
- 案例:实现评论列表组件
- 组件样式管理
- 组件样式分离-样式表
- CSS模块化
- 生命周期
- React组件生命周期
- Counter组件来学习组件生命周期
- 生命周期总结
- 生命周期案例
- React评论列表
- React双向数据绑定
- React版todolist
- 其它提高(了解)
- 组件默认值和数据类型验证
- 绑定this并传参的三种方式
- 祖孙级和非父子组件传递数据(了解)
- React路由
- 路由基础
- 动态路由
- 路由严格模式
- 路由导航定位
- 路由重定向
- 路由懒加载
- WolfMovie项目
- 项目初始化
- AntDesign使用
- 其它相关了解