简介
nodejs搭建多页面效劳端衬着
手艺点
- koa 搭建效劳
- koa-router 建立页面路由
- nunjucks 模板引擎组合html
- webpack打包多页面
- node端异步要求
- 效劳端日记打印
项目源码 git clone
https://gitee.com/wjj0720/nod…运转
- npm i
- npm start
一、 当代效劳端衬着的由来
效劳端衬着观点: 是指,浏览器向效劳器发出要求页面,效劳端将准备好的模板和数据组装成完全的HTML返回给浏览器展现
1、前端后端星散
早在七八年前,险些一切网站都运用 ASP、Java、PHP做后端衬着,跟着收集的加速,客户端机能进步以及js自身的机能进步,我们最先往客户端增添更多的功用逻辑和交互,前端不再是简朴的html+css更多的是交互,前端页在这是从后端星散出来「前后端正式分爨」
2、客户端衬着
跟着ajax手艺的提高以及前端框架的兴起(jq、Angular、React、Vue) 框架的兴起,最先转向了前端衬着,运用 JS 来衬着页面大部份内容到达部份革新的作用
上风
- 部份革新,用户体验优
- 富交互
- 勤俭效劳器本钱
瑕玷
- 不利于SEO(爬虫没法爬取ajax)要求返来的数据
- 受浏览器机能限定、增添手机端的耗电
- 首屏衬着须要等js运转才展现数据
3、如今效劳端衬着
为了处置惩罚上面客户端衬着的瑕玷,然前后端星散后必不能合,假如要把前后端部门兼并,拆掉的肯定是前端部门
如今效劳端衬着的特性
- 前端开发人员编写html+css模板
- node中间效劳担任前端模板和背景数据的组合
- 数据依旧由java等前效劳端言语供应
上风
- 前后端分工明白
- SEO问题处置惩罚
4、前、后端衬着相干议论参考
二、 项目最先
确保你装置node
第一步 让效劳跑起来
目的: 建立node效劳,经由过程浏览器接见,返回’hello node!'(html页面实在就是一串字符串)
/** 建立项目目次构造以下 */
│─ package-lock.json
│─ package.json
│─ README.md
├─bin
│─ www.js
// 1. 装置依靠 npm i koa
// 2. 修正package.json文件中 scripts 属性以下
"scripts": {
"start": "node bin/www.js"
}
// 3. www.js写入以下代码
const Koa = require('koa');
let app = new Koa();
app.use(ctx => {
ctx.body = 'hello node!'
});
app.listen(3000, () => {
console.log('效劳器启动 http://127.0.0.1:3000');
});
// 4 npm start 浏览器接见 http://127.0.0.1:3000 检察结果
第二步 路由的运用
目的:运用koa-router依据差别url返回差别页面内容
依靠 npm i koa-router
koa-router 更多细节 请至npm检察/** 新增routers文件夹 目次构造以下 │─.gitignore │─package.json │─README.md ├─bin │ │─www.js ├─node_modules └─routers │─home.js │─index.js │─user.js */ //项目中应按照模块对路由举行分别,示例简朴将路由分别为首页(/)和用户页(/user) 在index中将路由集合治理导, 出并在app实例后挂载到app上
/** router/home.js 文件 */ // 引包 const homeRouter = require('koa-router')() //建立路由划定规矩 homeRouter.get(['/', '/index.html', '/index', '/home.html', '/home'], (ctx, next) => { ctx.body = 'home' }); // 导出路由备用 module.exports = homeRouter /** router/user.js 文件 */ const userRouter = require('koa-router')() userRouter.get('/user', (ctx, next) => { ctx.body = 'user' }); module.exports = userRouter
/** router/index.js 文件 */ // 路由集合点 const routers = [ require('./home.js'), require('./user.js') ] // 简朴封装 module.exports = function (app) { routers.forEach(router => { app.use(router.routes()) }) return routers[0] }
/** www.js 文件改写 */
// 引入koa
const Koa = require('koa')
const Routers = require('../routers/index.js')
// 实例化koa对象
let app = new Koa()
// 挂载路由
app.use((new Routers(app)).allowedMethods())
// 监听3000端口
app.listen(3000, () => {
console.log('效劳器启动 http://127.0.0.1:3000')
})
第三步 到场模板
目的:
1.运用nunjucks剖析html模板返回页面
2.相识koa中间件的运用
- 依靠 npm i nunjucks
/*
*我向项目目次下到场两个准备好的html文件 目次构造以下
│─.gitignore
│─package.json
│─README.md
├─bin
│ │─www.js
│─middlewares //新增中间件目次
│ ├─nunjucksMiddleware.js //nunjucks模板中间件
├─node_modules
│─routers
│ │─home.js
│ │─index.js
│ │─user.js
│─views //新增目次 作为视图层
├─home
│ ├─home.html
├─user
├─user.html
*/
/* nunjucksMiddleware.js 中间件的编写
*什么是中间件: 中间件就是在顺序实行过程当中增添辅佐功用
*nunjucksMiddleware作用: 给要求上下文加上render要领 将来在路由中运用
*/
const nunjucks = require('nunjucks')
const path = require('path')
const moment = require('moment')
let nunjucksEVN = new nunjucks.Environment(new nunjucks.FileSystemLoader('views'))
// 为nkj到场一个过滤器
nunjucksEVN.addFilter('timeFormate', (time, formate) => moment(time).format( formate || 'YYYY-MM-DD HH:mm:ss'))
// 推断文件是不是有html后缀
let isHtmlReg = /\.html$/
let resolvePath = (params = {}, filePath) => {
filePath = isHtmlReg.test(filePath) ? filePath : filePath + (params.suffix || '.html')
return path.resolve(params.path || '', filePath)
}
/**
* @description nunjucks中间件 增加render到要求上下文
* @param params {}
*/
module.exports = (params) => {
return (ctx, next) => {
ctx.render = (filePath, renderData = {}) => {
ctx.type = 'text/html'
ctx.body = nunjucksEVN.render(resolvePath(params, filePath), Object.assign({}, ctx.state, renderData))
}
// 中间件自身实行完成 须要挪用next去实行下一步设计
return next()
}
}
/* 中间件挂载 www.js中增添部份代码 */
// 头部引入文件
const nunjucksMiddleware = require('../middlewares/nunjucksMiddleware.js')
//在路由之前挪用 由于我们的中间件是在路由中运用的 故应该在路由前加到要求上下文ctx中
app.use(nunjucksMiddleware({
// 指定模板文件夹
path: path.resolve(__dirname, '../views')
})
/* 路由中挪用 以routers/home.js 为例 修正代码以下*/
const homeRouter = require('koa-router')()
homeRouter.get(['/', '/index.html', '/index', '/home.html', '/home'], (ctx, next) => {
// 衬着页面的数据
ctx.state.todoList = [
{name: '用饭', time: '2019.1.4 12:00'},
{name: '下午茶', time: '2019.1.4 15:10'},
{name: '放工', time: '2019.1.4 18:30'}
]
// 这里的ctx.render要领就是我们经由过程nunjucksMiddleware中间件增加的
ctx.render('home/home', {
title: '首页'
})
})
module.exports = homeRouter
第四步 抽取大众模板
目的: 抽取页面的公用部份 如导航/底部/html模板等
/**views目次下增添两个文件夹_layout(公用模板) _component(大众组件) 目次构造以下
│─.gitignore
│─package.json
│─README.md
├─bin
│ │─www.js /koa效劳
│─middlewares //中间件目次
│ ├─nunjucksMiddleware.js //nunjucks模板中间件
├─node_modules
│─routers //效劳路由目次
│ │─home.js
│ │─index.js
│ │─user.js
│─views //页面视图层
│─_component
│ │─nav.html (公用导航)
│─_layout
│ │─layout.html (公用html框架)
├─home
│ ├─home.html
├─user
├─user.html
*/
<!-- layout.html 文件代码 -->
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>{{ title }}</title>
</head>
<body>
<!-- 占位 称号为content的block将放在此处 -->
{% block content %}
{% endblock %}
</body>
</html>
<!-- nav.html 公用导航 -->
<ul>
<li><a href="/">首页</a></li>
<li><a href="/user">用户页</a></li>
</ul>
<!-- home.html 改写 -->
<!-- njk继续模板 -->
{% extends "../_layout/layout.html" %}
{% block content %}
<!-- njk引入大众模块 -->
{% include "../_component/nav.html" %}
<h1>待办事项</h1>
<ul>
<!-- 过滤器的挪用 timeFormate即我们在中间件中给njk加的过滤器 -->
{% for item in todoList %}
<li>{{item.name}} ---> {{item.time | timeFormate}}</li>
{% endfor %}
</ul>
{% endblock %}
<!-- user.html -->
{% extends "../_layout/layout.html" %}
{% block content %}
{% include "../_component/nav.html" %}
用户中间
{% endblock %}
第五步 静态资本处置惩罚
目的: 处置惩罚页面jscssimg等资本引入
依靠
- 用webpack打包静态资本 npm i webpack webpack-cli -D
- 处置惩罚js npm i @babel/core @babel/preset-env babel-loader -D
- 处置惩罚less npm i css-loader less-loader less mini-css-extract-plugin -D
- 处置惩罚文件 npm i file-loader copy-webpack-plugin -D
- 处置惩罚html npm i html-webpack-plugin -D
- 清算打包文件 npm i clean-webpack-plugin -D
> *相干插件运用 检察npm相干文档*
/* 项目目次 更改
│ .gitignore
│ package.json
│ README.md
├─bin
│ www.js
├─config //增添webpack设置目次
│ webpack.config.js
├─middlewares
│ nunjucksMiddleware.js
├─routers
│ home.js
│ index.js
│ user.js
├─src
│ │─template.html // + html模板 以此模板为每一个进口天生 引入对应js的模板
│ ├─images // +图资本目次
│ │ ww.jpg
│ ├─js // + js目次
│ │ ├─home
│ │ │ home.js
│ │ └─user
│ │ user.js
│ └─less // + css目次
│ ├─common
│ │ common.less
│ │ nav.less
│ ├─home
│ │ home.less
│ └─user
│ user.less
└─views
├─home
│ home.html
├─user
│ user.html
├─_component
│ nav.html
└─_layout // webpac打包后的html模板
├─home
│ home.html
└─user
user.html
*/
<!-- template.html 内容-->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>{{title}}</title>
</head>
<body>
<!-- njk模板继续后添补 -->
{% block content %}
{% endblock %}
</body>
</html>
/* src/js/home/home.js 一个进口文件*/
import '../../less/home/home.less' //引入css
import img from '../../images/ww.jpg' //引入图片
console.log(111);
let add = (a, b) => a + b; //箭头函数
let a = 3, b = 4;
let c = add(a, b);
console.log(c);
// 这里只做打包演示代码 不具任何意义
<!-- less/home/home.less 内容 -->
// 引入大众款式
@import '../common/common.less';
@import '../common/nav.less';
.list {
li {
color: rebeccapurple;
}
}
.bg-img {
width: 200px;
height: 200px;
background: url(../../images/ww.jpg); // 背景图片
margin: 10px 0;
}
/* webpack设置 webpack.config.js */
const path = require('path');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const CopyWebpackPlugin = require('copy-webpack-plugin');
// 多进口
let entry = {
home: 'src/js/home/home.js',
user: 'src/js/user/user.js'
}
module.exports = evn => ({
mode: evn.production ? 'production' : 'development',
// 给每一个进口 path.reslove
entry: Object.keys(entry).reduce((obj, item) => (obj[item] = path.resolve(entry[item])) && obj, {}),
output: {
publicPath: '/',
filename: 'js/[name].js',
path: path.resolve('dist')
},
module: {
rules: [
{ // bable 依据须要转换到对应版本
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env']
}
}
},
{ // 转换less 并交给MiniCssExtractPlug插件提取到零丁文件
test: /\.less$/,
loader: [MiniCssExtractPlugin.loader, 'css-loader', 'less-loader'],
exclude: /node_modules/
},
{ //将css、js引入的图片目次指到dist目次下的images 坚持与页面引入的一致
test: /\.(png|svg|jpg|gif)$/,
use: [{
loader: 'file-loader',
options: {
name: '[name].[ext]',
outputPath: './images',
}
}]
},
{
test: /\.(woff|woff2|eot|ttf|otf)$/,
use: [{
loader: 'file-loader',
options: {
name: '[name].[ext]',
outputPath: './font',
}
}]
}
]
},
plugins: [
// 删除上一次打包目次(一般来说删除本身输出过的目次 )
new CleanWebpackPlugin(['dist', 'views/_layout'], {
// 当设置文件与package.json不再统一目次时刻须要指定根目次
root: path.resolve()
}),
new MiniCssExtractPlugin({
filename: "css/[name].css",
chunkFilename: "[id].css"
}),
// 将src下的图片资本平移到dist目次
new CopyWebpackPlugin(
[{
from: path.resolve('src/images'),
to: path.resolve('dist/images')
}
]),
// HtmlWebpackPlugin 每一个进口天生一个html 并引入对应打包临盆好的js
...Object.keys(entry).map(item => new HtmlWebpackPlugin({
// 模块名对应进口称号
chunks: [item],
// 输入目次 (可自行定义 这边输入到views下面的_layout)
filename: path.resolve('views/_layout/' + entry[item].split('/').slice(-2).join('/').replace('js', 'html')),
// 基准模板
template: path.resolve('src/template.html')
}))
]
});
<!-- package.json中增加 -->
"scripts": {
"start": "node bin/www.js",
"build": "webpack --env.production --config config/webpack.config.js"
}
运转 npm run build 后天生 dist views/_layout 两个目次
<!-- 检察打包后天生的模板 views/_layout/home/home.html-->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>{{title}}</title>
<!-- 引入了css文件 -->
<link href="/css/home.css" rel="stylesheet"></head>
<body>
{% block content %}
{% endblock %}
<!-- 引入了js文件 此时打包后的js/css在dist目次下面 -->
<script type="text/javascript" src="/js/home.js"></script></body>
</html>
<!-- view/home/home.html 页面改写 -->
<!-- njk继续模板 继续的目的来自webpack打包天生 -->
{% extends "../_layout/home/home.html" %}
{% block content %}
<!-- njk引入大众模块 -->
{% include "../_component/nav.html" %}
<h1>待办事项</h1>
<ul class="list">
<!-- 过滤器的挪用 timeFormate即我们在中间件中给njk加的过滤器 -->
{% for item in todoList %}
<li>{{item.name}} ---> {{item.time | timeFormate}}</li>
{% endfor %}
</ul>
<div class="bg-img"> 背景图</div>
<!-- 页面图片引入体式格局 -->
<img src="/images/ww.jpg"/>
{% endblock %}
/**koa处置惩罚静态资本
* 依靠 npm i 'koa-static
*/
// www.js 增添 将静态资本目次指向 打包后的dist目次
app.use(require('koa-static')(path.resolve('dist')))
运转
npm run build
npm start
浏览器接见127.0.0.1:3000 检察页面 js css img 结果
第六步 监听编译
目的: 文件发作改及时编译打包
依靠 npm i pm2 concurrently
/**项目中文件发作更改 须要重启效劳才看到结果是一件蛋疼的事,故须要及时监听更改 */ <!-- 我们要监听的有两点 一是node效劳 而是webpack打包 package.json更改以下 --> "scripts": { // concurrently 监听同时监听两条敕令 "start": "concurrently \"npm run build:dev\" \"npm run server:dev\"", "dev": "npm start", // 临盆环境 实行两条敕令即可 无监听 "product": "npm run build:pro && npm run server:pro", // pm2 --watch参数监听效劳的代码更改 "server:dev": "pm2 start bin/www.js --watch", // 临盆不须要用监听 "server:pro": "pm2 start bin/www.js", // webpack --watch 对打包文件监听 "build:dev": "webpack --watch --env.production --config config/webpack.config.js", "build:pro": "webpack --env.production --config config/webpack.config.js" }
第七步 数据要求
目的: node要求接口数据 添补模板
依靠 npm i node-fetch
/*上面的代码中routers/home.js首页路由中我们向页面衬着了下面的一组数据 */ ctx.state.todoList = [ {name: '用饭', time: '2019.1.4 12:00'}, {name: '下午茶', time: '2019.1.4 15:10'}, {name: '放工1', time: '2019.1.4 18:30'} ] /*但 数据是同步的 项目中我们必然会向java猎取其他背景拿到衬着数据再添补页面 我们来看看怎么做*/
/*我们在根目次下建立一个util的目次作为东西库 并简朴封装fetch.js要求数据*/ const nodeFetch = require('node-fetch') module.exports = ({url, method, data = {}}) => { // get要求 将参数拼到url url = method === 'get' || !method ? "?" + Object.keys(data).map(item => `${item}=${data[item]}`).join('&') : url; return nodeFetch(url, { method: method || 'get', body: JSON.stringify(data), headers: { 'Content-Type': 'application/json' }, }).then(res => res.json()) }
/*在根目次下建立一个service的目次作为数据层 并建立一个exampleService.js 作为示例*/ //引入封装的 要求东西 const fetch = require('../util/fetch.js') module.exports = { getTodoList (params = {}) { return fetch({ url: 'https://www.easy-mock.com/mock/5c35a2a2ce7b4303bd93fbda/example/todolist', method: 'post', data: params }) }, //... }
/* 将要求到场到路由中 routers/home.js 改写 */ const homeRouter = require('koa-router')() let exampleService = require('../service/exampleService.js') // 引入service api //将路由婚配回调 改成async函数 并在请时刻 await数据返来 再挪用render homeRouter.get(['/', '/index.html', '/index', '/home.html', '/home'], async (ctx, next) => { // 要求数据 let todoList = await exampleService.getTodoList({name: 'ott'}) // 替代本来的静态数据 ctx.state.todoList = todoList.data ctx.render('home/home', { title: '首页' }) }) // 导出路由备用 module.exports = homeRouter
第八步 日记打印
目的: 使顺序运转可视
依靠 npm i log4js
/* 在util目次下建立 logger.js 代码以下 作简朴的logger封装 */ const log4js = require('log4js'); const path = require('path') // 定义log config log4js.configure({ appenders: { // 定义两个输出源 info: { type: 'file', filename: path.resolve('log/info.log') }, error: { type: 'file', filename: path.resolve('log/error.log') } }, categories: { // 为info/warn/debug 范例log挪用info输出源 error/fatal 挪用error输出源 default: { appenders: ['info'], level: 'info' }, info: { appenders: ['info'], level: 'info' }, warn: { appenders: ['info'], level: 'warn' }, debug: { appenders: ['info'], level: 'debug' }, error: { appenders: ['error'], level: 'error' }, fatal: { appenders: ['error'], level: 'fatal' }, } }); // 导出5种范例的 logger module.exports = { debug: (...params) => log4js.getLogger('debug').debug(...params), info: (...params) => log4js.getLogger('info').info(...params), warn: (...params) => log4js.getLogger('warn').warn(...params), error: (...params) => log4js.getLogger('error').error(...params), fatal: (...params) => log4js.getLogger('fatal').fatal(...params), }
/* 在fetch.js中是哟logger */ const nodeFetch = require('node-fetch') const logger = require('./logger.js') module.exports = ({url, method, data = {}}) => { // 到场要求日记 logger.info('要求url:', url , method||'get', JSON.stringify(data)) // get要求 将参数拼到url url = method === 'get' || !method ? "?" + Object.keys(data).map(item => `${item}=${data[item]}`).join('&') : url; return nodeFetch(url, { method: method || 'get', body: JSON.stringify(data), headers: { 'Content-Type': 'application/json' }, }).then(res => res.json()) } <!-- 日记打印 --> [2019-01-09T17:34:11.404] [INFO] info - 要求url: https://www.easy-mock.com/mock/5c35a2a2ce7b4303bd93fbda/example/todolist post {"name":"ott"}
注: 仅共进修参考,临盆设置自行推敲!转载请备注泉源!