Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Can't bundle html file along using file loader #621

Closed
ghost opened this issue Dec 25, 2020 · 13 comments · Fixed by #2816
Closed

Can't bundle html file along using file loader #621

ghost opened this issue Dec 25, 2020 · 13 comments · Fixed by #2816

Comments

@ghost
Copy link

ghost commented Dec 25, 2020

I want esbuild to copy my index.html file along with the js bundle to the public folder so that I can use it with the serve api. I'm getting this message:

$ esbuild 'src/index.tsx' --bundle --loader:.html=file --loader:.woff2=file --outdir=public
 > src/index.tsx: warning: Ignoring this import because "src/index.html" was marked as having no side effects
    1 │ import './index.html'
      ╵        ~~~~~~~~~~~~~~

What am I doing wrong?

@ghost
Copy link
Author

ghost commented Dec 25, 2020

Actually I just realized that esbuild doesn't serve the user's html file, instead you have to fetch the bundle from the serve api using a separate html file.

I think this is pretty inconvenient since you would have two almost identical html files, one for development fetching the bundle from the serve api and one for production that imports the actual bundle.

@Ventajou
Copy link

I just ran into this myself, your html file will be ignored unless you use its path. For example:

import indexUrl from './index.html';
console.log(indexUrl);

That will result in the file being copied.

I was about to open a ticket about this to see if maybe there's a way to bypass that with a plugin.

@ghost
Copy link
Author

ghost commented Jan 13, 2021

Ah that error message makes sense to me now. Thanks for clarifying.

Now that I think about it, maybe a better solution would be to instead use a shell command before building, like cp src/index.html build/index.html; esbuild --bundle?

@hum-n
Copy link

hum-n commented Jan 17, 2021

Even if I already have an HTML file in the public folder, I don't see it when I use the serve API. I only see JS files. Did you guys find a way?

@ghost
Copy link
Author

ghost commented Jan 17, 2021

The idea is that the serve API serves the bundled files, not your html file. Then, in your html file you import from the URL of the serve API. So if it is running on localhost:3000, use a path like <script src="localhost:3000/script.js"> in your own html file.

As I said in a previous comment, I think the serve API should serve your html file too because currently you need to use different file paths for dev and prod. I ended up just using nginx to serve everything and watch-exec to execute esbuild everytime I save a file.

@hum-n
Copy link

hum-n commented Jan 17, 2021

Oh, I see. It's possible to do something for the path, but you still need to serve the HTML somehow. 😕

@nettybun
Copy link

nettybun commented Jan 18, 2021

@AntoineBoulanger Here's a zero-dependency reverse-proxy server that wraps esbuild's serve in 50 lines:

import esbuild from 'esbuild';
import http from 'http';
import fs from 'fs';
import path from 'path';

let __dirname = path.dirname(new URL(import.meta.url).pathname);

esbuild
  .serve({ port: 3000 }, {
    entryPoints: ['./index.tsx'],
    format: 'esm',
    bundle: true,
  })
  .catch(err => {
    console.error(err);
    process.exit(1);
  });

let mime = {
  '.html': 'text/html',
  '.css': 'text/css',
};
let server = http.createServer((req, res) => {
  let filename = req.url && req.url !== '/' ? req.url : 'index.html';
  let filepath = path.join(__dirname, filename);
  if (fs.existsSync(filepath)) {
    let stream = fs.createReadStream(filepath);
    stream.on('ready', () => {
      let type = mime[path.parse(filepath).ext] || 'application/octet-stream';
      console.log(`${req.method} ${req.url} => 200 ${type}`);
      res.writeHead(200, { 'content-type': type });
      stream.pipe(res);
    });
    stream.on('error', err => {
      console.log(`${req.method} ${req.url} => 500 ${filepath} ${err.name}`);
      res.writeHead(500, err.name);
      res.end(JSON.stringify(err));
    });
  } else {
    let reqProxy = http.request({ path: req.url, port: 3000 });
    reqProxy.on('response', resProxy => {
      let type = resProxy.headers['content-type'];
      console.log(`${req.method} ${req.url} => ${resProxy.statusCode} ${type} via esbuild`);
      res.writeHead(resProxy.statusCode, { 'content-type': type });
      resProxy.pipe(res);
    });
    req.pipe(reqProxy);
  }
});
server.listen(4000, err => {
  if (err) throw err;
  console.log('Served on http://localhost:4000');
});

Then I don't even have to worry about paths. My script tag points to "/index.js':

❯ ls
serve.js index.html index.tsx

❯ grep src index.html
<script type="module" src="/index.js"></script>

❯ node serve
Served on http://localhost:4000
GET / => 200 text/html
GET /index.js => 200 application/javascript via esbuild

If you're serving a bunch of different content types then you'll have to add to mime or use express or something more complex

@ghost
Copy link
Author

ghost commented Feb 13, 2021

Looks like with v0.8.45 we were gifted the servedir flag which solves the serve API issue, and since the original question about bundling a html file along was answered too I'll close this

@ghost ghost closed this as completed Feb 13, 2021
@David-Else
Copy link

Hello, some time has passed since the original question, and the only simple answer seems to be:

I just ran into this myself, your html file will be ignored unless you use its path. For example:

import indexUrl from './index.html';
console.log(indexUrl);

That will result in the file being copied.

I was about to open a ticket about this to see if maybe there's a way to bypass that with a plugin.

I can't find a way to simply copy my index.html file into my dist directory when I build. I must be missing something? Here are my settings so far:

    entryPoints: ["./src/mod.ts"],
    outfile: "./dist/bundle.js",
    bundle: true,
    format: "esm",
    minify: true,
    sourcemap: true,

Has this been solved, or do I need to use Deno or Node to copy over index.html?

@zaydek
Copy link

zaydek commented Jul 20, 2021

In my experience when I've needed to copy entire folders I needed to write some Go / JS (essentially using Deno for your use case). This is how I went about it at the time but things may have changed: #982 (comment). Hopefully there are better approaches, this is simple what's worked for me.

Once HTML becomes a first-class citizen in esbuild I don't think this will be an issue anymore.

@Ventajou
Copy link

Now that plugins have Start and End callbacks, one thing you can do is maintain a list of files to copy:

  • reset the list in the Start callback
  • add args.path to that list in the Load callback, you can have the callback return an empty string as JS for esbuild to be happy
  • copy all the files in the list in to the outdir in the End callback

There are some interesting advantages to this approach:

  • you explicitly declare the files you want to copy by adding an import in your source
  • everything is self-contained in a plugin instead of some extra script
  • your files will show up in the esbuild manifest so you could run some stats around your build

@cameronelliott
Copy link

It might be worth noting here that the 0.17.0 commit mentions adding index.html --loader:.html=copy as a way to get index.html in your output dir. Haven't tested it yet.

@jamesgpearce
Copy link

Thanks @cameronelliott! The copy loader works great for HTML, images etc. Just not documented as far as I can see.

This issue was closed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

7 participants