socketstream/socketstream

View on GitHub
docs/partials/tutorials/client_side_code.html

Summary

Maintainability
Test Coverage
<a href="https://github.com/socketstream/socketstream/edit/master/src/docs/tutorials/en/client_side_code.ngdoc" class="improve-docs"><i class="icon-edit"> </i>Improve this doc</a><h1><code ng:non-bindable=""></code>
<div><span class="hint"></span>
</div>
</h1>
<div><div class="client-side-code-page"><h2 id="client-side-code">Client-Side Code</h2>
<p>SocketStream allows you to write and structure client-side Javascript in exactly the same way as server-side code, allowing you to easily share modules between both.</p>
<h4 id="client-side-code_how-to-use-modules">How to use Modules</h4>
<p>All files which aren&#39;t <code>libs</code> (see below) are treated as modules. You have exactly the same ability to export functions, <code>require()</code> other modules, and cache values within modules as you do when writing server-side code in Node.js.</p>
<p>Client-side code lives in <code>/client/code</code>. Create as many subdirectories as you wish. Reference your modules relatively, e.g. <code>require(&#39;../../image/processor&#39;)</code>, or absolutely <code>require(&#39;/path/to/my/module.js&#39;)</code>. It all work as you would expect, just bear in mind a module will never be executed unless it is explicitly <code>require()</code>&#39;d.</p>
<p>Top tip: Type <code>require.modules</code> in the browser console to see a list of all modules you can <code>require()</code> in your app</p>
<h4 id="client-side-code_special-exceptions">Special Exceptions</h4>
<p>While we try to keep the experience between browser and server as similar as possible, there are a few special cases to be aware of:</p>
<h5 id="client-side-code_special-exceptions_libs---legacy-libraries">&#39;libs&#39; - Legacy (non Common JS) Libraries</h5>
<p>Any file which lives in a directory called &#39;libs&#39; will NOT be served as a module. Instead these files will be sent as-is without any modification. Typically you&#39;ll want to ensure jQuery and other libraries which use the <code>window</code> variable are always placed in a <code>/client/code</code> directory called &#39;libs&#39;.</p>
<p>As load order is critically important for non Common JS libraries <strong>either</strong> name your files alphanumerically within the <code>libs</code> directory <strong>or</strong> list each file explicitly in your <code>ss.client.define()</code> command - your choice.</p>
<h5 id="client-side-code_special-exceptions_system---system-modules">&#39;system&#39; - System Modules</h5>
<p>System modules are similar to regular modules but with one important difference: they are accessed without a leading slash - just like you would <code>require(&#39;url&#39;)</code> or <code>require(&#39;querystring&#39;)</code> in Node.js.</p>
<p>So why do we need this distinction? Because some libraries such as Backbone.js (when used as a module, rather than in a &#39;libs&#39; directory) depend upon other system modules. In this case Backbone calls <code>require(&#39;underscore&#39;)</code> internally, therefore both <code>backbone.js</code> and <code>underscore.js</code> must live in a <code>system</code> directory.</p>
<p>As SocketStream uses code from <a href="https://github.com/substack/node-browserify">Browserify</a>, the &#39;system&#39; directory also allows you to use one of Node&#39;s inbuilt modules in the browser. Just head over to <a href="https://github.com/substack/node-browserify/tree/master/builtins">https://github.com/substack/node-browserify/tree/master/builtins</a> and copy the libraries you need into any directory within <code>/client/code</code> called <code>system</code>.</p>
<p>Tip: If you&#39;re making a new folder called <code>/client/code/system</code>, don&#39;t forget to add <code>system</code> to the list of code directores to serve in the <code>ss.client.define()</code> statement within <code>app.js</code>.</p>
<h5 id="client-side-code_special-exceptions_/entryjs---a-single-point-of-entry">&#39;/entry.js&#39; - A single point of entry</h5>
<p>The <code>entry</code> module has a special distinction: it is the only module to be required automatically once all files have been
sent to the browser.</p>
<p>An <code>entry.js</code> (or <code>entry.coffee</code>) file is created for you in the <code>app</code> view by default when you make a new project.
It contains a small amount of boiler-plate code which you may modify to handle the websocket connection going down,
reconnecting, and (critically), what module to <code>require()</code> next once the websocket connection is established.</p>
<p>The <code>code</code> is set to <code>[&#39;libs/jquery.min.js&#39;, &#39;app&#39;]</code>. The first module with an <code>entry.js</code> is taken as the main module for the view.
So in this case the view is started automatically with the call <code>require(&#39;./code/app/entry&#39;);</code>. This allows you to share code
directories, and use more than one in a view.</p>
<h4 id="client-side-code_implications-for-ondemand-loading-in-production">Implications for ondemand loading in production</h4>
<p>In production code is loaded as a single bundle, but as additional modules can be loaded on demand the code is copied into the static assets.
The code should be under <code>/client/static/assets/&lt;namespace&gt;/code</code>. By default the namespace is the client name, but if you want to share
code you can define a common namespace. During development this will be served on-the-fly.</p>
<h4 id="client-side-code_should-i-put-library-x-in-libs-or-system">Should I put library X in &#39;libs&#39; or &#39;system&#39;?</h4>
<p>It depends if it needs access to the <code>window</code> variable. For example, Backbone.js works great as a <code>system</code> module unless you&#39;re using Backbone.history as this requires access to <code>window</code>.</p>
<h4 id="client-side-code_what-happens-if-i-try-to-require-a-module-which-doesnt-exist">What happens if I try to require a module which doesn&#39;t exist?</h4>
<p>You&#39;ll see an error in the browser&#39;s console. In the future SocketStream will be able to catch these problems before they arise.</p>
<h4 id="client-side-code_using-require-in-the-browser">Using require in the browser</h4>
<p>Within client view code you should generally require another module by relative path (<code>&quot;./controller&quot;</code>) or system module (<code>&quot;facility&quot;</code>).
If you use absolute path it will be relative to the client directory, so if you want to require a directory module within the default
code directory do something like,</p>
<pre><code>require(&quot;/code/app/part1&quot;)
</code></pre><p>This will load the module for the source file <code>/code/app/part1/index.js</code> or <code>/code/app/part1/index.coffee</code>. If part1 isn&#39;t a directory
it can also be used to load <code>/code/app/part1.js</code>. You could also do this explicitly using <code>require(&quot;/code/app/part1/index.js&quot;)</code>,
but this isn&#39;t advised as it locks you in to the exact source structure.</p>
<h4 id="client-side-code_loading-modules-on-demand">Loading modules on demand</h4>
<p>You don&#39;t necessarily have to send all modules to the browser at once, you can also <a href="https://github.com/socketstream/socketstream/blob/master/doc/guide/en/loading_assets_on_demand.md">load them on demand</a>.</p>
<h4 id="client-side-code_options">Options</h4>
<ul>
<li><strong>browserifyExcludePaths {Array} </strong> - to disable automatically packaging client side code into modules for certain paths, for example <code>client/code/app/controllers/**/*.js</code> and <code>client/code/app/directives/**/*.js</code> put the following in your server-side <code>app.js</code> code:
<pre class="prettyprint linenums">
ss.client.set({ browserifyExcludePaths: ['app/controllers', 'app/directives'] });
</pre></li>
<li><strong>entryModuleName {string} </strong> - to change or disable automatically execuring the client side
code for the entry module. If null or a blank string is given no entry module is executed during load of the view in the browser. If for example your app is <code>my</code> and entryModuleName is <code>app</code> the view will execute <code>client/code/my/app.js</code>. Be aware that this name will apply to all views.
<pre class="prettyprint linenums">
ss.client.set({ entryModuleName: null });
</pre></li>
</ul>
<p><strong>Note</strong>, that paths for excluding should be relative to <code>client/code/</code> and that file <code>client/code/app/entry.js</code> could not be excluded in any cases.</p>
<p>If you need to exclude from automatically packaging certain file, just specify the file&#39;s relative path:
<pre class="prettyprint linenums">
ss.client.set({ browserifyExcludePaths: ['app/routes.js'] });
</pre>
<h4 id="client-side-code_background-info">Background info</h4>
<p>Getting client-code right was a major goal for SocketStream from the beginning.</p>
<p>For too long web developers have had to wade through a mess of unstructured JavaScript files without anyway to manage namespacing or dependencies.</p>
<p>Solutions such as <a href="http://requirejs.org">Require.js</a> and other AMD approaches have successfully brought order to chaos, but put the onus on the developer to manually track and list dependencies. What&#39;s more, they use a different syntax to <code>require()</code> files - instantly killing all hopes of sharing the same file between the client and server.</p>
<p>We wanted to do much better with SocketStream. After all, we are in the unique position of managing both the client and server stack. The solution came in the form of <a href="https://github.com/substack/node-browserify">Browserify</a> - an awesome, lightweight, library which solves all these problems once and for all.</p>
<p>SocketStream doesn&#39;t depend upon the Browserify module (as it contains code we don&#39;t need), but we use major components from it (including the critical code which performs all the <code>require()</code> magic). Our thanks go to Substack for coming up with a clean solution to a very tricky problem.</p>
</div></div>