ractivejs / rvc

RequireJS loader plugin for Ractive components
20 stars 10 forks source link

Publish as npm package #1

Closed MartinKolarik closed 10 years ago

MartinKolarik commented 10 years ago

@Rich-Harris can you publish this as a npm package?

Rich-Harris commented 10 years ago

thought I already had! oops. done: https://www.npmjs.org/package/rvc

MartinKolarik commented 10 years ago

Thanks! How do I use it? require('rvc') throws an error as it tries to load Ractive using RequireJS.

Rich-Harris commented 10 years ago

Ha, I really should write some docs. The short version - to load a component inside an AMD module:

1. Set up paths config

require.config({
  // ...
  paths: {
    rvc: 'path/to/rvc',
    ractive: 'path/to/ractive'
  }
});

2. Use the plugin!path syntax

// assuming there's a component definition at path/to/component.html
// (note the extension is omitted):
define([ 'rvc!path/to/component' ], function ( Component ) {
  var view = new Component({ el: whatever });
});

Is that what you're trying to do? Or are you trying to compile a component in node.js?

MartinKolarik commented 10 years ago

Or are you trying to compile a component in node.js?

Yes. I should have said so. I'm already using this on the front end and I want to be able to reuse the same files on the back end, so I'm working on ractivejs/ractive#538.

Rich-Harris commented 10 years ago

Ah, okay. There's another repo, https://github.com/ractivejs/rcu, which is a sort of toolbelt for creating component loaders/builders. Is equally lacking in documentation but it goes something like this:

var rcu = require( 'rcu' ), Ractive = require( 'ractive' );

rcu.init( Ractive ); // otherwise nothing will work

fs.readFile( 'path/to/component.html', function ( err, result ) {
  if ( err ) throw err;

  // this next line gives you an object with five properties:
  //   `template` - the *parsed* template
  //   `css` - self-explanatory
  //   `imports` - any components referenced by e.g. `<link rel='ractive' href='./foo.html'>`
  //   `modules` - any modules referenced by e.g. `d3 = require('d3')`
  //   `script` - any JS code in a `<script>` element
  var parsed = rcu.parse( result.toString() );
  doSomethingWith( parsed );
});

Haven't tried actually rendering components in node.js using this method, only unadorned templates. Though it should be fine as long as the component itself doesn't have any browser-specific requirements... will be interested to hear how you get on.

Rich-Harris commented 10 years ago

Actually I take that back, you might run into problems rendering components as the rcu.createFunction method uses a filthy hack to make it possible to locate syntax errors in scripts - it turns the code into a data URI then injects a <script src='[datauri]'></script> on to the page. Might need to have a fallback (though I suppose it might be easier to just do new Function()).

Other methods exposed:

martypdx commented 10 years ago

@Rich-Harris Here's what I did in another project to get to the errors.


try{
    var fn = new Function('return (' + js + ');')  //project specific append js to return
}
catch(e){
    eval(js)
    throw e; //should never get here, eval should throw, but...
}
Rich-Harris commented 10 years ago

@martypdx but how do you know where the syntax error occurs in the code? The stack trace includes the eval(js) call, but not the code itself. The data URI hack makes debugging much easier:

screen shot 2014-05-31 at 16 56 37

screen shot 2014-05-31 at 17 04 36

martypdx commented 10 years ago

Right. Maybe something like https://github.com/stephenmathieson/node-sourceurl.

martypdx commented 10 years ago

Oh, can we write the script out as a tmp file and then require it? For node I mean. Or do we not care if not in the client?

Rich-Harris commented 10 years ago

Yeah, that sounds like it could work

martypdx commented 10 years ago

This example not fully baked, but you can create a module dynamically:


function requireFromString(src, filename) {
  var Module = module.constructor;
  var m = new Module();
  m._compile(src, filename);
  return m.exports;
}

var js = 'var ;'

var fn = requireFromString('var js = "' + js + '";\nmodule.exports = function(){ eval(js)};' )
console.log( fn() );
Rich-Harris commented 10 years ago

Nice!

MartinKolarik commented 10 years ago

@Rich-Harris what is the difference between load and build? I know that load calls make which is exactly why it doesn't work on node, but when I changed this to always use build it worked like a charm.

Rich-Harris commented 10 years ago

@MartinKolarik RequireJS plugins change their behaviour depending on whether they're being used at runtime or during optimisation. The job of r.js (the RequireJS optimiser) is to create a single file from a starting file (say, app.js) by concatenating its dependencies (and its dependencies' dependencies, recursively).

So during development, when AMD modules are typically loaded asynchronously, load() is used - this calls rcu.make(), which returns a Ractive subclass, ready to be instantiated. But in production, it would be inefficient to a) include rvc.js in the build, and b) do the necessary transformation at runtime. During optimisation, the build() function takes a component.html and turns it into pure JavaScript that can be inlined.

Hope that sheds some light?

Rich-Harris commented 10 years ago

@MartinKolarik Might also be instructive to look at broccoli-ractive - it's more environment-agnostic, and can optionally transform a component into an AMD, CommonJS, or ES6 module

MartinKolarik commented 10 years ago

@Rich-Harris

So during development, when AMD modules are typically loaded asynchronously, load() is used - this calls rcu.make(), which returns a Ractive subclass, ready to be instantiated.

Still can't see why we can't always use build. What is the advantage in using load? As I said, I changed the rvc to always use build and component in the following code is the ready-to-use Ractive subclass.

var requireJS = require('requireJS');

requireJS.config({
    paths: {
        rvc: 'node_modules/rvc/rvc',
        ractive: 'node_modules/ractive/ractive'
    }
});

requireJS(['rvc!views/index'], function(Component) {
    new Component({ ... });
});
Rich-Harris commented 10 years ago

@MartinKolarik load creates a subclass, build creates a string representing an AMD module that will return a subclass once executed. It's interesting that forcing rvc to use build at runtime works - presumably RequireJS executes the string as JavaScript. Wouldn't necessarily have expected that (I've never used RequireJS in node).

This method involves a fair bit of indirection though. Is there any reason to use RequireJS in this context other than to use rvc? We might be able to do things in a more nodey way by just using rcu directly, if we get rid of that browser requirement. (Though to be fair, if it works, it works... :)

MartinKolarik commented 10 years ago

@Rich-Harris Yeah, RequireJS must be executing it as typeof component returns function.

I'm using require not only for components, but also for other RequireJS modules, so RequireJS is needed anyway.

var SelectFilesView = require('rvc!search/components/select-files');
var tooltipDecorator = require('decorators/tooltip');// this is a standard RequireJS module
MartinKolarik commented 10 years ago

@Rich-Harris Any reason not to use var here?

Rich-Harris commented 10 years ago

@MartinKolarik nope! oversight on my part. thanks

Rich-Harris commented 10 years ago

@MartinKolarik In case you're interested:

Funnily enough, I need to use server-rendered components in the project I started yesterday. I didn't want to use RequireJS in node - I've learned over the last few days not to trust it, as big a fan of RequireJS as I normally am - but it occurred to me that the ractive-load plugin could work in node if we just replace XHR with fs.

So that's what I've done - ractive-load is now isomorphic, to use the cool kids' lingo:

load = require( 'ractive-load' );
load( 'foo.html', function ( Foo ) { ... });
MartinKolarik commented 10 years ago

@Rich-Harris thanks for info! Have you experienced any specific problems with RequireJS in node? I'm not a big fan of adding a big library to save 10 lines of code, but the ability to reuse the code wins (for me) in this case.

Rich-Harris commented 10 years ago

@MartinKolarik It's basically 'emulating' node things like module, but imperfectly. You don't have access to things like __dirname, for example - here's a quote from James Burke:

When using the RequireJS require/define APIs it does not support all of the Node idioms, like require.paths, since they do not work across JS environments. dirname and filename are those types of APIs.

In a similar vein:

I decided it was better to not try to keep up with node internals, which would require constant gardening

To me that's a big red flag that says 'don't use RequireJS in node'. It will inevitably find a way to break your expectations in a hard-to-debug way at the most inconvenient possible time. I've been bitten by the reverse problem with browserify's imperfect implementations of core node modules in the browser, and since then I steer clear of things that try to increase code portability by pretending to be things they're not, and bend code to match the environment rather than vice versa. But YMMV!

MartinKolarik commented 10 years ago

@Rich-Harris I see your point, but I don't think it'll be a problem in this case as I'm not trying to do any fancy stuff. That said, it might be a good idea to create our own implementation to handle define() calls and drop r.js; time will tell. Anyway, I'll publish ractive-express soon (for now with r.js).

Btw, just in case you missed it, I commented on dd234b6712cd572e0cc80860714a29c9a9599678 yesterday. I see you solved the Module thing and published a new version, but the syntax error is still there.

Rich-Harris commented 10 years ago

@MartinKolarik ah, I did miss that. Will look into it. (That's a perfect example of a bug caused by using RequireJS in node!)

Great news about ractive-express.

MartinKolarik commented 10 years ago

@Rich-Harris I ended up supporting both RVC + RequireJS and ractive-load ;)