parcel-bundler / parcel

The zero configuration build tool for the web. 📦🚀
https://parceljs.org
MIT License
43.49k stars 2.27k forks source link

Is parcel meant to work with server-side code insluding HMR? #355

Open pie6k opened 6 years ago

pie6k commented 6 years ago

Let's consider simple node.js api based on express that has one endpoint /test that would simply return Hello world.

Goal would be to change any file of server and have it hot reloaded without need of restarting the server.

Is something like this possible/meant to be possible/planned with parcel?

For now - it's seems like front-end only packager.

There is something like ts-node or babel-node. Would be great to have parcel-node with first-class hot reloading support.

shawwn commented 6 years ago

If you set up a git repo with an express server and some basic code, using your front end of choice, I'll look into integrating parcel with it so that you can serve files.

It seems like we can expose an express middleware pretty easily.

ksafranski commented 6 years ago

Express middleware would be awesome. Right now my API service and UI can be separate, so I have a workaround setup for hacking on both simultaneously:

Server (./server/index.js):

const express = require('express')
const app = express()
const { spawn } = require('child_process')

const PORT0 = process.env.PORT0
const PORT1 = process.env.PORT1

app.get('/ping', (req, res) => {
  res.status(200).send('Ok')
})

app.listen(PORT0, () => {
  console.log(`Service running on ${PORT0}`)

// Run Parcel if we're in dev mode...
  if (process.env.NODE_DEV) {
    const opts = {
      env: process.env,
      cwd: process.cwd(),
      stdio: ['inherit', process.stdout, process.stdout]
    }
    // Run parcel
    spawn('parcel', [ './src/index.html', '-p', PORT1 ], opts)
  }

})

Then in my package.json I have a script that starts everything with Nodemon on the server:

"scripts": {
  "dev": "PORT0=8888 PORT1=8889 NODE_DEV=true nodemon ./server/index.js"
}

You have to add this to package.json as well to prevent a loop when parcel builds:

"nodemonConfig": {
    "ignore": ["src/*", "dist/*", ".cache/*"]
  }

Kinda hacky but it at least allows me to spin-up a single command to work between my REST API and UI.

devongovett commented 6 years ago

Parcel actually already has an express middleware. It's not well documented at the moment, but this is basically how it works:

const Bundler = require('parcel-bundler');
const express = require('express');

let bundler = new Bundler('path/to/index.html');
let app = express();

app.use(bundler.middleware());
app.listen(5000);
ksafranski commented 6 years ago

Do you have any documentation on this? I gave it a shot and am getting an error (on 1.3.1):

TypeError: Cannot read property 'type' of undefined
    at sendIndex (/home/fluidbyte/workspace/sandbox/react-parcel/node_modules/parcel-bundler/src/Server.js:33:29)
    at respond (/home/fluidbyte/workspace/sandbox/react-parcel/node_modules/parcel-bundler/src/Server.js:23:16)
    at /home/fluidbyte/workspace/sandbox/react-parcel/node_modules/parcel-bundler/src/Server.js:15:7
    at Layer.handle [as handle_request] (/home/fluidbyte/workspace/sandbox/react-parcel/node_modules/express/lib/router/layer.js:95:5)
    at trim_prefix (/home/fluidbyte/workspace/sandbox/react-parcel/node_modules/express/lib/router/index.js:317:13)
    at /home/fluidbyte/workspace/sandbox/react-parcel/node_modules/express/lib/router/index.js:284:7
    at Function.process_params (/home/fluidbyte/workspace/sandbox/react-parcel/node_modules/express/lib/router/index.js:335:12)
    at next (/home/fluidbyte/workspace/sandbox/react-parcel/node_modules/express/lib/router/index.js:275:10)
    at expressInit (/home/fluidbyte/workspace/sandbox/react-parcel/node_modules/express/lib/middleware/init.js:40:5)
    at Layer.handle [as handle_request] (/home/fluidbyte/workspace/sandbox/react-parcel/node_modules/express/lib/router/layer.js:95:5)

My /server/index.js:

const express = require('express')
const Bundler = require('parcel-bundler')
const path = require('path')
const bundler = new Bundler(path.resolve(__dirname, '../src/index.html'))
const app = express()

const PORT0 = process.env.PORT0

app.use(bundler.middleware())

app.get('/ping', (req, res) => {
  res.status(200).send('Ok')
})

app.listen(PORT0, () => {
  console.log(`Service running on ${PORT0}`)
})
nozzlegear commented 6 years ago

@Fluidbyte I ran into the same issue. The middleware attempts to check the type prop on bundler.mainAsset, but bundler.mainAsset will be undefined until the bundler bundles at least once. I worked around this problem by calling await bundler.bundle() once on my sever:

if (Constants.ISLIVE) {
    // Redirect http requests to https when live
    app.use(httpsRedirect());
} else {
    // Run Parcel bundler for client development
    const bundler = new Bundler(path.resolve(__dirname, "../client/index.html"))
    // Bugfix: parcel bundler must call bundle at least once using middleware
    await bundler.bundle();
    app.use(bundler.middleware());
}

Edit: filed a bug report #442

shawwn commented 6 years ago

I'd like to re-open this issue for a few reasons:

I've been studying IntelliJ's PSI architecture, and we could probably borrow ideas from their architecture. They've solved a lot of the same things we're running into: How can users extend the core behavior via plugins? What about services? When are things loaded, and in what order? How do I get notified when content changes? How do I create "virtual files" that aren't saved to disk? Etc.

jerrygreen commented 6 years ago

Holy moly! I wanted to create server rendering topic but found it's already here. This is important topic, guys!

I really like how Parcel works for typical single page app. But looks like Parcel forces me to use *.html file as an entrypoint. And then I wanna ask a question...

Still, I love Parcel. Please, make some progress on this ;)

P.S. If you guys direct me, I could probably help

danscan commented 6 years ago

@JerryGreen Parcel can take a .js file as an entrypoint. I'm exploring the same use case now. I'll post an update here if I get something nice working.

devjj commented 6 years ago

I found a relatively simple workaround for my setup that allows me to have hot reloading with parceljs and hot reloading of my express app. I have two repos that are separate, one for my Vue app (parcel runs here), and another for my express/node backend. I've symlinked the public directory in my express app to the dist directory that parcel outputs by default.

In the Vue/parcel directory, I run parcel watch --public-url "http://localhost:3000/" index.htm and in my Express/node directory, I just run nodemon app.js as usual. Everything seems to be working as you'd expect. Admittedly this is a simple setup for an app I'm just starting with.

pie6k commented 6 years ago

I'd like to clarify, that when creating this issue, I've meant hot reloading server code. Meaning - is parcel suitable for hot-reloading back-end service like api that doesn't have even single html or css file.

@devongovett example looks really promising, but it seems to be browser/frontend related. Would that work for node.js only? eg my api responses to http://localhost:3000/hello with World and being able to 'hot-reload' it when file is changed without restarting the server.

mvlabat commented 6 years ago

Hi everyone! I've just got parcel bundler working for my node.js project with hot reloading. Had to write a custom bundler for it: https://gist.github.com/mvlabat/265b3f5a89417d30d891642cf93ad642 It will work out of the box if you place this file next to your index.js, you can also customize it any way you want, adding specific build options etc.

I run it with the following yarn scripts, so it gets really convenient to use:

  "scripts": {
    "serve": "node bundler.js run",
    "build": "NODE_ENV=production node bundler.js"
  }

I think maybe that's something that Parcel should do out of the box, when serving js file as node.js target. Or at least such way of using Parcel bundler should be documented.

Update. I've just realized the main topic is about HMR. This solution doesn't make use of Hot Module Replacement, but just restarts the application on every rebuild. So it may be suitable for small projects, but not for large applications with heavy bootstrapping process.

rodoabad commented 6 years ago

Are you able to use parcel in HapiJS? @devongovett

DeMoorJasper commented 6 years ago

@rodoabad This seems to be unrelated to this thread. Anyways you can probably register the middleware of parcel to onRequest using this: https://hapijs.com/api#-serverextevents you'll have to write some code to make it compatible but shouldn't be too hard

cdaringe commented 5 years ago

this issue can be closed, imho. parcel ships a dev server, middleware, and a hmr api. it's all documented on https://parceljs.org

jerrygreen commented 5 years ago

It's an old issue, there's no guarantee the bug is still there

Devs are not actively looking into the issues. There's 612 of them already. May probably close, idk

DeMoorJasper commented 5 years ago

@JerryGreen we do actively look into and fix issues. Most issues are just vague, non reproducible, already fixed or will be fixed in Parcel 2.

Closing issues out of nowhere is kinda rude and pointless. Therefore we have so many. Sometimes we go through old issues and most of the time we can just close them...

Sent with GitHawk

jerrygreen commented 5 years ago

@DeMoorJasper, yea, sorry, I may look rude by myself

Closing issues out of nowhere is kinda rude and pointless

I understand this. That's true

I've planned to start a little project in some near future (in a week or two, idk), very likely with server rendering. Will be able to check is this still relevant or not / to make some reproducible steps. Will be here in touch

DeMoorJasper commented 5 years ago

@JerryGreen SSR is kind of impossible with Parcel 1, multiple issues have come up with the main one being that parcel's workers need the config and node.js config overwrites browser config and vice versa which causes incorrect builds. However most of these bugs could be hacked around either by applying hotfixes to Parcel 1 or by working around them in your project. I've tried it along with another guy and AirBnB also experimented with it a lil while back. Parcel 2 will resolve this and probably make making such tooling very easy as we're working together with other teams to create the most optimal API.

I would definitely love to experiment with Node.js HMR although I'm not sure how this would work. Examples would be very welcome so I can possibly build this for Parcel 2 :)

I think most SSR frameworks currently load in JS bundles dynamically on page request at least that's how I would do it.

jerrygreen commented 5 years ago

@DeMoorJasper there should be a server running which is doing 2 things (in most cases frameworks do only one of these things):

  1. Takes a cache of a statically generated page on page request (some landings, .etc, pages which are always the same; unable to use such approach for dynamic data)
  2. Generates new html on page request (more universal approach, however, a bit slower than first approach, so no real need to use this approach for static pages)

Btw, can't wait Parcel 2, are there any estimates of when it comes?

DeMoorJasper commented 5 years ago

@JerryGreen Alpha 1 is pretty close you can track progress here: https://github.com/parcel-bundler/parcel/milestones

justin-calleja commented 5 years ago

Hi,

I cannot use console.log in my middleware when I mount the bundler middleware e.g. I don't get "in middleware". Instead - I just see "Built in ..." of Parcel:

        app.use((req, res, next) => {
          console.log('in middleware...');
          const ejs = readFileSync(ejsPath).toString();
          const compiledTmpl = template(ejs);
          const htmlStr = compiledTmpl({
            initData: JSON.stringify({
              a: 111,
              b: 222
            })
          });
          writeFileSync(indexHTMLPath, htmlStr)
          next();
        });
        app.use(bundler.middleware());
mischnic commented 5 years ago

Maybe setting the logLevel option to a smaller level works: https://parceljs.org/api.html

justin-calleja commented 5 years ago

@mischnic I haven't tried that but I doubt it'd work because the problem isn't logging - it does log - it's that the console is cleared right after. There doesn't seem to be a conditional around console.clear() so I'm guessing it's not possible.

Thanks anyway.

defrex commented 5 years ago

To anyone following along who's impatient for v2, I've managed to solve this for v1. Here is a minimal example.

https://github.com/defrex/parcel-hot-server-example

eddyLazar commented 5 years ago

Looks like now if I use it with express I can't assign any other routes. So if I have something like this

app.use(bundler.middleware());
app.get('/hello', (req, res) => {
  res.send('Hello world update was great\n');
});

/hello will direct me to parcel index route

DeMoorJasper commented 5 years ago

@eddyLazar I think you have to switch those around as parcel will handle every route and express will never go past the parcel catch-all middleware

app.get('/hello', (req, res) => {
  res.send('Hello world update was great\n');
});
app.use(bundler.middleware());
eddyLazar commented 5 years ago

Oh, thank you @DeMoorJasper My fault...

Danacus commented 4 years ago

Does Parcel v2 support this feature too? I can't figure out how to use it.

jerrygreen commented 4 years ago

I have a recommendation for you guys.

there should be a server running which is doing 2 things (in most cases frameworks do only one of these things):

  1. Takes a cache of a statically generated page on page request (some landings, .etc, pages which are always the same; unable to use such approach for dynamic data)
  2. Generates new html on page request (more universal approach, however, a bit slower than first approach, so no real need to use this approach for static pages)

It's a huge problem (the reason why this issue exists) but it's solved by NextJS:

https://nextjs.org/blog/next-9 (read: "Automatic Static Optimization")

So I recommend you guys (literally to everyone in the web field) to use NextJS and all their stack, whether it's frontend part of a web app, or both frontend and backend (backend API).

Their stack is using Webpack under the hood, however. But the good thing, - you don't have to mess with Webpack config. If you do need to change bundler's behaviour, - they have tons of plugins which are easy to install (you may also write one yourself, - which will be equally painful to setup of webpack but for most things the pain is already on other dev's shoulders already bundled in these plugins). GL!

DeMoorJasper commented 4 years ago

I'm a big fan of NextJS but adopting an entire framework just for this feature seems overkill.

Parcel 2 supports a config for configuring proxies based on the create-react-app proxy support. https://github.com/parcel-bundler/parcel/pull/3281 so this should be even easier in Parcel.

Using plugins you would also be able to go even further and bundle node code alongside browser code and run the node code alongside browser code as Parcel supports multiple targets.

faergeek commented 4 years ago

Sorry, I didn't read the whole thread carefully just to understand it, but what's the status of this? Did somebody take this into work already? Or ideas are not settled yet?

tl;dr I would like to help 👍

DeMoorJasper commented 4 years ago

@faergeek I dont think anyone has taken this up yet.

It could be done fairly easily with a plugin.

On Parcel 2 this should probably be a reporter plugin similar to the hmr server one. We’re gonna work on better node support for parcel 2 so maybe this should be part of that? Maybe it should be opt-in?

Sent with GitHawk

faergeek commented 4 years ago

@DeMoorJasper Yeah, I also think it should be opt-in. But may be it would not hurt to have some kind of officially supported simple plugin for that anyway? As a reference at least. And yes, I am talking about version 2. Didn't know it is even possible on version 1. Is it?)

mischnic commented 4 years ago

We’re gonna work on better node support for parcel 2

(-> https://github.com/parcel-bundler/parcel/issues/2493)

mikestopcontinues commented 4 years ago

It looks like two issues are being discussed on this thread:

Are there other issues being discussed? Which of these is being added to Parcel 2? Which has been solved (and how) at the moment?

DeMoorJasper commented 4 years ago

@mikestopcontinues I only consider the original issue as tracked by this issue, which is automatically restarting a node script on changes. For other feature requests look at other issues or create another.

Which has not been implemented yet but could be implemented fairly easily with a reporter. The only concern I have is what to do with multiple targets in this case? Do we run all node.js targets or add a flag in targets config or ... ?

mikestopcontinues commented 4 years ago

Personally, I'd really like to see a separate middleware instance for each endpoint, which would help the targets issue. This way, only the changed endpoint is swapped out. (Or endpoints, in the event of a shared lib change.) Perhaps the easy way to implement this is to create a separate parcel instance behind the scenes for each target in use.

The reason I think it should would on the endpoint level is that restarting a heavy express api becomes a major impediment to development. Also, it's easy enough for users to reload the whole server on change now. It's not my desired endgame, but I strung it together with very basic package.json scripts:

"api": "npm-run-all api:*",
"api:build": "parcel watch -t node -d .parcel/api --cache-dir .parcel/cache local/api.js",
"api:watch": "nodemon .parcel/api/api.js",
LucaConfa commented 4 years ago
npm-run-all

I tried something similar but it doesn't really work for me.. I can see parcel doing it's job, but nodemon never actually spin up the server