本文梗概
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 哦,希望能幫到大家。有什么疑問可評論喔!