mirror of
https://github.com/nuxt/nuxt.git
synced 2024-11-22 05:35:13 +00:00
feat(nitro, vite): use native module (#252)
Co-authored-by: Daniel Roe <daniel@roe.dev>
This commit is contained in:
parent
569d4f3cb3
commit
6318438415
5
.github/workflows/test-compat.yml
vendored
5
.github/workflows/test-compat.yml
vendored
@ -43,7 +43,4 @@ jobs:
|
||||
run: yarn build
|
||||
|
||||
- name: Test
|
||||
run: TEST_COMPAT=1 yarn jest --ci
|
||||
|
||||
# - name: Coverage
|
||||
# uses: codecov/codecov-action@v1
|
||||
run: TEST_COMPAT=1 yarn test:presets
|
||||
|
5
.github/workflows/test.yml
vendored
5
.github/workflows/test.yml
vendored
@ -44,10 +44,7 @@ jobs:
|
||||
run: yarn build
|
||||
|
||||
- name: Test
|
||||
run: yarn jest --ci
|
||||
|
||||
# - name: Coverage
|
||||
# uses: codecov/codecov-action@v1
|
||||
run: yarn test:presets
|
||||
|
||||
- name: Release Edge
|
||||
if: github.event_name == 'push'
|
||||
|
@ -2,9 +2,9 @@
|
||||
|
||||
> How to deploy Nuxt to a Node.js host with Nuxt Nitro
|
||||
|
||||
- Support for ultra-minimal SSR build
|
||||
- Zero millisecond cold start
|
||||
- More configuration required
|
||||
- Support for ultra-minimal SSR build
|
||||
- Zero millisecond cold start
|
||||
- More configuration required
|
||||
|
||||
## Setup
|
||||
|
||||
@ -28,7 +28,7 @@ This `.output` folder can be deployed to your Node.js host and the server can be
|
||||
To start the server in production mode, run:
|
||||
|
||||
```bash
|
||||
node .output/server
|
||||
node .output/server/index.mjs
|
||||
```
|
||||
|
||||
For example, using `pm2`:
|
||||
|
@ -1,8 +1,8 @@
|
||||
# Node.js server
|
||||
|
||||
- Default preset if none is specified or auto-detected
|
||||
- Loads only the chunks required to render the request for optimal cold start timing
|
||||
- Useful for debugging
|
||||
- Default preset if none is specified or auto-detected
|
||||
- Loads only the chunks required to render the request for optimal cold start timing
|
||||
- Useful for debugging
|
||||
|
||||
### Entrypoint
|
||||
|
||||
@ -11,7 +11,7 @@ With `{ preset: 'server' }` the result will be an entrypoint that launches a rea
|
||||
#### Example
|
||||
|
||||
```bash
|
||||
node .output/server
|
||||
node .output/server/index.mjs
|
||||
# > Load chunks/nitro/server (10.405923ms)
|
||||
# > Cold Start (26.289817ms)
|
||||
# Listening on http://localhost:3000
|
||||
|
@ -7,6 +7,6 @@
|
||||
"scripts": {
|
||||
"dev": "nu dev",
|
||||
"build": "nu build",
|
||||
"start": "node .output/server"
|
||||
"start": "node .output/server/index.mjs"
|
||||
}
|
||||
}
|
||||
|
@ -7,6 +7,6 @@
|
||||
"scripts": {
|
||||
"dev": "nu dev",
|
||||
"build": "nu build",
|
||||
"start": "node .output/server"
|
||||
"start": "node .output/server/index.mjs"
|
||||
}
|
||||
}
|
||||
|
@ -7,6 +7,6 @@
|
||||
"scripts": {
|
||||
"dev": "nu dev",
|
||||
"build": "nu build",
|
||||
"start": "node .output/server"
|
||||
"start": "node .output/server/index.mjs"
|
||||
}
|
||||
}
|
||||
|
@ -7,6 +7,6 @@
|
||||
"scripts": {
|
||||
"dev": "nu dev",
|
||||
"build": "nu build",
|
||||
"start": "node .output/server"
|
||||
"start": "node .output/server/index.mjs"
|
||||
}
|
||||
}
|
||||
|
@ -7,6 +7,6 @@
|
||||
"scripts": {
|
||||
"dev": "nu dev",
|
||||
"build": "nu build",
|
||||
"start": "node .output/server"
|
||||
"start": "node .output/server/index.mjs"
|
||||
}
|
||||
}
|
||||
|
@ -7,6 +7,6 @@
|
||||
"scripts": {
|
||||
"dev": "nu dev",
|
||||
"build": "nu build",
|
||||
"start": "node .output/server"
|
||||
"start": "node .output/server/index.mjs"
|
||||
}
|
||||
}
|
||||
|
@ -7,6 +7,6 @@
|
||||
"scripts": {
|
||||
"dev": "nu dev",
|
||||
"build": "nu build",
|
||||
"start": "node .output/server"
|
||||
"start": "node .output/server/index.mjs"
|
||||
}
|
||||
}
|
||||
|
@ -8,6 +8,6 @@
|
||||
"scripts": {
|
||||
"dev": "nu dev",
|
||||
"build": "nu build",
|
||||
"start": "node .output/server"
|
||||
"start": "node .output/server/index.mjs"
|
||||
}
|
||||
}
|
||||
|
@ -1,9 +0,0 @@
|
||||
module.exports = {
|
||||
testEnvironment: 'node',
|
||||
transform: {
|
||||
'\\.[jt]sx?$': './scripts/jest-transform.mjs'
|
||||
},
|
||||
testPathIgnorePatterns: [
|
||||
'.output/.*'
|
||||
]
|
||||
}
|
15
package.json
15
package.json
@ -16,8 +16,9 @@
|
||||
"example": "yarn workspace example-$0 dev",
|
||||
"example:build": "yarn workspace example-$0 build",
|
||||
"lint": "eslint --ext .vue,.ts,.js .",
|
||||
"test": "yarn lint && jest",
|
||||
"test:compat": "TEST_COMPAT=1 jest",
|
||||
"test": "yarn lint && yarn test:presets",
|
||||
"test:presets": "mocha test/presets/*.mjs",
|
||||
"test:compat": "TEST_COMPAT=1 yarn test:presets",
|
||||
"version": "yarn && git add yarn.lock"
|
||||
},
|
||||
"resolutions": {
|
||||
@ -26,17 +27,23 @@
|
||||
"devDependencies": {
|
||||
"@nuxtjs/eslint-config": "^6.0.1",
|
||||
"@nuxtjs/eslint-config-typescript": "^6.0.1",
|
||||
"@types/jest": "^26.0.24",
|
||||
"@types/chai": "^4.2.21",
|
||||
"@types/jsdom": "^16",
|
||||
"@types/mocha": "^8.2.3",
|
||||
"@types/node": "^14.17.5",
|
||||
"@types/object-hash": "^2",
|
||||
"chai": "^4.3.4",
|
||||
"esbuild": "^0.12.15",
|
||||
"eslint": "^7.30.0",
|
||||
"eslint-plugin-jsdoc": "^35.4.3",
|
||||
"execa": "^5.1.1",
|
||||
"globby": "^11.0.4",
|
||||
"jest": "^27.0.6",
|
||||
"jiti": "^1.10.1",
|
||||
"jsdom": "^16.6.0",
|
||||
"lerna": "^4.0.0",
|
||||
"mocha": "^9.0.2",
|
||||
"object-hash": "^2.2.0",
|
||||
"ts-mocha": "^8.0.0",
|
||||
"typescript": "^4.3.5",
|
||||
"unbuild": "^0.3.2",
|
||||
"upath": "^2.0.1"
|
||||
|
@ -1,10 +1,8 @@
|
||||
import head from '#app/plugins/head'
|
||||
import preload from '#app/plugins/preload.server'
|
||||
|
||||
<%= utils.importSources(app.plugins.map(p => p.src)) %>
|
||||
|
||||
const commonPlugins = [
|
||||
head,
|
||||
<%= app.plugins.filter(p => !p.mode || p.mode === 'all').map(p => utils.importName(p.src)).join(',\n ') %>
|
||||
]
|
||||
|
||||
|
@ -47,10 +47,10 @@ export async function build (nitroContext: NitroContext) {
|
||||
// Compile html template
|
||||
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$/, '.js').replace('app.', 'document.')
|
||||
htmlTemplate.dst = htmlTemplate.src.replace(/.html$/, '.mjs').replace('app.', 'document.')
|
||||
htmlTemplate.contents = await readFile(htmlTemplate.src, 'utf-8')
|
||||
await nitroContext._internal.hooks.callHook('nitro:document', htmlTemplate)
|
||||
htmlTemplate.compiled = 'module.exports = ' + serializeTemplate(htmlTemplate.contents)
|
||||
htmlTemplate.compiled = 'export default ' + serializeTemplate(htmlTemplate.contents)
|
||||
await writeFile(htmlTemplate.dst, htmlTemplate.compiled)
|
||||
|
||||
nitroContext.rollupConfig = getRollupConfig(nitroContext)
|
||||
|
@ -1,6 +1,6 @@
|
||||
import fetch from 'node-fetch'
|
||||
import { resolve } from 'upath'
|
||||
import { resolveModule } from '@nuxt/kit'
|
||||
import { readFile, writeFile } from 'fs-extra'
|
||||
import { build, generate, prepare } from './build'
|
||||
import { getNitroContext, NitroContext } from './context'
|
||||
import { createDevServer } from './server/dev'
|
||||
@ -55,16 +55,25 @@ export default function nuxt2CompatModule () {
|
||||
|
||||
// Nitro client plugin
|
||||
this.addPlugin({
|
||||
fileName: 'nitro.client.js',
|
||||
src: resolve(nitroContext._internal.runtimeDir, 'app/nitro.client.js')
|
||||
fileName: 'nitro.client.mjs',
|
||||
src: resolve(nitroContext._internal.runtimeDir, 'app/nitro.client.mjs')
|
||||
})
|
||||
|
||||
// Fix module resolution
|
||||
nuxt.hook('webpack:config', (configs) => {
|
||||
for (const config of configs) {
|
||||
if (config.name === 'client') {
|
||||
config.resolve.alias.ufo = resolveModule('ufo/dist/index.mjs')
|
||||
config.resolve.alias.ufo = 'ufo/dist/index.mjs'
|
||||
config.resolve.alias.ohmyfetch = 'ohmyfetch/dist/index.mjs'
|
||||
}
|
||||
})
|
||||
|
||||
// Generate mjs resources
|
||||
nuxt.hook('build:compiled', async ({ name }) => {
|
||||
if (name === 'server') {
|
||||
await writeFile(resolve(nuxt.options.buildDir, 'dist/server/server.mjs'), 'export { default } from "./server.js"', 'utf8')
|
||||
} else if (name === 'client') {
|
||||
const manifest = await readFile(resolve(nuxt.options.buildDir, 'dist/server/client.manifest.json'), 'utf8')
|
||||
await writeFile(resolve(nuxt.options.buildDir, 'dist/server/client.manifest.mjs'), 'export default ' + manifest, 'utf8')
|
||||
}
|
||||
})
|
||||
|
||||
|
@ -22,7 +22,7 @@ if ('serviceWorker' in navigator) {
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<link rel="prefetch" href="${routerBase}sw.js">
|
||||
<link rel="prefetch" href="${routerBase}_server/index.js">
|
||||
<link rel="prefetch" href="${routerBase}_server/index.mjs">
|
||||
<script>
|
||||
async function register () {
|
||||
const registration = await navigator.serviceWorker.register('${routerBase}sw.js')
|
||||
@ -65,7 +65,7 @@ if ('serviceWorker' in navigator) {
|
||||
tmpl.compiled = tmpl.compiled.replace('</body>', script + '</body>')
|
||||
},
|
||||
async 'nitro:compiled' ({ output }: NitroContext) {
|
||||
await writeFile(resolve(output.publicDir, 'sw.js'), `self.importScripts('${input._nuxt.routerBase}_server/index.js');`)
|
||||
await writeFile(resolve(output.publicDir, 'sw.js'), `self.importScripts('${input._nuxt.routerBase}_server/index.mjs');`)
|
||||
|
||||
// Temp fix
|
||||
await writeFile(resolve(output.publicDir, 'index.html'), html)
|
||||
|
@ -11,7 +11,7 @@ export const cloudflare: NitroPreset = extendPreset(worker, {
|
||||
],
|
||||
hooks: {
|
||||
async 'nitro:compiled' ({ output, _nuxt }: NitroContext) {
|
||||
await writeFile(resolve(output.dir, 'package.json'), JSON.stringify({ private: true, main: './server/index.js' }, null, 2))
|
||||
await writeFile(resolve(output.dir, 'package.json'), JSON.stringify({ private: true, main: './server/index.mjs' }, null, 2))
|
||||
await writeFile(resolve(output.dir, 'package-lock.json'), JSON.stringify({ lockfileVersion: 1 }, null, 2))
|
||||
let inDir = prettyPath(_nuxt.rootDir)
|
||||
if (inDir) {
|
||||
|
@ -8,7 +8,7 @@ export const server: NitroPreset = extendPreset(node, {
|
||||
serveStatic: true,
|
||||
hooks: {
|
||||
'nitro:compiled' ({ output }: NitroContext) {
|
||||
consola.success('Ready to run', hl('node ' + prettyPath(output.serverDir)))
|
||||
consola.success('Ready to run', hl('node ' + prettyPath(output.serverDir) + '/index.mjs'))
|
||||
}
|
||||
}
|
||||
})
|
||||
|
@ -60,7 +60,7 @@ export const getRollupConfig = (nitroContext: NitroContext) => {
|
||||
delete env.alias['node-fetch'] // FIX ME
|
||||
|
||||
if (nitroContext.sourceMap) {
|
||||
env.polyfill.push('source-map-support/register')
|
||||
env.polyfill.push('source-map-support/register.js')
|
||||
}
|
||||
|
||||
const buildServerDir = join(nitroContext._nuxt.buildDir, 'dist/server')
|
||||
@ -70,7 +70,7 @@ export const getRollupConfig = (nitroContext: NitroContext) => {
|
||||
input: resolvePath(nitroContext, nitroContext.entry),
|
||||
output: {
|
||||
dir: nitroContext.output.serverDir,
|
||||
entryFileNames: 'index.js',
|
||||
entryFileNames: 'index.mjs',
|
||||
chunkFileNames (chunkInfo) {
|
||||
let prefix = ''
|
||||
const modules = Object.keys(chunkInfo.modules)
|
||||
@ -88,10 +88,10 @@ export const getRollupConfig = (nitroContext: NitroContext) => {
|
||||
} else if (lastModule.includes('assets')) {
|
||||
prefix = 'assets'
|
||||
}
|
||||
return join('chunks', prefix, '[name].js')
|
||||
return join('chunks', prefix, '[name].mjs')
|
||||
},
|
||||
inlineDynamicImports: nitroContext.inlineDynamicImports,
|
||||
format: 'cjs',
|
||||
format: 'esm',
|
||||
exports: 'auto',
|
||||
intro: '',
|
||||
outro: '',
|
||||
@ -232,6 +232,7 @@ export const getRollupConfig = (nitroContext: NitroContext) => {
|
||||
'~~',
|
||||
'@@/',
|
||||
'virtual:',
|
||||
'ohmyfetch', // TODO: Webpack externals forces default import!
|
||||
nitroContext._internal.runtimeDir,
|
||||
nitroContext._nuxt.srcDir,
|
||||
nitroContext._nuxt.rootDir,
|
||||
@ -241,7 +242,8 @@ export const getRollupConfig = (nitroContext: NitroContext) => {
|
||||
],
|
||||
traceOptions: {
|
||||
base: '/',
|
||||
processCwd: nitroContext._nuxt.rootDir
|
||||
processCwd: nitroContext._nuxt.rootDir,
|
||||
exportsOnly: true
|
||||
}
|
||||
})))
|
||||
}
|
||||
@ -252,7 +254,13 @@ export const getRollupConfig = (nitroContext: NitroContext) => {
|
||||
preferBuiltins: true,
|
||||
rootDir: nitroContext._nuxt.rootDir,
|
||||
moduleDirectories,
|
||||
mainFields: ['main'] // Force resolve CJS (@vue/runtime-core ssrUtils)
|
||||
// 'module' is intentionally not supported because of externals
|
||||
mainFields: ['main'],
|
||||
exportConditions: [
|
||||
'default',
|
||||
'module',
|
||||
'import'
|
||||
]
|
||||
}))
|
||||
|
||||
// Automatically mock unresolved externals
|
||||
|
@ -1,5 +1,4 @@
|
||||
import consola from 'consola'
|
||||
import { normalize } from 'upath'
|
||||
|
||||
const internalRegex = /^\.|\?|\.[mc]?js$|.ts$|.json$/
|
||||
|
||||
@ -10,7 +9,7 @@ export function autoMock () {
|
||||
if (src && !internalRegex.test(src)) {
|
||||
consola.warn('Auto mock external ', src)
|
||||
return {
|
||||
id: normalize(require.resolve('unenv/runtime/mock/proxy'))
|
||||
id: 'unenv/runtime/mock/proxy'
|
||||
}
|
||||
}
|
||||
return null
|
||||
|
@ -103,16 +103,20 @@ export default function dynamicRequire(id) {
|
||||
|
||||
function TMPL_LAZY ({ chunks }: TemplateContext) {
|
||||
return `
|
||||
function dynamicWebpackModule(id, getChunk) {
|
||||
function dynamicWebpackModule(id, getChunk, ids) {
|
||||
return function (module, exports, require) {
|
||||
const r = getChunk()
|
||||
if (r instanceof Promise) {
|
||||
if (typeof r.then === 'function') {
|
||||
module.exports = r.then(r => {
|
||||
const realModule = { exports: {}, require };
|
||||
r.modules[id](realModule, realModule.exports, realModule.require);
|
||||
for (const _id of ids) {
|
||||
if (_id === id) continue;
|
||||
r.modules[_id](realModule, realModule.exports, realModule.require);
|
||||
}
|
||||
return realModule.exports;
|
||||
});
|
||||
} else {
|
||||
} else if (r && typeof r.modules[id] === 'function') {
|
||||
r.modules[id](module, exports, require);
|
||||
}
|
||||
};
|
||||
@ -121,7 +125,7 @@ function dynamicWebpackModule(id, getChunk) {
|
||||
function webpackChunk (meta, getChunk) {
|
||||
const chunk = { ...meta, modules: {} };
|
||||
for (const id of meta.moduleIds) {
|
||||
chunk.modules[id] = dynamicWebpackModule(id, getChunk);
|
||||
chunk.modules[id] = dynamicWebpackModule(id, getChunk, meta.moduleIds);
|
||||
};
|
||||
return chunk;
|
||||
};
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { resolve, dirname, normalize } from 'upath'
|
||||
import { resolve, dirname } from 'upath'
|
||||
import { copyFile, mkdirp } from 'fs-extra'
|
||||
import { nodeFileTrace, NodeFileTraceOptions } from '@vercel/nft'
|
||||
import type { Plugin } from 'rollup'
|
||||
@ -13,11 +13,11 @@ export interface NodeExternalsOptions {
|
||||
}
|
||||
|
||||
export function externals (opts: NodeExternalsOptions): Plugin {
|
||||
const resolvedExternals = new Set<string>()
|
||||
const trackedExternals = new Set<string>()
|
||||
|
||||
return {
|
||||
name: 'node-externals',
|
||||
resolveId (id) {
|
||||
async resolveId (id, importer, options) {
|
||||
// Internals
|
||||
if (!id || id.startsWith('\x00') || id.includes('?') || id.startsWith('#')) {
|
||||
return null
|
||||
@ -44,11 +44,10 @@ export function externals (opts: NodeExternalsOptions): Plugin {
|
||||
}
|
||||
}
|
||||
|
||||
// Try to resolve for nft
|
||||
// Track externals
|
||||
if (opts.trace !== false) {
|
||||
let _resolvedId = _id
|
||||
try { _resolvedId = normalize(require.resolve(_resolvedId, { paths: opts.moduleDirectories })) } catch (_err) { }
|
||||
resolvedExternals.add(_resolvedId)
|
||||
const resolved = await this.resolve(id, importer, { ...options, skipSelf: true }).then(r => r.id)
|
||||
trackedExternals.add(resolved)
|
||||
}
|
||||
|
||||
return {
|
||||
@ -58,14 +57,25 @@ export function externals (opts: NodeExternalsOptions): Plugin {
|
||||
},
|
||||
async buildEnd () {
|
||||
if (opts.trace !== false) {
|
||||
const tracedFiles = await nodeFileTrace(Array.from(resolvedExternals), opts.traceOptions)
|
||||
const tracedFiles = await nodeFileTrace(Array.from(trackedExternals), opts.traceOptions)
|
||||
.then(r => r.fileList.map(f => resolve(opts.traceOptions.base, f)))
|
||||
.then(r => r.filter(file => file.includes('node_modules')))
|
||||
|
||||
// // Find all unique package names
|
||||
const pkgs = new Set<string>()
|
||||
for (const file of tracedFiles) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const [_, baseDir, pkgName, _importPath] = /(.+\/node_modules\/)([^/]+)\/(.*)/.exec(file)
|
||||
pkgs.add(resolve(baseDir, pkgName, 'package.json'))
|
||||
}
|
||||
|
||||
for (const pkg of pkgs) {
|
||||
if (!tracedFiles.includes(pkg)) {
|
||||
tracedFiles.push(pkg)
|
||||
}
|
||||
}
|
||||
|
||||
await Promise.all(tracedFiles.map(async (file) => {
|
||||
if (!file.includes('node_modules')) {
|
||||
return
|
||||
}
|
||||
// TODO: Minify package.json
|
||||
const src = resolve(opts.traceOptions.base, file)
|
||||
const dst = resolve(opts.outDir, 'node_modules', file.split('node_modules/').pop())
|
||||
await mkdirp(dirname(dst))
|
||||
|
@ -32,8 +32,12 @@ export function staticAssets (context: NitroContext) {
|
||||
'#static': `
|
||||
import { promises } from 'fs'
|
||||
import { resolve } from 'path'
|
||||
import { dirname } from 'path'
|
||||
import { fileURLToPath } from 'url'
|
||||
import assets from '#static-assets'
|
||||
|
||||
const mainDir = dirname(fileURLToPath(globalThis.entryURL))
|
||||
|
||||
export function readAsset (id) {
|
||||
return promises.readFile(resolve(mainDir, getAsset(id).path))
|
||||
}
|
||||
@ -50,7 +54,7 @@ export function dirnames (): Plugin {
|
||||
name: 'dirnames',
|
||||
renderChunk (code, chunk) {
|
||||
return {
|
||||
code: code + (chunk.isEntry ? 'globalThis.mainDir="undefined"!=typeof __dirname?__dirname:require.main.filename;' : ''),
|
||||
code: code + (chunk.isEntry ? 'globalThis.entryURL = import.meta.url' : ''),
|
||||
map: null
|
||||
}
|
||||
}
|
||||
|
@ -2,7 +2,7 @@ import { createRenderer } from 'vue-bundle-renderer'
|
||||
import devalue from '@nuxt/devalue'
|
||||
import { runtimeConfig } from './config'
|
||||
// @ts-ignore
|
||||
import htmlTemplate from '#build/views/document.template.js'
|
||||
import htmlTemplate from '#build/views/document.template.mjs'
|
||||
|
||||
function _interopDefault (e) { return e && typeof e === 'object' && 'default' in e ? e.default : e }
|
||||
|
||||
@ -17,9 +17,9 @@ async function loadRenderer () {
|
||||
// @ts-ignore
|
||||
const { renderToString } = await import('#nitro-renderer')
|
||||
// @ts-ignore
|
||||
const createApp = await import('#build/dist/server/server')
|
||||
const createApp = await import('#build/dist/server/server.mjs')
|
||||
// @ts-ignore
|
||||
const clientManifest = await import('#build/dist/server/client.manifest.json')
|
||||
const clientManifest = await import('#build/dist/server/client.manifest.mjs')
|
||||
_renderer = createRenderer(_interopDefault(createApp), {
|
||||
clientManifest: _interopDefault(clientManifest),
|
||||
renderToString
|
||||
|
@ -14,7 +14,7 @@ import type { NitroContext } from '../context'
|
||||
|
||||
export function createDevServer (nitroContext: NitroContext) {
|
||||
// Worker
|
||||
const workerEntry = resolve(nitroContext.output.dir, nitroContext.output.serverDir, 'index.js')
|
||||
const workerEntry = resolve(nitroContext.output.dir, nitroContext.output.serverDir, 'index.mjs')
|
||||
let pendingWorker: Worker | null
|
||||
let activeWorker: Worker
|
||||
let workerAddress: string | null
|
||||
|
@ -36,7 +36,7 @@ function filesToMiddleware (files: string[], baseDir: string, basePath: string,
|
||||
}
|
||||
|
||||
export function scanMiddleware (serverDir: string, onChange?: (results: ServerMiddleware[], event: 'add' | 'addDir' | 'change' | 'unlink' | 'unlinkDir', file: string) => void): Promise<ServerMiddleware[]> {
|
||||
const pattern = '**/*.{js,ts}'
|
||||
const pattern = '**/*.{ts,mjs,js,cjs}'
|
||||
const globalDir = resolve(serverDir, 'middleware')
|
||||
const apiDir = resolve(serverDir, 'api')
|
||||
|
||||
@ -78,7 +78,7 @@ export function resolveMiddleware (nuxt: Nuxt) {
|
||||
middleware.push({
|
||||
...m,
|
||||
handle: tryResolvePath(handle, {
|
||||
extensions: ['.ts', '.js'],
|
||||
extensions: ['.ts', '.mjs', '.js', '.cjs'],
|
||||
alias: nuxt.options.alias,
|
||||
base: nuxt.options.srcDir
|
||||
}),
|
||||
|
@ -32,6 +32,11 @@ export async function buildServer (ctx: ViteBuildContext) {
|
||||
ssr: true,
|
||||
rollupOptions: {
|
||||
input: resolve(ctx.nuxt.options.buildDir, 'entry.mjs'),
|
||||
output: {
|
||||
entryFileNames: 'server.mjs',
|
||||
preferConst: true,
|
||||
format: 'module'
|
||||
},
|
||||
onwarn (warning, rollupWarn) {
|
||||
if (!['UNUSED_EXTERNAL_IMPORT'].includes(warning.code)) {
|
||||
rollupWarn(warning)
|
||||
@ -51,8 +56,8 @@ 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")', 'utf8')
|
||||
await writeFile(resolve(serverDist, 'client.manifest.json'), 'false', 'utf8')
|
||||
await writeFile(resolve(serverDist, 'client.manifest.mjs'), 'export default false', 'utf8')
|
||||
|
||||
const onBuild = () => ctx.nuxt.callHook('build:resources', wpfs)
|
||||
|
||||
|
@ -113,6 +113,10 @@ export default class VueSSRClientPlugin {
|
||||
|
||||
await mkdirp(dirname(this.options.filename))
|
||||
await writeFile(this.options.filename, src)
|
||||
|
||||
const mjsSrc = 'export default ' + src
|
||||
await writeFile(this.options.filename.replace('.json', '.mjs'), mjsSrc)
|
||||
|
||||
// assets[this.options.filename] = {
|
||||
// source: () => src,
|
||||
// size: () => src.length
|
||||
|
@ -74,6 +74,21 @@ export default class VueSSRServerPlugin {
|
||||
size: () => src.length
|
||||
}
|
||||
|
||||
const mjsSrc = 'export default ' + src
|
||||
assets[this.options.filename.replace('.json', '.mjs')] = {
|
||||
source: () => mjsSrc,
|
||||
map: () => null,
|
||||
size: () => mjsSrc.length
|
||||
}
|
||||
|
||||
// TODO: Workaround for webpack
|
||||
const serverJS = 'export { default } from "./server.js"'
|
||||
assets['server.mjs'] = {
|
||||
source: () => serverJS,
|
||||
map: () => null,
|
||||
size: () => serverJS.length
|
||||
}
|
||||
|
||||
cb()
|
||||
})
|
||||
})
|
||||
|
@ -7,6 +7,6 @@
|
||||
"scripts": {
|
||||
"dev": "nu dev",
|
||||
"build": "nu build",
|
||||
"start": "node .output/server"
|
||||
"start": "node .output/server/index.mjs"
|
||||
}
|
||||
}
|
||||
|
@ -1,33 +0,0 @@
|
||||
import { transformSync } from 'esbuild'
|
||||
|
||||
// https://jestjs.io/docs/next/code-transformation
|
||||
export default {
|
||||
process (src, path, _opts) {
|
||||
const r = transformSync(src, {
|
||||
target: 'node14',
|
||||
format: 'cjs',
|
||||
sourcefile: path,
|
||||
loader: path.endsWith('.ts') ? 'ts' : 'default'
|
||||
})
|
||||
r.code = r.code.replace(/import(\(.*\))/g, (_, id) => {
|
||||
let openBrackets = 0
|
||||
|
||||
for (let pos = 0; pos < id.length; pos++) {
|
||||
const char = id[pos]
|
||||
switch (char) {
|
||||
case '(':
|
||||
openBrackets++
|
||||
break
|
||||
case ')':
|
||||
openBrackets--
|
||||
if (!openBrackets) {
|
||||
return 'Promise.resolve(require' + id.slice(0, pos) + ')' + id.slice(pos)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
return 'Promise.resolve(require' + id + ')'
|
||||
})
|
||||
return r
|
||||
}
|
||||
}
|
7
test/fixtures/basic/nuxt.config.ts
vendored
7
test/fixtures/basic/nuxt.config.ts
vendored
@ -1,3 +1,8 @@
|
||||
import { defineNuxtConfig } from '@nuxt/kit'
|
||||
|
||||
export default defineNuxtConfig({})
|
||||
export default defineNuxtConfig({
|
||||
buildDir: process.env.NITRO_BUILD_DIR,
|
||||
nitro: {
|
||||
output: { dir: process.env.NITRO_OUTPUT_DIR }
|
||||
}
|
||||
})
|
||||
|
6
test/fixtures/compat/nuxt.config.ts
vendored
6
test/fixtures/compat/nuxt.config.ts
vendored
@ -6,5 +6,9 @@ global.__NUXT_PREPATHS__ = (global.__NUXT_PREPATHS__ || []).concat(__dirname)
|
||||
export default defineNuxtConfig({
|
||||
buildModules: [
|
||||
'@nuxt/nitro/compat'
|
||||
]
|
||||
],
|
||||
buildDir: process.env.NITRO_BUILD_DIR,
|
||||
nitro: {
|
||||
output: { dir: process.env.NITRO_OUTPUT_DIR }
|
||||
}
|
||||
})
|
||||
|
70
test/presets/_tests.mjs
Normal file
70
test/presets/_tests.mjs
Normal file
@ -0,0 +1,70 @@
|
||||
import { resolve } from 'path'
|
||||
import destr from 'destr'
|
||||
import { listen } from 'listhen'
|
||||
import { $fetch } from 'ohmyfetch/node'
|
||||
import execa from 'execa'
|
||||
import { expect } from 'chai'
|
||||
import { fixtureDir, resolveWorkspace } from '../utils.mjs'
|
||||
|
||||
const isCompat = Boolean(process.env.TEST_COMPAT)
|
||||
|
||||
export function importModule (path) {
|
||||
return import(path)
|
||||
}
|
||||
|
||||
export function setupTest (preset) {
|
||||
const fixture = isCompat ? 'compat' : 'basic'
|
||||
const rootDir = fixtureDir(fixture)
|
||||
const buildDir = resolve(rootDir, '.nuxt-' + preset)
|
||||
|
||||
const ctx = {
|
||||
rootDir,
|
||||
outDir: resolve(buildDir, 'output'),
|
||||
fetch: url => $fetch(url, { baseURL: ctx.server.url })
|
||||
}
|
||||
|
||||
it('nitro build', async () => {
|
||||
const nuxtCLI = isCompat
|
||||
? resolve(ctx.rootDir, 'node_modules/nuxt/bin/nuxt.js')
|
||||
: resolveWorkspace('packages/cli/bin/nuxt.js')
|
||||
|
||||
await execa('node', [nuxtCLI, 'build', ctx.rootDir], {
|
||||
env: {
|
||||
NITRO_PRESET: preset,
|
||||
NITRO_BUILD_DIR: buildDir,
|
||||
NITRO_OUTPUT_DIR: ctx.outDir,
|
||||
NODE_ENV: 'production'
|
||||
}
|
||||
})
|
||||
}).timeout(60000)
|
||||
|
||||
after('Cleanup', async () => {
|
||||
if (ctx.server) {
|
||||
await ctx.server.close()
|
||||
}
|
||||
})
|
||||
|
||||
return ctx
|
||||
}
|
||||
|
||||
export async function startServer (ctx, handle) {
|
||||
ctx.server = await listen(handle)
|
||||
}
|
||||
|
||||
export function testNitroBehavior (ctx, getHandler) {
|
||||
let handler
|
||||
|
||||
it('setup handler', async () => {
|
||||
handler = await getHandler()
|
||||
})
|
||||
|
||||
it('SSR Works', async () => {
|
||||
const { data } = await handler({ url: '/' })
|
||||
expect(data).to.have.string('Hello Vue')
|
||||
})
|
||||
|
||||
it('API Works', async () => {
|
||||
const { data } = await handler({ url: '/api/hello' })
|
||||
expect(destr(data)).to.have.string('Hello API')
|
||||
})
|
||||
}
|
@ -1,103 +0,0 @@
|
||||
import { RequestListener } from 'http'
|
||||
import { resolve } from 'upath'
|
||||
import destr from 'destr'
|
||||
import consola from 'consola'
|
||||
import { Listener, listen } from 'listhen'
|
||||
import { $fetch } from 'ohmyfetch/node'
|
||||
import createRequire from 'create-require'
|
||||
import type { LoadNuxtOptions } from '@nuxt/kit'
|
||||
import { fixtureDir, buildFixture, loadFixture } from '../utils'
|
||||
|
||||
const isCompat = Boolean(process.env.TEST_COMPAT)
|
||||
|
||||
export interface TestContext {
|
||||
rootDir: string
|
||||
outDir: string
|
||||
nuxt?: any
|
||||
fetch: (url: string) => Promise<any>
|
||||
server?: Listener
|
||||
}
|
||||
|
||||
export interface AbstractRequest {
|
||||
url: string
|
||||
headers?: any
|
||||
method?: string
|
||||
body?: any
|
||||
}
|
||||
|
||||
export interface AbstractResponse {
|
||||
data: string
|
||||
}
|
||||
|
||||
export type AbstractHandler = (req: AbstractRequest) => Promise<AbstractResponse>
|
||||
|
||||
export function setupTest (): TestContext {
|
||||
const fixture = isCompat ? 'compat' : 'basic'
|
||||
const rootDir = fixtureDir(fixture)
|
||||
const outDir = resolve(__dirname, '.output', fixture)
|
||||
|
||||
const ctx: TestContext = {
|
||||
rootDir,
|
||||
outDir,
|
||||
fetch: url => $fetch<any>(url, { baseURL: ctx.server.url })
|
||||
}
|
||||
|
||||
beforeAll(() => {
|
||||
jest.mock('jiti', () => createRequire)
|
||||
consola.wrapAll()
|
||||
consola.mock(() => jest.fn())
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
if (ctx.nuxt) {
|
||||
await ctx.nuxt.close()
|
||||
}
|
||||
if (ctx.server) {
|
||||
await ctx.server.close()
|
||||
}
|
||||
})
|
||||
|
||||
return ctx
|
||||
}
|
||||
|
||||
export function testNitroBuild (ctx: TestContext, preset: string) {
|
||||
test('nitro build', async () => {
|
||||
ctx.outDir = resolve(ctx.outDir, preset)
|
||||
|
||||
const loadOpts: LoadNuxtOptions = { rootDir: ctx.rootDir, dev: false, version: isCompat ? 2 : 3 }
|
||||
await buildFixture(loadOpts)
|
||||
const nuxt = await loadFixture(loadOpts, {
|
||||
nitro: {
|
||||
preset,
|
||||
minify: false,
|
||||
serveStatic: false,
|
||||
externals: preset === 'cloudflare' ? false : { trace: false },
|
||||
output: { dir: ctx.outDir }
|
||||
}
|
||||
})
|
||||
await nuxt.callHook('build:done', {})
|
||||
ctx.nuxt = nuxt
|
||||
}, 60000)
|
||||
}
|
||||
|
||||
export async function startServer (ctx: TestContext, handle: RequestListener) {
|
||||
ctx.server = await listen(handle)
|
||||
}
|
||||
|
||||
export function testNitroBehavior (_ctx: TestContext, getHandler: () => Promise<AbstractHandler>) {
|
||||
let handler
|
||||
|
||||
test('setup handler', async () => {
|
||||
handler = await getHandler()
|
||||
})
|
||||
|
||||
test('SSR Works', async () => {
|
||||
const { data } = await handler({ url: '/' })
|
||||
expect(data).toMatch('Hello Vue')
|
||||
}, 10000)
|
||||
|
||||
test('API Works', async () => {
|
||||
const { data } = await handler({ url: '/api/hello' })
|
||||
expect(destr(data)).toEqual('Hello API')
|
||||
})
|
||||
}
|
@ -1,15 +1,14 @@
|
||||
import { resolve } from 'upath'
|
||||
import { readFile } from 'fs-extra'
|
||||
import { resolve } from 'path'
|
||||
import { promises as fsp } from 'fs'
|
||||
import { JSDOM } from 'jsdom'
|
||||
|
||||
import { setupTest, testNitroBuild, testNitroBehavior } from './_utils'
|
||||
import { setupTest, testNitroBehavior } from './_tests.mjs'
|
||||
|
||||
// TODO: fix SyntaxError: Unexpected end of input on script executation
|
||||
describe.skip('nitro:preset:cloudflare', () => {
|
||||
const ctx = setupTest()
|
||||
testNitroBuild(ctx, 'cloudflare')
|
||||
describe('nitro:preset:cloudflare', () => {
|
||||
const ctx = setupTest('cloudflare')
|
||||
testNitroBehavior(ctx, async () => {
|
||||
const script = await readFile(resolve(ctx.outDir, 'server/index.js'), 'utf-8')
|
||||
const script = await fsp.readFile(resolve(ctx.outDir, 'server/index.mjs'), 'utf-8')
|
||||
const dom = new JSDOM(
|
||||
`<!DOCTYPE html>
|
||||
<html>
|
@ -1,11 +1,10 @@
|
||||
import { resolve } from 'upath'
|
||||
import { testNitroBuild, setupTest, testNitroBehavior } from './_utils'
|
||||
import { resolve } from 'path'
|
||||
import { setupTest, testNitroBehavior, importModule } from './_tests.mjs'
|
||||
|
||||
describe('nitro:preset:lambda', () => {
|
||||
const ctx = setupTest()
|
||||
testNitroBuild(ctx, 'lambda')
|
||||
const ctx = setupTest('lambda')
|
||||
testNitroBehavior(ctx, async () => {
|
||||
const { handler } = await import(resolve(ctx.outDir, 'server/index.js'))
|
||||
const { handler } = await importModule(resolve(ctx.outDir, 'server/index.mjs'))
|
||||
return async ({ url: rawRelativeUrl, headers, method, body }) => {
|
||||
// creating new URL object to parse query easier
|
||||
const url = new URL(`https://example.com${rawRelativeUrl}`)
|
16
test/presets/node.test.mjs
Normal file
16
test/presets/node.test.mjs
Normal file
@ -0,0 +1,16 @@
|
||||
import { resolve } from 'path'
|
||||
import { startServer, setupTest, testNitroBehavior, importModule } from './_tests.mjs'
|
||||
|
||||
describe('nitro:preset:node', () => {
|
||||
const ctx = setupTest('node')
|
||||
testNitroBehavior(ctx, async () => {
|
||||
const { handle } = await importModule(resolve(ctx.outDir, 'server/index.mjs'))
|
||||
await startServer(ctx, handle)
|
||||
return async ({ url }) => {
|
||||
const data = await ctx.fetch(url)
|
||||
return {
|
||||
data
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
@ -1,17 +0,0 @@
|
||||
import { resolve } from 'upath'
|
||||
import { testNitroBuild, startServer, setupTest, testNitroBehavior } from './_utils'
|
||||
|
||||
describe('nitro:preset:node', () => {
|
||||
const ctx = setupTest()
|
||||
testNitroBuild(ctx, 'node')
|
||||
testNitroBehavior(ctx, async () => {
|
||||
const { handle } = await import(resolve(ctx.outDir, 'server/index.js'))
|
||||
await startServer(ctx, handle)
|
||||
return async ({ url }) => {
|
||||
const data = await ctx.fetch(url)
|
||||
return {
|
||||
data
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
@ -1,11 +1,10 @@
|
||||
import { resolve } from 'upath'
|
||||
import { testNitroBuild, setupTest, startServer, testNitroBehavior } from './_utils'
|
||||
import { resolve } from 'path'
|
||||
import { setupTest, startServer, testNitroBehavior, importModule } from './_tests.mjs'
|
||||
|
||||
describe('nitro:preset:vercel', () => {
|
||||
const ctx = setupTest()
|
||||
testNitroBuild(ctx, 'vercel')
|
||||
const ctx = setupTest('vercel')
|
||||
testNitroBehavior(ctx, async () => {
|
||||
const handle = await import(resolve(ctx.outDir, 'functions/node/server/index.js'))
|
||||
const handle = await importModule(resolve(ctx.outDir, 'functions/node/server/index.mjs'))
|
||||
.then(r => r.default || r)
|
||||
await startServer(ctx, handle)
|
||||
return async ({ url }) => {
|
@ -1,15 +1,27 @@
|
||||
import { existsSync, readFileSync, writeFileSync, rmSync, mkdirSync } from 'fs'
|
||||
import { execSync } from 'child_process'
|
||||
import { resolve, dirname } from 'upath'
|
||||
import { resolve, dirname } from 'path'
|
||||
import { fileURLToPath } from 'url'
|
||||
import defu from 'defu'
|
||||
import hash from 'object-hash'
|
||||
import type { LoadNuxtOptions, NuxtConfig } from '@nuxt/kit'
|
||||
import execa from 'execa'
|
||||
|
||||
export function fixtureDir (name: string) {
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url))
|
||||
|
||||
export function resolveWorkspace (name) {
|
||||
return resolve(__dirname, '../', name)
|
||||
}
|
||||
|
||||
export function fixtureDir (name) {
|
||||
return resolve(__dirname, 'fixtures', name)
|
||||
}
|
||||
|
||||
export async function loadFixture (opts: LoadNuxtOptions, unhashedConfig?: NuxtConfig) {
|
||||
export async function execNuxtCLI (args, opts) {
|
||||
const nuxtCLI = resolveWorkspace('packages/cli/bin/nuxt.js')
|
||||
await execa('node', [nuxtCLI, ...args], opts)
|
||||
}
|
||||
|
||||
export async function loadFixture (opts, unhashedConfig) {
|
||||
const buildId = hash(opts)
|
||||
const buildDir = resolve(opts.rootDir, '.nuxt', buildId)
|
||||
const { loadNuxt } = await import('@nuxt/kit')
|
||||
@ -17,7 +29,7 @@ export async function loadFixture (opts: LoadNuxtOptions, unhashedConfig?: NuxtC
|
||||
return nuxt
|
||||
}
|
||||
|
||||
export async function buildFixture (opts: LoadNuxtOptions) {
|
||||
export async function buildFixture (opts) {
|
||||
const buildId = hash(opts)
|
||||
const buildDir = resolve(opts.rootDir, '.nuxt', buildId)
|
||||
|
||||
@ -75,6 +87,6 @@ function waitWhile (check, interval = 100, timeout = 30000) {
|
||||
})
|
||||
}
|
||||
|
||||
function gitHead (): string {
|
||||
function gitHead () {
|
||||
return execSync('git rev-parse HEAD').toString('utf8').trim()
|
||||
}
|
@ -11,7 +11,8 @@
|
||||
"resolveJsonModule": true,
|
||||
"types": [
|
||||
"node",
|
||||
"jest",
|
||||
"mocha",
|
||||
"chai",
|
||||
"@nuxt/app",
|
||||
"@nuxt/nitro",
|
||||
]
|
||||
|
Loading…
Reference in New Issue
Block a user