понедельник, 28 ноября 2016 г.

Custom React-Redux Provider

Файл custom-provider.js, позволяющий помимо доступа к store вызывать функцию setAppState(), обновляющую state всего приложения и перерисовывающая его.

import React, {Component, PropTypes, Children} from 'react';

let didWarnAboutReceivingStore = false;
function warnAboutReceivingStore () {
    if (didWarnAboutReceivingStore) {return;}
    didWarnAboutReceivingStore = true;
    warning(
            '<Provider> does not support changing `store` on the fly. '
        + 'It is most likely that you see this error because you updated to '
        + 'Redux 2.x and React Redux 2.x which no longer hot reload reducers '
        + 'automatically. See https://github.com/reactjs/react-redux/releases/'
        + 'tag/v2.0.0 for the migration instructions.'
    );
}

// Prints a warning in the console if it exists.
// @param {String} message The warning message.
// @returns {void}
function warning (message) {
    /* eslint-disable no-console */
    if (typeof console !== 'undefined' && typeof console.error === 'function') {
        console.error(message);
    }
    /* eslint-enable no-console */
    try {
        // This error was thrown as a convenience so that if you enable
        // "break on all exceptions" in your console,
        // it would pause the execution at this line.
        throw new Error(message);
    /* eslint-disable no-empty */
    } catch (e) {}
    /* eslint-enable no-empty */
}

export default class Provider extends Component {
    getChildContext () {
        return {
              store: this.store
            , setAppState: this.setState.bind(this)
        };
    }
    constructor (props, context) {
        super(props, context)
        this.store = props.store;
    }
    render () {
        return Children.only(this.props.children);
    }
}

if (process.env.NODE_ENV !== 'production') {
    Provider.prototype.componentWillReceiveProps = function (nextProps) {
        const store = this.store
                , nextStore = nextProps.store;
        if (store !== nextStore) {warnAboutReceivingStore();}
    }
}

const storeShape = PropTypes.shape({
      subscribe: PropTypes.func.isRequired
    , dispatch: PropTypes.func.isRequired
    , getState: PropTypes.func.isRequired
});

Provider.propTypes = {
      store: storeShape.isRequired
    , children: PropTypes.element.isRequired
};

Provider.childContextTypes = {
      store: storeShape.isRequired
    , setAppState: React.PropTypes.func
};

Файл index.js с примером кода приложения.

import React, {Component} from 'react';
import ReactDOM from 'react-dom';
import {createStore, combineReducers} from 'redux';
import Provider from './custom-provider.js';

class RandomNumberGenerator extends Component {
    constructor (props, context) {
        super(props, context);
        this.store = this.context.store;
        this.setAppState = this.context.setAppState;
    }
    render () {
        return (
            <div>
                <p>Текущее случайное число = {this.store.getState().randomNumber}</p>
                <div style={{display: 'inline-block', border: '1px solid black'}} onClick={this.handleClick.bind(this)}>Сгенерировать новое случайное число</div>
            </div>
        );
    }
    handleClick () {
        this.store.dispatch({type: 'GENERATE_RANDOM_NUMBER'});
        this.setAppState({});
    }
    shouldComponentUpdate () {
        console.log('RandomNumberGenerator updated');
        return true;
    }
}

RandomNumberGenerator.contextTypes = {
      store: React.PropTypes.object
    , setAppState: React.PropTypes.func
};

class Hello extends Component {
    constructor (props, context) {
        super(props, context);
        this.store = this.context.store;
    }
    render () {
        return (
            <div>
                <h1>Привет! Ваше случайное число = {this.store.getState().randomNumber}</h1>
                <RandomNumberGenerator />
            </div>
        );
    }
    shouldComponentUpdate () {
        console.log('Hello updated');
        return true;
    }
}

Hello.contextTypes = {
    store: React.PropTypes.object
};

class App extends Component {
    render () {
        return <Hello />;
    }
}

var initialState = {randomNumber: 0.2841463617514819};

function randomNumberReducer (state = initialState.randomNumber, action) {
    switch (action.type) {
        case 'GENERATE_RANDOM_NUMBER': return Math.random();
        default: return state;
    }
}

var rootReducer = combineReducers({
    randomNumber: randomNumberReducer
});

const store = createStore(rootReducer, initialState);

ReactDOM.render(<Provider store={store}><App /></Provider>, document.getElementById('app'));

Файл index.html

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title>React Redux Webpack</title>
</head>
<body>
    <div id="app"></div>
    <script type="text/javascript" src="../dist/bundle.js"></script>
</body>
</html>

вторник, 25 октября 2016 г.

Detect a click outside of a React Component

Данный код необходим, если вы хотите обработать клик вне компонента React.
Например у вас есть компонент представляющий из себя модальное окно и вы хотите скрыть его при клике вне его границ.

class WindowComponent extends React.Component () {

    constructor (props) {
        super(props);
        this.state = {
            visible: true
        };
    }
 
    componentDidMount () {
        document.addEventListener('click', this.handleClickOutside.bind(this), true);
    }

    componentWillUnmount () {
        document.removeEventListener('click', this.handleClickOutside.bind(this), true);
    }

    handleClickOutside (event) {
        const domNode = ReactDOM.findDOMNode(this);
        if ((!domNode || !domNode.contains(event.target))) {
            this.setState({
                visible: false
            });
        }
    }
 
    render () {
        return (
            <div className={this.state.visible ? '' : 'hidden'}>Модальное окно</div>
        );
    }

}

понедельник, 24 октября 2016 г.

Как заставить работать event.stopPropagation в React, если у document есть свои события

Если требуется, чтобы клик по элементу, созданному с помощью React не доходил до уровня document, на который могут быть навешаны свои события, то для этого надо при создании данного элемента отказаться от добавления событий стандартным способом вроде
<Element onClick={this.handleClick.bind(this)} />, а вместо этого добавить слушатель события внутри функции componentDidMount () {}.

Для примера.

Document имеет свой слушатель события click:

document.addEventListener('click', function () {alert('Событие клика дошло до уровня document');});

Элемент имеет свой слушатель события click:

import React, {Component} from 'react';
import ReactDOM from 'react-dom';

class Element extends Component {
  handleClick (event) {
      event.stopPropagation();
      alert('Вы кликнули по элементу.');
  }
  render () {
      return <div onClick={this.handleClick.bind(this)}>Кликни по мне</div>;
  }
}

ReactDOM.render(<Element />, document.getElementById('root'));

Если в таком виде кликнуть по надписи "Кликни по мне", то сработает событие клика по элементу и событие клика по document, что приведет к выводу на экран двух сообщений alert.

Чтобы заблокировать распространения события необходимо добавление клика на элемент перенести в функцию componentDidMount:

class Element extends Component {
  handleClick (event) {
      event.stopPropagation();
      alert('Вы кликнули по элементу.');
  }
  componentDidMount () {
      ReactDOM.findDOMNode(this.refs.btn).addEventListener('click', this.handleClick);
  }
  render () {
      return <div ref="btn">Кликни по мне</div>;
  }
}

Теперь, если кликнуть по надписи "Кликни по мне", то сработает событие клика только по самому элементу, и событие клика по document, не сработате, что приведет к выводу на экран только одного сообщения alert.

Не забудьте удалить событие клика по элементу, если будет удалять сам компонент:

  componentWillUnmount () {
      ReactDOM.findDOMNode(this.refs.btn).removeEventListener('click', this.handleClick);
  }

Полный вариант кода примера:

import React, {Component} from 'react';
import ReactDOM from 'react-dom';

document.addEventListener('click', function () {alert('Событие клика дошло до уровня document');});

class Element extends Component {
  handleClick (event) {
      event.stopPropagation();
      alert('Вы кликнули по элементу.');
  }
  componentDidMount () {
      ReactDOM.findDOMNode(this.refs.btn).addEventListener('click', this.handleClick);
  }
  componentWillUnmount () {
      ReactDOM.findDOMNode(this.refs.btn).removeEventListener('click', this.handleClick);
  }
  render () {
      return <div ref="btn">Кликни по мне</div>;
  }
}

ReactDOM.render(<Element />, document.getElementById('root'));

четверг, 20 октября 2016 г.

Мой Webpack, TypeScript, Babel, React, Redux, ESLint, JSCS, Sass, Flow Boilerplate

Как установить файлы через Typings:
typings install dt~jquery --global --save

Структура файлов и папок:
- папка flow
- папка node
- папка source
- файл .flowconfig
- файл flow.exe
- файл index.html
- файл version.txt

Структура файлов и папок внутри flow:
- файл CSSModule.js.flow
- файл WebpackAsset.js.flow

Структура файлов и папок внутри node:
- папка node_modules
- папка typings
- файл node.exe
- файл npm.cmd
- файл typings.cmd
- файл webpack.cmd
- файл package.json
- файл .eslintrc
- файл .jscsrc
- файл .typingsrc
- файл typings.json
- файл tslint.json
- файл eslint.json
- файл stylelint.json
- файл tsconfig.json
- файл webpack.config.js
- файл build.js
- файл server.js

Структура файлов и папок внутри source:
- папка app
- папка config
- папка lib
- файл index.js

Файл /flow/CSSModule.js.flow

// @flow

declare export default { [key: string]: string }

Файл /flow/WebpackAsset.js.flow

// @flow

declare export default string

Файл /.flowconfig

[ignore]
.*/node/.*

[include]
source/index.js

[libs]

[options]
module.name_mapper.extension='css' -> '<PROJECT_ROOT>/flow/CSSModule.js.flow'
module.name_mapper.extension='scss' -> '<PROJECT_ROOT>/flow/CSSModule.js.flow'
module.name_mapper.extension='png' -> '<PROJECT_ROOT>/flow/WebpackAsset.js.flow'
module.name_mapper.extension='jpg' -> '<PROJECT_ROOT>/flow/WebpackAsset.js.flow'
module.name_mapper.extension='gif' -> '<PROJECT_ROOT>/flow/WebpackAsset.js.flow'
esproposal.class_static_fields=enable
esproposal.class_instance_fields=enable
suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe
suppress_comment=\\(.\\|\n\\)*\\$FlowIssue

Файл /version.txt

1.4.0

Файл /index.html

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <link href="build/bundle.css?v=1.0.0-1472804427607" rel="stylesheet" type="text/css" />
    <title>Мой сайт</title>
</head>
<body>
    <div id="root"></div>
    <p id="version">Мой сайт (v1.0.0)</p>
    <script src="build/bundle.js?v=1.0.0-1472804427607" type="text/javascript"></script>
</body>
</html>

Файл /node/package.json

{
      "version": "1.0.0"
    , "name": "webpack-all-in-one"
    , "description": "Webpack all in one"
    , "keywords": ["webpack", "all"]
    , "author": "John Doe"
    , "engines": {
          "node": "6.9.0"
        , "npm": "3.10.9"
      }
    , "scripts": {
          "build": "node build"
      }
    , "dependencies": {
          "bluebird": "3.4.6"
        , "jquery": "3.1.1"
        , "react": "15.3.2"
        , "react-dom": "15.3.2"
        , "react-router": "2.8.1"
        , "react-router-redux": "4.0.6"
        , "react-redux": "4.4.5"
        , "redux": "3.6.0"
        , "redux-router": "2.1.2"
        , "redux-thunk": "2.1.0"
        , "redux-logger": "2.7.0"
      }
    , "devDependencies": {
          "webpack": "1.13.2"
        , "webpack-dev-middleware": "1.8.4"
        , "webpack-hot-middleware": "2.13.0"
        , "clean-webpack-plugin": "0.1.13"
        , "extract-text-webpack-plugin": "1.0.1"
        , "express": "4.14.0"
        , "typescript": "2.0.3"
        , "ts-loader": "0.9.5"
        , "tslint": "3.15.1"
        , "tslint-react": "1.1.0"
        , "tslint-loader": "2.1.5"
        , "babel-core": "6.17.0"
        , "babel-polyfill": "6.16.0"
        , "babel-preset-es2015": "6.16.0"
        , "babel-preset-stage-0": "6.16.0"
        , "babel-preset-react": "6.16.0"
        , "babel-preset-react-hmre": "1.1.1"
        , "babel-plugin-syntax-flow": "6.13.0"
        , "babel-plugin-transform-flow-strip-types": "6.14.0"
        , "babel-plugin-tcomb": "0.3.19"
        , "babel-loader": "6.2.5"
        , "babel-eslint": "7.0.0"
        , "eslint": "3.8.1"
        , "eslint-plugin-react": "6.4.1"
        , "eslint-loader": "1.6.0"
        , "jscs": "3.0.7"
        , "jscs-loader": "0.3.0"
        , "node-sass": "3.10.1"
        , "sass-loader": "4.0.2"
        , "css-loader": "0.25.0"
        , "style-loader": "0.13.1"
        , "stylelint": "7.5.0"
        , "stylelint-loader": "6.2.0"
        , "file-loader": "0.9.0"
        , "url-loader": "0.5.7"
        , "html-loader": "0.4.4"
        , "json-loader": "0.5.4"
        , "source-map-loader": "0.1.5"
        , "tcomb": "3.2.15"
        , "tcomb-react": "0.9.3"
        , "@types/core-js": "0.9.34"
        , "@types/bluebird": "3.0.35"
        , "@types/glob": "5.0.30"
        , "@types/filesaver": "0.0.30"
        , "@types/jquery": "2.0.33"
        , "@types/history": "2.0.39"
        , "@types/react": "0.14.41"
        , "@types/react-dom": "0.14.18"
        , "@types/react-router": "2.0.38"
        , "@types/react-router-redux": "4.0.34"
        , "@types/react-redux": "4.4.32"
        , "@types/redux": "3.6.31"
        , "@types/redux-router": "1.0.33"
        , "@types/redux-thunk": "2.1.31"
        , "@types/redux-logger": "2.6.32"
        , "@types/tcomb": "1.0.27"
      }
    , "repository": {
          "type": "git"
        , "url": "http://stash.ca.sbrf.ru/scm/sirius/sirius-arm-new.git"
      }
}

Файл /node/.eslintrc

{
      "extends": "eslint:recommended"
    , "parser": "babel-eslint"
    , "env": {
          "browser": true
        , "node": true
      }
    , "plugins": [
          "react"
      ]
    , "rules": {
          "no-debugger": 0
        , "no-console": 0
        , "new-cap": 0
        , "strict": 0
        , "no-underscore-dangle": 0
        , "no-use-before-define": 0
        , "eol-last": 0
        , "quotes": [2, "single"]
        , "jsx-quotes": [1, "prefer-single"]
        , "react/jsx-no-undef": 1
        , "react/jsx-uses-react": 1
        , "react/jsx-uses-vars": 1
    }
}


Файл /node/.jscsrc

пуст


Файл /node/.typingsrc

{
    "rejectUnauthorized": false
}

Файл /node/typings.json

{
    "globalDependencies": {
        "jquery": "registry:dt/jquery#1.10.0+20160704162008",
        "react": "registry:dt/react#0.14.0+20160805125551",
        "react-dom": "registry:dt/react-dom#0.14.0+20160412154040"
    }
}


Файл /node/tslint.json

{
  "extends": ["tslint:latest", "tslint-react"],
  "rules": {
    "indent": [ true, "spaces" ],
    "quotemark": [ true, "single", "jsx-double" ],
    "no-var-requires": false,
    "ordered-imports": false,
    "no-unused-variable": [true, "react"],
    "member-ordering": false,
    "object-literal-sort-keys": false,
    "no-shadowed-variable": false,
    "no-console": false
  }
}

Файл /node/eslint.json

/*
{
      // Здесь нет правил для ECMAScript 6
      "parserOptions": { // Настройки парсера кода
          "ecmaVersion": 3 // версия проверяемого кода ECMAScript
        , "sourceType": "script" // источник кода - отдельно загружаемые файлы
        , "ecmaFeatures": { // особенности кода ECMAScript
            "impliedStrict": true // проверять, что код написан с соблюдением правил строгого режима "use strict"; ?
          }
      }
    , "env": { // Настройки окружения
          "browser": true // разрешить использование глобальных браузерных переменных?
        , "commonjs": true // разрешить использование глобальных переменных CommonJS?
        , "worker": true // разрешить использование глобальных переменных web workers?
        , "amd": true // разрешить использование глобальных переменных require() и define() ?
        , "jquery": true // разрешить использование глобальных переменных jQuery?
      }
    , "globals": { // перечень глобальных переменных, которые явно не определены, то могут использоваться в каждом файле
          "require": true
        , "define": true
        , "ActiveXObject": true
      }
    , "rules": { // Правила
        // Possible Errors - случаи возможных ошибок в коде
          "comma-dangle": [2, "never"] // наличие запятой в конце списка элементов объекта или массива
        , "no-cond-assign":  [2, "always"] // запретить присвоение значений внутри условия: if (x = 1)
        , "no-console": 2 // запретить использование console в коде
        , "no-constant-condition": 2 // запретить писать только true или false внутри устловия: if (true)
        , "no-control-regex": 2 // запретить скрытые символы внутри регулярных выражений: /\\x1f/
        , "no-debugger": 2 // запретить использование debugger; в коде
        , "no-dupe-args": 2 // запретить дублирование аргументов в функции
        , "no-dupe-keys": 2 // запретить дублирование ключей в объектах
        , "no-duplicate-case": 2 // запретить дублирование case в switch
        , "no-empty": 2 // запретить пустые блоки в коде
        , "no-empty-character-class": 2 // запретить пустые квадратные скобки в регулярных выражениях: /^abc[]/
        , "no-ex-assign": 2 // запретить присвоение занчений переменной error в блоке catch: catch (error) {error = 10;}
        , "no-extra-boolean-cast": 2 // запретить преобразование к типу Boolen посредством символов !!: if (!!foo) {}
        , "no-extra-parens": 0 // запретить писать лишние круглые скобки
        , "no-extra-semi": 2 // запретить писать лишние точки с запятыми
        , "no-func-assign": 2 // запретить переопределение уже созаднных функций
        , "no-inner-declarations": 2 // запретить опеределение функций и определений внутри условий
        , "no-invalid-regexp": 2 // проверить правильность написания регулярного выражения
        , "no-irregular-whitespace": 2 // запретить писать необычные виды пробелов
        , "no-negated-in-lhs": 2 // запретить писать условия с отрицанием вида if (!key in object)
        , "no-obj-calls": 2 // запретить вызов глобальных объектов: Math(); или JSON();
        , "no-regex-spaces": 2 // запретить писать простые пробелы внутри регулярных выражений: /foo   bar/
        , "no-sparse-arrays": 2 // запретить пропускать элементы в массивах: ["red", , "blue"]
        , "no-unexpected-multiline": 2 // запретить неожиданный переход на следующую строку
        , "no-unreachable": 2 // запретить написание кода, который никогда не будет выполнен
        , "use-isnan": 2 // запретить написание прямого сравнения с NaN: if (foo == NaN)
        , "valid-jsdoc": 2 // проверить правильность написания комментариев в формате JSDoc
        , "valid-typeof": 2 // проверить правильность написания сравнения с типом данных вида: typeof foo === "strnig"
        // Best Practices - наилучшие практики проверки кода
        , "accessor-pairs": 2 // проверить наличие get для каждого свойства объекта, для которого прописан set
        , "array-callback-return": 0 // проверить наличие return в функции, использующейся в качестве обработчика массива
        , "block-scoped-var": 2 // запретить использование переменной вне блока, в котором она была опеределена
        , "complexity": [2, 15] // цикломатическая сложность программы
        , "consistent-return": 0 // проверить, что все return возвращают значения или не возвращают ничего
        , "curly": 2 // проверить наличие фигурных скобок
        , "default-case": 2 // проверить наличие default в switch
        , "dot-location": [2, "property"] // проверить, что точка привязана к свойству, а не к объекту
        , "dot-notation": 2 // проверить, что вместо [] используется точка для получения значения свойств объекта
        , "eqeqeq": 2 // проверить, что используется только строго сравнение === и !==
        , "guard-for-in": 2 // проверить, что при перечислении свойств объекта используется проверка: if (foo.hasOwnProperty(key))
        , "no-alert": 2 // проверить, что в коде нет функции alert();, confirm();, prompt();
        , "no-caller": 2 // запретить использование в коде arguments.caller и arguments.callee
        , "no-case-declarations": 2 // запретить объявление переменных let, const, function и class внутри case и default в switch
        , "no-div-regex": 2 // проверить наличия escape для регулярного выражения вида /=foo/, которое может быть интерпретировано, как операция деления /=
        , "no-else-return": 0 // запретить написание внутри функции необязательно блока с кодом else {return x;}
        , "no-empty-function": 0 // запретить наличие в коде функций с пустым телом
        , "no-empty-pattern": 2 // запретить наличие в коде пустых паттернов деструктурирования объектов и массивов
        , "no-eq-null": 2 // запретить нестрогое сравнение с null: if (foo == null)
        , "no-eval": 2 // запретить использование функции eval
        , "no-extend-native": [2, {"exceptions": []}] // запретить расширение встроенных объектов
        , "no-extra-bind": 2 // запретить бессмысленное примение функции bind
        , "no-extra-label": 2 // запретить бессмысленное примение label
        , "no-fallthrough": 2 // запретить конструкции case без break; или return;
        , "no-floating-decimal": 2 // запретить писпть числа с плавающей точкой без нулей: .2 или 2.
        , "no-implicit-coercion": 0 // запретить преобразование типов с помощью конструкций вида: "" + foo, !!foo, +foo, ~foo
        , "no-implicit-globals": 2 // запретить объявление глобальных переменных без привязки к window
        , "no-implied-eval": 2 // запретить выполнять код через setTimeout(), setInterval() и execScript() вида: setTimeout("alert('Hi!');", 100); или execScript("alert('Hi!')");
        , "no-invalid-this": 2 // запретить неправильное использование this
        , "no-iterator": 2 // запретить изменять свойство __iterator__
        , "no-labels": 2 // запретить использование меток label
        , "no-lone-blocks": 2 // запретить использование одиночных блоков { }
        , "no-loop-func": 0 // запретить создавать функции внутри циклов
        , "no-magic-numbers": 0 // запретить использование магических чисел
        , "no-multi-spaces": 2 // запретить использование множественных пробелов в коде
        , "no-multi-str": 2 // запретить разбиение строк с помощью символа \
        , "no-native-reassign": 2 // запретить переопределение встроенных объектов: String = "hello world";
        , "no-new": 2 // запретить вызов функции конструктора объектов с new без присвоения его значения переменной: new Thing();
        , "no-new-func": 2 // запретить использование Function: var x = new Function("a", "b", "return a + b");
        , "no-new-wrappers": 2 // запретить использовать String, Number и Boolean с оператором new
        , "no-octal": 2 // запретить использовать литералы восьмиричных чисел: var num = 071;
        , "no-octal-escape": 2 // запретить использовать восьмиричные escape-последовательности: var foo = "Copyright \251";
        , "no-param-reassign": 0 // запретить измнение значений параметров функций: function foo(bar) {bar = 13;}
        , "no-process-env": 2 // запретить использование process.env: if (process.env.NODE_ENV === "development")
        , "no-proto": 2 // запретить использование __proto__: var a = obj.__proto__;
        , "no-redeclare": 2 // запретить повторное объявление переменных: var a = 3; var a = 10;
        , "no-return-assign": 2 // запретить присвоение значений в return: function doSomething() {return foo = bar + 2;}
        , "no-script-url": 2 // запретить писать JavaScript-код в url: location.href = "javascript:void(0)";
        , "no-self-assign": 2 // запретить присваивать значение переменной самой себе: foo = foo;
        , "no-self-compare": 2 // запретить сравнивать значение перемнной с самой собой: if (x === x)
        , "no-sequences": 2 // запретить писать последовательности кода через запятую: a = b += 5, a + b;
        , "no-throw-literal": 2 // запретить выбрасывать исключения с помощью литералов, не являющихся объектами типа Error: throw "error"; throw 0; throw undefined; throw null;
        , "no-unmodified-loop-condition": 2 // запретить изменение переменных, использующихся в циклах, вне этих циклов
        , "no-unused-expressions": 2 // запретить наличие в коде неиспользуемых выражений: c = a, b;
        , "no-unused-labels": 2 // запретить наличие в коде неиспользуемых меток label циклов
        , "no-useless-call": 2 // запретить бессмысленное применение call и apply: foo.call(undefined, 1, 2, 3); или foo.apply(null, [1, 2, 3]);
        , "no-useless-concat": 2 // запретить бессмысленное объединение строк: var foo = "a" + "b";
        , "no-void": 2 // запретить использование оператора void
        , "no-with": 2 // запретить использование в коде with
        , "radix": [2, "always"] // требуется писать значение второго аргумента в функции parseInt()
        , "vars-on-top": 0 // все переменные должны быть объявлены на самом верху
        , "wrap-iife": [0, "outside"] // тип обертывания в круглые скобки немедленно вызываемой функйции: var x = (function () { return {y: 1};})();
        , "yoda": 2 // запретить стиль Йоды при написании условных выражений: if ("red" === color)
        // Strict Mode - правила строгого режима: "use strict";
        , "strict": [2, "never"] // запретить использование инструкции "use strict"; в коде
        // Variables - правила создания переменных
        , "no-catch-shadow": 2 // запретить называть переменную внутри catch уже использующимся именем другой переменной
        , "no-delete-var": 2 // запретить применять delete к обычным переменным
        , "no-label-var": 2 // запретить называть метку цикла label уже использующимся именем другой переменной
        , "no-restricted-globals": [2, "event"] // запретить использование данных глобальных переменных
        , "no-shadow-restricted-names": 2 // запретить создавать переменные с именами: NaN, Infinity, undefined, eval, arguments
        , "no-undef": 2 // запретить использование необъявленных переменных, кроме тех, что перечислены в "globals"
        , "no-undef-init": 2 // запретить присваивать переменной значение undefined: var foo = undefined;
        , "no-undefined": 0 // запретить переписывать значение переменной undefined
        , "no-unused-vars": 2 // запретить наличие в коде неиспользуемых переменных и функций
        , "no-use-before-define": 0 // запретить обращение к элементу до того, как он был объявлен в коде
        // Stylistic Issues - правила оформления кода
        , "array-bracket-spacing": [2, "never"] // запретить пробелы между скобками массивов: ['foo', 'bar', 'baz']
        , "block-spacing": [2, "never"] // запретить пробелы между скобками блоков: if (foo) {bar = 0;}
        , "brace-style": [0, "1tbs", {"allowSingleLine": true}] // стиль расположения скобок в конструкциях типа if () {} else {}
        , "camelcase": 2 // имена переменных должны быть записаны в стиле camelCase
        , "comma-spacing": [2, {"before": false, "after": true}] // стиль написания пробелов до и после запятой
        , "comma-style": [2, "first"] // стиль написания запятой - ведущая запятая
        , "computed-property-spacing": [2, "never"] // стиль пробелов в свойствах
        , "consistent-this": [2, "that", "self"] // переменные, заменяющие значение this
        , "eol-last": 0 // требовать оставлять пустую строку в конце каждого файла
        , "func-names": 0 // требовать прописывать имена функциям в выражениях: Foo.prototype.bar = function bar() {};
        , "id-length": [2, {"min": 1}] // минимальное число символов в названиях переменных и функций
        , "indent": 0 // минимальный отступ от левого края: 4 пробела
        , "key-spacing": [2, {"beforeColon": false, "afterColon": true}] // пробелы в свойствах объектов
        , "keyword-spacing": 2 // пробелы перед ключевыми словами
        , "linebreak-style": [2, "windows"] // стиль перехода на следующую строку
        , "max-depth": [2, {"maximum": 5}] // максимальная вложенность блоков кода друг в друга
        , "max-len": [2, {"code": 300, "tabWidth": 4, "ignoreUrls": true, "ignoreTrailingComments": true}] // максимальная длина строки
        , "max-nested-callbacks": [2, {"maximum": 3}] // максимальное число вложенных обратных вызовов
        , "max-params": [2, {"maximum": 5}] // максимальное числов аргументов в функции
        , "max-statements": [2, 50, {"ignoreTopLevelFunctions": true}] // максимальное число предложений в функции
        , "new-cap": 0 // проверять, что названия всех функций конструкторов объектов начинаются с заглваной буквы: var friend = new Person();
        , "new-parens": 2 // проверять наличие круглых скобков при вызове функции конструктора объектов без аргументов: var person = new Person();
        , "no-array-constructor": 2 // запретить использование функции Array: new Array(0, 1, 2)
        , "no-bitwise": 2 // запретить побитовые операции: var x = y | z;
        , "no-mixed-spaces-and-tabs": 2 // запретить писать в одной строке пробелы и табы
        , "no-multiple-empty-lines": [2, {"max": 2}] // запретить множественные пустые строки
        , "no-negated-condition": 0 // запретить отрицающие сравнения:if (!a) {doSomething();} else {doSomethingElse();}
        , "no-new-object": 2 // запретить создание пустых объектов с помощью new Object();
        , "no-spaced-func": 2 // запретить пробелы перед круглыми скобками при вызовах функций: fn ()
        , "no-trailing-spaces": [2, {"skipBlankLines": true}] // запретить пробелы в конце строк
        , "no-unneeded-ternary": 2 // запретить ненужные тернарные операторы: var isYes = answer === 1 ? true : false;
        , "no-whitespace-before-property": 2 // запретить пробелы перед свойствами объекта: foo. bar. baz
        , "object-curly-spacing": [2, "never"] // запретить пробелы между фигурными скобками объектов
        , "one-var-declaration-per-line": [2, "always"] // допустимо использование только 1 var на линию кода
        , "operator-assignment": [2, "always"] // lдопустимо только присвоение значений вида: x += y;
        , "operator-linebreak": [2, "before"] // допустимы только ведущие +, -, *, / при разбиении выражения на несколько строк
        , "padded-blocks": 0 // требуется всегда делать отступы в коде внутри блоков
        , "quotes": [2, "single", "avoid-escape"] // стиль написания кавычек
        , "require-jsdoc": [0, { // требуется для функций, методов и классов писать комментарии в формате JSDoc
                                            "require": {
                                                "FunctionDeclaration": true,
                                                "MethodDefinition": true,
                                                "ClassDeclaration": true
                                            }
                                        }]
        , "semi": [2, "always"] // требуется всегда писать в коде точку с запятой
        , "semi-spacing": 2 // требуется всегда ставить пробел после точки с запятой
        , "space-before-blocks": [0, {"keywords": "always", "classes": "always"}] // требуется писать пробелы переред открывающей фигурной скобкой
        , "space-before-function-paren": [0, {"anonymous": "always", "named": "always"}] // требуется писать пробелы переред открывающей круглой скобкой
        , "space-in-parens": [2, "never"] // запретить пробелы внутри круглых скобок: var foo = (1 + 2) * 3;
        , "space-infix-ops": 2 // требуется писать пробелы вокруг инфиксных операторов: a + b
        , "space-unary-ops": 2 // ребуется писать пробелы вокруг унарных операторов: +"3";
        , "spaced-comment": [0, "always"] // требуется, чтобы первым символом в комментарии был пробел
        , "wrap-regex": 0 // требуется обертывать литералы регулярных выражений в круглые скобки: (/foo/).test("bar");
      }
    , "plugins": [
          "react"
      ]
    , "extends": ["eslint:all", "plugin:react/all"]
}
*/
{
  "extends": "eslint:recommended",
  "parser": "babel-eslint",
  "env": {
    "browser": true,
    "node": true
  },
  "plugins": [
    "react"
  ],
  "rules": {
    "no-debugger": 0,
    "no-console": 0,
    "new-cap": 0,
    "strict": 0,
    "no-underscore-dangle": 0,
    "no-use-before-define": 0,
    "eol-last": 0,
    "quotes": [2, "single"],
    "jsx-quotes": [1, "prefer-single"],
    "react/jsx-no-undef": 1,
    "react/jsx-uses-react": 1,
    "react/jsx-uses-vars": 1
  }
}

Файл /node/stylelint.json

{
  "rules": {
    "block-no-empty": null,
    "color-no-invalid-hex": true,
    "declaration-colon-space-after": "always",
    "indentation": ["tab", {
      "except": ["value"]
    }],
    "max-empty-lines": 2,
    "unit-whitelist": ["px", em", "rem", "%", "s"]
  }
}

Файл /node/tsconfig.json

{
    "compilerOptions": {
          "declaration": false // true | false (default) - генерировать файлы .d.ts с описанием кода (declare) из TypeScript-файлов - Generates a .d.ts definitions file for compiled TypeScript files
        , "emitDecoratorMetadata": false // true | false (default) - сгенерировать мета-данные для типа/параметра декораторов - Emit metadata for type/parameter decorators.
        , "experimentalDecorators": false // true | false (default) - включить экспериментальную поддержку ES7 декораторов - Enables experimental support for ES7 decorators
        , "inlineSourceMap": false // true | false (default) - сгененрировать одиночный source map файл, включающий в себя все source map всех файлов вместо того, чтобы создавать отдельные .js.map файлы - Emit a single file that includes source maps instead of emitting a separate .js.map file.
        , "inlineSources": false // true | false (default) - Объединить весь исходный TypeScript код вместе со всеми source map в единый файл. Требуется чтобы правило inlineSourceMap было установлено в true. - Emit the TypeScript source alongside the sourcemaps within a single file. Requires inlineSourceMap to be set.
        , "isolatedModules": false // true | false (default) - убедиться, что вывод безопасен для компиляции только одиночных файлов путем создания ситуаций, которые сломают одиночный файл и приведут к ошибке - Ensures that the output is safe to only emit single files by making cases that break single-file transpilation an error
//     , "mapRoot": "" // "/maps" - корневая директория, в которой дебаггеру следует искать source map файлы вместо исходного их положения, определяемого при компиляции - Specifies the location where debugger should locate map files instead of generated locations.
        , "module": "amd" // "amd" (default) | "commonjs" | "system" | "umd" | "" - в какой тип модулей (amd, commonjs, system или umd) компилировать файлы - Specify module style for code generation
        , "newLine": "CRLF" // "CRLF" | "LF" | "" (default) - какие символы перехода на следующую строку (CRLF или LF) должны быть прописаны в скомпилированных файлах - Explicitly specify newline character (CRLF or LF); if omitted, uses OS default.
        , "noEmit": false // true | false (default) - проверить код на возможность компиляции, но не компилировать итоговые файлы даже, если в коде отсутсвуют ошибки - Check, but do not emit JS, even in the absence of errors.
        , "noEmitHelpers": true // true | false (default) - не вставлять в скомпилированные файлы вспомогательные функции типа __extends - Do not generate custom helper functions like __extends in compiled output.
        , "noImplicitAny": false // true | false (default) - запретить использовать тип any - выводить предупреждения, если выражения или объявления переменных имеют тип any - Warn on expressions and declarations with an implied any type.
        , "noResolve": true // true | false (default) - не добавлять тройной слэш /// или import target для модуля в скомпилированные файлы - Do not add triple-slash references or module import targets to the compilation context.
//     , "out": "" // "" - компиляция в разные файлы, если указана пустая строка "" или компиляция в единый файл, если указан итоговый файл "./js/single.js" -  - Concatenate and emit output to a single file.
//     , "outDir": "" // "dist" - если указана пустая строка "", то записывать скомпилированные файлы рядом с TypeScript-файлом или перенести итоговые скомпилированные файлы, включая их иерархическое расположение в подпапках, в заданную директорию "dist" - Redirect output structure to the directory.
        , "preserveConstEnums": false // true | false (default) - константы перечислений будут сохранены в качестве перечислений в скомпилированных файлах - Const enums will be kept as enums in the emitted JS.
        , "removeComments": false // true (default)| false - удалять или включать исходные комментарии в итоговые скомпилированные файлы - Configures if comments should be included in the output
        , "sourceMap": false // true (default) | false - генерировать source map файлы - для Generates corresponding .map file
//     , "sourceRoot": "" // "/dev" - корневая директория, в которой дебаггеру следует искать исхдные TypeScript-файлы вместо исходного их положения - Specifies the location where debugger should locate TypeScript files instead of source locations.
        , "suppressImplicitAnyIndexErrors": false // true | false (default) - если установлено true, то TypeScript позволит получить доступ к свойствам объекта по строковому индексу, если правило noImplicitAny активно, даже если TypeScript не знает о них. Это свойство не действует до тех пор, пока правило noImplicitAny не будет установлено активным. - If set to true, TypeScript will allow access to properties of an object by string indexer when noImplicitAny is active, even if TypeScript doesn"t know about them. This setting has no effect unless noImplicitAny is active.
        , "target": "es5" // "es3" | "es5" (default) | "es6" - в какую версию ECMAScript (3, 5 или 6) компилировать файлы - Specify ECMAScript target version": "es3", "es5", or "es6"
        , "allowJs": true // true | false (default) - разрешить компиляцию чистого JavaScript-кода
        , "strictNullChecks": true // true | false (default) - разрешить строгую проверку null
        , "noImplicitUseStrict": true // true | false (default) - запретить запись 'use strict' в итоговые файлы
        , "allowUnreachableCode": false // true | false (default) - запретить выводить сообщения об ошибка при обнаружении недостижимого кода
        , "jsx": "react" // "preserve" | "react" - определить тип генерируемого кода JSX
    }
  , "files": [
          "typings/index.d.ts"
    ]
}

Файл /node/webpack.config.js

const fs = require('fs')
        , path = require('path')
        , webpack = require('webpack')
        , CleanWebpackPlugin = require('clean-webpack-plugin')
        , ExtractTextPlugin = require('extract-text-webpack-plugin');

const fs = require('fs')
        , path = require('path')
        , webpack = require('webpack')
        , CleanWebpackPlugin = require('clean-webpack-plugin')
        , ExtractTextPlugin = require('extract-text-webpack-plugin');
        
const environment = require('./config/environment');

const indexFile = path.resolve(__dirname, '../source/index.js');

const entry = environment ? [
                                              'babel-polyfill'
                                            , indexFile
                                          ]
                                        : [
                                              'webpack/hot/dev-server'
                                            , 'webpack-hot-middleware/client'
                                            , 'babel-polyfill'
                                            , indexFile
                                          ];

module.exports = {
      entry: entry
    , resolve: {
            extensions: ['', '.js', '.jsx', '.ts', '.tsx']
          , alias: {
                  resetStyle: path.resolve(__dirname, '../source/app/common/reset.scss')
                , bodyStyle: path.resolve(__dirname, '../source/app/common/body.scss')
                , rootStyle: path.resolve(__dirname, '../source/app/common/root.scss')
                , hiddenStyle: path.resolve(__dirname, '../source/app/common/hidden.scss')
                , sortIconsStyle: path.resolve(__dirname, '../source/app/common/sort-icons.scss')
                , versionStyle: path.resolve(__dirname, '../source/app/common/version.scss')
                , windowOnErrorStyle: path.resolve(__dirname, '../source/app/window-onerror/window-onerror.scss')

                , 'react': path.resolve(__dirname, 'node_modules/react/lib/React.js')
                , 'react-dom': path.resolve(__dirname, 'node_modules/react/lib/ReactDOM.js')
                , 'react-router': path.resolve(__dirname, 'node_modules/react-router/lib/index.js')
                , 'react-router-redux': path.resolve(__dirname, 'node_modules/react-router-redux/lib/index.js')
                , 'react-redux': path.resolve(__dirname, 'node_modules/react-redux/lib/index.js')
                , 'redux': path.resolve(__dirname, 'node_modules/redux/lib/index.js')
                , 'redux-thunk': path.resolve(__dirname, 'node_modules/redux-thunk/lib/index.js')
                , 'redux-logger': path.resolve(__dirname, 'node_modules/redux-logger/lib/index.js')
                , 'tcomb': path.resolve(__dirname, 'node_modules/babel-plugin-tcomb/lib/index.js')

                , getHistory: path.resolve(__dirname, '../source/app/history/get-history.js')
                , historyStyle: path.resolve(__dirname, '../source/app/history/style.scss')
                , historyView: path.resolve(__dirname, '../source/app/history/history.js')
                , historyConstants: path.resolve(__dirname, '../source/app/history-data/history-constants.js')
                , historySortAction: path.resolve(__dirname, '../source/app/history-data/history-sort-action.js')
                , historySortReducer: path.resolve(__dirname, '../source/app/history-data/history-sort-reducer.js')
                , historyGetDataAction: path.resolve(__dirname, '../source/app/history-data/history-get-data-action.js')
                , historyGetDataReducer: path.resolve(__dirname, '../source/app/history-data/history-get-data-reducer.js')
            }
      }
    , output: {
          path: path.resolve(__dirname, '../build')
        , filename: 'bundle.js'
        , publicPath: '/build/'
        , pathinfo: true
      }
    , resolveLoader: {
          root: path.join(__dirname, 'node_modules')
      }
    , module: {
           preLoaders: [
                {
                      test: /\.js$/
                    , exclude: /node_modules/
                    , loader: 'jscs-loader!eslint-loader'
                }
              , {
                      test: /\.ts?$/
                    , exclude: /node_modules/
                    , loader: 'tslint-loader'
               }
              , {
                      test: /\.(sass|scss)$/
                    , loader: 'stylelint-loader'
               }
           ]
         , loaders: [
                {
                      test: /\.tsx?$/
                    , exclude: /node_modules/
                    , loader: 'awesome-typescript-loader'
                }
              , {
                      test: /\.jsx?$/
                    , exclude: /node_modules/
                    , loader: 'babel-loader'
                    , query: {
                          presets: [
                                  path.join(__dirname, 'node_modules/babel-preset-react-hmre')
                                , path.join(__dirname, 'node_modules/babel-preset-stage-0')
                                , path.join(__dirname, 'node_modules/babel-preset-react')
                                , path.join(__dirname, 'node_modules/babel-preset-es2015')
                          ]
                        , passPerPreset: true
                        , plugins: [
                                  path.join(__dirname, 'node_modules/babel-plugin-syntax-flow')
                                , path.join(__dirname, 'node_modules/babel-plugin-tcomb')
                                , path.join(__dirname, 'node_modules/babel-plugin-transform-flow-strip-types')
                          ]
                      }
                }
              , {
                      test: /\.html/
                    , exclude: /node_modules/
                    , loader: 'html-loader'
                }
              , {
                      test: /\.scss/
                    , exclude: /node_modules/
                    , loader: ExtractTextPlugin.extract('style-loader', 'css-loader!sass-loader')
                }
              , {
                      test: /\.(jpe?g|png|gif)$/i
                    , exclude: /node_modules/
                    , loader: 'url-loader'
                    , query: {
                            limit: 10000 // 10 kb
                          , name: 'images/[hash].[ext]'
                      }
                }
              , {
                      test: /\.json$/
                    , loader: 'json-loader'
                }
          ]
       }
     , plugins: [
            new CleanWebpackPlugin(['build'], {root: path.resolve(__dirname, '../')})
          , new ExtractTextPlugin('../build/bundle.css')
          , new webpack.BannerPlugin('Build date: ' + new Date().toString())
          , new webpack.optimize.DedupePlugin()
          , new webpack.optimize.OccurenceOrderPlugin()
          , new webpack.HotModuleReplacementPlugin()
          , new webpack.NoErrorsPlugin()
          , new webpack.DefinePlugin({VERSION: JSON.stringify(fs.readFileSync(path.join(__dirname, '../version.txt'), 'utf-8')).trim()})
      ]
    , bail: true
    , debug: true
    , eslint: {
            configFile: path.join(__dirname, 'eslint.json')
          , failOnError: true
       }
     , jscs: {
            validateIndentation: 2
          , emitErrors: false
          , failOnHint: false
          , reporter: function(errors) {}
       }
     , tslint: {
            configuration: {
                rules: {
                    quotemark: [true, 'double']
                }
            }
          , failOnHint: true
       }
     , stylelint: {
            configFile: path.join(__dirname, 'stylelint.json')
          , configOverrides: {
                rules: {
                    "block-no-empty": null
                }
            }
       }
};

Файл /node/build.js

const fs = require('fs')
        , path = require('path')
        , indexFilePath = path.join(__dirname, '../index.html')
        , versionFilePath = path.join(__dirname, '../version.txt');

fs.writeFileSync(
      indexFilePath
    , fs.readFileSync(indexFilePath, 'utf-8').replace(
              /\?v=\d+\.\d+\.\d+-\d+"/g
            , `?v=${fs.readFileSync(versionFilePath, 'utf-8')}-${new Date().getTime()}"`
      ).replace(
              /v\d+\.\d+\.\d+/g
            , `v${fs.readFileSync(versionFilePath, 'utf-8')}`
      )
);

require('./server');

Файл /node/server.js

const path = require('path')
        , webpack = require('webpack')
        , webpackDevMiddleware = require('webpack-dev-middleware')
        , webpackHotMiddleware = require('webpack-hot-middleware')
        , webpackConfig = require('./webpack.config')
        , express = require('express')
        , http = require('http');

const app = express();

const webpackCompiler = webpack(webpackConfig);

app.use(webpackDevMiddleware(webpackCompiler, {noInfo: true, publicPath: webpackConfig.output.publicPath}));
app.use(webpackHotMiddleware(webpackCompiler));

app.use(express.static(path.resolve(__dirname, '../')));

app.get('/', function (request, response) {
    response.sendFile(path.resolve(__dirname, '../index.html'));
});

app.get('/path/to/options', function (request, response){
//  proxyToServer('10.80.238.27', 9081, 'http://10.80.238.27:9081/path/to/options', 'GET', response);
    response.json(generateOptions());
});

app.listen(80, '127.0.0.1', function (error) {
    if (error) {throw error;}
    console.log('Server is running at http://127.0.0.1:80');
});

function proxyToServer (host, port, path, method, response) {
    const options = {
          host: host // host to forward to
        , port: port // port to forward to
        , path: path // path to forward to
        , method: method // request method
        , headers: response.headers // headers to send
    };

    let data = '';

    const proxy = http.request(options, function (proxyResponse) {
        proxyResponse.setEncoding('utf8');
        proxyResponse.on('data', function (chunk) {
            data += chunk;
        });
        proxyResponse.on('end', function () {
            response.writeHead(200, {'Content-Type': 'application/json'});
            response.write(data);
            response.end();
        });
    });

    proxy.on('error', function (error) {
        console.log(`Problem with proxy request: ${error.message}`);
        response.writeHead(500);
        response.end();
    });

    proxy.end();
}

function randomNumber (min, max) {
    min = parseInt(min, 10);
    max = parseInt(max, 10);
    return Math.floor(Math.random() * (max - min + 1)) + min;
}

function randomString (len) {
    const possibleChars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
    let text = '';
    for (let i = 0; i < len; i++) {
        text += possibleChars.charAt(Math.floor(Math.random() * possibleChars.length));
    }
    return text;
}

function generateOptions () {
    const result = [
          {
              'options': ['news', 'history']
            , 'message': ''
          }
        , {
              'options': []
            , 'message': 'У вас нет доступных опций.'
          }
    ][randomNumber(0, 1)];
    // return result;
    return {
          'options': ['news', 'history']
        , 'message': ''
    };
}

среда, 19 октября 2016 г.

Typescript with React and Redux Snippets

Basic react component

import * as React from "react";

interface IComponentNameProps {};

interface IComponentNameState {};

class ComponentName extends React.Component<IComponentNameProps, IComponentNameState> {
    public render(): JSX.Element {
        return (<span>Body</span>);
    }
}
export default ComponentName;

Redux container

import * as React from "react";
import { connect } from "react-redux";

interface IComponentNameProps {};

interface IComponentNameState {};

class ComponentName extends React.Component<IComponentNameProps, IComponentNameState> {
    public render(): JSX.Element {
        return (<span>Body</span>);
    }
}

export default connect()(ComponentName);

Redux container with implemented connect

import * as React from "react";
import { connect } from "react-redux";

interface IComponentNameProps {};

interface IComponentNameState {};

class ComponentName extends React.Component<IComponentNameProps, IComponentNameState> {
    public render(): JSX.Element {
        return (<span>Body</span>);
    }
}

export default connect(
    (state) => ({
        // Map state to props
    }),
    {
        // Map dispatch to props
    })(ComponentName);

Как установить React, TypeScript и Webpack

1) Создайте папку, в которой будут храниться все файлы вашего проекта. Назовите её для примера "project".

2) В этой папке создайте:
- папку "src"
- папку "dist"
- файл "package.json"
- файл "tsconfig.json"
- файл "webpack.config.js"
- файл "index.html"

3) Внутри папки "src" создайте:
- папку "components"
- файл "index.tsx"

4) Внутри папки "components" создайте:
- файл "Hello.tsx"

5) Поместите в созданные файлы следующий код.

Код для файла package.json

{
      "version": "1.0.0"
    , "name": "react-typescript-webpack"
    , "description": "React, TypeScript and Webpack"
    , "keywords": ["react", "typescript", "webpack"]
    , "author": "John Doe"
    , "engines": {
          "node": "6.9.0"
        , "npm": "3.10.9"
      }
    , "dependencies": {
          "react": "15.3.2"
        , "react-dom": "15.3.2"
      }
    , "devDependencies": {
          "webpack": "1.13.2"
        , "typescript": "2.0.3"
        , "ts-loader": "0.9.4"
        , "source-map-loader": "0.1.5"
        , "@types/react": "0.14.41"
        , "@types/react-dom": "0.14.18"
      }
}

Код для файла tsconfig.json

{
    "compilerOptions": {
          "target": "es5"
        , "module": "commonjs"
        , "jsx": "react"
        , "sourceMap": true
        , "noImplicitAny": true
    }
}

Код для файла webpack.config.js

const path = require('path');

module.exports = {

      entry: './src/index.tsx'

    , output: {
        filename: './dist/bundle.js'
      }

    // When importing a module whose path matches one of the following, just
    // assume a corresponding global variable exists and use that instead.
    // This is important because it allows us to avoid bundling all of our
    // dependencies, which allows browsers to cache those libraries between builds.
    , externals: {
          'react': 'React'
        , 'react-dom': 'ReactDOM'
      }

    , resolve: {
          // Add '.ts' and '.tsx' as resolvable extensions.
          extensions: ['', '.webpack.js', '.web.js', '.ts', '.tsx', '.js']
        , alias: {
            Hello: path.resolve(__dirname, './src/components/Hello.tsx')
          }
      }

    , module: {
          preLoaders: [
            // All output '.js' files will have any sourcemaps re-processed by 'source-map-loader'.
            {test: /\.js$/, loader: 'source-map-loader'}
          ]

        , loaders: [
            // All files with a '.ts' or '.tsx' extension will be handled by 'ts-loader'.
            {test: /\.tsx?$/, loader: 'ts-loader'}
          ]
      }

    // Enable sourcemaps for debugging webpack's output.
    , devtool: 'source-map'

};

Код для файла index.html

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8" />
        <title>Hello React!</title>
    </head>
    <body>
        <div id="example"></div>

        <!-- Dependencies -->
        <script src="./node_modules/react/dist/react.js"></script>
        <script src="./node_modules/react-dom/dist/react-dom.js"></script>

        <!-- Main -->
        <script src="./dist/bundle.js"></script>
    </body>
</html>

Код для файла index.tsx

import * as React from 'react';
import * as ReactDOM from 'react-dom';

import {Hello} from 'Hello';

ReactDOM.render(<Hello compiler="TypeScript" framework="React" />, document.getElementById('example'));

Код для файла Hello.tsx

import * as React from 'react';

export interface HelloProps {
    compiler: string;
    framework: string;
}

export class Hello extends React.Component<HelloProps, {}> {
    render () {
        return <h1>Hello from {this.props.compiler} and {this.props.framework}!</h1>;
    }
}

6) Откройте командную строку и перейдите в ней в папку вашего проекта "project":
cd C:\path\to\project

7) Выполните команду:
npm install

8) Выполните команду:
webpack

9) Откройте файл "index.html" в браузере, чтобы убедиться. что всё работает и увидеть надпись "Hello from TypeScript and React!".

Как запустить Node.js без инсталляции

Как запустить Node.js без инсталляции.

Если необходима портативная версия Node.js, то достаточно просто скачать файл

http://nodejs.org/dist/latest/win-x86/node.exe

и поместить его в папку с вашими серверными JavaScript-файлами.

После этого в командной строке перейдите в папку вашего проекта и запустите ваш скрипт стандартным образом:

\path\to\saved\node.exe scriptname.js

При желании вы можете прописать ссылку на файл node.exe в системную переменную PATH, чтобы запускать ваши скрипты из любого места так:

node scriptname.js

Если вам нужен NPM, то скачайте его архив ZIP по ссылке:

https://github.com/npm/npm

и распакуйте его в папку, в которую поместили файл node.exe.

Переименуйте папку с файлами NPM в "npm", убрав из названия номер версии, если он есть.

Создайте рядом с файлом node.exe папку "node_modules" и перенесите в неё папку "npm".

Скопируйте в папку с файлом node.exe файл npm.cmd из папки "node_modules/npm/bin/".

После этого NPM автоматически заработает из командной строки стандартным образом.

Для того, чтобы в этом убедиться, наберите в командной строке команду:

npm -h

Если NPM не может скачать пакеты в Node.js для версии выше 0.12, то необходимо в командной строке выполнить команду:

npm config set strict-ssl=false

После этого пакеты начнут скачиваться.

Когда вам потребуется обновить Node.js, то просто замените файл node.exe на новую версию.

А для обновления NPM удалите старую папку "npm" и распакуйте в проект архив ZIP с новой версией NPM, таким образом, как было описано выше.

Ваша стартовая структура расположения папок и файлов должна выглядеть в итоге так:

C:\path\to\your\project\server.js - ваш сценарий JavaScript
C:\path\to\your\project\node.exe - файл новой версии Node.js
C:\path\to\your\project\node_modules\npm - папка из архива ZIP с новой версией NPM
C:\path\to\your\project\npm.cmd - файл из архива ZIP с новой версией NPM

вторник, 18 октября 2016 г.

JavaScript Type.js - How to check type in runtime

Файл type.js

/*
Примеры:

type.String(1, 'Wrong value.');

var a = type.func(
      [type.Number, type.String], type.Object
    , function (a, b) {
        return {
            a: a
          , b: b
        };
    }
);

a(1, 2);

type.add('Password', function (value, message) {
    if (not (typeof value === 'string' && value.length > 6)) {
        if (message) {
            throw new Error(message);
        } else {
            throw new Error('Type of value ' + value + ' must be password, but it has type ' + Object.prototype.toString.call(value).slice(8, -1).toLowerCase() + '.');
        }
    }
});

type.Password(1, 'Wrong value.');

var point = type.obj(
      {
          x: type.Number
        , y: type.Number
      }
    , {
          x: 1
        , y: '2'
    }
);

var list = type.arr(
      [type.Number, type.String, type.Object] // или просто type.Number для всех значений
    , [1, '2', {a: 3}]
);

*/

function not (bool) {return !bool;}

var type = {};

type.add = function (name, predicate) {
    if (name in type) {throw new Error('Type ' + name + ' already defined.');}
    type[name] = predicate;
};

type.alias = function (name, typeFunction) {
    if (name in type) {throw new Error('Type ' + name + ' already defined.');}
    type[name] = typeFunction;
}

type.func = function (argumentTypesArray, returnValueType, functionBody) {
    return function () {
        for (var i = 0, len = argumentTypesArray.length; i < len; i++) {
            argumentTypesArray[i](arguments[i]);
        }
        var result = functionBody.apply(null, arguments);
        returnValueType(result);
        return result;
    };
};

type.obj = function (types, object) {
    for (var key in types) {
        if (Object.prototype.hasOwnProperty.call(types, key)) {
            if (not (Object.prototype.hasOwnProperty.call(object, key))) {
                throw new Error('Object don\'t have key ' + key + '.');
            } else {
                types[key](object[key]);
            }
        }
    }
    return object;
};

type.arr = function (types, array) {
    var typesIndex = 0
        , arrayIndex = 0
        , typesLength
        , arrayLength = array.length;
    if (Object.prototype.toString.call(types) === '[object Array]') {
        for (typesLength = types.length; typesIndex < typesLength; typesIndex++, arrayIndex++) {
            types[typesIndex](array[arrayIndex]);
        }
    } else if (typeof types === 'function') {
        for (; arrayIndex < arrayLength; arrayIndex++) {
            types(array[arrayIndex]);
        }
    }
    return array;
};

type.Any = function () {
    // can be any value
};

type.Undefined = function (value, message) {
    if (not (value === void 0)) {
        if (message) {
            throw new Error(message);
        } else {
            throw new Error('Type of value ' + value + ' must be undefined, but it has type ' + Object.prototype.toString.call(value).slice(8, -1).toLowerCase() + '.');
        }
    }
};

type.Null = function (value, message) {
    if (not (value === null)) {
        if (message) {
            throw new Error(message);
        } else {
            throw new Error('Type of value ' + value + ' must be null, but it has type ' + Object.prototype.toString.call(value).slice(8, -1).toLowerCase() + '.');
        }
    }
};

type.Nil = function (value, message) {
    if (not (value === void 0 || value === null)) {
        if (message) {
            throw new Error(message);
        } else {
            throw new Error('Type of value ' + value + ' must be undefined or null, but it has type ' + Object.prototype.toString.call(value).slice(8, -1).toLowerCase() + '.');
        }
    }
};

type.Boolean = function (value, message) {
    if (not (value === true || value === false)) {
        if (message) {
            throw new Error(message);
        } else {
            throw new Error('Type of value ' + value + ' must be boolean, but it has type ' + Object.prototype.toString.call(value).slice(8, -1).toLowerCase() + '.');
        }
    }
};

type.Number = function (value, message) {
    if (not (typeof value === 'number' && value === value && isFinite(value))) {
        if (message) {
            throw new Error(message);
        } else {
            throw new Error('Type of value ' + value + ' must be number, but it has type ' + Object.prototype.toString.call(value).slice(8, -1).toLowerCase() + '.');
        }
    }
};

type.String = function (value, message) {
    if (not (typeof value === 'string')) {
        if (message) {
            throw new Error(message);
        } else {
            throw new Error('Type of value ' + value + ' must be string, but it has type ' + Object.prototype.toString.call(value).slice(8, -1).toLowerCase() + '.');
        }
    }
};

type.Array = function (value, message) {
    if (not (Object.prototype.toString.call(value) === '[object Array]')) {
        if (message) {
            throw new Error(message);
        } else {
            throw new Error('Type of value ' + value + ' must be array, but it has type ' + Object.prototype.toString.call(value).slice(8, -1).toLowerCase() + '.');
        }
    }
};

type.Object = function (value, message) {
    if (not (Object.prototype.toString.call(value) === '[object Object]')) {
        if (message) {
            throw new Error(message);
        } else {
            throw new Error('Type of value ' + value + ' must be object, but it has type ' + Object.prototype.toString.call(value).slice(8, -1).toLowerCase() + '.');
        }
    }
};

type.Function = function (value, message) {
    if (not (typeof value === 'function')) {
        if (message) {
            throw new Error(message);
        } else {
            throw new Error('Type of value ' + value + ' must be function, but it has type ' + Object.prototype.toString.call(value).slice(8, -1).toLowerCase() + '.');
        }
    }
};

type.Date = function (value, message) {
    if (not (value instanceof Date)) {
        if (message) {
            throw new Error(message);
        } else {
            throw new Error('Type of value ' + value + ' must be date, but it has type ' + Object.prototype.toString.call(value).slice(8, -1).toLowerCase() + '.');
        }
    }
};

type.RegExp = function (value, RegExp) {
    if (not (value instanceof Date)) {
        if (message) {
            throw new Error(message);
        } else {
            throw new Error('Type of value ' + value + ' must be regular expression, but it has type ' + Object.prototype.toString.call(value).slice(8, -1).toLowerCase() + '.');
        }
    }
};

type.Error = function (value, message) {
    if (not (value instanceof Error)) {
        if (message) {
            throw new Error(message);
        } else {
            throw new Error('Type of value ' + value + ' must be error, but it has type ' + Object.prototype.toString.call(value).slice(8, -1).toLowerCase() + '.');
        }
    }
};