'use strict';

Main Entry Point

This is the entry point webpack loads when we are not testing. Normally all your app code would initialize here. This is however just a test to load bootstrap using bootstrap-webpack

require('bootstrap-webpack!./bootstrap.config.js');
var $ = require('jquery');

Affix our navigation when scrolling past the main header.

$(function () {
  var $header = $('#header');
  $('#nav').affix({
    offset: {
      top: function () {
        return $header.offset().top + $header.outerHeight(true);
      }
    }
  });
});

index.html

 

Html Header

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <meta name="description" content="">
    <meta name="author" content="">

    <title>boostrap-webpack example</title>

    

HTML5 shim and Respond.js for IE8 support of HTML5 elements and media queries

    <!--[if lt IE 9]>
      <script src="https://oss.maxcdn.com/html5shiv/3.7.2/html5shiv.min.js"></script>
      <script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>
    <![endif]-->
  </head>

  <body>

    <div class="container">
      

Main Header

      <div id="header" class="jumbotron">
        <h2>bootstrap-webpack example</h2>
        <p>
          This is an example of using <a href="https://github.com/bline/bootstrap-webpack">bootstrap-webpack</a>.
          You may also find the gulpfile useful if you use <a href="http://gulpjs.com/">gulp</a>.
        </p>
        <p>
          <a class="btn btn-primary" href="https://github.com/bline/bootstrap-webpack">bootstrap-webpack</a>
          <a class="btn btn-info" href="https://github.com/bline/bootstrap-webpack-example">boostrap-webpack-example</a>
        </p>
      </div>

This navigation bar is affixed to the top of the screen once the browser scrolls past the [Main Header].

      <div id="nav">
        <nav class="navbar navbar-default navbar-static">
          <div class="container">
            

.btn-navbar is used as the toggle for collapsed navbar content

            <a class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
              <span class="glyphicon glyphicon-bar"></span>
              <span class="glyphicon glyphicon-bar"></span>
              <span class="glyphicon glyphicon-bar"></span>
            </a>
            <div class="navbar-collapse collapse">
              <ul class="nav navbar-nav" role="tablist">
                

Loop over each file and present a navigation link to activate the tab.

                <% _.forEach(docs, function (doc, i) { %>
                  <li role="presentation" class="<%= i === 0 ? 'active' : '' %>">
                    <a href="#<%= doc.id %>" role="tab" data-toggle="tab"><%= doc.file.relative %></a>
                  </li>
                <% }); %>
              </ul>
            </div>
          </div>
        </nav>
      </div>

Main Content

Outputs the generated html and docs in two panes.

      <div class="tab-content doc-content">
        <% _.forEach(docs, function (doc, i) { %>
          <div role="tabpanel" class="tab-pane fade<%= i === 0 ? ' in active' : '' %>" id="<%= doc.id %>">
            <table class="file-table">
              

doc.docco is an array of sections. Each one contains the

 

html for displaying the documention and code associated with it.

              <% _.forEach(doc.docco, function (section, i) { %>
                <tr>
                  <td class="file-docs">
                    <%= section.docsHtml %>
                  </td>
                  

If no code text, leave an empty cell for formatting

                  <td class="file-code">
                    <% if (!(/^\s*$/).test(section.codeText)) { %>
                      <%= section.codeHtml %>
                    <% } else { %>
                      &nbsp;
                    <% } %>
                  </td>
                </tr>
              <% }); %>
            </table>
          </div>
        <% }); %>
      </div>
    </div>
    

Scripts

 

The main entry point generated by webpack

    <script src="main.js"></script>
    

Use google-code-prettify to enhance the code view.

    <script src="https://google-code-prettify.googlecode.com/svn/loader/run_prettify.js"></script>
  </body>
</html>

'use strict';

Webpack Configuration

This file is used for both testing under karma-webpack and gulp-webpack.

var path = require('path');

module.exports = {
  cache: true,

Output

karma-webpack will specify the output path when testing. This setting is used for building.

  output: {
    path: path.join(__dirname, './dist'),
    filename: 'main.js'
  },
  module: {

Loaders

    loaders: [

IMPORTANT This is needed so that each bootstrap js file required by bootstrap-webpack has access to the jQuery object

      { test: /bootstrap\/js\//, loader: 'imports?jQuery=jquery' },

Needed for the css-loader when bootstrap-webpack loads bootstrap's css.

      { test: /\.woff(\?v=\d+\.\d+\.\d+)?$/,   loader: "url?limit=10000&minetype=application/font-woff" },
      { test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/,    loader: "url?limit=10000&minetype=application/octet-stream" },
      { test: /\.eot(\?v=\d+\.\d+\.\d+)?$/,    loader: "file" },
      { test: /\.svg(\?v=\d+\.\d+\.\d+)?$/,    loader: "url?limit=10000&minetype=image/svg+xml" }
    ]
  }
};


(function () {
  'use strict';

Setup

  var _ = require('lodash');
  var del = require("del");
  var gulp = require("gulp");
  var $ = require('gulp-load-plugins')();
  var opn = require('opn');
  var marked = require('marked');
  var part = require('code-part');

Karma for running browser tests in PhantomJS.

  var karma = require('karma').server;

Load config for webpack target below. karma.conf.js uses the same configuration file.

  var webpackConfig = require("./webpack.config.js");

Karma needs a full path to the config file.

  var KarmaConfig = require('path').join(__dirname, './karma.conf.js');

Sources for generating index.html.

  var IndexSources = [
    'index.js',
    'index.html',
    'webpack.config.js',
    'gulpfile.js',
    'karma.conf.js',
    'style.less',
    'bootstrap.config.less',
    'bootstrap.config.js'
  ];

Helper Functions

highlight

Returns code with google-code-prettify markup setup to use line numbers started at specified line. Used in both docco and doccoHtml to add prettify markup to the code sections of the docs.

  var highlight = function (code, startLine) {
    var html = '<?prettify';
    if (_.isNumber(startLine))
      html += ' linenums=' + startLine;
    html += '><pre class="prettyprint">' + _.escape(code) + '</pre>'
    return html;
  };

Setup marked to use our highlighter.

  marked.setOptions({ highlight: highlight });

docco

code-part To parse out code/docs and marked to format the docs. marked is manually applied because we are using google-code-prettify to highlight code.

  var docco = function (path, code, config) {
    var sections = part(path, code, config);
    _.forEach(sections, function (section) {
      section.codeHtml = highlight(section.codeText, section.codeLine);
      section.docsHtml = marked(section.docsText);
    });
    return sections;
  }

Tasks

task clean

Cleans up dist directory using del.

  gulp.task("clean", function (done) {
    del(["dist/*"], done);
  });

task index

Build's the index file with documentation from docco with the index.html lodash template.

  gulp.task("index", ["clean"], function (done) {
    var docs = [];
    gulp.src(IndexSources)
      .pipe($.tap(function (file) {
        docs.push({
          file: file,
          docco: docco(file.path, file.contents.toString()),
          id: _.uniqueId('file-')
        })}))

After we've created the docs array, build the template.

      .on('end', function () {
        gulp.src('index.html')
          .pipe($.template({ docs: docs }))
          .pipe(gulp.dest('dist'))
          .on('end', function () { done() })
      });
  });

task webpack

Builds the main.js and any resources (bootstrap uses a few) into the dist directory. Uses gulp-webpack.

  gulp.task("webpack", ["clean"], function () {
    return gulp.src("index.js")
      .pipe($.webpack(webpackConfig))
      .pipe(gulp.dest('dist'));
  });

task build

Build index.html and main.js.

  gulp.task("build", ["webpack", "index"]);

task watch

Build and serve index.html on localhost port 3000 launching a browser with opn to view. If you have the livereload plugin for chrome installed it will also reload your browser when files in the dist directory change.

  gulp.task("watch", ["build"], function () {
    $.livereload.listen();
    gulp.watch('dist/**/*').on('change', $.livereload.changed);
    gulp.watch(IndexSources, ['build']);
    opn("http://127.0.0.1:3000/");
    return $.serve('dist')();
  });

task deploy

Deploy to Github pages. UNTESTED

  gulp.task("deploy", ['build'], function () {
    return gulp.src("dist/**/*")
      .pipe($.ghPages('git@github.com:bline/bootstrap-webpack-example.git'));
  });

task test

Run tests in Karma using FantomJS.

  gulp.task("test", function (done) {
    karma.start({
      configFile: KarmaConfig,
      singleRun: true
    }, done);
  });

task default

Run test by default.

  gulp.task("default", ["test"]);
})();

'use strict';

Karma Configuration

Karma is used for running tests in this demo with the help of karma-webpack.

module.exports = function (config) {
  config.set({
  • frameworks - Test frameworks we are using. mocha and chai.
    frameworks: ['mocha', 'chai', 'chai-as-promised'],

  • files - Sets the entry point(s). Use a single entry point here unless you don't mind a webpack per file.
    files: [
      'test/entry.js'
    ],
    preprocessors: {
      'test/entry.js': ['webpack']
    },
  • webpack - Uses the same webpack configuration as everything. karma-webpack overrides the output path and uses in-memory fs so it doesn't hit to disk for tests.
    webpack: require('./webpack.config.js'),
    webpackServer: {
      quiet: true,
      stats: true
    },
  • port - Sets the port the run on when running tests. If this is conflicting with something, set it in the gulpfile.js
    port: 8080,
  • logLevel - set to config.LOG_WARN for more debugging output
    logLevel: config.LOG_INFO,
  • colors - Who doesn't like colors?
    colors: true,
  • autoWatch - Tests are single run. Keep this off.
    autoWatch: false,
  • browsers - Start these browsers, currently available:
    • Chrome
    • ChromeCanary
    • Firefox
    • Opera
    • Safari (only Mac)
    • PhantomJS
    • IE (only Windows)
    browsers: ['PhantomJS'],
  • reporters - reporters: ['progress'] option is handy for silent output and can be passed in in the gulpfile.js as any option can.
    reporters: ['mocha'],
  • captureTimeout - Increase if your tests take more than 60 seconds.
    captureTimeout: 60000,
  • singleRun - We only want one run in our setup.
    singleRun: true,
  • plugins - List of plugins we are using for frameworks, preprecessors, and reporters.
    plugins: [
      require('karma-webpack'),
      require('karma-mocha'),
      require('karma-mocha-reporter'),
      require('karma-phantomjs-launcher'),
      require('karma-chai-plugins')
    ]
  });
};


 

Baseline

Included from bootstrap.config.less.

body {
  padding-top: 20px;
}

.doc-content {
  padding-top: 10px;
}

When nav is affixed, set it's position and size.

#nav.affix {
  position: fixed;
  .opacity(0.8);
  top: 3px;
  width: 80%;
  z-index: 10;
}

Code

Set up table to render two column view of documentation on left and code, syntax highlighted on the right.

table.file-table {
  width: 100%;
  border-radius: @border-radius-base;
  border: 0px;
  border-spacing: 0px;
  display: table;
  table-layout: fixed;

  td.file-docs {
    background-color: #fff;
    width: 30%;
    max-width: 30%;
    text-overflow: ellipsis;
    padding: 5px;
    vertical-align: top;
  }

  td.file-code {
    border-left: 1px groove darken(#f5f5f5, 20%);
    background-color: #f5f5f5;
    color: #333;
    width: 70%;
    max-width: 70%;
    text-overflow: ellipsis;
    padding-left: 5px;
    vertical-align: bottom;
  }

Remove extra separation between the code so it looks more like one continuous block.

  pre.prettyprint {
    padding: 0 !important;
    border: 0 !important;
    margin: 0 0 !important;
  }
}


Customize Variables

Remove borders around <pre> block so the code flows with our right column layout.

@pre-border-color: @pre-bg; // hide the border.

Include Less

Include our main less style sheet here so we can take advantage of boostrap variables and mixins.

@import "style.less";


'use strict';

bootstrap-webpack Configuration

module.exports = {

Scripts

Any scripts here set to false will never make it to the client, it's not packaged by webpack.

  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

Enable or disable certain less components and thus remove the css for them from the build.

  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": true,
    "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
  }
};