Merge pull request #193 from sveltejs/gh-15

minify HTML templates
This commit is contained in:
Rich Harris
2018-03-11 14:14:39 -04:00
committed by GitHub
10 changed files with 70 additions and 35 deletions

View File

@@ -2,7 +2,7 @@
"name": "sapper",
"version": "0.9.3",
"description": "Military-grade apps, engineered by Svelte",
"main": "middleware.js",
"main": "dist/middleware.ts.js",
"bin": {
"sapper": "./sapper"
},
@@ -23,6 +23,7 @@
"clorox": "^1.0.3",
"devalue": "^1.0.1",
"glob": "^7.1.2",
"html-minifier": "^3.5.10",
"mkdirp": "^0.5.1",
"node-fetch": "^1.7.3",
"polka": "^0.3.4",

View File

@@ -3,11 +3,12 @@ import * as path from 'path';
import * as clorox from 'clorox';
import mkdirp from 'mkdirp';
import rimraf from 'rimraf';
import { minify_html } from './utils/minify_html';
import { create_compilers, create_main_manifests, create_routes, create_serviceworker_manifest } from '../core'
import { dest } from '../config';
import { locations } from '../config';
export async function build() {
const output = dest();
const output = locations.dest();
mkdirp.sync(output);
rimraf.sync(path.join(output, '**/*'));
@@ -40,6 +41,11 @@ export async function build() {
console.log(clorox.inverse(`\nbuilt service worker`).toString());
console.log(serviceworker_stats.toString({ colors: true }));
}
// minify app/template.html
// TODO compile this to a function? could be quicker than str.replace(...).replace(...).replace(...)
const template = fs.readFileSync(`${locations.app()}/template.html`, 'utf-8');
fs.writeFileSync(`${output}/template.html`, minify_html(template));
}
function compile(compiler: any) {

View File

@@ -9,7 +9,7 @@ import rimraf from 'rimraf';
import format_messages from 'webpack-format-messages';
import prettyMs from 'pretty-ms';
import * as ports from 'port-authority';
import { dest } from '../config';
import { locations } from '../config';
import { create_compilers, create_main_manifests, create_routes, create_serviceworker_manifest } from '../core';
type Deferred = {
@@ -84,7 +84,7 @@ export async function dev(opts: { port: number }) {
port = await ports.find(3000);
}
const dir = dest();
const dir = locations.dest();
rimraf.sync(dir);
mkdirp.sync(dir);
@@ -95,12 +95,12 @@ export async function dev(opts: { port: number }) {
const hot_update_server = create_hot_update_server(dev_port);
watch_files('routes/**/*', ['add', 'unlink'], () => {
watch_files(`${locations.routes()}/**/*`, ['add', 'unlink'], () => {
const routes = create_routes();
create_main_manifests({ routes, dev_port });
});
watch_files('app/template.html', ['change'], () => {
watch_files(`${locations.app()}/template.html`, ['change'], () => {
hot_update_server.send({
action: 'reload'
});

View File

@@ -5,10 +5,10 @@ import cheerio from 'cheerio';
import URL from 'url-parse';
import fetch from 'node-fetch';
import * as ports from 'port-authority';
import { dest } from '../config';
import { locations } from '../config';
export async function exporter(export_dir: string) {
const build_dir = dest();
const build_dir = locations.dest();
// Prep output directory
sander.rimrafSync(export_dir);

View File

@@ -0,0 +1,21 @@
import { minify } from 'html-minifier';
export function minify_html(html: string) {
return minify(html, {
collapseBooleanAttributes: true,
collapseWhitespace: true,
conservativeCollapse: true,
decodeEntities: true,
html5: true,
minifyCSS: true,
minifyJS: true,
removeAttributeQuotes: true,
removeComments: true,
removeOptionalTags: true,
removeRedundantAttributes: true,
removeScriptTypeAttributes: true,
removeStyleLinkTypeAttributes: true,
sortAttributes: true,
sortClassName: true
});
}

View File

@@ -1,5 +1,10 @@
import * as path from 'path';
export const dev = () => process.env.NODE_ENV !== 'production';
export const src = () => path.resolve(process.env.SAPPER_ROUTES || 'routes');
export const dest = () => path.resolve(process.env.SAPPER_DEST || '.sapper');
export const locations = {
base: () => path.resolve(process.env.SAPPER_BASE || ''),
app: () => path.resolve(process.env.SAPPER_BASE || '', process.env.SAPPER_APP || 'app'),
routes: () => path.resolve(process.env.SAPPER_BASE || '', process.env.SAPPER_ROUTES || 'routes'),
dest: () => path.resolve(process.env.SAPPER_BASE || '', process.env.SAPPER_DEST || '.sapper')
};

View File

@@ -3,18 +3,20 @@ import * as path from 'path';
import * as glob from 'glob';
import create_routes from './create_routes';
import { posixify, write_if_changed } from './utils';
import { dev } from '../config';
import { dev, locations } from '../config';
import { Route } from '../interfaces';
export function create_main_manifests({ routes, dev_port }: {
routes: Route[];
dev_port?: number;
}) {
const client_manifest = generate_client(routes, dev_port);
const server_manifest = generate_server(routes);
const path_to_routes = path.relative(`${locations.app()}/manifest`, locations.routes());
write_if_changed(`app/manifest/client.js`, client_manifest);
write_if_changed(`app/manifest/server.js`, server_manifest);
const client_manifest = generate_client(routes, path_to_routes, dev_port);
const server_manifest = generate_server(routes, path_to_routes);
write_if_changed(`${locations.app()}/manifest/client.js`, client_manifest);
write_if_changed(`${locations.app()}/manifest/server.js`, server_manifest);
}
export function create_serviceworker_manifest({ routes, client_files }: {
@@ -34,10 +36,10 @@ export function create_serviceworker_manifest({ routes, client_files }: {
export const routes = [\n\t${routes.filter((r: Route) => r.type === 'page' && !/^_[45]xx$/.test(r.id)).map((r: Route) => `{ pattern: ${r.pattern} }`).join(',\n\t')}\n];
`.replace(/^\t\t/gm, '').trim();
write_if_changed('app/manifest/service-worker.js', code);
write_if_changed(`${locations.app()}/manifest/service-worker.js`, code);
}
function generate_client(routes: Route[], dev_port?: number) {
function generate_client(routes: Route[], path_to_routes: string, dev_port?: number) {
let code = `
// This file is generated by Sapper — do not edit it!
export const routes = [
@@ -47,7 +49,7 @@ function generate_client(routes: Route[], dev_port?: number) {
return `{ pattern: ${route.pattern}, ignore: true }`;
}
const file = posixify(`../../routes/${route.file}`);
const file = posixify(`${path_to_routes}/${route.file}`);
if (route.id === '_4xx' || route.id === '_5xx') {
return `{ error: '${route.id.slice(1)}', load: () => import(/* webpackChunkName: "${route.id}" */ '${file}') }`;
@@ -79,12 +81,12 @@ function generate_client(routes: Route[], dev_port?: number) {
return code;
}
function generate_server(routes: Route[]) {
function generate_server(routes: Route[], path_to_routes: string) {
let code = `
// This file is generated by Sapper — do not edit it!
${routes
.map(route => {
const file = posixify(`../../routes/${route.file}`);
const file = posixify(`${path_to_routes}/${route.file}`);
return route.type === 'page'
? `import ${route.id} from '${file}';`
: `import * as ${route.id} from '${file}';`;

View File

@@ -1,9 +1,9 @@
import * as path from 'path';
import glob from 'glob';
import { src } from '../config';
import { locations } from '../config';
import { Route } from '../interfaces';
export default function create_routes({ files } = { files: glob.sync('**/*.*', { cwd: src(), nodir: true }) }) {
export default function create_routes({ files } = { files: glob.sync('**/*.*', { cwd: locations.routes(), nodir: true }) }) {
const routes: Route[] = files
.map((file: string) => {
if (/(^|\/|\\)_/.test(file)) return;

View File

@@ -6,7 +6,7 @@ import rimraf from 'rimraf';
import devalue from 'devalue';
import { lookup } from './middleware/mime';
import { create_routes, create_compilers } from './core';
import { dest, dev } from './config';
import { locations, dev } from './config';
import { Route, Template } from './interfaces';
import sourceMapSupport from 'source-map-support';
@@ -40,7 +40,7 @@ interface Req extends ClientRequest {
export default function middleware({ routes }: {
routes: RouteObject[]
}) {
const output = dest();
const output = locations.dest();
const client_info = JSON.parse(fs.readFileSync(path.join(output, 'client_info.json'), 'utf-8'));
@@ -80,7 +80,7 @@ function serve({ prefix, pathname, cache_control }: {
? (req: Req) => req.pathname === pathname
: (req: Req) => req.pathname.startsWith(prefix);
const output = dest();
const output = locations.dest();
const cache: Map<string, Buffer> = new Map();
@@ -112,8 +112,8 @@ const resolved = Promise.resolve();
function get_route_handler(chunks: Record<string, string>, routes: RouteObject[]) {
const template = dev()
? () => fs.readFileSync('app/template.html', 'utf-8')
: (str => () => str)(fs.readFileSync('app/template.html', 'utf-8'));
? () => fs.readFileSync(`${locations.app()}/template.html`, 'utf-8')
: (str => () => str)(fs.readFileSync(`${locations.dest()}/template.html`, 'utf-8'));
function handle_route(route: RouteObject, req: Req, res: ServerResponse) {
req.params = route.params(route.pattern.exec(req.pathname));

View File

@@ -1,4 +1,4 @@
import { dest, dev } from './config';
import { locations, dev } from './config';
export default {
dev: dev(),
@@ -6,13 +6,13 @@ export default {
client: {
entry: () => {
return {
main: './app/client'
main: `${locations.app()}/client`
};
},
output: () => {
return {
path: `${dest()}/client`,
path: `${locations.dest()}/client`,
filename: '[hash]/[name].js',
chunkFilename: '[hash]/[name].[id].js',
publicPath: '/client/'
@@ -23,13 +23,13 @@ export default {
server: {
entry: () => {
return {
server: './app/server'
server: `${locations.app()}/server`
};
},
output: () => {
return {
path: dest(),
path: locations.dest(),
filename: '[name].js',
chunkFilename: '[hash]/[name].[id].js',
libraryTarget: 'commonjs2'
@@ -40,13 +40,13 @@ export default {
serviceworker: {
entry: () => {
return {
'service-worker': './app/service-worker'
'service-worker': `${locations.app()}/service-worker`
};
},
output: () => {
return {
path: dest(),
path: locations.dest(),
filename: '[name].js',
chunkFilename: '[name].[id].[hash].js'
}