Skip to content
This repository has been archived by the owner on Apr 6, 2023. It is now read-only.

feat(nuxi): move module-builder to unified nuxi cli #7606

Closed
wants to merge 4 commits into from
Closed
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
82 changes: 82 additions & 0 deletions packages/nuxi/src/commands/build-module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import { promises as fsp } from 'node:fs'
import { pathToFileURL } from 'node:url'
import { resolve } from 'pathe'
import consola from 'consola'

import { writeModuleTypes, writeModuleCJSStub, loadUnbuild } from '../utils/build-module'
import { defineNuxtCommand } from './index'

import type { NuxtModule } from '@nuxt/schema'

export default defineNuxtCommand({
meta: {
name: 'build-module',
usage: 'npx nuxi build-module [--stub] [--outDir] [rootDir]',
description: 'Build a nuxt module for development & production'
},
async invoke (args) {
const rootDir = resolve(args._[0] || '.')
const outDir = args.outDir || 'dist'

const { build } = await loadUnbuild(rootDir)

await build(rootDir, false, {
declaration: true,
stub: args.stub,
entries: [
'src/module',
{ input: 'src/runtime/', outDir: `${outDir}/runtime`, ext: 'mjs' }
],
rollup: {
emitCJS: false,
cjsBridge: true
},
externals: [
'@nuxt/schema',
'@nuxt/schema-edge',
'@nuxt/kit',
'@nuxt/kit-edge',
'nuxt',
'nuxt-edge',
'nuxt3',
'vue'
],
hooks: {
async 'rollup:done' (ctx) {
// Generate CommonJS setup
await writeModuleCJSStub(ctx.options.outDir)

// Load module meta
const moduleEntryPath = resolve(ctx.options.outDir, 'module.mjs')
const moduleFn: NuxtModule<any> = await import(
pathToFileURL(moduleEntryPath).toString()
).then(r => r.default || r).catch((err) => {
consola.error(err)
consola.error('Cannot load module. Please check dist:', moduleEntryPath)
return null
})

// If module is not a function, return error
if (!moduleFn) return consola.error('It seems that `export default defineNuxtModule()` is not used in module.ts. Please make sure to define it as a default export.')

const moduleMeta = await moduleFn.getMeta()

// Enhance meta using package.json
if (ctx.pkg) {
moduleMeta.name = moduleMeta?.name ?? ctx.pkg.name
moduleMeta.version = moduleMeta?.version ?? ctx.pkg.version
}

// Write meta
const metaFile = resolve(ctx.options.outDir, 'module.json')
await fsp.writeFile(metaFile, JSON.stringify(moduleMeta, null, 2), 'utf8')

// Generate types
await writeModuleTypes(ctx.options.outDir, {
meta: moduleMeta
})
}
}
})
}
})
1 change: 1 addition & 0 deletions packages/nuxi/src/commands/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ const _rDefault = (r: any) => r.default || r
export const commands = {
dev: () => import('./dev').then(_rDefault),
build: () => import('./build').then(_rDefault),
['build-module']: () => import('./build-module').then(_rDefault),
cleanup: () => import('./cleanup').then(_rDefault),
clean: () => import('./cleanup').then(_rDefault),
preview: () => import('./preview').then(_rDefault),
Expand Down
83 changes: 83 additions & 0 deletions packages/nuxi/src/utils/build-module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import { existsSync, promises as fsp } from 'node:fs'
import { resolve } from 'pathe'
import { findExports } from 'mlly'
import { importModule } from './cjs'

import type { ModuleMeta } from '@nuxt/schema'

export interface IWriteTypesOptions {
meta: ModuleMeta
// TODO: Use nuxt options to generate types
options?: any
}

export const loadUnbuild = async (rootDir: string): Promise<typeof import('unbuild')> => {
try {
return await importModule('unbuild', rootDir) as typeof import('unbuild')
} catch (e: any) {
if (e.toString().includes("Cannot find module 'unbuild'")) {
throw new Error('nuxi build-module requires `unbuild` to be installed in your module project to build project. Try installing `unbuild` first.')
}
throw e
}
}

export async function writeModuleTypes (distDir: string, { meta }: IWriteTypesOptions) {
const dtsFile = resolve(distDir, 'types.d.ts')

// Read generated module types
const moduleTypesFile = resolve(distDir, 'module.d.ts')
const moduleTypes = await fsp.readFile(moduleTypesFile, 'utf8').catch(() => '')
const typeExports = findExports(moduleTypes)
const isStub = moduleTypes.includes('export *')

const hasExportOption = (name: string) => isStub || typeExports.find(exp => exp.names.includes(name))

const schemaShims = []
const moduleImports = []
const moduleImportKeys = [
{ key: 'ModuleOptions', interfaces: ['NuxtConfig', 'NuxtOptions'] },
{ key: 'ModuleHooks', interfaces: ['ModuleHooks'] },
{ key: 'ModulePublicRuntimeConfig', interfaces: ['PublicRuntimeConfig'] },
{ key: 'ModulePrivateRuntimeConfig', interfaces: ['PrivateRuntimeConfig'] }
]

// Generate schema shims
for (const { key, interfaces } of moduleImportKeys) {
if (hasExportOption(key)) {
moduleImports.push(key)
for (const iface of interfaces) {
if (iface === 'NuxtConfig' && meta.configKey) {
schemaShims.push(` interface ${iface} { ['${meta.configKey}']?: Partial<${key}> }`)
} else if (iface === 'NuxtOptions' && meta.optionsKey) {
schemaShims.push(` interface ${iface} { ['${meta.configKey}']?: ${key} }`)
} else {
schemaShims.push(` interface ${iface} extends ${key} {}`)
}
}
}
}

const dtsContents = `
import { ${moduleImports.join(', ')} } from './module'
${schemaShims.length ? `declare module '@nuxt/schema' {\n${schemaShims.join('\n')}\n}\n` : ''}
export { ${typeExports[0].names.join(', ')} } from './module'
`

await fsp.writeFile(dtsFile, dtsContents, 'utf8')
}

export async function writeModuleCJSStub (distDir: string) {
const cjsStubFile = resolve(distDir, 'module.cjs')

// If CJS stub already exists, skip
if (existsSync(cjsStubFile)) return

const cjsStub = `module.exports = function(...args) {
return import('./module.mjs').then(m => m.default.call(this, ...args))
}
const _meta = module.exports.meta = require('./module.json')
module.exports.getMeta = () => Promise.resolve(_meta)
`
await fsp.writeFile(cjsStubFile, cjsStub, 'utf8')
}