From b6cace2ab297f297f0227530d5c72838dc926584 Mon Sep 17 00:00:00 2001 From: Alec Larson <1925840+aleclarson@users.noreply.github.com> Date: Tue, 15 Nov 2022 14:17:02 -0500 Subject: [PATCH 1/2] chore: emit .d.cts files This will allow CJS projects using "moduleResolution: node16" to import Vite. --- packages/vite/package.json | 8 +++-- packages/vite/rollup.config.ts | 21 ++++++++++- packages/vite/scripts/emitCjsTypes.ts | 51 +++++++++++++++++++++++++++ 3 files changed, 77 insertions(+), 3 deletions(-) create mode 100644 packages/vite/scripts/emitCjsTypes.ts diff --git a/packages/vite/package.json b/packages/vite/package.json index 0105685dca0195..d3142dff3b357a 100644 --- a/packages/vite/package.json +++ b/packages/vite/package.json @@ -13,7 +13,10 @@ "types": "./dist/node/index.d.ts", "exports": { ".": { - "types": "./dist/node/index.d.ts", + "types": { + "import": "./dist/node/index.d.ts", + "require": "./dist/node-cjs/index.d.cts" + }, "import": "./dist/node/index.js", "require": "./index.cjs" }, @@ -46,12 +49,13 @@ "dev": "rimraf dist && pnpm run build-bundle -w", "build": "rimraf dist && run-s build-bundle build-types", "build-bundle": "rollup --config rollup.config.ts --configPlugin typescript", - "build-types": "run-s build-types-temp build-types-pre-patch build-types-roll build-types-post-patch build-types-check", + "build-types": "run-s build-types-temp build-types-pre-patch build-types-roll build-types-post-patch build-types-check build-emit-cjs-types", "build-types-temp": "tsc --emitDeclarationOnly --outDir temp/node -p src/node", "build-types-pre-patch": "tsx scripts/prePatchTypes.ts", "build-types-roll": "api-extractor run && rimraf temp", "build-types-post-patch": "tsx scripts/postPatchTypes.ts", "build-types-check": "tsc --project tsconfig.check.json", + "build-emit-cjs-types": "tsx scripts/emitCjsTypes.ts", "lint": "eslint --cache --ext .ts src/**", "format": "prettier --write --cache --parser typescript \"src/**/*.ts\"", "prepublishOnly": "npm run build" diff --git a/packages/vite/rollup.config.ts b/packages/vite/rollup.config.ts index e25beca7b2fd6e..6af8b3eebe6596 100644 --- a/packages/vite/rollup.config.ts +++ b/packages/vite/rollup.config.ts @@ -1,6 +1,7 @@ import { readFileSync } from 'node:fs' import path from 'node:path' import { fileURLToPath } from 'node:url' +import { spawn } from 'node:child_process' import nodeResolve from '@rollup/plugin-node-resolve' import typescript from '@rollup/plugin-typescript' import commonjs from '@rollup/plugin-commonjs' @@ -179,7 +180,11 @@ function createCjsConfig(isProduction: boolean) { ...Object.keys(pkg.dependencies), ...(isProduction ? [] : Object.keys(pkg.devDependencies)) ], - plugins: [...createNodePlugins(false, false, false), bundleSizeLimit(120)] + plugins: [ + ...createNodePlugins(false, false, false), + !isProduction && cjsTypesPlugin(), + bundleSizeLimit(160) + ] }) } @@ -323,4 +328,18 @@ function bundleSizeLimit(limit: number): Plugin { } } +function cjsTypesPlugin(): Plugin { + return { + name: 'cjs-types', + generateBundle() { + const proc = spawn('pnpm', ['build-emit-cjs-types'], { + stdio: 'inherit' + }) + return new Promise((resolve) => { + proc.once('close', resolve) + }) + } + } +} + // #endregion diff --git a/packages/vite/scripts/emitCjsTypes.ts b/packages/vite/scripts/emitCjsTypes.ts new file mode 100644 index 00000000000000..ed7bec34c72411 --- /dev/null +++ b/packages/vite/scripts/emitCjsTypes.ts @@ -0,0 +1,51 @@ +import fs from 'node:fs' +import path from 'node:path' +import glob from 'fast-glob' +import * as lexer from 'es-module-lexer' +import colors from 'picocolors' + +async function main() { + const typeFiles = await glob('**/*.d.ts', { + cwd: 'dist', + absolute: true + }) + + for (const file of typeFiles) { + let text = fs.readFileSync(file, 'utf8') + + const [imports] = lexer.parse(text) + for (const i of [...imports].reverse()) { + let id = i.n + if (!id || !/^\.\.?(?:\/|$)/.test(id)) { + continue + } + + const importedFile = path.resolve(path.dirname(file), id) + if (isDirectory(importedFile)) { + id += '/index' + } + + const cjsModuleSpec = id + '.cjs' + text = text.slice(0, i.s) + cjsModuleSpec + text.slice(i.e) + } + + const outFile = file + .replace('/node/', '/node-cjs/') + .replace(/\.ts$/, '.cts') + + fs.mkdirSync(path.dirname(outFile), { recursive: true }) + fs.writeFileSync(outFile, text) + } + + console.log(colors.green(colors.bold(`emitted CJS types`))) +} + +function isDirectory(file: string) { + try { + return fs.statSync(file).isDirectory() + } catch (e) { + return false + } +} + +main() From cbd08422b0e837a8b0b659bd77f128aa6c367981 Mon Sep 17 00:00:00 2001 From: Alec Larson <1925840+aleclarson@users.noreply.github.com> Date: Tue, 15 Nov 2022 15:20:42 -0500 Subject: [PATCH 2/2] fix: support `export type` rewrites --- packages/vite/scripts/emitCjsTypes.ts | 49 +++++++++++++++++++++++++-- 1 file changed, 47 insertions(+), 2 deletions(-) diff --git a/packages/vite/scripts/emitCjsTypes.ts b/packages/vite/scripts/emitCjsTypes.ts index ed7bec34c72411..448071c5943c32 100644 --- a/packages/vite/scripts/emitCjsTypes.ts +++ b/packages/vite/scripts/emitCjsTypes.ts @@ -14,9 +14,14 @@ async function main() { let text = fs.readFileSync(file, 'utf8') const [imports] = lexer.parse(text) - for (const i of [...imports].reverse()) { + const typeImports = parseTypeImports(text, true) + const allImports = [...imports, ...typeImports] + // In reverse order + .sort((a, b) => b.s - a.s) + + for (const i of allImports) { let id = i.n - if (!id || !/^\.\.?(?:\/|$)/.test(id)) { + if (!id || !/^\.\.?(?:\/|$)/.test(id) || id.endsWith('.cjs')) { continue } @@ -48,4 +53,44 @@ function isDirectory(file: string) { } } +// This assumes no each import/export statement is on its own line. +function parseTypeImports(code: string, exportsOnly?: boolean) { + const imports = [] + + const openKeywords = exportsOnly ? ['export'] : ['import', 'export'] + const openPattern = [['', '\n'], openKeywords, ['type']] + const fromPattern = [['from'], ['"', "'"]] + + let cursor = 0 + let pattern = openPattern + let patternIndex = 0 + + const words = code.split(/([\w/\-@.]+|\n)/g) + for (let i = 0; i < words.length; i++) { + const word = words[i].replace(/ /g, '') + if (pattern[patternIndex].includes(word)) { + if (++patternIndex === pattern.length) { + patternIndex = 0 + if (pattern === openPattern) { + pattern = fromPattern + } else if (pattern === fromPattern) { + pattern = openPattern + const moduleSpecifier = words[i + 1] + const start = cursor + words[i].length + imports.push({ + s: start, + e: start + moduleSpecifier.length, + n: moduleSpecifier + }) + } + } + } else if (patternIndex > 0 && word) { + patternIndex = 0 + } + cursor += words[i].length + } + + return imports +} + main()