├── splash.png ├── .gitignore ├── vitest.config.ts ├── tsconfig.json ├── .eslintrc.json ├── package.json ├── tests ├── helpers.spec.ts └── api.spec.ts ├── src └── index.ts └── README.md /splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davestewart/nuxt-layers-utils/HEAD/splash.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # system 2 | .idea 3 | node_modules/ 4 | *.log 5 | 6 | # project 7 | dist/ 8 | tests/coverage/ 9 | -------------------------------------------------------------------------------- /vitest.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vitest/config' 2 | 3 | export default defineConfig({ 4 | test: { 5 | globals: true, 6 | environment: 'node', 7 | coverage: { 8 | reportsDirectory: './tests/coverage' 9 | } 10 | }, 11 | }) 12 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "module": "ESNext", 5 | "moduleResolution": "node", 6 | "outDir": "./dist", 7 | "declaration": true, 8 | "declarationMap": true, 9 | "esModuleInterop": true, 10 | "forceConsistentCasingInFileNames": true, 11 | "strict": true, 12 | "skipLibCheck": true 13 | }, 14 | "include": ["src/**/*"], 15 | "exclude": [ 16 | "node_modules", 17 | "dist", 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "standard", 4 | "plugin:@typescript-eslint/recommended" 5 | ], 6 | "parser": "@typescript-eslint/parser", 7 | "parserOptions": { 8 | "ecmaVersion": 2020, 9 | "sourceType": "module" 10 | }, 11 | "plugins": [ 12 | "@typescript-eslint" 13 | ], 14 | "rules": { 15 | "no-console": "warn", 16 | "brace-style": ["error", "stroustrup"], 17 | "comma-dangle": ["error", { 18 | "arrays": "always-multiline", 19 | "objects": "always-multiline", 20 | "imports": "always-multiline", 21 | "exports": "always-multiline", 22 | "functions": "never" 23 | }] 24 | }, 25 | "ignorePatterns": [ 26 | "dist" 27 | ] 28 | } 29 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nuxt-layers-utils", 3 | "description": "A collection of utilities to work with Nuxt layers", 4 | "version": "0.3.0", 5 | "author": "Dave Stewart", 6 | "files": [ 7 | "dist/*" 8 | ], 9 | "type": "module", 10 | "main": "dist/index.js", 11 | "types": "dist/index.d.ts", 12 | "scripts": { 13 | "build": "tsup src/index.ts --ignore-watch src/**/*.spec.ts --format esm --dts && tsc --emitDeclarationOnly", 14 | "test": "vitest --reporter=verbose", 15 | "test:coverage": "vitest --coverage", 16 | "lint": "eslint . --ext .ts", 17 | "release": "vitest run && npm run build" 18 | }, 19 | "dependencies": { 20 | "pathe": "^1.1.2" 21 | }, 22 | "peerDependencies": { 23 | "nuxt": "^3.0.0" 24 | }, 25 | "devDependencies": { 26 | "@typescript-eslint/eslint-plugin": "^7.8.0", 27 | "@typescript-eslint/parser": "^7.8.0", 28 | "@vitest/coverage-v8": "^1.6.0", 29 | "eslint": "^8.57.0", 30 | "eslint-config-standard": "^17.1.0", 31 | "tsup": "^8.0.2", 32 | "typescript": "^5.4.5", 33 | "vitest": "^1.6.0" 34 | }, 35 | "repository": { 36 | "type": "git", 37 | "url": "git+https://github.com/davestewart/nuxt-layers-utils.git" 38 | }, 39 | "keywords": [ 40 | "nuxt", 41 | "layers" 42 | ], 43 | "license": "ISC" 44 | } 45 | -------------------------------------------------------------------------------- /tests/helpers.spec.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest' 2 | import { useLayers, logConfig } from '../src' 3 | import { resolve } from 'pathe' 4 | 5 | // helper function for demo 6 | function defineNuxtConfig (config: T): T { 7 | return config 8 | } 9 | 10 | // configure helper 11 | const layers = useLayers(__dirname, { 12 | core: 'core', 13 | auth: 'layers/auth', 14 | account: 'layers/account', 15 | services: 'layers/services', 16 | site: 'layers/site', 17 | blog: 'layers/blog', 18 | }) 19 | 20 | // set up global aliases; note is a mix of layer and non layer code 21 | const alias = { 22 | // layers 23 | ...layers.alias('#'), 24 | 25 | // core 26 | ...layers.only('core').alias('~/', [ 27 | 'components', 28 | 'composables', 29 | 'utils', 30 | ]), 31 | 32 | // site 33 | ...layers.only('site').alias('~/', [ 34 | 'public', 35 | 'pages', 36 | ]), 37 | 38 | // third party 39 | '~/libs': resolve('../../libs'), 40 | } 41 | 42 | describe('helpers', () => { 43 | describe('logConfig', () => { 44 | // remove skip to see result 45 | it.skip('should log the final config', function () { 46 | // build the final config 47 | logConfig(defineNuxtConfig({ 48 | // add all layers 49 | extends: layers.extends(), 50 | 51 | // reconfigure core nuxt folders 52 | dir: { 53 | ...layers.dir('core', [ 54 | 'middleware', 55 | 'modules', 56 | 'plugins', 57 | ]), 58 | ...layers.dir('site', [ 59 | 'assets', 60 | 'layouts', 61 | 'pages', 62 | 'public', 63 | ]), 64 | }, 65 | 66 | content: { 67 | sources: layers.only('site blog').contentSources() 68 | }, 69 | 70 | // add additional layer auto-import folders 71 | imports: { 72 | dirs: [ 73 | ...layers.importsDirs([ 74 | 'config', 75 | 'state', 76 | ]), 77 | ], 78 | }, 79 | 80 | // add layer aliases 81 | alias, 82 | 83 | // configure vite to use the same aliases 84 | vite: { 85 | resolve: { 86 | alias: layers.viteResolveAlias(alias), 87 | }, 88 | }, 89 | })) 90 | }) 91 | }) 92 | }) 93 | -------------------------------------------------------------------------------- /tests/api.spec.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest' 2 | import { useLayers } from '../src' 3 | 4 | // variables 5 | const baseDir = '/projects/project' 6 | const layers = useLayers(baseDir, { 7 | core: 'core', 8 | blog: 'layers/blog', 9 | site: 'layers/site', 10 | }) 11 | 12 | describe('api', () => { 13 | describe('extends', () => { 14 | it('should return all layer folders', () => { 15 | expect(layers.extends()).toEqual([ 16 | 'core', 17 | 'layers/blog', 18 | 'layers/site', 19 | ]) 20 | }) 21 | }) 22 | 23 | describe('dir', () => { 24 | it('should return the directory mapping for specified folders', () => { 25 | const result = layers.dir('blog', ['assets', 'plugins']) 26 | expect(result).toEqual({ 27 | assets: 'layers/blog/assets', 28 | plugins: 'layers/blog/plugins', 29 | }) 30 | }) 31 | }) 32 | 33 | describe('dirPath', () => { 34 | it('should return the directory path for a given folder', () => { 35 | expect(layers.dirPath('site', 'assets')).toBe('layers/site/assets') 36 | }) 37 | }) 38 | 39 | describe('importsDirs', () => { 40 | it('should generate import paths for default directories', () => { 41 | const result = layers.importsDirs() 42 | expect(result).toEqual([ 43 | 'core/components', 44 | 'core/composables', 45 | 'core/utils', 46 | 'layers/blog/components', 47 | 'layers/blog/composables', 48 | 'layers/blog/utils', 49 | 'layers/site/components', 50 | 'layers/site/composables', 51 | 'layers/site/utils', 52 | ]) 53 | }) 54 | }) 55 | 56 | describe('components', () => { 57 | it('should return component paths with defaults', () => { 58 | const result = layers.components() 59 | expect(result).toEqual([ 60 | { path: '~/core/components', global: false, pathPrefix: false }, 61 | { path: '~/layers/blog/components', global: false, pathPrefix: false }, 62 | { path: '~/layers/site/components', global: false, pathPrefix: false }, 63 | ]) 64 | }) 65 | 66 | it('should handle options for component paths', () => { 67 | const result = layers.components(false, 'Custom') 68 | expect(result).toEqual([ 69 | { path: '~/core/components', global: false, prefix: 'Custom', pathPrefix: false }, 70 | { path: '~/layers/blog/components', global: false, prefix: 'Custom', pathPrefix: false }, 71 | { path: '~/layers/site/components', global: false, prefix: 'Custom', pathPrefix: false }, 72 | ]) 73 | }) 74 | }) 75 | 76 | describe('contentSources', () => { 77 | it('should return a content source', () => { 78 | const result = layers.only('site').contentSources() 79 | expect(result).toEqual({ 80 | site: { 81 | base: '/projects/project/layers/site/content', 82 | driver: 'fs', 83 | }, 84 | }) 85 | }) 86 | 87 | it('should automatically prefix later layers', () => { 88 | const result = layers.only('site blog').contentSources() 89 | expect(result).toEqual({ 90 | site: { 91 | base: '/projects/project/layers/site/content', 92 | driver: 'fs', 93 | }, 94 | blog: { 95 | prefix: '/blog', 96 | base: '/projects/project/layers/blog/content', 97 | driver: 'fs', 98 | }, 99 | }) 100 | }) 101 | 102 | it('should skip prefixing if set to false', () => { 103 | const result = layers.only('site blog').contentSources(false) 104 | expect(result).toEqual({ 105 | site: { 106 | base: '/projects/project/layers/site/content', 107 | driver: 'fs', 108 | }, 109 | blog: { 110 | base: '/projects/project/layers/blog/content', 111 | driver: 'fs', 112 | }, 113 | }) 114 | }) 115 | 116 | it('should remap prefixes when object passed', () => { 117 | const result = layers.only('site blog').contentSources({ site: 'the-site', blog: 'the-blog' }) 118 | expect(result).toEqual({ 119 | site: { 120 | base: '/projects/project/layers/site/content', 121 | prefix: '/the-site', 122 | driver: 'fs', 123 | }, 124 | blog: { 125 | base: '/projects/project/layers/blog/content', 126 | prefix: '/the-blog', 127 | driver: 'fs', 128 | }, 129 | }) 130 | }) 131 | }) 132 | 133 | describe('alias', () => { 134 | it('should return aliases for layers when no options are passed', () => { 135 | const result = layers.alias('#') 136 | expect(result).toEqual({ 137 | '#core': '/projects/project/core', 138 | '#blog': '/projects/project/layers/blog', 139 | '#site': '/projects/project/layers/site', 140 | }) 141 | }) 142 | 143 | it('should return aliases for user-defined layers when folders is true', () => { 144 | const result = layers.alias('#', true) 145 | expect(result).toEqual({ 146 | '#components': '/projects/project/core/components', 147 | '#composables': '/projects/project/core/composables', 148 | '#utils': '/projects/project/core/utils', 149 | }) 150 | }) 151 | 152 | it('should return aliases for provided folders', () => { 153 | const result = layers.alias('#', ['utils']) 154 | expect(result).toEqual({ 155 | '#utils': '/projects/project/core/utils', 156 | }) 157 | }) 158 | }) 159 | 160 | describe('viteResolveAlias', () => { 161 | it('should convert aliases to Vite resolve format', () => { 162 | const aliases = { '#core': '/projects/project/core' } 163 | const result = layers.viteResolveAlias(aliases) 164 | expect(result).toEqual([{ find: '#core', replacement: '/projects/project/core' }]) 165 | }) 166 | }) 167 | }) 168 | 169 | describe('utilities', () => { 170 | describe('only', () => { 171 | it('should filter specified layers when passing a string', () => { 172 | const newLayers = layers.only('core') 173 | expect(newLayers.extends()).toEqual([ 174 | 'core', 175 | ]) 176 | }) 177 | 178 | it('should filter specified layers when passing an array', () => { 179 | const newLayers = layers.only(['core', 'blog']) 180 | expect(newLayers.extends()).toEqual([ 181 | 'core', 182 | 'layers/blog', 183 | ]) 184 | }) 185 | 186 | it('should order specified layers by the order of keys passed', () => { 187 | const newLayers = layers.only('site blog') 188 | expect(newLayers.extends()).toStrictEqual([ 189 | 'layers/site', 190 | 'layers/blog', 191 | ]) 192 | }) 193 | }) 194 | 195 | describe('rel', () => { 196 | it('should return the correct relative path for a layer', () => { 197 | expect(layers.rel('core')).toBe('core') 198 | }) 199 | 200 | it('should return the correct relative path for a layer and folder', () => { 201 | expect(layers.rel('blog', 'assets')).toBe('layers/blog/assets') 202 | }) 203 | 204 | it('throws an error for invalid layer keys', () => { 205 | expect(() => layers.rel('invalid')).toThrow('Invalid layer "invalid"') 206 | }) 207 | }) 208 | 209 | describe('abs', () => { 210 | it('should return the correct absolute path for a layer', () => { 211 | expect(layers.abs('core')).toBe('/projects/project/core') 212 | }) 213 | 214 | it('should return the correct absolute path for a layer and folder', () => { 215 | expect(layers.abs('blog', 'assets')).toBe('/projects/project/layers/blog/assets') 216 | }) 217 | 218 | it('throws an error for invalid layer keys', () => { 219 | expect(() => layers.abs('invalid')).toThrow('Invalid layer "invalid"') 220 | }) 221 | }) 222 | 223 | describe('obj', () => { 224 | it('should return a hash of config options', () => { 225 | expect(layers.only('core site').obj((key, rel, abs, index) => { 226 | return [key, rel, abs, index].join('|') 227 | })).toStrictEqual({ 228 | core: 'core|core|/projects/project/core|0', 229 | site: 'site|layers/site|/projects/project/layers/site|1', 230 | }) 231 | }) 232 | }) 233 | 234 | describe('arr', () => { 235 | it('should return an array of config options', () => { 236 | expect(layers.only('core site').arr((key, rel, abs, index) => { 237 | return [key, rel, abs, index].join('|') 238 | })).toStrictEqual([ 239 | 'core|core|/projects/project/core|0', 240 | 'site|layers/site|/projects/project/layers/site|1', 241 | ]) 242 | }) 243 | }) 244 | }) 245 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { inspect } from 'node:util' 2 | import { join } from 'pathe' 3 | 4 | export type Layers = Record 5 | 6 | export type NuxtDir = 7 | | 'assets' 8 | | 'layouts' 9 | | 'middleware' 10 | | 'modules' 11 | | 'pages' 12 | | 'plugins' 13 | | 'public' 14 | 15 | const AUTO_IMPORTS = ['components', 'composables', 'utils'] 16 | 17 | /** 18 | * Helper function to debug generated config 19 | */ 20 | export function logConfig (config: T): T { 21 | /* v8 ignore next 4 */ 22 | // eslint-disable-next-line 23 | console.log(inspect(config, { depth: 20, colors: true })) 24 | return config 25 | } 26 | 27 | /** 28 | * Helper function to split string of layer keys to array of layer keys 29 | */ 30 | function getKeys (filter: string | string[]) { 31 | return Array.isArray(filter) 32 | ? filter 33 | : filter 34 | .trim() 35 | .split(/\s+/) 36 | } 37 | 38 | /** 39 | * Nuxt layers helper 40 | * 41 | * Factory function to provide a standardised interface to generate layer and path-specific config 42 | * Methods are named after the config they provide. 43 | * 44 | * @param baseDir The absolute path to the base `nuxt.config.ts` file 45 | * @param layers A hash of layer keys and config-relative relative folder paths 46 | */ 47 | export function useLayers (baseDir = __dirname, layers: Layers) { 48 | /** 49 | * Test layer key is valid 50 | * 51 | * @param key A valid layer key, i.e. 'core' 52 | */ 53 | const assertLayerKey = (key: string) => { 54 | if (!Object.keys(layers).includes(key)) { 55 | throw new Error(`Invalid layer "${key}"`) 56 | } 57 | } 58 | 59 | return { 60 | /** 61 | * Get the original layers config 62 | * @returns A hash of key:relative path pairs 63 | */ 64 | get layers () { 65 | return layers 66 | }, 67 | 68 | /** 69 | * Generate `config.extends` layer folders array 70 | * 71 | * @see https://nuxt.com/docs/api/nuxt-config#extends 72 | * 73 | * @returns An array of relative paths 74 | */ 75 | extends () { 76 | return Object.values(layers) 77 | }, 78 | 79 | /** 80 | * Generate partial `config.dir` relative folders hash 81 | * 82 | * @see https://nuxt.com/docs/api/nuxt-config#dir 83 | * 84 | * @param key A valid layer key, i.e. 'core' 85 | * @param folders An array of valid config.dir folder keys 86 | * @returns A hash of key:relative path pairs 87 | */ 88 | dir (key: keyof typeof layers, folders: NuxtDir[]) { 89 | assertLayerKey(key) 90 | return folders.reduce((output, folder) => { 91 | output[folder] = this.rel(key, folder) 92 | return output 93 | }, {} as Record) 94 | }, 95 | 96 | /** 97 | * Generate single `config.dir` relative path 98 | * 99 | * @see https://nuxt.com/docs/api/nuxt-config#dir 100 | * 101 | * @param key A valid layer key, i.e. 'core' 102 | * @param folder A valid config.dir folder, i.e. 'assets' 103 | * @returns A relative path 104 | */ 105 | dirPath (key: keyof typeof layers, folder?: NuxtDir) { 106 | assertLayerKey(key) 107 | return this.rel(key, folder) 108 | }, 109 | 110 | /** 111 | * Generate `config.imports.dir` config 112 | * 113 | * @see https://nuxt.com/docs/api/nuxt-config#imports 114 | * 115 | * @param folders An optional list of folders 116 | * @returns An array of relative paths 117 | */ 118 | importsDirs (folders = AUTO_IMPORTS) { 119 | return this.arr((key, rel) => { 120 | return folders.map((folder: string) => { 121 | return join(rel, folder) 122 | }) 123 | }).flat() 124 | }, 125 | 126 | /** 127 | * Generate `config.components` array 128 | * 129 | * Note that Nuxt registers layer component folders by default, using the following effective config: 130 | * 131 | * { pathPrefix: true, global: false, prefix: '' } 132 | * 133 | * So, only use this helper when you want to change the defaults of one or more layers. 134 | * 135 | * @see https://nuxt.com/docs/guide/directory-structure/components#custom-directories 136 | * @see https://nuxt.com/docs/api/nuxt-config#components 137 | * 138 | * @param global Optional Boolean to register the components globally, defaults to false 139 | * @param prefix Optional String to prefix component names, defaults to "" 140 | * @param pathPrefix Optional Boolean to prefix component names with the full path, defaults to false 141 | * @returns An array of component config options 142 | */ 143 | components (global = false, prefix?: string, pathPrefix = false) { 144 | return this.arr((key, rel) => { 145 | const path = `~/${rel}/components` 146 | return { path, global, prefix, pathPrefix } 147 | }) 148 | }, 149 | 150 | /** 151 | * Generate `content.sources` hash 152 | * 153 | * @see https://content.nuxt.com/get-started/configuration#sources 154 | * 155 | * Tip: combine with layers.only() to target only layers with content folders 156 | * 157 | * @param prefix An optional prefixing option; defaults to 'auto' 158 | * - 'auto' to prefix all but the first layer 159 | * - an object to map layer keys to prefixes 160 | * - true to prefix with the layer key, i.e. '/blog' 161 | * - false for no prefix 162 | * @returns A hash of key:source pairs 163 | */ 164 | contentSources (prefix: 'auto' | Record | boolean = 'auto') { 165 | return this.obj((key, rel, abs, index) => { 166 | const prefixed = (prefix === 'auto' && index === 0) || prefix === false 167 | ? {} 168 | : prefix && typeof prefix === 'object' 169 | ? { prefix: `/${prefix[key]}` } 170 | : { prefix: `/${key}` } 171 | return { 172 | ...prefixed, 173 | base: join(abs, 'content'), 174 | driver: 'fs', 175 | } 176 | }) 177 | }, 178 | 179 | /** 180 | * Generate `config.alias` hash 181 | * 182 | * @see https://nuxt.com/docs/api/nuxt-config#alias 183 | * 184 | * @param prefix A required alias prefix 185 | * @param folders An optional 186 | * @returns A hash of key:absolute path pairs 187 | */ 188 | alias (prefix: string = '#', folders?: string[] | true): Layers { 189 | const output: Record = {} 190 | if (folders) { 191 | const key = Object.keys(layers).shift() 192 | if (key) { 193 | for (const folder of Array.isArray(folders) ? folders : AUTO_IMPORTS) { 194 | output[prefix + folder] = this.abs(key, folder) 195 | } 196 | } 197 | } 198 | else { 199 | for (const key of Object.keys(layers)) { 200 | output[prefix + key] = this.abs(key) 201 | } 202 | } 203 | return output 204 | }, 205 | 206 | /** 207 | * Generate `config.vite.resolve.alias` aliases 208 | * 209 | * This seems to be required when adding additional layer aliases 210 | * 211 | * @see https://nuxt.com/docs/api/nuxt-config#resolve 212 | * 213 | * @param aliases The same alias hash used in `config.alias` 214 | * @returns An array of find:replace pairs 215 | */ 216 | viteResolveAlias (aliases: Record) { 217 | return Object 218 | // note the object format does not seem to work, so we use the array format 219 | // https://github.com/rollup/plugins/tree/master/packages/alias#entries 220 | .entries(aliases) 221 | .map(([find, replacement]) => { 222 | return { find, replacement } 223 | }) 224 | }, 225 | 226 | /** 227 | * Choose only certain layers to get config for 228 | * 229 | * @param filter A space-delimited string of layer keys, or an array of layer keys 230 | * @returns A new useLayers() instance with only the specified layers 231 | */ 232 | only (filter: string | Array) { 233 | const keys = getKeys(filter) 234 | keys.forEach(assertLayerKey) 235 | const filtered = keys 236 | .reduce((output, key) => { 237 | output[key] = layers[key] 238 | return output 239 | }, {} as Record) 240 | return useLayers(baseDir, filtered) 241 | }, 242 | 243 | /** 244 | * Get the relative path for a layer, or one of its folders 245 | * 246 | * @see https://nuxt.com/docs/api/nuxt-config#dir 247 | * 248 | * @param key A valid layer key, i.e. 'core' 249 | * @param folder A valid config.dir folder, i.e. 'assets' 250 | * @returns A relative path 251 | */ 252 | rel (key: keyof typeof layers, folder = '') { 253 | assertLayerKey(key) 254 | return join(layers[key], folder) 255 | }, 256 | 257 | /** 258 | * Get the absolute path for a layer, or one of its folders 259 | * 260 | * @param key A valid layer key, i.e. 'core' 261 | * @param folder A valid `config.dir` folder, i.e. 'assets' 262 | * @returns An absolute path 263 | */ 264 | abs (key: keyof typeof layers, folder = '') { 265 | assertLayerKey(key) 266 | return join(baseDir, layers[key], folder) 267 | }, 268 | 269 | /** 270 | * Utility function to return a hash of config options from a user-defined callback 271 | * 272 | * @param callback Callback function passing key, rel, abs and index values 273 | * @returns A hash of key:user-defined options 274 | */ 275 | obj (callback: (key: keyof typeof layers, rel: string, abs: string, index: number) => T): Record { 276 | const output: Record = {} 277 | let index = 0 278 | for (const key of Object.keys(layers)) { 279 | output[key] = callback(key, layers[key], this.abs(key), index++) 280 | } 281 | return output 282 | }, 283 | 284 | /** 285 | * Utility function to return an array of config options from a user-defined callback 286 | * 287 | * @param callback Callback function passing key, rel, abs and index values 288 | * @returns An array of user-defined options 289 | */ 290 | arr (callback: (key: keyof typeof layers, rel: string, abs: string, index: number) => T): T[] { 291 | return Object.entries(layers).map(([key, rel], index) => { 292 | return callback(key, rel, this.abs(key), index) 293 | }) 294 | }, 295 | } 296 | } 297 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Nuxt Layers Utils 4 | 5 | > A collection of utilities to work with Nuxt layers 6 | 7 |

8 | Nuxt Layers Utils logo 9 |

10 | 11 | ## Abstract 12 | 13 | [Nuxt Layers](https://nuxt.com/docs/guide/going-further/layers) are great to modularise your applications, but they can be fiddly, verbose and repetitive to configure when you have many layers, reconfigured folders, aliases, nested component folders, auto-imports, etc. 14 | 15 | Nuxt Layers Utils provides a [common interface](#api) to generate these disparate configuration options – hopefully saving you from shouting at your laptop because you misconfigured some path setting. 16 | 17 | ## Usage 18 | 19 | ### Installation 20 | 21 | Install from NPM: 22 | 23 | ```bash 24 | npm i --save-dev nuxt-layers-utils 25 | ``` 26 | 27 | ### Configuration 28 | 29 | > [!IMPORTANT] 30 | > Read this before designing your layers 31 | 32 | Nuxt's [path-oriented configuration settings](#nuxt-config) take a variety of formats: [absolute](https://nuxt.com/docs/api/nuxt-config#alias), [relative](https://nuxt.com/docs/api/nuxt-config#dir), [folder names](https://nuxt.com/docs/api/nuxt-config#extends), [root-relative](https://nuxt.com/docs/api/nuxt-config#srcdir), [layer-relative](https://nuxt.com/docs/api/nuxt-config#extends), [aliased](https://nuxt.com/docs/api/nuxt-config#components), etc, etc, and it's not always clear which should be used when. Additionally, not all settings [work as expected](https://nuxt.com/docs/api/nuxt-config#imports) when configured in [layer config](https://nuxt.com/docs/guide/going-further/layers) files. 33 | 34 | And because it's tricky to chase down path-related config across multiple folders, I **strongly recommend** to configure everything *path-related* in your project's **main** `nuxt.config.ts`. 35 | 36 | See my article on Nuxt Layers for more information: 37 | 38 | - [davestewart.co.uk/blog/nuxt-layers](https://davestewart.co.uk/blog/nuxt-layers) 39 | 40 | ### Overview 41 | 42 | The package provides a `useLayers()` factory which: 43 | 44 | - takes an absolute `baseDir` path 45 | - takes a `layers` config object 46 | - provides a set of named config methods, i.e. `alias()`, `dir()` 47 | - provides additional utility and helper functions, i.e. `only()`, `rel()`, `obj()` 48 | 49 | You create a `layers` helper, then sprinkle generated config into your main `nuxt.config.ts` as required. 50 | 51 | ### Simple example 52 | 53 | A simple example with: 54 | 55 | - three layers 56 | - path aliases 57 | 58 | ```ts 59 | import { useLayers } from 'nuxt-layers-utils' 60 | 61 | const layers = useLayers(__dirname, { 62 | core: 'core', 63 | blog: 'layers/blog', 64 | site: 'layers/site', 65 | }) 66 | 67 | export default defineNuxtConfig({ 68 | extends: layers.extends(), 69 | alias: layers.alias('#'), 70 | }) 71 | ``` 72 | 73 | That generates: 74 | 75 | ```js 76 | export default { 77 | extends: [ 78 | 'core', 79 | 'layers/blog', 80 | 'layers/site' 81 | ], 82 | alias: { 83 | '#core': '/Volumes/Projects/some-project/core', 84 | '#blog': '/Volumes/Projects/some-project/layers/blog', 85 | '#site': '/Volumes/Projects/some-project/layers/site', 86 | }, 87 | } 88 | ``` 89 | 90 | ### Complex example 91 | 92 | A complex example with: 93 | 94 | - multiple layers 95 | - layer aliases 96 | - additional custom aliases 97 | - reconfigured default folders 98 | - additional auto-imports 99 | - vite aliases 100 | 101 | ```ts 102 | import { useLayers } from 'nuxt-layers-utils' 103 | import { defineNuxtConfig } from 'nuxt/config' 104 | import { resolve } from 'pathe' 105 | 106 | // configure helper 107 | const layers = useLayers(__dirname, { 108 | core: 'core', 109 | auth: 'layers/auth', 110 | account: 'layers/account', 111 | services: 'layers/services', 112 | site: 'layers/site', 113 | }) 114 | 115 | // set up global aliases; note is a mix of layer and non layer code 116 | const alias = { 117 | // layers 118 | ...layers.alias('#'), 119 | 120 | // core 121 | ...layers.only('core').alias('~/', [ 122 | 'components', 123 | 'composables', 124 | 'utils', 125 | ]), 126 | 127 | // site 128 | ...layers.only('site').alias('~/', [ 129 | 'public', 130 | 'pages', 131 | ]), 132 | 133 | // third party 134 | '~/libs': resolve('../../libs'), 135 | } 136 | 137 | // build the final config 138 | export default defineNuxtConfig({ 139 | // add all layers 140 | extends: layers.extends(), 141 | 142 | // reconfigure core nuxt folders 143 | dir: { 144 | ...layers.dir('core', [ 145 | 'middleware', 146 | 'modules', 147 | 'plugins', 148 | ]), 149 | ...layers.dir('site', [ 150 | 'assets', 151 | 'layouts', 152 | 'pages', 153 | 'public', 154 | ]), 155 | }, 156 | 157 | // add additional layer auto-import folders 158 | imports: { 159 | dirs: [ 160 | ...layers.importsDirs([ 161 | 'config', 162 | 'state', 163 | ]), 164 | ], 165 | }, 166 | 167 | // add layer aliases 168 | alias, 169 | 170 | // configure vite to use the same aliases 171 | vite: { 172 | resolve: { 173 | alias: layers.viteResolveAlias(alias), 174 | }, 175 | }, 176 | }) 177 | ``` 178 | 179 | A manual setup might look more like: 180 | 181 | ```js 182 | export default { 183 | // layers 184 | extends: [ 185 | 'core', 186 | 'layers/auth', 187 | 'layers/account', 188 | 'layers/services', 189 | 'layers/site' 190 | ], 191 | 192 | // aliases, layers, core and third party 193 | alias: { 194 | // layer-level aliases (prefixed with a `#`) 195 | '#core': '/Volumes/Projects/some-project/core', 196 | '#auth': '/Volumes/Projects/some-project/layers/auth', 197 | '#account': '/Volumes/Projects/some-project/layers/account', 198 | '#tools': '/Volumes/Projects/some-project/layers/services', 199 | '#site': '/Volumes/Projects/some-project/layers/site', 200 | 201 | // core 202 | '~/components': '/Volumes/Projects/some-project/core/components', 203 | '~/composables': '/Volumes/Projects/some-project/core/composables', 204 | '~/utils': '/Volumes/Projects/some-project/core/utils', 205 | '~/public': '/Volumes/Projects/some-project/layers/site/public', 206 | '~/pages': '/Volumes/Projects/some-project/layers/site/pages', 207 | 208 | // third-party 209 | '~/libs': '/Volumes/Projects/libs', 210 | '#basicscroll': 'basicscroll' 211 | }, 212 | 213 | // auto-imports 214 | imports: { 215 | dirs: [ 216 | 'core/config', 217 | 'core/state', 218 | 'layers/auth/config', 219 | 'layers/auth/state', 220 | 'layers/account/config', 221 | 'layers/account/state', 222 | 'layers/services/config', 223 | 'layers/services/state', 224 | 'layers/site/config', 225 | 'layers/site/state' 226 | ] 227 | }, 228 | 229 | // default folders 230 | dir: { 231 | // core 232 | middleware: 'core/middleware', 233 | modules: 'core/modules', 234 | plugins: 'core/plugins', 235 | 236 | // site 237 | assets: 'layers/site/assets', 238 | layouts: 'layers/site/layouts', 239 | pages: 'layers/site/pages', 240 | public: 'layers/site/public' 241 | }, 242 | 243 | // aliases for vite (transformed version of core aliases) 244 | vite: { 245 | resolve: { 246 | alias: [ 247 | { find: '...', replace: '...' }, 248 | ... 249 | ] 250 | } 251 | } 252 | } 253 | ``` 254 | 255 | ## API 256 | 257 | Factory function: 258 | 259 | - [useLayers()](#uselayers) 260 | 261 | Config methods: 262 | 263 | > Named after the config they provide, and listed in most-useful order 264 | 265 | - [layers.extends()](#extends) 266 | - [layers.alias()](#alias) 267 | - [layers.dir()](#dir) 268 | - [layers.dirPath()](#dirpath) 269 | - [layers.importsDirs()](#importsdirs) 270 | - [layers.components()](#components) 271 | - [layers.contentSources()](#contentsources) 272 | - [layers.viteResolveAlias()](#viteresolvealias) 273 | 274 | Utilities: 275 | 276 | - [layers.only()](#only) 277 | - [layers.rel()](#rel) 278 | - [layers.abs()](#abs) 279 | - [layers.obj()](#obj) 280 | - [layers.arr()](#arr) 281 | - [layers.layers](#layers) 282 | 283 | Helpers: 284 | 285 | - [logConfig()](#logconfig) 286 | 287 | See the `tests` folder for working code examples. 288 | 289 | ### `useLayers()` 290 | 291 | Factory function to provide a standardised interface to generate layer and path-specific config. 292 | 293 | **Params:** 294 | 295 | ``` 296 | @param baseDir The absolute path to the project `nuxt.config.ts` 297 | @param layers A hash of layer keys and relative folder paths 298 | ``` 299 | 300 | **Example:** 301 | 302 | ```ts 303 | const layers = useLayers(__dirname, { 304 | core: 'core', 305 | blog: 'layers/blog', 306 | site: 'layers/site', 307 | ... 308 | }) 309 | 310 | export default defineNuxtConfig({ 311 | extends: layers.extends(), 312 | ... 313 | }) 314 | ``` 315 | 316 | ### `extends()` 317 | 318 | > Used with [`config.extends`](https://nuxt.com/docs/api/nuxt-config#extends) 319 | 320 | Generates the array of relative folder paths which Nuxt should treat as layers 321 | 322 | **Example:** 323 | 324 | ```ts 325 | { 326 | extends: layers.extends() 327 | } 328 | ``` 329 | 330 | **Result:** 331 | 332 | ```js 333 | { 334 | extends: [ 335 | 'core', 336 | 'layers/blog', 337 | 'layers/site' 338 | ] 339 | } 340 | ``` 341 | 342 | ### `alias()` 343 | 344 | > Used with [`config.alias`](https://nuxt.com/docs/api/nuxt-config#alias) 345 | 346 | Generates path aliases for both named layers and arbitrary folders. 347 | 348 | **Params:** 349 | 350 | ``` 351 | @param prefix The required alias prefix 352 | @param folders An optional set of folder paths, defaults to layers config 353 | ``` 354 | 355 | **Example 1;** generate default layer aliases: 356 | 357 | ```ts 358 | { 359 | alias: layers.alias('#') 360 | } 361 | ``` 362 | 363 | **Result:** 364 | 365 | ```js 366 | { 367 | alias: { 368 | '#core': '/Volumes/Projects/some-project/core', 369 | '#blog': '/Volumes/Projects/some-project/layers/blog', 370 | '#site': '/Volumes/Projects/some-project/layers/site', 371 | } 372 | } 373 | ``` 374 | 375 | **Example 2;** generate custom aliases: 376 | 377 | ```ts 378 | { 379 | alias: layers.alias('~/', [ 380 | 'foo/components', 381 | ]) 382 | } 383 | ``` 384 | 385 | **Result:** 386 | 387 | ```js 388 | { 389 | alias: { 390 | '~/foo/components': '/Volumes/Projects/some-project/foo/components', 391 | } 392 | } 393 | ``` 394 | 395 | ### `dir()` 396 | 397 | > Used with [`config.dir`](https://nuxt.com/docs/api/nuxt-config#dir) 398 | 399 | Reconfigures Nuxt's core default folders, such as `assets`, `modules`, `server`, etc. 400 | 401 | **Params:** 402 | 403 | ``` 404 | @param key A valid layer key, i.e. 'core' 405 | @param folders An array of valid config.dir folder keys 406 | ``` 407 | 408 | **Example:** 409 | 410 | ```ts 411 | { 412 | dir: layers.dir('core', ['assets', 'modules']) 413 | } 414 | ``` 415 | 416 | **Result:** 417 | 418 | ```js 419 | { 420 | dir: { 421 | assets: 'core/assets', 422 | modules: 'core/modules' 423 | } 424 | } 425 | ``` 426 | 427 | ### `dirPath()` 428 | 429 | > Used with [`config.dir[folder]`](https://nuxt.com/docs/api/nuxt-config#dir) 430 | 431 | Generate a single relative path from a named layer. 432 | 433 | **Params:** 434 | 435 | ``` 436 | @param key A valid layer key, i.e. 'core' 437 | @param folder A valid config.dir folder, i.e. 'assets' 438 | ``` 439 | 440 | **Example:** 441 | 442 | ```ts 443 | { 444 | dir: { 445 | assets: layers.dirPath('site', 'assets') 446 | } 447 | } 448 | ``` 449 | 450 | **Result:** 451 | 452 | ```js 453 | { 454 | dir: { 455 | assets: 'layers/site/assets' 456 | } 457 | } 458 | ``` 459 | 460 | ### `importsDirs()` 461 | 462 | > Used with [`config.imports.dirs`](https://nuxt.com/docs/api/nuxt-config#imports) 463 | 464 | Determines which folders should be auto-imported by Nuxt. 465 | 466 | **Params:** 467 | 468 | ``` 469 | @param folders An optional list of folders, defaults to AUTO_IMPORTS 470 | ``` 471 | 472 | **Example:** 473 | 474 | ```ts 475 | { 476 | imports: { 477 | dirs: layers.importsDirs([ 478 | 'store' 479 | ]) 480 | } 481 | } 482 | ``` 483 | 484 | **Result:** 485 | 486 | ```js 487 | { 488 | imports: { 489 | dirs: [ 490 | 'core/store', 491 | 'layers/blog/store', 492 | 'layers/site/store', 493 | ] 494 | } 495 | } 496 | ``` 497 | 498 | ### `components()` 499 | 500 | Used with [`config.components`](https://nuxt.com/docs/api/nuxt-config#components) 501 | 502 | Override default [component naming](https://nuxt.com/docs/guide/directory-structure/components#custom-directories) and registration, to prevent path-prefixing for components. 503 | 504 | Note that Nuxt registers layers' component folders by default, using the default of path-prefixing sub-folder components, with the following effective config: 505 | 506 | ```js 507 | { pathPrefix: true, global: false, prefix: '' } 508 | ``` 509 | 510 | So, you should only need to use this helper to change those defaults of one or more layers. 511 | 512 | **Params:** 513 | 514 | ``` 515 | @param global Optional Boolean to register the components globally, defaults to false 516 | @param prefix Optional String to prefix component names, defaults to "" 517 | @param pathPrefix Optional Boolean to prefix component names with the full path, defaults to false 518 | ``` 519 | 520 | **Example:** 521 | 522 | ```ts 523 | { 524 | components: layers.components() 525 | } 526 | ``` 527 | 528 | **Result:** 529 | 530 | ```js 531 | { 532 | components: [ 533 | { path: '~/core/components', pathPrefix: false }, 534 | { path: '~/layers/blog/components', pathPrefix: false }, 535 | { path: '~/layers/site/components', pathPrefix: false }, 536 | ] 537 | } 538 | ``` 539 | 540 | 541 | ### `contentSources()` 542 | 543 | Used with [`content.sources`](https://content.nuxt.com/get-started/configuration#sources) 544 | 545 | Generates Nuxt Content sources. 546 | 547 | Tips: 548 | 549 | - combine with `layers.only()` to target only layers with content folders 550 | - if you need something more complex, consider [`obj()`](#obj) or [`abs()`](#abs) 551 | 552 | **Params:** 553 | 554 | ``` 555 | @param prefix An optional prefixing option; defaults to 'auto' 556 | - 'auto' to prefix all but the first layer 557 | - an object to map layer keys to prefixes 558 | - true to prefix with the layer key, i.e. '/blog' 559 | - false for no prefix 560 | ``` 561 | 562 | **Example:** 563 | 564 | ```ts 565 | { 566 | content: { 567 | sources: layers.only('site blog').contentSources() 568 | } 569 | } 570 | ``` 571 | 572 | **Result:** 573 | 574 | ```js 575 | { 576 | content: { 577 | sources: { 578 | site: { 579 | // note – no prefix for the first picked layer from `only()` 580 | base: '/Volumes/Projects/some-project/layers/site/content', 581 | driver: 'fs' 582 | }, 583 | blog: { 584 | prefix: '/blog', 585 | base: '/Volumes/Projects/some-project/layers/blog/content', 586 | driver: 'fs' 587 | } 588 | } 589 | } 590 | } 591 | ``` 592 | 593 | ### `viteResolveAlias()` 594 | 595 | > Used with [`config.vite.resolve.alias`](https://vitejs.dev/config/shared-options.html#resolve-alias) 596 | 597 | > [!Note] 598 | > This seems to be required when adding additional layer aliases (but do your own checks) 599 | 600 | Generate path aliases for Vite. 601 | 602 | **Params:** 603 | 604 | ``` 605 | @param aliases The same alias hash used in `config.alias` 606 | ``` 607 | 608 | **Example:** 609 | 610 | 611 | ```ts 612 | // pass the same `alias` hash to both configurations 613 | const alias = { ... } 614 | 615 | { 616 | alias: layers.alias(alias) 617 | vite: { 618 | resolve: { 619 | alias: layers.viteResolveAlias(alias) 620 | } 621 | } 622 | } 623 | ``` 624 | 625 | **Result:** 626 | 627 | ```js 628 | { 629 | vite: { 630 | alias: { ... }, 631 | resolve: { 632 | alias: [ 633 | { find: '#core', replace: '/Volumes/Projects/some-project/core' }, 634 | { find: '#blog', replace: '/Volumes/Projects/some-project/layers/blog' }, 635 | { find: '#site', replace: '/Volumes/Projects/some-project/layers/site' }, 636 | ] 637 | } 638 | } 639 | } 640 | ``` 641 | 642 | ## Utils 643 | 644 | ### `only()` 645 | 646 | Choose only certain layers to get config for. 647 | 648 | Note that the hash will be rebuilt in the order of the specified keys. 649 | 650 | **Params:** 651 | 652 | ``` 653 | @param filter A space-delimited string of layer keys, or an array of layer keys 654 | ``` 655 | 656 | **Example:** 657 | 658 | ```ts 659 | const alias = { 660 | ...layers.only('site').alias('~/', [ 661 | 'components', 662 | 'composables', 663 | 'utils', 664 | ]), 665 | } 666 | ``` 667 | 668 | **Result:** 669 | 670 | ```js 671 | { 672 | alias: { 673 | '~/components' : '/Volumes/Projects/some-project/layers/site/components', 674 | '~/composables' : '/Volumes/Projects/some-project/layers/site/composables', 675 | '~/utils' : '/Volumes/Projects/some-project/layers/site/utils', 676 | } 677 | } 678 | ``` 679 | 680 | ### `rel()` 681 | 682 | Generate the relative path to a layer folder or sub-folder. 683 | 684 | ``` 685 | @param key A valid layer key, i.e. 'site' 686 | @param folder An optional folder, i.e. 'assets' 687 | ``` 688 | 689 | **Example:** 690 | 691 | ```ts 692 | layers.rel('site', 'assets') 693 | ``` 694 | 695 | **Result:** 696 | 697 | ```js 698 | 'layers/site/assets' 699 | ``` 700 | 701 | ### `abs()` 702 | 703 | Generate the absolute path to a layer folder or sub-folder. 704 | 705 | ``` 706 | @param key A valid layer key, i.e. 'site' 707 | @param folder An optional folder, i.e. 'assets' 708 | ``` 709 | 710 | **Example:** 711 | 712 | ```ts 713 | layers.abs('site', 'assets') 714 | ``` 715 | 716 | **Result:** 717 | 718 | ```js 719 | '/Volumes/Projects/some-project/layers/site/assets' 720 | ``` 721 | 722 | ### `obj()` 723 | 724 | Utility function to return a hash of config options from a user-defined callback. 725 | 726 | Note that `this` is bound to the `useLayers()` instance. 727 | 728 | ``` 729 | @param callback Callback function passing key, rel, abs and index values 730 | ``` 731 | 732 | **Example:** 733 | 734 | ```ts 735 | { 736 | someConfig: layers.only('site blog').obj((key, rel, abs, index) => { 737 | return { 738 | key, 739 | rel, 740 | abs, 741 | index, 742 | } 743 | }) 744 | } 745 | 746 | ``` 747 | 748 | **Result:** 749 | 750 | ```js 751 | { 752 | site: { 753 | key: 'site', 754 | rel: 'layers/site/assets', 755 | abs: '/Volumes/Projects/some-project/layers/site/assets', 756 | index: 0, 757 | } 758 | blog: { 759 | key: 'blog', 760 | rel: 'layers/blog/assets', 761 | abs: '/Volumes/Projects/some-project/layers/blog/assets', 762 | index: 1, 763 | } 764 | } 765 | ``` 766 | 767 | ### `arr()` 768 | 769 | Utility function to return an array of config options from a user-defined callback 770 | 771 | Note that `this` is bound to the `useLayers()` instance. 772 | 773 | ``` 774 | @param callback Callback function passing key, rel, abs and index values 775 | ``` 776 | 777 | **Example:** 778 | 779 | ```ts 780 | { 781 | someConfig: layers.only('site blog').arr((key, rel, abs, index) => { 782 | return { 783 | key, 784 | rel, 785 | abs, 786 | index, 787 | } 788 | }) 789 | } 790 | ``` 791 | 792 | **Result:** 793 | 794 | ```js 795 | [ 796 | { 797 | key: 'site', 798 | rel: 'layers/site/assets', 799 | abs: '/Volumes/Projects/some-project/layers/site/assets', 800 | index: 0, 801 | } 802 | { 803 | key: 'blog', 804 | rel: 'layers/blog/assets', 805 | abs: '/Volumes/Projects/some-project/layers/blog/assets', 806 | index: 1, 807 | } 808 | ] 809 | ``` 810 | 811 | ### `layers` 812 | 813 | Reference to the original layers config 814 | 815 | **Example:** 816 | 817 | ```ts 818 | useLayers({ blog: 'layers/blog' }).layers 819 | ``` 820 | 821 | **Result:** 822 | 823 | ```js 824 | { blog: 'layers/blog' } 825 | ``` 826 | 827 | 828 | 829 | ## Helpers 830 | 831 | ### `logConfig()` 832 | 833 | Dump the full, expanded and colorized content of your Nuxt config to the console. 834 | 835 | **Params:** 836 | 837 | ``` 838 | @param config A Nuxt configuration object 839 | ``` 840 | 841 | **Example:** 842 | 843 | ```ts 844 | import { logConfig } from 'nuxt-layers-utils' 845 | 846 | export default logConfig(defineNuxtConfig({ 847 | extends: ..., 848 | dir: ..., 849 | imports: ..., 850 | alias: ..., 851 | vite: ..., 852 | })) 853 | ``` 854 | 855 | **Result:** 856 | 857 | ```js 858 | { 859 | extends: [ 860 | 'core', 861 | 'layers/auth', 862 | ... 863 | ], 864 | dir: { 865 | middleware: 'core/middleware', 866 | modules: 'core/modules', 867 | ... 868 | }, 869 | imports: { 870 | dirs: [ 871 | 'core/config', 872 | 'core/state', 873 | '... 874 | ] 875 | }, 876 | alias: { 877 | '#core': '/Volumes/Projects/some-project/core', 878 | '#auth': '/Volumes/Projects/some-project/layers/auth', 879 | ... 880 | }, 881 | vite: { 882 | resolve: { 883 | alias: [ 884 | { 885 | find: '#core', 886 | replacement: '/Volumes/Projects/some-project/core' 887 | }, 888 | { 889 | find: '#auth', 890 | replacement: '/Volumes/Projects/some-project/layers/auth' 891 | }, 892 | ... 893 | ] 894 | } 895 | } 896 | } 897 | ``` 898 | 899 | ## Addendum 900 | 901 | ### Nuxt config 902 | 903 | Nuxt config options which may reference paths or folders. 904 | 905 | **Layer-specific:** 906 | 907 | - [alias](https://nuxt.com/docs/api/nuxt-config#alias) 908 | - [components](https://nuxt.com/docs/api/nuxt-config#components) 909 | - [dir](https://nuxt.com/docs/api/nuxt-config#dir) 910 | - [extends](https://nuxt.com/docs/api/nuxt-config#extends) 911 | - [imports.dirs](https://nuxt.com/docs/api/nuxt-config#imports) 912 | 913 | **Core folders:** 914 | 915 | - [analyseDir](https://nuxt.com/docs/api/nuxt-config#analyzedir) 916 | - [app.buildAssetsDir](https://nuxt.com/docs/api/nuxt-config#buildassetsdir) 917 | - [buildDir](https://nuxt.com/docs/api/nuxt-config#builddir) 918 | - [modulesDir](https://nuxt.com/docs/api/nuxt-config#modulesdir) 919 | - [rootDir](https://nuxt.com/docs/api/nuxt-config#rootdir) 920 | - [serverDir](https://nuxt.com/docs/api/nuxt-config#serverdir) 921 | - [srcDir](https://nuxt.com/docs/api/nuxt-config#srcdir) 922 | - [workspaceDir](https://nuxt.com/docs/api/nuxt-config#workspacedir) 923 | 924 | **Other:** 925 | 926 | - [build.templates](https://nuxt.com/docs/api/nuxt-config#templates) 927 | - [css](https://nuxt.com/docs/api/nuxt-config#css) 928 | - [experimental.localLayerAliases](https://nuxt.com/docs/api/nuxt-config#templates) 929 | - [ignore](https://nuxt.com/docs/api/nuxt-config#ignore) 930 | - [modules](https://nuxt.com/docs/api/nuxt-config#modules-1) 931 | - [plugins](https://nuxt.com/docs/api/nuxt-config#plugins-1) 932 | - [watch](https://nuxt.com/docs/api/nuxt-config#watch) 933 | - [serverHandlers](https://nuxt.com/docs/api/nuxt-config#serverhandlers) 934 | - [spaLoadingTemplate](https://nuxt.com/docs/api/nuxt-config#spaloadingtemplate) 935 | - [vite.publicDir](https://nuxt.com/docs/api/nuxt-config#publicdir) 936 | - [vite.resolve.alias](https://nuxt.com/docs/api/nuxt-config#resolve) 937 | - [vite.root](https://nuxt.com/docs/api/nuxt-config#root) 938 | - [webpack.analyze](https://nuxt.com/docs/api/nuxt-config#analyze-1) 939 | --------------------------------------------------------------------------------