• Что происходит в этом примере?

    maxfarseer
    @maxfarseer
    https://maxpfrontend.ru, обучаю реакту и компании
    ::this.setState => this.setState.bind(this)

    В функцию subscribe передают функцию this.setState с контекстом this.
    Ответ написан
    Комментировать
  • Актуальны ли Каторовские уроки по NodeJs?

    maxfarseer
    @maxfarseer
    https://maxpfrontend.ru, обучаю реакту и компании
    А какие требования были? Если: видео, русский язык - то лучше курса я не знаю.
    Если же требования: видео/статьи, язык: русский/английский, то думаю да, после изучения первой части курса, лучше поискать боевой пример поновее. Но! Много не потеряете, если посмотрите вторую часть и поделаете на старой версии.
    Ответ написан
    Комментировать
  • Работает ли React в IE8?

    maxfarseer
    @maxfarseer
    https://maxpfrontend.ru, обучаю реакту и компании
    Тем не менее баги почему-то фиксят (последний пункт в этом блоке), так что видимо как никак работает.
    Ответ написан
    Комментировать
  • Как в связке Redux + React.js кешировать ответы сервера и где?

    maxfarseer
    @maxfarseer
    https://maxpfrontend.ru, обучаю реакту и компании
    Как вам уже написал Aves в компоненте перед вызовом экшена проверяйте, если данные уже есть - не делайте запрос. Выглядеть может так:

    loadData() {
      if (state.data.length) {
        // ничего не делай
      } else {
        this.props.callActionAndLoadData()
      }
    }
    Ответ написан
    Комментировать
  • Npm, Node, Webpack — как разобраться новичку?

    maxfarseer
    @maxfarseer
    https://maxpfrontend.ru, обучаю реакту и компании
    1. Странно, я до этого не встречал, чтобы node был разной версии в разных директориях. Давайте здесь поподробнее со скринами посмотрим.

    2 и 3 - предлагаю полностью удалить node, а затем установить себе NVM, и когда он установится через него установить последнюю и другие нужные версии node, если будет нужно (обычно хватает последней, но для старых проектов может потребоваться иная).

    4 - просто копируете конфиг вебпака из проекта в проекти пилите. Потом, например, вам приходит мысль: так, у этих пяти проектов были общие файлы такие-то, общая структура папок и вот такой параметр в конфиге. Как мне это обобщить? Как вынести в отдельную директорию, из которой я потом буду просто копировать готовый шаблон? (обычно нужно просто скопировать ;) ). Когда у вас будет готовый шаблон, вы приобщитесь к миру с возгласом: у меня есть свой boilerplate. Тогда можете глянуть на чужие (гуглите webpack boilerplate), но не берите сразу чужой просто так, а разберите по кусочкам.

    p.s. мне еще ниразу не пригодилось брать "просто для верстки" webpack. Для этих целей использую gulp + browserify, а когда пишу на реакте - webpack.

    p.p.s. что почитать? В кратце в гугле поищите следующее: что такое node.js, как npm с ним связан, что происходит когда вы выполняете npm install, как работает секция scripts в package.json.
    Ответ написан
    Комментировать
  • Как реализовать отправку отрендерившегося сообщения (React) чата в другой html-класс?

    maxfarseer
    @maxfarseer
    https://maxpfrontend.ru, обучаю реакту и компании
    Использовать flux/redux
    Использовать "события" (хоть custom Event , хоть EventEmitter (после заголовка Глобальная система событий), хоть jquery events)
    Ответ написан
    Комментировать
  • Почему webpack-dev-server ен обновляет страницу?

    maxfarseer
    @maxfarseer
    https://maxpfrontend.ru, обучаю реакту и компании
    в секции с лоадерами:
    {
            test: /\.js$/,
            loaders: ['react-hot', 'babel'],
            //include/exclude и тд
          },


    в секции с плагинами:
    plugins: [
        new WebpackNotifierPlugin, // и другие...
        new webpack.HotModuleReplacementPlugin()
      ],
    Ответ написан
    Комментировать
  • Почему React DevTools не появляется в браузере?

    maxfarseer
    @maxfarseer
    https://maxpfrontend.ru, обучаю реакту и компании
    Вы запускаете на неком сервере (mamp, webpack-dev-server и т.д.)? Dev tools не работает, если просто открыт html файл (адрес типа file:// )
    Ответ написан
    1 комментарий
  • Как отрендерить JSON в React.js?

    maxfarseer
    @maxfarseer
    https://maxpfrontend.ru, обучаю реакту и компании
    Всегда когда вам приходит какая-то структура, нужно пробегать ее полностью по каждому элементу, чтобы сгенерировать JSX-элемент. Для массивов обычно используют .map метод, для объектов - например Object.keys (так же подойдут конечно, и цикл forEach для массива, и цикл for in для объекта и т.д.)

    Так как у вас в JSON приходит не массив объектов, а объект объектов то вам нужно пробегать с помощью Object.keys, например, и потом еще применить map к получившемуся массиву ключей. Пример получившегося массива:

    6b13d2ad4f6241f4bb3c378385646082.jpg

    Применяем map:

    const template = Object.keys(data.books).map(item => <span key={data.books[item].id}>{data.books[item].author} - {data.books[item].name}</span>)


    В данном случае в переменной template окажутся 2 JSX-элемента (тэги span с данными). Вам скорее всего нужно, чтобы это было tr. Поменять не трудно)

    А json-loader вам нужен для того, чтобы webpack мог корректно импортировать такие файлы, так как пока json-лоадера нет, вебпак не знает как обрабатывать файлы с расширением .json. По такому же принципу вы подключаете css-loader, например, чтобы webpack мог "импортить цсски"
    Ответ написан
    1 комментарий
  • Как работает роутинг в связке angular2 + express?

    maxfarseer
    @maxfarseer
    https://maxpfrontend.ru, обучаю реакту и компании
    А как вы вообще планируете использовать express ? MEAN стэк подразумевает, что ваш бэкэнд - это только REST API, если не ошибаюсь. Следовательно - у вас все запросы на youhost.com/api/, например, идут в express и там он разруливает роуты, а все остальные запросы идут в клиентское приложение и там разруливает ангуляр.
    Ответ написан
    Комментировать
  • Не срабатывает onchange в React Datepicker?

    maxfarseer
    @maxfarseer
    https://maxpfrontend.ru, обучаю реакту и компании
    У вас на onChange функция, которая просто форматирует дату. И все, я даже затрудняюсь сказать, какой от этого будет эффект, но помоему никакой.

    Нужно на onChange прокидывать функцию - создатель действия, например, у нас это будет myAction.

    Следовательно, где-то там в actions будет примерно такой код:

    export function myAction(date) {
      return {
        type: 'CHANGE_DATE',
        data: date
      }
    }


    Затем где-то в редьюсере, должно быть соответствующее условие, например, на case 'CHANGE_DATE' у вас будет устанавливаться в current дата. (специально не пишу код, чтобы немного поработали сами).

    В конце концов, в вашем контейнере, у вас должны быть mapStateToProps и mapDispatchToProps примерно такого вида:

    const mapStateToProps = (state) => ({
      dates: state.dates,
    })
    
    const mapDispatchToProps = (dispatch) => ({
      myAction: (date) => dispatch(myAction(date)),
    })


    Ну и само подключение DayPickerSection будет выглядеть примерно так:

    <DayPickerSection selected={this.props.dates.current}  myAction={this.props.myAction} />


    А в рендере (имеется в виду в рендере DayPickerSection) будет следовательно:

    const DayPickerSection = ({selected, myAction}) => {
        return (
            <div>
                <DatePicker selected={selected}
                            onChange={(date) => myAction(date) }/>
            </div>
        );
    };


    Итого (читать медленно): в onChange компонента DatePicker попадет свойство this.props.myAction "прокинутое" в DayPickerSection. В свою очередь, в DayPickerSection это свойство будет равно функции, которая будет "прокинута" с помощью mapDispatchToProps в this.props контейнера. Контейнер, называйте его как хотите, в нашем случае это родительский элемент, например, который "приконекчен" (c помощью функции connect), и который из-за этого принимает к себе в this.props данные описанные в mapStateToProps и в mapDispatchToProps, которые в свою очередь являются чем то вроде карты. То есть, в конечном счете в this.props.myAction вашего контейнера попадет функция myAction из вашей папки actions. Конечно, для этого ее нужно импортировать в начале файла (import { myAction } from '../actions'.)

    Контейнер может выглядеть так:

    import React, { Component } from 'react'
    import { connect } from 'react-redux'
    import { myAction } from '../actions'
    
    class App extends Component {
      render() {
        const { dates, myAction } = this.props
    
        return (
          <div className='my-app'>
            <DayPickerSection selected={dates.current}  myAction={myAction} />
          </div>
        )
      }
    }
    
    const mapStateToProps = (state) => ({
      dates: state.dates,
    })
    
    const mapDispatchToProps = (dispatch) => ({
      myAction: (date) => dispatch(myAction(date)),
    })
    
    export default connect(mapStateToProps, mapDispatchToProps)(App)
    Ответ написан
    1 комментарий
  • Какие данные необходимо записывать в jwt (node.js + mongo)?

    maxfarseer
    @maxfarseer
    https://maxpfrontend.ru, обучаю реакту и компании
    Логин класть в токен не нужно. В токене не должно быть "личной информации", а вот ObjectId уже можно. Это не подходит "под личную", так как по 5821d94dbb021a1360582da3 нельзя узнать что-то о пользователе, если у вас не своровали базу (могу ошибаться).

    Приведу на всякий случай полный код роута с выдачей токена, если вам не особо пригодится, так может кто поругает, так как в бэкэнде не силен. Код не на промисах, а на коллбэках (как в древние времена). Это плохо. С помощью промисов будет "более плоский" и удобный в поддержке код.

    const express = require('express')
    const router = express.Router()
    const User = require('../models/user')
    const v4 = require('node-uuid').v4
    const jwt = require('jsonwebtoken')
    
    router.post('/signup', (req, res, next) => {
    
      req.check('email', 'Please enter a valid email').len(1).isEmail()
      req.check('password', 'Please enter a password with a length between 4 and 34 digits').len(4, 34)
    
      const errors = req.validationErrors()
    
      if (errors) {
        return res.status(400).json({ errors })
      } else {
        User.hashPassword(req.body.password, (err, passwordHash) => {
          if (err) {
            return res.status(400).json({ error: err.message })
          }
    
          const user = new User({
            name: req.body.name,
            nickname: req.body.nickname,
            email: req.body.email,
            password: req.body.password,
          })
    
          user.passwordHash = passwordHash
          user.save((err, item) => {
            if (err) {
              return res.status(400).json({ error: err.message })
            }
            const payload = {
              _id: item._id,
              iss: 'http://localhost:3000',
              permissions: 'poll',
            }
            const options = {
              expiresIn: '7d',
              jwtid: v4(),
            }
            const secret = new Buffer(process.env.AUTH0_CLIENT_SECRET, 'base64')
            jwt.sign(payload, secret, options, (err, token) => {
              return res.json({ data: token })
            })
          })
        })
      }
    })
    
    router.post('/signin', (req, res, next) => {
    
      req.check('email', 'Please enter a valid email').len(1).isEmail()
      req.check('password', 'Please enter a password with a length between 4 and 34 digits').len(4, 34)
    
      const errors = req.validationErrors()
      const password = req.body.password
    
      if (errors) {
        return res.status(400).json({ errors })
      } else {
        User.findOne({ email: req.body.email }, (err, user) => {
          if (err) {
            return res.status(400).json({ error: err.message })
          }
          if (!user) {
            return res.status(400).json({ error: 'User not found' })
          }
          User.comparePasswordAndHash(password, user.passwordHash, (err, areEqual) => {
            if (err) {
              return res.status(400).json({ error: err.message })
            }
            if (!areEqual) {
              return res.status(400).json({ error: 'Wrong password' })
            }
            const payload = {
              _id: user._id,
              iss: 'http://localhost:3000',
              permissions: 'poll',
            }
            const options = {
              expiresIn: '7d',
              jwtid: v4(),
            }
            const secret = new Buffer(process.env.AUTH0_CLIENT_SECRET, 'base64')
            jwt.sign(payload, secret, options, (err, token) => {
              return res.json({ data: token })
            })
          })
        })
      }
    })
    
    module.exports = router;


    В дальнейшем, кусочек payload:
    const payload = {
              _id: item._id,
              iss: 'http://localhost:3000',
              permissions: 'poll',
            }

    можно "декодировать" прямо на клиенте, что очень удобно.
    Ответ написан
    Комментировать
  • Что с react-datepicker?

    maxfarseer
    @maxfarseer
    https://maxpfrontend.ru, обучаю реакту и компании
    Наверное, забыли подключить файл со стилями.

    require('react-datepicker/dist/react-datepicker.css');
    Ответ написан
    7 комментариев
  • Datepicker React/Redux?

    maxfarseer
    @maxfarseer
    https://maxpfrontend.ru, обучаю реакту и компании
    Универсальный рецепт для любых сторонних компонентов.

    1. Сторонний компонент, если он хороший, обычно ничего не знает о данных. Он их только отображает. Это то что нам нужно.

    2. Раз компонент ожидает от нас данные, мы должны их в него передать. Например в контейнере СтраницаБилетов, у вас подключается компонент ВыборДаты. Супер, чтобы в ВыборДаты передать данные используются props. Будет примерно так:
    <ВыборДаты текущаяДата={Дата_из_стора/или роута} />

    либо EN вариант:
    <DatePicker current={myDate} />

    3. Далее, у стороннего компонента (опять же, если он хороший) наверняка, есть какой-то onChange обработчик. Предположим, что он называется onDateChange. Следовательно, мы в контейнере подключаем AC (создатель действия, экшен), и передаем его в компонент. Выглядеть будет примерно так:
    <DatePicker onDateChange={this.props.getTickets} />


    либо русский вариант (просто для понимания, вдруг пригодится)
    <ВыборДаты текущая={мояДата} onDateChange={this.props.загрузиБилеты} />


    Причем, функция загрузки новых билетов для этой даты, это не что иное, как экспортируемая функция из папки с вашими экшенами.
    ---

    Приведу пример с кодом, хотя так как я его вытащил с реального проекта, он может вас запутать некоторой сложностью:

    Это мой компонент DayPicker, который внутри себя подключает компонент DatePicker
    class DayPicker extends Component {
      constructor(props) {
        super(props)
        this.state = {
          start: null,
        }
      }
      componentWillMount() {
        const { startTime } = this.props
        this.setState({ start: startTime })
      }
      componentWillReceiveProps(nextProps) {
        const { startTime } = nextProps
        this.setState({ start: startTime })
      }
      render() {
        const { start } = this.state
        const { onDateChange } = this.props
    
        return (
          <section>
            <div className='date-picker'>
              <DatePicker
                className='form-control'
                placeholderText={'Введите дату'}
                selected={moment(start)}
                onChange={ (moment) => onDateChange('startTime', moment) }/>
            </div>
          </section>
        )
      }
    }


    Тем не менее, это компонент. И он должен как-то получать в себя данные. Это происходит в контейнере, кода там много, приведу кусочек:

    // обработчик onDateChange
    onDateChange(dateField, moment) {
      const query = this.props.location.query
      const nextQuery = {
        ...query,
        [dateField]: moment.format('YYYY-MM-DD'),
      }
      // так как у меня все завязано на роутинге, здесь другой вызов ниже,
      // но для простоты примеры, здесь необходимо вызвать экшен, например getTickets(dateField)
    }
    
    // это находится в render функции
    const { startTime } = this.props
    
    <div className='row row-margin'>
      <div className='col-md-12'>
        <DayPicker
          onDateChange={this.onDateChange}
          startTime={startTime}
        />
      </div>
    </div>
    
    // ваш экшен getTickets(dateField) наверняка подключается так
    import { getTickets } from '../../actions/TicketsActions'
    //...
    const mapDispatchToProps = (dispatch) => ({
      getTickets: (dateField) => dispatch(getUsers(dateField)),
    })
    //...
    
    // ваша переменная startTime наверняка приходит из редьюсера / роута, у меня из роута
    const mapStateToProps = (state, ownProps) => ({
      startTime: ownProps.location.query.startTime,
    })
    Ответ написан
    8 комментариев
  • Как поставить 2 redux thunk на разные редюсеры?

    maxfarseer
    @maxfarseer
    https://maxpfrontend.ru, обучаю реакту и компании
    Не совсем понял ваш вопрос, но thunk middleware никак не влияет на редьюсер.

    Redux-thunk - это всего лишь вспомогательная функция для ваших экшен-крейтеров (или просто экшенов, как удобнее). Если посмотреть на исходный код, то можно описать его так: если AC (экшен, экшен крейтор, создатель действия...) возвращает простой объект - прокидывай его дальше, если возвращает функцию, то "прокинь" в эту функцию dispatch и getState функции в качестве аргументов. Например, это дает нам возможность работать с асинхронными запросами. Так как, чтобы достучаться до reducer'a мы же должны использовать функцию dispatch. А если бы не было redux-thunk, то откуда бы она взялась у нас в коде экшена: импорта в файл нет, нигде не описано, что const dispatch = ...?

    Приведу пример:

    export function logout {
      return {
        type: LOGOUT_SUCCESS,
      }
    }


    AC возвращает простой объект, который (если нет других middleware влияющих на работу) - сразу попадает в reducer.

    export function login(data) {
      return dispatch => {
        dispatch({ type: LOGIN_REQUEST })
    
        return request.post(`${API_ROOT_V1}/auth/signin`)
          .send(data)
          .then(res => {
            if (!res.ok) {
              dispatch({ type: LOGIN_FAILURE })
              dispatch(showNotification({
                status: 'err',
                text: 'something going wrong',
              }))
    
            } else {
              dispatch({
                type: LOGIN_SUCCESS,
                data: res.body.data,
              })
              localStorage.setItem('cks_token', res.body.data)
              dispatch(push('/'))
            }
          }, err => {
            dispatch({ type: LOGIN_FAILURE })
            dispatch(showNotification({
              status: 'err',
              text: err.body && err.body.error || err.message,
            }))
          })
      }
    }


    Особое внимание на return dispatch => { - это значит, что АС возвращает функцию, а следовательно в этой функции из-за redux-thunk доступен dispatch. Если вводит в заблуждение ES2015 синтаксис, то это можно было бы написать как return function(dispatch) { ...

    P.S. Опишите подробнее, что вы имеете в виду под "А как разделить логику обработки?"
    Ответ написан
    Комментировать
  • React + Redux, как начать правильно?

    maxfarseer
    @maxfarseer
    https://maxpfrontend.ru, обучаю реакту и компании
    Как с этим работать, чтобы не сойти с ума?

    Не торопиться, если хочется вдумчиво разобраться =)

    Первая проблема, как принять данные?

    Использовать react lifecycle hooks: в момент componentDidMount (или в componentWillMount) вызвать actionCreator (AC = создатель действия) -> в AC вы генерируете событие с типом REQUEST (которое ловит ваш редьюсер), а затем производите "ajax запрос", скажем так. В случае удачного результата - генерируете событие SUCCESS и ваш редьюсер устанавливает данные. Далее, ваш компонент формы, видит, что есть новые "props" (свойства, которые изменились в редьюсере, о которых компонент узнал, так как он приконекчен с помощью функции "connect" из библиотеки react-redux) и запускается перерендер. Вуаля, после такой кучи действий ваша форма "приняла данные".

    Конечно, если просто сделать нативный xhr запрос, кода будет гораздо меньше и все в одном файле, но это уже нужно исходить из ваших пожеланий. Если вам нужно, чтобы данные были "прогнаны" через редьюсер и оказались в store, значит "много действий". Если не нужно - просто обычный xhr запрос, либо если угодно $.ajax и установка данных в state компонента.

    Например, на onchange селекта мне нужно брать данные с него, что-то считать и выводить в какой-то инпут... какая здесь цепочка действий, и главное в каких файлах?


    Если используется redux, то цепочка следующая: на onChange селекта вызывается обработчик, то есть функция в вашем компоненте, которая вызывает AC (создатель действия, который располагается в папке actions). Далее уходит запрос на сервер. Затем с сервера приходит успешный ответ - вы генерируете событие, типа "DATA_RESPONSE_SUCCESS" и ваш редьюсер его "ловит". Итак, вы оказываетесь в третьем файле - в файле с редьюсером. Там вы манипулируете данными, так как вместе с типом события, вы так же должны были передать из action'а и данные, которые пришли с сервера. После того, как вы установите новые данные в редьюсере - начинается магия (которая заключается в том, что ваш компонент слушает изменения в объекте стора, с помощью функции connect). Ваш компонент перерендеривается и в инпуте оказываются нужные данные, так как инпут в качестве value использует, например: this.props.myNewValueFromServerAfterSelectManipulation (имя переменной, конечно, шуточное).

    Итого: вы потрогали файл компонента, файл из папки actions и файл из папки reducers.

    Супер итого:
    1) вам нужно понять, как сделать форму без redux. Как в ней с помощью this.state и методов жизненого цикла устанавливать и изменять данные в зависимости от того, что выбрал/ввел пользователь. Сделать это не сложно, если начать с туториала на официальной странице, либо заглянуть в РУ туториал здесь.

    2) вам нужно понять, почему в actions располагаются асинхронные запросы, и как при этом работает redux-thunk (а так же middlewares в общем). Почему в reducers производится только изменение данных. И самое главное, почему компонент при этом перерисовывается. Опять же, ссылки на официальные руководства уже дали. Русский перевод там не закончен, поэтому, хоть и устаревают версии библиотек, этот туториал по редуксу до сих пор актуален.

    P.S. в туториалах есть примеры с кодом
    Ответ написан
    3 комментария
  • Нужен ли React без Flux?

    maxfarseer
    @maxfarseer
    https://maxpfrontend.ru, обучаю реакту и компании
    Я думаю, если React использовать без Flux/Redux и подобных, то ваши заказы - это какие-то плагины.
    Исходя из своего опыта, могу сказать, что задачи на разработку плагинов под реакт - редкость.

    В любом случае, "flux" вы поймете и обкатаете еще раньше, чем сможете сказать: я 100% знаю реакт и могу его использовать для сложных задач.
    Ответ написан
    Комментировать
  • Накидайте больших примеров react & redux приложений?

    maxfarseer
    @maxfarseer
    https://maxpfrontend.ru, обучаю реакту и компании
    Не знаю насколько большие, но внимание уделить им стоит:

    https://github.com/WebbyLab/itsquiz-wall
    www.robinwieruch.de/the-soundcloud-client-in-react... (git)

    в остальном: разрастается папка с экшенами и редьюсерами, появляется папка с какими-то хелперами (функциями, которые используются в разных компонентах, например форматирование даты в ЧЧ:ММ:СС из секунд...), появляется больше зависимостей (как и на всех других проектах). Приходится писать больше тестов и сборка/пересборка происходит медленнее.
    Ответ написан
  • Как работает серверный рендеринг React.js с объектами браузера в компонентах?

    maxfarseer
    @maxfarseer
    https://maxpfrontend.ru, обучаю реакту и компании
    Могу быть не прав, но я так понял, что все объекты браузера мокаются (как будто в тестах, то есть делается например "ложный localStorage", ведь это по сути объект у котрого есть getter и setter. Для тестов я использовал - mock-local-storage). "Мокнуть" можно и самому, для этого писать нужно в global, так как в node окружении глобальный объект не "window". Подробнее можно посмотреть в коде библиотеки.

    Опять таки, давайте продолжим про localStorage, так как удобнее на конкретном примере. Почему мок нормально подойдет, как мне кажется? Потому что это будет как будто к вам на сайт зашел пользователь, у которого ничего в LS по вашему сайту не сохранено. Главное чтобы сработало: localStorage.setItem / getItem, и так как вы эти функции написали и объявили глобально - все получится (естественно, в коде у вас должно быть localStorage без window, либо нужно будет делать мок не просто в global, а в global.window - иными словами мы создаем в глобальом объекте объект window).

    P.S. на практике я не делал серьезного SS рендеринга, поэтому буду рад услышать, мнение тех, кто уже разобрался. В зарубежных источниках есть информация (например), и даже неплохо разобраны вещи с асинхронными запросами, но тем не менее, хотелось бы увидеть еще.
    Ответ написан
    2 комментария
  • Webpack годится в продакшн?

    maxfarseer
    @maxfarseer
    https://maxpfrontend.ru, обучаю реакту и компании
    Добрый день, как уже было сказано выше - задача webpack'a собрать файл (со стилями и js, или только js), который называется в общем случае "бандл". Для этого, бандл нужно собрать (сделать билд) с помощью production конфига для webpack. Продакшен конфиг обычно включает в себя различные "штуки" для оптимизации конечного размера бандл файла, ведь чем меньше - тем лучше.

    Далее бандл вы выкладываете к себе на сервер и отдаете как обычный статичный файл. В таком случае, после изменений кода, вы должны будете:
    а) заново сделать билд (собрать новый бандл)
    б) перезаписать файл на сервере

    Обычно в проекте есть 2 конфига: dev (девелоперский, то есть конфиг удобный для разработчика) и prod (конфиг, в котором главное - пожать файл как можно сильнее).

    В dev конфиге, например, часто используют webpack-dev-server, который позволяет пользоваться такой штукой как HMR (hot module replacement), которая в свою очередь позволяет очень комфортно разрабатывать: вы изменяете что-то в файле, и у вас сразу в открытом окне браузера отображаются изменения (без перезагрузки страницы).

    В prod конфиге такое конечно не нужно.

    Пример команды, для того, чтобы сделать prod бандл:
    NODE_ENV=production webpack --progress --config ./webpack.prod.config.js -p


    Главное здесь -p, так как эта опция говорит вебпаку, что необходимо сделать продакшен-сборку. Так же, в этой команде указан специальный конфигурационный файл (webpack.prod.config.js) и переменная NODE_ENV имеет значение 'production'

    Поэтому, точнее будет ответить: с вебпаком можно собрать файл скриптов (и, если настроено, стилей) для продакшена.
    Ответ написан
    Комментировать