Skip to content

Commit

Permalink
Merge pull request #163 from formkit/nuxt-module
Browse files Browse the repository at this point in the history
Nuxt module
  • Loading branch information
justin-schroeder committed Sep 6, 2023
2 parents f23797a + e7f8e31 commit 6218460
Show file tree
Hide file tree
Showing 23 changed files with 9,682 additions and 2,592 deletions.
11 changes: 8 additions & 3 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,21 @@ jobs:
options: --user 1001
steps:
- name: Checkout
uses: actions/checkout@v2
uses: actions/checkout@v3
- name: pnpm
uses: pnpm/action-setup@v2
with:
version: 8

# Install NPM dependencies, cache them correctly
# and run all Cypress tests
- name: Cypress run
uses: cypress-io/github-action@v2
uses: cypress-io/github-action@v6
env:
CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }}
with:
browser: chrome
wait-on: 'http://localhost:5555'
wait-on-timeout: 180
record: true
start: yarn cypress:server
start: pnpm cypress:server
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ temp
/.aws/credentials
.yalc
.idea
.nuxt
1 change: 1 addition & 0 deletions .npmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
shamefully-hoist=true
228 changes: 228 additions & 0 deletions build/build-nuxt.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,228 @@
import { existsSync, promises as fsp } from "fs"
import { pathToFileURL } from "url"
import { resolve } from "pathe"
import { consola } from "consola"
import type { ModuleMeta, NuxtModule, NuxtConfig } from "@nuxt/schema"
import { findExports } from "mlly"

interface BuildModuleOptions {
rootDir: string
srcDir?: string
sourcemap?: boolean
stub?: boolean
outDir?: string
}

interface PrepareModuleOptions {
rootDir: string
srcDir: string
}

/**
* Original source: https://github.com/nuxt/module-builder/blob/main/src/prepare.ts
* @param options
*/
async function prepareModule (options: PrepareModuleOptions) {
const { runCommand } = await import('nuxi')

return runCommand('prepare', [resolve(options.rootDir, 'build/nuxt-playground')], {
overrides: {
typescript: {
builder: 'shared'
},
imports: {
autoImport: false
},
modules: [
resolve(options.rootDir, `${options.srcDir}/module`),
function (_options, nuxt) {
nuxt.hooks.hook('app:templates', (app) => {
for (const template of app.templates) {
template.write = true
}
})
}
]
} satisfies NuxtConfig
})
}


/**
* Original source: https://github.com/nuxt/module-builder/blob/main/src/build.ts
* @param opts - options
*/
export async function buildModule(opts: BuildModuleOptions) {
const { build } = await import("unbuild")

const outDir = opts.outDir || "dist"
const srcDir = opts.srcDir || "src"

await prepareModule({ rootDir: opts.rootDir, srcDir })

await build(opts.rootDir, false, {
clean: false, // auto-animate’s build does its own cleaning
failOnWarn: false, // unbuild will validate the package.json, but we don’t want to fail on warnings
declaration: true,
sourcemap: opts.sourcemap,
stub: opts.stub,
outDir,
entries: [
{ input: `${srcDir}/module`, outDir: `${outDir}/nuxt` },
{ input: `${srcDir}/runtime/`, outDir: `${outDir}/nuxt/runtime`, ext: "mjs" },
],
rollup: {
emitCJS: false,
cjsBridge: false,
dts: {
tsconfig: "./build/nuxt-playground/tsconfig.json",
},
},
externals: [
"@nuxt/schema",
"@nuxt/schema-edge",
"@nuxt/kit",
"@nuxt/kit-edge",
"nuxt",
"nuxt-edge",
"nuxt3",
"vue",
"vue-demi",
],
hooks: {
async "rollup:done"(ctx) {
const outDir = resolve(ctx.options.outDir, 'nuxt')
// Generate CommonJS stub
await writeCJSStub(outDir)

// Load module meta
const moduleEntryPath = resolve(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 (!moduleFn || !moduleFn.getMeta) {
return
}
const moduleMeta = await moduleFn.getMeta()

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

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

// Generate types
await writeTypes(outDir, moduleMeta)
},
},
})
}

async function writeTypes(distDir: string, meta: ModuleMeta) {
const dtsFile = resolve(distDir, "types.d.ts")
const dtsFileMts = resolve(distDir, "types.d.mts")
if (existsSync(dtsFile)) {
return
}

// Read generated module types
const moduleTypesFile = resolve(distDir, "module.d.ts")
const moduleTypes = await fsp
.readFile(moduleTypesFile, "utf8")
.catch(() => "")
const typeExports = findExports(
// Replace `export { type Foo }` with `export { Foo }`
moduleTypes.replace(/export\s*{.*?}/gs, (match) =>
match.replace(/\btype\b/g, "")
)
)
const isStub = moduleTypes.includes("export *")

const schemaShims = []
const moduleImports = []

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

if (meta.configKey && hasTypeExport("ModuleOptions")) {
moduleImports.push("ModuleOptions")
schemaShims.push(
` interface NuxtConfig { ['${meta.configKey}']?: Partial<ModuleOptions> }`
)
schemaShims.push(
` interface NuxtOptions { ['${meta.configKey}']?: ModuleOptions }`
)
}
if (hasTypeExport("ModuleHooks")) {
moduleImports.push("ModuleHooks")
schemaShims.push(" interface NuxtHooks extends ModuleHooks {}")
}
if (hasTypeExport("ModuleRuntimeConfig")) {
moduleImports.push("ModuleRuntimeConfig")
schemaShims.push(" interface RuntimeConfig extends ModuleRuntimeConfig {}")
}
if (hasTypeExport("ModulePublicRuntimeConfig")) {
moduleImports.push("ModulePublicRuntimeConfig")
schemaShims.push(
" interface PublicRuntimeConfig extends ModulePublicRuntimeConfig {}"
)
}

const dtsContents = `
import { ${moduleImports.join(", ")} } from './module'
${
schemaShims.length
? `declare module '@nuxt/schema' {\n${schemaShims.join("\n")}\n}\n`
: ""
}
${
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")
if (!existsSync(dtsFileMts)) {
await fsp.writeFile(dtsFileMts, dtsContents, "utf8")
}
}

async function writeCJSStub(distDir: string) {
const cjsStubFile = resolve(distDir, "module.cjs")
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")
}
11 changes: 11 additions & 0 deletions build/build.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import createJITI from "jiti"
import { fileURLToPath } from "url"
import { dirname, resolve } from "path"

const __filename = fileURLToPath(import.meta.url)

const jiti = createJITI(__filename, {
esmResolve: true,
})

jiti("./bundle.ts")
Loading

1 comment on commit 6218460

@vercel
Copy link

@vercel vercel bot commented on 6218460 Sep 6, 2023

Choose a reason for hiding this comment

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

Please sign in to comment.