code generation – generate type definitions from environment variables

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.

Generate type definition .drawio.svg 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.