前言
搭建官網(wǎng)随常,考慮后期的SEO優(yōu)化掩蛤,一般會(huì)使用前端寫靜態(tài)頁面哮伟,后端來渲染的模式潭辈。但其實(shí)前端也可以進(jìn)行服務(wù)端渲染,今天講的主要內(nèi)容就是前端方面的服務(wù)端渲染澈吨。本案例使用node做中間層向后端請(qǐng)求數(shù)據(jù)把敢,再用koa-swig進(jìn)行服務(wù)端模板渲染。
首先配置webpack文件谅辣,這個(gè)主要是習(xí)慣了寫es6修赞,還有用到它的公共代碼提取和代碼壓縮。
官網(wǎng)如圖所示桑阶,主要內(nèi)容有首頁柏副、案例、行業(yè)和案例詳情蚣录、資訊詳情等割择。選主要的2個(gè)點(diǎn),案例和案例詳情頁萎河,這樣就可以了荔泳。
1蕉饼、配置webpack
1、電腦安裝node
2玛歌、在目標(biāo)文件夾輸入
npm init
開始新建項(xiàng)目
輸入項(xiàng)目名稱后也可以一直回車完成昧港。
3、全局安裝webpack
4支子、在文件目錄里新建webpack.config.js创肥,這個(gè)就是webpack的配置文件
var webpack = require("webpack");
var path = require("path");
var glob = require('glob');
// var HtmlWebpackPlugin = require('html-webpack-plugin');
var ROOT_PATH = path.resolve(__dirname,'./dist/js');
var BUILD_PATH = path.resolve(ROOT_PATH, '../es6js');
/**
* 根據(jù)目錄獲取入口
* @param {[type]} globPath [description]
* @return {[type]} [description]
*/
function getEntry(globPath) {
let entries = {};
glob.sync(globPath).forEach(function(entry) {
let basename = path.basename(entry, path.extname(entry)),
pathname = path.dirname(entry);
if (!entry.match(/js\/lib\//)) {
entries[basename] = pathname + '/' + basename;
}
});
return entries;
}
let entryJs = getEntry('./dist/js/*.js');
module.exports = {
resolve: {
modules: [path.resolve(__dirname, 'node_modules')]
},
entry: entryJs,
output: {
path: BUILD_PATH,
publicPath: BUILD_PATH,
filename: "[name].js"
},
module: {
rules: [
{
test: /\.scss$/,
loader: ["style-loader", "css-loader", "sass-loader", ]
},
{
test: /\.js$/,
loader: ["babel-loader?cacheDirectory"],
},
{
test: /\.css$/,
loader: ["style-loader", "css-loader"],
include: path.resolve(__dirname, './src/es6/route')
}
]
},
plugins: [
/**
* 抽出公共JS
*/
new webpack.optimize.CommonsChunkPlugin({
name: "common",
filename: "common.js",
minChunks: 2,
}),
new webpack.optimize.UglifyJsPlugin()
// new HtmlWebpackPlugin()
]
}
引入依賴項(xiàng),引入的都是需要使用npm安裝的值朋,當(dāng)然叹侄,你也可以使用淘寶鏡像。
其中ROOT_PATH是轉(zhuǎn)換前的源文件昨登,BUILD_PATH是轉(zhuǎn)換為es5打包后的文件地址圈膏。
插件:
- CommonsChunkPlugin是用來抽取公共引入的部分打包成common.js,這里配置的是引入2次及以上的模塊會(huì)被打包進(jìn)common.js篙骡。
- UglifyJsPlugin這個(gè)是用來進(jìn)行代碼壓縮稽坤。
5、配置編譯es6還需要加個(gè)文件糯俗,.babelrc
.babelrc代碼
{
"presets": [
[
"es2015",
{
"modules": false
}
]
],
"plugins": []
}
這樣就可以把es6直接轉(zhuǎn)換為es5運(yùn)行了尿褪。
6、目錄解釋:我把模板文件放在了views文件夾得湘,其他附帶的js\css\img等統(tǒng)一放在靜態(tài)目錄dist文件夾.設(shè)置后可以當(dāng)做根目錄直接使用端口調(diào)用杖玲。
7、node服務(wù)端app.js
const Koa = require("koa");
const serv = require("koa-static");
const render = require("koa-swig");
const co = require("co");
const path = require("path");
const app = new Koa();
const initRouter = require("./router");
app.use(serv(__dirname+'/dist'));
app.context.render = co.wrap(render(app, {
root: path.join(__dirname, 'views'),
autoescape: true,
cache: 'memory',
writeBody: false,
ext: 'html'
}));
initRouter(app);
app.listen( (process && process.env && process.env.PORT) || 3000, ()=>{
console.log('服務(wù)已啟動(dòng)淘正,port:' + ((process && process.env && process.env.PORT) || 3000));
});
注意其中app.use(serv(__dirname+'/dist'))就是設(shè)置靜態(tài)目錄摆马。
8、node端請(qǐng)求封裝base.js
const request = require('request');
const querystring = require('querystring');
let baseHttp = "http://172.1.1.1/TB/"; //這里是你的后端的接口默認(rèn)地址鸿吆。
class myRequest{
constructor(){
}
get(url,data,callback){
let myUrl = `${url}?`;
let dataArray = [];
for(let k in data){
let paramStr = `${k}=${data[k]}`;
dataArray.push(paramStr);
}
let urlStr = dataArray.join("=");
myUrl = `${myUrl}${urlStr}`;
request(myUrl,(error, response, body)=> {
if (response && response.statusCode && response.statusCode === 200) {
try{
body = JSON.parse(body);
}catch(e){
body = null;
}
callback&&callback(body);
}
});
}
post(url,data){
return new Promise(function (resolve, reject) {
request.post({
url: `${baseHttp}${url}`,
json: true,
form:(data)
}, function(error, response, data) {
if (response) {
if (!error && response.statusCode == 200) {
resolve(data);
// callback && callback(data.Data);
}else{
reject('error===');
}
} else {
console.log(error);
//后臺(tái)程序錯(cuò)誤
var data = {
ResultCode:0,
Message:'與后臺(tái)通信異常'
}
resolve(data);
}
});
})
}
}
module.exports=myRequest;
9囤采、路由router.js
const Router = require('koa-router')
const koaBody = require('koa-body');
const myrequest = require('./modules/base');
let myRequest = new myrequest();
module.exports = function (app) {
const router = new Router();
app.use(router.routes());
app.use(router.allowedMethods());
//案例頁路由
router.get('/cases.html',async (ctx,next)=>{
// ctx.router available
let cbdata = {};
let data = {};
let pagecurrent = ctx.query.page || 1;
data = await myRequest.post('/api/Home/GetProjectCaseList',{PageNo:pagecurrent,PageSize:20});
if(data && data.ResultCode=="6666"){
cbdata = data.Data;
}
cbdata.base = {
title:'案例頁',
self:'cases',
};
if(cbdata.PageInfo){
cbdata.PageInfo.PageList = [];
let count = cbdata.PageInfo.PageCount || 0;
let className = "";
for(let i=0;i<count;i++){
if(i == (pagecurrent-1)){
className = "active";
}else{
className = "";
}
cbdata.PageInfo.PageList.push(className);
}
}
ctx.body = await ctx.render('cases',cbdata);
});
//案例詳情頁路由
router.get('/casesdetail.html',async (ctx,next)=>{
// ctx.router available
let id = ctx.query.id;
let cbdata = {};
let data = {};
data = await myRequest.post('/api/Home/GetProjectCaseById',{'id':id});
if(data && data.ResultCode=="6666"){
cbdata = data.Data;
}
cbdata.base = {
title:'案例詳情頁',
self:'casesdetail',
};
ctx.body = await ctx.render('casesdetail',cbdata);
});
}
這個(gè)js寫的比較粗糙,主要作用:
router.get('/cases.html'惩淳,async(ctx,next)=>{
let id = ctx.query.id;
ctx.body = await ctx.render('cases',cbdata);
}
根據(jù)koa-router路由捕獲到觸發(fā)路由cases.html后蕉毯,ctx.query.id獲取參數(shù),例如瀏覽器顯示路由/cases.html?id=12345,那么就可以直接提取到12345作為id.可以用這個(gè)來進(jìn)行分頁和判斷選擇了那個(gè)類目
ctx.body = await ctx.render('cases',cbdata);就是從views文件夾里尋找文件名為cases的文件思犁,將值cbdata放進(jìn)模板渲染然后形成一個(gè)html文件代碼返回給ctx.body顯示代虾。
cbdata.base = {
title:'案例頁',
self:'cases',
};
里面的self用來在公共模板按每個(gè)不同的頁面引入文件名對(duì)應(yīng)的js
10、模板文件:
- 模板文件也是html后綴激蹲,頁面頂部導(dǎo)航和底部導(dǎo)航公用棉磨,所以提取出來作為header和footer
- header.html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
{% if ObjDetail %}
<meta name="keyword" content="{{ObjDetail.SeoKeywords}}">
{% endif %}
{% if ObjDetail %}
<meta name="description" content="{{ObjDetail.SeoDescription}}">
{% endif %}
{% if base %}
<title>{{base.title}}</title>
<link href="./plugin/bootstrap/css/bootstrap.min.css" rel="stylesheet">
<!-- <link href="./css/common.css" rel="stylesheet"> -->
<link href="./css/{{base.self}}.css" rel="stylesheet">
<!--[if lt IE 9]>
<script src="https://cdn.bootcss.com/html5shiv/3.7.3/html5shiv.min.js"></script>
<script src="https://cdn.bootcss.com/respond.js/1.4.2/respond.min.js"></script>
<![endif]-->
</head>
<body>
<div class="header">
<div class="navbar navbar-default nocolor" role="navigation">
<div class="navbar-header">
<!-- .navbar-toggle樣式用于toggle收縮的內(nèi)容,即nav-collapse collapse樣式所在元素 -->
<button class="navbar-toggle" type="button" data-toggle="collapse" data-target=".navbar-responsive-collapse">
<span class="sr-only">導(dǎo)航菜單</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
</div>
<!-- 屏幕寬度小于768px時(shí)学辱,div.navbar-responsive-collapse容器里的內(nèi)容都會(huì)隱藏乘瓤,顯示icon-bar圖標(biāo)环形,當(dāng)點(diǎn)擊icon-bar圖標(biāo)時(shí),再展開馅扣。屏幕大于768px時(shí),默認(rèn)顯示着降。 -->
<div class="collapse navbar-collapse navbar-responsive-collapse">
<div class="nav-center">
<ul class="nav navbar-nav">
<li class="hnav"><a href="/">首頁</a></li>
<li class="snav"><a href="/service.html">服務(wù)</a></li>
<li class="cnav"><a href="/cases.html">案例</a></li>
<li><a href="javascript:void(0);"><img src="./img/logo.png"/></a></li>
<li class="nnav"><a href="/news.html">行業(yè)資訊</a></li>
<li class="anav"><a href="/about.html">關(guān)于我們</a></li>
</ul>
</div>
</div>
</div>
</div>
{% endif %}
{{}}插值運(yùn)算
判斷是否存在:{% if ... %},以{% endif %}結(jié)尾
- cases.html
{% include 'header.html' %}
<div class="banner"></div>
<div class="main">
<div class="con">
<div class="head">
<p class="mtitle">用真實(shí)案例說話</p>
<p class="stitle">“為客戶打造傳奇品牌差油,成就電影人生”</p>
</div>
<div class="mcon">
{% if List %}
<ul class="itemlist">
{% for item in List %}
<li class="item" data-id="{{item.ProjectCaseId}}">
<a href="javascript:void(0);">
<div class="himgcon">
<img class="simg" src="{{item.PhotoURL}}"/>
<div class="mask">
<div class="post_info">
<div class="play_in">
<span class="line1"></span>
<span class="line2"></span>
<span class="line3"></span>
</div>
<div class="post_tt" title="{{item.Title}}">{{item.Title}}</div>
</div>
</div>
</div>
</a>
<!-- <div class="introduce" title="{{item.Title}}">
{{item.Title}}
</div> -->
</li>
{% endfor %}
</ul>
{% endif %}
<div class="clb"></div>
<div class="pagelist">
{% if (PageInfo && PageInfo.PageList) %}
<div class="page">
{% for item in PageInfo.PageList %}
<span class="page-num {{item}}">{{loop.index}}</span>
{% endfor %}
</div>
{% endif %}
</div>
</div>
</div>
</div>
{% include 'footer.html' %}
{% include template%}引入模板
{% for i in k %}、{% endfor %}循環(huán)渲染
- casedetail.html
{% include 'headerdetail.html' %}
<div class="banner"></div>
<div class="main">
<div class="con">
{% if ObjDetail %}
<div class="head">
<p class="title">{{ObjDetail.Title}}</p>
<p class="subintro">{{ObjDetail.PublishDate}} {{ObjDetail.Author}}</p>
</div>
<div class="content">
{% autoescape false %}
{{ ObjDetail.Content }}
{% endautoescape %}
</div>
</div>
</div>
{% include 'footer.html' %}
{% autoescape false %}任洞、{% endautoescape %}html渲染,相當(dāng)于jq的.html()和vue的v-html
這樣模板就渲染好了蓄喇,footer.js中我也引入了common.js和各自對(duì)應(yīng)的文件名js文件
好了,這樣就做完了交掏,css我是用sass寫的妆偏,不知道怎么用的也可以看我之前的文章。使用命令:webpack -w可以打包文件,node app.js可以運(yùn)行項(xiàng)目盅弛,然后在瀏覽器中打開钱骂,當(dāng)然也可以在package.json中配置start,這樣直接運(yùn)行npm start也可以直接運(yùn)行項(xiàng)目.
在這里還要特別感謝沃土社區(qū)老胡挪鹏,你的指導(dǎo)讓我們?cè)絹碓綇?qiáng)大
寫完了见秽,覺得辛苦就支持下吧,另外有小項(xiàng)目兼職的也可以給我一點(diǎn)小單一起做啊