diff --git a/package.json b/package.json index c9eaaf504..d7d071afe 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/cli/build.ts b/src/cli/build.ts index b9a8f080c..60756f6e4 100644 --- a/src/cli/build.ts +++ b/src/cli/build.ts @@ -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) { diff --git a/src/cli/dev.ts b/src/cli/dev.ts index f936f38a8..dba4b6799 100644 --- a/src/cli/dev.ts +++ b/src/cli/dev.ts @@ -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' }); diff --git a/src/cli/export.ts b/src/cli/export.ts index 6f2efb6a4..91a5c808c 100644 --- a/src/cli/export.ts +++ b/src/cli/export.ts @@ -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); diff --git a/src/cli/utils/minify_html.ts b/src/cli/utils/minify_html.ts new file mode 100644 index 000000000..b675c1056 --- /dev/null +++ b/src/cli/utils/minify_html.ts @@ -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 + }); +} \ No newline at end of file diff --git a/src/config.ts b/src/config.ts index db59d02db..8f98481ad 100644 --- a/src/config.ts +++ b/src/config.ts @@ -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') +}; \ No newline at end of file diff --git a/src/core/create_manifests.ts b/src/core/create_manifests.ts index 1fdc57aa8..36143593f 100644 --- a/src/core/create_manifests.ts +++ b/src/core/create_manifests.ts @@ -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}';`; diff --git a/src/core/create_routes.ts b/src/core/create_routes.ts index 0a247c0dc..133b865b5 100644 --- a/src/core/create_routes.ts +++ b/src/core/create_routes.ts @@ -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; diff --git a/src/middleware.ts b/src/middleware.ts index d9136247b..bc23a3584 100644 --- a/src/middleware.ts +++ b/src/middleware.ts @@ -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 = new Map(); @@ -112,8 +112,8 @@ const resolved = Promise.resolve(); function get_route_handler(chunks: Record, 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)); diff --git a/src/webpack.ts b/src/webpack.ts index 7fd3e863d..55b245c38 100644 --- a/src/webpack.ts +++ b/src/webpack.ts @@ -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' }