Repository: nuxt/nuxt.js
Pull request #6285 (2019-08-22 19:01:35)

feat(builder): `extendPlugins` option and `builder:extendPlugins'` hook
Currently there's no way to determine the final ordering of user-space plugins in relation to plugins registered automatically by modules (and not all modules necessarily use `addPlugin()`, which uses `unshift()`). The problem is complex and likely to have other takes, but as @pi0 said:

<img width="711" alt="Screen Shot 2019-08-26 at 17 19 08" src="https://user-images.githubusercontent.com/12291/63720498-b6ef6c00-c825-11e9-8178-f91c5261c034.png">

So this introduces a new `build:extendPlugins` configuration hook, similar to `extendRoutes()`, that allows users to completely modify the order of all plugins before the build.

manniL: Besides my annotation in the code, I'd like to see a more illuminating description for the PR, e.g., why introducing the change, what benefits, maybe even potential drawbacks.

For other contributors (besides the core team), the description isn't helpful as is and not transparent.
pimlie: By request of @galvez I've added support for `options.extendPlugins` which follows the same doctrine as `options.router.extendRoutes`

Patch:
diff --git a/packages/builder/src/builder.js b/packages/builder/src/builder.js
index 0b7d16cd5..3964862df 100644
--- a/packages/builder/src/builder.js
+++ b/packages/builder/src/builder.js
@@ -227,8 +227,7 @@ export default class Builder {
       this.bundleBuilder.pauseWatch()
     }
 
-    // Plugins
-    this.plugins = Array.from(this.normalizePlugins())
+    this.plugins = Array.from(await this.normalizePlugins())
 
     const templateContext = this.createTemplateContext()
 
@@ -255,7 +254,19 @@ export default class Builder {
     consola.success('Nuxt files generated')
   }
 
-  normalizePlugins () {
+  async normalizePlugins () {
+    // options.extendPlugins allows for returning a new plugins array
+    if (typeof this.options.extendPlugins === 'function') {
+      const extendedPlugins = this.options.extendPlugins(this.options.plugins)
+
+      if (Array.isArray(extendedPlugins)) {
+        this.options.plugins = extendedPlugins
+      }
+    }
+
+    // extendPlugins hook only supports in-place modifying
+    await this.nuxt.callHook('builder:extendPlugins', this.options.plugins)
+
     const modes = ['client', 'server']
     const modePattern = new RegExp(`\\.(${modes.join('|')})(\\.\\w+)*$`)
     return uniqBy(
diff --git a/packages/config/src/config/_app.js b/packages/config/src/config/_app.js
index f4c412c7e..12df0a492 100644
--- a/packages/config/src/config/_app.js
+++ b/packages/config/src/config/_app.js
@@ -20,6 +20,8 @@ export default () => ({
 
   plugins: [],
 
+  extendPlugins: null,
+
   css: [],
 
   modules: [],


Test patch:
diff --git a/packages/builder/test/builder.plugin.test.js b/packages/builder/test/builder.plugin.test.js
index 0b5ba6f4b..0406c4174 100644
--- a/packages/builder/test/builder.plugin.test.js
+++ b/packages/builder/test/builder.plugin.test.js
@@ -18,7 +18,7 @@ describe('builder: builder plugins', () => {
     jest.clearAllMocks()
   })
 
-  test('should normalize plugins', () => {
+  test('should normalize plugins', async () => {
     const nuxt = createNuxt()
     nuxt.options.plugins = [
       '/var/nuxt/plugins/test.js',
@@ -26,9 +26,12 @@ describe('builder: builder plugins', () => {
       { src: '/var/nuxt/plugins/test.server', mode: 'server' },
       { src: '/var/nuxt/plugins/test.client', ssr: false }
     ]
+
     const builder = new Builder(nuxt, BundleBuilder)
+    const plugins = await builder.normalizePlugins()
 
-    const plugins = builder.normalizePlugins()
+    expect(nuxt.callHook).toBeCalledTimes(1)
+    expect(nuxt.callHook).toBeCalledWith('builder:extendPlugins', nuxt.options.plugins)
 
     expect(plugins).toEqual([
       {
@@ -54,14 +57,39 @@ describe('builder: builder plugins', () => {
     ])
   })
 
-  test('should warning and fallback invalid mode when normalize plugins', () => {
+  test('should overwrite plugins from options', async () => {
+    const nuxt = createNuxt()
+
+    nuxt.options.plugins = [ '/var/nuxt/plugins/foo-bar.js' ]
+    nuxt.options.extendPlugins = jest.fn().mockReturnValue([
+      '/var/nuxt/plugins/fizz-fuzz.js'
+    ])
+
+    const builder = new Builder(nuxt, BundleBuilder)
+    const plugins = await builder.normalizePlugins()
+
+    expect(nuxt.options.extendPlugins).toHaveBeenCalledTimes(1)
+    expect(nuxt.options.extendPlugins).toHaveBeenCalledWith([
+      '/var/nuxt/plugins/foo-bar.js'
+    ])
+
+    expect(plugins).toEqual([
+      {
+        mode: 'all',
+        name: 'nuxt_plugin_fizzfuzz_hash(/var/nuxt/plugins/fizz-fuzz.js)',
+        src: 'resolveAlias(/var/nuxt/plugins/fizz-fuzz.js)'
+      }
+    ])
+  })
+
+  test('should warning and fallback invalid mode when normalize plugins', async () => {
     const nuxt = createNuxt()
     nuxt.options.plugins = [
       { src: '/var/nuxt/plugins/test', mode: 'abc' }
     ]
     const builder = new Builder(nuxt, BundleBuilder)
 
-    const plugins = builder.normalizePlugins()
+    const plugins = await builder.normalizePlugins()
 
     expect(plugins).toEqual([
       {
@@ -129,7 +157,7 @@ describe('builder: builder plugins', () => {
     ])
   })
 
-  test('should detect plugin mode for client/server plugins', () => {
+  test('should detect plugin mode for client/server plugins', async () => {
     const nuxt = createNuxt()
     const builder = new Builder(nuxt, BundleBuilder)
     builder.options.plugins = [
@@ -138,7 +166,7 @@ describe('builder: builder plugins', () => {
       { src: '/var/nuxt/plugins/test.server' }
     ]
 
-    const plugins = builder.normalizePlugins()
+    const plugins = await builder.normalizePlugins()
 
     expect(plugins).toEqual([
       { mode: 'all',
diff --git a/packages/config/test/__snapshots__/options.test.js.snap b/packages/config/test/__snapshots__/options.test.js.snap
index df8249045..9f9917d0c 100644
--- a/packages/config/test/__snapshots__/options.test.js.snap
+++ b/packages/config/test/__snapshots__/options.test.js.snap
@@ -161,6 +161,7 @@ Object {
   },
   "editor": undefined,
   "env": Object {},
+  "extendPlugins": null,
   "extensions": Array [
     "js",
     "mjs",
diff --git a/packages/config/test/config/__snapshots__/index.test.js.snap b/packages/config/test/config/__snapshots__/index.test.js.snap
index 6fd7695c6..c56a3e057 100644
--- a/packages/config/test/config/__snapshots__/index.test.js.snap
+++ b/packages/config/test/config/__snapshots__/index.test.js.snap
@@ -141,6 +141,7 @@ Object {
   },
   "editor": undefined,
   "env": Object {},
+  "extendPlugins": null,
   "extensions": Array [],
   "fetch": Object {
     "client": true,
@@ -471,6 +472,7 @@ Object {
   },
   "editor": undefined,
   "env": Object {},
+  "extendPlugins": null,
   "extensions": Array [],
   "fetch": Object {
     "client": true,