Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: allow typescript compilation of nuxt.config #198

Closed
wants to merge 15 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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ node_modules

# Dist folders
dist
.tmp

# Coverage reports
coverage
Expand Down
42 changes: 41 additions & 1 deletion docs/guide/runtime.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

# Runtime (optional)

TypeScript runtime is needed for files not compiled by Webpack, such as **nuxt.config** file, local **modules** and **serverMiddlewares**.
Expand Down Expand Up @@ -42,3 +41,44 @@ All you need to do is update your **package.json** file:
:::

You can now use TypeScript for **nuxt.config** file, local **modules** and **serverMiddlewares**.

## Build (alpha)

::: warning
This feature is in an alpha state. Bug reports are appreciated.
:::

We now support compiling local modules, serverMiddleware and `nuxt.config.ts` using TypeScript. If you would like to test, follow these steps:

1. **Move `@nuxt/typescript-runtime` to your `devDependencies`**

```json{2-5}
...
"dependencies": {
"nuxt": "latest"
},
"devDependencies": {
"@nuxt/typescript-runtime": "latest",
"@nuxt/typescript-build": "latest"
}
```

2. **Update your build and start scripts**

```json{2-5}
"scripts": {
...
"build": "nuxt-ts build --compile",
"start": "nuxt start -c .nuxt.config/nuxt.config.js"
},
```

3. **Add `.nuxt.config` to your `.gitignore`**

4. **Use!**

```bash
yarn build && yarn start
```

The build process will generate a directory (`.nuxt.config`) in which will be placed your compiled file and any dependencies, including serverMiddleware and local modules.
12 changes: 10 additions & 2 deletions packages/types/cli/index.d.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,16 @@
import { Configuration } from '../config'

type Command = any // TBD
type Command = {
name: 'build' | 'dev' | 'generate' // TBD
description: string
usage: string
options: {
[key: string]: any // TBD
}
[key: string]: any // TBD
}

export interface Hooks {
config?(config: Configuration): void
'run:before'?(params: { argv: string [], command: Command, rootDir: string }): void
'run:before'?(params: { argv: string [], cmd: Command, rootDir: string }): void
}
7 changes: 7 additions & 0 deletions packages/typescript-runtime/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,17 @@
},
"dependencies": {
"@nuxt/types": "0.5.3",
"esm": "^3.2.25",
"execa": "^3.3.0",
"fs-extra": "^8.1.0",
"replace-in-file": "^4.2.0",
"ts-node": "^8.5.4",
"typescript": "~3.6"
},
"publishConfig": {
"access": "public"
},
"devDependencies": {
"@types/fs-extra": "^8.0.1"
}
}
146 changes: 146 additions & 0 deletions packages/typescript-runtime/src/compile.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
import path from 'path'
import { existsSync, readJson, mkdirp } from 'fs-extra'
import replaceInFile from 'replace-in-file'
import execa from 'execa'
import { Configuration as NuxtConfiguration } from '@nuxt/types'

const esm = require('esm')

interface JsonOptions {
[key: string]: number | boolean | string | Array<number | boolean | string>
}

interface CompileTypescriptOptions {
rootDir: string
tscOptions?: JsonOptions
tsConfigName?: string
}

function exec (cmd: string, args: string[]) {
args = args.filter(Boolean)

return execa(cmd, args, {
stdout: process.stdout,
stderr: process.stderr
})
}

function getNuxtConfig (
rootDir: string,
nuxtConfigName: string
): NuxtConfiguration {
const _esm = esm(module)
const nuxtConfigFile = _esm(path.resolve(rootDir, nuxtConfigName))
return nuxtConfigFile.default || nuxtConfigFile
}

function getNuxtConfigName (rootDir: string): string {
for (const filename of ['nuxt.config.ts', 'nuxt.config.js']) {
if (existsSync(path.resolve(rootDir, filename))) {
return filename
}
}
throw new Error(`Cannot read nuxt.config from ${rootDir}`)
}

async function getTypescriptCompilerOptions (
rootDir: string,
options: JsonOptions = {},
tsConfigName: string = 'tsconfig.json'
): Promise<string[]> {
let compilerOptions: string[] = []

options = await readAndMergeOptions(path.join(rootDir, tsConfigName), options)

compilerOptions = Object.keys(options).reduce((compilerOptions, option) => {
if (compilerOptions && !['rootDirs', 'paths'].includes(option)) {
compilerOptions.push(`--${option}`, String(options[option]))
}
return compilerOptions
}, [] as string[])

return [
...compilerOptions,
'--noEmit',
'false',
'--rootDir',
rootDir,
'--outDir',
path.join(rootDir, '.nuxt.config'),
'--allowJs',
'true'
]
}

async function readAndMergeOptions (
filename: string,
options: JsonOptions
): Promise<JsonOptions> {
try {
const tsConfig: { compilerOptions?: JsonOptions } = await readJson(filename)
return { ...tsConfig.compilerOptions, ...options }
} catch (e) {
throw new Error(`Cannot read ${filename}.`)
}
}

export async function compileTypescriptBuildFiles ({
rootDir,
tsConfigName,
tscOptions
}: CompileTypescriptOptions): Promise<void> {
const compileDir = '.nuxt.config'
const nuxtConfigName = getNuxtConfigName(rootDir)
const compilerOptions = await getTypescriptCompilerOptions(
rootDir,
tscOptions,
tsConfigName
)

// Compile nuxt.config
await mkdirp(compileDir)
await exec('tsc', [...compilerOptions, path.join(rootDir, nuxtConfigName)])

// Compile local modules and serverMiddleware
const nuxtConfigFile = getNuxtConfig(
path.join(rootDir, compileDir),
'nuxt.config.js'
)
const srcDir = '.'
const { serverMiddleware, modules } = nuxtConfigFile

const filesToCompile = [
...(serverMiddleware || []),
...(modules || [])
].reduce((filesToCompile, item) => {
let itemPath = ''

if (typeof item === 'string') {
itemPath = item
} else if (typeof item === 'object' && Array.isArray(item)) {
itemPath = item[0]
} else if (typeof item === 'object' && typeof item.handler === 'string') {
itemPath = item.handler
}

if (itemPath) {
const resolvedPath = path.resolve(
rootDir,
itemPath.replace(/^[@~.]\//, `${srcDir}/`).replace(/\.ts$/, '')
)
if (existsSync(`${resolvedPath}.ts`)) {
filesToCompile.push(resolvedPath)
replaceInFile.sync({
files: path.resolve(rootDir, `${compileDir}/nuxt.config.js`),
from: new RegExp(`(?<=['"\`])${itemPath}(?=['"\`])`, 'g'),
to: itemPath.replace(/\.ts$/, '')
})
}
}
return filesToCompile
}, [] as string[])

await Promise.all(
filesToCompile.map(file => exec('tsc', [...compilerOptions, file]))
)
}
35 changes: 23 additions & 12 deletions packages/typescript-runtime/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,36 @@
import { resolve } from 'path'
import { register } from 'ts-node'
import { Hooks } from '@nuxt/types/cli'
import { compileTypescriptBuildFiles } from './compile'

const hooks: Hooks = {
'run:before' ({ argv, rootDir }) {
const customPath = argv.find((_arg, index) => index > 0 && argv[index - 1] === '--tsconfig')
const tsConfigPath = resolve(customPath || rootDir, customPath && customPath.endsWith('.json') ? '' : 'tsconfig.json')
async 'run:before' ({ argv, rootDir, cmd }) {
const customPath = argv.find(
(_arg, index) => index > 0 && argv[index - 1] === '--tsconfig'
)
const tsConfigPath = resolve(
customPath || rootDir,
customPath && customPath.endsWith('.json') ? '' : 'tsconfig.json'
)

register({
project: tsConfigPath,
compilerOptions: {
module: 'commonjs'
}
})
if (cmd.name === 'build' && argv.includes('--compile')) {
await compileTypescriptBuildFiles({
rootDir,
tsConfigName: customPath || 'tsconfig.json'
})
} else {
register({
project: tsConfigPath,
compilerOptions: {
module: 'commonjs'
}
})
}
},

config (config) {
config.extensions = [...(config.extensions || []), 'ts']
}
}

export {
hooks
}
export { hooks }