Merge pull request #101 from sveltejs/restructure

Restructure codebase
This commit is contained in:
Rich Harris
2018-02-03 11:44:57 -05:00
committed by GitHub
28 changed files with 776 additions and 602 deletions

8
.gitignore vendored
View File

@@ -4,3 +4,11 @@ cypress/screenshots
test/app/.sapper
runtime.js
yarn.lock
cli.js
cli.js.map
middleware.js
middleware.js.map
core.js
core.js.map
webpack/config.js
webpack/config.js.map

View File

@@ -1,47 +0,0 @@
process.env.NODE_ENV = 'production';
const fs = require('fs');
const path = require('path');
const mkdirp = require('mkdirp');
const rimraf = require('rimraf');
const { client, server } = require('./utils/compilers.js');
const create_app = require('./utils/create_app.js');
const generate_asset_cache = require('./utils/generate_asset_cache.js');
const { dest } = require('./config.js');
module.exports = () => {
mkdirp.sync(dest);
rimraf.sync(path.join(dest, '**/*'));
// create main.js and server-routes.js
create_app();
return new Promise((fulfil, reject) => {
function handleErrors(err, stats) {
if (err) {
reject(err);
process.exit(1);
}
if (stats.hasErrors()) {
console.error(stats.toString({ colors: true }));
reject(new Error(`Encountered errors while building app`));
}
}
client.run((err, clientStats) => {
handleErrors(err, clientStats);
const clientInfo = clientStats.toJson();
fs.writeFileSync(path.join(dest, 'stats.client.json'), JSON.stringify(clientInfo, null, ' '));
server.run((err, serverStats) => {
handleErrors(err, serverStats);
const serverInfo = serverStats.toJson();
fs.writeFileSync(path.join(dest, 'stats.server.json'), JSON.stringify(serverInfo, null, ' '));
generate_asset_cache(clientInfo, serverInfo);
fulfil();
});
});
});
};

View File

@@ -1,21 +0,0 @@
const path = require('path');
const mkdirp = require('mkdirp');
const rimraf = require('rimraf');
exports.dev = process.env.NODE_ENV !== 'production';
exports.templates = path.resolve(process.env.SAPPER_TEMPLATES || 'templates');
exports.src = path.resolve(process.env.SAPPER_ROUTES || 'routes');
exports.dest = path.resolve(process.env.SAPPER_DEST || '.sapper');
if (exports.dev) {
mkdirp.sync(exports.dest);
rimraf.sync(path.join(exports.dest, '**/*'));
}
exports.entry = {
client: path.resolve(exports.templates, '.main.rendered.js'),
server: path.resolve(exports.dest, 'server-entry.js')
};

View File

@@ -1,30 +0,0 @@
const glob = require('glob');
const create_routes = require('./utils/create_routes.js');
const { src, dev } = require('./config.js');
const callbacks = [];
exports.onchange = fn => {
callbacks.push(fn);
};
function update() {
exports.routes = create_routes(
glob.sync('**/*.+(html|js|mjs)', { cwd: src })
);
callbacks.forEach(fn => fn());
}
update();
if (dev) {
const watcher = require('chokidar').watch(`${src}/**/*.+(html|js|mjs)`, {
ignoreInitial: true,
persistent: false
});
watcher.on('add', update);
watcher.on('change', update);
watcher.on('unlink', update);
}

View File

@@ -1,11 +0,0 @@
const path = require('path');
const relative = require('require-relative');
const webpack = relative('webpack', process.cwd());
exports.client = webpack(
require(path.resolve('webpack.client.config.js'))
);
exports.server = webpack(
require(path.resolve('webpack.server.config.js'))
);

View File

@@ -1,82 +0,0 @@
const fs = require('fs');
const path = require('path');
const route_manager = require('../route_manager.js');
const { src, entry, dev } = require('../config.js');
function posixify(file) {
return file.replace(/[/\\]/g, '/');
}
function create_app() {
const { routes } = route_manager;
function create_client_main() {
const template = fs.readFileSync('templates/main.js', 'utf-8');
const code = `[${
routes
.filter(route => route.type === 'page')
.map(route => {
const params = route.dynamic.length === 0 ?
'{}' :
`{ ${route.dynamic.map((part, i) => `${part}: match[${i + 1}]`).join(', ') } }`;
const file = posixify(`${src}/${route.file}`);
return `{ pattern: ${route.pattern}, params: match => (${params}), load: () => import(/* webpackChunkName: "${route.id}" */ '${file}') }`
})
.join(', ')
}]`;
let main = template
.replace(/__app__/g, posixify(path.resolve(__dirname, '../../runtime/app.js')))
.replace(/__routes__/g, code)
.replace(/__dev__/g, String(dev));
if (dev) {
const hmr_client = posixify(require.resolve(`webpack-hot-middleware/client`));
main += `\n\nimport('${hmr_client}?path=/__webpack_hmr&timeout=20000'); if (module.hot) module.hot.accept();`
}
fs.writeFileSync(entry.client, main);
// need to fudge the mtime, because webpack is soft in the head
const { atime, mtime } = fs.statSync(entry.client);
fs.utimesSync(entry.client, new Date(atime.getTime() - 999999), new Date(mtime.getTime() - 999999));
}
function create_server_routes() {
const imports = routes
.map(route => {
const file = posixify(`${src}/${route.file}`);
return route.type === 'page' ?
`import ${route.id} from '${file}';` :
`import * as ${route.id} from '${file}';`;
})
.join('\n');
const exports = `export { ${routes.map(route => route.id)} };`;
fs.writeFileSync(entry.server, `${imports}\n\n${exports}`);
const { atime, mtime } = fs.statSync(entry.server);
fs.utimesSync(entry.server, new Date(atime.getTime() - 999999), new Date(mtime.getTime() - 999999));
}
create_client_main();
create_server_routes();
}
if (dev) {
route_manager.onchange(create_app);
const watcher = require('chokidar').watch(`templates/main.js`, {
ignoreInitial: true,
persistent: false
});
watcher.on('add', create_app);
watcher.on('change', create_app);
watcher.on('unlink', create_app);
}
module.exports = create_app;

View File

@@ -1,3 +1,4 @@
--require source-map-support/register
--recursive
test/unit/**/*.js
test/common/test.js

577
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -2,13 +2,13 @@
"name": "sapper",
"version": "0.5.1",
"description": "Military-grade apps, engineered by Svelte",
"main": "lib/index.js",
"main": "middleware.js",
"bin": {
"sapper": "cli/index.js"
},
"files": [
"cli",
"lib",
"cli.js",
"middleware.js",
"runtime",
"runtime.js",
"webpack"
@@ -22,8 +22,11 @@
"chokidar": "^1.7.0",
"code-frame": "^5.0.0",
"escape-html": "^1.0.3",
"express": "^4.16.2",
"glob": "^7.1.2",
"locate-character": "^2.0.5",
"mkdirp": "^0.5.1",
"node-fetch": "^1.7.3",
"relative": "^3.0.2",
"require-relative": "^0.8.7",
"rimraf": "^2.6.2",
@@ -35,20 +38,24 @@
"webpack-hot-middleware": "^2.21.0"
},
"devDependencies": {
"@std/esm": "^0.19.7",
"@types/glob": "^5.0.34",
"@types/mkdirp": "^0.5.2",
"@types/rimraf": "^2.0.2",
"css-loader": "^0.28.7",
"eslint": "^4.13.1",
"eslint-plugin-import": "^2.8.0",
"express": "^4.16.2",
"get-port": "^3.2.0",
"mocha": "^4.0.1",
"nightmare": "^2.10.0",
"node-fetch": "^1.7.3",
"npm-run-all": "^4.1.2",
"rollup": "^0.53.0",
"rollup-plugin-typescript": "^0.8.1",
"source-map-support": "^0.5.2",
"style-loader": "^0.19.1",
"svelte": "^1.49.1",
"svelte-loader": "^2.3.2",
"ts-node": "^4.1.0",
"tslib": "^1.8.1",
"typescript": "^2.6.2",
"wait-on": "^2.0.2"

View File

@@ -1,13 +1,97 @@
import typescript from 'rollup-plugin-typescript';
import pkg from './package.json';
const external = [].concat(
Object.keys(pkg.dependencies),
Object.keys(process.binding('natives')),
'sapper/core.js'
);
const paths = {
'sapper/core.js': './core.js'
};
export default [
// cli.js
{
input: 'src/cli/index.ts',
output: {
file: 'cli.js',
format: 'cjs',
banner: '#!/usr/bin/env node',
paths,
sourcemap: true
},
external,
plugins: [
typescript({
typescript: require('typescript')
})
]
},
// core.js
{
input: 'src/core/index.ts',
output: {
file: 'core.js',
format: 'cjs',
banner: '#!/usr/bin/env node',
paths,
sourcemap: true
},
external,
plugins: [
typescript({
typescript: require('typescript')
})
]
},
// middleware.js
{
input: 'src/middleware/index.ts',
output: {
file: 'middleware.js',
format: 'cjs',
paths,
sourcemap: true
},
external,
plugins: [
typescript({
typescript: require('typescript')
})
]
},
// runtime.js
{
input: 'src/runtime/index.ts',
output: {
file: 'runtime.js',
format: 'es'
format: 'es',
paths,
sourcemap: true
},
external,
plugins: [
typescript({
typescript: require('typescript')
})
]
},
// webpack/config.js
{
input: 'src/webpack/index.ts',
output: {
file: 'webpack/config.js',
format: 'cjs',
paths,
sourcemap: true
},
external,
plugins: [
typescript({
typescript: require('typescript')

1
runtime.js.map Normal file

File diff suppressed because one or more lines are too long

1
runtime/README.md Normal file
View File

@@ -0,0 +1 @@
This directory exists for legacy reasons and should be deleted before releasing version 1.

View File

@@ -1,12 +1,11 @@
#!/usr/bin/env node
const build = require('../lib/build.js');
import { build, export as exporter } from 'sapper/core.js';
import { dest, dev, entry, src } from '../config';
const cmd = process.argv[2];
const start = Date.now();
if (cmd === 'build') {
build()
build({ dest, dev, entry, src })
.then(() => {
const elapsed = Date.now() - start;
console.error(`built in ${elapsed}ms`); // TODO beautify this, e.g. 'built in 4.7 seconds'
@@ -17,8 +16,8 @@ if (cmd === 'build') {
} else if (cmd === 'export') {
const start = Date.now();
build()
.then(() => require('../lib/utils/export.js')())
build({ dest, dev, entry, src })
.then(() => exporter({ src, dest }))
.then(() => {
const elapsed = Date.now() - start;
console.error(`extracted in ${elapsed}ms`); // TODO beautify this, e.g. 'built in 4.7 seconds'

12
src/config.ts Normal file
View File

@@ -0,0 +1,12 @@
import * as path from 'path';
export const dev = process.env.NODE_ENV !== 'production';
export const templates = path.resolve(process.env.SAPPER_TEMPLATES || 'templates');
export const src = path.resolve(process.env.SAPPER_ROUTES || 'routes');
export const dest = path.resolve(process.env.SAPPER_DEST || '.sapper');
export const entry = {
client: path.resolve(templates, '.main.rendered.js'),
server: path.resolve(dest, 'server-entry.js')
};

62
src/core/build.ts Normal file
View File

@@ -0,0 +1,62 @@
import * as fs from 'fs';
import * as path from 'path';
import mkdirp from 'mkdirp';
import rimraf from 'rimraf';
import create_compilers from './create_compilers.js';
import create_app from './create_app.js';
import create_assets from './create_assets.js';
export default function build({
src,
dest,
dev,
entry
}: {
src: string;
dest: string;
dev: boolean;
entry: { client: string, server: string }
}) {
mkdirp.sync(dest);
rimraf.sync(path.join(dest, '**/*'));
// create main.js and server-routes.js
create_app({ dev, entry, src });
return new Promise((fulfil, reject) => {
function handleErrors(err, stats) {
if (err) {
reject(err);
process.exit(1);
}
if (stats.hasErrors()) {
console.error(stats.toString({ colors: true }));
reject(new Error(`Encountered errors while building app`));
}
}
const { client, server } = create_compilers();
client.run((err, client_stats) => {
handleErrors(err, client_stats);
const client_info = client_stats.toJson();
fs.writeFileSync(
path.join(dest, 'stats.client.json'),
JSON.stringify(client_info, null, ' ')
);
server.run((err, server_stats) => {
handleErrors(err, server_stats);
const server_info = server_stats.toJson();
fs.writeFileSync(
path.join(dest, 'stats.server.json'),
JSON.stringify(server_info, null, ' ')
);
create_assets({ src, dest, dev, client_info, server_info });
fulfil();
});
});
});
}

90
src/core/create_app.ts Normal file
View File

@@ -0,0 +1,90 @@
import * as fs from 'fs';
import * as path from 'path';
import create_routes from './create_routes';
function posixify(file: string) {
return file.replace(/[/\\]/g, '/');
}
function fudge_mtime(file: string) {
// need to fudge the mtime so that webpack doesn't go doolally
const { atime, mtime } = fs.statSync(file);
fs.utimesSync(
file,
new Date(atime.getTime() - 999999),
new Date(mtime.getTime() - 999999)
);
}
function create_app({
src,
dev,
entry
}: {
src: string;
dev: boolean;
entry: { client: string; server: string };
}) {
const routes = create_routes({ src });
function create_client_main() {
const code = `[${routes
.filter(route => route.type === 'page')
.map(route => {
const params =
route.dynamic.length === 0
? '{}'
: `{ ${route.dynamic
.map((part, i) => `${part}: match[${i + 1}]`)
.join(', ')} }`;
const file = posixify(`${src}/${route.file}`);
return `{ pattern: ${
route.pattern
}, params: match => (${params}), load: () => import(/* webpackChunkName: "${
route.id
}" */ '${file}') }`;
})
.join(', ')}]`;
let main = fs
.readFileSync('templates/main.js', 'utf-8')
.replace(
/__app__/g,
posixify(path.resolve(__dirname, '../../runtime/app.js'))
)
.replace(/__routes__/g, code)
.replace(/__dev__/g, String(dev));
if (dev) {
const hmr_client = posixify(
require.resolve(`webpack-hot-middleware/client`)
);
main += `\n\nimport('${hmr_client}?path=/__webpack_hmr&timeout=20000'); if (module.hot) module.hot.accept();`;
}
fs.writeFileSync(entry.client, main);
fudge_mtime(entry.client);
}
function create_server_routes() {
const imports = routes
.map(route => {
const file = posixify(`${src}/${route.file}`);
return route.type === 'page'
? `import ${route.id} from '${file}';`
: `import * as ${route.id} from '${file}';`;
})
.join('\n');
const exports = `export { ${routes.map(route => route.id)} };`;
fs.writeFileSync(entry.server, `${imports}\n\n${exports}`);
fudge_mtime(entry.server);
}
create_client_main();
create_server_routes();
}
export default create_app;

View File

@@ -1,23 +1,26 @@
const fs = require('fs');
const path = require('path');
const glob = require('glob');
const templates = require('../templates.js');
const route_manager = require('../route_manager.js');
const { dest, dev } = require('../config.js');
import * as fs from 'fs';
import * as path from 'path';
import glob from 'glob';
import { create_templates, render } from './templates';
import create_routes from './create_routes';
function ensure_array(thing) {
return Array.isArray(thing) ? thing : [thing]; // omg webpack what the HELL are you doing
}
module.exports = function generate_asset_cache(clientInfo, serverInfo) {
const main_file = `/client/${ensure_array(clientInfo.assetsByChunkName.main)[0]}`;
export default function create_assets({ src, dest, dev, client_info, server_info }) {
create_templates(); // TODO refactor this...
const chunk_files = clientInfo.assets.map(chunk => `/client/${chunk.name}`);
const main_file = `/client/${ensure_array(client_info.assetsByChunkName.main)[0]}`;
const service_worker = generate_service_worker(chunk_files);
const chunk_files = client_info.assets.map(chunk => `/client/${chunk.name}`);
const service_worker = generate_service_worker({ chunk_files, src });
const index = generate_index(main_file);
if (dev) {
const routes = create_routes({ src });
if (dev) { // TODO move this into calling code
fs.writeFileSync(path.join(dest, 'service-worker.js'), service_worker);
fs.writeFileSync(path.join(dest, 'index.html'), index);
}
@@ -33,8 +36,9 @@ module.exports = function generate_asset_cache(clientInfo, serverInfo) {
return lookup;
}, {}),
routes: route_manager.routes.reduce((lookup, route) => {
lookup[route.id] = `/client/${ensure_array(clientInfo.assetsByChunkName[route.id])[0]}`;
// TODO confusing that `routes` refers to an array *and* a lookup
routes: routes.reduce((lookup, route) => {
lookup[route.id] = `/client/${ensure_array(client_info.assetsByChunkName[route.id])[0]}`;
return lookup;
}, {}),
@@ -43,18 +47,20 @@ module.exports = function generate_asset_cache(clientInfo, serverInfo) {
},
server: {
entry: path.resolve(dest, 'server', serverInfo.assetsByChunkName.main)
entry: path.resolve(dest, 'server', server_info.assetsByChunkName.main)
},
service_worker
};
};
}
function generate_service_worker(chunk_files) {
function generate_service_worker({ chunk_files, src }) {
const assets = glob.sync('**', { cwd: 'assets', nodir: true });
const routes = create_routes({ src });
const route_code = `[${
route_manager.routes
routes
.filter(route => route.type === 'page')
.map(route => `{ pattern: ${route.pattern} }`)
.join(', ')
@@ -68,7 +74,7 @@ function generate_service_worker(chunk_files) {
}
function generate_index(main_file) {
return templates.render(200, {
return render(200, {
styles: '',
head: '',
html: '<noscript>Please enable JavaScript!</noscript>',

View File

@@ -0,0 +1,16 @@
import * as path from 'path';
import relative from 'require-relative';
export default function create_compilers() {
const webpack = relative('webpack', process.cwd());
return {
client: webpack(
require(path.resolve('webpack.client.config.js'))
),
server: webpack(
require(path.resolve('webpack.server.config.js'))
)
};
}

View File

@@ -1,6 +1,7 @@
const path = require('path');
import * as path from 'path';
import glob from 'glob';
module.exports = function create_matchers(files) {
export default function create_routes({ src, files = glob.sync('**/*.+(html|js|mjs)', { cwd: src }) }) {
const routes = files
.map(file => {
if (/(^|\/|\\)_/.test(file)) return;
@@ -87,4 +88,4 @@ module.exports = function create_matchers(files) {
});
return routes;
};
}

View File

@@ -1,29 +1,32 @@
const path = require('path');
const sander = require('sander');
const app = require('express')();
const cheerio = require('cheerio');
const fetch = require('node-fetch');
const URL = require('url-parse');
const generate_asset_cache = require('./generate_asset_cache.js');
const sapper = require('../index.js');
import * as path from 'path';
import * as sander from 'sander';
import express from 'express';
import cheerio from 'cheerio';
import fetch from 'node-fetch';
import URL from 'url-parse';
import create_assets from './create_assets.js';
// import middleware from '../middleware/index.js';
const { PORT = 3000, OUTPUT_DIR = 'dist' } = process.env;
const { dest } = require('../config.js');
const origin = `http://localhost:${PORT}`;
const app = express();
function read_json(file) {
return JSON.parse(sander.readFileSync(file, { encoding: 'utf-8' }));
}
module.exports = function() {
export default function exporter({ src, dest }) { // TODO dest is a terrible name in this context
// Prep output directory
sander.rimrafSync(OUTPUT_DIR);
const { service_worker } = generate_asset_cache(
read_json(path.join(dest, 'stats.client.json')),
read_json(path.join(dest, 'stats.server.json'))
);
const { service_worker } = create_assets({
src, dest,
dev: false,
client_info: read_json(path.join(dest, 'stats.client.json')),
server_info: read_json(path.join(dest, 'stats.server.json'))
});
sander.copydirSync('assets').to(OUTPUT_DIR);
sander.copydirSync(dest, 'client').to(OUTPUT_DIR, 'client');
@@ -60,7 +63,7 @@ module.exports = function() {
return fetch(url, opts);
};
app.use(sapper());
app.use(require('./middleware')()); // TODO this is filthy
const server = app.listen(PORT);
const seen = new Set();
@@ -95,4 +98,4 @@ module.exports = function() {
return handle(new URL(origin)) // TODO all static routes
.then(() => server.close());
};
}

11
src/core/index.ts Normal file
View File

@@ -0,0 +1,11 @@
import { create_templates, render, stream } from './templates'; // TODO templates is an anomaly... fix post-#91
export { default as build } from './build';
export { default as export } from './export.js';
export { default as create_app } from './create_app';
export { default as create_assets } from './create_assets';
export { default as create_compilers } from './create_compilers';
export { default as create_routes } from './create_routes';
export const templates = { create_templates, render, stream };

View File

@@ -1,9 +1,8 @@
const fs = require('fs');
const glob = require('glob');
const chalk = require('chalk');
const framer = require('code-frame');
const { locate } = require('locate-character');
const { dev } = require('./config.js');
import * as fs from 'fs';
import glob from 'glob';
import chalk from 'chalk';
import framer from 'code-frame';
import { locate } from 'locate-character';
let templates;
@@ -16,7 +15,7 @@ function error(e) {
process.exit(1);
}
function create_templates() {
export function create_templates() {
templates = glob.sync('*.html', { cwd: 'templates' })
.map(file => {
const template = fs.readFileSync(`templates/${file}`, 'utf-8');
@@ -97,31 +96,20 @@ function create_templates() {
};
})
.sort((a, b) => b.specificity - a.specificity);
return templates;
}
create_templates();
if (dev) {
const watcher = require('chokidar').watch('templates/**.html', {
ignoreInitial: true,
persistent: false
});
watcher.on('add', create_templates);
watcher.on('change', create_templates);
watcher.on('unlink', create_templates);
}
exports.render = (status, data) => {
export function render(status, data) {
const template = templates.find(template => template.test(status));
if (template) return template.render(data);
return `Missing template for status code ${status}`;
};
}
exports.stream = (res, status, data) => {
export function stream(res, status, data) {
const template = templates.find(template => template.test(status));
if (template) return template.stream(res, data);
return `Missing template for status code ${status}`;
};
}

View File

@@ -1,9 +1,8 @@
const fs = require('fs');
const path = require('path');
const chalk = require('chalk');
const compilers = require('./compilers.js');
const generate_asset_cache = require('./generate_asset_cache.js');
const { dest } = require('../config.js');
import * as fs from 'fs';
import * as path from 'path';
import chalk from 'chalk';
import { create_app, create_assets, create_routes, templates } from 'sapper/core.js';
import { dest } from '../config.js';
function deferred() {
const d = {};
@@ -16,7 +15,7 @@ function deferred() {
return d;
}
module.exports = function create_watcher() {
export default function create_watcher({ compilers, dev, entry, src, onroutes }) {
const deferreds = {
client: deferred(),
server: deferred()
@@ -32,10 +31,11 @@ module.exports = function create_watcher() {
const server_info = server_stats.toJson();
fs.writeFileSync(path.join(dest, 'stats.server.json'), JSON.stringify(server_info, null, ' '));
return generate_asset_cache(
client_stats.toJson(),
server_stats.toJson()
);
return create_assets({
src, dest, dev,
client_info: client_stats.toJson(),
server_info: server_stats.toJson()
});
});
function watch_compiler(type) {
@@ -60,6 +60,34 @@ module.exports = function create_watcher() {
});
}
const chokidar = require('chokidar');
function watch_files(pattern, callback) {
const watcher = chokidar.watch(pattern, {
persistent: false
});
watcher.on('add', callback);
watcher.on('change', callback);
watcher.on('unlink', callback);
}
watch_files('routes/**/*.+(html|js|mjs)', () => {
const routes = create_routes({ src });
onroutes(routes);
create_app({ dev, entry, src }); // TODO this calls `create_routes` again, we should pass `routes` to `create_app` instead
});
watch_files('templates/main.js', () => {
create_app({ dev, entry, src });
});
watch_files('templates/**.html', () => {
templates.create_templates();
// TODO reload current page?
});
const watcher = {
ready: invalidate(),
client: watch_compiler('client'),
@@ -72,4 +100,4 @@ module.exports = function create_watcher() {
};
return watcher;
};
}

View File

@@ -1,19 +1,28 @@
const fs = require('fs');
const path = require('path');
const serialize = require('serialize-javascript');
const route_manager = require('./route_manager.js');
const templates = require('./templates.js');
const create_app = require('./utils/create_app.js');
const create_watcher = require('./utils/create_watcher.js');
const compilers = require('./utils/compilers.js');
const generate_asset_cache = require('./utils/generate_asset_cache.js');
const escape_html = require('escape-html');
const { dest, dev } = require('./config.js');
import * as fs from 'fs';
import * as path from 'path';
import mkdirp from 'mkdirp';
import rimraf from 'rimraf';
import serialize from 'serialize-javascript';
import escape_html from 'escape-html';
import { create_routes, templates, create_compilers, create_assets } from 'sapper/core.js';
import create_watcher from './create_watcher';
import { dest, dev, entry, src } from '../config';
function connect_dev() {
create_app();
mkdirp.sync(dest);
rimraf.sync(path.join(dest, '**/*'));
const watcher = create_watcher();
const compilers = create_compilers();
let routes;
const watcher = create_watcher({
dev, entry, src,
compilers,
onroutes: _ => {
routes = _;
}
});
let asset_cache;
@@ -54,7 +63,7 @@ function connect_dev() {
fn: pathname => asset_cache.client.chunks[pathname]
}),
get_route_handler(() => asset_cache),
get_route_handler(() => asset_cache, () => routes),
get_not_found_handler(() => asset_cache)
]);
@@ -68,10 +77,14 @@ function connect_dev() {
}
function connect_prod() {
const asset_cache = generate_asset_cache(
read_json(path.join(dest, 'stats.client.json')),
read_json(path.join(dest, 'stats.server.json'))
);
const asset_cache = create_assets({
src, dest,
dev: false,
client_info: read_json(path.join(dest, 'stats.client.json')),
server_info: read_json(path.join(dest, 'stats.server.json'))
});
const routes = create_routes({ src }); // TODO rename update
const middleware = compose_handlers([
set_req_pathname,
@@ -97,7 +110,7 @@ function connect_prod() {
fn: pathname => asset_cache.client.chunks[pathname]
}),
get_route_handler(() => asset_cache),
get_route_handler(() => asset_cache, () => routes),
get_not_found_handler(() => asset_cache)
]);
@@ -109,7 +122,7 @@ function connect_prod() {
return middleware;
}
module.exports = dev ? connect_dev : connect_prod;
export default dev ? connect_dev : connect_prod;
function set_req_pathname(req, res, next) {
req.pathname = req.url.replace(/\?.+/, '');
@@ -129,7 +142,7 @@ function get_asset_handler(opts) {
const resolved = Promise.resolve();
function get_route_handler(fn) {
function get_route_handler(get_assets, get_routes) {
function handle_route(route, req, res, next, { client, server }) {
req.params = route.exec(req.pathname);
@@ -201,8 +214,9 @@ function get_route_handler(fn) {
resolved
.then(() => {
for (const route of route_manager.routes) {
if (route.test(url)) return handle_route(route, req, res, next, fn());
const routes = get_routes();
for (const route of routes) {
if (route.test(url)) return handle_route(route, req, res, next, get_assets());
}
// no matching route — 404

View File

@@ -1,6 +1,6 @@
const { dest, dev, entry } = require('../lib/config.js');
import { dest, dev, entry } from '../config';
module.exports = {
export default {
dev,
client: {

View File

@@ -1,11 +1,10 @@
const path = require('path');
const assert = require('assert');
const Nightmare = require('nightmare');
const express = require('express');
const serve = require('serve-static');
const Nightmare = require('nightmare');
const getPort = require('get-port');
const fetch = require('node-fetch');
const walkSync = require('walk-sync');
const fetch = require('node-fetch');
run('production');
run('development');
@@ -82,16 +81,18 @@ function run(env) {
let sapper;
if (env === 'production') {
const cli = path.resolve(__dirname, '../../cli/index.js');
const cli = path.resolve(__dirname, '../../cli.js');
exec_promise = exec(`node ${cli} export`);
}
return exec_promise.then(() => {
const resolved = require.resolve('../..');
const resolved = require.resolve('../../middleware.js');
delete require.cache[resolved];
delete require.cache[require.resolve('../../core.js')]; // TODO remove this
sapper = require(resolved);
return getPort();
return require('get-port')();
}).then(port => {
PORT = port;
base = `http://localhost:${PORT}`;

View File

@@ -1,11 +1,11 @@
const path = require('path');
const assert = require('assert');
const create_routes = require('../../lib/utils/create_routes.js');
const { create_routes } = require('../../core.js');
describe('create_routes', () => {
it('sorts routes correctly', () => {
const routes = create_routes(['index.html', 'about.html', '[wildcard].html', 'post/foo.html', 'post/[id].html', 'post/bar.html']);
const routes = create_routes({
files: ['index.html', 'about.html', '[wildcard].html', 'post/foo.html', 'post/[id].html', 'post/bar.html']
});
assert.deepEqual(
routes.map(r => r.file),
@@ -21,7 +21,9 @@ describe('create_routes', () => {
});
it('generates params', () => {
const routes = create_routes(['index.html', 'about.html', '[wildcard].html', 'post/[id].html']);
const routes = create_routes({
files: ['index.html', 'about.html', '[wildcard].html', 'post/[id].html']
});
let file;
let params;
@@ -40,7 +42,9 @@ describe('create_routes', () => {
});
it('ignores files and directories with leading underscores', () => {
const routes = create_routes(['index.html', '_foo.html', 'a/_b/c/d.html', 'e/f/g/h.html', 'i/_j.html']);
const routes = create_routes({
files: ['index.html', '_foo.html', 'a/_b/c/d.html', 'e/f/g/h.html', 'i/_j.html']
});
assert.deepEqual(
routes.map(r => r.file),
@@ -52,8 +56,12 @@ describe('create_routes', () => {
});
it('matches /foo/:bar before /:baz/qux', () => {
const a = create_routes(['foo/[bar].html', '[baz]/qux.html']);
const b = create_routes(['[baz]/qux.html', 'foo/[bar].html']);
const a = create_routes({
files: ['foo/[bar].html', '[baz]/qux.html']
});
const b = create_routes({
files: ['[baz]/qux.html', 'foo/[bar].html']
});
assert.deepEqual(
a.map(r => r.file),
@@ -68,16 +76,22 @@ describe('create_routes', () => {
it('fails if routes are indistinguishable', () => {
assert.throws(() => {
create_routes(['[foo].html', '[bar]/index.html']);
create_routes({
files: ['[foo].html', '[bar]/index.html']
});
}, /The \[foo\].html and \[bar\]\/index.html routes clash/);
assert.throws(() => {
create_routes(['foo.html', 'foo.js']);
create_routes({
files: ['foo.html', 'foo.js']
});
}, /The foo.html and foo.js routes clash/);
});
it('matches nested routes', () => {
const route = create_routes(['settings/[submenu].html'])[0];
const route = create_routes({
files: ['settings/[submenu].html']
})[0];
assert.deepEqual(route.exec('/settings/foo'), {
submenu: 'foo'
@@ -89,7 +103,9 @@ describe('create_routes', () => {
});
it('prefers index routes to nested routes', () => {
const routes = create_routes(['settings/[submenu].html', 'settings.html']);
const routes = create_routes({
files: ['settings/[submenu].html', 'settings.html']
});
assert.deepEqual(
routes.map(r => r.file),
@@ -98,7 +114,9 @@ describe('create_routes', () => {
});
it('matches deeply nested routes', () => {
const route = create_routes(['settings/[a]/[b]/index.html'])[0];
const route = create_routes({
files: ['settings/[a]/[b]/index.html']
})[0];
assert.deepEqual(route.exec('/settings/foo/bar'), {
a: 'foo',

View File

@@ -1 +0,0 @@
import 'webpack-hot-middleware/client?path=/__webpack_hmr&timeout=20000';