JSX hacks
汎用木構造データ生成機としてのJSX�- TypeScriptでJSXをHTML以外に活用する -
Node学園 33時限目
フューチャー(株)渋川よしき
お前誰よ
渋川よしき� 本田技術研究所:〜2010年12月� DeNA:〜2017年8月� フューチャー(株):2017年9月〜� 三女の父��著書� つまみぐい勉強法(共著)、� Real World HTTP、Mithril、� Goならわかるシステムプログラミング��翻訳� エキスパートPythonプログラミング、� ソフトウェア開発スクラム、� ポモドーロテクニック入門、� アート・オブ・コミュニティetc�
好きな言語� JavaScript®️ / Go / Python��プログラミング以外� インラインスケート@光が丘公園��アカウント� github.com/shibukawa� twitter.com/shibu_jp�
2
有名企業のお客様のシステムを多数手がけています
弊社プレスリリースサイトから抜粋
https://www.future.co.jp/press_room/
フューチャーをざっくり言うと
2016年4月1日に持株会社体制に移行
CONFIDENTIAL
Copyright ©2017 by Future Architect, Inc. Japan
5
フューチャーで有名なのは
https://github.com/future-architect/vuls
6
Cheetah Grid
https://github.com/future-architect/cheetah-grid
7
その他のお知らせ
第2特集
Webに携わるエンジニアの必須科目
ベーシックなJavaScriptをちゃんと身につけよう
ちなみに、第一特集も弊社メンバーが関わっています!
8
JSX hacks
汎用木構造データ生成機としてのJSX: JSXをHTML以外に活用する
Node学園 33時限目
フューチャー(株)渋川よしき
JSX
JSXについての感想
妄想
JSXを利用するメリット
| オリジナル言語 | JSX | 内部DSL |
字句解析 | | ○ | ○ |
構文解析 | | ○ | ○ |
意味解析 | | △ | |
出力 | | | |
エラー出力 | | △ | |
コードハイライト | | ○ | ○ |
コード補完 | | ○ | ○ |
JSXを使って言語を作るメリット
ほとんど、出力部分だけ実装すればOK
JSXの動きの仕組み(1): タグの変換
render() {� return (� <App>� <>� <Header name="yosuke_furukawa"/>� </>� </App>� );�}
render() {� return (� React.createElement(App, null, � React.createElement(undefined, null,� React.createElement(� Header, {name: "yosuke_furukawa"}� )� )� )� );�}
JSXの動きの仕組み(1): タグの変換
render() {� return (� <App>� <>� <Header name="yosuke_furukawa"/>� </>� </App>� );�}
render() {� return (� React.createElement(App, null, � React.createElement(undefined, null,� React.createElement(� Header, {name: "yosuke_furukawa"}� )� )� )� );�}
タグがReact.createElement呼び出しに変換
タグがReact.createElement呼び出しに変換
JSXの動きの仕組み(1): タグの変換
render() {� return (� <App>� <>� <Header name="yosuke_furukawa"/>� </>� </App>� );�}
render() {� return (� React.createElement(App, null, � React.createElement(undefined, null,� React.createElement(� Header, {name: "yosuke_furukawa"}� )� )� )� );�}
タグがReact.createElement呼び出しに変換
タグ属性は第二引数に(省略時はnull)
JSXの動きの仕組み(1): タグの変換
render() {� return (� <App>� <>� <Header name="yosuke_furukawa"/>� </>� </App>� );�}
render() {� return (� React.createElement(App, null, � React.createElement(undefined, null,� React.createElement(� Header, {name: "yosuke_furukawa"}� )� )� )� );�}
タグがReact.createElement呼び出しに変換
子要素は第三引数以降に(可変長引数)
JSXの動きの仕組み(1): タグの変換
render() {� return (� <App>� <>� <Header name="yosuke_furukawa"/>� </>� </App>� );�}
render() {� return (� React.createElement(App, null, � React.createElement(undefined, null,� React.createElement(� Header, {name: "yosuke_furukawa"}� )� )� )� );�}
タグがReact.createElement呼び出しに変換
Fragment構文の時は先頭がundefinedに
JSXの動きの仕組み(1): タグの変換
JSXの動きの仕組み(1): タグの変換
自作レンダラーを作る
JSXの動きの仕組み(2): タグの処理順序
例: 2つのコンポーネント(Parent, Child)
render() {� return (� <div className="p1">� <Child>� <div className="p2">hello world</div>� </Child>� </div>� );�}
render() {� return (� <div className="c1">� {this.props.children}� </div>� );�}
等価な式
render() {� return (� <div className="p1">� <div className="c1">� <div className="p2">hello world</div>� </div>� </div>� );�}
お題1: JSONを作ってみる
const React = {� createElement<P extends {}, T extends Component<P, C>, C>(� component: new (props?: P) => T,� props?: P,� ...children: C[]� ): T | C[] {� let childrenValue: C | C[] | undefined;� if (children.length === 1) {� childrenValue = children[0];� } else if (children.length > 1) {� childrenValue = children;� }� if (component === undefined) {� // <> </> fragment syntax� return children;� } else {� const instance = new component(� Object.assign({}, props, { children: childrenValue })� );� return instance;� }� }�};��export default React;
type PropsWithChildren<P extends {}, C> = P & { children?: C | C[] };��interface NodeArray extends Array<Node> {}�type Text = string | number;�type Child = Text;�export type Node = Child | {} | NodeArray | boolean | null | undefined | Component;��export abstract class BaseComponent {� public componentWillMount(): void {}� public componentDidMount(): void {}� public abstract render(props: any): any;�}��export abstract class Component<P extends {} = {},C = {}> extends BaseComponent {� get props(): PropsWithChildren<P, C> {� return this._props;� }�� constructor(private _props: PropsWithChildren<P, C>) {� super();� }�� public abstract render(props: PropsWithChildren<P, C>): any;�}
function renderTraverse(instance: any): any {� if (isLiteral(instance)) {� return instance;� } else if (instance instanceof Component) {� instance.componentWillMount();� const vdom = instance.render(instance.props);� const result = renderTraverse(vdom);� instance.componentDidMount();� return result;� } else if (Array.isArray(instance)) {� return instance� .map(node => { return renderTraverse(node); })� .filter(node => { return node !== undefined && node !== null; });� } else if (typeof instance === "object") {� const result: { [key: string]: any } = {};� for (const [key, value] of Object.entries(instance)) {� const entry = renderTraverse(value);� if (entry !== undefined) {� result[key] = entry;� }� }� return result;� }�}
export function render(component: Component): any {� return renderTraverse(component);�}��function isLiteral(source: any): boolean {� return (
source === null ||
source instanceof Date ||
typeof source === "number" ||� typeof source === "string" ||
typeof source === "boolean"
);�}
雑に作った第1弾:JSON
class Child extends Component<{name: string}> {� public render(): Node {� return { type: "child", name: this.props.name };� }�}��class Parent extends Component<{name: string}, Child> {� public render(): Node {� return { type: "parent", name: this.props.name, children: this.props.children};� }�}��class Root extends Component {� public render(): Node {� return (<Parent name="test1"><Child name="test2" /></Parent>);� }�}��render(<Root />)
雑に作った第1弾: JSON
雑に作った第2弾:Excel
render(� <Excel filename="sample.xlsx">� <Sheet name="test">� <Cell>{[[1, 2, 3, 4], [5, 6, 7, 8]]}</Cell>� <Cell origin={3}>� {[� ["あのイーハトーヴォのすきとおった風、"],� ["夏でも底に冷たさをもつ青いそら、"],� ["うつくしい森で飾られたモリーオ市、"],� ["郊外のぎらぎらひかる草の波。"]� ]}� </Cell>� </Sheet>� </Excel>� );
雑に作った第2.1弾:Excel方眼紙
render(� <Excel� filename="sample2.xlsx"� template={join(__dirname, "hougan.xlsx")}� >� <Sheet name="申請書">� <Cell hougan={12} origin="C3">� 東京都千代田区丸の内1丁目9−2� </Cell>� <Cell hougan={12} origin="C6">� リクルートテクノロジーズ� </Cell>� <Cell origin="C8">古川陽介</Cell>� </Sheet>� </Excel>� );
雑に作った第2弾:Excel
おまけ: エラーメッセージ用にタグ定義箇所を取得
import { get as getStackTrace } from "stack-trace";��const React = {� createElement(...) {� const stackTrace = getStackTrace();� const filePath = relative(process.cwd(), stackTrace[1].getFileName());� const funcName = stackTrace[1].getFunctionName();� const columnNumber = stackTrace[1].getColumnNumber();� const lineNumber = stackTrace[1].getLineNumber();� const position = `${funcName} in ${filePath} (${lineNumber}:${columnNumber})`;� }�}
まとめ
最後に
Real World HTTPミニ版