Vue-Loader 打包单文件组件实战

本文是我在学习过程中记录学习的点点滴滴,目的是为了学完之后巩固一下顺便也和大家分享一下,日后忘记了也可以方便快速的复习。


前言

今天学习的主要是关于webpack 结合 Vue-loader 来打包 Vue 单文件组件知识的理解和应用。主要内容包括什么是单文件组件、vue-loader 是什么,单文件组件结构及代码编写,利用 Vue-Loader 打包单文件组件,为什么要采用 render 函数来渲染,webpack 模块热替换(HMR)如何启用及使用。


一、webpack 结合 Vue-Loader 打包单文件组件基本认识

总结:
1、.vue文件是一个个的组件,里面的是被template标签包裹的内容,且template只能有一个子标签,但是可以有无数个孙标签。
2、安装好vue-loader 和 vue-template-compiler两个后需要在webpack.config.js配置相关信息,导入插件、实例化插件、配置规则。
3、需要把vue文件当作模块引入到main.js中然后打包运行。

1.1、什么是单文件组件

引出问题:
前 面 创 建 组 件 通 过 Vue.component ( 全 局 组 件 ) 或
compontents 选项(局部组件),并且写在 html 文件中的 js 部分,小规模项目这样写没问题,但是项目大这样很多组件全部写在一个html 文件中就非常不好。因此我们希望能够把组件内容单独提出来,后续需要使用组件把它作为模块去引入就好。

因 此 , vue 为 我 们 提 供 了 单 文 件 组 件 ( single-file
components):就是一种扩展名为 .vue 的文件,是 vue.js 自定义的一种文件格式,在文件内可以封装组件相关的 html、css、js。也就是说一个 vue 文件由三部分组成:
<template>——html 代码
<style>——css 样式
<script>——js 代码
当然不是三部分必须都有,但 html 代码部分必须有,其他二部分可以没有,也可以有其中一部分。

官方示例:hello.vue
在这里插入图片描述但是浏览器能够直接识别扩展名为.vue 的文件吗?
——不能。
需要采用 webpack 对其打包,打包成浏览器能够识别的文件。但是前面讲过 webpack 本身只能对 js 文件进行打包,那怎么办呢?
vue 官网推荐了 Vue-Loader 加载器

1.2、什么是 Vue-Loader

Vue Loader 是一个 webpack 的 loader(加载器),它允许你以一种名为单文件组件 (SFCs)的格式撰写 Vue 组件。
简单说:Vue-Loader 就是官方为 webpack 提供的一个加载器,用来配合 webpack 对单文件组件进行打包(成浏览器能够识别的文件)

1.3、手动配置 webpack 集成 vue-loader

(1)安装 vue-loader 和 vue-template-compiler
vue-template-compiler 是 vue 的编译器,所以要同步安装。
直接复制第 15 章的项目到第 16 章目录下。接着通过 vs 终端安装。即执行如下代码:
npm install -D vue-loader vue-template-compiler
(2)配置 webpack.config.js,在该文件中添加以下 3 处黄色底纹代码,其含义见注释。

//引用 node.js 中的 path 模块,用来处理文件路径
const path = require(“path”);
//引入插件 html-webpack-plugin
const HtmlWebpackPlugin = require(‘html-webpack-plugin’);
//1.导入 vue-loader 插件
const VueLoaderPlugin = require(‘vue-loader/lib/plugin’)

// 导出一个 webpack 具有特殊属性配置的对象
// 安装下 Node Snippets 插件,输入 module 会有智能提示
module.exports = {
mode: ‘none’,//指定打包为生产环境、开发环境或者设置 none
//入口
entry: ‘./src/main.js’,// 入口模块文件路径
//出口对象
output: {
// path 必须是一个绝对路径 , __dirname 是当前配置文件
webpack.config.js 的绝对路径。然后与输出目录 dist 拼接成一个决定路径
path: path.join(__dirname, ‘./dist’),
filename: ‘bundle.js’ },// 配置插件
plugins: [
new HtmlWebpackPlugin({
//指定要打包的模板页面 index.html,采用的是相对路径,与当前配置文件在同级目录,所以为./。就会找到把 index.html 文件并把它打包到与输出文件 bundle.js 的同级目录下
template: ‘./index.html’ }),
// 3.请确保引入这个插件!(实例化插件)
new VueLoaderPlugin()

],//实时重新加载
devServer: {
//在当前目的 dist 目录下查找文件
contentBase: ‘./dist’, },
module: {
rules: [ //配置 css 转 js 的规则
{
test: /.css$/,//正则表达式,匹配以 css 结尾文件
use: [ //下面 2 个加载器的顺序不能反
‘style-loader’,//让 javascript 识别转换后的 js(css)
‘css-loader’ //css 转为 js
]
},{
test: /.(png|svg|jpg|gif)$/, use: [
‘file-loader’ ]
},{//解决兼容性问题
test: /.m?js$/, exclude: /(node_modules)/,//排除 node_modules【是各种插件安
装目录】下的代码不用 babel_loader 去转换
use: {
loader: ‘babel-loader’, options: {
presets: [‘@babel/preset-env’]//babel 中内置的转换规则工
具,刚才配套一起安装的就还有这个
}
}
},{ //2.指定扩展名为.vue 的文件用 vue-loader 加载
test: /.vue$/,
loader: ‘vue-loader’ }

]
}
}

说明:第 3 步实例化 new VueLoaderPlugin() 插件是必须的。它的职责是将你定义过的其它规则复制并应用到 .vue 文件里相应语言的块。
例如,如果你有一条匹配 /.js$/ 的规则,那么它会应用到.vue 文件里的 <script> 块。

1.4、创建单文件组件

在 src 文件夹下新建一个单文件组件,比如:Hello.vue ,编写代码如下:

//有且只有一个根元素
<template>
<div>
<div>欢迎您学习 vue</div>
</div>
</template>
<script>
export default {
}
</script>
<style>
</style>

接下来把该单文件组件 Hello.vue 当做一个模块来引入

1.5、在 main.js 文件中把 Hello.vue 当做一个模块引入

把 main.js 原来有的代码先删除。输入下面代码导入Hello.vue文件
import Hello from ‘./Hello.vue’
然后执行下打包:
npm run start
编译没有报错,说明上面的导入语法就没有问题。

1.6、查看打包后的代码

进入打包后的文件bundle.js文件可以看到Hello.vue中的代码被打包成类似下面代码:
return _c(“div”, [_vm._v(“欢迎您学习 vue”)])
这个就是浏览器能够识别的(虽然我们不太认识)。

1.7、创建单文件组件(.vue)的快捷方法

(1)安装插件 Vetur(第 2 章已安装)
(2)配置模 Vetur 板代码片段
依次选择 “文件 ->首选项->用户代码片段”,此时,会弹出
一个搜索框,输入 vue , 默认内容如下:在这里插入图片描述把中间注释内容改为如下:

"Print to console": {
"prefix": "vue", 
"body": [ "<template>",
" <div>",
" $0", //指光标出现位置
" </div>", 
"</template>", 
"", 
"<script>", 
"export default {",
" data () {",
" return {",
" }", 
" },", 
"", 
" components: {},",
"", "}",
"</script>",
"", 
"<style scoped>",
"</style>"
],
"description": "Log output to console"
}

或者直接打开源代码下面有个 vue.json,复制内容即可。最后保存下即可。
(3)后面创建.vue 文件,创建好空白文件后,输入“vue”回
车或者 Tab 键,即可生成默认模板代码。

二、webpack 结合 Vue-Loader 打包单文件组件实战

总结:
在main中引入vue因为没有确切的指定版本,所以他会默认导入运行时版本(不具有编译功能),我们有两种解决方法,第一种就是指定确切版本,第二种就是在 webpack.config.js 配置好命令,使导入vue就是导入完整版的vue.js。

2.1、项目结构

webpack-demo1
|- index.html //单页面入口文件
|-src //源文件目录
	|-main.js //打包入口文件
	|-App.vue //根组件,替换 index.html 中的 app 处
	|-router.js //路由配置文件
	|-components //存放组件的文件夹
|-webpack.config.js //webpack 配置文件
|-package.json //项目配置文件
|-node_modules //安装项目各种依赖的目录

因此把Hello.vue重命名为App.vue;src文件夹下添加router.js文件;src 文件夹下添加 components 文件夹,项目大还可以在components 文件夹下再进行组件分类,建立不同的文件夹。
index.html 文件 body 节中添加:
<!-- 这是 vue 的入口 -->
<div id=“app”></div>

2.2、下载安装 vue,这里指定版本

npm install vue@2.6.1

2.3、如何引用 vue 文件【编写 main.js 文件代码】

原来我们都是通过下面代码引入 vue。
<script src=“js/vue.js”></script>
现在我们只要在 main.js 把 vue 作为一个组件引入即:

// Vue 是自己取的名称,一般 V 大写,后面的 vue 是安装的 vue 组件名称,不能更改。会自动到项目文件夹下的 node_modules 文件夹下去找,找哪个文件?vue.js???——后面告诉答案
import Vue from 'vue' //在 main.js 中要把 App.vue 作为其子组件使用,就要把它导入,并取名为 App 。./就表示当前目录
import App from './App.vue' //导入 Vue 之后就可以使用 Vue 了
new Vue({
el:"#app",//app 名称与 index.html 中 id 名称保持一致即可
//注册子组件
components:{ //上面导入了 App.vue,这里就把它作为components 的一个选项设置即可
// App:App 简写为 App
App
},//为 Vue 组件(根组件)设置模板
//表示用上面注册的组件 App 替换 index.html 里面的<div id="app"></div>
template:'<App />', //写成<app></app> a 大写或小写都可以的
})

2.4、打包项目 npm start

之后打开打包后的文件 bundle.js 文件。有 9000 多行代码,把vue 文件内容都打包进去了。
打开打包后的index.htm(l 即是 dist目录下的 index.html 文件),多了下面一句。
<script type=“text/javascript” src=“bundle.js”></script>

2.5、运行 index.html

访问 dist/index.html ,发现App组件没有被渲染出来,按 F12查看控制台发现报警告:
在这里插入图片描述警告大意:您使用是 Vue 仅运行时版本,其中模板编译器不可用。要么将模板预编译成呈现函数,要么使用包含编译器的 vue 进行
即是说:原因是因为在 main.js 导入 vue 版本有问题。原先我们引入 vue.js 是如下方式,我们实际发现 vue 是有很多版本,我们原先引入的是 vue.js 是完整版本(具有编译和运行功能)在这里插入图片描述这种方法导入的 vue 不是完整版本,不具有编译功能,即不能对 App.vue 进行编译,而我们需要编译成浏览器能够识别的 js 代码,这种方法导入是运行时版本。
也就是说,template 渲染的字符串,运行时版本 vue 无法解
析。
为什么说默认导入的是运行时版本的 vue 呢?因为
import Vue from ‘vue’ 导入的 vue 文件默认是
node_modules\vue\ package.json 中的 main 属性指定的文件,可以发现它并不是我们熟悉的 vue.js 完整版文件,import 的是运行时版本
【 “main”:“dist/vue.runtime.common.js”】,不是完整版。在这里插入图片描述但是我们不好对安装的依赖文件夹下的 node_modules\vue
package.json 中的 main 属性进行修改,因为只要后续用户一安装那个 vue 就会覆盖了。那怎么办呢?

2.6、解决组件内容没有渲染出来

因为默认 main.js 文件中导入的不是 vue 完整版,下面提供 2种解决方案:
第 1 种解决方法:
就在 main.js 文件中把导入的 vue 改为完整版本,即如下:
import Vue from ‘vue/dist/vue.js’
这里 vue 自然会找到根目录下的 node_modules 文件夹下的 vue。
然后重新打包 npm start,完了之后可以查看下打包后的
bundle.js 文件发现有 12000 多行,就是因为这时打包的是完整版的vue.js。
最后再重新运行 dist/index.html,发现 App.vue 组件中的内容
渲染出来了。在这里插入图片描述但是这种解决方案不是很好的。用的不多
第 2 种解决方案:
(1)main.js 导入 vue 的代码保持不变
import Vue from ‘vue’
(2)在 webpack.config.js 增加一个属性,放在最后一个 } 前即可。
……
, // 去引用完整版 vue.js

resolve:
{
alias:
{ ‘vue$’: ‘vue/dist/vue.js’ }
}
}

重新打包,npm start,打包之后之后可以查看下打包后的
bundle.js 文件发现有 12000 多行,就是因为这时打包的是完整版的vue.js。最后重新运行下 dist/index.html,发现 App.vue 组件中的内容渲染出来了。

三、持续改进——采用 render 函数渲染组

总结:
虽然使用vue.js完成版可以编译,但是它太大了,浪费性能,所以我们依旧使用默认的运行时版本,将编译任务交给 vue-loader 加载器,vue运行时只需要负责渲染功能就行。
之前引入组件都是使用template ,但是template 自
身没有渲染功能,最终渲染底层都是通过 render 函数实现的,但我们又要把 template 属性编译成 render 函数,编译过程有一定的性能损耗。所以可以直接使用运行时版本 vue ,然后直接通过render 函数来渲染组件即可。

3.1、总结上面 2 种解决子组件内容没有渲染出来的方案

(1)以上两种方法都可以解决。但是 vue 完整版比运行时 版本大,性能不如运行时版本 vue。
(2)官方更推荐运行时版本 vue,因为 vue-loader 可以编
译 .vue 文件,所以是不需要 vue 的编译功能的,只需要渲染功能即可。
(3)前面都是通过 template 属性引入组件,而 template 自
身没有渲染功能,最终渲染底层都是通过 render 函数实现的。因此需要把 template 属性编译成 render 函数,这个编译过程有一定的性能损耗。
因此,最佳方案是:
使用运行时版本 vue ,然后直接通过render 函数来渲染组件即可

3.2、改进 main.js

通过 template 属性导入 App 组件,改为直接通过 render 函数渲染。此外,通过 render 函数进行渲染组件,注册组件都可以不需要,因为通过作为 h 函数的参数,默认就是子组件。改动的代码如下最后的render。

// Vue 是自己取的名称,一般 V 大写,后面的 vue 是安装的 vue 组件名称,不能更改
//这种方法导入的 vue 不是完整版本,不具有编译功能.因为这 vue 是指安装的 node_modules\vue\ package.json 中的 main 属性执行的vue 版本 dist/vue.runtime.common.js。而这个版本是运行时版本,不具有编译功能,所以会有警告错误
import Vue from 'vue' //解决方法 1:手动引入完整版本
//import Vue from 'vue/dist/vue.js' //在 main.js 中要把 App.vue 作为其子组件使用,就要把它导入,并取名为 App
import App from './App.vue' //导入 Vue 之后就可以使用 Vue 了
new Vue({
el:"#app", /* //注册子组件
components:{ //上面导入了 App.vue,这里就把它作为
components 的一个选项设置即可
// App:App 简写为 App
App
}, */
//为 Vue 组件(根组件)设置模板
//template 没有编译和渲染功能,编译功能可以使用 vue-loader进行编译
//而渲染功能可以通过 render 函数进行,所以在此处只需要指定render 函数渲染组件即可
//template:'<App />', //<app></app> a 大写也可以的
render:function(h){ //h 是一个函数,这个函数用于接收要渲染的组件,一般就是根组件(App,也就是 App.vue,该组件中相关依赖的组件都会被渲染)
return h(App) //函数返回值就是渲染的结果
}
})

3.3、注释掉 main.js 中的下面的引入完整版的 vue.js 代码

/* // 去引用完整版 vue.js
resolve:
{
alias:
{ 'vue$': 'vue/dist/vue.js' }
} */

3.4、打包运行测试

npm star

浏览 dist/index.htm,结果没问题。

3.5、采用箭头函数优化下 render 函数

(1)上面 render 函数代码,可改为使用箭头函数,并通过$mount 挂载元素,即代码:

new Vue({
render: h => h(App), }).$mount('#app')

这时实例化 vue 代码实际上很简单,如下。含义可以这么理解:渲染子组件 App(相对 new Vue 这个 根组件)到 index.html 文件指定的 app 处。(如果要问为什么是渲染到 index.html 文件的 app处,因为在 webpack.config.js 中配置好了要打包的模板文件为main.js,而 main.js 中又引用了 App.vue 及 vue.js,所以实质就是把 main.js 和 App.vue 及 vue 一起打包到 bundle.js 文件中(如果还有引入/依赖了其他组件都会被一起打包的),同时打包后的bundle.js 文件被引入到 index.html 文件中【实际上可以这么理解这些组件和 index.html 组合在一起去了】,而这个时候在 bundel.js 中
指定的 app 就是当前文件 index.html 中的 app)。
强调一点:如果子组件还引入其他组件(即是组件依赖的组件),那么打包时会层层打包到各个依赖组件(根据依赖关系都会打包)。

四、完善改进——丰富 Vue 单文件组件

总结:
1、src下只能存在一个根组件,其余的组件都写在components 里面。
2、在根组件引入子组件且.vue后缀不能省:
import Childapp from “./components/Childapp.vue”;
export default {
//2.注册子组件
components: {
Childapp
}}
引入后还要在export default 中定义该子组件,default 指的就是这个根组件自身这组件。
使用子组件的话<Childapp></Childapp>和
<childapp />都可以
3、<style scoped></style>里面的scoped属性是指该样式是否应用到全局的意思,使用了这个属性可以使父子组件的样式不会互相影响。

上面例子中只有一个根文件组件 App.vue

4.1、新建子组件(也是单文件组件)

在 components 文件夹下面添加单文件组件 Childapp.vue,输入内容如下:

<template>
<div>
<h3>我是 App 的子组件</h3>
</div>
</template>
<script>
</script>
<style>
</style>

4.2、在根组件 App.vue 中引入子组件

<template>
<div>
<div>欢迎您学习 vue!!!</div>
<!--3.使用。下面 2 种写法都可以的 -->
<!-- <Childapp></Childapp> -->
<!-- <childapp /> -->
<Childapp />
</div>
</template>
<script>
//1.要使用某个组件,需要先导入,然后再使用。扩展名.vue 不能少
import Childapp from "./components/Childapp.vue";
export default {
//2.注册子组件
components: {
Childapp
}
};
</script>
<style>
</style>

4.3、打包浏览测试

npm start
或者
npm run dev(以服务方式运行),建议这种方式,更改 App.vue等组件内容,浏览器自动更新内容。
在这里插入图片描述运行成功!
如果 npm run dev 报如下错误:
在这里插入图片描述可能原因是端口被占用了

4.4、丰富下 App.vue组件

<template>
<div>
<div>欢迎您学习 vue!!!</div>
<!-- 下面 2 种写法都可以的 -->
<!-- <Childapp></Childapp> -->
<!-- <childapp /> -->
<Childapp />
{{msg}}
</div>
</template>
<script>
//要使用某个组件,需要先导入,然后再使用
import Childapp from "./components/Childapp.vue";
//导出一个默认成员对象,它就是当前组件对象,可以直接在对象中使用 Vue 的选项,data、methods、components、watch、钩子函数等,template 选项不需要,因为 template 选项的内容就是上面<template>标签的内容
export default {
//注册子组件
components: {
Childapp
},data(){
return {
msg:"徐照兴欢迎您!!"
}
}
};
</script>
<style>
</style>

打开浏览器查看,即发现多了 msg 内容“徐照兴欢迎您
注意:组件的 template 标签下都要有且只有一个根元素(一般 就用 div),否则可能会导致 scoped 属性失效。 作用:在 Vue 文件中的 style 标签上有一个特殊的属性,scoped。 当一个 style 标签拥有 scoped 属性时候,它的 css 样式只能用于当 前的Vue 组件,可以使组件的样式不相互污染

五、webpack 模块热替换(HMR)(局部更新)

该部分内容只是对开发人员有用,可以实时局部自动更新,可以提高开发效率。

参考官网:
https://www.webpackjs.com/guides/hot-module-replacement/

5.1、模块热替换

模块热替换(Hot Module Replacement 或 HMR)是
webpack 提供的最有用的功能之一。它允许在运行时更新各种模块,而无需进行完全刷新【即可以做到局部刷新】。
HMR 不适用于生产环境,这意味着它应当只在开发环境使用。与上一章讲的使用 webpack-dev-server 插件实现自动打包和刷新页面的区别是:使用 webpack-dev-server 实现刷新页面是当有内容修改时会刷新整个页面。而模块热替换是局部刷新内容更改处。

5.2、启用 HRM 步骤

5.2.1、安装 webpack-dev-server

本项目已安装(安装在前面讲过)

5.3、配置 webpack.config.js

三个步骤导入 webpack、启动模块热替换、实例化模块热替换插件,如下黄色底纹代码:
//(1)导入 webpack,模块热替换需要
const webpack = require(‘webpack’);
// 配置插件
plugins: [
new HtmlWebpackPlugin({
//指定要打包的模板页面 index.html,采用的是相对路径,与当
前配置文件在同级目录,所以为./。就会找到把 index.html 文件并把它
打包到与输出文件 bundle.js 的同级目录下
template: ‘./index.html’ }), // 3.请确保引入这个插件!
new VueLoaderPlugin(),
//(3)实例化模块热替换插件
new webpack.HotModuleReplacementPlugin()

],//实时重新加载
devServer: {
//在当前目的 dist 目录下查找文件
contentBase: ‘./dist’,
hot: true //(2)开启模块热替换
},

5.4、测试效果

说明:这个测试启用只能用 npm run dev
因为 dev 才是通过 webpack-dev-server 去启用服务,这个时候采用使用模块热加载。见 package.json 中配置:
“dev”: “webpack-dev-server --open”
假设修改下 Childapp.vue 或 App.vue 组件的内容,保存下,就会自动重新编译,稍等一会就看到更新后的页面内容,但是并不是整个页面刷新。注意观察:刷新按钮不会转动。【要接收下原来的运行重新执行 npm run dev】

模块热替换只是针对模块(单文件组件),如果更新了
index.html 或 js 文件(main.js)并不会立即更新,需要重新打包后重新浏览才会看到修改后的结果。

💕 原 创 不 易 , 还 希 望 各 位 大 佬 支 持 一 下 \textcolor{blue}{原创不易,还希望各位大佬支持一下}

👍 点 赞 , 你 的 认 可 是 我 创 作 的 动 力 ! \textcolor{orange}{点赞,你的认可是我创作的动力!}

收 藏 , 你 的 青 睐 是 我 努 力 的 方 向 ! \textcolor{red}{收藏,你的青睐是我努力的方向!}

🥕 评 论 , 你 的 意 见 是 我 进 步 的 财 富 ! \textcolor{green}{评论,你的意见是我进步的财富!}

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小小福仔

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值