mirror of
https://github.com/nuxt/nuxt.git
synced 2024-11-11 08:33:53 +00:00
feat: use virtual filesystem for templates (#292)
Co-authored-by: Daniel Roe <daniel@roe.dev>
This commit is contained in:
parent
ea0d2788a4
commit
bec2720930
@ -1 +0,0 @@
|
||||
export { default } from '#app/entry'
|
@ -6,6 +6,6 @@
|
||||
<body {{ BODY_ATTRS }}>
|
||||
{{ APP }}
|
||||
<% if (nuxt.options.vite && nuxt.options.dev) { %><script type="module" src="/@vite/client"></script>
|
||||
<script type="module" src="/__build/entry.mjs"></script><% } %>
|
||||
<script type="module" src="/__app/entry.mjs"></script><% } %>
|
||||
</body>
|
||||
</html>
|
||||
|
@ -12,7 +12,6 @@ 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()
|
||||
|
@ -21,6 +21,8 @@ export interface Nuxt {
|
||||
|
||||
/** The production or development server */
|
||||
server?: any
|
||||
|
||||
vfs: Record<string, string>
|
||||
}
|
||||
|
||||
export interface NuxtPlugin {
|
||||
|
@ -48,7 +48,7 @@ export async function build (nitroContext: NitroContext) {
|
||||
const htmlSrc = resolve(nitroContext._nuxt.buildDir, `views/${{ 2: 'app', 3: 'document' }[2]}.template.html`)
|
||||
const htmlTemplate = { src: htmlSrc, contents: '', dst: '', compiled: '' }
|
||||
htmlTemplate.dst = htmlTemplate.src.replace(/.html$/, '.mjs').replace('app.', 'document.')
|
||||
htmlTemplate.contents = await readFile(htmlTemplate.src, 'utf-8')
|
||||
htmlTemplate.contents = nitroContext.vfs[htmlTemplate.src] || await readFile(htmlTemplate.src, 'utf-8')
|
||||
await nitroContext._internal.hooks.callHook('nitro:document', htmlTemplate)
|
||||
htmlTemplate.compiled = 'export default ' + serializeTemplate(htmlTemplate.contents)
|
||||
await writeFile(htmlTemplate.dst, htmlTemplate.compiled)
|
||||
|
@ -29,6 +29,7 @@ export interface NitroContext {
|
||||
nuxtHooks: configHooksT
|
||||
ignore: string[]
|
||||
env: Preset
|
||||
vfs: Record<string, string>
|
||||
output: {
|
||||
dir: string
|
||||
serverDir: string
|
||||
@ -82,6 +83,7 @@ export function getNitroContext (nuxtOptions: NuxtOptions, input: NitroInput): N
|
||||
scannedMiddleware: [],
|
||||
ignore: [],
|
||||
env: {},
|
||||
vfs: {},
|
||||
hooks: {},
|
||||
nuxtHooks: {},
|
||||
output: {
|
||||
|
@ -1,9 +1,9 @@
|
||||
import { resolve, join, relative, dirname } from 'upath'
|
||||
import { resolve, join, relative } from 'upath'
|
||||
import globby from 'globby'
|
||||
import lodashTemplate from 'lodash/template'
|
||||
import defu from 'defu'
|
||||
import { tryResolvePath, resolveFiles, Nuxt, NuxtApp, NuxtTemplate, NuxtPlugin } from '@nuxt/kit'
|
||||
import { mkdirp, writeFile, readFile } from 'fs-extra'
|
||||
import { readFile } from 'fs-extra'
|
||||
import * as templateUtils from './template.utils'
|
||||
|
||||
export function createApp (nuxt: Nuxt, options: Partial<NuxtApp> = {}): NuxtApp {
|
||||
@ -43,12 +43,17 @@ export async function generateApp (nuxt: Nuxt, app: NuxtApp) {
|
||||
// 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
|
||||
})))
|
||||
// Compile templates into vfs
|
||||
const templateContext = { utils: templateUtils, nuxt, app }
|
||||
await Promise.all(app.templates.map(async (template) => {
|
||||
const contents = await compileTemplate(template, templateContext)
|
||||
|
||||
const fullPath = resolve(nuxt.options.buildDir, template.path)
|
||||
nuxt.vfs[fullPath] = contents
|
||||
|
||||
const aliasPath = '#build/' + template.path.replace(/\.\w+$/, '')
|
||||
nuxt.vfs[aliasPath] = contents
|
||||
}))
|
||||
|
||||
await nuxt.callHook('app:templatesGenerated', app)
|
||||
}
|
||||
@ -78,23 +83,21 @@ export async function resolveApp (nuxt: Nuxt, app: NuxtApp) {
|
||||
await nuxt.callHook('app:resolve', app)
|
||||
}
|
||||
|
||||
async function generateTemplate (tmpl: NuxtTemplate, destDir: string, ctx) {
|
||||
let compiledSrc: string = ''
|
||||
async function compileTemplate (tmpl: NuxtTemplate, ctx: any) {
|
||||
const data = { ...ctx, ...tmpl.data }
|
||||
if (tmpl.src) {
|
||||
try {
|
||||
const srcContents = await readFile(tmpl.src, 'utf-8')
|
||||
compiledSrc = lodashTemplate(srcContents, {})(data)
|
||||
return 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)
|
||||
if (tmpl.compile) {
|
||||
return tmpl.compile(data)
|
||||
}
|
||||
throw new Error('Invalid template:' + tmpl)
|
||||
}
|
||||
|
||||
async function resolvePlugins (nuxt: Nuxt) {
|
||||
|
@ -10,6 +10,11 @@ export function initNitro (nuxt: Nuxt) {
|
||||
|
||||
nuxt.server = createDevServer(nitroDevContext)
|
||||
|
||||
if (nuxt.vfs) {
|
||||
nitroContext.vfs = nuxt.vfs
|
||||
nitroDevContext.vfs = nuxt.vfs
|
||||
}
|
||||
|
||||
// Connect hooks
|
||||
// @ts-ignore
|
||||
nuxt.hooks.addHooks(nitroContext.nuxtHooks)
|
||||
|
@ -12,7 +12,8 @@ export function createNuxt (options: NuxtOptions): Nuxt {
|
||||
callHook: hooks.callHook,
|
||||
hook: hooks.hook,
|
||||
ready: () => initNuxt(nuxt),
|
||||
close: () => Promise.resolve(hooks.callHook('close', nuxt))
|
||||
close: () => Promise.resolve(hooks.callHook('close', nuxt)),
|
||||
vfs: {}
|
||||
}
|
||||
|
||||
return nuxt
|
||||
|
@ -1,4 +1,3 @@
|
||||
import { resolve } from 'upath'
|
||||
import * as vite from 'vite'
|
||||
import vitePlugin from '@vitejs/plugin-vue'
|
||||
import { cacheDirPlugin } from './plugins/cache-dir'
|
||||
@ -16,10 +15,7 @@ export async function buildClient (ctx: ViteBuildContext) {
|
||||
},
|
||||
build: {
|
||||
outDir: 'dist/client',
|
||||
assetsDir: '.',
|
||||
rollupOptions: {
|
||||
input: resolve(ctx.nuxt.options.buildDir, 'client.js')
|
||||
}
|
||||
assetsDir: '.'
|
||||
},
|
||||
plugins: [
|
||||
replace({ 'process.env': 'import.meta.env' }),
|
||||
|
43
packages/vite/src/plugins/virtual.ts
Normal file
43
packages/vite/src/plugins/virtual.ts
Normal file
@ -0,0 +1,43 @@
|
||||
import { dirname, join } from 'upath'
|
||||
|
||||
import { Plugin } from 'rollup'
|
||||
|
||||
const PREFIX = '\0virtual:'
|
||||
|
||||
export default function virtual (vfs: Record<string, string>): Plugin {
|
||||
const extensions = ['', '.ts', '.vue', '.mjs', '.cjs', '.js', '.json']
|
||||
const resolveWithExt = (id) => {
|
||||
for (const ext of extensions) {
|
||||
const rId = id + ext
|
||||
if (rId in vfs) {
|
||||
return rId
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
return {
|
||||
name: 'virtual',
|
||||
|
||||
resolveId (id, importer) {
|
||||
const resolvedId = resolveWithExt(id)
|
||||
if (resolvedId) { return PREFIX + resolvedId }
|
||||
if (importer && id[0] !== '/') {
|
||||
const importerNoPrefix = importer.startsWith(PREFIX) ? importer.slice(PREFIX.length) : importer
|
||||
const importedDir = dirname(importerNoPrefix)
|
||||
const resolved = resolveWithExt(join(importedDir, id))
|
||||
if (resolved) { return PREFIX + resolved }
|
||||
}
|
||||
return null
|
||||
},
|
||||
|
||||
load (id) {
|
||||
if (!id.startsWith(PREFIX)) { return null }
|
||||
const idNoPrefix = id.slice(PREFIX.length)
|
||||
return {
|
||||
code: vfs[idNoPrefix],
|
||||
map: null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -31,7 +31,6 @@ export async function buildServer (ctx: ViteBuildContext) {
|
||||
outDir: resolve(ctx.nuxt.options.buildDir, 'dist/server'),
|
||||
ssr: true,
|
||||
rollupOptions: {
|
||||
input: resolve(ctx.nuxt.options.buildDir, 'entry.mjs'),
|
||||
output: {
|
||||
entryFileNames: 'server.mjs',
|
||||
preferConst: true,
|
||||
|
@ -7,7 +7,7 @@ export async function warmupViteServer (server: ViteDevServer, entries: string[]
|
||||
if (warmedUrls.has(url)) { return undefined }
|
||||
warmedUrls.add(url)
|
||||
await server.transformRequest(url)
|
||||
const deps = Array.from(server.moduleGraph.urlToModuleMap.get(url).importedModules)
|
||||
const deps = Array.from(server.moduleGraph.urlToModuleMap.get(url)?.importedModules || [])
|
||||
await Promise.all(deps.map(m => warmup(m.url)))
|
||||
}
|
||||
|
||||
|
@ -1,10 +1,12 @@
|
||||
import * as vite from 'vite'
|
||||
import { resolve } from 'upath'
|
||||
import consola from 'consola'
|
||||
import type { Nuxt } from '@nuxt/kit'
|
||||
import type { InlineConfig, SSROptions } from 'vite'
|
||||
import type { Options } from '@vitejs/plugin-vue'
|
||||
import { buildClient } from './client'
|
||||
import { buildServer } from './server'
|
||||
import virtual from './plugins/virtual'
|
||||
import { warmupViteServer } from './utils/warmup'
|
||||
|
||||
export interface ViteOptions extends InlineConfig {
|
||||
@ -33,8 +35,8 @@ export async function bundle (nuxt: Nuxt) {
|
||||
extensions: ['.mjs', '.js', '.ts', '.jsx', '.tsx', '.json', '.vue'],
|
||||
alias: {
|
||||
...nuxt.options.alias,
|
||||
'#build': nuxt.options.buildDir,
|
||||
'#app': nuxt.options.appDir,
|
||||
'/__app': nuxt.options.appDir,
|
||||
'/__build': nuxt.options.buildDir,
|
||||
'~': nuxt.options.srcDir,
|
||||
'@': nuxt.options.srcDir,
|
||||
@ -54,8 +56,14 @@ export async function bundle (nuxt: Nuxt) {
|
||||
},
|
||||
clearScreen: false,
|
||||
build: {
|
||||
emptyOutDir: false
|
||||
emptyOutDir: false,
|
||||
rollupOptions: {
|
||||
input: resolve(nuxt.options.appDir, 'entry')
|
||||
}
|
||||
},
|
||||
plugins: [
|
||||
virtual(nuxt.vfs)
|
||||
],
|
||||
server: {
|
||||
fs: {
|
||||
strict: true,
|
||||
@ -67,8 +75,7 @@ export async function bundle (nuxt: Nuxt) {
|
||||
...nuxt.options.modulesDir
|
||||
]
|
||||
}
|
||||
},
|
||||
plugins: []
|
||||
}
|
||||
} as ViteOptions
|
||||
)
|
||||
}
|
||||
@ -77,7 +84,7 @@ export async function bundle (nuxt: Nuxt) {
|
||||
|
||||
nuxt.hook('vite:serverCreated', (server: vite.ViteDevServer) => {
|
||||
const start = Date.now()
|
||||
warmupViteServer(server, [`/@fs${nuxt.options.buildDir.replace(/\\/g, '/')}/entry.mjs`]).then(() => {
|
||||
warmupViteServer(server, ['/__app/entry']).then(() => {
|
||||
consola.info(`Vite warmed up in ${Date.now() - start}ms`)
|
||||
}).catch(consola.error)
|
||||
})
|
||||
|
@ -44,6 +44,7 @@
|
||||
"webpack-bundle-analyzer": "^4.4.2",
|
||||
"webpack-dev-middleware": "^5.0.0",
|
||||
"webpack-hot-middleware": "^2.25.0",
|
||||
"webpack-virtual-modules": "^0.4.3",
|
||||
"webpackbar": "^5.0.0-3"
|
||||
},
|
||||
"devDependencies": {
|
||||
@ -52,6 +53,7 @@
|
||||
"@types/webpack-bundle-analyzer": "^4.4.1",
|
||||
"@types/webpack-dev-middleware": "^5.0.1",
|
||||
"@types/webpack-hot-middleware": "^2.25.5",
|
||||
"@types/webpack-virtual-modules": "^0",
|
||||
"unbuild": "^0.3.2"
|
||||
}
|
||||
}
|
||||
|
@ -23,7 +23,7 @@ function baseConfig (ctx: WebpackConfigContext) {
|
||||
|
||||
ctx.config = {
|
||||
name: ctx.name,
|
||||
entry: { app: [resolve(options.buildDir, 'entry')] },
|
||||
entry: { app: [resolve(options.appDir, 'entry')] },
|
||||
module: { rules: [] },
|
||||
plugins: [],
|
||||
externals: [],
|
||||
|
@ -5,6 +5,7 @@ import webpack from 'webpack'
|
||||
import Glob from 'glob'
|
||||
import webpackDevMiddleware from 'webpack-dev-middleware'
|
||||
import webpackHotMiddleware from 'webpack-hot-middleware'
|
||||
import VirtualModulesPlugin from 'webpack-virtual-modules'
|
||||
import consola from 'consola'
|
||||
|
||||
import type { Compiler, Watching } from 'webpack'
|
||||
@ -25,6 +26,7 @@ class WebpackBundler {
|
||||
// TODO: change this when pify has better types https://github.com/sindresorhus/pify/pull/76
|
||||
devMiddleware: Record<string, Function & { close?: () => Promise<void>, context?: WebpackDevMiddlewareContext }>
|
||||
hotMiddleware: Record<string, Function>
|
||||
virtualModules: VirtualModulesPlugin
|
||||
mfs?: Compiler['outputFileSystem']
|
||||
__closed?: boolean
|
||||
|
||||
@ -46,6 +48,20 @@ class WebpackBundler {
|
||||
if (this.nuxt.options.dev) {
|
||||
this.mfs = createMFS() as Compiler['outputFileSystem']
|
||||
}
|
||||
|
||||
// Initialize virtual modules instance
|
||||
this.virtualModules = new VirtualModulesPlugin(nuxt.vfs)
|
||||
const writeFiles = () => {
|
||||
for (const filePath in nuxt.vfs) {
|
||||
this.virtualModules.writeModule(filePath, nuxt.vfs[filePath])
|
||||
}
|
||||
}
|
||||
// Workaround to initialize virtual modules
|
||||
nuxt.hook('build:compile', ({ compiler }) => {
|
||||
if (compiler.name === 'server') { writeFiles() }
|
||||
})
|
||||
// Update virtual modules when templates are updated
|
||||
nuxt.hook('app:templatesGenerated', writeFiles)
|
||||
}
|
||||
|
||||
getWebpackConfig (name) {
|
||||
@ -95,6 +111,10 @@ class WebpackBundler {
|
||||
|
||||
// Configure compilers
|
||||
this.compilers = webpackConfigs.map((config) => {
|
||||
// Support virtual modules (input)
|
||||
config.plugins.push(this.virtualModules)
|
||||
|
||||
// Create compiler
|
||||
const compiler = webpack(config)
|
||||
|
||||
// In dev, write files in memory FS
|
||||
|
30
yarn.lock
30
yarn.lock
@ -1473,6 +1473,7 @@ __metadata:
|
||||
"@types/webpack-bundle-analyzer": ^4.4.1
|
||||
"@types/webpack-dev-middleware": ^5.0.1
|
||||
"@types/webpack-hot-middleware": ^2.25.5
|
||||
"@types/webpack-virtual-modules": ^0
|
||||
"@vue/babel-preset-jsx": ^1.2.4
|
||||
"@vue/compiler-sfc": ^3.1.4
|
||||
babel-loader: ^8.2.2
|
||||
@ -1504,6 +1505,7 @@ __metadata:
|
||||
webpack-bundle-analyzer: ^4.4.2
|
||||
webpack-dev-middleware: ^5.0.0
|
||||
webpack-hot-middleware: ^2.25.0
|
||||
webpack-virtual-modules: ^0.4.3
|
||||
webpackbar: ^5.0.0-3
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
@ -2136,6 +2138,15 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/webpack-virtual-modules@npm:^0":
|
||||
version: 0.1.1
|
||||
resolution: "@types/webpack-virtual-modules@npm:0.1.1"
|
||||
dependencies:
|
||||
"@types/webpack": ^4
|
||||
checksum: b84900c1c0638979773c9d8574fa67739e2c7bc0919a720c05eb3a4768e7447572463df9e2a68b2f7261ef6d4af6b4600301f81f05bb1ce24c8d34940f98a760
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/webpack@npm:^4":
|
||||
version: 4.41.30
|
||||
resolution: "@types/webpack@npm:4.41.30"
|
||||
@ -4727,9 +4738,9 @@ __metadata:
|
||||
linkType: hard
|
||||
|
||||
"electron-to-chromium@npm:^1.3.723":
|
||||
version: 1.3.775
|
||||
resolution: "electron-to-chromium@npm:1.3.775"
|
||||
checksum: 6e62b08d9a6d8a7304984cb1e6557346fad497079ba2719cf604125130ef7f0be33ea46046fbedb077cec83e185e1daddf9fde6dfe5ba189a8caafc195349ce8
|
||||
version: 1.3.776
|
||||
resolution: "electron-to-chromium@npm:1.3.776"
|
||||
checksum: 9cdf721325f3c4cdecef157b12395c3749c9539c90320d63030d1fb860670c73e1433b90eddae4b3cf93e1628e5f5cbd9ea42ab615bc3fa6dc4d5215cac47579
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@ -10320,8 +10331,8 @@ fsevents@~2.3.2:
|
||||
linkType: hard
|
||||
|
||||
"rollup@npm:^2.38.5, rollup@npm:^2.52.0, rollup@npm:^2.53.1":
|
||||
version: 2.53.1
|
||||
resolution: "rollup@npm:2.53.1"
|
||||
version: 2.53.2
|
||||
resolution: "rollup@npm:2.53.2"
|
||||
dependencies:
|
||||
fsevents: ~2.3.2
|
||||
dependenciesMeta:
|
||||
@ -10329,7 +10340,7 @@ fsevents@~2.3.2:
|
||||
optional: true
|
||||
bin:
|
||||
rollup: dist/bin/rollup
|
||||
checksum: 7c63a7251e87715795a4141371f4f7b31ce46c7dd8273a0f4b5240977eb7ecaddd1e8d0fdaf8eabfc7cf73075cc9d83e03c67b89e56f31a2e85cf1089e9fc34f
|
||||
checksum: 41d32b1d44066f3e8eeb5239013397e3df74dbb37489aaa77f8357059b5998cb4af0108fb72b048d265ae5d88de933b0732007c07e5d5687f5ee3ae44fab9eee
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@ -12149,6 +12160,13 @@ fsevents@~2.3.2:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"webpack-virtual-modules@npm:^0.4.3":
|
||||
version: 0.4.3
|
||||
resolution: "webpack-virtual-modules@npm:0.4.3"
|
||||
checksum: 9d25285c2fcbfb54515e4fc0e12eb6261ad9565a6eb36e679e5277a0a4728425c59d75ae81f3d1536c73a6d446cb33338153b9e61b4f6c2c04c04394e57f06ff
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"webpack@npm:^5, webpack@npm:^5.1.0, webpack@npm:^5.38.1, webpack@npm:^5.44.0":
|
||||
version: 5.44.0
|
||||
resolution: "webpack@npm:5.44.0"
|
||||
|
Loading…
Reference in New Issue
Block a user