21

Vue.js Cli 3.0 多页面开发案例解析

 3 years ago
source link: https://my.oschina.net/lllomh/blog/4817395
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Vue.js Cli 3.0 多页面开发案例解析

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

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

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

@[toc]

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



About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK