Vuepress Within a Laravel Application
Let me kick this tutorial off right away, this is just an example of how I accomplished this. I feel that there could be multiple different ways, which I’d love to hear about. With that being said I did get Vuepress working within a Laravel application under a route. All Single Page Application features work as well such as navigation and loading of resources, but there are a few configurations I feel could be done differently. From the end user perspective though it is awesome and having the power of Vuepress within the application itself? Also amazing!!
Overview
For this tutorial we will be looking at 521dimensions.com and our open source project AmplitudeJS whose docs are now written in VuePress and are located at https://521dimensions.com/open-source/amplitudejs/docs.
Our 521dimensions.com site is written in Laravel 5.6 and all of the docs for AmplitudeJS are in VuePress. It’s nice to have the docs on the same domain, and look like just another route in the site.
How I Structured The Set Up
This is the first part that I felt I could be different. If you have any alternatives or suggestions, definitely let me know! So I made 2 directories. We have our /521dimensions.com
directory and I made an /amplitude-docs
directory outside of the actual site.
The reason I did this was because I use Laravel-Mix for compiling 521dimensions.com assets. Laravel Mix is amazing, however, it’s on web pack 3. If you read the VuePress guide on installation (Getting Started | VuePress), you can not install Vuepress as a local dependency with web pack 3. NOT A VUEPRESS PROBLEM, but a web pack problem. Web pack will generate a wrong dependency tree and everything will go to waste. They recommend using Yarn, but installing Yarn and Using NPM looks like a massive headache and I’ve read you can’t even make them work together.
The other option is to do a global install of vuepress. Thought about it, but I like keeping things local whenever possible. That’s why I made another directory called /amplitude-docs
.
Installing VuePress
I’m not going to go into the details of installing Vuepress since the guide: Getting Started | VuePress handles it beautifully. What I did was create a new NPM project in the /amplitude-docs
directory. I then followed the guide on getting started with VuePress and ran:
npm install -D vuepress
That installed VuePress as a local dependency in the project. Next I created an /amplitude-docs/docs
directory. I then put a README.MD file in that directory and created the hidden .vuepress
directory.
In the /docs
directory you can add folders that contain documentation in Markdown for your app. In the VuePress config, you can set how you want these to be structured on the sidebar, but that’s dependent upon the theme you are using. Note that the equivalent to an index.html
file is the README.md
file. You will be adding one of these to each of your directories.
Configuring VuePress To Work With Laravel
Now, in the hidden .vuepress
directory I added a config.js
file. For AmplitudeJS docs I am simply using the default theme provided by VuePress. I love the theme and customized it to fit what I needed. You can check out the live docs here: Overview – AmplitudeJS Documentation. If you want to know how I configured or did something with VuePress to make the docs act a certain way, I’d be glad to help, just comment below! However, any configuration beyond what I did to make VuePress work with Laravel, is outside of the scope of this tutorial.
With that being said, open up your .vuepress/config.js
file. There is an option in VuePress called base
. This is EXTREMELY important to make this work as a sub-directory in Laravel. Before we set this, be aware of two commands:
npm run docs:dev
npm run docs:build
These two commands are set up when you install VuePress. The first command npm run docs:dev
fires up a local development url so you can see your docs as you add them. npm run docs:build
actually compiles all of your documentation into a rendered app that you can deploy to a variety of places: Deploying | VuePress. In our case, we will be deploying it to our server.
Back to the base
configuration. If you look at the URL for our AmplitudeJS docs at https://521dimensions.com/open-source/amplitudejs/docs, notice how it’s in the /open-source/amplitudejs/docs
directory? This will be set as your base! Your config should look like this for VuePress:
module.exports = {
title: '',
description: 'Official documentation for AmplitudeJS',
base: '/open-source/amplitudejs/docs/',
}
This is where another configuration step lies that I don’t like. When building your docs and testing them, you should REMOVE the base configuration or it won’t work because it won’t match the directory structure of your VuePress application. When I’m testing my docs, I remove the base, let VuePress fire up a development URL at https://localhost:8080
or whatever you have configured, then before I build, I add my base
config. Maybe you could remove this dependent on a node command? That could be another option. Anyway, to sum up, your base
config should match the subdirectory your docs will lie in your route within your Laravel application.
Right now, go ahead and build your docs! Throw some Markdown in your directories, test it out and get it to a point where you want to deploy. Of course if you are at this point and have questions about a specific VuePress config I have set up, reach out below! Next, we will be moving the compiled docs to our Laravel Application.
Building our Docs For Deployment
Once you have your docs built, it’s ready for deployment! To build your docs within Vuepress, make sure your base
is set to match the subdirectory or sub route they will end up in within your Laravel application. For our case this is /open-source/amplitudejs/docs
. Then run npm run docs:build
. Your documentation will be compiled into a single page application that’s rendered server side. Essentially this is a bunch of HTML, CSS, Javascript, and images used by your docs.
When npm run docs:build
has completed, the docs will be compiled and placed in the /docs/.vuepress/dist
directory. Essentially this is what you want to copy to your Laravel application! These are your production ready docs, they contain everything you need.
Now heres, the another step. You will need to place these docs in a public facing directory on your Laravel application. Right now, I’m assuming your docs do not contain sensitive information. IF THEY DO, do NOT place them in a public directory. Reach out, and I can help block off your docs through an authorization policy.
Right now, assume all docs are public. I will be placing my docs in a /public/docs/amplitudejs
directory within my Laravel installation. To make this easier on me for my development environment, within VuePress I wrote a simple node script that was npm run docs:deploy
. What that does is literally copy the compiled docs directory /docs/.vuepress/dist
to the /public/docs/amplitudejs
directory within my Laravel install. You can copy this however you want.
Now that our docs are inside of our Laravel installation, we have to do a few tricks to make them work!
Configuring Laravel to Work with VuePress
This the trick. We want our docs to reside at https://521dimensions.com/open-source/amplitudejs/docs. This is within our Laravel installation and under the route we defined. As I mentioned before, when we built our docs for production, we set our base as /open-source/amplitudejs/docs
. This sets up all of the VuePress configuration and router to assume that the docs are at that directory. This is nice so all links actually stay consistent with the URL even within the single page application! Imagine that! Your Google Analytics will work as well if you configured those.
So in the last step we copied all of the production ready docs into a public directory at /public/docs/amplitudejs
within the Laravel installation.
Our first step is to add the actual routes in Laravel that we want to load the docs with. Just a heads up, I have an AmplitudeController.php
file that handles all routes pertaining to our AmplitudeJS section of the site such as landing page, examples, etc. We are using Laravel 5.6, so we need to open the /routes/web.php
file and add the following routes:
Route::get('/open-source/amplitudejs/docs/', 'AmplitudeController@getAmplitudeDocsAsset');
Route::any('/open-source/amplitudejs/docs/{asset}', 'AmplitudeController@getAmplitudeDocsAsset')->where('asset', '.*');
There is a whole bunch going on within these two routes. The first one, is essentially a placeholder for saying hey, we are going to return docs at this address. The second one is a wild card and is really one of the major keys to making the VuePress docs work within laravel. Remember that base we set up? WELL, here’s where it comes to play. All resources are accessed from VuePress to that base URL. What the second route does is assigns ANYTHING after the /docs
part of the URL to an $asset
variable. It can literally be anything. This catches ALL requests back to load resources which we will be intercepting and returning from our public directory. We are essentially proxying ALL requests and loading the associated file from the public directory and returning it.
Notice the two routes access the same method on the AmplitudeController.php
? That’s not by error. We only need one method to do all the things!
Let’s look at that method:
public function getAmplitudeDocsAsset( $asset = 'index.html' ){
/*
Disable new relic auto run so we don't get extra JS that will
break the VuePress SPA
*/
if (extension_loaded('newrelic')) {
newrelic_disable_autorum();
}
/*
If the extension is javascript, css, svg or does NOT
contain .html redirect to the page they requested and the
index.html file within that directory.
*/
if( pathinfo( $asset, PATHINFO_EXTENSION ) != 'js'
&& pathinfo( $asset, PATHINFO_EXTENSION ) != 'css'
&& pathinfo( $asset, PATHINFO_EXTENSION ) != 'svg'
&& strpos( $asset, '.html' ) == null ){
return redirect( '/open-source/amplitudejs/docs/'.$asset.'/index.html' );
}
/*
Get the contents of the asset. This can be CSS, JS, images or HTML.
*/
$contents = File::get(public_path() . '/docs/amplitudejs/'.$asset );
/*
Build a response with the asset being requested.
*/
$response = Response::make($contents);
/*
If the asset is CSS, ensure the Content-Type text/css header is passed
along with the file.
*/
switch( pathinfo($asset, PATHINFO_EXTENSION) ){
case 'css':
$response->header('Content-Type', 'text/css');
break;
}
/*
Return the response
*/
return $response;
}
There’s a LOT going on in this method, so let’s break it down. Essentially this is the key to making EVERYTHING work.
First, we disable NewRelic (if you are using NewRelic for monitoring or whatever):
/*
Disable new relic auto run so we don't get extra JS that will
break the VuePress SPA
*/
if (extension_loaded('newrelic')) {
newrelic_disable_autorum();
}
We need to disable this because they throw extra javascript into our application. This breaks the VuePress SPA big time! We don’t want this at this point.
Next, we do a check on what URL the user accessed:
/*
If the extension is javascript, css, svg or does NOT
contain .html redirect to the page they requested and the
index.html file within that directory.
*/
if( pathinfo( $asset, PATHINFO_EXTENSION ) != 'js'
&& pathinfo( $asset, PATHINFO_EXTENSION ) != 'css'
&& pathinfo( $asset, PATHINFO_EXTENSION ) != 'svg'
&& strpos( $asset, '.html' ) == null ){
return redirect( '/open-source/amplitudejs/docs/'.$asset.'/index.html' );
}
If the URL is not Javascript, CSS, SVG and does not contain .html, we assume the user wants to visit a piece of the documentation. This is part of the trick. Remember, we are proxying all assets to be loaded through this route, so the page could be requesting Javascript, CSS, or image resources. We don’t want this redirect if they are requesting any of those. Now we have this in here to redirect to the index.html file for the appropriate sub directory. This is so VuePress handles the kicking off of the SPA correctly. Not a huge fan of this part, but it’s the way it needs to work so it doesn’t through a 404 when you try to navigate.
The next part of the method is pretty slick:
/*
Get the contents of the asset. This can be CSS, JS, images or HTML.
*/
$contents = File::get(public_path() . '/docs/amplitudejs/'.$asset );
/*
Build a response with the asset being requested.
*/
$response = Response::make($contents);
/*
If the asset is CSS, ensure the Content-Type text/css header is passed
along with the file.
*/
switch( pathinfo($asset, PATHINFO_EXTENSION) ){
case 'css':
$response->header('Content-Type', 'text/css');
break;
}
/*
Return the response
*/
return $response;
What this does is get the asset being requested from the public path. We have the $asset
variable containing essentially the URL relative to the docs
that VuePress generated. This is the same structure that is in our public
directory so we can grab the file itself using the public_path()
helper in Laravel, appending the source of where we placed the file, and grabbing the file based on the $asset
parameter.
We then make a response using the Response
facade that comes with Laravel and pass it the contents of the file we just loaded.
The only gotcha before we return the response is Laravel won’t add the proper header for Content-Type
if the file is a CSS file. This becomes an issue because the browser will interpret it as text and won’t render the CSS making for an ugly page. So what we do is check to see if the extension on the $asset
variable is css
. If it is, we throw a quick header out there for the Content-Type: text/css
so it will be interpreted correctly.
Finally, we return the $response
with the file. This is all we need to do to get VuePress working within Laravel! Essentially we set a base of the VuePress docs to where we will be referencing it through a Laravel route. We then move the files to a public directory, and write a proxy for any requests from the VuePress SPA to be handled by our proxy for all images, scripts, styles, html, etc.
Conclusion
It is kind of a round about way to get this to work. Once I got it working, I am extremely happy with the results. You can see the live AmplitudeJS docs written in VuePress through our Laravel Application here: Overview – AmplitudeJS Documentation. Let me know your thoughts! Of course if there are questions, concerns, need more information or have a better way to do something, please reach out! There’s comments below! I have a lot of faith in VuePress for the future of docs and making this process more efficient would be awesome!