对于先后端分离,如何把一个页面的公共部分好比head, header, footer, content等组合成一个完整的html 是一个值得考虑的地方。php
对于php,咱们能够利用include加载其余页面,像yii框架,能够利用render将输出的内容嵌入到父模板,从而造成一个完整的页面。css
那对于纯静态的html咱们如何拼接呢?html
能够想到市面上的多种模板引擎,好比artTemplate, doT, ejs等,他们能够使用require或include等特殊标记的语法来引入其余模块。但若是每一个页面咱们都去写若干个require,好比:vue
require('head.html') require('header.html') ... require('side-bar.html') require('footer.html')
是否是略显麻烦?另外head内的title如何自定义?对于要求head内根据不一样页面有不一样引用的icon或者css甚至js,该如何配置呢?webpack
这时咱们就想着去寻找一套自动化的拼接和可配置的灵活方案,html-webpack-plugin 就能够帮咱们完成这些。git
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。github
html-webpack-plugin默认集成了ejs模板引擎,因此咱们能够直接使用ejs模板。固然咱们也能够引入其余模板,包括handlebars等均可以使用。web
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>
<%= %> 用来引入变量app
<% %> 用来执行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
若有错误请指正,有更好的构建方式期待留言交流