pages235/scripts/esbuildPlugins.mjs

309 lines
10 KiB
JavaScript

//@ts-check
import { polyfillNode } from 'esbuild-plugin-polyfill-node'
import { join, dirname } from 'path'
import * as fs from 'fs'
import { filesize } from 'filesize'
const prod = process.argv.includes('--prod')
let connectedClients = []
/** @type {import('esbuild').Plugin[]} */
const plugins = [
{
name: 'strict-aliases',
setup (build) {
build.onResolve({
filter: /^minecraft-protocol$/,
}, async ({ kind, resolveDir }) => {
return {
path: (await build.resolve('minecraft-protocol/src/index.js', { kind, resolveDir })).path,
}
})
// build.onResolve({
// filter: /^\.\/data.js$/,
// }, ({ resolveDir, path }) => {
// if (!resolveDir.endsWith('minecraft-data')) return
// return {
// namespace: 'load-global-minecraft-data',
// path
// }
// })
// build.onLoad({
// filter: /.+/,
// namespace: 'load-global-minecraft-data',
// }, () => {
// return {
// contents: 'module.exports = window.minecraftData',
// loader: 'js',
// }
// })
// build.onResolve({
// filter: /^minecraft-assets$/,
// }, ({ resolveDir, path }) => {
// // if (!resolveDir.endsWith('minecraft-data')) return
// return {
// namespace: 'load-global-minecraft-assets',
// path
// }
// })
// build.onLoad({
// filter: /.+/,
// namespace: 'load-global-minecraft-assets',
// }, async () => {
// const resolvedPath = await build.resolve('minecraft-assets/index.js', { kind: 'require-call', resolveDir: process.cwd() })
// let contents = (await fs.promises.readFile(resolvedPath.path, 'utf8'))
// contents = contents.slice(0, contents.indexOf('const data = ')) + 'const data = window.minecraftAssets;' + contents.slice(contents.indexOf('module.exports.versions'))
// return {
// contents,
// loader: 'js',
// resolveDir: dirname(resolvedPath.path),
// }
// })
}
},
{
name: 'data-assets',
setup (build) {
const customMcDataNs = 'custom-mc-data'
build.onResolve({
filter: /.*/,
}, async ({ path, ...rest }) => {
if (join(rest.resolveDir, path).replaceAll('\\', '/').endsWith('minecraft-data/data.js')) {
return {
namespace: customMcDataNs,
path
}
}
if (['.woff', '.woff2', '.ttf'].some(ext => path.endsWith(ext))) {
return {
path,
namespace: 'assets',
external: true,
}
}
})
build.onLoad({
filter: /.*/,
namespace: customMcDataNs,
}, async ({ path, ...rest }) => {
const resolvedPath = await build.resolve('minecraft-data/minecraft-data/data/dataPaths.json', { kind: 'require-call', resolveDir: process.cwd() })
const dataPaths = JSON.parse(await fs.promises.readFile(resolvedPath.path, 'utf8'))
// bedrock unsupported
delete dataPaths.bedrock
const allowOnlyList = process.env.ONLY_MC_DATA?.split(',') ?? []
// skip data for 0.30c, snapshots and pre-releases
const ignoredVersionsRegex = /(^0\.30c$)|w|-pre|-rc/
const includedVersions = []
let contents = 'module.exports =\n{\n'
for (const platform of Object.keys(dataPaths)) {
contents += ` '${platform}': {\n`
for (const version of Object.keys(dataPaths[platform])) {
if (allowOnlyList.length && !allowOnlyList.includes(version)) continue
if (ignoredVersionsRegex.test(version)) continue
includedVersions.push(version)
contents += ` '${version}': {\n`
for (const dataType of Object.keys(dataPaths[platform][version])) {
const loc = `minecraft-data/data/${dataPaths[platform][version][dataType]}/`
contents += ` get ${dataType} () { return require("./${loc}${dataType}.json") },\n`
}
contents += ' },\n'
}
contents += ' },\n'
}
contents += '}\n'
if (prod) {
console.log('Included mc-data versions:', includedVersions)
}
return {
contents,
loader: 'js',
resolveDir: join(dirname(resolvedPath.path), '../..'),
}
})
build.onEnd(async ({ metafile, outputFiles }) => {
// write outputFiles
for (const file of outputFiles) {
// if (file.path.endsWith('index.js.map')) {
// const map = JSON.parse(file.text)
// map.sourcesContent = map.sourcesContent.map((c, index) => {
// if (map.sources[index].endsWith('.json')) return ''
// return c
// })
// // data.sources = data.sources.filter(source => !source.endsWith('.json'))
// await fs.promises.writeFile(file.path, JSON.stringify(map), 'utf8')
// } else {
await fs.promises.writeFile(file.path, file.contents)
// }
}
if (!prod) return
// const deps = Object.entries(metafile.inputs).sort(([, a], [, b]) => b.bytes - a.bytes).map(([x, { bytes }]) => [x, filesize(bytes)]).slice(0, 5)
//@ts-ignore
const sizeByExt = {}
//@ts-ignore
Object.entries(metafile.inputs).sort(([, a], [, b]) => b.bytes - a.bytes).forEach(([x, { bytes }]) => {
const ext = x.slice(x.lastIndexOf('.'))
sizeByExt[ext] ??= 0
sizeByExt[ext] += bytes
})
console.log('Input size by ext:')
console.log(Object.fromEntries(Object.entries(sizeByExt).map(x => [x[0], filesize(x[1])])))
})
},
},
{
name: 'prevent-incorrect-linking',
setup (build) {
build.onResolve({
filter: /.+/,
}, ({ resolveDir }) => {
// disallow imports from outside the root directory to ensure modules are resolved from node_modules of this workspace
if (!resolveDir.startsWith(process.cwd())) {
throw new Error(`Restricted import from outside the root directory: ${resolveDir}`)
}
return undefined
})
}
},
{
name: 'watch-notify',
setup (build) {
let count = 0
let time
build.onStart(() => {
time = Date.now()
})
build.onEnd(({ errors, outputFiles, metafile, warnings }) => {
const elapsed = Date.now() - time
// write metafile to disk if needed
// fs.writeFileSync('dist/meta.json', JSON.stringify(metafile, null, 2))
console.log(`Done in ${elapsed}ms`)
if (count++ === 0) {
return
}
if (errors.length) {
connectedClients.forEach((res) => {
res.write(`data: ${JSON.stringify({ errors: errors.map(error => error.text) })}\n\n`)
res.flush()
})
return
}
connectedClients.forEach((res) => {
res.write(`data: ${JSON.stringify({ update: { time: elapsed } })}\n\n`)
res.flush()
})
connectedClients.length = 0
})
}
},
{
name: 'esbuild-readdir',
setup (build) {
build.onResolve({
filter: /^esbuild-readdir:.+$/,
}, ({ resolveDir, path }) => {
return {
namespace: 'esbuild-readdir',
path,
pluginData: {
resolveDir: join(resolveDir, path.replace(/^esbuild-readdir:/, ''))
},
}
})
build.onLoad({
filter: /.+/,
namespace: 'esbuild-readdir',
}, async ({ pluginData }) => {
const { resolveDir } = pluginData
const files = await fs.promises.readdir(resolveDir)
return {
contents: `module.exports = ${JSON.stringify(files)}`,
resolveDir,
loader: 'js',
}
})
}
},
{
name: 'esbuild-import-glob',
setup (build) {
build.onResolve({
filter: /^esbuild-import-glob\(path:(.+),skipFiles:(.+)\)+$/,
}, ({ resolveDir, path }) => {
return {
namespace: 'esbuild-import-glob',
path,
pluginData: {
resolveDir
},
}
})
build.onLoad({
filter: /.+/,
namespace: 'esbuild-import-glob',
}, async ({ pluginData, path }) => {
const { resolveDir } = pluginData
//@ts-ignore
const [, userPath, skipFiles] = /^esbuild-import-glob\(path:(.+),skipFiles:(.+)\)+$/g.exec(path)
const files = (await fs.promises.readdir(join(resolveDir, userPath))).filter(f => !skipFiles.includes(f))
return {
contents: `module.exports = { ${files.map(f => `'${f}': require('./${join(userPath, f)}')`).join(',')} }`,
resolveDir,
loader: 'js',
}
})
}
},
{
name: 'fix-dynamic-require',
setup (build) {
build.onResolve({
filter: /1\.14\/chunk/,
}, async ({ resolveDir, path }) => {
if (!resolveDir.includes('prismarine-provider-anvil')) return
return {
namespace: 'fix-dynamic-require',
path,
pluginData: {
resolvedPath: `${join(resolveDir, path)}.js`,
resolveDir
},
}
})
build.onLoad({
filter: /.+/,
namespace: 'fix-dynamic-require',
}, async ({ pluginData: { resolvedPath, resolveDir } }) => {
const resolvedFile = await fs.promises.readFile(resolvedPath, 'utf8')
return {
contents: resolvedFile.replace("require(`prismarine-chunk/src/pc/common/BitArray${noSpan ? 'NoSpan' : ''}`)", "noSpan ? require(`prismarine-chunk/src/pc/common/BitArray`) : require(`prismarine-chunk/src/pc/common/BitArrayNoSpan`)"),
resolveDir,
loader: 'js',
}
})
}
},
polyfillNode({
polyfills: {
fs: false,
crypto: false,
events: false,
http: false,
stream: false,
buffer: false,
perf_hooks: false,
net: false,
},
})
]
export { plugins, connectedClients as clients }