Vue 是很好用,可是以往的都是单页面应用,这就致使了一些传统的项目移植困难,一些用了 JQ 的插件的等等写法都要改变。也还用专门找到相对于的 Vue 的插件才行,此次的 Cli 3.0 能够在原来项目的基础上直接移植,很是方便。css
在本文中,会讲到以下内容:html
Vue 多页面的优点与劣势
Cli 3.0 的基本配置
Cli 3.0 多页面的打包上线
Cli 3.0 的目录解析
如何提高构建效率
受众人群:常常用 Vue 单页面开发的人员,对多页面有兴趣,且实际工做中有需求。老项目想先后端分离,考虑效率又不想用单页面重写的开发人员。前端
@[toc]vue
Vue.js 3.0 支持单页面也支持多页面,不过对用久了 2.0 人的来讲,开始还会有一点不习惯。建立方式、目录结构、运行命令都有差别。node
本文将围绕实际多页面开发案例,剖析多页面从构建到上线一条龙的过程。自定义配置有蛮多种,这里只是只说其中一种。供你们参考使用。webpack
本篇文章的目录结构核心以下: ios
单页面应用开发(SPA)git
多页面应用开发(MPA)web
以上一对比,多页面仍是有蛮多优点的,特别是在老项目想先后端分离的时候,尤其突出。虽然近几年 Vue 等框架兴起,可是之前用 JQ、JS、其余插件写的项目也很多。大多都是没有分离的,先后端分工不明确,甚至致使前端只是个写页面的,丢给跟后台去套数据。通俗点来讲就是个页面仔、效果仔了。npm
多页面(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 调试的时候是不须要后缀的,要上线的时候得把全部的 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