From 7ae8483d21f3f5a229e48a9aabb5d1c0402d4045 Mon Sep 17 00:00:00 2001 From: Pooya Parsa Date: Sat, 28 Nov 2020 22:11:14 +0100 Subject: [PATCH] feat: support typescript via esbuild (closes #42) --- packages/nitro/src/module/nuxt2.ts | 3 + packages/nitro/src/rollup/config.ts | 4 + packages/nitro/src/rollup/esbuild.ts | 165 +++++++++++++++++++++++++++ 3 files changed, 172 insertions(+) create mode 100644 packages/nitro/src/rollup/esbuild.ts diff --git a/packages/nitro/src/module/nuxt2.ts b/packages/nitro/src/module/nuxt2.ts index 9682cdc27a..b80a17088c 100644 --- a/packages/nitro/src/module/nuxt2.ts +++ b/packages/nitro/src/module/nuxt2.ts @@ -30,6 +30,9 @@ export default function (nuxt, moduleContainer) { // Expose process.env.SIGMA_PRESET nuxt.options.env.SIGMA_PRESET = sigmaContext.preset + // .ts is supported for serverMiddleware + nuxt.options.extensions.push('ts') + // Replace nuxt server if (nuxt.server) { nuxt.server.__closed = true diff --git a/packages/nitro/src/rollup/config.ts b/packages/nitro/src/rollup/config.ts index 0a69e70aaf..748693819a 100644 --- a/packages/nitro/src/rollup/config.ts +++ b/packages/nitro/src/rollup/config.ts @@ -20,6 +20,7 @@ import { dynamicRequire } from './dynamic-require' import { externals } from './externals' import { timing } from './timing' import { autoMock } from './automock' +import esbuild from './esbuild' export type RollupConfig = InputOptions & { output: OutputOptions } @@ -110,6 +111,9 @@ export const getRollupConfig = (sigmaContext: SigmaContext) => { } })) + // ESBuild (typescript) + rollupConfig.plugins.push(esbuild({})) + // Dynamic Require Support rollupConfig.plugins.push(dynamicRequire({ dir: resolve(sigmaContext._nuxt.buildDir, 'dist/server'), diff --git a/packages/nitro/src/rollup/esbuild.ts b/packages/nitro/src/rollup/esbuild.ts new file mode 100644 index 0000000000..163aa27256 --- /dev/null +++ b/packages/nitro/src/rollup/esbuild.ts @@ -0,0 +1,165 @@ +// Based on https://github.com/egoist/rollup-plugin-esbuild (MIT) + +import { existsSync, statSync } from 'fs' +import { extname, resolve, dirname, join, relative } from 'path' +import { Plugin, PluginContext } from 'rollup' +import { startService, Loader, Service, TransformResult } from 'esbuild' +import { createFilter, FilterPattern } from '@rollup/pluginutils' + +const defaultLoaders: { [ext: string]: Loader } = { + '.ts': 'ts' +} + +export type Options = { + include?: FilterPattern + exclude?: FilterPattern + sourceMap?: boolean + minify?: boolean + target?: string | string[] + jsxFactory?: string + jsxFragment?: string + define?: { + [k: string]: string + } + /** + * Use this tsconfig file instead + * Disable it by setting to `false` + */ + tsconfig?: string | false + /** + * Map extension to esbuild loader + * Note that each entry (the extension) needs to start with a dot + */ + loaders?: { + [ext: string]: Loader | false + } +} + +export default (options: Options = {}): Plugin => { + let target: string | string[] + + const loaders = { + ...defaultLoaders + } + + if (options.loaders) { + for (const key of Object.keys(options.loaders)) { + const value = options.loaders[key] + if (typeof value === 'string') { + loaders[key] = value + } else if (value === false) { + delete loaders[key] + } + } + } + + const extensions: string[] = Object.keys(loaders) + const INCLUDE_REGEXP = new RegExp( + `\\.(${extensions.map(ext => ext.slice(1)).join('|')})$` + ) + const EXCLUDE_REGEXP = /node_modules/ + + const filter = createFilter( + options.include || INCLUDE_REGEXP, + options.exclude || EXCLUDE_REGEXP + ) + + let service: Service | undefined + + const stopService = () => { + if (service) { + service.stop() + service = undefined + } + } + + return { + name: 'esbuild', + + async buildStart () { + if (!service) { + service = await startService() + } + }, + + async transform (code, id) { + if (!filter(id)) { + return null + } + + const ext = extname(id) + const loader = loaders[ext] + + if (!loader || !service) { + return null + } + + target = options.target || 'node12' + + const result = await service.transform(code, { + loader, + target, + define: options.define, + sourcemap: options.sourceMap !== false, + sourcefile: id + }) + + printWarnings(id, result, this) + + return ( + result.code && { + code: result.code, + map: result.map || null + } + ) + }, + + buildEnd (error) { + // Stop the service early if there's error + if (error && !this.meta.watchMode) { + stopService() + } + }, + + async renderChunk (code) { + if (options.minify && service) { + const result = await service.transform(code, { + loader: 'js', + minify: true, + target + }) + if (result.code) { + return { + code: result.code, + map: result.map || null + } + } + } + return null + }, + + generateBundle () { + if (!this.meta.watchMode) { + stopService() + } + } + } +} + +function printWarnings ( + id: string, + result: TransformResult, + plugin: PluginContext +) { + if (result.warnings) { + for (const warning of result.warnings) { + let message = '[esbuild]' + if (warning.location) { + message += ` (${relative(process.cwd(), id)}:${warning.location.line}:${warning.location.column + })` + } + message += ` ${warning.text}` + plugin.warn(message) + } + } +}