rfcs icon indicating copy to clipboard operation
rfcs copied to clipboard

Module Improvements

Open pi0 opened this issue 5 years ago • 9 comments

TLDR

A powerful engine is not usable without a good interface. Nuxt modular refactor made a long time ago but it is still lacking a good SDK.

History

The initial proposal for modules started with a project called nuxt-helpers, a wrapper function around export inside nuxt.config.js. Nuxt was hackable enough to inject any parts of it. But we wanted a more stable and official way to access and modify Nuxt internals and hack things beyond webpack extend. Nuxt core has been broken into smaller modules with their specific tasks that inherit a global options object and communicate with each using Nuxt class (The hub). Then we needed to create an entry point for modules that can start hacking Nuxt from zero to build and start. We thought about different ways like an exported object that contains strict options but it was against the creativity of module authors. With a simple function, module author is free to get the nuxt instance and hook-up into any part of built-ins. Actually, this pattern is powerful more than enough. I've never thought we should change it. Modules like nuxt7 built on top of this simple pattern can customize anything!

So the problem

Modules, while being free to touch any internal by using nuxt reference should be somehow isolated not only because of providing them utilities like addPlugin() but also because nuxt internals (or it's dependencies like webpack 3 => webpack 4) may be changed at any time. The bad decision (most of mine) was using ModuleContainer for utility container. This has several disadvantages:

  • ModuleContainer is bound to the nuxt runtime (ie, currently running nuxt version). Modules don't know who is running them and what utilities are available for them! This is why after initial version almost nothing new supported in ModuleContainer and instead module authors had to write their own.
  • If a bug is inside ModuleContainer it is not fixable by modules
  • It has not backward/forward compatible utilities
  • It is implicitly injected by bounding module's function into moduleContainer context. Splitting module into smaller logical functions is like a pain in a**!
  • No class support
  • It is optimized and not hackable by people
  • Modules execute every f** time that nuxt starts. Development or Production. Actually, module authors can use this.nuxt.hook guard to lazy require and only configure webpack options when we are building project but in reality, no-one did this (Maybe because it was hard?)
    • nuxt-start requires all module packages to be installed. Only the built-only ones.

The solution

The module utilities (SDK) should be provided as a separated package (Maybe even a dedicated repository in nuxt-community). Other than regular utilities this package can provide a HOC (wrapper function) and Base class that finally evaluates into a plain function usable by Nuxt engine. This guarantees both old and new modules can work with old and new versions of Nuxt while module authors are just encouraged to use SDK instead of using legacy ModuleContainer helpers. Module authors can create their HOC functions too.

Usage

With class

import { NuxtModule } from '@nuxt/module'

class MyModule extends NuxtModule {
  static get key() {
    return 'myModule' 
  }

  // This mapping can be moved into base class too
  static get hooks() {
    return {
      'nuxt:init': 'onNuxtInit',
      'bulder:build': 'onBuild'
    }
  }

  async onBuild() {
    this.addPlugin(...)
  }

  async onNuxtInit() {
    this.nuxt.render = (_, res) => res.end('🌈')
  }
}

// Compile class into a plain function
export default MyModule.export()

With wrapper function

import { createModule, addPlugin } from '@nuxt/module'

export default createModule({
  name: 'MyModule',
  key: 'myModule',
  hooks: {
    'nuxt:init'(nuxt) {
       this.nuxt.render = (_, res) => res.end('🌈')
    },
    'builder:build'(nuxt, builder) {
        addPlugin(nuxt, ...)
    }
  }
})

Personally, I like the wrapper pattern as it is closer to the Vue exports, is simpler to write (no export() call) and most importantly it is much better for implementing lazy-requires that are basically context-less pure functions. But we may implement both :)

Chore

  1. What do we call this package? @nuxt/sdk | @nuxt/module | @nuxt/module-utilities ?
  2. It should be a part of the monorepo or separate repository?
  3. Should we keep old ModuleContainer codes or move them to the new SDK and depend @nuxt/core on the latest version of it?

pi0 avatar Nov 18 '18 09:11 pi0