Настроить реакцию + машинописный текст с нуля (3): веб-пакет

Webpack
Настроить реакцию + машинописный текст с нуля (3): веб-пакет

Эта статья从零开始配置 react + typescriptВ третьей части серии вам предстоит завершить настройку веб-пакета проекта шаблона. Конфигурацией всего проекта я стремлюсь достичь следующих целей:

гибкий:Когда я настраиваю eslint, я выбираю использовать формат js вместо json, просто для гибкости, использование файлов js позволяет импортировать другие модули, динамически настраивать в соответствии со средой разработки и в полной мере использовать возможности языка js.

Модный:Я считаю, что это прекрасное качество – всегда быть в поиске чего-то нового и пытаться это использовать. Конечно, легко наткнуться на ямы при погоне за новыми, но это не беда, я наступил на нее для вас, и не буду писать, если не смогу наступить на нее. от моего эслинтаparserOptions.ecmaVersionУстановите его на 2020 год и возвращайтесь почащеyarn upgrade --latestможет проявиться.

строгий:Точно так же, как я обычно сужу о равенстве, в большинстве случаев я использую strict и т. д.===, вместо нестрогих и т. д.==, я думаю, чем она строже, тем четче будет анализ, и тем быстрее можно будет найти проблемы. Например, позже мы будем использовать некоторые плагины веб-пакетов, чтобы строго проверять регистр модулей и проверять наличие циклических зависимостей.

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

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

Эта статья будет разделена на три части:

  1. dev server
  2. Оптимизация среды разработки
  3. Оптимизация производственной среды

Если читатель видит эту статью впервые, рекомендуется прочитать первые две статьи:

  1. Настроить реакцию + машинописный текст с нуля (один): dotfiles
  2. Настраиваем react + typescript с нуля (два): линтеры и форматтер

адрес проекта:react-typescript-boilerplate

dev server

Я думаю, когда я только начал изучать фронтенд-фреймворк, меня замучил вебпак, и я начал писать фронтенд только после того, как научился сначала ноду, очень удобно писать нодэйс, и он идет со своим модульное решение.commonjs, Для написания фронтенд-проекта нужно настроить инструмент упаковки. В то время самым популярным инструментом для упаковки уже был webpack, за ним последовалиgulp. Настройка веб-пакета всегда забывает, какие поля находятся в конфигурации веб-пакета, но также включает в себя множество связанных инструментов, таких как компилятор ES6.babel, препроцессор CSSsass/less, CSS Post Processorpostcss, а также различные загрузчики и плагины веб-пакетов. Затем я какое-то время использовал официальные леса, если это было проблематично, и просто использовал реакциюcra, то есть,create-react-app, просто используйте vuevue-cli. На самом деле это довольно полезно, но, честно говоря, лично я думаю,craНетvue-cliДизайн хорошая, оба простота использования и масштабируемости являются полными, CRA неудобно для пользователей модифицировать конфигурацию WebPack, Vue-CLI не только легко изменить конфигурацию WebPack, но также позволяет пользователям сохранять шаблоны и приносить их Собственный плагин. Я чувствую, что реадгициал также знают об этом, поэтому официальные утверждения о том, что он будет сосредоточен на оптимизации связанных с подходящими инструменталками в ближайшем будущем. Теперь, если я создаю новый интерфейсный проект, я выберу свою собственную конфигурацию вместо того, чтобы использовать официальный CLI, потому что я думаю, что я вполне знаком с различными инструментами в интерфейсах, и я закончу свой выпускной и работу на охоту Первая половина года. Я должен извлечь некоторые общие конфигурации в пакет NPM. Теперь это слишком утомительно для копирования и изменения каждый раз, когда я пишу проект. Конфигурация сборки одного проекта оптимизирована, а другие проекты должны быть вручную синхронизирован, что слишком неэффективно.

Технический отбор

Как статически типизированный язык, TypeScript, несомненно, имеет огромное улучшение в подсказках типов по сравнению с js. С помощью подсказок типа IDE и автодополнения нам нужно знать, какие поля в объекте конфигурации webpack, поэтому нам не нужно сверяться с официальной документацией, и мы не можем ошибиться, это очень удобно, поэтому выбираем язык разработкиTypeScript.

В официальной документации есть специальный разделConfiguration LanguagesПредставьте, как инструмент командной строки webpack использует файл конфигурации формата ts, я думаюwebpack-dev-serverИнструменты командной строки должны быть одинаковыми.

Но я не планирую использовать метод, представленный официальной документацией, я не планирую использовать инструмент командной строки вообще, просто используйте API узла.самый гибкийспособ конфигурации. настроитьwebpack devServerПодводя итог, можно выделить следующие способы:

  1. webpack-dev-server, это самый негибкий способ, конечно, очень удобный, когда сценарий использования простой
  2. webpack-dev-serverAPI узла, вызываемый в сценарии узлаweb-dev-serverAPI узла, предоставляемый пакетом для запуска devServer.
  3. express + webpack devServer 相关中间件, по фактуwebpack-dev-serverэто использоватьexpressИ разработано некоторое промежуточное ПО, связанное с devServer. Таким образом, различное промежуточное ПО становится доступным напрямую, и мы можем гибко настраивать параметры каждого промежуточного ПО.
  4. koa + webpack devServer 相关中间件, я действительно нашел промежуточное ПО webpack, связанное с webpack devServer, на github. По сути, webpack devServer — это всего лишь узел-сервер, и неважно, какая технология фреймворка используется для его достижения, главное, чтобы он мог выполнять нужные нам функции.

Я закончил тем, что использовалexpress + webpack devServer 相关中间件Кстати, почему бы не использоватьkoa? Потому что я думаю, что официальное использованиеexpress,использоватьexpressопределенно лучше, чемkoaБолее зрелый и стабильный, с меньшим количеством ям.

Реализовать самые основные функции упаковки

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

Файл записи конфигурации

Сначала установите TypeScript:

# 本地安装开发依赖 typescript
yarn add typescript -D

Каждый проект TypeScript должен иметь одинtsconfig.jsonconfig, используйте следующую команду вsrcновый каталогtsconfig.jsonдокумент:

cd src && npx tsc --init && cd ..

Мы временно приспосабливаемся к этому:

{
    "compilerOptions": {
        /* Basic Options */
        "jsx": "react",
        "isolatedModules": true,

        /* Strict Type-Checking Options */
        "strict": true,

        /* Additional Checks */
        "noUnusedLocals": true,
        "noUnusedParameters": true,
        "noImplicitReturns": true,
        "noFallthroughCasesInSwitch": true,

        /* Module Resolution Options */
        "moduleResolution": "node",
        "esModuleInterop": true,
        "resolveJsonModule": true,
        "baseUrl": "./",
        "paths": {
            // 配置模块路径映射
            "@/*": ["./*"],
        },

        /* Experimental Options */
        "experimentalDecorators": true,
        "emitDecoratorMetadata": true,

        /* Advanced Options */
        "forceConsistentCasingInFileNames": true,
        "skipLibCheck": true,

        // 下面这些选项对 babel 编译 TypeScript 没有作用但是可以让 VSCode 等编辑器正确提示错误
        "target": "ES2019",
        "module": "ESNext"
    }
}

Мы будем использовать Babel для компиляции TypeScript. Когда Babel компилирует код TypeScript, он напрямую удаляет тип TypeScript, а затем использует различные плагины для его компиляции как обычный код javascript. tsc не вмешивается в процесс компиляции, поэтомуtsconfig.jsonмножество вариантов, таких какtargetа такжеmoduleбесполезно.

включитьisolatedModulesопция обеспечит некоторые дополнительные проверки, когда Babel компилирует код,esModuleInteropЭта опция используется, чтобы разрешить модулям без атрибута по умолчанию использовать импорт по умолчанию.Например, если эта опция не включена, вы можете только импортировать модули fs следующим образом:

import * as fs from 'fs';

После его включения вы можете напрямую использовать импорт по умолчанию:

import fs from 'fs';

По сути, импорт ESM по умолчанию является атрибутом импортируемого модуля по умолчанию:

import fs from 'fs';
// 等同于
import * as __module__ from 'fs';
let fs = __module__.default;

Но встроенный модуль Node FS не имеет атрибута по умолчанию, открытьisolatedModulesПараметры автоматически конвертируются без атрибута по умолчанию:

import fs, { resolve } from 'fs';
// 转换成
import * as fs from 'fs';
let { resolve } = fs;

Добавляем входной файлsrc/index.tsx, содержание очень простое:

import plus from './plus';

console.log(plus(404, 404, 404, 404, 404)); // => 2020

src/plus.tsСодержание:

export default function plus(...nums: number[]) {
  return nums.reduce((pre, current) => pre + current, 0);
}

Скомпилировать TypeScript

Мы знаем, что модульная система веб-пакета по умолчанию поддерживает только файлы js.Для других типов файлов, таких как шрифты jsx, ts, tsx, vue и image, нам необходимо установить соответствующий загрузчик. Для файлов ts в настоящее время существует три популярных схемы:

  1. babel + @babel/preset-typescript

  2. ts-loader

  3. awesome-typescript-loader

Потрясающе-нагрузчик-погрузчик забывает об этом, автор отказался от обслуживания. Прежде всего, мы должны использовать Babel, потому что есть много практических плагинов в экосистеме Вавила. Хотя Babel можно использовать с TS-Loader, TS-Loader официально дал примерreact-babel-karma-gulp, но я думаю, что раз Babel уже может компилировать TypeScript, нам не нужно добавлять ts-загрузчик, поэтому я выбираю первый вариант. Следует отметить, что babel не проверяет тип TypeScript по умолчанию, и мы настроим его позже в части плагина webpack.fork-ts-checker-webpack-pluginДля решения этой проблемы.

Добавить конфигурацию веб-пакета

Мы поместим все нод-скрипты в корень проектаscriptsпапка, потому чтоsrcПапки — это интерфейсные проекты, аscriptsПапка - это проект узла, мы должны настроить его отдельноtsconfig.json, в котором начальнаяtsconfig.jsonдокумент:

cd ./scripts && npx tsc --init && cd ..

Подгоняем под соус:

// scripts/tsconfig.json
{
    "compilerOptions": {
        /* Basic Options */
        "target": "ES2019",
        "module": "commonjs",

        /* Strict Type-Checking Options */
        "strict": true,

        /* Additional Checks */
        "noUnusedLocals": true,
        "noUnusedParameters": true,
        "noImplicitReturns": true,
        "noFallthroughCasesInSwitch": true,

        /* Module Resolution Options */
        "moduleResolution": "node",
        "esModuleInterop": true,
        "resolveJsonModule": true,

        /* Experimental Options */
        "experimentalDecorators": true,
        "emitDecoratorMetadata": true,

        /* Advanced Options */
        "forceConsistentCasingInFileNames": true,
        "skipLibCheck": true
    }
}

Несколько замечаний:

  • "target": "ES2019", на самом деле, для вас не проблема настроить очень низкий уровень компиляции.Вы можете использовать расширенный синтаксис tsc для перекодирования.Недостаток в том, что размер кода, как правило, становится больше после перекодирования, а также снижается эффективность выполнения .Нативный синтаксис в целом оптимизирован. Мне нравится немного подкручивать его, обычно это нормально, если вы не используете синтаксис, который не поддерживается на платформе, на которой работает ваш код. Поскольку TypeScript 3.7 поддерживает необязательную цепочку, я начал пытаться использовать ее в TypeScript, но проблема в том, что я всегда устанавливал самый высокий уровень компиляции до этого, то естьESNext, так как необязательная цепочка находится вES2020Это уже стандарт, поэтому tsc не перекодирует необязательные цепочки. Затем узел 12 не поддерживает необязательную цепочку, он сообщит о синтаксической ошибке, поэтому я опускаю его доES2019.

  • Strict Type-Checking Options, эта часть полностью открыта, так как она находится на корабле TypeScript, используйте самую строгую проверку типов и откажитесь от AnyScript

Затем мы создаем новыйscripts/configsПапка, которая используется для хранения файлов конфигурации, включая webpack. Создайте в нем три новых файла конфигурации webpackwebpack.common.ts,webpack.dev.tsа такжеwebapck.prod.ts.webpack.common.tsСохраните некоторые общедоступные файлы конфигурации,webpack.dev.tsОн используется в среде разработки и будет прочитан devServer.webapck.prod.tsЭто то, что мы используем при создании производственного пакета.

Затем мы устанавливаем webpack и webpack-merge и их файлы объявления типов:

yarn add webpack webpack-merge @types/webpack @types/webpack-merge -D

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

Чтобы скомпилировать tsx, нам нужно установитьbabel-loaderи сопутствующие плагины:

yarn add babel-loader @babel/core @babel/preset-typescript -D

Создайте новый файл конфигурации babelbabel.config.js, теперь мы просто добавляем предустановку TypeScript:

// babel.config.js
module.exports = function (api) {
  api.cache(true);

  const presets = ['@babel/preset-typescript'];
  const plugins = [];

  return {
    presets,
    plugins,
  };
};

Добавьте загрузчик babel вwebpack.common.ts:

// webpack.common.ts`
import { Configuration } from 'webpack';
import { projectName, projectRoot, resolvePath } from '../env';

const commonConfig: Configuration = {
  context: projectRoot,
  entry: resolvePath(projectRoot, './src/index.tsx'),
  output: {
    publicPath: '/',
    path: resolvePath(projectRoot, './dist'),
    filename: 'js/[name]-[hash].bundle.js',
    // 加盐 hash
    hashSalt: projectName || 'react typescript boilerplate',
  },
  resolve: {
    // 我们导入ts 等模块一般不写后缀名,webpack 会尝试使用这个数组提供的后缀名去导入
    extensions: ['.ts', '.tsx', '.js', '.json'],
  },
  module: {
    rules: [
      {
        // 导入 jsx 的人少喝点
        test: /\.(tsx?|js)$/,
        loader: 'babel-loader',
        // 开启缓存
        options: { cacheDirectory: true },
        exclude: /node_modules/,
      },
    ],
  },
};

Я не думаю, что в этом проекте react + ts должен быть файл jsx.Если файл jsx импортирован, webpack сообщит об ошибке, что соответствующий загрузчик не может быть найден, чтобы мы могли вовремя разобраться с этим проблемным файлом.

Разработать devServer с экспресс

Давайте сначала установимexpressИ немного промежуточного ПО, связанного с webpack devServer:

yarn add express webpack-dev-middleware webpack-hot-middleware @types/express @t
ypes/webpack-dev-middleware @types/webpack-hot-middleware -D

webpack-dev-middlewareэтоexpressОсновная роль промежуточного ПО:

  1. В качестве статического файлового сервера используйте файловую систему в памяти для размещения пакетов, скомпилированных webpack.
  2. Если файл был изменен, запрос сервера будет отложен до завершения компиляции.
  3. Сотрудничатьwebpack-hot-middlewareРеализовать функцию горячего обновления

webpack-hot-middlewareЭто промежуточное ПО Express зарегистрирует себя как подключаемый модуль веб-пакета и будет прослушивать события компиляции веб-пакета. Какая запись вам нужна для достижения горячего обновления, вам нужно импортировать плагин, указанный в этой записиwebpack-hot-middleware/client.jsПатч клиента. Этот интерфейсный код получит devServerServer Sent EventsConnect, когда происходит событие компиляции, devServer отправит уведомление клиенту. После того, как клиент получит уведомление, он определит, является ли локальный код последним, сравнив значение хеш-функции, если нет, он вытащит исправление обновления с devServer с помощью некоторых других инструментов, таких какreact-hot-loaderРеализуйте горячее обновление.

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

hash

update

Значение h, возвращенное первым запросом, можно угадать, переместив пальцы ног, чтобы оно было хеш-значением.После того, как вы обнаружите, что оно не соответствует локальному хеш-значению, снова запросите исправление обновления.

Мы создаем новый файлscripts/start.tsЧтобы запустить наш devServer:

import chalk from 'chalk';
import getPort from 'get-port';
import logSymbols from 'log-symbols';
import open from 'open';
import { argv } from 'yargs';
import express, { Express } from 'express';
import webpack, { Compiler, Stats } from 'webpack';
import historyFallback from 'connect-history-api-fallback';
import cors from 'cors';
import webpackDevMiddleware from 'webpack-dev-middleware';
import webpackHotMiddleware from 'webpack-hot-middleware';

import proxy from './proxy';
import devConfig from './configs/webpack.dev';
import { hmrPath } from './env';

function openBrowser(compiler: Compiler, address: string) {
    if (argv.open) {
        let hadOpened = false;
        // 编译完成时执行
        compiler.hooks.done.tap('open-browser-plugin', async (stats: Stats) => {
            // 没有打开过浏览器并且没有编译错误就打开浏览器
            if (!hadOpened && !stats.hasErrors()) {
                await open(address);
                hadOpened = true;
            }
        });
    }
}

function setupMiddlewares(compiler: Compiler, server: Express) {
    const publicPath = devConfig.output!.publicPath!;

    // 设置代理
    proxy(server);

    // 使用 browserRouter 需要重定向所有 html 页面到首页
    server.use(historyFallback());

    // 开发 chrome 扩展的时候可能需要开启跨域,参考:https://juejin.cn/post/6844904049276354567
    server.use(cors());

    const devMiddlewareOptions: webpackDevMiddleware.Options = {
        // 保持和 webpack 中配置一致
        publicPath,
        // 只在发生错误或有新的编译时输出
        stats: 'minimal',
        // 需要输出文件到磁盘可以开启
        // writeToDisk: true
    };
    server.use(webpackDevMiddleware(compiler, devMiddlewareOptions));

    const hotMiddlewareOptions: webpackHotMiddleware.Options = {
        // sse 路由
        path: hmrPath,
        // 编译出错会在网页中显示出错信息遮罩
        overlay: true,
        // webpack 卡住自动刷新页面
        reload: true,
    };
    server.use(webpackHotMiddleware(compiler, hotMiddlewareOptions));
}

async function start() {
    const HOST = '127.0.0.1';
    // 4个备选端口,都被占用会使用随机端口
    const PORT = await getPort({ port: [3000, 4000, 8080, 8888] });
    const address = `http://${HOST}:${PORT}`;

    // 加载 webpack 配置
    const compiler = webpack(devConfig);
    openBrowser(compiler, address);

    const devServer = express();
    setupMiddlewares(compiler, devServer);

    const httpServer = devServer.listen(PORT, HOST, err => {
        if (err) {
            console.error(err);
            return;
        }
        // logSymbols.success 在 windows 平台渲染为 √ ,支持的平台会显示 ✔
        console.log(
            `DevServer is running at ${chalk.magenta.underline(address)} ${logSymbols.success}`,
        );
    });

    // 我们监听了 node 信号,所以使用 cross-env-shell 而不是 cross-env
    // 参考:https://github.com/kentcdodds/cross-env#cross-env-vs-cross-env-shell
    ['SIGINT', 'SIGTERM'].forEach((signal: any) => {
        process.on(signal, () => {
            // 先关闭 devServer
            httpServer.close();
            // 在 ctrl + c 的时候随机输出 'See you again' 和 'Goodbye'
            console.log(
                chalk.greenBright.bold(`\n${Math.random() > 0.5 ? 'See you again' : 'Goodbye'}!`),
            );
            // 退出 node 进程
            process.exit();
        });
    });
}

// 写过 python 的人应该不会陌生这种写法
// require.main === module 判断这个模块是不是被直接运行的
if (require.main === module) {
    start();
}

webpackHotMiddlewareизoverlayОпция - включить маску ошибки или нет:

overlay

Я написал много подробностей в комментариях и установил некоторые используемые в нем библиотеки инструментов:

yarn add get-port log-symbols open yarg -D

Первые триsindresorhusМагистерская работа,get-portдля получения доступных портов,log-symbolsПредусмотрены следующие четыре символа журнала:openОткрыт для системных приложенийuri(uriвключая файлы и URL-адреса, которые должен знать каждый),yargsИспользуется для разбора аргументов командной строки.

log-symbols

webpack-dev-middlewareне поддерживаетсяwebpack-dev-serverсерединаhistoryFallbackа такжеproxyфункция, это не имеет большого значения, мы можем пройтиDIYНаш экспресс-сервер для достижения мы можем даже использоватьexpressинтегрироватьmockФункция. Установите два соответствующих промежуточных ПО:

yarn add connect-history-api-fallback http-proxy-middleware @types/connect-history-api-fallback @types/http-proxy-middleware -D

connect-history-api-fallbackможет быть непосредственно использован какexpressПромежуточное ПО интегрировано в экспресс-сервер и инкапсулированоhttp-proxy-middleware, допустимыйproxyTableДобавьте свою собственную конфигурацию прокси в:

import { createProxyMiddleware } from 'http-proxy-middleware';
import chalk from 'chalk';

import { Express } from 'express';
import { Options } from 'http-proxy-middleware/dist/types';

interface ProxyTable {
    [path: string]: Options;
}

const proxyTable: ProxyTable = {
    // 示例配置
    '/path_to_be_proxy': { target: 'http://target.domain.com', changeOrigin: true },
};

// 修饰链接的辅助函数, 修改颜色并添加下划线
function renderLink(str: string) {
    return chalk.magenta.underline(str);
}

function proxy(server: Express) {
    Object.entries(proxyTable).forEach(([path, options]) => {
        const from = path;
        const to = options.target as string;
        console.log(`proxy ${renderLink(from)} ${chalk.green('->')} ${renderLink(to)}`);

        // eslint-disable-next-line no-param-reassign
        if (!options.logLevel) options.logLevel = 'warn';
        server.use(path, createProxyMiddleware(options));

        // 如果需要更灵活的定义方式,请在下面直接使用 server.use(path, proxyMiddleware(options)) 定义
    });
    process.stdout.write('\n');
}

export default proxy;

Чтобы запустить devServer, нам также необходимо установить два инструмента командной строки:

yarn add ts-node cross-env -D

ts-nodeпозволяет нам напрямую запускать код TypeScript,cross-envэто инструмент для установки переменных среды в операционных системах, добавления команд запуска в скрипт npm:

// package.json
{
    "scripts": {
        "start": "cross-env-shell NODE_ENV=development ts-node --files -P ./scripts/tsconfig.json ./scripts/start.ts --open",
    }
}

cross-envВ официальной документации упоминается, что если вы хотите обрабатывать сигналы узлов на платформе Windows, напримерSIGINT, то есть мыctrl + cСигнал срабатывал, когдаcross-env-shellКоманда вместоcross-env.

ts-node Для повышения скорости выполнения он не будет читаться по умолчанию.tsconfig.jsonсерединаfiles, includeа такжеexcludeполя, но считываются на основе зависимостей модуля. Это приводит к некоторым глобальным.d.tsФайл не будет прочитан, для этого нам нужно указать--filesпараметры, подробности можно посмотретьhelp-my-types-are-missing. У нас не так много кода узла, и мы не часто перезапускаем проект, пусть ts-node сканирует весьscriptsПапки не имеют большого значения.

Запустите наш сервер разработки и выйдите с помощью Ctrl + C:

npm start

dev server

Оптимизация среды разработки

plugins

Каждый плагин webpack — это класс, который содержит метод применения, когда мы вызываемcompiler.runилиcompiler.watchвызывается, передавая его компилятору в качестве аргумента. Объект компилятора предоставляет хуки для каждого периода, и мы можем использовать эти хуки для монтирования функций обратного вызова для реализации различных функций, таких как сжатие, статистика оптимизации и уведомление об успешной компиляции после компиляции.

hooks

Показать процесс упаковки

webpack-dev-serverиспользовать при упаковке--progressПараметр будет выводить процент в консоль в режиме реального времени для индикации текущего прогресса упаковки, но из рисунка выше видно, что выводятся только некоторые статистические данные (stats). Я знаю три способа отображения процесса упаковки в режиме реального времени:

  1. встроенный веб-пакетwebpack.ProgressPluginплагин

  2. progress-bar-webpack-plugin

  3. webpackbar

ВстроенныйProgressPlugigОчень примитивно, можно получить текущий прогресс в callback-функции, а потом распечатать в удобном для вас формате:

const handler = (percentage, message, ...args) => {
  // e.g. Output each progress message directly to the console:
  console.info(percentage, message, ...args);
};
new webpack.ProgressPlugin(handler);

progress-bar-webpack-pluginВместо отображения процентов этот плагин отображает индикатор выполнения, нарисованный символами:

progress-bar-webpack-plugin

Этот плагин на самом деле довольно простой и практичный, но есть ошибка: если при печати индикатора выполнения выводятся другие операторы, индикатор выполнения будет не на своем месте, и наш devServer выдаст адрес после запуска:

console.log(`DevServer is running at ${chalk.magenta.underline(address)} ${logSymbols.success}`);

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

progress-bar-webpack-plugin

webpackbarЭто библиотека в рамках проекта nuxt, поддерживаемаяnuxt, качество абсолютно гарантировано. я использовалprogress-bar-webpack-plugin, потому что я искал на официальном сайте npmwebpack progress, в целом надежнее,webpackbarНе найден. прочитай этоwebpackbarизpackage.json,В самом делеkeywordsвсе пустые.webpackBarя все еще изучаюant designКонфиг вебпака увидел, что он использует этот плагин и нашел это сокровище:

webpackbar

Установитьwebpackbar:

yarn add webpackbar @types/webpackbar -D

Добавить конфигурацию вwebpack.common.tsМассив плагинов, цвет, который мы используем, реагирует на синий:

import { Configuration } from 'webpack';

const commonConfig: Configuration = {
  plugins: [
    new WebpackBar({
      name: 'react-typescript-boilerplate',
      // react 蓝
      color: '#61dafb',
    }),
  ],
};

Оптимизировать вывод консоли

Мы используемfriendly-errors-webpack-pluginПлагин делает вывод консоли более дружественным.Следующее использует эффект при успешной компиляции:

build successful

yarn add friendly-errors-webpack-plugin @types/friendly-errors-webpack-plugin -D
// webpack.common.ts
import FriendlyErrorsPlugin from 'friendly-errors-webpack-plugin';

const commonConfig: Configuration = {
  plugins: [new FriendlyErrorsPlugin()],
};

уведомление о сборке

build notification

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

Мы используемwebpack-build-notifierДля поддержки уведомлений об ошибках этот плагин написан на TypeScript и не требует установки типов:

yarn add webpack-build-notifier -D
// webpack.common.ts
import WebpackBuildNotifierPlugin from 'webpack-build-notifier';

const commonConfig: Configuration = {
  plugins: [
    // suppressSuccess: true 设置只在第一次编译成功时输出成功的通知, rebuild 成功的时候不通知
    new WebpackBuildNotifierPlugin({ suppressSuccess: true }),
  ],
};

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

Строго проверяйте регистр пути

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

path case

Мы используемcase-sensitive-paths-webpack-pluginСтрогая проверка регистра путей:

yarn add case-sensitive-paths-webpack-plugin @types/case-sensitive-paths-webpack-plugin -D
// webpack.common.ts
import CaseSensitivePathsPlugin from 'case-sensitive-paths-webpack-plugin';

const commonConfig: Configuration = {
  plugins: [new CaseSensitivePathsPlugin()],
};

path-case-check

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

case sensitive

Циклическая проверка зависимостей

circle-dependencies

По умолчанию webpack не будет сообщать об ошибке циклических зависимостей черезcircular-dependency-plugin Этот плагин веб-пакета может помочь нам обнаружить циклические зависимости во времени:

yarn add circular-dependency-plugin @types/circular-dependency-plugin -D
// webpack.common.ts
import CircularDependencyPlugin from 'circular-dependency-plugin';

import { projectRoot, resolvePath } from '../env';

const commonConfig: Configuration = {
  plugins: [
    new CircularDependencyPlugin({
      exclude: /node_modules/,
      failOnError: true,
      allowAsyncCycles: false,
      cwd: projectRoot,
    }),
  ],
};

circle dependencies error

Кстати вотcwdТо есть проблема рабочего пути, официальный документ используется напрямуюprocess.cwd(), это плохая практика, путь проекта и рабочий путь - это два разных понятия. В узле означает, что путь к проекту никогда не должен использоватьсяpreocess.cwd(), Поскольку всегда будет несколько пользователей Sand Sculpture, которые не переходят в корневой каталог проекта.process.cwd()То есть рабочий путь возвращает путь, на котором запущен узел, при условии, что проект находится в/code/projectRoot, некоторые пользователи открывают терминал прямо в корневом каталоге системы, приходят к выводуnode ./code/projectRoot/index.js, тогдаindex.jsсерединаprocess.cwd()Возвращает корневой путь системы/, а не как некоторые думают или/code/projectRoot.

Получение пути к проекту должно использоватьpath.resolve:

project root

Эта проблема eslint-import-plugin также сообщит об ошибке, а иногда в проектах TypeScript необходимо импортировать файлы по кругу, поэтому он не интегрирован.

Очистить последний связанный пакет

Некоторые модные плагины были представлены ранее, и некоторые плагины, которые поддерживают наш проект в рабочем состоянии.Теперь мы начинаем вводить некоторые плагины для упаковки.

clean-webpack-pluginон будет удален при первой компиляцииdistВсе файлы в каталоге, но останутсяdistпапка и каждый разrebuildудалит все файлы, которые больше не используются.

Этот проект тоже написан на TypeScript, и мне всегда кажется, что проект, написанный на TypeScript, имеет необъяснимое ощущение солидности:

yarn add clean-webpack-plugin -D
// webpack.common.ts
import { CleanWebpackPlugin } from 'clean-webpack-plugin';

const commonConfig: Configuration = {
  plugins: [new CleanWebpackPlugin()],
};

Автоматически генерировать index.html

Как мы все знаем, передовые интервью Tencent любят тестировать компьютерные сети, Меня много раз спрашивали, как обновить сильный кеш. Чтобы решить проблему немедленного обновления сильного кеша, мы обычно вставляем хеш-значение содержимого файла в имя файла, и тогда домашняя страница не использует сильный кеш. Таким образом, пока вы обновляете сильно кэшированный файл ресурсов, значение хеш-функции обновленного содержимого будет меняться, а также изменится сгенерированное имя файла.Таким образом, когда вы запрашиваете домашнюю страницу, потому что вы при доступе к новому пути к ресурсу он запросит последний ресурс с сервера. Информацию о HTTP-кешировании браузера см. в другой моей статье:Изучите механизм HTTP-кэширования браузера с помощью практики -koa2-server..

Когда мы впоследствии оптимизируем производственную среду, мы разделим CSS в отдельные файлы, если Index.html вставлен во внешний стильlinkпомеченhrefМы задаем его вручную, тогда каждый раз, когда мы изменяем файл стиля, будет генерироваться новое значение хеш-функции.Приходится вручную изменять этот путь, что слишком хлопотно, не говоря уже о том, что файл сохраняется в файловой системе памяти в среда разработки.Вы даже не можете увидеть имя файла.

build hash

использоватьhtml-webpack-pluginФайл index.html может быть сгенерирован автоматически, а также могут быть вставлены пути к ресурсам, такие как указанный пакет и разделенный CSS.

Ссылаться наcreat-react-appшаблон, мы создаем новыйpublicпапку и добавить в нееindex.html,favico.ico,manifest.jsonи другие документы.publicПапки используются для хранения некоторых файлов, которые будут упакованы вdistПапки публикуются вместе с файлами.

Установить и настроитьhtml-webpack-plugin:

yarn add html-webpack-plugin @types/html-webpack-plugin -D
import HtmlWebpackPlugin from 'html-webpack-plugin';
import { __DEV__, projectName, resolvePath, projectRoot, hmrPath } from '../env';

const htmlMinifyOptions: HtmlMinifierOptions = {
    collapseWhitespace: true,
    collapseBooleanAttributes: true,
    collapseInlineTagWhitespace: true,
    removeComments: true,
    removeRedundantAttributes: true,
    removeScriptTypeAttributes: true,
    removeStyleLinkTypeAttributes: true,
    minifyCSS: true,
    minifyJS: true,
    minifyURLs: true,
    useShortDoctype: true,
};

const commonConfig: Configuration = {
    output: {
        publicPath: '/',
    },
    plugins: [
        new HtmlWebpackPlugin({
            // HtmlWebpackPlugin 会调用 HtmlMinifier 对 HTMl 文件进行压缩
            // 只在生产环境压缩
            minify: __DEV__ ? false : htmlMinifyOptions,
            // 指定 html 模板路径
            template: resolvePath(projectRoot, './public/index.html'),
            // 类型不好定义,any 一时爽...
            // 定义一些可以在模板中访问的模板参数
            templateParameters: (...args: any[]) => {
                const [compilation, assets, assetTags, options] = args;
                const rawPublicPath = commonConfig.output!.publicPath!;
                return {
                    compilation,
                    webpackConfig: compilation.options,
                    htmlWebpackPlugin: {
                        tags: assetTags,
                        files: assets,
                        options,
                    },
        			// 除掉 publicPath 的反斜杠,让用户在模板中拼接路径更自然
                    PUBLIC_PATH: rawPublicPath.endsWith('/')
                        ? rawPublicPath.slice(0, -1)
                        : rawPublicPath,
                };
            },
        }),
    ],
};

Чтобы пользователи моглиcreate-react-appКак вindex.htmlпройти внутрьPUBLIC_PATHЧтобы получить доступ к пути выпуска, требуется конфигурацияtemplateParametersдобавлена ​​опцияPUBLIC_PATHпеременная в параметр шаблона,html-webpack-pluginПо умолчанию поддерживается некоторый синтаксис ejs, и мы можем динамически установить его следующими способами.favicon.ico , mainfest.jsonРавный путь к ресурсам:

<!DOCTYPE html>
<html lang="en">
  <head>
    <link rel="icon" href="<%= `${PUBLIC_PATH}/favicon.ico` %>" />
    <link rel="apple-touch-icon" href="<%= `${PUBLIC_PATH}/logo192.png` %>" />
    <link rel="manifest" href="<%= `${PUBLIC_PATH}/manifest.json` %>" />
    <title>React App</title>
  </head>
  <body>
    <noscript>You need to enable JavaScript to run this app.</noscript>
    <div id="root"></div>
  </body>
</html>

скопировать файлы в dist

publicВ папке есть несколько файлов, напримерfavico.iconа такжеmainfest.jsonнеобходимо скопировать вdistпапка, мы можем использоватьcopy-webpack-pluginСкопируйте файл в файловую систему в памяти при использовании devServer и на диск при сборке рабочей среды:

yarn add copy-webpack-plugin @types/copy-webpack-plugin -D
// webpack.common.ts
import CopyPlugin from 'copy-webpack-plugin';

const commonConfig: Configuration = {
  plugins: [
    new CopyPlugin(
      [
        {
          // 所有一级文件
          from: '*',
          to: resolvePath(projectRoot, './dist'),
          // 目标类型是文件夹
          toType: 'dir',
          // index.html 会通过 html-webpack-plugin 自动生成,所以需要被忽略掉
          ignore: ['index.html'],
        },
      ],
      { context: resolvePath(projectRoot, './public') }
    ),
  ],
};

Проверьте типы TypeScript

Чтобы повысить скорость компиляции, babel поддерживает только компиляцию синтаксиса TypeScript, но не проверку типов.Чтобы поддерживать проверку типов ts при упаковке webpack, мы будем использовать плагин webpackfork-ts-checker-webpack-plugin, этот плагин вебпака будет выполнять проверку типов TypeScript параллельно в отдельном процессе, этот проект тоже написан на TypeScript, нам не нужно устанавливать типы.

yarn add fork-ts-checker-webpack-plugin -D

добавить вwebpack.dev.ts, используемая память ограничена 1G:

import ForkTsCheckerWebpackPlugin from 'fork-ts-checker-webpack-plugin';

const devConfig = merge(commonConfig, {
  mode: 'development',
  plugins: [
    new ForkTsCheckerWebpackPlugin({
      memoryLimit: 1024,
      // babel 转换的是我们前端代码,所以是指向前端代码的 tsconfig.json
      tsconfig: resolvePath(projectRoot, './src/tsconfig.json'),
    }),
  ],
});

Изменить заодноwebpack.prod.ts, потому что наша конструкция производственной среды долго не занимает память, поэтому мы можем настроить ее на больший размер.Мы ограничиваем память, используемую конструкцией производственной среды, до 2G по умолчанию:

// webpack.prod.ts
const prodConfig = merge(commonConfig, {
  mode: 'production',
  plugins: [
    new ForkTsCheckerWebpackPlugin({
      memoryLimit: 1024 * 2,
      tsconfig: resolvePath(projectRoot, './src/tsconfig.json'),
    }),
  ],
});

артефакт кеша

hard-source-webpack-pluginэто подарокmodulesПредоставьте плагин webpack для промежуточного шага кеша, чтобы увидеть эффект, нам может понадобиться запустить дважды, первый раз - нормальная скорость компиляции, второй раз может быть много раз, возьмите одну из моих разработокПлагин VSCodeДавайте проверим это:

я первый поставилnode_modules/.cache/hard-sourceУдалите папку кеша и посмотрите скорость компиляции при отсутствии кеша:

no cache

На перекомпиляцию ушло 3,075 секунды:

cache

Вау 🚀, это более чем в 3,6 раза быстрее...

Фактическое измерение показало, что этот плагин займет много времени для упаковки в первый раз, и грядущий webpack 5 будет иметь встроенную функцию.Подробности см. В этом выпуске:[spec: webpack 5] - A module disk cache between build processes . Поэтому наш проект не будет интегрировать этот плагин.

Что ж, плагиновая часть представлена, приступим к настройке загрузчиков!

loaders

По умолчанию webpack поддерживает только импорт js и не может обрабатывать другие файлы, необходимо настроить соответствующий загрузчик, напримерexcel-loaderВы можете анализировать excel как объект,file-loaderИзображение png может быть проанализировано как окончательный путь выпуска. Загрузчики воздействуют на класс файлов, а плагины воздействуют на различные этапы компиляции веб-пакета.

Раньше мы только настраивалиbabel-loader, чтобы веб-пакет мог обрабатывать файлы TypeScript.В реальной разработке нам также необходимо поддерживать импорт файлов стилей, файлов изображений, файлов шрифтов и т. д.

Работа с файлами стилей.

Конечной целью, которую мы хотим достичь, является поддержка трех синтаксисов css/less/sass, а также черезpostcssа такжеautoprefixerПлагин реализует такие функции, как автоматическое завершение заголовков браузера.

CSS

Для обработки файлов css нам нужно установитьstyle-loaderа такжеcss-loader:

yarn add css-loader style-loader -D

css-loaderРоль заключается в обработке файла CSS@importа такжеurl()возвращает объединенную строку CSS, аstyle-loaderОтвечает за преобразование возвращенной строки CSS с помощьюstyleТеги вставляются в DOM, а также реализуют интерфейс горячего обновления веб-пакета.

style-loaderОфициальная примерная конфигурация такова:

module.exports = {
  module: {
    rules: [
      {
        // i 后缀忽略大小写
        test: /\.css$/i,
        use: ['style-loader', 'css-loader'],
      },
    ],
  },
};

Вы можете видеть, что используется соответствующий регулярныйiСуффикс, думаю, это нехорошо, не должен улучшать какую-то бессмысленную отказоустойчивость, использовать.CSSВыполнение суффикса не должно позволять компилировать webpack. Мы знаем, что порядок загрузки загрузчиков веб-пакета справа налево, поэтому его нужно выполнить первым.css-loaderдолжно быть выполнено послеstyle-loaderПозади:

// webpack.common.ts
const commonConfig: Configuration = {
  module: {
    rules: [
      {
        test: /\.css$/,
        use: [
          'style-loader',
          {
            loader: 'css-loader',
            options: {
              // CSS modules 比较耗性能,默认就是禁用的
              modules: false,
              // 开启 sourcemap
              sourceMap: true,
              // 指定在 CSS loader 处理前使用的 laoder 数量
              importLoaders: 0,
            },
          },
        ],
      },
    ],
  },
};
less

less-loaderполагатьсяless:

yarn add less less-loader -D
// webpack.common.ts
const commonConfig: Configuration = {
  module: {
    rules: [
      {
        test: /\.less$/,
        use: [
          'style-loader',
          {
            loader: 'css-loader',
            options: {
              modules: false,
              sourceMap: true,
              // 需要先被 less-loader 处理,所以这里设置为 1
              importLoaders: 1,
            },
          },
          {
            // 先让 less-loader 将 less 文件转换成 css 文件
            // 再交给 css-loader 处理
            loader: 'less-loader',
            options: {
              sourceMap: true,
            },
          },
        ],
      },
    ],
  },
};
sass

На самом деле, я никогда не используюlessа такжеstylus, я использовалsass.sassСуществует два синтаксических формата, отличающихся суффиксным именем..sassсуффикс похожymlотступ,.scssОн похож на фигурные скобки CSS, но поддерживает такие функции, как вложенность и переменные. Учитывая, что я практически не видел проектов, использующихymlСлишком мало людей используют формат написания, а наш шаблон поддерживает толькоscssсуффикс.sass-loaderтакже зависит отnode-sass,node-sassЭто реально сука, я не могу установить его без агента, поэтому я в первой части серии.npmrcтолько что настроенnode-sassЗеркало:

yarn add node-sass sass-loader -D
// webpack.common.ts
const commonConfig: Configuration = {
  module: {
    rules: [
      {
        test: /\.scss$/,
        use: [
          'style-loader',
          {
            loader: 'css-loader',
            options: {
              modules: false,
              sourceMap: true,
              importLoaders: 1,
            },
          },
          {
            loader: 'sass-loader',
            options: {
              // 中间每个 loader 都要开启 sourcemap,才能生成正确的 soucemap
              sourceMap: true,
            },
          },
        ],
      },
    ],
  },
};
postcss

browser prefix

Я помню, когда я изучал CSS3 на уроке веб-дизайна на первом курсе, мне приходилось добавлять в заголовок браузера множество свойств для обеспечения совместимости.В то время мой интерес к CSS сильно снизился, и это было слишком хлопотно. С момента появления Node фронтенд-инжиниринг начал стремительно развиваться: раньше фронтенд назывался Chetuzi, теперь фронтенд-инженеры могут использовать Node и для псевдо-full-stack разработки.

postcssявляется инструментом постпроцессора CSS, потому что сначала появился CSS,postcssЧтобы обработать его позже, так называемый постпроцессор.

less/sassназываются препроцессорами CSS, потому что они должны бытьlessилиnode-sassВы предварительно компилируете код в PS.

Ссылаться наКонфигурация postcss приложения create-реагировать, установите следующие плагины:

yarn add postcss-loader postcss-flexbugs-fixes postcss-preset-env autoprefixer postcss-normalize -D

Добавить кpostcss.config.jsдля конфигурацииpostcss:

module.exports = {
  plugins: [
    // 修复一些和 flex 布局相关的 bug
    require('postcss-flexbugs-fixes'),
    // 参考 browserslist 的浏览器兼容表自动对那些还不支持的现代 CSS 特性做转换
    require('postcss-preset-env')({
      // 自动添加浏览器头
      autoprefixer: {
        // will add prefixes only for final and IE versions of specification
        flexbox: 'no-2009',
      },
      stage: 3,
    }),
    // 根据 browserslist 自动导入需要的 normalize.css 内容
    require('postcss-normalize'),
  ],
};

Нам также нужно добавитьbrowserslistнастроить наpackage.json

// package.json
{
	"browserslist": [
        "last 2 versions",
        // ESR(Extended Support Release) 长期支持版本
        "Firefox ESR",
        "> 1%",
        "ie >= 11"
    ],
}

Оглядываясь назад на конфигурацию CSS, less и sass, мы видим, что здесь много повторений, мы провели рефакторинг и модифицировали.importLoadersОпции:

function getCssLoaders(importLoaders: number) {
  return [
    'style-loader',
    {
      loader: 'css-loader',
      options: {
        modules: false,
        sourceMap: true,
        importLoaders,
      },
    },
    {
      loader: 'postcss-loader',
      options: { sourceMap: true },
    },
  ];
}

const commonConfig: Configuration = {
  module: {
    rules: [
      {
        test: /\.css$/,
        use: getCssLoaders(1),
      },
      {
        test: /\.less$/,
        use: [
          // postcss-loader + less-loader 两个 loader,所以 importLoaders 应该设置为 2
          ...getCssLoaders(2),
          {
            loader: 'less-loader',
            options: {
              sourceMap: true,
            },
          },
        ],
      },
      {
        test: /\.scss$/,
        use: [
          ...getCssLoaders(2),
          {
            loader: 'sass-loader',
            options: { sourceMap: true },
          },
        ],
      },
    ],
  },
};

Работа с изображениями и шрифтами

Вообще говоря, наш проект будет использовать некоторые изображения для проверки эффекта во время разработки, а затем заменит их на CDN вместо использования локальных изображений, упакованных веб-пакетом. Есть два широко используемых загрузчика для обработки файлов:file-loaderа такжеurl-loader,file-loaderОн используется для анализа импортированного файла как URL-адреса во время публикации и вывода файла в указанное место, в то время как последний является инкапсуляцией первого, что обеспечивает преобразование изображений ниже порогового объема (установленного на 8192 байт ниже) в base64. Я вдруг вспомнил, что интервьюер в Tencent раньше задавал такой вопрос: Есть ли вред от использования base64? На самом деле я считаю преимуществом base64 то, что нет необходимости делать повторный запрос, а недостатком то, что объем изображения, преобразованного в base64, станет больше, и он станет на четыре трети исходного размера.

base64

yarn add url-loader -D
const commonConfig: Configuration = {
  module: {
    rules: [
      {
        test: [/\.bmp$/, /\.gif$/, /\.jpe?g$/, /\.png$/],
        use: [
          {
            loader: 'url-loader',
            options: {
              // 图片低于 10k 会被转换成 base64 格式的 dataUrl
              limit: 10 * 1024,
              // [hash] 占位符和 [contenthash] 是相同的含义
              // 都是表示文件内容的 hash 值,默认是使用 md5 hash 算法
              name: '[name].[contenthash].[ext]',
              // 保存到 images 文件夹下面
              outputPath: 'images',
            },
          },
        ],
      },
      {
        test: /\.(ttf|woff|woff2|eot|otf)$/,
        use: [
          {
            loader: 'url-loader',
            options: {
              name: '[name]-[contenthash].[ext]',
              outputPath: 'fonts',
            },
          },
        ],
      },
    ],
  },
};

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

sourcemap

devtool скорость наращивания восстановить скорость Производственная среда качественный
(none) +++ +++ yes упакованный код
eval +++ +++ no сгенерированный код
cheap-eval-source-map + ++ no Переведенный код (только строки)
cheap-module-eval-source-map o ++ no Исходный код исходного кода (только линейка)
eval-source-map -- + no Оригинальный исходный код
cheap-source-map + o yes Переведенный код (только строки)
cheap-module-source-map o - yes Оригинальный исходный код (только строка)
inline-cheap-source-map + o no Переведенный код (только строки)
inline-cheap-module-source-map o - no Оригинальный исходный код (только строка)
source-map -- -- yes оригинальный исходный код
inline-source-map -- -- no оригинальный исходный код
hidden-source-map -- -- yes оригинальный исходный код
nosources-source-map -- -- yes Нет содержимого исходного кода

+++очень быстро,++быстро,+Быстрее,oСредняя,-помедленнее,--медленный

Sourcemap является незаменимой функцией для многих инструментов во внешнем мире.Webpack, TypeScript, babel, Powder-assert и другие инструменты преобразования кода должны обеспечивать функцию исходной карты.Исходный код сжат, запутан, полифиллен и без исходной карты. нет способа отладить это проблема позиционирования.

Учитывая скорость компиляции и удобство отладки, я выбираюeval-source-map, если пользователь чувствует себя медленно при упаковке и не может терпеть отсутствие номера столбца, рассмотрите возможность изменить его наcheap-eval-source-map.

мы модифицируемwebpack.dev.tsИнструмент для разработчиковeval-source-map:

// webpack.dev.ts
import commonConfig from './webpack.common';

const devConfig = merge(commonConfig, {
  devtool: 'eval-source-map',
});

Кстати, здесь упоминается плагин webpackerror-overlay-webpack-plugin, который обеспечивает ту же маскировку ошибок, что и create-react-app:

error overlay

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

Горячее обновление

Мы добавили devServer ранееwebpack-hot-middlewareMiddleware, ссылаясь на его документацию, нам нужно сначала добавить плагин webapckwebpack.HotModuleReplacementPlugin:

// webpack.dev.ts
import { HotModuleReplacementPlugin, NamedModulesPlugin } from 'webpack';

const devConfig = merge(commonConfig, {
  plugins: [new HotModuleReplacementPlugin()],
});

также добавить'webpack-hot-middleware/client'Для горячего обновления патча в наш бандл добавьте его в массив записей:

// webpack.common.ts
import { __DEV__, hmrPath } from '../env';


const commonConfig: Configuration = {
    entry: [resolvePath(projectRoot, './src/index.tsx')],
};

if (__DEV__) {
    (commonConfig.entry as string[]).unshift(`webpack-hot-middleware/client?path=${hmrPath}`);
}

Добавляя после записиqueryStringСпособ позволяет настроить некоторые параметры, как это реализовано? Проверить'webpack-hot-middleware/client'Исходный код можно посмотреть, вебпак будетqueryStringВставьте этот файл как глобальную переменную:

entry query

На самом деле, мы также поддерживаем горячее обновление CSS (style-loader реализует интерфейс горячего обновления), если мы хотим поддерживать горячее обновление компонентов реакции, нам также необходимо настроитьreact-hot-loader, давайте оптимизируем нашу конфигурацию babel перед ее настройкой.

оптимизация конфигурации бабеля

Ранее мы настроили только один@babel/preset-typescriptПлагины используются для компиляции TypeScript, и на самом деле есть много моментов, которые можно оптимизировать.

@babel/preset-env

В вавилоне,пресет представляет собой набор плагинов,@babel/preset-envМы можем заставить Babel добавить только синтаксис и многолифики, которые необходимо преобразовать в соответствии с нашим настроенным браузером.

Установить@babel/preset-env:

yarn add @babel/preset-env -D

@babel/plugin-transform-runtime

Мы знаем, что по умолчанию babel будет вставлять некоторые вспомогательные функции при необходимости при компиляции каждого модуля, например_extend, каждый требуемый модуль будет генерировать эту вспомогательную функцию, что приведет к ненужному раздуванию кода,@babel/plugin-transform-runtimeЭтот плагин преобразует все вспомогательные функции из@babel/runtimeИмпортируйте, чтобы уменьшить размер кода.

yarn add @babel/plugin-transform-runtime -D

@babel/preset-react

несмотря на то что@babel/preset-typescriptможет конвертировать код tsx в js, но@babel/preset-reactОн также интегрирует некоторые полезные плагины для реактивных проектов.

@babel/preset-reactПо умолчанию включены следующие плагины:

если установленоdevelopment: trueтакже включится:

Установить зависимости@babel/preset-react:

yarn add @babel/preset-react -D

react-hot-loader

Чтобы добиться частичного обновления компонентов, нам нужно установитьreact-hot-loaderПлагин Бабель.

yarn add react-hot-loader

Этот плагин не нужно устанавливатьdevDependencies, который не будет выполняться в производственной среде и будет занимать минимум места. На самом деле, чиновник разрабатывает следующее поколение плагина быстрого обновления реакции.React Fast Refresh, но в настоящее время не поддерживает webpack.

Для того, чтобы увидеть эффект теста, мы устанавливаем ведро семейства React и настраиваем его.srcСодержимое по умолчанию в папке:

yarn add react react-dom react-router-dom
yarn add @types/react @types/react-dom @types/react-router-dom -D

reactэто основной интерфейс фреймворка,react-domОтвечает за монтирование наших реагирующих компонентов в настоящий DOM,react-dom-routerреализуетсяreact-routerИнтерфейс к библиотеке маршрутизации для веб-платформы.

Позволятьreact-hot-loaderВозьмите на себя наш корневой компонент реакции, на самом деле, эта горячая функция являетсяhocХорошо:

// App.ts
import React from 'react';
import { hot } from 'react-hot-loader/root';

import './App.scss';

const App = () => {
  return (
    <div className="app">
      <h2 className="title">react typescript boilerplate</h2>
    </div>
  );
};

export default hot(App);

Добавьте исправление в запись веб-пакета:

const commonConfig: Configuration = {
  entry: ['react-hot-loader/patch', resolvePath(projectRoot, './src/index.tsx')],
};

В официальной документации упоминается, что если требуется поддержкаreact hooksГорячее обновление, нам тоже нужно установить@hot-loader/react-dom, используйте его для замены значения по умолчаниюreact-domчтобы добавить некоторые дополнительные функции горячего обновления, чтобы заменитьreact-domНам нужно настроить псевдоним webpack:

// webpack.common.ts
module.exports = {
  resolve: {
    alias: {
      'react-dom': '@hot-loader/react-dom',
    },
  },
};

В сочетании с вышеупомянутым плагином babel окончательная модификацияbabel.config.jsстали:

const envPreset = [
  '@babel/preset-env',
  {
    // 只导入需要的 polyfill
    useBuiltIns: 'usage',
    // 指定 corjs 版本
    corejs: 3,
    // 禁用模块化方案转换
    modules: false,
  },
];

module.exports = function (api) {
  api.cache(true);
  return {
    presets: ['@babel/preset-typescript', envPreset],
    plugins: ['@babel/plugin-transform-runtime'],
    env: {
      // 开发环境配置
      development: {
        presets: [['@babel/preset-react', { development: true }]],
        plugins: ['react-hot-loader/babel'],
      },
      // 生产环境配置
      production: {
        presets: ['@babel/preset-react'],
        plugins: ['@babel/plugin-transform-react-constant-elements', '@babel/plugin-transform-react-inline-elements'],
      },
    },
  };
};

Обратите внимание, что в нашей производственной среде также установлены два подключаемых модуля для оптимизации производственной среды:

yarn add @babel/plugin-transform-react-constant-elements @babel/plugin-transform-react-inline-elements -D

@babel/plugin-transform-react-constant-elementsЭффект состоит в том, чтобы поднять переменные в функциональном компоненте из функции следующим образом, чтобы избежать повторных объявлений и ненужной сборки мусора каждый раз, когда функциональный компонент вызывается снова:

const Hr = () => {
  return <hr className="hr" />;
};

// 转换成

const _ref = <hr className="hr" />;

const Hr = () => {
  return _ref;
};

@babel/plugin-transform-react-inline-elementsЧитатели могут обратиться к этому вопросу реакции:Optimizing Compiler: Inline ReactElements.

Оптимизация производственной среды

Добавить уведомление об авторских правах

Это построено непосредственно с помощью веб-пакетаBannerPluginТолько что:

import { BannerPlugin } from 'webpack';

const mergedConfig = merge(commonConfig, {
  mode: 'production',
  plugins: [
    new BannerPlugin({
      raw: true,
      banner: `/** @preserve Powered by react-typescript-boilerplate (https://github.com/tjx666/react-typescript-boilerplate) */`,
    }),
  ],
});

copyright

Следует отметить, что мы добавили в комментарии уведомление об авторских правах@preserveотметка, которую мы будем использовать позжеterserПри сжатии кода при сборке производственной среды все комментарии удаляются при сжатии кода, за исключением некоторых комментариев, содержащих специальные теги, например те, которые мы добавили@preserve.

Разделение CSS

Если CSS включен в пакет JS, который мы упаковали, это приведет к большому конечному объему, а в тяжелых случаях доступ к домашней странице вызовет короткий белый экран. Разделить CSS, который мы используем напрямуюmini-css-extract-plugin:

yarn add mini-css-extract-plugin -D

Измените конфигурацию производственной среды:

// webpack.prod.ts
import MiniCssExtractPlugin from 'mini-css-extract-plugin';

const prodConfig = merge(commonConfig, {
  mode: 'production',
  plugins: [
    new MiniCssExtractPlugin({
      // 文件名中插入文件内容的 hash 值
      filename: 'css/[name].[contenthash].css',
      chunkFilename: 'css/[id].[contenthash].css',
      ignoreOrder: false,
    }),
  ],
});

mini-css-extract-pluginтакже обеспечиваетmini-css-extract-plugin.loader, нельзя сочетать сstyle-loaderсосуществовать, поэтому мы модифицируемwebpack.common.tsКонфигурация позволяет среде разработки использоватьstyle-loaderИспользование в производственной средеmini-css-extract-plugin.loader:

import { loader as MiniCssExtractLoader } from 'mini-css-extract-plugin';
import { __DEV__ } from '../env';

function getCssLoaders(importLoaders: number) {
  return [
    __DEV__ ? 'style-loader' : MiniCssExtractLoader,
    {
      loader: 'css-loader',
      options: {
        modules: false,
        sourceMap: true,
        importLoaders,
      },
    },
    {
      loader: 'postcss-loader',
      options: { sourceMap: true },
    },
  ];
}

сжатие кода

Сжатие JavaScript

Многие учебники в Интернете используют webpack для сжатия кода.uglifyjs-webpack-plugin, на самом деле, этот репозиторий уже давно отказался от поддержки, и он не поддерживает синтаксис ES6, основной разработчик веб-пакетаevilebottnawiобратиться к обслуживаниюterser-webpack-plugin. Мы используемterser-webpack-pluginКод сжимается в производственной среде, и мы можем воспользоваться преимуществами нового webpack4.tree-shakingУдалите мертвый код в коде, чтобы еще больше уменьшить размер пакета:

yarn add terser-webpack-plugin @types/terser-webpack-plugin -D

Treeshake должен быть вpackage.jsonСредняя конфигурацияsideEffectsполе, вы можете прочитать официальную документацию для деталей:Tree Shaking.

CSS-сжатие

Сжать CSS с помощьюoptimize-css-assets-webpack-plugin:

yarn add optimize-css-assets-webpack-plugin @types/optimize-css-assets-webpack-plugin -D

Исправлятьwebpack.prod.ts:

import TerserPlugin from 'terser-webpack-plugin';
import OptimizeCSSAssetsPlugin from 'optimize-css-assets-webpack-plugin';

const prodConfig = merge(commonConfig, {
  mode: 'production',
  optimization: {
    // 使用 minimizer 而不是默认的 uglifyJS
    minimize: true,
    // 两个 minimizer:TerserPlugin 和 OptimizeCSSAssetsPlugin
    minimizer: [new TerserPlugin({ extractComments: false }), new OptimizeCSSAssetsPlugin()],
  },
});

Анализ сборки

Добавляем несколько плагинов webpack для анализа сборки

статистика времени

speed measure

Мы используемspeed-measure-webpack-pluginСтатистика по времени упаковки:

yarn add speed-measure-webpack-plugin -D

На этом этапе проекта мы наконец столкнулись с первой библиотекой без файла объявления типа TypeScript.scripts/typings/index.d.tsфайл, так как типов для записи мало,index.d.tsТак же, как файл глобального объявления, добавьтеspeed-measure-webpack-pluginОбъявление внешнего модуля:

// scripts/typings/index.d.ts
declare module 'speed-measure-webpack-plugin' {
    import { Configuration, Plugin } from 'webpack';

    // 查看官方文档,需要哪些选项就声明哪些选项就行
  	// 可以看出 TypeScript 是非常灵活的
    interface SpeedMeasurePluginOptions {
        disable: boolean;
        outputFormat: 'json' | 'human' | 'humanVerbose' | ((outputObj: object) => void);
        outputTarget: string | ((outputObj: string) => void);
        pluginNames: object;
        granularLoaderData: boolean;
    }

    // 继承 Plugin 类, Plugin 类都有 apply 方法
    class SpeedMeasurePlugin extends Plugin {
        constructor(options?: Partial<SpeedMeasurePluginOptions>);
        wrap(webpackConfig: Configuration): Configuration;
    }

    export = SpeedMeasurePlugin;
}

Исправлятьwebpack.prod.ts:

import SpeedMeasurePlugin from 'speed-measure-webpack-plugin';

const mergedConfig = merge(commonConfig, {
  // ...
});

const smp = new SpeedMeasurePlugin();
const prodConfig = smp.wrap(mergedConfig);

пакетный анализ

bundle analyze

yarn add BundleAnalyzerPlugin @types/BundleAnalyzerPlugin -D

Добавляем npm-скрипт для сборки с анализом пакетов, потому что иногда нам не хочется открывать браузер для анализа размера и доли каждого модуля:

"scripts": {
    "build": "cross-env-shell NODE_ENV=production ts-node --files -P scripts/tsconfig.json scripts/build",
    "build-analyze": "cross-env-shell NODE_ENV=production ts-node --files -P scripts/tsconfig.json scripts/build --analyze",
},

Исправлятьwebpack.prod.ts:

// 添加
import { isAnalyze } from '../env';

if (isAnalyze) {
    mergedConfig.plugins!.push(new BundleAnalyzerPlugin());
}

Таким образом, мы можем запускать, когда хотим увидеть размер и пропорции каждого модуля в комплекте.npm run build-analyze, автоматически откроет страницу, показанную на изображении выше, в вашем браузере.

Подготовьте версию, сжатую gzip.

Мы используем официально поддерживаемыеcompression-webpack-pluginЧтобы подготовить сжатые gzip версии каждого упакованного файла:

yarn add compression-webpack-plugin @types/compression-webpack-plugin -D

Отслеживание размера сжатого ресурса

trace size

size-pluginЭто продукт, созданный Google, который показывает размер каждого фрагмента веб-пакета после сжатия gzip и изменение размера по сравнению с прошлым разом. Войти.

yarn add size-plugin -D

Есть ли официальный файл типов для этой библиотеки, мы добавляемsize-pluginОбъявление внешнего модуля:

// scripts/typings/index.d.ts
declare module 'size-plugin' {
    import { Plugin } from 'webpack';

    interface SizePluginOptions {
        pattern: string;
        exclude: string;
        filename: string;
        publish: boolean;
        writeFile: boolean;
        stripHash: Function;
    }

    class SizePlugin extends Plugin {
        constructor(options?: Partial<SizePluginOptions>);
    }

    export = SizePlugin;
}
// webpack.prod.ts
const mergedConfig = merge(commonConfig, {
  plugins: [
    // 不输出文件大小到磁盘
    new SizePlugin({ writeFile: false }),
  ],
});

Суммировать

только что выучил словоTL; DR, что на самом деле:

Too long; didn't read.

На самом деле, я часто бываю таким, ха-ха. Здесь уже более 10 000 слов, по моим оценкам, мало кто это увидит. Я думаю, что это по-прежнему очень естественно, от среды разработки до производственной среды, от базовой конфигурации до консоли оптимизации, подготовить шаги версии Gzip для сжатия этих лагерей. На написание этой статьи на самом деле уходит основное время, каждый плагин я стараюсь описать их роль, если есть платная, то тоже выйду в коде аннотацию или текстовое описание. Я знаю что эта статья относительно плоха для каких-то базовых границ или как настроить WebPack.Вероятно не видно.Это нормально.Раньше я был таким,но думаю можно зубы кусать.Хотя,хотя много где не понятно, всегда что-нибудь полезное для себя узнаешь, а можно и как словарь собрать. Эта статья не сильно связана с React + TypeScript, вы не можете разработать Vue-Loader для разработки VUE?Что еще более важно, я надеюсь, что некоторые читатели смогут почерпнуть из нее дух исследования. Страх не означает невозможность. Только практикуя и исследуя, мы можем постичь истинное знание.

Наконец, мы добавляем наш скрипт сборкиbuild.ts:

// scripts/build.ts
import webpack from 'webpack';

import prodConfig from './configs/webpack.prod';
import { isAnalyze } from './env';

const compiler = webpack(prodConfig);

compiler.run((error, stats) => {
  if (error) {
    console.error(error);
    return;
  }

  const prodStatsOpts = {
    preset: 'normal',
    modules: isAnalyze,
    colors: true,
  };

  console.log(stats.toString(prodStatsOpts));
});

effect

Недавно я был занят выпускным и искал работу, и следующая может быть через месяц или около того. Если читателю ничего не понятно в статье, рекомендуется сначала прочитать ееисходный код, если у вас остались вопросы, вы можетеgithub issuesИли задавайте мне вопросы в области комментариев издательской платформы.Если вы считаете, что эта статья полезна для вас, вы можете поставить звезду 😁.

В следующей статье должно быть описано, как интегрироватьant design,lodashДождитесь появления популярных библиотек и оптимизируйте их упаковку...

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