mattdesl / budo

:clapper: a dev server for rapid prototyping
MIT License
2.18k stars 106 forks source link

feature: write all router paths to disk #60

Open yoshuawuyts opened 9 years ago

yoshuawuyts commented 9 years ago

It might be cool if budo's routes were able to be persisted to disk. This would allow users to prototype using a live-reloading environment, and once they're happy with the results store the output to disk and deploy it to GH-pages.

Currently the following modules are available to do this:

Usage looks like this:

const toServer = require('wayfarer-to-server')
const toFs = require('wayfarer-to-fs')
const wayfarer = require('wayfarer')

const router = toServer(wayfarer())
router.on('/', {
  get: (req, res) => fs.createReadStream(__dirname + '/index.html').pipe(res))
})

toFs(router, __dirname + '/dist', (err) => {
  if (err) throw err
})

What do you think of adding such functionality to budo?

mattdesl commented 9 years ago

How might this look with regard to budo? I'm trying to envision how a small GH pages site/demo like this could use wayfarer-to-server to avoid having to manually write the index.html and browserify + uglify the bundle.js (unless I'm mistaken about the goals of this?).

Can you give example of other routes that might be worth persisting?

yoshuawuyts commented 9 years ago

Ah yes, I should've been a bit more clear in that respect. What you're outlining is exactly the use case for this.

budo currently server index.html and bundle.js files, which is neat when prototyping. However, when you're done prototyping it'd be cool if those files could be written to disk and uploaded to GH-pages using the same tool (as opposed to pulling in grunt or something equally gross to do the job).

A simplified config could look like:

./router.js

const toServer = require('wayfarer-to-server')
const html = require('simple-html-index')
const wayfarer = require('wayfarer')
const watchify = require('watchify')
const uglify = require('uglifyify')

const router = toServer(wayfarer())
module.exports = router

router.on('/', {
  get: (req, res) => html().pipe(res))
})

const b = watchify('./index.js')
  .plugin(uglify())

router.on('/bundle.js', {
  get: (req, res) => b.bundle().pipe(res)
})

./to-fs.js

const toFs = require('wayfarer-to-fs')
const router = require('./router')
const path = require('path')

const directory = path.join(__dirname, 'dist')
const overrides = { '/': '/index.html' }

toFs(router, directory, overrides, (err) => {
  if (err) throw err
})
mattdesl commented 9 years ago

This looks good; the only thing is that typically you end up with different dev and prod configurations; like using installify during development or uglify only during production. Any ideas how that would be handled?

Also, I guess how would it manifest itself on the end-user? How would these two lines be replaced? As it states in uglifyify readme, you generally still want to run uglifyjs on the final bundle.

yoshuawuyts commented 9 years ago

budo allows flag passing to the underlying browserify instance. This could still be done when writing to disk:

  "scripts": {
    "start": "budo index.js:bundle.js --live -- -t babelify | garnish",
    "build": "budo --write index.js:bundle.js -t babelify -t uglifyify -o ./dist"
  }

Does this answer your question?

edit: yeah, this would mean that direct unix pipes don't work, but luckily practically all bundle actions are available as browserify transforms.

edit: oh, you're right about the uglifyify output. I usually don't minify (gzip works pretty well), but i think this is a pretty specific case. In this case it might be best to have a separate optimization step that is called after writing files to disk. e.g. one could do:

  "scripts": {
    "start": "budo index.js:bundle.js --live -- -t babelify | garnish",
    "build": "budo --write index.js:bundle.js -t babelify -t uglifyify -o ./dist && npm run optimize",
    "optimize": "uglify -s dist/bundle.js | sponge | dist/bundle.js
  }

I admit it's not ideal, but I think it's somewhat of a corner case. Alternatively we could use a flag to pipe some of the files through to stdout (which would mean extending wayfarer-to-fs):

$ budo --write --raw index.js -t babelify -t uglifyify -o ./dist | uglify > dist/bundle.js

but I think this approach creates more problems than it solves.

mattdesl commented 9 years ago

I think browserify options could still be passed after the full stop, and maybe the flag could look something like this:

budo index.js:bundle.js --write=./static/ -- -t babelify -t uglifyify

Could be named --write or --save.

I actually think this feature would be pretty nice.

andyinabox commented 8 years ago

+1!

mattdesl commented 8 years ago

More thoughts:

The way I've solved this for my projects is to use quick-build which installs latest browserify and uglify-js and adds a build script to the package.json. This also integrates nicely with my ghpages script. All together, I can set up a demo in a couple commands, and then build + deploy it when necessary with another couple commands.

Anyways, this is still unresolved since I'm open to discussing it further, but hopefully we can find a way that isn't too complex/painful. :smile:

timwis commented 8 years ago

Pardon the necro but I just wanted to chime in to point out that it seems the hold up here is around optimization for production; I'd like to suggest that there's a need for output/export/write in a prototyping tool -- to share your prototype. Yes, you can just use browserify index.js -o bundle.js but what about the index.html file that budo generates? I usually use a dist directory so that I only deploy what needs to be deployed (bundle.js, index.html, styles.css).

Currently, my build script looks something like browserify index.js -o dist/bundle.js && cp index.html dist/ && cp styles.css dist/. It seems silly to use curl to pull these files into the dist directory when they could just be written to disk instead of served over an http server.

Generating a dist directory and sharing it around, for me at least, comes before optimization is necessary. I can always upgrade my build script later.

bankai build will help, but budo does have some pretty nice features :-/

dy commented 5 years ago

Can't wait for the feature, in the meantime https://github.com/dy/budo

mattdesl commented 5 years ago

FWIW I implemented this in canvas-sketch and it introduces a number of new considerations:

This feature might be a bit awkward if implemented in budo currently, as budo wasn't really designed with it in mind, and it really increases the opinions and surface area of budo.

I'm open to other creative solutions, though.

PS:

Forking budo is actually a fine solution, or even creating a new npm module on top of budo. Budo is pretty stable, and probably won't be receiving many more changes as I'd rather keep it bare-bones. The canvas-sketch-cli tool is built directly on top of budo so you can see how it's used to build more bespoke and reusable build chains.