webpack多页应用架构专题系列 4

第四章:webpack的进阶应用

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

前言

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

那么,有没有办法让我们随时随地都能根据业务的需要来自定义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 preLoaders adding !! to a request will disable all loaders specified in the configuration adding -! 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的话,请务必在里手写

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

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

    由于我的脚手架项目还是比较简单的,所以这些公共组件的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

    总结

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



你可能感兴趣的:(webpack)