enso/app/ide-desktop/lib/content/esbuild-config.ts

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

213 lines
9.0 KiB
TypeScript
Raw Normal View History

/** @file Configuration for the esbuild bundler and build/watch commands.
*
* The bundler processes each entry point into a single file, each with no external dependencies and
* minified. This primarily involves resolving all imports, along with some other transformations
* (like TypeScript compilation).
*
* See the bundlers documentation for more information:
* https://esbuild.github.io/getting-started/#bundling-for-node. */
import * as childProcess from 'node:child_process'
import * as fs from 'node:fs/promises'
import * as fsSync from 'node:fs'
import * as pathModule from 'node:path'
import * as url from 'node:url'
import * as esbuild from 'esbuild'
import * as esbuildPluginNodeGlobals from '@esbuild-plugins/node-globals-polyfill'
import * as esbuildPluginNodeModules from '@esbuild-plugins/node-modules-polyfill'
import esbuildPluginAlias from 'esbuild-plugin-alias'
import esbuildPluginCopyDirectories from 'esbuild-plugin-copy-directories'
import esbuildPluginTime from 'esbuild-plugin-time'
import esbuildPluginYaml from 'esbuild-plugin-yaml'
import * as utils from '../../utils'
import BUILD_INFO from '../../build.json' assert { type: 'json' }
// =================
// === Constants ===
// =================
const THIS_PATH = pathModule.resolve(pathModule.dirname(url.fileURLToPath(import.meta.url)))
// =============================
// === Environment variables ===
// =============================
/** Arguments that must always be supplied, because they are not defined as
* environment variables. */
export interface PassthroughArguments {
/** `true` if in development mode (live-reload), `false` if in production mode. */
devMode: boolean
/** Whether the application may have the local backend running. */
supportsLocalBackend: boolean
/** Whether the application supports deep links. This is only true when using
* the installed app on macOS and Windows. */
supportsDeepLinks: boolean
}
/** Mandatory build options. */
export interface Arguments extends PassthroughArguments {
/** List of files to be copied from WASM artifacts. */
wasmArtifacts: string
/** Directory with assets. Its contents are to be copied. */
assetsPath: string
/** Path where bundled files are output. */
outputPath: string
/** The main JS bundle to load WASM and JS wasm-pack bundles. */
ensoglAppPath: string
}
/** Get arguments from the environment. */
export function argumentsFromEnv(passthroughArguments: PassthroughArguments): Arguments {
const wasmArtifacts = utils.requireEnv('ENSO_BUILD_GUI_WASM_ARTIFACTS')
const assetsPath = utils.requireEnv('ENSO_BUILD_GUI_ASSETS')
const outputPath = pathModule.resolve(utils.requireEnv('ENSO_BUILD_GUI'), 'assets')
const ensoglAppPath = utils.requireEnv('ENSO_BUILD_GUI_ENSOGL_APP')
return { ...passthroughArguments, wasmArtifacts, assetsPath, outputPath, ensoglAppPath }
}
2023-01-27 03:09:09 +03:00
// ===================
// === Git process ===
// ===================
/** Get output of a git command.
* @param command - Command line following the `git` program.
* @returns Output of the command. */
function git(command: string): string {
// TODO [mwu] Eventually this should be removed, data should be provided by the build script
// through `BUILD_INFO`. The bundler configuration should not invoke git,
// it is not its responsibility.
return childProcess.execSync(`git ${command}`, { encoding: 'utf8' }).trim()
}
// ================
// === Bundling ===
// ================
/** Generate the builder options. */
export function bundlerOptions(args: Arguments) {
const {
outputPath,
ensoglAppPath,
wasmArtifacts,
assetsPath,
devMode,
supportsLocalBackend,
supportsDeepLinks,
} = args
const buildOptions = {
// The names come from a third-party API and cannot be changed.
/* eslint-disable @typescript-eslint/naming-convention */
absWorkingDir: THIS_PATH,
bundle: true,
loader: {
'.html': 'copy',
'.css': 'copy',
'.map': 'copy',
'.wasm': 'copy',
// The `file` loader copies the file, and replaces the import with the path to the file.
'.svg': 'file',
'.png': 'file',
'.ttf': 'copy',
},
entryPoints: [
pathModule.resolve(THIS_PATH, 'src', 'index.ts'),
pathModule.resolve(THIS_PATH, 'src', 'index.html'),
pathModule.resolve(THIS_PATH, 'src', 'run.js'),
pathModule.resolve(THIS_PATH, 'src', 'style.css'),
pathModule.resolve(THIS_PATH, 'src', 'serviceWorker.ts'),
...wasmArtifacts.split(pathModule.delimiter),
...fsSync
.readdirSync(assetsPath)
.map(fileName => pathModule.resolve(assetsPath, fileName)),
].map(path => ({ in: path, out: pathModule.basename(path, pathModule.extname(path)) })),
outdir: outputPath,
outbase: 'src',
plugins: [
{
name: 'override-loaders',
setup: build => {
// This file MUST be in CommonJS format because it is loaded using `Function()`
// in `ensogl/pack/js/src/runner/index.ts`.
// All other files are ESM because of `"type": "module"` in `package.json`.
build.onLoad({ filter: /[/\\]pkg\.js$/ }, async info => {
const { path } = info
return {
contents: await fs.readFile(path),
loader: 'copy',
}
})
// `.png` and `.svg` files not in the `assets` module should not use the `file`
// loader.
build.onLoad({ filter: /(?:\.png|\.svg)$/ }, async info => {
const { path } = info
if (!/[/\\]assets[/\\][^/\\]*(?:\.png|\.svg)$/.test(path)) {
return {
contents: await fs.readFile(path),
loader: 'copy',
}
} else {
return
}
})
},
},
esbuildPluginCopyDirectories(),
esbuildPluginYaml.yamlPlugin({}),
esbuildPluginNodeModules.NodeModulesPolyfillPlugin(),
esbuildPluginNodeGlobals.NodeGlobalsPolyfillPlugin({ buffer: true, process: true }),
esbuildPluginAlias({ ensogl_app: ensoglAppPath }),
esbuildPluginTime(),
],
define: {
GIT_HASH: JSON.stringify(git('rev-parse HEAD')),
GIT_STATUS: JSON.stringify(git('status --short --porcelain')),
BUILD_INFO: JSON.stringify(BUILD_INFO),
/** Whether the application is being run locally. This enables a service worker that
* properly serves `/index.html` to client-side routes like `/login`. */
IS_DEV_MODE: JSON.stringify(devMode),
/** Overrides the redirect URL for OAuth logins in the production environment.
* This is needed for logins to work correctly under `./run gui watch`. */
REDIRECT_OVERRIDE: 'undefined',
SUPPORTS_LOCAL_BACKEND: JSON.stringify(supportsLocalBackend),
SUPPORTS_DEEP_LINKS: JSON.stringify(supportsDeepLinks),
},
pure: ['assert'],
sourcemap: true,
minify: !devMode,
metafile: true,
format: 'esm',
platform: 'browser',
color: true,
logOverride: {
// Happens in Emscripten-generated MSDF (msdfgen_wasm.js):
// 1 │ ...typeof module!=="undefined"){module["exports"]=Module}process["o...
'commonjs-variable-in-esm': 'silent',
// Happens in Emscripten-generated MSDF (msdfgen_wasm.js):
// 1 │ ...y{table.grow(1)}catch(err){if(!err instanceof RangeError){throw ...
'suspicious-boolean-not': 'silent',
},
/* eslint-enable @typescript-eslint/naming-convention */
} satisfies esbuild.BuildOptions
// The narrower type is required to avoid non-null assertions elsewhere.
// The intersection with `esbuild.BuildOptions` is required to allow mutation.
const correctlyTypedBuildOptions: esbuild.BuildOptions & typeof buildOptions = buildOptions
return correctlyTypedBuildOptions
}
/** The basic, common settings for the bundler, based on the environment variables.
*
* Note that they should be further customized as per the needs of the specific workflow
* (e.g. watch vs. build). */
export function bundlerOptionsFromEnv(passthroughArguments: PassthroughArguments) {
return bundlerOptions(argumentsFromEnv(passthroughArguments))
}
/** esbuild options for bundling the package for a one-off build.
*
* Relies on the environment variables to be set. */
export function bundleOptions(passthroughArguments: PassthroughArguments) {
return bundlerOptionsFromEnv(passthroughArguments)
}