esbuild#Plugin TypeScript Examples

The following examples show how to use esbuild#Plugin. You can vote up the ones you like or vote down the ones you don't like, and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: server.ts    From farrow with MIT License 6 votes vote down vote up
createResolveDirnamePlugin = (dist: string): Plugin => {
  return {
    name: 'farrow.resolve.dirname',
    setup: (build) => {
      build.onLoad(
        {
          filter: /.*\.(j|t)sx?$/,
        },
        async (args) => {
          const content = (await fs.readFile(args.path)).toString()
          const dirname = path.dirname(args.path)
          const relative = path.relative(dist, dirname)
          const extname = path.extname(args.path)
          return {
            contents: content.replace(/__dirname/g, `require('path').join(__dirname, "${slash(relative)}")`),
            loader: extname.slice(1) as 'ts' | 'tsx' | 'js' | 'jsx',
          }
        },
      )
    },
  }
}
Example #2
Source File: esbuild.ts    From magic-js with MIT License 6 votes vote down vote up
/**
 * Creates a list of plugins to replace
 * externalized packages with a global variable.
 */
function globalsPlugin(globals: Record<string, string>): Plugin[] {
  return Object.entries(globals).map(([packageName, globalVar]) => {
    const namespace = `globals-plugin:${packageName}`;
    return {
      name: namespace,
      setup(builder) {
        builder.onResolve({ filter: new RegExp(`^${packageName}$`) }, (args) => ({
          path: args.path,
          namespace,
        }));

        builder.onLoad({ filter: /.*/, namespace }, () => {
          const contents = `module.exports = ${globalVar}`;
          return { contents };
        });
      },
    };
  });
}
Example #3
Source File: copy.ts    From nota with MIT License 6 votes vote down vote up
copyPlugin = ({ extensions }: CopyPluginOptions): Plugin => ({
  name: "copy",
  setup(build) {
    let outdir = build.initialOptions.outdir;
    if (!outdir) {
      throw `outdir must be specified`;
    }

    let paths: [string, string][] = [];
    let filter = new RegExp(extensions.map(_.escapeRegExp).join("|"));
    build.onResolve({ filter }, async args => {
      let absPath = path.join(args.resolveDir, args.path);
      let matchingPaths = await glob(absPath);
      paths = paths.concat(matchingPaths.map(p => [p, path.join(outdir!, path.basename(p))]));
      return { path: args.path, namespace: "copy" };
    });

    build.onLoad({ filter: /.*/, namespace: "copy" }, async _args => ({ contents: "" }));
    build.onEnd(_ => {
      paths.forEach(([inPath, outPath]) => fs.promises.copyFile(inPath, outPath));
    });
  },
})
Example #4
Source File: esm-externals.ts    From nota with MIT License 6 votes vote down vote up
esmExternalsPlugin = ({ externals }: EsmExternalsPluginOptions): Plugin => ({
  name: "esm-externals",
  setup(build) {
    externals = externals.filter(m => ALLOWLIST.includes(m));
    let filter = new RegExp("^(" + externals.map(_.escapeRegExp).join("|") + ")(\\/.*)?$");

    let namespace = "esm-externals";
    build.onResolve({ filter: /.*/, namespace }, args => ({
      path: args.path,
      external: true,
    }));

    build.onResolve({ filter }, args => ({
      path: args.path,
      namespace,
    }));

    build.onLoad({ filter: /.*/, namespace }, args => ({
      contents: `export * as default from "${args.path}"; export * from "${args.path}";`,
    }));
  },
})
Example #5
Source File: executable.ts    From nota with MIT License 6 votes vote down vote up
executablePlugin = ({ paths }: ExecutablePluginOptions): Plugin => ({
  name: "executable",
  setup(build) {
    build.initialOptions.banner = {
      js: `#!/usr/bin/env node`,
    };

    build.onEnd(async () => {
      await Promise.all(paths.map(p => fs.promises.chmod(p, fs.constants.S_IRWXU)));
    });
  },
})
Example #6
Source File: tsc.ts    From nota with MIT License 6 votes vote down vote up
tscPlugin = (): Plugin => ({
  name: "tsc",
  setup(build) {
    let opts = ["-emitDeclarationOnly"];
    if (build.initialOptions.watch) {
      opts.push("-w");
    }

    let tsc = cp.spawn("tsc", opts);
    tsc.stdout!.on("data", data => {
      // Get rid of ANSI codes so the terminal isn't randomly cleared by tsc's output.
      console.log(data.toString().replace(ANSI_REGEX, "").trim());
    });
  },
})
Example #7
Source File: compile.ts    From vanilla-extract with MIT License 6 votes vote down vote up
vanillaExtractFilescopePlugin = (): Plugin => ({
  name: 'vanilla-extract-filescope',
  setup(build) {
    const packageInfo = getPackageInfo(build.initialOptions.absWorkingDir);

    build.onLoad({ filter: cssFileFilter }, async ({ path }) => {
      const originalSource = await fs.readFile(path, 'utf-8');

      const source = addFileScope({
        source: originalSource,
        filePath: path,
        rootPath: build.initialOptions.absWorkingDir!,
        packageName: packageInfo.name,
      });

      return {
        contents: source,
        loader: path.match(/\.(ts|tsx)$/i) ? 'ts' : undefined,
        resolveDir: dirname(path),
      };
    });
  },
})
Example #8
Source File: esbuildDepPlugin.ts    From toy-vite with MIT License 6 votes vote down vote up
export function esbuildDepPlugin(deps: Record<string, string>): Plugin {
  return {
    name: 'dep-pre-bundle',
    setup(build) {
      build.onResolve(
        { filter: /^[\w@][^:]/ },
        async ({ path: id, importer, kind, resolveDir }) => {
          const isEntry = !importer;
          if (id in deps) {
            return isEntry
              ? { path: id, namespace: 'dep' }
              : { path: deps[id] };
          } else {
            return {};
          }
        },
      );

      // @ts-ignore
      build.onLoad({ filter: /.*/, namespace: 'dep' }, ({ path: id }) => {
        let contents: string = '';
        contents += `import d from "${deps[id]}";export default d;\n`;
        contents += `export * from "${deps[id]}";\n`;

        let loader = extname(deps[id]).slice(1);
        if (loader === 'mjs') {
          loader = 'js';
        }

        return {
          loader,
          contents,
          resolveDir: appRoot,
        };
      });
    },
  };
}
Example #9
Source File: index.ts    From esbuild-plugin-less with Do What The F*ck You Want To Public License 5 votes vote down vote up
lessLoader = (options: Less.Options = {}, loaderOptions: LoaderOptions = {}): Plugin => {
  return {
    name: 'less-loader',
    setup: (build) => {
      const filter = loaderOptions.filter;
      // Resolve *.less files with namespace
      build.onResolve({ filter: filter || /\.less$/, namespace: 'file' }, (args) => {
        const filePath = path.resolve(process.cwd(), path.relative(process.cwd(), args.resolveDir), args.path);
        return {
          path: filePath,
          watchFiles: !!build.initialOptions.watch ? [filePath, ...getLessImports(filePath)] : undefined,
        };
      });

      // Build .less files
      build.onLoad({ filter: filter || /\.less$/, namespace: 'file' }, async (args) => {
        const content = await fs.readFile(args.path, 'utf-8');
        const dir = path.dirname(args.path);
        const filename = path.basename(args.path);
        try {
          const result = await less.render(content, {
            filename,
            rootpath: dir,
            ...options,
            paths: [...(options.paths || []), dir],
          });

          return {
            contents: result.css,
            loader: 'css',
            resolveDir: dir,
          };
        } catch (e) {
          return {
            errors: [convertLessError(e)],
            resolveDir: dir,
          };
        }
      });
    },
  };
}
Example #10
Source File: buildConfig.ts    From build-scripts with MIT License 5 votes vote down vote up
buildConfig = async (fileName: string, mjs: boolean): Promise<string> => {
  const pluginExternalDeps: Plugin = {
    name: 'plugin-external-deps',
    setup(build) {
      build.onResolve({ filter: /.*/ }, (args) => {
        const id = args.path;
        if (id[0] !== '.' && !path.isAbsolute(id)) {
          return {
            external: true,
          };
        }
      });
    },
  };
  const pluginReplaceImport: Plugin = {
    name: 'plugin-replace-import-meta',
    setup(build) {
      build.onLoad({ filter: /\.[jt]s$/ }, (args) => {
        const contents = fs.readFileSync(args.path, 'utf8');
        return {
          loader: args.path.endsWith('.ts') ? 'ts' : 'js',
          contents: contents
            .replace(
              /\bimport\.meta\.url\b/g,
              JSON.stringify(`file://${args.path}`),
            )
            .replace(
              /\b__dirname\b/g,
              JSON.stringify(path.dirname(args.path)),
            )
            .replace(/\b__filename\b/g, JSON.stringify(args.path)),
        };
      });
    },
  };

  const result = await esbuild({
    entryPoints: [fileName],
    outfile: 'out.js',
    write: false,
    platform: 'node',
    bundle: true,
    format: mjs ? 'esm' : 'cjs',
    metafile: true,
    plugins: [pluginExternalDeps, pluginReplaceImport],
  });
  const { text } = result.outputFiles[0];

  return text;
}
Example #11
Source File: index.ts    From vanilla-extract with MIT License 5 votes vote down vote up
export function vanillaExtractPlugin({
  outputCss,
  externals = [],
  runtime = false,
  processCss,
  identifiers,
}: VanillaExtractPluginOptions = {}): Plugin {
  if (runtime) {
    // If using runtime CSS then just apply fileScopes to code
    return vanillaExtractFilescopePlugin();
  }

  return {
    name: 'vanilla-extract',
    setup(build) {
      build.onResolve({ filter: virtualCssFileFilter }, (args) => {
        return {
          path: args.path,
          namespace: vanillaCssNamespace,
        };
      });

      build.onLoad(
        { filter: /.*/, namespace: vanillaCssNamespace },
        async ({ path }) => {
          let { source, fileName } = await getSourceFromVirtualCssFile(path);

          if (typeof processCss === 'function') {
            source = await processCss(source);
          }

          const rootDir = build.initialOptions.absWorkingDir ?? process.cwd();

          const resolveDir = dirname(join(rootDir, fileName));

          return {
            contents: source,
            loader: 'css',
            resolveDir,
          };
        },
      );

      build.onLoad({ filter: cssFileFilter }, async ({ path }) => {
        const { source, watchFiles } = await compile({
          filePath: path,
          externals,
          cwd: build.initialOptions.absWorkingDir,
        });

        const contents = await processVanillaFile({
          source,
          filePath: path,
          outputCss,
          identOption:
            identifiers ?? (build.initialOptions.minify ? 'short' : 'debug'),
        });

        return {
          contents,
          loader: 'js',
          watchFiles,
        };
      });
    },
  };
}
Example #12
Source File: esbuildPluginSvelte.ts    From elderjs with MIT License 4 votes vote down vote up
function esbuildPluginSvelte({ type, svelteConfig, elderConfig, sveltePackages = [] }: IEsBuildPluginSvelte): Plugin {
  return {
    name: 'esbuild-plugin-elderjs',

    setup(build) {
      try {
        // clean out old css files
        build.onStart(() => {
          if (type === 'ssr') {
            del.sync(elderConfig.$$internal.ssrComponents);
            del.sync(resolve(elderConfig.$$internal.distElder, `.${sep}assets${sep}`));
            del.sync(resolve(elderConfig.$$internal.distElder, `.${sep}props${sep}`));
          } else if (type === 'client') {
            del.sync(resolve(elderConfig.$$internal.distElder, `.${sep}svelte${sep}`));
          }
        });

        if (sveltePackages.length > 0) {
          const filter =
            sveltePackages.length > 1
              ? new RegExp(`(${sveltePackages.join('|')})`)
              : new RegExp(`${sveltePackages[0]}`);
          build.onResolve({ filter }, ({ path, importer }) => {
            // below largely adapted from the rollup svelte plugin
            // ----------------------------------------------

            if (!importer || path[0] === '.' || path[0] === '\0' || isAbsolute(path)) return null;
            // if this is a bare import, see if there's a valid pkg.svelte
            const parts = path.split('/');

            let dir;
            let pkg;
            let name = parts.shift();
            if (name[0] === '@') {
              name += `/${parts.shift()}`;
            }

            try {
              const file = `.${sep}${['node_modules', name, 'package.json'].join(sep)}`;
              const resolved = resolve(process.cwd(), file);
              dir = dirname(resolved);
              // eslint-disable-next-line import/no-dynamic-require
              pkg = require(resolved);
            } catch (err) {
              if (err.code === 'MODULE_NOT_FOUND') return null;
              throw err;
            }

            // use pkg.svelte
            if (parts.length === 0 && pkg.svelte) {
              return {
                path: resolve(dir, pkg.svelte),
                pluginName: 'esbuild-plugin-elderjs',
              };
            }
            return null;
          });
        }

        build.onResolve({ filter: /\.svelte$/ }, ({ path, importer, resolveDir }) => {
          const importee = resolve(resolveDir, path);
          resolveFn(importee, importer);
          return {};
        });

        build.onResolve({ filter: /\.css$/ }, ({ path, importer, resolveDir }) => {
          const importee = resolve(resolveDir, path);
          resolveFn(importee, importer);

          return { path: importee };
        });

        build.onLoad({ filter: /\.css$/ }, async ({ path }) => {
          loadCss(path);

          return {
            contents: undefined,
          };
        });

        build.onLoad({ filter: /\.svelte$/ }, async ({ path }) => {
          const code = await fsPromises.readFile(path, 'utf-8');

          const { output, warnings } = await transformFn({
            svelteConfig,
            elderConfig,
            type,
          })(code, path);

          const out = {
            contents: output.code,
            warnings: type === 'ssr' ? warnings.map((w) => convertWarning(code, w, 'warning')).filter((w) => w) : [],
          };
          return out;
        });

        build.onEnd(async () => {
          if (type === 'ssr') {
            const s = Date.now();
            const r = await minifyCss('all', elderConfig);
            console.log(`>>>> minifying css and adding sourcemaps took ${Date.now() - s}ms`);
            const hash = md5(r.styles);

            const svelteCss = resolve(elderConfig.$$internal.distElder, `.${sep}assets${sep}svelte-${hash}.css`);

            if (process.env.NODE_ENV !== 'production' || process.env.NODE_ENV !== 'production') {
              const sourceMapFileRel = `/${relative(
                elderConfig.distDir,
                resolve(elderConfig.$$internal.distElder, `${svelteCss}.map`),
              )}`;
              r.styles = `${r.styles}\n /*# sourceMappingURL=${sourceMapFileRel} */`;
            }

            fs.outputFileSync(svelteCss, r.styles);

            if (r.sourceMap) {
              fs.outputFileSync(`${svelteCss}.map`, r.sourceMap.toString());
            }
          }
        });
      } catch (e) {
        console.error(e);
      }
    },
  };
}
Example #13
Source File: ssr.ts    From nota with MIT License 4 votes vote down vote up
ssrPlugin = (opts: SsrPluginOptions = {}): Plugin => ({
  name: "ssr",
  async setup(build) {
    let watch = build.initialOptions.watch;

    build.initialOptions.outExtension = {
      ...(build.initialOptions.outExtension || {}),
      ".js": ".mjs",
    };

    build.onResolve({ filter: /\.html$/ }, args => ({
      path: args.path,
      namespace: "ssr",
    }));

    let getPathParts = (p: string): { name: string; dir: string } => {
      let { name, dir } = path.parse(p);
      let outfile = build.initialOptions.outfile;
      name = outfile ? path.parse(outfile).name : name;
      return { name, dir };
    };

    let SSR_CLASS = "ssr-env";
    let NOTA_READY = "window.NOTA_READY";

    build.onLoad({ filter: /./, namespace: "ssr" }, args => {
      let p = path.resolve(args.path);
      let { name, dir } = getPathParts(p);
      let script = `./${name}.mjs`;
      let templatePath = "@nota-lang/esbuild-utils/dist/template.js";
      if (opts.template) {
        templatePath = "." + path.sep + path.relative(dir, opts.template);
      }

      let render_timeout = opts.inPageRenderTimeout || 1000;

      // TODO 1: there should be some kind of indicator while the shadow page is rendering
      // TODO 2: it would be ideal if Nota committed to having plugins say when they're done,
      //   so we don't need to watch mutations
      // TODO 3: this is a lot of code to exist as a string. can we factor it into a module?
      let contents = `
      import React from "react";
      import ReactDOM from "react-dom";
      import Doc, * as doc_mod from "./${path.parse(p).name}.nota"
      import Template from "${templatePath}";

      let key = "metadata";
      let metadata = key in doc_mod ? doc_mod[key] : {};
      let Page = (props) => <Template {...props}><div id="root"><Doc /></div></Template>;

      let wait_to_render = async (element) => {
        let last_change = Date.now();
        let observer = new MutationObserver(evt => { last_change = Date.now(); });
        observer.observe(element, {subtree: true, childList: true, attributes: true});
        
        return new Promise(resolve => {
          let intvl = setInterval(() => {
            if (Date.now() - last_change > ${render_timeout}) {
              clearInterval(intvl);
              observer.disconnect();
              resolve();
            }
          }, 50);
        });  
      };  

      let main = async () => {
        let html = document.documentElement;
        if (html.classList.contains("${SSR_CLASS}")) {
          html.classList.remove("${SSR_CLASS}");      
          ReactDOM.render(<Page {...metadata} script={"${script}"} />, html);
          await wait_to_render(html);
          ${NOTA_READY} = true;  
        } else {
          let root = document.getElementById("root");
          let new_root = document.createElement('div');
          ReactDOM.render(<Doc />, new_root);                            
          await wait_to_render(new_root);
          root.parentNode.replaceChild(new_root, root);
        }
      };                 

      main();
      `;

      return { contents, loader: "jsx", resolveDir: dir };
    });

    let port = opts.port || 8000;
    const MAX_TRIES = 10;
    for (let i = 0; i < MAX_TRIES; i++) {
      let in_use = await tcpPortUsed.check(port, "localhost");
      if (!in_use) {
        break;
      }
      port++;
    }
    let browser = await puppeteer.launch();
    let fileServer = new statik.Server("./dist", {
      headers: {
        "Access-Control-Allow-Origin": "*",
        "Access-Control-Allow-Methods": "GET",
        "Access-Control-Allow-Headers": "Content-Type",
      },
    });
    let httpServer = http
      .createServer((request, response) =>
        request.addListener("end", () => fileServer.serve(request, response)).resume()
      )
      .listen(port);

    build.onEnd(async _args => {
      let entryPoints = build.initialOptions.entryPoints as string[];
      let commonPrefix =
        entryPoints.length == 1 ? path.dirname(entryPoints[0]) : commonPathPrefix(entryPoints);

      let promises = entryPoints.map(async p => {
        let { name, dir } = getPathParts(path.relative(commonPrefix, p));

        let page = await browser.newPage();

        // Pipe in-page logging to the terminal for debugging purposes
        page
          .on("console", message => log.info(message.text()))
          .on("pageerror", err => log.error(err.toString()))
          .on("error", err => log.error(err.toString()));

        // Put the HTML into the page and wait for initial load
        let html = `<!DOCTYPE html>
        <html lang="en" class="${SSR_CLASS}">
          <body><script src="http://localhost:${port}/${dir}/${name}.mjs" type="module"></script></body>
        </html>`;
        await page.setContent(html, { waitUntil: "domcontentloaded" });

        // Then wait for NOTA_READY to be set by the SSR script
        let timeout = opts.externalRenderTimeout || 10000;
        await page.waitForFunction(NOTA_READY, { timeout });

        // And write the rendered HTML to disk once it's ready
        let content = await page.content();
        let htmlPath = path.join("dist", dir, name + ".html");
        await fs.promises.writeFile(htmlPath, content);
      });

      await Promise.all(promises);

      if (!watch) {
        httpServer.close();
        await browser.close();
      }
    });
  },
})