前言
專案環境
- 後端框架:Phalcon
- 前端框架:Bootstrap + jQuery
什麼是前後端分離?
傳統專案大多數是 MVC 架構,直接使用 PHP 等後端語言渲染 HTML 模板,返回給瀏覽器
現在,前後端分離不需要後端渲染模板,而是交由瀏覽器 Javascript 渲染,後端只需要返回前端渲染所需要的資料即可
::: tip
前後端分離的本質:
- 路由分離
- 模板分離
:::
前後端偽分離?
傳統 MVC 專案直接升級到前後端分離需要大量的時間與人力,在業務多變的階段並不適合,所以便有了本文的過渡方案探索
- 路由先不分離,仍然採用 PHP 提供的路由
- 模板部分分離,在原 PHP 模板中,引入 Vue 編譯後的模板,為此需要約定
示例
新建控制器 TestController.php
<?php
namespace App\Controller;
class TestController
{
public function indexAction()
{
}
}
新建模板 test/index.volt
<div id="app">
<!-- 約定 一個頁面對應一個 Vue 元件 -->
<index-view></index-view>
</div>
<!-- 約定 一個頁面對應一個前端控制器 -->
<script src="/mix/dist/js/test/index.js?v={{ time() }}"></script>
::: tip
暫時找不到很好解決快取的方案,所以統一不快取
:::
新建前端控制器 public/mix/resources/js/test/index.js
import Vue from 'vue';
import ElementUI from 'element-ui';
import IndexView from '@views/test/index.vue';
import Mixin from '@utils/mixin';
Vue.use(ElementUI);
Vue.use(Mixin); // 全域性元件、方法、計算屬性等
new Vue({
el: '#app',
components: { IndexView },
});
新建 Vue 元件 public/mix/resources/views/test/index.vue
<template>
<div>
Hello Vue!
</div>
</template>
<script>
export default {
components: {},
props: {},
data() {
return {};
},
beforeCreate() {
},
created() {
console.log('Created');
},
beforeMount() {
},
mounted() {
},
beforeUpdate() {
},
updated() {
},
beforeDestroy() {
},
destroyed() {
},
watch: {},
computed: {},
methods: {},
};
</script>
<style lang="scss" scoped>
</style>
前後端偽分離
- 後端框架:Phalcon + Hyperf
- 前端框架:Bootstrap + jQuery + Vue
前端編譯使用 Laravel Mix 工具,這會節省大量前端配置時間
根目錄新建檔案 webpack.mix.js
const fs = require('fs');
const mix = require('laravel-mix');
const rs_root = 'public/mix/resources'; // 資源 源目錄
const rs_output = 'public/mix/dist'; // 資源 打包目錄
const js_entry = `${ rs_root }/js`; // js 源目錄
const js_output = `${ rs_output }/js`; // js 打包目錄
const css_entry = `${ rs_root }/css`; // css 源目錄
const css_output = `${ rs_output }/css`; // css 打包目錄
mix.webpackConfig({
resolve: {
alias: {
'@': path.resolve(__dirname, rs_root),
'@api': path.resolve(__dirname, `${ rs_root }/api`),
'@components': path.resolve(__dirname, `${ rs_root }/components`),
'@utils': path.resolve(__dirname, `${ rs_root }/utils`),
'@views': path.resolve(__dirname, `${ rs_root }/views`),
},
},
});
// 按照約定,編譯對應的資源
fs.readdirSync(path.resolve(__dirname, js_entry)).forEach(dir => {
fs.readdirSync(path.resolve(__dirname, `${ js_entry }/${ dir }`)).forEach(file => {
mix.js(`${ js_entry }/${ dir }/${ file }`, `${ js_output }/${ dir }/${ file }`);
});
});
mix.sass(`${ css_entry }/app.scss`, `${ css_output }/app.css`); // 公共 CSS
mix.setPublicPath(rs_output);
mix.setResourceRoot('/mix/dist/');
流程
- 按照示例配置一個頁面
- Yarn 安裝前端依賴
- Yarn 前端編譯,此時,PHP 模板中已正確引入 Vue
- 訪問路由,PHP 渲染模板,返回給瀏覽器
- 瀏覽器載入 Vue,交由 Vue 渲染頁面
侷限
- 不能做到全域性自動載入元件
- 編譯後的檔案大小可能會很大
優勢
- 可以更好地編寫複雜的頁面
- 更好的維護性
許可權互動
更新 2020/03/13
隨著頁面重構,檔案越來越多,導致編譯後總檔案大小足足 150 M,而且 Git 合併困難,大大降低了開發效率和前端效能,這明顯不合預期;
分析原因:每個頁面都引入了公共模組,接下來只要把公共模組分開一個檔案即可,並且要做快取控制
快取控制
新增公共函式
<?php
// /app/lib/WidgetLib.php
namespace App\Lib;
class WidgetLib
{
public static function get_version($file)
{
return json_decode(file_get_contents(BASE_PATH . '/public/mix/dist/mix-manifest.json'), true)[$file];
}
}
註冊公共函式
<?php
// /public/index.php
$compiler->addFunction('get_version', function ($resolvedArgs, $exprArgs) {
return 'App\Lib\WidgetLib::get_version(' . $resolvedArgs . ')';
});
使用公共函式
<link rel="stylesheet" href="/mix/dist{{ get_version('/css/app.css') }}">
{% if app is not defined %}
{% set app = 'search' %}
{% endif %}
<div id="app">
<{{ router.getControllerName() }}-{{ router.getActionName() }}/>
</div>
<script src="/mix/dist{{ get_version('/js/manifest.js') }}"></script>
<script src="/mix/dist{{ get_version('/js/vendor.js') }}"></script>
<script src="/mix/dist{{ get_version('/js/'~app~'.js') }}"></script>
laravel-mix 配置
const path = require('path')
const mix = require('laravel-mix')
const rs_root = 'public/mix/resources' // 資源 源目錄
const rs_output = 'public/mix/dist' // 資源 打包目錄
const js_output = `${rs_output}/js` // js 打包目錄
const css_entry = `${rs_root}/css` // css 源目錄
const css_output = `${rs_output}/css` // css 打包目錄
mix.webpackConfig({
resolve: {
alias: {
'@': path.resolve(__dirname, rs_root),
'@api': path.resolve(__dirname, `${rs_root}/api`),
'@components': path.resolve(__dirname, `${rs_root}/components`),
'@utils': path.resolve(__dirname, `${rs_root}/utils`),
'@views': path.resolve(__dirname, `${rs_root}/views`),
},
},
})
.disableNotifications()
.setPublicPath(`${rs_output}`)
.setResourceRoot('/mix/dist/')
.js(`${rs_root}/search.js`, js_output)
.js(`${rs_root}/new.js`, js_output)
.js(`${rs_root}/edit.js`, js_output)
.js(`${rs_root}/other.js`, js_output)
.sass(`${css_entry}/app.scss`, css_output)
.extract()
.version()
入口
按照頁面性值,分為四個入口檔案:
- search.js
- edit.js
- new.js
- other.js
// /public/mix/resources/new.js
import Vue from 'vue'
import Router from 'vue-router'
import Mixin from '@utils/mixin'
import ElementUI from 'element-ui'
import * as COMMONAPI from '@api/common'
// 一個頁面
import gameDemandsNew from '@views/game-demands/new'
Vue.use(Router)
Vue.use(Mixin)
Vue.use(ElementUI)
Object.entries(COMMONAPI).forEach(item => {
Vue.prototype[item[0]] = item[1]
})
Vue.config.productionTip = false
// eslint-disable-next-line no-new
new Vue({
el: '#app',
router: new Router({
mode: 'history',
scrollBehavior: () => ({ y: 0 }),
routes: [
// 頁面路由
{ path: '/game-demands/new', component: gameDemandsNew },
],
}),
components: {
// 頁面元件
gameDemandsNew,
},
})
/public/mix/resources/js
資料夾可以刪掉了,編譯後的總檔案大小約 2.5 M
至此,最佳化完成,完美解決了開發流程的痛點
後記
目前仍在不斷地探索中
本作品採用《CC 協議》,轉載必須註明作者和本文連結
:bug: 我的小破站