Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat: Add Bundler and tighten Docker Image #17

Merged
merged 15 commits into from
Dec 7, 2023
Merged

Conversation

ardelato
Copy link
Contributor

@ardelato ardelato commented Dec 1, 2023

Description

This has some major changes to the build logic and dev environment. The core logic remains the same.

Changes:

  • Restructured the files in the repo to be more granular with the content
  • Added tsup and tsc-alias to compile and bundle the code for specific uses and targets
  • Added path aliases in the tsconfig.json
  • Added new helper scripts to get Devs setup quickly and run docker

CR

The main goal of these changes were to allow us to have individual folders for our Typescript code -- i.e. config, core, and scripts. Then when we needed to execute the code, it would be properly compiled without nested folders in the dist folder. To accomplish that, we needed a bundler that could parse all the Typescript code and compile it for Node as standalone JS files.

I tried out different bundlers: rspack, swc, and webpack. They all had their own issues and I couldn't get them to work. This was mainly due to a Node issue with ES modules and CJS modules not being able to be mixed but also not being able to be separated when working on a Node app:
evanw/esbuild#1921 (comment).

At the end of the day I only got esbuild to work and in the desired way to address both #11 and #14. That said, esbuild still had its own issues which led me to discover the tool tsup. tsup is a wrapper around esbuild and fixes some of the underlying issues of working with the esbuild API including shimming modules -- https://tsup.egoist.dev/#inject-cjs-and-esm-shims.

Aside from that, we also need the ability to not include the config files -- i.e lh-config.js and urls.json -- in the bundled dist/index.js file in order to manually configure these options for the Docker container. If they were included then we would have to rebuild the Docker image every time we wanted to change the configs and would need to build and tag multiple images for variations in the configs. d9d6942 provides further details and explanation around this requirement.

QA

  • The setup script works
    1. Run pnpm run setup
    2. Confirm the prior templated files (.env, lh-config.js, and urls.json) have been copied to the appropriate locations.
  • The run-docker script works
    1. Follow the README setup instructions
    2. Build the docker image docker build -t vigilo .
    3. Run pnpm run start:docker
  • Confirm the .env file is not copied over to the docker image
    1. Follow the README setup instructions
    2. Build the docker image docker build -t vigilo .
    3. Run docker run --rm -it vigilo /bin/bash
    4. ls to ensure the .env file was not copied
  • start script still works - Reference the instructions from Port lighthouse-docker to TS in New Repo #1
  • updateDatadogMetricsMetadata script still works - Reference the instructions from Feature: Add Ability to Update Metadata for a Metric in Datadog #9
  • createDashboards script still works - Reference the instructions from Add script to create dashboard #8

Closes: #11
Closes: #14

cr_req 2

This separates the TS files into `src/core` and `src/scripts` to
differentiate between the files that are used in the main app and the
standalone scripts that are run from the command line. On top of that,
we will have a `src/config` folder for the config files. Since the
files are ignored by git, we need to add a `.gitkeep` file to keep the
folder in the repo.

Lastly, to make the imports easier, we will add path aliases to the
`tsconfig.json` file so we can drop the relative paths
This allows us to import the urls.json file directly, instead of
having to read the file and parse it. To enable this we need to set
the `resolveJsonModule` compiler option to `true`.
This bundler is a wrapper around esbuild, and allows us to easily
bundle our code for Node as ESM. In addition, it allows us to
better control over what we want to compile and will automatically
import dependent modules for the entry points we specify.

SWC, Rspack, Webpack, and vanilla esbuild were all considered, but
tsup was the easiest to use and configure for the simple use case of
compiling our ESM TS code for Node while still being in ESM format. On
top of that, it has finer control over the entrypoints and automatically
imports dependent modules.

The build.js file is a simple wrapper around tsup that will allow us
better control over the build process which will significant in later
commits.
This update introduces yargs options to selectively build 'core' or
'scripts' files. This enhancement allows for targeted builds, saving time
by avoiding unnecessary builds.

Tsup's automatic handling of dependencies eliminates the need for direct transpilation of core files for script builds and verbose pathing to core
and config files. This results in standalone output files, free from runtime external file dependencies.

Furthermore, the script files are now output to a 'bin' directory, maintaining a clean 'dist' directory and indicating that the bin files are standalone scripts executable from the CLI and are not part of the main app.
For Docker builds, we don't want to bundle the config files into the
dist/index.js file. By bundling them, we lose the ability to change
them without rebuilding the image. Rebuilding the image every time
we want to change the config files is not ideal and would prevent us
from using the image for other sites.

Solving this problem is a bit tricky because we want to use the same
build script for both Docker and non-Docker builds, keep the independent
core and scripts bundling, and retain the path aliasing functionality.
To achieve all of this, I used the following:
- tsup's 'external' option to exclude the "@config" modules from the bundle
- The tsc-alias package to resolve the "@config" paths to a relative path
- The process.env.DOCKER variable to determine whether to use tsup's
  'external' option or not (this variable is set in the Dockerfile).

By doing all of this, we can now pass the config files into the
container as volumes and change them without rebuilding the image. At
the same time, we can still use the same build script for local
development and retain the simplicity of standalone builds.

The use of tsc-alias was discovered in this issue:
evanw/esbuild#394 (comment).
Without it, "@config/*" paths would remain in the bundle and would
cause errors as there is no way for "@config" to be resolved as
a local path.
When we build the files for Docker, we will import the urls.json file
locally instead of the contents being bundled into the file. This
raises ERR_IMPORT_ASSERTION_TYPE_MISSING when we try to import the
file. To resolve this issue, we need to add the `type: "json"` to the
import statement as well as change the "module" and "moduleResolution"
options in the tsconfig.json file as import assertions are only supported
in 'esnext' or 'nodenext'.
These files are not meant to be used directly, but rather copied to
specific directories and modified as needed.

This is an attempt to make the root directory less cluttered.
These files are not meant to be used directly, but rather copied to
specific directories and modified as needed.

This is an attempt to make the root directory less cluttered.
dotenv will automatically remove quotes from values, but if we pass
the .env file to the Docker container with the '--env-file' option,
the quotes will be preserved. This will cause 403 Forbidden errors with
the Datadog API because its interpreted the quotes as part of the
value. The simplest solution is to add a comment to the .env.template
and leave it up to the user to ensure they don't wrap values with
quotes.
We now build a bin directory, so we need to ignore it.

The config files, 'lh-config.js' and 'urls.json' will be volume mounted
into the container, so we can ignore them.

Lastly, we no longer want to include the .env file in the image, as
we will be deploying the image to ECR and we don't want our DD keys
in the image.
This should make it easier to get started with the project.
This script will run the docker container with the config
files mounted and the .env file sourced. We can then run this script
through pnpm.
@ghost
Copy link

ghost commented Dec 1, 2023

👇 Click on the image for a new way to code review

Review these changes using an interactive CodeSee Map

Legend

CodeSee Map legend

@deltuh-vee deltuh-vee self-assigned this Dec 1, 2023
@deltuh-vee deltuh-vee added the QAing Under QA team review label Dec 1, 2023
@deltuh-vee
Copy link
Contributor

dev_block ✊
Ran into this error when trying to run pnpm run start.
CleanShot 2023-12-01 at 11 08 03

@deltuh-vee deltuh-vee removed the QAing Under QA team review label Dec 1, 2023
@ardelato
Copy link
Contributor Author

ardelato commented Dec 1, 2023

un_dev_block ☁️ , @deltuh-vee try running it with node v18. There is a warning message at the top saying node v16 was used

@deltuh-vee deltuh-vee added the QAing Under QA team review label Dec 1, 2023
@deltuh-vee
Copy link
Contributor

Was index.js supposed to be moved out of the src directory? I still see it there.
CleanShot 2023-12-01 at 14 59 20

@deltuh-vee deltuh-vee removed the QAing Under QA team review label Dec 1, 2023
@deltuh-vee
Copy link
Contributor

deltuh-vee commented Dec 1, 2023

QA 🎬

  • pnpm run setup copies .env to the root directory while lh-config.js, and urls.json are copied to the config folder
  • The run-docker script works
  • The .env file is not copied over to the docker image
  • The start, updateDatadogMetricsMetadata, createDashboards scripts still work.

batbattur
batbattur previously approved these changes Dec 4, 2023
Copy link
Contributor

@batbattur batbattur left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CR 🚛 makes sense to me. Nice work @ardelato!

I'm going to set cr req 2 since I'm not very familiar with bundlers

@batbattur
Copy link
Contributor

Sorry, accidentally pushed unrelated commit to the branch! Reverted to the last commit.

CR 🚛 from #17 (review)
QA 🛻 from #17 (comment)

Copy link
Member

@mlahargou mlahargou left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CR 📱

@@ -132,7 +133,7 @@ async function captureLighthouseMetrics(pageType: string, url: string, audits: s
console.log(`Done running Lighthouse for ${url} with form factor: ${formFactor}\n`);
}
(async() => {
let inspectList: Record<string, string[]> = JSON.parse(await fs.promises.readFile('urls.json', 'utf8'));
let inspectList: Record<string, string[]> = URLS;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice!

@ardelato ardelato merged commit 77033d4 into main Dec 7, 2023
2 checks passed
@ardelato ardelato deleted the add-tsup-bundler branch December 7, 2023 21:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Modify the Dockerfile to not copy over the lh-config.js and .env file Feature: Add a Project Bundler
4 participants