├── presentation
├── index.js
├── .gitignore
├── slides
│ ├── 0_title.md
│ ├── 10_agenda.md
│ ├── 99_thank_you.md
│ ├── 30_summary.md
│ └── 20_main.md
├── assets
│ └── ogp.png
├── .fusumarc.yml
├── package.json
├── styles.css
└── gulpfile.js
├── ls-web-local
├── .gitignore
├── src
│ ├── consts.ts
│ ├── util.ts
│ ├── components
│ │ ├── Editor.tsx
│ │ ├── App.tsx
│ │ ├── Errors.tsx
│ │ └── Logger.tsx
│ ├── types.ts
│ ├── index.tsx
│ ├── logger.ts
│ ├── contexts
│ │ ├── LscContext.tsx
│ │ └── ConfigureContext.tsx
│ ├── lang-service
│ │ ├── lib-defitions.ts
│ │ ├── svc.ts
│ │ ├── project.ts
│ │ ├── script-info.ts
│ │ └── lang-service.ts
│ ├── index.html
│ ├── editor
│ │ └── index.ts
│ └── lang-service-client
│ │ └── index.ts
├── package.json
├── webpack.config.js
├── .yo-rc.json
└── tsconfig.json
├── sample-projects
└── symple
│ ├── .gitignore
│ ├── test_tsserver.sh
│ ├── package.json
│ ├── yarn.lock
│ └── tsconfig.json
├── ls-sample
├── package.json
├── src
│ └── parse-performance.ts
├── tsconfig.json
└── yarn.lock
├── README.md
├── LICENSE
└── .gitignore
/presentation/index.js:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/ls-web-local/.gitignore:
--------------------------------------------------------------------------------
1 | dist/
2 | built/
3 |
--------------------------------------------------------------------------------
/presentation/.gitignore:
--------------------------------------------------------------------------------
1 | dist/
2 | fusuma_wd/
3 | .figmatoken
4 |
--------------------------------------------------------------------------------
/sample-projects/symple/.gitignore:
--------------------------------------------------------------------------------
1 | .vscode/
2 | sample.ts
3 | *.log
4 |
--------------------------------------------------------------------------------
/ls-web-local/src/consts.ts:
--------------------------------------------------------------------------------
1 | export const TS_LIB_FILE_PREFIX = "tslib://";
2 |
--------------------------------------------------------------------------------
/presentation/slides/0_title.md:
--------------------------------------------------------------------------------
1 | # **TypeScript Server Side Anatomy**
2 |
3 | ### TypeScript meetup #1
4 |
--------------------------------------------------------------------------------
/presentation/slides/10_agenda.md:
--------------------------------------------------------------------------------
1 | ## About me
2 |
3 | - Yosuke Kurami (@Quramy)
4 | - Web front-end developer
5 |
--------------------------------------------------------------------------------
/presentation/assets/ogp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Quramy/ts-server-side-anatomy/HEAD/presentation/assets/ogp.png
--------------------------------------------------------------------------------
/presentation/slides/99_thank_you.md:
--------------------------------------------------------------------------------
1 | # Thank you!
2 |
3 | [*https://github.com/Quramy/tsjp-resources*](https://github.com/Quramy/tsjp-resources)
4 |
--------------------------------------------------------------------------------
/presentation/slides/30_summary.md:
--------------------------------------------------------------------------------
1 | ## **Summray**
2 |
3 | ---
4 |
5 | ### TypeScript server implements:
6 |
7 | * Editor agnostic protocol
8 | * High-performance mutation
9 | * Script file updating
10 | * Incremental parsing
11 |
12 | ---
13 |
14 | # **tsserver is awesome** 😎
15 |
--------------------------------------------------------------------------------
/sample-projects/symple/test_tsserver.sh:
--------------------------------------------------------------------------------
1 | cat << EOF > sample.ts
2 | function hoge(x: string) {
3 | console.log(x);
4 | }
5 | EOF
6 |
7 | npx tsserver << EOF | tail -n 1 | jq .body
8 | {"command":"open","arguments":{"file":"${PWD}/sample.ts"}}
9 | {"command":"quickinfo","arguments":{"file":"${PWD}/sample.ts","line":2,"offset":15}}
10 | EOF
11 |
--------------------------------------------------------------------------------
/sample-projects/symple/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "symple",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1"
8 | },
9 | "keywords": [],
10 | "author": "",
11 | "license": "ISC",
12 | "dependencies": {
13 | "typescript": "^3.5.1"
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/ls-web-local/src/util.ts:
--------------------------------------------------------------------------------
1 | import { Location } from "./types";
2 |
3 | export function pos2loc(input: { row: number, column: number }) {
4 | return {
5 | line: input.row + 1,
6 | offset: input.column + 1,
7 | } as Location;
8 | }
9 |
10 | export function loc2pos(input: Location) {
11 | return {
12 | row: input.line - 1,
13 | column: input.offset - 1,
14 | };
15 | }
16 |
--------------------------------------------------------------------------------
/sample-projects/symple/yarn.lock:
--------------------------------------------------------------------------------
1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
2 | # yarn lockfile v1
3 |
4 |
5 | typescript@^3.5.1:
6 | version "3.5.1"
7 | resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.5.1.tgz#ba72a6a600b2158139c5dd8850f700e231464202"
8 | integrity sha512-64HkdiRv1yYZsSe4xC1WVgamNigVYjlssIoaH2HcZF0+ijsk5YK2g0G34w9wJkze8+5ow4STd22AynfO6ZYYLw==
9 |
--------------------------------------------------------------------------------
/ls-web-local/src/components/Editor.tsx:
--------------------------------------------------------------------------------
1 | import React, { useContext } from "react";
2 | import { setupEditor } from "../editor";
3 | import { lscContext } from "../contexts/LscContext";
4 | import { conigCtx } from "../contexts/ConfigureContext";
5 |
6 | export const Editor = () => {
7 | const lsp = useContext(lscContext);
8 | return (
9 |
setupEditor(ref, lsp)} />
10 | );
11 | }
12 |
--------------------------------------------------------------------------------
/ls-web-local/src/types.ts:
--------------------------------------------------------------------------------
1 | import { Ace } from "ace-builds";
2 | import { Observable } from "rxjs";
3 |
4 | export type Location = {
5 | line: number,
6 | offset: number,
7 | };
8 |
9 | export type StreamType
= S extends Observable ? T : never;
10 |
11 | export type Optional = {[P in keyof T]?: T[P]};
12 |
13 | export type Configuration = {
14 | initialContent: string,
15 | debounceTime: number,
16 | useComplete: boolean,
17 | showLogger: boolean,
18 | };
19 |
--------------------------------------------------------------------------------
/ls-sample/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ls-sample",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1"
8 | },
9 | "keywords": [],
10 | "author": "",
11 | "license": "ISC",
12 | "dependencies": {
13 | "@types/react": "^16.8.19",
14 | "typescript": "^3.5.1"
15 | },
16 | "devDependencies": {
17 | "@types/node": "^12.0.3",
18 | "ts-node-dev": "^1.0.0-pre.39"
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/presentation/.fusumarc.yml:
--------------------------------------------------------------------------------
1 | meta:
2 | url: https://quramy.github.io/ts-server-side-anatomy/
3 | name: TypeScript Server Side Anatomy
4 | author: Quramy
5 | thumbnail: https://quramy.github.io/ts-server-side-anatomy/assets/ogp.png
6 | description: Explain what TypeScript server(tsserver) does under your editors
7 | sns:
8 | - twitter
9 | slide:
10 | loop: false
11 | sidebar: true
12 | targetBlank: true
13 | showIndex: false
14 | isVertical: false
15 | code:
16 | languages:
17 | - bash
18 | - javascript
19 | - typescript
20 | plugins: []
21 | theme: default
22 | extends:
23 | js: index.js
24 | css: styles.css
25 |
--------------------------------------------------------------------------------
/ls-web-local/src/index.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { render } from "react-dom";
3 | import { App } from "./components/App";
4 |
5 | import { LscProvider } from "./contexts/LscContext";
6 | import { rootLogger } from "./logger";
7 | import { ConfigProvider, conigCtx } from "./contexts/ConfigureContext";
8 |
9 | const elm = document.getElementById("app");
10 |
11 | rootLogger.getStream().subscribe(x => console.log(x));
12 |
13 | if (elm) {
14 | const { Consumer } = conigCtx;
15 | render((
16 |
17 |
18 | {({ initial }) => }
19 |
20 |
21 | ), elm);
22 | }
23 |
--------------------------------------------------------------------------------
/presentation/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "presentation",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "start": "fusuma start",
8 | "prebuild": "rimraf dist && gulp",
9 | "build": "fusuma build -d fusuma_wd && mv fusuma_wd/dist dist",
10 | "postbuild": "gulp postBuild",
11 | "deploy": "gh-pages -d dist"
12 | },
13 | "keywords": [],
14 | "author": "",
15 | "license": "ISC",
16 | "devDependencies": {
17 | "fusuma": "^1.1.2",
18 | "gh-pages": "^2.0.1",
19 | "gulp": "^4.0.2",
20 | "gulp-replace": "^1.0.0",
21 | "image-downloader": "^3.4.2",
22 | "request": "^2.88.0",
23 | "request-promise": "^4.2.4",
24 | "rimraf": "^2.6.3"
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/ls-web-local/src/logger.ts:
--------------------------------------------------------------------------------
1 | import { ReplaySubject } from "rxjs";
2 |
3 | export type LoggerArguments = {
4 | id: string,
5 | category: string,
6 | eventName: string,
7 | args: any[],
8 | };
9 |
10 | let version = 0;
11 |
12 | const subject = new ReplaySubject(200);
13 |
14 | export const rootLogger = {
15 | log(category: string, eventName: string, ...args: any[]) {
16 | subject.next({ id: ++version + "", category, eventName, args });
17 | },
18 | getCategory(category: string) {
19 | return {
20 | log(eventName: string, ...args: any[]) {
21 | subject.next({ id: ++version + "", category, eventName, args });
22 | }
23 | };
24 | },
25 | getStream() {
26 | return subject.asObservable();
27 | },
28 | };
29 |
--------------------------------------------------------------------------------
/ls-web-local/src/contexts/LscContext.tsx:
--------------------------------------------------------------------------------
1 | import React, { useContext, createContext } from "react";
2 | import { Configuration } from "../types";
3 | import { LsClient } from "../lang-service-client";
4 |
5 | const ctx = createContext(null as any);
6 | const { Provider } = ctx;
7 |
8 | export type Props = {
9 | config: Configuration,
10 | };
11 |
12 | export class LscProvider extends React.Component {
13 | private lsc = new LsClient({
14 | initialContents: [{ fileName: "/main.ts", content: this.props.config.initialContent }],
15 | debounceTime: this.props.config.debounceTime,
16 | useComplete: this.props.config.useComplete,
17 | });
18 |
19 | render() {
20 | return (
21 |
22 | {this.props.children}
23 |
24 | )
25 | }
26 | }
27 |
28 | export const lscContext = ctx;
29 |
--------------------------------------------------------------------------------
/ls-web-local/src/components/App.tsx:
--------------------------------------------------------------------------------
1 | import React, { useContext } from "react";
2 | import { setupEditor } from "../editor";
3 | import SplitPane from "react-split-pane";
4 | import { Errors } from "./Errors";
5 | import { Logger } from "./Logger";
6 | import { Editor } from "./Editor";
7 | import { conigCtx } from "../contexts/ConfigureContext";
8 |
9 | export const App = () => {
10 | const config = useContext(conigCtx);
11 | const { showLogger } = config.initial;
12 | if (showLogger) {
13 | return (
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 | );
22 | } else {
23 | return (
24 |
25 |
26 |
27 |
28 | );
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # TypeScript Server Side Anatomy
2 |
3 | I've created this repo to explain **what TypeScript server(tsserver) does under your editor**.
4 |
5 | - How editor/IDEs communicate with tsserver
6 | - File structure(Script Version Cache) in language service host
7 | - How TS's incremental parser works
8 | - etc...
9 |
10 | This repository, created for tsjp meetup #1, includes the following:
11 |
12 | - Presentation slides
13 | - Web editor application using TS language service
14 | - Test script to measure TS's incremental parser performance
15 |
16 | ## Presentation slides
17 |
18 | [https://quramy.github.io/ts-server-side-anatomy](https://quramy.github.io/ts-server-side-anatomy)
19 |
20 |
21 | ## Web editor application using TS language service
22 |
23 | See [ls-web-local](ls-web-local) dir.
24 |
25 | And you can check live demonstration out [here](https://quramy.github.io/ts-server-side-anatomy/assets/editor/dist/index.html?no-delay=true).
26 |
27 | ## Figures
28 |
29 | See [Figma](https://www.figma.com/file/DhwRUPAASvvdlFcM0BlRYhE3/ts-meetup-images).
30 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 Yosuke Kurami
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 |
8 | # Runtime data
9 | pids
10 | *.pid
11 | *.seed
12 | *.pid.lock
13 |
14 | # Directory for instrumented libs generated by jscoverage/JSCover
15 | lib-cov
16 |
17 | # Coverage directory used by tools like istanbul
18 | coverage
19 |
20 | # nyc test coverage
21 | .nyc_output
22 |
23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
24 | .grunt
25 |
26 | # Bower dependency directory (https://bower.io/)
27 | bower_components
28 |
29 | # node-waf configuration
30 | .lock-wscript
31 |
32 | # Compiled binary addons (https://nodejs.org/api/addons.html)
33 | build/Release
34 |
35 | # Dependency directories
36 | node_modules/
37 | jspm_packages/
38 |
39 | # TypeScript v1 declaration files
40 | typings/
41 |
42 | # Optional npm cache directory
43 | .npm
44 |
45 | # Optional eslint cache
46 | .eslintcache
47 |
48 | # Optional REPL history
49 | .node_repl_history
50 |
51 | # Output of 'npm pack'
52 | *.tgz
53 |
54 | # Yarn Integrity file
55 | .yarn-integrity
56 |
57 | # dotenv environment variables file
58 | .env
59 |
60 | # next.js build output
61 | .next
62 |
--------------------------------------------------------------------------------
/ls-web-local/src/lang-service/lib-defitions.ts:
--------------------------------------------------------------------------------
1 | import { TS_LIB_FILE_PREFIX } from "../consts";
2 |
3 | const rawFileMap = new Map();
4 |
5 | rawFileMap.set("/lib.d.ts", require("typescript/lib/lib.d.ts").default);
6 | rawFileMap.set("/lib.es5.d.ts", require("typescript/lib/lib.es5.d.ts").default);
7 | rawFileMap.set("/lib.esnext.d.ts", require("typescript/lib/lib.esnext.d.ts").default);
8 | rawFileMap.set("/lib.esnext.full.d.ts", require("typescript/lib/lib.esnext.full.d.ts").default);
9 | rawFileMap.set("/lib.dom.d.ts", require("typescript/lib/lib.dom.d.ts").default);
10 | rawFileMap.set("/lib.webworker.importscripts.d.ts", require("typescript/lib/lib.webworker.importscripts.d.ts").default);
11 | rawFileMap.set("/lib.scripthost.d.ts", require("typescript/lib/lib.scripthost.d.ts").default);
12 |
13 | export function getTypeScriptDefinitionText(name: string) {
14 | if (!name.startsWith(TS_LIB_FILE_PREFIX)) {
15 | throw new Error("invalid prefix: " + name);
16 | }
17 | const libname = name.slice(TS_LIB_FILE_PREFIX.length);
18 | const content = rawFileMap.get(libname);
19 | if (!content) {
20 | throw new Error("not included lib.d.ts: " + libname);
21 | }
22 | return content;
23 | };
24 |
--------------------------------------------------------------------------------
/ls-web-local/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ls-web-local",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "start": "webpack-dev-server",
8 | "prebuild": "rimraf dist",
9 | "build": "webpack --mode production",
10 | "postbuild": "rimraf ../presentation/assets/editor && mkdir -p ../presentation/assets/editor && cp -rf dist ../presentation/assets/editor",
11 | "test": "echo \"Error: no test specified\" && exit 1"
12 | },
13 | "keywords": [],
14 | "author": "",
15 | "license": "ISC",
16 | "devDependencies": {
17 | "file-loader": "^3.0.1",
18 | "html-webpack-plugin": "^3.2.0",
19 | "raw-loader": "^2.0.0",
20 | "rimraf": "^2.6.3",
21 | "ts-loader": "^6.0.2",
22 | "uglifyjs-webpack-plugin": "^2.1.3",
23 | "webpack": "^4.32.2",
24 | "webpack-cli": "^3.3.2",
25 | "webpack-dev-server": "^3.4.1"
26 | },
27 | "dependencies": {
28 | "@types/node": "^12.0.4",
29 | "@types/react": "^16.8.19",
30 | "@types/react-dom": "^16.8.4",
31 | "ace-builds": "^1.4.4",
32 | "emotion": "^10.0.9",
33 | "react": "^16.8.6",
34 | "react-dom": "^16.8.6",
35 | "react-json-view": "^1.19.1",
36 | "react-split-pane": "^0.1.87",
37 | "rxjs": "^6.5.2",
38 | "typescript": "^3.5.1"
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/ls-web-local/src/lang-service/svc.ts:
--------------------------------------------------------------------------------
1 | import * as ts from "typescript/lib/tsserverlibrary";
2 | import { Location } from "../types";
3 |
4 | interface AbsolutePositionAndLineText {
5 | absolutePosition: number;
6 | lineText: string | undefined;
7 | }
8 |
9 | export declare class ScriptVersionCache {
10 | private changes;
11 | private readonly versions;
12 | private minVersion;
13 | private currentVersion;
14 | private static readonly changeNumberThreshold;
15 | private static readonly changeLengthThreshold;
16 | private static readonly maxVersions;
17 | private versionToIndex;
18 | private currentVersionToIndex;
19 | edit(pos: number, deleteLen: number, insertedText?: string): void;
20 | getSnapshot(): ts.IScriptSnapshot;
21 | private _getSnapshot;
22 | getSnapshotVersion(): number;
23 | getAbsolutePositionAndLineText(oneBasedLine: number): AbsolutePositionAndLineText;
24 | lineOffsetToPosition(line: number, column: number): number;
25 | positionToLineOffset(position: number): Location;
26 | lineToTextSpan(line: number): ts.TextSpan;
27 | getTextChangesBetweenVersions(oldVersion: number, newVersion: number): ts.TextChangeRange | undefined;
28 | getLineCount(): number;
29 | static fromString(script: string): ScriptVersionCache;
30 | }
31 |
32 | export function createSvcFromString(script: string) {
33 | return (ts.server as any)["ScriptVersionCache"].fromString(script) as ScriptVersionCache;
34 | }
35 |
--------------------------------------------------------------------------------
/ls-web-local/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
59 |
60 |
61 |
62 |
63 |
64 |
--------------------------------------------------------------------------------
/ls-web-local/src/contexts/ConfigureContext.tsx:
--------------------------------------------------------------------------------
1 | import React, { createContext } from "react";
2 | import { BehaviorSubject } from "rxjs";
3 |
4 | import { Configuration, Optional } from "../types";
5 | import { Observable } from "rxjs";
6 |
7 | export type ConfigHolder = {
8 | initial: Configuration,
9 | stream: Observable,
10 | }
11 |
12 | export const conigCtx = createContext(null as any);
13 |
14 | const content = `const num1 = 1;
15 | const num2 = 1;
16 |
17 | function sum(x: number, y: number) {
18 | return x + y;
19 | }
20 |
21 | console.log(sum(num1, ));`;
22 |
23 | const { Provider } = conigCtx;
24 |
25 | export class ConfigProvider extends React.Component {
26 |
27 | configHolder: ConfigHolder;
28 |
29 | constructor(props: { }) {
30 | super(props);
31 | const q = new URLSearchParams(location.search);
32 |
33 | const initalConfig: Configuration = {
34 | showLogger: q.get("disabled-logger") === "true" ? false : true,
35 | useComplete: q.get("disabled-completion") === "true" ? false : true,
36 | debounceTime: q.get("no-delay") === "true" ? 100: 1500,
37 | initialContent: content,
38 | };
39 |
40 | this.configHolder = {
41 | initial: initalConfig,
42 | stream: new BehaviorSubject(initalConfig),
43 | };
44 | }
45 |
46 | render() {
47 | return (
48 |
49 | {this.props.children}
50 |
51 | )
52 | }
53 |
54 | }
55 |
56 |
--------------------------------------------------------------------------------
/presentation/styles.css:
--------------------------------------------------------------------------------
1 | :root {
2 | --gutter-large: 50px;
3 | --fig-scale: .5;
4 | }
5 |
6 | body {
7 | font-size: 32px;
8 | }
9 |
10 | @media screen and (max-width: 750px) {
11 | :root {
12 | --gutter-large: 20px;
13 | --fig-scale: .18;
14 | }
15 |
16 | body {
17 | font-size: 18px;
18 | }
19 |
20 | pre[class*='language-'],
21 | code[class*='language-'] {
22 | font-size: 12px;
23 | }
24 |
25 | .chart_big {
26 | display: none;
27 | }
28 |
29 | img.figma_fig {
30 | margin: var(--gutter-large) auto;
31 | }
32 | }
33 | @media screen and (min-width: 751px) {
34 | .chart_small {
35 | display: none;
36 | }
37 | }
38 |
39 | section:not(.current) iframe {
40 | display: none;
41 | }
42 |
43 | .editorFrame {
44 | width: 100%;
45 | height: 80vh;
46 | }
47 |
48 | iframe:not(.editorFrame) {
49 | margin: 20px auto;
50 | }
51 |
52 | section.slide > h2 + *:not([id]) {
53 | margin-top: var(--gutter-large);
54 | }
55 |
56 | ul:not(:first-child) {
57 | margin-top: var(--gutter-large);
58 | }
59 |
60 | ul:not(:last-child) {
61 | margin-top: var(--gutter-large);
62 | }
63 |
64 | li > ul:not(:first-child) {
65 | margin-top: 0;
66 | }
67 |
68 | ul>li {
69 | line-height: 1.6;
70 | }
71 |
72 | blockquote {
73 | margin-top: var(--gutter-large);
74 | }
75 |
76 | blockquote p {
77 | font-size: 3.8rem;
78 | line-height: 5rem;
79 | font-style: italic;
80 | }
81 |
82 | img.figma_fig {
83 | zoom: var(--fig-scale);
84 | }
85 |
86 | li, p {
87 | line-height: 4rem;
88 | }
89 |
--------------------------------------------------------------------------------
/ls-web-local/src/components/Errors.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useContext, useEffect } from "react";
2 | import { lscContext } from "../contexts/LscContext";
3 | import { StreamType } from "../types";
4 | import { css } from "emotion";
5 |
6 | const styles = {
7 | root: css`
8 | background-color: #2f3129;
9 | color: red;
10 | min-height: 50px;
11 | height: 100%;
12 | font-size: 1.2rem;
13 | font-family: SFMono-Regular,Consolas,Liberation Mono,Menlo,Courier,monospace;
14 | `,
15 | list: css`
16 | padding: 8px 50px;
17 | `,
18 | item: css`
19 | &:not(:first-child) {
20 | margin-top: 4px;
21 | }
22 | `,
23 | col: css`
24 | margin-right: 8px;
25 | `,
26 | };
27 |
28 | export type Props = {
29 | fileName: string,
30 | }
31 | export const Errors = ({ fileName }: Props) => {
32 | const lspClient = useContext(lscContext);
33 | const errors$ = lspClient.getErrors$(fileName);
34 | const [errors, updateErrors] = useState>([]);
35 |
36 | useEffect(() => errors$.subscribe(updateErrors).unsubscribe, [lspClient]);
37 |
38 | return (
39 |
40 |
41 | {errors.map(error => (
42 | -
43 | {error.row + 1}:{error.column + 1}
44 | [TS{error.code}]
45 | {error.text}
46 |
47 | ))}
48 |
49 |
50 | );
51 | }
52 |
--------------------------------------------------------------------------------
/ls-web-local/src/components/Logger.tsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState, useRef } from "react";
2 | import ReactJson from 'react-json-view'
3 | import { rootLogger, LoggerArguments } from "../logger";
4 | import { css } from "emotion";
5 |
6 | const styles = {
7 | root: css`
8 | background-color: #ddd;
9 | overflow-y: scroll;
10 | height: 100%;
11 | list-style: none;
12 | font-size: 1.3rem;
13 | `,
14 | item: css`
15 | display: flex;
16 | align-items: flex-start;
17 | border-bottom: 1px solid #aaa;
18 | padding: 2px 32px 2px 10px;
19 | &:not(:first-of-type) {
20 | margin-top: 6px;
21 | }
22 | `,
23 | header: css`
24 | color: #20204e;
25 | font-family: monospace;
26 | margin-right: 5px;
27 | `,
28 | obj: css`
29 | flex: auto;
30 | `,
31 | };
32 |
33 | export const Logger = () => {
34 | const [id, updateId] = useState("");
35 | const [logs] = useState([]);
36 | const bottomRef = useRef(null);
37 | useEffect(() => {
38 | return rootLogger.getStream().subscribe(x => {
39 | logs.push(x);
40 | updateId(x.id);
41 | setTimeout(() => {
42 | if (bottomRef && bottomRef.current) {
43 | bottomRef.current.scrollIntoView();
44 | }
45 | }, 100);
46 | }).unsubscribe;
47 | }, [rootLogger]);
48 | return (
49 |
50 | {logs.map(({ id, category, eventName, args })=> (
51 | -
52 |
53 | {args[0] ?
54 |
55 |
: null}
56 |
57 | ))}
58 |
59 |
60 | );
61 | };
62 |
--------------------------------------------------------------------------------
/ls-sample/src/parse-performance.ts:
--------------------------------------------------------------------------------
1 | import * as ts from "typescript";
2 |
3 | const insertText = "\ndeclare namespace hoge { export function fooobar(): void; }"
4 |
5 | const rangeItems = (n: number) => {
6 | const arr = [];
7 | for(let i = 0; i < n; i++) {
8 | arr[i] = i;
9 | }
10 | return arr;
11 | };
12 |
13 | function checkPerf(lines: number) {
14 | const originalSourceText = `declare namespace fuga {
15 | ${rangeItems(lines - 3).map((_, i) => " function fn" + i + "(): number;").join("\n")}
16 | }
17 | `;
18 |
19 | const origSource = ts.createSourceFile("index.d.ts", originalSourceText, ts.ScriptTarget.ESNext, true, ts.ScriptKind.TS);
20 |
21 | const s1 = Date.now();
22 | let sourceFile = origSource;
23 | for (let i = 0; i < insertText.length; i++) {
24 | const pos = sourceFile.end;
25 | sourceFile = ts.updateSourceFile(sourceFile, sourceFile.getText() + insertText[i], { newLength: 1, span: { start: pos, length: 0 } });
26 | }
27 | const e1 = Date.now();
28 |
29 | const s2 = Date.now();
30 | for (let i = 0; i < insertText.length; i++) {
31 | const origSource = ts.createSourceFile("index.d.ts", originalSourceText + insertText.slice(0, i), ts.ScriptTarget.ESNext, true, ts.ScriptKind.TS);
32 | }
33 | const e2 = Date.now();
34 |
35 | console.log("==================================")
36 | console.log(`originalSourceText lines: ${originalSourceText.split("\n").length}`);
37 | console.log(`originalSourceText length: ${originalSourceText.length}`);
38 | console.log(`insertText length: ${insertText.length}`);
39 | console.log(`elapsed time with incremental parsing: ${e1 - s1}`);
40 | console.log(`elapsed time with non-incremental parsing: ${e2 - s2}`);
41 | console.log("");
42 | }
43 |
44 | checkPerf(100);
45 | checkPerf(500);
46 | checkPerf(1000);
47 | checkPerf(5000);
48 | checkPerf(10000);
49 |
--------------------------------------------------------------------------------
/ls-web-local/webpack.config.js:
--------------------------------------------------------------------------------
1 | const webpack = require('webpack');
2 | const path = require('path');
3 | const HtmlWebpackPlugin = require('html-webpack-plugin');
4 |
5 | /*
6 | * SplitChunksPlugin is enabled by default and replaced
7 | * deprecated CommonsChunkPlugin. It automatically identifies modules which
8 | * should be splitted of chunk by heuristics using module duplication count and
9 | * module category (i. e. node_modules). And splits the chunks…
10 | *
11 | * It is safe to remove "splitChunks" from the generated configuration
12 | * and was added as an educational example.
13 | *
14 | * https://webpack.js.org/plugins/split-chunks-plugin/
15 | *
16 | */
17 |
18 | /*
19 | * We've enabled UglifyJSPlugin for you! This minifies your app
20 | * in order to load faster and run less javascript.
21 | *
22 | * https://github.com/webpack-contrib/uglifyjs-webpack-plugin
23 | *
24 | */
25 |
26 | const UglifyJSPlugin = require('uglifyjs-webpack-plugin');
27 |
28 | module.exports = {
29 | resolve: {
30 | extensions: [".tsx", ".ts", ".js"],
31 | },
32 | module: {
33 | rules: [
34 | {
35 | include: [path.resolve(__dirname, 'src')],
36 | loader: 'ts-loader',
37 | test: /\.tsx?$/,
38 | },
39 | {
40 | include: [path.resolve(__dirname, 'node_modules/typescript/lib')],
41 | loader: 'raw-loader',
42 | test: /lib.*\.d\.ts$/,
43 | },
44 | ],
45 | },
46 |
47 | output: {
48 | chunkFilename: '[name].[chunkhash].js',
49 | filename: '[name].[chunkhash].js'
50 | },
51 |
52 | mode: 'development',
53 |
54 | plugins: [
55 | new HtmlWebpackPlugin({
56 | template: "./src/index.html",
57 | }),
58 | ],
59 |
60 | optimization: {
61 | splitChunks: {
62 | cacheGroups: {
63 | vendors: {
64 | priority: -10,
65 | test: /[\\/]node_modules[\\/]/
66 | }
67 | },
68 |
69 | chunks: 'async',
70 | minChunks: 1,
71 | minSize: 30000,
72 | name: true
73 | }
74 | },
75 | devServer: {
76 | contentBase: path.join(__dirname, 'dist'),
77 | compress: true,
78 | port: 9000
79 | },
80 | };
81 |
--------------------------------------------------------------------------------
/presentation/gulpfile.js:
--------------------------------------------------------------------------------
1 | const path = require("path");
2 | const fs = require("fs");
3 | const { parse } = require('url');
4 | const querystring = require('querystring');
5 |
6 | const gulp = require("gulp");
7 | const replace = require("gulp-replace");
8 | const rp = require("request-promise");
9 | const imgDownload = require("image-downloader");
10 |
11 | const token = fs.readFileSync(".figmatoken", "utf-8").trim();
12 |
13 | let images = [];
14 |
15 | function copyAssets() {
16 | return gulp.src(["assets/**/*"]).pipe(gulp.dest("dist/assets"));
17 | }
18 |
19 | function copy() {
20 | return gulp.src([".fusumarc.yml", "index.js", "styles.css"]).pipe(gulp.dest("fusuma_wd"));
21 | }
22 |
23 | function replaceIframe() {
24 | images = [];
25 | return gulp.src("slides/**/*.md")
26 | .pipe(replace(/