From 0f1e36e60058049f508d6e76b055f9348df94690 Mon Sep 17 00:00:00 2001
From: Adam Bradley
Date: Sat, 17 Jul 2021 16:45:36 -0500
Subject: [PATCH] chore: add comments to build scripts
---
scripts/api.ts | 16 +++-
scripts/build.ts | 8 ++
scripts/copy-files.ts | 4 +
scripts/devserver.ts | 3 +
scripts/index.js | 15 +++-
scripts/package-json.ts | 123 ++++++++++++++++++-------------
scripts/submodule-core.ts | 7 ++
scripts/submodule-jsx-runtime.ts | 13 +++-
scripts/submodule-optimizer.ts | 3 +
scripts/submodule-qwikloader.ts | 7 +-
scripts/submodule-server.ts | 6 ++
scripts/submodule-testing.ts | 3 +
scripts/util.ts | 56 +++++++++++++-
13 files changed, 207 insertions(+), 57 deletions(-)
diff --git a/scripts/api.ts b/scripts/api.ts
index f0e931c870f..8f093a7da6a 100644
--- a/scripts/api.ts
+++ b/scripts/api.ts
@@ -3,7 +3,11 @@ import { readFileSync, writeFileSync } from 'fs';
import { join } from 'path';
import { loadConfig } from './util';
-export function apiExtractor() {
+/**
+ * Create each submodule's bundled dts file, and ensure
+ * the public API has not changed for a production build.
+ */
+function apiExtractor() {
const config = loadConfig(process.argv.slice(2));
function createTypesApi(submodule: string, corePath?: string) {
@@ -21,24 +25,32 @@ export function apiExtractor() {
fixDtsContent(dtsPath, dtsPath, corePath);
}
+ // Run the api extractor for each of the submodules
createTypesApi('core');
createTypesApi('optimizer');
createTypesApi('server', '../core');
createTypesApi('testing', '../core');
+ // the jsx-runtime.d.ts file was already generated with tsc, use this one
const jsxRuntimeSrcPath = join(config.tscDir, 'src', 'jsx_runtime.d.ts');
const jsxRuntimeDestPath = join(config.pkgDir, 'jsx-runtime.d.ts');
fixDtsContent(jsxRuntimeSrcPath, jsxRuntimeDestPath, './core');
- console.log('🦖', 'api generator');
+ console.log('🦖', 'submodule APIs generated');
}
+/**
+ * Fix up the generated dts content, and ensure it's using a relative
+ * path to find the core.d.ts file, rather than node resolving it.
+ */
function fixDtsContent(srcPath: string, destPath: string, corePath?: string) {
let dts = readFileSync(srcPath, 'utf-8');
if (corePath) {
dts = dts.replace(/@builder\.io\/qwik/g, corePath);
}
+ // for some reason api-extractor is adding this in ¯\_(ツ)_/¯
dts = dts.replace('{};', '');
+
writeFileSync(destPath, dts);
}
diff --git a/scripts/build.ts b/scripts/build.ts
index 8cfb2b2b163..8f330e16777 100644
--- a/scripts/build.ts
+++ b/scripts/build.ts
@@ -10,6 +10,13 @@ import { submoduleTesting } from './submodule-testing';
import { mkdirSync, rmSync } from 'fs';
import type { BuildConfig } from './util';
+/**
+ * Complete a full build for all of the package's submodules. Passed in
+ * config has all the correct absolute paths to read from and write to.
+ * Additionally, a dev build does not empty the directory, and uses
+ * esbuild for each of the submodules for speed. A production build will
+ * use TSC + Rollup + Terser for the core submodule.
+ */
export async function build(config: BuildConfig) {
try {
if (!config.dev) {
@@ -34,5 +41,6 @@ export async function build(config: BuildConfig) {
await submoduleOptimizer(config);
} catch (e) {
console.error(e);
+ process.exit(1);
}
}
diff --git a/scripts/copy-files.ts b/scripts/copy-files.ts
index 57fb9333f0f..b2168578aba 100644
--- a/scripts/copy-files.ts
+++ b/scripts/copy-files.ts
@@ -2,6 +2,10 @@ import type { BuildConfig } from './util';
import { copyFile } from 'fs/promises';
import { basename, join } from 'path';
+/**
+ * Manually copy some root files, such as README.md and LICENSE
+ * to the published package directory.
+ */
export async function copyFiles(config: BuildConfig) {
const rootFiles = ['README.md', 'LICENSE'];
diff --git a/scripts/devserver.ts b/scripts/devserver.ts
index da485244bed..812aa1bdfd3 100644
--- a/scripts/devserver.ts
+++ b/scripts/devserver.ts
@@ -2,6 +2,9 @@ import { build } from 'esbuild';
import { join } from 'path';
import { BuildConfig, importPath, watcher } from './util';
+/**
+ * Generate the internal integration dev server cjs module.
+ */
export async function buildDevServer(config: BuildConfig) {
const integrationDir = join(config.rootDir, 'integration');
diff --git a/scripts/index.js b/scripts/index.js
index 8115e9b273a..c194a118404 100644
--- a/scripts/index.js
+++ b/scripts/index.js
@@ -1,6 +1,13 @@
-const { register } = require('esbuild-register/dist/node');
+/**
+ * This is the root build scripts module (keep in commonjs). It's only a .js file
+ * but will handling registering typescript files with esbuild-register
+ * to allow NodeJS to build .ts files on-demand.
+ */
+
const { dirname, join } = require('path');
+const { register } = require('esbuild-register/dist/node');
+// allows NodeJS to compile TypeScript files
register({ target: 'node15' });
const { build } = require('./build.ts');
@@ -8,11 +15,17 @@ const { loadConfig } = require('./util.ts');
const args = process.argv.slice(2);
+// load our build config, which figures out all the paths
+// the rest of the build process uses.
const config = loadConfig(args);
if (process.env.BAZEL_NODE_MODULES_ROOTS) {
+ // This is a signal that Bazel has started this script
+ // If Bazel is running this, then find out where it
+ // would like to see the build output to be written.
config.pkgDir = dirname(join(process.cwd(), args[0]));
config.dev = true;
}
+// let's do this!
build(config);
diff --git a/scripts/package-json.ts b/scripts/package-json.ts
index 8c331f047aa..7a7b898b6a1 100644
--- a/scripts/package-json.ts
+++ b/scripts/package-json.ts
@@ -1,18 +1,23 @@
-import type { BuildConfig } from './util';
-import { access, readFile, writeFile } from 'fs/promises';
+import type { BuildConfig, PackageJSON } from './util';
+import { readFile, writeFile } from 'fs/promises';
import { join } from 'path';
+/**
+ * The published build does not use the package.json found in the root directory.
+ * This function generates the package.json file for package to be published.
+ * Note that some of the properties can be pulled from the root package.json.
+ */
export async function generatePackageJson(config: BuildConfig) {
const pkgJsonRoot = join(config.rootDir, 'package.json');
const pkgJsonDist = join(config.pkgDir, 'package.json');
- const pkg = JSON.parse(await readFile(pkgJsonRoot, 'utf-8'));
+ const rootPkg: PackageJSON = JSON.parse(await readFile(pkgJsonRoot, 'utf-8'));
- const distPkg = {
- name: pkg.name,
- version: pkg.version,
- description: pkg.description,
- license: pkg.license,
+ const distPkg: PackageJSON = {
+ name: rootPkg.name,
+ version: rootPkg.version,
+ description: rootPkg.description,
+ license: rootPkg.license,
main: './core.cjs',
module: './core.mjs',
types: './core.d.ts',
@@ -44,12 +49,17 @@ export async function generatePackageJson(config: BuildConfig) {
},
'./package.json': './package.json',
},
- contributors: pkg.contributors,
- homepage: pkg.homepage,
- repository: pkg.repository,
- bugs: pkg.bugs,
- keywords: pkg.keywords,
- engines: pkg.engines,
+ files: Array.from(new Set(PACKAGE_FILES)).sort((a, b) => {
+ if (a.toLocaleLowerCase() < b.toLocaleLowerCase()) return -1;
+ if (a.toLocaleLowerCase() > b.toLocaleLowerCase()) return 1;
+ return 0;
+ }),
+ contributors: rootPkg.contributors,
+ homepage: rootPkg.homepage,
+ repository: rootPkg.repository,
+ bugs: rootPkg.bugs,
+ keywords: rootPkg.keywords,
+ engines: rootPkg.engines,
};
const pkgContent = JSON.stringify(distPkg, null, 2);
@@ -59,39 +69,52 @@ export async function generatePackageJson(config: BuildConfig) {
console.log('👻', 'generate package.json');
}
-export async function validatePackageJson(config: BuildConfig) {
- const pkgPath = join(config.pkgDir, 'package.json');
- const pkg = JSON.parse(await readFile(pkgPath, 'utf-8'));
-
- await Promise.all([
- validatePath(config, pkg.main),
- validatePath(config, pkg.module),
- validatePath(config, pkg.types),
- ]);
-
- const exportKeys = Object.keys(pkg.exports);
-
- await Promise.all(
- exportKeys.map(async (exportKey) => {
- const val = pkg.exports[exportKey];
- if (typeof val === 'string') {
- await validatePath(config, val);
- } else {
- await validatePath(config, val.import);
- await validatePath(config, val.require);
- }
- })
- );
-}
-
-async function validatePath(config: BuildConfig, path: string) {
- try {
- await access(join(config.pkgDir, path));
- } catch (e) {
- console.error(
- `Error validating path "${path}" inside of "${join(config.pkgDir, 'package.json')}"`
- );
- console.error(e);
- process.exit(1);
- }
-}
+/**
+ * These are the exact outputs that should end up in the published package.
+ * This is used to create the package.json "files" property.
+ */
+const PACKAGE_FILES = [
+ 'core.cjs',
+ 'core.cjs.map',
+ 'core.min.mjs',
+ 'core.mjs',
+ 'core.mjs.map',
+ 'core.d.ts',
+ 'jsx-runtime.cjs',
+ 'jsx-runtime.mjs',
+ 'jsx-runtime.d.ts',
+ 'LICENSE',
+ 'optimizer.cjs',
+ 'optimizer.cjs.map',
+ 'optimizer.mjs',
+ 'optimizer.mjs.map',
+ 'optimizer.d.ts',
+ 'package.json',
+ 'qwikloader.js',
+ 'qwikloader.debug.js',
+ 'qwikloader.optimize.js',
+ 'qwikloader.optimize.debug.js',
+ 'README.md',
+ 'server/index.cjs',
+ 'server/index.cjs.map',
+ 'server/index.mjs',
+ 'server/index.mjs.map',
+ 'server/index.d.ts',
+ 'testing/index.cjs',
+ 'testing/index.cjs.map',
+ 'testing/index.mjs',
+ 'testing/index.mjs.map',
+ 'testing/index.d.ts',
+ 'testing/jest-preprocessor.cjs',
+ 'testing/jest-preprocessor.cjs.map',
+ 'testing/jest-preprocessor.mjs',
+ 'testing/jest-preprocessor.mjs.map',
+ 'testing/jest-preset.cjs',
+ 'testing/jest-preset.cjs.map',
+ 'testing/jest-preset.mjs',
+ 'testing/jest-preset.mjs.map',
+ 'testing/jest-setuptestframework.cjs',
+ 'testing/jest-setuptestframework.cjs.map',
+ 'testing/jest-setuptestframework.mjs',
+ 'testing/jest-setuptestframework.mjs.map',
+];
diff --git a/scripts/submodule-core.ts b/scripts/submodule-core.ts
index fa4b09cd159..2079f4865e7 100644
--- a/scripts/submodule-core.ts
+++ b/scripts/submodule-core.ts
@@ -5,6 +5,13 @@ import { minify } from 'terser';
import { BuildConfig, banner, target, watcher, fileSize } from './util';
import { readFile, writeFile } from 'fs/promises';
+/**
+ * Build the core package which is also the root package: @builder.io/qwik
+ *
+ * Uses esbuild during development (cuz it's super fast) and
+ * TSC + Rollup + Terser for production, because it generates smaller code
+ * that minifies better.
+ */
export function submoduleCore(config: BuildConfig) {
if (config.dev) {
return submoduleCoreDev(config);
diff --git a/scripts/submodule-jsx-runtime.ts b/scripts/submodule-jsx-runtime.ts
index c72313e6514..876ee1109ac 100644
--- a/scripts/submodule-jsx-runtime.ts
+++ b/scripts/submodule-jsx-runtime.ts
@@ -1,7 +1,18 @@
import { build, BuildOptions } from 'esbuild';
import { join } from 'path';
-import { BuildConfig, banner, importPath, target, watcher } from './util';
+import { BuildConfig, importPath, target, watcher } from './util';
+/**
+ * Builds @builder.io/qwik/jsx-runtime
+ *
+ * This build mainly is re-exports the jsx runtime functions
+ * provided by the core. Internally, the core's JSX renderer
+ * is using the latest jsx transform, but Qwik provides both
+ * the legacy `h()` jsx factory, and the `jsx()` runtime.
+ *
+ * - https://reactjs.org/blog/2020/09/22/introducing-the-new-jsx-transform.html
+ * - https://www.typescriptlang.org/tsconfig#jsxImportSource
+ */
export async function submoduleJsxRuntime(config: BuildConfig) {
const submodule = 'jsx-runtime';
diff --git a/scripts/submodule-optimizer.ts b/scripts/submodule-optimizer.ts
index 345fe0d7b85..eeb2e85f3f7 100644
--- a/scripts/submodule-optimizer.ts
+++ b/scripts/submodule-optimizer.ts
@@ -3,6 +3,9 @@ import { join } from 'path';
import { BuildConfig, banner, nodeBuiltIns, target, watcher } from './util';
import { readFileSync } from 'fs';
+/**
+ * Builds @builder.io/optimizer
+ */
export async function submoduleOptimizer(config: BuildConfig) {
const submodule = 'optimizer';
diff --git a/scripts/submodule-qwikloader.ts b/scripts/submodule-qwikloader.ts
index e1d539d06cb..cc889883830 100644
--- a/scripts/submodule-qwikloader.ts
+++ b/scripts/submodule-qwikloader.ts
@@ -1,10 +1,15 @@
import { InputOptions, OutputOptions, rollup, Plugin } from 'rollup';
import { minify, MinifyOptions } from 'terser';
import { BuildConfig, fileSize, rollupOnWarn } from './util';
-import { statSync } from 'fs';
import { join } from 'path';
import { Optimizer } from '../src/optimizer';
+/**
+ * Builds the qwikloader javascript files. These files can be used
+ * by other tooling, and are provided in the package so CDNs could
+ * point to them. The @builder.io/optimizer submodule also provides
+ * a utility function.
+ */
export async function submoduleQwikLoader(config: BuildConfig) {
const optimizer = new Optimizer({
rootDir: config.rootDir,
diff --git a/scripts/submodule-server.ts b/scripts/submodule-server.ts
index 04e32091829..6d0129f05f8 100644
--- a/scripts/submodule-server.ts
+++ b/scripts/submodule-server.ts
@@ -2,6 +2,12 @@ import { build, BuildOptions } from 'esbuild';
import { join } from 'path';
import { BuildConfig, banner, importPath, target, watcher, nodeBuiltIns } from './util';
+/**
+ * Builds @builder.io/server
+ *
+ * This is submodule for helping to generate server-side rendered pages,
+ * along with providing utilities for prerendering and unit testing.
+ */
export async function submoduleServer(config: BuildConfig) {
const submodule = 'server';
diff --git a/scripts/submodule-testing.ts b/scripts/submodule-testing.ts
index 2d6bae282a1..9420c1ea9ae 100644
--- a/scripts/submodule-testing.ts
+++ b/scripts/submodule-testing.ts
@@ -10,6 +10,9 @@ import {
injectDirname,
} from './util';
+/**
+ * Builds @builder.io/testing
+ */
export async function submoduleTesting(config: BuildConfig) {
const submodule = 'testing';
diff --git a/scripts/util.ts b/scripts/util.ts
index 934bc6861b1..0cf4f75eb20 100644
--- a/scripts/util.ts
+++ b/scripts/util.ts
@@ -3,6 +3,11 @@ import { join } from 'path';
import mri from 'mri';
import { stat } from 'fs/promises';
+/**
+ * Contains information about the build we're generating by parsing
+ * CLI args, and figuring out all the absolute file paths the
+ * build will be reading from and writing to.
+ */
export interface BuildConfig {
rootDir: string;
distDir: string;
@@ -10,11 +15,36 @@ export interface BuildConfig {
scriptsDir: string;
tscDir: string;
pkgDir: string;
-
dev?: boolean;
watch?: boolean;
}
+/**
+ * Interface for package.json
+ */
+export interface PackageJSON {
+ name: string;
+ version: string;
+ description: string;
+ license: string;
+ main: string;
+ module: string;
+ types: string;
+ type: string;
+ files: string[];
+ exports: { [key: string]: string | { [key: string]: string } };
+ contributors: { [key: string]: string }[];
+ homepage: string;
+ repository: { [key: string]: string };
+ bugs: { [key: string]: string };
+ keywords: string[];
+ engines: { [key: string]: string };
+}
+
+/**
+ * Create the `BuildConfig` from the process args, and set the
+ * absolute paths the build will be reading from and writing to.
+ */
export function loadConfig(args: string[] = []) {
const config: BuildConfig = mri(args) as any;
@@ -28,6 +58,9 @@ export function loadConfig(args: string[] = []) {
return config;
}
+/**
+ * Esbuild plugin to change an import path, but keep it an external path.
+ */
export function importPath(filter: RegExp, newModulePath: string) {
const plugin: Plugin = {
name: 'importPathPlugin',
@@ -41,10 +74,13 @@ export function importPath(filter: RegExp, newModulePath: string) {
return plugin;
}
+/**
+ * Esbuild plugin to print out console logs the rebuild has finished or if it has errors.
+ */
export function watcher(config: BuildConfig, filename?: string): WatchMode | boolean {
if (config.watch) {
return {
- onRebuild(error, result) {
+ onRebuild(error) {
if (error) console.error('watch build failed:', error);
else {
if (filename) console.log('rebuilt:', filename);
@@ -55,6 +91,9 @@ export function watcher(config: BuildConfig, filename?: string): WatchMode | boo
return false;
}
+/**
+ * Standard license banner to place at the top of the generated files.
+ */
export const banner = {
js: `
/**
@@ -66,14 +105,24 @@ export const banner = {
`.trim(),
};
+/**
+ * The JavaScript target we're going for. Reusing a constant just to make sure
+ * all the builds are using the same target.
+ */
export const target = 'es2018';
+/**
+ * Helper just to know which NodeJS modules that should stay external.
+ */
export const nodeBuiltIns = ['child_process', 'crypto', 'fs', 'module', 'os', 'path', 'tty', 'url'];
export function injectDirname(config: BuildConfig) {
return join(config.scriptsDir, 'shim', '__dirname.js');
}
+/**
+ * Utility just to ignore certain rollup warns we already know aren't issues.
+ */
export function rollupOnWarn(warning: any, warn: any) {
// skip certain warnings
if (warning.code === `PREFER_NAMED_EXPORTS`) return;
@@ -81,6 +130,9 @@ export function rollupOnWarn(warning: any, warn: any) {
warn(warning);
}
+/**
+ * Helper just to get and format a file's size for logging.
+ */
export async function fileSize(filePath: string) {
const bytes = (await stat(filePath)).size;
if (bytes === 0) return '0b';