Skip to content

Instantly share code, notes, and snippets.

@IngwiePhoenix
Created December 1, 2015 18:56
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save IngwiePhoenix/5339b3c1fed369d0ef21 to your computer and use it in GitHub Desktop.
Save IngwiePhoenix/5339b3c1fed369d0ef21 to your computer and use it in GitHub Desktop.
Uniter

WebPack+Uniter

During the processing of any .php OR .uniter.php file, the first-found composer.lock file will be loaded. It will be used to create a map of namespaces and files, allowing us to emulate the autoloader during the static analysis.

To track dependencies:

  • Look for use statements.
  • Look for new statements.

For a brief example, consider the following:

<?php

use Foo\Application;
use Exception;
use App\Controller\MainController;

$app = new Application(new MainController);

We would find out that this file uses:

  • \Foo\Application
  • \Exception
  • \App\Controller\MainController

Since \Exception is internal, we'd skip it.

For the other two, we'd look at the collected composer information, and resolve the files:

  • .../vendor/Foo/src/Application.php
  • ./app/Controller/MainController.php

We'd further process these files, and repeat.

This should result in a top level dependency graph. That one should allow us to make basic programs work. In order to make these namespaces available, we'd create a map from namespace to module.

var exampleMap = {
    "\\Foo\\Application": require("..."),
    "\\App\\Controller\\MainController": require("...")
}

Next, the second level of dependencies, would be tracked by the require and include statements. Those are to be resolved relative to the files. But we'd replace the actual call.

Before:

<?php
include "./foo.php";

After:

<?php
__webpack_uniter_require__("./foo.php");

Usually, we'd resolve all the files through webpack. This could possibly be the definition of the function above:

var filemap = {
    "/absolute/path/to/foo.php": require(...)
}
function __webpack_uniter_require__(moduleId, once) {
    if(once) {
        // Reset module - aka. remove result from cache and force re-run.
        if(typeof require.cache[moduleId] != "undefined") {
            delete require.cache[moduleId];
        }
    }
    return require(moduleId);
}

WebPack allows us to handily inject plugins into it's parser, so we can find each instance of a specific function, and re-write it.

To shaddow the local paths used during compilations, we could also replace the paths by IDs.

"./foo.php" -> /some/path/foo.php -> id(1)

This would turn:

include "./foo.php";

to:

__wepback_uniter_require__("./foo.php");

and ultimatively to:

__webpack_uniter_require__(1);

Possible problems

  • WebPack's module loader caches modules. In order to use *_once() style functions, we'd have to purge the file off the cache. Not impossible at all, but something to keep in mind.
  • Creating the module index will require a WebPack plugin, in order to obtain numbers of modules. WebPack modules are horribly documented, so this will be a challenge.
  • A tool to scan and edit the Uniter AST will have to be made.

Workflow

  • A WebPack loader is configured for .php files.
  • It receives a request for such a file.
  • First, the file is parsed.
  • The generated AST will be searched for dependencies. require_once/include_once statements will be re-written as __webpack_uniter_require__(moduleId, once).
  • Then, the dependencies will become resolved. The responsible JS maps will be made and populated.
  • First we insert the dependency code and add it as WebPack dependencies.
  • Then we return the generated JS.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment