欢迎光临散文网 会员登陆 & 注册

第五节:WordPress 通过 REST API 和 Vue3 开发设置选项 - 使用Vite打包JS资源

2023-06-30 15:13 作者:Npcink_牧泽  | 我要投稿

承接上文,我们将常用选项中的两个功能给实现了,这一节,我们将对 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 项目

  1. npm create vite@latest vites -- --template vue

稍等片刻后,分别执行以下几个命令

  1.  cd vites

  2.  npm install

  3.  npm run dev

会提示一个网址,


我们将其在浏览器中打开,即可看到如下显示

我们同时按下 Ctrl+c 暂停当前运行,回到控制台中。

安装Axios

在控制台中输入以下命令,安装 Axios

  1. npm install axios

安装完成后,打开vites文件夹下的 package.json 文件即可看到如下提示


即代表您安装成功了。

重写index.js

现在,我们需要重写 index.js 文件,我们在 vites/src/components/ 文件夹下,新建文件 Option.vue 文件。写入以下内容

  1. <script setup>

  2. import { reactive, onMounted } from "vue";

  3. import axios from "axios";

  4. const siteData = dataLocal.data;


  5. //存储获取的值

  6. const getData = reactive({

  7.  //存储获取的媒体库值

  8.  mediaList: [],

  9. });


  10. //存储选项值

  11. const datas = reactive({

  12.  dataOne: "",

  13.  dataTwo: "",

  14.  dataName: [],

  15.  dataImage: "",

  16.  dataSelectedImage: "",

  17. });


  18. //获取数据

  19. const get_option = () => {

  20.  axios

  21.    .post(dataLocal.route + "pf/v1/get_option", datas, {

  22.      headers: {

  23.        "X-WP-Nonce": dataLocal.nonce,

  24.        "Content-Type": "application/json",

  25.      },

  26.    })

  27.    .then((response) => {

  28.      const data = response.data;

  29.      datas.dataOne = data.dataOne;

  30.      datas.dataTwo = data.dataTwo;

  31.      datas.dataName = data.dataName;

  32.      datas.dataImage = data.dataImage;

  33.      datas.dataSelectedImage = data.dataSelectedImage;

  34.    })

  35.    .catch((error) => {

  36.      window.alert("连接服务器失败或后台读取出错!数据读取失败");

  37.      console.log(error);

  38.    });

  39. };


  40. //保存数据

  41. const update_option = () => {

  42.  console.log(datas);

  43.  axios

  44.    .post(dataLocal.route + "pf/v1/update_option", datas, {

  45.      headers: {

  46.        "X-WP-Nonce": dataLocal.nonce,

  47.      },

  48.    })

  49.    .then((response) => {

  50.      alert("保存成功");

  51.    })

  52.    .catch((error) => {

  53.      alert("保存失败");

  54.      console.log(error);

  55.    });

  56. };


  57. //上传图片

  58. const upload_img = (file) => {

  59.  const formData = new FormData();

  60.  formData.append("file", file);

  61.  return axios

  62.    .post(dataLocal.route + "wp/v2/media", formData, {

  63.      headers: {

  64.        "X-WP-Nonce": dataLocal.nonce,

  65.        "Content-Type": "multipart/form-data",

  66.      },

  67.    })

  68.    .then((response) => {

  69.      // 图片上传成功后的处理逻辑

  70.      const data = response.data;

  71.      //返回图片URL

  72.      return data.source_url;

  73.    })

  74.    .catch((error) => {

  75.      console.error(error);

  76.      // 图片上传失败后的处理逻辑

  77.    });

  78. };


  79. //处理图片上传事件

  80. const update_img = (event) => {

  81.  const file = event.target.files[0];

  82.  upload_img(file).then((url) => {

  83.    //将拿到的图片URL传给图片变量

  84.    datas.dataImage = url;

  85.  });

  86. };


  87. //清空选择图片

  88. const clear_img = () => {

  89.  datas.dataImage = "";

  90. };


  91. //获取媒体库图片

  92. const getMediaList = () => {

  93.  axios

  94.    .get(dataLocal.route + "wp/v2/media")

  95.    .then((response) => {

  96.      getData.mediaList = response.data;

  97.    })

  98.    .catch((error) => {

  99.      console.error(error);

  100.    });

  101. };


  102. //从媒体库选中图片

  103. const selectImage = (imageUrl) => {

  104.  datas.dataSelectedImage = imageUrl;

  105. };


  106. //页面初始加载

  107. onMounted(() => {

  108.  //获取选项值

  109.  get_option();

  110. });

  111. </script>


  112. <template>

  113.  <!--两个输入框-->

  114.  文本框1:<input type="text" v-model="datas.dataOne" /><br />

  115.  文本框2:<input type="text" v-model="datas.dataTwo" />

  116.  <hr />

  117.  <!--用户选择-->

  118.  用户选择:<select v-model="datas.dataName" multiple>

  119.    <option v-for="option in siteData.user" :key="option.id" :value="option.id">

  120.      {{ option.name }}

  121.    </option>

  122.  </select>

  123.  <p>你选择了:{{ datas.dataName }}</p>

  124.  <br />

  125.  按住command(control)按键即可进行多选

  126.  <hr />

  127.  <!--图片上传-->

  128.  <input type="file" @change.native="update_img" /><br />

  129.  <button type="button" @click="clear_img">清理</button><br />

  130.  <img :src="datas.dataImage" v-if="datas.dataImage" />

  131.  <hr />

  132.  <!--获取媒体库图片-->

  133.  <button @click="getMediaList">获取媒体库图片</button>

  134.  <div class="box">

  135.    <div v-for="media in getData.mediaList" :key="media.id" style="float: left">

  136.      <img :src="media.source_url" />

  137.      <button @click="selectImage(media.source_url)">选择</button>

  138.    </div>

  139.  </div>

  140.  <h2>{{ datas.dataSelectedImage ? "已" : "未" }}选择图片</h2>

  141.  <img :src="datas.dataSelectedImage" v-if="datas.dataSelectedImage" />

  142.  <hr />


  143.  <button class="button button-primary" @click="update_option">保存</button>

  144. </template>


  145. <style scoped>

  146. img {

  147.  max-width: 150px;

  148.  height: auto;

  149.  vertical-align: top;

  150. }

  151. .box {

  152.  max-width: 800px;

  153.  display: flex;

  154.  margin: 1em 0;

  155. }

  156. </style>

这里,对原有写法在语法糖 setup 的帮助下,进行了部分重写,再抽离了部分CSS样式,使得整体的代码更加健壮和容易维护了。

当然,还有更多方法可以优化,为了便于讲解,这里不再赘述。

修改 App.js

/vites/src/

模块制作好了,我们在 App.vue 文件中导入,写入以下内容

  1. <script setup>

  2. //import HelloWorld from "./components/HelloWorld.vue";

  3. import Option from "./components/Option.vue";

  4. </script>


  5. <template>

  6.  <Option></Option>

  7. </template>


  8. <style scoped></style>

将我们写的组件展示出来

修改main.js

/vites/src/

在之前的章节中,我们提前准备的ID 是 vuespa ,所以,需要修改下此文件为以下内容

  1. import { createApp } from 'vue'

  2. //import './style.css'

  3. import App from './App.vue'


  4. createApp(App).mount('#vuespa')

在这里,我还把默认的 CSS 样式给注释了

修改 vite.config.js

/vites/src/

为了让打包后的文件名与我们原有的文件名保持一致,我们需要修改下打包细节,替换该文件为以下内容

  1. import { defineConfig } from "vite";

  2. import vue from "@vitejs/plugin-vue";


  3. // https://vitejs.dev/config/

  4. export default defineConfig({

  5.  plugins: [vue()],

  6.  build: {

  7.    rollupOptions: {

  8.      output: {

  9.        // 指定 chunk 文件名(含导出的代码)

  10.        //chunkFileNames: 'js/[name].js',

  11.        // 指定静态资源文件名(不含导出的代码)

  12.        //assetFileNames: 'assets/[name].[ext]',

  13.        entryFileNames: "index.js",

  14.        assetFileNames: "[name][extname]",

  15.        chunkFileNames: "[name].js",

  16.      },

  17.    },

  18.  },

  19. });

这样,打包后就会产出 index.js 和 index.css 文件了,而不会携带别的字符。

wordpress 会缓存部分 JS 资源,记得在 vue-spa.php 文件中修改 vuespa_load_vues() 函数的版本号

打包

打包的过程,就是优化整合各代码的过程,我们定位到 vites 文件夹下,输入以下代码进行打包。

  1. npm run build

完成后,如下所示


我们可以在如下位置找到打包后的文件

  1. /vites/dist/

导入

有了打包好的 JS 文件和 CSS 文件,现在,我们将其在菜单中导入,修改 vue-spa.php 文件中的函数vuespa_load_vues()为以下内容

  1. //载入所需 JS 和 CSS 资源 并传递数据

  2. function vuespa_load_vues($hook)

  3. {

  4.    //判断当前页面是否是指定页面,是则继续加载

  5.    if ('toplevel_page_vuespa_id' != $hook) {

  6.        return;

  7.    }

  8.    //版本号

  9.    $ver = '55';

  10.    //加载到页面顶部

  11.    wp_enqueue_style('vite', plugin_dir_url(__FILE__) . 'vites/dist/index.css', array(), $ver, false);

  12.    //加载到页面底部

  13.    wp_enqueue_script('vite', plugin_dir_url(__FILE__) . 'vites/dist/index.js', array(), $ver, true);


  14.    $pf_api_translation_array = array(

  15.        'route' => esc_url_raw(rest_url()),     //路由

  16.        'nonce' => wp_create_nonce('wp_rest'), //验证标记

  17.        'data' => vuespa_data(),               //自定义数据

  18.    );

  19.    wp_localize_script('vite', 'dataLocal', $pf_api_translation_array); //传给vite项目

  20. }

  21. //样式加载到后台

  22. add_action('admin_enqueue_scripts', 'vuespa_load_vues');

这里,我们无需手动载入 vue.js 和 Axios.js 文件了,打包后的 index.js 文件中,都准备好了,减少了不必要的资源开销。

添加type属性

注意,因为我们打包后的 index.js 文件,是一个模块,需要给其添加一个 type 属性才能正常生效。

我们在 vue-spa.php 文件底部,添加以下代码,给导入的 index.js 添加type属性,

  1. //模块导入

  2. function add_type_attribute_to_script($tag, $handle)

  3. {

  4.    // 在这里判断需要添加 type 属性的 JS 文件,比如文件名包含 xxx.js

  5.    if (strpos($tag, 'index.js') !== false) {

  6.        // 在 script 标签中添加 type 属性

  7.        $tag = str_replace('<script', '<script type="module"', $tag);

  8.    }

  9.    return $tag;

  10. }

  11. add_filter('script_loader_tag', 'add_type_attribute_to_script', 10, 2);

效果如下:

  1. //使用函数前

  2. <script  src='<http://localhost:10004/wp-content/plugins/vue-spa/vites/dist/index.js?ver=53>' id='vite-js'></script>


  3. //使用函数后

  4. <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/

修改文件为以下内容,

  1. <body>

  2.    <div id="vuespa"></div>

  3.    <script type="module" src="/src/main.js"></script>

  4.  </body>

拿不到传来的 dataLocal

dataLocal 的值是外部传来的,vite 并不知道,您可以通过临时添加以下内容,进行模仿,但记得,在打包前进行注释。

  1. const dataLocal = {

  2.  route: "http://localhost:5173/wp-json/",

  3.  nonce: "asdf",

  4.  data: {

  5.    user: [

  6.      { id: 1, name: "111" },

  7.      { id: 2, name: "222" },

  8.    ],

  9.  },

  10. };


vue-spa.php完整代码

  1. <?php

  2. /*

  3. Plugin Name: Vue - SPA

  4. Plugin URI: https://www.npc.ink

  5. Description: 将vue构建的页面嵌入WordPress 中并产生交互

  6. Author: Muze

  7. Author URI: https://www.npc.ink

  8. Version: 1.0.0

  9. */



  10. //接口

  11. require_once plugin_dir_path(__FILE__) . 'interface.php';


  12. //创建一个菜单

  13. function vuespa_create_menu_page()

  14. {

  15.    add_menu_page(

  16.        'VueSpa选项',                   // 此菜单对应页面上显示的标题

  17.        'VueSpa',                      // 要为此实际菜单项显示的文本

  18.        'administrator',               // 哪种类型的用户可以看到此菜单

  19.        'vuespa_id',                   //  此菜单项的唯一ID(即段塞)

  20.        'vuespa_menu_page_display',    // 呈现此页面的菜单时要调用的函数的名称 'vuespa_menu_page_display'

  21.        'dashicons-admin-customizer',  //图标 - 默认图标

  22.        '500.1',                       //位置

  23.    );

  24. } // end vuespa_create_menu_page

  25. add_action('admin_menu', 'vuespa_create_menu_page');


  26. //菜单回调 - 展示的内容

  27. function vuespa_menu_page_display()

  28. {

  29. ?>


  30.    <!--在默认WordPress“包装”容器中创建标题-->

  31.    <div class="wrap">

  32.        <!--标题-->

  33.        <h2><?php echo esc_html(get_admin_page_title()); ?></h2>

  34.        <!--提供Vue挂载点-->

  35.        <div id="vuespa">此内容将在挂载Vue后被替换{{data}}</div>

  36.    </div>




  37. <?php


  38.    //展示准备的数据

  39.    echo "<pre>";

  40.    print_r(vuespa_data());

  41.    echo "</pre>";


  42.    echo "<h3>调用选项值</h3>";

  43.    echo get_option('dataOne');

  44.    echo "<br/>";

  45.    echo get_option('dataTwo');

  46.    echo "<br/>";

  47.    print_r(get_option('dataName'));

  48.    echo "<br/>";

  49.    echo get_option('dataImage');

  50.    echo "<br/>";

  51.    echo get_option('dataSelectedImage');

  52. } // vuespa_menu_page_display




  53. //载入所需 JS 和 CSS 资源 并传递数据

  54. function vuespa_load_vues($hook)

  55. {

  56.    //判断当前页面是否是指定页面,是则继续加载

  57.    if ('toplevel_page_vuespa_id' != $hook) {

  58.        return;

  59.    }

  60.    //版本号

  61.    $ver = '53';

  62.    //加载到页面顶部

  63.    wp_enqueue_style('vite', plugin_dir_url(__FILE__) . 'vites/dist/index.css', array(), $ver, false);

  64.    //加载到页面底部

  65.    wp_enqueue_script('vite', plugin_dir_url(__FILE__) . 'vites/dist/index.js', array(), $ver, true);


  66.    $pf_api_translation_array = array(

  67.        'route' => esc_url_raw(rest_url()),     //路由

  68.        'nonce' => wp_create_nonce('wp_rest'), //验证标记

  69.        'data' => vuespa_data(),               //自定义数据

  70.    );

  71.    wp_localize_script('vite', 'dataLocal', $pf_api_translation_array); //传给vite项目

  72. }

  73. //样式加载到后台

  74. add_action('admin_enqueue_scripts', 'vuespa_load_vues');



  75. //准备待传输的数据

  76. function vuespa_data()

  77. {

  78.    $person = [

  79.        "str" => "Hello, world! - Npcink",

  80.        "num" => 25,

  81.        "city" => [1, 2, 3, 4, 5],

  82.        "user" => vuespa_get_user_meat(),

  83.    ];

  84.    return $person;

  85. }



  86. //整理并提供用户信息

  87. function vuespa_get_user_meat()

  88. {

  89.    //获取所有角色

  90.    $editable_roles = wp_roles()->roles;

  91.    $roles = array_keys($editable_roles);

  92.    //获取除了'subscriber'(订阅者)角色之外的所有角色的用户数据

  93.    $subscriber_key = array_search('subscriber', $roles, true);

  94.    if (false !== $subscriber_key) {

  95.        $roles = array_slice($roles, 0, $subscriber_key);

  96.    }


  97.    $users = get_users(array('role__in' => $roles));


  98.    //转为关联数组

  99.    $user_data = array_map(function ($user) {

  100.        return [

  101.            'id'   => $user->ID,

  102.            'name' => $user->display_name,

  103.        ];

  104.    }, $users);


  105.    return $user_data;

  106. }


  107. //模块导入

  108. function add_type_attribute_to_script($tag, $handle)

  109. {

  110.    // 在这里判断需要添加 type 属性的 JS 文件,比如文件名包含 xxx.js

  111.    if (strpos($tag, 'index.js') !== false) {

  112.        // 在 script 标签中添加 type 属性

  113.        $tag = str_replace('<script', '<script type="module"', $tag);

  114.    }

  115.    return $tag;

  116. }

  117. add_filter('script_loader_tag', 'add_type_attribute_to_script', 10, 2);

总结

本章节中,我们使用 Vite 对 index.js 文件进行了打包等处理,基于现在的 Node 生态,您还可以

  • 使用 mockjs 提供拦截,更方便的进行本地开发

  • 使用现成的前端框架提升开发效率,例如 Element Plus

  • 使用 Pinia 进行数据的统一管理

  • 使用第三方库,实现数据校验

  • 使用TS约束变量类型,提升代码健壮性

因篇幅原因,此处不再赘述。

下面是我使用 Element Plus 做出的下拉选项卡,比浏览器默认的好用多了

如果您能坚持看到这里,相信您也会有所收获,希望您能基于此教程,做出更多有趣和实用的代码来。

最新文章

  • 后续文章持续撰写中,点个关注,获取平台最新文章推送。

  • 技术有限,还望诸位协助勘误,于评论区指出,

  • 常一文多发,最新勘定和增补文章于下方链接给出

  • https://www.npc.ink/277313.html


第五节:WordPress 通过 REST API 和 Vue3 开发设置选项 - 使用Vite打包JS资源的评论 (共 条)

分享到微博请遵守国家法律