前后端分离之vue2.0+webpack2 实战项目 -- html模板拼接

对于前后端分离,如何把一个页面的公共部分比如head, header, footer, content等组合成一个完整的html 是一个值得考虑的地方。

对于php,我们可以利用include加载其他页面,像yii框架,可以利用render将输出的内容嵌入到父模板,从而形成一个完整的页面。

那对于纯静态的html我们如何拼接呢?

可以想到市面上的多种模板引擎,比如artTemplate, doT, ejs等,他们可以使用require或include等特殊标记的语法来引入其他模块。但如果每个页面我们都去写若干个require,比如:

require('head.html')
require('header.html')
...
require('side-bar.html')
require('footer.html')

是不是略显麻烦?另外head内的title如何自定义?对于要求head内根据不同页面有不同引用的icon或者css甚至js,该如何配置呢?

这时我们就想着去寻找一套自动化的拼接和可配置的灵活方案,html-webpack-plugin 就可以帮我们完成这些。

var HtmlWebpackPlugin = require('html-webpack-plugin');
var webpackConfig = {
  entry: 'index.js',
  output: {
    path: 'dist',
    filename: 'bundle.js'
  },
  plugins: [
  new HtmlWebpackPlugin(
      title: 'this is mytitle a',
      _html: 'this is content a.',      
      filename: 'index.html',
          template: 'index.ejs',
    )
  ],
};

html-webpack-plugin的基本使用方法如上,它可以把模板template index.ejs转化成html,命名为index.html,并把bundle.js引入index.html。

html-webpack-plugin默认集成了ejs模板引擎,所以我们可以直接使用ejs模板。当然我们也可以引入其他模板,包括handlebars等都可以使用。

title,  _html为自定义的一些属性,你还可以增加比如content, data等等你想要的数据传到模板。传到模板后,ejs可以直接获取到传过来的值,获取方法如下:

<!-- index.ejs -->

<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=0, minimum-scale=1.0, maximum-scale=1.0">
<title><%= htmlWebpackPlugin.options.title %></title>
</head>
<body>
    <header style="text-align:center;color:#fff;font-size:20px;background:#333;">
        this is header.
    </header>

    <%= htmlWebpackPlugin.options._html %>

    <section id="app"></section>

    <footer style="text-align:center;color:#fff;font-size:20px;background:#333;">
        this is footer.
    </footer>
    <script src="./dist/build.js"></script>
</body>
</html>

<%= %> 用来引入变量

<% %> 用来执行js判断语句

这样我们就可以自定义一些内容输入到模板页面中,但我们如果有很多个模板,如何挨个生成呢?

官网给出的解决方案就是有几个模板就声明几次插件:

plugins: [
  new HtmlWebpackPlugin(
      title: 'this is mytitle a',
      _html: 'this is content a.',      
      filename: 'index_a.html',
          template: 'index_a.ejs',
    ),
      new HtmlWebpackPlugin(
      title: 'this is mytitle b',
      _html: 'this is content b.',      
      filename: 'index_b.html',
          template: 'index_b.ejs',
    ),
       new HtmlWebpackPlugin(
      title: 'this is mytitle c',
      _html: 'this is content c.',      
      filename: 'index_c.html',
          template: 'index_c.ejs',
    )
  ],

多个模板的问题解决了,但对于每个模板内部,如何抽离出公共部分(head等),我们每次写页面只关注内容部分呢?


正式构建模板布局框架

既然html-webpack-plugin的template可以接受多个模板,那我们也可以传给它一个js,只要js返回一个模板文件就可以,这样我们拼接的工作都可以用js和ejs完成。

在此之前我们说下html-webpack-plugin的 chunks 属性

{
  entry: {
      a: './a.js',
      b: './b.js',
      c: './c.js'
  },
  output: {
    path: 'dist',
    filename: 'js/[name].js'
  },
  plugins: [
    new HtmlWebpackPlugin({
      title: 'My App',
      filename: 'assets/admin.html',
      chunks: ['a','b']      
    })
  ]
}

chunks: 规定需要引入的模块。在这里例子中,只有a和b被插入到html中,c并不会被引入。

ok,接下来我们就可以针对不同的模板指定引入不同的js了。

先看下流程(如下示意图),假如现在要做income.html页面,我们只关注income.ejs,它是body中的内容部分,html-webpack-plugin 的 template 为 html/income.js,它会把我们的页面内容 income.ejs 传给 html/layout.js,在 layout.js 内,我们会引入html的各个公共部分,并把html/income.js 中定义的各种参数传给页面的各个部分,然后把这些公共部分传给 html/layout.ejs 组合并返回。html-webpack-plugin 就把返回的完整的模板转化为 目标html

代码如下:

/***** 生成组合后的html *****/

var pages = getEntry('./html/src/**/*.ejs')
for (var pathname in pages) {
    var conf = {
        filename: path.resolve(__dirname, './html/dist/' + pathname + '.html'), // html文件输出路径
        template: path.resolve(__dirname, './html/src/' + pathname + '/' + pathname + '.js'), // 模板路径
        inject: true, 
        cache: true, //只改动变动的文件
        minify: {
            removeComments: true,
            collapseWhitespace: false
        }
    }
    if (pathname in module.exports.entry) {
        conf.chunks = [pathname, 'vendors', 'manifest']
    }

    module.exports.plugins.push(new htmlWebpackPlugin(conf))
}

inject: ture/body 将js引用插入到body内,head将js引用插入到head内,false为不插入

cache:是否值编译改动的文件

minify: 压缩html

  removeComments: 去除注释

  collapseWhitespace: 去除空格

chunks: 自动引入公共模块 js 以及 当前 pathname 对应的 js 文件

template: 为入口js文件对应的用于拼接模板的js

这个js就有点像php的controller,可以定义当前页面的title等信息,并规定使用哪个ejs模板进行拼接

/* html/income/income.js */

const content = require('./income.ejs')  //使用income.ejs模板进行拼接
const layout = require('../layouts/layout.js') 
const pageTitle = '消息通知'  //自定义页面title并传给 layoutjs 分发给页面的公共模块

module.exports = layout.init(pageTitle).run(content({ pageTitle }))

 layout.js则引入各个公共模块,给他们传入需要的参数,并返回layout.ejs拼接后的结果

/* html/layout/layout.js */

const layout = require('./layout.ejs')
const header = require( './header.ejs') // 页头的模板
const footer = require('./footer.ejs') // 页脚的模板
const topNav = require('./top-nav.ejs') // 顶部栏的模板
const sideMenu = require('./side-menu.ejs') // 侧边栏的模板

/* 整理渲染公共部分所用到的模板变量 */

const pf= {
    pageTitle: ''
}
const moduleExports = {
  /* 处理各个页面传入而又需要在公共区域用到的参数 */
    init(pageTitle) {
        pf.pageTitle = pageTitle // 比如说页面名称,会在<title>或面包屑里用到
    
        //console.log('pf.pageTitle'+pf.pageTitle)

        return this
    },
  /* 整合各公共组件和页面实际内容,最后生成完整的HTML文档 */
    run(content) {
        const renderData = {
            header: header(),
            footer: footer(),
            topNav: topNav(pf),
            sideMenu: sideMenu(),
            content: content,
        }
        return layout(renderData)
    },
}

module.exports = moduleExports

layout.ejs 为终极模板,引入各个公共模块变量

<!-- html/layout/layout.ejs -->

<!DOCTYPE html>
<html>
<head>
    <title>vue</title>
</head>
<body>

    <%= header %>
    <div id="wrapper">
      <%= topNav %>
      <%= sideMenu %>
      <%= content %>
    </div>
    <%= footer %>
</body>
</html>

OK,到这里我们基本可以完成功能了,对每个新页面我们只需要关注这个页面的body部分,以及一个页面控制器 js 即可。无需在页面引入 css 和 js ,html-webpack-plugin会自动根据模板命名找到对应的js文件引入到html中,而css就只需要在相应的js文件中引入即可。

前后端分离的html拼接也就完成了。

目录结构参考:

源码:https://github.com/saysmy/vue2-webpack2-demo

如有错误请指正,有更好的构建方式期待留言交流

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏haifeiWu与他朋友们的专栏

测试环境服务器硬盘塞满问题排查

某天下午测试环境服务器出现tab无法补全命令,给出的提示大概意思就是说,无可用空间无法创建临时文件,不过这次跟上次出现的问题比较像,上次服务器出现的问题,因此楼...

1291
来自专栏Jackie技术随笔

基本TCP套接口编程

为了执行网络I/O,一个进程(无论是服务端还是客户端)必须做的第一件事情就是调用socket函数。

4256
来自专栏玄魂工作室

Python黑客编程3网络数据监听和过滤

课程的实验环境如下: • 操作系统:kali Linux 2.0 • 编程工具:Wing IDE • Python版本:2.7.9 • 涉及到的主要pytho...

1.1K3
来自专栏鸿的学习笔记

计算机网络整理

应用层(Application Layer) 传输层(Transport Layer) 网络层(Network Layer) 链接层(Link Layer) 实...

1141
来自专栏xx_Cc的学习总结专栏

iOS-网络编程(一)HTTP协议

3209
来自专栏张戈的专栏

WP Super Cache静态缓存插件纯代码版(兼容多域名网站)

中午,小熊发来一篇来自歧路亡羊博客的精彩教程:《wordpress 利用代码来实现缓存》。粗略看了一下,发现这个代码在几个月之前我就用过,不过由于此代码无法区分...

5107
来自专栏LanceToBigData

linux(九)之网络基础

一、ping命令   1.1、作用      用于检测主机。执行ping指令会使用ICMP传输协议,发出要求回应的信息,若远端主机的网络功能没有问题,就...

2297
来自专栏IT可乐

Java 多线程详解(五)------线程的声明周期

Java 多线程详解(一)------概念的引入:https://cloud.tencent.com/developer/article/1012542 Jav...

2195
来自专栏Google Dart

Dart服务器端 shelf_route包 原

简而言之,如果你想构建自己的堆栈,那么shelf_route和shelf_rest可能会更适合你。 如果你想要一个功能更全面的框架,同时仍然具有高度可扩展性,那...

944
来自专栏码洞

天下无难试之HTTP协议面试刁难大全

小编是一个非典型面试官,对于HTTP协议的第一个问题,一般人会问常用的状态码有哪些。小编不这么问,小编的问题是HTTP的全称是什么,把英语给我说出来!

1001

扫码关注云+社区

领取腾讯云代金券