webpack多页应用架构

来源:互联网 发布:阿里云域名指向ip 编辑:程序博客网 时间:2024/05/17 20:35

如何打造一个自定义的bootstrap?

前言

一般我们用bootstrap呐,都是用的从官网或github下载下来build好了的版本,千人一脸呐多没意思。当然,官网也给我们提供了自定义的工具,如下图所示,但每次要改些什么就要重新在官网上打包一份,而且还是个国外的网站,甭提有多烦躁了。

bootstrap官网 - 自定义打包

那么,有没有办法让我们随时随地都能根据业务的需要来自定义bootstrap呢?答案自然是肯定的,webpack有啥干不了的呀(大误)[手动滑稽]

sass/less的两套方案

bootstrap主要由两部分组成:样式和jQuery插件。这里要说的是样式,bootstrap有less的方案,也有sass的方案,因此,也存在两个loader分别对应这两套方案:less <=>bootstrap-webpack 和 sass <=>bootstrap-loader 。

我个人惯用的是less,因此本文以bootstrap-webpack为例来介绍如何打造一个自定义的bootstrap。

开工了!

先引入全局的jQuery

众所周知,bootstrap这货指明是要全局的jQuery的,甭以为现在用webpack打包的就有什么突破了。引入全局jQuery的方法请看这篇文章《老式jQuery插件还不能丢,怎么兼容?》(ProvidePlugin +expose-loader),我的脚手架项目Array-Huang/webpack-seed也是使用的这套方案。

如何加载bootstrap配置?

bootstrap-webpack提供一个默认选配下的bootstrap,不过默认的我要你何用(摔

好,言归正题,我们首先需要新建两个配置文件bootstrap.config.jsbootstrap.config.less,并将这俩文件放在同一级目录下(像我就把业务代码里用到的config全部丢到同一个目录里了哈哈哈)。

因为每个页面都需要,也只需要引用一次,因此我们可以找个每个页面都会加载的公共模块(用Array-Huang/webpack-seed来举例就是src/public-resource/logic/common.page.js,我每个页面都会加载这个js模块)来加载bootstrap:

require('!!bootstrap-webpack!bootstrapConfig'); // bootstrapConfig是我在webpack配置文件中设好的alias,不设的话这里就填实际的路径就好了

上文已经说到,bootstrap-webpack其实就是一个webpack的loader,所以这里是用loader的语法。需要注意的是,如果你在webpack配置文件中针对js文件设置了loader(比如说babel),那么在加载bootstrap-webpack的时候请在最前面加个!!,表示这个require语句忽略webpack配置文件中所有loader的配置,还有其它的用法,看自己需要哈:

adding ! to a request will disable configured preLoadersadding !! to a request will disable all loaders specified in the configurationadding -! to a request will disable configured preLoaders and loaders but not the postLoaders

如何配置bootstrap?

上文提到有两个配置文件,bootstrap.config.jsbootstrap.config.less,显然,它们的作用是不一样的。

bootstrap.config.js

bootstrap.config.js的作用就是配置需要加载哪些组件的样式和哪些jQuery插件,可配置的内容跟官网是一致的,官方给出这样的例子:

module.exports = {  scripts: {    // add every bootstrap script you need    'transition': true  },  styles: {    // add every bootstrap style you need    "mixins": true,    "normalize": true,    "print": true,    "scaffolding": true,    "type": true,  }};

当时我是一下子懵逼了,就这么几个?完整的例子/文档在哪里?后来终于被我找到默认的配置了,直接拿过来在上面改改就能用了:

var ExtractTextPlugin = require('extract-text-webpack-plugin');module.exports = {  styleLoader: ExtractTextPlugin.extract('css?minimize&-autoprefixer!postcss!less'),  scripts: {    transition: true,    alert: true,    button: true,    carousel: true,    collapse: true,    dropdown: true,    modal: true,    tooltip: true,    popover: true,    scrollspy: true,    tab: true,    affix: true,  },  styles: {    mixins: true,    normalize: true,    print: true,    scaffolding: true,    type: true,    code: true,    grid: true,    tables: true,    forms: true,    buttons: true,    'component-animations': true,    glyphicons: false,    dropdowns: true,    'button-groups': true,    'input-groups': true,    navs: true,    navbar: true,    breadcrumbs: true,    pagination: true,    pager: true,    labels: true,    badges: true,    jumbotron: true,    thumbnails: true,    alerts: true,    'progress-bars': true,    media: true,    'list-group': true,    panels: true,    wells: true,    close: true,    modals: true,    tooltip: true,    popovers: true,    carousel: true,    utilities: true,    'responsive-utilities': true,  },};

这里的scripts项就是jQuery插件了,而styles项则是样式,可以分别对照着bootstrap英文版文档来查看。

需要解释的是styleLoader项,这表示用什么loader来加载bootstrap的样式,相当于webpack配置文件中针对.less文件的loader配置项吧,这里我也是直接从webpack配置文件里抄过来的。

另外,由于我使用了iconfont作为图标的解决方案,因此就去掉了glyphicons;如果你要使用glyphicons的话,请务必在webpack配置中设置好针对各类字体文件的loader配置,否则可是会报错的哦。

bootstrap.config.less

bootstrap.config.less配置的是less变量,bootstarp官网上也有相同的配置,这里就不多做解释了,直接放个官方例子:

@font-size-base: 24px;@btn-default-color: #444;@btn-default-bg: #eee;

需要注意的是,我一开始只用了bootstrap.config.js而没建bootstrap.config.less,结果发现报错了,还来建了个空的bootstrap.config.less就编译成功了,因此,无论你有没有配置less变量的需要,都请新建一个bootstrap.config.less

总结

至此,一个可自定义的bootstrap就出炉了,你想怎么折腾都行了,什么不用的插件不用的样式,统统给它去掉,把体积减到最小,哈哈哈。

后话

此方案有个缺点:此方案相当于每次编译项目时都把整个bootstrap编译一遍,而bootstrap是一个庞大的库,每次编译都会耗费不少的时间,如果只是编译一次也就算了,每次都要耗这时间那可真恶心呢。所以,我打算折腾一下看能不能有所改进,在这里先记录下原始的方案,后面如果真能改进会继续写文的了哈。

预打包Dll,实现webpack音速编译

前言

书承上文《如何打造一个自定义的bootstrap》。

上文说到我们利用webpack来打包一个可配置的bootstrap,但文末留下一个问题:由于bootstrap十分庞大,因此每次编译都要耗费大部分的时间在打包bootstrap这一块,而换来的仅仅是配置的便利,十分不划算。

我也并非是故意卖关子,这的确是我自己开发中碰到的问题,而在撰写完该文后,我立即着手探索解决之道。终于,发现了webpack这一大杀器:DllPlugin&DllReferencePlugin,打包时间过长的问题得到完美解决。

解决方案的机制和原理

DllPlugin&DllReferencePlugin这一方案,实际上也是属于代码分割的范畴,但与CommonsChunkPlugin不一样的是,它不仅仅是把公用代码提取出来放到一个独立的文件供不同的页面来使用,它更重要的一点是:把公用代码和它的使用者(业务代码)从编译这一步就分离出来,换句话说,我们可以分别来编译公用代码和业务代码了。这有什么好处呢?很简单,业务代码常改,而公用代码不常改,那么,我们在日常修改业务代码的过程中,就可以省出编译公用代码那一部分所耗费的时间了(是不是马上就联想到坑爹的bootstrap了呢)。

整个过程大概是这样的:

  1. 利用DllPlugin把公用代码打包成一个“Dll文件”(其实本质上还是js,只是套用概念而已);除了Dll文件外,DllPlugin还会生成一个manifest.json文件作为公用代码的索引供DllReferencePlugin使用。
  2. 在业务代码的webpack配置文件中配置好DllReferencePlugin并进行编译,达到利用DllReferencePlugin让业务代码和Dll文件实现关联的目的。
  3. 在各个页面
    中,先加载Dll文件,再加载业务代码文件。

适用范围

Dll文件里只适合放置不常改动的代码,比如说第三方库(谁也不会有事无事就升级一下第三方库吧),尤其是本身就庞大或者依赖众多的库。如果你自己整理了一套成熟的框架,开发项目时只需要在上面添砖加瓦的,那么也可以把这套框架也打包进Dll文件里,甚至可以做到多个项目共用这一份Dll文件。

如何配置哪些代码需要打包进Dll文件?

我们需要专门为Dll文件建一份webpack配置文件,不能与业务代码共用同一份配置:

const webpack = require('webpack');const ExtractTextPlugin = require('extract-text-webpack-plugin');const dirVars = require('./webpack-config/base/dir-vars.config.js'); // 与业务代码共用同一份路径的配置表module.exports = {  output: {    path: dirVars.dllDir,    filename: '[name].js',    library: '[name]', // 当前Dll的所有内容都会存放在这个参数指定变量名的一个全局变量下,注意与DllPlugin的name参数保持一致  },  entry: {    /*      指定需要打包的js模块      或是css/less/图片/字体文件等资源,但注意要在module参数配置好相应的loader    */    dll: [      'jquery', '!!bootstrap-webpack!bootstrapConfig',      'metisMenu/metisMenu.min', 'metisMenu/metisMenu.min.css',    ],  },  plugins: [    new webpack.DllPlugin({      path: 'manifest.json', // 本Dll文件中各模块的索引,供DllReferencePlugin读取使用      name: '[name]',  // 当前Dll的所有内容都会存放在这个参数指定变量名的一个全局变量下,注意与参数output.library保持一致      context: dirVars.staticRootDir, // 指定一个路径作为上下文环境,需要与DllReferencePlugin的context参数保持一致,建议统一设置为项目根目录    }),    /* 跟业务代码一样,该兼容的还是得兼容 */    new webpack.ProvidePlugin({      $: 'jquery',      jQuery: 'jquery',      'window.jQuery': 'jquery',      'window.$': 'jquery',    }),    new ExtractTextPlugin('[name].css'), // 打包css/less的时候会用到ExtractTextPlugin  ],  module: require('./webpack-config/module.config.js'), // 沿用业务代码的module配置  resolve: require('./webpack-config/resolve.config.js'), // 沿用业务代码的resolve配置};

如何编译Dll文件?

编译Dll文件的代码实际上跟编译业务代码是一样的,记得利用--config指定上述专供Dll使用的webpack配置文件就好了:

$ webpack --progress --colors --config ./webpack-dll.config.js

另外,建议可以把该语句写到npm scripts里,好记一点哈。

如何让业务代码关联Dll文件?

我们需要在供编译业务代码的webpack配置文件里设好DllReferencePlugin的配置项:

new webpack.DllReferencePlugin({  context: dirVars.staticRootDir, // 指定一个路径作为上下文环境,需要与DllPlugin的context参数保持一致,建议统一设置为项目根目录  manifest: require('../../manifest.json'), // 指定manifest.json  name: 'dll',  // 当前Dll的所有内容都会存放在这个参数指定变量名的一个全局变量下,注意与DllPlugin的name参数保持一致});

配置好DllReferencePlugin了以后,正常编译业务代码即可。不过要注意,必须要先编译Dll并生成manifest.json后再编译业务代码;而以后每次修改Dll并重新编译后,也要重新编译一下业务代码。

如何在业务代码里使用Dll文件打包的module/资源?

不需要刻意做些什么,该怎么require就怎么require,webpack都会帮你处理好的了。

如何整合Dll?

在每个页面里,都要按这个顺序来加载js文件:Dll文件 => CommonsChunkPlugin生成的公用chunk文件(如果没用CommonsChunkPlugin那就忽略啦) => 页面本身的入口文件。

有两个注意事项:

  • 如果你是像我一样利用HtmlWebpackPlugin来生成HTML并自动加载chunk的话,请务必在<head>里手写<script>来加载Dll文件。
  • 为了完全分离源文件和编译后生成的文件,也为了方便在编译前可以清空build目录,不应直接把Dll文件编译生成到build目录里,我建议可以先生成到源文件src目录里,再用file-loader给原封不动搬运过去。

光说不练假把式,来个跑分啊大兄弟!

下面以我的脚手架项目Array-Huang/webpack-seed为例,测试一下(使用开发环境的webpack配置文件webpack.dev.config.js)使用这套Dll方案前后的webpack编译时间:

  • 使用Dll方案前的编译时间为:10秒17
  • 使用Dll方案后的编译时间为:4秒29

由于该项目只是一个脚手架,涉及到的第三方库并不多,我只把jQuery、bootstrap、metisMenu给打包进Dll文件里了,尽管如此,还是差了将近6秒了,相信在实际项目中,这套DllPlugin&DllReferencePlugin的方案能为你省下更多的时间来找女朋友(大误)。

利用webpack生成HTML普通网页&页面模板

为什么要用webpack来生成HTML页面

按照我们前面的十一篇的内容来看,自己写一个HTML页面,然后在上面加载webpack打包的js或其它类型的资源,感觉不也用得好好的么?

是的没错,不用webpack用requireJs其实也可以啊,甚至于,传统那种人工管理模块依赖的做法也没有什么问题嘛。

但既然你都已经看到这一篇了,想必早已和我一样,追求着以下这几点吧:

  • 更懒,能自动化的事情绝不做第二遍。
  • 更放心,调通的代码比人靠谱,更不容易出错。
  • 代码洁癖,什么东西该放哪,一点都不能含糊,混在一起我就要死了。

那么,废话不多说,下面就来说说使用webpack生成HTML页面有哪些好处吧。

对多个页面共有的部分实现复用

在实际项目的开发过程中,我们会发现,虽然一个项目里会有很多个页面,但这些页面总有那么几个部分是相同或相似的,尤其是页头页尾,基本上是完全一致的。那我们要怎么处理这些共有的部分呢?

复制粘贴流

不就是复制粘贴的事嘛?写好一份完整的HTML页面,做下个页面的时候,直接copy一份文件,然后直接在copy的文件上进行修改不就好了吗?

谁是这么想这么做的,放学留下来,我保证不打死你!我曾经接受过这么一套系统,顶部栏菜单想加点东西,就要每个页面都改一遍,可维护性烂到爆啊。

Iframe流

Iframe流常见于管理后台类项目,可维护性OK,就是缺陷比较多,比如说:

  • 点击某个菜单,页面是加载出来了但是浏览器地址栏上的URL没变,刷新的话又回到首页了。
  • 搜索引擎收录完蛋,前台项目一般不能用Iframe来布局。
  • 没有逼格,Low爆了,这是最重要的一点(大误)。

SPA流

最近这几年,随着移动互联网的兴起,SPA也变得非常常见了。不过SPA的局限性也非常大,比如搜索引擎无法收录,但我个人最在意的,是它太复杂了,尤其是一些本来业务逻辑就多的系统,很容易懵圈。

后端模板渲染

这倒真是一个办法,只是,需要后端的配合,利用后端代码把页面的各个部分给拼合在一起,所以这方法对前端起家的程序员还是有点门槛的。

利用前端模板引擎生成HTML页面

所谓“用webpack生成HTML页面”,其实也并不是webpack起的核心作用,实际上靠的还是前端的模板引擎将页面的各个部分给拼合在一起来达到公共区域的复用。webpack更多的是组织统筹整个生成HTML页面的过程,并提供更大的控制力。最终,webpack生成的到底是完整的页面,还是供后端渲染的模板,就全看你自己把控了,非常灵活,外人甚至察觉不出来这到底是你自己写的还是代码统一生成的。

处理资源的动态路径

如果你想用在文件名上加hash的方法作为缓存方案的话,那么用webpack生成HTML页面就成为你唯一的选择了,因为随着文件的变动,它的hash也会变化,那么整个文件名都会改变,你总不能在每次编译后都手动修改加载路径吧?还是放心交给webpack吧。

自动加载webpack生成的css、less

如果你使用webpack来生成HTML页面,那么,你可以配置好每个页面加载的chunk(webpack打包后生成的js文件),生成出来的页面会自动用<script>来加载这些chunk,路径什么的你都不用管了哈(当然前提是你配置好了output.publicPath)。另外,用extract-text-webpack-plugin打包好的css文件,webpack也会帮你自动添加到<link>里,相当方便。

彻底分离源文件目录和生成文件目录

使用webpack生成出来的HTML页面可以很安心地跟webpack打包好的其它资源放到一起,相对于另起一个目录专门存放HTML页面文件来说,整个文件目录结构更加合理:

build  - index    - index      - entry.js      - page.html    - login      - entry.js      - page.html      - styles.css

如何利用webpack生成HTML页面

webpack生成HTML页面主要是通过html-webpack-plugin来实现的,下面来介绍如何实现。

html-webpack-plugin的配置项

每一个html-webpack-plugin的对象实例都只针对/生成一个页面,因此,我们做多页应用的话,就要配置多个html-webpack-plugin的对象实例:

pageArr.forEach((page) => {  const htmlPlugin = new HtmlWebpackPlugin({    filename: `${page}/page.html`,    template: path.resolve(dirVars.pagesDir, `./${page}/html.js`),    chunks: [page, 'commons'],    hash: true, // 为静态资源生成hash值    minify: true,    xhtml: true,  });  configPlugins.push(htmlPlugin);});

pageArr实际上是各个chunk的name,由于我在output.filename设置的是'[name]/entry.js',因此也起到构建文件目录结构的效果(具体请看这里),附上pageArr的定义:

module.exports = [  'index/login',  'index/index',  'alert/index',  'user/edit-password', 'user/modify-info',];

html-webpack-plugin的配置项真不少,这里仅列出多页应用常用到的配置:

  • filename,生成的网页HTML文件的文件名,注意可以利用/来控制文件目录结构的,其最终生成的路径,是基于webpack配置中的output.path的。
  • template,指定一个基于某种模板引擎语法的模板文件,html-webpack-plugin默认支持ejs格式的模板文件,如果你想使用其它格式的模板文件,那么需要在webpack配置里设置好相应的loader,比如handlebars-loaderhtml-loader啊之类的。如果不指定这个参数,html-webpack-plugin会使用一份默认的ejs模板进行渲染。如果你做的是简单的SPA应用,那么这个参数不指定也行,但对于多页应用来说,我们就依赖模板引擎给我们拼装页面了,所以这个参数非常重要。
  • inject,指示把加载js文件用的<script>插入到哪里,默认是插到<body>的末端,如果设置为'head',则把<script>插入到<head>里。
  • minify,生成压缩后的HTML代码。
  • hash,在html-webpack-plugin负责加载的js/css文件的网址末尾加个URL参数,此URL参数的值是代表本次编译的一个hash值,每次编译后该hash值都会变化,属于缓存解决方案。
  • chunks,以数组的形式指定由html-webpack-plugin负责加载的chunk文件(打包后生成的js文件),不指定的话就会加载所有的chunk。

生成一个简单的页面

下面提供一份供生成简单页面(之所以说简单,是因为不指定页面模板,仅用默认模板)的配置:

var HtmlWebpackPlugin = require('html-webpack-plugin');var webpackConfig = {  entry: 'index.js',  output: {    path: 'dist',    filename: 'index_bundle.js'  },  plugins: [new HtmlWebpackPlugin({    title: '简单页面',    filename: 'index.html',  })],};

使用这份配置编译后,会在dist目录下生成一个index.html,内容如下所示:

<!DOCTYPE html><html>  <head>    <meta charset="UTF-8">    <title>简单页面</title>  </head>  <body>    <script src="index_bundle.js"></script>  </body></html>

由于没有指定模板文件,因此生成出来的HTML文件仅有最基本的HTML结构,并不带实质内容。可以看出,这更适合React这种把HTML藏js里的方案。

利用模板引擎获取更大的控制力

接下来,我们演示如何通过制定模板文件来生成HTML的内容,由于html-webpack-plugin原生支持ejs模板,因此这里也以ejs作为演示对象:

<!DOCTYPE html><html>  <head>    <meta http-equiv="X-UA-Compatible" content="IE=edge" />    <meta http-equiv="Content-type" content="text/html; charset=utf-8"/>    <meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1" />     <title><%= htmlWebpackPlugin.options.title %></title>  </head>  <body>    <h1>这是一个用<b>html-webpack-plugin</b>生成的HTML页面</h1>    <p>大家仔细瞧好了</p>  </body></html>

'html-webpack-plugin'的配置里也要指定template参数:

var HtmlWebpackPlugin = require('html-webpack-plugin');var webpackConfig = {  entry: 'index.js',  output: {    path: 'dist',    filename: 'index_bundle.js'  },  plugins: [new HtmlWebpackPlugin({    title: '按照ejs模板生成出来的页面',    filename: 'index.html',    template: 'index.ejs',  })],};

那么,最后生成出来的HTML文件会是这样的:

<!DOCTYPE html><html>  <head>    <meta http-equiv="X-UA-Compatible" content="IE=edge" />    <meta http-equiv="Content-type" content="text/html; charset=utf-8"/>    <meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1" />     <title>按照ejs模板生成出来的页面</title>  </head>  <body>    <h1>这是一个用<b>html-webpack-plugin</b>生成的HTML页面</h1>    <p>大家仔细瞧好了</p>    <script src="index_bundle.js"></script>  </body></html>

到这里,我们已经可以控制整个HTML文件的内容了,那么生成后端渲染所需的模板也就不是什么难事了,以PHP的模板引擎smarty为例:

<!DOCTYPE html><html>  <head>    <meta http-equiv="X-UA-Compatible" content="IE=edge" />    <meta http-equiv="Content-type" content="text/html; charset=utf-8"/>    <meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1" />     <title><%= htmlWebpackPlugin.options.title %></title>  </head>  <body>    <h1>这是一个用<b>html-webpack-plugin</b>生成的HTML页面</h1>    <p>大家仔细瞧好了</p>    <p>这是用smarty生成的内容:<b>{$articleContent}</b></p>  </body></html>

处理资源的动态路径

接下来在上面例子的基础上,我们演示如何处理资源的动态路径:

var HtmlWebpackPlugin = require('html-webpack-plugin');var webpackConfig = {  entry: 'index.js',  output: {    path: 'dist',    filename: 'index_bundle.[chunkhash].js'  },  plugins: [new HtmlWebpackPlugin({    title: '按照ejs模板生成出来的页面',    filename: 'index.html',    template: 'index.ejs',  })],  module: {    loaders: {      // 图片加载器,雷同file-loader,更适合图片,可以将较小的图片转成base64,减少http请求      // 如下配置,将小于8192byte的图片转成base64码      test: /\.(png|jpg|gif)$/,      loader: 'url?limit=8192&name=./static/img/[hash].[ext]',    },  },};
<!DOCTYPE html><html>  <head>    <meta http-equiv="X-UA-Compatible" content="IE=edge" />    <meta http-equiv="Content-type" content="text/html; charset=utf-8"/>    <meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1" />     <title><%= htmlWebpackPlugin.options.title %></title>  </head>  <body>    <h1>这是一个用<b>html-webpack-plugin</b>生成的HTML页面</h1>    <p>大家仔细瞧好了</p>    <img src="<%= require('./imgs/login-bg.jpg')  %>" />  </body></html>

我们改动了什么呢?

  1. 参数output.filename里,我们添了个变量[chunkhash],这个变量的值会随chunk内容的变化而变化,那么,这个chunk文件最终的路径就会是一个动态路径了。
  2. 我们在页面上添加了一个<img>,它的src是require一张图片,相应地,我们配置了针对图片的loader配置,如果图片比较小,require()就会返回DataUrl,而如果图片比较大,则会拷贝到dist/static/img/目录下,并返回新图片的路径。

下面来看看,到底html-webpack-plugin能不能处理好这些动态的路径。

<!DOCTYPE html><html>  <head>    <meta http-equiv="X-UA-Compatible" content="IE=edge" />    <meta http-equiv="Content-type" content="text/html; charset=utf-8"/>    <meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1" />     <title>按照ejs模板生成出来的页面</title>  </head>  <body>    <h1>这是一个用<b>html-webpack-plugin</b>生成的HTML页面</h1>    <p>大家仔细瞧好了</p>    <img src="data:image/jpeg;base64,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" />    <script src="index_bundle.c3a064486c8318e5e11a.js"></script>  </body></html>

显然,html-webpack-plugin成功地将chunk加载了,又处理好了转化为DataUrl格式的图片,这一切,都是我们手工难以完成的事情。

还未结束

至此,我们实现了使用webpack生成HTML页面并尝到了它所带来的甜头,但我们尚未实现对多个页面共有的部分实现复用,下一篇《构建一个简单的模板布局系统》我们就来介绍这部分的内容。

构建一个简单的模板布局系统

前言

上文《利用webpack生成HTML普通网页&页面模板》我们基本上已经搞清楚如何利用html-webpack-plugin来生成HTML普通网页&页面模板,本文将以我的脚手架项目Array-Huang/webpack-seed介绍如何在这基础上搭建一套简单的模板布局系统。

模板布局系统架构图

模板布局系统架构图

模板布局系统各部分详解

上文我们说到,利用模板引擎&模板文件,我们可以控制HTML的内容,但这种控制总体来说还是比较有限的,而且很大程度受限于你对该模板引擎的熟悉程度,那么,有没有更简单的方法呢?

有!我们可以就用我们最熟悉的js来肆意组装、拼接出我们想要的HTML!

首先来看一个上文提到的例子:

var HtmlWebpackPlugin = require('html-webpack-plugin');var webpackConfig = {  entry: 'index.js',  output: {    path: 'dist',    filename: 'index_bundle.js'  },  plugins: [new HtmlWebpackPlugin(    title: '按照ejs模板生成出来的页面',    filename: 'index.html',    template: 'index.ejs',  )],};

这个例子是给html-webpack-plugin指定一个名为index.ejs的ejs模板文件,来达到生成HTML页面文件的目的,从html-webpack-plugin的文档我们可以看出,除了默认支持的ejs外,其实还可以使用其它模板引擎(例如jadehandlebarsunderscore),支持的方法是在webpack配置文件中配置好相应的loader即可。

因此,我们可以推理出,html-webpack-plugin其实并不关心你用的是什么模板引擎,只要你的模板最后export出来的是一份完整的HTML代码(字符串)就可以了。于是,我做了一个大胆的尝试,给html-webpack-plugintemplate参数指定一个js文件,然后在此js文件末尾export出一份完整的HTML代码来。这个js文件我命名为“模板接口”(上面架构图上有标识),意思是,不是光靠这一个js文件就能形成一份模板,“接口”之后是一套完整的模板布局体系。下面以webpack-seed项目里的src/pages/alert/index(“消息通知”页)作为例子进行说明。

html-webpack-plugin配置

先来看看我是如何给html-webpack-plugin指定一个js作为模板的:

/*   这是用来生成alert/index页的HtmlWebpackPlugin配置  在原项目中是循环批量new HtmlWebpackPlugin的,此处为了更容易理解,特别针对alert/index页做了修改*/new HtmlWebpackPlugin({    filename: `alert/index/page.html`,    template: path.resolve(dirVars.pagesDir, `./alert/index/html.js`), // 指定为一个js文件而非普通的模板文件    chunks: ['alert/index', 'commons'], // 自动加载上index/login的入口文件以及公共chunk    hash: true, // 为静态资源生成hash值    xhtml: true,  // 需要符合xhtml的标准});

模板接口

下面来介绍这个作为模板接口的js文件:

/* 选自webpack-seed/pages/alert/index/html.js  */const content = require('./content.ejs');  // 调取存放本页面实际内容的模板文件const layout = require('layout');  // 调用管理后台内部所使用的布局方案,我在webpack配置里定义其别名为'layout'const pageTitle = '消息通知'; // 页面名称// 给layout传入“页面名称”这一参数(当然有需要的话也可以传入其它参数),同时也传入页面实际内容的HTML字符串。content({ pageTitle })的意思就是把pageTitle作为模板变量传给ejs模板引擎并返回最终生成的HTML字符串。module.exports = layout.init({ pageTitle }).run(content({ pageTitle }));

从代码里我们可以看出,模板接口的作用实际上就是整理好当前页面独有的内容,然后交与layout作进一步的渲染;另一方面,模板接口直接把layout最终返回的结果(完整的HTML文档)给export出来,供html-webpack-plugin生成HTML文件使用。

页面实际内容长啥样?

<!-- 选自webpack-seed/pages/alert/index/content.ejs --><div id="page-wrapper">  <div class="container-fluid" >    <h2 class="page-header"><%= pageTitle %></h2>    <!-- ...... -->  </div></div>

消息通知页

layout

接着我们来看看整套模板布局系统的核心——layout。layout的主要功能就是接收各个页面独有的参数(比如说页面名称),并将这些参数传入各个公共组件生成各组件的HTML,然后根据layout本身的模板文件将各组件的HTML以及页面实际内容的HTML拼接在一起,最终形成一个完整的HTML页面文档。

/* 选自webpack-seed/src/public-resource/layout/layout/html.js */const config = require('configModule');const noJquery = require('withoutJqueryModule');const layout = require('./html.ejs'); // 整个页面布局的模板文件,主要是用来统筹各个公共组件的结构const header = require('../../components/header/html.ejs'); // 页头的模板const footer = require('../../components/footer/html.ejs'); // 页脚的模板const topNav = require('../../components/top-nav/html.ejs'); // 顶部栏的模板const sideMenu = require('../../components/side-menu/html.ejs'); // 侧边栏的模板const dirsConfig = config.DIRS;/* 整理渲染公共部分所用到的模板变量 */const pf = {  pageTitle: '',  constructInsideUrl: noJquery.constructInsideUrl,};const moduleExports = {  /* 处理各个页面传入而又需要在公共区域用到的参数 */  init({ pageTitle }) {    pf.pageTitle = pageTitle; // 比如说页面名称,会在<title>或面包屑里用到    return this;  },  /* 整合各公共组件和页面实际内容,最后生成完整的HTML文档 */  run(content) {    const headerRenderData = Object.assign(dirsConfig, pf); // 页头组件需要加载css/js等,因此需要比较多的变量    const renderData = {      header: header(headerRenderData),      footer: footer(),      topNav: topNav(pf),      sideMenu: sideMenu(pf),      content,    };    return layout(renderData);  },};module.exports = moduleExports;

接下来看看layout本身的模板文件长啥样吧:

<!-- 选自webpack-seed/src/public-resource/layout/layout/html.ejs --><%= header %><div id="wrapper">  <%= topNav %>  <%= sideMenu %>  <%= content %></div><%= footer %>

组件

整个页面的公共部分,被我以区域的形式切分成一个一个的组件,下面以页头组件作为例子进行解释:

<!DOCTYPE html><html lang="zh-cmn-Hans"><head>  <meta http-equiv="X-UA-Compatible" content="IE=edge" />  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />  <title><% if (pageTitle) { %> <%= pageTitle %> - <% } %> XXXX后台</title>  <meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1" />   <meta name="renderer" content="webkit" />  <link rel="stylesheet" type="text/css" href="<%= BUILD_FILE.dll.css %>">  <script type="text/javascript" src="<%= BUILD_FILE.dll.js %>"></script>  <!--[if lt IE 10]>    <script src="<%= BUILD_FILE.js.xdomain %>" slave="<%= SERVER_API_URL %>cors-proxy.html"></script>    <script src="<%= BUILD_FILE.js.html5shiv %>"></script>  <![endif]--></head><body>  <!--[if lt IE 9]>    <script src="<%= BUILD_FILE.js.respond %>"></script>  <![endif]-->

页头组件控制的范围基本上就是整个<head>以及<body>的头部。

不要小看这<body>的头部,由于webpack在使用extract-text-webpack-plugin生成CSS文件并自动加载时,会把<link>放在<head>的最后,而众所周知,实现IE8下Media Queries特性的respond.js是需要放在css后面来加载的,因此,我们就只能把respond.js放到<body>的头部来加载了。

由于我的脚手架项目还是比较简单的,所以这些公共组件的HTML都是直接根据模板文件来输出的;如果组件本身要处理的逻辑比较多,可以使用跟模板接口一样的思路,利用js文件来拼接。

至于组件本身行为的逻辑(js),可以一并放到各组件的目录里,在公共chunk里调用便是了。本文实际上只关注于如何生成HTML,这里提到这个只是提示一下组件的文件目录结构。

这里稍微再解释一下BUILD_FILE.js.*BUILD_FILE.dll.*是什么,这些其实都是没有用webpack打包起来的js/css,我用file-loader把这些文件从src目录搬到build目录了,这里模板变量输出的都是搬运后的路径,具体请看《听说webpack连图片和字体也能打包?》。启动搬运的代码放在webpack-seed/src/public-resource/config/build-file.config.js

总结

有了这套模板布局系统,我们就可以轻松地生成具有相同布局的多个静态页面了,如何管理页面布局公共部分这一多页应用的痛点也就顺利解决了。

No复制粘贴!多项目共用基础设施

前言

本文介绍如何在多项目间共用同一套基础设施,又或是某种层次的框架

基础设施是什么?

一个完整的网站,不可能只包含一个jQuery,或是某个MVVM框架,其中必定包含了许多解决方案,例如:如何上传?如何兼容IE?如何跨域?如何使用本地存储?如何做用户信息反馈?又或者具体到如何选择日期?等等等等……这里面必定包含了UI框架、JS框架、各种小工具库,不论是第三方的还是自己团队研发的。而以上所述的种种,就构成了一套完整的解决方案,也称基础设施

基础设施有个重要的特征,那就是与业务逻辑无关,不论是OA还是CMS又或是CRM,只要整体产品形态类似,我们就可以使用同一套基础设施。

框架

框架这个概念很泛,泛得让人心生困惑,但抽象出来说,框架就是一套定义代码在哪里写、怎么写的规则。不能说我们要怎么去框架,反倒是框架控制我们怎么去代码。

本系列前面的十来篇文章,分开来看是不同的,但如果所有文章合起来,并连同示例项目(Array-Huang/webpack-seed),实际上阐述的就是一套完整的多页应用框架(或称架构)。这套框架规定了整个应用的方方面面,举几个例子:

  • 每个页面的文件放在哪个目录?
  • 页面的HTML、入口文件、css、图片等等应该怎么放?
  • 编码规范(由ESLint来保证)。

当然,这只是我的框架,我希望你们可以看懂了,然后根据自己的需求来调整,变成你们的框架。甚至说,我自己在做不同类型的项目时,整体架构也都会有不少的变化。

为什么要共用基础设施/框架/架构?

缘起

数月前,我找同事要了一个他自己写的地区选择器,拉回来一看遍地都是ESLint的报错(他负责的项目没有用ESLint,比较随意),我这人有强迫症的怎么看得过眼,卷起袖子就开始改,改好也就正常使用了。过了一段时间,来了新需求,同事在他那改好了地区选择器又发了一份给我,我一看头都大了,又是满地报错,这不是又要我再改一遍吗?当时我就懵了,只好按着他的思路,对我的版本做了修改。从此,也确立了我们公司会有两份外观功能都一致,但是实现却不一样的地区选择器。

很坑爹是吧?

多项目共享架构变动

上面说的是组件级的,下面我们来说架构级别的。

我在公司主要负责的项目有两个,在我的不懈努力下,已经做到跟我的脚手架项目Array-Huang/webpack-seed大体上同构了。但维持同构显然是要付出代价的,我在脚手架项目试验过的改进,小至改个目录路径,大至引入个plugin啊loader啊什么的,都要分别在公司的两个项目里各做一遍,超烦哒(嫌弃脸

试想只是两个项目就已经这样了,如果是三个、四个,甚至六个、七个呢?堪忧啊堪忧啊!

快速创建新项目

不知道你们有没有这样子的经验:接到新项目时,灵机一动“这不就是我的XX项目吗?”,然后赶紧搬出XX项目的源码,然后删掉业务逻辑,保留可复用的基础设施。

也许你会说,这不已经比从零开始要好多了吗?总体上来说,是吧,但还不够好:

  • 你需要花时间重温整个项目的架构,搞清楚哪些要删、哪些要留。
  • 毕竟是快刀斩乱麻,清理好的架构比不上原先的思路那么清晰。
  • 清理完代码想着跑跑看,结果一大堆报错,一个一个来调烦的要命,而且还很可能是删错了什么了不得的东西,还要去原先额项目里搬回来。

以上这些问题,你每创建一个新项目都要经历一遍,我问你怕了没有。

脚手架不是可以帮助快速创建新项目吗?

是的没错,脚手架本身就算是一整套基础设施了,但依然有下列问题:

  • 维护一套脚手架你知道有多麻烦吗?公司项目一忙起来,加班都做不完,哪顾得上脚手架啊。最后新建项目的时候发现脚手架已经落后N多了,你到底是用呢还是不用呢?
  • 甭跟我提Github上开源的脚手架,像我这么有个性的人,会直接用那些妖艳贱货吗?
  • 不同类型的项目技术选型不一样,比如说:需不需要兼容低版本IE;是web版的还是Hybrid App的;是前台还是后台。每一套技术选型就是一套脚手架,难道你要维护这么多套脚手架吗?

上述问题,通过共用基础设施,都能解决

  • 既然共用了基础设施,要怎么改肯定都是所有项目一起共享的了,不论是组件层面的还是架构本身。
  • 假设你每个不同类型的项目都已经准备好了与其它项目共用基础设施,那么,你根本不需要花费多余的维护成本,创建新项目的时候看准了跟之前哪个项目是属于同一类型的,凑一脚就行了呗,轻松。

怎么实现多项目共用一套基础设施呢?

示例项目

在之前的文章里,我使用的一直都是Array-Huang/webpack-seed这个脚手架项目作为示例,而为了实践多项目共用基础设施,我对该项目的架构做了较大幅度的调整,升级为2.0.0版本。为免大家看前面的文章时发现示例项目货不对板,感到困惑,我新开了一个repo来存放调整后的脚手架:Array-Huang/webpack-seed-v2,并且,我在两个项目的README里我都注明了相应的内容,大家可不要混淆了哈。

下面就以从Array-Huang/webpack-seed到Array-Huang/webpack-seed-v2的改造过程来介绍如何实现多项目共用基础设施。

改造思路

改造思路其实很简单,就是把预想中多个项目都能用得上的部分从现有项目里抽离出来

如何抽离

抽离的说法是针对原项目的,如果单纯从文件系统的角度来说,只不过是移动了某些文件和目录。

移动到哪里了呢?自然是移动到与项目目录同级的地方,这样就方便多个项目引用这个核心了。

如果你跟我一样,在原项目中定义了大量路径和alias的话,移动这些文件/目录就只是个改变量的活了:

选自webpack-seed/webpack-config/base/dir-vars.config.js

var path = require('path');var moduleExports = {};// 源文件目录moduleExports.staticRootDir = path.resolve(__dirname, '../../'); // 项目根目录moduleExports.srcRootDir = path.resolve(moduleExports.staticRootDir, './src'); // 项目业务代码根目录moduleExports.vendorDir = path.resolve(moduleExports.staticRootDir, './vendor'); // 存放所有不能用npm管理的第三方库moduleExports.dllDir = path.resolve(moduleExports.srcRootDir, './dll'); // 存放由各种不常改变的js/css打包而来的dllmoduleExports.pagesDir = path.resolve(moduleExports.srcRootDir, './pages'); // 存放各个页面独有的部分,如入口文件、只有该页面使用到的css、模板文件等moduleExports.publicDir = path.resolve(moduleExports.srcRootDir, './public-resource'); // 存放各个页面使用到的公共资源moduleExports.logicDir = path.resolve(moduleExports.publicDir, './logic'); // 存放公用的业务逻辑moduleExports.libsDir = path.resolve(moduleExports.publicDir, './libs');  // 与业务逻辑无关的库都可以放到这里moduleExports.configDir = path.resolve(moduleExports.publicDir, './config'); // 存放各种配置文件moduleExports.componentsDir = path.resolve(moduleExports.publicDir, './components'); // 存放组件,可以是纯HTML,也可以包含js/css/image等,看自己需要moduleExports.layoutDir = path.resolve(moduleExports.publicDir, './layout'); // 存放UI布局,组织各个组件拼起来,因应需要可以有不同的布局套路// 生成文件目录moduleExports.buildDir = path.resolve(moduleExports.staticRootDir, './build'); // 存放编译后生成的所有代码、资源(图片、字体等,虽然只是简单的从源目录迁移过来)module.exports = moduleExports;

选自webpack-seed/webpack-config/resolve.config.js

var path = require('path');var dirVars = require('./base/dir-vars.config.js');module.exports = {  // 模块别名的配置,为了使用方便,一般来说所有模块都是要配置一下别名的  alias: {    /* 各种目录 */    iconfontDir: path.resolve(dirVars.publicDir, 'iconfont/'),    configDir: dirVars.configDir,    /* vendor */    /* bootstrap 相关 */    metisMenu: path.resolve(dirVars.vendorDir, 'metisMenu/'),    /* libs */    withoutJqueryModule: path.resolve(dirVars.libsDir, 'without-jquery.module'),    routerModule: path.resolve(dirVars.libsDir, 'router.module'),    libs: path.resolve(dirVars.libsDir, 'libs.module'),    /* less */    lessDir: path.resolve(dirVars.publicDir, 'less'),    /* components */    /* layout */    layout: path.resolve(dirVars.layoutDir, 'layout/html'),    'layout-without-nav': path.resolve(dirVars.layoutDir, 'layout-without-nav/html'),    /* logic */    cm: path.resolve(dirVars.logicDir, 'common.module'),    cp: path.resolve(dirVars.logicDir, 'common.page'),    /* config */    configModule: path.resolve(dirVars.configDir, 'common.config'),    bootstrapConfig: path.resolve(dirVars.configDir, 'bootstrap.config'),  },  // 当require的模块找不到时,尝试添加这些后缀后进行寻找  extentions: ['', 'js'],};

抽离对象

抽离的方法很简单,那么关键就看到底是哪些部分可以抽离、需要抽离了,这一点看我抽离后的成果就比较清晰了:

先来看根目录:

├─ core # 抽离出来的基础设施,或称“核心”├─ example-admin-1 # 示例项目1,被抽离后剩下的├─ example-admin-2 # 示例项目2,嗯,简单起见,直接复制了example-admin-1,不过还是要做一点调整的,比如说配置├─ npm-scripts # 没想到npm-scripts也能公用吧?├─ vendor # 无法在npm上找到的第三方库├─ .eslintrc # ESLint的配置文件├─ package.json # 所有的npm库依赖建议都写到这里,不建议写到具体项目的package.json里

再来看看core目录

├─ _webpack.dev.config.js # 整理好公用的开发环境webpack配置,以备继承├─ _webpack.product.config.js # 整理好公用的生产环境webpack配置,以备继承├─ webpack-dll.config.js # 用来编译Dll文件用的webpack配置文件├─ manifest.json # Dll文件的资源目录├─ package.json # 没有什么实质内容,我这里就放了个编译Dll用的npm script├─components # 各种UI组件│  ├─footer│  ├─header│  ├─side-menu│  └─top-nav├─config # 公共配置,有些是提供给具体项目的配置来继承的,有些本身就有用(比如说“核心”部分本身需要的配置)├─dll # 之前的文章里就说过,我建议把各种第三方库(包括npm库也包括非npm库)都打包成Dll来加速webpack编译过程,这部分明显就属于基础设施了├─iconfont # 字体图标能不能公用,这点我也是比较犹豫的,看项目实际需要吧,不折腾的话还是推荐公用├─layout # 布局,既然是同类型项目,布局肯定是基本一样的│  ├─layout│  └─layout-without-nav├─less # 样式基础,在我这项目里就是针对bootstrap的SB-Admin主题做了修改│  ├─base-dir│  └─components-dir├─libs # 自己团队研发的一些公共的方法/库,又或是针对第三方库的适配器(比如说对alert库封装一层,后面要更换库的时候就方便了)├─npm-scripts # 与根目录下的npm-scripts目录不一样,这里的不是用来公用的,而是“核心”使用到的script,比如我在这里就放了编译dll的npm script└─webpack-config # 公用的webpack配置,尤其是关系到“核心”部分的配置,比如说各第三方库的alias。这里的配置是用来给具体项目来继承的,老实说我现在继承的方法也比较复杂,回头看看有没有更简单的方法。    ├─base    ├─inherit    └─vendor

最后总结一下,是哪些资源被抽离出来了:

  • webpack配置中属于架构的部分,比如说各种loader、plugin、“核心”部分的alias。
  • “核心”部分所需的配置,比如我这项目里为了定制bootstrap而建的配置。
  • 各种与UI相关的资源,比如UI框架/样式、UI组件、字体图标。
  • 第三方库,以Dll文件的形式存在。
  • 自研库/适配器。

结构图

Array-Huang-webpack-seed-v2 结构图


0 0
原创粉丝点击