EasyDSS高性能流媒体服务器前端重构(一):从零开始搭建 webpack + vue + AdminLTE 多页面脚手架

来源:互联网 发布:如何注册淘宝达人 编辑:程序博客网 时间:2024/05/17 09:31

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 -vv6.10.0npm -v5.3.0mkdir easydss-web-srccd easydss-web-srcnpm 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-devnpm 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 打造流媒体服务器前端

阅读全文
0 0
原创粉丝点击