如何開發一個 Notadd Administration 模組的前端擴充套件

依劍聽雨發表於2017-03-07

閱讀此文件,需對 Laravel,VueJS 2,Webpack 有了解。

前端擴充套件,指的是,針對專案 notadd/administration 的前端部分進行擴充套件功能的開發。

完整示例,請參考模組專案 notadd/content

前端擴充套件包含的功能注入點如下:

  • 擴充套件安裝注入
  • 頭部選單注入
  • 路由注入
  • 側邊欄選單注入

說明

專案 notadd/administration 的前端部分,是基於 VueJS 2 實現的單頁應用(SPA)。

所以,對前端進行擴充套件,實際是對 VueJS 專案的擴充套件。

由於 VueJS 專案基於 Webpack 進行構建和打包,所以前端擴充套件專案也必須基於 Webpack 進行構建和打包。

如何建立和開發 VueJS 2 的專案,請參見 VueJS 官方文件

但是,Notadd 的前端擴充套件專案,並不是一個完整的 VueJS 2 的專案,因為 Notadd 只接受 UMD 模組風格的前端模組注入,所以在使用 Webpack 進行模組構建時,webpackConfig 中需要針對 output 引數進行調整,主要體現:

  • 必須定義 outputlibrary 別名,此名稱,必須與 捆綁 的模組或擴充套件專案中 composer.json 檔案中定義的 name 完全一致,否則無法載入前端擴充套件
  • 必須定義 outputlibraryTargetumd

配置程式碼參考如下(來自檔案 build/webpack.prod.conf.js):

var path = require('path')
var utils = require('./utils')
var webpack = require('webpack')
var config = require('../config')
var merge = require('webpack-merge')
var baseWebpackConfig = require('./webpack.base.conf')
var HtmlWebpackPlugin = require('html-webpack-plugin')
var ExtractTextPlugin = require('extract-text-webpack-plugin')
var OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin')

var env = config.build.env

var webpackConfig = merge(baseWebpackConfig, {
  module: {
    rules: utils.styleLoaders({
      sourceMap: config.build.productionSourceMap,
      extract: true
    })
  },
  output: {
    path: config.build.assetsRoot,
    filename: utils.assetsPath('js/extension.js'),
    library: 'notadd/content',                                                              // 必須定義 library 別名
    libraryTarget: "umd"                                                                    // 必須定義 libraryTarget 為 umd
  },
  plugins: [
    new webpack.DefinePlugin({
      'process.env': env
    }),
    new webpack.optimize.UglifyJsPlugin({
      compress: {
        warnings: false
      }
    }),
    new ExtractTextPlugin({
      filename: utils.assetsPath('css/[name].css')
    }),
    new OptimizeCSSPlugin()
  ]
})

if (config.build.productionGzip) {
  var CompressionWebpackPlugin = require('compression-webpack-plugin')

  webpackConfig.plugins.push(
    new CompressionWebpackPlugin({
      asset: '[path].gz[query]',
      algorithm: 'gzip',
      test: new RegExp(
        '\\.(' +
        config.build.productionGzipExtensions.join('|') +
        ')$'
      ),
      threshold: 10240,
      minRatio: 0.8
    })
  )
}

if (config.build.bundleAnalyzerReport) {
  var BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
  webpackConfig.plugins.push(new BundleAnalyzerPlugin())
}

module.exports = webpackConfig

預設匯出模組

使用 Webpack 匯出 UMD 模組風格的模組是,在 Webpack 配置中定義的 entry 入口檔案中,必須使用預設匯出模組,作為 Notadd 前端功能注入的注入點。

程式碼參考如下:

import {headerMixin, installMixin, routerMixin} from './helpers/mixes'

let Core = {}

headerMixin(Core)
installMixin(Core)
routerMixin(Core)

export default Core

如上程式碼所示,模組 Core 即為專案構建後的預設匯出模組,在該示例中,使用了 mixin 特性,為模組增加 header,install,router的注入邏輯。

擴充套件安裝注入

如上(預設匯出模組)述說,installMixin 為模組 Core 注入了 Core.install 的實現,具體程式碼如下:

export function installMixin (Core) {
  Core.install = function (Vue, Notadd) {
    Core.instance = Notadd
    vueMixin(Core, Vue)
  }
}
export function vueMixin (Core, Vue) {
  Core.http = Vue.http
}

Core.install 的呼叫者,為該方法提供了兩個物件,一個是 Vue 全域性物件,一個是 Notadd 全域性物件。

Vue 全域性物件提供的特性,可以參考 VueJS 2 的官方文件。

Notadd 全域性物件主要包含如下特性:

  • Notadd.Vue:Vue 全域性物件的副本
  • Notadd.http:axios 全域性物件的副本
  • Notadd.store:Vuex 物件的副本
  • Notadd.components:常用的功能型元件(符合 Vue 元件規範)
  • Notadd.layouts:常用的佈局型元件(符合 Vue 元件規範)

所以,如果模組 Core 中需要使用 Vue 或 Notadd 的任意物件,均可透過 mixin 特性來附加。

頭部選單注入

如上(預設匯出模組)述說,headerMixin 為模組 Core 注入了 Core.header 的實現,具體程式碼如下:

export function headerMixin (Core) {
  Core.header = function (menu) {
    menu.push({
      'text': '文章',
      'icon': 'icon icon-article',
      'uri': '/content'
    })
  }
}

路由注入

如上(預設匯出模組)述說,routerMixin 為模組 Core 注入了 Core.router 的實現,具體程式碼如下:

import ContentArticle from '../components/Article'
import ContentArticleCreate from '../components/ArticleCreate'
import ContentArticleDraft from '../components/ArticleDraft'
import ContentArticleDraftEdit from '../components/ArticleDraftEdit'
import ContentArticleEdit from '../components/ArticleEdit'
import ContentArticleRecycle from '../components/ArticleRecycle'
import ContentCategory from '../components/ArticleCategory'
import ContentComment from '../components/Comment'
import ContentComponent from '../components/Component'
import ContentDashboard from '../components/Dashboard'
import ContentExtension from '../components/Extension'
import ContentLayout from '../components/Layout'
import ContentPage from '../components/Page'
import ContentPageCategory from '../components/PageCategory'
import ContentPageCreate from '../components/PageCreate'
import ContentPageEdit from '../components/PageEdit'
import ContentTemplate from '../components/Template'
import ContentTag from '../components/ArticleTag'
export function routerMixin (Core) {
  Core.router = function (router) {
    router.modules.push({
      path: '/content',
      component: ContentLayout,
      children: [
        {
          path: '/',
          component: ContentDashboard,
          beforeEnter: router.auth
        },
        {
          path: 'article/all',
          component: ContentArticle,
          beforeEnter: router.auth
        },
        {
          path: 'article/create',
          component: ContentArticleCreate,
          beforeEnter: router.auth
        },
        {
          path: 'article/:id/draft',
          component: ContentArticleDraftEdit,
          beforeEnter: router.auth
        },
        {
          path: 'article/:id/edit',
          component: ContentArticleEdit,
          beforeEnter: router.auth
        },
        {
          path: 'article/category',
          component: ContentCategory,
          beforeEnter: router.auth
        },
        {
          path: 'article/tag',
          component: ContentTag,
          beforeEnter: router.auth
        },
        {
          path: 'article/recycle',
          component: ContentArticleRecycle,
          beforeEnter: router.auth
        },
        {
          path: 'article/draft',
          component: ContentArticleDraft,
          beforeEnter: router.auth
        },
        {
          path: 'page/all',
          component: ContentPage,
          beforeEnter: router.auth
        },
        {
          path: 'page/create',
          component: ContentPageCreate,
          beforeEnter: router.auth
        },
        {
          path: 'page/:id/edit',
          component: ContentPageEdit,
          beforeEnter: router.auth
        },
        {
          path: 'page/category',
          component: ContentPageCategory,
          beforeEnter: router.auth
        },
        {
          path: 'component',
          component: ContentComponent,
          beforeEnter: router.auth
        },
        {
          path: 'template',
          component: ContentTemplate,
          beforeEnter: router.auth
        },
        {
          path: 'extension',
          component: ContentExtension,
          beforeEnter: router.auth
        },
        {
          path: 'comment',
          component: ContentComment,
          beforeEnter: router.auth
        }
      ]
    })
  }
}

Core.router 的呼叫者,為該方法提供了一個 router 物件,該 router 物件中包含如下特性:

  • auth: 後臺登入驗證中介軟體
  • bases: 基礎路由定義
  • modules: 模組路由定義

側邊欄選單注入

側邊欄選單注入,提供了擴充套件管理子級選單的注入,由 Core.sidebar 提供注入,程式碼參考如下:

export default {
  sidebar: function (sidebar) {
    sidebar.push({
      text: '多說評論',
      icon: 'fa fa-comment',
      uri: '/duoshuo'
    })
  }
}

前端擴充套件構建和打包

在進行程式碼編寫和相關配置之後,使用命令 npm run build 即可完成對擴充套件模組的打包。

前端資源注入

透過前端工具構建和打包後,可以得到前端靜態資原始檔(js檔案,css檔案,圖片檔案等),可以模組中的類 ModuleServiceProvider 或擴充套件中的類 Extension 中將靜態資原始檔釋出到 public 目錄下。

類 ModuleServiceProvider 的程式碼參考如下:

<?php
/**
 * This file is part of Notadd.
 *
 * @author TwilRoad <269044570@qq.com>
 * @copyright (c) 2016, iBenchu.org
 * @datetime 2016-10-08 17:12
 */
namespace Notadd\Content;

use Illuminate\Support\ServiceProvider;

/**
 * Class Module.
 */
class ModuleServiceProvider extends ServiceProvider
{
    /**
     * Boot service provider.
     */
    public function boot()
    {
        $this->publishes([
            realpath(__DIR__ . '/../resources/mixes/administration/dist/assets/content/administration') => public_path('assets/content/administration'),
            realpath(__DIR__ . '/../resources/mixes/foreground/dist/assets/content/foreground') => public_path('assets/content/foreground'),
        ], 'public');
    }
}

然而,這樣並沒有結束,仍然需要告訴 Administration 模組你提供了哪些靜態資原始檔,給後臺的前端頁面使用。

在模組中的類 ModuleServiceProvider 或擴充套件中的類 Extension 中提供了相應注入點,script 方法將告訴後臺的前端頁面引用前面打包生成的 UMD 模組檔案,stylesheet 方法將告訴後臺的前端頁面引用前面打包生成樣式檔案。

具體程式碼參考如下:

<?php
/**
 * This file is part of Notadd.
 *
 * @author TwilRoad <269044570@qq.com>
 * @copyright (c) 2016, iBenchu.org
 * @datetime 2016-10-08 17:12
 */
namespace Notadd\Content;

use Illuminate\Support\ServiceProvider;

/**
 * Class Module.
 */
class ModuleServiceProvider extends ServiceProvider
{
    /**
     * Boot service provider.
     */
    public function boot()
    {
    }
    /**
     * Get script of extension.
     *
     * @return string
     * @throws \Illuminate\Contracts\Container\BindingResolutionException
     */
    public static function script()
    {
        return asset('assets/content/administration/js/module.js');
    }

    /**
     * Get stylesheet of extension.
     *
     * @return array
     * @throws \Illuminate\Contracts\Container\BindingResolutionException
     */
    public static function stylesheet()
    {
        return [
            asset('assets/content/administration/css/module.css'),
        ];
    }
}
本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章