Инструменты сборки

Лекция 26

Инструменты сборки

  • транспиляция js
  • компиляция стилей
  • компиляция шаблонов
  • сборка html
  • сборка спрайтов
  • тестирование
  • деплой
  • ...

Инструменты сборки

  • локальная
  • удаленная

Инструменты сборки

  • таск-ранеры (grunt, gulp, branch)
  • сборщики (webpack, rollup, browserify)

Webpack

Webpack - JS


const hello = name => console.log(`hello ${name}`);

hello('stranger');
      

Webpack - JS


npm i --save-dev webpack
npm i --save-dev webpack-cli
npm i --save-dev babel-core
npm i --save-dev babel-loader
npm i --save-dev babel-preset-env
      

Webpack - JS


// webpack.config.js
module.exports = {
  mode: 'development',
  entry: './app.js',
  output: {
    path: '.',
    filename: 'app.es5.js',
  },
  module: {
    rules: [{
      test: /\.js$/,
      exclude: /(node_modules)/,
      loader: 'babel-loader',
      options: {
        presets: ['babel-preset-env']
      }
    }]
  }
};
      

Webpack - JS


... код обёртки

var hello = function hello(name) {
  return console.log('hello ' + name);
};

hello('stranger');

... код обёртки
      

Webpack - React


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

const Hello = props => (
  <div>hello {props.name}</div>
);

ReactDOM.render(
  document.getElementById('app'),
  <Hello name="stranger"/>
);
      

Webpack - React


npm i react react-dom
npm i --save-dev babel-preset-react
      

Webpack - React


// webpack.config.js
module.exports = {
  ...
  module: {
    rules: [{
      ...
      options: {
        presets: [
          'babel-preset-env',
          'babel-preset-react',
        ]
      }
    }]
  }
};
      

Webpack - React


... код обёртки

var _react = __webpack_require__(/*! react */ \"./node_modules/react/index.js\");
var _react2 = _interopRequireDefault(_react);
var _reactDom = __webpack_require__(/*! react-dom */ \"./node_modules/react-dom/index.js\");
var _reactDom2 = _interopRequireDefault(_reactDom);

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

...
      

Webpack - React


...

var Hello = function Hello(props) {
  return _react2.default.createElement(
    'div',
    'hello ',
    props.name
  );
};

_reactDom2.default.render(
  document.getElementById('app'),
  _react2.default.createElement(
    Hello,
    { name: 'stranger' }
  )
);

... код обёртки
      

Webpack - Entry


entry: './path/to/my/entry/file.js'
      

Webpack - Entry


entry: {
  app: './src/app.js',
  vendors: './src/vendors.js'
}
      

entry: {
  pageOne: './src/pageOne/index.js',
  pageTwo: './src/pageTwo/index.js',
  pageThree: './src/pageThree/index.js'
}
      

Webpack - Output


output: {
  filename: 'bundle.js',
  path: '/home/proj/public/assets'
}
      

Webpack - Output


entry: {
  app: './src/app.js',
  search: './src/search.js'
},
output: {
  filename: '[name]-[hash].js',
  path: __dirname + '/dist'
}
      

Webpack - Mode


/* development:
- process.env.NODE_ENV='development'
- NamedModulesPlugin - относительные пути модулей
*/
      

Webpack - Mode


/* production:
- process.env.NODE_ENV='production'
- UglifyJsPlugin
- ModuleConcatenationPlugin
- NoEmitOnErrorsPlugin - фейл при ошибках
*/
      

Webpack - Loaders


// работают на уровне файла
module: {
  rules: [
    { test: /\.css$/, use: 'css-loader' },
    { test: /\.ts$/, use: 'ts-loader' }
  ]
}
      

Webpack - Loaders


// babel-preset-env
- babel-preset-latest
  or
- babel-preset-es2015
- babel-preset-es2016
- babel-preset-es2017
      

Webpack - Loaders


// babel-preset-env
"presets": [
  ["env", {
    "targets": {
      "browsers": [
        "last 2 versions",
        "safari >= 7"
      ]
    }
  }]
]
      

Webpack - Loaders


// babel-preset-stage-0
- stage 0-3 features
      

Webpack - Loaders


// babel-preset-stage-0

::this.func
this.func.bind(this)
      

Webpack - Loaders


// babel-preset-stage-0

export * as ns from 'mod';
      

Webpack - Loaders


// babel-preset-stage-0

let { x, y, ...z } = { x: 1, y: 2, a: 3, b: 4 };
console.log(x); // 1
console.log(y); // 2
console.log(z); // { a: 3, b: 4 }
      

Webpack - Plugins


// работают на уровне бандла
plugins:[
  new webpack.DefinePlugin({
    'REPLACE_TEXT': 'TO_THIS'
  })
]
      

Webpack - Resolution


import foo from 'path/to/module'
// or
require('path/to/module')
      

Webpack - HMR


npm i --save-dev webpack-dev-server
      

Webpack - HMR


// webpack.config.js
const webpack = require('webpack');

module.exports = {
  mode: 'development',
  entry: './app.js',
  devtool: 'inline-source-map',
  devServer: {
    contentBase: './dist',
    hot: true
  },
  ...
      

Webpack - HMR


// webpack.config.js
  ...
  module: {
    rules: [{
      test: /\.js$/,
      exclude: /(node_modules)/,
      loader: 'babel-loader',
      options: {
        presets: [
          'babel-preset-env',
          'babel-preset-react'
        ]
      }
    }]
  },
  ...
      

Webpack - HMR


// webpack.config.js
  ...
  plugins: [
    new webpack.NamedModulesPlugin(),
    new webpack.HotModuleReplacementPlugin()
  ],
  output: {
    filename: 'app.js',
    path: __dirname + '/dist'
  }
};
      

Webpack - HMR


// app.js
import React from 'react';
import ReactDOM from 'react-dom';

import { Hello } from './hello';

ReactDOM.render(
  <Hello />,
  document.getElementById('app')
);

if (module.hot) {
  module.hot.accept();
}
      

Webpack - HMR


// hello.js
import React from 'react';

export class Hello extends React.Component {
	render() {
		return (
			
Hello Stranger!
); } }

Webpack - Code splitting


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

const Hello = props => (
  <div>hello {props.name}</div>
);

ReactDOM.render(
  document.getElementById('app'),
  <Hello name="stranger"/>
);
      

Webpack - Code splitting


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

const Hello = props => (
  <div>hello {props.name}</div>
);

ReactDOM.render(
  document.getElementById('app'),
  <Hello name="ranger"/>
);
      

Webpack - Code splitting


module.exports = {
  mode: 'development',
  entry: {
    app: './app.js',
    app2: './app-2.js'
  },
  module: {
    rules: [{
      test: /\.js$/,
      exclude: /(node_modules)/,
      loader: 'babel-loader',
      options: {
        presets: ['babel-preset-env', 'babel-preset-react']
      }
    }]
  },
  ...
      

Webpack - Code splitting


  ...
  output: {
    filename: '[name].js',
    path: __dirname + '/dist'
  },
  optimization: {
    splitChunks: {
      cacheGroups: {
        commons: {
          test: /[\\/]node_modules[\\/]/,
          name: "vendors",
          chunks: "all"
        }
      }
    }
  },
};
      

Gulp

Gulp


npm i gulp --save-dev
      

Gulp


// gulpfile.js
const gulp = require('gulp');

gulp.task('hello', () => {
  console.log('hello world');
});
      

Gulp


npx gulp hello
      

Gulp - Dependencies


gulp.task('task1', () => {
  console.log('task1');
});

gulp.task('task2', () => {
  console.log('task2');
});

gulp.task('hello', ['task1','task2'], () => {
  console.log('hello world');
});
      

Gulp - src & dest


gulp.task('copy', () => {
  return gulp
    .src('./js/app/index.js')
    .dest('./js/app/copy.js');
});
      

Gulp - globs


gulp.task('bundle__js', () => {
    return gulp
      .src([
        './js/**/*.js',
        '!./js/**/*.min.js'
      ])
      .pipe(...);
});
      

Gulp - watch


gulp.task('watch__js', () => {
  return gulp.watch(
    'js/**/*.js',
    ['uglify','reload']
  );
});
      

Gulp - IRL


const gulp = require('gulp');
const browserSync = require('browser-sync')
  .create();

const sass = require('gulp-sass');
const csso = require('gulp-csso');
const autoprefixer = require('gulp-autoprefixer');

const webpackStream = require('webpack-stream');

const sourcemaps = require('gulp-sourcemaps');
const rename = require('gulp-rename');
const concat = require('gulp-concat');
const gutil = require('gulp-util');
const watch = require('gulp-watch');

const webpackConfig = require('./webpack.config');
      

Gulp - IRL


gulp.task('init__browser-sync', () => {
  browserSync.init({
    server: './public',
    open: 'local'
  });
});
      

Gulp - IRL


gulp.task('compile__js', () => {
  return gulp
    .src('./js/app/index.js')

    .pipe(webpackStream(webpackConfigProd))
    .on('error', function (error) {
      browserSync.notify('Error bundling JSX', 5000);

      gutil.log('\n\n',
        gutil.colors.red(error.stack),
        '\n\n');

      this.emit('end');
    })

    .pipe(rename('app.js'))
    .pipe(gulp.dest('./public/js'))
});
      

Gulp - IRL


gulp.task('bundle__js', () => {
  return gulp
    .src(['./js/**/*.js', '!./js/app/**/*'])
    .pipe(gulp.dest('./public/js'));
});
      

Gulp - IRL


gulp.task('build__js', ['compile__js', 'build__js'], () => {
  browserSync.reload();
});
      

Gulp - IRL


gulp.task('watch__js', () => {
  watch(
    './js/**/*.js',
    { ignoreInitial: false },
    () => {
      gulp.start('build__js')
    }
  );
});
      

Gulp - IRL


gulp.task('compile__css', () => {
  return gulp.src(`./sass/*.scss`)

    .pipe(sass())
    .on('error', function (error) {
      browserSync.notify('Error on compiling SCSS', 5000);
      gutil.log('\n\n', gutil.colors.red(error.stack), '\n\n');
      this.emit('end');
    })

    .pipe(autoprefixer())
    .pipe(csso())

    .pipe(concat(`site.css`))
    .pipe(gulp.dest('./public/css'))
});
      

Gulp - IRL


gulp.task('bundle__css', () => {
  return gulp
    .src('./css/**/*.css')
    .pipe(gulp.dest('./public/css'));
});
      

Gulp - IRL


gulp.task('build__css',
  ['compile__css', 'bundle__css'],
  () => {
    gulp.src('./public/css/**/*.css')
      .pipe(browserSync.stream({once: true}));
});
      

Gulp - IRL


gulp.task('watch__css', () =>  {
  watch('./sass/**/*.scss',
    { ignoreInitial: false },
    () => {
      gulp.start('build__css');
    }
  );
});
      

Gulp - IRL


gulp.task('build__html', () => {
  return gulp
    .src('./html/**/*.html')
    .pipe(gulp.dest('./public'))
    .pipe(browserSync.reload({stream:true}))
});
      

Gulp - IRL


gulp.task('watch__html', () => {
  watch('./html/**/*.html',
    { ignoreInitial: false },
    () => {
      gulp.start('build__html');
    }
  );
});
      

Gulp - IRL


gulp.task('build__images', () => {
  return gulp
    .src('./images/**/*')
    .pipe(gulp.dest('./public/images'))
    .pipe(browserSync.reload({stream:true}))
});
      

Gulp - IRL


gulp.task('watch__images', () => {
  watch('./images/**/*', { ignoreInitial: false }, () => {
    gulp.start('build__images');
  });
});
      

Gulp - IRL


gulp.task(
  'development',
  [
    'init__browser-sync',
    'watch__js',
    'watch__css',
    'watch__html',
    'watch__images'
  ],
  () => {}
);
      

npm scripts

npm scripts


// package.json
"scripts": {
  "jshint": "jshint src/js/*.js",
  "browsersync": "browser-sync start --server --files 'src/*'",
  "uglify": "uglify src/js/**/*.js > dist/js/script.min.js",
  "test": "mocha",
  "clean": "rm -rf dist",
  "sass:dist": "sass src/css/style.scss > dist/css/style.min.css",
  "htmlmin": "htmlmin -cs dist/index.html tmp/index.html",
  "imagemin": "imagemin src/images/* dist/images/* -p",
  "build:js": "npm run jshint && npm run uglify",
  "build": "npm run clean && npm run htmlmin && npm run build:js && npm run sass && npm runimagemin",
  "watch": "watch 'npm run build' ."
},
      

npm scripts


npm run build