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

Я уже некоторое время профессионально занимаюсь Vue. Как обычно, всякий раз, когда я создаю/настраиваю новый проект, я использую Vue CLI, который абстрагируется от большого веса создания нового проекта, и это здорово, особенно если вы когда-либо пытались настроить полную конфигурацию Webpack самостоятельно. То же самое применимо, если вы используете другие интерфейсы командной строки для создания своего проекта. Тем не менее, я хочу самостоятельно пройти процесс настройки конфигурации Webpack и выделить некоторые преимущества и недостатки использования Vue CLI. Я постараюсь максимально имитировать поведение Vue CLI, но, например, избегаю использования его плагинов.

Почему я хочу это сделать? Ну, как своего рода документация/изучение для себя (я не могу вспомнить, когда в последний раз я просматривал каждый загрузчик Webpack и настраивал его, и это здорово, не поймите меня неправильно), а также, может быть, чтобы помочь людям понять полный процесс и проблемы, связанные с такой настройкой (чтобы вы могли понять, почему интерфейсы командной строки отлично справляются с нашей задачей).

Контент будет разделен между Webpack, Vue и улучшениями. Сначала я рассмотрю Webpack и его концепции, сосредоточившись больше на примерах, чем на теории (Документация Webpack отлично подходит, если нам нужно глубоко погрузиться в некоторые концепции). После этого я хочу иметь минимальную конфигурацию Webpack для создания проекта Vue. В конце я рассмотрю некоторые улучшения, как я решил назвать это, в моей конфигурации Webpack. Под улучшениями я подразумеваю все, что улучшает опыт разработчиков или добавляет изюминку в нашу сборку (например, сжатие файлов или их разделение…).

Вебпак

Скорее всего, вы слышали о Webpack раньше, если нет, загляните на их официальный сайт, на котором есть тонны документации по этому поводу. Я думаю, что это изображение с официального веб-сайта довольно хорошо резюмирует то, что Webpack делает для нас:

Как только вы начнете читать документацию, вы обнаружите, что первые две зависимости, которые вам нужно установить для использования Webpack, — это webpack и webpack-cli. Отсюда мы можем начать настраивать нашу конфигурацию Webpack, которая сообщает Webpack, как он должен обрабатывать наши файлы.

Конфигурация Webpack начинается со свойства entry, которое указывает файл записи, который будет обрабатываться с помощью Webpack. Давайте создадим следующий файл webpack.config.js, который сообщает Webpack, что точкой входа является ./src/main.js:

module.exports = {
  entry: './src/main.js',
};

Если мы добавим любой код Javascript в main.js, он будет обработан Webpack, и вывод будет помещен в папку dist/ (по умолчанию). Для запуска Webpack нам просто нужно npx webpack --config webpack.config.js.

Он работает (если у нас создан файл main.js), но жалуется (предупреждает), что свойство mode не установлено. Из документов Webpack свойство mode:

Предоставление параметра конфигурации mode указывает веб-пакету использовать встроенную оптимизацию соответствующим образом.

Свойство mode может быть production, development или none. Чтобы установить свойство mode, мы можем просто передать --mode=MODE_WE_WANT нашему вызову npx webpack. Чтобы сделать вещи более структурированными, мы можем добавить две команды в наши сценарии NPM, одну для сборки для производства, а другую для разработки:

"scripts": {
  "build:production": "webpack --config webpack.config.js --mode=production",
  "build:development": "webpack --config webpack.config.js --mode=development"
}

Хорошо, теперь мы можем добавить код Javascript в main.js и построить его. Не очень полезно, но это базовая настройка, и мы заставили ее работать.

Погрузчики

Загрузчики — это волшебство Webpack. Они позволяют нам обрабатывать разные типы файлов по-разному, и результатом будет один (или несколько) связанных файлов. Из документов загрузчики определяются как:

webpack позволяет использовать загрузчики для предварительной обработки файлов. Это позволяет объединять любые статические ресурсы, выходящие за рамки JavaScript.

Когда мы разрабатываем для Интернета, у нас обычно возникает одна главная проблема: она должна работать в разных браузерах. Это означает, что он должен использовать резервные функции, которых нет в старых браузерах. Проектом, выполняющим такую ​​работу, является Babel, и мы можем через загрузчик использовать его с Webpack. Мы можем указать Webpack обрабатывать файлы Javascript с помощью Babel, который выполнит компиляцию нашего кода.

Чтобы использовать Babel с Webpack, нам нужно настроить его загрузчик. Из документации загрузчика Babel мы получаем следующую конфигурацию Webpack:

module.exports = {
  entry: './src/main.js',
  module: {
    rules: [{
      test: /\.js$/,
      exclude: /node_modules/,
      use: {
        loader: 'babel-loader',
      },
    }],
  },
};

Давайте сначала сделаем шаг назад. Если файл main.js содержит что-то, что не нужно компилировать, это не будет иметь большого значения. Например, если он содержит console.log('Hello World!'), вывод будет одинаковым с загрузчиком или без него. Становится интереснее, когда мы начали добавлять функции, которые поддерживаются не во всех браузерах.

Сначала давайте настроим файл конфигурации Babel, также известный как babelrc, который позволяет нам использовать специальный синтаксис, если мы хотим, или использовать хорошо известный @babel/preset-env, который:

 – это интеллектуальная предустановка, позволяющая использовать последнюю версию JavaScript без необходимости микроуправления тем, какие преобразования синтаксиса (и, при необходимости, полифиллы браузера) необходимы вашей целевой среде (средам). Это упростит вашу жизнь и уменьшит размер пакетов JavaScript!

Необходима следующая настройка:

{
  "presets": ["@babel/preset-env"]
}

Теперь наш проект готов к компиляции «нового» синтаксиса Javascript. В качестве примера возьмем следующий код:

async function hey() {
  await Promise.resolve();
  return 'Hey!';
}
hey();

Он использует новый синтаксис, который не будет работать в старых браузерах. Если мы создадим наш файл без конфигурации Babel, вывод будет примерно таким:

eval("async function hey() {\n  await Promise.resolve();\n  return 'Hey!';\n}\n\nhey();\n\n//# sourceURL=webpack:///./src/main.js?");

Если мы настроим конфигурационный файл Babel, вывод будет таким:

eval("function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { Promise.resolve(value).then(_next, _throw); } }\n\nfunction _asyncToGenerator(fn) { return function () { var self = this, args = arguments; return new Promise(function (resolve, reject) { var gen = fn.apply(self, args); function _next(value) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, \"next\", value); } function _throw(err) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, \"throw\", err); } _next(undefined); }); }; }\n\nfunction hey() {\n  return _hey.apply(this, arguments);\n}\n\nfunction _hey() {\n  _hey = _asyncToGenerator( /*#__PURE__*/regeneratorRuntime.mark(function _callee() {\n    return regeneratorRuntime.wrap(function _callee$(_context) {\n      while (1) {\n        switch (_context.prev = _context.next) {\n          case 0:\n            _context.next = 2;\n            return Promise.resolve();\n\n          case 2:\n            return _context.abrupt(\"return\", 'Hey!');\n\n          case 3:\n          case \"end\":\n            return _context.stop();\n        }\n      }\n    }, _callee);\n  }));\n  return _hey.apply(this, arguments);\n}\n\nhey();\n\n//# sourceURL=webpack:///./src/main.js?");

Хорошо, давайте сначала разобьем его на более мелкие части. Первый код — это, по сути, копия и вставка кода, который мы написали. Во втором есть какая-то магия, поэтому мы не видим, например, использование async/await. @babel/preset-env здесь выполняет свою работу, нам не нужно было говорить ему, что мы будем использовать определенную функцию. Теперь вопрос, который мы могли бы задать: что произойдет, если я захочу поддерживать только те браузеры, которые поддерживают async/await?

Ответ: Browserslist. Вы могли видеть файл с именем .browserslistrc в корне проекта или внутри package.json. Этот файл отвечает за указание, какие браузеры вы хотите поддерживать, и используется несколькими инструментами для объединения вашего кода. Бабель — один из них. Если вы запустите npx browserslist в своем проекте, вы увидите список браузеров, которые он должен поддерживать. По умолчанию на момент написания этой статьи список выглядит следующим образом (настройка defaults из browserslist):

and_chr 81
and_ff 68
and_qq 10.4
and_uc 12.12
android 81
baidu 7.12
chrome 83
chrome 81
chrome 80
edge 83
edge 81
edge 18
firefox 78
firefox 77
firefox 76
firefox 68
ie 11
ios_saf 13.4-13.5
ios_saf 13.3
ios_saf 12.2-12.4
kaios 2.5
op_mini all
op_mob 46
opera 69
opera 68
safari 13.1
safari 13
samsung 12.0
samsung 11.1-11.2

Здесь мы можем настроить наш собственный файл .browserslistrc в современных браузерах, например last 2 Chrome versions, и снова запустить нашу сборку. Вывод будет следующим:

eval("async function hey() {\n  await Promise.resolve();\n  return 'Hey!';\n}\n\nhey();\n\n//# sourceURL=webpack:///./src/main.js?");

Поскольку последние 2 версии Chrome поддерживают async/await, Babel не будет транспилировать его, поэтому мы получим «современный» код. В качестве примечания: если вы не уверены на 100%, какие браузеры вам нужно поддерживать и так далее, оставьте значения по умолчанию и не связывайтесь со своим пакетом.

Теперь все становится интереснее. Мы уже используем магию Webpack для обработки наших файлов. Теперь мы собираемся изучить другую концепцию Webpack, называемую плагинами, которые добавят больше возможностей нашей сборке.

Плагины

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

Давайте возьмем пример из Vue CLI, переменных среды. Вы можете определить некоторые переменные среды, они должны начинаться с VUE_APP, и они будут доступны внутри вашего приложения.

Если мы хотим сделать что-то подобное, скажем, мы хотим определить BASE_URL как переменную среды, для этого нам нужно использовать плагин. Webpack предоставляет плагин под названием DefinePlugin, который allows you to create global constants which can be configured at compile time..

Конфигурация Webpack будет выглядеть так:

plugins: [
  new webpack.DefinePlugin({
    'process.env': {
      BASE_URL: JSON.stringify('localhost'),
    },
  }),
],

Теперь мы можем использовать process.env.BASE_URL внутри нашего кода, и он будет заменен на 'localhost'. Vue CLI выполняет дополнительную работу и позволяет вам определить .env файлов, что пока выходит за рамки.

Окончательная конфигурация

Наш окончательный конфиг, после всех вышеперечисленных настроек, будет выглядеть следующим образом:

const webpack = require('webpack');
module.exports = {
  entry: './src/main.js',
  module: {
    rules: [{
      test: /\.js$/,
      exclude: /node_modules/,
      use: {
        loader: 'babel-loader',
      },
    }],
  },
  plugins: [
    new webpack.DefinePlugin({
      'process.env': {
        BASE_URL: JSON.stringify('localhost'),
      },
    }),
  ],
};

Пока мы будем обрабатывать файл src/main.js, он будет использовать DefinePlugin для замены process.env.BASE_URL на 'localhost', и он будет обработан Babel, который будет учитывать наше определение .browserslistrc для добавления (или нет) отсутствующих функций.

Это основа настройки Webpack. Вы можете себе представить, какую тяжелую работу выполняют интерфейсы командной строки, и я надеюсь, что мы сможем немного лучше понять, как все работает под капотом.