EasyDSS高性能RTMP、HLS(m3u8)、HTTP-FLV流媒体服务器前端重构(五)- webpack + vue-router 开发单页面前端实现按需加载

为了让页面更快完成加载, 第一时间呈现给客户端, 也为了帮助客户端节省流量资源, 我们可以开启 vue-router 提供的按需加载功能, 让客户端打开页面时, 只自动加载必要的资源文件, 当客户端操作页面, 切换功能模块, 触发页面路由变化时, 再去加载相应需要的资源.

本系列博客的前面几篇一直在讲利用 webpack + vue 开发多页面前端, 然而多页面并不是利剑所指. 本篇将重点介绍使用 vue-router 开发单页面前端, 并且实现按需加载的效果.

关于 vue-router 的按需加载, 官方的文档点这里, 这篇博客主要是根据文档的内容, 结合实例代码, 按步骤,把需要的操作过一遍, 如果你觉得文档描述不够细节, 可以往下看看.

下面, 我们基于 blog_4 的代码 , 把它改造成按需加载的单页面.

安装 vue-router

npm i vue-router -D

修改 webpack.config.js 配置

修改 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']
    },
    output: {
        path: resolve('dist'), // 指示发布目录
        chunkFilename: 'js/[name].[chunkhash:8].js',
        filename: 'js/[name].[chunkhash:8].js' //指示生成的页面入口js文件的目录和文件名, 中间包含8位的hash值
    },
    externals: {
        //video.js 作为外部资源引入
        'video.js': 'videojs'
    },
    //下面给一些常用组件和目录取别名, 方便在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'
        }),
        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: #app
            chunks: ['index'],
            template: './dll/template.html',
            minify: {
                removeComments: true,
                collapseWhitespace: false
            }
        })
    ]
};

只保留一个 entry, 一个 HtmlWebpackPlugin
在 output 中添加 chunkFilename 属性, 以对按需加载的 js 文件命名

新建路由文件

add src/router.js

import Vue from 'vue'
import Router from 'vue-router'
import AdminLTE from 'components/AdminLTE.vue'

import Index from 'components/Index.vue'
// import Player from 'components/Player.vue'
const Player = () => import(/* webpackChunkName: 'player' */ 'components/Player.vue')
const About = () => import(/* webpackChunkName: 'about' */ 'components/About.vue')

Vue.use(Router);

const router = new Router({
    routes: [
        {
            path: '/',
            component: AdminLTE,
            children: [
                {
                    path: '',
                    component: Index
                }, {
                    path: 'player',
                    component: Player
                }, {
                    path: 'about',
                    component: About
                }
            ]
        }
    ],
    linkActiveClass: 'active'
})

export default router;

/ --> Inex 直接加载
/player --> Player 懒加载
/about --> About 懒加载

其中, Player 和 About 两个组件以按需加载的方式的引入, 区别于 Index, 注意文件开始部分, import 的写法, 就是 vue-router 官方文档中 懒加载 部分的写法

改造入口文件

src/index.js

import Vue from 'vue'
import store from 'src/store'
import router from 'src/router'

new Vue({
  el: '#app',
  store,
  router,
  template: `
  <transition>
    <router-view></router-view>
  </transition>`
})

router-view 标签用于占位路由指向的目标组件

修改 Sider 为路由导航

components/Sider.vue

<template>
  <aside id="slider" class="main-sidebar">
    <section class="sidebar">
      <ul class="sidebar-menu">
          <router-link class="treeview" v-for="(item,index) in menus" :key="index" tag="li" :exact="item.path == '/'" :to="item.path">
            <a>
              <i :class="['fa', 'fa-' + item.icon]"></i>
              <span>{{item.text}}</span>
            </a>
          </router-link>
      </ul>
    </section>
  </aside>
</template>

<script>
export default {
  props: {
    menus : {
        default : () => []
    }
  }
}
</script>

router-link 定义路由导航链接, 这里路由链接渲染为 li > a 的形式, 当前访问路径为 router-link 或者其子路由时, 该 router-link 自动触发为**状态, **状态的 class name 可以通过 linkActiveClass 指定, 我们在前面的创建 router 的时候, 统一将其指定为 active

如果只想让访问路径严格匹配路由时, 触发为**状态, 即子路由**时, 父路由不**, 需要在父路由上使用 exact 标识. 这个选项, 常用于首页路由 /

改造 AdminLTE.vue

将原来的 slot 占位, 替换成 router-view 占位

<template>
  <div class="wrapper">
    <NaviBar :logoText="logoText" :logoMiniText="logoMiniText"></NaviBar>
    <Sider :menus="menus"></Sider>
    <div class="content-wrapper">
      <section class="content">
        <transition>
          <router-view @btnClick="btnClick"></router-view>
        </transition>
      </section>
    </div>
  </div>
</template>

<script>
import "font-awesome/css/font-awesome.css"
import "admin-lte/bootstrap/css/bootstrap.css"  
import "admin-lte/dist/css/AdminLTE.css"
import "admin-lte/dist/css/skins/_all-skins.css"

import "admin-lte/bootstrap/js/bootstrap.js"
import "admin-lte/dist/js/app.js"

import { mapState } from "vuex"
import Vue from 'vue'

import Sider from 'components/Sider'
import NaviBar from 'components/NaviBar'

export default {
  data() {
    return {
    }
  },
  mounted() {
    $(() => {
      $.AdminLTE.layout.fix();
    })
  },
  components: {
    NaviBar, Sider
  },
  computed: {
    //访问 vuex store 中的数据
    //此处用到 es6 stage-2 才有的三个点展开对象的语法, 对应 .babelrc 中的配置
    ...mapState([
      "logoText",
      "logoMiniText",
      "menus"
    ])
  },
  methods: {
    btnClick(msg){
      alert(msg);
    }
  }  
}
</script>

运行程序, 看看效果

npm run start

EasyDSS高性能RTMP、HLS(m3u8)、HTTP-FLV流媒体服务器前端重构(五)- webpack + vue-router 开发单页面前端实现按需加载

如上图所示, 当首次点击 [HLS 播放器] 改变路由时, 从 Netwok 观察到加载 player.xxx.js , 表明懒加载开启成功

代码位置: https://github.com/penggy/easydss-web-src/tree/blog_5

关于EasyDSS

EasyDSS(http://www.easydss.com)流媒体解决方案采用业界优秀的流媒体框架模式设计,服务运行轻量、高效、稳定、可靠、易维护,支持RTMP直播、RTMP推送、HTTP点播、HTTP-FLV直播、HLS直播,并支持关键帧缓冲,画面秒开等多种特性,能够接入Web、Android、iOS、H5、微信等全平台客户端,是移动互联网时代贴近企业点播/直播需求的一款接地气的流媒体服务器,配套OBS、EasyRTMP等直播推流工具以及EasyPlayer等网络播放器,可以形成一套完整的视频直播、录播解决方案,满足用户在各种行业场景的流媒体业务需求。
EasyDSS高性能RTMP、HLS(m3u8)、HTTP-FLV流媒体服务器前端重构(五)- webpack + vue-router 开发单页面前端实现按需加载

适用场景

EasyDSS高性能RTMP、HLS(m3u8)、HTTP-FLV流媒体服务器前端重构(五)- webpack + vue-router 开发单页面前端实现按需加载
EasyDSS高性能RTMP、HLS(m3u8)、HTTP-FLV流媒体服务器前端重构(五)- webpack + vue-router 开发单页面前端实现按需加载
EasyDSS高性能RTMP、HLS(m3u8)、HTTP-FLV流媒体服务器前端重构(五)- webpack + vue-router 开发单页面前端实现按需加载