move config into single file, add errors to help people migrate

This commit is contained in:
Rich Harris
2018-09-19 16:27:30 -04:00
parent 8f064fe5ac
commit 273823dfd7
16 changed files with 198 additions and 132 deletions

View File

@@ -11,6 +11,7 @@ import * as events from './interfaces';
import { copy_shimport } from './utils/copy_shimport';
import { Dirs, PageComponent } from '../interfaces';
import { CompileResult } from '../core/create_compilers/interfaces';
import read_template from '../core/read_template';
type Opts = {
legacy: boolean;
@@ -41,7 +42,7 @@ async function execute(emitter: EventEmitter, opts: Opts, dirs: Dirs) {
// minify src/template.html
// TODO compile this to a function? could be quicker than str.replace(...).replace(...).replace(...)
const template = fs.readFileSync(`${dirs.src}/template.html`, 'utf-8');
const template = read_template();
// remove this in a future version
if (template.indexOf('%sapper.base%') === -1) {
@@ -57,7 +58,7 @@ async function execute(emitter: EventEmitter, opts: Opts, dirs: Dirs) {
// create src/manifest/client.js and src/manifest/server.js
create_main_manifests({ bundler: opts.bundler, manifest_data });
const { client, server, serviceworker } = create_compilers(opts.bundler, dirs);
const { client, server, serviceworker } = await create_compilers(opts.bundler, dirs);
const client_result = await client.compile();
emitter.emit('build', <events.BuildEvent>{
@@ -70,7 +71,7 @@ async function execute(emitter: EventEmitter, opts: Opts, dirs: Dirs) {
if (opts.legacy) {
process.env.SAPPER_LEGACY_BUILD = 'true';
const { client } = create_compilers(opts.bundler, dirs);
const { client } = await create_compilers(opts.bundler, dirs);
const client_result = await client.compile();

View File

@@ -15,6 +15,7 @@ import * as events from './interfaces';
import validate_bundler from '../cli/utils/validate_bundler';
import { copy_shimport } from './utils/copy_shimport';
import { ManifestData } from '../interfaces';
import read_template from '../core/read_template';
export function dev(opts) {
return new Watcher(opts);
@@ -100,7 +101,7 @@ class Watcher extends EventEmitter {
};
// remove this in a future version
const template = fs.readFileSync(path.join(src, 'template.html'), 'utf-8');
const template = read_template();
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`;
@@ -185,7 +186,7 @@ class Watcher extends EventEmitter {
let deferred = new Deferred();
// TODO watch the configs themselves?
const compilers: Compilers = create_compilers(this.bundler, this.dirs);
const compilers: Compilers = await create_compilers(this.bundler, this.dirs);
let log = '';

View File

@@ -1,5 +1,5 @@
import * as child_process from 'child_process';
import { CompileResult } from '../core/create_compilers';
import { CompileResult } from '../core/create_compilers/interfaces';
export type ReadyEvent = {
port: number;

View File

@@ -1,15 +1,19 @@
import * as fs from 'fs';
export default function validate_bundler(bundler?: string) {
export default function validate_bundler(bundler?: 'rollup' | 'webpack') {
if (!bundler) {
bundler = (
fs.existsSync('rollup') ? 'rollup' :
fs.existsSync('webpack') ? 'webpack' :
fs.existsSync('rollup.config.js') ? 'rollup' :
fs.existsSync('webpack.config.js') ? 'webpack' :
null
);
if (!bundler) {
throw new Error(`Could not find a 'rollup' or 'webpack' directory`);
// 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`);
}
}
@@ -18,4 +22,17 @@ export default function validate_bundler(bundler?: string) {
}
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`);
}

View File

@@ -15,8 +15,8 @@ export default class RollupCompiler {
chunks: any[];
css_files: Array<{ id: string, code: string }>;
constructor(config: string) {
this._ = this.get_config(path.resolve(config));
constructor(config: any) {
this._ = this.get_config(config);
this.input = null;
this.warnings = [];
this.errors = [];
@@ -24,31 +24,8 @@ export default class RollupCompiler {
this.css_files = [];
}
async get_config(input: string) {
if (!rollup) rollup = relative('rollup', process.cwd());
const bundle = await rollup.rollup({
input,
external: (id: string) => {
return (id[0] !== '.' && !path.isAbsolute(id)) || id.slice(-5, id.length) === '.json';
}
});
const { code } = await bundle.generate({ format: 'cjs' });
// temporarily override require
const defaultLoader = require.extensions['.js'];
require.extensions['.js'] = (module: any, filename: string) => {
if (filename === input) {
module._compile(code, filename);
} else {
defaultLoader(module, filename);
}
};
const mod: any = require(input);
delete require.cache[input];
async get_config(mod: any) {
// TODO this is hacky, and doesn't need to apply to all three compilers
(mod.plugins || (mod.plugins = [])).push({
name: 'sapper-internal',
options: (opts: any) => {
@@ -157,4 +134,34 @@ export default class RollupCompiler {
}
});
}
static async load_config() {
if (!rollup) rollup = relative('rollup', process.cwd());
const input = path.resolve('rollup.config.js');
const bundle = await rollup.rollup({
input,
external: (id: string) => {
return (id[0] !== '.' && !path.isAbsolute(id)) || id.slice(-5, id.length) === '.json';
}
});
const { code } = await bundle.generate({ format: 'cjs' });
// temporarily override require
const defaultLoader = require.extensions['.js'];
require.extensions['.js'] = (module: any, filename: string) => {
if (filename === input) {
module._compile(code, filename);
} else {
defaultLoader(module, filename);
}
};
const config: any = require(input);
delete require.cache[input];
return config;
}
}

View File

@@ -1,4 +1,3 @@
import * as path from 'path';
import relative from 'require-relative';
import { CompileResult } from './interfaces';
import WebpackResult from './WebpackResult';
@@ -8,9 +7,9 @@ let webpack: any;
export class WebpackCompiler {
_: any;
constructor(config: string) {
constructor(config: any) {
if (!webpack) webpack = relative('webpack', process.cwd());
this._ = webpack(require(path.resolve(config)));
this._ = webpack(config);
}
oninvalid(cb: (filename: string) => void) {

View File

@@ -1,5 +1,4 @@
import * as fs from 'fs';
import { Dirs } from '../../interfaces';
import * as path from 'path';
import RollupCompiler from './RollupCompiler';
import { WebpackCompiler } from './WebpackCompiler';
@@ -11,27 +10,35 @@ export type Compilers = {
serviceworker?: Compiler;
}
export default function create_compilers(bundler: string, dirs: Dirs): Compilers {
export default async function create_compilers(bundler: 'rollup' | 'webpack'): Promise<Compilers> {
if (bundler === 'rollup') {
const sw = `${dirs.rollup}/service-worker.config.js`;
const config = await RollupCompiler.load_config();
validate_config(config, 'rollup');
return {
client: new RollupCompiler(`${dirs.rollup}/client.config.js`),
server: new RollupCompiler(`${dirs.rollup}/server.config.js`),
serviceworker: fs.existsSync(sw) && new RollupCompiler(sw)
client: new RollupCompiler(config.client),
server: new RollupCompiler(config.server),
serviceworker: config.serviceworker && new RollupCompiler(config.serviceworker)
};
}
if (bundler === 'webpack') {
const sw = `${dirs.webpack}/service-worker.config.js`;
const config = require(path.resolve('webpack.config.js'));
validate_config(config, 'webpack');
return {
client: new WebpackCompiler(`${dirs.webpack}/client.config.js`),
server: new WebpackCompiler(`${dirs.webpack}/server.config.js`),
serviceworker: fs.existsSync(sw) && new WebpackCompiler(sw)
client: new WebpackCompiler(config.client),
server: new WebpackCompiler(config.server),
serviceworker: config.serviceworker && new WebpackCompiler(config.serviceworker)
};
}
// this shouldn't be possible...
throw new Error(`Invalid bundler option '${bundler}'`);
}
function validate_config(config: any, bundler: 'rollup' | 'webpack') {
if (!config.client || !config.server) {
throw new Error(`${bundler}.config.js must export a { client, server, serviceworker? } object`);
}
}

View File

@@ -5,6 +5,11 @@ import { Page, PageComponent, ServerRoute, ManifestData } from '../interfaces';
import { posixify, reserved_words } from './utils';
export default function create_manifest_data(cwd = locations.routes()): ManifestData {
// TODO remove in a future version
if (!fs.existsSync(cwd)) {
throw new Error(`As of Sapper 0.21, the routes/ directory should become src/routes/`);
}
const components: PageComponent[] = [];
const pages: Page[] = [];
const server_routes: ServerRoute[] = [];

View File

@@ -30,7 +30,18 @@ export function create_serviceworker_manifest({ manifest_data, client_files }: {
manifest_data: ManifestData;
client_files: string[];
}) {
const files = glob('**', { cwd: locations.static(), filesOnly: true });
let files;
// TODO remove in a future version
if (fs.existsSync(locations.static())) {
files = glob('**', { cwd: locations.static(), filesOnly: true });
} else {
if (fs.existsSync('assets')) {
throw new Error(`As of Sapper 0.21, the assets/ directory should become static/`);
}
files = [];
}
let code = `
// This file is generated by Sapper — do not edit it!

17
src/core/read_template.ts Normal file
View File

@@ -0,0 +1,17 @@
import * as fs from 'fs';
import { locations } from '../config';
export default function read_template() {
try {
return fs.readFileSync(`${locations.src()}/template.html`, 'utf-8');
} catch (err) {
if (fs.existsSync(`app/template.html`)) {
throw new Error(`As of Sapper 0.21, the default folder structure has been changed:
app/ --> src/
routes/ --> src/routes/
assets/ --> static/`);
}
throw err;
}
}

View File

@@ -8,6 +8,7 @@ import fetch from 'node-fetch';
import { lookup } from './middleware/mime';
import { locations, dev } from './config';
import sourceMapSupport from 'source-map-support';
import read_template from './core/read_template';
sourceMapSupport.install();
@@ -288,8 +289,8 @@ function get_page_handler(
: (assets => () => assets)(JSON.parse(fs.readFileSync(path.join(output, 'build.json'), 'utf-8')));
const template = dev()
? () => fs.readFileSync(`${locations.src()}/template.html`, 'utf-8')
: (str => () => str)(fs.readFileSync(`${locations.dest()}/template.html`, 'utf-8'));
? () => read_template()
: (str => () => str)(read_template());
const { server_routes, pages } = manifest;
const error_route = manifest.error;

View File

@@ -1,5 +1,5 @@
// This file is generated by Sapper — do not edit it!
export const timestamp = 1537372892007;
export const timestamp = 1537382276284;
export const files = [
"favicon.png",

View File

@@ -0,0 +1,77 @@
const webpack = require('webpack');
const config = require('../../config/webpack.js');
const sapper_pkg = require('../../package.json');
const mode = process.env.NODE_ENV;
const isDev = mode === 'development';
module.exports = {
client: {
entry: config.client.entry(),
output: config.client.output(),
resolve: {
extensions: ['.js', '.html']
},
module: {
rules: [
{
test: /\.html$/,
exclude: /node_modules/,
use: {
loader: 'svelte-loader',
options: {
hydratable: true,
cascade: false,
store: true
}
}
}
]
},
mode,
plugins: [
isDev && new webpack.HotModuleReplacementPlugin()
].filter(Boolean),
devtool: isDev && 'inline-source-map'
},
server: {
entry: config.server.entry(),
output: config.server.output(),
target: 'node',
resolve: {
extensions: ['.js', '.html']
},
externals: [].concat(
Object.keys(sapper_pkg.dependencies),
Object.keys(sapper_pkg.devDependencies)
),
module: {
rules: [
{
test: /\.html$/,
exclude: /node_modules/,
use: {
loader: 'svelte-loader',
options: {
css: false,
cascade: false,
store: true,
generate: 'ssr'
}
}
}
]
},
mode,
performance: {
hints: false // it doesn't matter if server.js is large
}
},
serviceworker: {
entry: config.serviceworker.entry(),
output: config.serviceworker.output(),
mode
}
};

View File

@@ -1,34 +0,0 @@
const config = require('../../../config/webpack.js');
const webpack = require('webpack');
const mode = process.env.NODE_ENV;
const isDev = mode === 'development';
module.exports = {
entry: config.client.entry(),
output: config.client.output(),
resolve: {
extensions: ['.js', '.html']
},
module: {
rules: [
{
test: /\.html$/,
exclude: /node_modules/,
use: {
loader: 'svelte-loader',
options: {
hydratable: true,
cascade: false,
store: true
}
}
}
]
},
mode,
plugins: [
isDev && new webpack.HotModuleReplacementPlugin()
].filter(Boolean),
devtool: isDev && 'inline-source-map'
};

View File

@@ -1,36 +0,0 @@
const config = require('../../../config/webpack.js');
const sapper_pkg = require('../../../package.json');
module.exports = {
entry: config.server.entry(),
output: config.server.output(),
target: 'node',
resolve: {
extensions: ['.js', '.html']
},
externals: [].concat(
Object.keys(sapper_pkg.dependencies),
Object.keys(sapper_pkg.devDependencies)
),
module: {
rules: [
{
test: /\.html$/,
exclude: /node_modules/,
use: {
loader: 'svelte-loader',
options: {
css: false,
cascade: false,
store: true,
generate: 'ssr'
}
}
}
]
},
mode: process.env.NODE_ENV,
performance: {
hints: false // it doesn't matter if server.js is large
}
};

View File

@@ -1,7 +0,0 @@
const config = require('../../../config/webpack.js');
module.exports = {
entry: config.serviceworker.entry(),
output: config.serviceworker.output(),
mode: process.env.NODE_ENV
};