Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: backport layers feature from v3 #746

Closed
wants to merge 20 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
110 changes: 110 additions & 0 deletions packages/bridge/builderReplacement.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
const glob = require('glob')
const utils = require('@nuxt/utils')
const ignore = require('ignore');
const fsExtra = require('fs-extra');
const { resolve, relative } = require('pathe')

class Ignore {
constructor(options) {
this.rootDir = options.rootDir;
this.ignoreOptions = options.ignoreOptions;
this.ignoreArray = options.ignoreArray;
this.addIgnoresRules();
}
static get IGNORE_FILENAME() {
return ".nuxtignore";
}
findIgnoreFile() {
if (!this.ignoreFile) {
const ignoreFile = resolve(this.rootDir, Ignore.IGNORE_FILENAME);
if (fsExtra.existsSync(ignoreFile) && fsExtra.statSync(ignoreFile).isFile()) {
this.ignoreFile = ignoreFile;
this.ignore = ignore(this.ignoreOptions);
}
}
return this.ignoreFile;
}
readIgnoreFile() {
if (this.findIgnoreFile()) {
return fsExtra.readFileSync(this.ignoreFile, "utf8");
}
}
addIgnoresRules() {
const content = this.readIgnoreFile();
if (content) {
this.ignore.add(content);
}
if (this.ignoreArray && this.ignoreArray.length > 0) {
if (!this.ignore) {
this.ignore = ignore(this.ignoreOptions);
}
this.ignore.add(this.ignoreArray);
}
}
filter(paths) {
if (this.ignore) {
return this.ignore.filter([].concat(paths || []));
}
return paths;
}
reload() {
delete this.ignore;
delete this.ignoreFile;
this.addIgnoresRules();
}
}

const processPages = (config) => {
// keep information about layers directories
const layers = config._layers
const layerDirs = layers.map(layer => ({
src: layer.config.srcDir,
pages: resolve(layer.config.srcDir, layer.config.dir?.pages || 'pages')
}))

// keep defaults if config.router is not defined, otherwise use config.router values
const { routeNameSplitter = '-', trailingSlash = undefined } = config.router || {}

const createRoutes = () => {
const pages = []
for (const layerDir of layerDirs) {
const files = []
const ignoreInstance = new Ignore({
rootDir: layerDir.src,
ignoreArray: config?.ignore || [],
})
const supportedExtensions = ['vue', 'js', 'ts', 'tsx', 'cts', 'mts']
const pagesGlob = glob.sync(resolve(layerDir.pages, `**/*.{${supportedExtensions.join(',')}}`))
.map(file => relative(layerDir.src, file))
const pagesFiles = ignoreInstance.filter(pagesGlob)
for (const pageFile of pagesFiles) {
const page = pageFile
files.push(page)
}
pages.push(utils.createRoutes({
files,
srcDir: layerDir.src,
pagesDir: relative(layerDir.src, layerDir.pages),
routeNameSplitter,
supportedExtensions,
trailingSlash
}))
}
return pages.flat()
}

// check if config.build.createRoutes is defined
config.build = config.build || {}
if (config.build.createRoutes) {
const originalCreateRoutes = config.build.createRoutes
// merge original createRoutes with our createRoutes
config.build.createRoutes = () => {
return originalCreateRoutes().concat(createRoutes())
}
} else {
// if config.build.createRoutes is not defined, use our createRoutes
config.build.createRoutes = createRoutes
}
}

module.exports.processPages = processPages
169 changes: 134 additions & 35 deletions packages/bridge/module.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -3,51 +3,150 @@ module.exports = function (...args) {
return import('./dist/module.mjs').then(m => m.default.call(this, ...args))
}

const { resolve } = require('pathe')
const { loadConfig } = require('c12')
const pkg = require('./package.json')
const { processPages } = require('./builderReplacement.cjs')

module.exports.defineNuxtConfig = (config = {}) => {
if (config.bridge === false) { return config }
const getRootDir = () => {
const cwd = process.cwd()
const parentModulePath = module.parent?.path
return parentModulePath ?? cwd
}

// Add new handlers options
config.serverHandlers = config.serverHandlers || []
config.devServerHandlers = config.devServerHandlers || []
config.devServer = config.devServer || {}
const getNuxiMode = () => {
const cliArgv = JSON.parse(process.env.__CLI_ARGV__ || '[]')
const processArgv = process.argv
// check if process.argv has 2nd argument which doesn't start with `-`
if (processArgv[2] && !processArgv[2].startsWith('-')) {
return processArgv[2]
}
// check if process.env.__CLI_ARGV__ has 2nd argument which doesn't start with `-`
if (cliArgv[2] && !cliArgv[2].startsWith('-')) {
return cliArgv[2]
}
}

// Initialize typescript config for nuxi typecheck + prepare support
config.typescript = config.typescript || {}
const nuxiMode = getNuxiMode()
const isDev = nuxiMode === 'dev'

// Initialize nitro options
config.nitro = config.nitro || {}
const loadC12Config = async (rootDir) => {
return await loadConfig({
name: 'nuxt',
rcFile: '.nuxtrc',
configFile: 'nuxt.config',
extend: { extendKey: ['theme', 'extends'] },
dotenv: true,
globalRc: true,
cwd: rootDir ?? getRootDir()
})
}

// Nuxt kit depends on this flag to check bridge compatibility
config.bridge = typeof config.bridge === 'object' ? config.bridge : {}
config.bridge._version = pkg.version
if (!config.buildModules) {
config.buildModules = []
}
if (!config.buildModules.find(m => m === '@nuxt/bridge' || m === '@nuxt/bridge-edge')) {
// Ensure other modules register their hooks before
config.buildModules.push('@nuxt/bridge')
// in dev mode initial config loaded twice, so we triggers only on 2nd loading
const stopCount = isDev ? 1 : 0
let processingCounter = 0
let isVite = false

const shouldEnterC12 = () => {
if (isVite) {
return processingCounter === stopCount
} else {
return !new Error('_').stack.includes(loadC12Config.name)
}
config.buildModules.unshift(async function () {
const nuxt = this.nuxt
}

module.exports.defineNuxtConfig = (config = {}) => {
return async () => {
if (config.bridge === false) { return config }
if (config.bridge?.vite) {
isVite = true
}
// I suppose we'll have only one bridge config in a project
if (config.bridge?.config) {
// try to check if we are not in a c12
if (shouldEnterC12()) {
processingCounter += 1
const result = await loadC12Config(config.rootDir)
const { configFile, layers = [], cwd } = result
const nuxtConfig = result.config

if (!nuxtConfig) {
throw new Error('No nuxt config found')
}

const { nuxtCtx } = await import('@nuxt/kit')
// Fill config
nuxtConfig.rootDir = nuxtConfig.rootDir || cwd
nuxtConfig._nuxtConfigFile = configFile
nuxtConfig._nuxtConfigFiles = [configFile]

// Allow using kit composables in all modules
if (nuxtCtx.tryUse()) {
nuxtCtx.unset()
// Resolve `rootDir` & `srcDir` of layers
for (const layer of layers) {
layer.config = layer.config || {}
layer.config.rootDir = layer.config.rootDir ?? layer.cwd
layer.config.srcDir = resolve(layer.config.rootDir, layer.config.srcDir)
}

// Filter layers
const _layers = layers.filter(layer => layer.configFile && !layer.configFile.endsWith('.nuxtrc'))
nuxtConfig._layers = _layers

// Ensure at least one layer remains (without nuxt.config)
if (!_layers.length) {
_layers.push({
cwd,
config: {
rootDir: cwd,
srcDir: cwd
}
})
}
processPages(nuxtConfig)
return nuxtConfig
} else {
processingCounter += 1
}
}
nuxtCtx.set(nuxt)

// Mock _layers for nitro and auto-imports
nuxt.options._layers = nuxt.options._layers || [{
config: nuxt.options,
cwd: nuxt.options.rootDir,
configFile: nuxt.options._nuxtConfigFile
}]
})
return config

// Add new handlers options
config.serverHandlers = config.serverHandlers || []
config.devServerHandlers = config.devServerHandlers || []
config.devServer = config.devServer || {}

config.dir = config.dir || {}

// Initialize typescript config for nuxi typecheck + prepare support
config.typescript = config.typescript || {}

// Nuxt kit depends on this flag to check bridge compatibility
config.bridge = typeof config.bridge === 'object' ? config.bridge : {}
config.bridge._version = pkg.version
if (!config.buildModules) {
config.buildModules = []
}
if (!config.buildModules.find(m => m === '@nuxt/bridge' || m === '@nuxt/bridge-edge')) {
// Ensure other modules register their hooks before
config.buildModules.push('@nuxt/bridge')
}
config.buildModules.unshift(async function () {
const nuxt = this.nuxt

const { nuxtCtx } = await import('@nuxt/kit')

// Allow using kit composables in all modules
if (nuxtCtx.tryUse()) {
nuxtCtx.unset()
}
nuxtCtx.set(nuxt)

// Mock _layers for nitro and auto-imports
nuxt.options._layers = nuxt.options._layers || [{
config: nuxt.options,
cwd: nuxt.options.rootDir,
configFile: nuxt.options._nuxtConfigFile
}]
})
return config
}
}

module.exports.meta = {
Expand Down
2 changes: 2 additions & 0 deletions packages/bridge/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
"@vitejs/plugin-legacy": "^4.0.5",
"@vitejs/plugin-vue2": "^2.2.0",
"acorn": "^8.9.0",
"c12": "^1.4.1",
"cookie-es": "^1.0.0",
"defu": "^6.1.2",
"destr": "^2.0.0",
Expand All @@ -46,6 +47,7 @@
"globby": "^13.2.1",
"h3": "^1.7.1",
"hash-sum": "^2.0.0",
"ignore": "^5.2.4",
"knitwork": "^1.0.0",
"magic-string": "^0.30.1",
"mlly": "^1.4.0",
Expand Down
2 changes: 1 addition & 1 deletion packages/bridge/src/nitro.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ export async function setupNitroBridge () {
dir: resolve(nuxt.options.buildDir, 'dist/client')
},
...nuxt.options._layers
.map(layer => join(layer.config.srcDir, layer.config.dir.static))
.map(layer => join(layer.config.srcDir, layer.config.dir?.static || 'static'))
.filter(dir => existsSync(dir))
.map(dir => ({ dir }))
],
Expand Down
4 changes: 3 additions & 1 deletion packages/bridge/types.d.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/// <reference types="nitropack" />
import type { Nuxt2Config } from '@nuxt/bridge-schema'
import type { NuxtConfig as _NuxtConfig } from '@nuxt/schema'
import type { DefineConfig, InputConfig, UserInputConfig, ConfigLayerMeta } from 'c12'
import type { MetaInfo } from 'vue-meta'

export interface NuxtSSRContext extends SSRContext {
Expand Down Expand Up @@ -37,6 +38,7 @@ export interface BridgeConfig {
resolve: boolean
typescript: boolean
meta: boolean | null
config: boolean
}

export interface NuxtConfig extends Nuxt2Config, Omit<_NuxtConfig, keyof Nuxt2Config> {
Expand All @@ -58,4 +60,4 @@ declare module 'nitropack' {
}
}

export declare function defineNuxtConfig (config: NuxtConfig): NuxtConfig
export declare const defineNuxtConfig: DefineConfig<NuxtConfig, ConfigLayerMeta>
1 change: 1 addition & 0 deletions playground/layer/.nuxtignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pages/layer-ignored.vue
3 changes: 3 additions & 0 deletions playground/layer/composables/composable.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export function useLayeredComposable () {
return 'layered composable activated!'
}
12 changes: 12 additions & 0 deletions playground/layer/nuxt.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { defineNuxtConfig } from '@nuxt/bridge'

export default defineNuxtConfig({
app: {
head: {
title: 'Nuxt Bridge Playground',
meta: [
{ hid: 'layer', name: 'layer', content: 'layer activated' }
]
}
}
})
5 changes: 5 additions & 0 deletions playground/layer/pages/layer-extended.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<template>
<div>
extended page
</div>
</template>
5 changes: 5 additions & 0 deletions playground/layer/pages/layer-ignored.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<template>
<div>
I should be ignored
</div>
</template>
1 change: 1 addition & 0 deletions playground/layer/server/api/layered-hello.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export default defineEventHandler(() => 'Layered hello API')
9 changes: 9 additions & 0 deletions playground/layer/server/middleware/layer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { appendResponseHeader, defineEventHandler } from 'h3'

export default defineEventHandler((event) => {
appendResponseHeader(
event,
'x-layer',
'active'
)
})