diff --git a/.gitignore b/.gitignore index 8ac5504..c80bb4a 100644 --- a/.gitignore +++ b/.gitignore @@ -4,7 +4,7 @@ yarn-error.log node_modules cypress/screenshots test/app/.sapper -test/app/app/manifest +test/app/src/manifest test/app/export test/app/build sapper diff --git a/src/api/build.ts b/src/api/build.ts index 4315640..ec2e1d3 100644 --- a/src/api/build.ts +++ b/src/api/build.ts @@ -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; @@ -39,9 +40,9 @@ async function execute(emitter: EventEmitter, opts: Opts, dirs: Dirs) { mkdirp.sync(`${dirs.dest}/client`); copy_shimport(dirs.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(`${dirs.app}/template.html`, 'utf-8'); + const template = read_template(); // remove this in a future version if (template.indexOf('%sapper.base%') === -1) { @@ -54,10 +55,10 @@ async function execute(emitter: EventEmitter, opts: Opts, dirs: Dirs) { const manifest_data = create_manifest_data(); - // create app/manifest/client.js and app/manifest/server.js + // 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', { @@ -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(); @@ -79,7 +80,7 @@ async function execute(emitter: EventEmitter, opts: Opts, dirs: Dirs) { // TODO duration/warnings result: client_result }); - + client_result.to_json(manifest_data, dirs); build_info.legacy_assets = client_result.assets; delete process.env.SAPPER_LEGACY_BUILD; diff --git a/src/api/dev.ts b/src/api/dev.ts index b3f9e45..e8cb51b 100644 --- a/src/api/dev.ts +++ b/src/api/dev.ts @@ -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); @@ -23,7 +24,7 @@ export function dev(opts) { class Watcher extends EventEmitter { bundler: string; dirs: { - app: string; + src: string; dest: string; routes: string; rollup: string; @@ -53,7 +54,7 @@ class Watcher extends EventEmitter { } constructor({ - app = locations.app(), + src = locations.src(), dest = locations.dest(), routes = locations.routes(), 'dev-port': dev_port, @@ -65,7 +66,7 @@ class Watcher extends EventEmitter { rollup = 'rollup', port = +process.env.PORT }: { - app: string, + src: string, dest: string, routes: string, 'dev-port': number, @@ -80,7 +81,7 @@ class Watcher extends EventEmitter { super(); this.bundler = validate_bundler(bundler); - this.dirs = { app, dest, routes, webpack, rollup }; + this.dirs = { src, dest, routes, webpack, rollup }; this.port = port; this.closed = false; @@ -100,7 +101,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(); 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 `); error.code = `missing-sapper-base`; @@ -175,7 +176,7 @@ class Watcher extends EventEmitter { } ), - fs.watch(`${locations.app()}/template.html`, () => { + fs.watch(`${locations.src()}/template.html`, () => { this.dev_server.send({ action: 'reload' }); @@ -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 = ''; diff --git a/src/api/export.ts b/src/api/export.ts index d4deaca..e136398 100644 --- a/src/api/export.ts +++ b/src/api/export.ts @@ -13,6 +13,7 @@ import * as events from './interfaces'; type Opts = { build: string, dest: string, + static: string, basepath?: string, timeout: number | false }; @@ -46,7 +47,7 @@ async function execute(emitter: EventEmitter, opts: Opts) { // Prep output directory sander.rimrafSync(export_dir); - sander.copydirSync('assets').to(export_dir); + sander.copydirSync(opts.static).to(export_dir); sander.copydirSync(opts.build, 'client').to(export_dir, 'client'); if (sander.existsSync(opts.build, 'service-worker.js')) { diff --git a/src/api/interfaces.ts b/src/api/interfaces.ts index 1bd8e74..34d8ded 100644 --- a/src/api/interfaces.ts +++ b/src/api/interfaces.ts @@ -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; diff --git a/src/cli/build.ts b/src/cli/build.ts index 362bf99..5f47eb1 100644 --- a/src/cli/build.ts +++ b/src/cli/build.ts @@ -18,7 +18,7 @@ export function build(opts: { bundler?: string, legacy?: boolean }) { bundler }, { dest: locations.dest(), - app: locations.app(), + src: locations.src(), routes: locations.routes(), webpack: 'webpack', rollup: 'rollup' diff --git a/src/cli/export.ts b/src/cli/export.ts index 51a0246..214228e 100644 --- a/src/cli/export.ts +++ b/src/cli/export.ts @@ -15,6 +15,7 @@ export function exporter(export_dir: string, { try { const emitter = _exporter({ build: locations.dest(), + static: locations.static(), dest: export_dir, basepath, timeout diff --git a/src/cli/utils/validate_bundler.ts b/src/cli/utils/validate_bundler.ts index 38ea550..d3df428 100644 --- a/src/cli/utils/validate_bundler.ts +++ b/src/cli/utils/validate_bundler.ts @@ -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`); } \ No newline at end of file diff --git a/src/config.ts b/src/config.ts index 15aef1b..37074a4 100644 --- a/src/config.ts +++ b/src/config.ts @@ -4,7 +4,8 @@ export const dev = () => process.env.NODE_ENV !== 'production'; 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'), + src: () => path.resolve(process.env.SAPPER_BASE || '', process.env.SAPPER_SRC || 'src'), + static: () => path.resolve(process.env.SAPPER_BASE || '', process.env.SAPPER_STATIC || 'static'), + routes: () => path.resolve(process.env.SAPPER_BASE || '', process.env.SAPPER_ROUTES || 'src/routes'), dest: () => path.resolve(process.env.SAPPER_BASE || '', process.env.SAPPER_DEST || `.sapper/${dev() ? 'dev' : 'prod'}`) }; \ No newline at end of file diff --git a/src/core/create_compilers/RollupCompiler.ts b/src/core/create_compilers/RollupCompiler.ts index 2a01cd0..b900872 100644 --- a/src/core/create_compilers/RollupCompiler.ts +++ b/src/core/create_compilers/RollupCompiler.ts @@ -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; + } } \ No newline at end of file diff --git a/src/core/create_compilers/WebpackCompiler.ts b/src/core/create_compilers/WebpackCompiler.ts index f4d58bc..0463ff9 100644 --- a/src/core/create_compilers/WebpackCompiler.ts +++ b/src/core/create_compilers/WebpackCompiler.ts @@ -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) { diff --git a/src/core/create_compilers/index.ts b/src/core/create_compilers/index.ts index cf9c13a..c93ab5c 100644 --- a/src/core/create_compilers/index.ts +++ b/src/core/create_compilers/index.ts @@ -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 { 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`); + } } \ No newline at end of file diff --git a/src/core/create_manifest_data.ts b/src/core/create_manifest_data.ts index fd29a9b..67ff485 100644 --- a/src/core/create_manifest_data.ts +++ b/src/core/create_manifest_data.ts @@ -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[] = []; diff --git a/src/core/create_manifests.ts b/src/core/create_manifests.ts index 4b3d1f5..c7c52d1 100644 --- a/src/core/create_manifests.ts +++ b/src/core/create_manifests.ts @@ -10,7 +10,7 @@ export function create_main_manifests({ bundler, manifest_data, dev_port }: { manifest_data: ManifestData; dev_port?: number; }) { - const manifest_dir = path.join(locations.app(), 'manifest'); + const manifest_dir = path.join(locations.src(), 'manifest'); if (!fs.existsSync(manifest_dir)) fs.mkdirSync(manifest_dir); const path_to_routes = path.relative(manifest_dir, locations.routes()); @@ -30,20 +30,32 @@ export function create_serviceworker_manifest({ manifest_data, client_files }: { manifest_data: ManifestData; client_files: string[]; }) { - const assets = glob('**', { cwd: 'assets', 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! export const timestamp = ${Date.now()}; - export const assets = [\n\t${assets.map((x: string) => stringify(x)).join(',\n\t')}\n]; + export const files = [\n\t${files.map((x: string) => stringify(x)).join(',\n\t')}\n]; + export { files as assets }; // legacy export const shell = [\n\t${client_files.map((x: string) => stringify(x)).join(',\n\t')}\n]; export const routes = [\n\t${manifest_data.pages.map((r: Page) => `{ pattern: ${r.pattern} }`).join(',\n\t')}\n]; `.replace(/^\t\t/gm, '').trim(); - write_if_changed(`${locations.app()}/manifest/service-worker.js`, code); + write_if_changed(`${locations.src()}/manifest/service-worker.js`, code); } function generate_client( diff --git a/src/core/read_template.ts b/src/core/read_template.ts new file mode 100644 index 0000000..ee87cfa --- /dev/null +++ b/src/core/read_template.ts @@ -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; + } +} \ No newline at end of file diff --git a/src/interfaces.ts b/src/interfaces.ts index 313da87..a123903 100644 --- a/src/interfaces.ts +++ b/src/interfaces.ts @@ -43,7 +43,7 @@ export type ServerRoute = { export type Dirs = { dest: string, - app: string, + src: string, routes: string, webpack: string, rollup: string diff --git a/src/middleware.ts b/src/middleware.ts index 705a04d..4d37ec8 100644 --- a/src/middleware.ts +++ b/src/middleware.ts @@ -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.app()}/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; diff --git a/src/rollup.ts b/src/rollup.ts index 1240e21..3910f93 100644 --- a/src/rollup.ts +++ b/src/rollup.ts @@ -5,7 +5,7 @@ export default { client: { input: () => { - return `${locations.app()}/client.js` + return `${locations.src()}/client.js` }, output: () => { @@ -24,7 +24,7 @@ export default { server: { input: () => { - return `${locations.app()}/server.js` + return `${locations.src()}/server.js` }, output: () => { @@ -38,7 +38,7 @@ export default { serviceworker: { input: () => { - return `${locations.app()}/service-worker.js`; + return `${locations.src()}/service-worker.js`; }, output: () => { diff --git a/src/webpack.ts b/src/webpack.ts index 28fefec..6fc0ccb 100644 --- a/src/webpack.ts +++ b/src/webpack.ts @@ -6,7 +6,7 @@ export default { client: { entry: () => { return { - main: `${locations.app()}/client` + main: `${locations.src()}/client` }; }, @@ -23,7 +23,7 @@ export default { server: { entry: () => { return { - server: `${locations.app()}/server` + server: `${locations.src()}/server` }; }, @@ -40,7 +40,7 @@ export default { serviceworker: { entry: () => { return { - 'service-worker': `${locations.app()}/service-worker` + 'service-worker': `${locations.src()}/service-worker` }; }, diff --git a/test/app/app/client.js b/test/app/src/client.js similarity index 100% rename from test/app/app/client.js rename to test/app/src/client.js diff --git a/test/app/routes/[x]/[y]/[z].html b/test/app/src/routes/[x]/[y]/[z].html similarity index 100% rename from test/app/routes/[x]/[y]/[z].html rename to test/app/src/routes/[x]/[y]/[z].html diff --git a/test/app/routes/[x]/[y]/_layout.html b/test/app/src/routes/[x]/[y]/_layout.html similarity index 100% rename from test/app/routes/[x]/[y]/_layout.html rename to test/app/src/routes/[x]/[y]/_layout.html diff --git a/test/app/routes/[x]/_counts.js b/test/app/src/routes/[x]/_counts.js similarity index 100% rename from test/app/routes/[x]/_counts.js rename to test/app/src/routes/[x]/_counts.js diff --git a/test/app/routes/_error.html b/test/app/src/routes/_error.html similarity index 100% rename from test/app/routes/_error.html rename to test/app/src/routes/_error.html diff --git a/test/app/routes/_layout.html b/test/app/src/routes/_layout.html similarity index 100% rename from test/app/routes/_layout.html rename to test/app/src/routes/_layout.html diff --git a/test/app/routes/about.html b/test/app/src/routes/about.html similarity index 86% rename from test/app/routes/about.html rename to test/app/src/routes/about.html index 65ebb57..949ad84 100644 --- a/test/app/routes/about.html +++ b/test/app/src/routes/about.html @@ -9,7 +9,7 @@