本文围绕着实现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中 importresolve: {extensions: ['.js', '.vue', '.json'],alias: {'vue/div>: 'vue/dist/vue.common.js','jquery/div>: '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: #appchunks: ['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高性能流媒体服务器的前端框架来展开的,具体EasyDSS的相关信息可在:www.easydss.com 找到!

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

在上一篇博客中, 我们白手起家, 从零搭建了 webpack + vue + AdminLTE 多页面脚手架. 代码在这里: easydss-web-src , 我为第一篇博客建立了单独的分支 blog_1 , 并且我打算后面的系列都这样, 建立一个单独分支.

为什么要提取共用文件

我们已经创建了两个静态页面: index.html 和 about.html, 对应的入口 js 分别是 index.js 和 about.js.

运行编译

npm run build

![优化前的 build 耗时](https://img-blog.csdn.net/20170826220921335?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvc3Rhcml0c3Rhcml0/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast) build 两个如此简单的两个页面, 耗时 6 秒多, 可以预见在实际开发中将面临 build 耗时长的问题. 并且我们看到 build 后的 js 文件达到 1.05MB 之大. 实际上, index.js 和 about.js 两者是有共用部分的, 这其中包括 jquery , vue, AdminLTE 这些基础组件. 本篇的主题就是介绍如何提取这部分共用的组件出来单独编译.

webpack.DllPlugin

类似于 Windows 中 DLL 动态库的概念, 在 webpack 2 当中, 引入了 DllPlugin. 借助这个插件, 我们能够把共用的组件 build 到一块, 生成 vendor.js, 然后在静态页面中, 引用这个 vendor.js.

新建组件打包配置文件: webpack.dll.config.js

const HtmlWebpackPlugin = require('html-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const webpack = require('webpack');
const path = require('path');function resolve(dir) {return path.resolve(__dirname, dir)
}module.exports = {entry: {//提取共用组件, 打包成 vendor.jsvendor: ['jquery', 'vue', 'vuex', 'babel-polyfill','font-awesome/css/font-awesome.css', 'admin-lte/bootstrap/css/bootstrap.css','admin-lte/dist/css/AdminLTE.css', 'admin-lte/dist/css/skins/_all-skins.css','admin-lte/bootstrap/js/bootstrap.js', 'admin-lte/dist/js/app.js']},output: {path: resolve('dll'),filename: 'js/[name].[chunkhash:8].js',library: '[name]_library'},resolve: {extensions: ['.js', '.vue', '.json'],alias: {'vue/div>: 'vue/dist/vue.common.js','jquery/div>: 'admin-lte/plugins/jQuery/jquery-2.2.3.min.js'}},module: {rules: [{test: /\.css$/,loader: 'style-loader!css-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: /\.(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'}),         new webpack.HashedModuleIdsPlugin(),new CleanWebpackPlugin(['dll']),new webpack.DllPlugin({path: resolve("dll/[name]-manifest.json"),name: "[name]_library",context: __dirname}),new HtmlWebpackPlugin({filename: 'template.html',title: '<%= htmlWebpackPlugin.options.title %>',inject: 'head',chunks: ['vendor'],template: './src/template.html',minify: {removeComments: true,collapseWhitespace: false}})        ]
};

entry 当中, 指定哪些资源被视为共用组件
上面的配置将在当前目录下, 创建一个 dll 目录, 专门用来存放共用组件包 vendor.js, 并且生成一个 template.html, 这个 template.html 中包含对 vendor.js 的引用. template.html 将作为其他页面的基础模板使用, 这样一般静态页面就能够引用到 vendor.js 了.

webpack.DllReferencePlugin

DllReferencePlugin 这个插件用来告诉 webpack , 哪些引用到的资源已经被打包在共用组件包当中, 从而避免再次被打包.

我们来修改 webpack.config.js

const HtmlWebpackPlugin = require('html-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const CopyWebpackPlugin = require('copy-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中 importresolve: {extensions: ['.js', '.vue', '.json'],alias: {'vue/div>: 'vue/dist/vue.common.js','jquery/div>: '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'}),new webpack.DllReferencePlugin({context: __dirname,manifest: require('./dll/vendor-manifest.json')}),new CopyWebpackPlugin([{ from: 'dll', ignore: ['template.html', 'vendor-manifest.json'] }]),//编译前先清除 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: #appchunks: ['index'],template: './dll/template.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: './dll/template.html',minify: {removeComments: true,collapseWhitespace: false}})]
};

> DllReferencePlugin 通过读取 ./dll/vendor-manifest.json, 判断哪些资源引用被打包在共用组件包 > index.html 和 about.html 以 ./dll/template.html 作为模板, 从而获取对 vendor.js 的引用 > 引入 CopyWebpackPlugin 这个插件, 负责将共用组件包依赖的资源文件拷贝到发布目录下, 同时过滤掉发布时不需要的文件: template.html 和 vendor-manifest.json

使用新的编译方式

因为新引入 CopyWebpackPlugin 插件, 首先, 我们安装它

npm i copy-webpack-plugin --save-dev

修改 package.json , 添加 dll 编译配置:

{"name": "easydss-web-src","version": "1.0.0","description": "","main": "index.js","scripts": {"build": "webpack --progress --hide-modules","dll": "webpack --progress --hide-modules --config ./webpack.dll.config.js","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"}
}

编译共用组件, **你可能在整个开发过程中, 只需要执行一次对共用组件的 build, 因为一般情况下, 我们很少会对它做改动, 这一点是我优化编译时间的关键**.

npm run dll

编译静态页面, 这一次的耗时相对之前减少了 4 秒多.

npm run build

是不是离幸福又近了一点, 源码位置: easydss-web-src/tree/blog_2


EasyDSS高性能流媒体服务器前端重构(三): webpack + vue + AdminLTE 多页面引入 element-ui

接上篇《高性能流媒体服务器EasyDSS前端重构(二) webpack + vue + AdminLTE 多页面提取共用文件, 优化编译时间》

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

### element-ui 介绍 饿了么前端开发组件框架 [element-ui](http://element.eleme.io) 是 “一套为开发者、设计师和产品经理准备的基于 Vue 2.0 的组件库,提供了配套设计资源,帮助你的网站快速成型。” 本篇将在前面的脚手架基础上, 引入 element-ui, 并完成一个分页表格.

安装配置 element-ui

npm i element-ui babel-plugin-component --save-dev

> babel-plugin-component 用来控制只引入需要的组件,以达到减小项目体积的目的。 将 .babelrc 修改如下:

{"presets": ["es2015","stage-2"],"plugins": [[ "component", [{"libraryName": "element-ui","styleLibraryName": "theme-default"}]]]
}

在自己的组件中使用 element-ui

修改 index.vue 如下:

  <template><div class="container-fluid no-padding"><div class="alert alert-success">{{msg}}</div><el-table :data="tableData"><el-table-column prop="date" label="日期" width="180"></el-table-column><el-table-column prop="name" label="姓名" width="180"></el-table-column><el-table-column prop="address" label="地址"></el-table-column></el-table><br><el-pagination layout="prev,pager,next" class="pull-right" :total="total" :page-size.sync="pageSize" :current-page.sync="currentPage"></el-pagination></div>
</template><script>
import Vue from 'vue'
import { Table, TableColumn, Pagination } from 'element-ui'
Vue.use(Table);
Vue.use(TableColumn);
Vue.use(Pagination);export default {data() {return {currentPage: 1,pageSize: 10,tableData: [{date: '2016-05-02',name: '王小虎',address: '上海市普陀区金沙江路 1518 弄'}, {date: '2016-05-04',name: '王小虎',address: '上海市普陀区金沙江路 1517 弄'}, {date: '2016-05-01',name: '王小虎',address: '上海市普陀区金沙江路 1519 弄'}, {date: '2016-05-03',name: '王小虎',address: '上海市普陀区金沙江路 1516 弄'}],msg: "我是视频广场"}},computed: {total() {return this.tableData.length;},pageData() {let start = (this.currentPage - 1) * this.pageSize;let end = start + this.pageSize;return this.tableData.slice(start, end);}},
}
</script>

> 先 import 再 Vue.use ,就可以愉快的使用了. 因为前面我们安装并配置了 `babel-plugin-component` 它将自动为我们引入相关组件的样式 ### 来看看效果吧 基于前面搭建好的这一套脚手架, 后面再引入第三方组件库就变得很容易了, 我们来看看运行效果吧.

npm run start

源码位置: https://github.com/penggy/easydss-web-src/tree/blog_3


EasyDSS高性能流媒体服务器前端重构(四): webpack + video.js 打造流媒体服务器前端

接上篇《高性能流媒体服务器EasyDSS前端重构(三)- webpack + vue + AdminLTE 多页面引入 element-ui》

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

video.js 介绍

Video.js - open source HTML5 & Flash video player

作为一款高性能流媒体服务器的前端, 必不可少会用到流媒体播放器. 在播放器的选择上, 我们选中了功能强大并且开源的 video.js . 它可以用来播放 RTMP/HLS 直播流.

本篇介绍在 webpack 中集成 video.js 播放器组件, 我们将要完成一个 HLS 播放器 的小例子. 先来看一下效果图吧:

安装 video.js

我们要开发的 HLS 播放器 需要用到 video.js 的一个官方插件: videojs-contrib-hls

尽管 video.js 官方文档中给出了 webpack 集成的说明(http://docs.videojs.com/tutorial-webpack.html), 但是在实际开发过程中, 我还是和其他人一样遇到了很多坑(https://github.com/videojs/videojs-contrib-hls/issues/600) 最后, 算是将 video.js 集成好, 却发现插放 HLS 流, 不能切换到 Flash 模式. 最终, 我决定采用外部依赖的方式集成 video.js, 正好借此熟悉一下 webpack externals 的用法. 这里介绍的也就是 “外部依赖法”.

既是”外部依赖法”, 那我们首先把外部依赖的 video.js 文件准备好. 在 src 目录下新建 externals 目录, 把事先下载好的 video-js-5.19.2 目录文件拷贝到这里.

修改 template.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"><!-- video.js --><link rel="stylesheet" href="/video-js-5.19.2/video-js.css"/><script src="/video-js-5.19.2/video.js"></script><script src="/video-js-5.19.2/videojs-contrib-hls4.js"></script>        </head><body class="skin-green sidebar-mini"><div id="app"></div></body>
</html>

修改 webpack.dll.config.js 如下:

const HtmlWebpackPlugin = require('html-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const webpack = require('webpack');
const path = require('path');function resolve(dir) {return path.resolve(__dirname, dir)
}module.exports = {entry: {//提取共用组件, 打包成 vendor.jsvendor: ['jquery', 'vue', 'vuex', 'babel-polyfill','font-awesome/css/font-awesome.css', 'admin-lte/bootstrap/css/bootstrap.css','admin-lte/dist/css/AdminLTE.css', 'admin-lte/dist/css/skins/_all-skins.css','admin-lte/bootstrap/js/bootstrap.js', 'admin-lte/dist/js/app.js']},output: {path: resolve('dll'),filename: 'js/[name].[chunkhash:8].js',library: '[name]_library'},resolve: {extensions: ['.js', '.vue', '.json'],alias: {'vue/div>: 'vue/dist/vue.common.js','jquery/div>: 'admin-lte/plugins/jQuery/jquery-2.2.3.min.js'}},module: {rules: [{test: /\.css$/,loader: 'style-loader!css-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: /\.(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'}),new CleanWebpackPlugin(['dll']),new CopyWebpackPlugin([{ from: 'src/externals' }]),new webpack.DllPlugin({path: resolve("dll/[name]-manifest.json"),name: "[name]_library",context: __dirname}),new HtmlWebpackPlugin({filename: 'template.html',title: '<%= htmlWebpackPlugin.options.title %>',inject: 'head',chunks: ['vendor'],template: './src/template.html',minify: {removeComments: true,collapseWhitespace: false}})        ]
};

> 引入 CopyWebpackPlugin 将 externals 目录下的外部依赖文件拷贝到 dll 目录, 最终, 这些外部依赖文件将被拷贝到发布目录下 修改 webpack.config.js 如下:

const HtmlWebpackPlugin = require('html-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const CopyWebpackPlugin = require('copy-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'],player: ['babel-polyfill', './src/player.js'],about: ['babel-polyfill', './src/about.js']},output: {path: resolve('dist'), // 指示发布目录filename: 'js/[name].[chunkhash:8].js' //指示生成的页面入口js文件的目录和文件名, 中间包含8位的hash值},externals: {//video.js 作为外部资源引入'video.js': 'videojs'},//下面给一些常用组件和目录取别名, 方便在js中 importresolve: {extensions: ['.js', '.vue', '.json'],alias: {'vue/div>: 'vue/dist/vue.common.js','jquery/div>: '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'}),new webpack.DllReferencePlugin({context: __dirname,manifest: require('./dll/vendor-manifest.json')}),new CopyWebpackPlugin([{ from: 'dll', ignore: ['template.html', 'vendor-manifest.json'] }]),//编译前先清除 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: #appchunks: ['index'],template: './dll/template.html',minify: {removeComments: true,collapseWhitespace: false}}),new HtmlWebpackPlugin({filename: 'player.html',title: 'HLS 播放器',inject: true,chunks: ['player'],template: './dll/template.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: './dll/template.html',minify: {removeComments: true,collapseWhitespace: false}})]
};

> 重点是在 externals 块下面声明 videojs 作为外部资源使用 > 然后, 我们添加一个新的静态页面配置, 用做 HLS 播放器的入口

添加左侧菜单项

打开 src/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: "/player.html",icon: "play",text: "HLS 播放器"}, {path: "/about.html",icon: "support",text: "版本信息"}]},getters : {},mutations: {},actions : {}
})export default store;

编写HLS 播放器 页面

将 video.js 简单封装成组件, 新建 src/compontents/VideoJS.vue

<template><div class="player-wrapper"><div class="video-wrapper" style="padding-bottom:55%;position:relative;margin:0 auto;overflow:hidden;"><div class="video-inner" style="position:absolute;top:0;bottom:0;left:0;right:0;"></div></div></div>
</template><script>
videojs.options.flash.swf = '/video-js-5.19.2/video-js-fixed.swf';
videojs.options.techOrder = ['html5', 'flash'];if (videojs.browser.IE_VERSION) { // if IE use flash firstvideojs.options.techOrder = ['flash', 'html5'];
}export default {data() {return {player: null}},props: {videoUrl: {default: ""},autoplay: {default: true}},beforeDestroy() {this.destroyVideoJS();},deactivated() {this.destroyVideoJS();},watch: {videoUrl: function(val) {this.destroyVideoJS();this.initVideoJS();}},mounted() {this.initVideoJS();},computed: {type() {let _type = "application/x-mpegURL";if (this.rtmp) {_type = "rtmp/mp4";}return _type;},rtmp() {return (this.src || "").indexOf("rtmp") == 0;},src() {if (!this.videoUrl) {return "";}if (this.videoUrl.indexOf("/") === 0) {return location.protocol + "//" + location.host + this.videoUrl;}return this.videoUrl;},videoHtml() {return `<video class="video-js vjs-default-skin vjs-big-play-centered" style="width: 100%; height: 100%;" controls preload="none"><source src="${this.src}" type="${this.type}"></source><p class="vjs-no-js">To view this video please enable JavaScript, and consider upgrading to a web browser that<a href="http://videojs.com/html5-video-support/" target="_blank">supports HTML5 video</a></p></video>            `;}},methods: {destroyVideoJS() {if (this.player) {this.player.dispose();this.player = null;}},initVideoJS() {$(this.$el).find(".video-inner").empty().append(this.videoHtml);if (!this.src) {return;}if (this.rtmp) {this.player = videojs($(this.$el).find("video")[0], {notSupportedMessage: '您的浏览器没有安装或开启Flash',tech: ['flash'],autoplay: this.autoplay});this.player.on("error", e => {var $e = $(this.$el).find(".vjs-error .vjs-error-display .vjs-modal-dialog-content");var $a = $("<a href='http://www.adobe.com/go/getflashplayer' target='_blank'></a>").text($e.text());$e.empty().append($a);})} else {this.player = videojs($(this.$el).find("video")[0], {autoplay: this.autoplay});}}}
}
</script>

> 封装 video.js api 编写播放器弹出框组件, 新建 src/components/VideoDlg.vue

<template><div class="modal fade" data-keyboard="false" data-backdrop="static"><div class="modal-dialog modal-lg"><div class="modal-content"><div class="modal-header"><button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button><h4 class="modal-title text-success text-center">{{videoTitle}}</h4></div><div class="modal-body"><VideoJS v-if="bShow" :videoUrl="videoUrl"></VideoJS></div><div class="modal-footer"><button type="button" class="btn btn-default" data-dismiss="modal">关闭</button></div></div></div></div>
</template><script>
import VideoJS from './VideoJS.vue'export default {data() {return {videoUrl: "",videoTitle: "",bShow: false}},mounted() {$(document).on("hide.bs.modal", this.$el, () => {this.bShow = false;}).on("show.bs.modal", this.$el, () => {this.bShow = true;})},components: { VideoJS },methods: {play(src,title) {this.videoUrl = src||"";this.videoTitle = title||"";$(this.$el).modal("show");}}
}
</script>

封装 bootstrap 模态框

编写HLS播放器页面内容, 新建 src/components/Player.vue

<template><div class="container-fluid no-padding"><br><div class="col-sm-8 col-sm-offset-2"><form role="form" class="form-horizontal" id="url-form"><div class="form-group"><div class="input-group" id="input-url-group"><input type="text" class="form-control" id="input-url" name="url" placeholder="输入播放地址" v-model.trim="url" @keydown.enter.prevent="play"><span class="input-group-btn"><a class="btn btn-primary" role="button" @click.prevent="play"><i class="fa fa-play"></i> 播放</a></span></div></div></form></div></div>
</template><script>
import Vue from 'vue'
import { Message } from 'element-ui'Vue.prototype.$message = Message;export default {data() {return {url: ""}},methods: {play() {if (!this.url) {this.$message({type: 'error',message: "播放地址不能为空"});return;}this.$emit("play", { videoUrl: this.url, videoTitle: this.url});}}
}
</script>

这里顺带演示了 element-ui 的 Message 用法
点击播放按钮, 消息向父组件传递, 播放地址作为参数一起传递

编写入口 js , 新建 src/player.js

import Vue from 'vue'
import store from "./store";
import AdminLTE from './components/AdminLTE.vue'
import Player from './components/Player.vue'
import VideoDlg from './components/VideoDlg.vue'new Vue({el: '#app',store,template: `<AdminLTE><VideoDlg ref="videoDlg"></VideoDlg><Player @play="play"></Player></AdminLTE>`,components: {AdminLTE, Player, VideoDlg},methods: {play(video){this.$refs.videoDlg.play(video.videoUrl, video.videoTitle);}}
})

接收 Player 组件传来的播放消息, 打开播放器弹出框, 完成视频播放

运行

我们修改了 template.html 和 webpack.dll.config.js , 所以先要重新 build 共用组件库

npm run dll

然后

npm run start

源码位置: [https://github.com/penggy/easydss-web-src/tree/blog_4](https://github.com/penggy/easydss-web-src/tree/blog_4)

获取更多信息

邮件:support@easydarwin.org

WEB:www.EasyDarwin.org

Copyright © EasyDarwin.org 2012-2017


转自:
https://blog.csdn.net/xiejiashu/article/details/77647836

EasyDSS高性能流媒体服务器前端重构相关推荐

  1. EasyDSS高性能流媒体服务器前端重构(六)- webpack-dev-server 支持手机端访问

    很多时候,前端开发的页面,不仅要在PC端测试效果, 还要在手机端测试效果. 在开发阶段, 我们以 webpack-dev-server 来启动浏览器, 打开正在开发的页面. webpack-dev-s ...

  2. EasyDSS高性能流媒体服务器前端重构(五)- webpack + vue-router 开发单页面前端实现按需加载 - 副本...

    为了让页面更快完成加载, 第一时间呈现给客户端, 也为了帮助客户端节省流量资源, 我们可以开启 vue-router 提供的按需加载功能, 让客户端打开页面时, 只自动加载必要的资源文件, 当客户端操 ...

  3. EasyDSS高性能流媒体服务器开发RTMP直播同步输出HLS(m3u8)录像功能实现时移回放的方案

    EasyDSS商用流媒体服务器解决方案是由EasyDarwin开源团队原班人马开发的一套集流媒体点播.转码与管理.直播.录像.检索.时移回看于一体的一套完整的商用流媒体服务器解决方案,支持RTMP推流 ...

  4. webpack服务器性能,高性能流媒体服务器EasyDSS前端重构(三)- webpack + vue + AdminLTE 多页面引入 element-ui...

    接上篇 本文围绕着实现EasyDSS高性能流媒体服务器的前端框架来展开的,具体EasyDSS的相关信息可在:www.easydss.com 找到! element-ui 介绍 饿了么前端开发组件框架 ...

  5. 高性能流媒体服务器EasyDSS前端重构(二) webpack + vue + AdminLTE 多页面提取共用文件, 优化编译时间...

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

  6. EasyDSS高性能RTMP、HLS(m3u8)、HTTP-FLV、RTSP流媒体服务器前端源码重构(四)- webpack + video.js 打造流媒体服务器前端

    EasyPlayer播放器是基于EasyDSS流媒体服务器视频强大的后台管理能力,提供视频点播和直播播放能力的强大播放载体.流畅稳定的播放性能,集广告植入.数据监测等功能于一身,为开发者提供端到端的一 ...

  7. 使用Nginx架设高性能流媒体服务器

    前言* 随着Nginx 大量被各互联网公司使用,常见的Nginx 用来做反向代理WEB 服务器.缓存服务器,nginx 应用很广泛,今天我们来研究一下使用Nginx架设高性能流媒体服务器 . 一. 系 ...

  8. EasyDSS高性能RTMP、HLS(m3u8)、HTTP-FLV、RTSP流媒体服务器前端源码重构(三)- webpack + vue + AdminLTE 多页面引入 element-ui

    EasyDSS流媒体解决方案总体可划分成三个部分:前端视频源设备(PC.手机.摄像机)流媒体数据获取并即时回传.流媒体服务器端直播和录像与回放.客户端直播播放与录像检索回放.前端推流我们使用跨平台的R ...

  9. EasyDSS高性能RTMP、HLS(m3u8)、HTTP-FLV、RTSP流媒体服务器前端源码重构(二)-webpack + vue + AdminLTE 多页面提取共用文件, 优化编译时间

    接上篇 在上一篇博客中, 我们白手起家, 从零搭建了 webpack + vue + AdminLTE 多页面脚手架. 代码在这里: easydss-web-src , 我为第一篇博客建立了单独的分支 ...

最新文章

  1. dede 删除文章的同时自动删除生成的html文件夹,删除织梦自带编辑器自动加DIV的方法...
  2. [VSTO系列]三、简单的UI设计/QQ联系人导出(下)
  3. selenide_使用Selenide进行有效的UI测试
  4. 第十二届蓝桥杯A组省赛试题 I: 双向排序(Java)
  5. Spark structured 记录一次kudu扩容导致无法写入数据的问题
  6. python的实例属性_python 实例属性和类属性
  7. 微信小程序实现浮动按钮
  8. 几何画板椭圆九种画法_几何画板中椭圆的几种构造方法
  9. 计算机软件税负率,软件产品增值税超税负退税实务问题
  10. 深度学习模型并非“越大越好”,它可能引起气候变化问题
  11. 数据竞赛:工业互联网算法大赛能源赛道风机轴承剩余寿命预测
  12. Linux服务器下安装ANSYS
  13. 通过键盘移动鼠标光标 autohotkey
  14. 云服务器和虚拟主机有哪些区别
  15. Maven pom 继承聚合
  16. Docker制作base镜像
  17. matlab计算涡度的函数_涡度的计算
  18. 【三维点云】CC教程1(Context Capture)
  19. 轨道姿态常用编程缩写
  20. 微软官方Windows Server 2003相关教程免费下载

热门文章

  1. 虾扑ERP店铺管理系统,一键式上新帮助跨境卖家解决铺货难题 !
  2. Minecraft 1.19.2 Forge模组开发 04.动画效果物品
  3. 【EduCoder答案】Java高级特性- Java反射
  4. [错误解决] [Java] iphone 华为 搜狗 手机 浏览器 下载文件 名称乱码
  5. python实现对文章标题重写的小技巧!
  6. 基于单片机汽车超声波防盗系统设计(毕设课设资料)
  7. zhs16gbk java_oracle11g 修改字符集 修改为ZHS16GBK
  8. oracle 改成中文字符,修改 oracle xe 默认中文字符集成为:SIMPLIFIED CHINESE_CHINA.ZHS16GBK...
  9. 远程服务器可以挂游戏,什么远程桌面可以挂游?
  10. 工业企业用电管理系统建设-能耗监测系统开发