Compare commits

..

35 Commits

Author SHA1 Message Date
Rich Harris
a6dc61a182 -> v0.5.1 2018-01-16 08:47:50 -05:00
Rich Harris
96666d05ec only write to filesystem in dev mode 2018-01-16 08:47:44 -05:00
Rich Harris
6390ba692b -> v0.5.0 2018-01-15 10:28:48 -05:00
Rich Harris
0e131cc81e Merge pull request #88 from sveltejs/gh-9
Exporting
2018-01-15 10:24:35 -05:00
Rich Harris
bd3d5713cb Merge pull request #64 from lukeed/lazy-chokidar
Lazily Require Chokidar
2018-01-15 10:04:37 -05:00
Rich Harris
9ec23c47ad update test 2018-01-15 10:04:24 -05:00
Rich Harris
b7bb69925e rename extract to export, for familiarity to next.js devs 2018-01-15 10:03:27 -05:00
Rich Harris
25124f6ee7 Merge branch 'master' into lazy-chokidar 2018-01-15 09:58:39 -05:00
Rich Harris
73d491cd19 Merge branch 'master' into lazy-chokidar 2018-01-15 09:58:03 -05:00
Rich Harris
e25fceb4b8 node 6 is the new IE 2018-01-15 09:36:12 -05:00
Rich Harris
3807147c57 add missing dependency 2018-01-14 22:45:59 -05:00
Rich Harris
a523ba58ff save pages as index.html, ignore hashes 2018-01-14 22:45:51 -05:00
Rich Harris
fe03fd3a52 update lockfile 2018-01-14 18:21:37 -05:00
Rich Harris
89c430a0cb slightly different approach to extracting 2018-01-14 18:19:51 -05:00
Rich Harris
8ef312849c always write service-worker.js and shell index.html file 2018-01-14 18:16:10 -05:00
Rich Harris
4200446684 ignore trailing slash in pathnames 2018-01-14 18:15:40 -05:00
Rich Harris
681ed005b8 remove unused deps 2018-01-14 18:15:12 -05:00
Rich Harris
d457af8d51 update test 2018-01-14 18:14:07 -05:00
Rich Harris
0c158b9e1f Merge branch 'master' of https://github.com/freedmand/sapper into freedmand-master 2018-01-14 13:48:07 -05:00
Rich Harris
50011e2077 always print stdout/stderr, to avoid wild goose chase debugging 2018-01-14 13:47:23 -05:00
Rich Harris
f27b7973e3 Build before extracting 2018-01-14 13:46:25 -05:00
Rich Harris
2af2ab3cb9 Ensure output dir exists, return Promise 2018-01-14 13:45:47 -05:00
Rich Harris
6a4dc1901c Enforce prod mode, return a Promise so it can be used programmatically 2018-01-14 13:45:01 -05:00
Rich Harris
fbbc0e9e19 Merge branch 'master' of https://github.com/freedmand/sapper into freedmand-master 2018-01-14 12:29:15 -05:00
Rich Harris
1213c3da46 fix lockfile mess 2018-01-14 12:27:35 -05:00
Rich Harris
4cc2104088 add back test that got lost in a merge conflict somewhere 2018-01-14 12:23:07 -05:00
Rich Harris
d6dda371ca typo 2018-01-14 12:22:41 -05:00
Rich Harris
304c06085e Merge branch 'master' into master 2018-01-14 12:19:47 -05:00
Rich Harris
33b6450e34 Merge branch 'master' into master 2018-01-14 12:16:09 -05:00
freedmand
9ea4137b87 Add option to extract server-side routes at directories other than /api.
Also clarifies some texts and documentation.
2018-01-05 19:21:25 -08:00
freedmand
7588911108 Removes all async/await from the extraction pipeline, and adds unit tests for extracted client pages that match a regular expression 2018-01-05 14:56:58 -08:00
freedmand
fc8280adea Fixes small issue with reading chunk files 2018-01-05 14:42:04 -08:00
freedmand
d08f9eb5a4 Fixes funky indentation in extraction unit test 2018-01-05 14:29:46 -08:00
freedmand
30ddb3dd7e Adds a sapper extract CLI command, which scrapes the server to run as a static website starting at the site's root.
TESTED=Basic unit test ensuring relevant routes are added.
2018-01-05 14:16:30 -08:00
Luke Edwards
0c891ba79e require chokidar lazily; dev-mode only 2018-01-05 00:39:17 -08:00
16 changed files with 689 additions and 4742 deletions

3
.gitignore vendored
View File

@@ -2,4 +2,5 @@
node_modules
cypress/screenshots
test/app/.sapper
runtime.js
runtime.js
yarn.lock

View File

@@ -1,5 +1,14 @@
# sapper changelog
## 0.5.1
* Only write service-worker.js to filesystem in dev mode ([#90](https://github.com/sveltejs/sapper/issues/90))
## 0.5.0
* Experimental support for `sapper export` ([#9](https://github.com/sveltejs/sapper/issues/9))
* Lazily load chokidar, for faster startup ([#64](https://github.com/sveltejs/sapper/pull/64))
## 0.4.0
* `%sapper.main%` has been replaced with `%sapper.scripts%` ([#86](https://github.com/sveltejs/sapper/issues/86))

View File

@@ -1,8 +1,29 @@
#!/usr/bin/env node
const build = require('../lib/build.js');
const cmd = process.argv[2];
const start = Date.now();
if (cmd === 'build') {
process.env.NODE_ENV = 'production';
require('../lib/build.js')();
}
build()
.then(() => {
const elapsed = Date.now() - start;
console.error(`built in ${elapsed}ms`); // TODO beautify this, e.g. 'built in 4.7 seconds'
})
.catch(err => {
console.error(err ? err.details || err.stack || err.message || err : 'Unknown error');
});
} else if (cmd === 'export') {
const start = Date.now();
build()
.then(() => require('../lib/utils/export.js')())
.then(() => {
const elapsed = Date.now() - start;
console.error(`extracted in ${elapsed}ms`); // TODO beautify this, e.g. 'built in 4.7 seconds'
})
.catch(err => {
console.error(err ? err.details || err.stack || err.message || err : 'Unknown error');
});
}

View File

@@ -1,3 +1,5 @@
process.env.NODE_ENV = 'production';
const fs = require('fs');
const path = require('path');
const mkdirp = require('mkdirp');
@@ -14,29 +16,32 @@ module.exports = () => {
// create main.js and server-routes.js
create_app();
function handleErrors(err, stats) {
if (err) {
console.error(err ? err.details || err.stack || err.message || err : 'Unknown error');
process.exit(1);
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`));
}
}
if (stats.hasErrors()) {
console.log(stats.toString({ colors: true }));
process.exit(1);
}
}
client.run((err, clientStats) => {
handleErrors(err, clientStats);
const clientInfo = clientStats.toJson();
fs.writeFileSync(path.join(dest, 'stats.client.json'), JSON.stringify(clientInfo, null, ' '));
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, ' '));
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);
generate_asset_cache(clientInfo, serverInfo);
fulfil();
});
});
});
};
};

View File

@@ -1,5 +1,4 @@
const glob = require('glob');
const chokidar = require('chokidar');
const create_routes = require('./utils/create_routes.js');
const { src, dev } = require('./config.js');
@@ -20,7 +19,7 @@ function update() {
update();
if (dev) {
const watcher = chokidar.watch(`${src}/**/*.+(html|js|mjs)`, {
const watcher = require('chokidar').watch(`${src}/**/*.+(html|js|mjs)`, {
ignoreInitial: true,
persistent: false
});
@@ -28,4 +27,4 @@ if (dev) {
watcher.on('add', update);
watcher.on('change', update);
watcher.on('unlink', update);
}
}

View File

@@ -1,7 +1,6 @@
const fs = require('fs');
const glob = require('glob');
const chalk = require('chalk');
const chokidar = require('chokidar');
const framer = require('code-frame');
const { locate } = require('locate-character');
const { dev } = require('./config.js');
@@ -103,7 +102,7 @@ function create_templates() {
create_templates();
if (dev) {
const watcher = chokidar.watch('templates/**.html', {
const watcher = require('chokidar').watch('templates/**.html', {
ignoreInitial: true,
persistent: false
});
@@ -125,4 +124,4 @@ exports.stream = (res, status, data) => {
if (template) return template.stream(res, data);
return `Missing template for status code ${status}`;
};
};

View File

@@ -1,6 +1,5 @@
const fs = require('fs');
const path = require('path');
const chokidar = require('chokidar');
const route_manager = require('../route_manager.js');
const { src, entry, dev } = require('../config.js');
@@ -70,7 +69,7 @@ function create_app() {
if (dev) {
route_manager.onchange(create_app);
const watcher = chokidar.watch(`templates/main.js`, {
const watcher = require('chokidar').watch(`templates/main.js`, {
ignoreInitial: true,
persistent: false
});
@@ -80,4 +79,4 @@ if (dev) {
watcher.on('unlink', create_app);
}
module.exports = create_app;
module.exports = create_app;

View File

@@ -31,7 +31,7 @@ module.exports = function create_matchers(files) {
}
}
const pattern = new RegExp(`^${pattern_string || '\\/'}$`);
const pattern = new RegExp(`^${pattern_string}\\/?$`);
const test = url => pattern.test(url);

87
lib/utils/export.js Normal file
View File

@@ -0,0 +1,87 @@
const sander = require('sander');
const app = require('express')();
const cheerio = require('cheerio');
const fetch = require('node-fetch');
const URL = require('url-parse');
const sapper = require('../index.js');
const { PORT = 3000, OUTPUT_DIR = 'dist' } = process.env;
const { dest } = require('../config.js');
const origin = `http://localhost:${PORT}`;
module.exports = function() {
// Prep output directory
sander.rimrafSync(OUTPUT_DIR);
sander.copydirSync('assets').to(OUTPUT_DIR);
sander.copydirSync(`${dest}/client`).to(`${OUTPUT_DIR}/client`);
sander.copyFileSync(`${dest}/service-worker.js`).to(`${OUTPUT_DIR}/service-worker.js`);
// Intercept server route fetches
function save(res) {
res = res.clone();
return res.text().then(body => {
const { pathname } = new URL(res.url);
let dest = OUTPUT_DIR + pathname;
const type = res.headers.get('Content-Type');
if (type.startsWith('text/html;')) dest += '/index.html';
sander.writeFileSync(dest, body);
return body;
});
}
global.fetch = (url, opts) => {
if (url[0] === '/') {
url = `http://localhost:${PORT}${url}`;
return fetch(url, opts)
.then(r => {
save(r);
return r;
});
}
return fetch(url, opts);
};
app.use(sapper());
const server = app.listen(PORT);
const seen = new Set();
function handle(url) {
if (url.origin !== origin) return;
if (seen.has(url.pathname)) return;
seen.add(url.pathname);
return fetch(url.href)
.then(r => {
save(r);
return r.text();
})
.then(body => {
const $ = cheerio.load(body);
const hrefs = [];
$('a[href]').each((i, $a) => {
hrefs.push($a.attribs.href);
});
return hrefs.reduce((promise, href) => {
return promise.then(() => handle(new URL(href, url.href)));
}, Promise.resolve());
})
.catch(err => {
console.error(`Error rendering ${url.pathname}: ${err.message}`);
});
}
return handle(new URL(origin)) // TODO all static routes
.then(() => server.close());
};

755
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "sapper",
"version": "0.4.0",
"version": "0.5.1",
"description": "Military-grade apps, engineered by Svelte",
"main": "lib/index.js",
"bin": {
@@ -18,6 +18,7 @@
},
"dependencies": {
"chalk": "^2.3.0",
"cheerio": "^1.0.0-rc.2",
"chokidar": "^1.7.0",
"code-frame": "^5.0.0",
"escape-html": "^1.0.3",
@@ -26,7 +27,10 @@
"relative": "^3.0.2",
"require-relative": "^0.8.7",
"rimraf": "^2.6.2",
"sander": "^0.6.0",
"serialize-javascript": "^1.4.0",
"url-parse": "^1.2.0",
"walk-sync": "^0.3.2",
"webpack": "^3.10.0",
"webpack-hot-middleware": "^2.21.0"
},

3
test/app/.gitignore vendored
View File

@@ -3,4 +3,5 @@ node_modules
.sapper
yarn.lock
cypress/screenshots
templates/.*
templates/.*
dist

View File

@@ -32,7 +32,7 @@
},
preload({ params, query }) {
return fetch(`/api/blog`).then(r => r.json()).then(posts => {
return fetch(`/api/blog/contents`).then(r => r.json()).then(posts => {
return { posts };
});
}

View File

@@ -5,6 +5,7 @@ const serve = require('serve-static');
const Nightmare = require('nightmare');
const getPort = require('get-port');
const fetch = require('node-fetch');
const walkSync = require('walk-sync');
run('production');
run('development');
@@ -78,7 +79,7 @@ function run(env) {
if (env === 'production') {
const cli = path.resolve(__dirname, '../../cli/index.js');
exec_promise = exec(`${cli} build`);
exec_promise = exec(`${cli} build`).then(() => exec(`${cli} export`));
}
return exec_promise.then(() => {
@@ -295,6 +296,18 @@ function run(env) {
assert.equal(html, `URL is /show-url`);
});
});
it('calls a delete handler', () => {
return nightmare
.goto(`${base}/delete-test`)
.wait(() => window.READY)
.click('.del')
.wait(() => window.deleted)
.evaluate(() => window.deleted.id)
.then(id => {
assert.equal(id, 42);
});
});
});
describe('headers', () => {
@@ -312,20 +325,92 @@ function run(env) {
});
});
});
if (env === 'production') {
describe('export', () => {
it('export all pages', () => {
const dest = path.resolve(__dirname, '../app/dist');
// Pages that should show up in the extraction directory.
const expectedPages = [
'index.html',
'about/index.html',
'slow-preload/index.html',
'blog/index.html',
'blog/a-very-long-post/index.html',
'blog/how-can-i-get-involved/index.html',
'blog/how-is-sapper-different-from-next/index.html',
'blog/how-to-use-sapper/index.html',
'blog/what-is-sapper/index.html',
'blog/why-the-name/index.html',
'api/blog/contents',
'api/blog/a-very-long-post',
'api/blog/how-can-i-get-involved',
'api/blog/how-is-sapper-different-from-next',
'api/blog/how-to-use-sapper',
'api/blog/what-is-sapper',
'api/blog/why-the-name',
'favicon.png',
'global.css',
'great-success.png',
'manifest.json',
'service-worker.js',
'svelte-logo-192.png',
'svelte-logo-512.png',
];
// Client scripts that should show up in the extraction directory.
const expectedClientRegexes = [
/client\/_\..*?\.js/,
/client\/about\..*?\.js/,
/client\/blog_\$slug\$\..*?\.js/,
/client\/blog\..*?\.js/,
/client\/main\..*?\.js/,
/client\/show_url\..*?\.js/,
/client\/slow_preload\..*?\.js/,
];
const allPages = walkSync(dest);
expectedPages.forEach((expectedPage) => {
assert.ok(allPages.includes(expectedPage),
`Could not find page matching ${expectedPage}`);
});
expectedClientRegexes.forEach((expectedRegex) => {
// Ensure each client page regular expression matches at least one
// generated page.
let matched = false;
for (const page of allPages) {
if (expectedRegex.test(page)) {
matched = true;
break;
}
}
assert.ok(matched,
`Could not find client page matching ${expectedRegex}`);
});
});
});
}
});
}
function exec(cmd) {
return new Promise((fulfil, reject) => {
require('child_process').exec(cmd, (err, stdout, stderr) => {
if (err) {
process.stdout.write(stdout);
process.stderr.write(stderr);
const parts = cmd.split(' ');
const proc = require('child_process').spawn(parts.shift(), parts);
return reject(err);
}
fulfil();
proc.stdout.on('data', data => {
process.stdout.write(data);
});
proc.stderr.on('data', data => {
process.stderr.write(data);
});
proc.on('error', reject);
proc.on('close', () => fulfil());
});
}

4374
yarn.lock

File diff suppressed because it is too large Load Diff