dsebastien / modernWebDevBuild

An opinionated Modern Web Development build
https://npmjs.com/package/modern-web-dev-build
MIT License
66 stars 18 forks source link
build typescript

Modern Web Dev Build

NPM version Downloads Build Status Coverage Status Dependency Status devDependency Status Gitter License

About

A modern build for Web development.

Get started and use ES2015, TypeScript, SASS, code quality & style checking, testing, minification, bundling and whatnot TODAY! :)

ModernWebDevBuild abstracts away all the build pipeline boilerplate. Use it if you're not willing to dive too deep in the boring details of how to setup a proper build chain that takes care of transpiling, minifying, optimizing images and whatnot for production.

This project is very opinionated and technology choices are embedded. Although, the build is pretty flexible about code/assets organization (to some extent). Over time, it'll be interesting to see how customizable we can make this thing.

The provided build tasks are based on Gulp. Instructions are available below to get you started.

This project is available as an npm package: https://www.npmjs.com/package/modern-web-dev-build

Demo

<img src="http://img.youtube.com/vi/Wc5iTInYOBw/0.jpg" alt="ModernWebDev Build and Generator Demo" width="240" height="180" border="10" />

Background

The idea for this project emerged as I was rediscovering the state of the art for Web development (early 2015) and from my frustration of not finding everything I needed in a ready-to-use form.

What surprised me at first was that tooling had become so much more complex than it was in the past. I would argue that it is way too complex nowadays and that isn't good for the accessibility of the Web platform. Unfortunately for now, there aren't many alternatives and the benefits of a good build chain are too important to keep aside (who wouldn't want to use all the good stuff ES2015 has brought us?).

Note that this project is heavily inspired from:

Features

Check out the change log

Status & roadmap

Check out the issues/labels & milestones to get an idea of what's next. For existing features, refer to the previous section.

Embedded choices

As state above, some important technology choices are clearly embedded with this project. Here's a rundown of those choices:

Upgrade

Check out the upgrade page

Installation

General prereqs

Before you install the build (whether manually or through the project generator), you need to install some dependencies globally:

New projects

The easiest approach to integrate this build is to use our Yeoman Generator available over at https://github.com/dsebastien/modernWebDevGenerator and on npm: https://www.npmjs.com/package/generator-modern-web-dev. The generator will set up (almost) everything for you.

Existing projects

First configure the required dependencies in your package.json file:

You should get warnings about missing peer dependencies. Those are dependencies that are required by the build but that you should add to your own project. Install these one by one.

For now the required peer dependencies are as follows:

Next, check the minimal require file contents below!

Required folder structure and files

The build tries to provide a flexible structure, but given the technical choices that are embedded, some rules must be respected and the build expects certain folders and files to be present. In the future we'll see if we can make this more configurable.

Mandatory folder structure & files

Here's an overview of the structure imposed by ModernWebDevBuild. Note that if you've generated your project using the Yeoman generator, README files will be there to guide you.

Please make sure to check the file organization section for more background about the organization and usage guidelines.

Minimal (build-related) required file contents

Although we want to limit this list as much as possible, for everything to build successfully, some files need specific contents:

.babelrc

{
    "presets": ["es2015"],
    "plugins": ["transform-es2015-modules-commonjs"],
    "comments": false
}

With the configuration above, Babel will transpile ES2015 code to ES5 commonjs. For that configuration to work, the following devDependencies must also be added to your project:

"babel-plugin-transform-es2015-modules-commonjs": "6.3.x",
"babel-preset-es2015": "6.3.x",

gulpfile.babel.js

In order to use ModernWebDevBuild, your gulpfile must at least contain the following. The code below uses ES2015 (via gulpfile.babel.js), but if you're old school you can also simply use a gulpfile.js with ES5. Note that the build tasks provided by ModernWebDevBuild are transpiled to ES5 before being published

"use strict";

import gulp from "gulp";

import modernWebDevBuild from "modern-web-dev-build";
let options = undefined; // no options are supported yet

//options.minifyHTML = false;
//...

modernWebDevBuild.registerTasks(gulp, options);

With the above, all the gulp tasks provided by ModernWebDevBuild will be available to you.

.jscsrc

Valid configuration

.jshintrc

At least the following:

node_modules/**/*
jspm_packages/**/*
jspm.conf.js
dist/**/*
.tmp/**/*

jspm.conf.js

The SystemJS/JSPM configuration file plays a very important role;

If you choose to use the default JSPM support, then you can add dependencies to your project using jspm install; check the official JSPM documentation to know more about how to install packages.

With the help of this configuration file, SystemJS will be able to load your own application modules and well as third party dependencies. In your code, you'll be able to use ES2015 style (e.g., import {x} from "y"). In order for this to work, you'll also need to load SystemJS and the SystemJS/JSPM configuration file in your index.html (more on this afterwards).

If you have disabled the use of JSPM by the build then you can rename that file if you wish and inform the build (see the options), BUT make sure that any reference in the various configuration files is updated (e.g., karma configuration, jshint configuration, etc). Only rename it if it is really really useful to you :)

Here's an example:

System.config({
  defaultJSExtensions: true,
  transpiler: false,
  paths: {
    "github:*": "jspm_packages/github/*",
    "npm:*": "jspm_packages/npm/*",
    "core/*": "./.tmp/core/*",
    "pages/*": "./.tmp/pages/*"
  }
});

In the above:

package.json

In addition to the dependencies listed previously, you also need to have the following in your package.json file, assuming that you want to use JSPM:

  "jspm": {
    "directories": {},
    "configFile": "jspm.conf.js",
    "dependencies": {
    },
    "devDependencies: {
    }
  }

This is where you let JSPM know where to save the information about dependencies you install. This is also where you can easily add new dependencies; for example: "angular2": "npm:angular2@^2.0.0-beta.1",. Once a dependency is added there, you can invoke jspm install to get the files and transitive dependencies installed and get an updated jspm.conf.js file.

tsconfig.json

Given that TypeScript is one of the (currently) embedded choices of this project, the TypeScript configuration file is mandatory.

The tsconfig.json file contains:

Here's is the minimal required contents for ModernWebDevBuild. Note that the outDir value is important as it tells the compiler where to write the generated code! Make sure that you also DO have the rootDir property defined and pointing to "./app", otherwise the build will fail (more precisely, npm run serve will fail).

The build depends on the presence of those settings.

{
    "version": "1.7.3",
    "compilerOptions": {
        "target": "es5",
        "module": "commonjs",
        "declaration": false,
        "noImplicitAny": true,
        "suppressImplicitAnyIndexErrors": true,
        "removeComments": false,
        "emitDecoratorMetadata": true,
        "experimentalDecorators": true,
        "noEmitOnError": false,
        "preserveConstEnums": true,
        "inlineSources": false,
        "sourceMap": false,
        "outDir": "./.tmp",
        "rootDir": "./app",
        "moduleResolution": "node",
        "listFiles": false
    },
    "exclude": [
        "node_modules",
        "jspm_packages",
        "typings/browser",
        "typings/browser.d.ts"
    ]
}

Here's a more complete example including code style rules:

{
    "version": "1.7.3",
    "compilerOptions": {
        "target": "es5",
        "module": "commonjs",
        "declaration": false,
        "noImplicitAny": true,
        "suppressImplicitAnyIndexErrors": true,
        "removeComments": false,
        "emitDecoratorMetadata": true,
        "experimentalDecorators": true,
        "noEmitOnError": false,
        "preserveConstEnums": true,
        "inlineSources": false,
        "sourceMap": false,
        "outDir": "./.tmp",
        "rootDir": "./app",
        "moduleResolution": "node",
        "listFiles": false
    },
    "formatCodeOptions": {
        "indentSize": 2,
        "tabSize": 4,
        "newLineCharacter": "\r\n",
        "convertTabsToSpaces": false,
        "insertSpaceAfterCommaDelimiter": true,
        "insertSpaceAfterSemicolonInForStatements": true,
        "insertSpaceBeforeAndAfterBinaryOperators": true,
        "insertSpaceAfterKeywordsInControlFlowStatements": true,
        "insertSpaceAfterFunctionKeywordForAnonymousFunctions": false,
        "insertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis": false,
        "placeOpenBraceOnNewLineForFunctions": false,
        "placeOpenBraceOnNewLineForControlBlocks": false
    },
    "exclude": [
        "node_modules",
        "jspm_packages",
        "typings/browser",
        "typings/browser.d.ts"
    ]
}

Note the exclusion that we have made, all of which are relevant and there to avoid known issues (e.g., https://github.com/typings/discussions/issues/6 if you are using typings).

tslint.json

tslint.json is the configuration file for TSLint.

Although it is not strictly mandatory (the build will work without this file), we heavily recommend you to use it as it is very useful to ensure a minimal code quality level and can help you avoid common mistakes and unnecessary complicated code:

Here's an example:

{
  "rules": {
    "class-name": true,
    "curly": true,
    "eofline": true,
    "forin": true,
    "indent": [false, "tabs"],
    "interface-name": false,
    "label-position": true,
    "label-undefined": true,
    "max-line-length": false,
    "no-any": false,
    "no-arg": true,
    "no-bitwise": true,
    "no-console": [false,
      "debug",
      "info",
      "time",
      "timeEnd",
      "trace"
    ],
    "no-construct": true,
    "no-debugger": true,
    "no-duplicate-key": true,
    "no-duplicate-variable": true,
    "no-empty": true,
    "no-eval": true,
    "no-imports": true,
    "no-string-literal": false,
    "no-trailing-comma": true,
    "no-unused-variable": false,
    "no-unreachable": true,
    "no-use-before-declare": null,
    "one-line": [true,
      "check-open-brace",
      "check-catch",
      "check-else",
      "check-whitespace"
    ],
    "quotemark": [true, "double"],
    "radix": true,
    "semicolon": true,
    "triple-equals": [true, "allow-null-check"],
    "variable-name": false,
    "no-trailing-whitespace": true,
    "whitespace": [false,
      "check-branch",
      "check-decl",
      "check-operator",
      "check-separator",
      "check-type",
      "check-typecast"
    ]
  }
}

karma.conf.js

Karma loads his configuration from karma.conf.js. That file contains everything that Karma needs to know to execute your unit tests.

Here's an example configuration file that uses Jasmine. Note that the main Karma dependencies including PhantomJS are included in the build. You only need to add a dependency to jasmine, karma-jasmine and karma-jspm for the following to work.

If you choose not to use JSPM, then you can use karma-systemjs instead: https://www.npmjs.com/package/karma-systemjs

Example:

// Karma configuration
// reference: http://karma-runner.github.io/0.13/config/configuration-file.html

module.exports = function (config) {
    config.set({

        // base path that will be used to resolve all patterns (eg. files, exclude)
        //basePath: ".tmp/",

        plugins: [
            "karma-jspm",
            "karma-jasmine",
            "karma-phantomjs-launcher",
            "karma-chrome-launcher",
            "karma-firefox-launcher",
            "karma-ie-launcher",
            "karma-junit-reporter",
            "karma-spec-reporter"
        ],

        // frameworks to use
        // available frameworks: https://npmjs.org/browse/keyword/karma-adapter
        frameworks: [
            "jspm",
            "jasmine"
        ],

        // list of files / patterns to load in the browser (loaded before SystemJS)
        files: [],

        // list of files to exclude
        exclude: [],

        // list of paths mappings
        // can be used to map paths served by the Karma web server to /base/ content
        // knowing that /base corresponds to the project root folder (i.e., where this config file is located)
        proxies: {
            "/.tmp": "/base/.tmp" // without this, karma-jspm can't load the files
        },

        // preprocess matching files before serving them to the browser
        // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor
        preprocessors: {},

        // test results reporter to use
        // possible values: 'dots', 'progress', 'spec', 'junit'
        // available reporters: https://npmjs.org/browse/keyword/karma-reporter
        // https://www.npmjs.com/package/karma-junit-reporter
        // https://www.npmjs.com/package/karma-spec-reporter
        reporters: ["spec"],

        // web server port
        port: 9876,

        // enable / disable colors in the output (reporters and logs)
        colors: true,

        // level of logging
        // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
        logLevel: config.LOG_INFO,

        // enable / disable watching file and executing tests whenever any file changes
        autoWatch: true,

        // start these browsers
        // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher
        browsers: [
            "PhantomJS"
            //"Chrome",
            //"Firefox",
            //"PhantomJS",
            //"IE"
        ],

        // Continuous Integration mode
        // if true, Karma captures browsers, runs the tests and exits
        singleRun: false,

        junitReporter: {
            outputFile: "target/reports/tests-unit/unit.xml",
            suite: "unit"
        },

        // doc: https://www.npmjs.com/package/karma-jspm
        // reference config: https://github.com/gunnarlium/babel-jspm-karma-jasmine-istanbul
        jspm: {
            // Path to your SystemJS/JSPM configuration file
            config: "jspm.conf.js",

            // Where to find jspm packages
            //packages: "jspm_packages",

            // One use case for this is to only put test specs in loadFiles, and jspm will only load the src files when and if the test files require them.
            loadFiles: [
                // load all tests
                ".tmp/*.spec.js", // in case there are tests in the root folder
                ".tmp/**/*.spec.js"
            ],

            // Make additional files/a file pattern available for jspm to load, but not load it right away.
            serveFiles: [
                ".tmp/**/!(*.spec).js" // make sure that all files are available
            ],

            // SystemJS configuration specifically for tests, added after your config file.
            // Good for adding test libraries and mock modules
            paths: {}
        }
    });
};

Dev dependencies to add for the above Karma configuration:

    "jasmine": "...",
    "karma-jasmine": "...",
    "karma-jspm": "..."

Minimal (application-specific) required file contents

Although we want to limit this list as much as possible, for everything to build successfully, some files need specific contents:

core/app.ts

This should be the top element of your application. This should be loaded by core/boot.ts (see below).

"use strict";

export class App {
    ...
    constructor(){
        console.log("Hello world!");
    }
}

core/boot.ts

The boot.ts file is the entrypoint of your application. Currently, it is mandatory for this file to exist (with that specific name), although that could change or be customizable later.

The contents are actually not important but here's a starting point:

"use strict";

import {App} from "core/app";
// bootstrap your app

styles/main.scss

The main.scss file is where you should load all the stylesheets scattered around in your application.

Here's an example of a main.scss:

//
// Main stylesheet.
// Should import all the other stylesheets
//

// Variables, functions, mixins and utils
@import "base/variables";
@import "base/functions";
@import "base/mixins";
@import "base/utils";

// Base/generic style rules
@import "base/reset";
@import "base/responsive";
@import "base/fonts";
@import "base/typography";
@import "base/base";

// Layout
@import "layout/layout";
@import "layout/theme";
@import "layout/print";

// Components
@import "../components/posts/posts";

// Pages
@import "../pages/home/home";

In the example above, you can see that in the main.scss file, we import many other stylesheets (sass partials).

Here's another example, this time for vendor.scss:

//
// Includes/imports all third-party stylesheets used throughout the application.
// Should be loaded before the application stylesheets
//

// Nicolas Gallagher's Normalize.css
@import '../../jspm_packages/github/necolas/normalize.css@3.0.3/normalize.css'; // the path refers to the file at BUILD time

As you can see above, a third-party stylesheet is imported.

index.html

The index.html file is the entrypoint of your application. It is not mandatory per se for ModernWebDevBuild, but when you run npm run serve, it'll be opened. Also, the html build task will try and replace/inject content in it.

Here's the minimal required contents for index.html (required for production builds with minification and bundling):

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8">
    ...
    <!-- Stylesheets -->
    <!-- build:css-vendor -->
    <link rel="stylesheet" href="https://github.com/dsebastien/modernWebDevBuild/blob/master/styles/vendor.css">
    <!-- endbuild -->
    <!-- build:css-bundle -->
    <link rel="stylesheet" href="https://github.com/dsebastien/modernWebDevBuild/blob/master/styles/main.css">
    <!-- endbuild -->
</head>
<body>

    <!-- build:js-app -->
    <!-- for production, this is all replaced by a minified bundle -->
    <script src="https://github.com/dsebastien/modernWebDevBuild/raw/master/jspm_packages/system.src.js"></script>
    <script src="https://github.com/dsebastien/modernWebDevBuild/raw/master/jspm.conf.js"></script>
    <script>
        System.import('core/core.bootstrap').catch(console.error.bind(console));
    </script>
    <!-- endbuild -->
</body>
</html>

In the above, the most important parts are:

Also, note that during development, SystemJS is loaded (system.src.js), the JSPM configuration is loaded (jspm.conf.js) and SystemJS is used to load the entrypoint of the application (core/core.bootstrap).

Commands

Once you have added ModernWebDevBuild to your project, you can list all the available commands using gulp help. The command will give you a description of each task. The most important to start discovering are:

You can run the gulp -T command get an visual idea of the links between the different tasks.

Scripts

To make your life easier, you can add the following scripts to your package.json file. Note that if you have used the generator to create your project, you normally have these already:

Options

The build can be customized by passing options. Defining options is done as in the following example gulpfile.babel.js:

"use strict";

import gulp from "gulp";

import modernWebDevBuild from "modern-web-dev-build";

let options = {};

options.distEntryPoint = "core/core.bootstrap";

Available options:

FAQ

How can I inline some script in the production version of some HTML page?

Example:

<!-- inline ../node_modules/angular2/bundles/angular2-polyfills.js -->
<script inline src="https://github.com/dsebastien/modernWebDevBuild/raw/master/node_modules/angular2/bundles/angular2-polyfills.js"></script>

Note that the path specified in the <!-- inline ... comment is relative to the root of your project and NOT to the html file

Check out gulp-inline-source's documentation for more details.

Build dependencies

Contributing

Building from source

If you want to build from source, you need to:

To clean, you can run npm run clean

Project configuration files

The project includes multiple configuration files. Here's some information about these:

Releasing a version

Authors

Sebastien Dubois

License

This project and all associated source code is licensed under the terms of the MIT License.