文章目录
一、Vue 模板编译版和运行时版
1. Vue 提供的各个版本
Vue 的 NPM 包中有多个版本供我们实际使用时选择,路径为 node_modules\vue\dist
,内容如下:
虽然看上去版本非常多,但其实可以按照文件名规则对其进行归类,分类规则如下:
环境 | 符合 CommonJS 规范 | 符合 ES6 规范 | 符合 CommonJS + ES6 规范 |
---|---|---|---|
模板编译版本 | 前缀为:vue.common | 前缀为:vue.esm | vue.js 和 vue.min.js |
运行时版本 | 前缀为:vue.runtime.common | 前缀为:vue.runtime.esm | vue.runtime.js 和 vue.runtime.js |
Webpack 打包时,默认使用的是 符合 CommonJS 规范的运行时版本
, 详见路径:node_modules\vue\package.json
2. 从模板解析到渲染的大致流程
介绍几个概念后再理解图中的流程就简单多了:
- AST:抽象语法树(Abstract Syntax Tree),简单来说就是个树形结构的对象,HTML 模板字符串最终会转成该结构对象
- Render 函数:其函数体是通过递归 AST 树状对象动态生成的,该函数执行后会构建出 Virtual DOM
- Virtual DOM:又是一个树状对象,因为其出现的目的是减少 DOM 操作,所以又叫它虚拟 DOM
如果想详细的学习整个渲染原理,还是得阅读源码和源码分析的文章,推荐个学习地址 - Vue 源码系列 - Vue 中文社区
3. 模板编译版和运行时版的区别
前面我们已经知道了,从模板解析到渲染的大致流程,而那正是模板编译版的流程,运行时版本的流程和它是有区别的,流程如下图:
可以看出来,运行时版本相较模板编译版本,少了两个步骤:
- 把 HTML 模板 转成 AST 对象
- 递归 AST 对象,动态生成 Render 函数体
这样有一个优点,就是运行时版本没有 AST 相关代码,所以代码体积更小,适合生产环境,但同时又有一个问题,图中可以看出运行时版本是直接执行 Render 函数的,而 Render 函数的函数体,应该是基于 AST 动态生成,现在没有 AST 了,就代表 Render 函数没有函数体,那应该怎么办 ?
解决方式很简单,既然没有函数体,那么我们就手动为 Render 函数指定一个函数体,在 Vue 对象的 options
中有一个可选函数 render
,它会被 Vue 回调,这就是我们指定 Render 函数体的地方
1) options.render
函数在被调用时,会被传入一个参数 createElement
,所以可以将函数体声明为:
new Vue({
el:'#app',
render: function(createElement){
return null
}
})
// 或简写为箭头函数
new Vue({
el:'#app',
render: createElement => {
return null
}
})
2) 参数 createElement
也是一个函数,其用来在虚拟 DOM 上创建元素
createElement 的语法:createElement(element, properties, children)
,应用举例:
// 场景 1:对应 html 为,
// <div id='container' style='border:1px solid black; width:100px; height:100px'> 这是内容 </div>
new Vue({
el: '#app',
render: (createElement) => {
return createElement(
'div', {
id: 'container',
style: 'border:1px solid black; width:100px; height:100px'
},
['这是内容'])
}
})
// 场景 2:对应 html 为,
// <div id='container' style='border:1px solid black; width:100px; height:100px'>
// <span id="content">这是内容</span>
// </div>
new Vue({
el: '#app',
render: (createElement) => {
return createElement('div', {
id: 'container',
style: 'border:1px solid black; width:100px; height:100px'
}, [createElement('span', {id: 'content'}, ['这是内容'])])
}
})
所以当选择使用运行时版本的场景,一定要注意,不要使用 template
属性,而要手动指定 render
函数体
二、Webpack 中使用 Vue
通过往期的文章,已经有了 npm 和 webpack 的基础,现在我将一步步记录它们搭配 vue 的细节
1. 搭配 Vue 最原始的方式
1) 初始化项目,并安装必要依赖
创建项目文件夹,并在根目录通过 CMD 依次执行如下命令:
# 初始化为 npm 管理的项目
npm init -y
# 安装 webpack 相关依赖
npm i webpack webpack-cli -D
# 安装 vue
npm i vue --save
2) 创建入口 JS 文件
创建 项目/src/index.js
文件(默认入口),并在文件内导入 Vue 模块和使用 Vue 对象,内容如下:
import Vue from 'vue'
new Vue({
el: '#app',
data: {
message: '通过 webpack 引入的 vue 模块,已被正确使用!!!'
}
})
3) 创建 HTML 文件,并预先引入出口文件
创建 项目/index.html
文件,并预先引入出口文件( 此处我们使用的是默认出口,路径为 项目/dist/main.js
),最后在HTML 文件内使用 Vue 语法,目的是后期验证 Vue 语法否能被正确解析
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>vue-study</title>
<style>
p {
width: 800px;
height: 18px;
line-height: 18px;
border: 1px solid black;
background-color: gray;
}
</style>
</head>
<body>
<div id="app">
<p>这是 Vue 语法转换后的内容:{{message}}</p>
</div>
<script src="./dist/main.js"></script>
</body>
</html>
4) 修改 npm 的配置文件
修改 package.json
文件, 添加自定义脚本 "build":"webpack"
, 之后就可以通过 npm run build
来运行 webpack
{
"name": "vue-study",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build":"webpack"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"webpack": "^5.65.0",
"webpack-cli": "^4.9.1"
},
"dependencies": {
"vue": "^2.6.14"
}
}
5) 添加 webpack 的配置文件
将配置文件创建在 webpack 的默认查找路径中 项目/webpack.config.js
,Vue 默认使用的是运行时版本,本例要使用模板编译版本,所以要在这个文件中指定。当然如果使用运行时版本就不需要这个文件了,但不要忘了定义 render
函数
module.exports = {
mode: 'development',
resolve: {
alias: {
'vue$': 'vue/dist/vue.esm.js'
}
}
}
6) 通过 webpack 打包,并确认目录结构
项目根目录中通过 CMD 运行 npm run build
命令,进行 webpack 打包,打包后的项目结构如下:
7) 运行 HTML ,验证 Vue 语法是否被正确解析
2. 使用 template 属性代替挂载元素中的内容
我们前面学习 Vue 组件时知道,Vue 对象本身也可以视为一个组件,既根组件,所以它的 options
中,也包含template
属性,而根组件的 template
的内容很特殊,它会在运行时替换 el
属性挂载的元素的原始内容
现将前例做如下修改:
1) 修改 HTML
删除 index.html
中 #app
元素的内容,修改后如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>vue-study</title>
</head>
<body>
<div id="app"></div>
<script src="./dist/main.js"></script>
</body>
</html>
2) 修改入口 JS 文件
修改 index.js
,将原 #app
中的内容,写在 template
属性中,修改后如下:
import Vue from 'vue'
new Vue({
el: '#app',
template: '<p style="width: 800px;' +
'height: 18px;' +
'line-height: 18px;' +
'border: 1px solid black;' +
'background-color: gray;">这是 Vue 语法转换后的内容: {{message}}</p>',
data: {
message: '通过 webpack 引入的 vue 模块,已被正确使用!!!'
}
})
3) 打包编译,查看运行结果
这种方式可以将 HTML 文件变得非常整洁干净,文件中只有基本的 HTML 骨架代码、引入出口文件的语句和待挂载的空元素三类内容,可能很好奇,让 HTML 变得这么简洁有什么意义吗,其实这是为了迎合单页面应用的思想,单页面应用的具体概念会在后续的路由篇章介绍
三、Vue-Loader 和 .Vue 文件
前例中我们将布局内容都放到了 Vue 对象的 template
属性中来保持 HTML 文件的简洁,但这会带来一个问题
前面学 Vue 组件化 时知道, 布局代码写在 template
属性中,不易读也不易维护,所以我们当时使用了模板抽离来提升可维护性,模板抽离依托于 HTML 文件,而我们现在为了保证 HTML 文件的简洁,所以不能对其修改,那该怎么做呢?
1. 创建 .Vue 格式文件
为了解决这个问题,Vue 提供了新的文件格式来实现模板抽离,既 .Vue
格式,其文件语法很简单,分为三部分:
<template>
<!-- 第一部分:此处用来写 HTML 代码 -->
</template>
<script>
// 第二部分:此处用来写 JS 代码
</script>
<style>
/* 第三部分:此处用来写 CSS 代码 */
</style>
现在对之前的例子做修改:
1) 创建 .vue 文件
创建新的目录和文件,例中目录为 src/vue/App.vue
,内容如下:
<template>
<div>
<p>这是 Vue 语法转换后的内容:{{ message }}</p>
</div>
</template>
<script>
// 导出组件对象
export default {
data() {
return {
message: "通过 webpack 引入的 vue 模块,已被正确使用!!!",
}
}
}
</script>
<style scoped>
p {
width: 800px;
height: 18px;
line-height: 18px;
border: 1px solid black;
background-color: gray;
}
</style>
这种文件格式,将代码清晰的分成三部分,非常利于阅读和维护,其中 <style scoped>
中的 scoped
代表样式的作用域,加上后,只有当前组件受影响,如果不写,当多个组件拼成一个页面时,可能会影响其他组建的样式
2) 修改 index.js 文件
以模块的方式,导入.vue
文件中导出的 JS 对象,并以组件的方式注册到 Vue 对象中,实现如下:
import Vue from 'vue'
import App from './vue/App.vue'
new Vue({
el: '#app',
template: '<App></App>',
components: {
App
}
})
3) 确认当前项目结构
2. 使用 vue-loader 预编译 .vue 格式文件
我们在 index.js
文件中,使用 ES6 语法导入了 Vue 模块(既 .vue
格式文件),但 ES6 语法并不认识 .vue
格式文件,所以是不能直接使用 webpack 命令进行编译打包的,这个时候我们就要借助加载器,对入口文件中的特殊语法进行转译
1) 安装 vue-loader 加载器
# 安装 vue-loader 相关加载器,样式相关加载器也必须安装
npm i vue-loader vue-template-compiler css-loader style-loader -D
2) 修改 webpack 配置文件
修改 webpack.config.js
配置文件,添加 vue-loader 和 样式相关加载器的配置信息
const VUE_LOADER_PLUGIN = require('vue-loader/lib/plugin')
module.exports = {
mode: 'development',
resolve: {
alias: {
'vue$': 'vue/dist/vue.esm.js'
}
},
module: {
rules: [{
test: /\.vue$/,
use: ['vue-loader']
},
{
test: /\.css$/,
use: ['style-loader', 'css-loader']
}
]
},
plugins: [
new VUE_LOADER_PLUGIN()
]
}
3) 编译打包,验证结果
运行 npm run build
打包,并运行 index.html
查看结果
四、结语
webpack
+ .vue 格式文件
+ vue-loader
的方式,可以让我们未来实现单页面应用时,代码更清晰易读、易维护
我们想完成一个产品级的 Vue 项目搭建,需要手动创建项目结构,手动配置需要的 webpack 加载器和插件,这样做不仅很繁琐,而且配出来的也不一定是最优选择,之后我们要学习能帮我们自动搭建产品级 Vue 项目的工具 Vue-CLI
它会自动帮我们创建出大部分项目都在使用的项目结构及更严谨的 webpack 配置文件
不同版本可能配置会有差异,下面列出本文使用的包版本:
{
"devDependencies": {
"css-loader": "^6.5.1",
"style-loader": "^3.3.1",
"vue-loader": "^15.9.8",
"vue-template-compiler": "^2.6.14",
"webpack": "^5.65.0",
"webpack-cli": "^4.9.1"
},
"dependencies": {
"vue": "^2.6.14"
}
}