From f40b3e249294123a9954dad74d23a70a6b279497 Mon Sep 17 00:00:00 2001 From: Daniel Roe Date: Mon, 3 Apr 2023 14:18:24 +0100 Subject: [PATCH] feat(nuxt): support `~`/`~~`/`@`/`@@` aliases within layers (#19986) --- packages/nuxt/src/core/nuxt.ts | 20 +++++- .../nuxt/src/core/plugins/layer-aliasing.ts | 68 +++++++++++++++++++ packages/schema/src/config/experimental.ts | 3 + test/basic.test.ts | 4 ++ .../extends/node_modules/foo/alias/test.ts | 1 + .../extends/node_modules/foo/pages/foo.vue | 2 + 6 files changed, 96 insertions(+), 2 deletions(-) create mode 100644 packages/nuxt/src/core/plugins/layer-aliasing.ts create mode 100644 test/fixtures/basic/extends/node_modules/foo/alias/test.ts diff --git a/packages/nuxt/src/core/nuxt.ts b/packages/nuxt/src/core/nuxt.ts index 19db1559cd9..f820fd1f1a8 100644 --- a/packages/nuxt/src/core/nuxt.ts +++ b/packages/nuxt/src/core/nuxt.ts @@ -20,6 +20,7 @@ import { UnctxTransformPlugin } from './plugins/unctx' import type { TreeShakeComposablesPluginOptions } from './plugins/tree-shake' import { TreeShakeComposablesPlugin } from './plugins/tree-shake' import { DevOnlyPlugin } from './plugins/dev-only' +import { LayerAliasingPlugin } from './plugins/layer-aliasing' import { addModuleTranspiles } from './modules' import { initNitro } from './nitro' import schemaModule from './schema' @@ -80,13 +81,28 @@ async function initNuxt (nuxt: Nuxt) { addVitePlugin(ImportProtectionPlugin.vite(config)) addWebpackPlugin(ImportProtectionPlugin.webpack(config)) + if (nuxt.options.experimental.localLayerAliases) { + // Add layer aliasing support for ~, ~~, @ and @@ aliases + addVitePlugin(LayerAliasingPlugin.vite({ + sourcemap: nuxt.options.sourcemap.server || nuxt.options.sourcemap.client, + // skip top-level layer (user's project) as the aliases will already be correctly resolved + layers: nuxt.options._layers.slice(1) + })) + addWebpackPlugin(LayerAliasingPlugin.webpack({ + sourcemap: nuxt.options.sourcemap.server || nuxt.options.sourcemap.client, + // skip top-level layer (user's project) as the aliases will already be correctly resolved + layers: nuxt.options._layers.slice(1), + transform: true + })) + } + nuxt.hook('modules:done', () => { // Add unctx transform addVitePlugin(UnctxTransformPlugin(nuxt).vite({ sourcemap: nuxt.options.sourcemap.server || nuxt.options.sourcemap.client })) addWebpackPlugin(UnctxTransformPlugin(nuxt).webpack({ sourcemap: nuxt.options.sourcemap.server || nuxt.options.sourcemap.client })) // Add composable tree-shaking optimisations - const serverTreeShakeOptions : TreeShakeComposablesPluginOptions = { + const serverTreeShakeOptions: TreeShakeComposablesPluginOptions = { sourcemap: nuxt.options.sourcemap.server, composables: nuxt.options.optimization.treeShake.composables.server } @@ -94,7 +110,7 @@ async function initNuxt (nuxt: Nuxt) { addVitePlugin(TreeShakeComposablesPlugin.vite(serverTreeShakeOptions), { client: false }) addWebpackPlugin(TreeShakeComposablesPlugin.webpack(serverTreeShakeOptions), { client: false }) } - const clientTreeShakeOptions : TreeShakeComposablesPluginOptions = { + const clientTreeShakeOptions: TreeShakeComposablesPluginOptions = { sourcemap: nuxt.options.sourcemap.client, composables: nuxt.options.optimization.treeShake.composables.client } diff --git a/packages/nuxt/src/core/plugins/layer-aliasing.ts b/packages/nuxt/src/core/plugins/layer-aliasing.ts new file mode 100644 index 00000000000..3d72d3a6bff --- /dev/null +++ b/packages/nuxt/src/core/plugins/layer-aliasing.ts @@ -0,0 +1,68 @@ +import { createUnplugin } from 'unplugin' +import type { NuxtConfigLayer } from 'nuxt/schema' +import { resolveAlias } from '@nuxt/kit' +import { normalize } from 'pathe' +import MagicString from 'magic-string' + +interface LayerAliasingOptions { + sourcemap?: boolean + transform?: boolean + layers: NuxtConfigLayer[] +} + +const ALIAS_RE = /(?<=['"])[~@]{1,2}(?=\/)/g + +export const LayerAliasingPlugin = createUnplugin((options: LayerAliasingOptions) => { + const aliases = Object.fromEntries(options.layers.map(l => [l.config.srcDir || l.cwd, { + '~': l.config?.alias?.['~'] || l.config.srcDir || l.cwd, + '@': l.config?.alias?.['@'] || l.config.srcDir || l.cwd, + '~~': l.config?.alias?.['~~'] || l.config.rootDir || l.cwd, + '@@': l.config?.alias?.['@@'] || l.config.rootDir || l.cwd + }])) + const layers = Object.keys(aliases).sort((a, b) => b.length - a.length) + + return { + name: 'nuxt:layer-aliasing', + enforce: 'pre', + vite: { + resolveId: { + order: 'pre', + async handler (id, importer) { + if (!importer) { return } + + const layer = layers.find(l => importer.startsWith(l)) + if (!layer) { return } + + const resolvedId = resolveAlias(id, aliases[layer]) + if (resolvedId !== id) { + return await this.resolve(resolvedId, importer, { skipSelf: true }) + } + } + } + }, + + // webpack-only transform + transformInclude: (id) => { + if (!options.transform) { return false } + const _id = normalize(id) + return layers.some(dir => _id.startsWith(dir)) + }, + transform (code, id) { + if (!options.transform) { return } + + const _id = normalize(id) + const layer = layers.find(l => _id.startsWith(l)) + if (!layer || !code.match(ALIAS_RE)) { return } + + const s = new MagicString(code) + s.replace(ALIAS_RE, r => aliases[layer][r as '~'] || r) + + if (s.hasChanged()) { + return { + code: s.toString(), + map: options.sourcemap ? s.generateMap({ source: id, includeContent: true }) : undefined + } + } + } + } +}) diff --git a/packages/schema/src/config/experimental.ts b/packages/schema/src/config/experimental.ts index 22f5e03c12b..2469dd26801 100644 --- a/packages/schema/src/config/experimental.ts +++ b/packages/schema/src/config/experimental.ts @@ -162,5 +162,8 @@ export default defineUntypedSchema({ /** Allow disabling Nuxt SSR responses by setting the `x-nuxt-no-ssr` header. */ respectNoSSRHeader: false, + + /** Resolve `~`, `~~`, `@` and `@@` aliases located within layers with respect to their layer source and root directories. */ + localLayerAliases: true, } }) diff --git a/test/basic.test.ts b/test/basic.test.ts index 8ddcc37abd2..9e34bb31205 100644 --- a/test/basic.test.ts +++ b/test/basic.test.ts @@ -707,6 +707,10 @@ describe('extends support', () => { }) describe('middlewares', () => { + it('works with layer aliases', async () => { + const html = await $fetch('/foo') + expect(html).toContain('from layer alias') + }) it('extends foo/middleware/foo', async () => { const html = await $fetch('/foo') expect(html).toContain('Middleware | foo: Injected by extended middleware from foo') diff --git a/test/fixtures/basic/extends/node_modules/foo/alias/test.ts b/test/fixtures/basic/extends/node_modules/foo/alias/test.ts new file mode 100644 index 00000000000..ef078323614 --- /dev/null +++ b/test/fixtures/basic/extends/node_modules/foo/alias/test.ts @@ -0,0 +1 @@ +export const test = 'from layer alias' diff --git a/test/fixtures/basic/extends/node_modules/foo/pages/foo.vue b/test/fixtures/basic/extends/node_modules/foo/pages/foo.vue index 39541b4831a..9599ea6eac9 100644 --- a/test/fixtures/basic/extends/node_modules/foo/pages/foo.vue +++ b/test/fixtures/basic/extends/node_modules/foo/pages/foo.vue @@ -1,4 +1,5 @@