From 273823dfd7b6b23b90b0c39536aa875dd0682238 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Wed, 19 Sep 2018 16:27:30 -0400 Subject: [PATCH] move config into single file, add errors to help people migrate --- src/api/build.ts | 7 +- src/api/dev.ts | 5 +- src/api/interfaces.ts | 2 +- src/cli/utils/validate_bundler.ts | 25 ++++++- src/core/create_compilers/RollupCompiler.ts | 61 +++++++++------- src/core/create_compilers/WebpackCompiler.ts | 5 +- src/core/create_compilers/index.ts | 29 +++++--- src/core/create_manifest_data.ts | 5 ++ src/core/create_manifests.ts | 13 +++- src/core/read_template.ts | 17 +++++ src/middleware.ts | 5 +- test/app/src/manifest/service-worker.js | 2 +- test/app/webpack.config.js | 77 ++++++++++++++++++++ test/app/webpack/client.config.js | 34 --------- test/app/webpack/server.config.js | 36 --------- test/app/webpack/service-worker.config.js | 7 -- 16 files changed, 198 insertions(+), 132 deletions(-) create mode 100644 src/core/read_template.ts create mode 100644 test/app/webpack.config.js delete mode 100644 test/app/webpack/client.config.js delete mode 100644 test/app/webpack/server.config.js delete mode 100644 test/app/webpack/service-worker.config.js diff --git a/src/api/build.ts b/src/api/build.ts index 18df442..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; @@ -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', { @@ -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(); diff --git a/src/api/dev.ts b/src/api/dev.ts index ff549dd..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); @@ -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 `); 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 = ''; 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/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/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 212bd9b..7616974 100644 --- a/src/core/create_manifests.ts +++ b/src/core/create_manifests.ts @@ -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! 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/middleware.ts b/src/middleware.ts index c8a5ef5..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.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; diff --git a/test/app/src/manifest/service-worker.js b/test/app/src/manifest/service-worker.js index 01084bf..73d0279 100644 --- a/test/app/src/manifest/service-worker.js +++ b/test/app/src/manifest/service-worker.js @@ -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", diff --git a/test/app/webpack.config.js b/test/app/webpack.config.js new file mode 100644 index 0000000..9594b49 --- /dev/null +++ b/test/app/webpack.config.js @@ -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 + } +}; \ No newline at end of file diff --git a/test/app/webpack/client.config.js b/test/app/webpack/client.config.js deleted file mode 100644 index 9acbe9b..0000000 --- a/test/app/webpack/client.config.js +++ /dev/null @@ -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' -}; \ No newline at end of file diff --git a/test/app/webpack/server.config.js b/test/app/webpack/server.config.js deleted file mode 100644 index b88d1a6..0000000 --- a/test/app/webpack/server.config.js +++ /dev/null @@ -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 - } -}; \ No newline at end of file diff --git a/test/app/webpack/service-worker.config.js b/test/app/webpack/service-worker.config.js deleted file mode 100644 index aacb312..0000000 --- a/test/app/webpack/service-worker.config.js +++ /dev/null @@ -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 -}; \ No newline at end of file