diff --git a/packages/builder/src/builder.js b/packages/builder/src/builder.js index 093cf14245..c551da139a 100644 --- a/packages/builder/src/builder.js +++ b/packages/builder/src/builder.js @@ -110,9 +110,19 @@ export default class Builder { /[^a-zA-Z?\d\s:]/g, '' ) + + if (p.ssr === false) { + p.mode = 'client' + } else if (p.mode === undefined) { + p.mode = 'all' + } else if (!['client', 'server'].includes(p.mode)) { + consola.warn(`Invalid plugin mode (server/client/all): '${p.mode}'. Falling back to 'all'`) + p.mode = 'all' + } + return { src: this.nuxt.resolver.resolveAlias(p.src), - ssr: p.ssr !== false, + mode: p.mode, name: 'nuxt_plugin_' + pluginBaseName + '_' + hash(p.src) } }), @@ -135,6 +145,15 @@ export default class Builder { }) } + const modes = ['client', 'server'] + const modernPattern = new RegExp(`\\.(${modes.join('|')})\\.\\w+$`) + pluginFiles[0].replace(modernPattern, (_, mode) => { + // mode in nuxt.config has higher priority + if (p.mode === 'all' && modes.includes(mode)) { + p.mode = mode + } + }) + p.src = this.relativeToBuild(p.src) })) } diff --git a/packages/core/src/module.js b/packages/core/src/module.js index 3f244dcf5e..a8581c6e1e 100644 --- a/packages/core/src/module.js +++ b/packages/core/src/module.js @@ -63,7 +63,9 @@ export default class ModuleContainer { // Add to nuxt plugins this.options.plugins.unshift({ src: path.join(this.options.buildDir, dst), - ssr: template.ssr + // TODO: remove deprecated option in Nuxt 3 + ssr: template.ssr, + mode: template.mode }) } diff --git a/packages/vue-app/template/index.js b/packages/vue-app/template/index.js index 7654646f8a..a8469e3f42 100644 --- a/packages/vue-app/template/index.js +++ b/packages/vue-app/template/index.js @@ -12,7 +12,7 @@ import { setContext, getLocation, getRouteData, normalizeError } from './utils' /* Plugins */ <%= isTest ? '/* eslint-disable camelcase */' : '' %> -<% plugins.forEach((plugin) => { %>import <%= plugin.name %> from '<%= plugin.name %>' // Source: <%= relativeToBuild(plugin.src) %><%= (plugin.ssr===false) ? ' (ssr: false)' : '' %> +<% plugins.forEach((plugin) => { %>import <%= plugin.name %> from '<%= plugin.name %>' // Source: <%= relativeToBuild(plugin.src) %> (mode: '<%= plugin.mode %>') <% }) %> <%= isTest ? '/* eslint-enable camelcase */' : '' %> @@ -168,11 +168,18 @@ async function createApp(ssrContext) { // Plugin execution <%= isTest ? '/* eslint-disable camelcase */' : '' %> - <% plugins.filter(p => p.ssr).forEach((plugin) => { %> + <% plugins.filter(p => p.mode === 'all').forEach((plugin) => { %> if (typeof <%= plugin.name %> === 'function') await <%= plugin.name %>(app.context, inject)<% }) %> - <% if (plugins.filter(p => !p.ssr).length) { %> + + <% if (plugins.filter(p => p.mode === 'client').length) { %> if (process.client) { - <% plugins.filter((p) => !p.ssr).forEach((plugin) => { %> + <% plugins.filter(p => p.mode === 'client').forEach((plugin) => { %> + if (typeof <%= plugin.name %> === 'function') await <%= plugin.name %>(app.context, inject)<% }) %> + }<% } %> + + <% if (plugins.filter(p => p.mode === 'server').length) { %> + if (process.server) { + <% plugins.filter(p => p.mode === 'server').forEach((plugin) => { %> if (typeof <%= plugin.name %> === 'function') await <%= plugin.name %>(app.context, inject)<% }) %> }<% } %> <%= isTest ? '/* eslint-enable camelcase */' : '' %> diff --git a/packages/webpack/src/builder.js b/packages/webpack/src/builder.js index 1f68db3303..d0358f0b5c 100644 --- a/packages/webpack/src/builder.js +++ b/packages/webpack/src/builder.js @@ -63,18 +63,17 @@ export class WebpackBundler { for (const p of this.context.plugins) { // Client config if (!clientConfig.resolve.alias[p.name]) { - clientConfig.resolve.alias[p.name] = p.src + clientConfig.resolve.alias[p.name] = p.mode === 'server' ? './empty.js' : p.src } // Server config if (serverConfig && !serverConfig.resolve.alias[p.name]) { - // Alias to noop for ssr:false plugins - serverConfig.resolve.alias[p.name] = p.ssr ? p.src : './empty.js' + serverConfig.resolve.alias[p.name] = p.mode === 'client' ? './empty.js' : p.src } // Modern config if (modernConfig && !modernConfig.resolve.alias[p.name]) { - modernConfig.resolve.alias[p.name] = p.src + modernConfig.resolve.alias[p.name] = p.mode === 'client' ? './empty.js' : p.src } } diff --git a/test/fixtures/with-config/nuxt.config.js b/test/fixtures/with-config/nuxt.config.js index 13caab9570..95f39ec4c8 100644 --- a/test/fixtures/with-config/nuxt.config.js +++ b/test/fixtures/with-config/nuxt.config.js @@ -37,7 +37,9 @@ export default { extensions: 'ts', plugins: [ '~/plugins/test', - '~/plugins/test.plugin', + { src: '~/plugins/test.plugin', mode: 'abc' }, + '~/plugins/test.client', + '~/plugins/test.server', { src: '~/plugins/only-client.js', ssr: false } ], loading: '~/components/loading', diff --git a/test/fixtures/with-config/plugins/test.client.js b/test/fixtures/with-config/plugins/test.client.js new file mode 100644 index 0000000000..387e7fc54a --- /dev/null +++ b/test/fixtures/with-config/plugins/test.client.js @@ -0,0 +1 @@ +window.__test_plugin_client = 'test_plugin_client' diff --git a/test/fixtures/with-config/plugins/test.server.js b/test/fixtures/with-config/plugins/test.server.js new file mode 100644 index 0000000000..94df4661fa --- /dev/null +++ b/test/fixtures/with-config/plugins/test.server.js @@ -0,0 +1 @@ +global.__test_plugin_server = 'test_plugin_server' diff --git a/test/fixtures/with-config/with-config.test.js b/test/fixtures/with-config/with-config.test.js index 6d1b6da61c..a0ab037806 100644 --- a/test/fixtures/with-config/with-config.test.js +++ b/test/fixtures/with-config/with-config.test.js @@ -14,12 +14,15 @@ const hooks = [ describe('with-config', () => { buildFixture('with-config', () => { - expect(consola.warn).toHaveBeenCalledTimes(4) + expect(consola.warn).toHaveBeenCalledTimes(5) expect(consola.fatal).toHaveBeenCalledTimes(0) expect(consola.warn.mock.calls).toMatchObject([ [ 'Unknown mode: unknown. Falling back to universal' ], + [ + `Invalid plugin mode (server/client/all): 'abc'. Falling back to 'all'` + ], [{ message: 'Found 2 plugins that match the configuration, suggest to specify extension:', additional: expect.stringContaining('plugins/test.json') diff --git a/test/unit/with-config.test.js b/test/unit/with-config.test.js index 7860deab6e..4bb47d7fec 100644 --- a/test/unit/with-config.test.js +++ b/test/unit/with-config.test.js @@ -91,6 +91,8 @@ describe('with-config', () => { expect(window.__test_plugin).toBe(true) expect(window.__test_plugin_ext).toBe(true) + expect(window.__test_plugin_client).toBe('test_plugin_client') + expect(window.__test_plugin_server).toBeUndefined() }) test('/test/about (custom layout)', async () => {