Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Coffeescript Laravel Package

nodejs-php-fallback/coffeescript

CoffeeScript compiler for PHP with Node.js fallback support. Integrates .coffee-to-JavaScript compilation into Laravel and PHP projects via a simple API, helping asset pipelines handle CoffeeScript without making Node.js a hard runtime dependency.

View on GitHub
Deep Wiki
Context7

Getting Started

composer require nodejs-php-fallback/coffeescript
# optional but recommended — gives you real CoffeeScript 2.x instead of the PHP port
npm install coffeescript

Nothing to publish or configure: the class resolves a compiler at call time, preferring the coffee binary from node_modules/.bin, then a global npm install, then a bundled pure-PHP compiler.

Where to look first. The public surface is deliberately tiny. Open src/ and read, in this order: the CoffeeScript class (the compile() entry point and any set* options), the NodejsPhpFallback base class (getCommand(), isNodeInstalled(), exec()), and the fallback compiler class. Those three files answer 90% of the questions you will have about binary resolution and option passthrough. Also read composer.json before you commit — the version constraints on symfony/process and the fallback compiler tell you immediately whether it will install on your PHP version.

First use case — compile a snippet in a REPL/tinker session:

use NodejsPhpFallback\CoffeeScript;

$cs = new CoffeeScript();
echo $cs->compile('square = (x) -> x * x');

Expected output is an IIFE-wrapped var square; square = function(x) { return x * x; };. If you get that, the Node path is working. Then deliberately break it (setExecutablePath('/nope/coffee') or rename the binary) and run again — you should get equivalent-but-differently-formatted JS from the PHP fallback. Doing this once, up front, teaches you the single most important thing about this package: which compiler ran determines the output you get.

Implementation Patterns

Build-time compile with an mtime cache. The package has no cache of its own; invoking a process per request is the classic mistake. Compile in an Artisan command and skip unchanged files:

class CompileCoffee extends Command
{
    protected $signature = 'coffee:compile {--force}';

    public function handle()
    {
        $cs = app(CoffeeScript::class);
        $src = resource_path('coffee');
        $out = public_path('js');

        foreach (glob($src.'/*.coffee') as $file) {
            $target = $out.'/'.basename($file, '.coffee').'.js';
            if (! $this->option('force') && file_exists($target) && filemtime($target) >= filemtime($file)) {
                continue;
            }
            $this->info("Compiling $file");
            file_put_contents($target, $cs->compile(file_get_contents($file)));
        }
    }
}

Hook it into composer.json under post-autoload-dump or into your deploy script, and only run it when .coffee sources are newer than the artifacts.

Bind it as a singleton in a service provider so the binary path and options are resolved once, not per compile:

$this->app->singleton(CoffeeScript::class, function () {
    $cs = new CoffeeScript();
    if ($path = config('assets.coffee_binary')) {
        $cs->setExecutablePath($path);   // absolute path, not a PATH lookup
    }
    return $cs;
});

Blade/helper facade for a tiny asset pipeline. If you're converting an old preprocessor-heavy app, wrap the compile step behind a helper that reads from a public/js artifact directory and only falls back to compiling in local/testing environments. Production should never compile.

Compile-on-write for user-supplied templates. Where CoffeeScript snippets live in the database (theme editors, CMS widgets), compile at save time in a model observer and store both the source and the JS. This keeps the request path free of proc_open and lets you surface syntax errors as validation messages instead of 500s.

CI matrix. Run your test suite twice: once with node on the PATH and coffeescript installed, once with Node removed from the job image. If the outputs differ in ways your tests care about, you've found a real portability bug before production did.

Keep it out of the modern front-end build. If the project uses Vite, Mix, or Webpack, those already compile CoffeeScript via a loader. This package's niche is the other case: a PHP app with stray .coffee files and no JS toolchain, or a shared host where Node can't be guaranteed.

Gotchas and Tips

Fallback is silent and lossy. When Node is missing or the process fails, you get the PHP compiler's output instead. That port targets CoffeeScript 1.x: CoffeeScript 2's ES2015 output (real class, => semantics, import/export) will not round-trip, and --map source maps are not produced. If your .coffee uses CS2 syntax, the fallback will either throw a parse error or emit subtly wrong JS. Add a startup assertion that logs loudly when the fallback is used in non-local environments.

PATH is not your PATH. The web-server and cron users typically have no nvm shims and no /usr/local/bin from your shell profile. Never rely on which coffee. Set an absolute path from config, or install coffeescript locally as a project devDependency and let the class find node_modules/.bin/coffee.

Binary name quirks. On Debian/Ubuntu the Node binary has historically been nodejs, not node, and on Windows npm bin entries are .cmd shims. Both break naive process invocation with symfony/process. Symptom is an empty or exception-throwing compile on an otherwise healthy machine — inspect getCommand() to see exactly what it tried to run.

--bare is the difference between mergeable and unmergeable output. Default output wraps in an IIFE, which pollutes nothing but also can't share top-level variables across files. If you're concatenating, pass the bare option and be aware the fallback may not honour every flag identically — assert on output in a test rather than assuming parity.

Treat user input as code. compile() runs a compiler on whatever string you hand it. CoffeeScript's backtick escape emits raw JavaScript, so untrusted snippets are effectively an RCE vector once the output reaches a browser. Sandbox at the policy level (only admins can author snippets) rather than trying to sanitise the source.

Escape and timeout. If you build commands yourself anywhere near this package, use Process argument arrays, never string concatenation, and set a timeout — a pathological input will otherwise hang your queue worker.

Staleness is the real risk. Last release was January 2019 with no dependents. Before adopting, run composer update --dry-run on PHP 8.2/8.3 and grep the source for create_function, dynamic properties, and symfony/process major-version pins. If it installs but the fallback compiler is abandoned, vendor the two or three files you need or fork it — the wrapper logic is small enough to own.

Extension point. The class is built to be subclassed: override the binary name and command construction to force a specific local coffeescript version, or override the fallback getter to substitute your own compiler (or to throw). Subclassing to disable the fallback entirely is, in most production setups, the single most valuable change you can make to this package.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
unabomber/timezone
delatbabel/contacts
astritzeqiri/g-recaptcha
kevinongko/laravel-genode
developeruz/laravel-analytics
robbiep/afterthedeadline
frankkessler/incontact-laravel-oauth2-rest
jlxh/laravel-userlog
davidmgilo/timetables
qylinfly/role-permission
reflect-digital/conduit
takashato/laravel-vietqr
aitor24/laralang
kraenzle-ritter/entity-relations
dindigital/din-image
pomirleanu/image-colors
sawmainek/laravel-api-generator
delatbabel/jsonresponses
mouhamedfd/permissions-generator
eduplus/qr