Original link: https://blog.rxliuli.com/p/d867b35e62454483ae697185d93617ab/
foreword
Both the previous code generation – after generating dts from module css , this article implements the generation of interfaces from environment variables, so that it can have correct type hints during development.
motivation
In daily front-end development, environment variables are often used to distinguish the configuration of different environments, the most common being the server address. However, when using it, it is often impossible to get a prompt, or an environment variable is not available.
We usually use the following two ways to access environment variables
Use import.meta.env
in vite
import . meta . env . NODE_ENV ;
The type in vite importMeta.d.ts is defined as
interface ImportMeta { // 其他属性。。。 readonly env : ImportMetaEnv ; } interface ImportMetaEnv { [ key : string ] : any ; BASE_URL : string ; MODE : string ; DEV : boolean ; PROD : boolean ; SSR : boolean ; }
Or use process.env
in normal nodejs project
process . env . NODE_ENV ;
The type in nodejs process.d.ts is defined as
interface Dict < T > { [ key : string ] : T | undefined ; } interface ProcessEnv extends Dict < string > { /** * Can be used to change the default timezone at runtime */ TZ ? : string ; }
But in any case, they can’t define some custom environment variables in the project, which requires us to do it manually.
solve
Using vite as an example here, if you need to prompt custom environment variables when using import.meta.env
in the code, you need to add ImportMetaEnv
in vite- env.d.ts.
For example, add the type definition of the environment variable VITE_PORT/VITE_AUTH_TOKEN
interface ImportMetaEnv { VITE_PORT ? : string ; VITE_AUTH_TOKEN ? : string ; }
Now, the process of using environment variables becomes: add environment variable in .env => add type definition in vite-env.d.ts => use environment variable in code , as you can see, we basically added repeatedly Twice environment variables, just using a different syntax, this is exactly the problem we want to solve, automatically generating type definitions from environment variables.
accomplish
Get the path of the environment variable (vite’s environment variable type definition file has changed once and needs to be compatible)
async function getEnvPath ( cwd : string ) { let envPath = path . resolve ( cwd , "src/vite-env.d.ts" ) ; if ( await pathExists ( envPath ) ) { return envPath ; } envPath = path . resolve ( cwd , "src/env.d.ts" ) ; if ( await pathExists ( envPath ) ) { return envPath ; } throw new Error ( "未找到环境变量配置文件" ) ; }
scan all environment variables
export async function scan ( dir : string ) : Promise < string [ ] > { const files = await FastGlob ( ".env*" , { cwd : path . resolve ( dir ) , } ) ; const configs = await Promise . all ( files . map ( ( file ) => readFile ( path . resolve ( dir , file ) , "utf-8" ) ) ) ; return uniqueBy ( configs . map ( ( s ) => Object . keys ( parse ( s ) ) ) . flat ( ) ) ; }
Comparing environment variables and type definitions
export function eq ( a : string [ ] , b : string [ ] ) : boolean { const f = ( a : string , b : string ) => a . localeCompare ( b ) ; return JSON . stringify ( [ ... a ] . sort ( f ) ) === JSON . stringify ( [ ... b ] . sort ( f ) ) ; } export function getEnvs ( ast : n . ASTNode ) : string [ ] { return CodeUtil . iterator ( ast , n . TSInterfaceDeclaration ) . filter ( ( item ) => ( item . id as n . Identifier ) . name === "ImportMetaEnv" ) . flatMap ( ( ast ) => CodeUtil . iterator ( ast , n . TSPropertySignature ) ) . flatMap ( ( ast ) => CodeUtil . iterator ( ast , n . Identifier ) ) . map ( ( item ) => item . name ) ; }
Modify type definition
function convert ( ast : n . ASTNode , envs : string [ ] ) : n . ASTNode { let envInterface = CodeUtil . iterator ( ast , n . TSInterfaceDeclaration ) . find ( ( item ) => ( item . id as n . Identifier ) . name === "ImportMetaEnv" ) ; if ( ! envInterface ) { envInterface = b . tsInterfaceDeclaration ( b . identifier ( "ImportMetaEnv" ) , b . tsInterfaceBody ( [ ] ) ) ; ( ast as n . File ) . program . body . push ( envInterface ) ; } envInterface . body . body = envs . map ( ( name ) => b . tsPropertySignature . from ( { key : b . identifier ( name ) , typeAnnotation : b . tsTypeAnnotation ( b . tsStringKeyword ( ) ) , readonly : true , } ) ) ; return ast ; }
Finally, connect them
export async function gen ( cwd : string ) : Promise < void > { const envPath = await getEnvPath ( cwd ) ; const code = await readFile ( envPath , "utf-8" ) ; const ast = CodeUtil . parse ( code ) ; const envNames = await scan ( cwd ) ; if ( eq ( envNames , getEnvs ( ast ) ) ) { return ; } await writeFile ( envPath , CodeUtil . print ( convert ( ast , envNames ) ) ) ; }
Implement the vite plugin
import { Plugin } from "vite" ; import { gen } from "./gen" ; import * as path from "path" ; export function envDtsGen ( ) : Plugin { let rootPath : string ; return { name : "vite-plugin-env-dts-gen" , configResolved ( resolveConfig ) { rootPath = resolveConfig . root ; } , configureServer ( server ) { server . watcher . add ( ".env*" ) ; const listener = async ( filePath : string ) => { const relative = path . relative ( rootPath , filePath ) ; // console.log('filePath: ', relative) if ( relative . startsWith ( ".env" ) ) { await gen ( rootPath ) ; } } ; server . watcher . on ( "change" , listener ) ; server . watcher . on ( "add" , listener ) ; } , async buildStart ( ) { await gen ( rootPath ) ; } , } ; }
Full code ref: https://github.com/rxliuli/liuli-tools/blob/master/libs/vite-plugin-env-dts-gen
use
import { defineConfig } from "vite" ; import { envDtsGen } from "@liuli-util/vite-plugin-env-dts-gen" ; export default defineConfig ( { plugins : [ envDtsGen ( ) ] , } ) ;
Now, whenever the environment variable file is modified, the corresponding type definition will be automatically modified, and there will be prompts and verifications when writing code.
Epilogue
After that, two examples of existing code generation will be demonstrated.
- Generate code from graphql
- Generate type definitions from open api schema
This article is reproduced from: https://blog.rxliuli.com/p/d867b35e62454483ae697185d93617ab/
This site is for inclusion only, and the copyright belongs to the original author.