Repository: nuxt/nuxt.js
Pull request #6368 (2019-09-05 13:03:18)

feat(builder): `followSymlinks` option to allow for symlinks
<!--- Provide a general summary of your changes in the title above -->

## Types of changes
<!--- What types of changes does your code introduce? Put an `x` in all the boxes that apply: -->
- [ ] Bug fix (a non-breaking change which fixes an issue)
- [X] New feature (a non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to change)


## Description
<!--- Describe your changes in detail -->
<!--- Why is this change required? What problem does it solve? -->
<!--- If it resolves an open issue, please link to the issue here. For example "Resolves: #1337" -->
This PR adds a build.followSymlinks boolean(default: false), and passes it to the builder's glob function inside resolveFiles().

This allows for following symlinks inside layouts/middleware/pages/store, thus including them in the app.
## Checklist:
<!--- Put an `x` in all the boxes that apply. -->
<!--- If your change requires a documentation PR, please link it appropriately -->
<!--- If you're unsure about any of these, don't hesitate to ask. We're here to help! -->
- [X] My change requires a change to the documentation.
- [ ] I have updated the documentation accordingly. (PR: #)
- [X] I have added tests to cover my changes (if not applicable, please state why)
- [X] All new and existing tests are passing.



codecov-io: # [Codecov](https://codecov.io/gh/nuxt/nuxt.js/pull/6368?src=pr&el=h1) Report
> Merging [#6368](https://codecov.io/gh/nuxt/nuxt.js/pull/6368?src=pr&el=desc) into [dev](https://codecov.io/gh/nuxt/nuxt.js/commit/05a6efd1eb261aefde8301e4cd6e49e45ae67b0f?src=pr&el=desc) will **not change** coverage.
> The diff coverage is `n/a`.

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

```diff
@@           Coverage Diff           @@
##              dev    #6368   +/-   ##
=======================================
  Coverage   95.72%   95.72%           
=======================================
  Files          79       79           
  Lines        2666     2666           
  Branches      683      683           
=======================================
  Hits         2552     2552           
  Misses         98       98           
  Partials       16       16
```

| Flag | Coverage Δ | |
|---|---|---|
| #e2e | `100% <ø> (ø)` | :arrow_up: |
| #fixtures | `50.71% <ø> (ø)` | :arrow_up: |
| #unit | `92.38% <ø> (ø)` | :arrow_up: |

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

------

[Continue to review full report at Codecov](https://codecov.io/gh/nuxt/nuxt.js/pull/6368?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/6368?src=pr&el=footer). Last update [05a6efd...56d3a0c](https://codecov.io/gh/nuxt/nuxt.js/pull/6368?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).

pimlie: @SnirShechter Thanks for this pr. Would it maybe be possible to combine the tests with an existing fixture? Adding it to `basic` or `with-config` should be fine.
The reason for this is that every new fixture adds about ~10seconds to the test time. So we try to only add new fixtures for incompatible configs.

Also note that it seems you are using a different emailaddress in your local git config then for github. If you want you can either add that other emailaddress to your github account (Settings -> Emails) or correct the emailaddress & do a force-push (old commits will still be accessible though).

SnirShechter: > @SnirShechter Thanks for this pr. Would it maybe be possible to combine the tests with an existing fixture? Adding it to `basic` or `with-config` should be fine.
> The reason for this is that every new fixture adds about ~10seconds to the test time. So we try to only add new fixtures for incompatible configs.
> 
> Also note that it seems you are using a different emailaddress in your local git config then for github. If you want you can either add that other emailaddress to your github account (Settings -> Emails) or correct the emailaddress & do a force-push (old commits will still be accessible though).

Hey, sorry for the really late reply. I'm on it.
SnirShechter: It is something worth thinking of, but basically followSymlinks is (through my point of view) much more than a simple glob setting, since it allows separation of pages in different folders. For me and my company, this means that we can integrate one project into another, when built right.

Thanks for the approval 👍

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/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/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 () => {