Skip to content

Commit

Permalink
Merge pull request #2 from alienfast/monorepo
Browse files Browse the repository at this point in the history
Add `workspaces` option for monorepo
  • Loading branch information
rosskevin authored Aug 26, 2023
2 parents adb63c1 + 2062915 commit 414c4b0
Show file tree
Hide file tree
Showing 6 changed files with 954 additions and 894 deletions.
16 changes: 14 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

`yarn add -D vite-plugin-tsconfig`

Vite plugin to that allows you to specify an alternate tsconfig.
Vite plugin to that allows you to specify an alternate tsconfig filename.

```ts
import { defineConfig } from 'vite'
Expand All @@ -18,7 +18,11 @@ export default defineConfig({
tsconfig({
filename: 'tsconfig.build.json',

logLevel: 'info', // optional for additional information
// optional
logLevel: 'info',

// optional
workspaces: ['packages/ui', 'packages/notifications'],
}),
],
})
Expand All @@ -36,6 +40,14 @@ Preferably, this plugin should become obsolete if `vite` includes the option to
It's a total hack. In the `config` stage, if a current `tsconfig.json` exists, it will back it up. Once that is complete, it will write the content from the provided
alternate tsconfig file to the default filename of `tsconfig.json`. When the build is finishing, it will remove the generated file and replace the original (if one existed).

## `workspaces:` - why?

In monorepos, and specifically as we have seen with storybook usage and local tsconfig paths usage in development but not production, `vite` will try and `loadTsconfigJsonForFile`,
which resolves as the package's `tsconfig.json` _regardless_ of what the root level `tsconfig.json` specifies. This means that if your development configurarations contain
anythin incompatible with your CI/production configurations, vite is going to break. We added the `workspaces` property to allow this same file-swapping hack to operate
in the workspace packages. We specifically did not try and read the root's `package.json` `workspaces` property because it may contain other packages that are irrelevant
or that you do not want to provide the additional tsconfig `filename` to swap.

## Contributing

PRs are accepted! This project is configured with `auto`, so feel free to submit a PR and `auto` will automatically create a `canary` release for you to try out.
48 changes: 24 additions & 24 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,38 +48,38 @@
"vite": ">=3.1.6"
},
"devDependencies": {
"@alienfast/eslint-config": "^4.0.7",
"@alienfast/eslint-config": "^4.0.9",
"@alienfast/tsconfig": "^1.0.1",
"@auto-it/all-contributors": "^10.46.0",
"@auto-it/first-time-contributor": "^10.46.0",
"@auto-it/released": "^10.46.0",
"@auto-it/all-contributors": "^11.0.1",
"@auto-it/first-time-contributor": "^11.0.1",
"@auto-it/released": "^11.0.1",
"@types/marked": "^5",
"@types/marked-terminal": "^3.1.3",
"auto": "^10.46.0",
"eslint": "^8.43.0",
"eslint-config-prettier": "^8.8.0",
"eslint-import-resolver-typescript": "^3.5.5",
"auto": "^11.0.1",
"eslint": "^8.48.0",
"eslint-config-prettier": "^9.0.0",
"eslint-import-resolver-typescript": "^3.6.0",
"eslint-plugin-eslint-comments": "^3.2.0",
"eslint-plugin-import": "^2.27.5",
"eslint-plugin-jest": "^27.2.2",
"eslint-plugin-n": "^16.0.1",
"eslint-plugin-prettier": "^4.2.1",
"eslint-plugin-import": "^2.28.1",
"eslint-plugin-jest": "^27.2.3",
"eslint-plugin-n": "^16.0.2",
"eslint-plugin-prettier": "^5.0.0",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-simple-import-sort": "^10.0.0",
"eslint-plugin-storybook": "^0.6.12",
"eslint-plugin-unicorn": "^47.0.0",
"eslint-plugin-unused-imports": "^2.0.0",
"execa": "^7.1.1",
"eslint-plugin-storybook": "^0.6.13",
"eslint-plugin-unicorn": "^48.0.1",
"eslint-plugin-unused-imports": "^3.0.0",
"execa": "^8.0.1",
"husky": "^8.0.3",
"lint-staged": "^13.2.2",
"lint-staged": "^14.0.1",
"npm-run-all": "^4.1.5",
"prettier": "^2.8.8",
"prettier": "^3.0.2",
"rimraf": "^5.0.1",
"tsup": "^7.1.0",
"typescript": "^5.1.3",
"vite": "^4.3.9",
"vite-plugin-dts": "^3.0.0-beta.3",
"vitest": "^0.32.2"
"tsup": "^7.2.0",
"typescript": "^5.2.2",
"vite": "^4.4.9",
"vite-plugin-dts": "^3.5.2",
"vitest": "^0.34.3"
},
"auto": {
"plugins": [
Expand All @@ -90,7 +90,7 @@
]
},
"dependencies": {
"marked": "^5.1.2",
"marked": "^7.0.4",
"marked-terminal": "^5.2.0"
}
}
64 changes: 23 additions & 41 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import { marked } from 'marked'
import TerminalRenderer from 'marked-terminal'
import { createLogger, LogLevel, Plugin } from 'vite'

import { revertTsConfig, Swapped, swapTsConfig } from './util'

marked.setOptions({
renderer: new TerminalRenderer() as any,
})
Expand All @@ -29,6 +31,11 @@ export interface PluginOptions {
* Name of the replacement tsconfig file to use
*/
filename: string

/**
* Relative paths to packages that should also have their tsconfig.json files swapped. e.g. ['packages/foo', 'packages/bar']
*/
workspaces?: string[]
}

const TSCONFIG = 'tsconfig.json'
Expand All @@ -44,65 +51,40 @@ const factory = (options: PluginOptions) => {
const log = createLogger(logLevel, { prefix: '[tsconfig]' })

let root: string
let backupFilename: string
const swapped: Swapped[] = []

const plugin: Plugin = {
name: 'vite-plugin-tsconfig',

config(config) {
root ??= config.root ?? process.cwd()

const tsconfigPath = path.resolve(root, TSCONFIG)
// swap the workspace tsconfig.json files
if (options.workspaces) {
for (const workspace of options.workspaces) {
const dir = path.resolve(root, workspace)
if (!fs.existsSync(dir)) {
throw new Error(`Expected workspace ${dir} to exist`)
}

// if the tsconfig file already exists, we need to back it up and replace it later
if (fs.existsSync(tsconfigPath)) {
log.info(`${TSCONFIG} already exists, moving it to ${TSCONFIG}.bak`)
backupFilename = path.resolve(root, `${TSCONFIG}.bak`)

// paranoia check
if (fs.existsSync(backupFilename)) {
fs.rmSync(backupFilename)
const swap = swapTsConfig(filename, dir, log)
swapped.push(swap)
}

fs.renameSync(tsconfigPath, `${tsconfigPath}.bak`)
}

// now
const providedTsConfig = path.resolve(root, filename)
if (!fs.existsSync(providedTsConfig)) {
throw new Error(`${providedTsConfig} does not exist.`)
}

log.info(`Creating ${TSCONFIG} from ${filename}`)
const providedTsConfigContent = fs.readFileSync(providedTsConfig, 'utf8')
fs.writeFileSync(tsconfigPath, BANNER + providedTsConfigContent)
// swap the root tsconfig.json file
const swap = swapTsConfig(filename, root, log)
swapped.push(swap)
},

closeBundle() {
if (!root) {
throw new Error('Expected root to be set in the vite config hook.')
}

const tsconfigPath = path.resolve(root, TSCONFIG)

// perhaps we never created the tsconfig file?
if (!fs.existsSync(tsconfigPath)) {
log.info('No tsconfig file found, nothing to do.')
return
}

// perhaps the user has a standard tsconfig file but we did not create it?
if (!hasBanner(tsconfigPath)) {
log.info('tsconfig.json found but it does not contain theb banner, nothing to do.')
return
}

log.info('Removing generated tsconfig.json')
fs.rmSync(tsconfigPath)

if (fs.existsSync(backupFilename)) {
log.info(`Restoring ${TSCONFIG} from backup`)
fs.renameSync(backupFilename, tsconfigPath)
// revert the tsconfig.json files
for (const swap of swapped) {
revertTsConfig(swap, log)
}
},
}
Expand Down
101 changes: 101 additions & 0 deletions src/util.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import fs from 'node:fs'
import path from 'node:path'

import { marked } from 'marked'
import TerminalRenderer from 'marked-terminal'
import { Logger } from 'vite'

marked.setOptions({
renderer: new TerminalRenderer() as any,
})

const TSCONFIG = 'tsconfig.json'
const BANNER = `// GENERATED via 'vite-plugin-tsconfig' - this should be automatically created and deleted inside the build process. \n`
const BAK = 'bak.vite-plugin-tsconfig'

const hasBanner = (tsconfigPath: string) => {
const content = fs.readFileSync(tsconfigPath, 'utf8')
return content.startsWith(BANNER.trim())
}

export interface Swapped {
dir: string
backupFilePath: string | undefined
}

/**
*
* @param filename
* @param dir
* @param log
*/
export const swapTsConfig = (filename: string, dir: string, log: Logger): Swapped => {
if (!fs.existsSync) {
throw new Error(`Expected dir ${dir} to exist`)
}

const tsconfigPath = path.resolve(dir, TSCONFIG)
let backupFilePath: string | undefined = undefined

// if the tsconfig.json file already exists, we need to back it up and replace it later
if (fs.existsSync(tsconfigPath)) {
log.info(`${TSCONFIG} already exists, moving it to ${TSCONFIG}.${BAK} at ${dir}`)
backupFilePath = path.resolve(dir, `${TSCONFIG}.${BAK}`)

// paranoia check
if (fs.existsSync(backupFilePath)) {
fs.rmSync(backupFilePath)
}

fs.renameSync(tsconfigPath, `${tsconfigPath}.${BAK}`)
}

// now
const providedTsConfig = path.resolve(dir, filename)
if (!fs.existsSync(providedTsConfig)) {
throw new Error(`${providedTsConfig} does not exist.`)
}

log.info(`Creating ${TSCONFIG} from ${filename} at ${dir}`)
const providedTsConfigContent = fs.readFileSync(providedTsConfig, 'utf8')
fs.writeFileSync(tsconfigPath, BANNER + providedTsConfigContent)

return { dir, backupFilePath }
}

export const revertTsConfig = (swapped: Swapped, log: Logger) => {
const { dir, backupFilePath } = swapped
if (!fs.existsSync) {
throw new Error(`Expected dir ${dir} to exist`)
}

const tsconfigPath = path.resolve(dir, TSCONFIG)

// perhaps we never created the tsconfig file?
if (!fs.existsSync(tsconfigPath)) {
log.info(`No tsconfig file found at ${dir}, nothing to do.`)
return
}

// perhaps the user has a standard tsconfig file but we did not create it?
if (!hasBanner(tsconfigPath)) {
log.info(`tsconfig.json found at ${dir} but it does not contain theb banner, nothing to do.`)
return
}

log.info(`Removing generated tsconfig.json at ${dir}`)
fs.rmSync(tsconfigPath)

if (!backupFilePath) {
log.info(`No backup file to restore at ${dir}`)
return
}

if (fs.existsSync(backupFilePath)) {
log.info(`Restoring ${TSCONFIG} from backup at ${dir}`)
fs.renameSync(backupFilePath, tsconfigPath)
} else {
// at this point it is expected
log.error(`Backup file ${backupFilePath} does not exist.`)
}
}
2 changes: 1 addition & 1 deletion tsup.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,5 @@ export default defineConfig({
clean: true,
dts: true,
format: ['esm'],
minify: true,
minify: false, // no need, and easier to debug downstream
})
Loading

0 comments on commit 414c4b0

Please sign in to comment.