高性能流媒體服務器EasyDSS前端重構(一)-從零開始搭建 webpack + vue + AdminLTE 多頁面腳手架

本文圍繞着實現EasyDSS高性能流媒體服務器的前端框架來展開的,具體EasyDSS的相關信息可在:www.easydss.com 找到!javascript

EasyDSS 高性能流媒體服務器前端架構概述

EasyDSS 高性能流媒體服務器前端部分最初採用的是 AdminLTE + 各方 jQuery 插件的開發方式, 也就是網絡上一般講的 bootstrap + jquery plugins 的方式. 有經驗的前端開發者想必都瞭解這種架構下開發前端頁面的痛點. 當一個頁面上 UI 組件多起來的時候, 代碼組織就容易變得混亂, 各類 $(document).on 穿梭其中. 這樣的頁面開發好之後, 隔一段時間, 再來二次開發, 我去, 簡直了.css

爲了解決這樣的痛點, 我想重構前端, 引入 vue 的組件化開發模式, 藉助 element-ui 這樣的組件庫, 能夠用極少的代碼, 碼出豐富的功能. 這篇博客是 EasyDSS 高性能流媒體服務器前端重構系列博客的第一篇: 從零開始搭建 webpack + vue + AdmintLTE 多頁腳本架.html

安裝前端開發腳手架

首先, 從零搭建 webpack 腳手架. 這裏不借助 vue-cli 工具來生成腳手架, 而是一步步從 npm install 開始到手寫配置腳本. 由於, 我以爲 vue-cli 一會兒生成出來那麼多的配置文件和目錄, 會讓初學者眼花繚亂, 抓不住重點.前端

初始化工程目錄

node -v
v6.10.0
npm -v
5.3.0
mkdir easydss-web-src
cd easydss-web-src
npm init -y

安裝基礎包

npm i admin-lte font-awesome vue vuex webpack webpack-dev-server --save-dev

vuex : 用於 vue 組件間的狀態同步
font-awesome : 各類圖標vue

安裝經常使用的 webpack loader

npm i file-loader url-loader css-loader less less-loader style-loader vue-loader vue-template-compiler --save-dev
npm i babel-core babel-loader babel-preset-es2015 babel-preset-stage-2 babel-polyfill --save-dev

file-loader : 處理資源文件, 好比圖片, 字體等
url-loader : 對 file-loader 的封裝, 針對小圖片資源提供 base64 data blob
css-loader : 處理 css 文件中的 url 等
style-loader : 將 css 插入到頁面的 style 標籤
less-* : 將 less 轉成 css
vue-* : 處理 vue 單文件組件
babel-* : es6 語法支持, 詳細說明參考阮一峯的 Babel 入門教程java

安裝經常使用的 webpack 插件

npm i clean-webpack-plugin html-webpack-plugin --save-dev

clean-webpack-plugin : 用來清空發佈目錄
html-webpack-plugin : 用來生成入口頁面, 自動引入生成的 js 文件node

工程目錄結構預覽

首先, 看一下最終的工程目錄結構和運行效果, 作到心中有數. 後面將介紹這些目錄文件是如何一步步建立或生成的.jquery

easydss-web-src [工程根目錄]
├── .babelrc [babel全局配置文件]
├── dist [發佈目錄]
├── package.json
├── package-lock.json
├── src [源文件目錄]
│   ├── about.js
│   ├── assets [資源文件目錄]
│   │   └── images [資源圖片]
│   ├── components [組件目錄]
│   │   ├── About.vue
│   │   ├── AdminLTE.vue
│   │   ├── Index.vue
│   │   ├── NaviBar.vue
│   │   └── Sider.vue
│   ├── index.html
│   ├── index.js
│   └── store [狀態管理]
│       └── index.js
└── webpack.config.js [webpack 配置文件]

運行效果webpack

這裏寫圖片描述

看上圖, 最終產生兩個頁面 : 視頻廣場版本信息git

兩個頁面, 佈局相同 : 頂部導航 NaviBar, 左側菜單 Sider, 中間私有內容區

babel 配置

在工程根目錄下新建文件 .babelrc , 內容比較少, 以下:

{
    "presets": [ "es2015", "stage-2" ],
    "plugins": [] }

webpack 配置

重頭戲來了, 在工程根目錄下新建文件 webpack.config.js , 內容以下:

const HtmlWebpackPlugin = require('html-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const webpack = require('webpack');
const path = require('path');
require("babel-polyfill");

function resolve(dir) {
    return path.resolve(__dirname, dir)
}

module.exports = {
    //定義頁面的入口, 由於js中將要使用es6語法, 因此這裏須要依賴 babel 墊片
    entry: {
        index: ['babel-polyfill', './src/index.js'],
        about: ['babel-polyfill', './src/about.js']
    },
    output: {
        path: resolve('dist'), // 指示發佈目錄
        filename: 'js/[name].[chunkhash:8].js' //指示生成的頁面入口js文件的目錄和文件名, 中間包含8位的hash值
    },
    //下面給一些經常使用組件和目錄取別名, 方便在js中 import
    resolve: {
        extensions: ['.js', '.vue', '.json'],
        alias: {
            'vue$': 'vue/dist/vue.common.js',
            'jquery$': 'admin-lte/plugins/jQuery/jquery-2.2.3.min.js',
            'src': resolve('src'),
            'assets': resolve('src/assets'),
            'components': resolve('src/components')
        }
    },
    module: {
        //配置 webpack 加載資源的規則
        rules: [{
            test: /\.js$/,
            loader: 'babel-loader',
            include: [resolve('src')]
        }, {
            test: /\.vue$/,
            loader: 'vue-loader'
        }, {
            test: /\.css$/,
            loader: 'style-loader!css-loader'
        },
        {
            test: /\.less$/,
            loader: "less-loader"
        },
        {
            test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
            loader: 'url-loader?limit=10000&name=images/[name].[hash:8].[ext]'
        },
        {
            test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
            loader: 'url-loader?limit=10000&name=fonts/[name].[hash:8].[ext]'
        },
        {
            test: /\.(swf|mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/,
            loader: 'url-loader?limit=10000&name=media/[name].[hash:8].[ext]'
        }]
    },
    plugins: [
        //引入全局變量
        new webpack.ProvidePlugin({
            $: 'jquery',
            jQuery: 'jquery',
            "window.jQuery": 'jquery',
            "window.$": 'jquery'
        }),
        //編譯前先清除 dist 發佈目錄
        new CleanWebpackPlugin(['dist']),
        //生成視頻廣場首頁, 在這個頁面中自動引用入口 index --> dist/js/index.[chunkhash:8].js
        //以 src/index.html 這個文件做爲模板
        new HtmlWebpackPlugin({
            filename: 'index.html',
            title: '視頻廣場',
            inject: true, // head -> Cannot find element: #app
            chunks: ['index'],
            template: './src/index.html',
            minify: {
                removeComments: true,
                collapseWhitespace: false
            }
        }),
        //生成版本信息頁面, 在這個頁面中自動引用入口 about --> dist/js/about.[chunkhash:8].js
        //以 src/index.html 這個文件做爲模板
        new HtmlWebpackPlugin({
            filename: 'about.html',
            title: '版本信息',
            inject: true,
            chunks: ['about'],
            template: './src/index.html',
            minify: {
                removeComments: true,
                collapseWhitespace: false
            }
        })
    ]
};

建立網頁模板文件

上面 webpack.config.js 中, 咱們聲明瞭須要生成兩個頁面, 都是以 src/index.html 做爲模板文件, 實際上咱們最終生成的兩個發佈頁面 dist/index.htmldist/about.html 就是在這個模板文件基礎上, 插入 js 入口文件引用生成出來的(HtmlWebpackPlugin 配置項中的 inject).

下面建立這個模板文件:

src/index.html

<html>
    <head>
        <title><%= htmlWebpackPlugin.options.title %></title>
        <meta charset="utf-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
        <meta content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no" name="viewport">
    </head>
    <body class="skin-green sidebar-mini">
        <div id="app"></div>
    </body>
</html>

title 部分將會被 HtmlWebpackPlugin 中的 title 替換
聲明 #app div 用來掛載 vue 根組件

建立入口 js 文件

有了網頁模板文件, 接下來咱們要編寫入口 js 文件了. 在入口 js 文件裏面, 咱們建立 vue 根組件, 並將它掛載到模板頁面的 #app 上面.

先貼出兩個入口 js 內容, 再做說明.

src/index.js

import Vue from 'vue'
import store from "./store";
import AdminLTE from './components/AdminLTE'
import Index from './components/Index'

new Vue({
  el: '#app',
  store,
  template: ` <AdminLTE> <Index></Index> </AdminLTE>`,
  components: {
    AdminLTE, Index
  },
})

src/about.js

import Vue from 'vue'
import store from "./store";
import AdminLTE from './components/AdminLTE'
import About from './components/About'

new Vue({
  el: '#app',
  store,
  template: `
  <AdminLTE>
    <About @btnClick="btnClick"></About>
  </AdminLTE>`,
  components: {
    AdminLTE, About
  },
  methods: {
    btnClick(msg){
      alert(msg);
    }
  }
})

兩個 vue 根組件, 共同的地方是 :
1. 都引用了 vuex store 狀態管理, 咱們用它來保存各個頁面或組件之間共用的數據;
2. 都引用了 AdminLTE 這個子組件; 實際上在這個子組件裏面, 咱們定義了 AdminLTE 的總體佈局, 先是頂部導航和左側菜單欄佔位, 而後預留一個 slot 私有內容區域, 用以展現各個頁面不一樣的內容;
順帶說一下, about 頁面中演示了 父子組件間的數據交互

建立 vuex store

vuex store 中的數據在整個組件樹中共享, 只須要在根組件中引用一個 store. 子組件中經過 mapState, mapGetters, mapMutations, mapActions 訪問和修改. vuex 官方文檔傳送門.

這裏, 暫時想到的共享數據僅僅包括 左上角的 logo左側欄的菜單數據, 因此咱們的 store 文件很簡單:

store/index.js

import Vue from "vue";
import Vuex from "vuex";

Vue.use(Vuex);

const store = new Vuex.Store({
    state: {
        logoText: "EasyDSS",
        logoMiniText: "DSS",
        menus: [
            {
                path: "/index.html",
                icon: "mouse-pointer",
                text: "視頻廣場"
            }, {
                path: "/about.html",
                icon: "support",
                text: "版本信息"
            }
        ]
    },
    getters : {

    },
    mutations: {

    },
    actions : {

    }
})

export default store;

建立子組件

  • AdminLTE.vue

引入 adminlte 樣式和腳本文件, 指定界面佈局, 預留 slot 內容區

components/AdminLTE.vue

<template>
  <div class="wrapper">
    <NaviBar :logoText="logoText" :logoMiniText="logoMiniText"></NaviBar>
    <Sider :menus="menus"></Sider>
    <div class="content-wrapper">
      <section class="content">
        <slot></slot>
      </section>
    </div>
  </div>
</template>

<script> import "font-awesome/css/font-awesome.min.css"; import "admin-lte/bootstrap/css/bootstrap.min.css"; import "admin-lte/dist/css/AdminLTE.min.css"; import "admin-lte/dist/css/skins/_all-skins.css"; import "admin-lte/bootstrap/js/bootstrap.min.js"; import "admin-lte/dist/js/app.js"; import { mapState } from "vuex" import Vue from 'vue' import Sider from './Sider' import NaviBar from './NaviBar' export default { data() { return { } }, components: { NaviBar, Sider }, computed: { //訪問 vuex store 中的數據 //此處用到 es6 stage-2 纔有的三個點展開對象的語法, 對應 .babelrc 中的配置 ...mapState([ "logoText", "logoMiniText", "menus" ]) } } </script>
  • NaviBar.vue

頂部導航組件, 主要是 logo 和 菜單欄的 toggle, 數據從 AdminLTE 組件傳入

components/NaviBar.vue

<template> <header class="main-header"> <a href="index.html" class="logo"> <span class="logo-mini">{{logoMiniText}}</span> <span class="logo-lg">{{logoText}}</span> </a> <nav class="navbar navbar-static-top"> <a class="sidebar-toggle" data-toggle="offcanvas" role="button"> <span class="sr-only">Toggle navigation</span> </a> </nav> </header> </template> <script> export default { props: { logoText: { default: "AdminLte" }, logoMiniText: { default: "AD" } } } </script>
  • Sider.vue

左側菜單欄組件 , 菜單數據從 AdminLTE 組件傳入, 經過比較瀏覽器地址欄 path , 決定 active 菜單項

components/Sider.vue

<template> <aside id="slider" class="main-sidebar"> <section class="sidebar"> <ul class="sidebar-menu"> <li :class="['treeview', path == item.path ? 'active' : '']" v-for="(item,index) in menus" :key="index"> <a :href="item.path"> <i :class="['fa', 'fa-' + item.icon]"></i> <span>{{item.text}}</span> </a> </li> </ul> </section> </aside> </template> <script> export default { props: { menus : { default : () => [] } }, computed: { path(){ return location.pathname; } } } </script>
  • Index.vue

首頁內容區

components/Index.vue

 <template> <div class="container-fluid no-padding"> <div class="alert alert-success">{{msg}}</div> </div> </template> <script> export default { data() { return { msg : "我是視頻廣場" } } } </script>
  • About.vue

版本信息內容區

components/About.vue

<template> <div class="container-fluid no-padding"> <button class="btn btn-success" @click.prevent="btnClick">{{btnText}}</button> </div> </template> <script> export default { props: { btnText : { type : String, default : "" } }, methods: { btnClick(){ this.$emit("btnClick", "hello"); } } } </script> <style lang="less" scoped> </style> 

運行和編譯

編輯 package.json, 添加運行和編譯腳本指令, 留意其中的 scripts > build, start

{
  "name": "easydss-web-src",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": { "build": "webpack --progress --hide-modules", "start": "webpack-dev-server --open", "test": "echo \"Error: no test specified\" && exit 1" },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": { "admin-lte": "^2.3.11", "babel-core": "^6.26.0", "babel-loader": "^7.1.1", "babel-polyfill": "^6.26.0", "babel-preset-es2015": "^6.24.1", "babel-preset-stage-2": "^6.24.1", "clean-webpack-plugin": "^0.1.16", "css-loader": "^0.28.5", "file-loader": "^0.11.2", "font-awesome": "^4.7.0", "html-webpack-plugin": "^2.30.1", "less": "^2.7.2", "less-loader": "^4.0.5", "style-loader": "^0.18.2", "url-loader": "^0.5.9", "vue": "^2.4.2", "vue-loader": "^13.0.4", "vue-template-compiler": "^2.4.2", "vuex": "^2.3.1", "webpack": "^3.5.5", "webpack-dev-server": "^2.7.1" } }

命令行執行 :

npm run start #自動打開瀏覽器, 查看頁面效果

npm run build #生成發佈文件到 dist 目錄

總結

以上, 咱們從零開始, 建立了一個 webpack + vue + AdminLTE 多頁面工程的腳手架. 在此基礎上能夠體驗 vue 組件化前端開發的簡潔和高效了.

代碼地址 https://github.com/penggy/easydss-web-src

後續博客計劃:

EasyDSS高性能流媒體服務器前端重構(二): webpack + vue + AdminLTE 多頁面提取共用文件, 優化編譯時間

EasyDSS高性能流媒體服務器前端重構(三): webpack + vue + AdminLTE 多頁面引入 element-ui

EasyDSS高性能流媒體服務器前端重構(四): webpack + video.js 打造流媒體服務器前端

獲取更多信息

郵件:support@easydarwin.org

WEB:www.EasyDarwin.org

Copyright © EasyDarwin.org 2012-2017

EasyDarwin

相關文章
相關標籤/搜索