npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

vue-ssr-tools

v0.4.1

Published

Tooling for working with Vue server side

Downloads

19

Readme

Vue SSR Tools

Tooling for working with Vue server side

Table of Contents

Preparations

If you do not have a project already, create a new folder and initialize it:

mkdir my-cool-project
npm init

After you're done answerin the npm questions, edit your new package.json file and add a new line containing: type="module".

Dependencies

Some packages or some compatible equivalent needs to be installed:

npm i vue vue-router vue-server-renderer express

Node with ESM Module support needed. Node 8 and up supports this under the flag --experimental-modules and from Node 13.2 ESM Modules should be supported without the flag (as I write this Node 13.1 is the latest release).

Installation

npm i vue-ssr-tools

Usage

1. Basic server side render with router

This is the most basic implementation without client side hydration (Vue only runs server side)

index.js

import { VueRender } from 'vue-ssr-tools';
import vueServerRenderer from 'vue-server-renderer';
import Router from 'vue-router';
import Vue from 'vue';
import express from 'express';

// The router must be attached to the vue before anything else happends
// this is ugly as **** and will most likely be removed in Vue 3
Vue.use(Router);

const server = express();

// Instance of a SSR vue render thingie with routes and all
const vueRender = new VueRender({
	// Router constructor, must be compatible with vue-router
	// but you can hack your own if you like
	Router,

	// The base template all pages will share.
	// {{ title }} and {{{ head }}} will be covered later
	// <!-- vue-ssr-outlet --> will be replaced with
	// your main component (see above)
	template: `<!DOCTYPE html>
		<html lang="en">
			<head>
				<title>{{ title }}</title>
				<meta charset="utf-8" />
				{{{ head }}}
			</head>
			<body>
				<!--vue-ssr-outlet-->
			</body>
		</html>`,

	// Vue constructor
	Vue,

	// Vue server side renderer
	vueServerRenderer,
});

// Instantiate main component and routes for each request,
// since they might need request specific data.
// They are also async since when they get more complex in the
// future might need to resolve data and other async stuff
// before they are ready
// Then run the vue render minddleware
server.get('*', (req, res, cb) => {

	// The main vue component is the vue component that
	// will be mounted in the html template
	res.mainComponent = {
		template: `<div id="vue-main">
			<ul>
				<li><router-link to="/">Home</router-link></li>
				<li><router-link to="/foo">Foo</router-link></li>
			</ul>
			<router-view></router-view>
		</div>`
	};

	// Routes sent to the router, see vue-router documentation for
	// more options and how these actually work
	res.routes = [
		{ path: '/', component: { template: '<p>Home</p>' }},
		{ path: '/foo', component: { template: '<p>Foo</p>' }}
	];

	vueRender.middleware(req, res, cb);
});

server.listen(3000, err => {
	if (err) throw err;
	console.log('HTTP server started on port: "3000"');
});

test it out by running it from the console: node index.js (or for node 8-12: node --experimental-modules index.js)

2. Client side hydration

Make vue work on the client as well

2.1. Break apart the code we want to use both client- and serverside

In this step, we make no change to the actual code, just splitting it up in different files.


The main vue component is the vue component that will be mounted in the html template.

public/vue/components/main.js

export default async function () {
	return {
		template: `<div id="vue-main">
			<ul>
				<li><router-link to="/">Home</router-link></li>
				<li><router-link to="/foo">Foo</router-link></li>
			</ul>
			<router-view></router-view>
		</div>`
	};
}

Routes sent to the router, see vue-router documentation for more options and how these actually work.

public/vue/routes.js

export default async function () {
	return [
		{ path: '/', component: { template: '<p>Home</p>' }},
		{ path: '/foo', component: { template: '<p>Foo</p>' }}
	];
}

The base template all pages will share. {{ title }} and {{{ head }}} will be covered later. <!-- vue-ssr-outlet --> will be replaced with your main component (see above).

index.template.html

<!DOCTYPE html>
<html lang="en">
	<head>
		<title>{{ title }}</title>
		<meta charset="utf-8" />
		{{{ head }}}
	</head>
	<body>
		<!--vue-ssr-outlet-->
	</body>
</html>

Tie it all together in the index file.

index.js

import { VueRender } from 'vue-ssr-tools';
import vueServerRenderer from 'vue-server-renderer';
import Router from 'vue-router';
import Vue from 'vue';
import express from 'express';

// Imported stuff not needed when all was in the same file:
import mainComponentFactory from './public/vue/components/main.js';
import routesFactory from './public/vue/routes.js';
import fs from 'fs';

Vue.use(Router);

const server = express();

// Instance of a SSR vue render thingie with routes and all
const vueRender = new VueRender({
	Router,
	template: fs.readFileSync('./index.template.html', 'utf-8').toString(),
	Vue,
	vueServerRenderer,
});

// Instantiate main component and routes for each request
// and register the vueRender middleware on the express server
server.get('*', (req, res, cb) => {
	res.mainComponent = mainComponentFactory();
	res.routes = routesFactory();
	vueRender.middleware(req, res, cb);
});

server.listen(3000, err => {
	if (err) throw err;
	console.log('HTTP server started on port: "3000"');
});

2.2. Create new files needed client side

First we need to modify our index to serve our node_modules and public folders to the browser.

index.js

import { VueRender } from 'vue-ssr-tools';
import vueServerRenderer from 'vue-server-renderer';
import Router from 'vue-router';
import Vue from 'vue';
import express from 'express';

// Imported stuff not needed when all was in the same file:
import MainComponent from './public/vue/components/main.js';
import routes from './public/vue/routes.js';
import fs from 'fs';

Vue.use(Router);

const server = express();

// Serve the public folder as static files directly
server.use(express.static('public'));

// Server node modules publicly
server.use('/node_modules', express.static('node_modules'));

// Instance of a SSR vue render thingie with routes and all
const vueRender = new VueRender({
	Router,
	template: fs.readFileSync('./index.template.html', 'utf-8'),
	Vue,
	vueServerRenderer,
});

// Instantiate main component and routes for each request
// and register the vueRender middleware on the express server
server.get('*', (req, res, cb) => {
	res.mainComponent = mainComponentFactory();
	res.routes = routesFactory();
	vueRender.middleware(req, res, cb);
});

server.listen(3000, err => {
	if (err) throw err;
	console.log('HTTP server started on port: "3000"');
});

This file is only ran client side to create the vue app in the browser.

public/vue/entry.js

import { createApp } from '/node_modules/vue-ssr-tools/dist/index.js';
import Vue from '/node_modules/vue/dist/vue.esm.browser.min.js';
import Router from '/node_modules/vue-router/dist/vue-router.esm.browser.min.js';
import mainComponentFactory from './components/main.js';
import routesFactory from './routes.js';

Vue.use(Router);

(async () => {
	const { app } = await createApp({
		mainComponent: mainComponentFactory(),
		Router,
		routes: routesFactory(),
		url: window.location.pathname,
		Vue,
	});
	app.$mount('#vue-main')
})();

We also need to modify our base template to include our client entry file.

index.template.html

<!DOCTYPE html>
<html lang="en">
	<head>
		<title>{{ title }}</title>
		<meta charset="utf-8" />
		<script defer src="/vue/entry.js" type="module"></script>
		{{{ head }}}
	</head>
	<body>
		<!--vue-ssr-outlet-->
	</body>
</html>

3. Write component templates in separate HTML file

In this section we use a handy tool to write the vue component template part in a HTML file for better editor support and maintainability.

First we load a little tool that fetches html-files for us, we do this in index.js and public/vue/entry.js

index.js

import { VueRender, GetVueTmpl } from 'vue-ssr-tools';
import vueServerRenderer from 'vue-server-renderer';
import Router from 'vue-router';
import Vue from 'vue';
import express from 'express';

// Imported stuff not needed when all was in the same file:
import mainComponentFactory from './public/vue/components/main.js';
import routesFactory from './public/vue/routes.js';
import fs from 'fs';

Vue.use(Router);

const server = express();
const getVueTmpl = new GetVueTmpl({
	publicHost: 'http://localhost:3000',
	templatesBasePath: '/vue/templates/'
});

// Serve the public folder as static files directly
server.use(express.static('public'));

// Server node modules publicly
server.use('/node_modules', express.static('node_modules'));

// Instance of a SSR vue render thingie with routes and all
const vueRender = new VueRender({
	Router,
	template: fs.readFileSync('./index.template.html', 'utf-8').toString(),
	Vue,
	vueServerRenderer,
});

// Instantiate main component and routes for each request
// and register the vueRender middleware on the express server
server.get('*', (req, res, cb) => {
	res.mainComponent = mainComponentFactory({ getVueTmpl });
	res.routes = routesFactory();
	vueRender.middleware(req, res, cb);
});

server.listen(3000, err => {
	if (err) throw err;
	console.log('HTTP server started on port: "3000"');
});

public/vue/components/main.js

export default async function (options) {
	const { getVueTmpl } = options;

	return {
		template: await getVueTmpl.getString('main')
	};
}

public/vue/templates/main.html

<div id="vue-main">
	<ul>
		<li><router-link to="/">Home</router-link></li>
		<li><router-link to="/foo">Foo</router-link></li>
	</ul>
	<router-view></router-view>
</div>

public/vue/entry.js

import { createApp, GetVueTmpl } from '/node_modules/vue-ssr-tools/dist/index.js';
import Vue from '/node_modules/vue/dist/vue.esm.browser.min.js';
import Router from '/node_modules/vue-router/dist/vue-router.esm.browser.min.js';
import mainComponentFactory from './components/main.js';
import routesFactory from './routes.js';

Vue.use(Router);

const getVueTmpl = new GetVueTmpl({
	publicHost: 'http://localhost:3000',
	templatesBasePath: '/vue/templates/'
});

(async () => {
	const { app } = await createApp({
		mainComponent: mainComponentFactory({ getVueTmpl }),
		Router,
		routes: routesFactory(),
		url: window.location.pathname,
		Vue,
	});
	app.$mount('#vue-main')
})();