diff --git a/src/Stats.ts b/src/Stats.ts new file mode 100644 index 0000000000..fd2c45ce0a --- /dev/null +++ b/src/Stats.ts @@ -0,0 +1,73 @@ +const now = (typeof process !== 'undefined' && process.hrtime) + ? () => { + const t = process.hrtime(); + return t[0] * 1e3 + t[1] / 1e6; + } + : () => window.performance.now(); + +type Timing = { + label: string; + start: number; + end: number; + children: Timing[]; +} + +function collapseTimings(timings) { + const result = {}; + timings.forEach(timing => { + result[timing.label] = Object.assign({ + total: timing.end - timing.start + }, timing.children && collapseTimings(timing.children)); + }); + return result; +} + +export default class Stats { + startTime: number; + currentTiming: Timing; + currentChildren: Timing[]; + timings: Timing[]; + stack: Timing[]; + + constructor() { + this.startTime = now(); + this.stack = []; + this.currentChildren = this.timings = []; + } + + start(label) { + const timing = { + label, + start: now(), + end: null, + children: [] + }; + + this.currentChildren.push(timing); + this.stack.push(timing); + + this.currentTiming = timing; + this.currentChildren = timing.children; + } + + stop(label) { + if (label !== this.currentTiming.label) { + throw new Error(`Mismatched timing labels`); + } + + this.currentTiming.end = now(); + this.stack.pop(); + this.currentTiming = this.stack[this.stack.length - 1]; + this.currentChildren = this.currentTiming ? this.currentTiming.children : this.timings; + } + + toJSON() { + const timings = Object.assign({ + total: now() - this.startTime + }, collapseTimings(this.timings)); + + return { + timings + }; + } +} \ No newline at end of file diff --git a/src/generators/Generator.ts b/src/generators/Generator.ts index 5d8e472c5b..9a0e9acd43 100644 --- a/src/generators/Generator.ts +++ b/src/generators/Generator.ts @@ -2,6 +2,7 @@ import MagicString, { Bundle } from 'magic-string'; import isReference from 'is-reference'; import { walk, childKeys } from 'estree-walker'; import { getLocator } from 'locate-character'; +import Stats from '../Stats'; import deindent from '../utils/deindent'; import CodeBuilder from '../utils/CodeBuilder'; import getCodeFrame from '../utils/getCodeFrame'; @@ -76,6 +77,8 @@ childKeys.EachBlock = childKeys.IfBlock = ['children', 'else']; childKeys.Attribute = ['value']; export default class Generator { + stats: Stats; + ast: Parsed; parsed: Parsed; source: string; @@ -123,8 +126,12 @@ export default class Generator { name: string, stylesheet: Stylesheet, options: CompileOptions, + stats: Stats, dom: boolean ) { + stats.start('compile'); + this.stats = stats; + this.ast = clone(parsed); this.parsed = parsed; @@ -372,10 +379,13 @@ export default class Generator { } }); + this.stats.stop('compile'); + return { ast: this.ast, js, css, + stats: this.stats.toJSON(), // TODO deprecate code: js.code, diff --git a/src/generators/dom/index.ts b/src/generators/dom/index.ts index c3358f31d3..412742d874 100644 --- a/src/generators/dom/index.ts +++ b/src/generators/dom/index.ts @@ -11,6 +11,7 @@ import reservedNames from '../../utils/reservedNames'; import shared from './shared'; import Generator from '../Generator'; import Stylesheet from '../../css/Stylesheet'; +import Stats from '../../Stats'; import Block from './Block'; import { test } from '../../config'; import { Parsed, CompileOptions, Node } from '../../interfaces'; @@ -34,9 +35,10 @@ export class DomGenerator extends Generator { source: string, name: string, stylesheet: Stylesheet, - options: CompileOptions + options: CompileOptions, + stats: Stats ) { - super(parsed, source, name, stylesheet, options, true); + super(parsed, source, name, stylesheet, options, stats, true); this.blocks = []; this.readonly = new Set(); @@ -81,11 +83,12 @@ export default function dom( parsed: Parsed, source: string, stylesheet: Stylesheet, - options: CompileOptions + options: CompileOptions, + stats: Stats ) { const format = options.format || 'es'; - const generator = new DomGenerator(parsed, source, options.name || 'SvelteComponent', stylesheet, options); + const generator = new DomGenerator(parsed, source, options.name || 'SvelteComponent', stylesheet, options, stats); const { computations, diff --git a/src/generators/server-side-rendering/index.ts b/src/generators/server-side-rendering/index.ts index a4e3652359..e2501a826d 100644 --- a/src/generators/server-side-rendering/index.ts +++ b/src/generators/server-side-rendering/index.ts @@ -1,5 +1,6 @@ import deindent from '../../utils/deindent'; import Generator from '../Generator'; +import Stats from '../../Stats'; import Stylesheet from '../../css/Stylesheet'; import Block from './Block'; import visit from './visit'; @@ -20,9 +21,10 @@ export class SsrGenerator extends Generator { source: string, name: string, stylesheet: Stylesheet, - options: CompileOptions + options: CompileOptions, + stats: Stats ) { - super(parsed, source, name, stylesheet, options, false); + super(parsed, source, name, stylesheet, options, stats, false); this.bindings = []; this.renderCode = ''; this.appendTargets = []; @@ -45,11 +47,12 @@ export default function ssr( parsed: Parsed, source: string, stylesheet: Stylesheet, - options: CompileOptions + options: CompileOptions, + stats: Stats ) { const format = options.format || 'cjs'; - const generator = new SsrGenerator(parsed, source, options.name || 'SvelteComponent', stylesheet, options); + const generator = new SsrGenerator(parsed, source, options.name || 'SvelteComponent', stylesheet, options, stats); const { computations, name, templateProperties } = generator; diff --git a/src/index.ts b/src/index.ts index cc3c0681e5..3167498f85 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,6 +2,7 @@ import parse from './parse/index'; import validate from './validate/index'; import generate from './generators/dom/index'; import generateSSR from './generators/server-side-rendering/index'; +import Stats from './Stats'; import { assign } from './shared/index.js'; import Stylesheet from './css/Stylesheet'; import { Parsed, CompileOptions, Warning, PreprocessOptions, Preprocessor } from './interfaces'; @@ -109,20 +110,28 @@ export function compile(source: string, _options: CompileOptions) { const options = normalizeOptions(_options); let parsed: Parsed; + const stats = new Stats(); + try { + stats.start('parse'); parsed = parse(source, options); + stats.stop('parse'); } catch (err) { options.onerror(err); return; } + stats.start('stylesheet'); const stylesheet = new Stylesheet(source, parsed, options.filename, options.cascade !== false, options.dev); + stats.stop('stylesheet'); + stats.start('validate'); validate(parsed, source, stylesheet, options); + stats.stop('validate'); const compiler = options.generate === 'ssr' ? generateSSR : generate; - return compiler(parsed, source, stylesheet, options); + return compiler(parsed, source, stylesheet, options, stats); }; export function create(source: string, _options: CompileOptions = {}) { diff --git a/src/interfaces.ts b/src/interfaces.ts index 07692991bb..d44c6a030c 100644 --- a/src/interfaces.ts +++ b/src/interfaces.ts @@ -93,4 +93,4 @@ export interface PreprocessOptions { filename?: string } -export type Preprocessor = (options: {content: string, attributes: Record, filename?: string}) => { code: string, map?: SourceMap | string }; +export type Preprocessor = (options: {content: string, attributes: Record, filename?: string}) => { code: string, map?: SourceMap | string }; \ No newline at end of file