Контрольный список инженерной сборки для веб-приложений

JavaScript
Контрольный список инженерной сборки для веб-приложений

иллюстрировать

  1. Проект в основном обсуждает и записывает основной процесс построения системы веб-инженерии. (Без cli, предоставляемого фреймворком, создайте довольно полное веб-приложение с основ)
  2. Современные веб-проекты в основном представляют собой три фреймворка React, Vue и Angular. Выберите React для проекта.
  3. Почему стоит выбрать Реакт? Причина в том, что в работе используется Vue, для этого проекта не очень важно, что выбрать, главное,Обсудить процесс создания хорошего проекта.
  4. Угол обсуждения:
    • Управление версиями (git)
    • управление пакетами npm
    • webpack (создать среду разработки и упаковать онлайн-ресурсы)
    • Качество спецификации кода (eslint, stylint, prettier)
    • Модульное тестирование (компоненты пользовательского интерфейса, приватная служебная функция «шутка»)
    • Организация каталога проектов
    • Разделение интерфейса и сервера (MOCK, json-сервер)
    • Взаимодействие между клиентом и сервером (axios)
    • Спецификация написания стиля компонента
    • разделение компонентов

Давайте начнем

1. Управление версиями (git)

$ 如何安装 ?

# https://git-scm.com/downloads

$ 如何使用 ?

# https://git-scm.com/doc || git --help

⬆ вернуться к началу

2. Create a local .gitignore

Иногда некоторые файлы не хотятGitРегистрироватьсяGitHub..gitignoreФайл конфигурации может сказатьGitКакие файлы игнорировать.

$ touch .gitignore # starter/.gitignore

# dependencies
/node_modules

# testing
/coverage

# production
/build
dist

# misc
.DS_Store

# Log files
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# Editor directories and files
.vscode

⬆ вернуться к началу

3. node,npmа такжеyarn

$ node 是什么 ?

# http://nodejs.cn/

$ 如何安装 ?

# http://nodejs.cn/download/

$ npm 是什么 ?

# https://docs.npmjs.com/about-npm/

$ npm 如何使用 ?

# 安装 Node.js 时附带安装了 npm || npm -v

$ 创建包管理配置文件 package.json

# https://docs.npmjs.com/creating-a-package-json-file

$ package.json 文件中的要求 ?

# https://docs.npmjs.com/files/package.json.html

$ package-lock.json 是什么 ?

# https://docs.npmjs.com/files/package-lock.json.html

$ yarn 是什么?

# https://yarn.bootcss.com/

$ yarn 如何安装 ?

# https://yarn.bootcss.com/docs/install/#mac-stable

$ yarn 如何使用 ?

# https://yarn.bootcss.com/docs/

$ 初始化项目

# mkdir starter && npm init

⬆ вернуться к началу

初始工程目录а такжеpackage.jsonинформация ✅

Каталог проекта

└── starter
    ├── README.md
    └── package.json

package.json

{
  "name": "starter",
  "version": "1.0.0",
  "description": "List of engineering builds for web applications",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/cllemon/starter.git"
  },
  "keywords": [
    "javascript",
    "typescript",
    "react"
  ],
  "author": "cllemon",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/cllemon/starter/issues"
  },
  "homepage": "https://github.com/cllemon/starter#readme"
}

Примечание. Все установки пакетов в следующих описаниях используютyarnЗаказ

⬆ вернуться к началу

4. editorconfig

EditorConfigможет помочь разработчикам в разных редакторах иIDEОпределите и поддерживайте единый стиль кода между ними.

EditorConfig is awesome: editorconfig.org

$ touch .editoorconfig

# starter/.editoorconfig

root = true                       # 表明是最顶层的配置文件,发现设为 true 时,才会停止查找.editorconfig 文件。

[*]
charset = utf-8
indent_style = space              # tab 为 hard-tabs,space 为 soft-tabs。
indent_size = 2                   # 规定每级缩进的列数和 soft-tabs 的宽度(空格数)。如果设定为 tab,则会使用 tab_width 的值。
end_of_line = lf                  # 定义换行符,支持 lf(UNIX/Linux采用换行符 LF 表示下一行)、cr(MAC OS系统)则采用回车符 CR 表示下一行) 和 crlf。
insert_final_newline = true       # 设为 true 表明使文件以一个空白行结尾,false 反之
trim_trailing_whitespace = true   # 设为 true 表示会除去换行行首的任意空白字符,false 反之。

[*.md]                            # 校验 markdown 文档
insert_final_newline = false
trim_trailing_whitespace = false

⬆ вернуться к началу

5. browserslist

  • browserslistчто это такое?

    Используется для обмена целевыми браузерами иNode.jsконфигурация версии. НапримерAutoprefixer,Stylelintа такжеbabel-preset-env.

  • browserslistМетод конфигурации

    когда вы добавляете следующее вpackage.jsonили .browserslistrc, все инструменты автоматически найдут целевой браузер:

    # package.json
    
    {
      "browserslist": {
        "production": [             // 生产环境配置
          ">0.2%",                  // 支持市场份额大于 1% 的浏览器。
          "not dead",               // not(逻辑非)对 dead 取反,而浏览器被认为是 dead 条件是:最新的两个版本中发现其市场份额已经低于 0.5% 并且 24 个月内没有官方支持和更新。
          "not op_mini all"         // OperaMini or op_mini for Opera Mini.
        ],
        "development": [            // 开发环境配置
          "last 1 chrome version",  // 浏览器版本查询范围, chrome 最近的一个版本
          "last 1 firefox version",
          "last 1 safari version"
        ]
      }
    }
    
    或🔥
    
    # .browserslistrc
    
    $ touch .browserslist
    
      [production]
    
      > .2%
      not dead
      not op_mini all
    
      [development]
    
      last 1 chrome version
      last 1 firefox version
      last 1 safari version
    

⬆ вернуться к началу

6. импортировать веб-пакет

По сути,webpackсовременныйJavaScriptИнструмент упаковки статических модулей для приложений. когдаwebpackПри обработке приложение внутренне строит граф зависимостей (dependency graph), этот граф зависимостей отображает каждый модуль, требуемый проектом, и генерирует один или несколькоbundle.

  • Установите и создайте основные файлы

    $ mkdir src                                # 创建存放核心代码文件夹
    $ cd src && touch index.js                  # 创建入口文件
    
    $ yarn add -D webpack                      # 安装最新版本 webpack^4.41.2
    $ yarn add -D webpack-cli                  # 安装 webpack v4+ 版本,所需的 webpack-cli^3.3.9
    
    $ cd .. && touch webpack.config.js          # 根目录,创建 webpack 基本配置文件
    
  • Каталог проекта

    └── starter
    + ├── node_modules
    + ├── src
    + │   └── index.js
    + ├── webpack.config.js
      ├── package.json
      └── README.md
    

⬆ вернуться к началу

7. Импорт

A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Установите и создайте основные файлы

    $ yarn add react                 # 安装 react^16.10.2
    $ yarn add react-dom             # 安装 react-dom^16.10.2
    
    $ mkdir public                   # 新建公共资源文件夹
    $ cd public && touch index.html  # 新建 html 文件
    $ copy favicon.ico               # 添加 网页图标 文件
    $ cd ..                          # 回到根目录
    
  • Напишите файл index.html

    <!DOCTYPE html>
      <html lang="en">
    
      <head>
        <meta charset="utf-8" />
        <link rel="icon" href="./favicon.ico" />
        <meta name="viewport"
          content="width=device-width, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover" />
        <meta name="theme-color" content="#000000" />
        <meta name="description"
          content="This is a react application built from scratch with JavaScript, away from the cli tool." />
        <title>Starter</title>
      </head>
    
      <body>
        <noscript>You need to enable JavaScript to run this app.</noscript>
        <div id="root"></div>
      </body>
    
      </html>
    
  • Напишите файл index.js

    import React from 'react';
    import ReactDom from 'react-dom';
    
    const App = () => <h1>Hello, world!</h1>
    
    ReactDom.render(<App />, document.getElementById('root'));
    

Примечание. Поскольку браузер не поддерживает последний синтаксис JavaScript и синтаксический анализ react jsx, нам нужен компилятор, который нам поможет.

⬆ вернуться к началу

8. Представляем Вавилон

Babel — это цепочка инструментов, в основном используемая для преобразования кода ECMAScript 2015+ в обратно совместимые версии кода JavaScript в старых браузерах или средах.

  • Вавилонская инсталляция

    $ yarn add -D @babel/core                      # Babel 编译器核心模块
    $ yarn add -D @babel/preset-env                # 是一个智能预设,它使您可以使用最新的JavaScript,而无需微观管理目标环境所需的语法转换
    $ yarn add -D @babel/preset-react              # react 智能预设, 包含了解析 jsx 等插件
    $ yarn add -D babel-loader                     # Babel loader for webpack 该软件包允许使用 Babel 和 webpack 来转译 JavaScript 文件。
    
    $ touch .babelrc                               # 新建 babel 配置文件
    
  • вавилонская конфигурация

    // .babelrc
    {
      "presets": ["@babel/preset-env", "@babel/preset-react"],
    }
    
  • Написать конфигурацию веб-пакета

    // starter/webpack.config.js
    const path = require('path');
    
    module.exports = function() {
      const baseConfig = {
        entry: './src/index.js',
    
        output: {
          path: path.resolve(__dirname, 'dist'),
          filename: 'bundle.js'
        },
    
        module: {
          rules: [
            {
              test: /\.(js|jsx)$/,
              exclude: /node_modules/,
              loader: 'babel-loader'
            },
          ]
        }
      };
    
      return baseConfig;
    };
    
  • Измените package.json, чтобы добавить команду webpack для быстрого запуска.

      {
        "scripts": {
          "test": "echo \"Error: no test specified\" && exit 1",
    +     "build": "webpack --color --progress"
        }
      }
    
  • Измените index.html, чтобы импортировать файл bundle.js после упаковки.

      ...
      <div id="root"></div>
    + <script src="../dist/bundle.js"></script>
      ...
    
  • запустить проект

    $ yarn build # 打包文件
    
      Hash: 6e4adf36d533e9d646c0
      Version: webpack 4.41.2
      Time: 693ms
    
      Built at: 2019-10-19 11:41:22
          Asset      Size      Chunks             Chunk Names
         bundle.js  1.09 MiB    main   [emitted]     main
    
      Entrypoint main = bundle.js
    
      [./src/index.js] 233 bytes {main} [built]
    
    # 浏览器 打开 index.html 查看效果
    

    x

  • Каталог проекта

    └── starter
    + ├── dist
    + │   └── bundle.js
      ├── node_modules
    + ├── public
    + │   ├── favicon.ico
    + │   └── index.html
      ├── src
      │   └── index.js
      ├── webpack.config.js
      ├── package.json
      ├── README.md
    + └── yarn.lock
    

⬆ вернуться к началу

9. Настройка среды разработки — с помощью webpack-dev-server

webpack-dev-server предоставляет вам простой веб-сервер с возможностью перезагрузки в реальном времени.

  • Установить

    $ yarn add -D webpack-dev-server # 用于快速开发应用程序
    
  • Добавьте соответствующую конфигурацию

    // starter/webpack.config.js
    
    const path = require('path');
    
    module.exports = function() {
      const baseConfig = {
    
        devtool: 'inline-source-map', // 控制是否生成,以及如何生成 source map
    
        entry: './src/index.js',
    
        output: {
          path: path.resolve(__dirname, 'dist'),
          filename: 'bundle.js'
        },
    
        module: {
          rules: [
            {
              test: /\.(js|jsx)$/,
              exclude: /node_modules/,
              loader: 'babel-loader'
            },
          ]
        },
    
    +   devServer: {
    +     contentBase: path.resolve(__dirname, 'public'), // 告诉服务器从哪个目录中提供内容
    +     historyApiFallback: true,                       // 启用当使用 HTML5 History API 时,任意的 404 响应都可能需要被替代为 index.html。
    +     compress: true,                                 // 一切服务都启用 gzip 压缩
    +     open: true,                                     // 告诉 dev-server 在 server 启动后打开浏览器
    +     port: 3000,                                     // 指定要监听请求的端口号
    +     stats: 'errors-only',                           // 精确控制要显示的 bundle 信息 (在 bundle 中只显示错误)
    +   }
      };
    
      return baseConfig;
    };
    
  • Измените package.json, чтобы добавить команду webpack для быстрого запуска.

      {
        "scripts": {
          "test": "echo \"Error: no test specified\" && exit 1",
          "build": "webpack --color --progress",
    +     "server": "webpack-dev-server --color --progress"
        }
      }
    

    --color: включить/выключить вывод цвета консоли;--progress: вывод хода выполнения на консоль.

  • Измените путь к основному файлу index.html bundle.js.

      ...
      <div id="root"></div>
    - <script src="../dist/bundle.js"></script>
    + <script src="bundle.js"></script>
      ...
    
  • запустить проект

    $ yarn server
    
    # 结果:
    
    $ webpack-dev-server --color --progress
    
    10% building 1/1 modules 0 activeℹ 「wds」: Project is running at http://localhost:3000/
    ℹ 「wds」: webpack output is served from /
    ℹ 「wds」: Content not from webpack is served from /Users/mr.lemon/cl/CODE_CL/REACT/starter/public
    ℹ 「wds」: 404s will fallback to /index.html
    ℹ 「wdm」: Compiled successfully.
    

    Открытьhttp://localhost:3000/покажетHello, world!; Исправлятьsrc/index.jsБраузер будет обновлен, чтобы обновлять изменения в режиме реального времени. Попытайся!

  • Есть проблемы или точки улучшения, которые нужно улучшить

    1. Каждое изменение требует обновления всего браузера, что явно не соответствует современному опыту инженерных разработок!
    2. Недифференцированная среда (webpack.config.jsНекоторые конфигурации мы хотим иметь только в среде разработки, а в рабочей среде должны быть определенные конфигурации)

    С этими вопросами вперед! 👍

⬆ вернуться к началу

10. Настройка среды разработки — переменные среды

  • Установить

    $ yarn add -D cross-env # Cross platform setting of environment scripts
    
  • Изменить команду webpack package.json

    {
      "scripts": {
        "test": "echo \"Error: no test specified\" && exit 1",
    -     "build": "webpack --color --progress",
    +     "build": "cross-env NODE_ENV=production webpack --color --progress",
    -     "server": "webpack-dev-server --color --progress"
    +     "server": "cross-env NODE_ENV=development webpack-dev-server --color --progress"
      }
    }
    
  • Добавьте соответствующую конфигурацию в webpack.config.js.

      // starter/webpack.config.js
    
      const path = require('path');
    + const IS_PROD = process.env.NODE_ENV === 'production';
    
      module.exports = function() {
        const baseConfig = {
    +     mode: IS_PROD ? 'production' : 'development',
    -     devtool: 'inline-source-map', // 控制是否生成,以及如何生成 source map
    +     devtool: IS_PROD ? false : 'inline-source-map',
    
          entry: './src/index.js',
    
          output: {
            path: path.resolve(__dirname, 'dist'),
            filename: 'bundle.js'
          },
    
          module: {
            rules: [
              {
                test: /\.(js|jsx)$/,
                exclude: /node_modules/,
                loader: 'babel-loader'
              },
            ]
          },
    
    -     devServer: {
    -       contentBase: path.resolve(__dirname, 'public'), // 告诉服务器从哪个目录中提供内容
    -       historyApiFallback: true,                       // 启用当使用 HTML5 History API 时,任意的 404 响应都可能需要被替代为 index.html。
    -       compress: true,                                 // 一切服务都启用 gzip 压缩
    -       open: true,                                     // 告诉 dev-server 在 server 启动后打开浏览器
    -       port: 3000,                                     // 指定要监听请求的端口号
    -       stats: 'errors-only',                           // 精确控制要显示的 bundle 信息 (在 bundle 中只显示错误)
    -     }
        };
    
    +   if (!IS_PROD) {
    +     baseConfig.devServer = {
    +       contentBase: path.resolve(__dirname, 'public'), // 告诉服务器从哪个目录中提供内容
    +       historyApiFallback: true,                       // 启用当使用 HTML5 History API 时,任意的 404 响应都可能需要被替代为 index.html。
    +       compress: true,                                 // 一切服务都启用 gzip 压缩
    +       open: true,                                     // 告诉 dev-server 在 server 启动后打开浏览器
    +       port: 3000,                                     // 指定要监听请求的端口号
    +       stats: 'errors-only',                           // 精确控制要显示的 bundle 信息 (在 bundle 中只显示错误)
    +     };
    +   }
    
        return baseConfig;
      };
    

⬆ вернуться к началу

11. Настройка среды разработки - Горячая замена модуля

Горячая замена модуля (или HMR) — одна из самых полезных функций, предоставляемых webpack. Он позволяет обновлять все типы модулей во время выполнения без полного обновления.

  • Добавьте соответствующую конфигурацию в webpack.config.js.

      // starter/webpack.config.js
    
      const path = require('path');
    + const webpack = require('webpack');
      const IS_PROD = process.env.NODE_ENV === 'production';
    
      module.exports = function() {
        const baseConfig = {
          mode: IS_PROD ? 'production' : 'development',
    
          entry: './src/index.js',
    
          output: {
            path: path.resolve(__dirname, 'dist'),
            filename: 'bundle.js'
          },
    
          module: {
            rules: [
              {
                test: /\.(js|jsx)$/,
                exclude: /node_modules/,
                loader: 'babel-loader'
              },
            ]
          },
    
    +     plugins: []
        };
    
        if (!IS_PROD) {
          baseConfig.devServer = {
            contentBase: path.resolve(__dirname, 'public'), // 告诉服务器从哪个目录中提供内容
            historyApiFallback: true,                       // 启用当使用 HTML5 History API 时,任意的 404 响应都可能需要被替代为 index.html。
            compress: true,                                 // 一切服务都启用 gzip 压缩
            open: true,                                     // 告诉 dev-server 在 server 启动后打开浏览器
            port: 3000,                                     // 指定要监听请求的端口号
            stats: 'errors-only',                           // 精确控制要显示的 bundle 信息 (在 bundle 中只显示错误)
    +       hot: true                                       // 启用 webpack 的 模块热替换 功能
          };
    +     baseConfig.plugins.concat([
    +       new webpack.HotModuleReplacementPlugin()        // 热替换模块插件
    +     ]);
        }
    
        return baseConfig;
      };
    
  • Измените файл src/index.js.

    - import React from 'react';
    + import React, { useState } from 'react';
      import ReactDom from 'react-dom';
    
    
    - const App = () => <h1>Hello, world!</h1>;
    
    + const App = () => {
    +   const [title, setTitle] = useState('hello, world!');
    +   const reversedTitle = () =>
    +     setTitle(
    +       title
    +         .split('')
    +         .reverse()
    +         .join('')
    +     );
    +   return (
    +     <div>
    +       <h1>{ title }</h1>
    +       <button type='button' onClick={reversedTitle}>
    +         reversed title
    +       </button>
    +     </div>
    +   );
    + };
    
    + if (module.hot) {
    +   module.hot.accept();
    + }
    
      ReactDom.render(<App />, document.getElementById('root'));
    
  • запустить проект

    $ yarn server
    
    # 结果:
    
    $ cross-env NODE_ENV=development webpack-dev-server --color --progress
    10% building 1/1 modules 0 activeℹ 「wds」: Project is running at http://localhost:3000/
    ℹ 「wds」: webpack output is served from /
    ℹ 「wds」: Content not from webpack is served from /Users/mr.lemon/cl/CODE_CL/REACT/starter/public
    ℹ 「wds」: 404s will fallback to /index.html
    ℹ 「wdm」: Compiled successfully.
    
    # 浏览器 console
    [HMR] Waiting for update signal from WDS...      log.js:24
    [WDS] Hot Module Replacement enabled.            client:48
    [WDS] Live Reloading enabled.                    client:52
    

    Открытьhttp://localhost:3000/Исправлятьsrc/index.jsРеализована модификация необновляемого обновления браузера. Попытайся!

    x

  • Есть проблемы или точки улучшения, которые нужно улучшить

    1. Каждый раз, когда содержимое модифицируется, обновления обновления не происходит, но в то же время очищается значение внутреннего состояния компонента, что явно недопустимо.

    Имея в виду этот вопрос, вперед! ✈️

⬆ вернуться к началу

12. Создайте среду разработки - модуль горячей замены - импортируйте react-hot-loader

Настраивайте компоненты React в режиме реального времени.

  • иллюстрировать

    1. Поскольку проект выбирает фреймворк реакции, вводится react-hot-loader.
    2. Если вы переключаетесь на другие фреймворки, также есть соответствующие интеграции плагинов, такие как: vue-hot-reload-api, интегрированный в vue-loader.
    3. Конечно, вы также можете сделать это самостоятельно.
  • Установить

    $ yarn add react-hot-loader
    $ yarn add @hot-loader/react-dom # 替换了相同版本的 react-dom 软件包,但附加了一些补丁以支持热重装。
    
  • Буду"react-hot-loader/babel"добавить в свой.babelrcсередина

      {
        "presets": ["@babel/preset-env", "@babel/preset-react"],
    +   "plugins": ["react-hot-loader/babel"]
      }
    
  • сброс настроекreact-domсовместимыйhooks

      ...
    
      moduele.exports = function () {
    
        ...
    
    +   resolve: {
    +     alias: {
    +       'react-dom': '@hot-loader/react-dom' // react-hot-loader 兼容 hook 写法
    +     }
    +   },
    
        ...
    
      }
    
      ...
    
  • Измените основной файл src/index.js, чтобы пометить корневой компонент какhot-exported

    + import { hot } from 'react-hot-loader';
      import React, { useState } from 'react';
      import ReactDom from 'react-dom';
    
    - const App = () => {
    + const App = hot(module)(() => {
        const [title, setTitle] = useState('hello, world!');
    
        const reversedTitle = () =>
          setTitle(
            title
              .split('')
              .reverse()
              .join('')
          );
        return (
          <div>
            <h1>{title}</h1>
            <button type='button' onClick={reversedTitle}>
              reversed title!
            </button>
          </div>
        );
    - };
    + });
    
    - if (module.hot) {
    -   module.hot.accept();
    - }
    
      ReactDom.render(<App />, document.getElementById('root'));
    
  • запустить проект

    $ yarn server
    
    # 结果:
    
    $ cross-env NODE_ENV=development webpack-dev-server --color --progress
    10% building 1/1 modules 0 activeℹ 「wds」: Project is running at http://localhost:3000/
    ℹ 「wds」: webpack output is served from /
    ℹ 「wds」: Content not from webpack is served from /Users/mr.lemon/cl/CODE_CL/REACT/starter/public
    ℹ 「wds」: 404s will fallback to /index.html
    ℹ 「wdm」: Compiled successfully.
    ℹ 「wdm」: Compiling...
    ℹ 「wdm」: Compiled successfully.
    
    # 浏览器 console
    [HMR] Waiting for update signal from WDS...      log.js:24
    [WDS] App hot update...                          reloadApp.js:19
    [HMR] Checking for updates on the server...      log.js:24
    [HMR] Updated modules:                           log.js:24
    [HMR]  - ./src/index.js                          log.js:24
    [HMR] App is up to date.                         log.js:24
    

    Открытьhttp://localhost:3000/, нажмитеreversed titleзатем изменитьsrc/index.jsРеализована модификация обновления, сохраняющая состояние компонента без обновления браузера. Попытайся!

    x

  • сценический эпилог

    1. На данный момент создана простая среда разработки. 🐃
    2. Впереди еще много работы, продолжайте в том же духе! 🍺

⬆ вернуться к началу

13. Представьте CSS иSassобработка файла стилей

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

Примечание. Этот проект представляет sass, конечно, вы также не можете импортировать или импортировать другие, такие как: меньше, стилус.

  • Установить

    $ yarn add -D node-sass      # Node-sass是一个库,提供了 Node.js 与 LibSass(流行的样式表预处理器Sass的C版本)的绑定。 它使您能够以惊人的速度通过连接中间件自动将 .scss 文件本地编译为 css
    $ yarn add -D sass-loader    # Compiles Sass to CSS
    $ yarn add -D css-loader     # The css-loader interprets @import and url() like import/require() and will resolve them.
    $ yarn add -D style-loader   # Inject CSS into the DOM.
    

    Примечание: sass разработан на основе языка Ruby, поэтому перед установкой sass необходимо установить Ruby. (Примечание: Ruby не нужно устанавливать под Mac!)

    Зачем вам нужен node-sass : Из-за sass-loaderpeerDependenciesЗаявлено, что зависит от node-sass, поэтому его нужно предварительно установить, иначе будет предупреждение.

  • Конфигурация: измените webpack.config.js, чтобы расширить возможности парсинга css/sass.

      ...
    
      moduele.exports = function () {
    
        ...
    
        module: {
          rules: [
            ...
    
    +       {
    +         test: /\.(sa|sc|c)ss$/,
    +         exclude: /node_modules/,
    +         use: [
    +           {
    +             loader: 'style-loader'
    +           },
    +           {
    +             loader: 'css-loader',
    +             options: {
    +               sourceMap: !IS_PROD
    +             }
    +           },
    +           {
    +             loader: 'sass-loader',
    +             options: {
    +               sourceMap: !IS_PROD
    +             }
    +           }
    +         ]
    +       }
          ]
        }
    
        ...
    
      }
    
      ...
    
  • Добавлены файлы стилей src/index.scss и style/global.css.

    $ cd src && touch index.scss
    $ mkdir style && cd style
    $ touch global.css && touch reset.css
    
    // src/style/reset.css
    # reset 重置浏览器初始样式,具体样式参见项目 src/style/reset.css
    
    // src/style/global.css
    @import url('./reset.css');
    
    // src/index.scss
    .app {
      background-color: red;
    }
    
  • Измените src/index.js, чтобы импортировать таблицу стилей.

      import { hot } from 'react-hot-loader';
      import React, { useState } from 'react';
      import ReactDom from 'react-dom';
    + import './style/global.css';
    + import './index.scss';
    
      const App = hot(module)(() => {
        const [title, setTitle] = useState('hello, world!');
    
        const reversedTitle = () =>
          setTitle(
            title
              .split('')
              .reverse()
              .join('')
          );
        return (
    -     <div>
    +     <div className='app'>
            <h1>{title}</h1>
            <button type='button' onClick={reversedTitle}>
              reversed title!
            </button>
          </div>
        );
      });
    
      ReactDom.render(<App />, document.getElementById('root'));
    
  • запустить проект

    $ yarn server
    
    # 结果:
    
    $ cross-env NODE_ENV=development webpack-dev-server --color --progress
    10% building 1/1 modules 0 activeℹ 「wds」: Project is running at http://localhost:3000/
    ℹ 「wds」: webpack output is served from /
    ℹ 「wds」: Content not from webpack is served from /Users/mr.lemon/cl/CODE_CL/REACT/starter/public
    ℹ 「wds」: 404s will fallback to /index.html
    ℹ 「wdm」: Compiled successfully.
    

    Открытьhttp://localhost:3000/, как вы и написали, появляется красный фон. Попытайся!

    x

  • Проблемы и улучшения 🤔

    1. Отсутствие плагинов для автоматического управления префиксами браузера, парсингаCSSфайл и добавьте префикс браузера кCSSв содержании;postcss/autoprefixer
    2. При наличии большого количества файлов стилей компонентов во избежание конфликтов стилей можно использоватьcss-modulesДля решения этой проблемы. Конечно, вы также можете обойти эту проблему, приняв строгие соглашения об именах, такие как: БЭМ.

    Тогда вперёд! 💪

⬆ вернуться к началу

14. CSS-Modulesа такжеautoprefixer

  • Установить

    $ yarn add - D postcss-loader # 用于webpack的Loader以使用PostCSS处理CSS
    
    $ yarn add -D autoprefixer # Parse CSS and add vendor prefixes to rules by Can I Use
    
  • Создайте новый файл конфигурации postcss

    $ touch postcss.config.js # 新建 postcss 配置文件
    
    # starter/postcss.config.js 添加 autoprefixer 插件
    module.exports = {
      plugins: {
        autoprefixer: {},
      }
    };
    
  • Добавить конфигурацию webpack postcss

      ...
    
      moduele.exports = function () {
    
        ...
    
        module: {
          rules: [
            ...
    
            {
    -         test: /\.(sa|sc|c)ss$/,
    +         test: /\.(sa|sc)ss$/,
              exclude: /node_modules/,
              use: [
                {
                  loader: 'style-loader'
                },
                {
                  loader: 'css-loader',
    +             options: {
    +               sourceMap: !IS_PROD,
    +               importLoaders: 2,  // 启用/禁用或设置在CSS加载程序之前应用的加载程序的数量
    +               modules: {
    +                 context: path.resolve(__dirname, 'src'), // 允许为本地标识符名称重新定义基本的加载程序上下文。
    +                 localIdentName: '[name]__[local]-[hash:base64:5]' // 使用 localIdentName 查询参数配置生成类名
    +               }
    +             }
                },
    +           {
    +             loader: 'postcss-loader'
    +           }
                {
                  loader: 'sass-loader',
                  options: {
                    sourceMap: !IS_PROD
                  }
                }
              ]
            },
    +       {
    +          test: /\.css$/,
    +          exclude: /node_modules/,
    +          use: [
    +            'style-loader',
    +            {
    +              loader: 'css-loader',
    +              options: {
    +                sourceMap: !IS_PROD
    +              }
    +            },
    +            'post-loader'
    +          ]
    +       }
          ]
        }
    
        ...
    
      }
    
      ...
    

    postcss: одно использованиеJavaScriptконвертироватьCSSИнструмент
    css-loaderпоставкаCSSМодули и их конфигурация

  • Изменить запись имени класса src/index.js

      import { hot } from 'react-hot-loader';
      import React, { useState } from 'react';
      import ReactDom from 'react-dom';
      import './style/global.css';
    - import './index.scss';
    + import styles from './index.scss';
    
      const App = hot(module)(() => {
        const [title, setTitle] = useState('hello, world!');
    
        const reversedTitle = () =>
          setTitle(
            title
              .split('')
              .reverse()
              .join('')
          );
        return (
    -     <div className='app'>
    +     <div className={styles.app}>
            <h1>{title}</h1>
            <button type='button' onClick={reversedTitle}>
              reversed title!
            </button>
          </div>
        );
      });
    
      ReactDom.render(<App />, document.getElementById('root'));
    
  • запустить проект

    $ yarn server
    
    # 结果
    
    $ cross-env NODE_ENV=development webpack-dev-server --color --progress
    10% building 1/1 modules 0 activeℹ 「wds」: Project is running at http://localhost:3000/
    ℹ 「wds」: webpack output is served from /
    ℹ 「wds」: Content not from webpack is served from /Users/gt/LEMON/starter/public
    ℹ 「wds」: 404s will fallback to /index.html
    ℹ 「wdm」: Compiled successfully.
    

    Открытьhttp://localhost:3000/Проверьте, так ли это, как вы написали!

    x

⬆ вернуться к началу

15. Идем дальше и создаем наше приложениеyarn build

Пакет

$ yarn build

# 结果
$ cross-env NODE_ENV=production webpack --color --progress
Hash: 4f40eeb2a231c73dacd9
Version: webpack 4.41.2
Time: 4142ms
Built at: 2019-10-21 10:54:37

    Asset     Size        Chunks             Chunk Names
  bundle.js  136 KiB       0  [emitted]         main

Entrypoint main = bundle.js
[5] ./src/index.scss 498 bytes {0} [built]
[7] ./src/index.js 1.57 KiB {0} [built]
[8] (webpack)/buildin/harmony-module.js 573 bytes {0} [built]
[13] ./src/style/global.css 457 bytes {0} [built]
[14] ./node_modules/css-loader/dist/cjs.js!./node_modules/postcss-loader/src!./src/style/global.css 237 bytes {0} [built]
[15] ./node_modules/css-loader/dist/cjs.js!./src/style/reset.css 1.28 KiB {0} [built]
[16] ./node_modules/css-loader/dist/cjs.js??ref--5-1!./node_modules/postcss-loader/src!./node_modules/sass-loader/dist/cjs.js!./src/index.scss 238 bytes {0} [built]
    + 11 hidden modules
✨  Done in 5.90s.

Мы видим, что это касается только одногоbundle.jsЭтого явно недостаточно. Далее делаем несколько изменений!

⬆ вернуться к началу

Управление выводом

До сих пор мы вручную импортировали все ресурсы в файл index.html, однако по мере роста приложения и после того, как вы начнете использовать hash] в именах файлов и экспортировать несколько пакетов, если вы продолжите управлять файлом index.html вручную, станет трудный.

  • Изменить веб-пакет - вывод

      const path = require('path');
      const webpack = require('webpack');
      const IS_PROD = process.env.NODE_ENV === 'production';
    
      ...
    
        output: {
          path: path.resolve(__dirname, 'dist'),
    -     publicPath: '/',
    +     publicPath: IS_PROD ? '/starter/' : '/', // 公共路径
    -     filename: 'bundle.js'
    +     filename:  IS_PROD ? '[name].[contenthash:8].js' : '[name].js', // 输出文件的文件名
    +     chunkFilename: IS_PROD ? 'chunks/[name].[contenthash:8].js' : '[name].js', // 非入口(non-entry) chunk 文件的名称
        },
    
      ...
    
  • HtmlWebpackPlugin

    $ yarn add -D html-webpack-plugin # 安装插件
    
    <!-- starter/webpack.config.js -->
    
      const path = require('path');
      const webpack = require('webpack');
      const IS_PROD = process.env.NODE_ENV === 'production';
    +  const HtmlWebpackPlugin = require('html-webpack-plugin')
    
      ...
    
    -    plugins: []
    +    plugins: [
    +      new HtmlWebpackPlugin({
    +        title: 'Starter',
    +        filename: 'index.html',
    +        template: path.resolve(__dirname, 'public/index.html'),
    +        minify: IS_PROD
    +          ? {
    +            removeComments: true,
    +            collapseWhitespace: true,
    +            removeAttributeQuotes: true,
    +            collapseBooleanAttributes: true,
    +            removeScriptTypeAttributes: true
    +          }
    +          : {}
    +      }),
    +    ]
    
      ...
    
  • Изменить общедоступный/index.html

      <!DOCTYPE html>
      <html lang="en">
        <head>
          <meta charset="utf-8" />
          <link rel="icon" href="./favicon.ico" />
          <meta
            name="viewport"
            content="width=device-width, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover"
          />
          <meta name="theme-color" content="#000000" />
          <meta
            name="description"
            content="This is a react application built from scratch with JavaScript, away from the cli tool."
          />
    -     <title>Starter</title>
    +     <title><%= htmlWebpackPlugin.options.title %></title>
        </head>
    
        <body>
          <noscript>You need to enable JavaScript to run this app.</noscript>
          <div id="root"></div>
    -     <script src="bundle.js"></script>
        </body>
      </html>
    
  • С приведенной выше конфигурацией давайте посмотрим на эффект

    $ yarn build
    
    # 结果
    $ cross-env NODE_ENV=production webpack --color --progress
    Hash: 6bb93a13b6a8a7926f58
    Version: webpack 4.41.2
    Time: 4418ms
    Built at: 2019-10-21 11:47:05
    
              Asset         Size             Chunks                   Chunk Names
          index.html      553 bytes          [emitted]
        main.2f781ad1.js   136 KiB         0  [emitted] [immutable]      main
    
    Entrypoint main = main.2f781ad1.js
    [5] ./src/index.scss 498 bytes {0} [built]
    [7] ./src/index.js 1.57 KiB {0} [built]
    [8] (webpack)/buildin/harmony-module.js 573 bytes {0} [built]
    [13] ./src/style/global.css 457 bytes {0} [built]
    [14] ./node_modules/css-loader/dist/cjs.js!./node_modules/postcss-loader/src!./src/style/global.css 237 bytes {0} [built]
    [15] ./node_modules/css-loader/dist/cjs.js!./src/style/reset.css 1.28 KiB {0} [built]
    [16] ./node_modules/css-loader/dist/cjs.js??ref--5-1!./node_modules/postcss-loader/src!./node_modules/sass-loader/dist/cjs.js!./src/index.scss 238 bytes {0} [built]
        + 11 hidden modules
    Child html-webpack-plugin for "index.html":
        1 asset
        Entrypoint undefined = index.html
        [0] ./node_modules/html-webpack-plugin/lib/loader.js!./public/index.html 858 bytes {0} [built]
        [2] (webpack)/buildin/global.js 472 bytes {0} [built]
        [3] (webpack)/buildin/module.js 497 bytes {0} [built]
            + 1 hidden module
    ✨  Done in 6.04s.
    
    <!-- starter/dist/index.html -->
    <!DOCTYPE html><html lang=en><head><meta charset=utf-8><link rel=icon href=./favicon.ico><meta name=viewport content="width=device-width,minimum-scale=1,maximum-scale=1,user-scalable=no,viewport-fit=cover"><meta name=theme-color content=#000000><meta name=description content="This is a react application built from scratch with JavaScript, away from the cli tool."><title>React App TS</title></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id=root></div><script src=/starter/main.2f781ad1.js></script></body></html>
    

    Примечание. Если вы внимательно посмотрите на наш вывод, вы обнаружитеmain.2f781ad1.js size=136KiB, а наш код очень маленький, если вы откроете файл, то обнаружите, что он содержитreact.production.min.js babelНеобходимые вспомогательные функции и т.д.

    ⬆ вернуться к началу

разделение кода

  • mini-css-extract-plugin- отдельный css-код

    По умолчанию webpack помещает css и js в один файл, плагин извлекает CSS в отдельные файлы. Он создает файл CSS для каждого файла JS, содержащего CSS.

    Почему отдельно?webpack-contrib/mini-css-extract-plugin

    $ yarn add -D mini-css-extract-plugin # 安装
    
    <!-- starter/webpack.config.js -->
      ...
    + const MiniCssExtractPlugin = require('mini-css-extract-plugin');
    
      module: {
        rules: [
          {
            test: /\.(js|jsx)$/,
            exclude: /node_modules/,
            loader: 'babel-loader'
          },
          {
            test: /\.(sa|sc)ss$/,
            exclude: /node_modules/,
            use: [
              {
    -           loader: 'style-loader'
    +           loader: IS_PROD ? MiniCssExtractPlugin.loader : 'style-loader',
    +           options: IS_PROD ? { publicPath: '../' } : {}
              },
              {
                loader: 'css-loader',
                options: {
                  sourceMap: false,
                  importLoaders: 2,
                  modules: {
                    context: path.resolve(__dirname, 'src'),
                    localIdentName: '[name]__[local]-[hash:base64:5]'
                  }
                }
              },
              {
                loader: 'postcss-loader'
              },
              {
                loader: 'sass-loader'
              }
            ]
          },
          {
            test: /\.css$/,
            exclude: /node_modules/,
    -       use: ['style-loader', 'css-loader', 'postcss-loader']
    +       use: [
    +         {
    +           loader: IS_PROD ? MiniCssExtractPlugin.loader : 'style-loader',
    +           options: IS_PROD ? { publicPath: '../' } : {}
    +         },
    +         'css-loader',
    +         'postcss-loader'
    +       ]
          }
        ]
      },
    + plugins: [
        ...,
    +   new MiniCssExtractPlugin({
    +     filename: IS_PROD ? 'css/[name].[contenthash:8].css' : 'css/[name].css',
    +     chunkFilename: IS_PROD ? 'css/[name].[contenthash:8].css' : 'css/[name].css'
    +   })
      ]
    
      $ yarn build
    
      $ cross-env NODE_ENV=production webpack --color --progress
      Hash: 95fccf0e0844c2df588f
      Version: webpack 4.41.2
      Time: 4416ms
      Built at: 2019-10-21 13:56:23
                      Asset       Size           Chunks                 Chunk Names
    ! css/main.f9cee851.css     1.08 KiB       0  [emitted] [immutable]    main
                index.html      605 bytes         [emitted]
    !     main.ced0f821.js      131 KiB        0  [emitted] [immutable]    main
      Entrypoint main = css/main.f9cee851.css main.ced0f821.js
    
    

    После многократных упаковок мы нашли во многих местах много последних файлов результатов, которые явно не выдерживают w(゚Д゚)w; мы хотим удалить папку, сгенерированную предыдущей сборкой, перед каждой сборкой.

  • clean-webpack-pluginсодержать каталог в чистоте

    Используется для удаления папки сборки перед сборкой

    $ yarn add -D clean-webpack-plugin # 安装
    
    <!-- starter/webpack.config.js -->
    
      ...
    
    + const { CleanWebpackPlugin } = require('clean-webpack-plugin');
    
      ...
    
      plugins: [
        ...,
    
    +   new CleanWebpackPlugin()
      ]
    
      ...
    

    Попробуйте👀, я почистил (。・∀・)ノ゙Try it!

    ⬆ вернуться к началу

предотвратить повторение

  • optimization.splitChunksИзвлечь общие зависимые модули в существующий фрагмент записи

    <!-- starter/webpack.config.js -->
    
      ...
    
      module.exports = function () {
        const baseConfig = {
          ...
        }
    
    +   if (IS_PROD) {
    +     baseConfig.optimization = {
    +       minimizer: [
    +         // Automatically split vendor and commons
    +         splitChunks: {
    +           chunks: 'all'
    +         }
    +       ]
    +     }
    +   }
    
        return baseConfig;
      }
    
      $ yarn build # 打包查看效果
    
      # 结果
    
      $ cross-env NODE_ENV=production webpack --color --progress
      Hash: ebe27d1c4dc54ff22c4b
      Version: webpack 4.41.2
      Time: 4470ms
      Built at: 2019-10-21 14:28:59
    
                                Asset     Size                  Chunks             Chunk Names
    ! chunks/vendors~main.f501917c.js    129 KiB       1  [emitted] [immutable]   vendors~main
                css/main.f9cee851.css     1.08 KiB      0  [emitted] [immutable]      main
    !                     index.html     667 bytes        [emitted]
    !               main.76c9ecec.js     2.54 KiB      0  [emitted] [immutable]      main
    
      Entrypoint main = chunks/vendors~main.f501917c.js css/main.f9cee851.css main.76c9ecec.js
    
      # 注意:如果你仔细看 chunks/vendors~main.f501917c.js 你会发现 与 react 相关的库
      #(react.production.min.js、react-dom.production.min.js、scheduler.production.min.js)和你代
      # 码所引用的公共库都将被提取出来,防止重复引用。
    

    webpack 4: Code Splitting, chunk graph and the splitChunks optimization

    ⬆ вернуться к началу

  • @babel/plugin-transform-runtimeПлагин, который повторно использует вспомогательный код, внедренный Babel, для экономии размера кода.

    $ yarn add -D @babel/plugin-transform-runtime
    
    <!-- starter/postcss.config.js -->
    
      {
        "presets": [
          "@babel/preset-env",
          "@babel/preset-react"
        ],
        "plugins": [
    +     "@babel/plugin-transform-runtime",
          "react-hot-loader/babel"
        ]
      }
    
      $ yarn build
    
      # 结果
    
      $ cross-env NODE_ENV=production webpack --color --progress
      Hash: 6425898f896ed7244e2b
      Version: webpack 4.41.2
      Time: 4510ms
      Built at: 2019-10-21 15:18:47
    
                                Asset       Size                Chunks                Chunk Names
    ! chunks/vendors~main.e9e35553.js      130 KiB       1  [emitted] [immutable]     vendors~main
                css/main.f9cee851.css      1.08 KiB      0  [emitted] [immutable]        main
                            index.html      667 bytes        [emitted]
    !                main.5fb316df.js      2.07 KiB      0  [emitted] [immutable]        main
      Entrypoint main = chunks/vendors~main.e9e35553.js css/main.f9cee851.css main.5fb316df.js
    
      # 可以比对上次构建结果,主文件减少了一些。
    

    ⬆ вернуться к началу

  • webpack.DefinePluginПозволяет создать глобальную константу, которую можно настроить во время компиляции.

    Плагины могут настраивать некоторые глобальные переменные, которые будут заменены теми переменными, на которые есть ссылки в коде во время сборки. Например: NODE_ENV (часто используется для производственных сред и сред разработки). Если ведение журнала выполняется в сборке разработки, а не в сборке выпуска, можно использовать глобальную константу, чтобы решить, вести журнал или нет. Для этого и нужен DefinePlugin, настройте его и можете забыть о правилах для dev и production сборок.

    <!-- starter/webpack.config.js -->
    
      ...
    
      plugins: [
    
        ...,
    
    +   new webpack.DefinePlugin({
    +     'process.env': {
    +       NODE_ENV: JSON.stringify(process.env.NODE_ENV),
    +     }
    +   })
      ]
    
      ...
    

    Здесь, если ваш код не дифференцируется в среде и не выполняет специфическую обработку (удаление кода в среде разработки), размер пакета останется неизменным.

    ⬆ вернуться к началу

minify JavaScript / css

  • uglifyjs-webpack-plugin

    $ yarn add -D uglifyjs-webpack-plugin
    
    <!-- starter/webpack.config.js -->
    
      ...
    
    +  const UglifyjsWebpackPlugin = require('uglifyjs-webpack-plugin');
    
      ...
    
        if (IS_PROD) {
        baseConfig.optimization = {
    +     minimizer: [
    +       new UglifyjsWebpackPlugin({
    +         exclude: /node_modules/,
    +         sourceMap: false,  // 使用源映射将错误消息位置映射到模块(这会减慢编译速度)。如果您使用自己的缩小功能,请阅读缩小部分以正确处理源地图。
    +         cache: true, // 启用文件缓存
    +         parallel: true // 使用多进程并行运行可提高构建速度。并发运行的默认数量:os.cpus().length - 1.
    +       })
    +     ],
          splitChunks: {
            chunks: 'all',
          }
        };
      }
    
      $ yarn build # 打包验证 ✅
    
      # 结果
    
      $ cross-env NODE_ENV=production webpack --color --progress
      Hash: 3f450244bccc719560c5
      Version: webpack 4.41.2
      Time: 2209ms
      Built at: 2019-10-21 16:25:18
    
                                Asset       Size                  Chunks             Chunk Names
    ! chunks/vendors~main.1a122e64.js      129 KiB       1  [emitted] [immutable]    vendors~main
                css/main.f9cee851.css      1.08 KiB      0  [emitted] [immutable]       main
                            index.html      667 bytes        [emitted]
                      main.e82008bc.js      2.07 KiB      0  [emitted] [immutable]       main
    
      Entrypoint main = chunks/vendors~main.1a122e64.js css/main.f9cee851.css main.e82008bc.js
    

    Уведомление:uglifyjs-webpack-plugin v2.xверсия, основанная наuglify-js, не может поддерживатьES6компрессия

    Ссылаться на:Почему webpack4 по умолчанию поддерживает сжатие синтаксиса ES6?

    ⬆ вернуться к началу

  • terser-webpack-plugin

    мы используемterser-webpack-pluginзаменятьuglifyjs-webpack-plugin

    $ yarn add -D terser-webpack-plugin
    
    <!-- starter/webpack.config.js -->
    
    - const UglifyjsWebpackPlugin = require('uglifyjs-webpack-plugin');
    + const TerserPlugin = require('terser-webpack-plugin');
    
      if (IS_PROD) {
        baseConfig.optimization = {
          minimizer: [
    -       new UglifyjsWebpackPlugin({
    -         exclude: /node_modules/,
    -         sourceMap: false,
    -         cache: true,
    -         parallel: true
    -       }),
    +       new TerserPlugin({
    +         // Terser minify options.
    +         terserOptions: {
    +           parse: {
    +             // We want terser to parse ecma 8 code. However, we don't want it
    +             // to apply any minification steps that turns valid ecma 5 code
    +             // into invalid ecma 5 code. This is why the 'compress' and 'output'
    +             // sections only apply transformations that are ecma 5 safe
    +             ecma: 8,
    +           },
    +           compress: {
    +             ecma: 5,
    +             // display warnings when dropping unreachable code or unused declarations etc.
    +             warnings: false,
    +             // apply certain optimizations to binary nodes
    +             // Disabled because of an issue with Uglify breaking seemingly valid code:
    +             // Pending further investigation: https://github.com/mishoo/UglifyJS2/issues/2011
    +             comparisons: false,
    +             // inline calls to function with simple/return statement:
    +             // Disabled because of an issue with Terser breaking valid code:
    +             // Pending further investigation: https://github.com/terser-js/terser/issues/120
    +             inline: 2, // inline functions with arguments
    +           },
    +           mangle: {
    +             // Pass true to work around the Safari 10 loop iterator bug "Cannot declare a let variable twice".
    +             // See also: the safari10 output option.
    +             safari10: true,
    +           },
    +           // Added for profiling in devtools
    +           keep_classnames: true,
    +           keep_fnames: true,
    +           output: {
    +             ecma: 5,
    +             // pass true or "all" to preserve all comments, "some" to preserve some comments,
    +             // a regular expression string (e.g. /^!/) or a function.
    +             comments: false,
    +             // escape Unicode characters in strings and regexps (affects directives with non-ascii characters becoming invalid)
    +             // Turned on because emoji and regex is not minified properly using default
    +             ascii_only: true,
    +           },
    +         },
    +         // Use multi-process parallel running to improve the build speed.
    +         //Default number of concurrent runs: os.cpus().length - 1.
    +         parallel: true,
    +         cache: true, // Enable file caching
    +       }),
          ],
          splitChunks: {
            chunks: 'all',
          }
        };
      }
    
      $ yarn build
    
      $ cross-env NODE_ENV=production webpack --color --progress
      Hash: dbf5243d5591e4ac0268
      Version: webpack 4.41.2
      Time: 2461ms
      Built at: 2019-10-21 17:52:26
    
                                        Asset     Size             Chunks                  Chunk Names
    !         chunks/vendors~main.ae62441b.js    130 KiB       1  [emitted] [immutable]    vendors~main
    ! chunks/vendors~main.ae62441b.js.LICENSE    790 bytes        [emitted]
                        css/main.f9cee851.css    1.08 KiB      0  [emitted] [immutable]       main
                                    index.html    667 bytes        [emitted]
    !                        main.2130b172.js    2.52 KiB      0  [emitted] [immutable]       main
    
      Entrypoint main = chunks/vendors~main.ae62441b.js css/main.f9cee851.css main.2130b172.js
    

    ⬆ вернуться к началу

  • optimize-css-assets-webpack-plugin- Оптимизация/уменьшение активов CSS

    $ yarn add -D optimize-css-assets-webpack-plugin # 压缩 CSS
    $ yarn add -D postcss-safe-parser                # 查找并修复 CSS 语法错误
    
    <!-- starter/webpack.config.js -->
      ...
    
    + const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');
    + const SafePostCssParser = require('postcss-safe-parser');
    
        if (IS_PROD) {
          baseConfig.optimization = {
            minimizer: [
              ...
    
    +         new OptimizeCSSAssetsPlugin({
    +           // The options passed to the cssProcessor, defaults to {}
    +           // cssProcessor: The CSS processor used to optimize \ minimize the CSS, defaults to cssnano.
    +           //               This should be a function that follows cssnano.process interface
    +           //               (receives a CSS and options parameters and returns a Promise).
    +           cssProcessorOptions: {
    +             parser: SafePostCssParser,
    +             map: false,
    +           },
    +         })
            ],
    
            ...
          };
        }
    
      ...
    
      $ yarn build # 打包实验 ✅
    
    
      # 结果
    
      $ cross-env NODE_ENV=production webpack --color --progress
      Hash: dbf5243d5591e4ac0268
      Version: webpack 4.41.2
      Time: 3543ms
      Built at: 2019-10-21 20:17:23
    
                                        Asset     Size                  Chunks            Chunk Names
              chunks/vendors~main.ae62441b.js    130 KiB       1  [emitted] [immutable]   vendors~main
      chunks/vendors~main.ae62441b.js.LICENSE    790 bytes        [emitted]
    !                   css/main.f9cee851.css    869 bytes     0  [emitted] [immutable]       main
                                    index.html    667 bytes        [emitted]
                              main.2130b172.js    2.52 KiB      0  [emitted] [immutable]       main
    
      Entrypoint main = chunks/vendors~main.ae62441b.js css/main.f9cee851.css main.2130b172.js
    

    ⬆ вернуться к началу

Внешнее расширение (externals)

Исключите зависимости из выходного пакета; предотвратите упаковку некоторых импортированных пакетов в пакеты, а вместо этого извлеките эти внешние зависимости во время выполнения.

  • CDN — этот шаг можно пропустить

    <!-- starter/webpack.config.js -->
    
      ...
    
      module.exports = function() {
        const baseConfig = {
    
          ...
    
          resolve: {
            alias: {
              'react-dom': '@hot-loader/react-dom' // react-hot-loader 兼容 hook 写法
            }
          },
    
    +     externals: {
    +       react: 'React',
    +       'react-dom': 'ReactDOM'
    +     },
    
          ...
    
        }
    
        ...
    
    
    <!--  starter/public/index.html -->
      ...
    
        <div id="root"></div>
    +   <script crossorigin src="https://unpkg.com/react@16.10.2/umd/react.production.min.js"></script>
    +   <script crossorigin src="https://unpkg.com/@hot-loader/react-dom@16.10.2/umd/react-dom.production.min.js"></script>
    
      ...
    
      $ yarn build
    
      $ cross-env NODE_ENV=production webpack --color --progress
        Hash: 6bb2de2632bdaf2dc081
        Version: webpack 4.41.2
        Time: 2557ms
        Built at: 2019-10-21 21:35:38
                        Asset       Size                Chunks             Chunk Names
        css/main.34dd0d40.css     869 bytes     0  [emitted] [immutable]      main
                    index.html     811 bytes        [emitted]
              main.da7fbe78.js     3.85 KiB      0  [emitted] [immutable]      main
        Entrypoint main = css/main.34dd0d40.css main.da7fbe78.js
    

    1. Что такое CDN? Каковы преимущества использования CDN?
    2. Несколько публичных библиотек CDN:cdnjs,jsdelivr,unpkg
    3. Для повышения скорости доступа лучше всего исключить из выходного бандла зависимости нечасто обновляемых библиотек классов фронтенда, таких как react, react-dom, axios, moment и т.д.
    4. Напоминаем, что лучше всего получить его самостоятельно, и всегда безопасно использовать свой собственный 🤡

    ⬆ вернуться к началу

Каталог проекта

└── starter
+ ├── dist
+ │   └── chunks
+ │   │   ├── vendors~main.ae62441b.js
+ │   │   └── vendors~main.ae62441b.js.LICENSE
+     ├── css
+ │   │   └── main.f9cee851.css
+ │   ├── index.html
+ │   └── main.2130b172.js
  ├── node_modules
  ├── public
  │   ├── favicon.ico
  │   └── index.html
  ├── src
+ │   └── style
+ │   |   ├── global.css
+ │   |   └── reset.css
  |   ├── index.js
+ │   ├── index.scss
+ ├── postcss.config.js
  ├── webpack.config.js
  ├── package.json
  ├── README.md
  ├── LICENSE
  └── yarn.lock

сценический эпилог

  1. Пока грубо обсуждался весь процесс строительства и моменты оптимизации, сделанные в процессе строительства.Конечно, есть еще некоторые недочеты. 📚
  2. Для завершения проекта предстоит еще много работы, так что вперед! 🔥👇🔥

⬆ вернуться к началу

16. Импорт маршрутов

Для интерфейсных одностраничных приложений маршрутизация имеет важное значение.В настоящее время основные фреймворки имеют соответствующие подключаемые модули маршрутизации, которые представлены здесь вместе с выбранным фреймворком.react-router-dom

  • Установить

    $ yarn add react-router-dom
    
  • Создайте новую папку конфигурации маршрутизации

    $ cd src && mkdir router # 新建 router 文件夹
    $ cd router
    $ touch index.js         # 新建路由配置文件
    $ touch list.js          # 新建路由表文件
    
  • Написать конфигурацию маршрутизации и таблицу маршрутизации


    • Конфигурация маршрутизации — src/router/index.js

      import React from 'react';
      import {
        BrowserRouter,
        Route,
        Switch,
        Redirect
      } from 'react-router-dom';
      import routes from './list';
      
      function RouterView(route) {
        return (
          <Route
            path={route.path}
            render={(props) => {
              if (route.redirect) {
                return <Redirect to={route.redirect} />;
              }
              return (
                <route.component
                  {...props}
                  render={() => (
                    <Switch>
                      {route.routes.map((children) => (
                        <RouterView key={children.path} {...children} />
                      ))}
                    </Switch>
                  )}
                />
              );
            }}
          />
        );
      }
      
      export default function Router() {
        return (
          <BrowserRouter>
            <Switch>
              {routes.map((route) => (
                <RouterView key={route.path} {...route} />
              ))}
            </Switch>
          </BrowserRouter>
        );
      }
      

      Это написано в соответствии с документом конфигурации маршрутизации и используется только для DEMO; для получения подробной информации см.react-router: Route Config

    • Таблица маршрутизации — src/router/list.js

      import Github from '../views/Github/Github';
      import Setting from '../views/Setting/Setting';
      
      const routes = [
        {
          path: '/',
          exact: true,
          redirect: '/github'
        },
        {
          path: '/github',
          component: Github,
        },
        {
          path: '/setting',
          component: Setting,
        }
      ];
      
      export default routes;
      

      Вы можете создать любое имя здесь 🙄

    ⬆ вернуться к началу

  • Создайте новую настройку, страницу GitHub и напишите

    # 新建 Setting、GitHub 页面
    $ cd src/views
    $ mkdir Github && cd Github
    $ touch Github.js && touch Github.scss
    $ cd ..
    
    $ mkdir Setting && cd Setting
    $ touch Setting.js && touch Setting.scss
    $ cd ..
    
    // starter/Github/Github.js
    import React from 'react';
    import { useHistory } from 'react-router-dom';
    import styles from './Github.scss';
    
    function Github() {
      const history = useHistory();
    
      function handleClick() {
        history.push('/setting');
      }
    
      return (
        <div className={`${styles.root}`}>
          <h1>Github</h1>
          <div className={`${styles.bg} ${styles.wh}`}>
            {`当前环境: ${process.env.NODE_ENV}`}
          </div>
          <button type='button' onClick={handleClick}>
            Go setting
          </button>
        </div>
      );
    }
    
    export default Github;
    
    // starter/Setting/Setting.js
    import React from 'react';
    import { useHistory } from 'react-router-dom';
    import styles from './Setting.scss';
    
    function Setting() {
      const history = useHistory();
    
      function handleClick() {
        history.push('/github');
      }
    
      return (
        <div className={`${styles.root}`}>
          <h1>Setting</h1>
          <div className={`${styles.bg} ${styles.wh}`}>
            {`当前环境: ${process.env.NODE_ENV}`}
          </div>
          <button type='button' onClick={handleClick}>
            Go github
          </button>
        </div>
      );
    }
    
    export default Setting;
    
    // starter/Setting/Setting.scss
    .root {
      .wh {
        width: 200px;
        height: 180px;
      }
      .bg {
        text-align: center;
        line-height: 180px;
        background: no-repeat url('~assets/images/logo.png');
      }
    }
    // starter/Github/Github.scss
    .root {
      .wh {
        width: 200px;
        height: 200px;
      }
      .bg {
        text-align: center;
        line-height: 200px;
        background: no-repeat url('~assets/images/logo.png');
      }
    }
    

    Поскольку стиль вводит изображения, мы создаем новую папку хранения ресурсов для хранения этих ресурсов.

    $ cd src && mkdir assets
    $ cd assets && mkdir images
    $ cd images
    $ copy logo.png # 这里的图标是官网搂过来的,🤣
    
  • Измените наш основной файл src/index.js

      import { hot } from 'react-hot-loader';
    - import React, { useState } from 'react';
    + import React from 'react';
      import ReactDom from 'react-dom';
      import './style/global.css';
    - import styles from './index.scss';
    + import Router from './router/index';
    
    - const App = hot(module)(() => {
    -   const reversedTitle = () =>
    -     setTitle(
    -       title
    -         .split('')
    -         .reverse()
    -         .join('')
    -     );
    -   return (
    -     <div className={styles.app}>
    -       <h1>{title}</h1>
    -       <button type='button' onClick={reversedTitle}>
    -         reversed title!
    -       </button>
    -     </div>
    -   );
    - });
    
    + const App = hot(module)(() => (
    +   <div className='app'>
    +     <Router />
    +   </div>
    + ));
    
      ReactDom.render(<App />, document.getElementById('root'));
    
  • Теперь все готово, но прежде чем приступить к проекту, несколько моментов

    1. (Мы представили изображения на странице, и по мере роста проекта мы можем добавить значки шрифтов, аудио и другие файлы). Здесь мы используем веб-пакет, чтобы помочь нам управлять этими ресурсами унифицированным образом.
    2. По мере углубления проекта структура каталогов будет становиться все более сложной.webpack - resolve.alias, создавать псевдонимы для импорта или требовать, чтобы упростить импорт модулей.

    Сделайте некоторые улучшения ️ ⚓️

    ⬆ вернуться к началу

17. Управление ресурсами,оптимизацияРазрешение модуля

  • Разрешение модуля

    <!-- starter/webpack.config.js -->
      ...
    
        resolve: {
          alias: {
            'react-dom': '@hot-loader/react-dom', // react-hot-loader 兼容 hook 写法
    +       '@': path.resolve(__dirname, 'src'),
    +       assets: path.resolve(__dirname, 'src/assets'),
    +       style: path.resolve(__dirname, 'src/style')
          }
        },
    
      ...
    
  • Управление ресурсами

    # 安装
    
    $ yarn add -D url-loader  # 将文件转换为 base64 URI。
    $ yarn add -D file-loader # 将文件上的 import/require() 解析为 url,并将该文件发射到输出目录中。
    
    <!-- starter/webpack.config.js -->
    
      module: {
        rules: [
          ...
    
    +     {
    +       test: /\.(png|jpe?g|gif|webp)(\?.*)?$/, // 匹配这些格式的图片
    +       use: [
    +         {
    +           loader: 'url-loader',
    +           options: {
    +             limit: 4096, // 文件大小等于或大于限制,则将使用 file-loader。
    +             fallback: {
    +               loader: 'file-loader',
    +               options: {
    +                 name: 'images/[name].[hash:8].[ext]'
    +               }
    +             }
    +           }
    +         }
    +       ]
    +     },
    +     {
    +       test: /\.(svg)(\?.*)?$/,
    +       use: [
    +         {
    +           loader: 'file-loader',
    +           options: {
    +             name: 'svg/[name].[hash:8].[ext]'
    +           }
    +         }
    +       ]
    +     },
    +     {
    +       test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/i,
    +       use: [
    +         {
    +           loader: 'url-loader',
    +           options: {
    +             limit: 4096,
    +             fallback: {
    +               loader: 'file-loader',
    +               options: {
    +                 name: 'fonts/[name].[hash:8].[ext]'
    +               }
    +             }
    +           }
    +         }
    +       ]
    +     },
    +     {
    +       test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/,
    +       use: [
    +         {
    +           loader: 'url-loader',
    +           options: {
    +             limit: 4096,
    +             fallback: {
    +               loader: 'file-loader',
    +               options: {
    +                 name: 'media/[name].[hash:8].[ext]'
    +               }
    +             }
    +           }
    +         }
    +       ]
    +     }
    +   ]
      }
    

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

  • Хорошо, давайте начнем наш проект. Попытайся!

    $ yarn server
    

    x

  • Пакет

      $ yarn build
    
      # 结果
    
      $ cross-env NODE_ENV=production webpack --color --progress
      Hash: fb9c0cc487e7845fd915
      Version: webpack 4.41.2
      Time: 3533ms
      Built at: 2019-10-23 11:42:44
    
                                        Asset       Size              Chunks               Chunk Names
              chunks/vendors~main.64d1203b.js    160 KiB       1  [emitted] [immutable]    vendors~main
      chunks/vendors~main.64d1203b.js.LICENSE    1.01 KiB         [emitted]
    !                   css/main.b7d00a9e.css    1.19 KiB      0  [emitted] [immutable]        main
                    images/logo.581fa1d8.png     8.38 KiB         [emitted]
                                  index.html     667 bytes        [emitted]
    !                       main.cfa18e59.js     3.95 KiB      0  [emitted] [immutable]        main
    
      Entrypoint main = chunks/vendors~main.64d1203b.js css/main.b7d00a9e.css main.cfa18e59.js
    
  • Проблемы и моменты, требующие оптимизации

    1. По мере увеличения сложности проекта, когда приложение будет упаковано и построено, пакет JavaScript станет очень большим, что повлияет на загрузку страницы. Было бы более эффективно, если бы мы могли разделить компоненты, соответствующие разным маршрутам, на разные блоки кода, а затем загружать соответствующие компоненты при доступе к маршруту.

⬆ вернуться к началу

18. Отложенная загрузка маршрута@loadable/component

Примечание. Использование этого подключаемого модуля для разделения кода вашего приложения может помочь вам «лениво загружать» контент, который нужен текущему пользователю, что может значительно повысить производительность вашего приложения. Хотя это не уменьшает общий размер кода приложения, вы можете избежать загрузки кода, который никогда не понадобится пользователю, и уменьшить объем кода, который необходимо загрузить при начальной загрузке.

  • Установить

    # 当然你也可以选择,React.lazy 和 Suspense,但他们还不支持服务端渲染。这里直接选择功能更加强大的 @loadable/component
    
    $ yarn add @loadable/component
    
  • Изменить таблицу маршрутизации

    ! <!-- src/router/list -->
    
    - import Github from '@/views/Github/Github';
    - import Setting from '@/views/Setting/Setting';
    + import React from 'react';
    + import loadable from '@loadable/component';
    
    + const Github = import(/* webpackChunkName: "github" */ '@/views/Github/Github.js');
    + const Setting = import/* webpackChunkName: "setting" */ ('@/views/Setting/Setting.js');
    
    + const AsyncComponent = (loader) => loadable(loader, { fallback: <h3>Loading...</h3> });
    
      const routes = [
        {
          path: '/',
          exact: true,
          redirect: '/github'
        },
        {
          path: '/github',
    -     component: Github
    +     component: AsyncComponent(() => Github)
        },
        {
          path: '/setting',
    -     component: Setting
    +     component: AsyncComponent(() => Setting)
        }
      ];
    
      export default routes;
    
  • Упакуйте наше приложение и посмотрите на результаты разделения кода.

      $ yarn build
    
      # 结果
    
      $ cross-env NODE_ENV=production webpack --color --progress
      Hash: b93be70da668f4dff43b
      Version: webpack 4.41.2
      Time: 6077ms
      Built at: 2019-10-23 16:21:08
    
                                        Asset        Size                  Chunks            Chunk Names
    !               chunks/github.45dc6c0d.js    634 bytes       0  [emitted] [immutable]      github
    !             chunks/setting.316d765f.js     637 bytes       2  [emitted] [immutable]      setting
              chunks/vendors~main.a51021eb.js    164 KiB         3  [emitted] [immutable]     vendors~main
      chunks/vendors~main.a51021eb.js.LICENSE    1.01 KiB           [emitted]
    !                css/github.8de607a6.css     191 bytes       0  [emitted] [immutable]       github
    !               css/setting.1a0bfbdd.css     195 bytes       2  [emitted] [immutable]       setting
                        css/main.c1fb052e.css    830 bytes       1  [emitted] [immutable]        main
                    images/logo.581fa1d8.png     8.38 KiB           [emitted]
                                  index.html     667 bytes          [emitted]
                            main.02bdd0e7.js     4.96 KiB        1  [emitted] [immutable]        main
    
      Entrypoint main = chunks/vendors~main.a51021eb.js css/main.c1fb052e.css main.02bdd0e7.js
    
  • Каталог проекта

    └── starter
    + ├── dist
    + │   └── chunks
    + │   │   ├── github.45dc6c0d.js
    + │   │   ├── setting.316d765f.js
    + │   │   ├── vendors~main.a51021eb.js
    + │   │   └── vendors~main.a51021eb.js.LICENSE
    +     ├── css
    + │   │   ├── 0.8de607a6.css
    + │   │   ├── 2.1a0bfbdd.css
    + │   │   └── main.c1fb052e.css
    + │   ├── images
    + │   │   └── logo.581fa1d8.png
      │   ├── index.html
    + │   └── main.02bdd0e7.js
      ├── node_modules
      ├── public
      │   ├── favicon.ico
      │   └── index.html
      ├── src
    + │   ├── assets
    + │   │   └── images
    + │   │       └── logo.png
    + │   ├── router
    + │   │   ├── index.js
    + │   │   └── list.js
      │   ├── style
      │   |   ├── global.css
      │   |   └── reset.css
    + |   ├── views
    + │   |   ├── Github
    + │   |   │   ├── Github.js
    + │   |   │   └── Github.scss
    + │   |   └── Setting
    + │   |       ├── Setting.js
    + │   |       └── Setting.scss
    - │   ├── index.scss
      |   └──  index.js
      ├── postcss.config.js
      ├── webpack.config.js
      ├── package.json
      ├── README.md
      ├── LICENSE
      └── yarn.lock
    
    

    На данный момент мы добавили функцию маршрутизации, продолжим доработку! 🚘

⬆ вернуться к началу

19. Стандарты кодирования

Пока что количество кода в нашем проекте увеличивается, и написанный код все еще может иметь некоторые потенциальные проблемы (этого трудно избежать); другой, большой проект часто поддерживается командой, но стиль кода членов команды отличается. не совсем так. Исходя из этого, нам нужен инструмент для решения этих болевых точек.

  • инструмент

    • eslint:Обычно используется для проверки распространенных ошибок кода JavaScript, а также может выполнять проверки стиля кода.

    • stylelint:Мощный современный линтер, помогающий избежать ошибок и обеспечить соблюдение соглашений о стилях.

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


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

настроитьeslint

  • Установить

    $ yarn add -D eslint                          # eslint
    $ yarn add -D babel-eslint                    # 一个对 Babel 解析器的包装,使其能够与 ESLint 兼容
    $ yarn add -D eslint-plugin-react             # 检测 react 代码
    $ yarn add -D eslint-plugin-react-hooks       # 用于检测 hook 规则
    $ yarn add -D eslint-plugin-jsx-a11y          # 用于检测 jsx 规范
    $ yarn add -D eslint-plugin-import            # ESLint 插件,带有有助于验证正确导入的规则。
    $ yarn add -D eslint-import-resolver-webpack  # 用于 eslint-plugin-import的 Webpack-literate 模块解析插件。
    
  • Создайте новый файл конфигурации eslint

    $ touch .eslintrc     # eslint 配置文件
    $ touch .eslintignore # eslint 忽略检测配置文件
    
    <!-- starter/.eslintrc -->
    
      {
        "root": true,
        "env": {
          "es6": true,
          "browser": true,
        },
        "extends": ["eslint:recommended", "plugin:react/recommended", "plugin:jsx-a11y/recommended"],
        "parser": "babel-eslint",
        "plugins": ["react", "jsx-a11y", "react-hooks", "import"],
        "rules": {
          "semi": ["error", "always"],
          "quotes": ["error", "single"],
          "camelcase": [0, { "properties": "never" }],
          "no-console": [2, { "allow": ["warn", "error"] }],
          "react/jsx-filename-extension": [1, { "extensions": [".js", ".jsx"] }],
          "react/jsx-props-no-spreading": "off",
          "jsx-a11y/click-events-have-key-events": "off",
          "react-hooks/rules-of-hooks": "error",
          "react-hooks/exhaustive-deps": "warn",
          "react/no-unused-prop-types": "off"
        },
        "settings": {
          "react": {
            "version": "16.10.2"
          },
          "import/resolver": "webpack"
        },
        "globals": {
          "process": true,
          "module": true
        }
      }
    
    <!-- starter/.eslintignore -->
    
      node_modules
      dist
    
  • Инструкции по настройке

    1. "eslint:recommended"Включить рекомендуемые правила
    2. "plugin:react/recommended"Плагин экспортирует предлагаемые конфигурации для обеспечения соблюдения передового опыта React.
    3. "babel-eslint"Оболочка парсера Babel, чтобы сделать его совместимым с ESLint.
    4. правила: Пользовательские правила, которые могут переопределить расширенную конфигурацию.
    5. eslint-plugin-importЭтот плагин предназначен для поддержки проверки синтаксиса импорта/экспорта ES2015+ (ES6+) и предотвращения проблем с путями к файлам с ошибками и именами импорта.
    6. "import/resolver": "webpack": Решить проблему, вызванную конфигурацией псевдонима веб-пакета.eslint-plugin-importсообщить об ошибке.
    7. Эта конфигурация представляет собой простую подробную настройку, пожалуйста, обратитесь кConfiguring ESLint

    Примечание. Конфигурация eslint должна согласовать набор действующих спецификаций в соответствии с внутренней командой.

  • Измените package.json, чтобы создать новую команду быстрого доступа.

    <!-- starter/package.json -->
    
         "scripts": {
           "test": "echo \"Error: no test specified\" && exit 1",
           "server": "cross-env NODE_ENV=development webpack-dev-server --color --progress",
           "build": "cross-env NODE_ENV=production webpack --color --progress",
    +      "lint:script": "eslint --ext '.js,.jsx' src",
    +      "lint-fix:script": "npm run lint:script -- --fix"
         },
    
  • Выполните команду, чтобы увидеть, есть ли какие-либо нарушения

    $ yarn lint:script     # 执行 lint
    $ yarn lint-fix:script # 执行 lint 并自动修复
    
    # 结果, 如果存在错误,则根据文档自行修复。
    
    $ npm run lint:script -- --fix
    
    > starter@1.0.0 lint:script /Users/gt/LEMON/starter
    > eslint --ext '.js,.jsx' src "--fix"
    
    ✨  Done in 2.59s.
    
  • Кроме того, мы хотим каждый раз выполнять код форматирования lint перед транспилированием файлов js и jsx.

    # 安装
    
    $ yarn add -D eslint-loader  # eslint loader (for webpack)
    
    // 修改 webpack.config.js 配置
    
      ...
    
        module: {
          rules: [
    +       {
    +         test: /\.(js|jsx)$/,
    +         exclude: /node_modules/,
    +         include: path.resolve(__dirname, 'src'),
    +         enforce: 'pre',
    +         use: [
    +           {
    +             loader: 'eslint-loader',
    +             options: {
    +               cache: false,
    +               fix: true
    +             }
    +           }
    +         ]
    +       },
            {
              test: /\.(js|jsx)$/,
              exclude: /node_modules/,
              loader: 'babel-loader'
            },
            ...
          ]
        }
    
      ...
    

    Тестируй, пробуй 🚨


    ⬆ вернуться к началу


настроитьstylelint

  • Установить

    $ yarn add -D stylelint                    # 强大的现代化 linter,可帮助您避免错误并在样式中强制执行约定。
    $ yarn add -D stylelint-config-recommended # Stylelint 的推荐可共享配置
    $ yarn add -D postcss-reporter             # 在控制台中记录 PostCSS 消息
    
    # $ yarn add -D stylelint-config-standard  # Stylelint 的标准可共享配置
    # stylelint 插件通过 PostCSS 注册警告 。因此,您需要用于打印警告的 PostCSS 运行器或插件,其目的是格式化和打印警告(例如 postcss-reporter)
    
  • Создайте новый файл конфигурации stylelint

    $ touch .stylelintrc     # stylelint 配置文件
    
    <!-- starter/.eslintrc -->
    # 你也可以使用 stylint 推荐开启的规则, 只需引入扩展推荐包即可。
    # 你也可以 使用 rules 扩充规则或者覆盖推荐规则,这取决于你!
    
    {
      "extends": "stylelint-config-recommended",
      "rules": {
        "indentation": 2,                              // 缩进
        "declaration-colon-space-after": "always",     // 在冒号声明后需要一个空格或禁止使用空格。 a { color:pink } => a { color: pink }
        "declaration-colon-space-before": "never",     // 在冒号之前需要一个空格或禁止空格。 a { color : pink } => a { color: pink }
        "function-comma-space-after": "always",        // 在功能的逗号后面需要一个空格或不允许空格。 a { transform: translate(1,1) } => a { transform: translate(1, 1) }
        "function-url-quotes": "always",               // 要求或禁止使用网址引号 a { background: url(x.jpg) } => a { background: url("x.jpg") }
        "media-feature-colon-space-before": "never",   // 媒体功能中的冒号之前需要单个空格或不允许使用空格。@media (max-width :600px) {} => @media (max-width:600px) {}
        "media-feature-name-no-vendor-prefix": true,   // 禁止使用媒体功能名称的供应商前缀。@media (-webkit-min-device-pixel-ratio: 1) {} => @media (min-resolution: 96dpi) {}
        "max-empty-lines": 5,                          // 限制相邻的空行数。
        "number-leading-zero": "never",                // 小数部分小于或等于1的前导零。a { line-height: 0.5; } => a { line-height: .5; }
        "number-no-trailing-zeros": true,              // 禁止数字尾随零。a { top: 1.0px } => a { top: 1px }
        "at-rule-semicolon-newline-after": "always",   // 规则后的分号换行符 @import url("x.css"); a {} => @import url("x.css");\n a {}
        "selector-list-comma-space-before": "never",   // 选择器列表的逗号前需要一个空格或不允许空格 a ,b { color: pink; } => a, b { color: pink; }
        "selector-list-comma-newline-after": "always", // 选择器列表的逗号后需要换行符或不允许使用空格。a, b { color: pink; } => a,\n b { color: pink; }
        "string-quotes": "single",                     // 在字符串周围指定单引号或双引号。 a { content: “x”; } => a { content: 'x'; }
      }
    }
    
  • Расширенная общая конфигурация и таблица правил

  • Добавить команды быстрого доступа

    <!-- starter/package.json -->
    
         "scripts": {
           "test": "echo \"Error: no test specified\" && exit 1",
           "server": "cross-env NODE_ENV=development webpack-dev-server --color --progress",
           "build": "cross-env NODE_ENV=production webpack --color --progress",
           "lint:script": "eslint --ext '.js,.jsx' src",
           "lint-fix:script": "npm run lint:script -- --fix",
    +      "lint:style": "stylelint 'src/**/*.css' 'src/**/*.scss' --syntax scss",
    +      "lint-fix:style": " npm run lint:style -- --fix",
         },
    
  • Настроить postcss-репортер

    Журнал сообщений PostCSS в консоли

    
    
    <!-- starter/postcss.config.js -->
    
      module.exports = {
        plugins: {
          autoprefixer: {},
    +     'postcss-reporter': {
    +       clearReportedMessages: true, # 插件将在记录结果消息后清除它们。这样可以防止其他插件或您使用的任何运行程序再次记录相同的信息并引起混乱。
    +       throwError: true             # 在插件记录您的消息后,如果发现任何警告,它将引发错误。
    +     },
        }
      };
    
  • Выполните команду, чтобы увидеть, есть ли какие-либо нарушения

    $ yarn lint:style     # 格式化 style
    $ yarn lint-fix:style # 格式化 style 并自动修复
    
    # 结果
    
    $  npm run lint:style -- --fix
    
    > starter@1.0.0 lint:style /Users/gt/LEMON/starter
    > stylelint 'src/**/*.css' 'src/**/*.scss' --syntax scss "--fix"
    
    src/style/reset.css
    54:1  ✖  Expected selector "h1" to come before selector "h1:first-child"   no-descending-specificity
    54:1  ✖  Expected selector "h1" to come before selector "h1:last-child"    no-descending-specificity
    58:1  ✖  Expected selector "h2" to come before selector "h2:first-child"   no-descending-specificity
    58:1  ✖  Expected selector "h2" to come before selector "h2:last-child"    no-descending-specificity
    62:1  ✖  Expected selector "h3" to come before selector "h3:first-child"   no-descending-specificity
    62:1  ✖  Expected selector "h3" to come before selector "h3:last-child"    no-descending-specificity
    66:1  ✖  Expected selector "h4" to come before selector "h4:first-child"   no-descending-specificity
    66:1  ✖  Expected selector "h4" to come before selector "h4:last-child"    no-descending-specificity
    67:1  ✖  Expected selector "h5" to come before selector "h5:first-child"   no-descending-specificity
    67:1  ✖  Expected selector "h5" to come before selector "h5:last-child"    no-descending-specificity
    68:1  ✖  Expected selector "h6" to come before selector "h6:first-child"   no-descending-specificity
    68:1  ✖  Expected selector "h6" to come before selector "h6:last-child"    no-descending-specificity
    
    # no-descending-specificity 禁止较低特异性的选择器在覆盖较高特异性的选择器之后出现。
    # 根据规则表修复 reset.css 文件
    
    # 再次运行,结果:
    
    $  npm run lint:style -- --fix
    
    > starter@1.0.0 lint:style /Users/gt/LEMON/starter
    > stylelint 'src/**/*.css' 'src/**/*.scss' --syntax scss "--fix"
    
    ✨  Done in 1.96s.
    

    Протестируй, попробуй 💄


    ⬆ вернуться к началу


настроитьprettier

  • Установить

    $ yarn add -D prettier
    $ yarn add -D eslint-plugin-prettier # 将 Prettier 作为 ESLint 规则运行,并将差异报告为单个ESLint问题
    
    $ yarn add -D eslint-config-prettier # 关闭所有不必要的或可能与 Prettier 冲突的规则。
    $ yarn add -D stylelint-config-prettier # 禁用与 Prettier 冲突的规则的配置
    

    Эти правила отключения см.eslint-config-prettier#special-rules, stylelint-config-prettier special-rules

  • Расширить красивее в конфигурации eslint

    <!-- starter/.eslintrc -->
    
      {
        ...
    
    -   "extends": ["eslint:recommended", "plugin:react/recommended", "plugin:jsx-a11y/recommended"],
    +   "extends": [
    +     "eslint:recommended",
    +     "plugin:react/recommended",
    +     "plugin:jsx-a11y/recommended",
    +     "plugin:prettier/recommended",
    +     "prettier/react"
    +   ],
    
        ...
    
      }
    
    
    <!-- 说明 -->
    
    "plugin:prettier/recommended" does three things:
    
      1. Enables eslint-plugin-prettier.
      2. Sets the prettier/prettier rule to "error".
      3. Extends the eslint-config-prettier configuration.
    
    "prettier/react"
    
      为了支持特殊的 ESLint 插件(eslint-plugin-react)所添加额外的排除项
    

    Конечно вы можете.prettierrcустановить в файлPrettierсобственные варианты.

  • Создайте новый более красивый файл конфигурации

    $ touch .prettierrc     # prettier 配置文件
    
    <!-- starter/.prettierrc -->
    
      {
        "semi": true,
        "singleQuote": true,
        "trailingComma": 'all',
      }
    
    
  • Расширить красивее в конфигурации stylelint

    <!-- starter/.stylelintrc -->
    
      {
        ...
    
    -   "extends": "stylelint-config-recommended",
    +   "extends": [
    +     "stylelint-config-recommended",
    +     "stylelint-config-prettier"
    +   ],
    
        ...
    
      }
    
  • иллюстрировать

    • Выше мы расширяем конфигурации eslint и stylelint, чтобы интегрировать инструменты и интегрировать их вместе. Итак, вы видите отключение любых существующих правил форматирования в другом линтере, которые могут конфликтовать с тем, как Prettier хочет форматировать код.

  • Добавить ярлык командной строки

    <!-- starter/package.json -->
    
      ...
    
      "scripts": {
        "test": "echo \"Error: no test specified\" && exit 1",
        "server": "cross-env NODE_ENV=development webpack-dev-server --color --progress",
        "build": "cross-env NODE_ENV=production webpack --color --progress",
        "lint:script": "eslint --ext '.js,.jsx' src",
        "lint-fix:script": "npm run lint:script -- --fix",
        "lint:style": "stylelint 'src/**/*.css' 'src/**/*.scss' --syntax scss",
        "lint-fix:style": " npm run lint:style -- --fix",
    +   "prettier": "prettier --check --write 'src/**/*.{js,jsx,scss,css}' --config ./.prettierrc"
      },
    
      ...
    

    Дополнительные параметры см.Prettier CLI

  • Запустите команду, отформатируйте код

    $ yarn prettier
    
    # 结果, 它帮你格式化的代码如下
    
    $ prettier --check --write './src/**/*.js' './src/**/*.jsx'
    
    Checking formatting...
    
    src/index.js
    src/router/index.js
    src/router/list.js
    src/views/Github/Github.js
    src/views/Setting/Setting.js
    
    Code style issues fixed in the above file(s).
    ✨  Done in 0.79s.
    

    ⬆ вернуться к началу


настроитьHusky

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

  • Установить

    $ yarn add -D husky # 🐶 Git hooks made easy
    $ yarn add -D lint-staged # 对暂存的 git 文件运行 linters,不要让💩进入您的代码库!
    
  • настроить

    <!-- starter/package.json -->
    
      {
        "scripts": {
          "test": "echo \"Error: no test specified\" && exit 1",
          "server": "cross-env NODE_ENV=development webpack-dev-server --color --progress",
          "build": "cross-env NODE_ENV=production webpack --color --progress",
    +     "lint": "npm run lint:style && npm run lint:script",
    +     "lint-fix": "npm run lint-fix:style && npm run lint-fix:script",
          "lint:script": "eslint --ext '.js,.jsx' src",
          "lint-fix:script": "npm run lint:script -- --fix",
          "lint:style": "stylelint 'src/**/*.css' 'src/**/*.scss' --syntax scss",
          "lint-fix:style": " npm run lint:style -- --fix",
          "prettier": "prettier --check --write 'src/**/*.{js,jsx,scss,css}' --config ./.prettierrc"
        },
    +   "husky": {
    +     "hooks": {
    +       "pre-commit": "lint-staged"
    +     }
    +   },
    +   "lint-staged": {
    +     "src/**/*.{js, jsx, css, scss}": [
    +       "npm run prettier",
    +       "npm run lint-fix",
    +       "git add"
    +     ]
    +   }
      }
    

    Нажмите код, чтобы проверить его! Попытайся!🎊🎊


    ⬆ вернуться к началу


  • Не по теме: спецификация журнала изменений коммита

    # feat:     添加新功能(feature)
    # fix :     修复 bug
    # docs:     文档(documentation)
    # style:    样式及代码格式化等不涉及逻辑的改动点
    # refactor: 重构
    # test:     添加测试用例
    # chore:    构建过程或辅助工具的变动
    
    # 这里推荐一个 lint 插件 commitlint。可根据需要添加
    # 详细参考:https://github.com/conventional-changelog/commitlint
    
    # 关于 commit 信息编写的更多规范指南
    # 请参考:http://www.ruanyifeng.com/blog/2016/01/commit_message_change_log.html
    

    ⬆ вернуться к началу


    На этом этапе содержание спецификации кодирования в основном сформулировано, и количество вещей, которые можно сказать, ограничено.Как его настроить, зависит от вас или требований вашей команды! Иди 🚠

20. Улучшить приложение

Для лучшего обсуждения давайте выполним небольшое требование.

x

  • Преобразование нашего проекта в соответствии с потребностями эскиза

    • Изменить таблицу маршрутизации

      import React from 'react';
      import loadable from '@loadable/component';
      import Loading from '@/components/Loading/Loading';
      
      const BottomTabNavigator = import(
        /* webpackChunkName: "bottom-tab-navigator" */ '@/components/BottomTabNavigator/BottomTabNavigator'
      );
      const Empty = import(
        /* webpackChunkName: 'not-found' */ '@/components/Empty/Empty'
      );
      const Github = import(/* webpackChunkName: "github" */ '@/views/Github/Github');
      const Setting = import(
        /* webpackChunkName: "setting" */ '@/views/Setting/Setting'
      );
      
      const AsyncComponent = loader => loadable(loader, { fallback: <Loading /> });
      
      const routes = [
        {
          path: '/',
          exact: true,
          redirect: '/dashboard/github',
        },
        {
          path: '/dashboard',
          component: AsyncComponent(() => BottomTabNavigator),
          routes: [
            {
              path: '/dashboard/github',
              component: AsyncComponent(() => Github),
            },
            {
              path: '/dashboard/setting',
              component: AsyncComponent(() => Setting),
            },
          ],
        },
        {
          path: '*',
          component: AsyncComponent(() => Empty),
        },
      ];
      
      export default routes;
      
    • Страница модернизации Github

      /*
       * 路径: starter/src/views/Github
       * 说明:
       *      RepositoriesCard   根据草图编写的仓库信息卡片
       *      Loading            加载态组件
       *      Empty              空数据态组件
       *      useRequest         自定义 hook,用于包装请求
       *      searchRepositories 统一 API 请求封装
       *
       * 提示: 说明涉及到的组件,可以参考项目;你也可以自己实现,这不重要。
       */
      
      import React from 'react';
      import styles from './Github.scss';
      import RepositoriesCard from '@/components/RepositoriesCard/RepositoriesCard';
      import Loading from '@components/Loading/Loading';
      import Empty from '@components/Empty/Empty';
      import useRequest from '@/containers/useRequest';
      import { searchRepositories } from '@/services/api/github';
      
      function Github() {
        const [loading, data] = useRequest(searchRepositories, { q: 'javascript' });
      
        if (loading === true) {
          return <Loading />
        }
      
        return (
          <div className={styles.root}>
            {(data && data.items.map(
              ({
                description,
                id,
                name,
                forks_count,
                stargazers_count,
                language,
                owner
              }) => (
                <RepositoriesCard
                  key={id}
                  name={name}
                  avatarUrl={owner.avatar_url}
                  description={description}
                  stargazersCount={stargazers_count}
                  forksCount={forks_count}
                  language={language}
                />
              )
            ))
              || <Empty />}
          </div>
        );
      }
      
      export default Github;
      

    • Измените страницу настроек (не изменяйте 😜)


    Мы преобразуем страницу Github, вызывая метод запроса внутри компонента и единообразно инкапсулируя запрос.Прежде чем продолжить работу по преобразованию, давайте посмотримИнтерфейсные и внутренние взаимодействия

⬆ вернуться к началу

21. Интерфейсные и внутренние взаимодействияAxios

x

  • Установить

    $ yarn add axios # Promise based HTTP client for the browser and node.js
    
  • Новые связанные файлы

    # 新建 services 文件夹
    
    $ cd src && mkdir services
    $ cd services && touch index.js   # 基于 axios 简单封装
    $ mkdir interface && cd interface # 用于存在项目所有接口
    $ touch github.js                 # 用于存放 GitHub 相关请求
    
  • Простой пакет src/services/index.js на основе axios

    /**
     * 说明: AXIOS_DEFAULT_OPTIONS 默认配置,详细参考 utils
     *
     * 注: 以下封装仅仅简单包装一层,你也可以自己实现。
     */
    import axios from 'axios';
    import constants from '@/utils/constants';
    
    // 使用自定义配置新建一个 axios 实例
    const instance = axios.create(constants.AXIOS_DEFAULT_OPTIONS);
    
    // 请求拦截器
    instance.interceptors.request.use(
      (AxiosRequsetConfig) => AxiosRequsetConfig, // 在发送请求之前做些什么
      (error) => Promise.reject(error) // 对请求错误做些什么
    );
    
    // 响应拦截器
    instance.interceptors.response.use(
      (AxiosResponse) => AxiosResponse, // 对响应数据做点什么
      (error) => Promise.reject(error) // 对响应错误做点什么, 如,处理一些鉴权类问题
    );
    
    export default function (options = {}, customConfig = {}) {
      return new Promise((resolve, reject) => {
        const finalConfig = Object.assign(options, customConfig);
        instance(finalConfig)
          .then(({ data }) => {
            if (data) {
              return resolve(data);
            }
            return reject(new Error('Request return result exception!'));
          })
          .catch((reason) => reject(reason));
      });
    }
    
  • Слой бизнес-интерфейса src/services/interface/github.js

    import network from '../index';
    
    /**
    * @desc 搜索仓库
    *
    * @param {Object} data 请求参数
    * @returns {Promise}
    */
    export const searchRepositories = (data = {}) => network({
      url: '/search/repositories',
      params: data
    });
    

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

⬆ вернуться к началу

22. Модернизация проекта - Компоненты

UI Component

  • Методические рекомендации

    1. Самая основная форма компонентов, таких как: кнопки, метки.
    2. нет статуса
    3. Чистый статический дисплей
    4. Базовая структура композиции (реквизит+рендер)
    5. Нет зависимости от жизненного цикла
    6. Одинарная нагрузка, мультиплексирование.
  • Образец

    import React from 'react';
    import PropTypes from 'prop-types';
    
    const UI = ({ title }) => {
      return (
        <div className="UI">
          { title }
        </div>
      );
    };
    
    UI.propTypes = {
      title: PropTypes.string,
    };
    
    UI.defaultProps = {
      title: 'UI Component !',
    };
    
    export default UI;
    

Container Component

  • Методические рекомендации

    1. Принцип единой ответственности для уменьшения связанности компонентов
    2. Предоставьте данные (например, включите логику обработки возвращаемых данных Ajax)
    3. Взаимодействовать с инструментами управления состоянием (например, включать логику внедрения Redux)
    4. состояние
    5. Меньше стилей и DOM
  • Образец

    import { connect } from 'react-redux';
    import Demo from 'components/Demo/Demo';
    import {
      incrementEnthusiasm,
      decrementEnthusiasm
    } from 'actions/index';
    
    export function mapStateToProps({ enthusiasm }) {
      return {
        enthusiasm,
      };
    }
    
    export function mapDispatchToProps(dispatch) {
      return {
        onIncrement: () => dispatch(actions.incrementEnthusiasm()),
        onDecrement: () => dispatch(actions.decrementEnthusiasm()),
      };
    }
    
    export default connect(mapStateToProps, mapDispatchToProps)(Demo);
    

Tip: Поскольку я не очень хорошо знаком с реакцией, говорить об этом относительно просто.Вот рекомендуемая ссылка:Presentational and Container Components,Напишите устойчивые компоненты

⬆ вернуться к началу

23. Трансформация проекта - Адаптация мобильного терминала

Здесь мы непосредственно вводимpostcss-px-to-viewportплагин.

  • Установить

    $ yarn add -D postcss-px-to-viewport`
    
  • настроить

    <!-- starter/postcss-config.js -->
    
      module.exports = {
        plugins: {
          autoprefixer: {}
        },
        'postcss-reporter': {
          clearReportedMessages: true,
          throwError: true
        },
    +   'postcss-px-to-viewport': {
    +     viewportWidth: 375,                           // 设计稿的视口宽度
    +     viewportHeight: 812,                          // 设计稿的视口高度
    +     unitPrecision: 5,                             // 单位转换后保留的精度
    +     viewportUnit: 'vw',                           // 希望使用的视口单位
    +     fontViewportUnit: 'vw',                       // 字体使用的视口单位
    +     selectorBlackList: ['.ignore', '.hairlines'], // 需要忽略的CSS选择器,不会转为视口单位,使用原有的px等单位。
    +     minPixelValue: 1,                             // 设置最小的转换数值,如果为1的话,只有大于1的值会被转换
    +     mediaQuery: false,                            // 媒体查询里的单位是否需要转换单位
    +     exclude: [/node_modules/]                     // 需要排除的
    +   }
      };
    
  • Запустите проект и увидите эффект!

      $ yarn server # 运行项目
    
      # 结果
    
      $ cross-env NODE_ENV=development webpack-dev-server --color --progress
      10% building 1/1 modules 0 activeℹ 「wds」: Project is running at http://localhost:3000/
      ℹ 「wds」: webpack output is served from /
      ℹ 「wds」: Content not from webpack is served from /Users/mr.lemon/cl/CODE_CL/REACT/starter/public
      ℹ 「wds」: 404s will fallback to /index.html
      ℹ 「wdm」: Compiled successfully.
    

    x


    🔥 Хорошая работа! 🎉 🔥


    Посмотреть полный каталог проектов на данном этапе
    └── starter
      ├── dist
      │   ├── chunks
      │   │   ├── bottom-tab-navigator.77d17027.js
      │   │   ├── github.4e7f6c35.js
      │   │   ├── not-found.638dbdfc.js
      │   │   ├── setting.bc3fbe14.js
      │   │   ├── vendors~github.7acdaa67.js
      │   │   ├── vendors~github.7acdaa67.js.LICENSE
      │   │   ├── vendors~main.b1a4bdbf.js
      │   │   └── vendors~main.b1a4bdbf.js.LICENSE
      │   ├── css
      │   │   ├── bottom-tab-navigator.25c0dead.css
      │   │   ├── github.866c72ba.css
      │   │   ├── main.45091b7c.css
      │   │   ├── not-found.d566b1be.css
      │   │   └── setting.2b60ef7c.css
      │   ├── fonts
      │   │   ├── iconfont.63765329.woff
      │   │   ├── iconfont.c2eabadd.ttf
      │   │   └── iconfont.cad7bb52.eot
      │   ├── images
      │   │   ├── empty-data.788c1924.png
      │   │   ├── logo.581fa1d8.png
      │   │   └── webpage-lost.a02f7942.png
      │   ├── svg
      │   │   └── iconfont.1247822e.svg
      │   ├── main.a010b425.js
      │   └── index.html
      ├── node_modules
      |   ├──  ...
      |   └──  ...
      ├── public
      │   ├── favicon.ico
      │   └── index.html
      ├── src
      │   ├── assets
      │   │   ├── font
      │   │   │   ├── iconfont.css
      │   │   │   ├── iconfont.eot
      │   │   │   ├── iconfont.svg
      │   │   │   ├── iconfont.ttf
      │   │   │   └── iconfont.woff
      │   │   └── images
      │   │       ├── empty-data.png
      │   │       ├── logo.png
      │   │       └── webpage-lost.png
      │   ├── components
      │   │   ├── BottomTabNavigator
      │   │   │   ├── BottomTabNavigator.js
      │   │   │   ├── BottomTabNavigator.scss
      │   │   │   └── index.zh-CN.md
      │   │   ├── Circle
      │   │   │   ├── Circle.js
      │   │   │   ├── Circle.scss
      │   │   │   └── index.zh-CN.md
      │   │   ├── Empty
      │   │   │   ├── Empty.js
      │   │   │   ├── Empty.scss
      │   │   │   └── index.zh-CN.md
      │   │   ├── Loading
      │   │   │   ├── Loading.js
      │   │   │   ├── Loading.scss
      │   │   │   └── index.zh-CN.md
      │   │   ├── README.md
      │   │   └── RepositoriesCard
      │   │       ├── RepositoriesCard.js
      │   │       ├── RepositoriesCard.scss
      │   │       └── index.zh-CN.md
      │   ├── containers
      │   │   ├── README.md
      │   │   └── useRequest.js
      │   ├── index.js
      │   ├── router
      │   │   ├── index.js
      │   │   └── list.js
      │   ├── services
      │   │   ├── index.js
      │   │   └── interface
      │   │       └── github.js
      │   ├── style
      │   │   ├── global.css
      │   │   ├── reset.css
      │   │   └── variable.scss
      │   ├── utils
      │   │   ├── constants.js
      │   │   ├── enume.js
      │   │   └── tools.js
      │   └── views
      │       ├── Github
      │       │   ├── Github.js
      │       │   └── Github.scss
      │       └── Setting
      │           ├── Setting.js
      │           └── Setting.scss
      ├── webpack.config.js
      ├── postcss.config.js
      ├── package.json
      ├── LICENSE
      ├── README.md
      └── yarn.lock
    

Реновация проекта в основном завершена, но в будущем еще есть над чем работать 💊😯. Продолжать!

⬆ вернуться к началу

24. Разделяйте макеты спереди назад

Фронтенд и бэкенд разделены, что позволяет разрабатывать фронтенд независимо от бэкенда. Большую роль играет мокап. В реальном развитии бизнеса нам нужен метод, который может перехватывать запросы и возвращать смоделированные данные без вторжения в существующий код. мы используемjson-serverПомогите нам удовлетворить эту потребность.

  • Установить

    $ yarn add -D json-server
    
  • Создайте новую фиктивную папку

    $ mkdir mock
    $ cd mock && touch index.js
    $ mkdir interface && cd interface
    $ touch index.js && touch github.js
    
    // starter/mock/index.js
    const data = require('./interface/index');
    module.exports = function Mock() {
      return data;
    };
    
    // starter/mock/interface/index.js
    const github = require('./github');
    module.exports = {
      ...github,
    };
    
    // starter/mock/interface/github.js
    const repositories = {
      "items": [
        {
          "id": 6498492,
          "name": "javascript",
          "full_name": "airbnb/javascript",
          "owner": {
            "login": "airbnb",
            "id": 698437,
            "avatar_url": "https://avatars3.githubusercontent.com/u/698437?v=4",
          },
          "description": "JavaScript Style Guide",
          "size": 3002,
          "stargazers_count": 89966,
          "watchers_count": 89966,
          "language": "JavaScript",
          "forks_count": 17404,
          "open_issues_count": 110,
          "license": {
            "key": "mit",
            "name": "MIT License",
          },
          "forks": 17404,
          "open_issues": 110,
          "watchers": 89966,
          "default_branch": "master",
          "score": 151.055
        },
        {
          "id": 18286232,
          "name": "javascript",
          "full_name": "GitbookIO/javascript",
          "private": false,
          "owner": {
            "login": "GitbookIO",
            "id": 7111340,
            "avatar_url": "https://avatars0.githubusercontent.com/u/7111340?v=4",
          },
          "description": "GitBook teaching programming basics with Javascript",
          "size": 1267,
          "stargazers_count": 1923,
          "watchers_count": 1923,
          "language": "javascript",
          "forks_count": 730,
          "open_issues_count": 43,
          "license": {
            "key": "apache-2.0",
            "name": "Apache License 2.0",
          },
          "forks": 730,
          "open_issues": 43,
          "watchers": 1923,
          "default_branch": "master",
          "score": 104.4313
        },
      ]
    }
    
    module.exports = {
      repositories
    };
    

    Данные поступают с GitHub, который предназначен только для демонстрации, поэтому данные публикуются напрямую. Если вам нужно динамически генерировать данные, вы можете ввестиmockjsпомочь вам сгенерировать данные. Я не буду вдаваться в подробности здесь!

  • Настроить ярлык команды start/package.json

      ...
      {
        "scripts": {
          "test": "echo \"Error: no test specified\" && exit 1",
          "server": "cross-env NODE_ENV=development webpack-dev-server --color --progress",
    +     "server:mock": "npm run mock & cross-env NODE_ENV=development MOCK=true webpack-dev-server --color --progress",
    +     "mock": "json-server mock/index.js --watch --port 3001",
          "build": "cross-env NODE_ENV=production webpack --color --progress",
          "lint": "npm run lint:style && npm run lint:script",
          "lint-fix": "npm run lint-fix:style && npm run lint-fix:script",
          "lint:script": "eslint --ext '.js,.jsx' src",
          "lint-fix:script": "npm run lint:script -- --fix",
          "lint:style": "stylelint 'src/**/*.css' 'src/**/*.scss' --syntax scss",
          "lint-fix:style": " npm run lint:style -- --fix",
          "prettier": "prettier --check --write 'src/**/*.{js,jsx,scss,css}' --config ./.prettierrc"
        },
      }
      ...
    
  • Настройте агент dev-сервера

    # 新建代理文件
    
    $ cd ../.. && mkdir config
    $ cd config && touch proxy.js
    
    // starter/config/proxy.js
    
    /**
     * @desc mock 服务代理配置
     */
    const MOCK_SERVER_PROXY = {
      '/search/*': {
        target: 'http://localhost:3001/$1',
      }
    }
    
    /**
     * @desc 默认服务代理
     */
    const DEFAULT_PROXY = {};
    
    /**
     * @desc dev-server 代理配置
     * @param {Boolean} IS_MOCK mock 标识
     * @param {Object} Proxy
     */
    module.exports = function({ IS_MOCK }) {
      if (IS_MOCK) return MOCK_SERVER_PROXY;
      return DEFAULT_PROXY;
    }
    

    Конкретно как настроить прокси, по кастомному интерфейсу! Для получения дополнительной информации см.devServer - proxy

      # starter/webpack.config.js
    
      ...
    + const IS_MOCK = process.env.MOCK === 'true';
    + const filterProxy = require('./config/proxy');
    
        ...
    
          baseConfig.devServer = {
            ...
    +       proxy: filterProxy({ IS_MOCK })
          }
    
      ...
    
  • запустить проект

    $ yarn server:mock
    
    # 结果
    
    $ npm run mock & cross-env NODE_ENV=development MOCK=true webpack-dev-server --color --progress
    
    > starter@1.0.0 mock /Users/mr.lemon/cl/CODE_CL/REACT/starter
    > json-server mock/index.js --watch --port 3001
    
      \{^_^}/ hi!
    
      Loading mock/index.js
      Done
    
      Resources
      http://localhost:3001/repositories
    
      Home
      http://localhost:3001
    
      Type s + enter at any time to create a snapshot of the database
      Watching...
    
    10% building 1/1 modules 0 activeℹ 「wds」: Project is running at http://192.168.0.102:3000/
    ℹ 「wds」: webpack output is served from /
    ℹ 「wds」: Content not from webpack is served from /Users/mr.lemon/cl/CODE_CL/REACT/starter/public
    ℹ 「wds」: 404s will fallback to /index.html
    ℹ 「wdm」: Compiled successfully.
    

    x
    x

Выше описана только фиктивная часть, а что касается разделения фронтэнда и бэкенда, вот вопрос и ответ.Имеет ли смысл разделять переднюю и заднюю части Интернета?

⬆ вернуться к началу

25. Модульное тестированиеjest

Модульное тестирование — это работа по тестированию модуля, функции или класса для проверки правильности. В отрасли существует множество отличных фреймворков для тестирования, так что выбирайте прямо здесь.jest.

  • Установить

    $ yarn add -D jest                    # Jest is a delightful JavaScript Testing Framework with a focus on simplicity.
    $ yarn add -D babel-jest              # Jest plugin to use babel for transformation
    $ yarn add -D enzyme                  # 一种用于 React 的 JavaScript 测试实用程序,可以更轻松地测试 React 组件的输出。您还可以操纵,遍历并以某种方式模拟给定输出的运行时。
    $ yarn add -D enzyme-adapter-react-16 # react 16 适配器
    $ yarn add -D identity-obj-proxy      # 模拟一个代理以启用 className 查找
    
  • Создайте новую папку для хранения тестовых случаев и файла конфигурации jest.

    $ touch jest.config.js
    $ cd src && mkdir __tests__
    $ cd __tests__
    $ mkdir __mocks__ && mkdir ui && touch setup.js
    $ cd __mocks__ && touch fileMock.js
    $ cd ../ui && touch Loading.spec.js
    
  • настроить шутку

    <!-- starter/jest.config.js -->
    
    module.exports = {
      testRegex: '(\\.)(test|spec)(\\.)jsx?$',
      // 处理静态文件
      // 样式表和图像等,这些文件在测试中无足轻重,因为我们可以安全地 mock 他们。
      // 模拟 CSS 模块,用类名查找模拟一个代理
      moduleNameMapper: {
        '\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$':
          '<rootDir>/src/__tests__/__mocks__/fileMock.js',
        '\\.(css|scss|sass)$': 'identity-obj-proxy',
        '^@/(.*)$': '<rootDir>/src/$1'
      },
      // 为转换源文件提供同步功能的模块
      transform: {
        '^.+\\.(js|jsx)$': 'babel-jest'
      },
      // 在每次测试之前配置或设置测试环境
      setupFiles: ['<rootDir>/src/__tests__/setupTests.js']
    };
    
    <!-- starter/src/__tests__/__mocks__/fileMock.js -->
    
    module.exports = 'test-file-stub';
    
  • Зарегистрируйте конфигурацию адаптера фермента

    // starter/src/__tests__/setup.js
    
    import enzyme from 'enzyme';
    import Adapter from 'enzyme-adapter-react-16';
    
    enzyme.configure({ adapter: new Adapter() });
    
  • Настройте команды быстрого запуска

    <!-- starter/package.json -->
    
      {
        ...
    
        "scripts": {
    -     "test": "echo \"Error: no test specified\" && exit 1",
    +     "test": "jest --config jest.config.js --no-cache",
          "server": "cross-env NODE_ENV=development webpack-dev-server --color --progress",
          "server:mock": "npm run mock & cross-env NODE_ENV=development MOCK=true webpack-dev-server --color --progress",
          "mock": "json-server mock/index.js --watch --port 3001",
          "build": "cross-env NODE_ENV=production webpack --color --progress",
          "lint": "npm run lint:style && npm run lint:script",
          "lint-fix": "npm run lint-fix:style && npm run lint-fix:script",
          "lint:script": "eslint --ext '.js,.jsx' src",
          "lint-fix:script": "npm run lint:script -- --fix",
          "lint:style": "stylelint 'src/**/*.css' 'src/**/*.scss' --syntax scss",
          "lint-fix:style": " npm run lint:style -- --fix",
          "prettier": "prettier --check --write 'src/**/*.{js,jsx,scss,css}' --config ./.prettierrc"
        },
    
        ...
    
      }
    
  • Пишите тестовые случаи

    import React from 'react';
    import { shallow } from 'enzyme';
    import Loading from '../../components/Loading/Loading';
    
    describe('Loading 组件基础测试组合!', () => {
      it('<Loading /> 组件默认标题应该是 "loading..."', () => {
        const loading = shallow(<Loading />);
        expect(loading.find('span').text()).toBe('loading...');
      });
      it('<Loading /> 组件标题应该是 "加载中..."', () => {
        const loading = shallow(<Loading title='加载中...' />);
        expect(loading.find('span').text()).toBe('加载中...');
      });
    });
    

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

  • запустить тест

    $ yarn test
    
    # 结果
    
    $ jest --config jest.config.js --no-cache
     PASS  src/__tests__/ui/Loading.spec.js
      Loading 组件基础测试组合!
        ✓ <Loading /> 组件默认标题应该是 "loading..." (7ms)
        ✓ <Loading /> 组件标题应该是 "加载中..." (1ms)
    
    Test Suites: 1 passed, 1 total
    Tests:       2 passed, 2 total
    Snapshots:   0 total
    Time:        1.66s
    Ran all test suites.
    ✨  Done in 2.44s.
    
  • иллюстрировать

    1. Написание тестовых случаев важно! Выше обсуждается только то, как получить доступ к шутке и написать ее в соответствии с реальными потребностями.
    2. Рекомендуется сосредоточиться на приватных функциях инструмента и компонентах UI, про бизнес писать не рекомендуется из-за слишком большой вариативности!
    3. Для тестовых случаев вы можете обратиться к некоторым библиотекам компонентов пользовательского интерфейса в отрасли, например:element-UI,antd
    4. Рекомендовать статьюПрактика фронтенд-тестирования

    try it!🍁

⬆ вернуться к началу

26. Развертывание запущено

Пакет ресурсов для анализа

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

  • Webpack Bundle Analyzer

     # 安装
     $ yarn add -D webpack-bundle-analyzer # 交互式可缩放树图可视化webpack输出文件的大小。
    
    # 添加相应配置 starter/webpack.config.js
    
      ...
      const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');
    
        if (IS_PROD) {
          ...
    +     baseConfig.plugins.push(
    +       new BundleAnalyzerPlugin()
    +     );
        }
    
      ...
    
    $ yarn build
    
    # 结果
    
    $ cross-env NODE_ENV=production webpack --color --progress
    98% after emitting SizeLimitsPluginWebpack Bundle Analyzer is started at http://127.0.0.1:8888
    Use Ctrl+C to close it
    Hash: 3bf5742858f4d53ed50d
    Version: webpack 4.41.2
    Time: 3079ms
    Built at: 2019-10-27 21:43:54
    
                                        Asset       Size  Chunks                         Chunk Names
      chunks/bottom-tab-navigator.04852ffb.js   1.54 KiB       0  [emitted] [immutable]  bottom-tab-navigator
                    chunks/github.a1f67f6e.js   4.65 KiB    1, 3  [emitted] [immutable]  github
                 chunks/not-found.b1d86988.js  576 bytes       3  [emitted] [immutable]  not-found
                   chunks/setting.34d43c5a.js  673 bytes       4  [emitted] [immutable]  setting
            chunks/vendors~github.d521d263.js   24.5 KiB       5  [emitted] [immutable]  vendors~github
    chunks/vendors~github.d521d263.js.LICENSE  120 bytes          [emitted]
              chunks/vendors~main.89c9b5d2.js    166 KiB       6  [emitted] [immutable]  vendors~main
      chunks/vendors~main.89c9b5d2.js.LICENSE   1.01 KiB          [emitted]
        css/bottom-tab-navigator.b9b2f600.css   1.54 KiB       0  [emitted] [immutable]  bottom-tab-navigator
                      css/github.e0893952.css   1.99 KiB    1, 3  [emitted] [immutable]  github
                        css/main.09cf98ab.css     11 KiB       2  [emitted] [immutable]  main
                   css/not-found.091bbea2.css   64 bytes       3  [emitted] [immutable]  not-found
                     css/setting.42e9f58f.css  252 bytes       4  [emitted] [immutable]  setting
                 fonts/iconfont.63765329.woff   4.58 KiB          [emitted]
                  fonts/iconfont.c2eabadd.ttf   7.52 KiB          [emitted]
                  fonts/iconfont.cad7bb52.eot   7.69 KiB          [emitted]
               images/empty-data.788c1924.png   11.7 KiB          [emitted]
                     images/logo.581fa1d8.png   8.38 KiB          [emitted]
             images/webpage-lost.a02f7942.png   13.5 KiB          [emitted]
                                   index.html  667 bytes          [emitted]
                             main.02466eca.js   6.64 KiB       2  [emitted] [immutable]  main
                    svg/iconfont.1247822e.svg     22 KiB          [emitted]
    
    Entrypoint main = chunks/vendors~main.89c9b5d2.js css/main.09cf98ab.css main.02466eca.js
    

    x

Travis CI

  1. В реальной среде разработки внутри компании будет набор элементов непрерывной интеграции, которые помогут нам в развертывании. Здесь для удобства демонстрации мы используемTravis CIПомогите нам сделать это.

  2. Travis CI: вносите изменения в код, автоматически запускайте сборки и тесты и сообщайте о результатах.

  3. Как использовать конфигурацию? Пожалуйста, обратитесь кУчебное пособие Travis CI по службе непрерывной интеграции

  4. yml-скрипт этого проекта см.проект

  5. предварительный просмотр"предварительный просмотр"

    x

Эпилог

  • На этом этапе обсуждается весь процесс создания веб-приложения!
  • Вся статья больше похожа на список и частичное резюме. Если я смогу достичь этого шаг за шагом, я верю, что это определенно что-то принесет!
  • Я так много написал...
  • Наконец, если есть ошибка в тексте, спасибо указать!
  • 【адрес проекта】

⬆ вернуться к началу

видеть