feat: support auto import of plugins (#169)

This commit is contained in:
Sébastien Chopin 2021-02-19 02:08:45 +01:00 committed by GitHub
parent 498a234039
commit bece3b85ab
11 changed files with 88 additions and 27 deletions

View File

@ -1,9 +1,12 @@
import '@nuxt/nitro/dist/runtime/app/nitro.client' import '@nuxt/nitro/dist/runtime/app/nitro.client'
import logs from 'nuxt/app/plugins/logs.client.dev' import logs from 'nuxt/app/plugins/logs.client.dev'
import progress from 'nuxt/app/plugins/progress.client' import progress from 'nuxt/app/plugins/progress.client'
<% const plugins = app.plugins.filter(p => p.mode === 'client').map(p => p.src) %>
<%= nxt.importSources(plugins) %>
const plugins = [ const plugins = [
progress progress,
<%= plugins.map(nxt.importName).join(',\n\t') %>
] ]
if (process.dev) { if (process.dev) {

View File

@ -3,9 +3,13 @@ import router from 'nuxt/app/plugins/router'
import vuex from 'nuxt/app/plugins/vuex' import vuex from 'nuxt/app/plugins/vuex'
import legacy from 'nuxt/app/plugins/legacy' import legacy from 'nuxt/app/plugins/legacy'
<% const plugins = app.plugins.filter(p => p.mode === 'all').map(p => p.src) %>
<%= nxt.importSources(plugins) %>
export default [ export default [
head, head,
router, router,
vuex, vuex,
legacy legacy,
<%= plugins.map(nxt.importName).join(',\n\t') %>
] ]

View File

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

View File

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

View File

@ -81,6 +81,7 @@ export function createNuxt (options: CreateOptions) {
} }
export function applyPlugin (nuxt: Nuxt, plugin: Plugin) { export function applyPlugin (nuxt: Nuxt, plugin: Plugin) {
if (typeof plugin !== 'function') { return }
return callWithNuxt(nuxt, () => plugin(nuxt)) return callWithNuxt(nuxt, () => plugin(nuxt))
} }

View File

@ -25,6 +25,7 @@ export default <Plugin> function router (nuxt) {
routes routes
}) })
app.use(router) app.use(router)
nuxt.provide('router', router)
const previousRoute = shallowRef(router.currentRoute.value) const previousRoute = shallowRef(router.currentRoute.value)
router.afterEach((_to, from) => { router.afterEach((_to, from) => {
@ -35,7 +36,7 @@ export default <Plugin> function router (nuxt) {
get: () => previousRoute.value get: () => previousRoute.value
}) })
nuxt.hooks.hook('app:created', async () => { nuxt.hook('app:created', async () => {
if (process.server) { if (process.server) {
router.push(nuxt.ssrContext.url) router.push(nuxt.ssrContext.url)
} }

View File

@ -2,12 +2,13 @@ import { resolve } from 'path'
import defu from 'defu' import defu from 'defu'
import { Builder } from './builder' import { Builder } from './builder'
import { NuxtRoute, resolvePagesRoutes } from './pages' import { NuxtRoute, resolvePagesRoutes } from './pages'
import { NuxtPlugin, resolvePlugins } from './plugins'
export interface NuxtApp { export interface NuxtApp {
main: string main: string
routes: NuxtRoute[] routes: NuxtRoute[]
dir: string dir: string
extensions: string[] extensions: string[]
plugins: NuxtPlugin[]
templates: Record<string, string> templates: Record<string, string>
pages?: { pages?: {
dir: string dir: string
@ -26,6 +27,7 @@ export async function createApp (
dir: nuxt.options.srcDir, dir: nuxt.options.srcDir,
extensions: nuxt.options.extensions, extensions: nuxt.options.extensions,
routes: [], routes: [],
plugins: [],
templates: {}, templates: {},
pages: { pages: {
dir: 'pages' dir: 'pages'
@ -54,8 +56,6 @@ export async function createApp (
children: [] children: []
}) })
} }
// TODO: Hook to extend routes
app.templates.routes = serializeRoutes(app.routes)
} }
// Fallback app.main // Fallback app.main
@ -65,24 +65,8 @@ export async function createApp (
app.main = resolve(nuxt.options.appDir, 'app.tutorial.vue') app.main = resolve(nuxt.options.appDir, 'app.tutorial.vue')
} }
// Resolve plugins/
app.plugins = await resolvePlugins(builder, app)
return app return app
} }
function serializeRoutes (routes: NuxtRoute[]) {
return JSON.stringify(
routes.map(formatRoute),
null,
2
).replace(/"{(.+)}"/g, '$1')
}
function formatRoute (route: NuxtRoute) {
return {
name: route.name,
path: route.path,
children: route.children.map(formatRoute),
// TODO: avoid exposing to prod, using process.env.NODE_ENV ?
__file: route.file,
component: `{() => import('${route.file}' /* webpackChunkName: '${route.name}' */)}`
}
}

View File

@ -65,6 +65,7 @@ function watch (builder: Builder) {
nuxtAppWatcher.watchAll(debounce(() => compileTemplates(builder.templates, nuxt.options.buildDir), 100)) nuxtAppWatcher.watchAll(debounce(() => compileTemplates(builder.templates, nuxt.options.buildDir), 100))
// Watch user app // Watch user app
// TODO: handle multiples app dirs
const appPattern = `${builder.app.dir}/**/*.{${nuxt.options.extensions.join(',')}}` const appPattern = `${builder.app.dir}/**/*.{${nuxt.options.extensions.join(',')}}`
const appWatcher = createWatcher(appPattern, { ...options, cwd: builder.app.dir }, ignore) const appWatcher = createWatcher(appPattern, { ...options, cwd: builder.app.dir }, ignore)
// appWatcher.debug('srcDir') // appWatcher.debug('srcDir')
@ -73,6 +74,8 @@ function watch (builder: Builder) {
appWatcher.watch(/^(A|a)pp\.[a-z]{2,3}/, refreshTemplates, ['add', 'unlink']) appWatcher.watch(/^(A|a)pp\.[a-z]{2,3}/, refreshTemplates, ['add', 'unlink'])
// Watch for page changes // Watch for page changes
appWatcher.watch(new RegExp(`^${nuxt.options.dir.pages}/`), refreshTemplates, ['add', 'unlink']) appWatcher.watch(new RegExp(`^${nuxt.options.dir.pages}/`), refreshTemplates, ['add', 'unlink'])
// Watch for plugins changes
appWatcher.watch(/^plugins/, refreshTemplates, ['add', 'unlink'])
// Shared Watcher // Shared Watcher
const watchHookDebounced = debounce((event, file) => builder.nuxt.callHook('builder:watch', event, file), 100) const watchHookDebounced = debounce((event, file) => builder.nuxt.callHook('builder:watch', event, file), 100)
@ -84,9 +87,11 @@ export async function generate (builder: Builder) {
const { nuxt } = builder const { nuxt } = builder
builder.app = await createApp(builder) builder.app = await createApp(builder)
// Todo: Call app:created hook
const templatesDir = join(builder.nuxt.options.appDir, '_templates') const templatesDir = join(builder.nuxt.options.appDir, '_templates')
const appTemplates = await scanTemplates(templatesDir, templateData(builder)) const appTemplates = await scanTemplates(templatesDir, templateData(builder))
// Todo: Call app:templates hook
builder.templates = [...appTemplates] builder.templates = [...appTemplates]

View File

@ -0,0 +1,32 @@
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')
export const importName = (src: string) => `${camelCase(basename(src, extname(src))).replace(/[^a-zA-Z?\d\s:]/g, '')}_${hash(src)}`
export const importSources = (sources: string | string[], { lazy = false } = {}) => {
if (!Array.isArray(sources)) {
sources = [sources]
}
return sources.map((src) => {
if (lazy) {
return `const ${importName(src)} = () => import('${src}' /* webpackChunkName: '${src}' */)`
}
return `import ${importName(src)} from '${src}'`
}).join('\n')
}
export const serializeRoute = (route: NuxtRoute) => {
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

@ -0,0 +1,26 @@
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) => {
const [, mode = 'all'] = src.match(MODES_REGEX) || []
return mode
}
export function resolvePlugins (builder: Builder, app: NuxtApp) {
return resolveFiles(builder, 'plugins/**/*.{js,ts}', app.dir)
.then(plugins => plugins.map((src) => {
return {
src,
mode: getPluginMode(src)
}
}))
}

View File

@ -2,6 +2,7 @@ import { join, relative, dirname } from 'path'
import fsExtra from 'fs-extra' import fsExtra from 'fs-extra'
import globby from 'globby' import globby from 'globby'
import lodashTemplate from 'lodash/template' import lodashTemplate from 'lodash/template'
import * as nxt from './nxt'
export interface NuxtTemplate { export interface NuxtTemplate {
src: string // Absolute path to source file src: string // Absolute path to source file
@ -13,7 +14,8 @@ export function templateData (builder) {
return { return {
globals: builder.globals, globals: builder.globals,
app: builder.app, app: builder.app,
nuxtOptions: builder.nuxt.options nuxtOptions: builder.nuxt.options,
nxt
} }
} }