Skip to content

Commit

Permalink
feat: add 'PREACT_APP_' prefixed env vars automatically & pickup .env…
Browse files Browse the repository at this point in the history
… file (#1671)
  • Loading branch information
rschristian committed Apr 11, 2022
1 parent fcd0375 commit 8d3bd42
Show file tree
Hide file tree
Showing 12 changed files with 116 additions and 14 deletions.
7 changes: 7 additions & 0 deletions .changeset/stupid-suns-crash.md
@@ -0,0 +1,7 @@
---
'preact-cli': minor
---

Any environment variables prefixed with 'PREACT_APP_' will automatically be available for reference and use in your application without having to configure `DefinePlugin` any more. Furthermore, if a `.env` file exists in the root of your application, any variables it defines will automatically be available for use.

Huge shout out to [robinvdvleuten](https://github.com/robinvdvleuten) who provided this functionality through the [`preact-cli-plugin-env-vars`](https://github.com/robinvdvleuten/preact-cli-plugin-env-vars) package in the past.
32 changes: 23 additions & 9 deletions README.md
Expand Up @@ -328,19 +328,33 @@ The default templates comes with a `.css` file for each component. You can start

### Using Environment Variables

You can reference and use environment variables in your `preact.config.js` by using `process.env`:
You can reference and use any environment variable in your application that has been prefixed with `PREACT_APP_` automatically:

> `src/index.js`
```js
export default {
webpack(config, env, helpers, options) {
if (process.env.MY_VARIABLE) {
/** You can add a config here that will only used when your variable is truthy **/
}
},
};
console.log(process.env.PREACT_APP_MY_VARIABLE);
```

If you'd like to use these variables in your application, you can use the [DefinePlugin] config from our recipes wiki.
If your variable is not prefixed, you can still add it manually by using your `preact.config.js` (see [DefinePlugin] config in the recipes wiki).

You can set and store variables using a `.env` file in the root of your project:

> `.env`
```
PREACT_APP_MY_VARIABLE="my-value"
```

You can also reference environment variables in your `preact.config.js`:

```js
export default (config, env, helpers, options) => {
if (process.env.MY_VARIABLE) {
/** You can add a config here that will only used when your variable is truthy **/
}
};
```

### Route-Based Code Splitting

Expand Down
7 changes: 7 additions & 0 deletions packages/cli/lib/commands/build.js
Expand Up @@ -92,6 +92,13 @@ async function command(src, argv) {
argv.production = toBool(argv.production);

let cwd = resolve(argv.cwd);

// we explicitly set the path as `dotenv` otherwise uses
// `process.cwd()` -- this would cause issues in environments
// like mono-repos or our test suite subjects where project root
// and the current directory differ.
require('dotenv').config({ path: resolve(cwd, '.env') });

if (argv.clean === void 0) {
let dest = resolve(cwd, argv.dest);
await promisify(rimraf)(dest);
Expand Down
9 changes: 9 additions & 0 deletions packages/cli/lib/commands/watch.js
Expand Up @@ -2,6 +2,7 @@ const runWebpack = require('../lib/webpack/run-webpack');
const { isPortFree, toBool, warn } = require('../util');
const { validateArgs } = require('./validate-args');
const getPort = require('get-port');
const { resolve } = require('path');

const options = [
{
Expand Down Expand Up @@ -105,6 +106,14 @@ async function command(src, argv) {
argv.sw = toBool(argv.sw);
}

let cwd = resolve(argv.cwd);

// we explicitly set the path as `dotenv` otherwise uses
// `process.cwd()` -- this would cause issues in environments
// like mono-repos or our test suite subjects where project root
// and the current directory differ.
require('dotenv').config({ path: resolve(cwd, '.env') });

argv.port = await determinePort(argv.port);

if (argv.https || process.env.HTTPS) {
Expand Down
20 changes: 15 additions & 5 deletions packages/cli/lib/lib/webpack/webpack-base-config.js
Expand Up @@ -306,11 +306,21 @@ module.exports = function createBaseConfig(env) {

plugins: [
new webpack.NoEmitOnErrorsPlugin(),
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify(
isProd ? 'production' : 'development'
),
}),
new webpack.DefinePlugin(
Object.keys(process.env)
.filter(key => /^PREACT_APP_/.test(key))
.reduce(
(env, key) => {
env[`process.env.${key}`] = JSON.stringify(process.env[key]);
return env;
},
{
'process.env.NODE_ENV': JSON.stringify(
isProd ? 'production' : 'development'
),
}
)
),
new webpack.ProvidePlugin({
h: ['preact', 'h'],
Fragment: ['preact', 'Fragment'],
Expand Down
1 change: 1 addition & 0 deletions packages/cli/package.json
Expand Up @@ -97,6 +97,7 @@
"critters-webpack-plugin": "^2.5.0",
"cross-spawn-promise": "^0.10.1",
"css-loader": "^5.2.4",
"dotenv": "^16.0.0",
"ejs-loader": "^0.5.0",
"envinfo": "^7.8.1",
"esm": "^3.2.25",
Expand Down
15 changes: 15 additions & 0 deletions packages/cli/tests/build.test.js
Expand Up @@ -309,4 +309,19 @@ describe('preact build', () => {
);
});
});

it('should use a custom `.env` with prefixed environment variables', async () => {
let dir = await subject('custom-dotenv');
await build(dir);

const bundleFile = (await readdir(`${dir}/build`)).find(file =>
/bundle\.\w{5}\.js$/.test(file)
);
const transpiledChunk = await readFile(
`${dir}/build/${bundleFile}`,
'utf8'
);
// "Hello World!" should replace 'process.env.PREACT_APP_MY_VARIABLE'
expect(transpiledChunk.includes('console.log("Hello World!")')).toBe(true);
});
});
1 change: 1 addition & 0 deletions packages/cli/tests/subjects/custom-dotenv/.env
@@ -0,0 +1 @@
PREACT_APP_MY_VARIABLE="Hello World!"
1 change: 1 addition & 0 deletions packages/cli/tests/subjects/custom-dotenv/index.js
@@ -0,0 +1 @@
console.log(process.env.PREACT_APP_MY_VARIABLE);
4 changes: 4 additions & 0 deletions packages/cli/tests/subjects/custom-dotenv/package.json
@@ -0,0 +1,4 @@
{
"private": true,
"name": "preact-custom-dotenv"
}
28 changes: 28 additions & 0 deletions packages/cli/tests/watch.test.js
Expand Up @@ -34,6 +34,34 @@ describe('preact', () => {

server.close();
});

it('should use a custom `.env` with prefixed environment variables', async () => {
let app = await create('default');

let header = resolve(app, './src/components/header/index.js');
let original = await readFile(header, 'utf8');
let update = original.replace(
'<h1>Preact App</h1>',
'<h1>{process.env.PREACT_APP_MY_VARIABLE}</h1>'
);
await writeFile(header, update);
await writeFile(
resolve(app, '.env'),
'PREACT_APP_MY_VARIABLE="Hello World!"'
);

server = await watch(app, 8085);

let page = await loadPage(chrome, 'http://127.0.0.1:8085/');

// "Hello World!" should replace 'process.env.PREACT_APP_MY_VARIABLE'
await waitUntilExpression(
page,
`document.querySelector('header > h1').innerText === 'Hello World!'`
);

server.close();
});
});

describe('should determine the correct port', () => {
Expand Down
5 changes: 5 additions & 0 deletions yarn.lock
Expand Up @@ -5668,6 +5668,11 @@ dot-prop@^6.0.1:
dependencies:
is-obj "^2.0.0"

dotenv@^16.0.0:
version "16.0.0"
resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.0.0.tgz#c619001253be89ebb638d027b609c75c26e47411"
integrity sha512-qD9WU0MPM4SWLPJy/r2Be+2WgQj8plChsyrCNQzW/0WjvcJQiKQJ9mH3ZgB3fxbUUxgc/11ZJ0Fi5KiimWGz2Q==

dotenv@^5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-5.0.1.tgz#a5317459bd3d79ab88cff6e44057a6a3fbb1fcef"
Expand Down

0 comments on commit 8d3bd42

Please sign in to comment.