之前我们所讲述内容,都围绕的script
标签引入的。
vue
的脚手架工具和React
的脚手架工具一样,综合了很多东西,如打包、构建、单元测试等,它其实就是一个集成开发环境,提供了很多工具。
Vue 提供了一个脚手架工具,帮助我们快速搭建本地项目:vue-cli
vue-cli 是 vue 提供的一个用于自动化构建和开发项目的工具,也称为:脚手架,它是一系列工具的集合,它主要有:
https://cli.vuejs.org/zh/
npm install -g @vue/cli
// OR
yarn global add @vue/cli
看是否安装成功,可以输出vue
命令
有点类似create-react-app
脚手架工具,但是它提供了更多地特性。
查看版本
vue --version
// OR
vue -V
帮助
vue --help
// OR
vue -h
vue-cli 提供了两种使用方式
// 命令行
vue create 项目名称
// UI
vue ui
运行命令以后,根据提示进行选择
vue create 项目名称
vue create app
进入一个交互式页面:
第一个提示使用上下键选择,想要的选项:
默认配置(安装babel、eslint
)
手动配置(手动选择特性)
选择第二个回车,然后选择可以用到的去安装
提示 空格
选择、a
全选 、 i
反选
PWA
渐进式web应用
Vuex
状态管理方案
CSS Pre-processors
CSS预处理器
Linter / Formatter
代码格式化、代码标准(代码规范)
Unit Testing
单元测试
E2E Testting
端到端测试,模拟用户与浏览器的交互,但不是人点,而是通过代码打开浏览器,用代码模拟人操作
选择完后,回车安装:
In dedicated config files: 单独保存在各自的配置文件中 => 刚才选择安装文件最终形成一个配置文件,可单独存放
In package.json: 保存在package.json文件中 => 融入package.json
选第一个
提示,是否把配置存起来,以备下次直接使用。
选n
,开始安装
运行
项目创建成功以后,进入项目根目录,打开 package.json 文件,我们可以看到
{
...,
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build"
},
...
}
https://cli.vuejs.org/zh/guide/cli-service.html#%E4%BD%BF%E7%94%A8%E5%91%BD%E4%BB%A4
cd app
yarn serve
vue ui
vue ui
安装完成后
PWA
应用是指那些使用指定技术和标准模式来开发的web应用
,这将同时赋予它们web应用
和原生应用
的特性。
例如,web应用
更加易于发现——相比于安装应用,访问一个网站显然更加容易和迅速,并且你可以通过一个链接来分享web应用
。
在另一方面,原生应用与操作系统可以更加完美的整合,也因此为用户提供了无缝的用户体验。你可以通过安装应用使得它在离线的状态下也可以运行,并且相较于使用浏览器访问,用户也更喜欢通过点击主页上的图标来访问它们喜爱的应用。
PWA
赋予了我们创建同时拥有以上两种优势的应用的能力。
这并不是一个新概念——这样的想法在过去已经在web平台
上通过许多方法出现了多次。渐进式增强和响应式设计已经可以让我们构建对移动端友好的网站。在多年以前的Firefox OS
的生态系统中离线运行和安装web应用
已经成为了可能。
PWAs
, 不但如此,更是提供了所有的甚至是更多的特性,来让web更加优秀。
npm build
yarn build
最终丢到服务器的代码就是dist
里的文件。
- node_modules/ 安装的第三方插件及依赖
- public/ 应用的index.html,打包后的文件最终的入口
- src/ 开发过程中的目录
- assets/ 开发用到的资源(通过import引入),如图片
- components/ 组件
- app.vue
- main.js 应用入口
- ... 几乎都是配置文件等,不是重点
src
先来说一个最重要的目录 src ,这个就是存放的就是我们项目源码的目录,我们开发过程中大部分的时间就在这个目录中
项目的入口文件
首先,这是 vue 提供的一种单文件组件的文件模式(后续会讲),一个 .vue 文件就是一个独立的组件,这里的 App.vue 是应用的根组件
存放组件的目录
存放静态资源的目录,比如:图片,css 等。这里的文件与外层 public 目录存放的静态资源的最大区别是:assets 存放的资源是通过 import 等方式作为模块导入,最后打包处理的。而 public 中的资源并不通过模块方式导入,一般都是通过 script 、link 、img 等方式从浏览器引入的资源,比如无法通过模块化处理的 js 文件(这样的需求情况并不多)
public
一些并非通过模块方式引入的资源文件存放的位置,一般都是通过 script 、link 、img 等方式从浏览器引入的资源,比如无法通过模块化处理的 js 文件(这样的需求情况并不多)
\test01\app\src\main.js
import Vue from 'vue'
import App from './App.vue'
Vue.config.productionTip = false // Vue 在启动时的生产提示
// 构建根组件
new Vue({
// 没有采用template,而是render
render: h => h(App), // 对jsx进行渲染,h就是似虚拟dom的构建函数,类似react的dom底下的create方法,然后把组件传进去,进行渲染
}).$mount('#app') // 挂载到页面id的app上
参考:https://https://github.com/6xiaoDi/blog-vue-Novice/tree/a1.56
Branch: branch04commit description:a1.56(vue脚手架工具构建项目)
tag:a1.56
vue
为了方便进行组件开发,特意弄了.vue
的文件,它帮助我们更为方便地进行组件开发,不过该文件不能直接被浏览器解析。实际上在webpack
中加了vue-loader
,最终被webpack
解析成一个js
文件,它返回回去的是一个对象(templete
、样式等均组织到一个对象里与它进行融合)。
实际返回回去就是一个组件对象。
\app\src\App.vue
<template>
<div id="app">
<img alt="Vue logo" src="./assets/logo.png">
<HelloWorld msg="Welcome to Your Vue.js App"/>
div>
template>
<script>
import HelloWorld from './components/HelloWorld.vue'
export default {
name: 'App',
components: {
HelloWorld
}
}
script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
style>
vue 的单文件组件是官方提供的一种用来组织组件代码的形式,该文件以 .vue
为后缀,该文件会被 vue-cli 内置的 webpack 解析生成对应的 javascript、html、css 文件
https://vue-loader-v14.vuejs.org/zh-cn/start/spec.html
vue 也是基于组件的开发模式,我们知道一个 UI 组件包含
为了能够更加方便的编写组件,vue 提供了一个特殊的组件定义文件:.vue 文件,我们也称为 单文件组件
一个单文件组件的 结构、样式、行为 分别通过三个标签来进行定义和划分(方便对组件进行编写)
单文件组件把一个组件所包含的 结构、样式、行为 分别通过 template、style、script 进行分离包含,然后统一组织在一个文件中
注意:一个单文件组件最少必须包含 template,可以不需要 script 和 style
无论是 template、script 还是 style,都可以通过 lang 属性来指定它们所使用的语言
template默认是html
语言,我们这里设置成jade
script默认是js
,这里改成用ts
。
<template lang="jade">
div.example
p {{ msg }}
</template>
<script lang="ts">
import Vue from 'vue'
export default Vue.extend({
data () {
return {
msg: 'Hello world'
}
}
})
</script>
<style lang="stylus">
.example
color red
</style>
如果一个组件的模板脚本内容太多了,导致文件太大,可以把template、style、script 进行分离
可以通过 src 属性把文件分离到单独的文件中
<template src="./template.html">template>
<style src="./style.css">style>
<script src="./script.js">script>
这里的 src 同样遵循模块化的导入规则,
./
开头的表示相对路径,/
开头表示 NPM 包中的资源
当 style
标签有 scoped
属性时,它的 CSS 只作用于当前组件中的元素。这类似于 Shadow DOM 中的样式封装
<style scoped>
.example {
color: red;
}
style>
<template>
<div class="example">hidiv>
template>
解析后:
<style>
.example[data-v-f3f3eg9] {
color: red;
}
style>
<template>
<div class="example" data-v-f3f3eg9>hidiv>
template>
\app\src\components\HelloWorld.vue
<template>
<div class="hello">
<p>这里是hello - {{msg}}p>
div>
template>
<script>
export default {
name: 'HelloWorld',
props: {
msg: String
}
}
script>
<style>
style>
\app\src\App.vue
<template>
<div id="app">
<h1>软件开发h1>
<p>前端Vuep>
<hr>
<HelloWorld msg="Welcome to Your Vue.js App"/>
div>
template>
<script>
import HelloWorld from './components/HelloWorld.vue'
export default {
name: 'App',
components: {
HelloWorld
}
}
script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
style>
vue脚手架demo\app\src\components\HelloWorld.vue
<template>
<div class="hello">
<p>这里是hello - {{msg}}p>
div>
template>
<script>
export default {
name: 'HelloWorld',
props: {
msg: String
}
}
script>
<style>
p {
color: red;
}
style>
vue脚手架demo\app\src\App.vue
<template>
<div id="app">
<h1>软件开发h1>
<p>前端Vuep>
<hr>
<HelloWorld msg="Welcome to Your Vue.js App"/>
div>
template>
<script>
import HelloWorld from './components/HelloWorld.vue'
export default {
name: 'App',
components: {
HelloWorld
}
}
script>
我们发现这里会有一个问题 =>
编译后的html
的样式,这个样式会影响全局。它把全部的p
标签(组件里和组件外的p
标签),但是封装组件的目的是使数据、模板是独立的,样式也应该使独立的。
但是CSS
中没有默认作用域的问题,所以它采取了一个特别的手段,加载了一个第三方模块,主要用来处理CSS module
的(模块问题)。
参考:https://https://github.com/6xiaoDi/blog-vue-Novice/tree/a1.57
Branch: branch04commit description:a1.57(example01-1——css无独立作用域的隐患)
tag:a1.57
<style scoped>
p{
color: red;
}
style>
scoped
它会使组件相关的模板,都加了data-v-469af010
,469af010
是一个hash值,每一个组件都有自己独立的hash
值。组件中所有标签都会加上该属性,并且在css
中加一个选择器data-v-469af010
,这样就只会处理该类组件的标签元素了。这样就解决css
作用域的问题了。
参考:https://https://github.com/6xiaoDi/blog-vue-Novice/tree/a1.58
Branch: branch04commit description:a1.58(example01-2——用scoped解决css无独立作用域的隐患)
tag:a1.58
<style>
/* 全局样式 */
style>
<style scoped>
/* 本地样式 */
style>
在项目开发中,我们经常会碰到要引入外部资源的需求,vue 单文件系统中,对资源引入路径有一定的特殊处理
\app\src\components\HelloWorld.vue
<template>
<div class="hello">
<p>这里是hello - {{msg}}p>
<img src="../assets/logo.png" alt="">
div>
template>
<script>
export default {
name: 'HelloWorld',
props: {
msg: String
}
}
script>
<style scoped>
p{
color: red;
}
style>
Webpack
会将其作为模块进行打包,然后把它构建生成到指定的目录里。
但现在有一个问题是,Webpack
有一个http
服务(Webpack server
),它生成后把所有的编译内容都是存在内存中的,没有真正地生成文件,在这里是找不到最终打包的东西的(都在内存中)。
如果想上线开发完成了,这个时候才能使用build
命令,对其进行构建,才能在硬盘中生成。
参考:https://https://github.com/6xiaoDi/blog-vue-Novice/tree/a1.59
Branch: branch04commit description:a1.59(example02——引入图片资源)
tag:a1.59
就是以 /
、https://www.csdn.net/
等这样的绝对路径开头的(不会被Webpack解析),不做任何处理,保持原样地址不动,直到被浏览器解析。
vue04\vue脚手架demo\app\src\components\HelloWorld.vue
<template>
<div class="hello">
<p>这里是hello - {{msg}}p>
<img src="/assets/logo.png" alt="">
div>
template>
<script>
export default {
name: 'HelloWorld',
props: {
msg: String
}
}
script>
<style scoped>
p {
color: red;
}
style>
现在暂时没有资源,所以访问不到图片。
这个时候就会保持原地址不变,直接交给浏览器处理,由于没这个资源,显然显示不出来。
实际上webpack
会指定一个根目录(public/index.html
),如果把它丢到public
文件夹里。
\app\src\components\HelloWorld.vue
<template>
<div class="hello">
<p>这里是hello - {{msg}}p>
<img src="/logo.png" alt="">
div>
template>
src/assets/
文件夹中文件是需要经过Webpack
打包处理的,作为模块打包的,而public
存放的文件是不需要经过Webpack
打包处理的,而是通过浏览器发送http
请求处理的资源。
参考:https://https://github.com/6xiaoDi/blog-vue-Novice/tree/a1.60
Branch: branch04commit description:a1.60(example03-1——相对路径引入图片资源)
tag:a1.60
线上地址也不会被webpack
处理,而是交给浏览器请求。
<template>
<div class="hello">
<p>这里是hello - {{msg}}p>
<img src="https://csdnimg.cn/cdn/content-toolbar/dragonBoat-white.gif" alt="">
div>
template>
参考:https://https://github.com/6xiaoDi/blog-vue-Novice/tree/a1.61
Branch: branch04commit description:a1.61(example03-2——引入网上图片资源)
tag:a1.61
就是以 ./
、../
这样的相对路径开头的,将会被看作相对的模块依赖,并按照你的本地文件系统上的目录结构进行解析,如:
解析后:
,类似的还包括 background: url(...)
和 @import
。
~
开头,其后的部分将会被看作模块依赖。这意味着你可以用该特性来引用一个 node(NPM 包) 依赖中的资源,如:
@
开头,也会被看作模块依赖。如果你的 webpack 配置中给 @
配置了 alias,这就很有用了。所有 vue-cli
创建的项目都默认配置了将 @
指向 /src
如果使用相当路径,如果修改了引用资源文件的路径,也得修改资源路径,这样使用也不太好。
以 @
开头,所有 vue-cli
创建的项目都默认配置了将 @
指向 /src
,也会被看作模块依赖。
无论我们的helloword
组件移动到哪,都不会受到影响,都是表示src
目录下的/assets/logo.png
。
\app\src\components\HelloWorld.vue
<template>
<div class="hello">
<p>这里是hello - {{msg}}p>
<img src="https://csdnimg.cn/cdn/content-toolbar/dragonBoat-white.gif" alt="">
<img src="@/assets/logo.png" alt="">
div>
template>
参考:https://https://github.com/6xiaoDi/blog-vue-Novice/tree/a1.62
Branch: branch04commit description:a1.62(example04——@特殊前缀的使用)
tag:a1.62
npx http-server --help
可看帮助
去npm搜指令:
npx http-server ./dist -c-l
官网有对应的配置:https://cli.vuejs.org/zh/config/#vue-config-js
我们之前用的都是默认配置,有的时候需要更改一些配置,这个时候可以单独创建一个vue.config.js
文件,类似webpack.config
。
Type: string
Default: '/'
部署应用包时的基本 URL。用法和 webpack 本身的 output.publicPath
一致,但是 Vue CLI 在一些其他地方也需要用到这个值,所以请始终使用 publicPath
而不要直接修改 webpack 的 output.publicPath
。
默认情况下,Vue CLI 会假设你的应用是被部署在一个域名的根路径上,例如 https://www.my-app.com/
。如果应用被部署在一个子路径上,你就需要用这个选项指定这个子路径。例如,如果你的应用被部署在 https://www.my-app.com/my-app/
,则设置 publicPath
为 /my-app/
。
这个值也可以被设置为空字符串 (''
) 或是相对路径 ('./'
),这样所有的资源都会被链接为相对路径,这样打出来的包可以被部署在任意路径,也可以用在类似 Cordova hybrid 应用的文件系统中。
相对 publicPath 的限制
相对路径的
publicPath
有一些使用上的限制。在以下情况下,应当避免使用相对publicPath
:
- 当使用基于 HTML5
history.pushState
的路由时;- 当使用
pages
选项构建多页面应用时。
这个值在开发环境下同样生效。如果你想把开发服务器架设在根路径,你可以使用一个条件式的值:
module.exports = {
publicPath: process.env.NODE_ENV === 'production'
? '/production-sub-path/'
: '/'
}
如:
\test01\app\vue.config.js
‘/’
—— 如果公司在一个项目上开发一些子项目,就可能有子目录了。// vue.config.js
module.exports = {
// 选项...
publicPath: '/Web/subindex'
}
yarn serve
Type: string
Default: 'dist'
当运行 vue-cli-service build
时生成的生产环境构建文件的目录。注意目标目录在构建之前会被清除 (构建时传入 --no-clean
可关闭该行为)。
提示
请始终使用
outputDir
而不要修改 webpack 的output.path
。
Type: string
Default: ''
放置生成的静态资源 (js、css、img、fonts) 的 (相对于 outputDir
的) 目录。
提示
从生成的资源覆写 filename 或 chunkFilename 时,
assetsDir
会被忽略。
如:
// vue.config.js
module.exports = {
// 选项...
publicPath: '/Web/subindex',
assetsDir: './publicSource'
}
yarn build
Type: string
Default: 'index.html'
指定生成的 index.html
的输出路径 (相对于 outputDir
)。也可以是一个绝对路径。
Type: boolean
Default: true
默认情况下,生成的静态资源在它们的文件名中包含了 hash 以便更好的控制缓存。然而,这也要求 index 的 HTML 是被 Vue CLI 自动生成的。如果你无法使用 Vue CLI 生成的 index HTML,你可以通过将这个选项设为 false
来关闭文件名哈希。
Type: Object
Default: undefined
在 multi-page 模式下构建应用。每个“page”应该有一个对应的 JavaScript 入口文件。其值应该是一个对象,对象的 key 是入口的名字,value 是:
entry
, template
, filename
, title
和 chunks
的对象 (除了 entry
之外都是可选的);entry
的字符串。module.exports = {
pages: {
index: {
// page 的入口
entry: 'src/index/main.js',
// 模板来源
template: 'public/index.html',
// 在 dist/index.html 的输出
filename: 'index.html',
// 当使用 title 选项时,
// template 中的 title 标签需要是 <%= htmlWebpackPlugin.options.title %>
title: 'Index Page',
// 在这个页面中包含的块,默认情况下会包含
// 提取出来的通用 chunk 和 vendor chunk。(首页当中提取的js)
chunks: ['chunk-vendors', 'chunk-common', 'index']
},
// 如果还有其他页面,可继续往下配置,根pages一样
// 当使用只有入口的字符串格式时,
// 模板会被推导为 `public/subpage.html`
// 并且如果找不到的话,就回退到 `public/index.html`。
// 输出文件名会被推导为 `subpage.html`。
subpage: 'src/subpage/main.js'
}
}
提示
当在 multi-page 模式下构建时,webpack 配置会包含不一样的插件 (这时会存在多个
html-webpack-plugin
和preload-webpack-plugin
的实例)。如果你试图修改这些插件的选项,请确认运行vue inspect
。
Type: Object
所有 webpack-dev-server
的选项都支持。注意:
host
、port
和 https
可能会被命令行参数覆写。publicPath
和 historyApiFallback
不应该被修改,因为它们需要和开发服务器的 publicPath 同步以保障正常的工作。Type: string | Object
如果你的前端应用和后端 API 服务器没有运行在同一个主机上,你需要在开发环境下将 API 请求代理到 API 服务器。这个问题可以通过 vue.config.js
中的 devServer.proxy
选项来配置。
devServer.proxy
可以是一个指向开发环境 API 服务器的字符串:
module.exports = {
devServer: {
proxy: 'http://localhost:4000'
}
}
这会告诉开发服务器将任何未知请求 (没有匹配到静态文件的请求) 代理到http://localhost:4000
。
如果你想要更多的代理控制行为,也可以使用一个 path: options
成对的对象。完整的选项可以查阅 http-proxy-middleware 。
module.exports = {
devServer: {
proxy: {
'/api': {
target: '' ,
ws: true,
changeOrigin: true
},
'/foo': {
target: ''
}
}
}
}
npm install vue-router
参考:https://https://github.com/6xiaoDi/blog-vue-Novice/tree/a1.63
Branch: branch04commit description:a1.63(安装vue-router)
tag:a1.63
我们看一下打包之前的index.js
源码
这里导出去了VueRouter
,这里面有一个方法是install
,其实既可以传函数,又可以传包装install
方法的对象,如果传的是函数,它就直接执行了。如果传的是对象,就会找对象底下的install
方法,去调用它。最终vue
的使用则是:Vue.use(VueRouter)
,之后就会执行它底下的install
方法了,进行插件安装了。
重点看install
方法源码实现 => 大概分析一下
import View from './components/view'
import Link from './components/link'
export let _Vue
export function install (Vue) {
// 判断插件是否安装了,如果安装了,并且_Vue === Vue,即use多次,不会重复执行install (单例模式)
if (install.installed && _Vue === Vue) return
install.installed = true
_Vue = Vue
// 判断变量v是否定义过
const isDef = v => v !== undefined
// 注册实例 vm:组件对象 vm.$options 组件对象的配置选项
// vm.$options._parentVnode 父组件
const registerInstance = (vm, callVal) => {
let i = vm.$options._parentVnode
// 判断父组件是否注册过,对i进行赋值,赋完值判断data是否存在,判断路由实例是否注册过
if (isDef(i) && isDef(i = i.data) && isDef(i = i.registerRouteInstance)) {
// 都判断过了,就会调用父组件的_parentVnode,并把vm组件对象实例及callVal传过去
i(vm, callVal)
}
}
// 做事,每一个组件及子组件,只要有组件一渲染就首先调用路由底下的beforeCreate方法
Vue.mixin({
beforeCreate () {
// 判断创建的Vue对象配置有没有router属性对象,如果有则进行设置及处理
if (isDef(this.$options.router)) {
this._routerRoot = this
this._router = this.$options.router
this._router.init(this)
Vue.util.defineReactive(this, '_route', this._router.history.current)
} else {
this._routerRoot = (this.$parent && this.$parent._routerRoot) || this
}
registerInstance(this, this)
},
// 销毁该实例
destroyed () {
registerInstance(this)
}
})
// $router 获取路由对象
Object.defineProperty(Vue.prototype, '$router', {
get () { return this._routerRoot._router }
})
// $route 获取当前匹配的路由对象
Object.defineProperty(Vue.prototype, '$route', {
get () { return this._routerRoot._route }
})
// 创建两个组件
Vue.component('RouterView', View)
Vue.component('RouterLink', Link)
// 创建相关生命周期
const strats = Vue.config.optionMergeStrategies
// use the same hook merging strategy for route hooks
strats.beforeRouteEnter = strats.beforeRouteLeave = strats.beforeRouteUpdate = strats.created
}