文章目录
- 第63个视频 - 父子组件通信 - 结合双向绑定案例
- 第64个视频 - 结合双向绑定案例 - 画图分析
- 第65个视频 - 结合双向绑定案例 - watch实现
- 第66个视频 - 父访问子 - children - refs
- 第67个视频 - 子访问父 - parent - root
- 第68个视频 - slot - 插槽的基本使用
- 第69个视频 - slot - 具名插槽的使用
- 第70个视频 - 编译作用域的概念
- 第71个视频 - 作用域插槽的使用
- 第72个视频 - 前端代码复杂带来的问题
- 下午 - 14:30 - 18:00
- 第73个视频 - 前端模块化雏形和CommonJS
- 第74个视频 - es6模块化的导入和导出
- 第75个视频 - webpack的介绍和安装
- 第76个视频 - webpack的基本使用过程
- 第77个视频 - webpack.config.js配置和package.json配置
- 第78个视频 - webpack中使用css文件的配置
![image-20210829174459646](https://i-blog.csdnimg.cn/blog_migrate/fc3cd9a883fe2c86a749ee161338b72f.png)
第63个视频 - 父子组件通信 - 结合双向绑定案例
代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<!--父组件模板-->
<div id="app">
<h2>这是父组件的num1:{{num1}}</h2>
<h2>这是父组件的num2:{{num2}}</h2>
<cpn :number1="num1" :number2="num2"></cpn>
</div>
<!--子组件模板-->
<template id="cpn">
<div>
<hr>
<h2>这是子组件的number1: {{number1}}</h2>
<input type="text" v-model="number1">子组件双向绑定number1
<h2>这是子组件的number2: {{number2}}</h2>
<input type="text" v-model="number2">子组件双向绑定number2
</div>
</template>
<script src="../js/vue.js"></script>
<script>
// 子组件
const cpn = {
template: '#cpn',
props: {
number1: Number,
number2: Number,
},
}
// 父组件
const app = new Vue({
el: '#app',
data: {
message: '',
num1: 1,
num2: 0
},
components: {
cpn
}
})
</script>
</body>
</html>
解决的办法:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<!--父组件模板-->
<div id="app">
<h2>这是父组件的num1:{{num1}}</h2>
<h2>这是父组件的num2:{{num2}}</h2>
<cpn :number1="num1" :number2="num2"></cpn>
</div>
<!--子组件模板-->
<template id="cpn">
<div>
<hr>
<h2>这是子组件的number1: {{dnumber1}}</h2>
<input type="text" v-model="dnumber1">子组件双向绑定number1
<h2>这是子组件的number2: {{dnumber2}}</h2>
<input type="text" v-model="dnumber2">子组件双向绑定number2
</div>
</template>
<script src="../js/vue.js"></script>
<script>
// 子组件
const cpn = {
template: '#cpn',
props: {
number1: Number,
number2: Number,
},
data(){
return {
dnumber1: this.number1,
dnumber2: this.number2
}
}
}
// 父组件
const app = new Vue({
el: '#app',
data: {
message: '',
num1: 1,
num2: 0
},
components: {
cpn
}
})
</script>
</body>
</html>
王红元父子通信双向绑定的需求描述是:
- 父组件data有两个num1和num2,通过父传子,传给子的props
- 子的props和data动态绑定到子的input上,绑定到用户的输入上。
- 用户的输入,引起子的data变化,通过子传父,传递给父的data的num1和num2
整体是通过父传子和子传父,形成了一个闭环,形成了一个圈。
加上了数据的双向绑定。
王红元的父子通信双向绑定案例的逻辑是:
- 父的data传子的props
- 子的props传子的data
- 子的data双向绑定用户输入
- 子的data和method自定义事件传父的method和data。
王红元的父子通信双向绑定案例的逻辑示意图是:
王红元父子通信双向绑定的示例代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
<cpn :childnum1="fathernum1"
:childnum2="fathernum2"
@num1change="num1change"
@num2change="num2change"></cpn>
</div>
<template id="cpn">
<div>
<h2>子组件props:{{childnum1}}</h2>
<h2>子组件data:{{datanum1}}</h2>
<input type="text" :value="datanum1" @input="num1Input">
<h2>子组件props:{{childnum2}}</h2>
<h2>子组件data:{{datanum2}}</h2>
<input type="text" :value="datanum2" @input="num2Input">
</div>
</template>
<script src="../js/vue.js"></script>
<script>
// 子组件
const cpn = {
template: '#cpn',
props: {
childnum1: Number,
childnum2: Number,
},
data() {
return {
datanum1: this.childnum1,
datanum2: this.childnum2
};
},
methods: {
num1Input(event) {
// 1. 将input当中的value,放到datanum1中
this.datanum1 = event.target.value;
// 2. 触发自定义事件,改变父组件的fathernum1,进而改变子组件的props中childnum1的值。
this.$emit('num1change', this.datanum1);
// 3. 根据子组件datanum1的值,改变子组件datanum2的值
this.datanum2 = this.datanum1 * 100;
// 4. 触发自定义事件,改变父组件的fathernum2,进而改变子组件的props中childnum2的值。
this.$emit('num2change', this.datanum2);
},
num2Input(event) {
// 1. 将input当中的value,放到datanum2中。
this.datanum2 = event.target.value;
// 2. 触发自定义事件,改变父组件的fathernum2,进而改变子组件的props中childnum2的值。
this.$emit('num2change', this.datanum2);
// 3. 根据子组件datanum2的值,改变子组件datanum2的值
this.datanum1 = this.datanum2 / 100;
// 4. 触发自定义事件,改变父组件的fathernum1,进而改变子组件的props中childnum1的值。
this.$emit('num1change', this.datanum1);
}
}
};
// 父组件
const app = new Vue({
el: '#app',
data: {
message: '',
fathernum1: 1,
fathernum2: 2,
},
components: {
cpn
},
methods: {
num1change(value) {
this.fathernum1 = parseFloat(value);
},
num2change(value) {
this.fathernum2 = parseFloat(value);
}
}
});
</script>
</body>
</html>
考试
问题:你描述一下王红元这个案例的需求?
答案:在父组件当中有两个数,这两个数要传到子组件的props中。
子组件中有data,基于props两个数,双向绑定到用户输入。
用户输入触发事件,修改父组件的数,修改子组件的第二个数,触发各自的事件。
第64个视频 - 结合双向绑定案例 - 画图分析
这个视频中,没有讲什么内容。苗苗。
所以,这个视频,不用考试。
第65个视频 - 结合双向绑定案例 - watch实现
watch可以用于监听某些属性的改变。
watch和props、data是并列的。
watch是对象。
在对象里面可以写函数。
监听哪个属性,函数名字就写哪个名称。
这个函数一般有两个参数,newValue和oldValue。一般用得比较多的是newValue。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
<cpn :number1="num1"
:number2="num2"
@num1change="num1change"
@num2change="num2change"/>
</div>
<template id="cpn">
<div>
<h2>props: {{number1}}</h2>
<h2>data: {{dnumber1}}</h2>
<input type="text" v-model="dnumber1">
<h2>props: {{number2}}</h2>
<h2>data: {{dnumber2}}</h2>
<input type="text" v-model="dnumber2">
</div>
</template>
<script src="../js/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
num1: 1,
num2: 0
},
methods: {
num1change(value) {
this.num2 = parseFloat(value)
},
num2change(value) {
this.num2 = parseFloat(value)
}
},
components: {
cpn: {
template: '#cpn',
props: {
number1: Number,
number2: Number
},
data(){
return {
dnumber1: this.number1,
dnumber2: this.number2
}
},
watch: {
dnumber1(newValue){
this.dnumber2 = newValue * 100;
this.$emit('num1change',newValue);
},
dnumber2(newValue){
this.dnumber1 = newValue / 100;
this.$emit('num2change',newValue);
}
}
}
}
});
</script>
</body>
</html>
考试
问题:vue实例或者组件实例中,watch是什么意思?
答案:watch是和data、props一样级别的属性。是可以监听某个属性的改变。
第66个视频 - 父访问子 - children - refs
业务场景:某些情况,父组件希望能拿到子组件的对象,子组件对象中有某个方法,父组件想要调用一下。或者有些时候,子组件想要拿到父组件的对象,去做一些事情。
通过对象,直接访问。
$children
$children的演示代码,如下所示:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
<cpn></cpn>
<br>
<cpn></cpn>
<br>
<cpn></cpn>
<br>
<button @click="btnClick">按钮</button>
</div>
<template id="cpn">
<div>我是子组件</div>
</template>
<script src="../js/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
message: ''
},
methods: {
btnClick() {
console.log(this.$children);
// this.$children[0].showMessage();
// console.log(this.$children[0].name);
for (let c of this.$children) {
console.log(c.name);
c.showMessage()
}
}
},
components: {
cpn: {
template: '#cpn',
data() {
return {
name: '我是景甜'
};
},
methods: {
showMessage() {
console.log('王子文');
}
}
}
}
});
</script>
</body>
</html>
请注意: c h i l d r e n 拿 到 的 子 组 件 对 象 是 一 个 数 组 , 因 为 父 组 件 中 可 能 有 多 个 子 组 件 。 实 际 开 发 中 , 我 们 一 般 不 会 使 用 children拿到的子组件对象是一个数组,因为父组件中可能有多个子组件。实际开发中,我们一般不会使用 children拿到的子组件对象是一个数组,因为父组件中可能有多个子组件。实际开发中,我们一般不会使用children来拿子组件对象。就是因为$children拿到的是数组,我们如果用它,还要数好子组件的顺序。不好用。
$refs
默认$refs是一个空的对象。你必须在子组件上面加属性,例如ref=“aaa”,这时候,refs里面就有东西了。
这其实,给每个子组件加了一个key。
代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
<cpn ref="aaa"></cpn>
<br>
<cpn ref="bbb"></cpn>
<br>
<cpn ref="ccc"></cpn>
<br>
<button @click="btnClick">按钮</button>
</div>
<template id="cpn">
<div>我是子组件</div>
</template>
<script src="../js/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
message: ''
},
methods: {
btnClick() {
console.log(this.$refs.aaa);
}
},
components: {
cpn: {
template: '#cpn',
data() {
return {
name: '我是景甜'
};
},
methods: {
showMessage() {
console.log('王子文');
}
}
}
}
});
</script>
</body>
</html>
考试
问题: c h i l d r e n 和 children和 children和refs的区别是什么?
答案: c h i l d r e n 是 数 组 类 型 的 , 数 组 中 的 每 一 项 , 就 是 一 个 子 组 件 对 象 。 children是数组类型的,数组中的每一项,就是一个子组件对象。 children是数组类型的,数组中的每一项,就是一个子组件对象。refs是对象类型的,需要配合给子组件上添加ref属性,作为惹$refs对象当中的key,每个key对应一个子组件对象。
第67个视频 - 子访问父 - parent - root
子访问父,这个用得更少了,开发当中是不建议这样用的。
$parent
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
<cpn></cpn>
</div>
<template id="cpn">
<div>
<h2>我是子组件</h2>
<button @click="btnClick">子组件按钮</button>
<ccpn></ccpn>
</div>
</template>
<template id="ccpn">
<div>
<h2>我是ccpn组件</h2>
<button @click="cbtnClick">ccpn按钮</button>
</div>
</template>
<script src="../js/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
message: ''
},
components: {
cpn: {
template: '#cpn',
data(){
return {
name: '我是cpn的name'
}
},
methods: {
btnClick(){
//访问父组件
console.log(this.$parent);
}
},
components: {
ccpn: {
template: '#ccpn',
methods: {
cbtnClick(){
//访问父组件
console.log(this.$parent.name);
}
}
}
}
}
}
});
</script>
</body>
</html>
为什么开发当中不建议这么用呢?
因为你如果子访问父的话,损失了子组件的复用性。
$root
使用$root,直接访问到vue实例。
考试
问题: c h i l d r e n 、 children、 children、refs、 p a r e n t 、 parent、 parent、root使用频率怎么样
答案: r e f s − 非 常 多 , refs-非常多, refs−非常多,children和 r o o t 会 用 但 是 不 多 , root会用但是不多, root会用但是不多,parent非常少,因为它是用于子组件访问父组件,损害了父组件的独立性。
第68个视频 - slot - 插槽的基本使用
举例
共性的东西,封装在组件中,异性的东西,写在slot中。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
<cpn>
<i>我是苗蔓</i>
<div>我是div元素</div>
<p>我是p元素</p>
</cpn>
<cpn><img src="http://ww1.sinaimg.cn/large/006clBEWly1gtz4ugj6gxj30c20c1jvm.jpg" alt=""></cpn>
<cpn><img src="http://ww1.sinaimg.cn/large/006clBEWly1gtz4v81l3zj30ne0ne43m.jpg" alt=""></cpn>
<cpn></cpn>
</div>
<template id="cpn">
<div>
<h2>我是组件</h2>
<p>我是组件,哈哈哈哈</p>
<slot><button>默认按钮</button></slot>
</div>
</template>
<script src="../js/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
message: ''
},
components: {
cpn: {
template: '#cpn',
}
}
})
</script>
</body>
</html>
总结
1 - slot的基本使用
2 - slot的默认值
3 - 如果有多个元素,同时放到组件中,替换一个slot,这多个元素会一起放到元素当中的。
考试
问题:一个自定义组件叫cpn,cpn组件中有一个slot,那么该如何使用呢?
答案:在调用子组件的时候,写<cpn><替换元素></cpn>
第69个视频 - slot - 具名插槽的使用
直接上代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
<cpn><span slot="center">我是张澍</span></cpn>
<cpn><button slot="left">我是江疏影</button></cpn>
<cpn><img slot="right" src="http://ww1.sinaimg.cn/large/006clBEWly1gtz5dckhsuj304g04gt92.jpg" alt=""></cpn>
</div>
<template id="cpn">
<div>
<slot name="left"><span>我是左边</span></slot>
<slot name="center"><span>我是中间</span></slot>
<slot name="right"><span>我是右边</span></slot>
</div>
</template>
<script src="../js/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
message: ''
},
components: {
cpn: {
template: '#cpn',
}
}
})
</script>
</body>
</html>
直接看效果:
考试
问题:怎么使用具名插槽呢?
答案:在定义子组件的时候,通过slot标签定义插槽,给slot标签设置name属性和值,设置具名插槽。
第二步,在父组件中使用子组件的时候,在子组件标签content位置,写替换元素,元素增加一个slot属性,属性名是子组件设置的name值。
第70个视频 - 编译作用域的概念
先要理解编译作用域,然后才能够理解作用域插槽。
什么叫做编译作用域呢?
我们通过代码来理解一下。
一般程序员用github和stack overflow。看stack overflow时候,一般直接看代码,就知道它想说什么了。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body>
<div id="app">
<cpn v-show="isShow"></cpn>
</div>
<template id="cpn">
<div>
<h2>我是子组件</h2>
<p>我是子组件,哈哈哈</p>
<button v-show="isShow">按钮</button>
</div>
</template>
<script src="../js/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
message: '',
isShow: false
},
components: {
cpn: {
template: '#cpn',
data(){
return {
isShow: false
}
}
}
}
})
</script>
</body>
</html>
考试
问题:什么是编译作用域?
答案:在父组件中调用子组件,子组件标签上,使用v-show、v-if这些指令,用到的变量,都是父组件的实例中的变量,这就叫做作用域。
第71个视频 - 作用域插槽的使用
作用域插槽是slot中比较难理解的点,官方文档说得也是不清不楚的。
先看看王红元老师的总结:
父组件替换插槽里面的标签,但是内容又是由子组件来决定的。
- 子组件的插槽,可以占位,父组件可以替换插槽。
- 子组件的插槽,可以设置默认值,父组件可以替换插槽。
- 子组件的插槽,可以具名占位,父组件可以具名替换插槽。
- 子组件的插槽,里面只有展示形式,父组件改变子组件的插槽展示形式。
【锚点】小总结
- c h i l d r e n 、 children、 children、refs、 p a r e n t 、 parent、 parent、root,这里是父拿子的对象,子拿父的对象,是在实例当中操作。
- slot的name,slot的data,这是父拿子的元素,是在template当中操作。
作用域插槽的演示代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
<cpn></cpn>
<cpn>
<!--目的是获取子组件当中的pLanguages-->
<!--vue的2.5.x以下,必须使用template,2.5.x以上,可以不使用template,使用div -->
<!--为了让代码有更强的适应性,我们还使用template-->
<!--template的目的是,去拿子组件当中的data-->
<template slot-scope="slot">
<!--<span v-for="item in slot.data">{{item}} -</span>-->
<span>{{slot.data.join(' - ')}}</span>
</template>
</cpn>
<cpn>
<template slot-scope="slot">
<!--<span v-for="item in slot.data">{{item}} *</span>-->
<span>{{slot.data.join(' * ')}}</span>
</template>
</cpn>
</div>
<template id="cpn">
<div>
<slot :data="pLanguages">
<ul>
<li v-for="item in pLanguages">{{item}}</li>
</ul>
</slot>
</div>
</template>
<script src="../js/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
message: ''
},
components: {
cpn: {
template: '#cpn',
data(){
return {
pLanguages: ['JavaScript', 'Java', 'Python', 'C++', 'C#', 'Go']
}
}
}
}
})
</script>
</body>
</html>
考试
问题:什么是作用域插槽,怎么用?
答案:作用域插槽的应用场景是,父组件对子组件的数据展示方式不满意,父组件要自己展示。
第一步,子组件当中通过动态绑定属性x,绑定子组件当中的数据。
第二步,在父组件当中,在子组件标签content位置中,写一个template标签,属性写成slot-scope=“slot”。
第三步,在template标签内,进行数据展示,通过slot.属性名获取数据,可以操作了。
【锚点】组件化全部总结
第72个视频 - 前端代码复杂带来的问题
第一部分,我们讲了邂逅vuejs。
第二部分,我们讲了vue的基础语法。
第三部分,我们讲了组件化开发。
第四部分,我们讲一下模块化开发。
考试
问题:为什么会出现前端模块化?
答案:团队开发,一个项目,小明命名了flag,小红也命名了flag,小明操作flag为true,小红操作flag为false,乱套了。
下午 - 14:30 - 18:00
第73个视频 - 前端模块化雏形和CommonJS
解决全局变量重名问题
我们可以使用闭包:
匿名闭包带来的问题
降低了代码复用性,不能够用之前其他js文件中定义的函数或其他。
小明写了两个js文件:aaa.js和mmm.js。在aaa.js当中定义了flag和sum函数,想要在mmm.js当中使用,咋整?
现在为了解决全局变量重名的问题,每个js文件当中的代码,都使用了匿名闭包来包裹这,上面的需求根本实现不了的。
es5模块化雏形
通过下面的代码,通过对象,可以解决上面的问题,这种解决问题的方式,就属于模块化的雏形。
这样在项目当中,我们只需要人为规定,模块的命名不要重复,就可以在es5的条件下,实现模块化开发了。
但是。
现在我们不采用这种模块化的方案。
我们使用别人已经规定好的模块化方案,去写自己的代码。
回顾PPT
CommonJS只是一个规范而已。
CommonJS非常著名的就是node实现。
node里面使用的模块化,就是按照CommonJS的。
大家在学习node当中,会更加具体学习CommonJS。
我们在项目当中主要用的是es6的模块化。
但是后面我们讲解webpack的时候,就必须使用CommonJS的导出。
AMD和CMD这两种东西,用的是比较少的。
commonjs
模块化当中一个js文件就可以看成一个模块。
而且这个模块,有自己的作用域,不会和别人出现命名冲突的问题。
webpack要想正常运行,是依赖node环境的,node环境当中是具有commonjs的支撑的。
commonjs的导出呢?
考试
问题:模块化方案一般有哪几种?
答案:commonjs,amd,cmd,还有es6的modules。
第74个视频 - es6模块化的导入和导出
我们的重点是es6的模块化。
es6当中新增了两个关键字。
一个叫做import,这个是导入。
一个叫做export,这个是导出。
我们先来讲一下es6当中怎么导出,然后讲讲怎么导入。
es6的导出是不支持像是上面的这种写法的。注意。
对比es6模块化和commonjs的写法
- commonjs
- 导出:
module.exports = {}
- 导入:
let x = require('')
- 导出:
- es6
- 导出:
export {}
- 导入:
import {} from " "
- 导出:
es6的基本使用
多种导出方式
第一种导出方式,导出对象:
第二种导出方式,直接导出:
第三种导出方式,导出函数:
第四种导出方式,导出类:
第五种导出方式:export default。
某些情况下,我们的模块当中包含某个功能,我们并不希望给这个功能起一个名字,我们希望导入者自己来给这个功能命名。
这个时候,我们就使用export default。
需要注意的点:
- 一个模块当中默认导出的,export default导出的元素,只能够有一个。
- 在导入的时候,就不需要加上大括号了,而且导入的名字是可以随便起的。
es6导入导出的总结
- 导出
- 导出一个对象
- 定义的时候直接导出
- 导出一个函数
- 导出一个类
- export default,一个模块当中只能够有一个。
- 导入
- 导入一个对象
- 导入export default定义的东西
- 统一导入:
import * as xxx from './aaa.js'
考试
问题:commonjs和es6的导入导出有什么区别。
答案:commonjs的导出是modules.export = {}
,导入是let x = require(‘xxx.js’)。es6的导出是export {}
,导入是import x from 'xxx.js'
。
【锚点】开小差
到了这里的时候,我就想起来了,之前看的impress.js的代码了。
这个时候,我就能够理解impress.js当中为什么是这样写的呢?
这种形式,应该就是匿名函数闭包,就是为了避免整个项目当中,全局变量重名的问题。但是降低了代码的复用性。
这个匿名函数就是:
(function(document,window){}){document,window}
根据我的理解,就是从外面传入了document和window两个参数给这个函数。
- 为什么impress.js不使用es6的语法写呢?难道是因为es5有更好的浏览器兼容性的问题吗?
- 难道impress.js当中各个js文件之间,是没有什么复用的代码的吗?
这是我的一点疑问。后面我要好好看看impress.js当中的代码的。
第75个视频 - webpack的介绍和安装
webpack第1页ppt
webpack第2页ppt
什么是webpack。
js的静态模块打包工具。
打包目前市场上有很多的打包工具:grunt、gulp、webpack、rollup。
vue的源码是使用rollup进行构建的。
webpack第3页ppt
现阶段,我们在浏览器当中,只能够使用es6的模块化规范,这是因为浏览器底层做了一些对es6的底层支撑。
我们是在代码中不能够直接使用AMD和CMD和commonjs的规范的,因为浏览器底层没有支撑。
但是在webpack当中,我们是可以随便使用模块化规范的。
因为webpack给我们提供了支撑。
在webpack给我们的代码进行打包的时候,会对代码进行一定的转换的。
转换成为es6相关的规范,这样运行的时候,浏览器就能够支撑了。
我们在开发的过程当中是可以使用commonjs代码的,但是打包的时候,是没有commonjs代码的。
这就是webpack的强大的地方。
之前学习的gulp是不支持模块化的。
webpack第4页ppt
webpack第5页ppt
webpack第6页ppt
webpack想要使用它的话,是依赖node环境的。
开发这些东西的时候,是通过模块化的方式进行开发的。
不能够直接将这些东西放到服务器上进行部署,如果直接把这些东西放到服务器上的话,意味着用户通过浏览器从服务器上请求到的ts,less,js肯定是不能够正常运行。
我们通过webpack做一层中间的模块化打包,等你把项目开发完了,你使用webpack执行一条指令进行模块化打包,生成一个文件夹,只需要把文件夹放到服务器进行部署就可以了。
webpack要想要正常运行,依赖一个环境就是node.js环境。
我们还没有学过node,但是没有关系,我们只需要安装一下就可以了。
webpack依赖node。
node环境为了可以正常执行很多代码,它必须有各种包。这些包如果你手动管理的话,是非常费劲的。
所以一般在安装node的时候,会自动帮你安装一个工具,叫做npm工具。
npm工具只是为了帮你管理,node下面的各种包而已。
npm的意思就是node package manager,就是node包管理器。
王红元安装的node版本是:
这就算是比较新的版本了?之前的教室装的是6.x的版本,这6.x的版本对我们来说是不能够用的。因为6.x不利于我们使用脚手架。要求你的node版本必须大于8.9。
一旦你有了node之后,你可以通过node当中的npm来安装webpack。
我们安装webpack的哪个版本呢?
我们安装webpack的3.6.0。这是因为这个版本是vue cli 2.x依赖的webpack版本。
vue cli 2.x里面才能够看到各种webpack的配置。
如果你用的是vue cli 3.x的话,webpack的各种配置都被隐藏起来了。
我们的方式是:我们按照脚手架2的方式自己去配置webpack,我们配置完了之后,可以跟脚手架2.x自动生成的webpack配置来进行一个对比。这样的话,我们去读脚手架的配置,就比较容易一点。
局部安装webpack和全局安装webpack有啥区别?
全局安装的意思是不管你是在cmd终端当中,还是在webstorm的cmd终端当中,执行webpack,用的都是全局的。
我们之后会有一个文件,叫做package.json,这个文件当中能够定义很多的scripts,如果你定义了很多的脚本,并且,在脚本当中用了webpack的话,用的就是局部的。
第76个视频 - webpack的基本使用过程
webpack第7页PPT
distribution:发布的意思。
source:源码的意思。
webpack ./src/main.js ./dist/bundle.js
我们看看bundle.js当中的代码:
总结一下:
我们可以在src下面,使用commonjs的规范来进行开发。
然后我们使用webpack将js代码打包到dist下面。
然后我们在index.html当中引入dist中打包好的bundle.js,这样,我们就可以通过跑页面来执行js代码了。
考试
问题:我们可以在一个js代码当中,既使用commonjs的模块化规范,又使用es6的模块化规范,然后通过webpack来进行打包吗?
答案:可以。
问题:webpack的基础打包命令是什么?
答案:webpack ./src/main.js ./dist/bundle.js
第77个视频 - webpack.config.js配置和package.json配置
webpack第8页PPT
webpack第9页PPT
我之前打包的时候,是通过webpack ./src/main.js ./dist/bundle.js
这个命令来进行打包的。这里的main.js是我们的入口文件,这里的bundle.js是我们打包出口的文件。
我们现在的想法是,我们执行打包命令的时候,只写一个webpack命令,就能够完成上面的操作,怎么做呢?
那么我们肯定是需要在某个位置告诉webpack,入口和出口是在什么位置的。
webpack.config.js
为什么实现上面的目标。我们需要在项目当中新建一个js文件。
这个js文件的名字叫做:webpack.config.js
。
这个文件是webpack的配置文件。
我们可以在这个配置文件当中配置打包入口和打包出口。
我们具体的配置就如下图所示:
请注意,上面的图。在配置打包出口的时候,我们配置path路径的时候,这里需要是绝对路径。
我们怎么获取绝对路径呢?
我们需要利用到node的path模块。
我们在利用node之前,我们要先使用npm init来初始化我们的项目。
npm init
一旦你当前项目当中,准备用node相关的东西,就执行npm init
。
这是要起一个包的名字。如果直接敲回车,就是使用小括号当中的名字。
这是版本号,直接敲回车。
这是描述信息,直接敲回车。
entry point是入口,我们不需要把webpack.config.js作为入口,我们随便写一个index.js。
这里入口文件,我们无论写什么东西,我们都暂时用不上这个玩意。
在讲node的时候,会详细讲这个玩意的。
直接敲回车。
直接敲回车。
直接敲回车。
直接敲回车。
直接敲回车。
直接敲回车。
node教程当中,会详细讲解这个package.json
。
如果在项目当中,你想要依赖node的一些东西,我们这里一般都是有一个package.json文件的。
如果你的package.json当中有依赖一些其他的包的话,我们一般会执行npm install
来进行安装。
这样就会根据package.json来进行一些安装的。
在这里,我执行了npm install
,这样就生产了package-lock.json
文件。
在我们的webpack第7页ppt当中,准备阶段,就已经建议了,需要提前把package.json这个文件建好。
path
node的内建的path模块当中有一个函数叫做:path.resolve()
。
这个函数的作用是,可以对两个路径进行拼接。
node当中有一个全局变量叫做__dirname
,这个全局变量表示的是当前文件webpack.config.js的路径。
注意是路径。
具体的如下图所示:
如上的配置过之后,就可以实现我们文章一开始所提出来的需求。
这样,我们就将webpack的入口和出口,放到了一个配置文件当中了。
开发时候的打包命令
在开发的时候,我们一般都不会直接敲webpack命令来进行打包。
我们敲另外的一个命令。
npm run build
。
怎么样才能够将npm run build
这个命令映射到webpack
这个命令呢?
因为事实上webpack打包的时候,后面会跟一大堆的参数,会比较长比较麻烦的。
我们会使用npm run build
来映射到我们的打包命令上。
webpack命令映射
如图,在package.json当中有scripts的配置项:
上面的这个脚本配置的意思是,我们执行npm run test
的时候,实际上执行的是test对应的echo \"Error: no test specified\" && exit 1
命令。
做了上面的配置之后,我们执行npm run build
就是执行webpack
命令了。
package.json命令映射的小优点
当我们在package.json当中做了npm的命令映射之后,使用npm run build命令执行打包。
跟我们直接在终端当中输入webpack进行打包。
相比较而言,有一点不同。
就是,使用npm run build的话,会优先去本地找对应的命令webpack。
任何项目都有一个本地的webpack的包。
全局安装的是4.x的版本,但是你的项目可能特殊,需要的是3.x的版本。
所以开发过程当中,每个项目一般都有一个本地的webpack的包。
开发时依赖和运行时依赖
- 开发时依赖
- 只有在开发阶段,才用webpack这个东西,真正项目运行的时候,并不需要webpack这个东西。
- 加上
--save-dev
npm install webpack@3.6.0 --save-dev
- 运行时依赖
- 这是指项目运行的时候,还需要的依赖。
本地安装webpack
这个时候打开package.json,就会发现新增了依赖配置:
本地安装是安装在哪里呢?
如图,就是安装到当前目录下的位置。
用哪个webpack打包?
这个时候,我们在webstorm的终端当中敲入webpack命令,是用全局的webpack还是用局部的webpack呢?
只要是在终端当中敲命令,无论是cmd还是webstorm终端,用的都是全局的。
我们在package.json当中定义了脚本之后,执行命令,就可以使用本地的webpack了。
如果想要直接用本地的webpack,就需要像下面的做法,才可以:
webpack第10页PPT
webpack第11页PPT
webpack第12页PPT
考试
问题:webpack.config.js这个文件是怎么产生的?
答案:这个是手动创建的webpack的配置文件,在里面可以配置打包的入口和出口等信息。
问题:package.json这个文件是怎么产生的?
答案:这个文件是在项目目录中执行npm init的时候产生的,如果项目要用到node相关的东西,就要使用npm init。在package.json当中可以配置npm命令映射。
问题:package-lock.json这个文件是怎么产生的?
答案:这个文件是执行npm install后产生的。
第78个视频 - webpack中使用css文件的配置
猛回头
讲到这里,你就可以发现一个问题。
以后我们如果只开发js相关的代码,我们就可以都使用模块化了。
第一,我们使用npm init来初始化我们的项目
第二,我们本地安装webpack。
然后,我们就可以进行模块化的开发的。
webpack第13页PPT
我们如果学react的话,就会写.jsx文件。我们的vue当中使用的就是.vue文件。
webpack第14页PPT
css咋办?
webpack打包的时候,只会根据我们的入口依次去寻找,去确定依赖关系,只有依赖的东西,最终去打包。
像是上面的css文件,我们根本就没有在任何的文件当中依赖这个玩意,咋办?
我们把css当成一个模块。
我们在入口文件main.js当中依赖我们的css代码。
然后我们使用webpack打包:
报错信息说:你可能是需要一个合适的loader来处理css文件。
那么使用哪个loader呢?
webpack.org.js,这个才是webpack的官网。一般一些非盈利组织的官网都是用org域名的。
例如vuejs.org。
css-loader
我们也可以直接看中文的网站:https://www.webpackjs.com/
我们可以直接查看中文文档。
我们安装一下:
这样就安装成功了。
然后进行配置:
module.exports = {
module: {
rules: [
{
test: /\.css$/,
use: [ 'style-loader', 'css-loader' ]
}
]
}
}
我们在代码当中进行配置:
然后运行npm run build。
css-loader版本过高的问题
得到下面的报错
这里报错的原因是:css-loader版本过高的原因。
详细的内容,我们可以参考这篇文章。
我们可以通过下面的命令卸载和重装css-loader。
npm uninstall css-loader
npm install css-loader@3.6.0 --save-dev
在视频当中,王红元安装的版本是:
我重装了一个css-loader@3.6.0
。
然后我们重新运行npm run build
。
这个时候,并没有报错。
但是打开我们的index.html页面,样式也没有生效。
css-loader和style-loader的分工
只负责帮你将css文件进行加载,不负责解析,也不负责将css代码放到html当中生效。
如果想要生效的话, 还需要安装style.loader
。
style.loader
如下图,安装成功:
当然了,上面我们安装的style-loader是@3.2.1的版本的。
我为了防止还是出现上面的,loader版本过高的问题。
我直接参考王红元的版本。
我重新卸载了,安装一遍。
然后进行style.loader的配置。
上面的正则表达式是:/\.css$/
。
//
两个是包围着正则表达式。/.
中,这是因为.
在正则表达式当中有特殊的含义,/.
表达的是转义,意思表示的代表真正的.
。$
表示结尾。^
表示开始。- 这个正则表达式的目的是匹配所有
.css
结尾的文件。
上面的配置的意思是:匹配到css文件的时候,就use下面的loader。
styler-loader的作用是负责将样式添加到DOM当中。
styler-loader和css-loader在webpack.config.js当中的顺序
webpack.config.js当中,使用多个loader的时候,是从右向左读的。
所以,我们要写成下面的顺序:
use: [ 'style-loader', 'css-loader' ]
测试效果
样式生效了。成功了。
webpack第15页PPT
webpack第16页PPT
webpack第17页PPT
考试
问题:webpack.config.js当中配置loader的顺序是什么?
答案:从右向左的先后顺序。