From 9732d63c74b394706150ef35cc06c65d3fb185ad Mon Sep 17 00:00:00 2001 From: pooya parsa Date: Thu, 29 Apr 2021 13:51:54 +0200 Subject: [PATCH] feat: update vite implementation (#130) --- packages/app/package.json | 3 +- .../app/src/_templates/plugins.client.mjs | 4 +- packages/app/src/_templates/plugins.mjs | 8 +- .../app/src/_templates/plugins.server.mjs | 2 +- packages/app/src/plugins/router/index.ts | 4 +- packages/nuxt3/src/builder.ts | 3 +- packages/vite/package.json | 4 + packages/vite/src/client.ts | 54 ++++++ packages/vite/src/plugins/cache-dir.ts | 13 ++ packages/vite/src/plugins/replace.ts | 17 ++ packages/vite/src/server.ts | 85 +++++++++ packages/vite/src/utils/warmup.ts | 15 ++ packages/vite/src/utils/wpfs.ts | 7 + packages/vite/src/vite.ts | 177 ++++++------------ packages/webpack/src/configs/server.ts | 2 - packages/webpack/src/presets/base.ts | 4 +- yarn.lock | 4 + 17 files changed, 274 insertions(+), 132 deletions(-) create mode 100644 packages/vite/src/client.ts create mode 100644 packages/vite/src/plugins/cache-dir.ts create mode 100644 packages/vite/src/plugins/replace.ts create mode 100644 packages/vite/src/server.ts create mode 100644 packages/vite/src/utils/warmup.ts create mode 100644 packages/vite/src/utils/wpfs.ts diff --git a/packages/app/package.json b/packages/app/package.json index 616feb4306..c82614c165 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -5,7 +5,8 @@ "license": "MIT", "exports": { ".": "./dist", - "./meta": "./meta.js" + "./meta": "./meta.js", + "./dist/*": "./dist/*" }, "main": "./dist", "module": "./dist", diff --git a/packages/app/src/_templates/plugins.client.mjs b/packages/app/src/_templates/plugins.client.mjs index 880dee0ee5..a066df7c0f 100644 --- a/packages/app/src/_templates/plugins.client.mjs +++ b/packages/app/src/_templates/plugins.client.mjs @@ -1,6 +1,6 @@ import { $fetch } from 'ohmyfetch' -import logs from 'nuxt/app/plugins/logs.client.dev' -import progress from 'nuxt/app/plugins/progress.client' +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) %> diff --git a/packages/app/src/_templates/plugins.mjs b/packages/app/src/_templates/plugins.mjs index 3a753da41e..8efd58c4ef 100644 --- a/packages/app/src/_templates/plugins.mjs +++ b/packages/app/src/_templates/plugins.mjs @@ -1,7 +1,7 @@ -import head from 'nuxt/app/plugins/head' -import router from 'nuxt/app/plugins/router' -import vuex from 'nuxt/app/plugins/vuex' -import legacy from 'nuxt/app/plugins/legacy' +import head from '#app/plugins/head' +import router from '#app/plugins/router' +import vuex from '#app/plugins/vuex' +import legacy from '#app/plugins/legacy' <% const plugins = app.plugins.filter(p => p.mode === 'all').map(p => p.src) %> <%= nxt.importSources(plugins) %> diff --git a/packages/app/src/_templates/plugins.server.mjs b/packages/app/src/_templates/plugins.server.mjs index a383d2336a..d6ec2485a2 100644 --- a/packages/app/src/_templates/plugins.server.mjs +++ b/packages/app/src/_templates/plugins.server.mjs @@ -1,4 +1,4 @@ -import preload from 'nuxt/app/plugins/preload.server' +import preload from '#app/plugins/preload.server' <% const plugins = app.plugins.filter(p => p.mode === 'server').map(p => p.src) %> <%= nxt.importSources(plugins) %> diff --git a/packages/app/src/plugins/router/index.ts b/packages/app/src/plugins/router/index.ts index 9f0b8c1ea2..360d91739c 100644 --- a/packages/app/src/plugins/router/index.ts +++ b/packages/app/src/plugins/router/index.ts @@ -7,9 +7,9 @@ import { } from 'vue-router' import type { Plugin } from '@nuxt/app' // @ts-ignore -import routes from 'nuxt/build/routes' -// @ts-ignore import NuxtPage from './NuxtPage.vue' +import routes from '#build/routes' +// @ts-ignore export default function router (nuxt) { const { app } = nuxt diff --git a/packages/nuxt3/src/builder.ts b/packages/nuxt3/src/builder.ts index 4d7bc2ece1..8332ae800b 100644 --- a/packages/nuxt3/src/builder.ts +++ b/packages/nuxt3/src/builder.ts @@ -1,4 +1,4 @@ -import { join, relative } from 'path' +import { join, relative, resolve } from 'upath' import fsExtra from 'fs-extra' import { debounce } from 'lodash' import { Nuxt } from '@nuxt/kit' @@ -46,6 +46,7 @@ async function _build (builder: Builder) { if (!nuxt.options.dev) { await fsExtra.emptyDir(nuxt.options.buildDir) } + await fsExtra.emptyDir(resolve(nuxt.options.buildDir, 'dist')) await generate(builder) if (nuxt.options.dev) { diff --git a/packages/vite/package.json b/packages/vite/package.json index 9e26390982..52bb5f3093 100644 --- a/packages/vite/package.json +++ b/packages/vite/package.json @@ -11,14 +11,18 @@ "prepack": "unbuild" }, "devDependencies": { + "@types/debounce": "^1.2.0", "unbuild": "^0.2.3" }, "dependencies": { "@nuxt/kit": "^0.5.3", "@vitejs/plugin-vue": "^1.2.2", "@vue/compiler-sfc": "^3.0.11", + "chokidar": "^3.5.1", "consola": "^2.15.3", + "debounce": "^1.2.1", "fs-extra": "^9.1.0", + "upath": "^2.0.1", "vite": "^2.2.3", "vue": "3.0.11" } diff --git a/packages/vite/src/client.ts b/packages/vite/src/client.ts new file mode 100644 index 0000000000..fc8a85046f --- /dev/null +++ b/packages/vite/src/client.ts @@ -0,0 +1,54 @@ +import { resolve } from 'path' +import * as vite from 'vite' +import vitePlugin from '@vitejs/plugin-vue' +import { cacheDirPlugin } from './plugins/cache-dir' +import { replace } from './plugins/replace' +import { ViteBuildContext, ViteOptions } from './vite' + +export async function buildClient (ctx: ViteBuildContext) { + const clientConfig: vite.InlineConfig = vite.mergeConfig(ctx.config, { + define: { + 'process.server': false, + 'process.client': true, + 'module.hot': false, + global: 'globalThis' + }, + build: { + outDir: 'dist/client', + assetsDir: '.', + rollupOptions: { + input: resolve(ctx.nuxt.options.buildDir, 'client.js') + } + }, + plugins: [ + replace({ 'process.env': 'import.meta.env' }), + cacheDirPlugin(ctx.nuxt.options.rootDir, 'client'), + vitePlugin(ctx.config.vue) + ], + server: { + middlewareMode: true + } + } as ViteOptions) + + await ctx.nuxt.callHook('vite:extendConfig', clientConfig, { isClient: true, isServer: false }) + + const viteServer = await vite.createServer(clientConfig) + await ctx.nuxt.callHook('vite:serverCreated', viteServer) + + const viteMiddleware = (req, res, next) => { + // Workaround: vite devmiddleware modifies req.url + const originalURL = req.url + if (req.url === '/_nuxt/client.js') { + return res.end('') + } + viteServer.middlewares.handle(req, res, (err) => { + req.url = originalURL + next(err) + }) + } + await ctx.nuxt.callHook('server:devMiddleware', viteMiddleware) + + ctx.nuxt.hook('close', async () => { + await viteServer.close() + }) +} diff --git a/packages/vite/src/plugins/cache-dir.ts b/packages/vite/src/plugins/cache-dir.ts new file mode 100644 index 0000000000..b28f9b05e5 --- /dev/null +++ b/packages/vite/src/plugins/cache-dir.ts @@ -0,0 +1,13 @@ +import type { Plugin } from 'vite' +import { resolve } from 'upath' + +export function cacheDirPlugin (rootDir, name: string) { + const optimizeCacheDir = resolve(rootDir, 'node_modules/.cache/vite', name) + return { + name: 'nuxt:cache-dir', + configResolved (resolvedConfig) { + // @ts-ignore + resolvedConfig.optimizeCacheDir = optimizeCacheDir + } + } +} diff --git a/packages/vite/src/plugins/replace.ts b/packages/vite/src/plugins/replace.ts new file mode 100644 index 0000000000..986b9c95bc --- /dev/null +++ b/packages/vite/src/plugins/replace.ts @@ -0,0 +1,17 @@ +import type { Plugin } from 'vite' + +export function replace (replacements: Record) { + return { + name: 'nuxt:replace', + transform (code) { + Object.entries(replacements).forEach(([key, value]) => { + const escapedKey = key.replace(/\./g, '\\.') + code = code.replace(new RegExp(escapedKey, 'g'), value) + }) + return { + code, + map: null + } + } + } +} diff --git a/packages/vite/src/server.ts b/packages/vite/src/server.ts new file mode 100644 index 0000000000..aa49c2bbcc --- /dev/null +++ b/packages/vite/src/server.ts @@ -0,0 +1,85 @@ +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' +import { ViteBuildContext, ViteOptions } from './vite' +import { wpfs } from './utils/wpfs' +import { cacheDirPlugin } from './plugins/cache-dir' + +export async function buildServer (ctx: ViteBuildContext) { + const serverConfig: vite.InlineConfig = vite.mergeConfig(ctx.config, { + define: { + 'process.server': true, + 'process.client': false, + 'typeof window': '"undefined"', + 'typeof document': '"undefined"', + 'typeof navigator': '"undefined"', + 'typeof location': '"undefined"', + 'typeof XMLHttpRequest': '"undefined"' + }, + ssr: { + external: [ + 'axios' + ] + }, + build: { + outDir: 'dist/server', + ssr: true, + rollupOptions: { + input: resolve(ctx.nuxt.options.buildDir, 'entry.server.mjs'), + onwarn (warning, rollupWarn) { + if (!['UNUSED_EXTERNAL_IMPORT'].includes(warning.code)) { + rollupWarn(warning) + } + } + } + }, + plugins: [ + cacheDirPlugin(ctx.nuxt.options.rootDir, 'server'), + vuePlugin() + ] + } as ViteOptions) + + await ctx.nuxt.callHook('vite:extendConfig', serverConfig, { isClient: false, isServer: true }) + + const serverDist = resolve(ctx.nuxt.options.buildDir, 'dist/server') + await mkdirp(serverDist) + + await writeFile(resolve(serverDist, 'server.js'), 'try { module.exports = require("./entry.server") } catch (err) { module.exports = () => { throw err } }', 'utf8') + await writeFile(resolve(serverDist, 'client.manifest.json'), 'false', 'utf8') + + const onBuild = () => ctx.nuxt.callHook('build:resources', wpfs) + + if (!ctx.nuxt.options.ssr) { + await onBuild() + return + } + + const build = debounce(async () => { + const start = Date.now() + await vite.build(serverConfig) + await onBuild() + consola.info(`Server built in ${Date.now() - start}ms`) + }, 300) + + 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() + }) +} diff --git a/packages/vite/src/utils/warmup.ts b/packages/vite/src/utils/warmup.ts new file mode 100644 index 0000000000..eb94a76e6f --- /dev/null +++ b/packages/vite/src/utils/warmup.ts @@ -0,0 +1,15 @@ +import type { ViteDevServer } from 'vite' + +export async function warmupViteServer (server: ViteDevServer, entries: string[]) { + const warmedUrls = new Set() + + const warmup = async (url: string) => { + if (warmedUrls.has(url)) { return undefined } + warmedUrls.add(url) + await server.transformRequest(url) + const deps = Array.from(server.moduleGraph.urlToModuleMap.get(url).importedModules) + await Promise.all(deps.map(m => warmup(m.url))) + } + + await Promise.all(entries.map(entry => warmup(entry))) +} diff --git a/packages/vite/src/utils/wpfs.ts b/packages/vite/src/utils/wpfs.ts new file mode 100644 index 0000000000..ed033735df --- /dev/null +++ b/packages/vite/src/utils/wpfs.ts @@ -0,0 +1,7 @@ +import { join } from 'upath' +import fsExtra from 'fs-extra' + +export const wpfs = { + ...fsExtra, + join +} diff --git a/packages/vite/src/vite.ts b/packages/vite/src/vite.ts index a5978ece30..9847e7c6cb 100644 --- a/packages/vite/src/vite.ts +++ b/packages/vite/src/vite.ts @@ -1,131 +1,74 @@ -import { resolve } from 'path' -import type { Nuxt } from '@nuxt/kit' -import { mkdirp, writeFile } from 'fs-extra' -import vue from '@vitejs/plugin-vue' -import consola from 'consola' import * as vite from 'vite' +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 { warmupViteServer } from './utils/warmup' -interface ViteBuildContext { +export interface ViteOptions extends InlineConfig { + vue?: Options + ssr?: SSROptions +} + +export interface ViteBuildContext { nuxt: Nuxt - config: vite.InlineConfig + config: ViteOptions } export async function bundle (nuxt: Nuxt) { const ctx: ViteBuildContext = { nuxt, - config: { - root: nuxt.options.buildDir, - mode: nuxt.options.dev ? 'development' : 'production', - logLevel: 'warn', - resolve: { - alias: { - 'nuxt/app': nuxt.options.appDir, - 'nuxt/build': nuxt.options.buildDir, - '~': nuxt.options.srcDir, - '@': nuxt.options.srcDir - } - }, - clearScreen: false, - plugins: [ - vue({}) - ], - build: { - emptyOutDir: false - } - } + config: vite.mergeConfig( + nuxt.options.vite as any || {}, + { + root: nuxt.options.buildDir, + mode: nuxt.options.dev ? 'development' : 'production', + logLevel: 'warn', + define: { + 'process.dev': nuxt.options.dev + }, + resolve: { + extensions: ['.mjs', '.js', '.ts', '.jsx', '.tsx', '.json', '.vue'], + alias: { + ...nuxt.options.alias, + '#build': nuxt.options.buildDir, + '#app': nuxt.options.appDir, + '~': nuxt.options.srcDir, + '@': nuxt.options.srcDir, + 'web-streams-polyfill/ponyfill/es2018': 'unenv/runtime/mock/empty', + // Cannot destructure property 'AbortController' of .. + 'abort-controller': 'unenv/runtime/mock/empty' + } + }, + vue: {}, + css: {}, + optimizeDeps: { + exclude: [] + }, + esbuild: { + jsxFactory: 'h', + jsxFragment: 'Fragment' + }, + clearScreen: false, + build: { + emptyOutDir: false + }, + plugins: [] + } as ViteOptions + ) } await nuxt.callHook('vite:extend', ctx) - await mkdirp(nuxt.options.buildDir) - await mkdirp(resolve(nuxt.options.buildDir, '.vite/temp')) + nuxt.hook('vite:serverCreated', (server: vite.ViteDevServer) => { + const start = Date.now() + warmupViteServer(server, ['/entry.client.mjs']).then(() => { + consola.info(`Vite warmed up in ${Date.now() - start}ms`) + }).catch(consola.error) + }) - const callBuild = async (fn, name) => { - try { - const start = Date.now() - await fn(ctx) - const time = (Date.now() - start) / 1000 - consola.success(`${name} compiled successfully in ${time}s`) - } catch (err) { - consola.error(`${name} compiled with errors:`, err) - } - } - - if (nuxt.options.dev) { - await Promise.all([ - callBuild(buildClient, 'Client'), - callBuild(buildServer, 'Server') - ]) - } else { - await callBuild(buildClient, 'Client') - await callBuild(buildServer, 'Server') - } -} - -async function buildClient (ctx: ViteBuildContext) { - const clientConfig: vite.InlineConfig = vite.mergeConfig(ctx.config, { - define: { - 'process.server': false, - 'process.client': true - }, - build: { - outDir: 'dist/client', - assetsDir: '.', - rollupOptions: { - input: resolve(ctx.nuxt.options.buildDir, './entry.client') - } - }, - server: { - middlewareMode: true - } - } as vite.InlineConfig) - - if (ctx.nuxt.options.dev) { - const viteServer = await vite.createServer(clientConfig) - await ctx.nuxt.callHook('server:devMiddleware', (req, res, next) => { - // Workaround: vite devmiddleware modifies req.url - const originalURL = req.url - viteServer.middlewares.handle(req, res, (err) => { - req.url = originalURL - next(err) - }) - }) - } else { - await vite.build(clientConfig) - } -} - -async function buildServer (ctx: ViteBuildContext) { - const serverConfig: vite.InlineConfig = vite.mergeConfig(ctx.config, { - define: { - 'process.server': true, - 'process.client': false, - window: undefined - }, - build: { - outDir: 'dist/server', - ssr: true, - rollupOptions: { - input: resolve(ctx.nuxt.options.buildDir, './entry.server'), - onwarn (warning, rollupWarn) { - if (!['UNUSED_EXTERNAL_IMPORT'].includes(warning.code)) { - rollupWarn(warning) - } - } - } - } - } as vite.InlineConfig) - - const serverDist = resolve(ctx.nuxt.options.buildDir, 'dist/server') - await mkdirp(serverDist) - await writeFile(resolve(serverDist, 'client.manifest.json'), 'false') - await writeFile(resolve(serverDist, 'server.js'), 'const entry = require("./entry.server"); module.exports = entry.default || entry;') - - await vite.build(serverConfig) - - if (ctx.nuxt.options.dev) { - ctx.nuxt.hook('builder:watch', () => { - vite.build(serverConfig).catch(consola.error) - }) - } + await buildClient(ctx) + await buildServer(ctx) } diff --git a/packages/webpack/src/configs/server.ts b/packages/webpack/src/configs/server.ts index 172c89c78b..140b883017 100644 --- a/packages/webpack/src/configs/server.ts +++ b/packages/webpack/src/configs/server.ts @@ -34,8 +34,6 @@ function serverStandalone (ctx: WebpackConfigContext) { // TODO: Refactor this out of webpack const inline = [ 'src/', - 'nuxt/app', - 'nuxt/build', '@nuxt/app', 'vuex5', '!', diff --git a/packages/webpack/src/presets/base.ts b/packages/webpack/src/presets/base.ts index a98e729395..084388e7b4 100644 --- a/packages/webpack/src/presets/base.ts +++ b/packages/webpack/src/presets/base.ts @@ -109,8 +109,8 @@ function baseAlias (ctx: WebpackConfigContext) { const { options } = ctx ctx.alias = { - 'nuxt/app': options.appDir, - 'nuxt/build': options.buildDir, + '#app': options.appDir, + '#build': options.buildDir, ...options.alias, ...ctx.alias } diff --git a/yarn.lock b/yarn.lock index 37b8d8d542..d49c15b386 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1716,11 +1716,15 @@ __metadata: resolution: "@nuxt/vite-builder@workspace:packages/vite" dependencies: "@nuxt/kit": ^0.5.3 + "@types/debounce": ^1.2.0 "@vitejs/plugin-vue": ^1.2.2 "@vue/compiler-sfc": ^3.0.11 + chokidar: ^3.5.1 consola: ^2.15.3 + debounce: ^1.2.1 fs-extra: ^9.1.0 unbuild: ^0.2.3 + upath: ^2.0.1 vite: ^2.2.3 vue: 3.0.11 languageName: unknown