webpack+koa框架搭建官网

前言

搭建官网,考虑后期的SEO优化,一般会使用前端写静态页面,后端来渲染的模式。但其实前端也可以进行服务端渲染,今天讲的主要内容就是前端方面的服务端渲染。本案例使用node做中间层向后端请求数据,再用koa-swig进行服务端模板渲染。

首先配置webpack文件,这个主要是习惯了写es6,还有用到它的公共代码提取和代码压缩。


image.png

官网如图所示,主要内容有首页、案例、行业和案例详情、资讯详情等。选主要的2个点,案例和案例详情页,这样就可以了。

1、配置webpack
1、电脑安装node
2、在目标文件夹输入
npm init

开始新建项目


image.png

输入项目名称后也可以一直回车完成。

3、全局安装webpack
4、在文件目录里新建webpack.config.js,这个就是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');

/**
 * 根据目录获取入口
 * @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()
    ]
}

引入依赖项,引入的都是需要使用npm安装的,当然,你也可以使用淘宝镜像。
其中ROOT_PATH是转换前的源文件,BUILD_PATH是转换为es5打包后的文件地址。
插件:

  • CommonsChunkPlugin是用来抽取公共引入的部分打包成common.js,这里配置的是引入2次及以上的模块会被打包进common.js。
  • UglifyJsPlugin这个是用来进行代码压缩。
5、配置编译es6还需要加个文件,.babelrc
image.png

.babelrc代码

{
    "presets": [
        [
            "es2015",
            {
                "modules": false
            }
        ]
    ],
    "plugins": []
}

这样就可以把es6直接转换为es5运行了。

6、目录解释:我把模板文件放在了views文件夹,其他附带的js\css\img等统一放在静态目录dist文件夹.设置后可以当做根目录直接使用端口调用。
image.png
7、node服务端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('服务已启动,port:' + ((process && process.env && process.env.PORT) || 3000));
});

注意其中app.use(serv(__dirname+'/dist'))就是设置静态目录。

8、node端请求封装base.js
const request = require('request');
const querystring = require('querystring');

let baseHttp = "http://172.1.1.1/TB/";        //这里是你的后端的接口默认地址。

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);
                    //后台程序错误
                    var data = {
                        ResultCode:0,
                        Message:'与后台通信异常'
                    }
                    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);
  });

}

这个js写的比较粗糙,主要作用:
router.get('/cases.html',async(ctx,next)=>{
let id = ctx.query.id;
ctx.body = await ctx.render('cases',cbdata);
}
根据koa-router路由捕获到触发路由cases.html后,ctx.query.id获取参数,例如浏览器显示路由/cases.html?id=12345,那么就可以直接提取到12345作为id.可以用这个来进行分页和判断选择了那个类目
ctx.body = await ctx.render('cases',cbdata);就是从views文件夹里寻找文件名为cases的文件,将值cbdata放进模板渲染然后形成一个html文件代码返回给ctx.body显示。
cbdata.base = {
title:'案例页',
self:'cases',
};
里面的self用来在公共模板按每个不同的页面引入文件名对应的js

10、模板文件:
image.png
  • 模板文件也是html后缀,页面顶部导航和底部导航公用,所以提取出来作为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收缩的内容,即nav-collapse collapse样式所在元素 -->
                   <button class="navbar-toggle" type="button" data-toggle="collapse" data-target=".navbar-responsive-collapse">
                     <span class="sr-only">导航菜单</span>
                     <span class="icon-bar"></span>
                     <span class="icon-bar"></span>
                     <span class="icon-bar"></span>
                   </button>
              </div>
              <!-- 屏幕宽度小于768px时,div.navbar-responsive-collapse容器里的内容都会隐藏,显示icon-bar图标,当点击icon-bar图标时,再展开。屏幕大于768px时,默认显示。 -->
              <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">服务</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">行业资讯</a></li>
                      <li class="anav"><a href="/about.html">关于我们</a></li>
                 </ul>
                </div>
              </div>
          </div>
      </div>
      {% endif %}

{{}}插值运算
判断是否存在:{% if ... %},以{% endif %}结尾

  • cases.html
{% include 'header.html' %}
<div class="banner"></div>
<div class="main">
  <div class="con">
    <div class="head">
      <p class="mtitle">用真实案例说话</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 %}循环渲染

  • 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渲染,相当于jq的.html()和vue的v-html
这样模板就渲染好了,footer.js中我也引入了common.js和各自对应的文件名js文件

好了,这样就做完了,css我是用sass写的,不知道怎么用的也可以看我之前的文章。使用命令:webpack -w可以打包文件,node app.js可以运行项目,然后在浏览器中打开,当然也可以在package.json中配置start,这样直接运行npm start也可以直接运行项目.
image.png

在这里还要特别感谢沃土社区老胡,你的指导让我们越来越强大
写完了,觉得辛苦就支持下吧,另外有小项目兼职的也可以给我一点小单一起做啊

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,039评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,223评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,916评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,009评论 1 291
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,030评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,011评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,934评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,754评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,202评论 1 309
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,433评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,590评论 1 346
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,321评论 5 342
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,917评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,568评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,738评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,583评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,482评论 2 352