zoukankan      html  css  js  c++  java
  • 高性能流媒体服务器EasyDSS前端重构(一)-从零开始搭建 webpack + vue + AdminLTE 多页面脚手架

    本文围绕着实现EasyDSS高性能流媒体服务器的前端框架来展开的,具体EasyDSS的相关信息可在:www.easydss.com 找到!

    EasyDSS 高性能流媒体服务器前端架构概述

    EasyDSS 高性能流媒体服务器前端部分最初采用的是 AdminLTE + 各方 jQuery 插件的开发方式, 也就是网络上通常讲的 bootstrap + jquery plugins 的方式. 有经验的前端开发者想必都了解这种架构下开发前端页面的痛点. 当一个页面上 UI 组件多起来的时候, 代码组织就容易变得混乱, 各种 $(document).on 穿梭其中. 这样的页面开发好以后, 隔一段时间, 再来二次开发, 我去, 简直了.

    为了解决这样的痛点, 我想重构前端, 引入 vue 的组件化开发模式, 借助 element-ui 这样的组件库, 可以用极少的代码, 码出丰富的功能. 这篇博客是 EasyDSS 高性能流媒体服务器前端重构系列博客的第一篇: 从零开始搭建 webpack + vue + AdmintLTE 多页脚本架.

    安装前端开发脚手架

    首先, 从零搭建 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 : 各种图标

    安装常用的 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 入门教程

    安装常用的 webpack 插件

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

    clean-webpack-plugin : 用来清空发布目录
    html-webpack-plugin : 用来生成入口页面, 自动引入生成的 js 文件

    工程目录结构预览

    首先, 看一下最终的工程目录结构和运行效果, 做到心中有数. 后面将介绍这些目录文件是如何一步步创建或生成的.

    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 配置文件]

    运行效果

    这里写图片描述

    看上图, 最终产生两个页面 : 视频广场版本信息

    两个页面, 布局相同 : 顶部导航 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

  • 相关阅读:
    invalid expression: missing ) after argument list in xxx 或者 console.error(("[Vue warn]: " + msg + trace));
    js的alert()
    第9节列表渲染
    第8节条件渲染
    第7节class与style绑定
    CF1215D Ticket Game 博弈论
    CF833A The Meaningless Game 思维
    蚯蚓 队列
    洛谷P2566[SCOI2009]围豆豆
    ants 思维
  • 原文地址:https://www.cnblogs.com/babosa/p/7468283.html
Copyright © 2011-2022 走看看