webpack 布局架构 静态页面_webpack 页面静态化 - CSDN
精华内容
参与话题
  • 如何打造一个自定义的bootstrap? 前言 一般我们用bootstrap呐,都是用的从官网或github下载下来build好了的版本,千人一脸呐多没意思。当然,官网也给我们提供了自定义的工具,如下图所示,但每次要改些什么就要...

    如何打造一个自定义的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-seedArray-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打包而来的dll
    moduleExports.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 结构图


    展开全文
  • Nuxt笔记

    千次阅读 2018-03-13 19:43:46
    学习了Nuxt 官方文档写的非常详细,读完做个记录~ ...为cs架构予特性 异步数据加载 中间件支持 布局 流程图 总结: 取消手动nuxt generate 调用AWS Lambda 引用 每次数据更新就重新动态化...

    学习了Nuxt 官方文档写的非常详细,读完做个记录~
    nuxt笔记

    api:
    Nuxt api

    Nuxt.js

    基本

    • vue通用应用框架
    • UI渲染
    • 服务器渲染
    • 静态化
      • nuxt generate命令:为vue应用生成静态站点
        • 根据路由配置
    • 为cs架构予特性
      • 异步数据加载
      • 中间件支持
      • 布局
    • 流程图
    • 总结:
      • 取消手动nuxt generate
        • 调用AWS Lambda
      • 引用
        • 每次数据更新就重新动态化一下

    安装

    • 模版
    • 手动新建
      • package.json
      • Npm install —save nuxt

    目录

    • Layouts
      • 布局目录为nuxt.js保留,不可更改
    • components
      • 组件目录,不可asyncData as页面组件
    • middleware
      • 中间件,在页面获取数据前对数据获取处理
    • pages
      • 页面目录,nuxt 给此下.vue自动生成路由
    • static
      • 静态文件,不会被nuxt构建编译
    • store
      • vuex状态树
        • 用index.js激活

    路由

    • 基础路由
    • 动态路由
      • 下划线为前缀
      • 路由参数校验
        • vaildate({ param }){ return: }
    • 嵌套路由
      • 于文件夹同名的目录存放子视图组件
      • 父组件
    • 动态嵌套路由
    • 过渡特效
      • assets下创建main.css
        • Transition
    • 中间件
      • middleware
        • 定义一个函数 运行在页面渲染之前
      • 接收context做第一参数
      • 执行流程顺序
        • Nuxt.config.js
        • 匹配布局
        • 匹配页面
      • 使用
        • router:{middleware:’stats’}

    视图

    • 默认布局
      • layouts/default.vue
    • 错误页面
      • layouts/error.vue
    • 个性化布局
      • layouts下
        • 确保有
        • 制定使用布局
          • layout: ‘blog’
    • 页面
      • vue组件plus nuxt给的特殊配置项(asyncData, fetch, head..)看配置
    • html头部
      • vue-meta更新
      • 子主题

    异步数据asyncData方法

    • 在设置组件数据前异步获取或处理数据
    • 第一个参数设定为当前上下文对象
    • 返回给data给组件
    • 无法使用this引用组件对象
    • 使用
      • 返回promise
      • async或await
      • 回调函数

    资源文件(webpack是否处理)

    • 是:放在assets目录
      • 加载器
      • vue-loader
      • File-loader
      • URL-loader
    • 否:放在static目录
      • 使用/在根目录引用静态资源

    插件plugin

    • axios做http请求
      • 安装npm包
      • import
      • get
    • 使用vue插件
      • import
      • Nuxt.config.js 配置 plugins
        • vendor配置项配置
      • ssr:false
    • vuex状态树
      • 使用
        • 引用vuex模块
        • vendor配置项配置
        • 设置vue根实例store配置项
      • 方式
        • 普通方式
          • store/index.js返回vuex.store实例
        • 模块方式
          • index根模块,其他模块转化成子模块
      • fetch方法
        • 渲染页面前
        • 填充状态树
      • nuxtServerInit
        • 设置在index.js中

    命令

    • nuxt
      • 启动热加载
    • nuxt build
      • 利用webpack编译(发布时)
    • nuxt start
      • 先执行nuxt build,再启动服务器
    • nuxt generate
      • 编译,生成对应HTML文件
    • 总结
      • 开发使用
        • nuxt
        • npm run dev(需配置package.json)
      • 发布部署
        • 1 nuxt build
        • 2 nuxt start
    展开全文
  • 手把手从0打造电商平台前端

    千次阅读 2018-12-08 08:44:07
    第一章 课程介绍与开发前的准备工作 1.1 课程目标  掌握一个电商网站从设计到上线的整个过程所涉及的流程 ...架构设计  前后端完全分离  分层架构  模块化 技术选型  HTML css js jQuery...

    第一章 课程介绍与开发前的准备工作

    1.1

    课程目标

        掌握一个电商网站从设计到上线的整个过程所涉及的流程

        具备独立开发一个前端项目的能力

        让你一开始就比别人起点更高,获得更快速的成长

        掌握开发过程中各种坑的解决思路和方法

    架构设计

        前后端完全分离

        分层架构

        模块化

    技术选型

        HTML css js jQuery

    辅助工具

        webpack 

        nodejs

        npm

        shell

    效率工具

        sublime

        Chrome

        Charles

         Git

    课程安排

     

    1.2电商平台需求分析

     

    电商平台有什么

    需求拆分原则

        单个迭代不宜太大

        需求可交付,能够形成功能闭环

        有成本意识,遵循二八原则

        有预期的价值体现

    提炼核心需求

    电商功能拆分--用户端

        商品->首页,商品列表,商品详情

        购物车->购物车数量,添加删除商品,购物车提交

        订单->订单确认(地址管理),订单提交,订单列表,订单详情

        支付->支付

        用户->登录,注册,个人信息,找回密码,修改密码

    电商功能拆分--管理后台

        商品管理->添加编辑商品,查看商品,下架

        品类管理->添加品类,查看品类

        订单管理->订单列表,订单详情,发货

        权限->管理员登录

    参与感

        更深入了解业务和需求

        丰富其他领域的知识

        提防不靠谱的需求

     

    1.3 架构设计

    架构设计--分层架构

        定义:把功能相似,抽象级别相近的实现进行分层隔离

        优势:松散耦合(易维护,易复用,易扩展)

        常见分层方式:MVC,MVVM

    架构设计--模块化

        定义:解决一个复杂问题时,自顶向下逐层把系统划分成若干模块的过程

        意义:解耦,可并行开发

        模块化方案:AMD,CMD,CommonJS,ES6

    模块化方案

        AMD,reactjs在推广化过程中的规范化产出

        CMD,

        CommonJS,

        ES6

    架构设计

        

    技术选型

    软件过程选择--敏捷开发

        定义:以用户的需求进化为核心,采用迭代,循序渐进的方法进行软件开发

        是一种迭代的意识和方法,而不是概念和工具

        优点:能够应对满足不断变化的需求

        不足:对团队成员的能力要求比较高

    前后端分离方式--不分离

        前后端共用同一项目目录,甚至页面内嵌js,css

        本地开发环境搭建成本高

        共同维护成本高

        发布风险高

    前后端分离方式--部分分离

        后端负责页面模板(JSP/velocity/freemarker)

        本地开发环境搭建成本较高

        更新页面模板仍需后端协助,效率不够高

        需要前后端同时发布

    前后端分离方式--完全分离1

        方案1:velocity,发布的时候同步到后端

        优点:完全分离,能直接生成动态的模块,利于SEO

        缺点,系统复杂度高,需要前后端同时发布

    前后端分离方式--完全分离2

        方案2:纯静态html,完全通过接口做数据交互

        优点:完全脱离后端模板,系统复杂度低

        缺点:不太利于 SEO

        优化方案:server render/蜘蛛定制页面

    框架选型

    构建工具

    版本控制:

    发布方式--发布过程

    发布方式--域名分布

    刚刚都做了哪些选择?

        软件过程:敏捷开发

        前后端分离:完全分离,纯静态方式

        模块化方案:CommonJS+webpack

        框架选择:用户端jQuery+css,管理系统react+sass

        版本控制:git

        发布过程:拉去代码->编译打包->发布到线上机器

     

    第二章 开发环境搭建与开发工具使用技巧

     

    2.1 开发环境的搭建

     

    开发环境

        语言环境:

        开发工具:

     

    2.2 node.js,npm的介绍和安装

     

    nodejs简介

        定义:js的服务器端运行环境

        用途:构建webpack的环境依赖

        特点:单线程,异步编程

        应用场景:低运算,高I/O

    nodejs的包管理工具--npm

    nodejs安装:

     

    2.3 Git的安装和配置

     

    介绍:Git是一款免费,来源的分布式版本控制系统,用于敏捷高效的处理任何或小或大的项目

    Git常用命令

    Git配置--gitconfig

     

    2.4 sublime使用技巧

     

    sublime优点:

        功能设计合理,支持多光标操作

        轻量级,运行速度快,使用流畅

        界面简洁美观,有利于集中注意力

        扩展性良好

    sublime常用设置

    快捷键:

     

    2.5 Chrome调试工具

     

     

    2.6 代理神器Charles/fiddler

     

    HTTP代理的原理

    项目开发中的作用

    Charles or fiddler

     

    2.7 本章回顾

     

     

     

     

    第三章 项目初始化与基于模块化的脚手架搭建

     

    3.1 项目初始化

     

    有什么好初始化的?

        项目目录

        项目工具

     

    3.2 git仓库的建立

    git仓库的建立:

        git项目建立

        git权限配置

        gitignore的配置

    目录结构的设计:

        

    新建组织->新建项目

    git clone 远程ssh地址->配置ssh key->项目公钥管理->git clone 远程ssh地址

    .gitigonre:

     

     

    3.3 项目脚手架的搭建

     

    npm init

    npm install

    webpack

    webpack的安装

    webpack安装的疑问

    webpack.config.js

    webpack loaders

    webpack常用命令

    webpack-dev-server

     

    3.4 脚手架搭建

    npm init

     

     

    3.5 webpack对脚本和样式的处理

    对脚本的处理

    对样式的处理

     

     

     

     

     

     

     

     

     

     

     

     

    第四章 项目通用功能开发

     

    通用js工具的封装

            网络请求工具

            URL路径工具

            模板渲染工具--Hogan

            字段验证&&通用提示

            统一跳转

    页面布局

     

    页面布局技巧

            定宽布局

            通用部分的抽离

            icon-font的引用

            通用样式的定义

    没有设计师,程序员怎么办

            能爽死强迫症的对齐

            你不知道的对称美

            扁平化多简单

            保持一定距离

            配色是一门学问,但灰色比较百搭

    通用导航条的开发

     

    通用页面头部的开发

    通用侧边导航的开发

    通用结果提示页的开发

    未完...

    展开全文
  • webpack多页应用架构专题系列 4

    千次阅读 2016-10-25 10:18:43
    第四章:webpack的进阶应用 如何打造一个自定义的bootstrap? 前言 一般我们用bootstrap呐,都是用的从官网或github下载下来build好了的版本,千人一脸呐多没意思。当然,官网也给我们提供了自定义的工具,如下图...

    第四章:webpack的进阶应用

    如何打造一个自定义的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 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的话,请务必在<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

    总结

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



    展开全文
  • 本文首发于Array_Huang的技术博客——实用至上,非经作者同意,请勿转载。原文地址:https://segmentfault.com/a/1190000008203380如果您对本系列文章感兴趣,欢迎关注订阅... 前言 近年来前端领域发展迅猛,前后...
  • 电商平台搭建笔记

    2018-04-02 15:00:01
    架构设计:前后端完全分离、分层架构、模块化技术选型:html、css、js、jquery辅助工具:Webpack、NodeJs、NPM、Shell效率工具:Sublime、Chrome、Charles、Git 课程安排: 一:基础框架的搭建(1)双平台的开发...
  • 废话不多说,的WebPack系列生态的更新速度真是让人感觉每天赶飞机一样,入坑已经很久,使用过程中避免不了将生态相关的插件更新到新版,这个过程中可能会遇到很多坑,举个最简单的例子,当的WebPack刚刚出4的时候看...
  • 前端架构体系技术

    千次阅读 2016-05-17 22:22:32
    伸缩布局:grid网格布局 基础UI样式:元素reset、按钮、图片、菜单、表单 组件UI样式:按钮组、字体图标、下拉菜单、输入框组、导航组、面包屑、分页、标签、轮播、弹出框、列表、多媒体、警告 响应式布局...
  • Vue 知识点汇总(下)--附案例代码及项目地址

    千次阅读 多人点赞 2020-01-14 01:53:43
    文章目录Vue预备知识与后续知识及项目案例一、简介1.Vue (读音 /vjuː/,类似于 view)的简单认识2.Vue.js安装二、Vue知识量化三、内容1、Webpack 详解什么是Webpack?前端模块化**和grunt/gulp的对比**webpack安装...
  • 最近半年都在做VueJS的前端开发,搭了几个框架,今天开源个还算不错的,分享出来下,如果你觉得不错就点个赞!效果图如下: ... UI用的是elementUI2.2.0版本的,VueJS ...vwn-基于vuejs2.5-elementUI2.2-webpack3....
  • 本文首发于Array_Huang的技术博客——实用至上,非经作者同意,请勿转载。原文地址:https://segmentfault.com/a/1190000010317802如果您对本系列文章感兴趣,欢迎关注订阅... 前言 一个成熟的项目,自然离不开迭...
  • Nuxt.js实现SSR的应用

    千次阅读 2018-07-10 16:00:58
    seo 即 搜索引擎优化(Search Engine Optimization),它是指通过站内优化,如:网站结构调整、网站内容建设、网站代码优化以及站外优化等方法,来进行搜索引擎优化。 简单说: 通过各种技术(手段)来确保,你的...
  • 本文首发于Array_Huang的技术博客——实用至上,非经作者同意,请勿转载。原文地址:https://segmentfault.com/a/1190000007159115如果您对本系列文章感兴趣,欢迎关注订阅这里:... 前言 上文《webpack多页应用...
  • 在前端刀耕火种的年代,布局用 table 标签,CSS 样式大量内联;校验 JS代码是否规范,需要复制代码到 jshint(相信很多 “前辈” 对此都不陌生);压缩合并代码,也是通过压缩工具压缩完再复制到一个文件里;修改了...
  • webpack以及前端发展

    2020-09-14 16:00:52
    webpack 是一个现代 JavaScript 应用程序的静态模块打包器(module bundler)。当 webpack 处理应用程序时,它会递归地构建一个依赖关系图(dependency graph),其中包含应用程序需要的每个模块,然后将所有这些模块...
  • 工作总结

    千次阅读 2019-05-31 10:16:10
    总结:JavaScript异步、事件循环与消息队列、微任务与宏任务 图解JS执行机制 HTTP1.0、HTTP1.1 和 HTTP2.0 的区别
  • 整体架构 业务功能实现 系统整体可用性,可维护性,可扩展性 前后端分离 API接口交互 分层架构设计 定义:把功能相似,抽象级别相近的实现进行分层隔离 优势:松散耦合(易维护、易复用、易扩展) ...
  • 紫色(Rendering):样式计算和布局,即重排 绿色(Painting):重绘 灰色(other):其它事件花费的时间 白色(Idle):空闲时间 事件总结: Loading事件 事件描述 Parse HTML浏览器执行HTML解析 Fini...
  • 搭建一个简单的cms前端框架

    万次阅读 2018-01-25 15:39:54
    明确一点,我们的公司的项目是要求前后端分离 ...1、技术选型:这套框架是搭建给后台人员用的,所以尽量去贴合他们的需要,我选用还是比较老的技术方法: node+require+bootstrap+ jquery+fis(webpack)+ jquery.templ
  • 2019前端趋势分析,干货多多

    千次阅读 2019-02-20 17:11:29
    Npm热门前端下载分析(框架、工具) 总结: 一、React 继续保持统治地位 React 多年来一直主导 Web 开发,2018 年它根本没有放慢速度。根据 Stackoverflow 调查,它仍然是最受欢迎的库之一。...
1 2 3 4 5 ... 20
收藏数 1,880
精华内容 752
关键字:

webpack 布局架构 静态页面