feat: optional pages and refactor nuxt3 (#142)

This commit is contained in:
Pooya Parsa 2021-05-20 13:42:41 +02:00
parent d95e002d5b
commit 6b62d456d7
42 changed files with 355 additions and 570 deletions

View File

@ -0,0 +1 @@
export { default } from '<%= app.main %>'

View File

@ -1,28 +0,0 @@
<%= nuxtOptions.vite ? "import('vite/dynamic-import-polyfill')" : '' %>
import { createSSRApp, nextTick } from 'vue'
import { createNuxt, applyPlugins } from '@nuxt/app'
import plugins from './plugins'
import clientPlugins from './plugins.client'
import App from '<%= app.main %>'
async function initApp () {
const app = createSSRApp(App)
const nuxt = createNuxt({ app })
await applyPlugins(nuxt, plugins)
await applyPlugins(nuxt, clientPlugins)
await nuxt.hooks.callHook('app:created', app)
await nuxt.hooks.callHook('app:beforeMount', app)
app.mount('#__nuxt')
await nuxt.hooks.callHook('app:mounted', app)
await nextTick()
nuxt.isHydrating = false
}
initApp().catch((error) => {
console.error('Error while mounting app:', error) // eslint-disable-line no-console
})

View File

@ -0,0 +1 @@
export { default } from '#app/entry'

View File

@ -1,18 +0,0 @@
import { createApp } from 'vue'
import { createNuxt, applyPlugins } from '@nuxt/app'
import plugins from './plugins'
import serverPlugins from './plugins.server'
import App from '<%= app.main %>'
export default async function createNuxtAppServer (ssrContext = {}) {
const app = createApp(App)
const nuxt = createNuxt({ app, ssrContext })
await applyPlugins(nuxt, plugins)
await applyPlugins(nuxt, serverPlugins)
await nuxt.hooks.callHook('app:created', app)
return app
}

View File

@ -1,20 +0,0 @@
import { $fetch } from 'ohmyfetch'
import logs from '#app/plugins/logs.client.dev'
import progress from '#app/plugins/progress.client'
<% const plugins = app.plugins.filter(p => p.mode === 'client').map(p => p.src) %>
<%= nxt.importSources(plugins) %>
if (!globalThis.$fetch) {
globalThis.$fetch = $fetch
}
const plugins = [
progress,
<%= plugins.map(nxt.importName).join(',\n\t') %>
]
if (process.dev) {
plugins.push(logs)
}
export default plugins

View File

@ -1,15 +1,23 @@
import head from '#app/plugins/head'
import router from '#app/plugins/router'
import vuex from '#app/plugins/vuex'
import legacy from '#app/plugins/legacy'
import preload from '#app/plugins/preload.server'
<% const plugins = app.plugins.filter(p => p.mode === 'all').map(p => p.src) %>
<%= nxt.importSources(plugins) %>
<%= utils.importSources(app.plugins.map(p => p.src)) %>
export default [
const commonPlugins = [
head,
router,
vuex,
legacy,
<%= plugins.map(nxt.importName).join(',\n\t') %>
<%= app.plugins.filter(p => !p.mode || p.mode === 'all').map(p => utils.importName(p.src)).join(',\n ') %>
]
export const clientPluigns = [
...commonPlugins,<%= app.plugins.filter(p => p.mode === 'client').map(p => utils.importName(p.src)).join(',\n ') %>
]
export const serverPluigns = [
...commonPlugins,
preload,
<%= app.plugins.filter(p => p.mode === 'server').map(p => utils.importName(p.src)).join(',\n ') %>
]
export default process.client ? clientPluigns : serverPluigns

View File

@ -1,8 +0,0 @@
import preload from '#app/plugins/preload.server'
<% const plugins = app.plugins.filter(p => p.mode === 'server').map(p => p.src) %>
<%= nxt.importSources(plugins) %>
export default [
preload
<%= plugins.map(nxt.importName).join(',\n\t') %>
]

View File

@ -1,2 +0,0 @@
// TODO: Use webpack-virtual-modules
export default <%= nxt.serialize(app.routes.map(nxt.serializeRoute)) %>

View File

@ -6,8 +6,8 @@
<body {{ BODY_ATTRS }}>
{{ BODY_SCRIPTS_PREPEND }}
<div id="__nuxt">{{ APP }}</div>
<% if (nuxtOptions.vite && nuxtOptions.dev) { %><script type="module" src="/@vite/client"></script>
<script type="module" src="/entry.client.mjs"></script><% } %>
<% if (nuxt.options.vite && nuxt.options.dev) { %><script type="module" src="/@vite/client"></script>
<script type="module" src="/entry.mjs"></script><% } %>
{{ BODY_SCRIPTS }}
</body>
</html>

View File

@ -1,23 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<title>Server error</title>
<meta charset="utf-8">
<meta content="width=device-width,initial-scale=1.0,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no" name=viewport>
<style>
.__nuxt-error-page{padding: 1rem;background:#f7f8fb;color:#47494e;text-align:center;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;font-family:sans-serif;font-weight:100!important;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%;-webkit-font-smoothing:antialiased;position:absolute;top:0;left:0;right:0;bottom:0}.__nuxt-error-page .error{max-width:450px}.__nuxt-error-page .title{font-size:24px;font-size:1.5rem;margin-top:15px;color:#47494e;margin-bottom:8px}.__nuxt-error-page .description{color:#7f828b;line-height:21px;margin-bottom:10px}.__nuxt-error-page a{color:#7f828b!important;text-decoration:none}.__nuxt-error-page .logo{position:fixed;left:12px;bottom:12px}
</style>
</head>
<body>
<div class="__nuxt-error-page">
<div class="error">
<svg xmlns="http://www.w3.org/2000/svg" width="90" height="90" fill="#DBE1EC" viewBox="0 0 48 48"><path d="M22 30h4v4h-4zm0-16h4v12h-4zm1.99-10C12.94 4 4 12.95 4 24s8.94 20 19.99 20S44 35.05 44 24 35.04 4 23.99 4zM24 40c-8.84 0-16-7.16-16-16S15.16 8 24 8s16 7.16 16 16-7.16 16-16 16z"/></svg>
<div class="title">Server error</div>
<div class="description">{{ message }}</div>
</div>
<div class="logo">
<a href="https://nuxtjs.org" target="_blank" rel="noopener">Nuxt.js</a>
</div>
</div>
</body>
</html>

View File

@ -1,3 +0,0 @@
<template>
<router-view />
</template>

47
packages/app/src/entry.ts Normal file
View File

@ -0,0 +1,47 @@
import { createSSRApp, createApp, nextTick } from 'vue'
import { createNuxt, applyPlugins } from '@nuxt/app'
// @ts-ignore
import plugins from '#build/plugins'
// @ts-ignore
import App from '#build/app'
let entry: Function
if (process.server) {
entry = async function createNuxtAppServer (ssrContext = {}) {
const app = createApp(App)
const nuxt = createNuxt({ app, ssrContext })
await applyPlugins(nuxt, plugins)
await nuxt.hooks.callHook('app:created', app)
return app
}
}
if (process.client) {
entry = async function initApp () {
const app = createSSRApp(App)
const nuxt = createNuxt({ app })
await applyPlugins(nuxt, plugins)
await nuxt.hooks.callHook('app:created', app)
await nuxt.hooks.callHook('app:beforeMount', app)
app.mount('#__nuxt')
await nuxt.hooks.callHook('app:mounted', app)
await nextTick()
nuxt.isHydrating = false
}
entry().catch((error) => {
console.error('Error while mounting app:', error) // eslint-disable-line no-console
})
}
export default ctx => entry(ctx)

View File

@ -1 +0,0 @@
export * from './index.ts'

View File

@ -11,6 +11,8 @@ import type { TemplateOpts, PluginTemplateOpts } from '../types/module'
*
* If a fileName is not provided or the template is string, target file name defaults to
* [dirName].[fileName].[pathHash].[ext].
*
* This file is available to import with `#build/${filename}`
*/
export function addTemplate (tmpl: TemplateOpts | string) {
const nuxt = useNuxt()

View File

@ -1,4 +1,4 @@
import { Nuxt } from './nuxt'
import { Nuxt, NuxtApp } from './nuxt'
import type { IncomingMessage, ServerResponse } from 'http'
import type { Compiler, Configuration, Stats } from 'webpack'
import type { NuxtConfig, NuxtOptions } from '..'
@ -23,6 +23,11 @@ export interface NuxtHooks {
// Don't break usage of untyped hooks
[key: string]: (...args: any[]) => HookResult
// nuxt3
'app:resolve': (app: NuxtApp) => HookResult
'app:templates': (app: NuxtApp) => HookResult
'builder:generateApp': () => HookResult
// @nuxt/builder
'build:before':
(builder: Builder, buildOptions: NuxtOptions['build']) => HookResult

View File

@ -22,3 +22,24 @@ export interface Nuxt {
/** The production or development server */
server?: any
}
export interface NuxtPlugin {
src: string
mode?: 'server' | 'client' | 'all',
options?: Record<string, any>
}
export interface NuxtTemplate {
path: string // Relative path of destination
src?: string // Absolute path to source file
compile?: (data: Record<string, any>) => string
data?: any
}
export interface NuxtApp {
main?: string
dir: string
extensions: string[]
plugins: NuxtPlugin[]
templates: NuxtTemplate[]
}

View File

@ -1,5 +1,6 @@
import { existsSync, lstatSync } from 'fs'
import { resolve, join } from 'upath'
import globby from 'globby'
export interface ResolveOptions {
/**
@ -93,3 +94,11 @@ export function tryResolvePath (path: string, opts: ResolveOptions = {}) {
} catch (e) {
}
}
export async function resolveFiles (path: string, pattern: string) {
const files = await globby(pattern, {
cwd: path,
followSymbolicLinks: true
})
return files.map(p => resolve(path, p))
}

View File

@ -1,6 +1,6 @@
import consola from 'consola'
const internalRegex = /^\.|\?|\.[mc]?js$|.ts$/
const internalRegex = /^\.|\?|\.[mc]?js$|.ts$|.json$/
export function autoMock () {
return {

View File

@ -20,6 +20,7 @@
"@nuxt/app": "^0.3.3",
"@nuxt/kit": "^0.5.3",
"@nuxt/nitro": "^0.6.3",
"@nuxt/pages": "^0.1.0",
"@nuxt/vite-builder": "^0.3.3",
"@nuxt/webpack-builder": "^0.3.4",
"chokidar": "^3.5.1",

View File

@ -1,79 +1,103 @@
import { resolve } from 'path'
import { resolve, join, relative, dirname } from 'upath'
import globby from 'globby'
import lodashTemplate from 'lodash/template'
import defu from 'defu'
import { tryResolvePath } from '@nuxt/kit'
import { Builder } from './builder'
import { NuxtRoute, resolvePagesRoutes } from './pages'
import { NuxtPlugin, resolvePlugins } from './plugins'
import { tryResolvePath, resolveFiles, Nuxt, NuxtApp, NuxtTemplate, NuxtPlugin } from '@nuxt/kit'
import { mkdirp, writeFile, readFile } from 'fs-extra'
import * as templateUtils from './template.utils'
export interface NuxtApp {
main?: string
routes: NuxtRoute[]
dir: string
extensions: string[]
plugins: NuxtPlugin[]
templates: Record<string, string>
pages?: {
dir: string
}
}
// Scan project structure
export async function createApp (
builder: Builder,
options: Partial<NuxtApp> = {}
): Promise<NuxtApp> {
const { nuxt } = builder
// Create base app object
const app: NuxtApp = defu(options, {
export function createApp (nuxt: Nuxt, options: Partial<NuxtApp> = {}): NuxtApp {
return defu(options, {
dir: nuxt.options.srcDir,
extensions: nuxt.options.extensions,
routes: [],
plugins: [],
templates: {},
pages: {
dir: 'pages'
}
templates: {}
} as NuxtApp)
}
// Resolve app.main
export async function generateApp (nuxt: Nuxt, app: NuxtApp) {
// Resolve app
await resolveApp(nuxt, app)
// Scan templates
const templatesDir = join(nuxt.options.appDir, '_templates')
const templateFiles = await globby(join(templatesDir, '/**'))
app.templates = templateFiles
.filter(src => !src.endsWith('.d.ts'))
.map(src => ({
src,
path: relative(templatesDir, src),
data: {}
} as NuxtTemplate))
// Extend templates
await nuxt.callHook('app:templates', app)
// Generate templates
await Promise.all(app.templates.map(t => generateTemplate(t, nuxt.options.buildDir, {
utils: templateUtils,
nuxt,
app
})))
await nuxt.callHook('app:templatesGenerated', app)
}
export async function resolveApp (nuxt: Nuxt, app: NuxtApp) {
const resolveOptions = {
base: nuxt.options.srcDir,
alias: nuxt.options.alias,
extensions: nuxt.options.extensions
}
// Resolve main (app.vue)
if (!app.main) {
app.main = tryResolvePath('~/App', resolveOptions) ||
tryResolvePath('~/app', resolveOptions)
app.main = tryResolvePath('~/App', resolveOptions) || tryResolvePath('~/app', resolveOptions)
}
// Resolve pages/
if (app.pages) {
app.routes.push(...(await resolvePagesRoutes(builder, app)))
}
if (app.routes.length) {
// Add 404 page is not added
const page404 = app.routes.find(route => route.name === '404')
if (!page404) {
app.routes.push({
name: '404',
path: '/:catchAll(.*)*',
file: resolve(nuxt.options.appDir, 'pages/404.vue'),
children: []
})
}
}
// Fallback app.main
if (!app.main && app.routes.length) {
app.main = resolve(nuxt.options.appDir, 'app.pages.vue')
} else if (!app.main) {
if (!app.main) {
app.main = resolve(nuxt.options.appDir, 'app.tutorial.vue')
}
// Resolve plugins/
app.plugins = await resolvePlugins(builder, app)
// Resolve plugins
app.plugins = [
...nuxt.options.plugins,
...await resolvePlugins(nuxt)
]
return app
// Extend app
await nuxt.callHook('app:resolve', app)
}
async function generateTemplate (tmpl: NuxtTemplate, destDir: string, ctx) {
let compiledSrc: string = ''
const data = { ...ctx, ...tmpl.data }
if (tmpl.src) {
try {
const srcContents = await readFile(tmpl.src, 'utf-8')
compiledSrc = lodashTemplate(srcContents, {})(data)
} catch (err) {
console.error('Error compiling template: ', tmpl)
throw err
}
} else if (tmpl.compile) {
compiledSrc = tmpl.compile(data)
}
const dest = join(destDir, tmpl.path)
await mkdirp(dirname(dest))
await writeFile(dest, compiledSrc)
}
async function resolvePlugins (nuxt: Nuxt) {
const plugins = await resolveFiles(nuxt.options.srcDir, 'plugins/**/*.{js,ts}')
return plugins.map(src => ({
src,
mode: getPluginMode(src)
})
)
}
function getPluginMode (src: string) {
const [, mode = 'all'] = src.match(/\.(server|client)(\.\w+)*$/) || []
return mode as NuxtPlugin['mode']
}

View File

@ -1,117 +1,48 @@
import { join, relative, resolve } from 'upath'
import fsExtra from 'fs-extra'
import { debounce } from 'lodash'
import chokidar from 'chokidar'
import { Nuxt } from '@nuxt/kit'
import { emptyDir } from 'fs-extra'
import { createApp, generateApp } from './app'
import {
templateData,
compileTemplates,
scanTemplates,
NuxtTemplate
} from './template'
import { createWatcher, WatchCallback } from './watch'
import { createApp, NuxtApp } from './app'
import Ignore from './utils/ignore'
export async function build (nuxt: Nuxt) {
// Clear buildDir once
await emptyDir(nuxt.options.buildDir)
export class Builder {
nuxt: Nuxt
globals: any
ignore: Ignore
templates: NuxtTemplate[]
app: NuxtApp
constructor (nuxt: Nuxt) {
this.nuxt = nuxt
this.ignore = new Ignore({
rootDir: nuxt.options.srcDir,
ignoreArray: nuxt.options.ignore.concat(
relative(nuxt.options.rootDir, nuxt.options.buildDir)
)
})
}
build () {
return _build(this)
}
close () {
// TODO: close watchers
}
}
// Extends VueRouter
async function _build (builder: Builder) {
const { nuxt } = builder
if (!nuxt.options.dev) {
await fsExtra.emptyDir(nuxt.options.buildDir)
}
await fsExtra.emptyDir(resolve(nuxt.options.buildDir, 'dist'))
await generate(builder)
const app = createApp(nuxt)
await generateApp(nuxt, app)
if (nuxt.options.dev) {
watch(builder)
watch(nuxt)
nuxt.hook('builder:watch', async (event, path) => {
if (event !== 'change' && /app|plugins/i.test(path)) {
await generateApp(nuxt, app)
}
})
nuxt.hook('builder:generateApp', () => generateApp(nuxt, app))
}
await bundle(builder)
await nuxt.callHook('build:done', builder)
await bundle(nuxt)
await nuxt.callHook('build:done', { nuxt })
}
function watch (builder: Builder) {
const { nuxt, ignore } = builder
// Watch internal templates
const options = nuxt.options.watchers.chokidar
const nuxtAppWatcher = createWatcher(nuxt.options.appDir, { ...options, cwd: nuxt.options.appDir }, ignore)
nuxtAppWatcher.watchAll(debounce(() => compileTemplates(builder.templates, nuxt.options.buildDir), 100))
// Watch user app
// TODO: handle multiples app dirs
const appPattern = `${builder.app.dir}/**/*{${nuxt.options.extensions.join(',')}}`
const appWatcher = createWatcher(appPattern, { ...options, cwd: builder.app.dir }, ignore)
// appWatcher.debug('srcDir')
const refreshTemplates = debounce(() => generate(builder), 100)
// Watch for App.vue creation
appWatcher.watch(/^(A|a)pp\.[a-z]{2,3}/, refreshTemplates, ['add', 'unlink'])
// Watch for page changes
appWatcher.watch(new RegExp(`^${nuxt.options.dir.pages}/`), refreshTemplates, ['add', 'unlink'])
// Watch for plugins changes
appWatcher.watch(/^plugins/, refreshTemplates, ['add', 'unlink'])
// Shared Watcher
const watchHook: WatchCallback = (event, path) => builder.nuxt.callHook('builder:watch', event, path)
const watchHookDebounced = debounce(watchHook, 100)
appWatcher.watchAll(watchHookDebounced)
nuxtAppWatcher.watchAll(watchHookDebounced)
function watch (nuxt: Nuxt) {
const watcher = chokidar.watch(nuxt.options.srcDir, {
...nuxt.options.watchers.chokidar,
cwd: nuxt.options.srcDir,
ignoreInitial: true,
ignored: [
'.nuxt',
'.output',
'node_modules'
]
})
const watchHook = (event, path) => nuxt.callHook('builder:watch', event, path)
watcher.on('all', watchHook)
nuxt.hook('close', () => watcher.close())
return watcher
}
export async function generate (builder: Builder) {
const { nuxt } = builder
builder.app = await createApp(builder)
// Todo: Call app:created hook
const templatesDir = join(builder.nuxt.options.appDir, '_templates')
const appTemplates = await scanTemplates(templatesDir, templateData(builder))
// Todo: Call app:templates hook
builder.templates = [...appTemplates]
await compileTemplates(builder.templates, nuxt.options.buildDir)
}
async function bundle ({ nuxt }: Builder) {
// @ts-ignore
async function bundle (nuxt: Nuxt) {
const useVite = !!nuxt.options.vite
const { bundle } = await (useVite ? import('@nuxt/vite-builder') : import('@nuxt/webpack-builder'))
return bundle(nuxt)
}
export function getBuilder (nuxt: Nuxt) {
return new Builder(nuxt)
}
export function build (nuxt: Nuxt) {
return getBuilder(nuxt).build()
}

View File

@ -49,6 +49,7 @@ export async function loadNuxt (opts: LoadNuxtOptions): Promise<Nuxt> {
options.appDir = appDir
options._majorVersion = 3
options.alias.vue = require.resolve('vue/dist/vue.esm-bundler.js')
options.buildModules.push(require.resolve('@nuxt/pages/module'))
const nuxt = createNuxt(options)

View File

@ -1,26 +0,0 @@
import { NuxtApp } from './app'
import { Builder } from './builder'
import { resolveFiles } from './utils'
export interface NuxtPlugin {
src: string
mode: 'server' | 'client' | 'all'
}
const MODES_REGEX = /\.(server|client)(\.\w+)*$/
const getPluginMode = (src: string) => {
const [, mode = 'all'] = src.match(MODES_REGEX) || []
return mode as NuxtPlugin['mode']
}
export async function resolvePlugins (builder: Builder, app: NuxtApp) {
const plugins = await resolveFiles(builder, 'plugins/**/*.{js,ts}', app.dir)
return plugins.map(src => ({
src,
mode: getPluginMode(src)
})
)
}

View File

@ -1,58 +0,0 @@
import fsExtra from 'fs-extra'
import globby from 'globby'
import lodashTemplate from 'lodash/template'
import { join, relative, dirname } from 'upath'
import * as nxt from './utils/nxt'
import type { Builder } from './builder'
export interface NuxtTemplate {
src: string // Absolute path to source file
path: string // Relative path of destination
data?: any
}
export function templateData (builder: Builder) {
return {
globals: builder.globals,
app: builder.app,
nuxtOptions: builder.nuxt.options,
nxt
}
}
async function compileTemplate (tmpl: NuxtTemplate, destDir: string) {
const srcContents = await fsExtra.readFile(tmpl.src, 'utf-8')
let compiledSrc: string
try {
compiledSrc = lodashTemplate(srcContents, {})(tmpl.data)
} catch (err) {
console.error('Error compiling template: ', tmpl)
throw err
}
const dest = join(destDir, tmpl.path)
// consola.log('Compile template', dest)
await fsExtra.mkdirp(dirname(dest))
await fsExtra.writeFile(dest, compiledSrc)
}
export function compileTemplates (templates: NuxtTemplate[], destDir: string) {
return Promise.all(templates.map(t => compileTemplate(t, destDir)))
}
export async function scanTemplates (dir: string, data?: Record<string, any>) {
const templateFiles = (await globby(join(dir, '/**')))
return templateFiles.filter(src => !src.endsWith('.d.ts')).map(src => ({
src,
path: relative(dir, src),
data
}))
}
export function watchTemplate (template: NuxtTemplate, _watcher: any, _cb: () => any) {
template.data = new Proxy(template.data, {
// TODO: deep watch option changes
})
// TODO: Watch fs changes
}

View File

@ -1,8 +1,6 @@
import { basename, extname } from 'path'
import hash from 'hash-sum'
import { camelCase } from 'scule'
import { NuxtRoute } from '../pages'
// NXT is a set of utils for serializing JavaScript data to JS code
export const serialize = data => JSON.stringify(data, null, 2).replace(/"{(.+)}"/g, '$1')
@ -19,25 +17,3 @@ export const importSources = (sources: string | string[], { lazy = false } = {})
return `import ${importName(src)} from '${src}'`
}).join('\n')
}
interface SerializedRoute {
name?: string
path: string
children: SerializedRoute[]
/**
* @private
*/
__file: string
component: string
}
export const serializeRoute = (route: NuxtRoute): SerializedRoute => {
return {
name: route.name,
path: route.path,
children: route.children.map(serializeRoute),
// TODO: avoid exposing to prod, using process.env.NODE_ENV ?
__file: route.file,
component: `{() => import('${route.file}' /* webpackChunkName: '${route.name}' */)}`
}
}

View File

@ -1,79 +0,0 @@
import path from 'path'
import fs from 'fs-extra'
import ignore from 'ignore'
type IgnoreInstance = ReturnType<typeof ignore>
type IgnoreOptions = Parameters<typeof ignore>[0]
interface Options {
rootDir: string
ignore?: IgnoreInstance
ignoreArray?: Array<string | IgnoreInstance>
ignoreOptions?: IgnoreOptions
}
export default class Ignore {
rootDir: string
ignore?: IgnoreInstance
ignoreArray?: Array<string | IgnoreInstance>
ignoreFile?: string
ignoreOptions?: IgnoreOptions
constructor ({ ignoreArray, ignoreOptions, rootDir }: Options) {
this.rootDir = rootDir
this.ignoreOptions = ignoreOptions
this.ignoreArray = ignoreArray
this.addIgnoresRules()
}
static get IGNORE_FILENAME () {
return '.nuxtignore'
}
findIgnoreFile () {
if (!this.ignoreFile) {
const ignoreFile = path.resolve(this.rootDir, Ignore.IGNORE_FILENAME)
if (fs.existsSync(ignoreFile) && fs.statSync(ignoreFile).isFile()) {
this.ignoreFile = ignoreFile
this.ignore = ignore(this.ignoreOptions)
}
}
return this.ignoreFile
}
readIgnoreFile () {
if (this.findIgnoreFile() && this.ignoreFile) {
return fs.readFileSync(this.ignoreFile, 'utf8')
}
}
addIgnoresRules () {
const content = this.readIgnoreFile()
if (!this.ignore) {
this.ignore = ignore(this.ignoreOptions)
}
if (content) {
this.ignore.add(content)
}
if (this.ignoreArray && this.ignoreArray.length > 0) {
this.ignore.add(this.ignoreArray)
}
}
filter (paths: string[] = []) {
if (this.ignore) {
return this.ignore.filter(([] as string[]).concat(paths))
}
return paths
}
ignores (pathname: string) {
return this.ignore && this.ignore.ignores(pathname)
}
reload () {
delete this.ignore
delete this.ignoreFile
this.addIgnoresRules()
}
}

View File

@ -1,13 +0,0 @@
import { resolve } from 'path'
import globby from 'globby'
import { Builder } from '../builder'
// TODO: move to core resolver
export async function resolveFiles (builder: Builder, pattern: string, srcDir: string) {
const { nuxt } = builder
return builder.ignore.filter(await globby(pattern, {
cwd: srcDir,
followSymbolicLinks: nuxt.options.build.followSymlinks
})).map(p => resolve(srcDir, p))
}

View File

@ -1,51 +0,0 @@
import chokidar, { WatchOptions } from 'chokidar'
import defu from 'defu'
import consola from 'consola'
import Ignore from './utils/ignore'
export type WatchEvent = 'add' | 'addDir' | 'change' | 'unlink' | 'unlinkDir'
export type WatchCallback = (event: WatchEvent, path: string) => void
export type WatchFilter = (event: WatchEvent, path: string) => boolean | null
export function createWatcher (
pattern: string,
options?: WatchOptions,
ignore?: Ignore
) {
const opts = defu(options!, {
ignored: [],
ignoreInitial: true
})
const watcher = chokidar.watch(pattern, opts)
const watchAll = (cb: WatchCallback, filter?: WatchFilter) => {
watcher.on('all', (event, path: string) => {
if (ignore && ignore.ignores(path)) {
return
}
if (!filter || filter(event, path)) {
cb(event, path)
}
})
}
const watch = (pattern: string | RegExp, cb: WatchCallback, events?: WatchEvent[]) =>
watchAll(cb, (event, path) => path.match(pattern) && (!events || events.includes(event)))
const debug = (tag: string = '[Watcher]') => {
consola.log(tag, 'Watching ', pattern)
watchAll((event, path) => {
consola.log(tag, event, path)
})
}
return {
watchAll,
watch,
debug,
close: () => watcher.close()
}
}
export type Watcher = ReturnType<typeof createWatcher>

View File

@ -0,0 +1,12 @@
import { defineBuildConfig } from 'unbuild'
export default defineBuildConfig({
declaration: true,
entries: [
'src/module',
{ input: 'src/runtime/', outDir: 'dist/runtime', format: 'esm' }
],
dependencies: [
'vue-router'
]
})

1
packages/pages/module.js Normal file
View File

@ -0,0 +1 @@
module.exports = require('./dist/module')

View File

@ -0,0 +1,24 @@
{
"name": "@nuxt/pages",
"version": "0.1.0",
"repository": "nuxt/framework",
"license": "MIT",
"types": "./dist/module.d.ts",
"files": [
"dist",
"module.js"
],
"scripts": {
"prepack": "unbuild"
},
"dependencies": {
"@nuxt/kit": "^0.5.3",
"globby": "^11.0.3",
"ufo": "^0.7.5",
"upath": "^2.0.1",
"vue-router": "^4.0.6"
},
"devDependencies": {
"unbuild": "^0.2.3"
}
}

View File

@ -0,0 +1,59 @@
import { existsSync } from 'fs'
import { defineNuxtModule } from '@nuxt/kit'
import { resolve } from 'upath'
import { resolvePagesRoutes } from './utils'
export default defineNuxtModule({
name: 'router',
setup (_options, nuxt) {
const runtimeDir = resolve(__dirname, 'runtime')
const pagesDir = resolve(nuxt.options.srcDir, nuxt.options.dir.pages)
const routerPlugin = resolve(runtimeDir, 'router')
nuxt.hook('builder:watch', async (event, path) => {
// Regenerate templates when adding or removing pages (plugin and routes)
if (event !== 'change' && path.startsWith('pages/')) {
await nuxt.callHook('builder:generateApp')
}
})
nuxt.hook('app:resolve', (app) => {
if (!existsSync(pagesDir)) {
return
}
app.plugins.push({ src: routerPlugin })
if (app.main.includes('app.tutorial')) {
app.main = resolve(runtimeDir, 'app.vue')
}
})
nuxt.hook('app:templates', async (app) => {
if (!existsSync(pagesDir)) {
return
}
// Resolve routes
const routes = await resolvePagesRoutes(nuxt)
// Add 404 page is not added
const page404 = routes.find(route => route.name === '404')
if (!page404) {
routes.push({
name: '404',
path: '/:catchAll(.*)*',
file: resolve(runtimeDir, '404.vue'),
children: []
})
}
// Add routes.js
app.templates.push({
path: 'routes.js',
compile: () => {
const serializedRoutes = routes.map(route => ({ ...route, component: `{() => import('${route.file}')}` }))
return `export default ${JSON.stringify(serializedRoutes, null, 2).replace(/"{(.+)}"/g, '$1')}`
}
})
})
}
})

View File

@ -1,6 +1,5 @@
<template>
<div>
<!-- TODO: Move this page to @nuxt/nice -->
404 | Page Not Found
</div>
</template>

View File

@ -1,3 +1,3 @@
<template>
<Nuxt />
<RouterView />
</template>

View File

@ -5,11 +5,11 @@ import {
createMemoryHistory,
RouterLink
} from 'vue-router'
import NuxtPage from './NuxtPage.vue'
// @ts-ignore
import NuxtPage from './page.vue'
import type { Plugin } from '@nuxt/app'
// @ts-ignore
import routes from '#build/routes'
// @ts-ignore
export default <Plugin> function router (nuxt) {
const { app } = nuxt

View File

@ -1,10 +1,7 @@
import { resolve, extname, relative } from 'path'
import { extname, relative, resolve } from 'upath'
import { encodePath } from 'ufo'
import { NuxtApp } from './app'
import { Builder } from './builder'
import { resolveFiles } from './utils'
import { Nuxt, resolveFiles } from '@nuxt/kit'
// Check if name has [slug]
export interface NuxtRoute {
name?: string
path: string
@ -12,14 +9,12 @@ export interface NuxtRoute {
children: NuxtRoute[]
}
// TODO: should be const
enum SegmentParserState {
initial,
static,
dynamic,
}
// TODO: should be const
enum SegmentTokenType {
static,
dynamic,
@ -30,19 +25,17 @@ interface SegmentToken {
value: string
}
export async function resolvePagesRoutes (builder: Builder, app: NuxtApp) {
const pagesDir = resolve(app.dir, app.pages!.dir)
const pagesPattern = `${app.pages!.dir}/**/*{${app.extensions.join(',')}}`
const files = await resolveFiles(builder, pagesPattern, app.dir)
export async function resolvePagesRoutes (nuxt: Nuxt) {
const pagesDir = resolve(nuxt.options.srcDir, nuxt.options.dir.pages)
const files = await resolveFiles(pagesDir, `**/*{${nuxt.options.extensions.join(',')}}`)
// Sort to make sure parent are listed first
return generateRoutesFromFiles(files.sort(), pagesDir)
files.sort()
return generateRoutesFromFiles(files, pagesDir)
}
export function generateRoutesFromFiles (
files: string[],
pagesDir: string
): NuxtRoute[] {
export function generateRoutesFromFiles (files: string[], pagesDir: string): NuxtRoute[] {
const routes: NuxtRoute[] = []
for (const file of files) {
@ -57,7 +50,7 @@ export function generateRoutesFromFiles (
children: []
}
// array where routes should be added, useful when adding child routes
// Array where routes should be added, useful when adding child routes
let parent = routes
for (let i = 0; i < segments.length; i++) {

View File

@ -1,7 +1,6 @@
import { resolve } from 'path'
import * as vite from 'vite'
import vuePlugin from '@vitejs/plugin-vue'
import { watch } from 'chokidar'
import { mkdirp, writeFile } from 'fs-extra'
import debounce from 'debounce'
import consola from 'consola'
@ -29,7 +28,7 @@ export async function buildServer (ctx: ViteBuildContext) {
outDir: 'dist/server',
ssr: true,
rollupOptions: {
input: resolve(ctx.nuxt.options.buildDir, 'entry.server.mjs'),
input: resolve(ctx.nuxt.options.buildDir, 'entry.mjs'),
onwarn (warning, rollupWarn) {
if (!['UNUSED_EXTERNAL_IMPORT'].includes(warning.code)) {
rollupWarn(warning)
@ -48,7 +47,7 @@ export async function buildServer (ctx: ViteBuildContext) {
const serverDist = resolve(ctx.nuxt.options.buildDir, 'dist/server')
await mkdirp(serverDist)
await writeFile(resolve(serverDist, 'server.js'), 'module.exports = require("./entry.server")', 'utf8')
await writeFile(resolve(serverDist, 'server.js'), 'module.exports = require("./entry")', 'utf8')
await writeFile(resolve(serverDist, 'client.manifest.json'), 'false', 'utf8')
const onBuild = () => ctx.nuxt.callHook('build:resources', wpfs)
@ -67,19 +66,6 @@ export async function buildServer (ctx: ViteBuildContext) {
await build()
const watcher = watch([
ctx.nuxt.options.buildDir,
ctx.nuxt.options.srcDir,
ctx.nuxt.options.rootDir
], {
ignored: [
'**/dist/server/**'
]
})
watcher.on('change', () => build())
ctx.nuxt.hook('close', async () => {
await watcher.close()
})
ctx.nuxt.hook('builder:watch', () => build())
ctx.nuxt.hook('app:templatesGenerated', () => build())
}

View File

@ -64,7 +64,7 @@ export async function bundle (nuxt: Nuxt) {
nuxt.hook('vite:serverCreated', (server: vite.ViteDevServer) => {
const start = Date.now()
warmupViteServer(server, ['/entry.client.mjs']).then(() => {
warmupViteServer(server, ['/entry.mjs']).then(() => {
consola.info(`Vite warmed up in ${Date.now() - start}ms`)
}).catch(consola.error)
})

View File

@ -23,7 +23,7 @@ function baseConfig (ctx: WebpackConfigContext) {
ctx.config = {
name: ctx.name,
entry: { app: [resolve(options.buildDir, `entry.${ctx.name}`)] },
entry: { app: [resolve(options.buildDir, 'entry')] },
module: { rules: [] },
plugins: [],
externals: [],

View File

@ -1,6 +1,6 @@
<template>
<div>
...
Hello
</div>
</template>

View File

@ -1741,6 +1741,19 @@ __metadata:
languageName: unknown
linkType: soft
"@nuxt/pages@^0.1.0, @nuxt/pages@workspace:packages/pages":
version: 0.0.0-use.local
resolution: "@nuxt/pages@workspace:packages/pages"
dependencies:
"@nuxt/kit": ^0.5.3
globby: ^11.0.3
ufo: ^0.7.5
unbuild: ^0.2.3
upath: ^2.0.1
vue-router: ^4.0.6
languageName: unknown
linkType: soft
"@nuxt/vite-builder@^0.3.3, @nuxt/vite-builder@workspace:packages/vite":
version: 0.0.0-use.local
resolution: "@nuxt/vite-builder@workspace:packages/vite"
@ -10663,6 +10676,7 @@ __metadata:
"@nuxt/app": ^0.3.3
"@nuxt/kit": ^0.5.3
"@nuxt/nitro": ^0.6.3
"@nuxt/pages": ^0.1.0
"@nuxt/vite-builder": ^0.3.3
"@nuxt/webpack-builder": ^0.3.4
"@types/fs-extra": ^9.0.11
@ -14864,7 +14878,7 @@ typescript@^4.2.4:
languageName: node
linkType: hard
"vue-router@npm:^4.0.8":
"vue-router@npm:^4.0.6, vue-router@npm:^4.0.8":
version: 4.0.8
resolution: "vue-router@npm:4.0.8"
dependencies: