merge master -> crawl-queue

This commit is contained in:
Richard Harris
2019-02-01 06:04:00 -05:00
318 changed files with 9867 additions and 6289 deletions

View File

@@ -2,40 +2,58 @@ import * as fs from 'fs';
import * as path from 'path';
import mkdirp from 'mkdirp';
import rimraf from 'rimraf';
import { EventEmitter } from 'events';
import minify_html from './utils/minify_html';
import { create_compilers, create_main_manifests, create_routes, create_serviceworker_manifest } from '../core';
import * as events from './interfaces';
import { create_compilers, create_main_manifests, create_manifest_data, create_serviceworker_manifest } from '../core';
import { copy_shimport } from './utils/copy_shimport';
import read_template from '../core/read_template';
import { CompileResult } from '../core/create_compilers/interfaces';
import { noop } from './utils/noop';
import validate_bundler from './utils/validate_bundler';
export function build(opts: {}) {
const emitter = new EventEmitter();
type Opts = {
cwd?: string;
src?: string;
routes?: string;
dest?: string;
output?: string;
static?: string;
legacy?: boolean;
bundler?: 'rollup' | 'webpack';
oncompile?: ({ type, result }: { type: string, result: CompileResult }) => void;
};
execute(emitter, opts).then(
() => {
emitter.emit('done', <events.DoneEvent>{}); // TODO do we need to pass back any info?
},
error => {
emitter.emit('error', <events.ErrorEvent>{
error
});
}
);
export async function build({
cwd,
src = 'src',
routes = 'src/routes',
output = '__sapper__',
static: static_files = 'static',
dest = '__sapper__/build',
return emitter;
}
bundler,
legacy = false,
oncompile = noop
}: Opts = {}) {
bundler = validate_bundler(bundler);
cwd = path.resolve(cwd);
src = path.resolve(cwd, src);
dest = path.resolve(cwd, dest);
routes = path.resolve(cwd, routes);
output = path.resolve(cwd, output);
static_files = path.resolve(cwd, static_files);
if (legacy && bundler === 'webpack') {
throw new Error(`Legacy builds are not supported for projects using webpack`);
}
async function execute(emitter: EventEmitter, {
dest = 'build',
app = 'app',
webpack = 'webpack',
routes = 'routes'
} = {}) {
mkdirp.sync(dest);
rimraf.sync(path.join(dest, '**/*'));
mkdirp.sync(`${dest}/client`);
copy_shimport(dest);
// minify app/template.html
// minify src/template.html
// TODO compile this to a function? could be quicker than str.replace(...).replace(...).replace(...)
const template = fs.readFileSync(`${app}/template.html`, 'utf-8');
const template = read_template(src);
// remove this in a future version
if (template.indexOf('%sapper.base%') === -1) {
@@ -46,64 +64,74 @@ async function execute(emitter: EventEmitter, {
fs.writeFileSync(`${dest}/template.html`, minify_html(template));
const route_objects = create_routes();
const manifest_data = create_manifest_data(routes);
// create app/manifest/client.js and app/manifest/server.js
create_main_manifests({ routes: route_objects });
const { client, server, serviceworker } = create_compilers({ webpack });
const client_stats = await compile(client);
emitter.emit('build', <events.BuildEvent>{
type: 'client',
// TODO duration/warnings
webpack_stats: client_stats
// create src/manifest/client.js and src/manifest/server.js
create_main_manifests({
bundler,
manifest_data,
cwd,
src,
dest,
routes,
output,
dev: false
});
const client_info = client_stats.toJson();
fs.writeFileSync(path.join(dest, 'client_assets.json'), JSON.stringify(client_info.assetsByChunkName));
const { client, server, serviceworker } = await create_compilers(bundler, cwd, src, dest, true);
const server_stats = await compile(server);
emitter.emit('build', <events.BuildEvent>{
const client_result = await client.compile();
oncompile({
type: 'client',
result: client_result
});
const build_info = client_result.to_json(manifest_data, { src, routes, dest });
if (legacy) {
process.env.SAPPER_LEGACY_BUILD = 'true';
const { client } = await create_compilers(bundler, cwd, src, dest, true);
const client_result = await client.compile();
oncompile({
type: 'client (legacy)',
result: client_result
});
client_result.to_json(manifest_data, { src, routes, dest });
build_info.legacy_assets = client_result.assets;
delete process.env.SAPPER_LEGACY_BUILD;
}
fs.writeFileSync(path.join(dest, 'build.json'), JSON.stringify(build_info));
const server_stats = await server.compile();
oncompile({
type: 'server',
// TODO duration/warnings
webpack_stats: server_stats
result: server_stats
});
let serviceworker_stats;
if (serviceworker) {
const client_files = client_result.chunks
.filter(chunk => !chunk.file.endsWith('.map')) // SW does not need to cache sourcemap files
.map(chunk => `client/${chunk.file}`);
create_serviceworker_manifest({
routes: route_objects,
client_files: client_stats.toJson().assets.map((chunk: { name: string }) => `client/${chunk.name}`)
manifest_data,
output,
client_files,
static_files
});
serviceworker_stats = await compile(serviceworker);
serviceworker_stats = await serviceworker.compile();
emitter.emit('build', <events.BuildEvent>{
oncompile({
type: 'serviceworker',
// TODO duration/warnings
webpack_stats: serviceworker_stats
result: serviceworker_stats
});
}
}
function compile(compiler: any) {
return new Promise((fulfil, reject) => {
compiler.run((err: Error, stats: any) => {
if (err) {
reject(err);
process.exit(1);
}
if (stats.hasErrors()) {
console.error(stats.toString({ colors: true }));
reject(new Error(`Encountered errors while building app`));
}
else {
fulfil(stats);
}
});
});
}
}

View File

@@ -5,34 +5,59 @@ import * as child_process from 'child_process';
import * as ports from 'port-authority';
import mkdirp from 'mkdirp';
import rimraf from 'rimraf';
import format_messages from 'webpack-format-messages';
import { locations } from '../config';
import { EventEmitter } from 'events';
import { create_routes, create_main_manifests, create_compilers, create_serviceworker_manifest } from '../core';
import { create_manifest_data, create_main_manifests, create_compilers, create_serviceworker_manifest } from '../core';
import { Compiler, Compilers } from '../core/create_compilers';
import { CompileResult } from '../core/create_compilers/interfaces';
import Deferred from './utils/Deferred';
import * as events from './interfaces';
import validate_bundler from './utils/validate_bundler';
import { copy_shimport } from './utils/copy_shimport';
import { ManifestData, FatalEvent, ErrorEvent, ReadyEvent, InvalidEvent } from '../interfaces';
import read_template from '../core/read_template';
import { noop } from './utils/noop';
export function dev(opts) {
type Opts = {
cwd?: string,
src?: string,
dest?: string,
routes?: string,
output?: string,
static?: string,
'dev-port'?: number,
live?: boolean,
hot?: boolean,
'devtools-port'?: number,
bundler?: 'rollup' | 'webpack',
port?: number
};
export function dev(opts: Opts) {
return new Watcher(opts);
}
class Watcher extends EventEmitter {
bundler: 'rollup' | 'webpack';
dirs: {
app: string;
cwd: string;
src: string;
dest: string;
routes: string;
webpack: string;
output: string;
static: string;
}
port: number;
closed: boolean;
dev_port: number;
live: boolean;
hot: boolean;
devtools_port: number;
dev_server: DevServer;
proc: child_process.ChildProcess;
filewatchers: Array<{ close: () => void }>;
deferreds: {
client: Deferred;
server: Deferred;
};
deferred: Deferred;
crashed: boolean;
restarting: boolean;
@@ -44,24 +69,42 @@ class Watcher extends EventEmitter {
}
constructor({
app = locations.app(),
dest = locations.dest(),
routes = locations.routes(),
webpack = 'webpack',
cwd = '.',
src = 'src',
routes = 'src/routes',
output = '__sapper__',
static: static_files = 'static',
dest = '__sapper__/dev',
'dev-port': dev_port,
live,
hot,
'devtools-port': devtools_port,
bundler,
port = +process.env.PORT
}: {
app: string,
dest: string,
routes: string,
webpack: string,
port: number
}) {
}: Opts) {
super();
this.dirs = { app, dest, routes, webpack };
cwd = path.resolve(cwd);
this.bundler = validate_bundler(bundler);
this.dirs = {
cwd,
src: path.resolve(cwd, src),
dest: path.resolve(cwd, dest),
routes: path.resolve(cwd, routes),
output: path.resolve(cwd, output),
static: path.resolve(cwd, static_files)
};
this.port = port;
this.closed = false;
this.dev_port = dev_port;
this.live = live;
this.hot = hot;
this.devtools_port = devtools_port;
this.filewatchers = [];
this.current_build = {
@@ -72,7 +115,7 @@ class Watcher extends EventEmitter {
};
// remove this in a future version
const template = fs.readFileSync(path.join(app, 'template.html'), 'utf-8');
const template = read_template(src);
if (template.indexOf('%sapper.base%') === -1) {
const error = new Error(`As of Sapper v0.10, your template.html file must include %sapper.base% in the <head>`);
error.code = `missing-sapper-base`;
@@ -91,7 +134,7 @@ class Watcher extends EventEmitter {
async init() {
if (this.port) {
if (!await ports.check(this.port)) {
this.emit('fatal', <events.FatalEvent>{
this.emit('fatal', <FatalEvent>{
message: `Port ${this.port} is unavailable`
});
return;
@@ -100,27 +143,39 @@ class Watcher extends EventEmitter {
this.port = await ports.find(3000);
}
const { dest } = this.dirs;
const { cwd, src, dest, routes, output, static: static_files } = this.dirs;
rimraf.sync(dest);
mkdirp.sync(dest);
mkdirp.sync(`${dest}/client`);
if (this.bundler === 'rollup') copy_shimport(dest);
const dev_port = await ports.find(10000);
if (!this.dev_port) this.dev_port = await ports.find(10000);
// Chrome looks for debugging targets on ports 9222 and 9229 by default
if (!this.devtools_port) this.devtools_port = await ports.find(9222);
let manifest_data: ManifestData;
try {
const routes = create_routes();
create_main_manifests({ routes, dev_port });
manifest_data = create_manifest_data(routes);
create_main_manifests({
bundler: this.bundler,
manifest_data,
dev: true,
dev_port: this.dev_port,
cwd, src, dest, routes, output
});
} catch (err) {
this.emit('fatal', <events.FatalEvent>{
this.emit('fatal', <FatalEvent>{
message: err.message
});
return;
}
this.dev_server = new DevServer(dev_port);
this.dev_server = new DevServer(this.dev_port);
this.filewatchers.push(
watch_dir(
locations.routes(),
routes,
({ path: file, stats }) => {
if (stats.isDirectory()) {
return path.basename(file)[0] !== '_';
@@ -128,41 +183,40 @@ class Watcher extends EventEmitter {
return true;
},
() => {
const routes = create_routes();
create_main_manifests({ routes, dev_port });
try {
const routes = create_routes();
create_main_manifests({ routes, dev_port });
const new_manifest_data = create_manifest_data(routes);
create_main_manifests({
bundler: this.bundler,
manifest_data, // TODO is this right? not new_manifest_data?
dev: true,
dev_port: this.dev_port,
cwd, src, dest, routes, output
});
manifest_data = new_manifest_data;
} catch (err) {
this.emit('error', <events.ErrorEvent>{
this.emit('error', <ErrorEvent>{
message: err.message
});
}
}
),
fs.watch(`${locations.app()}/template.html`, () => {
fs.watch(`${src}/template.html`, () => {
this.dev_server.send({
action: 'reload'
});
})
);
this.deferreds = {
server: new Deferred(),
client: new Deferred()
};
let deferred = new Deferred();
// TODO watch the configs themselves?
const compilers = create_compilers({ webpack: this.dirs.webpack });
let log = '';
const compilers: Compilers = await create_compilers(this.bundler, cwd, src, dest, false);
const emitFatal = () => {
this.emit('fatal', <events.FatalEvent>{
message: `Server crashed`,
log
this.emit('fatal', <FatalEvent>{
message: `Server crashed`
});
this.crashed = true;
@@ -174,34 +228,35 @@ class Watcher extends EventEmitter {
invalid: filename => {
this.restart(filename, 'server');
this.deferreds.server = new Deferred();
},
result: info => {
this.deferreds.client.promise.then(() => {
handle_result: (result: CompileResult) => {
deferred.promise.then(() => {
const restart = () => {
log = '';
this.crashed = false;
ports.wait(this.port)
.then((() => {
this.emit('ready', <events.ReadyEvent>{
this.emit('ready', <ReadyEvent>{
port: this.port,
process: this.proc
});
this.deferreds.server.fulfil();
this.dev_server.send({
status: 'completed'
});
if (this.hot && this.bundler === 'webpack') {
this.dev_server.send({
status: 'completed'
});
} else {
this.dev_server.send({
action: 'reload'
});
}
}))
.catch(err => {
if (this.crashed) return;
this.emit('fatal', <events.FatalEvent>{
message: `Server is not listening on port ${this.port}`,
log
this.emit('fatal', <FatalEvent>{
message: `Server is not listening on port ${this.port}`
});
});
};
@@ -214,21 +269,28 @@ class Watcher extends EventEmitter {
restart();
}
this.proc = child_process.fork(`${dest}/server.js`, [], {
// we need to give the child process its own DevTools port,
// otherwise Node will try to use the parent's (and fail)
const debugArgRegex = /--inspect(?:-brk|-port)?|--debug-port/;
const execArgv = process.execArgv.slice();
if (execArgv.some((arg: string) => !!arg.match(debugArgRegex))) {
execArgv.push(`--inspect-port=${this.devtools_port}`);
}
this.proc = child_process.fork(`${dest}/server/server.js`, [], {
cwd: process.cwd(),
env: Object.assign({
PORT: this.port
}, process.env),
stdio: ['ipc']
stdio: ['ipc'],
execArgv
});
this.proc.stdout.on('data', chunk => {
log += chunk;
this.emit('stdout', chunk);
});
this.proc.stderr.on('data', chunk => {
log += chunk;
this.emit('stderr', chunk);
});
@@ -245,31 +307,37 @@ class Watcher extends EventEmitter {
}
});
let first = true;
this.watch(compilers.client, {
name: 'client',
invalid: filename => {
this.restart(filename, 'client');
this.deferreds.client = new Deferred();
deferred = new Deferred();
// TODO we should delete old assets. due to a webpack bug
// i don't even begin to comprehend, this is apparently
// quite difficult
},
result: info => {
fs.writeFileSync(path.join(dest, 'client_assets.json'), JSON.stringify(info.assetsByChunkName, null, ' '));
this.deferreds.client.fulfil();
handle_result: (result: CompileResult) => {
fs.writeFileSync(
path.join(dest, 'build.json'),
const client_files = info.assets.map((chunk: { name: string }) => `client/${chunk.name}`);
// TODO should be more explicit that to_json has effects
JSON.stringify(result.to_json(manifest_data, this.dirs), null, ' ')
);
const client_files = result.chunks.map(chunk => `client/${chunk.file}`);
create_serviceworker_manifest({
routes: create_routes(),
client_files
manifest_data,
output,
client_files,
static_files
});
deferred.fulfil();
// we need to wait a beat before watching the service
// worker, because of some webpack nonsense
setTimeout(watch_serviceworker, 100);
@@ -281,11 +349,7 @@ class Watcher extends EventEmitter {
watch_serviceworker = noop;
this.watch(compilers.serviceworker, {
name: 'service worker',
result: info => {
fs.writeFileSync(path.join(dest, 'serviceworker_info.json'), JSON.stringify(info, null, ' '));
}
name: 'service worker'
});
}
: noop;
@@ -318,7 +382,7 @@ class Watcher extends EventEmitter {
};
process.nextTick(() => {
this.emit('invalid', <events.InvalidEvent>{
this.emit('invalid', <InvalidEvent>{
changed: Array.from(this.current_build.changed),
invalid: {
server: this.current_build.rebuilding.has('server'),
@@ -332,82 +396,34 @@ class Watcher extends EventEmitter {
}
}
watch(compiler: any, { name, invalid = noop, result }: {
watch(compiler: Compiler, { name, invalid = noop, handle_result = noop }: {
name: string,
invalid?: (filename: string) => void;
result: (stats: any) => void;
handle_result?: (result: CompileResult) => void;
}) {
compiler.hooks.invalid.tap('sapper', (filename: string) => {
invalid(filename);
});
compiler.oninvalid(invalid);
compiler.watch({}, (err: Error, stats: any) => {
compiler.watch((err?: Error, result?: CompileResult) => {
if (err) {
this.emit('error', <events.ErrorEvent>{
this.emit('error', <ErrorEvent>{
type: name,
message: err.message
});
} else {
const messages = format_messages(stats);
const info = stats.toJson();
this.emit('build', {
type: name,
duration: info.time,
errors: messages.errors.map((message: string) => {
const duplicate = this.current_build.unique_errors.has(message);
this.current_build.unique_errors.add(message);
return mungeWebpackError(message, duplicate);
}),
warnings: messages.warnings.map((message: string) => {
const duplicate = this.current_build.unique_warnings.has(message);
this.current_build.unique_warnings.add(message);
return mungeWebpackError(message, duplicate);
}),
duration: result.duration,
errors: result.errors,
warnings: result.warnings
});
result(info);
handle_result(result);
}
});
}
}
const locPattern = /\((\d+):(\d+)\)$/;
function mungeWebpackError(message: string, duplicate: boolean) {
// TODO this is all a bit rube goldberg...
const lines = message.split('\n');
const file = lines.shift()
.replace('', '') // careful — there is a special character at the beginning of this string
.replace('', '')
.replace('./', '');
let line = null;
let column = null;
const match = locPattern.exec(lines[0]);
if (match) {
lines[0] = lines[0].replace(locPattern, '');
line = +match[1];
column = +match[2];
}
return {
file,
line,
column,
message: lines.join('\n'),
originalMessage: message,
duplicate
};
}
const INTERVAL = 10000;
class DevServer {
@@ -460,14 +476,12 @@ class DevServer {
}
}
function noop() {}
function watch_dir(
dir: string,
filter: ({ path, stats }: { path: string, stats: fs.Stats }) => boolean,
callback: () => void
) {
let watch;
let watch: any;
let closed = false;
import('cheap-watch').then(CheapWatch => {
@@ -475,7 +489,7 @@ function watch_dir(
watch = new CheapWatch({ dir, filter, debounce: 50 });
watch.on('+', ({ isNew }) => {
watch.on('+', ({ isNew }: { isNew: boolean }) => {
if (isNew) callback();
});

View File

@@ -1,129 +1,150 @@
import * as child_process from 'child_process';
import * as path from 'path';
import * as sander from 'sander';
import URL from 'url-parse';
import * as url from 'url';
import fetch from 'node-fetch';
import * as yootils from 'yootils';
import * as ports from 'port-authority';
import { EventEmitter } from 'events';
import clean_html from './utils/clean_html';
import minify_html from './utils/minify_html';
import Deferred from './utils/Deferred';
import * as events from './interfaces';
import { noop } from './utils/noop';
export function exporter(opts: {}) {
const emitter = new EventEmitter();
type Opts = {
build_dir?: string,
export_dir?: string,
cwd?: string,
static?: string,
basepath?: string,
timeout?: number | false,
oninfo?: ({ message }: { message: string }) => void;
onfile?: ({ file, size, status }: { file: string, size: number, status: number }) => void;
};
execute(emitter, opts).then(
() => {
emitter.emit('done', <events.DoneEvent>{}); // TODO do we need to pass back any info?
},
error => {
emitter.emit('error', <events.ErrorEvent>{
error
});
}
);
return emitter;
function resolve(from: string, to: string) {
return url.parse(url.resolve(from, to));
}
async function execute(emitter: EventEmitter, {
build = 'build',
dest = 'export',
basepath = ''
} = {}) {
const export_dir = path.join(dest, basepath);
type URL = url.UrlWithStringQuery;
export { _export as export };
async function _export({
cwd,
static: static_files = 'static',
build_dir = '__sapper__/build',
export_dir = '__sapper__/export',
basepath = '',
timeout = 5000,
oninfo = noop,
onfile = noop
}: Opts = {}) {
basepath = basepath.replace(/^\//, '')
cwd = path.resolve(cwd);
static_files = path.resolve(cwd, static_files);
build_dir = path.resolve(cwd, build_dir);
export_dir = path.resolve(cwd, export_dir, basepath);
// Prep output directory
sander.rimrafSync(export_dir);
sander.copydirSync('assets').to(export_dir);
sander.copydirSync(build, 'client').to(export_dir, 'client');
sander.copydirSync(static_files).to(export_dir);
sander.copydirSync(build_dir, 'client').to(export_dir, 'client');
if (sander.existsSync(build, 'service-worker.js')) {
sander.copyFileSync(build, 'service-worker.js').to(export_dir, 'service-worker.js');
if (sander.existsSync(build_dir, 'service-worker.js')) {
sander.copyFileSync(build_dir, 'service-worker.js').to(export_dir, 'service-worker.js');
}
if (sander.existsSync(build, 'service-worker.js.map')) {
sander.copyFileSync(build, 'service-worker.js.map').to(export_dir, 'service-worker.js.map');
if (sander.existsSync(build_dir, 'service-worker.js.map')) {
sander.copyFileSync(build_dir, 'service-worker.js.map').to(export_dir, 'service-worker.js.map');
}
const port = await ports.find(3000);
const origin = `http://localhost:${port}`;
const root = new URL(basepath || '', origin);
const protocol = 'http:';
const host = `localhost:${port}`;
const origin = `${protocol}//${host}`;
emitter.emit('info', {
const root = resolve(origin, basepath);
if (!root.href.endsWith('/')) root.href += '/';
oninfo({
message: `Crawling ${root.href}`
});
const proc = child_process.fork(path.resolve(`${build}/server.js`), [], {
cwd: process.cwd(),
const proc = child_process.fork(path.resolve(`${build_dir}/server/server.js`), [], {
cwd,
env: Object.assign({
PORT: port,
NODE_ENV: 'production',
SAPPER_DEST: build,
SAPPER_EXPORT: 'true'
}, process.env)
});
const seen = new Set();
const saved = new Set();
const deferreds = new Map();
function get_deferred(pathname: string) {
pathname = pathname.replace(root.pathname, '');
if (!deferreds.has(pathname)) {
deferreds.set(pathname, new Deferred());
}
return deferreds.get(pathname);
}
proc.on('message', message => {
if (!message.__sapper__ || message.event !== 'file') return;
const pathname = new URL(message.url, origin).pathname;
let file = pathname.slice(1);
let { body } = message;
function save(path: string, status: number, type: string, body: string) {
const { pathname } = resolve(origin, path);
let file = decodeURIComponent(pathname.slice(1));
if (saved.has(file)) return;
saved.add(file);
const is_html = message.type === 'text/html';
const is_html = type === 'text/html';
if (is_html) {
file = file === '' ? 'index.html' : `${file}/index.html`;
if (pathname !== '/service-worker-index.html') {
file = file === '' ? 'index.html' : `${file}/index.html`;
}
body = minify_html(body);
}
emitter.emit('file', <events.FileEvent>{
onfile({
file,
size: body.length,
status: message.status
status
});
sander.writeFileSync(export_dir, file, body);
}
get_deferred(pathname).fulfil();
proc.on('message', message => {
if (!message.__sapper__ || message.event !== 'file') return;
save(message.url, message.status, message.type, message.body);
});
async function handle(url: URL) {
const pathname = (url.pathname.replace(root.pathname, '') || '/');
let pathname = url.pathname;
if (pathname !== '/service-worker-index.html') {
pathname = pathname.replace(root.pathname, '') || '/'
}
if (seen.has(pathname)) return;
seen.add(pathname);
const deferred = get_deferred(pathname);
const timeout_deferred = new Deferred();
const the_timeout = setTimeout(() => {
timeout_deferred.reject(new Error(`Timed out waiting for ${url.href}`));
}, timeout);
const r = await Promise.race([
fetch(url.href, {
redirect: 'manual'
}),
timeout_deferred.promise
]);
clearTimeout(the_timeout); // prevent it hanging at the end
let type = r.headers.get('Content-Type');
let body = await r.text();
const r = await fetch(url.href);
const range = ~~(r.status / 100);
if (range === 2) {
if (r.headers.get('Content-Type') === 'text/html') {
const body = await r.text();
if (type === 'text/html' && pathname !== '/service-worker-index.html') {
const urls: URL[] = [];
const cleaned = clean_html(body);
@@ -133,7 +154,7 @@ async function execute(emitter: EventEmitter, {
const base_match = /<base ([\s\S]+?)>/m.exec(cleaned);
const base_href = base_match && get_href(base_match[1]);
const base = new URL(base_href || '/', url.href);
const base = resolve(url.href, base_href);
let match;
let pattern = /<a ([\s\S]+?)>/gm;
@@ -143,8 +164,9 @@ async function execute(emitter: EventEmitter, {
const href = get_href(attrs);
if (href) {
const url = new URL(href, base.href);
if (url.origin === origin) {
const url = resolve(base.href, href);
if (url.protocol === protocol && url.host === host) {
promise = q.add(() => handle(url));
}
}
@@ -154,18 +176,29 @@ async function execute(emitter: EventEmitter, {
}
}
await deferred.promise;
if (range === 3) {
const location = r.headers.get('Location');
type = 'text/html';
body = `<script>window.location.href = "${location.replace(origin, '')}"</script>`;
await handle(resolve(root.href, location));
}
save(pathname, r.status, type, body);
}
return ports.wait(port)
.then(() => {
// TODO all static routes
return handle(root);
})
.then(() => proc.kill());
.then(() => handle(root))
.then(() => handle(resolve(root.href, 'service-worker-index.html')))
.then(() => proc.kill())
.catch(err => {
proc.kill();
throw err;
});
}
function get_href(attrs: string) {
const match = /href\s*=\s*(?:"(.+?)"|'(.+?)'|([^\s>]+))/.exec(attrs);
const match = /href\s*=\s*(?:"(.*?)"|'(.+?)'|([^\s>]+))/.exec(attrs);
return match[1] || match[2] || match[3];
}

View File

@@ -1,8 +1,7 @@
import { locations } from '../config';
import { create_routes } from '../core';
import { create_manifest_data } from '../core';
export function find_page(pathname: string, cwd = locations.routes()) {
const { pages } = create_routes(cwd);
export function find_page(pathname: string, cwd = 'src/routes') {
const { pages } = create_manifest_data(cwd);
for (let i = 0; i < pages.length; i += 1) {
const page = pages[i];

View File

@@ -1,44 +0,0 @@
import * as child_process from 'child_process';
export type ReadyEvent = {
port: number;
process: child_process.ChildProcess;
};
export type ErrorEvent = {
type: string;
message: string;
};
export type FatalEvent = {
message: string;
log?: string;
};
export type InvalidEvent = {
changed: string[];
invalid: {
client: boolean;
server: boolean;
serviceworker: boolean;
}
};
export type BuildEvent = {
type: string;
errors: Array<{ message: string, duplicate: boolean }>;
warnings: Array<{ message: string, duplicate: boolean }>;
duration: number;
webpack_stats: any;
}
export type FileEvent = {
file: string;
size: number;
}
export type FailureEvent = {
}
export type DoneEvent = {}

View File

@@ -0,0 +1,9 @@
import * as fs from 'fs';
export function copy_shimport(dest: string) {
const shimport_version = require('shimport/package.json').version;
fs.writeFileSync(
`${dest}/client/shimport@${shimport_version}.js`,
fs.readFileSync(require.resolve('shimport/index.js'))
);
}

View File

@@ -7,6 +7,7 @@ export default function minify_html(html: string) {
conservativeCollapse: true,
decodeEntities: true,
html5: true,
ignoreCustomComments: [/^#/],
minifyCSS: true,
minifyJS: false,
removeAttributeQuotes: true,

1
src/api/utils/noop.ts Normal file
View File

@@ -0,0 +1 @@
export function noop() {}

View File

@@ -0,0 +1,38 @@
import * as fs from 'fs';
export default function validate_bundler(bundler?: 'rollup' | 'webpack') {
if (!bundler) {
bundler = (
fs.existsSync('rollup.config.js') ? 'rollup' :
fs.existsSync('webpack.config.js') ? 'webpack' :
null
);
if (!bundler) {
// TODO remove in a future version
deprecate_dir('rollup');
deprecate_dir('webpack');
throw new Error(`Could not find rollup.config.js or webpack.config.js`);
}
}
if (bundler !== 'rollup' && bundler !== 'webpack') {
throw new Error(`'${bundler}' is not a valid option for --bundler — must be either 'rollup' or 'webpack'`);
}
return bundler;
}
function deprecate_dir(bundler: 'rollup' | 'webpack') {
try {
const stats = fs.statSync(bundler);
if (!stats.isDirectory()) return;
} catch (err) {
// do nothing
return;
}
// TODO link to docs, once those docs exist
throw new Error(`As of Sapper 0.21, build configuration should be placed in a single ${bundler}.config.js file`);
}