Skip to content

Commit

Permalink
feat(nuxt): improve nuxt env stub (#443)
Browse files Browse the repository at this point in the history
* wip

* feat: more commented code

* wip

* fix: WORKS OMG

* test(lint): fix

* chore: console.log

* fix: builds now

* fix: nuxt close

* feat: onPreview + close nuxt on preview command

* feat(config): build.excludeFromVendorsChunk
  • Loading branch information
Akryum authored Feb 15, 2023
1 parent 598fe9b commit b73b2da
Show file tree
Hide file tree
Showing 14 changed files with 281 additions and 35 deletions.
11 changes: 11 additions & 0 deletions examples/nuxt3/components/AutoImport.story.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
<script setup>
/* eslint-disable no-undef */
console.log('useNuxtApp', useNuxtApp())
console.log('useNuxtApp().$config', useNuxtApp().$config)
const config = useRuntimeConfig()
console.log('useRuntimeConfig', config)
</script>

<template>
<Story>
<Meow />

<h3>Nuxt runtime config</h3>
<pre>{{ config }}</pre>
</Story>
</template>
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,16 @@ const mountComponent = ref(null)
watchEffect(async () => {
const clientPlugin = clientSupportPlugins[props.story.file?.supportPluginId]
console.log(props.story.file?.supportPluginId, clientPlugin)
if (clientPlugin) {
const pluginModule = await clientPlugin()
mountComponent.value = markRaw(pluginModule.RenderStory)
console.log(clientPlugin)
try {
const pluginModule = await clientPlugin()
mountComponent.value = markRaw(pluginModule.RenderStory)
} catch (e) {
console.error(e)
throw e
}
}
})
</script>
Expand Down
5 changes: 4 additions & 1 deletion packages/histoire-plugin-nuxt/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,10 @@
"watch": "tsc -d -w --sourceMap"
},
"dependencies": {
"@nuxt/kit": "^3.0.0-rc.11"
"@nuxt/kit": "^3.0.0-rc.11",
"h3": "^1.4.0",
"ofetch": "^1.0.0",
"unenv": "^1.1.1"
},
"devDependencies": {
"@types/node": "^17.0.32",
Expand Down
5 changes: 5 additions & 0 deletions packages/histoire-plugin-nuxt/src/global.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
declare global {
const __HST_COLLECT__: boolean
}

export {}
92 changes: 75 additions & 17 deletions packages/histoire-plugin-nuxt/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,27 +19,52 @@ export function HstNuxt (): Plugin {
name: '@histoire/plugin-nuxt',

async defaultConfig () {
const nuxtConfig = await useNuxtViteConfig()
nuxt = nuxtConfig.nuxt
const plugins = nuxtConfig.viteConfig.plugins.filter((p: any) => !ignorePlugins.includes(p?.name))
const nuxtViteConfig = await useNuxtViteConfig()
const { viteConfig } = nuxtViteConfig
nuxt = nuxtViteConfig.nuxt // We save it to close it later
const plugins = viteConfig.plugins.filter((p: any) => !ignorePlugins.includes(p?.name))
return {
vite: {
define: nuxtConfig.viteConfig.define,
server: {
watch: viteConfig.server.watch,
fs: {
allow: viteConfig.server.fs.allow,
},
middlewareMode: false,
},
define: viteConfig.define,
resolve: {
alias: nuxtConfig.viteConfig.resolve.alias,
extensions: nuxtConfig.viteConfig.resolve.extensions,
dedupe: nuxtConfig.viteConfig.resolve.dedupe,
alias: viteConfig.resolve.alias,
extensions: viteConfig.resolve.extensions,
dedupe: viteConfig.resolve.dedupe,
},
plugins,
css: nuxtConfig.viteConfig.css,
publicDir: nuxtConfig.viteConfig.publicDir,
optimizeDeps: nuxtConfig.viteConfig.optimizeDeps,
css: viteConfig.css,
publicDir: viteConfig.publicDir,
optimizeDeps: viteConfig.optimizeDeps,
// @ts-expect-error Vue-specific config
vue: nuxtConfig.viteConfig.vue,
vue: viteConfig.vue,
logLevel: 'info',
},
setupCode: [
`${nuxt.options.css.map(file => `import '${file}'`).join('\n')}`,
`import { setupNuxtApp } from '@histoire/plugin-nuxt/dist/runtime/app-setup.js'
export async function setupVue3 () {
await setupNuxtApp()
}`,
],
viteNodeInlineDeps: [
/\/(nuxt|nuxt3)\//,
/^#/,
...(nuxt.options.build.transpile.filter(
r => typeof r === 'string' || r instanceof RegExp,
) as Array<string | RegExp>),
],
build: {
excludeFromVendorsChunk: [
/nuxt\/dist\/app/,
],
},
}
},

Expand All @@ -49,7 +74,29 @@ export function HstNuxt (): Plugin {
})
},

onBuild () {
onBuild (api) {
api.changeViteConfig(config => {
const emptyId = 'histoire-nuxt-empty'
config.plugins.push({
name: 'histoire-nuxt-ignore',
enforce: 'pre',
resolveId (id) {
if (id.includes('unenv')) {
return emptyId
}
},
load (id) {
if (id === emptyId) {
return 'export default {}'
}
},
})
})

nuxt?.close()
},

onPreview () {
nuxt?.close()
},
}
Expand All @@ -58,10 +105,14 @@ export function HstNuxt (): Plugin {
async function useNuxtViteConfig () {
const { loadNuxt, buildNuxt } = await import('@nuxt/kit')
const nuxt = await loadNuxt({
// cwd: process.cwd(),
ready: false,
dev: true,
overrides: {
ssr: false,
app: {
rootId: 'nuxt-test',
},
},
})
if (nuxt.options.builder as string !== '@nuxt/vite-builder') {
Expand All @@ -81,8 +132,9 @@ async function useNuxtViteConfig () {
imports: stubbedComposables,
})
})

return {
viteConfig: await new Promise<ViteConfig>((resolve) => {
viteConfig: await new Promise<ViteConfig>((resolve, reject) => {
nuxt.hook('modules:done', () => {
nuxt.hook('components:extend', (components) => {
for (const name of ['NuxtLink']) {
Expand All @@ -94,12 +146,18 @@ async function useNuxtViteConfig () {
})
nuxt.hook('vite:extendConfig', (config, { isClient }) => {
// @ts-ignore
if (isClient) resolve({ ...config })
if (isClient) {
resolve({ ...config })
}
})
})
nuxt.ready().then(async () => {
buildNuxt(nuxt)
})
nuxt.ready()
.then(() => buildNuxt(nuxt))
.catch(err => {
if (!err.toString().includes('_stop_')) {
reject(err)
}
})
}),
nuxt,
}
Expand Down
70 changes: 70 additions & 0 deletions packages/histoire-plugin-nuxt/src/runtime/app-setup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import type { App } from 'h3'
import { createApp } from 'h3'
import { createFetch } from 'ofetch'

export async function setupNuxtApp () {
const win = window as unknown as Window & {
__app: App
__registry: Set<string>
__NUXT__: any
$fetch: any
fetch: any
IntersectionObserver: any
Headers: any
}

win.__NUXT__ = {
serverRendered: false,
config: {
public: {},
app: { baseURL: '/' },
},
data: {},
state: {},
}

const app = win.document.createElement('div')
// this is a workaround for a happy-dom bug with ids beginning with _
app.id = 'nuxt-test'
win.document.body.appendChild(app)

win.IntersectionObserver =
win.IntersectionObserver ||
class IntersectionObserver {
observe () {
// noop
}
}

const h3App = createApp()

const registry = new Set<string>()

if (__HST_COLLECT__) {
const {
toNodeListener,
} = await import('h3')
const {
createCall,
createFetch: createLocalFetch,
} = await import('unenv/runtime/fetch/index')
// @ts-expect-error TODO: fix in h3
const localCall = createCall(toNodeListener(h3App))
const localFetch = createLocalFetch(localCall, globalThis.fetch)

win.fetch = (init: string, options?: any) => {
if (typeof init === 'string' && registry.has(init)) {
init = '/_' + init
}
return localFetch(init, options)
}
}

win.$fetch = createFetch({ fetch: win.fetch, Headers: win.Headers as any })

win.__registry = registry
win.__app = h3App

// @ts-ignore
await import('#app/entry')
}
12 changes: 11 additions & 1 deletion packages/histoire-shared/src/types/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,7 @@ export interface HistoireConfig {
/**
* Transpile dependencies when collecting stories on Node.js
*/
viteNodeInlineDeps?: RegExp[]
viteNodeInlineDeps?: (string | RegExp)[]
/**
* Determine the transform method of modules
*/
Expand All @@ -232,6 +232,16 @@ export interface HistoireConfig {
* By default based on available number of cores.
*/
collectMaxThreads?: number
/**
* Build options
*/
build?: {
/**
* By default all dependencies in `node_modules` are bundled into a single 'vendors' file.
* You can use this option to exclude some dependencies from this file.
*/
excludeFromVendorsChunk?: (string | RegExp)[]
}
}

export type ConfigMode = 'build' | 'dev'
18 changes: 13 additions & 5 deletions packages/histoire-shared/src/types/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type path from 'pathe'
import type fs from 'fs-extra'
import type pc from 'picocolors'
import type chokidar from 'chokidar'
import type { InlineConfig as ViteInlineConfig } from 'vite'
import type {
ServerStoryFile,
ServerStory,
Expand Down Expand Up @@ -56,13 +57,16 @@ export interface PluginApiDev extends PluginApiBase {
watcher: typeof chokidar
}

export type BuildEndCallback = () => Promise<void> | void
export type PreviewStoryCallback = (payload: { file: string, story: ServerStory, variant: ServerVariant, url: string }) => Promise<void> | void
export type ChangeViteConfigCallback = (config: ViteInlineConfig) => Awaitable<void>
export type BuildEndCallback = () => Awaitable<void>
export type PreviewStoryCallback = (payload: { file: string, story: ServerStory, variant: ServerVariant, url: string }) => Awaitable<void>

export interface PluginApiBuild extends PluginApiBase {
changeViteConfigCallbacks: ChangeViteConfigCallback[]
buildEndCallbacks: BuildEndCallback[]
previewStoryCallbacks: PreviewStoryCallback[]

changeViteConfig: (cb: ChangeViteConfigCallback) => void
onBuildEnd: (cb: BuildEndCallback) => void
onPreviewStory: (cb: PreviewStoryCallback) => void
}
Expand Down Expand Up @@ -98,16 +102,20 @@ export interface Plugin {
/**
* Use this hook to read and store the final resolved histoire config.
*/
configResolved?: (config: HistoireConfig) => void | Promise<void>
configResolved?: (config: HistoireConfig) => Awaitable<void>
/**
* Use this hook to do processing during development. The `onCleanup` hook
* should handle cleanup tasks when development server is closed.
*/
onDev?: (api: PluginApiDev, onCleanup: (cb: () => void | Promise<void>) => void) => void | Promise<void>
onDev?: (api: PluginApiDev, onCleanup: (cb: () => Awaitable<void>) => void) => Awaitable<void>
/**
* Use this hook to do processing during production build.
*/
onBuild?: (api: PluginApiBuild) => void | Promise<void>
onBuild?: (api: PluginApiBuild) => Awaitable<void>
/**
* Use this hook to do processing when preview is started.
*/
onPreview?: () => Awaitable<void>
/**
* This plugin exposes a support plugin (example: Vue, Svelte, etc.)
*/
Expand Down
Loading

0 comments on commit b73b2da

Please sign in to comment.