Overview of the Front-end architecture of LiveQing High-performance Streaming server

The front-end part of LiveQing high performance streaming media server initially adopts the development mode of AdminLTE + jQuery plugins, which is also commonly referred to as bootstrap + jQuery plugins on the network. Experienced front-end developers know the pain points of developing front-end pages in this architecture. When there are too many UI components on a page, the code organization tends to get messy, with $(document).on shuttled through. After such page development, after a period of time, to secondary development, I go, simply.

To solve this pain point, I want to rebuild the front end and introduce the component-based development model of Vue. With the help of component libraries like Element-UI, we can write rich functions with very little code. This post is the first in a series of LiveQing high performance streaming server front-end refactoring blogs: Build webPack + Vue + AdmintLTE multi-page scripting scaffolding from scratch.

Install front-end development scaffolding

First, build webpack scaffolding from scratch. Instead of using vue-CLI tools to generate scaffolding, step by step from NPM install to handwritten configuration scripts. Because, I think vue-CLI generates so many configuration files and directories all at once, it will make beginners dizzy, miss the point.

Initialize the project directory

Node -v v6.10.0 NPM -v 5.3.0 mkdir easydss-web-src CD easydss-web-src NPM init-yCopy the code

Installing base Packages

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

Vuex: used for status synchronization between VUE components font-awesome: ICONS

Install the common 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
Copy the code

Url-loader: encapsulates file-loader and provides base64 data blob CSs-loader for small image resources. Handle urls in CSS files, etc. Style-loader: insert CSS into the page style tag less-* : convert less to CSS vue-* : process vue single file component Babel -* : Es6 syntax support, detailed instructions refer to Ruan Yifeng’s Introduction to Babel tutorial

Install common WebPack plug-ins

npm i clean-webpack-plugin html-webpack-plugin --save-dev
Copy the code

Clean-webpack-plugin: used to empty the publishing directory html-webpack-plugin: used to generate an entry page that automatically imports generated JS files

Project directory structure preview

First, take a look at the final project directory structure and performance to get a clear idea. How these directory files are created or generated step by step will be described later.

Liveqing-web-src [project root] ├─.babelrc [release directory] ├─ package.json ├─ SRC [source file directory] │ ├ ─ ─ the about the js │ ├ ─ ─ assets/resources directory │ │ └ ─ ─ images [images] resources │ ├ ─ ─ components [components directory] │ │ ├ ─ ─ the about. Vue │ │ ├ ─ ─ │ ├── ├─ ├─ ├─ ├.htm │ ├── ├.htm │ ├── ├.htm │ ├── ├.htm │ ├── ├.htm │ ├ ─ ├ ─ ├ ─ garbage, └─ ├ ─ garbage, ├ ─ garbageCopy the code

Babel configuration

Create a new file. Babelrc in the project root directory.

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

Webpack configuration

Create a new webpack.config.js file in the project root directory.

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 = {// define the entry of the page. {index: ['babel-polyfill', './ SRC /index.js'], about: ['babel-polyfill', './ SRC /about.js']}, output: {path: resolve('dist'), // 'js/[name].[chunkhash:8].js' // indicates the directory and filename of the generated page-entry js file, which contains the 8-bit hash value}. { 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: {// configure webpack to load resources 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: [hash:8].[ext]'}]}, plugins: [// new webpack.providePlugin ({$: 'jquery', jQuery: 'jquery', "window.jQuery": 'jquery', "window.$": New CleanWebpackPlugin(['dist']), new CleanWebpackPlugin(['dist']), Dist /js/index.[chunkhash:8].js // SRC /index.html as a template new HtmlWebpackPlugin({filename: 'index.html', title: 'video square ', inject: true, // head -> Cannot find Element: #app chunks: ['index'], template: './src/index.html', minify: { removeComments: true, collapseWhitespace: False}}), // generate version information page, Dist /js/about.[chunkhash:8].js // SRC /index.html as a template new HtmlWebpackPlugin({filename: 'about.html', title: 'version info ', Inject: true, chunks: ['about'], template: './ SRC /index.html', minify: {removeComments: true, collapseWhitespace: false } }) ] };Copy the code

Create a web page template file

In webpack.config.js above, we declare that we need to generate two pages using SRC /index.html as the template file. In fact, the two publishing pages dist/index.html and dist/about. HTML that we finally generate are the inject generated by inserting JS entry file to reference on the basis of this template file.

Create this template file:

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>
Copy the code

The title part will be used by the title replacement declaration #app div in HtmlWebpackPlugin to mount the vue root component

Create the entry js file

With the web template file in place, it’s time to write the entry JS file. In the entry js file, we create the vue root component and mount it on the #app of the template page.

First post two entry JS content, and then explain.

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
  },
})
Copy the code

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); }}})Copy the code

The two vUE root components have in common:

  1. Both reference vuex Store state management, which we use to store data shared between pages or components;
  2. Both reference the AdminLTE child component; In fact, within this sub-component, we define the overall layout of AdminLTE, starting with the top navigation and left menu bar placeholder, and reservating a slot private content area for displaying different content on each page. By the way, the About page demonstrates data interaction between parent and child components

Create vuex store

The data in the Vuex store is shared across the component tree, and only one store needs to be referenced in the root component. Vuex official document portal is accessed and modified through mapState, mapGetters, mapMutations, mapActions.

Here, the shared data that comes to mind for the moment only includes the logo in the upper left corner and the menu data in the left column, so our Store file is simple:

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: HTML ", icon: "support", text: "version info"}]}, mutations: { }, actions : { } }) export default store;Copy the code

Creating child components

  • AdminLTE.vue

Introduce adminLTE styles and script files, specify interface layout, and reserve slot content areas

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: {// Access data in the vuex store // Use es6 stage-2's three-point expansion object syntax, corresponding to the configuration in.babelrc... mapState([ "logoText", "logoMiniText", "menus" ]) } } </script>Copy the code
  • NaviBar.vue

The top navigation component, mainly the logo and menu bar toggle, comes in data from the AdminLTE component

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>
Copy the code
  • Sider.vue

The left menubar component, where the menu data is passed in from the AdminLTE component, determines the active menu item by comparing the browser address bar path

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>
Copy the code
  • Index.vue

Content area of home Page

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: "I am a video square"}}} </script>Copy the code
  • About.vue

Version information content area

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>
Copy the code

Run and compile

Edit package.json to add instructions to run and compile the script, noting scripts > build, start

{" name ":" liveqing - 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", "the clean - webpack - plugin" : "^ 0.1.16", "CSS - loader" : "^ 0.28.5 file -", "loader" : "^ 0.11.2", "the 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 - the template -", "compiler" : "^" 2.4.2, "vuex" : "^ 2.3.1", "webpack" : "^ 2.6.2," "webpack - dev - server" : "^ 2.7.1"}}Copy the code

Command line execution:

NPM run start # Automatically open the browser to see the page effect

NPM run build # Generates the distribution file to the dist directory

conclusion

Above, we created a scaffolding for the WebPack + Vue + AdminLTE multi-page project from scratch. On this basis, you can experience the simplicity and efficiency of vUE componentized front-end development.

WEB: www.liveqing.com

Future blog plans:

LiveQing High-performance streaming media server front-end reconstruction (II): Webpack + Vue + AdminLTE Multi-page extraction of common files, optimize compilation time

LiveQing High-performance streaming media server front-end reconfiguration (3): WebPack + Vue + AdminLTE Multi-page introduction of Element-UI

LiveQing High-performance Streaming server front-end Reconstruction (4): Webpack + video.js to build a streaming server front-end