Реагировать с нуля, часть 1: Webpack

Многие другие руководства по началу работы с React обычно упускают из виду все сопутствующие вещи, такие как WebPack и код на стороне сервера. Они, как правило, заставляют вас клонировать существующее репо, использовать кодовое слово или использовать упрощенную цепочку инструментов, чтобы показать основы. Здесь еще бред…

В этой серии статей я хочу показать, как начать с нуля. Начиная с отсутствия файлов и заканчивая работающим проектом.

В этой статье мы рассмотрим основы подготовки к разработке на React.

Предпосылки

Для этого путешествия вам понадобятся:

И это все.

Шаг 1. Настройка

Давайте создадим где-нибудь нашу папку проекта:

$ mkdir react-from-scratch
$ cd react-from-scratch/

Теперь давайте запустим NPM и Git.

$ git init
Initialized empty Git repository in blah/react-from-scratch/.git/
$ npm init
This utility will walk you through blah blah blah
About to write to blah/react-from-scratch/package.json
{
  "name": "react-from-scratch",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "MIT"
}
Is this ok? (yes)
$

Далее установим Экспресс для базового хостинга.

$ npm install --save express
npm notice created a lockfile as package-lock.json. You should commit this file.
npm WARN [email protected] No description
npm WARN [email protected] No repository field.
+ [email protected]
added 42 packages in 8.109s
$

И создайте server/index.js файл

const express = require('express');
const app = express();
app.use(express.static('www'));
app.listen(3000, () => {
  console.log('Example app listening on port 3000!')
});

И client/index.html файл

<!DOCTYPE html>
<html lang="en">
<head>
    <title>React from scratch</title>
</head>
<body>
    Hello World!
</body>
</html>

И беги!

$ node server
Example app listening on port 3000!

Теперь у нас есть очень простой веб-сервер со статическим файловым хостингом. Пока оставим это так.

Примечание. Я пропускаю такие вещи, как создание папок, добавление материалов в .gitignore, фиксацию, настройку редактора и т. д.

Шаг 2: веб-пакет

Затем мы установим веб-пакет, который будет нашим сборщиком. Он возьмет все наши ресурсы и скрипты и должным образом скоммит их.

$ npm install webpack --save-dev

> [email protected] postinstall blah
> node lib/post_install.js
npm WARN [email protected] No description
npm WARN [email protected] No repository field.
npm WARN optional SKIPPING OPTIONAL DEPENDENCY: [email protected] (node_modules\fsevents):
npm WARN notsup SKIPPING OPTIONAL DEPENDENCY: Unsupported platform for [email protected]: wanted {"os":"darwin","arch":"any"} (current: {"os":"win32","arch":"x64"})
+ [email protected]
added 362 packages in 47.499s
$

Примечание: этот раздел более или менее соответствует руководству по WebPack с меньшим изложением. Если вам нужны подробности, загляните сюда…

Для webpack необходим файл конфигурации. Для начала создадим базовый webpack.config.js файл:

const path = require('path');
module.exports = {
    entry: './client/index.js',
    output: {
        filename: 'bundle.js',
        path: path.resolve(__dirname, 'www')
    }
};

И базовый client/index.js файл

function component() {
    var element = document.createElement('div');
    element.innerHTML = "Hello world!";
    return element;
}
document.body.appendChild(component());

Теперь, если вы запустите webpack из командной строки, он построит вывод:

$ npx webpack
Hash: fa1454c9f41b0eb6748e
Version: webpack 3.5.4
Time: 57ms
    Asset     Size  Chunks             Chunk Names
bundle.js  2.65 kB       0  [emitted]  main
   [0] ./client/index.js 175 bytes {0} [built]
$

Примечание. Вы могли заметить, что я звоню npx выше. Это было включено в npm 5.2.0 и позволяет легко запускать сценарии пакетов npm. Если у вас его нет, вы можете обновить npm, запустив npm install -g npm (может потребоваться sudo в Linux).

Теперь давайте изменим наш html так, чтобы он ссылался на связанный файл:

<!DOCTYPE html>
<html lang="en">
<head>
    <title>React from scratch</title>
</head>
<body>
    <script src="bundle.js"></script>
</body>
</html>

И беги!

$ node server
Example app listening on port 3000!

Теперь, когда у нас есть рабочая настройка веб-пакета, давайте добавим несколько модулей, чтобы сделать вещи более полезными.

Модуль HTML

Во-первых, давайте заменим этот HTML-файл, который мы создали сами, на файл, созданный с помощью webpack. Установите это:

$ npm install --save-dev html-webpack-plugin
... stuff ...
+ [email protected]
added 153 packages in 27.109s

И отредактируйте файл webpack.config.js, чтобы использовать новый плагин:

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
    entry: './client/index.js',
    plugins: [
        new HtmlWebpackPlugin({
            title: 'React from scratch'
        })
    ],
    output: {
        filename: 'bundle.js',
        path: path.resolve(__dirname, 'www')
    }
};

Теперь, когда мы построим, он сгенерирует файл index.html для нас:

$ ./node_modules/.bin/webpack
Hash: d451e987f4ef8beb712e
Version: webpack 3.5.4
Time: 418ms
     Asset       Size  Chunks             Chunk Names
 bundle.js    2.65 kB       0  [emitted]  main
index.html  189 bytes          [emitted]
   [0] ./client/index.js 175 bytes {0} [built]
Child html-webpack-plugin for "index.html":
     1 asset
       [2] (webpack)/buildin/global.js 509 bytes {0} [built]
       [3] (webpack)/buildin/module.js 517 bytes {0} [built]
        + 2 hidden modules

Чистый модуль

Также добавим чистый модуль. Это обеспечит удаление старых файлов из нашего www каталога.

$ npm install clean-webpack-plugin --save-dev
npm WARN optional ...
npm WARN notsup ...
+ clean-webpack-plugin@0.1.16
added 122 packages in 19.149s

А затем наш конфиг веб-пакета:

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const dist = path.resolve(__dirname, 'www');
module.exports = {
    entry: './client/index.js',
    plugins: [
        new CleanWebpackPlugin([dist]),
        new HtmlWebpackPlugin({
            title: 'React from scratch'
        })
    ],
    output: {
        filename: 'bundle.js',
        path: path.resolve(__dirname, dist)
    }
};

Я также добавил константу dist для удобства.

Исходные карты

Чтобы сделать разработку практичной, давайте включим исходные карты в webpack.config.js:

module.exports = {
    entry: './client/index.js',
    devtool: 'inline-source-map',
    plugins: [
    ...
};

Есть еще несколько вариантов на выбор, но давайте пока воспользуемся inline-source-map. Этот параметр обычно меняется в производственной среде, но пока мы оставим его таким.

Сервер

Чтобы упростить процесс разработки, было бы полезно, если бы пакет перестраивался каждый раз при изменении файла. Для этого есть три варианта: использовать webpack-dev-server, который берет на себя хостинг и имеет все встроенные тонкости (например, перезагрузку в реальном времени). Есть режим просмотра (запуск webpack --watch), в котором webpack работает в фоновом режиме и наблюдает за файлами. Еще есть webpack-dev-middleware, который делает то же самое, но будет работать как часть нашего сервера Express.

В этом руководстве мы будем использовать webpack-dev-middleware.

Почему бы не использовать webpack-dev-server? Это входит в основу данного руководства. Я жалуюсь на то, что во многих новых технологиях есть инструменты, которые делают все за вас. Вроде webpack-dev-server. Хотя изначально они упрощают задачу, они не помогут вам в тот день, когда вам нужно перейти в производство. Я знаю проекты, где это было такой проблемой, что они в конечном итоге использовали webpack-dev-server в производстве. Так что давайте лучше придумаем практическое решение, которое нам не нужно будет существенно менять, когда мы перейдем к производству.

$ npm install --save-dev webpack-dev-middleware

Согласно документации меняем server/index.js:

const express = require('express');
const webpack = require('webpack');
const webpackconfig = require('../webpack.config');
const webpackMiddleware = require("webpack-dev-middleware");
const app = express();
app.use(express.static('www'));
const wpmw = webpackMiddleware(webpack(webpackconfig),{});
app.use(wpmw);
app.listen(3000, () => {
  console.log('Example app listening on port 3000!')
});

А теперь, если мы запустим:

$ node server
clean-webpack-plugin: ... has been removed.
Example app listening on port 3000!
Hash: d451e987f4ef8beb712e
Version: webpack 3.5.4
Time: 423ms
     Asset       Size  Chunks             Chunk Names
 bundle.js    6.47 kB       0  [emitted]  main
index.html  189 bytes          [emitted]
   [0] ./client/index.js 175 bytes {0} [built]
Child html-webpack-plugin for "index.html":
         Asset    Size  Chunks  Chunk Names
    index.html  544 kB       0
       [0] ./node_modules/html-webpack-plugin/lib/loader.js!./node_modules/html-webpack-plugin/default_index.ejs 538 bytes {0} [built]
       [1] ./node_modules/lodash/lodash.js 540 kB {0} [built]
       [2] (webpack)/buildin/global.js 509 bytes {0} [built]
       [3] (webpack)/buildin/module.js 517 bytes {0} [built]
webpack: Compiled successfully.

Он компилирует веб-пакет при запуске и хранится в памяти. Теперь измените файл client/index.js и посмотрите, что произойдет:

webpack: Compiling...
Hash: 163359edcf1649bf3d75
Version: webpack 3.5.4
Time: 51ms
     Asset       Size  Chunks             Chunk Names
 bundle.js    6.49 kB       0  [emitted]  main
index.html  189 bytes          [emitted]
   [0] ./client/index.js 182 bytes {0} [built]
Child html-webpack-plugin for "index.html":
         Asset    Size  Chunks  Chunk Names
    index.html  544 kB       1
       [0] ./node_modules/html-webpack-plugin/lib/loader.js!./node_modules/html-webpack-plugin/default_index.ejs 538 bytes {1}
       [1] ./node_modules/lodash/lodash.js 540 kB {1}
       [2] (webpack)/buildin/global.js 509 bytes {1}
       [3] (webpack)/buildin/module.js 517 bytes {1}
webpack: Compiled successfully.

Теперь, если вы перезагрузите страницу, она обновится!

Шаг 3: стили и файлы

Итак, теперь у нас функционирует веб-пакет, объединяющий наш JavaScript (еще не React, но мы доберемся до него), давайте перейдем к изображениям и стилям.

Картинки

В качестве примера давайте загрузим это изображение: https://facebook.github.io/react/img/logo.svg, которое выглядит так:

И положи в client/content.

Чтобы иметь возможность импортировать изображение, нам понадобится file-loader:

$ npm install --save-dev file-loader

+ [email protected]
added 116 packages in 14.652s

И нам нужно обновить нашу конфигурацию, чтобы использовать загрузчик файлов:

module.exports = {
    entry: './client/index.js',
    devtool: 'inline-source-map',
    plugins: [
        ...
    ],
    module: {
        rules: [
            {
                test: /\.(png|svg|jpg|gif)$/,
                use: ['file-loader']
            }
        ]
    },
    output: ...
};

Теперь давайте импортируем его из нашего JavaScript:

import reactlogo from './content/react.svg';
function component() {
    var element = document.createElement('div');
    element.innerHTML = "Hello world!";
    var logo = new Image();
    logo.src = reactlogo;
    element.appendChild(logo);
    return element;
}

И сборка webpack:

$ ./node_modules/.bin/webpack
clean-webpack-plugin: blah has been removed.
Hash: b981df6dcd6b8f00c593
Version: webpack 3.5.4
Time: 482ms
                               Asset       Size  Chunks             Chunk Names
3d593052bb2819bdf7709d9f7e512c8f.svg    1.84 kB          [emitted]
                           bundle.js    7.71 kB       0  [emitted]  main
                          index.html  189 bytes          [emitted]
   [0] ./client/index.js 307 bytes {0} [built]
   [1] ./client/content/react.svg 82 bytes {0} [built]
Child html-webpack-plugin for "index.html":
     1 asset
       [2] (webpack)/buildin/global.js 509 bytes {0} [built]
       [3] (webpack)/buildin/module.js 517 bytes {0} [built]
        + 2 hidden modules

Как вы можете видеть, теперь он также создал svg. Если мы запустим, то увидим логотип!

Поскольку это ужасно, давайте перейдем к стилям.

Стили

Для стилей CSS можно использовать style-loader и css-loader, однако мы будем использовать Sass вместо обычных файлов CSS.

Для начала давайте создадим файл sass, чтобы все выглядело немного лучше. Создать client/styles/main.scss:

body {
    background: #222;
    color: white;
    font-size: 1.1rem;
    font-family: Arial, Helvetica, sans-serif;
    div {
        margin: 20px;
        font-size: 2em;
        text-align: center;
        & > img {
            width: 300px;
            height: 300px;
            display: block;
            margin: 20px auto;
        }
    }
}

Установите sass-loader. Нам также понадобятся node-sass, а также style-loader и css-loader. Это преобразует импорт из SCSS в CSS, из CSS в javascript и из javascript в узлы стилей.

$ npm install sass-loader node-sass \
              css-loader style-loader --save-dev

+ [email protected]
+ [email protected]
+ [email protected]
+ [email protected]
added 114 packages and updated 4 packages in 29.43s

Нам нужно зарегистрировать их в нашей конфигурации webpack.

...
module: {
    rules: [
        {
            test: /\.(png|svg|jpg|gif)$/,
            use: ['file-loader']
        },
        {
            test: /\.(scss|sass)$/,
            use: ['style-loader', 'css-loader', 'sass-loader']
        }
    ]
},
...

Теперь мы можем импортировать его из нашего client/index.js файла:

import reactlogo from './content/react.svg';
import './styles/main.scss';

А теперь, если мы запустим:

Шаг 4: другие вещи

Нет, поскольку у нас есть базовая настройка веб-пакета, мы можем посмотреть на некоторые другие вещи.

Замена горячего модуля

Горячая замена / перезагрузка / обновление / замена модулей (только HMR) позволяет перезагружать модули (изображения, стили и т. Д.) Во время работы приложения (в вашем браузере). Это достигается путем обнаружения изменений в файлах (во время работы приложения), перекомпиляции веб-пакета и синхронизации изменений в вашем браузере. Если вы используете сервер webpack dev, это почти бесплатно, но если вы используете webpack-dev-middleware, нам нужно внести пару небольших изменений. Я написал другую статью, в которой идет речь. Сейчас хорошее время посмотреть на него, если вам нужна эта функция:



Опн

Иногда бывает полезно открывать браузер при каждом запуске проекта. Мы можем получить это, используя opn. Добавим:

$ npm install --save-dev opn

И обновите server/index.js:

const express = require('express');
...
const opn = require('opn');
const development = process.env.NODE_ENV != 'production';
const port = 3000;
const app = express();
...
app.listen(port, () => {
    console.log(`Example app listening on port ${port}!`);
    if (development) {
        opn(`http://127.0.0.1:${port}/`);
    }
});

Теперь браузер будет открываться каждый раз при запуске сервера! Намного удобнее.

Некоторые случайные настройки

Мы можем добавить несколько скриптов в наш package.json, чтобы упростить задачу:

{
  "name": "react-from-scratch",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\"",
    "build": "webpack",
    "start": "node server"
  },
  "author": "Cillié Malan",
  ...
}

так что теперь мы можем:

$ npm run build
> [email protected] build blah
> webpack
clean-webpack-plugin: blah blah
Hash: blah blah
yada yada
...
...

or:

$ npm start
> [email protected] start blah
> node server
clean-webpack-plugin: blah blah
Example app listening on port 3000!
webpack: wait until bundle finished: /
webpack built 65f222c3fcfabd3d95d2 in 1266ms
blah blah
...

Эти вещи делают вещи только немного лучше.

Что дальше

Далее мы рассмотрим добавление React в имеющуюся у нас настройку webpack dev.

Чтобы увидеть весь код, который мы сделали в этой статье, вы можете проверить конкретную ветку в репозитории github:

Https://github.com/cilliemalan/react-from-scratch/tree/step-1

Далее: Реагировать на примере…