prebuild.js 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114
  1. import esbuild from 'esbuild';
  2. import { red } from 'kleur/colors';
  3. import fs from 'node:fs';
  4. import path from 'node:path';
  5. import { fileURLToPath, pathToFileURL } from 'node:url';
  6. import glob from 'tiny-glob';
  7. function escapeTemplateLiterals(str) {
  8. return str.replace(/\`/g, '\\`').replace(/\$\{/g, '\\${');
  9. }
  10. export default async function prebuild(...args) {
  11. let buildToString = args.indexOf('--to-string');
  12. if (buildToString !== -1) {
  13. args.splice(buildToString, 1);
  14. buildToString = true;
  15. }
  16. let minify = true;
  17. let minifyIdx = args.indexOf('--no-minify');
  18. if (minifyIdx !== -1) {
  19. minify = false;
  20. args.splice(minifyIdx, 1);
  21. }
  22. let patterns = args;
  23. let entryPoints = [].concat(
  24. ...(await Promise.all(
  25. patterns.map((pattern) => glob(pattern, { filesOnly: true, absolute: true }))
  26. ))
  27. );
  28. function getPrebuildURL(entryfilepath, dev = false) {
  29. const entryURL = pathToFileURL(entryfilepath);
  30. const basename = path.basename(entryfilepath);
  31. const ext = path.extname(entryfilepath);
  32. const name = basename.slice(0, basename.indexOf(ext));
  33. const outname = dev ? `${name}.prebuilt-dev${ext}` : `${name}.prebuilt${ext}`;
  34. const outURL = new URL('./' + outname, entryURL);
  35. return outURL;
  36. }
  37. async function prebuildFile(filepath) {
  38. let tscode = await fs.promises.readFile(filepath, 'utf-8');
  39. // If we're bundling a client directive, modify the code to match `packages/astro/src/core/client-directive/build.ts`.
  40. // If updating this code, make sure to also update that file.
  41. if (filepath.includes(`runtime${path.sep}client`)) {
  42. // `export default xxxDirective` is a convention used in the current client directives that we use
  43. // to make sure we bundle this right. We'll error below if this convention isn't followed.
  44. const newTscode = tscode.replace(
  45. /export default (.*?)Directive/,
  46. (_, name) =>
  47. `(self.Astro || (self.Astro = {})).${name} = ${name}Directive;window.dispatchEvent(new Event('astro:${name}'))`
  48. );
  49. if (newTscode === tscode) {
  50. console.error(
  51. red(
  52. `${filepath} doesn't follow the \`export default xxxDirective\` convention. The prebuilt output may be wrong. ` +
  53. `For more information, check out ${fileURLToPath(import.meta.url)}`
  54. )
  55. );
  56. }
  57. tscode = newTscode;
  58. }
  59. const esbuildOptions = {
  60. stdin: {
  61. contents: tscode,
  62. resolveDir: path.dirname(filepath),
  63. loader: 'ts',
  64. sourcefile: filepath,
  65. },
  66. format: 'iife',
  67. target: ['es2018'],
  68. minify,
  69. bundle: true,
  70. write: false,
  71. };
  72. const results = await Promise.all(
  73. [
  74. {
  75. build: await esbuild.build(esbuildOptions),
  76. dev: false,
  77. },
  78. filepath.includes('astro-island')
  79. ? {
  80. build: await esbuild.build({
  81. ...esbuildOptions,
  82. define: { 'process.env.NODE_ENV': '"development"' },
  83. }),
  84. dev: true,
  85. }
  86. : undefined,
  87. ].filter((entry) => entry)
  88. );
  89. for (const result of results) {
  90. const code = result.build.outputFiles[0].text.trim();
  91. const rootURL = new URL('../../', import.meta.url);
  92. const rel = path.relative(fileURLToPath(rootURL), filepath);
  93. const mod = `/**
  94. * This file is prebuilt from ${rel}
  95. * Do not edit this directly, but instead edit that file and rerun the prebuild
  96. * to generate this file.
  97. */
  98. export default \`${escapeTemplateLiterals(code)}\`;`;
  99. const url = getPrebuildURL(filepath, result.dev);
  100. await fs.promises.writeFile(url, mod, 'utf-8');
  101. }
  102. }
  103. await Promise.all(entryPoints.map(prebuildFile));
  104. }