1 入手項(xiàng)目
進(jìn)入一家老的公司,公司規(guī)模也不大,但是我們從git上下載后,我們猛然發(fā)現(xiàn),這項(xiàng)目有一種心痛的感覺(jué)。
image.png
|
image.png
|
|---|
這都2020年了,居然還有人用這么古老的方案去進(jìn)行一個(gè)項(xiàng)目。那么這時(shí)候,我們?cè)撛趺崔k,我們陷入了沉思,繼續(xù)維護(hù)?還是準(zhǔn)備跑路?忍住,我們先看看代碼...
目錄 一個(gè)公共的index代碼....居然都沒(méi)有抽離...如下代碼大概有個(gè)1000多行....
blockquote,body,dd,dl,dt,fieldset,form,h1,h2,h3,h4,h5,h6,hr,html,iframe,input,legend,li,ol,p,pre,td,textarea,th,ul{padding:0;margin:0}
html{-webkit-overflow-scrolling:touch;-webkit-text-size-adjust:100%;font-family:Arial, Helvetica, sans-serif;}
body{-webkit-overflow-scrolling:touch;-webkit-box-sizing:border-box;box-sizing:border-box}
a,body,select,select:focus,textarea,textarea:focus{-webkit-tap-highlight-color:transparent;outline:0;-webkit-appearance:none}
li{list-style-type:none}
table{border-collapse:collapse;border-spacing:0}
fieldset{border:none}
legend{display:none}
a:active,a:hover,button{outline:0}
input[type=checkbox],input[type=radio]{-webkit-box-sizing:border-box;box-sizing:border-box}
b,em,i{font-style:normal;font-weight:400}
a{text-decoration:none;-webkit-tap-highlight-color:transparent}
@media screen and (min-width:1440px){html{font-size:200%}}
@media screen and (max-width:1440px){html{font-size:200%}}
@media screen and (max-width:1024px){html{font-size:150%}}
@media screen and (max-width:980px){html{font-size:150%}}
@media screen and (max-width:750px){html{font-size:150%}}
@media screen and (max-width:720px){html{font-size:150%}}
@media screen and (max-width:640px){html{font-size:150%}}
@media screen and (max-width:540px){html{font-size:150%}}
@media screen and (max-width:480px){html{font-size:125%}}
@media screen and (max-width:432px){html{font-size:120%}}
@media screen and (max-width:414px){html{font-size:115%}}
@media screen and (max-width:400px){html{font-size:112.5%}}
@media screen and (max-width:393px){html{font-size:104%}}
@media screen and (max-width:375px){html{font-size:104%}}
@media screen and (max-width:360px){html{font-size:100%}}
@media screen and (max-width:320px){html{font-size:87.5%}}
@media screen and (max-width:240px){html{font-size:75%}}
body{background-color: #f7f7f7;}
.popBox,.popBind,.popBind_text,.popBind_error{position: fixed;width:100%;height:100%;background:rgba(0,0,0,0.5);color:#999999;display: none;top:0px;z-index:10;}
.popBoxCont{width:18.1875rem;background-color: #fff;border-radius:0.3125rem;position: absolute;left:50%;top:50%;transform:translate(-50%,-50%);padding-bottom:2.0313rem;}
.popBox_top{font-size:1rem;line-height:1.2rem;margin-top:2.75rem;margin-bottom:1.1875rem;}
.popBox_top i{font-size:1.3125rem;line-height:1.5rem;vertical-align:bottom;margin-left:0.4375rem;color:#5bba48;font-weight:bold;}
.popBoxDetail p{margin-left:2.375rem;margin-right:2.375rem;}
.popBoxDetail .popBox_counseName{font-size:1rem;line-height:1.3125rem;color:#5bbb47;background-color: #edfbea;margin-bottom:1.6563rem;position: relative;margin-left:1.9688rem;margin-right:1.9688rem;padding:0.2375rem 0.4688rem;}
2 項(xiàng)目分析
不能慌張,我們可是前端工程師...那么我們?cè)撛趺崔k呢?首先我們先分析一下我們可以怎么辦,那么我們首先分析一下他使用的技術(shù)棧和運(yùn)用場(chǎng)景。
2.1 運(yùn)用場(chǎng)景
通過(guò)和項(xiàng)目組,產(chǎn)品的溝通。該項(xiàng)目,是運(yùn)行在微信公眾號(hào)上的的一個(gè)h5頁(yè)面。那么能夠給你的時(shí)間,差不多是一周時(shí)間,去熟悉了解,整個(gè)項(xiàng)目。
2.2 技術(shù)棧架構(gòu)分析
1. js架構(gòu)使用技術(shù)棧
1.1 jquery
1.2 jweixin
1.3 swiper.min
2. css解決方案
純手寫(xiě),手動(dòng)rem
其實(shí)這時(shí)候,不難發(fā)現(xiàn),就是累加的js,http未封裝狀態(tài)...
| 方法 | 方案 | 缺點(diǎn) |
|---|---|---|
| 文件夾隔離 | 將老代碼,丟到我看不到的地方,繼續(xù)開(kāi)發(fā)新項(xiàng)目。(看不到,那就當(dāng)做沒(méi)有問(wèn)題) | 在老項(xiàng)目的代碼,硬傷還是硬傷,新代碼的架構(gòu)被迫跟隨 |
| 微前端 | 做一個(gè)大規(guī)模容器,將新老項(xiàng)目做一個(gè)中間的橋接,讓主架構(gòu)負(fù)責(zé)項(xiàng)目的溝通,保證2個(gè)服務(wù)器正常運(yùn)行 | 微前端的技術(shù)方案,大部分實(shí)例是作為后端管理系統(tǒng)中運(yùn)行,在手機(jī)端中的適配能力未知。 |
| webpack | 多文件打包方案,讓新老代碼,在工具中兼容 | 人力改代碼 |
3 webpack技術(shù)
webpack的優(yōu)點(diǎn)不言而喻,如果不清楚的,可以去看webpack官網(wǎng)的介紹
本質(zhì)上,webpack 是一個(gè)現(xiàn)代 JavaScript 應(yīng)用程序的靜態(tài)模塊打包器(module bundler)。當(dāng) webpack 處理應(yīng)用程序時(shí),它會(huì)遞歸地構(gòu)建一個(gè)依賴(lài)關(guān)系圖(dependency graph),其中包含應(yīng)用程序需要的每個(gè)模塊,然后將所有這些模塊打包成一個(gè)或多個(gè) bundle。
4 目錄結(jié)構(gòu)
首先我們肯定是不會(huì)想去想改動(dòng),老項(xiàng)目代碼,那畢竟是老項(xiàng)目...你去改動(dòng)了...萬(wàn)一東西墻崩潰..最后的事故可不小。那么新頁(yè)面呢..我們肯定是希望去使用單頁(yè)面技術(shù),畢竟加載和體驗(yàn)上,都有了畢竟好的體驗(yàn)。
先將原來(lái)的文件復(fù)制出一個(gè)來(lái),我們也不希望在改動(dòng)的過(guò)程中,破壞了代碼原本的功能
mkdir jq-webpack
yarn add webpack@4.19.1 webpack-cli@2.1.4 -D
touch webpack.config.js
這時(shí)候,項(xiàng)目需要將 package.json 添加上指令
"build": "webpack --mode production --config=webpack.config.js",
"server": "webpack-dev-server --hot --config=webpack.config.js"
然后開(kāi)始配置 webpack.config.js 原則上可以配置多種環(huán)境但是我們這邊為了簡(jiǎn)單就配置一種
在項(xiàng)目結(jié)構(gòu)不算過(guò)于復(fù)雜的情況下,其實(shí)我們還是可以理一下思路,就比如項(xiàng)目中,其實(shí)初始化css,js 都是可以抽離出來(lái)并且,可以形成一套完整的路由體系。那么就可以制作一個(gè)項(xiàng)目抽離的目錄結(jié)構(gòu)
- router
- index.js 組合文件
- resource.js 資源文件
- router.js 路由文件
- src
- common 公用的部分
- css
- js
- images
- pages 老項(xiàng)目的對(duì)應(yīng)關(guān)系
- index
- index.html
- index.js
- index.css
- activity
- index.html
- index.js
- index.css
- ....
- utils 工具庫(kù)
index.js
- public 難以做處理文件
- images
- lib
- package.json
- webpack.config.js
4.1 package.json
{
"name": "webpack",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"build": "webpack --mode production --config=webpack.config.js", // 打包指令
"server": "webpack-dev-server --hot --config=webpack.config.js", // 啟動(dòng)指令
"upload-test": "NODE_ENV=test node ./deploy", // 自動(dòng)化上傳-測(cè)試
"upload-prod": "NODE_ENV=prod node ./deploy" // 自動(dòng)化上傳-正式
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"autoprefixer": "^9.1.0",
"babel-plugin-import": "^1.13.3",
"chalk": "^4.1.0",
"clean-webpack-plugin": "^3.0.0",
"compression-webpack-plugin": "^6.0.0",
"copy-webpack-plugin": "^4.6.0",
"css-loader": "^3.3.0",
"cssnano": "^4.1.10",
"expose-loader": "1.0.3",
"extract-text-webpack-plugin": "^4.0.0-beta.0",
"file-loader": "^6.2.0",
"html-webpack-plugin": "4.5.0",
"html-withimg-loader": "^0.1.16",
"less": "^3.13.0",
"less-loader": "^4.1.0",
"mini-css-extract-plugin": "^1.3.2",
"optimize-css-assets-webpack-plugin": "^5.0.0",
"ora": "^5.1.0",
"post-loader": "^2.0.0",
"postcss-loader": "^2.1.1",
"postcss-pxtorem": "^5.0.0",
"postcss-safe-parser": "^5.0.2",
"progress-bar-webpack-plugin": "^2.1.0",
"scp2": "^0.5.0",
"style-loader": "^1.0.0",
"url-loader": "^4.1.1",
"vue-loader": "^15.9.5",
"vue-template-compiler": "^2.6.12",
"webpack": "4.19.1",
"webpack-cleanup-plugin": "0.5.1",
"webpack-cli": "^2.1.4",
"webpack-dev-server": "3.11.0"
},
"dependencies": {
"babel-polyfill": "^6.26.0",
"lib-flexible": "^0.3.2",
"vant": "^2.11.2"
}
}
5 多頁(yè)面配置
我們使用的技術(shù),是比較普遍的webpack,多頁(yè)面技術(shù)我們可以看到,每一個(gè)老項(xiàng)目的html 中,都會(huì)引入關(guān)于jq、自己的index.js、然后一股腦的images,又或許有些是放在自己的images里面...放在我們開(kāi)始做一些隔離,分類(lèi)組合,開(kāi)始開(kāi)多個(gè)文件夾,放入html、js、css。樣式將會(huì)比較清楚,這時(shí)候開(kāi)始做一些外部引入的操作。
首先我們配置一下router的文件,我這邊做了一些拆分,當(dāng)然你如果頁(yè)面能模塊化的,建議拆分的更細(xì)。
5.1 路由設(shè)置
resource.js 將文件路徑中的設(shè)置放入資源管理庫(kù)中
const entry = {
// 首頁(yè)
"index-css": "./src/index/index.css",
"index-js": "./src/index/index.js",
// 活動(dòng)頁(yè)
"activity-css": "./src/pages/activity/index.css",
"activity-js": "./src/pages/activity/index.js",
}
module.exports = {
entry
};
router.js 在路由頁(yè)面中去組合
const router = [
{
name: "首頁(yè)",
filename: "index.html",
chunks: ["index-css", "index-js"], // 如果多個(gè)可以引入多個(gè)
template: "./src/pages/index/index.html",
},
{
name: "活動(dòng)",
filename: "activity.html",
chunks: ["activity-css", "activity-js"],
template: "./src/pages/activity/index.html",
},
];
module.exports = {
router
};
index.js
const htmlPlugin = require("html-webpack-plugin");
const resource = require("./resource");
const routerObj = require("./router");
const htmlWebpackPlugins = [];
routerObj.router.forEach(item => {
htmlWebpackPlugins.push(
new htmlPlugin({
filename: item.filename, //打包后的文件名
minify: false,
chunks: item.chunks, //每個(gè)html只引入對(duì)應(yīng)的js和css
inject: true,
hash: true, //避免緩存js。
template: item.template
})
);
});
module.exports = {
htmlWebpackPlugins,
entry: resource.entry
};
當(dāng)然對(duì)于html也需要做一些處理處理,這里有兩種方法
- 直接使用cdn引入庫(kù)、這里又分為免費(fèi)庫(kù),和公司自己的庫(kù)兩種
- 放入公共的public中,頁(yè)面中引入
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>活動(dòng)頁(yè)面</title>
</head>
<body>
<script src="./public/lib/jquery-1.8.0.min.js" charset="utf-8"></script>
</body>
</html>
這里是一段艱苦的歷程.....
5.2 webpack.config.js 配置
const path = require("path");
const HtmlRouter = require("./router/index");
const CopyPlugin = require("copy-webpack-plugin");
const ExtractTextWebpackPlugin = require("extract-text-webpack-plugin");
const optimizeCss = require("optimize-css-assets-webpack-plugin");
const { CleanWebpackPlugin } = require("clean-webpack-plugin"); // 清除dist
const CompressionPlugin = require("compression-webpack-plugin");
const ProgressBarPlugin = require("progress-bar-webpack-plugin");
module.exports = {
devServer: {
contentBase: path.resolve("dist"),
host: "localhost", //服務(wù)器的IP地址,這里先使用loaclhost地址
compress: true, //服務(wù)端壓縮是否開(kāi)啟
port: "8888", //配置服務(wù)端口號(hào)
stats: "errors-only",
historyApiFallback: true,
overlay: true
},
entry: HtmlRouter.entry,
output: {
path: path.resolve("dist"),
filename: "js/[name].[hash:8].js"
},
plugins: [
new ProgressBarPlugin(),
new CleanWebpackPlugin(),
new optimizeCss({
cssProcessor: require("cssnano"), //引入cssnano配置壓縮選項(xiàng)
cssProcessorOptions: {
discardComments: { removeAll: true }
},
canPrint: true //是否將插件信息打印到控制臺(tái)
}),
new ExtractTextWebpackPlugin({
filename: "css/[name].[hash:8].css", // 配置提取出來(lái)的css名稱(chēng)
allChunks: true
}),
new CopyPlugin(
[
{
from: path.resolve(__dirname, "./src/public/lib"),
to: path.resolve(__dirname, "./dist/public/lib")
},
{
from: path.resolve(__dirname, "./src/public/images"),
to: path.resolve(__dirname, "./dist/public/images")
}
],
{ ignore: [], copyUnmodified: true }
),
new CompressionPlugin()
].concat(HtmlRouter.htmlWebpackPlugins),
resolve: {
alias: {
"@": path.resolve(__dirname, "src"),
"~": path.resolve(__dirname, "src/pages/vue-template")
}
},
module: {
rules: [
{
test: /\.(htm|html)$/i,
loader: "html-withimg-loader"
},
{
test: /\.css$/,
use: ExtractTextWebpackPlugin.extract({
fallback: "style-loader",
use: [
{
loader: "css-loader"
}
],
publicPath: "../"
})
},
{
test: /\.(png|jpg|gif|jpeg|svg)$/i,
use: [
{
loader: "url-loader",
options: {
//當(dāng)加載的圖片小于limit時(shí),會(huì)將圖片編譯成base64字符串的形式,
//當(dāng)圖片大于這個(gè)limit,會(huì)用file-loader進(jìn)行加載
limit: 10000,
//在webpack4.x必須顯式的指定fallback備用方法,這里指定為file-loader
fallback: require.resolve("file-loader"),
encoding: "base64",
outputPath: "images/",
publichPath: "images/",
name: "[name].[hash:8].[ext]",
esModule: false //解決方法
}
}
]
}
]
}
};
6 圖片路徑問(wèn)題
在處理這一段代碼的時(shí)候,最令人無(wú)奈的就是關(guān)于,jq中插入過(guò)html,你所有的語(yǔ)法是$('.xx').html(xx),在這個(gè)階段,你很容易會(huì)遇到一個(gè)巨大的坑..就是圖片無(wú)法被webpack去解析,這樣打包出來(lái)的圖片。
有三種解決方案的思路
- 直接以cdn的形式引入,一個(gè)http圖片,不存在這個(gè)問(wèn)題
- 把頁(yè)面放入到我們已經(jīng)準(zhǔn)備好的public目錄下,使用絕對(duì)路徑去解決
- 在代碼中使用require方法去引入一些圖片,然后作為代碼的解析
7 單頁(yè)面配置-Vue
前面做了那么多業(yè)務(wù),目的就是從業(yè)務(wù)上可以往vue頁(yè)面靠齊...那么肯定不會(huì)是以vue-cil 這樣方式出現(xiàn),那么我就要研究一下vue-cil的本質(zhì),其實(shí)還是一個(gè)webpack,那么為什么可以解析vue,less。既然是多頁(yè)面了,又怎么兼容?
7.1 vue項(xiàng)目建立
熟悉的項(xiàng)目格式又回來(lái)了,這里就不多做介紹了
- src
- pages
- vue-template
- index.html
- pages
- 404.vue
- home.vue
- routers
- index.js
- App.vue
7.2 配置單頁(yè)面
在我們剛剛的路由中,我們?cè)O(shè)置一下
resource.js 資源文件中增加
"vue-template-js": "./src/pages/vue-template/main.js"
router.js 路由文件中增加
{
name: "vue-template",
filename: "template.html",
chunks: ["babel-polyfill", "vue-template-js"],
template: "./src/pages/vue-template/index.html",
}
7.3 webpack配置
首先我們需要加載less,然后將我們熟悉的px,自動(dòng)轉(zhuǎn)rem、自動(dòng)加上兼容前綴
const VueLoaderPlugin = require("vue-loader/lib/plugin");
plugins: [
...
new VueLoaderPlugin()
]
resolve: {
alias: {
"@": path.resolve(__dirname, "src"),
"~": path.resolve(__dirname, "src/pages/vue-template"),
vue$: "vue/dist/vue.esm.js"
}
},
module: {
rules: [
{
test: /\.css$/,
use: ExtractTextWebpackPlugin.extract({
fallback: "style-loader",
use: [
{
loader: "css-loader",
},
{
loader: "postcss-loader",
},
],
publicPath: "../",
}),
},
{
test: /\.less$/,
use: ExtractTextWebpackPlugin.extract({
use: [
{
loader: "css-loader",
},
{
loader: "postcss-loader",
},
{
loader: "less-loader",
},
],
fallback: "style-loader",
}),
},
{
test: /\.vue$/,
loader: "vue-loader",
},
],
},
externals: {
vue: "Vue",
"vue-router": "VueRouter"
}
7.4 配置rem自動(dòng)化
postcss.config.js
module.exports = {
plugins: {
autoprefixer: {
overrideBrowserslist: ["Android >= 4.0", "iOS >= 7"]
},
"postcss-pxtorem": {
rootValue: 37.5,
propList: ["*"]
}
}
};
7.5 編寫(xiě)vue-template/html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta
name="viewport"
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, viewport-fit=cover"
/>
<title>vue模板</title>
</head>
<style>
html,
body,
#app {
height: 100%;
margin: 0;
padding: 0;
}
.webpack-home {
background-color: #303133;
height: 100%;
display: flex;
flex-direction: column;
}
.webpack-home__main {
user-select: none;
width: 100%;
flex-grow: 1;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
}
.webpack-home__footer {
width: 100%;
flex-grow: 0;
text-align: center;
padding: 1em 0;
}
.webpack-home__footer > a {
font-size: 12px;
color: #ababab;
text-decoration: none;
}
.webpack-home__loading {
height: 32px;
width: 32px;
margin-bottom: 20px;
}
.webpack-home__title {
color: #fff;
font-size: 14px;
margin-bottom: 10px;
}
.webpack-home__sub-title {
color: #ababab;
font-size: 12px;
}
.ql-editor {
min-height: 150px;
}
.ql-snow .ql-picker {
height: 36px !important;
}
@media only screen and (-webkit-min-device-pixel-ratio: 3),
only screen and (min--moz-device-pixel-ratio: 3),
only screen and (-o-min-device-pixel-ratio: 3/1),
only screen and (min-device-pixel-ratio: 3),
only screen and (min-resolution: 458dpi),
only screen and (min-resolution: 3dppx) {
.van-tabbar--fixed {
padding-bottom: 15px !important;
}
}
</style>
<body>
<div id="app">
<div class="webpack-home">
<div class="webpack-home__main">
<img
class="webpack-home__loading"
src="./svg/loading-spin.svg"
alt="loading"
/>
<div class="webpack-home__title">
正在加載資源
</div>
<div class="webpack-home__sub-title">
初次加載資源可能需要較多時(shí)間 請(qǐng)耐心等待
</div>
</div>
<div class="webpack-home__footer"></div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js"></script>
<script src="https://unpkg.com/vue-router/dist/vue-router.js"></script>
</body>
</html>
7.6 編寫(xiě)vue-template/main.js
實(shí)例化一個(gè)vue項(xiàng)目,這里只做一個(gè)簡(jiǎn)單的實(shí)例化配置,并添加路由
更多可以根據(jù)業(yè)務(wù)情況來(lái)配置,比如按需加載什么的,這里只是一個(gè)簡(jiǎn)單的闡述結(jié)構(gòu)
import routers from "./routers/index";
import App from "~/App.vue";
import Vant from "vant";
import "vant/lib/index.css";
import "lib-flexible";
Vue.use(VueRouter);
Vue.use(Vant);
const router = new VueRouter({
routes: routers
});
new Vue({
router,
render: h => h(App)
}).$mount("#app");
8 自動(dòng)化發(fā)布
在deploy目錄下,設(shè)置
products.js 服務(wù)器配置
/*
*定義多個(gè)服務(wù)器賬號(hào) 及 根據(jù) SERVER_ID 導(dǎo)出當(dāng)前環(huán)境服務(wù)器賬號(hào)
*/
const SERVER_LIST = [
{
id: 0,
name: "A-測(cè)試環(huán)境",
host: "127.0.0.1", // ip
url: "http://www.baidu.com",
port: 22, // 端口
username: "root", // 登錄服務(wù)器的賬號(hào)
password: "", // 登錄服務(wù)器的賬號(hào)
path: "" // 發(fā)布至靜態(tài)服務(wù)器的項(xiàng)目路徑
}
];
module.exports = SERVER_LIST;
index.js 執(zhí)行函數(shù)
const scpClient = require("scp2");
const ora = require("ora");
const chalk = require("chalk");
const servers = require("./products");
let server = servers[process.env.NODE_ENV === "prod" ? 1 : 0];
const spinner = ora(
"正在發(fā)布到" +
(process.env.NODE_ENV === "prod" ? "生產(chǎn)" : "測(cè)試") +
"服務(wù)器..."
);
var Client = require("ssh2").Client;
var conn = new Client();
conn
.on("ready", function() {
// rm 刪除dist文件,\n 是換行 換行執(zhí)行 重啟nginx命令 我這里是用docker重啟nginx
let dels = `rm -rf ${server.path}\n mkdir ${server.path}`;
conn.exec(dels, function(err, stream) {
if (err) throw err;
stream
.on("close", function(code, signal) {
// 在執(zhí)行shell命令后,把開(kāi)始上傳部署項(xiàng)目代碼放到這里面
spinner.start();
scpClient.scp(
"dist/",
{
host: server.host,
port: server.port,
username: server.username,
password: server.password,
path: server.path,
},
function(err) {
spinner.stop();
if (err) {
console.log(chalk.red("發(fā)布失敗.\n"));
throw err;
} else {
console.log(
chalk.green(
"Success! 成功發(fā)布到" +
(process.env.NODE_ENV === "prod" ? "生產(chǎn)" : "測(cè)試") +
"服務(wù)器! \n"
)
);
console.log(server.url);
}
}
);
conn.end();
})
.on("data", function(data) {
console.log("STDOUT: " + data);
})
.stderr.on("data", function(data) {
console.log("STDERR: " + data);
});
});
})
.connect({
host: server.host,
port: server.port,
username: server.username,
password: server.password,
});