webpack5-vue篇(六)
前言
若文章有误,欢迎读者留言反馈💻Installation1
git clone https://github.com/coding327/learn_webpack5.git
webpack-vue篇
前面我们已经对于js
代码进行打包了,其实我们编写的vue
代码也是属于js
代码
在webpack
中,我们是通过模块去安装vue
来使用它,这和CDN
引入或者说把vue
下载到本地,通过script
再引入使用有点不同
因为在webpack
我们是通过模块的方式来使用vue
,所以这里我们安装一下vue
核心代码包1
2
3# vue3已经是默认版本了,所以这里没指定版本号,另外-S可以省略是因为npm5.0+开始默认会加入到生产环境
# 这里解释一下生产环境,最终打包部署到静态服务器,用户下载的时候还是需要vue相关的核心代码的
npm install vue
接着进入src/main.js
文件中,去使用vue
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26// 在webpack中文件后缀可以省略,它会自动帮我们加上去
import { sum } from "./js/math"
// 从vue中引入createApp
import { createApp } from "vue"
const {priceFormat} = require('./js/format')
// 使用import引入文件,和main.js产生依赖关系
import "./js/element"
console.log(sum(20, 30))
console.log(priceFormat())
// 由于已经拿到createApp,这里我们就可以使用了
// 编写vue代码
const app = createApp({
template: `<h2>Hello World</h2>`,
data() {
return {
message: "Hello World"
}
}
})
// 由于index.html中已经有咱们的挂载容器了,直接放选择器在里面
app.mount("#app")
我们写的这一系列vue
代码是写在js
文件【先不说vue
文件】中的,它本质也是js
代码,所以应该是可以打包的
来打包测试一下,运行咱们打包文件夹里的index.html
,并没有把我们模板里的Hello World
给渲染出来,但是打包没有报错,也就是说渲染显示有问题
然后打开浏览器控制台再来看一下,发现有两条信息,
报错信息如下:
这里直接说一下第一个并不是主要原因,先看第二个有warn
的警告信息
大概意思就是:组件提供了模板选项,但是runtime compilation
不支持打包的vue
,然后需要配置
个人解释:我们编写的代码里面有个template
,Vue
源代码会对其进行解析,其实Vue
源代码给我们提供了特别多的这个版本,所有的版本又把它分为了以下两类:
Vue
版本一:runtime+compiler
Vue
版本二:runtime-only
这个版本一中的compiler
它的功能就是对template
来做编译的,但是它默认用的是版本二,这个runtime-only
不包含对template
的编译
关于它Vue
的这个源代码打包后,它可不止两个版本,这个我们可以去node_modules/vue
下找到这个打包的dist
文件夹,里面就有很多不同的版本
Vue源代码打包后不同版本解析
vue(.runtime).global(.prod).js
:- 通过浏览器中的
<script src= ".”>
直接使用【vue.global.js
】; - 我们之前通过
CDN
引入和下载的Vue
版本就是这个版本【vue.global.js
】; - 会暴露一个全局的
Vue
来使用; - 关于
(.runtime)
即vue.runtime.global.js
,(.runtime)
可有可无,本来的包是vue.global.js
,但是如果你只想用runtime
的版本不包含compiler
【不需要对template
做编译】,打包的时候它就会更小一点,到时候引入【vue.runtime.global.js
】; - 关于
(.prod)
即vue.global.prod.js
,(.prod)
可有可无,prod
表示的是production
版本,它是做过压缩的 ;
- 通过浏览器中的
vue(.runtime).esm-browser(.prod).js
:- 用于通过原生
ES
模块导入使用(在浏览器中通过<script type="module">
来使用) ;
- 用于通过原生
vue(.runtime).esm-bundler.js
:- 用于
webpack
,rollup
和parcel
等构建工具 ; - 构建工具中默认是
vue.runtime.esm-bundler.js
; - 如果我们需要解析模板
template
,那么需要手动指定vue.esm-bundler.js
;
- 用于
vue.cjs(.prod).js
:- 服务器端渲染使用 ;
- 通过
require()
在Node.js
中使用 ;
这里我们就去指定版本,回到main.js
文件中1
2// 从vue中引入createApp,这里指定一下版本
import { createApp } from "vue/dist/vue.esm-bundler"
这时我们再重新打包运行一下,发现咱们的模板就显示出来了
运行时+编译器 vs 仅运行时
- 在
Vue
的开发过程中我们有三种方式来编写DOM
元素︰- 方式一:
template
模板的方式(之前经常使用的方式); - 方式二:
render
函数的方式,使用h
函数来编写渲染的内容; - 方式三: 通过
.vue
文件中的template
来编写模板;
- 方式一:
- 它们的模板分别是如何处理的呢?
- 方式二中的
h
函数可以直接返回一个虚拟节点,也就是Vnode
节点; - 方式一和方式三的
template
都需要有特定的代码来对其进行解析∶- 方式三
.vue
文件中的template
可以通过在vue-loader
对其进行编译和处理 - 方式一中的
template
我们必须要通过源码中一部分代码来进行编译;
- 方式三
- 方式二中的
- 所以,
Vue
在让我们选择版本的时候分为运行时+编译器 vs 仅运行时- 运行时+编译器包含了对
template
模板的编译代码,更加完整,但是也更大一些; - 仅运行时没有包含对
template
版本的编译代码,相对更小一些;
- 运行时+编译器包含了对
真实开发中,我们是不可能在配置项里的
template
中写很多代码的,既没有代码高亮,太多堆积在配置项中也不好
所以这里我们先尝试把这个template
中的这么多模板代码转移到public/index.html
中1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<html lang="">
<head>
<meta charset="utf-8">
<meta http-equiv="X一UA-Compatible" content="TE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<title><%= htmlWebpackPlugin.options.title %></title>
</head>
<body>
<noscript>
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
<template id="my-app">
<h2>Hello World</h2>
<h2>{{message}}</h2>
</template>
<!-- built files will be auto injected-->
</body>
</html>
接着我们还要做一个绑定,回到main.js
文件中1
2
3
4
5
6
7
8
9
10
11
12
13
14...
// 由于已经拿到createApp,这里我们就可以使用了
// 编写vue代码
const app = createApp({
// template: `<h2>Hello World</h2>`,
template: "#my-app", // 把上面模板代码抽到index.html中,同时做绑定
data() {
return {
message: "Hello World"
}
}
})
// 由于index.html中已经有咱们的挂载容器了,直接放选择器在里面
app.mount("#app")
接着进行打包,打包成功,浏览器运行也没有什么问题
但是它还是有弊端,一个是在html
文件里面,一个是在main.js
文件里面,这个源代码相当于分开的,当我们在数据仓库中添加数据时,还要跑到另外一个文件中去编写,并且当我们组件比较多时,还要在html
文件中写一大堆template
,结构就会太混乱了
之前也说过createApp
里的配置项就是个组件【并且它还是个根组件】,我们想把这个根组件里的模板,加上逻辑,以及样式,把这三个整合到一个文件里面,也就是.vue
文件,这个文件也称为SFC
文件【single-file-components
(单文件组件)】
VSCode对SFC文件的支持
- 在前面我们提到过,真实开发中多数情况下我们都是使用
SFC
(single-file components
(单文件组件) ) - 我们先说一下
VSCode
对SFC
的支持:- 插件一:
Vetur
,从Vue2
开发就一直在使用的VSCode
支持Vue
的插件 ; - 插件二:
Vue Language Features(Volar)
,官方推荐的插件(后续会基于Vue Language Features(Volar)
开发官方的VSCode
插件);
- 插件一:
补充几个插件使用注意事项:
Vetur
、Vuter
和Vue Language Features(Volar)
插件vue2
我们使用的插件是vetur
或vuter
,vue3
使用的是Vue Language Features(Volar)
这个插件,注意使用哪个vue
版本就使用哪个插件
我们在src
下创建一个vue
文件夹,接着创建一个App.vue
文件,在这里面可以编写模板(template
)、逻辑(script
)、样式(style
)
把代码转移到这个vue
文件中:
- 把我们
index.html
之前写的模板中的内容移到App.vue
文件中; - 把
main.js
文件中data
配置项移到App.vue
文件中;
App.vue
代码如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23<template>
<h2>Hello World</h2>
<h2>{{ message }}</h2>
</template>
<script>
export default {
data() {
return {
message: "Hello World"
}
},
methods: {
}
}
</script>
<style>
h2 {
color: red;
}
</style>
main.js
导入根组件并作为配置项传入createApp
中;1
2
3
4
5
6
7
8
9
10
11
12
13
14// 在webpack中文件后缀可以省略,它会自动帮我们加上去
import { sum } from "./js/math"
// 从vue中引入createApp,这里指定一下版本
import { createApp } from "vue/dist/vue.esm-bundler"
// 导入根组件【注意后缀名.vue不要掉】
import App from "./vue/App.vue"
...
const app = createApp(App)
// 由于index.html中已经有咱们的挂载容器了,直接放选择器在里面
app.mount("#app")
重新打包,发现报错,其实我们也能想明白,你现在是一个.vue
文件,webpack
肯定是不识别的,就像之前css
文件,也就是需要loader
,就是vue-loader
关于vue-loader
安装vue-loader
1
npm install vue-loader -D
安装完vue-loader
,就需要去配置rules
,回到webpack.config.js
文件中1
2
3
4{
test: /\.vue$/,
loader: "vue-loader"
}, // 加载vue所需规则
再次打包,发现如下报错信息vue-loader was used without the corresponding plugin. Make sure to include VueLoaderPlugin in your webpack config.
大致意思是说,确保你的webpack
配置中包含VueLoaderPlugin
插件
这里我们需要从vue-loader
里面引入这个插件,并使用这个插件,回到webpack.config.js
文件中1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49...
// 引入VueLoaderPlugin插件,插件贯穿于整个webpack生命周期,它可以帮助vue-loader做一些事情
const { VueLoaderPlugin } = require("vue-loader/dist/index")
...
module.exports = {
...
module: { // 配置module
rules: [ // 注意rules是数组,以后会有多个规则
...
{
test: /\.vue$/,
loader: "vue-loader"
}, // 加载vue所需规则
{}, // 加载ts需要规则
// {
// test: /\.(css|less)$/,
// use: [
// "style-loader",
// "css-loader",
// "less-loader"
// ]
// }, // css、less合并写法
]
},
plugins: [
new CleanWebpackPlugin(), // 格式:根据CleanWebpackPlugin类创建出对象
new HtmlWebpackPlugin({
template: "./public/index.html",
title: "巧克力真美味" // 模板里的htmlWebpackPlugin.options.title中的htmlWebpackPlugin是new出来的对象,options就是传入的配置项,title就是我们这里配置的title
}), // 可以传入一个指定模板【不指定它有个默认模板】
new DefinePlugin({
BASE_URL: '"./"'
}), // 定义BASE_URL的值,注意这个引号里还要再加个引号,有点类似eval,会把引号里面内容当js语法解析
new CopyWebpackPlugin({
patterns: [
{
from: "public", // 从哪个文件夹里复制
to: "./", // 复制到哪个文件夹【注意它是在打包文件夹基础上,这里也可以不写,默认就是打包文件夹】
globOptions: {
ignore: [
"**/index.html" // 注意这两个**表示当前public文件夹下index.html以及子文件夹下的index.html
]
} // globOptions.ignore忽略某个文件,可以过滤掉某些不需要复制的文件如index.html模板
}
] // patterns匹配的意思
}), // 复制功能插件
new VueLoaderPlugin() // vue-loader插件,帮助vue-loader做一些事情
] // plugins是个数组【这个不用管顺序】,里面放的是一个个插件对象【其实它源码里面是拿到我们导出的这个大的对象,然后去取到我们所有的plugins,之后对它做了个for循环,for循环后对它做个注入,到时候就可以根据不同的hook的生命周期来回调这个插件里面对象的某个方法】
}
然后重新打包就能成功了,浏览器运行显示正常
真实开发是按照组件化开发,这里我们可以接着在vue
文件夹参创建一个HelloWorld
组件,注意组件名要使用大驼峰,编写一点代码
我打算把它先作为全局组件:
1 | <template> |
接着我们选择全局注册
- 全局注册是在
main.js
文件中,需要使用到app
应用实例的扩展方法1
2
3
4
5
6
7
8
9
10
11// 导入根组件【注意后缀名.vue不要掉】
import App from "./vue/App.vue"
// 导入HelloWorld组件
import HelloWorld from "./vue/HelloWorld.vue"
const app = createApp(App)
// 注册全局组件
app.component("HelloWorld", HelloWorld)
// 由于index.html中已经有咱们的挂载容器了,直接放选择器在里面
app.mount("#app")
在App.vue
根组件中使用,注意官方文档有说过,大驼峰命名的组件在使用组件标签时是可以使用短横线连接的1
2
3
4
5
6
7<template>
<h2>Hello World</h2>
<h2>{{ message }}</h2>
<hello-world></hello-world>
</template>
...
重新打包,在浏览器运行正常
在到
vue
文件夹中创建一个NavBar.vue
组件,来作为局部组件
1 | <template> |
在App.vue
文件中进行局部注册并使用1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31<template>
<h2>Hello World</h2>
<h2>{{ message }}</h2>
<hello-world></hello-world>
<NavBarVue />
</template>
<script>
import NavBarVue from './NavBar.vue';
export default {
components: {
NavBarVue
},
data() {
return {
message: "Hello World"
}
},
methods: {
}
}
</script>
<style scoped>
h2 {
color: red;
}
</style>
重新打包,在浏览器运行正常
最后,还有一个问题,就是浏览控制台一直有一个警告,如下
1 | Feature flags __VUE_OPTIONS_API__, __VUE_PROD_DEVTOOLS__ are not explicitly defined. |
通过他给的链接,我们可以了解到:Bundler
构建功能标志
从 3.0.0-rc.3
开始,esm-bundler
构建现在公开了可以在编译时覆盖的全局功能标志:
__VUE_OPTIONS_API__
(启用/禁用选项API
支持,默认值true
:)__VUE_PROD_DEVTOOLS__
(在生产中启用/禁用devtools
支持,默认值false
:)
该构建将在不配置这些标志的情况下工作,但是强烈建议正确配置它们以便在最终捆绑包中获得正确的 tree-shaking
。要配置这些标志:
webpack
:使用DefinePlugin
- 汇总:使用
@rollup/plugin-replace
Vite
:默认配置,但可以使用define
选项覆盖
注意:替换值必须是布尔值,不能是字符串,否则捆绑器/压缩器将无法正确评估条件。
__VUE_OPTIONS_API__
:它是来对vue2
做适配的,其实现在写的template
和data
都是options api
,在vue3
写的比较少,用的是setup
,那么在我们项目里面到底有没有这个东西呢,它默认情况下是true
,即是有这个东西的,那么到时候vue
源代码里面是有一部分来做这个options api
解析的代码的,但是如果你vue3
写的都是setup
代码,我就不需要options api
这部分代码了,它推荐我们可以设置为false
,它到时候可以做tree-shaking
,警告里面有这个词,tree-shaking
它在真正打包的时候可以把我们这部分代码本来是有的但是我发现你不需要有这个东西,它就会把我们这部分代码从我们源代码里面删除掉,那我们代码就可以变得更小一点
__VUE_PROD_DEVTOOLS__
:生产环境要不要做devtool
,它其实是一个vue
调试工具,调试工具一般在开发阶段使用,生产环境一般是不需要让它生效的,它刚好默认值就是false
,如果你想要生产环境生效就设置为true
怎么去除这个警告呢?
其实它上面也有写,webpack
:使用DefinePlugin
,这个DefinePlugin
之前我们有给index.html
设置BASE_URL
即favicon.ico
,这里回到webpack.config.js
文件中配置1
2
3
4
5
6
7
8
9
10
11
12
13
14
15...
// `DefinePlugin`插件,这个插件是`webpack`内置的一个插件
const { DefinePlugin } = require("webpack")
module.exports = {
...
plugins: [
...
new DefinePlugin({
BASE_URL: '"./"',
__VUE_OPTIONS_API__: true, // 这个就是开启options api,如果都是setup,vue3代码,可以关闭,减小vue源码体积
__VUE_PROD_DEVTOOLS__: false // 这个默认就是false,可以不用设置
}), // 定义BASE_URL的值,注意这个引号里还要再加个引号,有点类似eval,会把引号里面内容当js语法解析
...
}
这时候我们再次打包就发现警告消除了
小细节说一下
在我们以.vue
文件书写模板时,template
不再是那种原先的配置项中的属性了【它原先解析还需要依靠特定的版本】,这种.vue
文件最主要的是,他那个vue-loader
会依赖一个@vue/compiler-sfc
它会去解析template
标签及里面的内容,所以在打包我们的源代码时,它加载就已经做了解析,就不再需要再多做一次解析了,所以从vue
引入createApp
那里可以改一下了main.js
文件diff
如下:1
2
3
4
5
6
7...
// 从vue中引入createApp,这里指定一下版本
// import { createApp } from "vue/dist/vue.esm-bundler"
// 加载.vue文件可以直接从vue中引入,因为它不需要上面那个做解析,自己使用vue-loader依赖的@vue/compiler-sfc做解析
import { createApp } from "vue"
...
重新打包,浏览器运行显示正常,控制台也没有报错