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';