From bf8385091f34d6606f13f60f0c15a253a42dc67a Mon Sep 17 00:00:00 2001 From: Brian Takita Date: Tue, 20 Jul 2021 00:15:20 -0400 Subject: [PATCH] Sapper with hash based routing More similar to SvelteKit: ESM + ssr: global fetch,Request,Response,Headers https://github.com/sveltejs/sapper/issues/1791 --- .gitignore | 4 +- config/rollup.js | 3 +- config/webpack.js | 3 +- package.json | 100 ++++++++++-------- rollup.config.js | 45 +------- runtime/internal/shared.js | 3 + runtime/internal/shared.mjs | 5 - runtime/src/app/app.ts | 12 +-- runtime/src/app/goto/index.ts | 4 +- runtime/src/app/prefetch/index.ts | 10 +- runtime/src/app/prefetchRoutes/index.ts | 2 +- runtime/src/app/router/index.ts | 6 +- runtime/src/app/types.ts | 2 +- runtime/src/server/index.ts | 3 +- runtime/src/server/install-fetch.ts | 24 +++++ .../src/server/middleware/get_page_handler.ts | 5 +- .../middleware/get_server_route_handler.ts | 1 + runtime/src/server/middleware/index.ts | 9 +- .../src/server/middleware/replace_async.ts | 54 ++++++++++ .../server/middleware/sourcemap_stacktrace.ts | 20 ++-- sapper | 2 - site/rollup.config.js | 16 ++- src/api.ts | 8 +- src/api/build.ts | 24 ++--- src/api/dev.ts | 46 ++++---- src/api/export.ts | 27 +++-- src/api/find_page.ts | 2 +- src/api/utils/copy_runtime.ts | 43 ++++---- src/api/utils/copy_shimport.ts | 18 +++- src/cli.ts | 25 +++-- src/config/rollup.ts | 18 +++- src/config/webpack.ts | 2 +- src/core.ts | 6 +- src/core/create_app.ts | 12 +-- src/core/create_compilers/RollupCompiler.ts | 88 ++++++++------- src/core/create_compilers/RollupResult.ts | 18 ++-- src/core/create_compilers/WebpackCompiler.ts | 50 ++++++++- src/core/create_compilers/WebpackResult.ts | 6 +- src/core/create_compilers/config_extname.ts | 26 +++++ src/core/create_compilers/index.ts | 18 ++-- src/core/create_compilers/interfaces.ts | 2 +- src/core/create_compilers/require-relative.ts | 33 ++++++ .../rollup-dependency-tree.ts | 67 ++++++++++++ src/core/create_manifest_data.ts | 4 +- src/interfaces.ts | 2 +- .../sapper-dev-client.ts | 15 ++- tsconfig.json | 5 +- tsconfig.runtime.json | 30 ++++++ tsconfig.src.json | 30 ++++++ 49 files changed, 665 insertions(+), 293 deletions(-) create mode 100644 runtime/internal/shared.js delete mode 100644 runtime/internal/shared.mjs create mode 100644 runtime/src/server/install-fetch.ts create mode 100644 runtime/src/server/middleware/replace_async.ts delete mode 100755 sapper create mode 100644 src/core/create_compilers/config_extname.ts create mode 100644 src/core/create_compilers/require-relative.ts create mode 100644 src/core/create_compilers/rollup-dependency-tree.ts rename sapper-dev-client.js => src/sapper-dev-client.ts (84%) create mode 100644 tsconfig.runtime.json create mode 100644 tsconfig.src.json diff --git a/.gitignore b/.gitignore index ef36afff7..d9c96b529 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,8 @@ /test/**/node_modules __sapper__ dist +/runtime/app.js /runtime/app.mjs +/runtime/server.js /runtime/server.mjs -/.vscode/settings.json \ No newline at end of file +/.vscode/settings.json diff --git a/config/rollup.js b/config/rollup.js index 3b5c8be48..64c44c984 100644 --- a/config/rollup.js +++ b/config/rollup.js @@ -1 +1,2 @@ -module.exports = require('../dist/rollup.js'); \ No newline at end of file +import { default as rollup } from '../dist/config/rollup.js'; +export default rollup; diff --git a/config/webpack.js b/config/webpack.js index 15b21cfdf..da7025fc1 100644 --- a/config/webpack.js +++ b/config/webpack.js @@ -1 +1,2 @@ -module.exports = require('../dist/webpack.js'); \ No newline at end of file +import { default as webpack } from '../dist/config/webpack.js'; +export default webpack; diff --git a/package.json b/package.json index 8dfe83195..91b9f9128 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,19 @@ "version": "0.29.1", "description": "The next small thing in web development, powered by Svelte", "bin": { - "sapper": "./sapper" + "sapper": "./dist/cli.js" + }, + "type": "module", + "exports": { + "./package.json": { + "import": "./package.json" + }, + "./config/rollup.js": { + "import": "./config/rollup.js" + }, + "./config/webpack.js": { + "import": "./config/webpack.js" + } }, "files": [ "*.js", @@ -20,67 +32,67 @@ }, "dependencies": { "html-minifier": "^4.0.0", - "http-link-header": "^1.0.2", + "http-link-header": "^1.0.3", "shimport": "^2.0.5", - "source-map": "^0.6.1", - "sourcemap-codec": "^1.4.6", + "source-map": "^0.7.3", + "sourcemap-codec": "^1.4.8", "string-hash": "^1.1.3" }, "devDependencies": { - "@rollup/plugin-commonjs": "^13.0.0", + "@rollup/plugin-commonjs": "^20.0.0", "@rollup/plugin-json": "^4.1.0", - "@rollup/plugin-node-resolve": "^8.0.1", - "@rollup/plugin-replace": "^2.3.3", + "@rollup/plugin-node-resolve": "^13.0.4", + "@rollup/plugin-replace": "^3.0.0", + "@rollup/plugin-typescript": "^8.2.5", "@sveltejs/eslint-config": "github:sveltejs/eslint-config#v5.4.0", - "@types/cookie": "^0.4.0", - "@types/mocha": "^8.0.0", - "@types/node": "^10.0.0", - "@types/node-fetch": "^2.5.7", - "@types/puppeteer": "^3.0.1", - "@typescript-eslint/eslint-plugin": "^4.3.0", - "@typescript-eslint/parser": "^4.3.0", - "cheap-watch": "^1.0.2", + "@types/cookie": "^0.4.1", + "@types/mocha": "^9.0.0", + "@types/node": "^16.4.9", + "@types/node-fetch": "^2.5.12", + "@types/puppeteer": "^5.4.4", + "@typescript-eslint/eslint-plugin": "^4.28.5", + "@typescript-eslint/parser": "^4.28.5", + "cheap-watch": "^1.0.3", "cookie": "^0.4.1", - "cross-env": "^7.0.2", - "devalue": "^2.0.0", - "eslint": "^7.10.0", - "eslint-import-resolver-typescript": "^2.2.0", - "eslint-plugin-import": "^2.22.1", - "eslint-plugin-svelte3": "^2.7.3", - "kleur": "^4.0.0", - "mime": "^2.4.4", - "mocha": "^8.0.0", - "node-fetch": "^2.6.0", + "cross-env": "^7.0.3", + "devalue": "^2.0.1", + "eslint": "^7.32.0", + "eslint-import-resolver-typescript": "^2.4.0", + "eslint-plugin-import": "^2.23.4", + "eslint-plugin-svelte3": "^3.2.0", + "import-meta-resolve": "^1.1.1", + "kleur": "^4.1.4", + "mime": "^2.5.2", + "mocha": "^9.0.3", + "node-fetch": "^2.6.1", "npm-run-all": "^4.1.5", "polka": "^0.5.2", - "port-authority": "^1.0.5", - "pretty-bytes": "^5.3.0", - "puppeteer": "^5.0.0", - "require-relative": "^0.8.7", + "port-authority": "^1.1.2", + "pretty-bytes": "^5.6.0", + "puppeteer": "^10.1.0", "rimraf": "^3.0.2", - "rollup": "^2.29.0", - "rollup-dependency-tree": "0.0.14", + "rollup": "^2.55.1", "rollup-plugin-css-chunks": "^2.0.3", - "rollup-plugin-svelte": "^5.1.0", - "rollup-plugin-typescript2": "^0.27.1", - "sade": "^1.6.1", - "sirv": "^1.0.0", - "svelte": "^3.24.0", - "svelte-loader": "^2.13.6", - "ts-node": "^8.10.2", - "tslib": "^2.0.0", - "typescript": "^4.0.3", - "webpack": "^4.38.0", - "webpack-format-messages": "^2.0.5", + "rollup-plugin-svelte": "^7.1.0", + "sade": "1.7.4", + "sirv": "^1.0.12", + "string-replace-async": "^2.0.0", + "svelte": "^3.41.0", + "svelte-loader": "^3.1.2", + "ts-node": "^10.1.0", + "tslib": "^2.3.0", + "typescript": "^4.3.5", + "webpack": "^5.47.1", + "webpack-format-messages": "3.0.1", "webpack-modules": "^1.0.0" }, "peerDependencies": { - "svelte": "^3.17.3" + "svelte": "^3.41.0" }, "scripts": { "test": "mocha --config=.mocharc.yml", "pretest": "npm run check", - "build": "rimraf dist && rollup -c", + "build": "rimraf dist && tsc -p tsconfig.src.json && rollup -c", "check": "rimraf dist && cross-env TS_CHECK_ENABLED=true rollup -c", "lint": "eslint '{src,runtime,test}/**/*.{ts,js,svelte}' && cd site && npm run lint", "prepare": "npm run build", diff --git a/rollup.config.js b/rollup.config.js index 6a4c23408..2ef45056c 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -1,29 +1,18 @@ import commonjs from '@rollup/plugin-commonjs'; -import json from '@rollup/plugin-json'; import resolve from '@rollup/plugin-node-resolve'; -import typescript from 'rollup-plugin-typescript2'; -import pkg from './package.json'; +import typescript from '@rollup/plugin-typescript'; import { builtinModules } from 'module'; -const external = [].concat( - Object.keys(pkg.dependencies), - Object.keys(process.binding('natives')), - 'sapper/core.js', - 'svelte/compiler' -); - const tsOptions = { - check: !!process.env.TS_CHECK_ENABLED, - tsconfigOverride: { - compilerOptions: { module: 'esnext' } - } + checkJs: !!process.env.TS_CHECK_ENABLED, + module: 'esnext' }; function template(kind, external) { return { input: `runtime/src/${kind}/index.ts`, output: { - file: `runtime/${kind}.mjs`, + file: `runtime/${kind}.js`, format: 'es', paths: id => id.replace('@sapper', '.') }, @@ -41,30 +30,4 @@ function template(kind, external) { export default [ template('app', id => /^(svelte\/?|@sapper\/)/.test(id)), template('server', id => /^(svelte\/?|@sapper\/)/.test(id) || builtinModules.includes(id)), - - { - input: [ - `src/api.ts`, - `src/cli.ts`, - `src/core.ts`, - `src/config/rollup.ts`, - `src/config/webpack.ts` - ], - output: { - dir: 'dist', - format: 'cjs', - interop: false, - sourcemap: true, - chunkFileNames: '[name].js' - }, - external, - plugins: [ - json(), - resolve({ - extensions: ['.mjs', '.js', '.ts'] - }), - commonjs(), - typescript(tsOptions) - ] - } ]; diff --git a/runtime/internal/shared.js b/runtime/internal/shared.js new file mode 100644 index 000000000..d1bf011d7 --- /dev/null +++ b/runtime/internal/shared.js @@ -0,0 +1,3 @@ +export const CONTEXT_KEY = {}; + +export const preload = () => ({}); diff --git a/runtime/internal/shared.mjs b/runtime/internal/shared.mjs deleted file mode 100644 index 718d67b83..000000000 --- a/runtime/internal/shared.mjs +++ /dev/null @@ -1,5 +0,0 @@ -import { writable } from 'svelte/store'; - -export const CONTEXT_KEY = {}; - -export const preload = () => ({}); \ No newline at end of file diff --git a/runtime/src/app/app.ts b/runtime/src/app/app.ts index 1054d99b8..ef45e58fa 100644 --- a/runtime/src/app/app.ts +++ b/runtime/src/app/app.ts @@ -5,13 +5,13 @@ import { init as init_router, load_current_page, select_target -} from './router'; -import { get_prefetched, start as start_prefetching } from './prefetch'; +} from './router/index.js'; +import { get_prefetched, start as start_prefetching } from './prefetch/index.js'; import { ErrorComponent, components, root_comp -} from '@sapper/internal/manifest-client'; +} from '@sapper/internal/manifest-client.js'; import { HydratedTarget, Target, @@ -19,10 +19,10 @@ import { Branch, Page, InitialData -} from './types'; +} from './types.js'; import { PageContext } from '@sapper/common'; -import goto from './goto'; -import { page_store } from './stores'; +import goto from './goto/index.js'; +import { page_store } from './stores/index.js'; declare const __SAPPER__; export const initial_data: InitialData = typeof __SAPPER__ !== 'undefined' && __SAPPER__; diff --git a/runtime/src/app/goto/index.ts b/runtime/src/app/goto/index.ts index daff2ffc4..c4e4424ce 100644 --- a/runtime/src/app/goto/index.ts +++ b/runtime/src/app/goto/index.ts @@ -1,5 +1,5 @@ -import { cid, history, navigate, select_target } from '../router'; -import { get_base_uri } from '../baseuri_helper'; +import { cid, history, navigate, select_target } from '../router/index.js'; +import { get_base_uri } from '../baseuri_helper.js'; export default function goto( href: string, diff --git a/runtime/src/app/prefetch/index.ts b/runtime/src/app/prefetch/index.ts index 9ba4b31b9..713318686 100644 --- a/runtime/src/app/prefetch/index.ts +++ b/runtime/src/app/prefetch/index.ts @@ -1,8 +1,8 @@ -import { hydrate_target } from '../app'; -import { select_target } from '../router'; -import find_anchor from '../router/find_anchor'; -import { HydratedTarget, Target } from '../types'; -import { get_base_uri } from '../baseuri_helper'; +import { hydrate_target } from '../app.js'; +import { select_target } from '../router/index.js'; +import find_anchor from '../router/find_anchor.js'; +import { HydratedTarget, Target } from '../types.js'; +import { get_base_uri } from '../baseuri_helper.js'; let prefetching: { href: string; diff --git a/runtime/src/app/prefetchRoutes/index.ts b/runtime/src/app/prefetchRoutes/index.ts index 4650e8656..92011ba06 100644 --- a/runtime/src/app/prefetchRoutes/index.ts +++ b/runtime/src/app/prefetchRoutes/index.ts @@ -1,4 +1,4 @@ -import { components, routes } from '@sapper/internal/manifest-client'; +import { components, routes } from '@sapper/internal/manifest-client.js'; export default function prefetchRoutes(pathnames: string[]): Promise { return routes diff --git a/runtime/src/app/router/index.ts b/runtime/src/app/router/index.ts index 62082c4bf..2b07c1234 100644 --- a/runtime/src/app/router/index.ts +++ b/runtime/src/app/router/index.ts @@ -1,12 +1,12 @@ import { ScrollPosition, Target -} from '../types'; +} from '../types.js'; import { ignore, routes -} from '@sapper/internal/manifest-client'; -import find_anchor from './find_anchor'; +} from '@sapper/internal/manifest-client.js'; +import find_anchor from './find_anchor.js'; import { Page, Query } from '@sapper/common'; export let uid = 1; diff --git a/runtime/src/app/types.ts b/runtime/src/app/types.ts index 1c97ea728..a8edef971 100644 --- a/runtime/src/app/types.ts +++ b/runtime/src/app/types.ts @@ -1,4 +1,4 @@ -import { DOMComponentConstructor, Route } from '@sapper/internal/manifest-client'; +import { DOMComponentConstructor, Route } from '@sapper/internal/manifest-client.js'; export interface HydratedTarget { redirect?: Redirect; diff --git a/runtime/src/server/index.ts b/runtime/src/server/index.ts index 09e422fa0..cd88e950c 100644 --- a/runtime/src/server/index.ts +++ b/runtime/src/server/index.ts @@ -1 +1,2 @@ -export { default as middleware } from './middleware/index'; +import './install-fetch.js'; +export { default as middleware } from './middleware/index.js'; diff --git a/runtime/src/server/install-fetch.ts b/runtime/src/server/install-fetch.ts new file mode 100644 index 000000000..4b0153a7e --- /dev/null +++ b/runtime/src/server/install-fetch.ts @@ -0,0 +1,24 @@ +import fetch, { Response, Request, Headers } from 'node-fetch'; + +// exported for dev, prerender, and preview +export function __fetch_polyfill() { + Object.defineProperties(globalThis, { + fetch: { + enumerable: true, + value: fetch + }, + Response: { + enumerable: true, + value: Response + }, + Request: { + enumerable: true, + value: Request + }, + Headers: { + enumerable: true, + value: Headers + } + }); +} +__fetch_polyfill(); diff --git a/runtime/src/server/middleware/get_page_handler.ts b/runtime/src/server/middleware/get_page_handler.ts index 8ff69e425..504873808 100644 --- a/runtime/src/server/middleware/get_page_handler.ts +++ b/runtime/src/server/middleware/get_page_handler.ts @@ -1,7 +1,8 @@ import { writable } from 'svelte/store'; import fs from 'fs'; import path from 'path'; -import { parse } from 'cookie'; +import * as cookie from 'cookie'; +const { parse } = cookie; import devalue from 'devalue'; import fetch from 'node-fetch'; import URL from 'url'; @@ -259,7 +260,7 @@ export function get_page_handler( }); if (error instanceof Error && error.stack) { - error.stack = sourcemap_stacktrace(error.stack); + error.stack = await sourcemap_stacktrace(error.stack); } const pageContext: PageContext = { diff --git a/runtime/src/server/middleware/get_server_route_handler.ts b/runtime/src/server/middleware/get_server_route_handler.ts index 1aa30e64c..7693205d5 100644 --- a/runtime/src/server/middleware/get_server_route_handler.ts +++ b/runtime/src/server/middleware/get_server_route_handler.ts @@ -24,6 +24,7 @@ export function get_server_route_handler(routes: ServerRoute[]) { res.setHeader = function(name: string, value: string) { headers[name.toLowerCase()] = value; setHeader.apply(res, [name, value]); + return res; }; res.end = function(chunk?: any) { diff --git a/runtime/src/server/middleware/index.ts b/runtime/src/server/middleware/index.ts index f4278dcd9..532a746cd 100644 --- a/runtime/src/server/middleware/index.ts +++ b/runtime/src/server/middleware/index.ts @@ -1,6 +1,7 @@ import fs from 'fs'; import path from 'path'; -import mime from 'mime/lite'; +import * as mime from 'mime/lite.js'; +const { getType } = mime; import { Handler, SapperRequest, SapperResponse, build_dir, dev, manifest } from '@sapper/internal/manifest-server'; import { get_server_route_handler } from './get_server_route_handler'; import { get_page_handler } from './get_page_handler'; @@ -110,12 +111,12 @@ export function serve({ prefix, pathname, cache_control }: { ? (file: string) => fs.readFileSync(path.join(build_dir, file)) : (file: string) => (cache.has(file) ? cache : cache.set(file, fs.readFileSync(path.join(build_dir, file)))).get(file); - return (req: SapperRequest, res: SapperResponse, next: () => void) => { + return async (req: SapperRequest, res: SapperResponse, next: () => void) => { if (filter(req)) { - const type = mime.getType(req.path); + const type = getType(req.path); try { - const file = path.posix.normalize(decodeURIComponent(req.path)); + const file = path.posix.normalize(path.join('.', decodeURIComponent(req.path))); const data = read(file); res.setHeader('Content-Type', type); diff --git a/runtime/src/server/middleware/replace_async.ts b/runtime/src/server/middleware/replace_async.ts new file mode 100644 index 000000000..c9dd90e95 --- /dev/null +++ b/runtime/src/server/middleware/replace_async.ts @@ -0,0 +1,54 @@ +// TODO: Use string-replace-async packages when https://github.com/dsblv/string-replace-async/issues/10 is resolved +/* +The MIT License (MIT) + +Copyright (c) Dmitrii Sobolev (github.com/dsblv) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + */ +function replace_async( + string: string, + searchValue: { [Symbol.replace](string: string, replacer: (substring: string, ...args: any[]) => string): string; }, + replacer: (substring: string, ...args: any[]) => Promise +): Promise { + try { + if (typeof replacer === 'function') { + // 1. Run fake pass of `replace`, collect values from `replacer` calls + // 2. Resolve them with `Promise.all` + // 3. Run `replace` with resolved values + var values = [] + String.prototype.replace.call(string, searchValue, function () { + values.push(replacer.apply(undefined, arguments)) + return '' + }) + return Promise.all(values).then(function (resolvedValues) { + return String.prototype.replace.call(string, searchValue, function () { + return resolvedValues.shift() + }) + }) + } else { + return Promise.resolve( + String.prototype.replace.call(string, searchValue, replacer) + ) + } + } catch (error) { + return Promise.reject(error) + } +} +export { replace_async } diff --git a/runtime/src/server/middleware/sourcemap_stacktrace.ts b/runtime/src/server/middleware/sourcemap_stacktrace.ts index 8f9004d92..8352f9b10 100644 --- a/runtime/src/server/middleware/sourcemap_stacktrace.ts +++ b/runtime/src/server/middleware/sourcemap_stacktrace.ts @@ -1,6 +1,9 @@ import fs from 'fs'; import path from 'path'; -import { SourceMapConsumer, RawSourceMap } from 'source-map'; +import * as source_map from 'source-map'; +import type { RawSourceMap } from 'source-map'; +import { replace_async } from './replace_async'; +export const { SourceMapConsumer } = source_map; function get_sourcemap_url(contents: string) { const reversed = contents @@ -30,11 +33,11 @@ function get_file_contents(file_path: string) { } } -export function sourcemap_stacktrace(stack: string) { - const replace = (line: string) => - line.replace( +export async function sourcemap_stacktrace(stack: string) { + const replace = async (line: string) => + await replace_async(line, /^ {4}at (?:(.+?)\s+\()?(?:(.+?):(\d+)(?::(\d+))?)\)?/, - (input, var_name, file_path, line_num, column) => { + async (input, var_name, file_path, line_num, column) => { if (!file_path) return input; const contents = get_file_contents(file_path); @@ -70,7 +73,7 @@ export function sourcemap_stacktrace(stack: string) { return input; } - const consumer = new SourceMapConsumer(raw_sourcemap); + const consumer = await (new SourceMapConsumer(raw_sourcemap)); const pos = consumer.originalPositionFor({ line: Number(line_num), column: Number(column), @@ -89,8 +92,5 @@ export function sourcemap_stacktrace(stack: string) { file_cache.clear(); - return stack - .split('\n') - .map(replace) - .join('\n'); + return (await Promise.all(stack.split('\n').map(replace))).join('\n'); } diff --git a/sapper b/sapper deleted file mode 100755 index e6f34305f..000000000 --- a/sapper +++ /dev/null @@ -1,2 +0,0 @@ -#!/usr/bin/env node -require('./dist/cli.js'); \ No newline at end of file diff --git a/site/rollup.config.js b/site/rollup.config.js index 19638d08b..298b56112 100644 --- a/site/rollup.config.js +++ b/site/rollup.config.js @@ -2,11 +2,17 @@ import babel from '@rollup/plugin-babel'; import commonjs from '@rollup/plugin-commonjs'; import replace from '@rollup/plugin-replace'; -import resolve from '@rollup/plugin-node-resolve'; +import node_resolve from '@rollup/plugin-node-resolve'; import svelte from 'rollup-plugin-svelte'; import { terser } from 'rollup-plugin-terser'; import config from 'sapper/config/rollup.js'; -import pkg from './package.json'; +import { resolve } from 'import-meta-resolve'; + +const pkg = JSON.parse( + (await readFile( + new URL(await resolve('../package.json', import.meta.url)).pathname + )).toString() +) const mode = process.env.NODE_ENV; const dev = mode === 'development'; @@ -32,7 +38,7 @@ export default { hydratable: true } }), - resolve({ + node_resolve({ browser: true, dedupe: ['svelte'] }), @@ -80,7 +86,7 @@ export default { }, emitCss: false }), - resolve({ + node_resolve({ dedupe: ['svelte'] }), commonjs() @@ -97,7 +103,7 @@ export default { input: config.serviceworker.input(), output: config.serviceworker.output(), plugins: [ - resolve(), + node_resolve(), replace({ 'process.browser': true, 'process.env.NODE_ENV': JSON.stringify(mode) diff --git a/src/api.ts b/src/api.ts index 4a1af8165..a8b4ca03c 100644 --- a/src/api.ts +++ b/src/api.ts @@ -1,4 +1,4 @@ -export { dev } from './api/dev'; -export { build } from './api/build'; -export { export } from './api/export'; -export { find_page } from './api/find_page'; +export { dev } from './api/dev.js'; +export { build } from './api/build.js'; +export { export as export } from './api/export.js'; +export { find_page } from './api/find_page.js'; diff --git a/src/api/build.ts b/src/api/build.ts index c9a32a097..f154b95d4 100644 --- a/src/api/build.ts +++ b/src/api/build.ts @@ -1,14 +1,14 @@ import * as fs from 'fs'; import * as path from 'path'; -import minify_html from './utils/minify_html'; -import { create_compilers, create_app, 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'; -import { copy_runtime } from './utils/copy_runtime'; -import { rimraf, mkdirp } from './utils/fs_utils'; +import minify_html from './utils/minify_html.js'; +import { create_compilers, create_app, create_manifest_data, create_serviceworker_manifest } from '../core.js'; +import { copy_shimport } from './utils/copy_shimport.js'; +import read_template from '../core/read_template.js'; +import { CompileResult } from '../core/create_compilers/interfaces.js'; +import { noop } from './utils/noop.js'; +import validate_bundler from './utils/validate_bundler.js'; +import { copy_runtime } from './utils/copy_runtime.js'; +import { rimraf, mkdirp } from './utils/fs_utils.js'; type Opts = { cwd?: string; @@ -51,11 +51,11 @@ export async function build({ rimraf(output); mkdirp(output); - copy_runtime(output); + await copy_runtime(output); rimraf(dest); mkdirp(`${dest}/client`); - copy_shimport(dest); + await copy_shimport(dest); // minify src/template.html // TODO compile this to a function? could be quicker than str.replace(...).replace(...).replace(...) @@ -65,7 +65,7 @@ export async function build({ const manifest_data = create_manifest_data(routes, ext); - // create src/node_modules/@sapper/app.mjs and server.mjs + // create src/node_modules/@sapper/app.js and server.js create_app({ bundler, manifest_data, diff --git a/src/api/dev.ts b/src/api/dev.ts index 1d89628c1..aa5701356 100644 --- a/src/api/dev.ts +++ b/src/api/dev.ts @@ -4,16 +4,17 @@ import * as http from 'http'; import * as child_process from 'child_process'; import * as ports from 'port-authority'; import { EventEmitter } from 'events'; -import { create_manifest_data, create_app, 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 validate_bundler from './utils/validate_bundler'; -import { copy_shimport } from './utils/copy_shimport'; -import { ManifestData, FatalEvent, ErrorEvent, ReadyEvent, InvalidEvent } from '../interfaces'; -import { noop } from './utils/noop'; -import { copy_runtime } from './utils/copy_runtime'; -import { rimraf, mkdirp } from './utils/fs_utils'; +import CheapWatch from 'cheap-watch'; +import { create_manifest_data, create_app, create_compilers, create_serviceworker_manifest } from '../core.js'; +import { Compiler, Compilers } from '../core/create_compilers/index.js'; +import { CompileResult } from '../core/create_compilers/interfaces.js'; +import Deferred from './utils/Deferred.js'; +import validate_bundler from './utils/validate_bundler.js'; +import { copy_shimport } from './utils/copy_shimport.js'; +import { ManifestData, FatalEvent, ErrorEvent, ReadyEvent, InvalidEvent } from '../interfaces.js'; +import { noop } from './utils/noop.js'; +import { copy_runtime } from './utils/copy_runtime.js'; +import { rimraf, mkdirp } from './utils/fs_utils.js'; type Opts = { cwd?: string; @@ -141,11 +142,11 @@ class Watcher extends EventEmitter { rimraf(output); mkdirp(output); - copy_runtime(output); + await copy_runtime(output); rimraf(dest); mkdirp(`${dest}/client`); - if (this.bundler === 'rollup') copy_shimport(dest); + if (this.bundler === 'rollup') await copy_shimport(dest); if (!this.dev_port) this.dev_port = await ports.find(10000); @@ -289,7 +290,7 @@ class Watcher extends EventEmitter { this.emit('stderr', chunk); }); - this.proc.on('message', message => { + this.proc.on('message', (message: ExportMessage) => { if (message.__sapper__ && message.event === 'basepath') { this.emit('basepath', { basepath: message.basepath @@ -493,17 +494,14 @@ function watch_dir( let watch: any; let closed = false; - import('cheap-watch').then(({ default: CheapWatch }) => { - if (closed) return; + if (closed) return; + watch = new CheapWatch({ dir, filter, debounce: 50 }) as CheapWatch; - watch = new CheapWatch({ dir, filter, debounce: 50 }); + watch.on('+', callback); - watch.on('+', callback); + watch.on('-', callback); - watch.on('-', callback); - - watch.init(); - }); + watch.init(); return { close: () => { @@ -512,3 +510,9 @@ function watch_dir( } }; } + +interface ExportMessage { + __sapper__: boolean; + event: string; + basepath: string; +} diff --git a/src/api/export.ts b/src/api/export.ts index b2748486f..a34e39ea2 100644 --- a/src/api/export.ts +++ b/src/api/export.ts @@ -5,13 +5,13 @@ import * as urllib from 'url'; import { promisify } from 'util'; import fetch from 'node-fetch'; import * as ports from 'port-authority'; -import { exportQueue, FetchOpts, FetchRet } from './utils/export_queue'; -import clean_html from './utils/clean_html'; -import minify_html from './utils/minify_html'; -import Deferred from './utils/Deferred'; -import { noop } from './utils/noop'; +import { exportQueue, FetchOpts, FetchRet } from './utils/export_queue.js'; +import clean_html from './utils/clean_html.js'; +import minify_html from './utils/minify_html.js'; +import Deferred from './utils/Deferred.js'; +import { noop } from './utils/noop.js'; import { parse as parseLinkHeader } from 'http-link-header'; -import { rimraf, copy, mkdirp } from './utils/fs_utils'; +import { rimraf, copy, mkdirp } from './utils/fs_utils.js'; const writeFile = promisify(fs.writeFile); @@ -157,7 +157,7 @@ async function _export({ } } - const buffer = Buffer.from(body); + const buffer = typeof body === 'string' ? Buffer.from(body) : body; onfile({ file, @@ -169,7 +169,7 @@ async function _export({ if (fs.existsSync(export_file)) return; mkdirp(path.dirname(export_file)); - return writeFile(export_file, buffer); + return writeFile(export_file, new Uint8Array(buffer)); } function handle(url: URL, fetchOpts: FetchOpts, addCallback: (url: URL) => void) { @@ -301,7 +301,7 @@ async function _export({ } }); - proc.on('message', message => { + proc.on('message', (message: ExportMessage) => { if (!message.__sapper__ || message.event !== 'file') return; queue.addSave(save(message.url, message.status, message.type, message.body)); }); @@ -335,3 +335,12 @@ async function _export({ }); }); } + +interface ExportMessage { + __sapper__: boolean; + event: string; + url: string; + status: number; + type: string; + body: string | ArrayBuffer; +} diff --git a/src/api/find_page.ts b/src/api/find_page.ts index 23a30a1c7..8482e6cfb 100644 --- a/src/api/find_page.ts +++ b/src/api/find_page.ts @@ -1,4 +1,4 @@ -import { create_manifest_data } from '../core'; +import { create_manifest_data } from '../core.js'; export function find_page(pathname: string, cwd = 'src/routes') { const { pages } = create_manifest_data(cwd); diff --git a/src/api/utils/copy_runtime.ts b/src/api/utils/copy_runtime.ts index 1c6f7f120..5f19473ae 100644 --- a/src/api/utils/copy_runtime.ts +++ b/src/api/utils/copy_runtime.ts @@ -1,22 +1,29 @@ -import * as fs from 'fs'; +import { readFile, writeFile } from 'fs/promises'; import * as path from 'path'; -import { mkdirp } from './fs_utils'; +import { mkdirp } from './fs_utils.js'; -const runtime = [ - 'index.d.ts', - 'app.mjs', - 'server.mjs', - 'internal/shared.mjs', - 'internal/layout.svelte', - 'internal/error.svelte' -].map(file => ({ - file, - source: fs.readFileSync(path.join(__dirname, `../runtime/${file}`), 'utf-8') -})); +const runtime = await Promise.all( + [ + 'index.d.ts', + 'app.js', + 'server.js', + 'internal/shared.js', + 'internal/layout.svelte', + 'internal/error.svelte' + ].map(async file => ({ + file, + source: await readFile( + path.join(path.dirname(new URL(import.meta.url).pathname), `../../../runtime/${file}`), + 'utf-8' + ) + })) +); -export function copy_runtime(output: string) { - runtime.forEach(({ file, source }) => { - mkdirp(path.dirname(`${output}/${file}`)); - fs.writeFileSync(`${output}/${file}`, source); - }); +export async function copy_runtime(output: string) { + await Promise.all( + runtime.map(async ({ file, source }) => { + mkdirp(path.dirname(`${output}/${file}`)); + await writeFile(`${output}/${file}`, source); + }) + ); } diff --git a/src/api/utils/copy_shimport.ts b/src/api/utils/copy_shimport.ts index 7af7018d2..d1365d145 100644 --- a/src/api/utils/copy_shimport.ts +++ b/src/api/utils/copy_shimport.ts @@ -1,9 +1,17 @@ -import * as fs from 'fs'; -import { version as shimport_version } from 'shimport/package.json'; +import { readFileSync } from 'fs'; +import { readFile, writeFile } from 'fs/promises' +import { resolve } from 'import-meta-resolve' +const shimport_version = JSON.parse( + (await readFile( + new URL(await resolve('shimport/package.json', import.meta.url)).pathname + )).toString() +).version; -export function copy_shimport(dest: string) { - fs.writeFileSync( +export async function copy_shimport(dest: string) { + await writeFile( `${dest}/client/shimport@${shimport_version}.js`, - fs.readFileSync(require.resolve('shimport/index.js')) + readFileSync( + new URL(await resolve('shimport/index.js', import.meta.url)).pathname + ) ); } diff --git a/src/cli.ts b/src/cli.ts index 9e961bfef..fc2645eab 100755 --- a/src/cli.ts +++ b/src/cli.ts @@ -1,10 +1,18 @@ -import * as fs from 'fs'; +import { writeFileSync } from 'fs'; +import { readFile } from 'fs/promises'; import * as path from 'path'; import sade from 'sade'; import colors from 'kleur'; -import * as pkg from '../package.json'; -import { elapsed, repeat, left_pad, format_milliseconds } from './utils'; -import { InvalidEvent, ErrorEvent, FatalEvent, BuildEvent, ReadyEvent } from './interfaces'; +import { resolve } from 'import-meta-resolve'; +import * as pb from 'pretty-bytes'; +import { elapsed, repeat, left_pad, format_milliseconds } from './utils.js'; +import { InvalidEvent, ErrorEvent, FatalEvent, BuildEvent, ReadyEvent } from './interfaces.js'; + +const pkg = JSON.parse( + (await readFile( + new URL(await resolve('../package.json', import.meta.url)).pathname + )).toString() +) const prog = sade('sapper').version(pkg.version); @@ -47,7 +55,7 @@ prog.command('dev') 'build-dir': string; ext: string; }) => { - const { dev } = await import('./api/dev'); + const { dev } = await import('./api/dev.js'); try { const watcher = dev({ @@ -173,7 +181,7 @@ prog.command('build [dest]') const launcher = path.resolve(dest, 'index.js'); - fs.writeFileSync(launcher, ` + writeFileSync(launcher, ` // generated by sapper build at ${new Date().toISOString()} process.env.NODE_ENV = process.env.NODE_ENV || 'production'; process.env.PORT = process.env.PORT || ${opts.port || 3000}; @@ -231,8 +239,7 @@ prog.command('export [dest]') console.error(`\n> Built in ${elapsed(start)}`); } - const { export: _export } = await import('./api/export'); - const { default: pb } = await import('pretty-bytes'); + const { export: _export } = await import('./api/export.js'); await _export({ cwd: opts.cwd, @@ -281,7 +288,7 @@ async function _build( dest: string, ext: string ) { - const { build } = await import('./api/build'); + const { build } = await import('./api/build.js'); await build({ bundler, diff --git a/src/config/rollup.ts b/src/config/rollup.ts index 427301533..11b87cbd2 100644 --- a/src/config/rollup.ts +++ b/src/config/rollup.ts @@ -1,5 +1,7 @@ -import { dev, src, dest } from './env'; +import { accessSync, constants } from 'fs'; import { InputOption, OutputOptions } from 'rollup'; +import path from 'path'; +import { dev, src, dest } from './env.js'; const sourcemap = dev ? 'inline' : false; @@ -35,7 +37,7 @@ export default { output: (): OutputOptions => { return { dir: `${dest}/server`, - format: 'cjs', + format: get_server_format(), sourcemap }; } @@ -55,3 +57,15 @@ export default { } } }; +function get_server_format() { + const cwd = '.' + try { + const input = path.resolve(cwd, 'rollup.config.mjs') + accessSync(input, constants.F_OK | constants.R_OK) + return 'es' + } catch (_e) { + const input = path.resolve(cwd, 'rollup.config.js') + accessSync(input, constants.F_OK | constants.R_OK) + return 'cjs' + } +} diff --git a/src/config/webpack.ts b/src/config/webpack.ts index a97418626..ae89dea19 100644 --- a/src/config/webpack.ts +++ b/src/config/webpack.ts @@ -1,4 +1,4 @@ -import { dev, src, dest } from './env'; +import { dev, src, dest } from './env.js'; export default { dev, diff --git a/src/core.ts b/src/core.ts index e8e5c5519..07fe62c8d 100644 --- a/src/core.ts +++ b/src/core.ts @@ -1,3 +1,3 @@ -export * from './core/create_app'; -export { default as create_compilers } from './core/create_compilers/index'; -export { default as create_manifest_data } from './core/create_manifest_data'; +export * from './core/create_app.js'; +export { default as create_compilers } from './core/create_compilers/index.js'; +export { default as create_manifest_data } from './core/create_manifest_data.js'; diff --git a/src/core/create_app.ts b/src/core/create_app.ts index 7a33bcb29..552c67c55 100644 --- a/src/core/create_app.ts +++ b/src/core/create_app.ts @@ -1,7 +1,7 @@ import * as fs from 'fs'; import * as path from 'path'; -import { posixify, stringify, walk, write_if_changed } from '../utils'; -import { Page, PageComponent, ManifestData } from '../interfaces'; +import { posixify, stringify, walk, write_if_changed } from '../utils.js'; +import { Page, PageComponent, ManifestData } from '../interfaces.js'; export function create_app({ bundler, @@ -33,8 +33,8 @@ export function create_app({ const app = generate_app(manifest_data, path_to_routes); - write_if_changed(`${output}/internal/manifest-client.mjs`, client_manifest); - write_if_changed(`${output}/internal/manifest-server.mjs`, server_manifest); + write_if_changed(`${output}/internal/manifest-client.js`, client_manifest); + write_if_changed(`${output}/internal/manifest-server.js`, server_manifest); write_if_changed(`${output}/internal/App.svelte`, app); } @@ -148,7 +148,7 @@ function generate_client_manifest( export const routes = ${routes}; ${dev ? `if (typeof window !== 'undefined') { - import(${stringify(posixify(path.resolve(__dirname, '../sapper-dev-client.js')))}).then(client => { + import(${stringify(posixify(path.resolve(path.dirname(new URL(import.meta.url).pathname), '../sapper-dev-client.js')))}).then(client => { client.connect(${dev_port}); }); }` : ''} @@ -263,7 +263,7 @@ function generate_app(manifest_data: ManifestData, path_to_routes: string) {