301 lines
7.5 KiB
JavaScript
Executable File
301 lines
7.5 KiB
JavaScript
Executable File
#!/usr/bin/env -S node -r esbin
|
|
import "dotenv/config"
|
|
import { promises as fs } from "fs"
|
|
import { resolve } from "path"
|
|
import glob from "fast-glob"
|
|
import { program } from "commander"
|
|
import sass from "esbuild-plugin-sass"
|
|
import * as esbuild from "esbuild"
|
|
import type { PluginItem } from "@babel/core"
|
|
import { c } from "./utils"
|
|
import { cssModules } from "./plugins/esbuild-css-modules"
|
|
import { babelPlugin as babel } from "./plugins/esbuild-babel"
|
|
import { externalDep } from "./plugins/esbuild-external-dep"
|
|
import { stringImport } from "./plugins/esbuild-string-import"
|
|
import { yamlPlugin as yaml } from "./plugins/esbuild-yaml"
|
|
import { alias } from "./plugins/esbuild-alias"
|
|
import { getDefaultTarget } from "./plugins/esbuild-browserslist"
|
|
|
|
const dist = "dist"
|
|
const SERVE_PORT = 3114
|
|
|
|
program
|
|
.option("-g, --grep <pattern>", "only build packages that match <pattern>")
|
|
.option("-s, --silent", "hide all output")
|
|
.option(
|
|
"-e, --env <env>",
|
|
"set the NODE_ENV environment variable",
|
|
process.env.NODE_ENV || "production",
|
|
)
|
|
.option("-w, --watch", "watch for changes and rebuild")
|
|
.parse()
|
|
|
|
const { grep, silent, env, watch } = program.opts()
|
|
const __PROD__ = env === "production"
|
|
|
|
const log = silent ? () => {} : console.log
|
|
|
|
async function main() {
|
|
const queue = new Queue([
|
|
new Build("src/options/index.tsx", `${dist}/app/options`, { serve: true }),
|
|
new Build("src/popup/index.tsx", `${dist}/app/popup`),
|
|
new Build("src/server/index.ts", `${dist}/app/server`, {
|
|
platform: "node",
|
|
format: "cjs",
|
|
splitting: false,
|
|
minify: false,
|
|
}),
|
|
new Build("src/background/index.ts", `${dist}/app/background`),
|
|
new Build("src/background/compiler.ts", `${dist}/app/background/compiler`),
|
|
new Build("src/context/index.ts", `${dist}/app/context`, {
|
|
format: "iife",
|
|
splitting: false,
|
|
}),
|
|
new Build("src/devtools/index.ts", `${dist}/app/devtools`),
|
|
new Build("src/install/index.ts", `${dist}/app/install`),
|
|
new Build("src/graphiql/index.tsx", `${dist}/app/playground`),
|
|
new Build("src/reference/index.tsx", `${dist}/app/reference`, {
|
|
format: "iife",
|
|
splitting: false,
|
|
}),
|
|
]).filter(grep ? task => task.entry.includes(grep) : () => true)
|
|
|
|
if (!queue.length) {
|
|
return
|
|
}
|
|
|
|
await queue.run(task => task.clean())
|
|
|
|
log(
|
|
watch ? "Watching" : "Building",
|
|
`${c.blue(queue.length)} package(s) in ${c.blue(env)}`,
|
|
)
|
|
if (queue.length < 5) {
|
|
log(queue.tasks.map((t, i) => ` ${i + 1}. ${c.green(t.entry)}`).join("\n"))
|
|
}
|
|
|
|
await queue.build()
|
|
|
|
if (__PROD__) {
|
|
await printBundleSize()
|
|
}
|
|
}
|
|
|
|
if (require.main === module) {
|
|
Promise.resolve()
|
|
.then(() => main())
|
|
.catch(e => {
|
|
console.error(e)
|
|
process.exit(1)
|
|
})
|
|
}
|
|
|
|
type BuildOptions = Pick<
|
|
esbuild.BuildOptions,
|
|
"entryPoints" | "outdir" | "format" | "splitting" | "platform" | "minify"
|
|
> & {
|
|
serve?: boolean
|
|
}
|
|
|
|
export const getBasicOptions = ({
|
|
entryPoints,
|
|
minify = __PROD__,
|
|
plugins = [],
|
|
babelPlugins = [],
|
|
}: {
|
|
entryPoints: [string]
|
|
minify?: boolean
|
|
plugins?: esbuild.Plugin[]
|
|
babelPlugins?: PluginItem[]
|
|
}): Partial<esbuild.BuildOptions> => ({
|
|
entryPoints,
|
|
define: {
|
|
...Object.entries(process.env).reduce(
|
|
(acc, [key, value]) =>
|
|
key.startsWith("NEXT_PUBLIC_")
|
|
? { ...acc, [`process.env.${key}`]: JSON.stringify(value) }
|
|
: acc,
|
|
{} as Record<string, string>,
|
|
),
|
|
"process.env.NODE_ENV": JSON.stringify(env),
|
|
"process.env.NODE_DEBUG": "false",
|
|
"process.env.SERVE_PORT": String(SERVE_PORT),
|
|
"process.browser": "true",
|
|
global: "globalThis",
|
|
},
|
|
bundle: true,
|
|
format: "esm",
|
|
external: ["path", "glob", "fs", "util", "monaco-editor"],
|
|
plugins: [
|
|
...plugins,
|
|
externalDep(["prettier", "sass"]),
|
|
babel(babelPlugins),
|
|
yaml(),
|
|
stringImport(),
|
|
alias({
|
|
lodash: require.resolve("lodash-es"),
|
|
"webextension-polyfill": require.resolve("../src/vendor/webextension-polyfill"),
|
|
"monaco-editor": require.resolve("../src/options/monaco.ts"),
|
|
|
|
// https://github.com/MichalLytek/type-graphql/issues/366#issuecomment-511075437
|
|
"libphonenumber-js": require.resolve("lodash.noop"),
|
|
// we never use graphql-subscriptions
|
|
"graphql-subscriptions": require.resolve("lodash.noop"),
|
|
|
|
// Make esbuild use the module version
|
|
graphql: require.resolve("graphql/index.mjs"),
|
|
|
|
...(entryPoints[0].includes("graphiql") || __PROD__
|
|
? {}
|
|
: {
|
|
react: require.resolve("../src/vendor/why-did-you-render/index.js"),
|
|
"react/jsx-runtime": require.resolve(
|
|
"../src/vendor/why-did-you-render/jsx-runtime",
|
|
),
|
|
}),
|
|
}),
|
|
cssModules({
|
|
generateScopedName: __PROD__
|
|
? "[hash:base64:6]"
|
|
: "[name]__[local]___[hash:base64:5]",
|
|
localsConvention: "camelCaseOnly",
|
|
}),
|
|
sass(),
|
|
].filter(Boolean),
|
|
target: getDefaultTarget(),
|
|
banner: {
|
|
js: "/* eslint-disable */",
|
|
},
|
|
legalComments: "none",
|
|
keepNames: false,
|
|
tsconfig: "./tsconfig.json",
|
|
sourcemap: "linked",
|
|
minify,
|
|
splitting: true,
|
|
metafile: true,
|
|
loader: {
|
|
".eot": "file",
|
|
".png": "file",
|
|
".ttf": "file",
|
|
".woff": "file",
|
|
".woff2": "file",
|
|
},
|
|
})
|
|
|
|
async function printBundleSize() {
|
|
const root = resolve(dist, "app")
|
|
const files = await glob(["**/*", "!**/*.map", "!**/meta.json"], {
|
|
cwd: root,
|
|
onlyFiles: true,
|
|
})
|
|
const sizes = await Promise.all(
|
|
files.map(async file => {
|
|
const { size } = await fs.stat(resolve(root, file))
|
|
return { file, size }
|
|
}),
|
|
)
|
|
|
|
const list = sizes
|
|
.filter(a => a.size > 50000)
|
|
.sort((a, b) => b.size - a.size)
|
|
.slice(0, 10)
|
|
|
|
for (const { file, size } of list) {
|
|
log(
|
|
c.blue(`./dist/app/${file.padEnd(38, " ")}`),
|
|
c.green(`${humanFileSize(size).padStart(9, " ")}`),
|
|
)
|
|
}
|
|
}
|
|
|
|
function humanFileSize(bytes: number, si = false, dp = 1) {
|
|
const thresh = si ? 1000 : 1024
|
|
|
|
if (Math.abs(bytes) < thresh) {
|
|
return bytes + " B"
|
|
}
|
|
|
|
const units = si
|
|
? ["kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"]
|
|
: ["KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB"]
|
|
let u = -1
|
|
const r = 10 ** dp
|
|
|
|
do {
|
|
bytes /= thresh
|
|
++u
|
|
} while (Math.round(Math.abs(bytes) * r) / r >= thresh && u < units.length - 1)
|
|
|
|
return bytes.toFixed(dp) + " " + units[u]
|
|
}
|
|
|
|
class Build {
|
|
enabled = true
|
|
|
|
constructor(
|
|
readonly entry: string,
|
|
readonly outdir: string,
|
|
readonly options?: BuildOptions,
|
|
) {}
|
|
|
|
disable() {
|
|
this.enabled = false
|
|
return this
|
|
}
|
|
|
|
async clean() {
|
|
if (!this.enabled) return
|
|
|
|
await fs.rm(this.outdir, { recursive: true, force: true })
|
|
}
|
|
|
|
async build() {
|
|
if (!this.enabled) return
|
|
|
|
const { outdir, options: { serve, ...options } = {} } = this
|
|
|
|
const ctx = await esbuild.context({
|
|
...getBasicOptions({
|
|
entryPoints: [this.entry],
|
|
}),
|
|
...options,
|
|
outdir,
|
|
})
|
|
|
|
const { metafile } = await ctx.rebuild()
|
|
|
|
if (__PROD__) {
|
|
await fs.writeFile(resolve(outdir, "meta.json"), JSON.stringify(metafile))
|
|
}
|
|
|
|
if (watch) {
|
|
await ctx.watch()
|
|
if (serve) {
|
|
await ctx.serve({ port: SERVE_PORT })
|
|
}
|
|
} else {
|
|
await ctx.dispose()
|
|
}
|
|
}
|
|
}
|
|
|
|
class Queue {
|
|
constructor(public tasks: readonly Build[]) {}
|
|
|
|
get length() {
|
|
return this.tasks.length
|
|
}
|
|
|
|
filter(predicate: (task: Build) => boolean) {
|
|
return new Queue(this.tasks.filter(predicate))
|
|
}
|
|
|
|
run(fn: (task: Build) => Promise<void>) {
|
|
return Promise.all(this.tasks.map(fn))
|
|
}
|
|
|
|
build() {
|
|
return this.run(task => task.build())
|
|
}
|
|
}
|