Skip to content

Instantly share code, notes, and snippets.

@NoriSte
Last active October 7, 2021 02:24
Show Gist options
  • Save NoriSte/f9faf153e5cba3fb1c1b1675408cd748 to your computer and use it in GitHub Desktop.
Save NoriSte/f9faf153e5cba3fb1c1b1675408cd748 to your computer and use it in GitHub Desktop.
Mt very first plugin for Vite, discuss it on Twitter https://twitter.com/NoriSte/status/1445752344596021255
import type { Plugin } from 'vite'
import path from 'path'
const name = 'prevent-server-data-circular-imports'
const defaultOptions = { verbose: false }
// Detect if the module is in server-data
const serverDataFileRegexp = /(.*)?server-data\/src\//
// Extract the imported path
/*
import # every line starting with "import"
[\s\S] # everything, including line breaks
*? # any number of times, but greedy
# (greedy stopping at the first `from` occurrence)
from # matches `from`
(\"|') # followed by a space and a single or double quote
(?<path>.*) # capture the path and name it
(\"|') # matches the closing single/double quote
*/
const importPathRegexp = /import[\s\S]*?from (\"|')(?<path>.*)(\"|')/gim
function createLog(verbose: boolean) {
if (verbose) {
return (...params) => {
console.log(`[${name}]`, ...params)
}
}
return () => {}
}
/**
* This prevent the following build error
* FATAL ERROR: Ineffective mark-compacts near heap limit Allocation failed - JavaScript heap out of memory
* caused by a circular dependency.
*
* The error does not appear while developing, and it's caused by server-data modules that import
* from `server-data/src/index.ts` instead of pointing directly to the correct module.
*
* The purpose of `server-data/src/index.ts` is exposing a portion of its modules to the other
* packages and must not be used directly by the server-data modules.
*
* @param {Object} passedOptions Enable verbose logging
* @param {boolean} [passedOptions.verbose=false]
*/
export const preventServerDataCircularImports = (passedOptions = {}): Plugin => {
const options = {
...defaultOptions,
...passedOptions,
}
const log = createLog(options.verbose)
return {
name,
// `fullFilePath` example: /Volumes/dev/route-manager/local/server-data/src/store/createStore.ts
transform(fileContent, fullFilePath) {
// Bailout for files out of server-data
if (!serverDataFileRegexp.test(fullFilePath)) {
log(`The module is not a server-data one ${fullFilePath}`)
return null
}
log(`Parsing ${fullFilePath}`)
let importCount = 0
let importMatch: RegExpExecArray | null
// loops through all the imports
while ((importMatch = importPathRegexp.exec(fileContent))) {
importCount++
// ex. createStore.ts
const fileName = path.parse(fullFilePath).base
// ex. /Volumes/dev/route-manager/local/server-data/src/store/
const filePath = fullFilePath.replace(fileName, '')
/**
* Detect the absolute imports.
*
* @example
* import {...} from '@/local/server-data/index'
*/
if (importMatch.groups.path === '@/local/server-data/index') {
throw new Error(
`Server-data modules cannot import from server-data/src/index! File: ${fullFilePath} - Import: ${importMatch.groups.path}`,
)
}
// ex. ../../
const importPath = path.join(filePath, importMatch.groups.path)
/**
* Detect the relative imports
*
* @example
* import {...} from '../' // from server-data/src/store/createStore.ts
*/
if (importPath.endsWith('/local/server-data/src/')) {
throw new Error(
`Server-data modules cannot import from server-data/src/index! File: ${fullFilePath} - Import: ${importMatch.groups.path}`,
)
}
log(`Safe import ${importMatch[0]}`)
}
if (importCount === 0) {
log(`No imports found`)
}
// line break
log('')
// Nothing to do
return null
},
}
}
// eslint-disable-next-line import/no-default-export
export default preventServerDataCircularImports
exports.default = preventServerDataCircularImports
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment