Skip to content

Commit e59ff4f

Browse files
committed
feat: add env module, resolve #852
1 parent c182c3f commit e59ff4f

File tree

10 files changed

+150
-68
lines changed

10 files changed

+150
-68
lines changed

examples/nuxt/app.vue

+3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
<script setup lang="ts">
22
import { message } from '~build/meta';
3+
import { BUILD_MESSAGE } from '~build/env';
4+
5+
console.log('BUILD_MESSAGE:', BUILD_MESSAGE);
36
</script>
47

58
<template>

examples/nuxt/env.d.ts

+4
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,7 @@
33
declare module '~build/meta' {
44
export const message: string;
55
}
6+
7+
declare module '~build/env' {
8+
export const BUILD_MESSAGE: string;
9+
}

examples/nuxt/nuxt.config.ts

+3
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@ export default defineNuxtConfig({
33
info: {
44
meta: {
55
message: 'This is from nuxt.config.ts'
6+
},
7+
env: {
8+
BUILD_MESSAGE: 'invalid'
69
}
710
}
811
});

examples/nuxt/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
"build": "nuxi build",
88
"dev": "nuxi dev",
99
"generate": "nuxi generate",
10-
"start": "nuxi preview"
10+
"preview": "nuxi preview"
1111
},
1212
"devDependencies": {
1313
"nuxt": "^3.10.1",

src/core/index.ts

+69-61
Original file line numberDiff line numberDiff line change
@@ -10,81 +10,89 @@ import { BuildGitModule } from './modules/git';
1010
import { LegacyInfoModule } from './modules/info';
1111
import { BuildCIModule } from './modules/ci';
1212
import { BuildMetaModule } from './modules/meta';
13+
import { BuildEnvModule } from './modules/env';
1314
import { BuildPackageModule } from './modules/package';
1415

1516
export * from './types';
1617

17-
export const UnpluginInfo = createUnplugin<Options | undefined>((options = {}) => {
18-
const root = path.resolve(options?.root ?? process.cwd());
18+
export const UnpluginInfo = /* #__PURE__ */ createUnplugin<Options | undefined>(
19+
(options = {}, meta) => {
20+
const root = path.resolve(options?.root ?? process.cwd());
1921

20-
const modules = {
21-
Time: new BuildTimeModule(root, options),
22-
Git: new BuildGitModule(root, options),
23-
Info: new LegacyInfoModule(root, options),
24-
CI: new BuildCIModule(root, options),
25-
Meta: new BuildMetaModule(root, options),
26-
Package: new BuildPackageModule(root, options)
27-
};
22+
const modules = {
23+
Time: new BuildTimeModule(root, options),
24+
Git: new BuildGitModule(root, options),
25+
Info: new LegacyInfoModule(root, options),
26+
CI: new BuildCIModule(root, options),
27+
Meta: new BuildMetaModule(root, options),
28+
Env: new BuildEnvModule(root, options),
29+
Package: new BuildPackageModule(root, options)
30+
};
2831

29-
return {
30-
name: 'unplugin-info',
31-
async buildStart() {
32-
await Promise.all(Object.values(modules).map((mod) => mod.buildStart(this)));
33-
},
34-
async buildEnd() {
35-
await Promise.all(Object.values(modules).map((mod) => mod.buildEnd(this)));
36-
},
37-
resolveId(id) {
38-
if (
39-
Object.values(modules)
32+
return {
33+
name: 'unplugin-info',
34+
async buildStart() {
35+
await Promise.all(Object.values(modules).map((mod) => mod.buildStart(this)));
36+
},
37+
async buildEnd() {
38+
await Promise.all(Object.values(modules).map((mod) => mod.buildEnd(this)));
39+
},
40+
resolveId(id) {
41+
if (
42+
Object.values(modules)
43+
.map((m) => m.name)
44+
.includes(id)
45+
) {
46+
return `\0${id}`;
47+
}
48+
},
49+
loadInclude(id) {
50+
if (!id.startsWith('\0')) return false;
51+
id = id.slice(1);
52+
return Object.values(modules)
4053
.map((m) => m.name)
41-
.includes(id)
42-
) {
43-
return `\0${id}`;
44-
}
45-
},
46-
loadInclude(id) {
47-
if (!id.startsWith('\0')) return false;
48-
id = id.slice(1);
49-
return Object.values(modules)
50-
.map((m) => m.name)
51-
.includes(id);
52-
},
53-
async load(id) {
54-
if (!id.startsWith('\0')) return;
55-
id = id.slice(1);
54+
.includes(id);
55+
},
56+
async load(id) {
57+
if (!id.startsWith('\0')) return;
58+
id = id.slice(1);
5659

57-
for (const mod of Object.values(modules)) {
58-
if (id === mod.name) {
59-
if (id === modules.Info.name) {
60-
this.warn(
61-
`${modules.Info.name} is deprecated, please migrate to ${modules.Git.name} and ${modules.CI.name}`
62-
);
63-
}
60+
for (const mod of Object.values(modules)) {
61+
if (id === mod.name) {
62+
if (id === modules.Info.name) {
63+
this.warn(
64+
`${modules.Info.name} is deprecated, please migrate to ${modules.Git.name} and ${modules.CI.name}.`
65+
);
66+
}
67+
if (id === modules.Env.name && meta.framework !== 'vite') {
68+
this.warn(`${modules.Env.name} is only supported in Vite.`);
69+
return;
70+
}
6471

65-
return mod.load(this, id);
72+
return mod.load(this, id);
73+
}
6674
}
67-
}
68-
},
69-
vite: {
70-
handleHotUpdate({ file, server }) {
71-
// HMR: package.json
72-
if (file === normalizePath(path.resolve(root, 'package.json'))) {
73-
const module = server.moduleGraph.getModuleById('\0' + modules.Package.name);
74-
if (module) {
75-
// Invalidate module for reloading
76-
server.moduleGraph.invalidateModule(module);
75+
},
76+
vite: {
77+
handleHotUpdate({ file, server }) {
78+
// HMR: package.json
79+
if (file === normalizePath(path.resolve(root, 'package.json'))) {
80+
const module = server.moduleGraph.getModuleById('\0' + modules.Package.name);
81+
if (module) {
82+
// Invalidate module for reloading
83+
server.moduleGraph.invalidateModule(module);
7784

78-
// Reload client
79-
server.ws.send({
80-
type: 'full-reload'
81-
});
85+
// Reload client
86+
server.ws.send({
87+
type: 'full-reload'
88+
});
89+
}
8290
}
8391
}
8492
}
85-
}
86-
};
87-
});
93+
};
94+
}
95+
);
8896

8997
function normalizePath(filename: string) {
9098
return filename.split(path.win32.sep).join(path.posix.sep);

src/core/modules/env.ts

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { type Options, BuildInfoModule } from '../types';
2+
3+
export class BuildEnvModule extends BuildInfoModule {
4+
constructor(root: string, options: Options) {
5+
super('env', root, options);
6+
}
7+
8+
async load() {
9+
const { options } = this;
10+
const get = () => {
11+
if (!options?.env) return {};
12+
if (typeof options.env === 'function') {
13+
return options.env();
14+
}
15+
return options.env;
16+
};
17+
18+
const meta = await get();
19+
const body = Object.entries(meta).map(
20+
([key, value]) =>
21+
`export const ${key} = (import.meta.env.SSR ? process?.env?.['${key.replace(/'/g, '\\')}'] : undefined) ?? ${JSON.stringify(value, null, 2)};`
22+
);
23+
24+
return body.length > 0 ? body.join('\n') : 'export {};';
25+
}
26+
}

src/core/modules/meta.ts

+5-2
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,12 @@ export class BuildMetaModule extends BuildInfoModule {
1414
}
1515
return options.meta;
1616
};
17-
const body = Object.entries(await get()).map(
17+
18+
const meta = await get();
19+
const body = Object.entries(meta).map(
1820
([key, value]) => `export const ${key} = ${JSON.stringify(value, null, 2)};`
1921
);
20-
return body.join('\n');
22+
23+
return body.length > 0 ? body.join('\n') : 'export {};';
2124
}
2225
}

src/core/types.ts

+13-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
import type { SimpleGit } from 'simple-git';
22
import type { UnpluginBuildContext, UnpluginContext, TransformResult } from 'unplugin';
33

4-
type Metadata = Record<string | number, any>;
5-
64
export abstract class BuildInfoModule {
75
name: string;
86

@@ -26,6 +24,10 @@ export abstract class BuildInfoModule {
2624
): TransformResult | Promise<TransformResult>;
2725
}
2826

27+
type Metadata = Record<string | number, any>;
28+
29+
type Env = Record<string | number, any>;
30+
2931
export interface Options {
3032
/**
3133
* Git repo root path
@@ -54,6 +56,15 @@ export interface Options {
5456
*/
5557
meta?: Metadata | (() => Metadata | Promise<Metadata>);
5658

59+
/**
60+
* Pass environment variables to Vite SSR app
61+
*
62+
* For each key / value record
63+
* - In the SSR environment, it will read the environment variable (e.g. process.env.<key>)
64+
* - In the client environment, it will return the provided default value
65+
*/
66+
env?: Metadata | (() => Env | Promise<Env>);
67+
5768
/**
5869
* Custom virtual module prefix
5970
*

test/index.test.ts

+17-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { describe, expect, it } from 'vitest';
1+
import { beforeAll, describe, expect, it } from 'vitest';
22

33
import '../client.d';
44

@@ -11,6 +11,7 @@ describe('build timestamp', () => {
1111

1212
describe('build info', () => {
1313
it('should work', async () => {
14+
// @ts-ignore
1415
const { github } = await import('~build/info');
1516
expect(github).toMatchInlineSnapshot('"https://github.com/yjl9903/unplugin-info"');
1617
});
@@ -36,3 +37,18 @@ describe('build meta', () => {
3637
expect(message).toMatchInlineSnapshot('"This is set from vite.config.ts"');
3738
});
3839
});
40+
41+
describe('build env', () => {
42+
const MESSAGE = 'This is set from the vitest';
43+
44+
beforeAll(() => {
45+
// @ts-ignore
46+
process.env.BUILD_MESSAGE = MESSAGE;
47+
});
48+
49+
it('should work', async () => {
50+
// @ts-ignore
51+
const { BUILD_MESSAGE: message } = await import('~build/env');
52+
expect(message).toStrictEqual(MESSAGE);
53+
});
54+
});

vitest.config.ts

+9-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,15 @@
11
import { defineConfig } from 'vitest/config';
2+
23
import Info from './src/vite';
34

45
export default defineConfig({
56
test: {},
6-
plugins: [Info({ meta: { message: 'This is set from vite.config.ts' } })]
7+
plugins: [
8+
Info({
9+
meta: { message: 'This is set from vite.config.ts' },
10+
env: {
11+
BUILD_MESSAGE: 'This should be overwriten in SSR'
12+
}
13+
})
14+
]
715
});

0 commit comments

Comments
 (0)