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

Is there any way to pre-process files and send the output as stdin for live to refresh? #50

Closed
zanona opened this issue Jul 31, 2015 · 16 comments

Comments

@zanona
Copy link

zanona commented Jul 31, 2015

I am trying to think on a way that would allow budo requests to *.less,*.sass files to activate the pre-processor, run the output through auto-prefixer and render the outcome as text, directly injected into a page without the need to generate a physical file, basically the same as it does by default with javascript files.

Would that be somehow possible to achieve with budo?
The way it works is genius, but I only missing CSS pre-processors to really improve my workflow.

Ideally this would be achievable if the reload method accepted the file contents instead of its path only, like:

main.less

body {
    background: red;
    p {
        color: green;
    }
}

main.less compiled into .css and made available for rendering in-browser

app
  .watch('*.less', { interval: 300, usePolling: true })
  .live()
  .on('watch', function(type, file) {
    if (path.extname(file) === '.less') {
        less('./main.less').then(function (css) { 
            app.reload('main.less', css); //css => body { background: red; } p { color: green; }
        })
    }
  })

Thanks a lot

@mattdesl
Copy link
Owner

Interesting. 😁

What about just using a main.css in your <link href>, but using budo's file watcher to trigger less and write it to a file?

app
  .watch('*.less', { interval: 300, usePolling: true })
  .live()
  .on('watch', function(type, file) {
    if (path.extname(file) === '.less') {
        less('./main.less').then(function (css) { 
            fs.writeFile('main.css', css, function (err) {
              if (err) {
                // handle error nicely
              } else {
                app.reload('main.css');
              }
            });
        });
    }
  })

@zanona
Copy link
Author

zanona commented Jul 31, 2015

@mattdesl that is a nice idea, however it can get a bit confusing? specially when you have a production build process based on real file names?

I would be amazing if there could be a way to handle that as gracefully budo --live already does. No output file necessary. However I appreciate it can be trickier

We've managed to get a similar result for on BrowserSync/browser-sync#411
giving complete control over what is rendered as output. It works quite well 😄

@zanona
Copy link
Author

zanona commented Aug 1, 2015

hey @mattdesl,
I played around with the source code and have managed to achieve that by adding another feature to the API — Here you can see the comparison and see what has changed. I tried to go inline with your structure, but please let me know if you find anything wrong with it.

Basically I have managed to achieve what I wanted by adding a new event to the api called request where on('request', fn(req, res) {}) would give the user the ability to tweak the file content and render it through the server without having to provide physical files.

When adding adding a watch glob through the API, that glob would automatically be recreated in the server router as well, meaning, watch('**/*.less') will also give the user the ability to listen requests from the server.

Have a look on how the budo-config.js file makes use of the API below:

/*jslint node:true*/
var less = require('less'),
    fs = require('fs'),
    url = require('url'),
    path = require('path'),
    budo = require('budo')('app.js', { live: true, stream: process.stdout });

budo.watch('**/*.less')
    .on('watch', function (type, file) {
        if (path.extname(file) === '.less') {
            //trick to get live LESS injection
            //even though `main.css` does not exists ;)
            budo.reload(file.replace(/\.less$/, '.css'));
        }
    })
    .on('request', function (req, res) {
        // this method will only be called to new extensions
        // declared on budo.watch method
        var file = url.parse(req.url).pathname;
        file = path.join(process.cwd(), file);
        if (path.extname(file) === '.less') {
            fs.readFile(file, function (err, o) {
                less.render(o.toString(), function (err, o) {
                    //setting headers (optional)
                    res.setHeader('content-type', 'text/css');
                    res.setHeader('content-length', o.css.length);

                    res.end(o.css);
                });
            });
        }
    });

Now the trick to get live reload injection for LESS files was asking Budo to reload the same file, but with .css extension instead. It is strange, but works great.

budo.reload(file.replace(/\.less$/, '.css'));

Also, note that this new approach would allow users to have complete freedom on what they would like to do with the files. It can be pre-processed with SASS, Stylus, etc, in fact, the possibilities are huge since you could also create files in JADE, HAML or any other HTML pre-processor and it could also be rendered as html straight away. 👍

I am quite happy I was be able to achieve this with budo since and am amazed on how great module already is. Also, I am looking forward to hear your thoughts about it and perhaps we could think on a way to add that feature in

All the best.

@mattdesl
Copy link
Owner

mattdesl commented Aug 2, 2015

Very cool @zanona.

I have been wanting to make use of budo for some non-static sites as well, and maybe the 'request' event will help achieve this.

I have a pretty busy week coming up, and on Wednesday I start vacation in Europe for a couple of weeks. After that, I will take a closer look at this and hopefully get it working for the 5.0 release.

@zanona
Copy link
Author

zanona commented Aug 2, 2015

Great to hear you like the idea @mattdesl.
I hope you enjoy your holidays and let me know if you like to discuss this issue further once you get back.

@zanona
Copy link
Author

zanona commented Aug 25, 2015

hey @mattdesl, just to let you know I've been using https://github.com/zanona/budo-less which is based on the request functionality added through my fork of budo to achieve live less file reload. It's been working nicely so far.

you can try it out via npm i zanona/budo-less -g.

Let me know if you would like a PR.
Cheers

@mattdesl
Copy link
Owner

mattdesl commented Sep 6, 2015

Just FYI I have not forgotten about this. 😄

I'm adding something that should allow this feature in budo@5.

You will be able to pass a middleware function which can override any route, so that budo can be used for more than just simple static sites.

See here for example:
https://github.com/mattdesl/budo/blob/next/test/test-custom-server.js

A tool like budo that has LESS (or SASS) built-in could work very similarly to the way --serve and entry mapping works.

eg:

budo-less src/index.js:bundle.js --style=less/main.less:main.css

Now your index.html:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <link rel="stylesheet" type="text/css" href="main.css">
    <title>budo</title>
</head>
<body>
    <script src="bundle.js"></script>
</body>
</html>

And any requests to req.url === '/main.css' will get written with the compiled output from the less/main.less file. This way, when you go to publish your production build, your HTML file does not need to change.

@zanona
Copy link
Author

zanona commented Sep 6, 2015

that sounds a good idea @mattdesl.
How saving an external file imported by a less document would behave in this sittuation?
Like having a main.less that imports layout.less and then saving layout.less would live reload the content of main.css on the html page?

@mattdesl
Copy link
Owner

mattdesl commented Sep 6, 2015

One approach:

budo.watch([ 'less/**/*.less' ])
  .on('watch', (ev, file) => { 
     if (/\.less$/i.test(file))
         budo.reload('main.css')
  })

This will cause LiveReload to re-request main.css (without a hard reload), which when the server gets hit, will be compiled by lessc and will write as regular CSS.

A more sophisticated approach would be to use a LESS file watcher that is aware of the @import dependency tree. I'm not sure whether one exists.

@zanona
Copy link
Author

zanona commented Sep 6, 2015

sounds great @mattdesl. It will definitely be nice to see this implemented.
All the best

@mattdesl
Copy link
Owner

There is now an experimental / proof-of-concept CLI tool for quick prototyping with LESS and proper LiveReload. It is mostly just a thin wrapper around budo's API.

budo-less

For more complex pre-processors or features outside the scope of that CLI, I would still suggest using the budo API directly (example here).

@zanona
Copy link
Author

zanona commented Sep 12, 2015

Cool. Thanks a lot for that @mattdesl. Is there any option if I want to keep the file name with .less extension on the html file so it can be re-mapped?

@mattdesl
Copy link
Owner

You can specify the URL for the <link href> like so:

budo-less index.js --less main.less --css main.less

Curious what you mean by "re-mapped" ?

@zanona
Copy link
Author

zanona commented Sep 12, 2015

Thanks. That was exactly what I meant ;)

@MadebyAe
Copy link

MadebyAe commented Feb 12, 2016

Define this as npm run dev pretty straight forward: budo --install --css dist/app.css --title App src/scripts/index.js ; stylus -w -u nib src/styles/main.styl -o dist/app.css

@sholtomaud
Copy link

@MadebyAe that didn't work for me on OSX. I had to use "&", instead of ";" for stylus to rebuild onchange.

npm run dev

"scripts": {
    "dev": "budo --install --css ./app.css --title App index.js --live --verbose --open -t babelify & stylus -w -u nib stylus/index.styl -o ./app.css"
}

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

No branches or pull requests

4 participants