mirror of
https://github.com/bitwarden/browser
synced 2025-12-06 00:13:28 +00:00
build(cli): integrate nx (#16648)
* build(cli): integrate nx * refactor(project.json): rename "bit" builds to "commercial" * refactor(webpack.base): implement DEFAULT_PARAMS * refactor(webpack.base): move DEFAULT_PARAMS out of buildConfig
This commit is contained in:
1
.github/renovate.json5
vendored
1
.github/renovate.json5
vendored
@@ -147,6 +147,7 @@
|
||||
"@nx/eslint",
|
||||
"@nx/jest",
|
||||
"@nx/js",
|
||||
"@nx/webpack",
|
||||
"@types/chrome",
|
||||
"@types/firefox-webext-browser",
|
||||
"@types/glob",
|
||||
|
||||
86
apps/cli/project.json
Normal file
86
apps/cli/project.json
Normal file
@@ -0,0 +1,86 @@
|
||||
{
|
||||
"$schema": "../../node_modules/nx/schemas/project-schema.json",
|
||||
"name": "cli",
|
||||
"projectType": "application",
|
||||
"sourceRoot": "apps/cli/src",
|
||||
"tags": ["scope:cli", "type:app"],
|
||||
"targets": {
|
||||
"build": {
|
||||
"executor": "@nx/webpack:webpack",
|
||||
"outputs": ["{options.outputPath}"],
|
||||
"defaultConfiguration": "oss-dev",
|
||||
"options": {
|
||||
"outputPath": "dist/apps/cli",
|
||||
"webpackConfig": "apps/cli/webpack.config.js",
|
||||
"tsConfig": "apps/cli/tsconfig.json",
|
||||
"main": "apps/cli/src/bw.ts",
|
||||
"target": "node",
|
||||
"compiler": "tsc"
|
||||
},
|
||||
"configurations": {
|
||||
"oss": {
|
||||
"mode": "production",
|
||||
"outputPath": "dist/apps/cli/oss"
|
||||
},
|
||||
"oss-dev": {
|
||||
"mode": "development",
|
||||
"outputPath": "dist/apps/cli/oss-dev"
|
||||
},
|
||||
"commercial": {
|
||||
"mode": "production",
|
||||
"outputPath": "dist/apps/cli/commercial",
|
||||
"webpackConfig": "bitwarden_license/bit-cli/webpack.config.js",
|
||||
"main": "bitwarden_license/bit-cli/src/bw.ts",
|
||||
"tsConfig": "bitwarden_license/bit-cli/tsconfig.json"
|
||||
},
|
||||
"commercial-dev": {
|
||||
"mode": "development",
|
||||
"outputPath": "dist/apps/cli/commercial-dev",
|
||||
"webpackConfig": "bitwarden_license/bit-cli/webpack.config.js",
|
||||
"main": "bitwarden_license/bit-cli/src/bw.ts",
|
||||
"tsConfig": "bitwarden_license/bit-cli/tsconfig.json"
|
||||
}
|
||||
}
|
||||
},
|
||||
"serve": {
|
||||
"executor": "@nx/webpack:webpack",
|
||||
"defaultConfiguration": "oss-dev",
|
||||
"options": {
|
||||
"outputPath": "dist/apps/cli",
|
||||
"webpackConfig": "apps/cli/webpack.config.js",
|
||||
"tsConfig": "apps/cli/tsconfig.json",
|
||||
"main": "apps/cli/src/bw.ts",
|
||||
"target": "node",
|
||||
"compiler": "tsc",
|
||||
"watch": true
|
||||
},
|
||||
"configurations": {
|
||||
"oss-dev": {
|
||||
"mode": "development",
|
||||
"outputPath": "dist/apps/cli/oss-dev"
|
||||
},
|
||||
"commercial-dev": {
|
||||
"mode": "development",
|
||||
"outputPath": "dist/apps/cli/commercial-dev",
|
||||
"webpackConfig": "bitwarden_license/bit-cli/webpack.config.js",
|
||||
"main": "bitwarden_license/bit-cli/src/bw.ts",
|
||||
"tsConfig": "bitwarden_license/bit-cli/tsconfig.json"
|
||||
}
|
||||
}
|
||||
},
|
||||
"test": {
|
||||
"executor": "@nx/jest:jest",
|
||||
"outputs": ["{workspaceRoot}/coverage/{projectRoot}"],
|
||||
"options": {
|
||||
"jestConfig": "apps/cli/jest.config.js"
|
||||
}
|
||||
},
|
||||
"lint": {
|
||||
"executor": "@nx/eslint:lint",
|
||||
"outputs": ["{options.outputFile}"],
|
||||
"options": {
|
||||
"lintFilePatterns": ["apps/cli/**/*.ts"]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,16 +10,32 @@ module.exports.getEnv = function getEnv() {
|
||||
return { ENV };
|
||||
};
|
||||
|
||||
const DEFAULT_PARAMS = {
|
||||
localesPath: "./src/locales",
|
||||
modulesPath: [path.resolve("../../node_modules")],
|
||||
externalsModulesDir: "../../node_modules",
|
||||
outputPath: path.resolve(__dirname, "build"),
|
||||
watch: false,
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {{
|
||||
* configName: string;
|
||||
* entry: string;
|
||||
* tsConfig: string;
|
||||
* outputPath?: string;
|
||||
* mode?: string;
|
||||
* env?: string;
|
||||
* modulesPath?: string[];
|
||||
* localesPath?: string;
|
||||
* externalsModulesDir?: string;
|
||||
* watch?: boolean;
|
||||
* }} params
|
||||
*/
|
||||
module.exports.buildConfig = function buildConfig(params) {
|
||||
const { ENV } = module.exports.getEnv();
|
||||
params = { ...DEFAULT_PARAMS, ...params };
|
||||
const ENV = params.env || module.exports.getEnv().ENV;
|
||||
|
||||
const envConfig = config.load(ENV);
|
||||
config.log(`Building CLI - ${params.configName} version`);
|
||||
@@ -35,7 +51,7 @@ module.exports.buildConfig = function buildConfig(params) {
|
||||
|
||||
const plugins = [
|
||||
new CopyWebpackPlugin({
|
||||
patterns: [{ from: "./src/locales", to: "locales" }],
|
||||
patterns: [{ from: params.localesPath, to: "locales" }],
|
||||
}),
|
||||
new webpack.DefinePlugin({
|
||||
"process.env.BWCLI_ENV": JSON.stringify(ENV),
|
||||
@@ -61,7 +77,7 @@ module.exports.buildConfig = function buildConfig(params) {
|
||||
];
|
||||
|
||||
const webpackConfig = {
|
||||
mode: ENV,
|
||||
mode: params.mode || ENV,
|
||||
target: "node",
|
||||
devtool: ENV === "development" ? "eval-source-map" : "source-map",
|
||||
node: {
|
||||
@@ -77,19 +93,19 @@ module.exports.buildConfig = function buildConfig(params) {
|
||||
resolve: {
|
||||
extensions: [".ts", ".js"],
|
||||
symlinks: false,
|
||||
modules: [path.resolve("../../node_modules")],
|
||||
modules: params.modulesPath,
|
||||
plugins: [new TsconfigPathsPlugin({ configFile: params.tsConfig })],
|
||||
},
|
||||
output: {
|
||||
filename: "[name].js",
|
||||
path: path.resolve(__dirname, "build"),
|
||||
path: path.resolve(params.outputPath),
|
||||
clean: true,
|
||||
},
|
||||
module: { rules: moduleRules },
|
||||
plugins: plugins,
|
||||
externals: [
|
||||
nodeExternals({
|
||||
modulesDir: "../../node_modules",
|
||||
modulesDir: params.externalsModulesDir,
|
||||
allowlist: [/@bitwarden/],
|
||||
}),
|
||||
],
|
||||
@@ -97,6 +113,12 @@ module.exports.buildConfig = function buildConfig(params) {
|
||||
asyncWebAssembly: true,
|
||||
},
|
||||
};
|
||||
|
||||
if (params.watch) {
|
||||
webpackConfig.watch = true;
|
||||
webpackConfig.watchOptions = {
|
||||
ignored: /node_modules/,
|
||||
poll: 1000,
|
||||
};
|
||||
}
|
||||
return webpackConfig;
|
||||
};
|
||||
|
||||
@@ -1,7 +1,48 @@
|
||||
const path = require("path");
|
||||
const { buildConfig } = require("./webpack.base");
|
||||
|
||||
module.exports = buildConfig({
|
||||
module.exports = (webpackConfig, context) => {
|
||||
// Detect if called by Nx (context parameter exists)
|
||||
const isNxBuild = context && context.options;
|
||||
|
||||
if (isNxBuild) {
|
||||
// Nx build configuration
|
||||
const mode = context.options.mode || "development";
|
||||
if (process.env.NODE_ENV == null) {
|
||||
process.env.NODE_ENV = mode;
|
||||
}
|
||||
const ENV = (process.env.ENV = process.env.NODE_ENV);
|
||||
|
||||
return buildConfig({
|
||||
configName: "OSS",
|
||||
entry: context.options.main || "apps/cli/src/bw.ts",
|
||||
tsConfig: "tsconfig.base.json",
|
||||
outputPath: path.resolve(context.context.root, context.options.outputPath),
|
||||
mode: mode,
|
||||
env: ENV,
|
||||
modulesPath: [path.resolve("node_modules")],
|
||||
localesPath: "apps/cli/src/locales",
|
||||
externalsModulesDir: "node_modules",
|
||||
watch: context.options.watch || false,
|
||||
});
|
||||
} else {
|
||||
// npm build configuration
|
||||
if (process.env.NODE_ENV == null) {
|
||||
process.env.NODE_ENV = "development";
|
||||
}
|
||||
const ENV = (process.env.ENV = process.env.NODE_ENV);
|
||||
const mode = ENV;
|
||||
|
||||
return buildConfig({
|
||||
configName: "OSS",
|
||||
entry: "./src/bw.ts",
|
||||
tsConfig: "./tsconfig.json",
|
||||
outputPath: path.resolve(__dirname, "build"),
|
||||
mode: mode,
|
||||
env: ENV,
|
||||
modulesPath: [path.resolve("../../node_modules")],
|
||||
localesPath: "./src/locales",
|
||||
externalsModulesDir: "../../node_modules",
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,7 +1,48 @@
|
||||
const path = require("path");
|
||||
const { buildConfig } = require("../../apps/cli/webpack.base");
|
||||
|
||||
module.exports = buildConfig({
|
||||
module.exports = (webpackConfig, context) => {
|
||||
// Detect if called by Nx (context parameter exists)
|
||||
const isNxBuild = context && context.options;
|
||||
|
||||
if (isNxBuild) {
|
||||
// Nx build configuration
|
||||
const mode = context.options.mode || "development";
|
||||
if (process.env.NODE_ENV == null) {
|
||||
process.env.NODE_ENV = mode;
|
||||
}
|
||||
const ENV = (process.env.ENV = process.env.NODE_ENV);
|
||||
|
||||
return buildConfig({
|
||||
configName: "Commercial",
|
||||
entry: context.options.main || "bitwarden_license/bit-cli/src/bw.ts",
|
||||
tsConfig: "tsconfig.base.json",
|
||||
outputPath: path.resolve(context.context.root, context.options.outputPath),
|
||||
mode: mode,
|
||||
env: ENV,
|
||||
modulesPath: [path.resolve("node_modules")],
|
||||
localesPath: "apps/cli/src/locales",
|
||||
externalsModulesDir: "node_modules",
|
||||
watch: context.options.watch || false,
|
||||
});
|
||||
} else {
|
||||
// npm build configuration
|
||||
if (process.env.NODE_ENV == null) {
|
||||
process.env.NODE_ENV = "development";
|
||||
}
|
||||
const ENV = (process.env.ENV = process.env.NODE_ENV);
|
||||
const mode = ENV;
|
||||
|
||||
return buildConfig({
|
||||
configName: "Commercial",
|
||||
entry: "../../bitwarden_license/bit-cli/src/bw.ts",
|
||||
tsConfig: "../../bitwarden_license/bit-cli/tsconfig.json",
|
||||
outputPath: path.resolve(__dirname, "../../apps/cli/build"),
|
||||
mode: mode,
|
||||
env: ENV,
|
||||
modulesPath: [path.resolve("../../node_modules")],
|
||||
localesPath: "../../apps/cli/src/locales",
|
||||
externalsModulesDir: "../../node_modules",
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
208
docs/using-nx-to-build-projects.md
Normal file
208
docs/using-nx-to-build-projects.md
Normal file
@@ -0,0 +1,208 @@
|
||||
# Using Nx to Build Projects
|
||||
|
||||
Bitwarden uses [Nx](https://nx.dev/) to make building projects from the monorepo easier. To build, lint, or test a project you'll want to reference the project's `project.json` file for availible commands and their names. Then you'll run `npx nx [your_command] [your_project] [your_options]`. Run `npx nx --help` to see availible options, there are many.
|
||||
|
||||
Please note: the Nx implementation is a work in progress. Not all apps support Nx yet, CI still uses the old npm builds, and we have many "legacy" libraries that use hacks to get them into the Nx project graph.
|
||||
|
||||
## Quick Start
|
||||
|
||||
### Basic Commands
|
||||
|
||||
```bash
|
||||
# Build a project
|
||||
npx nx build cli
|
||||
npx nx build state # Modern libs and apps have simple, all lowercase target names
|
||||
npx nx build @bitwarden/common # Legacy libs have a special naming convention and include the @bitwarden prefix
|
||||
|
||||
# Test a project
|
||||
npx nx test cli
|
||||
|
||||
# Lint a project
|
||||
npx nx lint cli
|
||||
|
||||
# Serve/watch a project (for projects with serve targets)
|
||||
npx nx serve cli
|
||||
|
||||
# Build all projects that differ from origin/main
|
||||
nx affected --target=build --base=origin/main
|
||||
|
||||
# Build, lint, and test every project at once
|
||||
npx nx run-many --target=build,test,lint --all
|
||||
|
||||
# Most projects default to the "oss-dev" build, so if you need the bitwarden license build add a --configuration
|
||||
npx nx build cli --configuration=commercial-dev
|
||||
|
||||
# If you need a production build drop the "dev" suffix
|
||||
npx nx build cli --configuration=oss # or "commercial"
|
||||
|
||||
# Configurations can also be passed to run-many
|
||||
# For example: to run all Bitwarden licensed builds
|
||||
npx nx run-many --target=build,test,lint --all --configuration=commercial
|
||||
|
||||
# Outputs are distrubuted in a root level /dist/ folder
|
||||
|
||||
# Run a locally built CLI
|
||||
node dist/apps/cli/oss-dev/bw.js
|
||||
```
|
||||
|
||||
### Global Commands
|
||||
|
||||
```bash
|
||||
# See all projects
|
||||
npx nx show projects
|
||||
|
||||
# Run affected projects only (great for local dev and CI)
|
||||
npx nx affected:build
|
||||
npx nx affected:test
|
||||
npx nx affected:lint
|
||||
|
||||
# Show dependency graph
|
||||
npx nx dep-graph
|
||||
```
|
||||
|
||||
## Library Projects
|
||||
|
||||
Our libraries use two different Nx integration patterns depending on their migration status.
|
||||
|
||||
### Legacy Libraries
|
||||
|
||||
Most existing libraries use a facade pattern where `project.json` delegates to existing npm scripts. This approach maintains backward compatibility with the build methods we used before introducing Nx. These libraries are considered tech debt and Platform has a focus on updating them. For an example reference `libs/common/project.json`.
|
||||
|
||||
These libraries use `nx:run-script` executor to call existing npm scripts:
|
||||
|
||||
```json
|
||||
{
|
||||
"targets": {
|
||||
"build": {
|
||||
"executor": "nx:run-script",
|
||||
"options": {
|
||||
"script": "build"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Available Commands for Legacy Libraries
|
||||
|
||||
All legacy libraries support these standardized commands:
|
||||
|
||||
- **`nx build <library>`** - Build the library
|
||||
- **`nx build:watch <library>`** - Build and watch for changes
|
||||
- **`nx clean <library>`** - Clean build artifacts
|
||||
- **`nx test <library>`** - Run tests
|
||||
- **`nx lint <library>`** - Run linting
|
||||
|
||||
### Modern Libraries
|
||||
|
||||
Newer libraries like `libs/state` use native Nx executors for better performance and caching.
|
||||
|
||||
```json
|
||||
{
|
||||
"targets": {
|
||||
"build": {
|
||||
"executor": "@nx/js:tsc",
|
||||
"outputs": ["{options.outputPath}"],
|
||||
"options": {
|
||||
"outputPath": "dist/libs/state"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## What Happens When You Run An Nx Command
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
Start([You just ran an nx command]) --> ParseCmd[Nx parses command args]
|
||||
ParseCmd --> ReadWorkspace[Nx reads nx.json, workspace configuration, cache settings, and plugins]
|
||||
ReadWorkspace --> ReadProject[Nx reads project.json, finds the target configuration, and checks executor to use]
|
||||
ReadProject --> CheckCache{Nx checks the cache: has this exact build been done before?}
|
||||
|
||||
CheckCache -->|Cache hit| UseCached[Nx uses cached outputs, copies from .nx/cache, and skips execution]
|
||||
UseCached --> Done([Your command is done])
|
||||
|
||||
CheckCache -->|Cache miss| DetermineExecutor{Which executor is configured?}
|
||||
|
||||
DetermineExecutor -->|nx:run-script| FacadePattern[Legacy Facade Pattern]
|
||||
DetermineExecutor -->|nx/webpack:webpack| WebpackExecutor[Webpack Executor]
|
||||
DetermineExecutor -->|nx/js:tsc| TypeScriptExecutor[TypeScript Executor]
|
||||
DetermineExecutor -->|nx/jest:jest| JestExecutor[Jest Executor]
|
||||
DetermineExecutor -->|nx/eslint:lint| ESLintExecutor[ESLint Executor]
|
||||
|
||||
%% Facade Pattern Flow
|
||||
FacadePattern --> ReadPackageJson[The run-script executor finds npm script to run in package.json]
|
||||
ReadPackageJson --> RunNpmScript[Npm script is executed]
|
||||
RunNpmScript --> NpmDelegates{What does the npm script do?}
|
||||
|
||||
NpmDelegates -->|TypeScript| TSCompile[TypeScript compiles to JavaScript using tsconfig.json]
|
||||
NpmDelegates -->|Webpack| WebpackBuild[Webpack bundles and optimizes code]
|
||||
NpmDelegates -->|Jest| JestTest[Jest executes unit tests]
|
||||
|
||||
TSCompile --> FacadeOutput[Outputs written to libs/LIB/dist/]
|
||||
WebpackBuild --> FacadeOutput
|
||||
JestTest --> FacadeOutput
|
||||
FacadeOutput --> CacheResults1[Nx caches results in .nx/cache/]
|
||||
|
||||
%% Webpack Executor Flow
|
||||
WebpackExecutor --> ReadWebpackConfig[Webpack config read from apps/cli/webpack.config.js or bit-cli/webpack.config.js]
|
||||
ReadWebpackConfig --> ConfigureWebpack[Webpack configured with entry points, TypeScript paths, and plugins]
|
||||
ConfigureWebpack --> WebpackProcess[Webpack resolves paths, compiles TypeScript, bundles dependencies, and applies optimizations]
|
||||
WebpackProcess --> WebpackOutput[Single executable bundle written to dist/apps/cli/]
|
||||
WebpackOutput --> CacheResults2[Nx caches results in .nx/cache/]
|
||||
|
||||
%% TypeScript Executor Flow
|
||||
TypeScriptExecutor --> ReadTSConfig[TypeScript reads tsconfig.lib.json compilation options]
|
||||
ReadTSConfig --> TSProcess[TypeScript performs type checking, emits declarations, and compiles to JavaScript]
|
||||
TSProcess --> TSOutput[Outputs written to dist/libs/LIB/]
|
||||
TSOutput --> CacheResults3[Nx caches results in .nx/cache/]
|
||||
|
||||
%% Jest Executor Flow
|
||||
JestExecutor --> ReadJestConfig[Jest reads jest.config.js test configuration]
|
||||
ReadJestConfig --> JestProcess[Jest finds test files, runs suites, and generates coverage]
|
||||
JestProcess --> JestOutput[Test results and coverage reports output]
|
||||
JestOutput --> CacheResults4[Nx caches results in .nx/cache/]
|
||||
|
||||
%% ESLint Executor Flow
|
||||
ESLintExecutor --> ReadESLintConfig[ESLint reads .eslintrc.json rules and configuration]
|
||||
ReadESLintConfig --> ESLintProcess[ESLint checks code style, finds issues, and applies auto-fixes]
|
||||
ESLintProcess --> ESLintOutput[Lint results with errors and warnings output]
|
||||
ESLintOutput --> CacheResults5[Nx caches results in .nx/cache/]
|
||||
|
||||
%% All paths converge
|
||||
CacheResults1 --> UpdateGraph[Dependency graph updated to track project relationships]
|
||||
CacheResults2 --> UpdateGraph
|
||||
CacheResults3 --> UpdateGraph
|
||||
CacheResults4 --> UpdateGraph
|
||||
CacheResults5 --> UpdateGraph
|
||||
|
||||
UpdateGraph --> Done
|
||||
```
|
||||
|
||||
## Caching and Performance
|
||||
|
||||
### Nx Caching
|
||||
|
||||
Nx automatically caches build outputs and only rebuilds what changed:
|
||||
|
||||
```bash
|
||||
# First run builds everything
|
||||
npx nx build cli
|
||||
|
||||
# Second run uses cache (much faster)
|
||||
npx nx build cli
|
||||
```
|
||||
|
||||
### Clearing Cache
|
||||
|
||||
```bash
|
||||
# Clear all caches
|
||||
npx nx reset
|
||||
```
|
||||
|
||||
## Additional Resources
|
||||
|
||||
- [Nx Documentation](https://nx.dev/getting-started/intro)
|
||||
- [Nx CLI Reference](https://nx.dev/packages/nx/documents/cli)
|
||||
- [Nx Workspace Configuration](https://nx.dev/reference/project-configuration)
|
||||
2032
package-lock.json
generated
2032
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -170,6 +170,7 @@
|
||||
"@nx/eslint": "21.3.11",
|
||||
"@nx/jest": "21.3.11",
|
||||
"@nx/js": "21.3.11",
|
||||
"@nx/webpack": "21.3.11",
|
||||
"big-integer": "1.6.52",
|
||||
"braintree-web-drop-in": "1.44.0",
|
||||
"buffer": "6.0.3",
|
||||
|
||||
Reference in New Issue
Block a user