zoukankan      html  css  js  c++  java
  • Vue.js Cli 3.0 多页面开发案例解析

    本文梗概

    Vue 是很好用,但是以往的都是单页面应用,这就导致了一些传统的项目移植困难,一些用了 JQ 的插件的等等写法都要改变。也还用专门找到相对于的 Vue 的插件才行,这次的 Cli 3.0 可以在原来项目的基础上直接移植,非常方便。

    在本文中,会讲到如下内容:

    Vue 多页面的优势与劣势
    Cli 3.0 的基本配置
    Cli 3.0 多页面的打包上线
    Cli 3.0 的目录解析
    如何提升构建效率
    受众人群:经常用 Vue 单页面开发的人员,对多页面有兴趣,且实际工作中有需求。老项目想前后端分离,考虑效率又不想用单页面重写的开发人员。

    前言

    Vue.js 3.0 支持单页面也支持多页面,不过对用久了 2.0 人的来说,开始还会有一点不习惯。创建方式、目录结构、运行命令都有差异。

    本文将围绕实际多页面开发案例,剖析多页面从构建到上线一条龙的过程。自定义配置有蛮多种,这里只是只说其中一种。供大家参考使用。

    本篇文章的目录结构核心如下:

    在这里插入图片描述

    一、简述优劣势

    单页面应用开发(SPA)

    • 概念:只有一个 HTML 页面,所以跳转的方式是组件之间的切换;
    • 优点:跳转流畅,组件化开发,组件可复用,开发便捷;
    • 缺点:首屏加载过慢,SEO 优化不好。

    多页面应用开发(MPA)

    • 概念:有多个页面,跳转方式是页面之间的跳转;
    • 优点:组件化开发,组件可复用,开发便捷,首屏加载快,SEO 优化好;
    • 缺点:跳转是整个页面刷新 。

    以上一对比,多页面还是有蛮多优势的,特别是在老项目想前后端分离的时候,尤为突出。虽然近几年 Vue 等框架兴起,但是以前用 JQ、JS、其他插件写的项目也不少。大多都是没有分离的,前后端分工不明确,甚至导致前端只是个写页面的,丢给跟后台去套数据。通俗点来说就是个页面仔、效果仔了。

    多页面(MPA)完美地解决了这个问题,可以快速地在之前的情况下使用,并存。

    二、目录文件解析

    新建项目,执行:

    $ vue create demo
    

    这里选默认第一个就好。

    在这里插入图片描述

    接下来用哪个方式都行,我是习惯用 npm。

    在这里插入图片描述

    等待下载完成,初始化的目录 (为了大家清楚地对比多页面改造后的,我把初始化跟改造后都列出来来供大家比对分析):

    在这里插入图片描述

    
    ├── node_modules npm install 生成
    └── public 打包所需静态资源
        ├── index.html 模板文件
        └── favicon.ico 浏览器图标 
    └── src
        └── assets 项目静态资源
            ├── logo.png
        ├── components 业务组件
        ├── App.vue
        ├── main.js 
        ├── .gitignore Git提交时忽略配置
        ├── babel.config.js babel配置
        ├── package.json
        ├── package-lock.json
        ├── README.md
        └── vue.config.js ( 需要自行创建 )
    

    跟 2.0 相比,目录简化许多,webpack 配置也集成到 node_modules 去了。

    留有一个配置入口,就是 vue.config.js 文件,这需要自行创建,
    如没有就会用默认的 http://localhost:8080/。

    也就是 2.0 中 config/Index.js 的配置移到这里去了,包括代理都在这写。

    这里是已经改造成多页面的目录:

    在这里插入图片描述

    如上图,目录大体跟初始化的差不多,唯一的就是配置的在 src 中页面,一个目录生产出来就是一个单独的 HTML。

    详细对应描述:

    ├── dist 打包后目录
    ├── node_modules npm install 生成
    └── public 打包所需静态资源
        └── favicon.ico 浏览器图标 
    └── src
        └── assets 项目静态资源
            ├── logo.png
        ├── components 业务组件
        ├── pages 页面文件
             └── index 单个页面目录
                     └── index.html 单个页面目录
                     └── index.js 单个页面入口 js (相当于 2. 0 的 main.js)
                     └── index.vue 此页面页面组件
        ├── util 配置放置目录
                └── axiosTool.js 请求封装
                └── cssCopy.js 多页面 css 配置文件
                └── getPages.js 多页面 模板 配置文件
                └── htmlReplace.js 多页面 html 生成规则配置文件
                └── jsCopy.js 多页面 js 配置文件
        ├── .gitignore Git提交时忽略配置
        ├── babel.config.js babel配置
        ├── package.json
        ├── package-lock.json
        ├── postcss.config.js
        ├── README.md
        └── vue.config.js
    

    3.0 的目录结构简洁,多页面的目录可以自行修改。改造后,原本的 public 的 index.html 就不需要了,每个页面都有一个 index.html。

    三、改造配置

    配置文件

    配置分为四个模块,CSS 拷贝、JS 拷贝、HTML 规则以及获取页面。

    在 util 中新建 cssCopy.js、jsCopy.js、htmlReplace.js、getPages.js 文件。引入 Node 的 fs 跟 glob 模块:

    
    ### cssCopy.js:
    
    var fs = require( 'fs' );
    const glob = require('glob');
    /**
     * css文件拷贝
     * @param src
     * @param dst
     */
    var callbackFile = function( src, dst ){
        fs.readFile(src,'utf8',function(error,data){
            if(error){
                console.log(error);
                return false;
            }
            fs.writeFile(dst,data.toString(),'utf8',function(error){
                if(error){
                    console.log(error);
                    return false;
                }
                // console.log('CSS写入成功');
                fs.unlink(src,function () {// css删除成功
                })
            })
        })
    };
    // 复制目录
    glob.sync( './dist/css/*.css').forEach((filepath,name) => {
        let fileNameList = filepath.split('.');
        let fileName = fileNameList[1].split('/')[3];// 多页面页面目录
        let copyName = filepath.split('/')[3];
        let changeDirectory = `./dist/${fileName}/css`;// 多页面JS文件地存放址
        fs.exists( changeDirectory, function( exists ){
            if( exists ){// 已存在
               // console.log(`${fileName}下CSS文件已经存在`)
                callbackFile(filepath,`${changeDirectory}/${copyName}`)
            } else{// 不存在
                fs.mkdir( changeDirectory, function(){
                    callbackFile(filepath,`${changeDirectory}/${copyName}`)
                 //   console.log(`${fileName}下CSS文件创建成功`)
                });
            }
        });
    });
    
    
    
    ### jsCopy.js:
    
    var fs = require( 'fs' );
    const glob = require('glob');
    /**
     * JS文件拷贝
     * @param src
     * @param dst
     */
    let remoevePath = null
    var callbackFile = function( src, dst ){
        fs.readFile(src,'utf8',function(error,data){
            if(error){
                console.log(error);
                return false;
            }
            fs.writeFile(dst,data.toString(),'utf8',function(error){
                if(error){
                    console.log(error);
                    return false;
                }
                if(dst.includes('.map')){
                    // let srcName = src.split('/')[4];
                    // fs.unlink(`./dist/js/${srcName}.map`,function () {// 删除map
                    // })
                    // fs.unlink(`./dist/js/${srcName}`,function () {// 删除js
                    // })
                }else{//JS写入成功
                    callbackFile(dst,`${dst}.map`)
                }
            })
        })
    };
    // 复制目录
    glob.sync( './dist/js/*.js').forEach((filepath,name) => {
        let fileNameList = filepath.split('.');
        let fileName = fileNameList[1].split('/')[3];// 多页面页面目录
        let copyName = filepath.split('/')[3];
        let changeDirectory = `./dist/${fileName}/js`;// 多页面JS文件地存放址
        if(!fileName.includes('chunk-vendors')){
            fs.exists( changeDirectory, function( exists ){
                if( exists ){// 已存在
                    // console.log(`${fileName}下JS文件已经存在`)
                    callbackFile(filepath,`${changeDirectory}/${copyName}`)
                } else{// 不存在
                    fs.mkdir( changeDirectory, function(){
                        callbackFile(filepath,`${changeDirectory}/${copyName}`)
                        // console.log(`${fileName}下JS文件创建成功`)
                    });
                }
            });
        }
    });
    
    
    
    ### htmlReplace.js
    
    var fs = require( 'fs' );
    const glob = require('glob');
    /**
     * html文件替换
     * @param src
     * @param dst
     */
    var callbackFile = function( src,dst, name, filepath ){
        fs.readFile(src,'utf8',function(error,data){
            if(error){
                console.log(error);
                return false;
            }
            let regCss = new RegExp("/css/"+name+"",'g');
            let regJs = new RegExp("/js/"+name+"",'g');
            let htmlContent = data.toString().replace(regCss,`./css/${name}`).replace(regJs,`./js/${name}`);
            fs.writeFile(dst,htmlContent,'utf8',function(error){
                if(error){
                    console.log(error);
                    return false;
                }
                // console.log('html重新写入成功');
                if(src.indexOf('/index.html') == -1){
                    fs.unlink(src,function () {
                        //  console.log('html删除成功')
                    })
                }
                fs.unlink(filepath,function () {// css删除成功
                })
                fs.unlink(filepath+'.map',function () {// css删除成功
                })
            })
        })
    };
    // 复制目录
    glob.sync( './dist/js/*.js').forEach((filepath,name) => {
        let fileNameList = filepath.split('.');
        let fileName = fileNameList[1].split('/')[3];// 多页面页面目录
        let thisDirectory = `./dist/${fileName}/${fileName}.html`;// 多页面JS文件地存放址
        let changeDirectory = `./dist/${fileName}/index.html`;// 多页面JS文件地存放址
        if(!fileName.includes('chunk-vendors')){
            callbackFile(thisDirectory,changeDirectory,fileName,filepath)
        }
    });
    
    
    
    ### getPages.js
    
    const glob = require('glob')
    let pages = {}
    module.exports.pages = function (){
        glob.sync( './src/pages/*/*.js').forEach(filepath =>
        {
            let fileList = filepath.split('/');
            let fileName = fileList[fileList.length-2];
            pages[fileName] = {
                entry: `src/pages/${fileName}/${fileName}.js`,
                // 模板来源
                template: `src/pages/${fileName}/${fileName}.html`,
                // 在 dist/index.html 的输出
                filename: process.env.NODE_ENV === 'development'?`${fileName}.html`:`${fileName}/${fileName}.html`,
                // 提取出来的通用 chunk 和 vendor chunk。
                chunks: ['chunk-vendors', 'chunk-common', fileName]
            }
        })
        return pages
    };
    

    最后在 vue.config.js 中引入:

    let pageMethod = require('./util/getPages.js');
    pages = pageMethod.pages(); 
    module.exports = { 
    pages
    }
    

    以上的 jsCopy、cssCopy、htmlReplace 是在打包的时候执行的,在 package.json 中加入。

    在这里插入图片描述

    到这里,多页面的配置修改就完了。

    改造的原理就是,利用 Node 的文件系统把生成的文件,进行移动复制、组合,按照一个页面一个目录,一个页面三个文件,以达到能组件化开发,打包后多个 HTML 文件。

    运行

    $ npm run serve
    

    1. 检查

    下面是 src 目录文件:

    在这里插入图片描述

    一个目录一个 HTML 页面,目录中 index.html 是入口文件,相当于单页面中的 index.html。

    index.js 就相当于单页面的 man.js。index.vue 就相当于单页面中的内容组件了。

    这里引入:

    在这里插入图片描述

    也就是一个页面一个 Vue 实例,这目录中的三个文件名字最好一致,打包后就是一个页面。

    index.html 中可以把老项目中的 JS、CSS 全部在这引入。内容部分的就直接复制到 index.vue 中,有公用的部分,头部底部什么的就组件放在 components 中在 index.vue 中调用就行了。

    2. 注意

    除了 Vue 路由无法使用之外,其他都是可以使用的。包括 Vuex,用法跟单页面的一样。只是每个入口 JS 文件中要注册一次罢了。
    接下来就是页面跳转问题,跳转直接用 a 标签。

    目录下记得用绝对路径。多页面构建推荐用绝对路径。因打包后目录原因,开发环境跟生产环境中的路由有差异。也就是开发环境需要加上 .html 后缀,生产环境则不需要。也就是两种写法。

    <li><a href="/index.html/?orderNo=2"> index页面,dev 的时候可用的写法</a></li>
    
    <li><a href="/index/?orderNo=2"> index页面,production 可用的写法</a></li>
    

    就是在 dev 时候就等于这样:

    • dev 中页面跳转需要加 .html 文件后缀
    • production 跳转不需要文件后缀

    在这里插入图片描述

    这也就产生了一个问题,自己的 dev 调试的时候是不需要后缀的,要上线的时候得把所有的 a 标签跳转链接的后缀加上,这就太麻烦了。

    方法有蛮多,我这用的是判断 production 来替换。

    /**
     * 打包后路由修正
     * @returns {string}
     */
    export function urlRouter (){
        let urlRouters = ".html";
        if (process.env.NODE_ENV === "production") {
            // 为线上环境修改配置...
            urlRouters=""
        }
    
        return urlRouters
    
    }
    

    这样就全部统一写法了。反正就线上不需要后缀,在 dev 时候要后缀,大家自行想办法解决这问题。不一定非用我这方法。

    <a :href="`/index${configTool.urlRouter()}/?id=${id}`" >
    

    四、打包上线

    先来看一下打包之后的 dist 目录:

    在这里插入图片描述

    这两个目录是页面目录

    在这里插入图片描述

    里面就是该页面的资源文件。

    生成环境访问是这样:

    在这里插入图片描述

    因为每个目录下都有该页面的资源,一个目录下都有 index.html。 需要在 dist 中加一个总入口中转文件 index.html。

    在这里插入图片描述

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    <body>
    <script>
        window.location.href="/index/"
    </script>
    
    </body>
    </html>
    

    到此,上线发布就完成了。把 dist 目录的文件丢到服务器就可以了,推荐用的是放在根目录,不然会找不到资源。官网也推荐多页面应用的情况下避免用相对路径。

    官网文档中警告:

    在这里插入图片描述

    五、提高构建效率

    提高的方式有很多种,这里推荐使用 webpack-parallel-uglify-plugin 插件。

    优化原理

    默认情况下 webpack 使用 UglifyJS 插件进行代码压缩,但由于其采用单线程压缩,速度很慢。

    我们可以改用 webpack-parallel-uglify-plugin 插件,它可以并行运行 UglifyJS 插件,从而更加充分、合理的使用 CPU 资源,从而大大减少构建时间。

    操作步骤

    1. 执行如下命令安装 webpack-parallel-uglify-plugin:

    npm i webpack-parallel-uglify-plugin
    

    2. 打开 vue.config.js 文件,并作如下修改:

    const ParallelUglifyPlugin = require('webpack-parallel-uglify-plugin');
    //....
        // webpack 提供的 UglifyJS 插件删不删都行,随便,可以并存
        //new UglifyJsPlugin({
        //  uglifyOptions: {
        //    compress: {
        //      warnings: false
        //    }
        //  },
        //  sourceMap: config.build.productionSourceMap,
        //  parallel: true
        //}),
        // 增加 webpack-parallel-uglify-plugin来替换
        new ParallelUglifyPlugin({
          cacheDir: '.cache/',
          uglifyJS:{
            output: {
              comments: false
            },
            compress: {
              warnings: false
            }
          }
        }),
    

    3. 保存后再次构建项目,可以感觉到速度有所加快。

    六、总结

    多页面开发让前后端分离更加变得更加方便,对已有项目进行分离,不需要做太多的修改;让该项目不再依靠后端去套,后期维护也方便。

    对于前端来说,角色更加重要,不再会再出现,前端写好页面丢给后端,后端开发再嵌入项目中去,导致效果不一样,后续有扩展加进去又导致样式冲突;对于后端来说,也不需要做前端的事情。二者分清,各司其职。

    最后奉上本文测试的 Demo 哦,希望能帮到大家。有什么疑问可评论喔!

    http://download.lllomh.com/cliect/#/product/J417131589328672,获取完整demo


  • 相关阅读:
    Python操作Redis的实例(七)
    Python操作Redis(六)
    Redis的数据类型之set集合,zset有序集合类型操作 (五)
    Redis的数据类型之list列表类型操作 (四)
    Redis的数据类型之hash哈希类型操作 (三)
    Redis的数据类型之String字符串类型操作(二)
    Redis基础介绍以及编译安装过程(一)
    python操作IP---IPy模块
    安装cnpm报错
    vue-cli · Failed to download repo vuejs-templates/webpack: connect ECONNREF
  • 原文地址:https://www.cnblogs.com/lllomh/p/14991855.html
Copyright © 2011-2022 走看看