8000 feat: build dev server bundle using vite by pi0 · Pull Request #201 · nuxt/vite · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content
This repository was archived by the owner on Jan 4, 2023. It is now read-only.

feat: build dev server bundle using vite #201

Merged
merged 20 commits into from
Sep 30, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ jobs:
run: yarn build

- name: Test
run: yarn jest
run: yarn test< 8000 /td>

- name: Coverage
uses: codecov/codecov-action@v2
6 changes: 0 additions & 6 deletions jest.config.js

This file was deleted.

13 changes: 7 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,34 +23,35 @@
"fixture:start": "nuxt start test/fixture",
"lint": "eslint --ext .ts .",
"release": "yarn test && standard-version && git push --follow-tags && npm publish",
"test": "yarn lint && yarn jest"
"test": "mocha -b -r jiti/register ./test/*.test.*"
},
"dependencies": {
"@vitejs/ 10000 plugin-legacy": "^1.5.3",
"consola": "^2.15.3",
"fs-extra": "^10.0.0",
"p-debounce": "3",
"postcss-import": "^14.0.2",
"postcss-import-resolver": "^2.0.0",
"postcss-preset-env": "^6.7.0",
"postcss-url": "^10.1.3",
"semver": "^7.3.5",
"ufo": "^0.7.9",
"upath": "^2.0.1",
"vite": "^2.5.4",
"vite": "^2.5.7",
"vite-plugin-vue2": "^1.8.2"
},
"devDependencies": {
"@babel/preset-typescript": "^7.15.0",
"@nuxt/test-utils": "^0.2.2",
"@nuxt/types": "^2.15.8",
"@nuxtjs/composition-api": "^0.28.0",
"@nuxtjs/eslint-config-typescript": "^6.0.1",
"@types/fs-extra": "^9.0.12",
"@types/jest": "^27.0.1",
"@types/mocha": "^9.0.0",
"chai": "^4.3.4",
"eslint": "^7.32.0",
"jest": "^27.1.0",
"mkdist": "^0.3.3",
"mocha": "^9.1.2",
"nuxt": "^2.15.8",
"ohmyfetch": "^0.3.1",
"playwright": "^1.14.1",
"sass": "^1.39.0",
"siroc": "^0.16.0",
Expand Down
5 changes: 0 additions & 5 deletions src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,6 @@ export async function buildClient (ctx: ViteBuildContext) {
: `defaultexport:${p.src}`
}

// redirect '/_nuxt' to buildDir for dev
if (ctx.nuxt.options.dev) {
alias['/_nuxt'] = ctx.nuxt.options.buildDir
}

const clientConfig: vite.InlineConfig = vite.mergeConfig(ctx.config, {
define: {
'process.server': false,
Expand Down
15 changes: 1 addition & 14 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,25 +23,12 @@ function nuxtVite () {
return
}

// Disable SSR by default
const ssrEnabled = nuxt.options.ssr && nuxt.options.vite?.ssr
if (!ssrEnabled) {
nuxt.options.ssr = false
nuxt.options.render.ssr = false
nuxt.options.build.ssr = false
nuxt.options.mode = 'spa'
}

nuxt.options.cli.badgeMessages.push(`⚡ Vite Mode Enabled (v${version})`)
// eslint-disable-next-line no-console
if (nuxt.options.vite?.experimentWarning !== false && !nuxt.options.test) {
consola.log(
'🧪 Vite mode is experimental and some nuxt modules might be incompatible\n',
' If found a bug, please report via https://github.com/nuxt/vite/issues with a minimal reproduction.' + (
ssrEnabled
? '\n Unstable server-side rendering is enabled'
: '\n You can enable unstable server-side rendering using `vite: { ssr: true }` in `nuxt.config`'
)
' If found a bug, please report via https://github.com/nuxt/vite/issues with a minimal reproduction.'
)
}

Expand Down
32 changes: 2 additions & 30 deletions src/manifest.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { resolve } from 'path'
import { createHash } from 'crypto'
import { readJSON, remove, existsSync, readFile, writeFile, mkdirp } from 'fs-extra'
import { ViteBuildContext } from './types'
import { uniq, isJS, isCSS, hash } from './utils'

const DEFAULT_APP_TEMPLATE = `
<!DOCTYPE html>
Expand All @@ -27,7 +27,7 @@ export async function prepareManifests (ctx: ViteBuildContext) {
const DEV_TEMPLATE = APP_TEMPLATE
.replace(
'</body>',
'<script type="module" src="/@vite/client"></script><script type="module" src="/_nuxt/client.js"></script></body>'
'<script type="module" src="/@vite/client"></script><script type="module" src="/.nuxt/client.js"></script></body>'
)
const SPA_TEMPLATE = ctx.nuxt.options.dev ? DEV_TEMPLATE : APP_TEMPLATE
const SSR_TEMPLATE = ctx.nuxt.options.dev ? DEV_TEMPLATE : APP_TEMPLATE
Expand Down Expand Up @@ -169,34 +169,6 @@ async function writeClientManifest (clientManifest: any, buildDir: string) {
await writeFile(resolve(buildDir, 'dist/server/client.manifest.mjs'), `export default ${clientManifestJSON}`, 'utf-8')
}

function hash (input: string, length = 8) {
return createHash('sha256')
.update(input)
.digest('hex')
.substr(0, length)
}

function uniq<T> (arr: T[]): T[] {
return Array.from(new Set(arr))
}

// Copied from vue-bundle-renderer utils
const IS_JS_RE = /\.[cm]?js(\?[^.]+)?$/
const IS_MODULE_RE = /\.mjs(\?[^.]+)?$/
const HAS_EXT_RE = /[^./]+\.[^./]+$/
const IS_CSS_RE = /\.css(\?[^.]+)?$/

export function isJS (file: string) {
return IS_JS_RE.test(file) || !HAS_EXT_RE.test(file)
}

export function isModule (file: string) {
return IS_MODULE_RE.test(file) || !HAS_EXT_RE.test(file)
}

export function isCSS (file: string) {
return IS_CSS_RE.test(file)
}
function getModuleIds ([, value]: [string, any]) {
if (!value) { return [] }
// Only include legacy and css ids
Expand Down
211 changes: 179 additions & 32 deletions src/server.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import { resolve } from 'path'
import { builtinModules } from 'module'
import * as vite from 'vite'
import { createVuePlugin } from 'vite-plugin-vue2'
import consola from 'consola'
import { join } from 'upath'
import type { RollupWatcher } from 'rollup'
import { writeFile } from 'fs-extra'
import pDebounce from 'p-debounce'
import { ViteBuildContext, ViteOptions } from './types'
import { wpfs } from './utils/wpfs'
import { jsxPlugin } from './plugins/jsx'
import { generateDevSSRManifest } from './manifest'
import { uniq, hashId } from './utils'

export async function buildServer (ctx: ViteBuildContext) {
// Workaround to disable HMR
Expand Down Expand Up @@ -70,43 +72,188 @@ export async function buildServer (ctx: ViteBuildContext) {

const => ctx.nuxt.callHook('build:resources', wpfs)

// Production build
if (!ctx.nuxt.options.dev) {
const start = Date.now()
consola.info('Building server...')
await vite.build(serverConfig)
await onBuild()
consola.success(`Server built in ${Date.now() - start}ms`)
} else {
const watcher = await vite.build({
...serverConfig,
build: {
...serverConfig.build,
watch: {
include: [
join(ctx.nuxt.options.buildDir, '**/*'),
join(ctx.nuxt.options.srcDir, '**/*'),
join(ctx.nuxt.options.rootDir, '**/*')
],
exclude: [
'**/dist/server/**'
]
}
}
}) as RollupWatcher

let start = Date.now()
watcher.on('event', async (event) => {
if (event.code === 'BUNDLE_START') {
start = Date.now()
} else if (event.code === 'BUNDLE_END') {
await generateDevSSRManifest(ctx)
await onBuild()
consola.info(`Server rebuilt in ${Date.now() - start}ms`)
} else if (event.code === 'ERROR') {
consola.error(event.error)
return
}

// Start development server
const viteServer = await vite.createServer(serverConfig)
ctx.nuxt.hook('close', () => viteServer.close())

// Initialize plugins
await viteServer.pluginContainer.buildStart({})

// Generate manifest files
await writeFile(resolve(ctx.nuxt.options.buildDir, 'dist/server/ssr-manifest.json'), JSON.stringify({}, null, 2), 'utf-8')
await generateDevSSRManifest(ctx)

// Build and watch
const _doBuild = async () => {
const start = Date.now()
const { code } = await bundleRequest(viteServer, '/.nuxt/server.js')
await writeFile(resolve(ctx.nuxt.options.buildDir, 'dist/server/server.js'), code, 'utf-8')
const time = (Date.now() - start)
consola.info(`Server built in ${time}ms`)
await onBuild()
}
const doBuild = pDebounce(_doBuild, 300)

// Initial build
await _doBuild()

// Watch
viteServer.watcher.on('all', (_event, file) => {
if (file.indexOf(ctx.nuxt.options.buildDir) === 0) { return }
doBuild()
})
}

// ---- Vite Dev Bundler POC ----

interface TransformChunk {
id: string,
code: string,
deps: string[],
parents: string[]
}

interface SSRTransformResult {
code: string,
map: object,
deps: string[]
dynamicDeps: string[]
}

async function transformRequest (viteServer: vite.ViteDevServer, id) {
// Virtual modules start with `\0`
if (id && id.startsWith('/@id/__x00__')) {
id = '\0' + id.slice('/@id/__x00__'.length)
}

// Externals
if (builtinModules.includes(id)) {
return {
code: `() => require('${id}')`,
deps: [],
dynamicDeps: []
}
}

// Transform
const res: SSRTransformResult = await viteServer.transformRequest(id, { ssr: true }).catch((err) => {
// eslint-disable-next-line no-console
console.warn(`[SSR] Error transforming ${id}: ${err}`)
// console.error(err)
}) as SSRTransformResult || { code: '', map: {}, deps: [], dynamicDeps: [] }

// Wrap into a vite module
const code = `async function () {
const exports = {}
const module = { exports }
const __vite_ssr_exports__ = exports;
const __vite_ssr_exportAll__ = __createViteSSRExportAll__(__vite_ssr_exports__)
${res.code || '/* empty */'};
return module.exports;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note for @antfu: Vite transform is not handling CJS at all (Node.js and Rollup both have basic CJS-in-ESM proxy support). Had to do this because some node_modules use CJS style exports but we also have this issue for the browser side. Maybe escalating this to add basic support to vite or refactor as a vite transform plugin?

}`
return { code, deps: res.deps || [], dynamicDeps: res.dynamicDeps || [] }
}

async function transformRequestRecursive (viteServer: vite.ViteDevServer, id, parent = '<entry>', chunks: Record<string, TransformChunk> = {}) {
if (chunks[id]) {
chunks[id].parents.push(parent)
return
}
const res = await transformRequest(viteServer, id)
const deps = uniq([...res.deps, ...res.dynamicDeps])

chunks[id] = {
id,
code: res.code,
deps,
parents: [parent]
} as TransformChunk
for (const dep of deps) {
await transformRequestRecursive(viteServer, dep, id, chunks)
}
return Object.values(chunks)
}

async function bundleRequest (viteServer: vite.ViteDevServer, id) {
const chunks = await transformRequestRecursive(viteServer, id)

const listIds = ids => ids.map(id => `// - ${id} (${hashId(id)})`).join('\n')
const chunksCode = chunks.map(chunk => `
// --------------------
// Request: ${chunk.id}
// Parents: \n${listIds(chunk.parents)}
// Dependencies: \n${listIds(chunk.deps)}
// --------------------
const ${hashId(chunk.id)} = ${chunk.code}
`).join('\n')

const manifestCode = 'const $chunks = {\n' +
chunks.map(chunk => ` '${chunk.id}': ${hashId(chunk.id)}`).join(',\n') + '\n}'

const dynamicImportCode = `
const __vite_import_cache__ = Object.create({})
async function __vite_ssr_import__ (id) {
if (__vite_import_cache__[id]) {
return __vite_import_cache__[id]
}
const mod = await $chunks[id]()
if (mod && !('default' in mod)) {
mod.default = mod
}
__vite_import_cache__[id] = mod
return mod
}
function __vite_ssr_dynamic_import__(id) {
return __vite_ssr_import__(id)
}
`

// https://github.com/vitejs/vite/blob/fb406ce4c0fe6da3333c9d1c00477b2880d46352/packages/vite/src/node/ssr/ssrModuleLoader.ts#L121-L133
const helpers = `
function __createViteSSRExportAll__(ssrModule) {
return (sourceModule) => {
for (const key in sourceModule) {
if (key !== 'default') {
Object.defineProperty(ssrModule, key, {
enumerable: true,
configurable: true,
get() {
return sourceModule[key]
}
})
}
})
}
}
}
`

ctx.nuxt.hook('close', () => watcher.close())
// TODO: implement real HMR
const metaPolyfill = `
const __vite_ssr_import_meta__ = {
hot: {
accept() {}
}
}
`

const code = [
metaPolyfill,
chunksCode,
manifestCode,
dynamicImportCode,
helpers,
`module.exports = function (...args) { return ${hashId(id)}().then(r => r.default(...args)) }`
].join('\n\n')

return { code }
}
Loading
0