Изучите новые функции модульной федерации webpack5

Webpack

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

Why

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

  • Функция: Дети, читавшие результаты построения webpack3 или webpack4, должны знать, что webpack предоставляет только глобальный массив webpackJsonp (а не метод). угнали (модифицировали) на внутреннийwebpack_modulesНа этом объекте внутренние переменные могут получить доступ к объекту, но внешние не могут быть получены, что является полностью «операцией черного ящика», что также приводит к невозможности «федерации» модулей с внешней средой, из-за чего объединение модулей было введен в механизм webpack5. Благодаря этому механизму построенная кодовая база может бытьДинамический,время выполнениязапустить в другой кодовой базе.
  • Цель: Усовершенствовав функциональные модули, повторное использование компонентов, обмен сторонними библиотеками и онлайн-загрузку пакетов npm в зависимостях времени выполнения, он может лучше обслуживать режимы разработки, такие как многостраничные приложения и микроинтерфейсы.

How

Кратко опишите, как использовать этот плагин.

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

Конфигурация направляемой стороны

  • имя: обязательное и уникальное, используется в качестве имени ключа для сторонней ссылки, эквивалентно псевдониму, методу ссылки${name}/${expose}
  • библиотека: объявить имя переменной, смонтированной в глобале, где имя — это имя umd
  • Имя файла: имя имени чанка
  • Экспонирует: как наиболее важный элемент конфигурации упомянутой стороны, он используется для предоставления модулей, предоставляемых извне.
  • общие: объявить общие сторонние ресурсы
const ModuleFederationPlugin = require("webpack/lib/container/ModuleFederationPlugin")

new ModuleFederationPlugin({
  name: "zLib",
  library: { type: "var", name: "zLib" },
  filename: "zLib.js",
  exposes: {
    utils: "./src/utils.js"
  },
  shared: ['lodash']
})

конфигурация реферера

  • remotes: как наиболее важный элемент конфигурации реферера, он используется для объявления имени и имени модуля пакета удаленных ресурсов, на который необходимо ссылаться.
  • Другие элементы конфигурации такие же, как и выше.Вы также можете объявить поле exposes здесь, чтобы предоставить ресурсы вашего модуля для внешнего использования.
const ModuleFederationPlugin = require("webpack/lib/container/ModuleFederationPlugin")

new ModuleFederationPlugin({
  name: "zLocal",
  library: { type: "var", name: "zLocal" },
  remotes: {
    zLib: "zLib"
  },
  shared: ["lodash"]
})

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

<script src="/zLib/dist/zLib.js"></script>

Когда вам нужно сослаться на модуль ресурсов, передайтеimport('远程资源包名称/模块名')способ импортировать напрямую.

import('zLib/utils').then(({ timeDelayFn }) => {
  timeDelayFn(function () {
    console.log('from remote utils fn')
  }, 1000)
})

What

Глядя на код после сборки, вы можете видеть, что приведенные выше функции основаны на переписыванииwebpack_require.eЭтот метод реализован, до webpack5 этот метод использовался только для загрузки асинхронных чанков через jsonp и ссылки на загруженные модули в тогдашней микрозадаче. Но в webpack5 этот метод был модернизирован, и он будет проходиться и выполняться каждый раз при вызове.webpack_require.fТри метода объекта:

  • webpack_require.f.overridables для объединения общих модулей в чанке A с webpck_require.O в чанке B
  • webpack_require.f.remotes, для загрузки общих модулей в чанке B в webpack_modules в чанке A
  • webpack_require.f.jsonp используется для загрузки асинхронных чанков и внедрения webpack_modules в указанный чанк записи

Логика выполнения трех методов следующая:

  • Выполните переопределения, чтобы проверить, объявлен ли фрагмент, который необходимо загрузить, как общий ресурс в элементе конфигурации.__webpack_require__.OЕсли соответствующие ресурсы можно найти в Интернете, используйте их напрямую, не запрашивая ресурсы.
  • Выполните удаленные операции, чтобы проверить, объявлен ли загружаемый в данный момент блок в качестве удаленного удаленного ресурса в элементе конфигурации.Если соответствующие модули могут быть найдены в другом приложении с помощью метода get, удаленный ресурс асинхронно загружается и кэшируется в текущий__webpack_modules__на объекте
  • Выполнить jsonp для асинхронной загрузки ресурсов непосредственно на указанный адрес
// __webpack_require__.f.overridables
__webpack_require__.f.overridables = (chunkId, promises) => {
  if (__webpack_require__.o(chunkMapping, chunkId)) {
    chunkMapping[chunkId].forEach((id) => {
      if (__webpack_modules__[id]) return
      promises.push(Promise.resolve((__webpack_require__.O[idToNameMapping[id]] || fallbackMapping[id])()).then((factory) => {
        __webpack_modules__[id] = (module) => { // 模拟注入modules操作
          module.exports = factory()
        }
      }))
    })
  }
}

// __webpack_require__.f.remotes
__webpack_require__.f.remotes = (chunkId, promises) => {
  if (__webpack_require__.o(chunkMapping, chunkId)) {
    chunkMapping[chunkId].forEach((id) => {
      if (__webpack_modules__[id]) return
      var data = idToExternalAndNameMapping[id]
     promises.push(Promise.resolve(__webpack_require__(data[0]).get(data[1])).then((factory) => {
        __webpack_modules__[id] = (module) => {
          module.exports = factory()
        }
      }))
    })
  }
}

// jsonp部分代码太长,基本原理就是创建script标签插入页面是实现资源加载,下面有模拟实现
__webpack_require__.f.j = () => {}

Where

Что толку так много говорить? Тогда возможные сценарии применения, вероятно, таковы:

  • Интерфейс Micro обеспечивает загрузку общедоступных зависимых ресурсов через общий и удаленный доступ, уменьшает объем онлайн и упрощает обслуживание.
  • Ускоряется скорость компиляции.Ресурсы node_modules могут быть упакованы заранее и на них может ссылаться метод времени выполнения, а при компиляции создаются только исходные файлы проекта.
  • Многостраничное повторное использование ресурсов приложения, включая введение зависимостей во время выполнения, повторное использование компонентов и даже совместное использование всей страницы.

Demo

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

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

<template>
  <section class="module-federation">
    <myButton />
    <myInput />
  </section>
</template>

<script>
const myButton = () => import('./myButton.vue')
const myInput = () => import('./myInput.vue')

export default {
  components: { myButton, myInput }
}
</script>

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

new ModuleFederationPlugin({
  name: "zComp",
  library: { type: "var", name: "zComp" },
  filename: "zComp.js",
  exposes: {
    myButton: "./src/myButton.vue",
    myInput: './src/myInput.vue'
  },
  shared: ['vue']
})

Настройте имя пакета ресурсов как zComp, выставьте 2 компонента пользовательского интерфейса myButton и myInput, если проект, настроенный через удаленный доступ, можно использовать напрямую.zComp/myButtonметод, но здесь мы не меняем исходный проект, поэтому используем другой методzComp.get('myButton')Косвенная ссылка.

Основная идея заключается в асинхронной загрузке файлов пакетов общих ресурсов.zComp.js, файл смонтирует в окне глобальную переменную zComp. Для этой переменной существует два метода: get и override. Как упоминалось выше, get используется для получения общих ресурсов пакета общих ресурсов, а override используется для совместного использования текущий проект ресурсы ниже. Здесь основное внимание уделяется методу get, который после вызова возвращает фабричную функцию типа обещания:

// 调用get方法之后会先检查需要加载组件的依赖项
// 返回值为() => __webpack_require__("./src/myButton.vue")
Promise.all([
  __webpack_require__.e("vue"),
  __webpack_require__.e("src_myButton_vue")
]).then(() => () => __webpack_require__("./src/myButton.vue"))

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

Исходный код:

<template>
  <section class="module-federation">
    <h1>webpack5 module-federation</h1>
    <component :is="remoteButton" />
    <component :is="remoteInput" />
  </section>
</template>

<script>
import { asyncJsonp } from '@/utils'
export default {
  data () {
    return {
      remoteButton: null,
      remoteInput: null
    }
  },
  mounted () {
    this.getRemoteComp()
    this.getRemoteLib()
  },
  methods: {
    async getRemoteComp () {
      await asyncJsonp('/static/common-shared/zComp.js')
      console.log('zComp chunk loaded')
      // 引用button组件
      const buttonFactory = await zComp.get('myButton')
      this.remoteButton = buttonFactory().default
      // 引用input组件
      const inputFactory = await zComp.get('myInput')
      this.remoteInput = inputFactory().default
    },
    async getRemoteLib () {
      await asyncJsonp('/static/common-shared/zLib.js')
      console.log('zLib chunk loaded')
      // 引用另一个共享资源包中的utils工具库
      const factory = await zLib.get('utils')
      const utils = factory()
      utils.timeDelayFn(() => {
        console.log('from remote utils fn log')
      }, 2000)
    }
  }
}

вasyncJsonpМетод опирается на реализацию webpack Jsonp и просто инкапсулирует его в обещание:

export const asyncJsonp = (() => {
  const cacheMap = {}
  return (path, delay = 120) => {
    if (!path || cacheMap[path]) return
    return new Promise((resolve, reject) => {
      const script = document.createElement('script')
      script.charset = 'utf-8'
      script.timeout = delay
      script.src = path

      const onScriptComplete = (event) => {
        script.onerror = script.onload = null
        clearTimeout(timeout)
        if (event.type === 'load') {
          cacheMap[path] = true
          return resolve()
        }
        const error = new Error()
        error.name = 'Loading chunk failed.'
        error.type = event.type
        error.url = path
        reject(error)
      }

      const timeout = setTimeout(() => {
        onScriptComplete({ type: 'timeout', target: script })
      }, delay * 1000)

      script.onerror = script.onload = onScriptComplete
      document.head.appendChild(script)
    })
  }
})()

Summary

  • путем переписыванияwebpack_require.eПредставлены три метода переопределения, удаленного доступа и jsonp для реализации совместного использования зависимостей, повторного использования компонентов и асинхронной загрузки между различными приложениями. Однако необходимо обратить внимание на порядок введения различных ресурсов ввода приложений.
  • Совместное использование модулей между различными приложениями по существу использует глобальное окно в качестве моста и соединяет различные приложения с помощью методов получения и переопределения.
  • Текущая версия webpack5-beta14 еще не добавила плагин ModuleFederationPlugin, его нужно установить на практикеgit://github.com/webpack/webpack.git#dev-1Зависимость от версии для разработчиков, с нетерпением жду появления нормальной версии.

адрес демо-кода🤚