mirror of
https://github.com/jlfwong/speedscope.git
synced 2024-10-06 15:17:07 +03:00
Switch to browserify for better iteration time
This commit is contained in:
parent
f6772e3498
commit
355ceb8c6c
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,2 +1 @@
|
||||
node_modules
|
||||
dist/speedscope-dev.js
|
||||
|
73
aphrodite.d.ts
vendored
Normal file
73
aphrodite.d.ts
vendored
Normal file
@ -0,0 +1,73 @@
|
||||
// Type definitions for Aphrodite 0.5.0
|
||||
// Project: https://github.com/Khan/aphrodite
|
||||
// Definitions by: Alexey Svetliakov <https://github.com/asvetliakov>
|
||||
// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
|
||||
// TypeScript Version: 2.3
|
||||
|
||||
declare module 'aphrodite' {
|
||||
// Modified to remove dependency on 'react'
|
||||
|
||||
/**
|
||||
* Aphrodite style declaration
|
||||
*/
|
||||
export interface StyleDeclaration {
|
||||
[key: string]: any
|
||||
}
|
||||
|
||||
interface StyleSheetStatic {
|
||||
/**
|
||||
* Create style sheet
|
||||
*/
|
||||
create<T extends StyleDeclaration>(styles: T): T;
|
||||
/**
|
||||
* Rehydrate class names from server renderer
|
||||
*/
|
||||
rehydrate(renderedClassNames: string[]): void;
|
||||
}
|
||||
|
||||
export var StyleSheet: StyleSheetStatic;
|
||||
/**
|
||||
* Get class names from passed styles
|
||||
*/
|
||||
export function css(...styles: any[]): string;
|
||||
|
||||
interface StaticRendererResult {
|
||||
html: string;
|
||||
css: {
|
||||
content: string;
|
||||
renderedClassNames: string[];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Utilities for using Aphrodite server-side.
|
||||
*/
|
||||
interface StyleSheetServerStatic {
|
||||
renderStatic(renderFunc: () => string): StaticRendererResult;
|
||||
}
|
||||
|
||||
export var StyleSheetServer: StyleSheetServerStatic;
|
||||
|
||||
interface StyleSheetTestUtilsStatic {
|
||||
/**
|
||||
* Prevent styles from being injected into the DOM.
|
||||
*
|
||||
* This is useful in situations where you'd like to test rendering UI
|
||||
* components which use Aphrodite without any of the side-effects of
|
||||
* Aphrodite happening. Particularly useful for testing the output of
|
||||
* components when you have no DOM, e.g. testing in Node without a fake DOM.
|
||||
*
|
||||
* Should be paired with a subsequent call to
|
||||
* clearBufferAndResumeStyleInjection.
|
||||
*/
|
||||
suppressStyleInjection(): void;
|
||||
/**
|
||||
* Opposite method of preventStyleInject.
|
||||
*/
|
||||
clearBufferAndResumeStyleInjection(): void;
|
||||
}
|
||||
|
||||
export var StyleSheetTestUtils: StyleSheetTestUtilsStatic;
|
||||
|
||||
|
||||
}
|
2
dist/speedscope.js
vendored
2
dist/speedscope.js
vendored
File diff suppressed because one or more lines are too long
@ -1,9 +1,10 @@
|
||||
import {h, render, Component} from 'preact'
|
||||
import {h, Component} from 'preact'
|
||||
import {StyleSheet, css} from 'aphrodite'
|
||||
|
||||
import {Profile, Frame, CallTreeNode} from './profile'
|
||||
import regl, {vec2, vec3, mat3, ReglCommand, ReglCommandConstructor} from 'regl'
|
||||
import { Rect, Vec2, AffineTransform, clamp } from './math'
|
||||
import * as regl from 'regl'
|
||||
import { vec2, vec3, ReglCommand } from 'regl'
|
||||
import { Rect, Vec2, AffineTransform } from './math'
|
||||
import { atMostOnceAFrame } from "./utils";
|
||||
|
||||
enum FontFamily {
|
||||
@ -105,9 +106,7 @@ export class Flamechart {
|
||||
const aParts = parts(a)
|
||||
const bParts = parts(b)
|
||||
|
||||
const matching = 0
|
||||
const minLength = Math.min(aParts.length, bParts.length)
|
||||
const maxLength = Math.max(aParts.length, bParts.length)
|
||||
|
||||
let prefixMatchLength = 0
|
||||
for (let i = 0; i < minLength; i++) {
|
||||
@ -196,7 +195,6 @@ const ELLIPSIS = '\u2026'
|
||||
|
||||
function buildTrimmedText(text: string, length: number) {
|
||||
const prefixLength = Math.floor(length / 2)
|
||||
const suffixLength = Math.ceil(length / 2)
|
||||
const prefix = text.substr(0, prefixLength)
|
||||
const suffix = text.substr(text.length - prefixLength, prefixLength)
|
||||
return prefix + ELLIPSIS + suffix
|
||||
@ -212,7 +210,7 @@ function cachedMeasureTextWidth(ctx: CanvasRenderingContext2D, text: string): nu
|
||||
|
||||
function trimTextMid(ctx: CanvasRenderingContext2D, text: string, maxWidth: number) {
|
||||
if (cachedMeasureTextWidth(ctx, text) <= maxWidth) return text
|
||||
const [lo, hi] = binarySearch(0, text.length, (n) => {
|
||||
const [lo,] = binarySearch(0, text.length, (n) => {
|
||||
return cachedMeasureTextWidth(ctx, buildTrimmedText(text, n))
|
||||
}, maxWidth)
|
||||
return buildTrimmedText(text, lo)
|
||||
@ -246,7 +244,7 @@ interface FlamechartPanZoomViewProps {
|
||||
setNodeHover: (node: CallTreeNode | null, logicalViewSpacemous: Vec2) => void
|
||||
}
|
||||
|
||||
export class FlamechartPanZoomView extends Component<FlamechartPanZoomViewProps, void> {
|
||||
export class FlamechartPanZoomView extends Component<FlamechartPanZoomViewProps, {}> {
|
||||
renderer: ReglCommand<RectangleBatchRendererProps> | null = null
|
||||
|
||||
ctx: WebGLRenderingContext | null = null
|
||||
@ -265,9 +263,6 @@ export class FlamechartPanZoomView extends Component<FlamechartPanZoomViewProps,
|
||||
const colors: vec3[] = []
|
||||
|
||||
const layers = flamechart.getLayers()
|
||||
const duration = flamechart.getDuration()
|
||||
const maxStackHeight = layers.length
|
||||
|
||||
const frameColors = flamechart.getFrameColors()
|
||||
|
||||
this.labels = []
|
||||
@ -329,7 +324,6 @@ export class FlamechartPanZoomView extends Component<FlamechartPanZoomViewProps,
|
||||
}
|
||||
|
||||
private LOGICAL_VIEW_SPACE_FRAME_HEIGHT = 16
|
||||
private LOGICAL_VIEW_SPACE_LABEL_FONT_SIZE = 12
|
||||
|
||||
private configSpaceToPhysicalViewSpace() {
|
||||
return AffineTransform.betweenRects(
|
||||
@ -564,8 +558,6 @@ export class FlamechartPanZoomView extends Component<FlamechartPanZoomViewProps,
|
||||
|
||||
if (!configSpaceMouse) return
|
||||
|
||||
let labelUnderMouse: FlamechartFrameLabel | null = null
|
||||
|
||||
// This could be sped up significantly
|
||||
for (let label of this.labels) {
|
||||
if (label.configSpaceBounds.contains(configSpaceMouse)) {
|
||||
@ -718,11 +710,9 @@ export class FlamechartView extends Component<FlamechartViewProps, FlamechartVie
|
||||
)
|
||||
}
|
||||
|
||||
containerRef = (container: HTMLDivElement) => { this.container = container }
|
||||
containerRef = (container?: Element) => { this.container = container as HTMLDivElement || null }
|
||||
|
||||
render() {
|
||||
const { flamechart } = this.props
|
||||
|
||||
return (
|
||||
<div className={css(style.fill, style.clip)} ref={this.containerRef}>
|
||||
<FlamechartPanZoomView
|
||||
|
45
index.html
45
index.html
@ -5,50 +5,7 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
||||
<title>SpeedScope</title>
|
||||
<style>
|
||||
html, body, div, span, applet, object, iframe,
|
||||
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
|
||||
a, abbr, acronym, address, big, cite, code,
|
||||
del, dfn, em, img, ins, kbd, q, s, samp,
|
||||
small, strike, strong, sub, sup, tt, var,
|
||||
b, u, i, center,
|
||||
dl, dt, dd, ol, ul, li,
|
||||
fieldset, form, label, legend,
|
||||
table, caption, tbody, tfoot, thead, tr, th, td,
|
||||
article, aside, canvas, details, embed,
|
||||
figure, figcaption, footer, header, hgroup,
|
||||
menu, nav, output, ruby, section, summary,
|
||||
time, mark, audio, video {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
font-size: 100%;
|
||||
font: inherit;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
/* HTML5 display-role reset for older browsers */
|
||||
article, aside, details, figcaption, figure,
|
||||
footer, header, hgroup, menu, nav, section {
|
||||
display: block;
|
||||
}
|
||||
body {
|
||||
line-height: 1;
|
||||
}
|
||||
ol, ul {
|
||||
list-style: none;
|
||||
}
|
||||
blockquote, q {
|
||||
quotes: none;
|
||||
}
|
||||
blockquote:before, blockquote:after,
|
||||
q:before, q:after {
|
||||
content: '';
|
||||
content: none;
|
||||
}
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
border-spacing: 0;
|
||||
}
|
||||
<link rel="stylesheet" href="reset.css">
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
@ -6,14 +6,12 @@ import {importFromBGFlameGraph} from './import/bg-flamegraph'
|
||||
import {Profile} from './profile'
|
||||
import {Flamechart, FlamechartView} from './flamechart'
|
||||
|
||||
import { request } from 'https';
|
||||
|
||||
interface ApplicaionState {
|
||||
interface ApplicationState {
|
||||
profile: Profile | null
|
||||
flamechart: Flamechart | null
|
||||
}
|
||||
|
||||
class Application extends Component<{}, ApplicaionState> {
|
||||
class Application extends Component<{}, ApplicationState> {
|
||||
onDrop = (ev: DragEvent) => {
|
||||
const reader = new FileReader
|
||||
reader.addEventListener('loadend', () => {
|
||||
|
23
package.json
23
package.json
@ -4,26 +4,19 @@
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"serve": "./serveit -i node_modules 'node_modules/.bin/rollup -c rollup.config.dev.js'",
|
||||
"release": "rollup -c rollup.config.release.js"
|
||||
"serve": "budo index.tsx --force-default-index --css reset.css -- -p tsify",
|
||||
"release": "browserify index.tsx -p tsify | uglifyjs -cm > dist/speedscope.js"
|
||||
},
|
||||
"author": "",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@types/aphrodite": "^0.5.6",
|
||||
"@types/react-dom": "^16.0.3",
|
||||
"aphrodite": "^1.2.5",
|
||||
"preact": "^8.2.6",
|
||||
"react": "^16.1.1",
|
||||
"react-dom": "^16.1.1",
|
||||
"browserify": "^14.5.0",
|
||||
"budo": "^10.0.4",
|
||||
"preact": "^8.2.7",
|
||||
"regl": "^1.3.0",
|
||||
"rollup": "^0.51.5",
|
||||
"rollup-plugin-commonjs": "^8.2.6",
|
||||
"rollup-plugin-node-globals": "^1.1.0",
|
||||
"rollup-plugin-node-resolve": "^3.0.0",
|
||||
"rollup-plugin-replace": "^2.0.0",
|
||||
"rollup-plugin-typescript": "^0.8.1",
|
||||
"rollup-plugin-uglify-es": "0.0.1",
|
||||
"typescript": "^2.6.1"
|
||||
"tsify": "^3.0.4",
|
||||
"typescript": "^2.6.1",
|
||||
"uglify-es": "^3.2.2"
|
||||
}
|
||||
}
|
||||
|
@ -115,6 +115,9 @@ export class Profile {
|
||||
this.duration = duration
|
||||
}
|
||||
|
||||
getDuration() { return this.duration }
|
||||
getEvents() { return this.events }
|
||||
|
||||
forEachSample(fn: (stack: CallTreeNode[], timeDelta: number) => void) {
|
||||
const nodeToStack = new Map<CallTreeNode, CallTreeNode[]>()
|
||||
for (let i = 0; i < this.samples.length; i++) {
|
||||
|
32
regl.d.ts
vendored
32
regl.d.ts
vendored
@ -10,10 +10,6 @@ declare module "regl" {
|
||||
|
||||
type ReglValue<P> = ReglPrimitiveValue | {(context: any, props: P, batchId: number): ReglPrimitiveValue}
|
||||
|
||||
export type vec2 = [number, number]
|
||||
export type vec3 = [number, number, number]
|
||||
export type mat3 = [number, number, number, number, number, number, number, number, number]
|
||||
|
||||
interface ReglCommandParameters<P> {
|
||||
/** Source code of vertex shader */
|
||||
vert: string
|
||||
@ -35,21 +31,25 @@ declare module "regl" {
|
||||
offset?: number
|
||||
}
|
||||
|
||||
export interface ReglCommand<P> {
|
||||
(p: P): void
|
||||
}
|
||||
|
||||
export interface ReglCommandConstructor {
|
||||
<P>(params: ReglCommandParameters<P>): ReglCommand<P>
|
||||
}
|
||||
|
||||
function ReglConstructor(): ReglCommandConstructor
|
||||
function ReglConstructor(ctx: WebGLRenderingContext): ReglCommandConstructor
|
||||
function ReglConstructor(canvas: HTMLCanvasElement): ReglCommandConstructor
|
||||
function ReglConstructor(): ReglConstructor.ReglCommandConstructor
|
||||
function ReglConstructor(ctx: WebGLRenderingContext): ReglConstructor.ReglCommandConstructor
|
||||
function ReglConstructor(canvas: HTMLCanvasElement): ReglConstructor.ReglCommandConstructor
|
||||
|
||||
namespace ReglConstructor {
|
||||
function prop(name: string): ReglProp
|
||||
|
||||
export type vec2 = [number, number]
|
||||
export type vec3 = [number, number, number]
|
||||
export type mat3 = [number, number, number, number, number, number, number, number, number]
|
||||
|
||||
interface ReglCommand<P> {
|
||||
(p: P): void
|
||||
}
|
||||
|
||||
interface ReglCommandConstructor {
|
||||
<P>(params: ReglCommandParameters<P>): ReglCommand<P>
|
||||
}
|
||||
}
|
||||
|
||||
export default ReglConstructor
|
||||
export = ReglConstructor
|
||||
}
|
||||
|
@ -1,11 +1,7 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
||||
<title>SpeedScope</title>
|
||||
<style>
|
||||
/* http://meyerweb.com/eric/tools/css/reset/
|
||||
v2.0 | 20110126
|
||||
License: none (public domain)
|
||||
*/
|
||||
html, body, div, span, applet, object, iframe,
|
||||
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
|
||||
a, abbr, acronym, address, big, cite, code,
|
||||
@ -48,11 +44,4 @@ q:before, q:after {
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
border-spacing: 0;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script src="dist/speedscope-dev.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
}
|
@ -1,42 +0,0 @@
|
||||
import commonjs from 'rollup-plugin-commonjs';
|
||||
import nodeResolve from 'rollup-plugin-node-resolve';
|
||||
import typescript from 'rollup-plugin-typescript';
|
||||
import replace from 'rollup-plugin-replace';
|
||||
import nodeGlobals from 'rollup-plugin-node-globals';
|
||||
import uglify from 'rollup-plugin-uglify-es';
|
||||
|
||||
export function config(isRelease) {
|
||||
const plugins = [
|
||||
typescript({
|
||||
typescript: require('typescript')
|
||||
}),
|
||||
nodeResolve({
|
||||
jsnext: true,
|
||||
main: true
|
||||
}),
|
||||
commonjs({
|
||||
include: 'node_modules/**',
|
||||
namedExports: {
|
||||
'aphrodite': ['StyleSheet', 'css'],
|
||||
'react-dom': ['render'],
|
||||
'react': ['Component', 'createElement']
|
||||
},
|
||||
ignore: [ 'domain' ]
|
||||
}),
|
||||
replace({
|
||||
'process.env.NODE_ENV': JSON.stringify(isRelease ? 'prod' : 'development')
|
||||
}),
|
||||
nodeGlobals()
|
||||
]
|
||||
|
||||
if (isRelease) plugins.push(uglify())
|
||||
|
||||
return [{
|
||||
input: 'index.tsx',
|
||||
output: {
|
||||
file: isRelease ? 'dist/speedscope.js' : 'dist/speedscope-dev.js',
|
||||
format: 'iife'
|
||||
},
|
||||
plugins: plugins
|
||||
}]
|
||||
}
|
@ -1,2 +0,0 @@
|
||||
import {config} from './rollup.config.common.js'
|
||||
export default config(false)
|
@ -1,2 +0,0 @@
|
||||
import {config} from './rollup.config.common.js'
|
||||
export default config(true)
|
227
serveit
227
serveit
@ -1,227 +0,0 @@
|
||||
#!/usr/bin/env ruby
|
||||
|
||||
# Copyright (c) 2015 Gary Bernhardt
|
||||
#
|
||||
# MIT License
|
||||
#
|
||||
# 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.
|
||||
|
||||
require "find"
|
||||
require "webrick"
|
||||
require "open3"
|
||||
require "optparse"
|
||||
|
||||
class ServeIt
|
||||
VERSION = [0, 0, 2]
|
||||
|
||||
def self.main
|
||||
serve_dir, ignored_paths, command = parse_opts
|
||||
serve_dir = File.expand_path(serve_dir)
|
||||
Server.new(serve_dir, ignored_paths, command).serve
|
||||
end
|
||||
|
||||
def self.parse_opts
|
||||
options = {:serve_dir => ".",
|
||||
:ignored_paths => []}
|
||||
parser = OptionParser.new do |opts|
|
||||
opts.banner = "Usage: #{$PROGRAM_NAME} [options] command"
|
||||
opts.on_tail("-s", "--serve-dir DIR", "Root directory for server") do |dir|
|
||||
options[:serve_dir] = dir
|
||||
end
|
||||
opts.on_tail("-i", "--ignore PATH", "Ignore changes to file or directory") do |path|
|
||||
options[:ignored_paths] << path
|
||||
end
|
||||
opts.on_tail("--version", "Show version") do |dir|
|
||||
puts ServeIt::VERSION.join('.')
|
||||
exit
|
||||
end
|
||||
end
|
||||
|
||||
begin
|
||||
parser.parse!(ARGV)
|
||||
rescue OptionParser::InvalidOption => e
|
||||
$stderr.puts e
|
||||
$stderr.puts parser
|
||||
exit 1
|
||||
end
|
||||
|
||||
if ARGV.count == 0
|
||||
command = nil
|
||||
elsif ARGV.count == 1
|
||||
command = ARGV.fetch(0)
|
||||
else
|
||||
$stderr.write parser.to_s
|
||||
exit 1
|
||||
end
|
||||
|
||||
[options.fetch(:serve_dir), options.fetch(:ignored_paths), command]
|
||||
end
|
||||
|
||||
class Server
|
||||
def initialize(serve_dir, ignored_paths, command)
|
||||
@mutex = Mutex.new
|
||||
@serve_dir = serve_dir
|
||||
@command = command
|
||||
@rebuilder = Rebuilder.new(@command, ignored_paths) if @command
|
||||
end
|
||||
|
||||
def serve
|
||||
port = 8000
|
||||
puts "Starting server at http://localhost:#{port}"
|
||||
server = WEBrick::HTTPServer.new(:Port => port)
|
||||
|
||||
server.mount_proc '/' do |req, res|
|
||||
relative_path = req.path.sub(/^\//, '')
|
||||
local_abs_path = File.absolute_path(relative_path, @serve_dir)
|
||||
|
||||
if relative_path == "favicon.ico"
|
||||
respond_to_favicon(res)
|
||||
else
|
||||
respond_to_path(res, relative_path, local_abs_path)
|
||||
end
|
||||
end
|
||||
|
||||
trap 'INT' do server.shutdown end
|
||||
server.start
|
||||
end
|
||||
|
||||
def respond_to_favicon(res)
|
||||
res.status = 404
|
||||
end
|
||||
|
||||
def respond_to_path(res, relative_path, local_abs_path)
|
||||
begin
|
||||
rebuild_if_needed
|
||||
rescue Rebuilder::RebuildFailed => e
|
||||
return respond_to_error(res, e.to_s)
|
||||
end
|
||||
|
||||
if File.directory?(local_abs_path)
|
||||
respond_to_dir(res, relative_path, local_abs_path)
|
||||
else
|
||||
# We're building a file
|
||||
respond_to_file(res, local_abs_path)
|
||||
end
|
||||
end
|
||||
|
||||
def respond_to_error(res, message)
|
||||
res.content_type = "text/html"
|
||||
res.body = "<pre>" + message + "</pre>"
|
||||
end
|
||||
|
||||
def respond_to_dir(res, rel_path, local_abs_path)
|
||||
res.content_type = "text/html"
|
||||
res.body = (
|
||||
"<p><h3>Listing for /#{rel_path}</h3></p>\n" +
|
||||
Dir.entries(local_abs_path).select do |child|
|
||||
child != "."
|
||||
end.sort.map do |child|
|
||||
full_child_path_on_server = File.join("/", rel_path, child)
|
||||
%{<a href="#{full_child_path_on_server}">#{child}</a><br>}
|
||||
end.join("\n")
|
||||
)
|
||||
end
|
||||
|
||||
def respond_to_file(res, local_abs_path)
|
||||
res.body = File.read(local_abs_path)
|
||||
res.content_type = guess_content_type(local_abs_path)
|
||||
end
|
||||
|
||||
def guess_content_type(path)
|
||||
extension = File.extname(path).sub(/^\./, '')
|
||||
WEBrick::HTTPUtils::DefaultMimeTypes.fetch(extension) do
|
||||
"application/octet-stream"
|
||||
end
|
||||
end
|
||||
|
||||
def rebuild_if_needed
|
||||
# Webrick is multi-threaded; guard against concurrent builds
|
||||
@mutex.synchronize do
|
||||
if @rebuilder
|
||||
@rebuilder.rebuild_if_needed
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class Rebuilder
|
||||
def initialize(command, ignored_paths)
|
||||
@command = command
|
||||
@ignored_paths = ignored_paths
|
||||
@last_disk_state = nil
|
||||
end
|
||||
|
||||
def rebuild_if_needed
|
||||
if disk_state != @last_disk_state
|
||||
stdout_and_stderr, success = rebuild
|
||||
if !success
|
||||
message = "Failed to build! Command output:\n\n" + stdout_and_stderr
|
||||
raise RebuildFailed.new(message)
|
||||
end
|
||||
|
||||
# Get a new post-build disk state so we don't pick up changes made during
|
||||
# the build.
|
||||
@last_disk_state = disk_state
|
||||
[stdout_and_stderr, success]
|
||||
end
|
||||
end
|
||||
|
||||
def rebuild
|
||||
puts "Running command: #{@command}"
|
||||
puts " begin build".rjust(80, "=")
|
||||
start_time = Time.now
|
||||
stdout_and_stderr, status = Open3.capture2e(@command)
|
||||
print stdout_and_stderr
|
||||
puts (" built in %.03fs" % (Time.now - start_time)).rjust(80, "=")
|
||||
[stdout_and_stderr, status.success?]
|
||||
end
|
||||
|
||||
def disk_state
|
||||
start_time = Time.now
|
||||
paths = []
|
||||
|
||||
Find.find(".") do |path|
|
||||
if ignore_path?(path)
|
||||
Find.prune
|
||||
else
|
||||
paths << path
|
||||
end
|
||||
end
|
||||
|
||||
paths.map do |path|
|
||||
[path, File.stat(path).mtime.to_s]
|
||||
end.sort.tap do
|
||||
puts (" scanned in %.03fs" % (Time.now - start_time)).rjust(80, "=")
|
||||
end
|
||||
end
|
||||
|
||||
def ignore_path?(path)
|
||||
@ignored_paths.any? do |ignored_path|
|
||||
File.absolute_path(path) == File.absolute_path(ignored_path)
|
||||
end
|
||||
end
|
||||
|
||||
class RebuildFailed < RuntimeError; end
|
||||
end
|
||||
end
|
||||
|
||||
if __FILE__ == $PROGRAM_NAME
|
||||
ServeIt.main
|
||||
end
|
@ -3,6 +3,7 @@
|
||||
"module": "commonjs",
|
||||
"strict": true,
|
||||
"jsx": "react",
|
||||
"noUnusedLocals": true,
|
||||
"jsxFactory": "h",
|
||||
"target": "es2015"
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user