Closed pi0 closed 2 years ago
nuxt.config validation (Both type and keys) and IDE autocomplete
Might be hard (for now) because of modules which can freely choose their used keys. We can validate the core keys without problems though.
@manniL Exactly. At least the top level object cannot be strictly validated. We might add a special __validate__
key to the options that are validatable. If we use a well-defined object for schema definition we can magically use it for automated .d.ts
generation too.
Example:
{
__validate__: {
_strict: false, // <-- Allow modules freely adding new keys for BW compat
build: { type: Object }
},
build: {
__validate__: {
_strict: true, // <-- Indicates no extrra options are accepted
name: { type: String, required: false },
old: { type: String, deprecated: true }
}
}
}
We can also think about using a 3rd party schema validator that is able to generate types too. Like joi + joi-ts-generator
Supporting config/
directory and importing the files and merging them with nuxt.config.js
(that could be deprecated in the future).
Example:
config/
build/
babel.js
pages.js
app.js
This logic could use directly glob right inside @nuxt/config. So writing configuration could also feel like writing pages <3
@Atinux Nice, I really liked how @manniL went with his own website with a config
folder : https://github.com/manniL/lichter.io/tree/master/config. It seems to go in the same way of your config
directory support
I would be wary of using a directory called config
since there is already a popular package called config
that reads JSON files from this directory at the root of your project:
https://www.npmjs.com/package/config
I've used the config
package a number of times in Nuxt projects and it might get a little muddled if there are Nuxt related config JS files alongside static JSON config files.
Perhaps nuxt
is a better default? Appreciate that Nuxt generates a .nuxt
dir for dev
and build
, but nuxt
without the .
could be for config. Funnily enough, this is the convention that I have adopted for a recent project:
// nuxt.config.js
import generate from "./nuxt/generate"
import hooks from "./nuxt/hooks"
import head from "./nuxt/head"
import env from "./nuxt/env"
export default {
srcDir: "src",
generate,
hooks,
head,
env
}
@wagerfield If we implement that feature, the directory name will be changeable βΊοΈ But thanks for the heads up!
Another thing that might come in handy:
A context
object which can/will be passed to each sub config and can be set by the user. This might help to reduce async calls to APIs during build (e.g. when you want to define your routes for the sitemap, the blog feed and generate
).
@manniL context
could be of type NuxtConfiguration
in TypeScript environments: https://github.com/kevinmarrec/nuxt.js/blob/nuxt-configuration-types/packages/config/types/index.d.ts#L18
EDIT : might have misunderstood context
proposal
@kevinmarrec I thought about sth. like an argument that every sub-function could take (if it's written as function)
so the subconfigs would be sth. like export default (context) => { /* Do sth. here */ }
I saw that @TimKor (hope that was the right handle π) applied such a pattern:
const context = require('./shared/utils/context');
context.print();
module.exports = {
server: require('./config/server.js')(context),
env: require('./config/env.js')(context),
/*
** Headers of the page
*/
head: require('./config/head.js')(context),
// Sitemap settings:
sitemap: require('./config/sitemap.js')(context)
}
Oh yes right ! I'm now thinking of a new behavior for subconfigs π€
What about
// config/build.js
export default function (config, context) {
config.build = {
property: context.value
}
return config
}
The idea is to read every files in config folder and merge the values to build the final configuration file. People could preprend their filenames with numbers in cases they need to override same configuration properties in certain order in different files (rare case but could happen)
EDIT : It would work likely like Webpack extend config
Further to the discussion in #10 @pi0 suggested adding a presets
field to the Nuxt config that would work in the same way as Babel. As an alternative, I prefer ESLint's extends
field and behaviour for this use case since it supports a single string and an array of strings. I also think extends
is more semantically correct π€·ββοΈ
The value assigned to this field could either be a string or an array of strings that resolve to files exporting a Nuxt config:
// nuxt.config.js
export default {
// String value resolving to "some-nuxt-preset" module exporting a Nuxt config
extends: "some-nuxt-preset"
// Can also be an array of strings, merging these presets in order
extends: [
"some-nuxt-preset",
"another-nuxt-package/config"
]
}
@wagerfield I like the extends
more :+1:
@manniL Almost right . :) I like to build the configuration using functions that accept a context parameter. Context is in my cases just based on some environment variables.
Which could also have been just the env
key of the configuration. If Nuxt keeps track of a (configurable) order in which to load the configuration files, it would be possible to export a function like this:
~/config/env.js
<-- first one by default
export default (config) => {
return {
locale: 'en-US'
};
}
~/config/head.js
export default ({env}) => {
// We can use env in here, to generate the config of head
}
This way we might also be able to determine the order by the destructuring parameter (like dependencies). But this might be overkill.
Also possible for modules and their options. But here is the order somewhat more important. So you will need a config/modules/index.js
to specify them:
export default ({env}, load) => {
return [
'@nuxtjs/sentry',
[
'@nuxtjs/axios',
load('~/config/modules/axios')
]
]
}
There could be a second parameter which takes care of the loading, which just calls the default export with the current configuration as parameter. Similair to plugins.
I think this is easier for new Nuxt.js users than the extend
.
Plus loading order, we can support Unix style config naming to specify priority be prefixing with numbers. Like 1-env.js
/ etc.
@pi0 Actually I think it might also be possible to dynamically determine the order using a Proxy. Something like this:
function makeProxy(config) {
return new Proxy({}, {
get: function(obj, prop) {
if (prop in obj) {
return obj[prop];
} else {
console.log('Load: ' + prop);
return obj[prop] = loadConfiguration(prop);
}
}
});
}
var configuration = makeProxy({});
function loadConfiguration(prop) {
return require('~config/' + prop)(configuration);
}
Of course you'd need to watch out and warn for circular deps.
I made a Nuxt module to demonstrate a working POC of dynamic load order of the config files I suggested using a Proxy:
https://github.com/Timkor/nuxt-config
I am curious what you guys think, and if it is a possibility to add this functionality to nuxt.
Nuxt Module for splitting your nuxt.config.js into multiple files.
~/config/modules.js
will be added to Nuxt dynamically.~/config/env.js
:
// Support for object
export default {
quickBuild: true,
sentryDSN: '...'
}
~/config/build.js
:
// Support for function:
// Because of {env} this module will first import ~/config/env.js
export default ({env}) => {
return {
hardSource: env.quickBuild,
extend(config, ctx) {
}
}
}
~/config/plugins.js
:
// Support for Arrays
export default [
'~/plugins/google-analytics.js'
]
There is still one problem: circulair dependencies. If you would have two config functions:
~/config/foo.js
:
export default (config) => {
// Read bar
const bar = config.bar; // This will trigger to import ~/config/bar.js
}
~/config/bar.js
:
export default (config) => {
// Read foo
const foo = config.foo; // This will trigger to import ~/config/foo.js
}
This is not possible. So what happens now is:
~/config/bar.js
is executed
config.foo
is read~/config/foo.js
config.bar
is readfoo
continues and config.bar
is still undefinedconfig.foo
is equal to the result of ~/config/foo.js
~/config/bar.js
is continued.The circulair dependency warning is now:
Can not early load '~/config/bar.js' because of a circulair dependency
It would be nice to have a descriptive warning which tells:
An example of a better warning can be:
Can not load '~/config/bar.js' because of a circulair dependency:
- config.bar is referenced in ~/config/foo.js
- config.foo is referenced in ~/config/bar.js (resolved)
Some info about how to resolve/fix this.
This can also occur with a path which is larger than 2, for instance: a -> b -> c -> a
.
This can be achieved by implementing a dependency tree.
Hi @Timkor
Thank you so much for investigating in it and creating a module π
Actually, I am trying to understand the use case of exporting a method and receiving the config
as parameter. What is the real use case?
Actually, for env
, I don't know if we should keep it, people can use process.env
right inside their config files to conditionally update their config before exporting.
Would like some feedback from @nuxt/core-team
@Atinux It was interresting to develop. Though not really neccesary in any way.
I thought it would be nice to somewhere have a definition of the environment variables for better IDE support and better TypeScript integration. In addition, I could think of some use cases where you would want some configuration file dependant on some other properties of the nuxt.config.js. However, those use cases could be solved one way or another.
Edit: A good use case for this functionality would be for instance:
Generating a sitemap with localisation with the following modules: https://github.com/nuxt-community/sitemap-module https://github.com/nuxt-community/nuxt-i18n
It would be nice to filter the sitemap routes based on properties of localisation (deferred from req.headers.host
)
Then one would need access to the nuxt-i18n
config in:
// nuxt.config.js
// Filter routes by language
{
sitemap: {
filter ({ routes, options }) {
if (options.hostname === 'example.com') {
return routes.filter(route => getNuxtI18nLocaleOf(route) === 'en')
}
return routes.filter(route => getNuxtI18nLocaleOf(route) === 'fr')
}
}
}
Just an example.
Could be written as:
// config/sitemap.js
// Filter routes by language
export default ({i18n}) => {
function getNuxtI18nLocaleOf(route) {
// Use i18n here to determine locale of route
return ...
}
return {
filter ({ routes, options }) {
if (options.hostname === 'example.com') {
return routes.filter(route => getNuxtI18nLocaleOf(route) === 'en')
}
return routes.filter(route => getNuxtI18nLocaleOf(route) === 'fr')
}
}
}
Hi @Timkor
Thank you so much for investigating in it and creating a module π
Actually, I am trying to understand the use case of exporting a method and receiving the
config
as parameter. What is the real use case?Actually, for
env
, I don't know if we should keep it, people can useprocess.env
right inside their config files to conditionally update their config before exporting.Would like some feedback from @nuxt/core-team
This module seems to solve the problem with several sites in a single instance because most of the module options are configured in nuxt.config.js, but for several sites, there is a strong dependency on the host (postponed from req.headers.host ) that the site will be served, so there are few examples for multi-sites that has different settings.
Hi guys, is this RFC still alive?
Here is a quick recap of Nuxt 3 latest config improvements in relate to this RFC:
@nuxt/schema
with unjs/untyped powered by typescript and resolvers. Using single source of trust was finally making more sense than distributing to smaller package and easier to trace.app.config
Ideas welcome to both nuxt/framework and unjs/c12 repos.
Objectives:
options.js
nuxt.config
validation (Both type and keys) and IDE autocomplete.nuxt
dir (The Initial version made by @clarkdo but currently disabled). This is essential for improving the size of nuxt serverless builds.Related: https://github.com/nuxt/nuxt.js/issues/3985