From 8d3bd42759c72ed0cf2f7b8e6be11e109090c2e9 Mon Sep 17 00:00:00 2001 From: Ryan Christian <33403762+rschristian@users.noreply.github.com> Date: Mon, 11 Apr 2022 10:35:24 -0500 Subject: [PATCH] feat: add 'PREACT_APP_' prefixed env vars automatically & pickup .env file (#1671) --- .changeset/stupid-suns-crash.md | 7 ++++ README.md | 32 +++++++++++++------ packages/cli/lib/commands/build.js | 7 ++++ packages/cli/lib/commands/watch.js | 9 ++++++ .../lib/lib/webpack/webpack-base-config.js | 20 +++++++++--- packages/cli/package.json | 1 + packages/cli/tests/build.test.js | 15 +++++++++ .../cli/tests/subjects/custom-dotenv/.env | 1 + .../cli/tests/subjects/custom-dotenv/index.js | 1 + .../tests/subjects/custom-dotenv/package.json | 4 +++ packages/cli/tests/watch.test.js | 28 ++++++++++++++++ yarn.lock | 5 +++ 12 files changed, 116 insertions(+), 14 deletions(-) create mode 100644 .changeset/stupid-suns-crash.md create mode 100644 packages/cli/tests/subjects/custom-dotenv/.env create mode 100644 packages/cli/tests/subjects/custom-dotenv/index.js create mode 100644 packages/cli/tests/subjects/custom-dotenv/package.json diff --git a/.changeset/stupid-suns-crash.md b/.changeset/stupid-suns-crash.md new file mode 100644 index 000000000..1454c3b0e --- /dev/null +++ b/.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. diff --git a/README.md b/README.md index 9b547a613..a1fac105c 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/packages/cli/lib/commands/build.js b/packages/cli/lib/commands/build.js index 0f7059b43..2b965e552 100644 --- a/packages/cli/lib/commands/build.js +++ b/packages/cli/lib/commands/build.js @@ -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); diff --git a/packages/cli/lib/commands/watch.js b/packages/cli/lib/commands/watch.js index 50e67119d..20c78f7da 100644 --- a/packages/cli/lib/commands/watch.js +++ b/packages/cli/lib/commands/watch.js @@ -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 = [ { @@ -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) { diff --git a/packages/cli/lib/lib/webpack/webpack-base-config.js b/packages/cli/lib/lib/webpack/webpack-base-config.js index b56c30aee..67063aa3e 100644 --- a/packages/cli/lib/lib/webpack/webpack-base-config.js +++ b/packages/cli/lib/lib/webpack/webpack-base-config.js @@ -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'], diff --git a/packages/cli/package.json b/packages/cli/package.json index 9288bbf27..dd208fffe 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -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", diff --git a/packages/cli/tests/build.test.js b/packages/cli/tests/build.test.js index 14e258ddc..7405ce16e 100644 --- a/packages/cli/tests/build.test.js +++ b/packages/cli/tests/build.test.js @@ -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); + }); }); diff --git a/packages/cli/tests/subjects/custom-dotenv/.env b/packages/cli/tests/subjects/custom-dotenv/.env new file mode 100644 index 000000000..d7f22463c --- /dev/null +++ b/packages/cli/tests/subjects/custom-dotenv/.env @@ -0,0 +1 @@ +PREACT_APP_MY_VARIABLE="Hello World!" diff --git a/packages/cli/tests/subjects/custom-dotenv/index.js b/packages/cli/tests/subjects/custom-dotenv/index.js new file mode 100644 index 000000000..9e532391e --- /dev/null +++ b/packages/cli/tests/subjects/custom-dotenv/index.js @@ -0,0 +1 @@ +console.log(process.env.PREACT_APP_MY_VARIABLE); diff --git a/packages/cli/tests/subjects/custom-dotenv/package.json b/packages/cli/tests/subjects/custom-dotenv/package.json new file mode 100644 index 000000000..cdfa8eb1a --- /dev/null +++ b/packages/cli/tests/subjects/custom-dotenv/package.json @@ -0,0 +1,4 @@ +{ + "private": true, + "name": "preact-custom-dotenv" +} diff --git a/packages/cli/tests/watch.test.js b/packages/cli/tests/watch.test.js index bff7f6945..f7ac825fc 100644 --- a/packages/cli/tests/watch.test.js +++ b/packages/cli/tests/watch.test.js @@ -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( + '

Preact App

', + '

{process.env.PREACT_APP_MY_VARIABLE}

' + ); + 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', () => { diff --git a/yarn.lock b/yarn.lock index 9f10b0738..41b6c2a87 100644 --- a/yarn.lock +++ b/yarn.lock @@ -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"