Uso de uplugin para comparar los sistemas de complemento Webpack y Rollup

Ha habido un dicho en los ríos y lagos: el diseño del sistema de complemento de Rollup es más científico y fluido que el paquete web. (Hay innumerables quejas sobre los complementos de paquetes web escritos en Internet) Hablar es barato, este artículo se basa en el desenchufar de la biblioteca tripartita para comparar y estudiar los dos sistemas de complementos. Unplugin es una herramienta de escritura de complementos, que permite a los desarrolladores escribir complementos para paquetes principales al mismo tiempo con un conjunto de código, incluidos webpack, Rollup, Vite, esbuild, Rspack.

Desenchufar ganchos

Unplugin se basa en los ganchos de Rollup y tiene un total de 9 funciones de gancho de ciclo de vida, incluidos 6 ganchos de compilación, 1 gancho de generación de salida y 2 ganchos independientes. De esta forma, podemos hacernos una idea general de las capacidades comunes entre los distintos bundlers. A continuación, se presentará brevemente la función de gancho que contiene la propia lógica de unplugin. Para el resto, consulte la documentación oficial de Rollup.

buildStart 和 buildEnd

Es lo mismo que la función de enlace de Rollup, que representan respectivamente el inicio de la preparación de la compilación y el final de la compilación. La diferencia es que unplugin apunta el this de la función a su propia definición UnpluginBuildContext:

export interface UnpluginBuildContext {
    
    
  addWatchFile: (id: string) => void
  emitFile: (emittedFile: EmittedAsset) => void
  getWatchFiles: () => string[]
  parse: (input: string, options?: any) => AcornNode
}

El contexto proporciona cuatro métodos, y unplugin lo implementa para cada paquete y lo usa según sea necesario.

loadInclude 和 transformInclude

Una función de enlace especialmente adaptada para webpack, utilizada para filtrar módulos que necesitan cargarse o transformarse. Debido al diseño separado del cargador de paquetes web y el complemento, las funciones de carga y transformación las realiza el cargador. Si no hay una función de filtro, el complemento cargará todos los módulos, lo que afectará el rendimiento del paquete web.

Desconectar la implementación del módulo Webpack

Mirando profundamente la implementación de unplugin del módulo webpack, puede observar cómo la función de enlace de la clase Rollup se convierte en el sistema webpack.

Primero comprenda el diseño del complemento webpack. El ejemplo dado en el documento oficial es más tradicional: una clase con una función de aplicación recibe la configuración personalizada del usuario para el complemento a través del constructor. De hecho, webpack solo necesita un objeto con un método de aplicación. Unplugin también envuelve una capa adicional de funciones de generación para pasar la configuración del usuario a la función de definición de complementos de cada paquete, y también proporciona metaparámetros para indicar para qué paquete generará complementos:

export function getWebpackPlugin<UserOptions = {
      
      }>(
  factory: UnpluginFactory<UserOptions>,
): UnpluginInstance<UserOptions>['webpack'] {
    
    
  return (userOptions?: UserOptions) => {
    
    
      return {
    
    
          apply(compiler: WebpackCompiler) {
    
    
              // implementation
          }
      }
  }

El código factoryes para definir la función que ejecuta el complemento lógica específica en cada ciclo de vida, por ejemplo:

(options, meta) => {
    return {
        load() {
            // load 钩子函数
        }
    }
}

Antes de ejecutar la función de gancho, hay una serie de trabajos de inicialización. Primero inyecte su propio contexto en el compilador del paquete web.

const injected = compiler.$unpluginContext || {}
compiler.$unpluginContext = injected

Luego llame factorya la función para obtener la definición del complemento:

const rawPlugins = toArray(factory(userOptions!, meta))

unplugin admite la definición de varios complementos al mismo tiempo, por lo que se procesa convirtiéndolos toArrayen una matriz. Luego recorra la matriz y agregue propiedades públicas al complemento:

const plugin = Object.assign(
    rawPlugin,
    {
    
    
        __unpluginMeta: meta,
        __virtualModulePrefix: VIRTUAL_MODULE_PREFIX,
    },
) as ResolvedUnpluginOptions

// inject context object to share with loaders
injected[plugin.name] = plugin

compiler.hooks.thisCompilation.tap(plugin.name, (compilation) => {
    
    
    compilation.hooks.childCompiler.tap(plugin.name, (childCompiler) => {
    
    
        childCompiler.$unpluginContext = injected
    })
})

Tenga en cuenta que el contexto también se inyecta en childCompiler aquí. Esta serie de acciones de inyección de contexto es para permitir que todo el paquete web obtenga la definición del complemento. Es útil obtener la definición del complemento en el cargador de paquetes web, porque en la definición del cargador, es solo una función que acepta el código fuente y devuelve el código fuente traducido. loadA través de la inyección global, podemos obtener funciones de complemento y funciones en la función de definición del cargador transform.

A continuación, analice los códigos fuente de las funciones de enlace uno por uno de acuerdo con el orden de ejecución de las funciones de enlace.

construirInicio

if (plugin.watchChange || plugin.buildStart) {
    
    
    compiler.hooks.make.tapPromise(plugin.name, async (compilation) => {
    
    
        const context = createContext(compilation)
        if (plugin.watchChange && (compiler.modifiedFiles || compiler.removedFiles)) {
    
    
            // implementation
        }

        if (plugin.buildStart)
            return await plugin.buildStart.call(context)
    })
}

buildStarty watchChangese tratan juntos porque ambos usan contexto. Específicamente buildStart, solo proporcione contexty ejecute plugin.buildStart. Correspondiente al ciclo de vida del complemento del paquete web es make. Mirando la documentación del paquete web, podemos encontrar que unplugin omite una serie de funciones de enlace de inicialización del paquete web, como leer la configuración, inicializar el compilador, llamar a los complementos, etc. Debido a que se trata de la propia lógica del paquete web, no son compatibles con Rollup. make se activará después de crear una compilación y comenzará a leer los archivos desde la entrada. Conoce la definición de Rollup buildStart.

verCambiar

watchChangeEs una función gancho que es independiente del orden de ejecución. Cuando el empaquetador se ejecuta en modo de observación, se activa cuando cambia el archivo observado. En webpack, unplugin usa modifiedFilesla suma del compilador removedFilespara obtener el archivo correspondiente. Dado que Webpack volverá a ejecutar la compilación cada vez que cambie el archivo, la suma modifiedFilestambién se removedFilesactualizará en consecuencia.

modifiedFilesEs un nuevo atributo de Webpack 5.

resolverId

Rollup resolveIdtiene tres parámetros de entrada sourceimporteroptions :

type ResolveIdHook = (
    source: string,
    importer: string | undefined,
    options: {
    
    
        assertions: Record<string, string>;
        custom?: {
    
     [plugin: string]: any };
        isEntry: boolean;
    }
) => ResolveIdResult;

Los conceptos relacionados con la resolución en Webpack se encuentran en el objeto de resolución en la configuración y las configuraciones comunes, como el alias. Webpack proporciona específicamente una configuración de complemento para resolver, que es diferente de los complementos comunes y pertenece a ResolvePluginInstanceunplugin, que usa esta configuración para pasar resolveIdfunciones.

resolver
  .getHook('resolve')
  .tapAsync(plugin.name, async (request, resolveContext, callback) => {
    
    
    if (!request.request)
      return callback()

    // filter out invalid requests
    if (normalizeAbsolutePath(request.request).startsWith(plugin.__virtualModulePrefix))
      return callback()

    const id = normalizeAbsolutePath(request.request)

    const requestContext = (request as unknown as {
    
     context: {
    
     issuer: string } }).context
    const importer = requestContext.issuer !== '' ? requestContext.issuer : undefined
    const isEntry = requestContext.issuer === ''

    // call hook
    const resolveIdResult = await plugin.resolveId!(id, importer, {
    
     isEntry })
    // ...
  }
// ...
compiler.options.resolve.plugins = compiler.options.resolve.plugins || []
compiler.options.resolve.plugins.push(resolverPlugin)

Se puede ver idque importerambos provienen de resolvelos parámetros pasados ​​por esta función de enlace, pero desafortunadamente faltan instrucciones relevantes en la documentación del paquete web. optionsEn los parámetros, solo se proporcionan propiedades isEntry. Finalmente, vimos resolverPluginque se creó manualmente y se colocó en las opciones del compilador. Se puede ver que la capacidad del complemento webpack incluye la modificación del archivo de configuración, y la capacidad en realidad cubre completamente el cargador, que también se puede ver en las loadfunciones siguientes.transform

Desde el código fuente, veremos el código relacionado con el módulo virtual, que se omitirá en este artículo para simplificar la escena. Lo mismo a continuación.

carga

El cargador en Webpack se define en la configuración, por ejemplo:

module.exports = {
    
    
  module: {
    
    
    rules: [{
    
     test: /.txt$/, use: 'raw-loader' }],
  },
};

Use expresiones regulares para indicar el tipo de archivo y luego especifique el cargador. loadUnplugin realiza la función implementando manualmente un cargador y luego insertando reglas :

if (plugin.load) {
    
    
  compiler.options.module.rules.unshift({
    
    
    include(id) {
    
    
      if (id.startsWith(plugin.__virtualModulePrefix))
        id = decodeURIComponent(id.slice(plugin.__virtualModulePrefix.length))

      // load include filter
      if (plugin.loadInclude && !plugin.loadInclude(id)) return false

      // Don't run load hook for external modules
      return !externalModules.has(id)
    },
    enforce: plugin.enforce,
    use: [
      {
    
    
        loader: LOAD_LOADER,
        options: {
    
    
          unpluginName: plugin.name,
        },
      },
    ],
  })
}

Además de la coincidencia regular, Loader testtambién admite el filtrado de funciones, tomando la ruta del recurso importado como parámetro de entrada, que es la loadIncludefuente de su diseño. Del código anterior, también encontraré una propiedad enforce, que se usa para controlar el tiempo de ejecución del cargador. Esta es unshiftla razón por la cual unplugin usa la matriz de reglas de inserción. (El complemento de desconexión se carga en último lugar de forma predeterminada)

Mira específicamente LOAD_LOADERla implementación:

export default async function load(this: LoaderContext<any>, source: string, map: any) {
    
    
  const callback = this.async()
  const {
    
     unpluginName } = this.query
  const plugin = this._compiler?.$unpluginContext[unpluginName]
  let id = this.resource

  if (!plugin?.load || !id)
    return callback(null, source, map)

  const context: UnpluginContext = {
    
    
    error: error => this.emitError(typeof error === 'string' ? new Error(error) : error),
    warn: error => this.emitWarning(typeof error === 'string' ? new Error(error) : error),
  }

  if (id.startsWith(plugin.__virtualModulePrefix))
    id = decodeURIComponent(id.slice(plugin.__virtualModulePrefix.length))

  const res = await plugin.load.call(
    Object.assign(this._compilation && createContext(this._compilation) as any, context),
    normalizeAbsolutePath(id),
  )

  if (res == null)
    callback(null, source, map)
  else if (typeof res !== 'string')
    callback(null, res.code, res.map ?? map)
  else
    callback(null, res, map)
}

A través del contexto del paquete web, puede obtener la identificación del recurso y encontrar el complemento de desconexión correspondiente. El siguiente paso es proporcionar el propio contexto del complemento y luego llamar a la función de carga. De acuerdo con la definición de Rollup loaddel resultado devuelto, llame callbackal parámetro.

transformar

transform Las funciones loadson similares a las funciones, también personalizan un cargador y luego insertan reglas. La única diferencia es que al procesar la lógica de transformación, no se utiliza la función de inclusión, sino que se ejecuta el filtro en la función de uso transformInclude. (Aquí es donde se vuelve confuso, porque contradice la lógica para el diseño del complemento mencionado anteriormente transformInclude. Ninguna función de inclusión haría que el complemento cargara todos los módulos)

Unplugin procesará la lógica de transformación primero. Insertar reglas con unshift hará que las reglas generadas por la carga sean antes de la transformación. De acuerdo con el orden de carga predeterminado del paquete web, la transformación se activará antes de la carga. No estoy seguro si es un error o un comportamiento esperado de la desconexión.

buildEnd

buildEndCorresponde a la función de gancho de webpack emit.

if (plugin.buildEnd) {
    
    
  compiler.hooks.emit.tapPromise(plugin.name, async (compilation) => {
    
    
    await plugin.buildEnd!.call(createContext(compilation))
  })
}

escribirPaquete

writeBundleCorresponde a la función de gancho de webpack afterEmit. Llamar sin pasar ningún parámetro y sin contexto significa que no puede obtener todos los archivos creados por el paquete.

if (plugin.writeBundle) {
    
    
  compiler.hooks.afterEmit.tap(plugin.name, () => {
    
    
    plugin.writeBundle!()
  })
}

Resumir

Podemos encontrar que solo hay tres ganchos de paquete web realmente utilizados por unplugin: makeemit, afterEmit. loady transformlas funciones están alojadas en el cargador de paquetes web. make es una función de enlace clave para webpack, que indica que se ha completado una serie de trabajos de inicialización de webpack y comienza a compilar cada módulo desde el archivo de entrada.

En comparación con el resumen, el sistema de complementos de webpack tiene una gran cantidad de funciones de enlace. Además del compilador mencionado en este artículo, tiene una función de enlace, incluida la compilación, ContextModuleFactory, NormalModuleFactory e incluso el analizador de JavaScript tiene una serie de funciones de enlace. Al mismo tiempo, el diseño separado del complemento, el cargador y el solucionador también aumenta la complejidad del sistema. Por lo tanto, no es descabellado que las opiniones en Internet prefieran el resumen. Un sistema complejo no quiere decir que sea malo, pero sin duda aumenta el coste de aprendizaje de los usuarios.

Acerca de OpenTiny

OpenTiny es una solución de biblioteca de componentes de nivel empresarial que se adapta a múltiples terminales, como PC/móvil, cubre pilas multitecnología Vue2/Vue3/Angular y tiene herramientas que mejoran la eficiencia, como el sistema de configuración de temas/plantilla de fondo medio/comando CLI line, que puede ayudar a los desarrolladores a desarrollar aplicaciones web de manera eficiente.

Aspectos destacados principales:

  1. 跨端跨框架: Al usar la arquitectura de diseño de componentes sin procesamiento Renderless, un conjunto de códigos admite Vue2/Vue3, PC/móvil al mismo tiempo, y admite la personalización de la lógica a nivel de función y el reemplazo completo de la plantilla, con buena flexibilidad y sólidas capacidades de desarrollo secundario.
  2. 组件丰富: Hay más de 80 componentes en el lado de la PC y más de 30 componentes en el lado móvil, incluidos los componentes de alta frecuencia Table, Tree, Select, etc., con desplazamiento virtual incorporado para garantizar una experiencia fluida en escenarios de big data. Además de los componentes comunes en la industria, también proporcionamos algunos componentes de características únicas, como: divisor de panel dividido, cuadro de entrada de dirección IP de dirección IP, calendario de calendario, recorte de imagen recortada, etc.
  3. 配置式组件: El componente admite métodos de plantilla y configuración, adecuados para plataformas de código bajo. Actualmente, el equipo ha integrado OpenTiny en la plataforma interna de código bajo y ha realizado muchas optimizaciones para plataformas de código bajo.
  4. 周边生态齐全: proporciona una biblioteca de componentes TinyNG basada en Angular + TypeScript, una plantilla de fondo medio TinyPro con más de 10 funciones prácticas y más de 20 páginas típicas, una herramienta de ingeniería TinyCLI que cubre todo el proceso de desarrollo front-end y una poderosa plataforma de configuración de temas en línea Tema pequeño

Contáctenos:

Para obtener más contenido de video, también puede seguir la cuenta de la comunidad OpenTiny, Station B/Douyin/Xiaohongshu/Video.

Supongo que te gusta

Origin blog.csdn.net/OpenTiny/article/details/132163981
Recomendado
Clasificación