vue-父子组件和webpack及脚手架初始
一. 组件化开发
1.1. 父子组件的访问
children/refs
有时候我们需要父组件直接访问子组件,子组件直接访问父组件,或者是子组件访问跟组件。
父组件访问子组件:使用$children或$refs reference(引用)
子组件访问父组件:使用$parent
我们先来看下$children的访问
this.$children是一个数组类型,它包含所有子组件对象。
我们这里通过一个遍历,取出所有子组件的message状态。
$children的缺陷:
通过$children访问子组件时,是一个数组类型,访问其中的子组件必须通过索引值。
但是当子组件过多,我们需要拿到其中一个时,往往不能确定它的索引值,甚至还可能会发生变化。
有时候,我们想明确获取其中一个特定的组件,这个时候就可以使用$refs
$refs的使用:
$refs和ref指令通常是一起使用的。
首先,我们通过ref给某一个子组件绑定一个特定的ID。
其次,通过this.$refs.ID就可以访问到该组件了。
<!DOCTYPE html>
<html lang="en">
<head>
<title>Document</title>
</head>
<body>
<div id="app">
<cpn></cpn>
<cpn></cpn>
<cpn ref="aaa"></cpn>
<button @click="btnClick">按钮</button>
</div>
<template id="cpn">
<div>
我是子组件
</div>
</template>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data:{message: '你好呀'},
methods:{
btnClick() {
//1.一般不通过$children拿子组件的内容
// console.log(this.$children);
// for (let c of this.$children) {
// console.log(c.name);
// c.showMessage()
// }
//2.$refs ==>对象类型,默认是一个空的对象,必须加属性ref='aaa'
console.log(this.$refs.aaa.name);
}
},
components: {
cpn: {
template: '#cpn',
data() {
return {
name: '我是子组件的name'
}
},
methods: {
showMessage() {
console.log('showMessage');
}
}
}
}
})
</script>
</body>
</html>
parent/root
如果我们想在子组件中直接访问父组件,可以通过$parent
注意事项:
尽管在Vue开发中,我们允许通过$parent来访问父组件,但是在真实开发中尽量不要这样做。
子组件应该尽量避免直接访问父组件的数据,因为这样耦合度太高了。
如果我们将子组件放在另外一个组件之内,很可能该父组件没有对应的属性,往往会引起问题。
另外,更不好做的是通过$parent直接修改父组件的状态,那么父组件中的状态将变得飘忽不定,很不利于我的调试和维护。
<!DOCTYPE html>
<html lang="en">
<head>
<title>Document</title>
</head>
<body>
<div id="app">
<cpn></cpn>
</div>
<template id="cpn">
<div>
<h2>我是cpn组件</h2>
<ccpn></ccpn>
</div>
</template>
<template id="ccpn">
<div>
<h2>我是子组件</h2>
<button @click="btnClcik">按钮</button>
</div>
</template>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
const app = new Vue({
//爷爷
el: '#app',
data: { message: '你好呀' },
components: {
//爸爸
cpn: {
template: '#cpn',
data() {
return {
name: '我是cpn组件的name'
}
},
components: {
ccpn: {
//儿子
template: '#ccpn',
methods: {
btnClcik() {
//不建议使用耦合性太高
//1.访问父组件$parent
// console.log(this.$parent);
// console.log(this.$parent.name);
//2.访问根组件$root
console.log(this.$root)
console.log(this.$root.message);
}
},
}
}
}
}
})
</script>
</body>
</html>
1.2. slot的使用(插槽)
基本使用
slot翻译为插槽:
在生活中很多地方都有插槽,电脑的USB插槽,插板当中的电源插槽。
插槽的目的是让我们原来的设备具备更多的扩展性。
比如电脑的USB我们可以插入U盘、硬盘、手机、音响、键盘、鼠标等等。
组件的插槽:
组件的插槽也是为了让我们封装的组件更加具有扩展性。
让使用者可以决定组件内部的一些内容到底展示什么。
栗子:移动网站中的导航栏。
移动开发中,几乎每个页面都有导航栏。
导航栏我们必然会封装成一个插件,比如nav-bar组件。
一旦有了这个组件,我们就可以在多个页面中复用了。
但是,每个页面的导航是一样的吗?No,我以京东M站为例
如何去封装这类的组件呢?
它们也很多区别,但是也有很多共性。
如果,我们每一个单独去封装一个组件,显然不合适:比如每个页面都返回,这部分内容我们就要重复去封装。
但是,如果我们封装成一个,好像也不合理:有些左侧是菜单,有些是返回,有些中间是搜索,有些是文字,等等。
如何封装合适呢?抽取共性,保留不同。
最好的封装方式就是将共性抽取到组件中,将不同暴露为插槽。
一旦我们预留了插槽,就可以让使用者根据自己的需求,决定插槽中插入什么内容。
是搜索框,还是文字,还是菜单。由调用者自己来决定。
这就是为什么我们要学习组件中的插槽slot的原因。
基本使用
了解了为什么用slot,我们再来谈谈如何使用slot?
在子组件中,使用特殊的元素
该插槽插入什么内容取决于父组件如何使用。
我们通过一个简单的例子,来给子组件定义一个插槽:
有了这个插槽后,父组件如何使用呢?
<!DOCTYPE html>
<html lang="en">
<head>
<title>Document</title>
</head>
<body>
<!--
1. 插槽的基本使用<slot></slot>
2.插槽的默认值<slot>button</slot>
3.如果有多个值同时放入组件中进行替换时, 一起作为替换元素 -->
<div id="app">
<cpn><button>按钮</button></cpn>
<cpn>
<i>呵呵呵</i>
<div>我是div元素</div>
<p>我是p元素</p>
</cpn>
<cpn></cpn>
<cpn><button>按钮</button></cpn>
</div>
<template id="cpn">
<div>
<h2>我是子组件</h2>
<p>我是组件,哈哈哈哈</p>
<!-- <button>按钮</button> -->
<slot>呵呵</slot>
</div>
</template>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
message: '你好呀'
},
components: {
cpn: {
template: '#cpn'
}
}
})
</script>
</body>
</html>
具名插槽
当子组件的功能复杂时,子组件的插槽可能并非是一个。
比如我们封装一个导航栏的子组件,可能就需要三个插槽,分别代表左边、中间、右边。
那么,外面在给插槽插入内容时,如何区分插入的是哪一个呢?
这个时候,我们就需要给插槽起一个名字
如何使用具名插槽呢?
非常简单,只要给slot元素一个name属性即可
我们来给出一个案例:
这里我们先不对导航组件做非常复杂的封装,先了解具名插槽的用法。
<!DOCTYPE html>
<html lang="en">
<head>
<title>Document</title>
</head>
<body>
<div id="app">
<cpn><span slot="center">标题</span></cpn>
<cpn><button slot="left">返回</button></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="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
message: '你好呀'
},
components: {
cpn: {
template: '#cpn'
}
}
})
</script>
</body>
</html>
编译的作用域
在真正学习插槽之前,我们需要先理解一个概念:编译作用域。
官方对于编译的作用域解析比较简单,我们自己来通过一个例子来理解这个概念:
我们来考虑下面的代码是否最终是可以渲染出来的:
isShow属性包含在组件中,也包含在Vue实例中。
答案:最终可以渲染出来,也就是使用的是Vue实例的属性。
为什么呢?
官方给出了一条准则:父组件模板的所有东西都会在父级作用域内编译;子组件模板的所
有东西都会在子级作用域内编译。
而我们在使用
是相当于在父组件中出现的。
那么他的作用域就是父组件,使用的属性也是属于父组件的属性。
因此,isShow使用的是Vue实例中的属性,而不是子组件的属性。
<!DOCTYPE html>
<html lang="en">
<head>
<title>Document</title>
</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="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
//编译作用域
<script>
const app = new Vue({
el: '#app',
data: {
message: '我是父组件',
isShow: true
},
components: {
cpn: {
template: '#cpn',
data() {
return {
isShow: false
}
}
}
}
})
</script>
</body>
</html>
作用域插槽
作用域准备
作用域插槽是slot一个比较难理解的点,而且官方文档说的又有点不清晰。
这里,我们用一句话对其做一个总结,然后我们在后续的案例中来体会:
父组件替换插槽的标签,但是内容由子组件来提供。
我们先提一个需求:
子组件中包括一组数据,比如:pLanguages: ['JavaScript', 'Python', 'Swift', 'Go', 'C++']
需要在多个界面进行展示:
某些界面是以水平方向一一展示的,
某些界面是以列表形式展示的,
某些界面直接展示一个数组
内容在子组件,希望父组件告诉我们如何展示,怎么办呢?
利用slot作用域插槽就可以了
我们来看看子组件的定义:
作用域插槽:使用
在父组件使用我们的子组件时,从子组件中拿到数据:
我们通过<template slot-scope="slotProps">
获取到slotProps属性
在通过slotProps.data就可以获取到刚才我们传入的data了
<!DOCTYPE html>
<html lang="en">
<head>
<title>Document</title>
</head>
<body>
<div id="app">
<cpn></cpn>
<cpn>
<!-- //目的获取子组件中的pLanguage -->
<template slot-scope='slot'>
<!-- <span v-for='ites in slot.data'>{{ites}} - </span> -->
<span>{{slot.abc.join(' - ')}}</span>
</template>
</cpn>
<cpn>
<!-- //目的获取子组件中的pLanguage -->
<template slot-scope='slot'>
<!-- <span v-for='ites in slot.data'>{{ites}} * </span> -->
<span>{{slot.abc.join(' * ')}}</span>
</template>
</cpn>
</div>
<template id="cpn">
<div>
<slot :abc='PLanguage'>
<ul>
<li v-for="item in PLanguage">{{item}}</li>
</ul>
</slot>
</div>
</template>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
const app = new Vue({
el: "#app",
data: {
message: '我是父组件你好呀'
},
components: {
cpn: {
template: '#cpn',
data() {
return {
PLanguage: ['java', 'python', 'c++', 'C#', 'Go', 'PHP']
}
}
}
}
})
</script>
</body>
</html>
二. 前端模块化
2.1. 为什么要使用模块化
简单写js代码带来的问题
在网页开发的早期,js制作作为一种脚本语言,做一些简单的表单验证或动画实现等,那个时候代码还是很少的。
那个时候的代码是怎么写的呢?直接将代码写在<script>
标签中即可
随着ajax异步请求的出现,慢慢形成了前后端的分离
客户端需要完成的事情越来越多,代码量也是与日俱增。
为了应对代码量的剧增,我们通常会将代码组织在多个js文件中,进行维护。
但是这种维护方式,依然不能避免一些灾难性的问题。
比如全局变量同名问题:看右边的例子
另外,这种代码的编写方式对js文件的依赖顺序几乎是强制性的
但是当js文件过多,比如有几十个的时候,弄清楚它们的顺序是一件比较同时的事情。
而且即使你弄清楚顺序了,也不能避免上面出现的这种尴尬问题的发生。
闭包引起代码不可复用.
我们可以使用匿名函数来解决方面的重名问题
在aaa.js文件中,我们使用匿名函数
但是如果我们希望在main.js文件中,用到flag,应该如何处理呢?
显然,另外一个文件中不容易使用,因为flag是一个局部变量。
自己实现了简单的模块化
我们可以使用将需要暴露到外面的变量,使用一个模块作为出口,什么意思呢?
来看下对应的代码:
我们做了什么事情呢?
非常简单,在匿名函数内部,定义一个对象。
给对象添加各种需要暴露到外面的属性和方法(不需要暴露的直接定义即可)。
最后将这个对象返回,并且在外面使用了一个MoudleA接受。
接下来,我们在man.js中怎么使用呢?
我们只需要使用属于自己模块的属性和方法即可
这就是模块最基础的封装,事实上模块的封装还有很多高级的话题:
但是我们这里就是要认识一下为什么需要模块,以及模块的原始雏形。
幸运的是,前端模块化开发已经有了很多既有的规范,以及对应的实现方案。
常见的模块化规范:
CommonJS、AMD、CMD,也有ES6的Modules
AMD/CMD/CommonJS
模块化有两个核心:导出和导入
CommonJS的导出:
CommonJS的导入
2.2. ES6中模块化的使用
export
export指令用于导出变量,比如下面的代码:
上面的代码还有另外一种写法:
导出函数或类
上面我们主要是输出变量,也可以输出函数或者输出类
上面的代码也可以写成这种形式:
某些情况下,一个模块中包含某个的功能,我们并不希望给这个功能命名,而且让导入者可以自己来命名
这个时候就可以使用export default
我们来到main.js中,这样使用就可以了
这里的myFunc是我自己命名的,你可以根据需要命名它对应的名字
另外,需要注意:
export default在同一个模块中,不允许同时存在多个。
import
我们使用export指令导出了模块对外提供的接口,下面我们就可以通过import命令来加载对应的这个模块了
首先,我们需要在HTML代码中引入两个js文件,并且类型需要设置为module
-
import指令用于导入模块中的内容,比如main.js的代码
-
如果我们希望某个模块中所有的信息都导入,一个个导入显然有些麻烦:
- 通过*可以导入模块中所有的export变量
- 但是通常情况下我们需要给*起一个别名,方便后续的使用
代码-------
index
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<script src="aaa.js" type="module"></script>
<script src="bbb.js" type="module"></script>
<script src="mmm.js" type="module"></script>
</body>
</html>
aaa.js
let name = '小明'
let age = 18
let flag = true
function sum(num1, num2) {
return num1 + num2
}
if (flag) {
console.log(sum(20, 30));
}
// 1.导出方式一
export {
flag, sum
}
//2.导出方式二
export let num1 = 1000;
export let height = 1.88
// 3.导出函数和类
export function mu1(num1, num2) {
return num1 * num2
}
//4.导出ES6的类
export class Person {
run() {
console.log('在路上');
}
}
//5.export default
// const address = '北京市'
// export default address
export default function (argument){
console.log(argument)
}
bbb.js
import {sum} from "./aaa.js";
let name = '小红'
let flag = false
console.log(sum(100, 200));
mmm.js
//1. 导入的是对象{}中定义的变量
import {flag} from "./aaa.js";
if (flag) {
console.log('小明是天才哈哈');
}
//2. 直接导入export定义的变量
import {num1, height} from "./aaa.js";
console.log(num1, height);
//3.导入export的function
import {mu1} from "./aaa.js";
console.log(mu1(30, 50));
// 导入类
import {Person} from "./aaa.js";
const p = new Person();
p.run()
//5.导入默认 /只能有一个
import addr from "./aaa.js";
addr('你好呀')
//统一全部导入
import * as aaa from './aaa.js'
console.log(aaa.flag);
console.log(aaa.height);
三. webpack
3.1. 什么是webpack
什么是webpack?
这个webpack还真不是一两句话可以说清楚的。
我们先看看官方的解释:
At its core, webpack is a static module bundler for modern JavaScript applications.
从本质上来讲,webpack是一个现代的JavaScript应用的静态模块打包工具。
但是它是什么呢?用概念解释概念,还是不清晰。
我们从两个点来解释上面这句话:模块 和 打包
前端模块化
在前面学习中,我已经用了大量的篇幅解释了为什么前端需要模块化。
而且我也提到了目前使用前端模块化的一些方案:AMD、CMD、CommonJS、ES6。
在ES6之前,我们要想进行模块化开发,就必须借助于其他的工具,让我们可以进行模块化开发。
并且在通过模块化开发完成了项目后,还需要处理模块间的各种依赖,并且将其进行整合打包。
而webpack其中一个核心就是让我们可能进行模块化开发,并且会帮助我们处理模块间的依赖关系。
而且不仅仅是JavaScript文件,我们的CSS、图片、json文件等等在webpack中都可以被当做模块来使用(在后续我们会看到)。
这就是webpack中模块化的概念。
打包如何理解呢?
理解了webpack可以帮助我们进行模块化,并且处理模块间的各种复杂关系后,打包的概念就非常好理解了。
就是将webpack中的各种资源模块进行打包合并成一个或多个包(Bundle)。
并且在打包的过程中,还可以对资源进行处理,比如压缩图片,将scss转成css,将
ES6语法转成ES5语法,将TypeScript转成JavaScript等等操作。
但是打包的操作似乎grunt/gulp也可以帮助我们完成,它们有什么不同呢?
webpack和gulp对比
grunt/gulp的核心是Task
我们可以配置一系列的task,并且定义task要处理的事务(例如ES6、ts转化,图片压缩,scss转成css)
之后让grunt/gulp来依次执行这些task,而且让整个流程自动化。
所以grunt/gulp也被称为前端自动化任务管理工具。
我们来看一个gulp的task
下面的task就是将src下面的所有js文件转成ES5的语法。
并且最终输出到dist文件夹中。
什么时候用grunt/gulp呢?
如果你的工程模块依赖非常简单,甚至是没有用到模块化的概念。
只需要进行简单的合并、压缩,就使用grunt/gulp即可。
但是如果整个项目使用了模块化管理,而且相互依赖非常强,我们就可以使用更加强大的webpack了。
所以,grunt/gulp和webpack有什么不同呢?
grunt/gulp更加强调的是前端流程的自动化,模块化不是它的核心。
webpack更加强调模块化开发管理,而文件压缩合并、预处理等功能,是他附带的功能。
webpack依赖环境
必须依赖node环境.node环境为了可以正常执行很多代码必须其中包含各种依赖的包,所以一般在安装node的时候会自动帮你安装npm
安装webpack
安装webpack首先需要安装Node.js,Node.js自带了软件包管理工具npm
查看自己的node版本:
全局安装webpack(这里我先指定版本号3.6.0,因为vue cli2依赖该版本)
局部安装webpack(后续才需要)
--save-dev`是开发时依赖,项目打包后不需要继续使用的。
为什么全局安装后,还需要局部安装呢?
在终端直接执行webpack命令,使用的全局安装的webpack
当在package.json中定义了scripts时,其中包含了webpack命令,那么使用的是局部webpack
准备工作
我们创建如下文件和文件夹:
文件和文件夹解析:
dist文件夹:用于存放之后打包的文件
src文件夹:用于存放我们写的源文件
main.js:项目的入口文件。具体内容查看下面详情。
mathUtils.js:定义了一些数学工具函数,可以在其他地方引用,并且使用。具体内容查看下面的详情。
index.html:浏览器打开展示的首页html
package.json:通过npm init生成的,npm包管理的文件(暂时没有用上,后面才会用上)
mathUtils.js文件中的代码:
main.js文件中的代码:
JS文件的打包
现在的js文件中使用了模块化的方式进行开发,他们可以直接使用吗?不可以。
因为如果直接在index.html引入这两个js文件,浏览器并不识别其中的模块化代码。
另外,在真实项目中当有许多这样的js文件时,我们一个个引用非常麻烦,并且后期非常不方便对它们进行管理。
我们应该怎么做呢?使用webpack工具,对多个js文件进行打包。
我们知道,webpack就是一个模块化的打包工具,所以它支持我们代码中写模块化,可以对模块化的代码进行处理。(如何处理的,待会儿在原理中,我会讲解)
另外,如果在处理完所有模块之间的关系后,将多个js打包到一个js文件中,引入时就变得非常方便了。
OK,如何打包呢?使用webpack的指令即可
使用打包后的文件
打包后会在dist文件下,生成一个bundle.js文件
文件内容有些复杂,这里暂时先不看,后续再进行分析。
bundle.js文件,是webpack处理了项目直接文件依赖后生成的一个js文件,我们只需要将这个js文件在index.html中引入即可
3.2. webpack的起步
webpack配置: webpack.config.js/package.json(scripts)
入口和出口
我们考虑一下,如果每次使用webpack的命令都需要写上入口和出口作为参数,就非常麻烦,有没有一种方法可以将这两个参数写到配置中,在运行时,直接读取呢?
当然可以,就是创建一个webpack.config.js文件
局部安装webpack
目前,我们使用的webpack是全局的webpack,如果我们想使用局部来打包呢?
因为一个项目往往依赖特定的webpack版本,全局的版本可能很这个项目的webpack版本不一致,导出打包出现问题。
所以通常一个项目,都有自己局部的webpack。
第一步,项目中需要安装自己局部的webpack
这里我们让局部安装webpack3.6.0
Vue CLI3中已经升级到webpack4,但是它将配置文件隐藏了起来,所以查看起来不是很方便。
第二步,通过node_modules/.bin/webpack启动webpack打包
package.json中定义启动
但是,每次执行都敲这么一长串有没有觉得不方便呢?
OK,我们可以在package.json的scripts中定义自己的执行脚本。
package.json中的scripts的脚本在执行时,会按照一定的顺序寻找命令对应的位置。
首先,会寻找本地的node_modules/.bin路径中对应的命令。
如果没有找到,会去全局的环境变量中寻找。
如何执行我们的build指令呢?
3.3. webpack的loader
什么是loader?
loader是webpack中一个非常核心的概念。
webpack用来做什么呢?
在我们之前的实例中,我们主要是用webpack来处理我们写的js代码,并且webpack会自动处理js之间相关的依赖。
但是,在开发中我们不仅仅有基本的js代码处理,我们也需要加载css、图片,也包括一些高级的将ES6转成ES5代码,将
TypeScript转成ES5代码,将scss、less转成css,将.jsx、.vue文件转成js文件等等。
对于webpack本身的能力来说,对于这些转化是不支持的。
那怎么办呢?给webpack扩展对应的loader就可以啦。
loader使用过程:
步骤一:通过npm安装需要使用的loader
步骤二:在webpack.config.js中的modules关键字下进行配置
大部分loader我们都可以在webpack的官网中找到,并且学习对应的用法。
css文件处理 - 准备工作
项目开发过程中,我们必然需要添加很多的样式,而样式我们往往写到一个单独的文件中。
在src目录中,创建一个css文件,其中创建一个normal.css文件。
我们也可以重新组织文件的目录结构,将零散的js文件放在一个js文件夹中。
normal.css中的代码非常简单,就是将body设置为red
但是,这个时候normal.css中的样式会生效吗?
当然不会,因为我们压根就没有引用它。
webpack也不可能找到它,因为我们只有一个入口,webpack会从入口开始查找其他依赖的文件。
在入口文件中引用:
css文件处理 – 打包报错信息
重新打包,会出现如下错误
这个错误告诉我们:加载normal.css文件必须有对应的loader。
css-loader/style-loader
css文件处理 – css-loader
在webpack的官方中,我们可以找到如下关于样式的loader使用方法:
按照官方配置webpack.config.js文件
注意:配置中有一个style-loader,我们并不知道它是什么,所以可以暂时不进行配置。
重新打包项目:
但是,运行index.html,你会发现样式并没有生效。
原因是css-loader只负责加载css文件,但是并不负责将css具体样式嵌入到文档中。
这个时候,我们还需要一个style-loader帮助我们处理。
css文件处理 – style-loader
我们来安装style-loader
注意:style-loader需要放在css-loader的前面。
疑惑:不对吧?按照我们的逻辑,在处理css文件过程中,应该是css-loader先加载css文件,再由style-loader来进行进一步的处理,为什么会将style-loader放在前面呢?
答案:这次因为webpack在读取使用的loader的过程中,是按照从右向左的顺序读取的。
目前,webpack.config.js的配置如下:
less-loader/less
less文件处理 – 准备工作
如果我们希望在项目中使用less、scss、stylus来写样式,webpack是否可以帮助我们处理呢?
我们这里以less为例,其他也是一样的。
我们还是先创建一个less文件,依然放在css文件夹中
less文件处理 – less-loader
继续在官方中查找,我们会找到less-loader相关的使用说明
首先,还是需要安装对应的loader
注意:我们这里还安装了less,因为webpack会使用less对less文件进行编译
其次,修改对应的配置文件
添加一个rules选项,用于处理.less文件
url-loader/file-loader
图片文件处理 – 资源准备阶段
首先,我们在项目中加入两张图片:
一张较小的图片test01.jpg(小于8kb),一张较大的图片test02.jpeg(大于8kb)
待会儿我们会针对这两张图片进行不同的处理
我们先考虑在css样式中引用图片的情况,所以我更改了normal.css中的样式:
如果我们现在直接打包,会出现如下问题
图片文件处理 – url-loader
图片处理,我们使用url-loader来处理,依然先安装url-loader
修改webpack.config.js配置文件:
再次打包,运行index.html,就会发现我们的背景图片选出了出来。
而仔细观察,你会发现背景图是通过base64显示出来的
OK,这也是limit属性的作用,当图片小于8kb时,对图片进行base64编码
图片文件处理 – file-loader
么问题来了,如果大于8kb呢?我们将background的图片改成test02.jpg
这次因为大于8kb的图片,会通过file-loader进行处理,但是我们的项目中并没有file-loader
所以,我们需要安装file-loader
再次打包,就会发现dist文件夹下多了一个图片文件
图片文件处理 – 修改文件名称
我们发现webpack自动帮助我们生成一个非常长的名字
这是一个32位hash值,目的是防止名字重复
但是,真实开发中,我们可能对打包的图片名字有一定的要求
比如,将所有的图片放在一个文件夹中,跟上图片原来的名称,同时也要防止重复
所以,我们可以在options中添加上如下选项:
img:文件要打包到的文件夹
name:获取图片原来的名字,放在该位置
hash:8:为了防止图片名称冲突,依然使用hash,但是我们只保留8位
ext:使用图片原来的扩展名
但是,我们发现图片并没有显示出来,这是因为图片使用的路径不正确
默认情况下,webpack会将生成的路径直接返回给使用者
但是,我们整个程序是打包在dist文件夹下的,所以这里我们需要在路径下再添加一个dist/
babel-loader
ES6语法处理
如果你仔细阅读webpack打包的js文件,发现写的ES6语法并没有转成ES5,那么就意味着可能一些对ES6还不支持的浏览器没有办法很好的运行我们的代码。
在前面我们说过,如果希望将ES6的语法转成ES5,那么就需要使用babel。
而在webpack中,我们直接使用babel对应的loader就可以了。
npm install --save-dev babel-loader@7 babel-core babel-preset-es2015
配置webpack.config.js文件
重新打包,查看bundle.js文件,发现其中的内容变成了ES5的语法
3.4. webpack中配置Vue
vue-loader
引入vue.js
后续项目中,我们会使用Vuejs进行开发,而且会以特殊的文件来组织vue的组件。
所以,下面我们来学习一下如何在我们的webpack环境中集成Vuejs
现在,我们希望在项目中使用Vuejs,那么必然需要对其有依赖,所以需要先进行安装
注:因为我们后续是在实际项目中也会使用vue的,所以并不是开发时依赖
--save 加与不加-dev的区别
--save-dev
开发式依赖,只有开发时才依赖这个东西,最终运行时不需要依赖这个东西
--save
但是Vue运行也需要依赖
那么,接下来就可以按照我们之前学习的方式来使用Vue了
打包项目 – 错误信息
修改完成后,重新打包,运行程序:
打包过程没有任何错误(因为只是多打包了一个vue的js文件而已)
但是运行程序,没有出现想要的效果,而且浏览器中有报错
这个错误说的是我们使用的是runtime-only版本的Vue,什么意思呢?
这里我只说解决方案:Vue不同版本构建,后续我具体讲解runtime-only和runtime-compiler的区别。
所以我们修改webpack的配置,添加如下内容即可
el和template区别(一)
正常运行之后,我们来考虑另外一个问题:
如果我们希望将data中的数据显示在界面中,就必须是修改index.html
如果我们后面自定义了组件,也必须修改index.html来使用组件
但是html模板在之后的开发中,我并不希望手动的来频繁修改,是否可以做到呢?
定义template属性:
在前面的Vue实例中,我们定义了el属性,用于和index.html中的#app进行绑定,让Vue实例之后可以管理它其中的内容
这里,我们可以将div元素中的{{message}}内容删掉,只保留一个基本的id为div的元素
但是如果我依然希望在其中显示{{message}}的内容,应该怎么处理呢?
我们可以再定义一个template属性,代码如下
el和template区别(二)
重新打包,运行程序,显示一样的结果和HTML代码结构
那么,el和template模板的关系是什么呢?
在我们之前的学习中,我们知道el用于指定Vue要管理的DOM,可以帮助解析其中的指令、事件监听等等。
而如果Vue实例中同时指定了template,那么template模板的内容会替换掉挂载的对应el的模板。
这样做有什么好处呢?
这样做之后我们就不需要在以后的开发中再次操作index.html,只需要在template中写入对应的标签即可
但是,书写template模块非常麻烦怎么办呢?
没有关系,稍后我们会将template模板中的内容进行抽离。
会
分成三部分书写:template、script、style,结构变得非常清晰。
Vue组件化开发引入
在学习组件化开发的时候,我说过以后的Vue开发过程中,我们都会采用组件化开发的思想。
那么,在当前项目中,如果我也想采用组件化的形式进行开发,应该怎么做呢?
查看下面的代码:
当然,我们也可以将下面的代码抽取到一个js文件中,并且导出。
.vue文件封装处理
但是一个组件以一个js对象的形式进行组织和使用的时候是非常不方便的
一方面编写template模块非常的麻烦
另外一方面如果有样式的话,我们写在哪里比较合适呢?
现在,我们以一种全新的方式来组织一个vue的组件
但是,这个时候这个文件可以被正确的加载吗?
必然不可以,这种特殊的文件以及特殊的格式,必须有人帮助我们处理。
谁来处理呢?vue-loader以及vue-template-compiler。
安装vue-loader和vue-template-compiler
npm install vue-loader vue-template-compiler --save-dev
修改webpack.config.js的配置文件:
3.5. webpack的plugin
plugin是什么?
详解
plugin是插件的意思,通常是用于对某个现有的架构进行扩展。
webpack中的插件,就是对webpack现有功能的各种扩展,比如打包优化,文件压缩等等。
loader和plugin区别
loader主要用于转换某些类型的模块,它是一个转换器。
plugin是插件,它是对webpack本身的扩展,是一个扩展器。
plugin的使用过程:
步骤一:通过npm安装需要使用的plugins(某些webpack已经内置的插件不需要安装)
步骤二:在webpack.config.js中的plugins中配置插件。
下面,我们就来看看可以通过哪些插件对现有的webpack打包过程进行扩容,让我们的webpack变得更加好用。
添加版权的Plugin
我们先来使用一个最简单的插件,为打包的文件添加版权声明
该插件名字叫BannerPlugin,属于webpack自带的插件。
按照下面的方式来修改webpack.config.js的文件:
重新打包程序:查看bundle.js文件的头部,看到如下信息
打包html的plugin
目前,我们的index.html文件是存放在项目的根目录下的。
我们知道,在真实发布项目时,发布的是dist文件夹中的内容,但是dist文件夹中如果没有index.html文件,那么打包的js等文件也就没有意义了。
所以,我们需要将index.html文件打包到dist文件夹中,这个时候就可以使用HtmlWebpackPlugin插件
HtmlWebpackPlugin插件可以为我们做这些事情:
自动生成一个index.html文件(可以指定模板来生成)
将打包的js文件,自动通过script标签插入到body中
安装HtmlWebpackPlugin插件
npm install html-webpack-plugin --save-dev
使用插件,修改webpack.config.js文件中plugins部分的内容如下:
这里的template表示根据什么模板来生成index.html
另外,我们需要删除之前在output中添加的publicPath属性
否则插入的script标签中的src可能会有问题
js压缩的Plugin
在项目发布之前,我们必然需要对js等文件进行压缩处理
这里,我们就对打包的js文件进行压缩
我们使用一个第三方的插件uglifyjs-webpack-plugin,并且版本号指定1.1.1,和CLI2保持一致
npm install uglifyjs-webpack-plugin@1.1.1 --save-dev
修改webpack.config.js文件,使用插件:
查看打包后的bunlde.js文件,是已经被压缩过了。
3.6. 搭建本地服务器
webpack-dev-server
搭建本地服务器
webpack提供了一个可选的本地开发服务器,这个本地服务器基于node.js搭建,内部使用express框架,可以实现我们想要的让浏览器自动刷新显示我们修改后的结果。
不过它是一个单独的模块,在webpack中使用之前需要先安装它
npm install --save-dev webpack-dev-server@2.9.1
devserver也是作为webpack中的一个选项,选项本身可以设置如下属性:
contentBase:为哪一个文件夹提供本地服务,默认是根文件夹,我们这里要填写./dist
port:端口号
inline:页面实时刷新
historyApiFallback:在SPA页面中,依赖HTML5的history模式
webpack.config.js文件配置修改如下:
我们可以再配置另外一个scripts:
--open参数表示直接打开浏览器
3.7. 配置文件的分离
开发 与发布(编译) 进行分离
开发与发布公共的部分发在一个文件,
开发特殊的部分创建一个文件
生产也就是发布创建一个文件
合拼的时候
分离之后就可以删除webpack.config.js文件了 然后在脚本build 修改一下配置
upm run build然后发现打包文件不见了 打包到build中了 不在dist文件夹里了
解决方法
四. Vue CLI
4.1. 什么是CLI
脚手架是什么东西.
如果你只是简单写几个Vue的Demo程序, 那么你不需要Vue CLI.
如果你在开发大型项目, 那么你需要, 并且必然需要使用Vue CLI
使用Vue.js开发大型应用时,我们需要考虑代码目录结构、项目结构和部署、热加载、代码单元测试等事情。
如果每个项目都要手动完成这些工作,那无以效率比较低效,所以通常我们会使用一些脚手架工具来帮助完成这些事情。
CLI是什么意思?
CLI是Command-Line Interface, 翻译为命令行界面, 但是俗称脚手架.
Vue CLI是一个官方发布 vue.js 项目脚手架
使用 vue-cli 可以快速搭建Vue开发环境以及对应的webpack配置.
脚手架长什么样子?
CLI依赖webpack,node,npm
Vue CLI使用前提 - Node
安装NodeJS
可以直接在官方网站中下载安装.
网址: http://nodejs.cn/download/
检测安装的版本
默认情况下自动安装Node和NPM
Node环境要求8.9以上或者更高版本
什么是NPM呢?
NPM的全称是Node Package Manager
是一个NodeJS包管理和分发工具,已经成为了非官方的发布Node模块(包)的标准。
后续我们会经常使用NPM来安装一些开发过程中依赖包.
cnpm安装
由于国内直接使用 npm 的官方镜像是非常慢的,这里推荐使用淘宝 NPM 镜像。
你可以使用淘宝定制的 cnpm (gzip 压缩支持) 命令行工具代替默认的 npm:
npm install -g cnpm --registry=https://registry.npm.taobao.org
这样就可以使用 cnpm 命令来安装模块了:
cnpm install [name]
Vue CLI使用前提 - Webpack
Vue.js官方脚手架工具就使用了webpack模板
对所有的资源会压缩等优化操作
它在开发过程中提供了一套完整的功能,能够使得我们开发过程中变得高效。
Webpack的全局安装
npm install webpack -g
Vue CLI的使用
安装Vue脚手架
npm install -g @vue/cli
npm install -g @vue/cli@版本号
卸载
3.0以下:
npm uninstall vue-cli -g
3.0及以上:
npm uninstall -g @vue/cli
注意:上面安装的是Vue CLI3的版本,如果需要想按照Vue CLI2的方式初始化项目时不可以的。
Vue CLI2初始化项目
vue init webpack my-project
Vue CLI3初始化项目
vue create my-project
4.2. CLI2初始化项目的过程
4.3. CLI2生产的目录结构的解析
export(导出)/import(导入)
dist -> distribution(发布)
webpack ./src/main.js ./dist/bundle.js
开发时依赖
运行时依赖