Структура проекта

Балдин Кирилл
example-app
├── .gitignore
├── .editorconfig
├── .eslintrc.json
├── package.json
└── README.md

.gitignore


.idea
/*.env
node_modules
public/
npm-debug.log

.editorconfig


root = true

[*]
charset = utf-8
end_of_line = lf
indent_size = 4
indent_style = space
insert_final_newline = true
trim_trailing_whitespace = true

[*.json]
indent_style = space
indent_size = 2

[*.yml]
indent_style = space
indent_size = 2
        

.eslintrc.json


{
  "parserOptions": {
    "ecmaVersion": 6
  },
  "env": {
    "browser": true,
    "mocha": true,
    "node": true
  },
  "extends": "xo",
  "rules": {
    "indent": [2, 4, {"SwitchCase": 1}],
    "max-len": [2, 100],
    "max-params": [2, 3]
  }
}
        

package.json


{
  "name": "example-app",
  "version": "1.0.0",
  "main": "dist",
  "license": "MIT",
  "scripts": { ... },
  "dependencies": { ... },
  "devDependencies": { ... },
  "engines": {
    "node": "4.4.0"
  }
}
        

Разделение сервера и клиента

example-app
├── .gitignore
├── .editorconfig
├── package.json
├── README.md
├── client  //Клиенска часть
├── server  //Серверная часть (express api db)
└── public  //Папка которые отдаются браузеру

Серверная часть

example-app
├── .gitignore
├── .editorconfig
├── package.json
├── README.md
├── client
└─┬ server
  ├── app.js // Приложения
  └── routes.js // Роутинги приложения

MVC + express

example-app
├── .gitignore
├── .editorconfig
├── package.json
├── README.md
└─┬  server
  ├── middleware
  ├── utils
  ├── models
  ├── views
  ├── controllers
  ├── app.js
  └── routes.js

Компонентный подход

example-app
└─┬ client
  └─┬ components
    ├─┬ form
    │ ├── form.css
    │ └── form.jsx
    ├─┬ item
    │ ├── item.css
    │ └── item.jsx
    └─┬ list
      ├── list.css
      └── list.jsx

Компонентный подход

Меньше вероятность конфликтов

Легкое переиспользование кода

Компонентный подход. Ресурсы

example-app
└─┬ client
  └─┬ components
    ├─┬ form
    │ ├── form.css
    │ └── form.jsx
    ├─┬ item
    │ ├── item.css
    │ └── item.jsx
    └─┬ list
      ├── list.css
      └── list.jsx
    

Публичная папка

public
├── form.css
├── item.css
├── list.css
├── form.js
├── item.js
└── list.js

Можно сделать с помощью

Компонентный подход. Подключение


<head>
<link href="/form.css" rel="stylesheet">
<link href="/item.css" rel="stylesheet">
<link href="/list.css" rel="stylesheet">

<script src="/form.js"></script>
<script src="/item.js"></script>
<script src="/list.js"></script>
</head>

Работает

Медленно загружается

Много запросов за ресурсами

Много копирований при сборке

Ограничение на количество подключаемых ресурсов (в IE)

Зависимости ресурсов

AMD (Asynchronous Module Definition)


define(['my-module', 'my-module2'],
  function (MyModule, MyModule2) {
      return { ... };
});
      

CommonJS-обёртка


define(function (require, module, exports) {
  var MyModule = require('my-module');
  var MyModule2 = require('my-module2');
  module.exports = { ... };
});
      

CommonJS нативно реализован в Node.js/Rhino.


var MyModule = require('my-module');
var MyModule2 = require('my-module2');
module.exports = { ... };
      

ECMAScript 2015


import MyModule from 'my-module';
import MyModule2 from 'my-module2';
export { ... };
      

Экспорт по умолчанию


export default class MyModule {
    constructor(id, name) {
        this.id = id;
        this.name = name;
    }
};
      

import MyModule from 'my-module';
      

Деструктуризация


import MyModule {metod1, metod2} from 'my-module';
      

Ключевое слово as для переименование


import * as MyModule from 'my-module';
import {metod1 as alias1} from 'my-module2';
      

Проблема организации

Решение webpack

webpack


npm install --save-dev webpack

Начнем с простого примера

test-webpack
├── my-module.js
├── test.js
└── webpack.config.js
my-module.js

export function method1() {
    console.log('method1 my module');
}
export function method2() {
    console.log('method2 my module');
}
export default function () {
    console.log('init my module');
}
test.js

import MyModule ,{method1,method2} from './my-module'

MyModule();
method1();
method2();
webpack.config.js

module.exports = {
    entry: './test.js',
    output: {
        filename: 'test.bundle.js',
    }
}

Запускаем сборку


webpack
test-webpack
  ├── my-module.js
  ├── test.js
  ├── todo.bundle.js
  └── webpack.config.js
todo.bundle.js

/************************************************************************/
/******/ ([
/* 0 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {

"use strict";
/* harmony export (immutable) */ __webpack_exports__["b"] = method1;
/* harmony export (immutable) */ __webpack_exports__["c"] = method2;

function method1() {
    console.log('method1 my module');
}
function method2() {
    console.log('method2 my module');
}
/* harmony default export */ __webpack_exports__["a"] = function () {
    console.log('init my module');
};
/***/ }),
my-module.js

export function method1() {
    console.log('method1 my module');
}
export function method2() {
    console.log('method2 my module');
}
export default function () {
    console.log('init my module');
}
todo.bundle.js


/* 1 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {

"use strict";
Object.defineProperty(__webpack_exports__, "__esModule", { value: true });
/* harmony import */
var __WEBPACK_IMPORTED_MODULE_0_my_module__ = __webpack_require__(0);

__webpack_require__.i(__WEBPACK_IMPORTED_MODULE_0_my_module__["a" /* default */])();
__webpack_require__.i(__WEBPACK_IMPORTED_MODULE_0_my_module__["b" /* method1 */])();
__webpack_require__.i(__WEBPACK_IMPORTED_MODULE_0_my_module__["c" /* method2 */])();

/***/ })
/******/ ]);
test.js

import MyModule ,{method1,method2} from './my-module'

MyModule();
method1();
method2();

Сборка скриптов

example-app
└── webpack.config.js

module.exports = {
  context: __dirname + "/client",
  entry: "todo.js",
  output: {
    path: __dirname + "/public",
    filename: "todoPage.bundle.js"
  }
}
example-app
└─┬ client
  └── todo.jsx

import ReactDOM from 'react-dom';
import TodoPage from './pages/todoPage';
import React from 'react';
export default function (data) {
    const mountNode = document.getElementById("app");
    ReactDOM.render( , mountNode);
}

Babel

npm i --save-dev babel-loader babel-core
example-app
└── webpack.config.js

module.exports = {
  rules: [
    {
      test: /(\.jsx|\.js)$/, // Для филтрации по расширению
      include: __dirname + "/client", // Для филтрации по папке
      exclude: /node_modules/, // Не учитывать
      loader: "babel-loader"
    }
  ]
}
example-app
└── .babelrc

{
  "presets": ["es2015", "stage-0", "react"]
}

Запуск

example-app
└── package.json

"scripts": {
  "build": "webpack"
}

npm run build
example-app
└─┬ public
  ├── form.css
  ├── item.css
  ├── list.css
  └── todo.bundle.js

Подключение


<head>
<link href="/form.css" rel="stylesheet">
<link href="/item.css" rel="stylesheet">
<link href="/list.css" rel="stylesheet">

<script src="/todo.bundle.js"></script>
</head>

Сборка стилей

example-app
└─┬ client
  └─┬ components
    └─┬ form
      ├── form.css
      └── form.jsx


import './form.css';

npm run build

You may need an appropriate
loader to handle this file type.

Лоадеры

    npm i style-loader --save-dev
    npm i css-loader --save-dev
example-app
└── webpack.config.js

module.exports = {
  rules: [
      ...
      {
        test: /\.css$/,
        use: ['style-loader','css-loader']
    }
  ]
}

<head>
<style type="text/css">
body {
    font-family: Arial, sans-serif;
}
</style>

<style type="text/css">
header {
    border-bottom: 1px solid #ccc;
}
</style>
</head>
example-app
└── webpack.config.js

import ExtractTextPlugin from 'extract-text-webpack-plugin';

module.exports = {
  ...
  plugins: [
    new ExtractTextPlugin("bundle.css")
  ]
}
example-app
└── webpack.config.js

module.exports = {
  rules: [
    {
      test: /\.css$/,
      use: ExtractTextPlugin
        .extract('style-loader', 'css-loader')
    }
  ]
}
example-app
└─┬ public
  ├── bundle.css
  └── todo.bundle.js

Подключение


<head>
<link href="/bundle.css" rel="stylesheet">

<script src="/todo.bundle.js"></script>
</head>

Сборка картинок

example-app
└─┬ components
  └─┬ list
    ├── list.css
    ├── background.png
    └── list.jsx
example-app
└─┬ components
  └─┬ list
    └── list.css

.list {
  background: url(background.png)
}

Лоадеры

    npm install file-loader --save-dev
example-app
└── webpack.config.js

module.exports = {
  rules: [
    {
        test: /(\.png|\.jpg)$/,
        use: "file-loader"
    }
  ]
}
example-app
└─┬ public
  ├── bundle.css
  ├── todo.bundle.js
  └── 9d86ab26bc61bc94bf09d352edff07a1.png

  .list {
    background: url("./9d86ab2...7a1.png")
  }
  
Постпроцессинг CSS

расстановка браузерных префиксов

оптимизация и минификация

отладка

цветовые фунции

...

PostCSS

Набор js плагинов для трансформации стилй

postcss

npm i --save-dev postcss-loader
example-app
└── postcss.config.js

module.exports = {
  plugins: [
    require('postcss-cssnext')({ /* ...options */ }),      // plugins[1]
    require('autoprefixer')({ /* ...options */ })          // plugins[2]
    require('cssnano')({ /* ...options */ })               // plugins[3]
  ]
}
       

cssnext

npm i --save-dev postcss-cssnext

До


:root {
  --fontSize: 1rem;
}
.list {
  & .item {
    font-size: var(--fontSize);
    line-height: calc(var(--fontSize) * 1.5);
  }
}

После


.list .item {
  font-size: 16px;
  font-size: 1rem;
  line-height: 24px;
  line-height: 1.5rem;
}

расстановка браузерных префиксов

autoprefixer

npm i --save-dev autoprefixer

До


.list {
  display: flex;
}

После


.list {
  display: -webkit-box;
  display: -webkit-flex;
  display: -ms-flexbox;
  display: flex;
}

Минификация CSS

example-app
└─┬ components
  └─┬ list
    └── list.css

.list {
  div { margin: 0 0 0 0; }
  div {
    display: -webkit-box;
    display: -webkit-flex;
    display: -ms-flexbox;
    display: flex;
  }
}
example-app
└─┬ components
  └─┬ list
    └── list.css

.list {
  div { margin: 0 0 0 0; }
  div {
    display: -webkit-box;
    display: -webkit-flex;
    display: -ms-flexbox;
    display: flex;
  }
}
example-app
└─┬ components
  └─┬ list
    └── list.css

.list {
  div { margin: 0 0 0 0; }
  div {
    display: -webkit-box;
    display: -webkit-flex;
    display: -ms-flexbox;
    display: flex;
  }
}
example-app
└─┬ components
  └─┬ list
    └── list.css

.list {
  div { margin: 0 0 0 0; }
  div {
    display: -webkit-box;
    display: -webkit-flex;
    display: -ms-flexbox;
    display: flex;
  }
}
example-app
└─┬ components
  └─┬ list
    └── list.css

.list {
  div { margin: 0 0 0 0; }
  div { 
    display: -webkit-box; 
    display: -webkit-flex; 
    display: -ms-flexbox; 
    display: flex;
  }
}

cssnano

npm i --save-dev cssnano

До


.list {
  div { margin: 0 0 0 0; }
  div {
    display: -webkit-box;
    display: -webkit-flex;
    display: -ms-flexbox;
    display: flex;
  }
}

После


.list div{margin:0;display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex}

Минификация JS

example-app
└── webpack.config.js

var webpack = require('webpack');

module.exports = {
  ...
  plugins: [
    ...
    new webpack.optimize.UglifyJsPlugin()
  ]
}

После


!function(o){function n(e){if(t[e])return t[e]. ....
Без sourcemap

sourcemap

example-app
└── webpack.config.js

module.exports = {
  ...
  
  devtool: 'source-map'
}
example-app
└─┬ public
  ├── todo.bundle.js
  └── todo.bundle.js.map
example-app
└─┬ public
  └── todo.bundle.js

...
//# sourceMappingURL=todo.bundle.js.map
  └── todo.bundle.js.map

{"version":3,"sources":["List","map","item"],"mappings":";;AAAA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;;AAEA;

До

Без sourcemap

После

с sourcemap
Бандлы
example-app
└── webpack.config.js

module.exports = {
...

  entry: {
    mainPage: './mainPage.js',
    innerPage: './innerPage.js'
  },
    output: {
    filename: "[name].bundle.js"
  }
  plugins: [
    new ExtractTextPlugin("[name].css")
  ]
}
Пример сервиса «TODO»
Спасибо за вниманеи
Вопросы