diff --git a/packages/next/next-server/server/next-server.ts b/packages/next/next-server/server/next-server.ts index ffb75b48a6374a2a46338e930ac50b1adf541187..ad61d2a64e1fcf33f0a4af0eb4fa3c7d9ffa6e6c 100644 --- a/packages/next/next-server/server/next-server.ts +++ b/packages/next/next-server/server/next-server.ts @@ -85,7 +85,7 @@ export default class Server { } private compression?: Middleware router: Router - private dynamicRoutes?: Array<{ page: string; match: RouteMatch }> + protected dynamicRoutes?: Array<{ page: string; match: RouteMatch }> public constructor({ dir = '.', @@ -166,7 +166,7 @@ export default class Server { }) } - private currentPhase(): string { + protected currentPhase(): string { return PHASE_PRODUCTION_SERVER } @@ -212,13 +212,13 @@ export default class Server { public async prepare(): Promise {} // Backwards compatibility - private async close(): Promise {} + protected async close(): Promise {} - private setImmutableAssetCacheControl(res: ServerResponse) { + protected setImmutableAssetCacheControl(res: ServerResponse) { res.setHeader('Cache-Control', 'public, max-age=31536000, immutable') } - private generateRoutes(): Route[] { + protected generateRoutes(): Route[] { const routes: Route[] = [ { match: route('/_next/static/:path*'), @@ -379,7 +379,7 @@ export default class Server { * Resolves path to resolver function * @param pathname path of request */ - private resolveApiRequest(pathname: string) { + protected async resolveApiRequest(pathname: string): Promise { return getPagePath( pathname, this.distDir, @@ -388,7 +388,7 @@ export default class Server { ) } - private generatePublicRoutes(): Route[] { + protected generatePublicRoutes(): Route[] { const routes: Route[] = [] const publicFiles = recursiveReadDirSync(this.publicDir) const serverBuildPath = join( @@ -414,7 +414,7 @@ export default class Server { return routes } - private getDynamicRoutes() { + protected getDynamicRoutes() { const manifest = require(this.pagesManifest) const dynamicRoutedPages = Object.keys(manifest).filter(isDynamicRoute) return getSortedRoutes(dynamicRoutedPages).map(page => ({ @@ -429,7 +429,7 @@ export default class Server { } } - private async run( + protected async run( req: IncomingMessage, res: ServerResponse, parsedUrl: UrlWithParsedQuery @@ -453,7 +453,7 @@ export default class Server { await this.render404(req, res, parsedUrl) } - private async sendHTML( + protected async sendHTML( req: IncomingMessage, res: ServerResponse, html: string @@ -848,7 +848,7 @@ export default class Server { return true } - private readBuildId(): string { + protected readBuildId(): string { const buildIdFile = join(this.distDir, BUILD_ID_FILE) try { return fs.readFileSync(buildIdFile, 'utf8').trim() diff --git a/packages/next/package.json b/packages/next/package.json index 4efb0ff4cfabca86b2db94e03c449dbd46a094e5..ecc7b31cdc595daf32ec6efc8e2f78a06ba2ddea 100644 --- a/packages/next/package.json +++ b/packages/next/package.json @@ -163,6 +163,8 @@ "@types/send": "0.14.4", "@types/styled-jsx": "2.2.8", "@types/text-table": "0.2.1", + "@types/webpack-dev-middleware": "2.0.3", + "@types/webpack-hot-middleware": "2.16.5", "@types/webpack-sources": "0.1.5", "@zeit/ncc": "0.18.5", "arg": "4.1.0", diff --git a/packages/next/server/error-debug.js b/packages/next/server/error-debug.tsx similarity index 63% rename from packages/next/server/error-debug.js rename to packages/next/server/error-debug.tsx index e4a11b8a65d5cac6476ec8a2e660fab0cf9dd67d..e188edd93e9f2a74d6e4effaf5d4a4e296668b86 100644 --- a/packages/next/server/error-debug.js +++ b/packages/next/server/error-debug.tsx @@ -1,23 +1,36 @@ import React from 'react' + import Head from '../next-server/lib/head' // This component is only rendered on the server side. -export default function ErrorDebug ({ error, info }) { +export default function ErrorDebug({ + error, + info, +}: { + error: Error + info: any +}) { return ( -
+
- +
) } -const StackTrace = ({ error: { name, message, stack }, info }) => ( +const StackTrace = ({ + error: { name, message, stack }, + info, +}: { + error: Error + info: any +}) => (
-
{message || name}
-
{stack}
- {info &&
{info.componentStack}
} +
{message || name}
+
{stack}
+ {info &&
{info.componentStack}
}
) @@ -33,7 +46,7 @@ export const styles = { top: 0, bottom: 0, zIndex: 9999, - color: '#000000' + color: '#000000', }, stack: { @@ -45,7 +58,7 @@ export const styles = { margin: 0, whiteSpace: 'pre-wrap', wordWrap: 'break-word', - marginTop: '16px' + marginTop: '16px', }, heading: { @@ -56,6 +69,6 @@ export const styles = { lineHeight: '28px', color: '#000000', marginBottom: '0px', - marginTop: '0px' - } + marginTop: '0px', + }, } diff --git a/packages/next/server/hot-reloader.js b/packages/next/server/hot-reloader.ts similarity index 78% rename from packages/next/server/hot-reloader.js rename to packages/next/server/hot-reloader.ts index 5c9c28099b838bad999f4baa40780891887be4e0..4533aa8d49f25213248d798a7f92e27d1010c84c 100644 --- a/packages/next/server/hot-reloader.js +++ b/packages/next/server/hot-reloader.ts @@ -1,37 +1,44 @@ -import { relative as relativePath, join, normalize, sep } from 'path' +import fs from 'fs' +import { IncomingMessage, ServerResponse } from 'http' +import { join, normalize, relative as relativePath, sep } from 'path' +import { promisify } from 'util' +import webpack from 'webpack' import WebpackDevMiddleware from 'webpack-dev-middleware' import WebpackHotMiddleware from 'webpack-hot-middleware' -import errorOverlayMiddleware from './lib/error-overlay-middleware' -import onDemandEntryHandler, { normalizePage } from './on-demand-entry-handler' -import webpack from 'webpack' + +import { createEntrypoints, createPagesMapping } from '../build/entries' +import { watchCompilers } from '../build/output' import getBaseWebpackConfig from '../build/webpack-config' +import { NEXT_PROJECT_ROOT_DIST_CLIENT } from '../lib/constants' +import { fileExists } from '../lib/file-exists' +import { recursiveDelete } from '../lib/recursive-delete' import { + BLOCKED_PAGES, + CLIENT_STATIC_FILES_RUNTIME_AMP, IS_BUNDLED_PAGE_REGEX, ROUTE_NAME_REGEX, - BLOCKED_PAGES, - CLIENT_STATIC_FILES_RUNTIME_AMP } from '../next-server/lib/constants' -import { NEXT_PROJECT_ROOT_DIST_CLIENT } from '../lib/constants' import { route } from '../next-server/server/router' -import { createPagesMapping, createEntrypoints } from '../build/entries' -import { watchCompilers } from '../build/output' +import errorOverlayMiddleware from './lib/error-overlay-middleware' import { findPageFile } from './lib/find-page-file' -import { recursiveDelete } from '../lib/recursive-delete' -import { fileExists } from '../lib/file-exists' -import { promisify } from 'util' -import fs from 'fs' +import onDemandEntryHandler, { normalizePage } from './on-demand-entry-handler' +import { NextHandleFunction } from 'connect' +import { UrlObject } from 'url' const access = promisify(fs.access) const readFile = promisify(fs.readFile) -export async function renderScriptError (res, error) { +export async function renderScriptError(res: ServerResponse, error: Error) { // Asks CDNs and others to not to cache the errored page res.setHeader( 'Cache-Control', 'no-cache, no-store, max-age=0, must-revalidate' ) - if (error.code === 'ENOENT' || error.message === 'INVALID_BUILD_ID') { + if ( + (error as any).code === 'ENOENT' || + error.message === 'INVALID_BUILD_ID' + ) { res.statusCode = 404 res.end('404 - Not Found') return @@ -42,7 +49,7 @@ export async function renderScriptError (res, error) { res.end('500 - Internal Error') } -function addCorsSupport (req, res) { +function addCorsSupport(req: IncomingMessage, res: ServerResponse) { if (!req.headers.origin) { return { preflight: false } } @@ -51,10 +58,9 @@ function addCorsSupport (req, res) { res.setHeader('Access-Control-Allow-Methods', 'OPTIONS, GET') // Based on https://github.com/primus/access-control/blob/4cf1bc0e54b086c91e6aa44fb14966fa5ef7549c/index.js#L158 if (req.headers['access-control-request-headers']) { - res.setHeader( - 'Access-Control-Allow-Headers', - req.headers['access-control-request-headers'] - ) + res.setHeader('Access-Control-Allow-Headers', req.headers[ + 'access-control-request-headers' + ] as string) } if (req.method === 'OPTIONS') { @@ -71,7 +77,7 @@ const matchNextPageBundleRequest = route( ) // Recursively look up the issuer till it ends up at the root -function findEntryModule (issuer) { +function findEntryModule(issuer: any): any { if (issuer.issuer) { return findEntryModule(issuer.issuer) } @@ -79,8 +85,11 @@ function findEntryModule (issuer) { return issuer } -function erroredPages (compilation, options = { enhanceName: name => name }) { - const failedPages = {} +function erroredPages( + compilation: webpack.compilation.Compilation, + options = { enhanceName: (name: string) => name } +) { + const failedPages: { [page: string]: any[] } = {} for (const error of compilation.errors) { if (!error.origin) { continue @@ -110,7 +119,29 @@ function erroredPages (compilation, options = { enhanceName: name => name }) { } export default class HotReloader { - constructor (dir, { config, pagesDir, buildId } = {}) { + private dir: string + private buildId: string + private middlewares: any[] + private pagesDir: string + private webpackDevMiddleware: WebpackDevMiddleware.WebpackDevMiddleware | null + private webpackHotMiddleware: + | (NextHandleFunction & WebpackHotMiddleware.EventStream) + | null + private initialized: boolean + private config: any + private stats: any + private serverPrevDocumentHash: string | null + private prevChunkNames?: Set + private onDemandEntries: any + + constructor( + dir: string, + { + config, + pagesDir, + buildId, + }: { config: object; pagesDir: string; buildId: string } + ) { this.buildId = buildId this.dir = dir this.middlewares = [] @@ -124,7 +155,7 @@ export default class HotReloader { this.config = config } - async run (req, res, parsedUrl) { + async run(req: IncomingMessage, res: ServerResponse, parsedUrl: UrlObject) { // Usually CORS support is not needed for the hot-reloader (this is dev only feature) // With when the app runs for multi-zones support behind a proxy, // the current page is trying to access this URL via assetPrefix. @@ -138,7 +169,10 @@ export default class HotReloader { // we have to compile the page using on-demand-entries, this middleware will handle doing that // by adding the page to on-demand-entries, waiting till it's done // and then the bundle will be served like usual by the actual route in server/index.js - const handlePageBundleRequest = async (res, parsedUrl) => { + const handlePageBundleRequest = async ( + res: ServerResponse, + parsedUrl: UrlObject + ) => { const { pathname } = parsedUrl const params = matchNextPageBundleRequest(pathname) if (!params) { @@ -186,11 +220,11 @@ export default class HotReloader { return {} } - const { finished } = await handlePageBundleRequest(res, parsedUrl) + const { finished } = (await handlePageBundleRequest(res, parsedUrl)) as any for (const fn of this.middlewares) { await new Promise((resolve, reject) => { - fn(req, res, err => { + fn(req, res, (err: Error) => { if (err) return reject(err) resolve() }) @@ -200,18 +234,18 @@ export default class HotReloader { return { finished } } - async clean () { + async clean() { return recursiveDelete(join(this.dir, this.config.distDir)) } - async getWebpackConfig () { + async getWebpackConfig() { const pagePaths = await Promise.all([ findPageFile(this.pagesDir, '/_app', this.config.pageExtensions), - findPageFile(this.pagesDir, '/_document', this.config.pageExtensions) + findPageFile(this.pagesDir, '/_document', this.config.pageExtensions), ]) const pages = createPagesMapping( - pagePaths.filter(i => i !== null), + pagePaths.filter(i => i !== null) as string[], this.config.pageExtensions ) const entrypoints = createEntrypoints( @@ -221,7 +255,7 @@ export default class HotReloader { this.config ) - let additionalClientEntrypoints = {} + let additionalClientEntrypoints: { [file: string]: string } = {} additionalClientEntrypoints[CLIENT_STATIC_FILES_RUNTIME_AMP] = `.${sep}` + relativePath( @@ -236,7 +270,7 @@ export default class HotReloader { config: this.config, buildId: this.buildId, pagesDir: this.pagesDir, - entrypoints: { ...entrypoints.client, ...additionalClientEntrypoints } + entrypoints: { ...entrypoints.client, ...additionalClientEntrypoints }, }), getBaseWebpackConfig(this.dir, { dev: true, @@ -244,12 +278,12 @@ export default class HotReloader { config: this.config, buildId: this.buildId, pagesDir: this.pagesDir, - entrypoints: entrypoints.server - }) + entrypoints: entrypoints.server, + }), ]) } - async start () { + async start() { await this.clean() const configs = await this.getWebpackConfig() @@ -259,14 +293,14 @@ export default class HotReloader { const buildTools = await this.prepareBuildTools(multiCompiler) this.assignBuildTools(buildTools) - this.stats = (await this.waitUntilValid()).stats[0] + this.stats = ((await this.waitUntilValid()) as any).stats[0] } - async stop (webpackDevMiddleware) { + async stop(webpackDevMiddleware?: WebpackDevMiddleware.WebpackDevMiddleware) { const middleware = webpackDevMiddleware || this.webpackDevMiddleware if (middleware) { return new Promise((resolve, reject) => { - middleware.close(err => { + ;(middleware.close as any)((err: any) => { if (err) return reject(err) resolve() }) @@ -274,7 +308,7 @@ export default class HotReloader { } } - async reload () { + async reload() { this.stats = null await this.clean() @@ -288,13 +322,17 @@ export default class HotReloader { const oldWebpackDevMiddleware = this.webpackDevMiddleware this.assignBuildTools(buildTools) - await this.stop(oldWebpackDevMiddleware) + await this.stop(oldWebpackDevMiddleware!) } - assignBuildTools ({ + assignBuildTools({ webpackDevMiddleware, webpackHotMiddleware, - onDemandEntries + onDemandEntries, + }: { + webpackDevMiddleware: WebpackDevMiddleware.WebpackDevMiddleware + webpackHotMiddleware: NextHandleFunction & WebpackHotMiddleware.EventStream + onDemandEntries: any }) { this.webpackDevMiddleware = webpackDevMiddleware this.webpackHotMiddleware = webpackHotMiddleware @@ -304,11 +342,11 @@ export default class HotReloader { // must come before hotMiddleware onDemandEntries.middleware(), webpackHotMiddleware, - errorOverlayMiddleware({ dir: this.dir }) + errorOverlayMiddleware({ dir: this.dir }), ] } - async prepareBuildTools (multiCompiler) { + async prepareBuildTools(multiCompiler: webpack.MultiCompiler) { const tsConfigPath = join(this.dir, 'tsconfig.json') const useTypeScript = await fileExists(tsConfigPath) @@ -370,13 +408,13 @@ export default class HotReloader { if (this.initialized) { // detect chunks which have to be replaced with a new template // e.g, pages/index.js <-> pages/_error.js - const addedPages = diff(chunkNames, this.prevChunkNames) - const removedPages = diff(this.prevChunkNames, chunkNames) + const addedPages = diff(chunkNames, this.prevChunkNames!) + const removedPages = diff(this.prevChunkNames!, chunkNames) if (addedPages.size > 0) { for (const addedPage of addedPages) { let page = - '/' + ROUTE_NAME_REGEX.exec(addedPage)[1].replace(/\\/g, '/') + '/' + ROUTE_NAME_REGEX.exec(addedPage)![1].replace(/\\/g, '/') page = page === '/index' ? '/' : page this.send('addedPage', page) } @@ -385,7 +423,7 @@ export default class HotReloader { if (removedPages.size > 0) { for (const removedPage of removedPages) { let page = - '/' + ROUTE_NAME_REGEX.exec(removedPage)[1].replace(/\\/g, '/') + '/' + ROUTE_NAME_REGEX.exec(removedPage)![1].replace(/\\/g, '/') page = page === '/index' ? '/' : page this.send('removedPage', page) } @@ -402,7 +440,7 @@ export default class HotReloader { const ignored = [ /[\\/]\.git[\\/]/, /[\\/]\.next[\\/]/, - /[\\/]node_modules[\\/]/ + /[\\/]node_modules[\\/]/, ] let webpackDevMiddlewareConfig = { @@ -410,7 +448,7 @@ export default class HotReloader { noInfo: true, logLevel: 'silent', watchOptions: { ignored }, - writeToDisk: true + writeToDisk: true, } if (this.config.webpackDevMiddleware) { @@ -434,7 +472,7 @@ export default class HotReloader { { path: '/_next/webpack-hmr', log: false, - heartbeat: 2500 + heartbeat: 2500, } ) @@ -450,25 +488,27 @@ export default class HotReloader { pageExtensions: this.config.pageExtensions, publicRuntimeConfig: this.config.publicRuntimeConfig, serverRuntimeConfig: this.config.serverRuntimeConfig, - ...this.config.onDemandEntries + ...this.config.onDemandEntries, } ) return { webpackDevMiddleware, webpackHotMiddleware, - onDemandEntries + onDemandEntries, } } - waitUntilValid (webpackDevMiddleware) { + waitUntilValid( + webpackDevMiddleware?: WebpackDevMiddleware.WebpackDevMiddleware + ) { const middleware = webpackDevMiddleware || this.webpackDevMiddleware return new Promise(resolve => { - middleware.waitUntilValid(resolve) + middleware!.waitUntilValid(resolve) }) } - async getCompilationErrors (page) { + async getCompilationErrors(page: string) { const normalizedPage = normalizePage(page) // When we are reloading, we need to wait until it's reloaded properly. await this.onDemandEntries.waitUntilReloaded() @@ -476,9 +516,9 @@ export default class HotReloader { if (this.stats.hasErrors()) { const { compilation } = this.stats const failedPages = erroredPages(compilation, { - enhanceName (name) { - return '/' + ROUTE_NAME_REGEX.exec(name)[1] - } + enhanceName(name) { + return '/' + ROUTE_NAME_REGEX.exec(name)![1] + }, }) // If there is an error related to the requesting page we display it instead of the first error @@ -496,11 +536,11 @@ export default class HotReloader { return [] } - send = (action, ...args) => { - this.webpackHotMiddleware.publish({ action, data: args }) + send = (action: string, ...args: any[]) => { + this.webpackHotMiddleware!.publish({ action, data: args }) } - async ensurePage (page) { + async ensurePage(page: string) { // Make sure we don't re-build or dispose prebuilt pages if (page !== '/_error' && BLOCKED_PAGES.indexOf(page) !== -1) { return @@ -509,6 +549,6 @@ export default class HotReloader { } } -function diff (a, b) { +function diff(a: Set, b: Set) { return new Set([...a].filter(v => !b.has(v))) } diff --git a/packages/next/server/lib/error-overlay-middleware.js b/packages/next/server/lib/error-overlay-middleware.js deleted file mode 100644 index adc8fe81977d90ee3e645aaeedf85be138633e81..0000000000000000000000000000000000000000 --- a/packages/next/server/lib/error-overlay-middleware.js +++ /dev/null @@ -1,25 +0,0 @@ -import url from 'url' -import launchEditor from 'launch-editor' -import fs from 'fs' -import path from 'path' - -export default function errorOverlayMiddleware (options) { - return (req, res, next) => { - if (req.url.startsWith('/_next/development/open-stack-frame-in-editor')) { - const query = url.parse(req.url, true).query - const lineNumber = parseInt(query.lineNumber, 10) || 1 - const colNumber = parseInt(query.colNumber, 10) || 1 - - let resolvedFileName = query.fileName - - if (!fs.existsSync(resolvedFileName)) { - resolvedFileName = path.join(options.dir, resolvedFileName) - } - - launchEditor(`${resolvedFileName}:${lineNumber}:${colNumber}`) - res.end() - } else { - next() - } - } -} diff --git a/packages/next/server/lib/error-overlay-middleware.ts b/packages/next/server/lib/error-overlay-middleware.ts new file mode 100644 index 0000000000000000000000000000000000000000..8bc0db280429c58a933d4955e0536cc3ce070c80 --- /dev/null +++ b/packages/next/server/lib/error-overlay-middleware.ts @@ -0,0 +1,26 @@ +import url from 'url' +import launchEditor from 'launch-editor' +import fs from 'fs' +import path from 'path' +import { IncomingMessage, ServerResponse } from 'http' + +export default function errorOverlayMiddleware(options: { dir: string }) { + return (req: IncomingMessage, res: ServerResponse, next: Function) => { + if (req.url!.startsWith('/_next/development/open-stack-frame-in-editor')) { + const query = url.parse(req.url!, true).query + const lineNumber = parseInt(query.lineNumber as string, 10) || 1 + const colNumber = parseInt(query.colNumber as string, 10) || 1 + + let resolvedFileName = query.fileName + + if (!fs.existsSync(resolvedFileName as string)) { + resolvedFileName = path.join(options.dir, resolvedFileName as string) + } + + launchEditor(`${resolvedFileName}:${lineNumber}:${colNumber}`) + res.end() + } else { + next() + } + } +} diff --git a/packages/next/server/next-dev-server.js b/packages/next/server/next-dev-server.ts similarity index 76% rename from packages/next/server/next-dev-server.js rename to packages/next/server/next-dev-server.ts index 87e52aeff9767ff5a26c1b7f0eee1e46f0be3303..6b3533047daecf28a06449686b89ef9f5dd8d594 100644 --- a/packages/next/server/next-dev-server.js +++ b/packages/next/server/next-dev-server.ts @@ -1,29 +1,32 @@ -import Server from '../next-server/server/next-server' +import AmpHtmlValidator from 'amphtml-validator' +import fs from 'fs' +import { IncomingMessage, ServerResponse } from 'http' import { join, relative } from 'path' +import React from 'react' +import { UrlWithParsedQuery } from 'url' import { promisify } from 'util' -import HotReloader from './hot-reloader' -import { route } from '../next-server/server/router' -import { PHASE_DEVELOPMENT_SERVER } from '../next-server/lib/constants' -import ErrorDebug from './error-debug' -import AmpHtmlValidator from 'amphtml-validator' +import Watchpack from 'watchpack' + import { ampValidation } from '../build/output/index' import * as Log from '../build/output/log' +import { PUBLIC_DIR_MIDDLEWARE_CONFLICT } from '../lib/constants' +import { findPagesDir } from '../lib/find-pages-dir' import { verifyTypeScriptSetup } from '../lib/verifyTypeScriptSetup' -import Watchpack from 'watchpack' -import { setDistDir as setTelemetryDir } from '../telemetry/storage' -import { recordVersion } from '../telemetry/events' -import fs from 'fs' +import { PHASE_DEVELOPMENT_SERVER } from '../next-server/lib/constants' import { getRouteMatcher, getRouteRegex, getSortedRoutes, - isDynamicRoute + isDynamicRoute, } from '../next-server/lib/router/utils' -import React from 'react' -import { findPageFile } from './lib/find-page-file' +import Server, { ServerConstructor } from '../next-server/server/next-server' import { normalizePagePath } from '../next-server/server/normalize-page-path' -import { PUBLIC_DIR_MIDDLEWARE_CONFLICT } from '../lib/constants' -import { findPagesDir } from '../lib/find-pages-dir' +import { route } from '../next-server/server/router' +import { recordVersion } from '../telemetry/events' +import { setDistDir as setTelemetryDir } from '../telemetry/storage' +import ErrorDebug from './error-debug' +import HotReloader from './hot-reloader' +import { findPageFile } from './lib/find-page-file' if (typeof React.Suspense === 'undefined') { throw new Error( @@ -34,14 +37,22 @@ if (typeof React.Suspense === 'undefined') { const fsStat = promisify(fs.stat) export default class DevServer extends Server { - constructor (options) { + private devReady: Promise + private setDevReady?: Function + private webpackWatcher?: Watchpack | null + private hotReloader?: HotReloader + + constructor(options: ServerConstructor) { super({ ...options, dev: true }) this.renderOpts.dev = true - this.renderOpts.ErrorDebug = ErrorDebug + ;(this.renderOpts as any).ErrorDebug = ErrorDebug this.devReady = new Promise(resolve => { this.setDevReady = resolve }) - this.renderOpts.ampValidator = (html, pathname) => { + ;(this.renderOpts as any).ampValidator = ( + html: string, + pathname: string + ) => { return AmpHtmlValidator.getInstance().then(validator => { const result = validator.validateString(html) ampValidation( @@ -56,15 +67,15 @@ export default class DevServer extends Server { this.pagesDir = findPagesDir(this.dir) } - currentPhase () { + protected currentPhase() { return PHASE_DEVELOPMENT_SERVER } - readBuildId () { + protected readBuildId() { return 'development' } - async addExportPathMapRoutes () { + async addExportPathMapRoutes() { // Makes `next export` exportPathMap work in development mode. // So that the user doesn't have to define a custom server reading the exportPathMap if (this.nextConfig.exportPathMap) { @@ -76,7 +87,7 @@ export default class DevServer extends Server { dir: this.dir, outDir: null, distDir: this.distDir, - buildId: this.buildId + buildId: this.buildId, } ) // In development we can't give a default path mapping for (const path in exportPathMap) { @@ -99,13 +110,13 @@ export default class DevServer extends Server { const mergedQuery = { ...urlQuery, ...query } await this.render(req, res, page, mergedQuery, parsedUrl) - } + }, }) } } } - async startWatcher () { + async startWatcher() { if (this.webpackWatcher) { return } @@ -115,7 +126,7 @@ export default class DevServer extends Server { const pagesDir = this.pagesDir // Watchpack doesn't emit an event for an empty directory - fs.readdir(pagesDir, (_, files) => { + fs.readdir(pagesDir!, (_, files) => { if (files && files.length) { return } @@ -127,7 +138,7 @@ export default class DevServer extends Server { }) let wp = (this.webpackWatcher = new Watchpack()) - wp.watch([], [pagesDir], 0) + wp.watch([], [pagesDir!], 0) wp.on('aggregated', () => { const dynamicRoutedPages = [] @@ -137,7 +148,8 @@ export default class DevServer extends Server { continue } - let pageName = '/' + relative(pagesDir, fileName).replace(/\\+/g, '/') + let pageName = + '/' + relative(pagesDir!, fileName).replace(/\\+/g, '/') pageName = pageName.replace( new RegExp(`\\.+(?:${this.nextConfig.pageExtensions.join('|')})$`), @@ -155,7 +167,7 @@ export default class DevServer extends Server { this.dynamicRoutes = getSortedRoutes(dynamicRoutedPages).map(page => ({ page, - match: getRouteMatcher(getRouteRegex(page)) + match: getRouteMatcher(getRouteRegex(page)), })) if (!resolved) { @@ -166,7 +178,7 @@ export default class DevServer extends Server { }) } - async stopWatcher () { + async stopWatcher() { if (!this.webpackWatcher) { return } @@ -175,36 +187,40 @@ export default class DevServer extends Server { this.webpackWatcher = null } - async prepare () { - await verifyTypeScriptSetup(this.dir, this.pagesDir) + async prepare() { + await verifyTypeScriptSetup(this.dir, this.pagesDir!) this.hotReloader = new HotReloader(this.dir, { - pagesDir: this.pagesDir, + pagesDir: this.pagesDir!, config: this.nextConfig, - buildId: this.buildId + buildId: this.buildId, }) await super.prepare() await this.addExportPathMapRoutes() await this.hotReloader.start() await this.startWatcher() - this.setDevReady() + this.setDevReady!() setTelemetryDir(this.distDir) recordVersion({ cliCommand: 'dev' }) } - async close () { + protected async close() { await this.stopWatcher() if (this.hotReloader) { await this.hotReloader.stop() } } - async run (req, res, parsedUrl) { + async run( + req: IncomingMessage, + res: ServerResponse, + parsedUrl: UrlWithParsedQuery + ) { await this.devReady const { pathname } = parsedUrl - if (pathname.startsWith('/_next')) { + if (pathname!.startsWith('/_next')) { try { await fsStat(join(this.publicDir, '_next')) throw new Error(PUBLIC_DIR_MIDDLEWARE_CONFLICT) @@ -214,11 +230,11 @@ export default class DevServer extends Server { // check for a public file, throwing error if there's a // conflicting page if (this.nextConfig.experimental.publicDirectory) { - if (await this.hasPublicFile(pathname)) { + if (await this.hasPublicFile(pathname!)) { const pageFile = await findPageFile( - this.pagesDir, + this.pagesDir!, - normalizePagePath(pathname), + normalizePagePath(pathname!), this.nextConfig.pageExtensions ) @@ -227,13 +243,15 @@ export default class DevServer extends Server { `A conflicting public file and page file was found for path ${pathname} https://err.sh/zeit/next.js/conflicting-public-file-page` ) res.statusCode = 500 - return this.renderError(err, req, res, pathname, {}) + return this.renderError(err, req, res, pathname!, {}) } - return this.servePublic(req, res, pathname) + return this.servePublic(req, res, pathname!) } } - const { finished } = await this.hotReloader.run(req, res, parsedUrl) + const { finished } = (await this.hotReloader!.run(req, res, parsedUrl)) || { + finished: false, + } if (finished) { return } @@ -241,7 +259,7 @@ export default class DevServer extends Server { return super.run(req, res, parsedUrl) } - generateRoutes () { + generateRoutes() { const routes = super.generateRoutes() // In development we expose all compiled files for react-error-overlay's line show feature @@ -251,23 +269,26 @@ export default class DevServer extends Server { fn: async (req, res, params) => { const p = join(this.distDir, ...(params.path || [])) await this.serveStatic(req, res, p) - } + }, }) return routes } // In development public files are not added to the router but handled as a fallback instead - generatePublicRoutes () { + protected generatePublicRoutes() { return [] } // In development dynamic routes cannot be known ahead of time - getDynamicRoutes () { + protected getDynamicRoutes() { return [] } - _filterAmpDevelopmentScript (html, event) { + _filterAmpDevelopmentScript( + html: string, + event: { line: number; col: number; code: string } + ) { if (event.code !== 'DISALLOWED_SCRIPT_TAG') { return true } @@ -292,9 +313,9 @@ export default class DevServer extends Server { * Check if resolver function is build or request new build for this function * @param {string} pathname */ - async resolveApiRequest (pathname) { + protected async resolveApiRequest(pathname: string): Promise { try { - await this.hotReloader.ensurePage(pathname) + await this.hotReloader!.ensurePage(pathname) } catch (err) { // API route dosn't exist => return 404 if (err.code === 'ENOENT') { @@ -305,7 +326,13 @@ export default class DevServer extends Server { return resolvedPath } - async renderToHTML (req, res, pathname, query, options = {}) { + async renderToHTML( + req: IncomingMessage, + res: ServerResponse, + pathname: string, + query: { [key: string]: string }, + options = {} + ) { const compilationErr = await this.getCompilationError(pathname) if (compilationErr) { res.statusCode = 500 @@ -314,18 +341,18 @@ export default class DevServer extends Server { // In dev mode we use on demand entries to compile the page before rendering try { - await this.hotReloader.ensurePage(pathname).catch(async err => { - if (err.code !== 'ENOENT') { + await this.hotReloader!.ensurePage(pathname).catch(async (err: Error) => { + if ((err as any).code !== 'ENOENT') { throw err } - for (const dynamicRoute of this.dynamicRoutes) { + for (const dynamicRoute of this.dynamicRoutes || []) { const params = dynamicRoute.match(pathname) if (!params) { continue } - return this.hotReloader.ensurePage(dynamicRoute.page).then(() => { + return this.hotReloader!.ensurePage(dynamicRoute.page).then(() => { pathname = dynamicRoute.page query = Object.assign({}, query, params) }) @@ -344,8 +371,14 @@ export default class DevServer extends Server { return html } - async renderErrorToHTML (err, req, res, pathname, query) { - await this.hotReloader.ensurePage('/_error') + async renderErrorToHTML( + err: Error | null, + req: IncomingMessage, + res: ServerResponse, + pathname: string, + query: { [key: string]: string } + ) { + await this.hotReloader!.ensurePage('/_error') const compilationErr = await this.getCompilationError(pathname) if (compilationErr) { @@ -370,22 +403,22 @@ export default class DevServer extends Server { } } - sendHTML (req, res, html) { + sendHTML(req: IncomingMessage, res: ServerResponse, html: string) { // In dev, we should not cache pages for any reason. res.setHeader('Cache-Control', 'no-store, must-revalidate') return super.sendHTML(req, res, html) } - setImmutableAssetCacheControl (res) { + protected setImmutableAssetCacheControl(res: ServerResponse) { res.setHeader('Cache-Control', 'no-store, must-revalidate') } - servePublic (req, res, path) { + servePublic(req: IncomingMessage, res: ServerResponse, path: string) { const p = join(this.publicDir, path) return this.serveStatic(req, res, p) } - async hasPublicFile (path) { + async hasPublicFile(path: string) { try { const info = await fsStat(join(this.publicDir, path)) return info.isFile() @@ -394,8 +427,8 @@ export default class DevServer extends Server { } } - async getCompilationError (page) { - const errors = await this.hotReloader.getCompilationErrors(page) + async getCompilationError(page: string) { + const errors = await this.hotReloader!.getCompilationErrors(page) if (errors.length === 0) return // Return the very first error we found. diff --git a/packages/next/server/on-demand-entry-handler.js b/packages/next/server/on-demand-entry-handler.ts similarity index 73% rename from packages/next/server/on-demand-entry-handler.js rename to packages/next/server/on-demand-entry-handler.ts index 94572a1c3fba15eeb42a51f69ef359a73cc7679f..c54ec4db248453a9672e29d434470bab96d20f97 100644 --- a/packages/next/server/on-demand-entry-handler.js +++ b/packages/next/server/on-demand-entry-handler.ts @@ -1,88 +1,107 @@ -import DynamicEntryPlugin from 'webpack/lib/DynamicEntryPlugin' import { EventEmitter } from 'events' +import { IncomingMessage, ServerResponse } from 'http' import { join, posix } from 'path' +import { stringify } from 'querystring' import { parse } from 'url' -import { pageNotFoundError } from '../next-server/server/require' -import { normalizePagePath } from '../next-server/server/normalize-page-path' +import webpack from 'webpack' +import WebpackDevMiddleware from 'webpack-dev-middleware' +import DynamicEntryPlugin from 'webpack/lib/DynamicEntryPlugin' + +import { isWriteable } from '../build/is-writeable' +import * as Log from '../build/output/log' +import { API_ROUTE } from '../lib/constants' import { + IS_BUNDLED_PAGE_REGEX, ROUTE_NAME_REGEX, - IS_BUNDLED_PAGE_REGEX } from '../next-server/lib/constants' -import { stringify } from 'querystring' +import { normalizePagePath } from '../next-server/server/normalize-page-path' +import { pageNotFoundError } from '../next-server/server/require' import { findPageFile } from './lib/find-page-file' -import { isWriteable } from '../build/is-writeable' -import * as Log from '../build/output/log' -import { API_ROUTE } from '../lib/constants' const ADDED = Symbol('added') const BUILDING = Symbol('building') const BUILT = Symbol('built') // Based on https://github.com/webpack/webpack/blob/master/lib/DynamicEntryPlugin.js#L29-L37 -function addEntry (compilation, context, name, entry) { +function addEntry( + compilation: webpack.compilation.Compilation, + context: string, + name: string, + entry: string[] +) { return new Promise((resolve, reject) => { const dep = DynamicEntryPlugin.createDependency(entry, name) - compilation.addEntry(context, dep, name, err => { + compilation.addEntry(context, dep, name, (err: Error) => { if (err) return reject(err) resolve() }) }) } -export default function onDemandEntryHandler ( - devMiddleware, - multiCompiler, +export default function onDemandEntryHandler( + devMiddleware: WebpackDevMiddleware.WebpackDevMiddleware, + multiCompiler: webpack.MultiCompiler, { buildId, pagesDir, reload, pageExtensions, maxInactiveAge, - pagesBufferLength + pagesBufferLength, + }: { + buildId: string + pagesDir: string + reload: any + pageExtensions: string[] + maxInactiveAge: number + pagesBufferLength: number } ) { const { compilers } = multiCompiler const invalidator = new Invalidator(devMiddleware, multiCompiler) - let entries = {} + let entries: any = {} let lastAccessPages = [''] - let doneCallbacks = new EventEmitter() + let doneCallbacks: EventEmitter | null = new EventEmitter() let reloading = false let stopped = false - let reloadCallbacks = new EventEmitter() - let lastEntry = null + let reloadCallbacks: EventEmitter | null = new EventEmitter() + let lastEntry: string | null = null for (const compiler of compilers) { - compiler.hooks.make.tapPromise('NextJsOnDemandEntries', compilation => { - invalidator.startBuilding() + compiler.hooks.make.tapPromise( + 'NextJsOnDemandEntries', + (compilation: webpack.compilation.Compilation) => { + invalidator.startBuilding() - const allEntries = Object.keys(entries).map(async page => { - if (compiler.name === 'client' && page.match(API_ROUTE)) { - return - } - const { name, absolutePagePath } = entries[page] - const pageExists = await isWriteable(absolutePagePath) - if (!pageExists) { - Log.event('page was removed', page) - delete entries[page] - return - } + const allEntries = Object.keys(entries).map(async page => { + if (compiler.name === 'client' && page.match(API_ROUTE)) { + return + } + const { name, absolutePagePath } = entries[page] + const pageExists = await isWriteable(absolutePagePath) + if (!pageExists) { + Log.event('page was removed', page) + delete entries[page] + return + } - entries[page].status = BUILDING - return addEntry(compilation, compiler.context, name, [ - compiler.name === 'client' - ? `next-client-pages-loader?${stringify({ - page, - absolutePagePath - })}!` - : absolutePagePath - ]) - }) + entries[page].status = BUILDING + return addEntry(compilation, compiler.context, name, [ + compiler.name === 'client' + ? `next-client-pages-loader?${stringify({ + page, + absolutePagePath, + })}!` + : absolutePagePath, + ]) + }) - return Promise.all(allEntries).catch(err => console.error(err)) - }) + return Promise.all(allEntries).catch(err => console.error(err)) + } + ) } - function findHardFailedPages (errors) { + function findHardFailedPages(errors: any[]) { return errors .filter(e => { // Make sure to only pick errors which marked with missing modules @@ -99,13 +118,13 @@ export default function onDemandEntryHandler ( }) .map(e => e.module.chunks) .reduce((a, b) => [...a, ...b], []) - .map(c => { - const pageName = ROUTE_NAME_REGEX.exec(c.name)[1] + .map((c: any) => { + const pageName = ROUTE_NAME_REGEX.exec(c.name)![1] return normalizePage(`/${pageName}`) }) } - function getPagePathsFromEntrypoints (entrypoints) { + function getPagePathsFromEntrypoints(entrypoints: any) { const pagePaths = [] for (const [, entrypoint] of entrypoints.entries()) { const result = ROUTE_NAME_REGEX.exec(entrypoint.name) @@ -130,12 +149,12 @@ export default function onDemandEntryHandler ( const hardFailedPages = [ ...new Set([ ...findHardFailedPages(clientStats.compilation.errors), - ...findHardFailedPages(serverStats.compilation.errors) - ]) + ...findHardFailedPages(serverStats.compilation.errors), + ]), ] const pagePaths = new Set([ ...getPagePathsFromEntrypoints(clientStats.compilation.entrypoints), - ...getPagePathsFromEntrypoints(serverStats.compilation.entrypoints) + ...getPagePathsFromEntrypoints(serverStats.compilation.entrypoints), ]) // compilation.entrypoints is a Map object, so iterating over it 0 is the key and 1 is the value @@ -153,7 +172,7 @@ export default function onDemandEntryHandler ( entry.status = BUILT entry.lastActiveTime = Date.now() - doneCallbacks.emit(page) + doneCallbacks!.emit(page) } invalidator.doneBuilding() @@ -168,10 +187,10 @@ export default function onDemandEntryHandler ( reload() .then(() => { console.log('> Webpack reloaded.') - reloadCallbacks.emit('done') + reloadCallbacks!.emit('done') stop() }) - .catch(err => { + .catch((err: Error) => { console.error(`> Webpack reloading failed: ${err.message}`) console.error(err.stack) process.exit(1) @@ -179,7 +198,7 @@ export default function onDemandEntryHandler ( } }) - const disposeHandler = setInterval(function () { + const disposeHandler = setInterval(function() { if (stopped) return disposeInactiveEntries( devMiddleware, @@ -191,14 +210,14 @@ export default function onDemandEntryHandler ( disposeHandler.unref() - function stop () { + function stop() { clearInterval(disposeHandler) stopped = true doneCallbacks = null reloadCallbacks = null } - function handlePing (pg) { + function handlePing(pg: string) { const page = normalizePage(pg) const entryInfo = entries[page] let toSend @@ -236,23 +255,23 @@ export default function onDemandEntryHandler ( } return { - waitUntilReloaded () { + waitUntilReloaded() { if (!reloading) return Promise.resolve(true) return new Promise(resolve => { - reloadCallbacks.once('done', function () { + reloadCallbacks!.once('done', function() { resolve() }) }) }, - async ensurePage (page) { + async ensurePage(page: string) { await this.waitUntilReloaded() - let normalizedPagePath + let normalizedPagePath: string try { normalizedPagePath = normalizePagePath(page) } catch (err) { console.error(err) - throw pageNotFoundError(normalizedPagePath) + throw pageNotFoundError(page) } let pagePath = await findPageFile( @@ -294,7 +313,7 @@ export default function onDemandEntryHandler ( } if (entryInfo.status === BUILDING) { - doneCallbacks.once(normalizedPage, handleCallback) + doneCallbacks!.once(normalizedPage, handleCallback) return } } @@ -302,24 +321,24 @@ export default function onDemandEntryHandler ( Log.event(`build page: ${normalizedPage}`) entries[normalizedPage] = { name, absolutePagePath, status: ADDED } - doneCallbacks.once(normalizedPage, handleCallback) + doneCallbacks!.once(normalizedPage, handleCallback) invalidator.invalidate() - function handleCallback (err) { + function handleCallback(err: Error) { if (err) return reject(err) resolve() } }) }, - middleware () { - return (req, res, next) => { + middleware() { + return (req: IncomingMessage, res: ServerResponse, next: Function) => { if (stopped) { // If this handler is stopped, we need to reload the user's browser. // So the user could connect to the actually running handler. res.statusCode = 302 - res.setHeader('Location', req.url) + res.setHeader('Location', req.url!) res.end('302') } else if (reloading) { // Webpack config is reloading. So, we need to wait until it's done and @@ -327,18 +346,18 @@ export default function onDemandEntryHandler ( // So the user could connect to the new handler and webpack setup. this.waitUntilReloaded().then(() => { res.statusCode = 302 - res.setHeader('Location', req.url) + res.setHeader('Location', req.url!) res.end('302') }) } else { - if (!/^\/_next\/webpack-hmr/.test(req.url)) return next() + if (!/^\/_next\/webpack-hmr/.test(req.url!)) return next() - const { query } = parse(req.url, true) + const { query } = parse(req.url!, true) const page = query.page if (!page) return next() const runPing = () => { - const data = handlePing(query.page) + const data = handlePing(query.page as string) if (!data) return res.write('data: ' + JSON.stringify(data) + '\n\n') } @@ -352,17 +371,17 @@ export default function onDemandEntryHandler ( next() } } - } + }, } } -function disposeInactiveEntries ( - devMiddleware, - entries, - lastAccessPages, - maxInactiveAge +function disposeInactiveEntries( + devMiddleware: WebpackDevMiddleware.WebpackDevMiddleware, + entries: any, + lastAccessPages: any, + maxInactiveAge: number ) { - const disposingPages = [] + const disposingPages: any = [] Object.keys(entries).forEach(page => { const { lastActiveTime, status } = entries[page] @@ -382,7 +401,7 @@ function disposeInactiveEntries ( }) if (disposingPages.length > 0) { - disposingPages.forEach(page => { + disposingPages.forEach((page: any) => { delete entries[page] }) Log.event(`disposing inactive page(s): ${disposingPages.join(', ')}`) @@ -392,7 +411,7 @@ function disposeInactiveEntries ( // /index and / is the same. So, we need to identify both pages as the same. // This also applies to sub pages as well. -export function normalizePage (page) { +export function normalizePage(page: string) { const unixPagePath = page.replace(/\\/g, '/') if (unixPagePath === '/index' || unixPagePath === '/') { return '/' @@ -403,7 +422,15 @@ export function normalizePage (page) { // Make sure only one invalidation happens at a time // Otherwise, webpack hash gets changed and it'll force the client to reload. class Invalidator { - constructor (devMiddleware, multiCompiler) { + private multiCompiler: webpack.MultiCompiler + private devMiddleware: WebpackDevMiddleware.WebpackDevMiddleware + private building: boolean + private rebuildAgain: boolean + + constructor( + devMiddleware: WebpackDevMiddleware.WebpackDevMiddleware, + multiCompiler: webpack.MultiCompiler + ) { this.multiCompiler = multiCompiler this.devMiddleware = devMiddleware // contains an array of types of compilers currently building @@ -411,7 +438,7 @@ class Invalidator { this.rebuildAgain = false } - invalidate () { + invalidate() { // If there's a current build is processing, we won't abort it by invalidating. // (If aborted, it'll cause a client side hard reload) // But let it to invalidate just after the completion. @@ -430,11 +457,11 @@ class Invalidator { this.devMiddleware.invalidate() } - startBuilding () { + startBuilding() { this.building = true } - doneBuilding () { + doneBuilding() { this.building = false if (this.rebuildAgain) { diff --git a/packages/next/types/misc.d.ts b/packages/next/types/misc.d.ts index 079b683ac7ad242f798692bb1a2fd18111acec8e..00e0e8741f16cc6be3f1f61835e2c87fe332523b 100644 --- a/packages/next/types/misc.d.ts +++ b/packages/next/types/misc.d.ts @@ -1,6 +1,8 @@ declare module '@babel/plugin-transform-modules-commonjs' declare module 'webpack/lib/GraphHelpers' +declare module 'webpack/lib/DynamicEntryPlugin' declare module 'unfetch' +declare module 'launch-editor' declare module 'styled-jsx/server' declare module 'async-retry' @@ -93,3 +95,19 @@ declare module NodeJS { crossOrigin?: string } } + +declare module 'watchpack' { + import { EventEmitter } from 'events' + + class Watchpack extends EventEmitter { + watch(files: string[], directories: string[], startTime?: number): void + close(): void + + getTimeInfoEntries(): Map< + string, + { safeTime: number; timestamp: number; accuracy?: number } + > + } + + export default Watchpack +} diff --git a/yarn.lock b/yarn.lock index 0438a1b869e811cecbac2b2207db60166fc18ad4..913c61184cb7bdfc188001cfccdecaa04b23d376 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2205,6 +2205,13 @@ resolved "https://registry.yarnpkg.com/@types/lru-cache/-/lru-cache-5.1.0.tgz#57f228f2b80c046b4a1bd5cac031f81f207f4f03" integrity sha512-RaE0B+14ToE4l6UqdarKPnXwVDuigfFv+5j9Dze/Nqr23yyuqdNvzcZi3xB+3Agvi5R4EOgAksfv3lXX4vBt9w== +"@types/memory-fs@*": + version "0.3.2" + resolved "https://registry.yarnpkg.com/@types/memory-fs/-/memory-fs-0.3.2.tgz#5d4753f9b390cb077c8c8af97bc96463399ceccd" + integrity sha512-j5AcZo7dbMxHoOimcHEIh0JZe5e1b8q8AqGSpZJrYc7xOgCIP79cIjTdx5jSDLtySnQDwkDTqwlC7Xw7uXw7qg== + dependencies: + "@types/node" "*" + "@types/mime@*": version "2.0.1" resolved "https://registry.yarnpkg.com/@types/mime/-/mime-2.0.1.tgz#dc488842312a7f075149312905b5e3c0b054c79d" @@ -2411,6 +2418,24 @@ "@types/unist" "*" "@types/vfile-message" "*" +"@types/webpack-dev-middleware@2.0.3": + version "2.0.3" + resolved "https://registry.yarnpkg.com/@types/webpack-dev-middleware/-/webpack-dev-middleware-2.0.3.tgz#aefb145281b3326a052325d583d2339debbf1be3" + integrity sha512-DzNJJ6ah/6t1n8sfAgQyEbZ/OMmFcF9j9P3aesnm7G6/iBFR/qiGin8K89J0RmaWIBzhTMdDg3I5PmKmSv7N9w== + dependencies: + "@types/connect" "*" + "@types/memory-fs" "*" + "@types/webpack" "*" + loglevel "^1.6.2" + +"@types/webpack-hot-middleware@2.16.5": + version "2.16.5" + resolved "https://registry.yarnpkg.com/@types/webpack-hot-middleware/-/webpack-hot-middleware-2.16.5.tgz#5271eada42f34670a7ae79ddb6f1c419a19c985f" + integrity sha512-41qSQeyRGZkWSi366jMQVsLo5fdLT8EgmvHNoBwcCtwZcHrQk6An6tD+ZfC0zMdNHzVEFlzQvT2mTte8zDxqNw== + dependencies: + "@types/connect" "*" + "@types/webpack" "*" + "@types/webpack-sources@0.1.5": version "0.1.5" resolved "https://registry.yarnpkg.com/@types/webpack-sources/-/webpack-sources-0.1.5.tgz#be47c10f783d3d6efe1471ff7f042611bd464a92" @@ -9227,6 +9252,11 @@ log-update@^2.3.0: cli-cursor "^2.0.0" wrap-ansi "^3.0.1" +loglevel@^1.6.2: + version "1.6.4" + resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.6.4.tgz#f408f4f006db8354d0577dcf6d33485b3cb90d56" + integrity sha512-p0b6mOGKcGa+7nnmKbpzR6qloPbrgLcnio++E+14Vo/XffOGwZtRpUhr8dTH/x2oCMmEoIU0Zwm3ZauhvYD17g== + lolex@1.3.2: version "1.3.2" resolved "https://registry.yarnpkg.com/lolex/-/lolex-1.3.2.tgz#7c3da62ffcb30f0f5a80a2566ca24e45d8a01f31"