Небольшой опыт создания front-end проектов с помощью webpack

внешний интерфейс

введение

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

别跑

CHANGE LOG

2020.5.17

  • Ввести эслинта
  • Оптимизировать конфигурацию веб-пакета
  • Изменена ошибка @babel/transform-runtime, на которую указал @Lu Feifei.
  • Решена проблема с ошибкой псевдонима пути ts

2020.5.30

webpack

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

Помню, когда я начинал изучать фронтенд, я просто писал одну страницу и один файл + html/css/js все вместе, нет сомнения, что эффективность такой разработки очень низкая и она будет делать много лишнего и повторяющегося работы, поэтому необходимо ставить фронтенд Проект анализируется, организуется и строится как системный проект, и он более компонентный, модульный и стандартизованный на уровне разработки, что делает структуру проекта понятной, уменьшает избыточность нагрузки и повышает эффективность работы.

Фактически, фронтенд-инжиниринг также включает в себя разработку/развертывание/тестирование/автоматизацию/производительность/стабильность/доступность/обслуживаемость, а также некоторые концепции, такие как развертывание/выпуск/CI/CD/оттенки серого.

Основываясь на вышеизложенном, в интерфейсе появился ряд отличных модульных инструментов для создания и упаковки интерфейсов, таких как Grunt/Gulp/Webpack/Rollup и т. д. Каждый инструмент имеет свои собственные применимые сценарии и функции, которые не так хорош, как другие инструменты.Больше нет описания, в этой статье в основном описывается идея построения front-end проектов с помощью webpack и некоторые общие моменты оптимизации

Поднимите концепцию веб-пакета

По сути,webpackэто современное приложение JavaScriptсборщик статических модулей. Когда веб-пакет обрабатывает приложение, он рекурсивно создаетграфик зависимости, который содержит все модули, необходимые приложению, а затем упаковывает все эти модули в один или несколькоbundle.

Снова украсть изображение webpack

image-20200511230228015

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

В Интернете есть много отличных статей об обучении и создании проектов с нуля. Эта статья также опирается на статьи больших парней, чтобы научиться использовать веб-пакет ===>Webpack создает простой проект React, так что здесь я также просматриваю свое поверхностное понимание веб-пакета и непосредственно вхожу в процесс построения

Построить проект

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

Структура проекта

├── dist                        编译后项目文件
├── node_modules                依赖包
├── public                      静态资源文件
├── script                      构建文件
│   ├── analyzer.js               分析包大小    
│   ├── build.js                  打包    
│   ├── dll.js                    抽离公共的依赖    
│   ├── start.js                  开发环境启动    
│   ├── webpack.base.config.js    webpack基础配置    
├── src                         源码
│   ├── pages                     页面组件
│   ├── ...												其它
│   ├── index.tsx                 入口文件
├── .babelrc                    babel 配置
├── .eslintrc.js                eslint 配置
├── .gitignore                  忽略提交到git目录文件
├── .prettierrc                 代码美化
├── package.json                依赖包及配置信息文件
├── tsconfig.json               typescript 配置
├── README.md                   描述文件

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

  • webpack.base.config.js

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

    1. Извлеките общий код, сделав код более кратким и понятным.
    2. Убедитесь, что среды разработки и производства максимально согласованы, и избегайте некоторых бессмысленных проблем, вызванных различиями в средах.
  • start.js

    Запись для запуска среды разработки проекта, в которой объединены базовая конфигурация и конфигурация разработки, такие как devServer/HMR и другие элементы конфигурации среды разработки.

    plugins: [
      new webpack.HotModuleReplacementPlugin(),
      new webpack.NoEmitOnErrorsPlugin()
    ]
    
  • build

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

    new HappyPack({
      threads,
      id: 'jsx',
      loaders: ['babel-loader']
    }),
    
  • dll.js

    Извлеките сторонние модули, чтобы уменьшить объем сборки

  • analyzer.js

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

1. Стек технологий

Выбор фронтенд-технологии в основном неотделим от трех основных фреймворков react/vue/angular.Каждый фреймворк имеет применимые и неприменимые сценарии, а также экосистему, созданную фреймворком, затраты на обучение, стабильность и возможность сотрудничать с базовой конструкцией. и т.д. И так далее, ряд других соображений.

Поскольку это личный проект, я напрямую рассматриваю возможность разработки семейства typescript+react, а конфигурация относительно проста.

// babel编译 .babelrc配置
{
  test: /.jsx?$/,
  exclude: /(node_modules|bower_components)/,
  loader: 'babel-loader'
},
// 使用typescript
{
  test: /\.(ts|tsx)$/,
  exclude: /node_modules/,
  use: {
  	loader: 'ts-loader',
  },
}
 
// .babelrc
{
  "presets": [
    "@babel/react",
    "@babel/preset-env"
  ],
  "plugins": [
    "@babel/plugin-transform-runtime"
  ]
}

2. Совместимость

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

  • прогрессивное улучшение

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

  • изящная деградация

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

Здесь выбрана элегантная стратегия даунгрейда (потому что на самом деле я никогда не задумывался о совместимости браузеров младших версий =.=)

3. Спецификации кодирования

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

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

Однако, поскольку я достаточно уверен в своем стиле кодирования, этот проект бесполезен.

膨胀的很

В-четвертых, внешний мониторинг и оптимизация

Зрелый веб-сайт должен поддерживаться зрелой системой мониторинга. Хорошая система мониторинга может не только помочь мне проанализировать pv/uv/пользовательские привычки/поведение пользователей и т. д., но также иметь возможность своевременно обнаруживать отклонения системы/системы обработки, устранять ошибки и избегать серьезных производственных сбоев.

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

4. Некоторые неважные характеристики в этом проекте

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

  1. Управление ветвями кода
  2. Спецификация процесса
  3. спецификация сотрудничества
  4. Разумное и четкое разделение труда
  5. Процесс сборки релиза
  6. Спецификации качества кода
  7. Рекомендации по стилю кода
  8. Уточнение/обзор кода
  9. Спецификация испытаний
  10. Текущая программа технического обслуживания
  11. спецификация документа
  12. ...

Пять, некоторые моменты оптимизации

Динамичное введение

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

  1. Динамически импортировать маршруты

    let RouteMap = []
    // 白名单
    const WHITE_LIST = ['login']
    // NOTE: 默认不考虑路由传参的情况[xxx/:id],这种场景还没想好用什么方式来解决
    // 动态读取pages下的所有目录,把每个目录都当成一个路由
    const files = require.context('./pages', true, /\/index\.jsx$/)
    files.keys().forEach(key => {
      // const pattern = /(?<=\/).+(?=\/)/
      const pattern = /\.\/(.+)\/(\w+)\.jsx?$/
      const route = key.match(pattern)[1]
      // 白名单路由跳过
      if (!WHITE_LIST.includes(route)) {
        RouteMap.push({
          path: route,
          component: files(key).default
        })
      }
    })
    
    const LayoutRoute = (props) => (
      <Layout {...props}>
        <Switch>
          { RouteMap.map(({path, component}) => <Route key={path} exact path={`/app/${path}`} component={component} />) }
        </Switch>
      </Layout>
    )
    

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

  2. Динамически регистрировать избыточные модули

    // 自动注册reducer模块
    let rootReducer = {}
    const reducersFiles = require.context('./reducers', false, /\.js$/)
    reducersFiles.keys().map(filename => {
      // const pattern = /(?<=\.\/).+(?=\.js)/ 新语法,部分浏览器还不支持
      const pattern = /\/(\w+)\.jsx?$/
      try {
        const matchRes = filename.match(pattern)
        const key = matchRes[1]
        rootReducer[key] = reducersFiles(filename).default
      } catch (e) {
        console.log('无法注册', e)
      }
    })
    
    // 自动注册saga模块
    let rootSagaList = []
    const sagaFiles = require.context('./sagas', false, /\.js/)
    sagaFiles.keys().map(filename => {
      rootSagaList.push(fork(sagaFiles(filename).default))
    })
    

Через require.context можно сэкономить много бесполезного и повторяющегося кода.По крайней мере, не нужно импортировать файлы модулей один за другим.Идею dva можно изучить в будущем, а потом выполнять слой упаковки.Конечно, это отдельная история.

вавилонская оптимизация

Из-за совместимости между браузерами и разницы во времени между браузерами, реализующими спецификацию w3c, чтобы избежать того, чтобы API, который мы используем, не был реализован в браузере, обычно интерфейсные проекты вводят Babel для компиляции. Представление инструмента компиляции здесь также должно иметь возможность выполнять некоторые простые оптимизации структуры проекта.

  1. Исключить из компиляции node_modules/bower_components, исключить: /(node_modules|bower_components)/

    // webpack.base.config.js
    {
      test: /.jsx?$/,
      exclude: /(node_modules|bower_components)/,
      loader: 'babel-loader'
    }
    
  2. Используйте @babel/plugin-transform-runtime для извлечения общих модулей и уменьшения размера кода.

    // .babelrc
    "plugins": [
      "@babel/plugin-transform-runtime"
    ]
    
  3. Используйте happypack, чтобы открыть несколько тем

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

    const HappyPack = require('happypack')
    // 手动创建进程池
    const happyThreadPool =  HappyPack.ThreadPool({ size: os.cpus().length })
    
    module.exports = {
      module: {
        rules: [
          ...
          {
            test: /\.ts$/,
            // 问号后面的查询参数指定了处理这类文件的HappyPack实例的名字
            loader: 'happypack/loader?id=happyBabel',
            ...
          },
        ],
      },
      plugins: [
        ...
        new HappyPack({
          // 这个HappyPack的“名字”就叫做happyBabel,和楼上的查询参数遥相呼应
          id: 'happyBabel',
          // 指定进程池
          threadPool: happyThreadPool,
          loaders: ['babel-loader?cacheDirectory']
        })
      ],
    }
    

Мониторинг производительности упаковки

Если мы упакуем слишком большой пакет, это приведет к очень долгому времени загрузки страницы, что является очень, очень плохим опытом, поэтому нам нужен механизм подсказок.Когда наш упакованный файл слишком большой, мы можем своевременно Зная размер файла , вебпакТакже предусмотрена очень удобная функцияperformance

performance: {
  hints: 'warning', // 提示类型
    // 定一个创建后超过 200kb 的资源,将展示一条警告
    maxAssetSize: 1024 * 200,
    maxEntrypointSize: 1024 * 200,
}

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

Удалить сторонние зависимости

webpack предоставляет очень приятную функциюDllPlugin, он вполне может извлекать сторонние зависимости, а затем создавать файл сопоставленияmanifest.json

const OUTPUT_PATH = BUILD_OUTPUT_DIR ? `${BUILD_OUTPUT_DIR}/lib/` : path.resolve('dist/lib')
// ...
module.exports = {
  // ...
	plugins: [
		// 抽离包的插件
    new webpack.DllPlugin({
			context: __dirname,
			path: path.resolve(OUTPUT_PATH, 'manifest.json'),
			name: '[name]-[hash]'
		})
	]
}

После извлечения пакетов вам необходимо использовать эти пакеты через этот файл сопоставления, который будет использоваться в это время.DllReferencePluginСопоставление со связанными зависимостями.В это время, когда проект запущен, эти зависимости можно использовать в обычном режиме.

new webpack.DllReferencePlugin({
  context: __dirname,
  sourceType: "commonjs2",
  name: 'lib_dll',
  manifest: dllMap // manifest.json地址
})

splitChunks

После webpack4.x предоставляет более удобную функцию распаковкиsplitChunks, который является оптимизированной версиейDllPluginБар

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

optimization: {	
		// 打包后再拆包
    splitChunks: {
      // 这表示将选择哪些块进行优化。当提供一个字符串,有效值为 all, async 和 initial. 提供 all 可以特别强大,因为这意味着即使在异步和非异步块之间也可以共享块。
      chunks: 'all',
      // 要生产的块最小大小(以字节为单位)
      minSize: 10240,
      maxSize: 0,
      // 分割前必须共享模块的最小块数
      minChunks: 1,
      // 按需加载时的最大并行请求数
      maxAsyncRequests: 5,
      // 入口点处的最大并行请求数
      maxInitialRequests: 3,
      // 指定用于生成的名称的分割符 vendors~main.js
      automaticNameDelimiter: '~',
      // 拆分块的名称
      name: true,
      cacheGroups: {
        // 抽出css
        // styles: {
        //   name: 'static/css/styles',
        //   test: /\.(css|scss|sass)$/,
        //   chunks: 'all',
        //   enforce: true,
        // },
        // 抽出公共模块
        commons: {
          name: 'static/js/components',
          test: path.join(__dirname, '..', 'src/components'),
          minChunks: 3,
          priority: 5,
          reuseExistingChunk: true,
        },
        // 单独抽出react
        react: {
          test: /[\\/]node_modules[\\/](react)[\\/]/,
          name: 'static/js/react',
          priority: 20,
        },
        // 单独抽出react-dom
        reactDom: {
          test: /[\\/]node_modules[\\/](react-dom)[\\/]/,
          name: 'static/js/react-dom',
          priority: 20,
        },
        // 抽出第三方的包
        vendors: {
          name: 'static/js/vendors',
          test: /[\\/]node_modules[\\/]/,
          priority: 15,
        },
        default: {
          minChunks: 2,
          priority: -20,
          reuseExistingChunk: true,
        },
      },
    },
 }

Приведенная выше конфигурация извлекает общий компонент и относительно большие зависимости Webpack извлечет зависимости react-dom/react/другие сторонние и сгенерирует соответственно три файла Конечно, если есть другие сравнения Если есть большая зависимость, вы можете также добавьте правила для его извлечения.

6. Последующие планы

  1. Постоянно оптимизируйте конфигурацию веб-пакета
  2. Ввести код спецификации eslint
  3. Знакомство с модульным тестированием реализации JEST
  4. Создавайте стратегии кэширования для оптимизации взаимодействия с пользователем
  5. Проект модернизации для рендеринга ssr/server
  6. Извлечение общих компонентов и создание библиотек компонентов
  7. Оптимизация внешнего модуля мониторинга
  8. ...

заключительные замечания

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

知识盲区

👏адрес репозитория гитхаб

👏 Если заинтересованные друзья найдут что-то не так/неадекватно/нужно доработать/оптимизировать, со временем обновлю, давайте становиться лучше вместе!

👏 Если статья была вам полезна, жмите 👍 и вперёд!