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

承接上文,我们将常用选项中的两个功能给实现了,这一节,我们将对 JS 资源进行打包,最终产物只有一个 JS 文件和一个 CSS 文件,可以极大的提升网页加载速度,减少依赖。

打包后的文件中,就包含了 vue3 和 Axios 的代码内容,不必通过URL 引入 vue3 的所有功能了。而且,在打包之前,还能对各种选项进行自定义的验证,还能使用各种前端框架,对选项进行美化。

更重要的是,使用打包后的文件,可以明显减少页面卡顿。

  • 本系列代码分享在 GitHub 中,希望能帮助大家理解
  • github.com/muze-page/vu

本节流程

准备打包环境

  • 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. <scriptsetup>
  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 =newFormData();
  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:<inputtype="text"v-model="datas.dataOne"/><br/>
  115. 文本框2:<inputtype="text"v-model="datas.dataTwo"/>
  116. <hr/>
  117. <!--用户选择-->
  118. 用户选择:<selectv-model="datas.dataName"multiple>
  119. <optionv-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. <inputtype="file" @change.native="update_img"/><br/>
  129. <buttontype="button" @click="clear_img">清理</button><br/>
  130. <img :src="datas.dataImage"v-if="datas.dataImage"/>
  131. <hr/>
  132. <!--获取媒体库图片-->
  133. <button @click="getMediaList">获取媒体库图片</button>
  134. <divclass="box">
  135. <divv-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. <buttonclass="button button-primary" @click="update_option">保存</button>
  144. </template>
  145. <stylescoped>
  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:1em0;
  155. }
  156. </style>

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

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

修改 App.js

/vites/src/

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

  1. <scriptsetup>
  2. //import HelloWorld from "./components/HelloWorld.vue";
  3. importOption from "./components/Option.vue";
  4. </script>
  5. <template>
  6. <Option></Option>
  7. </template>
  8. <stylescoped></style>

将我们写的组件展示出来

修改main.js

/vites/src/

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

  1. import{ createApp }from'vue'
  2. //import './style.css'
  3. importAppfrom'./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. exportdefault 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. <divid="vuespa"></div>
  3. <scripttype="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. <divclass="wrap">
  32. <!--标题-->
  33. <h2><?php echo esc_html(get_admin_page_title());?></h2>
  34. <!--提供Vue挂载点-->
  35. <divid="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 做出的下拉选项卡,比浏览器默认的好用多了

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

赞(0)
未经允许不得转载:大象juǎn » 第五节:Vue3 开发WordPress设置选项 - 使用Vite打包JS资源