Skip to content

Commit 1691624

Browse files
authored
Merge pull request #176 from launchcodedev/esbuild-plugin
esbuild plugin v1
2 parents 9ded2fd + 6d76655 commit 1691624

File tree

32 files changed

+845
-179
lines changed

32 files changed

+845
-179
lines changed

.github/workflows/main.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,9 @@ jobs:
3434
env: { CI: 1 }
3535
if: matrix.os != 'ubuntu-latest'
3636

37-
# sanity check that the rollup plugin made a file without needing to loadConfig
37+
# sanity check that plugins made a file without needing to loadConfig
3838
- run: yarn --cwd ./examples/rollup-project start
39+
- run: yarn --cwd ./examples/esbuild-project start
3940

4041
- uses: codecov/codecov-action@v1
4142
if: matrix.os == 'ubuntu-latest'

.github/workflows/publishing.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,12 @@ jobs:
166166
token: ${{ secrets.NPM_TOKEN }}
167167
access: public
168168
package: ./app-config-vite/package.json
169+
- name: app-config-esbuild
170+
uses: JS-DevTools/npm-publish@v1
171+
with:
172+
token: ${{ secrets.NPM_TOKEN }}
173+
access: public
174+
package: ./app-config-esbuild/package.json
169175
- name: lcdev-app-config-inject
170176
uses: JS-DevTools/npm-publish@v1
171177
with:

app-config-config/src/index.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import {
1616
EnvironmentAliases,
1717
EnvironmentSource,
1818
asEnvOptions,
19+
currentEnvironment,
1920
} from '@app-config/node';
2021
import { markAllValuesAsSecret } from '@app-config/extensions';
2122
import { defaultExtensions, defaultEnvExtensions } from '@app-config/default-extensions';
@@ -44,6 +45,8 @@ export interface LoadedConfiguration {
4445
parsed: ParsedValue;
4546
parsedSecrets?: ParsedValue;
4647
parsedNonSecrets?: ParsedValue;
48+
/** the current environment that was loaded from given options */
49+
environment?: string;
4750
/** non-exhaustive list of files that were read (useful for reloading in plugins) */
4851
filePaths?: string[];
4952
/** if loadValidatedConfig, this is the normalized JSON schema that was used for validation */
@@ -79,7 +82,18 @@ export async function loadUnvalidatedConfig({
7982

8083
verifyParsedValue(parsed);
8184

82-
return { parsed, fullConfig: parsed.toJSON() };
85+
return {
86+
parsed,
87+
fullConfig: parsed.toJSON(),
88+
// NOTE: not checking meta values here
89+
environment: currentEnvironment(
90+
asEnvOptions(
91+
environmentOverride,
92+
environmentAliasesArg ?? defaultAliases,
93+
environmentSourceNamesArg,
94+
),
95+
),
96+
};
8397
} catch (error) {
8498
// having no APP_CONFIG environment variable is normal, and should fall through to reading files
8599
if (!NotFoundError.isNotFoundError(error)) throw error;
@@ -186,6 +200,7 @@ export async function loadUnvalidatedConfig({
186200
parsed,
187201
parsedSecrets: secrets,
188202
parsedNonSecrets: mainConfig.cloneWhere((v) => !v.meta.fromSecrets),
203+
environment: currentEnvironment(environmentOptions),
189204
fullConfig: parsed.toJSON(),
190205
filePaths: Array.from(filePaths),
191206
};

app-config-esbuild/.eslintrc.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
module.exports = require('@lcdev/eslint-config/cwd')(__dirname);

app-config-esbuild/README.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
## App Config esbuild
2+
3+
Use app-config with esbuild.
4+
5+
Install:
6+
7+
```sh
8+
yarn add -D @app-config/esbuild
9+
```
10+
11+
Then add it to your esbuild configuration:
12+
13+
```javascript
14+
const { createPlugin: appConfig } = require('@app-config/esbuild');
15+
16+
require('esbuild')
17+
.build({
18+
bundle: true,
19+
entryPoints: ['./src/index.ts'],
20+
outfile: './dist/index.js',
21+
// this is the line we care about
22+
plugins: [appConfig()],
23+
})
24+
.catch(() => process.exit(1));
25+
```
26+
27+
This will allow you to import `@app-config/main` from your application, with all
28+
filesystem and other Node.js code stripped out (when using `bundle`).

app-config-esbuild/package.json

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
{
2+
"name": "@app-config/esbuild",
3+
"description": "esbuild module resolution support for @app-config",
4+
"version": "2.7.2",
5+
"license": "MPL-2.0",
6+
"author": {
7+
"name": "Launchcode",
8+
"email": "[email protected]",
9+
"url": "https://lc.dev"
10+
},
11+
"repository": {
12+
"type": "git",
13+
"url": "https://github.com/launchcodedev/app-config.git"
14+
},
15+
"main": "dist/index.js",
16+
"module": "dist/es/index.js",
17+
"types": "dist/index.d.ts",
18+
"files": [
19+
"/dist",
20+
"!**/*.tsbuildinfo",
21+
"!**/*.test.*"
22+
],
23+
"scripts": {
24+
"build": "tsc -b",
25+
"build:es": "tsc -b tsconfig.es.json",
26+
"clean": "rm -rf dist *.tsbuildinfo",
27+
"lint": "eslint src",
28+
"fix": "eslint --fix src",
29+
"test": "jest",
30+
"prepublishOnly": "yarn clean && yarn build && yarn build:es"
31+
},
32+
"dependencies": {
33+
"@app-config/config": "^2.7.2",
34+
"@app-config/utils": "^2.7.2"
35+
},
36+
"devDependencies": {
37+
"@app-config/test-utils": "^2.7.2",
38+
"esbuild": "0.13"
39+
},
40+
"prettier": "@lcdev/prettier",
41+
"jest": {
42+
"preset": "@lcdev/jest"
43+
}
44+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// Jest Snapshot v1, https://goo.gl/fbAQLP
2+
3+
exports[`loads config correctly 1`] = `
4+
"(()=>{var r={foo:\\"bar\\"},e=(typeof window==\\"undefined\\"?globalThis:window)||{},t=e._appConfig||r;(typeof e._appConfig==\\"undefined\\"||!Object.isFrozen(e._appConfig))&&(e._appConfig=t);var a=t;console.log(a);})();
5+
"
6+
`;
7+
8+
exports[`loads currentEnvironment 1`] = `
9+
"(()=>{var t={foo:\\"bar\\"},e=(typeof window==\\"undefined\\"?globalThis:window)||{},r=e._appConfig||t;(typeof e._appConfig==\\"undefined\\"||!Object.isFrozen(e._appConfig))&&(e._appConfig=r);function o(){return\\"test\\"}console.log(o());})();
10+
"
11+
`;
12+
13+
exports[`loads validation function 1`] = `
14+
"(()=>{var v={foo:\\"bar\\"},n=(typeof window==\\"undefined\\"?globalThis:window)||{},m=n._appConfig||v;(typeof n._appConfig==\\"undefined\\"||!Object.isFrozen(n._appConfig))&&(n._appConfig=m);function P(){let s={};return function(p){\\"use strict\\";p.exports=i,p.exports.default=i;var b={type:\\"object\\",additionalProperties:!1,properties:{foo:{type:\\"string\\"}},$schema:\\"http://json-schema.org/draft-07/schema#\\"};function i(o,t){\\"use strict\\";if(t)var a=t.dataPath,g=t.parentData,h=t.parentDataProperty,y=t.rootData;else var a=\\"\\",g=void 0,h=void 0,y=o;var e=null,r=0;if(o&&typeof o==\\"object\\"&&!Array.isArray(o)){for(var f in o)if(f!==\\"foo\\"){var d={keyword:\\"additionalProperties\\",dataPath:a,schemaPath:\\"#/additionalProperties\\",params:{additionalProperty:f},message:\\"should NOT have additional properties\\"};e===null?e=[d]:e.push(d),r++}if(o.foo!==void 0&&typeof o.foo!=\\"string\\"){var l={keyword:\\"type\\",dataPath:a+\\"/foo\\",schemaPath:\\"#/properties/foo/type\\",params:{type:\\"string\\"},message:\\"should be string\\"};e===null?e=[l]:e.push(l),r++}}else{var c={keyword:\\"type\\",dataPath:a,schemaPath:\\"#/type\\",params:{type:\\"object\\"},message:\\"should be object\\"};e===null?e=[c]:e.push(c),r++}return i.errors=e,r===0}}(s),s.exports}var u=P();u({foo:12});})();
15+
"
16+
`;

app-config-esbuild/src/index.test.ts

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
import { withTempFiles } from '@app-config/test-utils';
2+
import { build } from 'esbuild';
3+
import createPlugin from './index';
4+
5+
it('loads config correctly', () =>
6+
withTempFiles(
7+
{
8+
'.app-config.schema.yml': `
9+
type: object
10+
additionalProperties: true
11+
`,
12+
'.app-config.yml': `
13+
foo: bar
14+
`,
15+
'a.js': `
16+
import config from '@app-config/main';
17+
console.log(config);
18+
`,
19+
},
20+
async (inDir) => {
21+
const res = await build({
22+
entryPoints: [inDir('a.js')],
23+
plugins: [createPlugin({ loadingOptions: { directory: inDir('.') } })],
24+
bundle: true,
25+
minify: true,
26+
write: false,
27+
});
28+
29+
expect(res.outputFiles[0].text).toMatchSnapshot();
30+
},
31+
));
32+
33+
it('fails when config is incorrect', () =>
34+
withTempFiles(
35+
{
36+
'.app-config.schema.yml': `
37+
type: object
38+
additionalProperties: false
39+
`,
40+
'.app-config.yml': `
41+
foo: bar
42+
`,
43+
'a.js': `
44+
import config from '@app-config/main';
45+
console.log(config);
46+
`,
47+
},
48+
async (inDir) => {
49+
await expect(
50+
build({
51+
entryPoints: [inDir('a.js')],
52+
plugins: [createPlugin({ loadingOptions: { directory: inDir('.') } })],
53+
bundle: true,
54+
minify: true,
55+
write: false,
56+
}),
57+
).rejects.toThrow(
58+
'error: [plugin: @app-config/esbuild] Config is invalid: config should NOT have additional properties',
59+
);
60+
},
61+
));
62+
63+
it('loads validation function', () =>
64+
withTempFiles(
65+
{
66+
'.app-config.schema.yml': `
67+
type: object
68+
additionalProperties: false
69+
properties:
70+
foo: { type: string }
71+
`,
72+
'.app-config.yml': `
73+
foo: bar
74+
`,
75+
'a.js': `
76+
import { validateConfig } from '@app-config/main';
77+
78+
validateConfig({ foo: 12 })
79+
`,
80+
},
81+
async (inDir) => {
82+
const res = await build({
83+
entryPoints: [inDir('a.js')],
84+
plugins: [createPlugin({ loadingOptions: { directory: inDir('.') } })],
85+
bundle: true,
86+
minify: true,
87+
write: false,
88+
});
89+
90+
expect(res.outputFiles[0].text).toMatchSnapshot();
91+
},
92+
));
93+
94+
it('loads currentEnvironment', () =>
95+
withTempFiles(
96+
{
97+
'.app-config.schema.yml': `
98+
type: object
99+
additionalProperties: false
100+
properties:
101+
foo: { type: string }
102+
`,
103+
'.app-config.yml': `
104+
foo: bar
105+
`,
106+
'a.js': `
107+
import { currentEnvironment } from '@app-config/main';
108+
109+
console.log(currentEnvironment())
110+
`,
111+
},
112+
async (inDir) => {
113+
const res = await build({
114+
entryPoints: [inDir('a.js')],
115+
plugins: [createPlugin({ loadingOptions: { directory: inDir('.') } })],
116+
bundle: true,
117+
minify: true,
118+
write: false,
119+
});
120+
121+
expect(res.outputFiles[0].text).toMatchSnapshot();
122+
},
123+
));

app-config-esbuild/src/index.ts

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import type { Plugin } from 'esbuild';
2+
import path from 'path';
3+
import { ConfigLoadingOptions, loadValidatedConfig } from '@app-config/config';
4+
import { generateModuleText, packageNameRegex } from '@app-config/utils';
5+
import type { SchemaLoadingOptions } from '@app-config/schema';
6+
7+
export interface Options {
8+
useGlobalNamespace?: boolean;
9+
loadingOptions?: ConfigLoadingOptions;
10+
schemaLoadingOptions?: SchemaLoadingOptions;
11+
injectValidationFunction?: boolean;
12+
}
13+
14+
export const createPlugin = ({
15+
useGlobalNamespace = true,
16+
loadingOptions,
17+
schemaLoadingOptions,
18+
injectValidationFunction = true,
19+
}: Options = {}): Plugin => ({
20+
name: '@app-config/esbuild',
21+
setup(build) {
22+
build.onResolve({ filter: packageNameRegex }, (args) => ({
23+
path: args.path,
24+
namespace: '@app-config/esbuild',
25+
}));
26+
27+
build.onLoad({ filter: /.*/, namespace: '@app-config/esbuild' }, async () => {
28+
const { fullConfig, environment, validationFunctionCode, filePaths } =
29+
await loadValidatedConfig(loadingOptions, schemaLoadingOptions);
30+
31+
const code = generateModuleText(fullConfig, {
32+
environment,
33+
useGlobalNamespace,
34+
validationFunctionCode: injectValidationFunction ? validationFunctionCode : undefined,
35+
esmValidationCode: true,
36+
});
37+
38+
return {
39+
loader: 'js',
40+
contents: code,
41+
resolveDir: path.parse(process.cwd()).root,
42+
watchFiles: filePaths,
43+
};
44+
});
45+
},
46+
});
47+
48+
export default createPlugin;

app-config-esbuild/tsconfig.es.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"extends": "./tsconfig.json",
3+
"compilerOptions": {
4+
"target": "es2019",
5+
"module": "es2020",
6+
"outDir": "./dist/es"
7+
}
8+
}

0 commit comments

Comments
 (0)