承接上文,我们将常用选项中的两个功能给实现了,这一节,我们将对 JS 资源进行打包,最终产物只有一个 JS 文件和一个 CSS 文件,可以极大的提升网页加载速度,减少依赖。
打包后的文件中,就包含了 vue3 和 Axios 的代码内容,不必通过URL 引入 vue3 的所有功能了。而且,在打包之前,还能对各种选项进行自定义的验证,还能使用各种前端框架,对选项进行美化。
更重要的是,使用打包后的文件,可以明显减少页面卡顿。
- 本系列代码分享在 GitHub 中,希望能帮助大家理解
- https://github.com/muze-page/vue-spa
本节流程
准备打包环境
- Node.js 安装配置 | 菜鸟教程 (runoob.com)
我们使用 Vite 进行打包,您需要提前安装 node 环境,
创建环境
使用 VS Code 打开我们的vue-spa文件夹,通过Ctrl+~ 打开终端,输入以下命令创建 Vue 3 项目
npm create vite@latest vites --
--template vue
稍等片刻后,分别执行以下几个命令
cd vites
npm install
npm run dev
会提示一个网址,
我们将其在浏览器中打开,即可看到如下显示
我们同时按下 Ctrl+c 暂停当前运行,回到控制台中。
安装Axios
在控制台中输入以下命令,安装 Axios
npm install axios
安装完成后,打开vites文件夹下的 package.json 文件即可看到如下提示
即代表您安装成功了。
重写index.js
现在,我们需要重写 index.js 文件,我们在 vites/src/components/ 文件夹下,新建文件 Option.vue
文件。写入以下内容
<script
setup>
import
{ reactive, onMounted } from "vue";
import axios from "axios";
const siteData = dataLocal.data;
//存储获取的值
const getData = reactive({
//存储获取的媒体库值
mediaList:
[],
});
//存储选项值
const datas = reactive({
dataOne:
"",
dataTwo:
"",
dataName:
[],
dataImage:
"",
dataSelectedImage:
"",
});
//获取数据
const get_option =
()
=>
{
axios
.post(dataLocal.route +
"pf/v1/get_option", datas,
{
headers:
{
"X-WP-Nonce": dataLocal.nonce,
"Content-Type":
"application/json",
},
})
.then((response)
=>
{
const data = response.data;
datas.dataOne = data.dataOne;
datas.dataTwo = data.dataTwo;
datas.dataName = data.dataName;
datas.dataImage = data.dataImage;
datas.dataSelectedImage = data.dataSelectedImage;
})
.catch((error)
=>
{
window.alert("连接服务器失败或后台读取出错!数据读取失败");
console.log(error);
});
};
//保存数据
const update_option =
()
=>
{
console.log(datas);
axios
.post(dataLocal.route +
"pf/v1/update_option", datas,
{
headers:
{
"X-WP-Nonce": dataLocal.nonce,
},
})
.then((response)
=>
{
alert("保存成功");
})
.catch((error)
=>
{
alert("保存失败");
console.log(error);
});
};
//上传图片
const upload_img =
(file)
=>
{
const formData =
new
FormData();
formData.append("file", file);
return axios
.post(dataLocal.route +
"wp/v2/media", formData,
{
headers:
{
"X-WP-Nonce": dataLocal.nonce,
"Content-Type":
"multipart/form-data",
},
})
.then((response)
=>
{
// 图片上传成功后的处理逻辑
const data = response.data;
//返回图片URL
return data.source_url;
})
.catch((error)
=>
{
console.error(error);
// 图片上传失败后的处理逻辑
});
};
//处理图片上传事件
const update_img =
(event)
=>
{
const file = event.target.files[0];
upload_img(file).then((url)
=>
{
//将拿到的图片URL传给图片变量
datas.dataImage = url;
});
};
//清空选择图片
const clear_img =
()
=>
{
datas.dataImage =
"";
};
//获取媒体库图片
const getMediaList =
()
=>
{
axios
.get(dataLocal.route +
"wp/v2/media")
.then((response)
=>
{
getData.mediaList = response.data;
})
.catch((error)
=>
{
console.error(error);
});
};
//从媒体库选中图片
const selectImage =
(imageUrl)
=>
{
datas.dataSelectedImage = imageUrl;
};
//页面初始加载
onMounted(()
=>
{
//获取选项值
get_option();
});
</script>
<template>
<!--两个输入框-->
文本框1:<input
type="text"
v-model="datas.dataOne"
/><br
/>
文本框2:<input
type="text"
v-model="datas.dataTwo"
/>
<hr
/>
<!--用户选择-->
用户选择:<select
v-model="datas.dataName"
multiple>
<option
v-for="option in siteData.user" :key="option.id" :value="option.id">
{{ option.name }}
</option>
</select>
<p>你选择了:{{ datas.dataName }}</p>
<br
/>
按住command(control)按键即可进行多选
<hr
/>
<!--图片上传-->
<input
type="file" @change.native="update_img"
/><br
/>
<button
type="button" @click="clear_img">清理</button><br
/>
<img :src="datas.dataImage"
v-if="datas.dataImage"
/>
<hr
/>
<!--获取媒体库图片-->
<button @click="getMediaList">获取媒体库图片</button>
<div
class="box">
<div
v-for="media in getData.mediaList" :key="media.id"
style="float: left">
<img :src="media.source_url"
/>
<button @click="selectImage(media.source_url)">选择</button>
</div>
</div>
<h2>{{ datas.dataSelectedImage ? "已" : "未" }}选择图片</h2>
<img :src="datas.dataSelectedImage"
v-if="datas.dataSelectedImage"
/>
<hr
/>
<button
class="button button-primary" @click="update_option">保存</button>
</template>
<style
scoped>
img {
max-width:
150px;
height:
auto;
vertical-align: top;
}
.box {
max-width:
800px;
display: flex;
margin:
1em
0;
}
</style>
这里,对原有写法在语法糖 setup 的帮助下,进行了部分重写,再抽离了部分CSS样式,使得整体的代码更加健壮和容易维护了。
当然,还有更多方法可以优化,为了便于讲解,这里不再赘述。
修改 App.js
/vites/src/
模块制作好了,我们在 App.vue 文件中导入,写入以下内容
<script
setup>
//import HelloWorld from "./components/HelloWorld.vue";
import
Option from "./components/Option.vue";
</script>
<template>
<Option></Option>
</template>
<style
scoped></style>
将我们写的组件展示出来
修改main.js
/vites/src/
在之前的章节中,我们提前准备的ID 是 vuespa
,所以,需要修改下此文件为以下内容
import
{ createApp }
from
'vue'
//import './style.css'
import
App
from
'./App.vue'
createApp(App).mount('#vuespa')
在这里,我还把默认的 CSS 样式给注释了
修改 vite.config.js
/vites/src/
为了让打包后的文件名与我们原有的文件名保持一致,我们需要修改下打包细节,替换该文件为以下内容
import
{ defineConfig }
from
"vite";
import vue from
"@vitejs/plugin-vue";
// https://vitejs.dev/config/
export
default defineConfig({
plugins:
[vue()],
build:
{
rollupOptions:
{
output:
{
// 指定 chunk 文件名(含导出的代码)
//chunkFileNames: 'js/[name].js',
// 指定静态资源文件名(不含导出的代码)
//assetFileNames: 'assets/[name].[ext]',
entryFileNames:
"index.js",
assetFileNames:
"[name][extname]",
chunkFileNames:
"[name].js",
},
},
},
});
这样,打包后就会产出 index.js 和 index.css 文件了,而不会携带别的字符。
wordpress 会缓存部分 JS 资源,记得在 vue-spa.php 文件中修改 vuespa_load_vues() 函数的版本号
打包
打包的过程,就是优化整合各代码的过程,我们定位到 vites 文件夹下,输入以下代码进行打包。
npm run build
完成后,如下所示
我们可以在如下位置找到打包后的文件
/vites/dist/
导入
有了打包好的 JS 文件和 CSS 文件,现在,我们将其在菜单中导入,修改 vue-spa.php
文件中的函数vuespa_load_vues()
为以下内容
//载入所需 JS 和 CSS 资源 并传递数据
function vuespa_load_vues($hook)
{
//判断当前页面是否是指定页面,是则继续加载
if
('toplevel_page_vuespa_id'
!= $hook)
{
return;
}
//版本号
$ver =
'55';
//加载到页面顶部
wp_enqueue_style('vite', plugin_dir_url(__FILE__)
.
'vites/dist/index.css', array(), $ver,
false);
//加载到页面底部
wp_enqueue_script('vite', plugin_dir_url(__FILE__)
.
'vites/dist/index.js', array(), $ver,
true);
$pf_api_translation_array = array(
'route'
=> esc_url_raw(rest_url()),
//路由
'nonce'
=> wp_create_nonce('wp_rest'),
//验证标记
'data'
=> vuespa_data(),
//自定义数据
);
wp_localize_script('vite',
'dataLocal', $pf_api_translation_array);
//传给vite项目
}
//样式加载到后台
add_action('admin_enqueue_scripts',
'vuespa_load_vues');
这里,我们无需手动载入 vue.js 和 Axios.js 文件了,打包后的 index.js 文件中,都准备好了,减少了不必要的资源开销。
添加type属性
注意,因为我们打包后的 index.js 文件,是一个模块,需要给其添加一个 type 属性才能正常生效。
我们在 vue-spa.php
文件底部,添加以下代码,给导入的 index.js 添加type属性,
//模块导入
function add_type_attribute_to_script($tag, $handle)
{
// 在这里判断需要添加 type 属性的 JS 文件,比如文件名包含 xxx.js
if
(strpos($tag,
'index.js')
!==
false)
{
// 在 script 标签中添加 type 属性
$tag = str_replace('<script',
'<script type="module"', $tag);
}
return $tag;
}
add_filter('script_loader_tag',
'add_type_attribute_to_script',
10,
2);
效果如下:
//使用函数前
<script src='<http://localhost:10004/wp-content/plugins/vue-spa/vites/dist/index.js?ver=53>' id='vite-js'></script>
//使用函数后
<script type="module" src='<http://localhost:10004/wp-content/plugins/vue-spa/vites/dist/index.js?ver=53>' id='vite-js'></script>
补充
若您需要在本地进行开发和预览,您可能需要以下几个知识
修改 index.html
/vites/
修改文件为以下内容,
<body>
<div
id="vuespa"></div>
<script
type="module"
src="/src/main.js"></script>
</body>
拿不到传来的 dataLocal
dataLocal 的值是外部传来的,vite 并不知道,您可以通过临时添加以下内容,进行模仿,但记得,在打包前进行注释。
const dataLocal =
{
route:
"http://localhost:5173/wp-json/",
nonce:
"asdf",
data:
{
user:
[
{ id:
1, name:
"111"
},
{ id:
2, name:
"222"
},
],
},
};
vue-spa.php完整代码
<?php
/*
Plugin Name: Vue - SPA
Plugin URI: https://www.npc.ink
Description: 将vue构建的页面嵌入WordPress 中并产生交互
Author: Muze
Author URI: https://www.npc.ink
Version: 1.0.0
*/
//接口
require_once plugin_dir_path(__FILE__)
.
'interface.php';
//创建一个菜单
function vuespa_create_menu_page()
{
add_menu_page(
'VueSpa选项',
// 此菜单对应页面上显示的标题
'VueSpa',
// 要为此实际菜单项显示的文本
'administrator',
// 哪种类型的用户可以看到此菜单
'vuespa_id',
// 此菜单项的唯一ID(即段塞)
'vuespa_menu_page_display',
// 呈现此页面的菜单时要调用的函数的名称 'vuespa_menu_page_display'
'dashicons-admin-customizer',
//图标 - 默认图标
'500.1',
//位置
);
}
// end vuespa_create_menu_page
add_action('admin_menu',
'vuespa_create_menu_page');
//菜单回调 - 展示的内容
function vuespa_menu_page_display()
{
?>
<!--在默认WordPress“包装”容器中创建标题-->
<div
class="wrap">
<!--标题-->
<h2><?php echo esc_html(get_admin_page_title());
?></h2>
<!--提供Vue挂载点-->
<div
id="vuespa">此内容将在挂载Vue后被替换{{data}}</div>
</div>
<?php
//展示准备的数据
echo "<pre>";
print_r(vuespa_data());
echo "</pre>";
echo "<h3>调用选项值</h3>";
echo get_option('dataOne');
echo "<br/>";
echo get_option('dataTwo');
echo "<br/>";
print_r(get_option('dataName'));
echo "<br/>";
echo get_option('dataImage');
echo "<br/>";
echo get_option('dataSelectedImage');
}
// vuespa_menu_page_display
//载入所需 JS 和 CSS 资源 并传递数据
function vuespa_load_vues($hook)
{
//判断当前页面是否是指定页面,是则继续加载
if
('toplevel_page_vuespa_id'
!= $hook)
{
return;
}
//版本号
$ver =
'53';
//加载到页面顶部
wp_enqueue_style('vite', plugin_dir_url(__FILE__)
.
'vites/dist/index.css', array(), $ver,
false);
//加载到页面底部
wp_enqueue_script('vite', plugin_dir_url(__FILE__)
.
'vites/dist/index.js', array(), $ver,
true);
$pf_api_translation_array = array(
'route'
=> esc_url_raw(rest_url()),
//路由
'nonce'
=> wp_create_nonce('wp_rest'),
//验证标记
'data'
=> vuespa_data(),
//自定义数据
);
wp_localize_script('vite',
'dataLocal', $pf_api_translation_array);
//传给vite项目
}
//样式加载到后台
add_action('admin_enqueue_scripts',
'vuespa_load_vues');
//准备待传输的数据
function vuespa_data()
{
$person =
[
"str"
=>
"Hello, world! - Npcink",
"num"
=>
25,
"city"
=>
[1,
2,
3,
4,
5],
"user"
=> vuespa_get_user_meat(),
];
return $person;
}
//整理并提供用户信息
function vuespa_get_user_meat()
{
//获取所有角色
$editable_roles = wp_roles()->roles;
$roles = array_keys($editable_roles);
//获取除了'subscriber'(订阅者)角色之外的所有角色的用户数据
$subscriber_key = array_search('subscriber', $roles,
true);
if
(false
!== $subscriber_key)
{
$roles = array_slice($roles,
0, $subscriber_key);
}
$users = get_users(array('role__in'
=> $roles));
//转为关联数组
$user_data = array_map(function
($user)
{
return
[
'id'
=> $user->ID,
'name'
=> $user->display_name,
];
}, $users);
return $user_data;
}
//模块导入
function add_type_attribute_to_script($tag, $handle)
{
// 在这里判断需要添加 type 属性的 JS 文件,比如文件名包含 xxx.js
if
(strpos($tag,
'index.js')
!==
false)
{
// 在 script 标签中添加 type 属性
$tag = str_replace('<script',
'<script type="module"', $tag);
}
return $tag;
}
add_filter('script_loader_tag',
'add_type_attribute_to_script',
10,
2);
总结
本章节中,我们使用 Vite 对 index.js 文件进行了打包等处理,基于现在的 Node 生态,您还可以
- 使用 mockjs 提供拦截,更方便的进行本地开发
- 使用现成的前端框架提升开发效率,例如 Element Plus
- 使用 Pinia 进行数据的统一管理
- 使用第三方库,实现数据校验
- 使用TS约束变量类型,提升代码健壮性
因篇幅原因,此处不再赘述。
下面是我使用 Element Plus 做出的下拉选项卡,比浏览器默认的好用多了
如果您能坚持看到这里,相信您也会有所收获,希望您能基于此教程,做出更多有趣和实用的代码