第五節(jié):WordPress 通過 REST API 和 Vue3 開發(fā)設置選項 - 使用Vite打包JS資源

承接上文,我們將常用選項中的兩個功能給實現(xiàn)了,這一節(jié),我們將對 JS 資源進行打包,最終產物只有一個 JS 文件和一個 CSS 文件,可以極大的提升網頁加載速度,減少依賴。
打包后的文件中,就包含了 vue3 和 Axios 的代碼內容,不必通過URL 引入 vue3 的所有功能了。而且,在打包之前,還能對各種選項進行自定義的驗證,還能使用各種前端框架,對選項進行美化。
更重要的是,使用打包后的文件,可以明顯減少頁面卡頓。
本系列代碼分享在 GitHub 中,希望能幫助大家理解
https://github.com/muze-page/vue-spa
本節(jié)流程

準備打包環(huán)境
Node.js 安裝配置 | 菜鳥教程 (runoob.com)
我們使用 Vite 進行打包,您需要提前安裝 node 環(huán)境,
創(chuàng)建環(huán)境
使用 VS Code 打開我們的vue-spa文件夾,通過Ctrl+~ 打開終端,輸入以下命令創(chuàng)建 Vue 3 項目
npm create vite@latest vites -- --template vue
稍等片刻后,分別執(zhí)行以下幾個命令
?cd vites
?npm install
?npm run dev
會提示一個網址,

我們將其在瀏覽器中打開,即可看到如下顯示

我們同時按下 Ctrl+c 暫停當前運行,回到控制臺中。
安裝Axios
在控制臺中輸入以下命令,安裝 Axios
npm install axios
安裝完成后,打開vites文件夾下的 package.json 文件即可看到如下提示

即代表您安裝成功了。
重寫index.js
現(xiàn)在,我們需要重寫 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: "",
});
//獲取數(shù)據
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("連接服務器失敗或后臺讀取出錯!數(shù)據讀取失敗");
? ? ?console.log(error);
? ?});
};
//保存數(shù)據
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樣式,使得整體的代碼更加健壯和容易維護了。
當然,還有更多方法可以優(yōu)化,為了便于講解,這里不再贅述。
修改 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/
在之前的章節(jié)中,我們提前準備的ID 是?vuespa
?,所以,需要修改下此文件為以下內容
import { createApp } from 'vue'
//import './style.css'
import App from './App.vue'
createApp(App).mount('#vuespa')
在這里,我還把默認的 CSS 樣式給注釋了
修改 vite.config.js
/vites/src/
為了讓打包后的文件名與我們原有的文件名保持一致,我們需要修改下打包細節(jié),替換該文件為以下內容
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',
? ? ? ?// 指定靜態(tài)資源文件名(不含導出的代碼)
? ? ? ?//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() 函數(shù)的版本號
打包
打包的過程,就是優(yōu)化整合各代碼的過程,我們定位到 vites 文件夾下,輸入以下代碼進行打包。
npm run build
完成后,如下所示

我們可以在如下位置找到打包后的文件
/vites/dist/
導入
有了打包好的 JS 文件和 CSS 文件,現(xiàn)在,我們將其在菜單中導入,修改?vue-spa.php
?文件中的函數(shù)vuespa_load_vues()
為以下內容
//載入所需 JS 和 CSS 資源 并傳遞數(shù)據
function vuespa_load_vues($hook)
{
? ?//判斷當前頁面是否是指定頁面,是則繼續(xù)加載
? ?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(), ? ? ? ? ? ? ? //自定義數(shù)據
? ?);
? ?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);
效果如下:
//使用函數(shù)前
<script ?src='<http://localhost:10004/wp-content/plugins/vue-spa/vites/dist/index.js?ver=53>' id='vite-js'></script>
//使用函數(shù)后
<script type="module" src='<http://localhost:10004/wp-content/plugins/vue-spa/vites/dist/index.js?ver=53>' id='vite-js'></script>
補充
若您需要在本地進行開發(fā)和預覽,您可能需要以下幾個知識
修改 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';
//創(chuàng)建一個菜單
function vuespa_create_menu_page()
{
? ?add_menu_page(
? ? ? ?'VueSpa選項', ? ? ? ? ? ? ? ? ? // 此菜單對應頁面上顯示的標題
? ? ? ?'VueSpa', ? ? ? ? ? ? ? ? ? ? ?// 要為此實際菜單項顯示的文本
? ? ? ?'administrator', ? ? ? ? ? ? ? // 哪種類型的用戶可以看到此菜單
? ? ? ?'vuespa_id', ? ? ? ? ? ? ? ? ? // ?此菜單項的唯一ID(即段塞)
? ? ? ?'vuespa_menu_page_display', ? ?// 呈現(xiàn)此頁面的菜單時要調用的函數(shù)的名稱 '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“包裝”容器中創(chuàng)建標題-->
? ?<div class="wrap">
? ? ? ?<!--標題-->
? ? ? ?<h2><?php echo esc_html(get_admin_page_title()); ?></h2>
? ? ? ?<!--提供Vue掛載點-->
? ? ? ?<div id="vuespa">此內容將在掛載Vue后被替換{{data}}</div>
? ?</div>
<?php
? ?//展示準備的數(shù)據
? ?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 資源 并傳遞數(shù)據
function vuespa_load_vues($hook)
{
? ?//判斷當前頁面是否是指定頁面,是則繼續(xù)加載
? ?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(), ? ? ? ? ? ? ? //自定義數(shù)據
? ?);
? ?wp_localize_script('vite', 'dataLocal', $pf_api_translation_array); //傳給vite項目
}
//樣式加載到后臺
add_action('admin_enqueue_scripts', 'vuespa_load_vues');
//準備待傳輸?shù)臄?shù)據
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'(訂閱者)角色之外的所有角色的用戶數(shù)據
? ?$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));
? ?//轉為關聯(lián)數(shù)組
? ?$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);
總結
本章節(jié)中,我們使用 Vite 對 index.js 文件進行了打包等處理,基于現(xiàn)在的 Node 生態(tài),您還可以
使用 mockjs 提供攔截,更方便的進行本地開發(fā)
使用現(xiàn)成的前端框架提升開發(fā)效率,例如 Element Plus
使用 Pinia 進行數(shù)據的統(tǒng)一管理
使用第三方庫,實現(xiàn)數(shù)據校驗
使用TS約束變量類型,提升代碼健壯性
因篇幅原因,此處不再贅述。
下面是我使用 Element Plus 做出的下拉選項卡,比瀏覽器默認的好用多了

如果您能堅持看到這里,相信您也會有所收獲,希望您能基于此教程,做出更多有趣和實用的代碼來。
最新文章
后續(xù)文章持續(xù)撰寫中,點個關注,獲取平臺最新文章推送。
技術有限,還望諸位協(xié)助勘誤,于評論區(qū)指出,
常一文多發(fā),最新勘定和增補文章于下方鏈接給出
https://www.npc.ink/277313.html