Repository: nuxt/nuxt.js
Pull request #6414 (2019-09-14 23:53:44)

feat(config): `serverMiddleware` as a simple key/value object
If using only named serverMiddleware routes, this PR makes it possible to do:

```
serverMiddleware: {
   '/route' (req, res, next) {
   }
}
```

codecov-io: # [Codecov](https://codecov.io/gh/nuxt/nuxt.js/pull/6414?src=pr&el=h1) Report
> Merging [#6414](https://codecov.io/gh/nuxt/nuxt.js/pull/6414?src=pr&el=desc) into [dev](https://codecov.io/gh/nuxt/nuxt.js/commit/0e0149d82138b868cad38f0a2b00d0ed7f6b0cad?src=pr&el=desc) will **increase** coverage by `<.01%`.
> The diff coverage is `100%`.

[![Impacted file tree graph](https://codecov.io/gh/nuxt/nuxt.js/pull/6414/graphs/tree.svg?width=650&token=nGD1PtMB3M&height=150&src=pr)](https://codecov.io/gh/nuxt/nuxt.js/pull/6414?src=pr&el=tree)

```diff
@@            Coverage Diff             @@
##              dev    #6414      +/-   ##
==========================================
+ Coverage   95.61%   95.61%   +<.01%     
==========================================
  Files          79       79              
  Lines        2689     2692       +3     
  Branches      694      695       +1     
==========================================
+ Hits         2571     2574       +3     
  Misses        101      101              
  Partials       17       17
```

| Flag | Coverage Δ | |
|---|---|---|
| #e2e | `100% <ø> (ø)` | :arrow_up: |
| #fixtures | `50.78% <33.33%> (-0.02%)` | :arrow_down: |
| #unit | `92.27% <100%> (ø)` | :arrow_up: |

| [Impacted Files](https://codecov.io/gh/nuxt/nuxt.js/pull/6414?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [packages/config/src/options.js](https://codecov.io/gh/nuxt/nuxt.js/pull/6414/diff?src=pr&el=tree#diff-cGFja2FnZXMvY29uZmlnL3NyYy9vcHRpb25zLmpz) | `100% <100%> (ø)` | :arrow_up: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/nuxt/nuxt.js/pull/6414?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/nuxt/nuxt.js/pull/6414?src=pr&el=footer). Last update [0e0149d...8e0f208](https://codecov.io/gh/nuxt/nuxt.js/pull/6414?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).

manniL: Could you elaborate what you mean with "as hash" exactly?
galvez: @manniL updated PR title.

Patch:
diff --git a/packages/builder/src/builder.js b/packages/builder/src/builder.js
index 874ca5a55..4cee71cfd 100644
--- a/packages/builder/src/builder.js
+++ b/packages/builder/src/builder.js
@@ -310,7 +310,8 @@ export default class Builder {
   async resolveFiles (dir, cwd = this.options.srcDir) {
     return this.ignore.filter(await glob(this.globPathWithExtensions(dir), {
       cwd,
-      ignore: this.options.ignore
+      ignore: this.options.ignore,
+      follow: this.options.build.followSymlinks
     }))
   }
 
diff --git a/packages/cli/src/commands/dev.js b/packages/cli/src/commands/dev.js
index c2047ab90..4aa5f7df3 100644
--- a/packages/cli/src/commands/dev.js
+++ b/packages/cli/src/commands/dev.js
@@ -27,16 +27,25 @@ export default {
   },
 
   async startDev (cmd, argv) {
+    let nuxt
     try {
-      const nuxt = await this._startDev(cmd, argv)
+      nuxt = await this._listenDev(cmd, argv)
+    } catch (error) {
+      consola.fatal(error)
+      return
+    }
 
-      return nuxt
+    try {
+      await this._buildDev(cmd, argv, nuxt)
     } catch (error) {
+      await nuxt.callHook('cli:buildError', error)
       consola.error(error)
     }
+
+    return nuxt
   },
 
-  async _startDev (cmd, argv) {
+  async _listenDev (cmd, argv) {
     const config = await cmd.getNuxtConfig({ dev: true, _build: true })
     const nuxt = await cmd.getNuxt(config)
 
@@ -60,6 +69,11 @@ export default {
       await Promise.all(openerPromises)
     }
 
+    // Return instance
+    return nuxt
+  },
+
+  async _buildDev (cmd, argv, nuxt) {
     // Create builder instance
     const builder = await cmd.getBuilder(nuxt)
 
diff --git a/packages/config/src/config/build.js b/packages/config/src/config/build.js
index 4a441a61b..c0c5eaa42 100644
--- a/packages/config/src/config/build.js
+++ b/packages/config/src/config/build.js
@@ -118,5 +118,6 @@ export default () => ({
   },
   friendlyErrors: true,
   additionalExtensions: [],
-  warningIgnoreFilters: []
+  warningIgnoreFilters: [],
+  followSymlinks: false
 })
diff --git a/packages/config/src/options.js b/packages/config/src/options.js
index aa2341727..243f673eb 100644
--- a/packages/config/src/options.js
+++ b/packages/config/src/options.js
@@ -410,5 +410,10 @@ export function getNuxtConfig (_options) {
     options.server.timing = { total: true, ...timing }
   }
 
+  if (isPureObject(options.serverMiddleware)) {
+    options.serverMiddleware = Object.entries(options.serverMiddleware)
+      .map(([ path, handler ]) => ({ path, handler }))
+  }
+
   return options
 }
diff --git a/packages/vue-app/template/utils.js b/packages/vue-app/template/utils.js
index a3ebd3c14..f2a50dd98 100644
--- a/packages/vue-app/template/utils.js
+++ b/packages/vue-app/template/utils.js
@@ -35,7 +35,7 @@ export function applyAsyncData (Component, asyncData) {
   Component.options._originDataFn = ComponentData
 
   Component.options.data = function () {
-    const data = ComponentData.call(this)
+    const data = ComponentData.call(this, this)
     if (this.$ssrContext) {
       asyncData = this.$ssrContext.asyncData[Component.cid]
     }
diff --git a/packages/webpack/src/config/base.js b/packages/webpack/src/config/base.js
index 8652e87d7..6234e1e61 100644
--- a/packages/webpack/src/config/base.js
+++ b/packages/webpack/src/config/base.js
@@ -68,6 +68,7 @@ export default class WebpackBaseConfig {
   get modulesToTranspile () {
     return [
       /\.vue\.js/i, // include SFCs in node_modules
+      /consola\/src/,
       ...this.normalizeTranspile({ pathNormalize: true })
     ]
   }
@@ -224,7 +225,8 @@ export default class WebpackBaseConfig {
   alias () {
     return {
       ...this.buildContext.options.alias,
-      consola: require.resolve(`consola/dist/consola${this.isServer ? '' : '.browser'}.js`)
+      consola: require.resolve(`consola/${this.isServer ? 'dist/consola' : 'src/browser'}.js`),
+      'vue-meta': require.resolve(`vue-meta${this.isServer ? '' : '/dist/vue-meta.esm.browser.js'}`)
     }
   }
 


Test patch:
diff --git a/packages/cli/test/unit/dev.test.js b/packages/cli/test/unit/dev.test.js
index e0b135160..2654b4ea0 100644
--- a/packages/cli/test/unit/dev.test.js
+++ b/packages/cli/test/unit/dev.test.js
@@ -41,7 +41,7 @@ describe('dev', () => {
     expect(consola.error).not.toHaveBeenCalled()
   })
 
-  test('catches build error', async () => {
+  test('catches build error and calls hook', async () => {
     const Nuxt = mockNuxt()
     const Builder = mockBuilder()
 
@@ -55,6 +55,7 @@ describe('dev', () => {
     await Nuxt.fileRestartHook(builder)
 
     expect(Nuxt.prototype.close).toHaveBeenCalled()
+    expect(Nuxt.prototype.callHook).toHaveBeenCalledWith('cli:buildError', expect.any(Error))
     expect(consola.error).toHaveBeenCalledWith(new Error('Build Error'))
   })
 
@@ -88,7 +89,7 @@ describe('dev', () => {
     builder.nuxt = new Nuxt()
     await Nuxt.fileRestartHook(builder)
 
-    expect(consola.error).toHaveBeenCalledWith(new Error('Config Error'))
+    expect(consola.fatal).toHaveBeenCalledWith(new Error('Config Error'))
     // expect(Builder.prototype.watchRestart).toHaveBeenCalledTimes(1)
   })
 
@@ -104,7 +105,7 @@ describe('dev', () => {
 
     await NuxtCommand.from(dev).run()
 
-    expect(consola.error).toHaveBeenCalledWith(new Error('Listen Error'))
+    expect(consola.fatal).toHaveBeenCalledWith(new Error('Listen Error'))
   })
 
   test('dev doesnt force-exit by default', async () => {
diff --git a/packages/cli/test/utils/mocking.js b/packages/cli/test/utils/mocking.js
index dc5edaef2..045a4e6e6 100644
--- a/packages/cli/test/utils/mocking.js
+++ b/packages/cli/test/utils/mocking.js
@@ -83,6 +83,7 @@ export const mockNuxt = (implementation) => {
       }
     },
     options: {},
+    callHook: jest.fn(),
     clearHook: jest.fn(),
     clearHooks: jest.fn(),
     close: jest.fn(),
diff --git a/packages/config/test/__snapshots__/options.test.js.snap b/packages/config/test/__snapshots__/options.test.js.snap
index 1bcf8cdc5..e8798e12d 100644
--- a/packages/config/test/__snapshots__/options.test.js.snap
+++ b/packages/config/test/__snapshots__/options.test.js.snap
@@ -39,6 +39,7 @@ Object {
       "img": [Function],
       "video": [Function],
     },
+    "followSymlinks": false,
     "friendlyErrors": true,
     "hardSource": false,
     "hotMiddleware": Object {},
diff --git a/packages/config/test/config/__snapshots__/index.test.js.snap b/packages/config/test/config/__snapshots__/index.test.js.snap
index 09eddd6fd..88799244b 100644
--- a/packages/config/test/config/__snapshots__/index.test.js.snap
+++ b/packages/config/test/config/__snapshots__/index.test.js.snap
@@ -26,6 +26,7 @@ Object {
       "img": [Function],
       "video": [Function],
     },
+    "followSymlinks": false,
     "friendlyErrors": true,
     "hardSource": false,
     "hotMiddleware": Object {},
@@ -376,6 +377,7 @@ Object {
       "img": [Function],
       "video": [Function],
     },
+    "followSymlinks": false,
     "friendlyErrors": true,
     "hardSource": false,
     "hotMiddleware": Object {},
diff --git a/packages/config/test/options.test.js b/packages/config/test/options.test.js
index 0f58aceb0..87166c45c 100644
--- a/packages/config/test/options.test.js
+++ b/packages/config/test/options.test.js
@@ -266,6 +266,18 @@ describe('config: options', () => {
   })
 })
 
+describe('config: serverMiddleware', () => {
+  test('should transform serverMiddleware hash', () => {
+    const serverMiddleware = {
+      '/resource': (req, res, next) => {
+      }
+    }
+    const config = getNuxtConfig({ serverMiddleware })
+    expect(config.serverMiddleware[0].path).toBe('/resource')
+    expect(config.serverMiddleware[0].handler).toBe(serverMiddleware['/resource'])
+  })
+})
+
 describe('config: router', () => {
   test('should sanitize router.base', () => {
     const config = getNuxtConfig({ router: { base: '/foo' } })
diff --git a/test/fixtures/basic/nuxt.config.js b/test/fixtures/basic/nuxt.config.js
index 74d5a6e74..f350ceefb 100644
--- a/test/fixtures/basic/nuxt.config.js
+++ b/test/fixtures/basic/nuxt.config.js
@@ -77,6 +77,7 @@ export default {
   build: {
     scopeHoisting: true,
     publicPath: '',
+    followSymlinks: true,
     postcss: {
       preset: {
         features: {
diff --git a/test/fixtures/basic/pages/symlink/deep/nested-symlinked.vue b/test/fixtures/basic/pages/symlink/deep/nested-symlinked.vue
new file mode 100644
index 000000000..5f2d4a4ff
--- /dev/null
+++ b/test/fixtures/basic/pages/symlink/deep/nested-symlinked.vue
@@ -0,0 +1,3 @@
+<template>
+  <h1>Nested symlink page</h1>
+</template>
diff --git a/test/fixtures/basic/pages/symlink/symlinked.vue b/test/fixtures/basic/pages/symlink/symlinked.vue
new file mode 100644
index 000000000..2a7c4a1c9
--- /dev/null
+++ b/test/fixtures/basic/pages/symlink/symlinked.vue
@@ -0,0 +1,3 @@
+<template>
+  <h1>Symlinked page</h1>
+</template>
diff --git a/test/fixtures/basic/pages2/deep/nested-symlinked.vue b/test/fixtures/basic/pages2/deep/nested-symlinked.vue
new file mode 100644
index 000000000..5f2d4a4ff
--- /dev/null
+++ b/test/fixtures/basic/pages2/deep/nested-symlinked.vue
@@ -0,0 +1,3 @@
+<template>
+  <h1>Nested symlink page</h1>
+</template>
diff --git a/test/fixtures/basic/pages2/symlinked.vue b/test/fixtures/basic/pages2/symlinked.vue
new file mode 100644
index 000000000..2a7c4a1c9
--- /dev/null
+++ b/test/fixtures/basic/pages2/symlinked.vue
@@ -0,0 +1,3 @@
+<template>
+  <h1>Symlinked page</h1>
+</template>
diff --git a/test/unit/basic.ssr.test.js b/test/unit/basic.ssr.test.js
index c0fa38202..74267d458 100644
--- a/test/unit/basic.ssr.test.js
+++ b/test/unit/basic.ssr.test.js
@@ -358,6 +358,16 @@ describe('basic ssr', () => {
     expect(html).toMatch('<h1>JS Layout</h1>')
     expect(html).toMatch('<h2>custom page</h2>')
   })
+  /* Testing symlinks functionality */
+  test('/symlink/symlinked', async () => {
+    const { html } = await nuxt.server.renderRoute('/symlink/symlinked')
+    expect(html).toContain('<h1>Symlinked page</h1>')
+  })
+
+  test('/symlink/deep/nested-symlinked', async () => {
+    const { html } = await nuxt.server.renderRoute('/symlink/deep/nested-symlinked')
+    expect(html).toContain('<h1>Nested symlink page</h1>')
+  })
 
   // Close server and ask nuxt to stop listening to file changes
   afterAll(async () => {