From 45e8331e261244628b134a18e3d0fbe0ebb3a7dc Mon Sep 17 00:00:00 2001
From: syuilo <syuilotan@yahoo.co.jp>
Date: Sat, 18 Mar 2017 20:05:11 +0900
Subject: [PATCH] :sushi:

Closes #12, #227 and #58
---
 package.json                                  |  2 +-
 .../common/text/core/syntax-highlighter.ts}   |  8 +-
 .../common/text/elements/bold.ts}             |  0
 .../common/text/elements/code.ts}             |  2 +-
 .../common/text/elements/emoji.ts}            |  0
 .../common/text/elements/hashtag.ts}          |  0
 .../common/text/elements/inline-code.ts}      |  2 +-
 .../common/text/elements/link.ts}             |  0
 .../common/text/elements/mention.ts}          |  0
 .../common/text/elements/url.ts}              |  0
 .../index.js => api/common/text/index.ts}     |  8 +-
 src/api/serializers/app.ts                    |  2 -
 src/api/serializers/auth-session.ts           |  2 -
 src/api/serializers/drive-file.ts             |  2 -
 src/api/serializers/drive-folder.ts           |  2 -
 src/api/serializers/drive-tag.ts              |  2 -
 src/api/serializers/messaging-message.ts      | 10 ++-
 src/api/serializers/notification.ts           |  2 -
 src/api/serializers/post.ts                   | 10 ++-
 src/api/serializers/signin.ts                 |  2 -
 src/api/serializers/user.ts                   |  2 -
 src/web/app/auth/script.js                    |  7 +-
 src/web/app/boot.js                           | 31 +++-----
 src/web/app/client/script.js                  |  2 +-
 src/web/app/common/mixins.js                  | 48 -----------
 src/web/app/common/mixins/api.js              |  8 ++
 src/web/app/common/{scripts => mixins}/i.js   |  4 +-
 src/web/app/common/mixins/index.js            |  9 +++
 src/web/app/common/mixins/stream.js           |  9 +++
 src/web/app/common/scripts/api.js             |  4 +-
 src/web/app/common/scripts/bytes-to-size.js   |  4 +-
 .../app/common/scripts/check-for-update.js    | 14 ++++
 src/web/app/common/scripts/config.js          |  2 +-
 src/web/app/common/scripts/contains.js        |  4 +-
 .../app/common/scripts/copy-to-clipboard.js   |  2 +-
 src/web/app/common/scripts/date-stringify.js  | 10 +--
 src/web/app/common/scripts/gcd.js             |  2 +-
 .../scripts/generate-default-userdata.js      |  4 +-
 src/web/app/common/scripts/get-cat.js         |  4 +-
 .../app/common/scripts/get-post-summary.js    |  2 +-
 src/web/app/common/scripts/is-promise.js      |  2 +-
 src/web/app/common/scripts/loading.js         |  2 +-
 .../app/common/scripts/messaging-stream.js    |  6 +-
 src/web/app/common/scripts/signout.js         |  4 +-
 src/web/app/common/scripts/stream.js          | 79 +++++++++++--------
 src/web/app/common/scripts/text-compiler.js   | 22 +++---
 src/web/app/common/scripts/uuid.js            |  6 +-
 src/web/app/common/tags/introduction.tag      |  8 +-
 src/web/app/common/tags/messaging/form.tag    |  4 +-
 src/web/app/common/tags/messaging/message.tag |  7 +-
 src/web/app/common/tags/messaging/room.tag    |  5 +-
 src/web/app/common/tags/public-timeline.tag   |  6 +-
 src/web/app/common/tags/raw.tag               |  5 +-
 src/web/app/common/tags/signin-history.tag    |  7 +-
 src/web/app/common/tags/signup.tag            |  4 +-
 src/web/app/common/tags/stream-indicator.tag  | 17 ++--
 src/web/app/common/tags/url-preview.tag       |  2 +-
 src/web/app/desktop/mixins.js                 | 41 ----------
 src/web/app/desktop/mixins/index.js           |  1 +
 .../{scripts => mixins}/user-preview.js       |  2 +-
 src/web/app/desktop/router.js                 |  4 +-
 src/web/app/desktop/script.js                 | 13 ++-
 src/web/app/desktop/scripts/autocomplete.js   |  4 +-
 src/web/app/desktop/scripts/dialog.js         |  4 +-
 src/web/app/desktop/scripts/fuck-ad-block.js  |  2 +-
 src/web/app/desktop/scripts/input-dialog.js   |  4 +-
 .../scripts/not-implemented-exception.js      |  8 ++
 src/web/app/desktop/scripts/notify.js         |  4 +-
 src/web/app/desktop/scripts/stream.js         |  7 +-
 src/web/app/desktop/scripts/update-avatar.js  | 10 +--
 src/web/app/desktop/scripts/update-banner.js  | 10 +--
 src/web/app/desktop/tags/analog-clock.tag     |  2 +-
 .../desktop/tags/autocomplete-suggestion.tag  |  2 +-
 .../app/desktop/tags/big-follow-button.tag    | 16 ++--
 src/web/app/desktop/tags/contextmenu.tag      |  2 +-
 src/web/app/desktop/tags/crop-window.tag      |  4 +-
 src/web/app/desktop/tags/donation.tag         |  2 +-
 src/web/app/desktop/tags/drive/browser.tag    | 33 ++++----
 .../desktop/tags/drive/file-contextmenu.tag   | 19 +++--
 src/web/app/desktop/tags/drive/file.tag       |  4 +-
 .../desktop/tags/drive/folder-contextmenu.tag |  5 +-
 src/web/app/desktop/tags/drive/folder.tag     |  5 +-
 src/web/app/desktop/tags/follow-button.tag    | 16 ++--
 .../tags/home-widgets/photo-stream.tag        |  7 +-
 .../app/desktop/tags/home-widgets/profile.tag | 10 ++-
 .../desktop/tags/home-widgets/rss-reader.tag  |  4 +-
 .../desktop/tags/home-widgets/timeline.tag    | 14 ++--
 src/web/app/desktop/tags/notifications.tag    | 13 ++-
 src/web/app/desktop/tags/pages/home.tag       | 17 ++--
 src/web/app/desktop/tags/pages/post.tag       |  8 +-
 src/web/app/desktop/tags/pages/search.tag     |  6 +-
 src/web/app/desktop/tags/pages/user.tag       |  8 +-
 src/web/app/desktop/tags/post-detail-sub.tag  | 10 ++-
 src/web/app/desktop/tags/post-detail.tag      | 10 ++-
 src/web/app/desktop/tags/post-form.tag        | 12 +--
 src/web/app/desktop/tags/post-preview.tag     |  5 +-
 src/web/app/desktop/tags/repost-form.tag      |  7 +-
 .../desktop/tags/set-avatar-suggestion.tag    |  5 +-
 .../desktop/tags/set-banner-suggestion.tag    |  5 +-
 src/web/app/desktop/tags/settings.tag         | 10 +--
 src/web/app/desktop/tags/sub-post-content.tag |  7 +-
 .../app/desktop/tags/timeline-post-sub.tag    |  5 +-
 src/web/app/desktop/tags/timeline-post.tag    | 14 ++--
 .../app/desktop/tags/ui-header-account.tag    |  4 +-
 src/web/app/desktop/tags/ui-header-nav.tag    | 10 ++-
 .../desktop/tags/ui-header-notifications.tag  |  2 +-
 src/web/app/desktop/tags/user-header.tag      | 12 +--
 src/web/app/desktop/tags/user-photos.tag      |  5 +-
 src/web/app/desktop/tags/user-timeline.tag    |  6 +-
 src/web/app/desktop/tags/window.tag           |  2 +-
 src/web/app/dev/router.js                     |  2 +-
 src/web/app/dev/script.js                     |  2 +-
 src/web/app/mobile/mixins.js                  | 25 ------
 src/web/app/mobile/router.js                  |  4 +-
 src/web/app/mobile/script.js                  |  8 +-
 src/web/app/mobile/scripts/open-post-form.js  | 15 ++++
 src/web/app/mobile/scripts/stream.js          | 11 ---
 src/web/app/mobile/scripts/ui-event.js        |  5 ++
 src/web/app/mobile/scripts/ui.js              |  7 --
 src/web/app/mobile/tags/drive.tag             | 19 +++--
 src/web/app/mobile/tags/drive/file-viewer.tag | 11 ++-
 src/web/app/mobile/tags/drive/file.tag        |  3 +-
 src/web/app/mobile/tags/follow-button.tag     | 16 ++--
 src/web/app/mobile/tags/home-timeline.tag     | 15 ++--
 .../app/mobile/tags/notification-preview.tag  |  3 +-
 src/web/app/mobile/tags/notification.tag      |  3 +-
 src/web/app/mobile/tags/notifications.tag     | 10 ++-
 src/web/app/mobile/tags/page/drive.tag        | 20 ++---
 src/web/app/mobile/tags/page/home.tag         | 27 ++++---
 .../app/mobile/tags/page/messaging-room.tag   |  5 +-
 src/web/app/mobile/tags/page/messaging.tag    |  5 +-
 .../app/mobile/tags/page/notifications.tag    | 11 ++-
 src/web/app/mobile/tags/page/post.tag         | 12 +--
 src/web/app/mobile/tags/page/search.tag       | 10 +--
 src/web/app/mobile/tags/page/settings.tag     |  4 +-
 src/web/app/mobile/tags/page/settings/api.tag |  4 +-
 .../tags/page/settings/authorized-apps.tag    |  4 +-
 .../app/mobile/tags/page/settings/signin.tag  |  4 +-
 .../app/mobile/tags/page/settings/twitter.tag |  4 +-
 .../app/mobile/tags/page/user-followers.tag   | 11 +--
 .../app/mobile/tags/page/user-following.tag   | 11 +--
 src/web/app/mobile/tags/page/user.tag         | 11 ++-
 src/web/app/mobile/tags/post-detail.tag       | 12 +--
 src/web/app/mobile/tags/post-form.tag         |  2 +-
 src/web/app/mobile/tags/sub-post-content.tag  |  6 +-
 src/web/app/mobile/tags/timeline-post.tag     | 15 ++--
 src/web/app/mobile/tags/ui-header.tag         | 10 +--
 src/web/app/mobile/tags/ui.tag                |  8 +-
 test/text.js                                  | 12 ++-
 webpack.config.ts                             |  4 +-
 150 files changed, 614 insertions(+), 613 deletions(-)
 rename src/{common/text/core/syntax-highlighter.js => api/common/text/core/syntax-highlighter.ts} (97%)
 rename src/{common/text/elements/bold.js => api/common/text/elements/bold.ts} (100%)
 rename src/{common/text/elements/code.js => api/common/text/elements/code.ts} (84%)
 rename src/{common/text/elements/emoji.js => api/common/text/elements/emoji.ts} (100%)
 rename src/{common/text/elements/hashtag.js => api/common/text/elements/hashtag.ts} (100%)
 rename src/{common/text/elements/inline-code.js => api/common/text/elements/inline-code.ts} (84%)
 rename src/{common/text/elements/link.js => api/common/text/elements/link.ts} (100%)
 rename src/{common/text/elements/mention.js => api/common/text/elements/mention.ts} (100%)
 rename src/{common/text/elements/url.js => api/common/text/elements/url.ts} (100%)
 rename src/{common/text/index.js => api/common/text/index.ts} (94%)
 delete mode 100644 src/web/app/common/mixins.js
 create mode 100644 src/web/app/common/mixins/api.js
 rename src/web/app/common/{scripts => mixins}/i.js (83%)
 create mode 100644 src/web/app/common/mixins/index.js
 create mode 100644 src/web/app/common/mixins/stream.js
 create mode 100644 src/web/app/common/scripts/check-for-update.js
 delete mode 100644 src/web/app/desktop/mixins.js
 create mode 100644 src/web/app/desktop/mixins/index.js
 rename src/web/app/desktop/{scripts => mixins}/user-preview.js (98%)
 create mode 100644 src/web/app/desktop/scripts/not-implemented-exception.js
 delete mode 100644 src/web/app/mobile/mixins.js
 create mode 100644 src/web/app/mobile/scripts/open-post-form.js
 delete mode 100644 src/web/app/mobile/scripts/stream.js
 create mode 100644 src/web/app/mobile/scripts/ui-event.js
 delete mode 100644 src/web/app/mobile/scripts/ui.js

diff --git a/package.json b/package.json
index 6e5e9a0570..8e35c9b29b 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
 {
   "name": "misskey",
   "author": "syuilo <i@syuilo.com>",
-  "version": "0.0.1355",
+  "version": "0.0.1361",
   "license": "MIT",
   "description": "A miniblog-based SNS",
   "bugs": "https://github.com/syuilo/misskey/issues",
diff --git a/src/common/text/core/syntax-highlighter.js b/src/api/common/text/core/syntax-highlighter.ts
similarity index 97%
rename from src/common/text/core/syntax-highlighter.js
rename to src/api/common/text/core/syntax-highlighter.ts
index 06c59777e7..c0396b1fc6 100644
--- a/src/common/text/core/syntax-highlighter.js
+++ b/src/api/common/text/core/syntax-highlighter.ts
@@ -114,7 +114,7 @@ const elements = [
 	// comment
 	code => {
 		if (code.substr(0, 2) != '//') return null;
-		const match = code.match(/^\/\/(.+?)\n/);
+		const match = code.match(/^\/\/(.+?)(\n|$)/);
 		if (!match) return null;
 		const comment = match[0];
 		return {
@@ -187,7 +187,7 @@ const elements = [
 				regexp += char;
 			}
 		}
-		
+
 		if (thisIsNotARegexp) return null;
 		if (regexp == '') return null;
 		if (regexp[0] == ' ' && regexp[regexp.length - 1] == ' ') return null;
@@ -299,7 +299,7 @@ const elements = [
 ];
 
 // specify lang is todo
-module.exports = (source, lang) => {
+export default (source: string, lang?: string) => {
 	let code = source;
 	let html = '';
 
@@ -317,6 +317,8 @@ module.exports = (source, lang) => {
 			if (e) {
 				push(e);
 				return true;
+			} else {
+				return false;
 			}
 		});
 
diff --git a/src/common/text/elements/bold.js b/src/api/common/text/elements/bold.ts
similarity index 100%
rename from src/common/text/elements/bold.js
rename to src/api/common/text/elements/bold.ts
diff --git a/src/common/text/elements/code.js b/src/api/common/text/elements/code.ts
similarity index 84%
rename from src/common/text/elements/code.js
rename to src/api/common/text/elements/code.ts
index 99fe6a183c..4821e95fe2 100644
--- a/src/common/text/elements/code.js
+++ b/src/api/common/text/elements/code.ts
@@ -2,7 +2,7 @@
  * Code (block)
  */
 
-const genHtml = require('../core/syntax-highlighter');
+import genHtml from '../core/syntax-highlighter';
 
 module.exports = text => {
 	const match = text.match(/^```([\s\S]+?)```/);
diff --git a/src/common/text/elements/emoji.js b/src/api/common/text/elements/emoji.ts
similarity index 100%
rename from src/common/text/elements/emoji.js
rename to src/api/common/text/elements/emoji.ts
diff --git a/src/common/text/elements/hashtag.js b/src/api/common/text/elements/hashtag.ts
similarity index 100%
rename from src/common/text/elements/hashtag.js
rename to src/api/common/text/elements/hashtag.ts
diff --git a/src/common/text/elements/inline-code.js b/src/api/common/text/elements/inline-code.ts
similarity index 84%
rename from src/common/text/elements/inline-code.js
rename to src/api/common/text/elements/inline-code.ts
index 37e9b1a0ff..9f9ef51a2b 100644
--- a/src/common/text/elements/inline-code.js
+++ b/src/api/common/text/elements/inline-code.ts
@@ -2,7 +2,7 @@
  * Code (inline)
  */
 
-const genHtml = require('../core/syntax-highlighter');
+import genHtml from '../core/syntax-highlighter';
 
 module.exports = text => {
 	const match = text.match(/^`(.+?)`/);
diff --git a/src/common/text/elements/link.js b/src/api/common/text/elements/link.ts
similarity index 100%
rename from src/common/text/elements/link.js
rename to src/api/common/text/elements/link.ts
diff --git a/src/common/text/elements/mention.js b/src/api/common/text/elements/mention.ts
similarity index 100%
rename from src/common/text/elements/mention.js
rename to src/api/common/text/elements/mention.ts
diff --git a/src/common/text/elements/url.js b/src/api/common/text/elements/url.ts
similarity index 100%
rename from src/common/text/elements/url.js
rename to src/api/common/text/elements/url.ts
diff --git a/src/common/text/index.js b/src/api/common/text/index.ts
similarity index 94%
rename from src/common/text/index.js
rename to src/api/common/text/index.ts
index ab1342230c..47127e8646 100644
--- a/src/common/text/index.js
+++ b/src/api/common/text/index.ts
@@ -13,7 +13,7 @@ const elements = [
 	require('./elements/emoji')
 ];
 
-function analyze(source) {
+export default (source: string) => {
 
 	if (source == '') {
 		return null;
@@ -40,6 +40,8 @@ function analyze(source) {
 				}
 				tokens.forEach(push);
 				return true;
+			} else {
+				return false;
 			}
 		});
 
@@ -66,6 +68,4 @@ function analyze(source) {
 			return a.concat(b);
 		}
 	});
-}
-
-module.exports = analyze;
+};
diff --git a/src/api/serializers/app.ts b/src/api/serializers/app.ts
index fdeef338d9..9d1c46dca4 100644
--- a/src/api/serializers/app.ts
+++ b/src/api/serializers/app.ts
@@ -1,5 +1,3 @@
-'use strict';
-
 /**
  * Module dependencies
  */
diff --git a/src/api/serializers/auth-session.ts b/src/api/serializers/auth-session.ts
index 4efb7729c4..a9acf1243a 100644
--- a/src/api/serializers/auth-session.ts
+++ b/src/api/serializers/auth-session.ts
@@ -1,5 +1,3 @@
-'use strict';
-
 /**
  * Module dependencies
  */
diff --git a/src/api/serializers/drive-file.ts b/src/api/serializers/drive-file.ts
index e6e2f6cae3..b4e2ab064a 100644
--- a/src/api/serializers/drive-file.ts
+++ b/src/api/serializers/drive-file.ts
@@ -1,5 +1,3 @@
-'use strict';
-
 /**
  * Module dependencies
  */
diff --git a/src/api/serializers/drive-folder.ts b/src/api/serializers/drive-folder.ts
index ac3bd13c3a..34fdc0d905 100644
--- a/src/api/serializers/drive-folder.ts
+++ b/src/api/serializers/drive-folder.ts
@@ -1,5 +1,3 @@
-'use strict';
-
 /**
  * Module dependencies
  */
diff --git a/src/api/serializers/drive-tag.ts b/src/api/serializers/drive-tag.ts
index 3e800ca5bd..2f152381bd 100644
--- a/src/api/serializers/drive-tag.ts
+++ b/src/api/serializers/drive-tag.ts
@@ -1,5 +1,3 @@
-'use strict';
-
 /**
  * Module dependencies
  */
diff --git a/src/api/serializers/messaging-message.ts b/src/api/serializers/messaging-message.ts
index da63f8b99e..4ab95e42a3 100644
--- a/src/api/serializers/messaging-message.ts
+++ b/src/api/serializers/messaging-message.ts
@@ -1,13 +1,12 @@
-'use strict';
-
 /**
  * Module dependencies
  */
 import * as mongo from 'mongodb';
+import deepcopy = require('deepcopy');
 import Message from '../models/messaging-message';
 import serializeUser from './user';
 import serializeDriveFile from './drive-file';
-import deepcopy = require('deepcopy');
+import parse from '../common/text';
 
 /**
  * Serialize a message
@@ -47,6 +46,11 @@ export default (
 	_message.id = _message._id;
 	delete _message._id;
 
+	// Parse text
+	if (_message.text) {
+		_message.ast = parse(_message.text);
+	}
+
 	// Populate user
 	_message.user = await serializeUser(_message.user_id, me);
 
diff --git a/src/api/serializers/notification.ts b/src/api/serializers/notification.ts
index 43add127e0..50952e5426 100644
--- a/src/api/serializers/notification.ts
+++ b/src/api/serializers/notification.ts
@@ -1,5 +1,3 @@
-'use strict';
-
 /**
  * Module dependencies
  */
diff --git a/src/api/serializers/post.ts b/src/api/serializers/post.ts
index b71b42e9a4..f459529697 100644
--- a/src/api/serializers/post.ts
+++ b/src/api/serializers/post.ts
@@ -1,16 +1,15 @@
-'use strict';
-
 /**
  * Module dependencies
  */
 import * as mongo from 'mongodb';
+import deepcopy = require('deepcopy');
 import Post from '../models/post';
 import Like from '../models/like';
 import Vote from '../models/poll-vote';
 import serializeApp from './app';
 import serializeUser from './user';
 import serializeDriveFile from './drive-file';
-import deepcopy = require('deepcopy');
+import parse from '../common/text';
 
 /**
  * Serialize a post
@@ -54,6 +53,11 @@ const self = (
 
 	delete _post.mentions;
 
+	// Parse text
+	if (_post.text) {
+		_post.ast = parse(_post.text);
+	}
+
 	// Populate user
 	_post.user = await serializeUser(_post.user_id, me);
 
diff --git a/src/api/serializers/signin.ts b/src/api/serializers/signin.ts
index 39226f8bd4..4068067678 100644
--- a/src/api/serializers/signin.ts
+++ b/src/api/serializers/signin.ts
@@ -1,5 +1,3 @@
-'use strict';
-
 /**
  * Module dependencies
  */
diff --git a/src/api/serializers/user.ts b/src/api/serializers/user.ts
index de215808a4..d367dc8657 100644
--- a/src/api/serializers/user.ts
+++ b/src/api/serializers/user.ts
@@ -1,5 +1,3 @@
-'use strict';
-
 /**
  * Module dependencies
  */
diff --git a/src/web/app/auth/script.js b/src/web/app/auth/script.js
index cded308091..19391b2b9e 100644
--- a/src/web/app/auth/script.js
+++ b/src/web/app/auth/script.js
@@ -5,10 +5,11 @@
 // Style
 import './style.styl';
 
-const riot = require('riot');
-document.title = 'Misskey | アプリの連携';
+import * as riot from 'riot';
 require('./tags');
-const boot = require('../boot.js');
+import boot from '../boot';
+
+document.title = 'Misskey | アプリの連携';
 
 /**
  * Boot
diff --git a/src/web/app/boot.js b/src/web/app/boot.js
index 4d008ad66f..24981c5889 100644
--- a/src/web/app/boot.js
+++ b/src/web/app/boot.js
@@ -2,12 +2,14 @@
  * boot
  */
 
-const riot = require('riot');
+import * as riot from 'riot';
 require('velocity-animate');
-const api = require('./common/scripts/api');
-const signout = require('./common/scripts/signout');
-const generateDefaultUserdata = require('./common/scripts/generate-default-userdata');
-const mixins = require('./common/mixins');
+import api from './common/scripts/api';
+import signout from './common/scripts/signout';
+import checkForUpdate from './common/scripts/check-for-update';
+import mixin from './common/mixins';
+import generateDefaultUserdata from './common/scripts/generate-default-userdata';
+import CONFIG from './common/scripts/config';
 require('./common/tags');
 
 /**
@@ -16,7 +18,7 @@ require('./common/tags');
 
 "use strict";
 
-const CONFIG = require('./common/scripts/config');
+console.info(`Misskey v${VERSION}`);
 
 document.domain = CONFIG.host;
 
@@ -56,21 +58,10 @@ if (localStorage.getItem('should-refresh') == 'true') {
 }
 
 // 更新チェック
-setTimeout(() => {
-	fetch(CONFIG.apiUrl + '/meta', {
-		method: 'POST'
-	}).then(res => {
-		res.json().then(meta => {
-			if (meta.version != VERSION) {
-				localStorage.setItem('should-refresh', 'true');
-				alert('Misskeyの新しいバージョンがあります。ページを再度読み込みすると更新が適用されます。');
-			}
-		});
-	});
-}, 3000);
+setTimeout(checkForUpdate, 3000);
 
 // ユーザーをフェッチしてコールバックする
-module.exports = callback => {
+export default callback => {
 	// Get cached account data
 	let cachedMe = JSON.parse(localStorage.getItem('me'));
 
@@ -113,7 +104,7 @@ module.exports = callback => {
 			}
 		}
 
-		mixins(me);
+		mixin(me);
 
 		const ini = document.getElementById('ini');
 		ini.parentNode.removeChild(ini);
diff --git a/src/web/app/client/script.js b/src/web/app/client/script.js
index ffc9c892cd..ee07edd60d 100644
--- a/src/web/app/client/script.js
+++ b/src/web/app/client/script.js
@@ -1,5 +1,5 @@
 /**
- * MISSKEY ENTRY POINT
+ * MISSKEY CLIENT ENTRY POINT
  */
 (() => {
 	const head = document.getElementsByTagName('head')[0];
diff --git a/src/web/app/common/mixins.js b/src/web/app/common/mixins.js
deleted file mode 100644
index 220e033846..0000000000
--- a/src/web/app/common/mixins.js
+++ /dev/null
@@ -1,48 +0,0 @@
-const riot = require('riot');
-
-module.exports = me => {
-	const i = me ? me.token : null;
-
-	require('./scripts/i')(me);
-
-	riot.mixin('api', {
-		api: require('./scripts/api').bind(null, i)
-	});
-
-	riot.mixin('cropper', {
-		Cropper: require('cropperjs')
-	});
-
-	riot.mixin('signout', {
-		signout: require('./scripts/signout')
-	});
-
-	riot.mixin('messaging-stream', {
-		MessagingStreamConnection: require('./scripts/messaging-stream')
-	});
-
-	riot.mixin('is-promise', {
-		isPromise: require('./scripts/is-promise')
-	});
-
-	riot.mixin('get-post-summary', {
-		getPostSummary: require('./scripts/get-post-summary')
-	});
-
-	riot.mixin('date-stringify', {
-		dateStringify: require('./scripts/date-stringify')
-	});
-
-	riot.mixin('text', {
-		analyze: require('../../../common/text/index'),
-		compile: require('./scripts/text-compiler')
-	});
-
-	riot.mixin('get-password-strength', {
-		getPasswordStrength: require('syuilo-password-strength')
-	});
-
-	riot.mixin('ui-progress', {
-		Progress: require('./scripts/loading')
-	});
-};
diff --git a/src/web/app/common/mixins/api.js b/src/web/app/common/mixins/api.js
new file mode 100644
index 0000000000..42d96db559
--- /dev/null
+++ b/src/web/app/common/mixins/api.js
@@ -0,0 +1,8 @@
+import * as riot from 'riot';
+import api from '../scripts/api';
+
+export default me => {
+	riot.mixin('api', {
+		api: api.bind(null, me ? me.token : null)
+	});
+};
diff --git a/src/web/app/common/scripts/i.js b/src/web/app/common/mixins/i.js
similarity index 83%
rename from src/web/app/common/scripts/i.js
rename to src/web/app/common/mixins/i.js
index 20c33c6402..5225147766 100644
--- a/src/web/app/common/scripts/i.js
+++ b/src/web/app/common/mixins/i.js
@@ -1,6 +1,6 @@
-const riot = require('riot');
+import * as riot from 'riot';
 
-module.exports = me => {
+export default me => {
 	riot.mixin('i', {
 		init: function() {
 			this.I = me;
diff --git a/src/web/app/common/mixins/index.js b/src/web/app/common/mixins/index.js
new file mode 100644
index 0000000000..29cb6c9b6a
--- /dev/null
+++ b/src/web/app/common/mixins/index.js
@@ -0,0 +1,9 @@
+import activateMe from './i';
+import activateApi from './api';
+import activateStream from './stream';
+
+export default me => {
+	activateMe(me);
+	activateApi(me);
+	activateStream(me);
+};
diff --git a/src/web/app/common/mixins/stream.js b/src/web/app/common/mixins/stream.js
new file mode 100644
index 0000000000..e3b616a1a5
--- /dev/null
+++ b/src/web/app/common/mixins/stream.js
@@ -0,0 +1,9 @@
+import * as riot from 'riot';
+import Connection from '../scripts/stream';
+
+export default me => {
+	const stream = me ? new Connection(me) : null;
+	riot.mixin('stream', {
+		stream: stream
+	});
+};
diff --git a/src/web/app/common/scripts/api.js b/src/web/app/common/scripts/api.js
index 3df54b645a..4855f736c7 100644
--- a/src/web/app/common/scripts/api.js
+++ b/src/web/app/common/scripts/api.js
@@ -2,7 +2,7 @@
  * API Request
  */
 
-const CONFIG = require('./config');
+import CONFIG from './config';
 
 let spinner = null;
 let pending = 0;
@@ -14,7 +14,7 @@ let pending = 0;
  * @param  {any} [data={}] Data
  * @return {Promise<any>} Response
  */
-module.exports = (i, endpoint, data = {}) => {
+export default (i, endpoint, data = {}) => {
 	if (++pending === 1) {
 		spinner = document.createElement('div');
 		spinner.setAttribute('id', 'wait');
diff --git a/src/web/app/common/scripts/bytes-to-size.js b/src/web/app/common/scripts/bytes-to-size.js
index 717f9ad507..e143387141 100644
--- a/src/web/app/common/scripts/bytes-to-size.js
+++ b/src/web/app/common/scripts/bytes-to-size.js
@@ -1,6 +1,6 @@
-module.exports = function(bytes) {
+export default bytes => {
 	var sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
 	if (bytes == 0) return '0Byte';
 	var i = parseInt(Math.floor(Math.log(bytes) / Math.log(1024)));
 	return Math.round(bytes / Math.pow(1024, i), 2) + sizes[i];
-}
+};
diff --git a/src/web/app/common/scripts/check-for-update.js b/src/web/app/common/scripts/check-for-update.js
new file mode 100644
index 0000000000..637e6a9fb8
--- /dev/null
+++ b/src/web/app/common/scripts/check-for-update.js
@@ -0,0 +1,14 @@
+import CONFIG from './config';
+
+export default function() {
+	fetch(CONFIG.apiUrl + '/meta', {
+		method: 'POST'
+	}).then(res => {
+		res.json().then(meta => {
+			if (meta.version != VERSION) {
+				localStorage.setItem('should-refresh', 'true');
+				alert('Misskeyの新しいバージョンがあります。ページを再度読み込みすると更新が適用されます。');
+			}
+		});
+	});
+};
diff --git a/src/web/app/common/scripts/config.js b/src/web/app/common/scripts/config.js
index 4203431bd0..16f75d6e16 100644
--- a/src/web/app/common/scripts/config.js
+++ b/src/web/app/common/scripts/config.js
@@ -9,7 +9,7 @@ const apiUrl = `${scheme}//api.${host}`;
 const devUrl = `${scheme}//dev.${host}`;
 const aboutUrl = `${scheme}//about.${host}`;
 
-module.exports = {
+export default {
 	host,
 	scheme,
 	url,
diff --git a/src/web/app/common/scripts/contains.js b/src/web/app/common/scripts/contains.js
index fe73666193..a5071b3f25 100644
--- a/src/web/app/common/scripts/contains.js
+++ b/src/web/app/common/scripts/contains.js
@@ -1,8 +1,8 @@
-module.exports = function(parent, child) {
+export default (parent, child) => {
 	let node = child.parentNode;
 	while (node) {
 		if (node == parent) return true;
 		node = node.parentNode;
 	}
 	return false;
-}
+};
diff --git a/src/web/app/common/scripts/copy-to-clipboard.js b/src/web/app/common/scripts/copy-to-clipboard.js
index 2e67024c85..3d2741f8d7 100644
--- a/src/web/app/common/scripts/copy-to-clipboard.js
+++ b/src/web/app/common/scripts/copy-to-clipboard.js
@@ -1,7 +1,7 @@
 /**
  * Clipboardに値をコピー(TODO: 文字列以外も対応)
  */
-module.exports = val => {
+export default val => {
 	const form = document.createElement('textarea');
 	form.textContent = val;
 	document.body.appendChild(form);
diff --git a/src/web/app/common/scripts/date-stringify.js b/src/web/app/common/scripts/date-stringify.js
index d803587f2c..e51de8833d 100644
--- a/src/web/app/common/scripts/date-stringify.js
+++ b/src/web/app/common/scripts/date-stringify.js
@@ -1,12 +1,12 @@
-module.exports = date => {
+export default date => {
 	if (typeof date == 'string') date = new Date(date);
 	return (
-		date.getFullYear()    + 'å¹´' + 
-		(date.getMonth() + 1) + '月' + 
+		date.getFullYear()    + 'å¹´' +
+		(date.getMonth() + 1) + '月' +
 		date.getDate()        + 'æ—¥' +
 		' ' +
-		date.getHours()       + '時' + 
-		date.getMinutes()     + '分' + 
+		date.getHours()       + '時' +
+		date.getMinutes()     + '分' +
 		' ' +
 		`(${['日', '月', '火', '水', '木', '金', '土'][date.getDay()]})`
 	);
diff --git a/src/web/app/common/scripts/gcd.js b/src/web/app/common/scripts/gcd.js
index 43bfbc57ae..9a19f9da66 100644
--- a/src/web/app/common/scripts/gcd.js
+++ b/src/web/app/common/scripts/gcd.js
@@ -1,2 +1,2 @@
 const gcd = (a, b) => !b ? a : gcd(b, a % b);
-module.exports = gcd;
+export default gcd;
diff --git a/src/web/app/common/scripts/generate-default-userdata.js b/src/web/app/common/scripts/generate-default-userdata.js
index fbe2e99075..c19228dd49 100644
--- a/src/web/app/common/scripts/generate-default-userdata.js
+++ b/src/web/app/common/scripts/generate-default-userdata.js
@@ -1,4 +1,4 @@
-const uuid = require('./uuid.js');
+import uuid from './uuid';
 
 const home = {
 	left: [
@@ -17,7 +17,7 @@ const home = {
 	]
 };
 
-module.exports = () => {
+export default () => {
 	const homeData = [];
 
 	home.left.forEach(widget => {
diff --git a/src/web/app/common/scripts/get-cat.js b/src/web/app/common/scripts/get-cat.js
index cd28c7bdaa..cad42c88c8 100644
--- a/src/web/app/common/scripts/get-cat.js
+++ b/src/web/app/common/scripts/get-cat.js
@@ -1,3 +1 @@
-module.exports = () => {
-	return '(=^・・^=)';
-};
+export default () => '(=^・・^=)';
diff --git a/src/web/app/common/scripts/get-post-summary.js b/src/web/app/common/scripts/get-post-summary.js
index 5e8319b61c..83eda8f6b4 100644
--- a/src/web/app/common/scripts/get-post-summary.js
+++ b/src/web/app/common/scripts/get-post-summary.js
@@ -32,4 +32,4 @@ const summarize = post => {
 	return summary.trim();
 };
 
-module.exports = summarize;
+export default summarize;
diff --git a/src/web/app/common/scripts/is-promise.js b/src/web/app/common/scripts/is-promise.js
index fd3dc42da3..3b4cd70b49 100644
--- a/src/web/app/common/scripts/is-promise.js
+++ b/src/web/app/common/scripts/is-promise.js
@@ -1 +1 @@
-module.exports = x => typeof x.then == 'function';
+export default x => typeof x.then == 'function';
diff --git a/src/web/app/common/scripts/loading.js b/src/web/app/common/scripts/loading.js
index fa7eafaf96..c48e626648 100644
--- a/src/web/app/common/scripts/loading.js
+++ b/src/web/app/common/scripts/loading.js
@@ -6,7 +6,7 @@ NProgress.configure({
 
 const root = document.getElementsByTagName('html')[0];
 
-module.exports = {
+export default {
 	start: () => {
 		root.classList.add('progress');
 		NProgress.start();
diff --git a/src/web/app/common/scripts/messaging-stream.js b/src/web/app/common/scripts/messaging-stream.js
index 0c8ce3c9d2..50d41c2be9 100644
--- a/src/web/app/common/scripts/messaging-stream.js
+++ b/src/web/app/common/scripts/messaging-stream.js
@@ -1,6 +1,6 @@
 const ReconnectingWebSocket = require('reconnecting-websocket');
-const riot = require('riot');
-const CONFIG = require('./config');
+import * as riot from 'riot';
+import CONFIG from './config';
 
 class Connection {
 	constructor(me, otherparty) {
@@ -40,4 +40,4 @@ class Connection {
 	}
 }
 
-module.exports = Connection;
+export default Connection;
diff --git a/src/web/app/common/scripts/signout.js b/src/web/app/common/scripts/signout.js
index 7242ebc5b0..6c95cfbc9c 100644
--- a/src/web/app/common/scripts/signout.js
+++ b/src/web/app/common/scripts/signout.js
@@ -1,6 +1,6 @@
-const CONFIG = require('./config');
+import CONFIG from './config';
 
-module.exports = () => {
+export default () => {
 	localStorage.removeItem('me');
 	document.cookie = `i=; domain=.${CONFIG.host}; expires=Thu, 01 Jan 1970 00:00:01 GMT;`;
 	location.href = '/';
diff --git a/src/web/app/common/scripts/stream.js b/src/web/app/common/scripts/stream.js
index fd7bac7faa..d6e6bf8aa5 100644
--- a/src/web/app/common/scripts/stream.js
+++ b/src/web/app/common/scripts/stream.js
@@ -1,40 +1,53 @@
 const ReconnectingWebSocket = require('reconnecting-websocket');
-const riot = require('riot');
-const CONFIG = require('./config');
-
-module.exports = me => {
-	let state = 'initializing';
-	const stateEv = riot.observable();
-	const event = riot.observable();
-	const host = CONFIG.apiUrl.replace('http', 'ws');
-	const socket = new ReconnectingWebSocket(`${host}?i=${me.token}`);
-
-	socket.onopen = () => {
-		state = 'connected';
-		stateEv.trigger('connected');
-	};
-
-	socket.onclose = () => {
-		state = 'reconnecting';
-		stateEv.trigger('closed');
-	};
-
-	socket.onmessage = message => {
+import * as riot from 'riot';
+import CONFIG from './config';
+
+class Connection {
+	constructor(me) {
+		// BIND -----------------------------------
+		this.onOpen =    this.onOpen.bind(this);
+		this.onClose =   this.onClose.bind(this);
+		this.onMessage = this.onMessage.bind(this);
+		this.close =     this.close.bind(this);
+		// ----------------------------------------
+
+		this.state = 'initializing';
+		this.stateEv = riot.observable();
+		this.event = riot.observable();
+		this.me = me;
+
+		const host = CONFIG.apiUrl.replace('http', 'ws');
+		this.socket = new ReconnectingWebSocket(`${host}?i=${me.token}`);
+		this.socket.addEventListener('open', this.onOpen);
+		this.socket.addEventListener('close', this.onClose);
+		this.socket.addEventListener('message', this.onMessage);
+
+		this.event.on('i_updated', me.update);
+	}
+
+	onOpen() {
+		this.state = 'connected';
+		this.stateEv.trigger('connected');
+	}
+
+	onClose() {
+		this.state = 'reconnecting';
+		this.stateEv.trigger('closed');
+	}
+
+	onMessage(message) {
 		try {
 			const msg = JSON.parse(message.data);
-			if (msg.type) {
-				event.trigger(msg.type, msg.body);
-			}
-		} catch (e) {
+			if (msg.type) this.event.trigger(msg.type, msg.body);
+		} catch(e) {
 			// noop
 		}
-	};
+	}
 
-	event.on('i_updated', me.update);
+	close() {
+		this.socket.removeEventListener('open', this.onOpen);
+		this.socket.removeEventListener('message', this.onMessage);
+	}
+}
 
-	return {
-		stateEv: stateEv,
-		getState: () => state,
-		event: event
-	};
-};
+export default Connection;
diff --git a/src/web/app/common/scripts/text-compiler.js b/src/web/app/common/scripts/text-compiler.js
index d4570ca923..1743a549aa 100644
--- a/src/web/app/common/scripts/text-compiler.js
+++ b/src/web/app/common/scripts/text-compiler.js
@@ -1,13 +1,13 @@
-const riot = require('riot');
+import * as riot from 'riot';
 //const emojinize = require('emojinize');
-const CONFIG = require('./config');
+import CONFIG from './config';
 
 const escape = text =>
 	text
 		.replace(/>/g, '&gt;')
 		.replace(/</g, '&lt;');
 
-module.exports = (tokens, shouldBreak) => {
+export default (tokens, shouldBreak) => {
 	if (shouldBreak == null) {
 		shouldBreak = true;
 	}
@@ -20,21 +20,21 @@ module.exports = (tokens, shouldBreak) => {
 				return escape(token.content)
 					.replace(/(\r\n|\n|\r)/g, shouldBreak ? '<br>' : ' ');
 			case 'bold':
-				return '<strong>' + escape(token.bold) + '</strong>';
+				return `<strong>${escape(token.bold)}</strong>`;
 			case 'url':
-				return '<mk-url href="' + escape(token.content) + '" target="_blank"></mk-url>';
+				return `<mk-url href="${escape(token.content)}" target="_blank"></mk-url>`;
 			case 'link':
-				return '<a class="link" href="' + escape(token.url) + '" target="_blank">' + escape(token.title) + '</a>';
+				return `<a class="link" href="${escape(token.url)}" target="_blank" title="${escape(token.url)}">${escape(token.title)}</a>`;
 			case 'mention':
-				return '<a href="' + CONFIG.url + '/' + escape(token.username) + '" target="_blank" data-user-preview="' + token.content + '" ' + (me && me.username == token.username ? 'data-is-me' : '') + '>' + token.content + '</a>';
+				return `<a href="${CONFIG.url + '/' + escape(token.username)}" target="_blank" data-user-preview="${token.content}" ${me && me.username == token.username ? 'data-is-me' : ''}>${token.content}</a>`;
 			case 'hashtag': // TODO
-				return '<a>' + escape(token.content) + '</a>';
+				return `<a>${escape(token.content)}</a>`;
 			case 'code':
-				return '<pre><code>' + token.html + '</code></pre>';
+				return `<pre><code>${token.html}</code></pre>`;
 			case 'inline-code':
-				return '<code>' + token.html + '</code>';
+				return `<code>${token.html}</code>`;
 			case 'emoji':
-				return '<i>' + token.content + '</i>';
+				return `<i>${token.content}</i>`;
 				//return emojinize.encode(token.content)
 		}
 	}).join('');
diff --git a/src/web/app/common/scripts/uuid.js b/src/web/app/common/scripts/uuid.js
index 6161190d63..6a103d6e04 100644
--- a/src/web/app/common/scripts/uuid.js
+++ b/src/web/app/common/scripts/uuid.js
@@ -1,12 +1,12 @@
-module.exports = function () {
+export default () => {
 	var uuid = '', i, random;
 	for (i = 0; i < 32; i++) {
 		random = Math.random() * 16 | 0;
 
 		if (i == 8 || i == 12 || i == 16 || i == 20) {
-			uuid += '-'
+			uuid += '-';
 		}
 		uuid += (i == 12 ? 4 : (i == 16 ? (random & 3 | 8) : random)).toString(16);
 	}
 	return uuid;
-}
+};
diff --git a/src/web/app/common/tags/introduction.tag b/src/web/app/common/tags/introduction.tag
index fda011efff..fa1b1e247a 100644
--- a/src/web/app/common/tags/introduction.tag
+++ b/src/web/app/common/tags/introduction.tag
@@ -1,9 +1,9 @@
 <mk-introduction>
 	<article>
-		<h1>Misskeyとは?</h1><p><ruby>Misskey<rt>みすきー</rt></ruby>は、<a href="http://syuilo.com" target="_blank">syuilo</a>が2014年くらいから<a href="https://github.com/syuilo/misskey" target="_blank">オープンソースで</a>開発・運営を行っている、ミニブログベースのSNSです。</p>
-<p>Twitter, Facebook, LINE, Google+ などを<del>パクって</del><i>参考にして</i>います。</p>
-<p>無料で誰でも利用でき、広告は一切掲載していません。</p>
-<p><a href={ CONFIG.aboutUrl } target="_blank">もっと知りたい方はこちら</a></p>
+		<h1>Misskeyとは?</h1>
+		<p><ruby>Misskey<rt>みすきー</rt></ruby>は、<a href="http://syuilo.com" target="_blank">syuilo</a>が2014年くらいから<a href="https://github.com/syuilo/misskey" target="_blank">オープンソースで</a>開発・運営を行っている、ミニブログベースのSNSです。</p>
+		<p>無料で誰でも利用でき、広告も掲載していません。</p>
+		<p><a href={ CONFIG.aboutUrl } target="_blank">もっと知りたい方はこちら</a></p>
 	</article>
 	<style>
 		:scope
diff --git a/src/web/app/common/tags/messaging/form.tag b/src/web/app/common/tags/messaging/form.tag
index 4e5f5262af..41a3599f68 100644
--- a/src/web/app/common/tags/messaging/form.tag
+++ b/src/web/app/common/tags/messaging/form.tag
@@ -119,7 +119,7 @@
 	<script>
 		this.mixin('api');
 
-		this.onpaste = (e) => {
+		this.onpaste = e => {
 			const data = e.clipboardData;
 			const items = data.items;
 			for (let i = 0; i < items.length; i++) {
@@ -130,7 +130,7 @@
 			}
 		};
 
-		this.onkeypress = (e) => {
+		this.onkeypress = e => {
 			if ((e.which == 10 || e.which == 13) && e.ctrlKey) {
 				this.send();
 			}
diff --git a/src/web/app/common/tags/messaging/message.tag b/src/web/app/common/tags/messaging/message.tag
index 8657d2ffdd..6d2651e5a1 100644
--- a/src/web/app/common/tags/messaging/message.tag
+++ b/src/web/app/common/tags/messaging/message.tag
@@ -203,17 +203,18 @@
 
 	</style>
 	<script>
+		import compile from '../../../common/scripts/text-compiler';
+
 		this.mixin('i');
-		this.mixin('text');
 
 		this.message = this.opts.message;
 		this.message.is_me = this.message.user.id == this.I.id;
 
 		this.on('mount', () => {
 			if (this.message.text) {
-				const tokens = this.analyze(this.message.text);
+				const tokens = this.message.ast;
 
-				this.refs.text.innerHTML = this.compile(tokens);
+				this.refs.text.innerHTML = compile(tokens);
 
 				this.refs.text.children.forEach(e => {
 					if (e.tagName == 'MK-URL') riot.mount(e);
diff --git a/src/web/app/common/tags/messaging/room.tag b/src/web/app/common/tags/messaging/room.tag
index 4393ea9b86..d34a019fe0 100644
--- a/src/web/app/common/tags/messaging/room.tag
+++ b/src/web/app/common/tags/messaging/room.tag
@@ -123,9 +123,10 @@
 
 	</style>
 	<script>
+		import MessagingStreamConnection from '../../scripts/messaging-stream';
+
 		this.mixin('i');
 		this.mixin('api');
-		this.mixin('messaging-stream');
 
 		this.user = this.opts.user;
 		this.init = true;
@@ -133,7 +134,7 @@
 		this.messages = [];
 		this.isNaked = this.opts.isNaked;
 
-		this.connection = new this.MessagingStreamConnection(this.I, this.user.id);
+		this.connection = new MessagingStreamConnection(this.I, this.user.id);
 
 		this.on('mount', () => {
 			this.connection.event.on('message', this.onMessage);
diff --git a/src/web/app/common/tags/public-timeline.tag b/src/web/app/common/tags/public-timeline.tag
index 7efdb2886c..c39a231813 100644
--- a/src/web/app/common/tags/public-timeline.tag
+++ b/src/web/app/common/tags/public-timeline.tag
@@ -87,12 +87,10 @@
 
 	</style>
 	<script>
-		this.mixin('text');
+		import compile from '../../common/scripts/text-compiler';
 
 		this.on('mount', () => {
-			this.mixin('text');
-			const tokens = this.analyze(this.text);
-			const text = this.compile(tokens);
+			const text = compile(this.ast);
 			this.refs.text.innerHTML = text;
 		});
 	</script>
diff --git a/src/web/app/common/tags/raw.tag b/src/web/app/common/tags/raw.tag
index 0637675c45..e1285694e4 100644
--- a/src/web/app/common/tags/raw.tag
+++ b/src/web/app/common/tags/raw.tag
@@ -2,7 +2,8 @@
 	<style>
 		:scope
 			display inline
-
 	</style>
-	<script>this.root.innerHTML = this.opts.content</script>
+	<script>
+		this.root.innerHTML = this.opts.content;
+	</script>
 </mk-raw>
diff --git a/src/web/app/common/tags/signin-history.tag b/src/web/app/common/tags/signin-history.tag
index 3868980058..00b0cecced 100644
--- a/src/web/app/common/tags/signin-history.tag
+++ b/src/web/app/common/tags/signin-history.tag
@@ -48,9 +48,12 @@
 
 	</style>
 	<script>
+		this.mixin('i');
 		this.mixin('api');
 		this.mixin('stream');
 
+		const stream = this.stream.event;
+
 		this.history = [];
 		this.fetching = true;
 
@@ -62,11 +65,11 @@
 				});
 			});
 
-			this.stream.on('signin', this.onSignin);
+			stream.on('signin', this.onSignin);
 		});
 
 		this.on('unmount', () => {
-			this.stream.off('signin', this.onSignin);
+			stream.off('signin', this.onSignin);
 		});
 
 		this.onSignin = signin => {
diff --git a/src/web/app/common/tags/signup.tag b/src/web/app/common/tags/signup.tag
index d5c7889bb1..9f81801694 100644
--- a/src/web/app/common/tags/signup.tag
+++ b/src/web/app/common/tags/signup.tag
@@ -175,7 +175,7 @@
 	</style>
 	<script>
 		this.mixin('api');
-		this.mixin('get-password-strength');
+		const getPasswordStrength = require('syuilo-password-strength');
 
 		this.usernameState = null;
 		this.passwordStrength = '';
@@ -257,7 +257,7 @@
 				return;
 			}
 
-			const strength = this.getPasswordStrength(password);
+			const strength = getPasswordStrength(password);
 			this.passwordStrength = strength > 0.7 ? 'high' : strength > 0.3 ? 'medium' : 'low';
 			this.update();
 			this.refs.passwordMetar.style.width = `${strength * 100}%`;
diff --git a/src/web/app/common/tags/stream-indicator.tag b/src/web/app/common/tags/stream-indicator.tag
index 7d343a438a..9c348b3987 100644
--- a/src/web/app/common/tags/stream-indicator.tag
+++ b/src/web/app/common/tags/stream-indicator.tag
@@ -1,13 +1,13 @@
 <mk-stream-indicator>
-	<p if={ state == 'initializing' }>
+	<p if={ stream.state == 'initializing' }>
 		<i class="fa fa-spinner fa-spin"></i>
 		<span>接続中<mk-ellipsis></mk-ellipsis></span>
 	</p>
-	<p if={ state == 'reconnecting' }>
+	<p if={ stream.state == 'reconnecting' }>
 		<i class="fa fa-spinner fa-spin"></i>
 		<span>切断されました 接続中<mk-ellipsis></mk-ellipsis></span>
 	</p>
-	<p if={ state == 'connected' }>
+	<p if={ stream.state == 'connected' }>
 		<i class="fa fa-check"></i>
 		<span>接続完了</span>
 	</p>
@@ -34,18 +34,16 @@
 
 	</style>
 	<script>
+		this.mixin('i');
 		this.mixin('stream');
 
 		this.on('before-mount', () => {
-			this.state = this.getStreamState();
-
-			if (this.state == 'connected') {
+			if (this.stream.state == 'connected') {
 				this.root.style.opacity = 0;
 			}
 		});
 
-		this.streamStateEv.on('connected', () => {
-			this.state = this.getStreamState();
+		this.stream.stateEv.on('connected', () => {
 			this.update();
 			setTimeout(() => {
 				Velocity(this.root, {
@@ -54,8 +52,7 @@
 			}, 1000);
 		});
 
-		this.streamStateEv.on('closed', () => {
-			this.state = this.getStreamState();
+		this.stream.stateEv.on('closed', () => {
 			this.update();
 			Velocity(this.root, {
 				opacity: 1
diff --git a/src/web/app/common/tags/url-preview.tag b/src/web/app/common/tags/url-preview.tag
index c21e431e5e..bc86f441f2 100644
--- a/src/web/app/common/tags/url-preview.tag
+++ b/src/web/app/common/tags/url-preview.tag
@@ -97,7 +97,7 @@
 		this.loading = true;
 
 		this.on('mount', () => {
-			fetch(CONFIG.url + '/api:url?url=' + this.url).then(res => {
+			fetch('/api:url?url=' + this.url).then(res => {
 				res.json().then(info => {
 					this.title = info.title;
 					this.description = info.description;
diff --git a/src/web/app/desktop/mixins.js b/src/web/app/desktop/mixins.js
deleted file mode 100644
index 9e93e5e6e5..0000000000
--- a/src/web/app/desktop/mixins.js
+++ /dev/null
@@ -1,41 +0,0 @@
-const riot = require('riot');
-
-module.exports = me => {
-	if (me) require('./scripts/stream')(me);
-
-	require('./scripts/user-preview');
-
-	riot.mixin('notify', {
-		notify: require('./scripts/notify')
-	});
-
-	const dialog = require('./scripts/dialog');
-
-	riot.mixin('dialog', {
-		dialog: dialog
-	});
-
-	riot.mixin('NotImplementedException', {
-		NotImplementedException: () => {
-			return dialog('<i class="fa fa-exclamation-triangle"></i>Not implemented yet', '要求された操作は実装されていません。<br>→<a href="https://github.com/syuilo/misskey" target="_blank">Misskeyの開発に参加する</a>', [{
-				text: 'OK'
-			}]);
-		}
-	});
-
-	riot.mixin('input-dialog', {
-		inputDialog: require('./scripts/input-dialog')
-	});
-
-	riot.mixin('update-avatar', {
-		updateAvatar: require('./scripts/update-avatar')
-	});
-
-	riot.mixin('update-banner', {
-		updateBanner: require('./scripts/update-banner')
-	});
-
-	riot.mixin('autocomplete', {
-		Autocomplete: require('./scripts/autocomplete')
-	});
-};
diff --git a/src/web/app/desktop/mixins/index.js b/src/web/app/desktop/mixins/index.js
new file mode 100644
index 0000000000..a7a3eb9485
--- /dev/null
+++ b/src/web/app/desktop/mixins/index.js
@@ -0,0 +1 @@
+require('./user-preview');
diff --git a/src/web/app/desktop/scripts/user-preview.js b/src/web/app/desktop/mixins/user-preview.js
similarity index 98%
rename from src/web/app/desktop/scripts/user-preview.js
rename to src/web/app/desktop/mixins/user-preview.js
index 1964384fde..3f483beb3a 100644
--- a/src/web/app/desktop/scripts/user-preview.js
+++ b/src/web/app/desktop/mixins/user-preview.js
@@ -1,4 +1,4 @@
-const riot = require('riot');
+import * as riot from 'riot';
 
 riot.mixin('user-preview', {
 	init: function() {
diff --git a/src/web/app/desktop/router.js b/src/web/app/desktop/router.js
index f4d2ec347a..afa8a2dce3 100644
--- a/src/web/app/desktop/router.js
+++ b/src/web/app/desktop/router.js
@@ -2,11 +2,11 @@
  * Desktop App Router
  */
 
-const riot = require('riot');
+import * as riot from 'riot';
 const route = require('page');
 let page = null;
 
-module.exports = me => {
+export default me => {
 	route('/',              index);
 	route('/i>mentions',    mentions);
 	route('/post::post',    post);
diff --git a/src/web/app/desktop/script.js b/src/web/app/desktop/script.js
index dd4d2ef933..af986b53bd 100644
--- a/src/web/app/desktop/script.js
+++ b/src/web/app/desktop/script.js
@@ -6,11 +6,11 @@
 import './style.styl';
 
 require('./tags');
-const riot = require('riot');
-const boot = require('../boot');
-const mixins = require('./mixins');
-const route = require('./router');
-const fuckAdBlock = require('./scripts/fuck-ad-block');
+require('./mixins');
+import * as riot from 'riot';
+import boot from '../boot';
+import route from './router';
+import fuckAdBlock from './scripts/fuck-ad-block';
 
 /**
  * Boot
@@ -31,9 +31,6 @@ boot(me => {
 		}
 	}
 
-	// Register mixins
-	mixins(me);
-
 	// Start routing
 	route(me);
 });
diff --git a/src/web/app/desktop/scripts/autocomplete.js b/src/web/app/desktop/scripts/autocomplete.js
index 60f28f5fab..8ca516e2a9 100644
--- a/src/web/app/desktop/scripts/autocomplete.js
+++ b/src/web/app/desktop/scripts/autocomplete.js
@@ -1,5 +1,5 @@
 const getCaretCoordinates = require('textarea-caret');
-const riot = require('riot');
+import * as riot from 'riot';
 
 /**
  * オートコンプリートを管理するクラス。
@@ -127,4 +127,4 @@ class Autocomplete {
 	}
 }
 
-module.exports = Autocomplete;
+export default Autocomplete;
diff --git a/src/web/app/desktop/scripts/dialog.js b/src/web/app/desktop/scripts/dialog.js
index 6fe7b6e8d7..c502d3fcb8 100644
--- a/src/web/app/desktop/scripts/dialog.js
+++ b/src/web/app/desktop/scripts/dialog.js
@@ -1,6 +1,6 @@
-const riot = require('riot');
+import * as riot from 'riot';
 
-module.exports = (title, text, buttons, canThrough, onThrough) => {
+export default (title, text, buttons, canThrough, onThrough) => {
 	const dialog = document.body.appendChild(document.createElement('mk-dialog'));
 	const controller = riot.observable();
 	riot.mount(dialog, {
diff --git a/src/web/app/desktop/scripts/fuck-ad-block.js b/src/web/app/desktop/scripts/fuck-ad-block.js
index 38208d34c0..98f758026b 100644
--- a/src/web/app/desktop/scripts/fuck-ad-block.js
+++ b/src/web/app/desktop/scripts/fuck-ad-block.js
@@ -1,7 +1,7 @@
 require('fuckadblock');
 const dialog = require('./dialog');
 
-module.exports = () => {
+export default () => {
 	if (fuckAdBlock === undefined) {
 		adBlockDetected();
 	} else {
diff --git a/src/web/app/desktop/scripts/input-dialog.js b/src/web/app/desktop/scripts/input-dialog.js
index ab9c57401f..954fabfb67 100644
--- a/src/web/app/desktop/scripts/input-dialog.js
+++ b/src/web/app/desktop/scripts/input-dialog.js
@@ -1,6 +1,6 @@
-const riot = require('riot');
+import * as riot from 'riot';
 
-module.exports = (title, placeholder, defaultValue, onOk, onCancel) => {
+export default (title, placeholder, defaultValue, onOk, onCancel) => {
 	const dialog = document.body.appendChild(document.createElement('mk-input-dialog'));
 	return riot.mount(dialog, {
 		title: title,
diff --git a/src/web/app/desktop/scripts/not-implemented-exception.js b/src/web/app/desktop/scripts/not-implemented-exception.js
new file mode 100644
index 0000000000..dd00c7662f
--- /dev/null
+++ b/src/web/app/desktop/scripts/not-implemented-exception.js
@@ -0,0 +1,8 @@
+import dialog from './dialog';
+
+export default () => {
+	dialog('<i class="fa fa-exclamation-triangle"></i>Not implemented yet',
+		'要求された操作は実装されていません。<br>→<a href="https://github.com/syuilo/misskey" target="_blank">Misskeyの開発に参加する</a>', [{
+		text: 'OK'
+	}]);
+};
diff --git a/src/web/app/desktop/scripts/notify.js b/src/web/app/desktop/scripts/notify.js
index 048f22aea7..e58a8e4d36 100644
--- a/src/web/app/desktop/scripts/notify.js
+++ b/src/web/app/desktop/scripts/notify.js
@@ -1,6 +1,6 @@
-const riot = require('riot');
+import * as riot from 'riot';
 
-module.exports = message => {
+export default message => {
 	const notification = document.body.appendChild(document.createElement('mk-ui-notification'));
 	riot.mount(notification, {
 		message: message
diff --git a/src/web/app/desktop/scripts/stream.js b/src/web/app/desktop/scripts/stream.js
index ea1548ecfd..383b552075 100644
--- a/src/web/app/desktop/scripts/stream.js
+++ b/src/web/app/desktop/scripts/stream.js
@@ -1,6 +1,5 @@
 const stream = require('../../common/scripts/stream');
 const getPostSummary = require('../../common/scripts/get-post-summary');
-const riot = require('riot');
 
 module.exports = me => {
 	const s = stream(me);
@@ -37,9 +36,5 @@ module.exports = me => {
 		setTimeout(n.close.bind(n), 6000);
 	});
 
-	riot.mixin('stream', {
-		stream: s.event,
-		getStreamState: s.getState,
-		streamStateEv: s.stateEv
-	});
+	return s;
 };
diff --git a/src/web/app/desktop/scripts/update-avatar.js b/src/web/app/desktop/scripts/update-avatar.js
index b3be78fc68..165c90567c 100644
--- a/src/web/app/desktop/scripts/update-avatar.js
+++ b/src/web/app/desktop/scripts/update-avatar.js
@@ -1,9 +1,9 @@
-const riot = require('riot');
-const CONFIG = require('../../common/scripts/config');
-const dialog = require('./dialog');
-const api = require('../../common/scripts/api');
+import * as riot from 'riot';
+import CONFIG from '../../common/scripts/config';
+import dialog from './dialog';
+import api from '../../common/scripts/api';
 
-module.exports = (I, cb, file = null) => {
+export default (I, cb, file = null) => {
 	const fileSelected = file => {
 		const cropper = riot.mount(document.body.appendChild(document.createElement('mk-crop-window')), {
 			file: file,
diff --git a/src/web/app/desktop/scripts/update-banner.js b/src/web/app/desktop/scripts/update-banner.js
index e5c3dcf69b..d83b2bf1b1 100644
--- a/src/web/app/desktop/scripts/update-banner.js
+++ b/src/web/app/desktop/scripts/update-banner.js
@@ -1,9 +1,9 @@
-const riot = require('riot');
-const CONFIG = require('../../common/scripts/config');
-const dialog = require('./dialog');
-const api = require('../../common/scripts/api');
+import * as riot from 'riot';
+import CONFIG from '../../common/scripts/config';
+import dialog from './dialog';
+import api from '../../common/scripts/api';
 
-module.exports = (I, cb, file = null) => {
+export default (I, cb, file = null) => {
 	const fileSelected = file => {
 		const cropper = riot.mount(document.body.appendChild(document.createElement('mk-crop-window')), {
 			file: file,
diff --git a/src/web/app/desktop/tags/analog-clock.tag b/src/web/app/desktop/tags/analog-clock.tag
index dcf4acaff8..6cd7103c6e 100644
--- a/src/web/app/desktop/tags/analog-clock.tag
+++ b/src/web/app/desktop/tags/analog-clock.tag
@@ -72,7 +72,7 @@
 				const length = Math.min(canvW, canvH) / 4;
 				const uv = new Vec2(Math.sin(angle), -Math.cos(angle));
 				ctx.beginPath();
-				ctx.strokeStyle = CONFIG.themeColor;
+				ctx.strokeStyle = THEME_COLOR;
 				ctx.lineWidth = 2;
 				ctx.moveTo(canvW / 2 - uv.x * length / 5, canvH / 2 - uv.y * length / 5);
 				ctx.lineTo(canvW / 2 + uv.x * length,     canvH / 2 + uv.y * length);
diff --git a/src/web/app/desktop/tags/autocomplete-suggestion.tag b/src/web/app/desktop/tags/autocomplete-suggestion.tag
index c7cc29dc19..b936360402 100644
--- a/src/web/app/desktop/tags/autocomplete-suggestion.tag
+++ b/src/web/app/desktop/tags/autocomplete-suggestion.tag
@@ -80,7 +80,7 @@
 
 	</style>
 	<script>
-		const contains = require('../../common/scripts/contains');
+		import contains from '../../common/scripts/contains';
 
 		this.mixin('api');
 
diff --git a/src/web/app/desktop/tags/big-follow-button.tag b/src/web/app/desktop/tags/big-follow-button.tag
index 9d12b5a53e..376355414c 100644
--- a/src/web/app/desktop/tags/big-follow-button.tag
+++ b/src/web/app/desktop/tags/big-follow-button.tag
@@ -70,12 +70,16 @@
 
 	</style>
 	<script>
+		import isPromise from '../../common/scripts/is-promise';
+
+		this.mixin('i');
 		this.mixin('api');
-		this.mixin('is-promise');
 		this.mixin('stream');
 
+		const stream = this.stream.event;
+
 		this.user = null;
-		this.userPromise = this.isPromise(this.opts.user)
+		this.userPromise = isPromise(this.opts.user)
 			? this.opts.user
 			: Promise.resolve(this.opts.user);
 		this.init = true;
@@ -87,14 +91,14 @@
 					init: false,
 					user: user
 				});
-				this.stream.on('follow', this.onStreamFollow);
-				this.stream.on('unfollow', this.onStreamUnfollow);
+				stream.on('follow', this.onStreamFollow);
+				stream.on('unfollow', this.onStreamUnfollow);
 			});
 		});
 
 		this.on('unmount', () => {
-			this.stream.off('follow', this.onStreamFollow);
-			this.stream.off('unfollow', this.onStreamUnfollow);
+			stream.off('follow', this.onStreamFollow);
+			stream.off('unfollow', this.onStreamUnfollow);
 		});
 
 		this.onStreamFollow = user => {
diff --git a/src/web/app/desktop/tags/contextmenu.tag b/src/web/app/desktop/tags/contextmenu.tag
index 8df11c3819..8d53772de7 100644
--- a/src/web/app/desktop/tags/contextmenu.tag
+++ b/src/web/app/desktop/tags/contextmenu.tag
@@ -95,7 +95,7 @@
 
 	</style>
 	<script>
-		const contains = require('../../common/scripts/contains');
+		import contains from '../../common/scripts/contains';
 
 		this.root.addEventListener('contextmenu', e => {
 			e.preventDefault();
diff --git a/src/web/app/desktop/tags/crop-window.tag b/src/web/app/desktop/tags/crop-window.tag
index 95910235dd..9d984d6e57 100644
--- a/src/web/app/desktop/tags/crop-window.tag
+++ b/src/web/app/desktop/tags/crop-window.tag
@@ -158,7 +158,7 @@
 
 	</style>
 	<script>
-		this.mixin('cropper');
+		const Cropper = require('cropperjs');
 
 		this.image = this.opts.file;
 		this.title = this.opts.title;
@@ -167,7 +167,7 @@
 
 		this.on('mount', () => {
 			this.img = this.refs.window.refs.img;
-			this.cropper = new this.Cropper(this.img, {
+			this.cropper = new Cropper(this.img, {
 				aspectRatio: this.aspectRatio,
 				highlight: false,
 				viewMode: 1
diff --git a/src/web/app/desktop/tags/donation.tag b/src/web/app/desktop/tags/donation.tag
index e313188f24..6c6b3a94ad 100644
--- a/src/web/app/desktop/tags/donation.tag
+++ b/src/web/app/desktop/tags/donation.tag
@@ -47,8 +47,8 @@
 
 	</style>
 	<script>
-		this.mixin('api');
 		this.mixin('i');
+		this.mixin('api');
 
 		this.close = e => {
 			e.preventDefault();
diff --git a/src/web/app/desktop/tags/drive/browser.tag b/src/web/app/desktop/tags/drive/browser.tag
index f83de4a86d..3e06e807a3 100644
--- a/src/web/app/desktop/tags/drive/browser.tag
+++ b/src/web/app/desktop/tags/drive/browser.tag
@@ -238,13 +238,16 @@
 
 	</style>
 	<script>
-		const contains = require('../../../common/scripts/contains');
+		import contains from '../../../common/scripts/contains';
+		import dialog from '../../scripts/dialog';
+		import inputDialog from '../../scripts/input-dialog';
 
+		this.mixin('i');
 		this.mixin('api');
-		this.mixin('dialog');
-		this.mixin('input-dialog');
 		this.mixin('stream');
 
+		const stream = this.stream.event;
+
 		this.files = [];
 		this.folders = [];
 		this.hierarchyFolders = [];
@@ -276,10 +279,10 @@
 				});
 			});
 
-			this.stream.on('drive_file_created', this.onStreamDriveFileCreated);
-			this.stream.on('drive_file_updated', this.onStreamDriveFileUpdated);
-			this.stream.on('drive_folder_created', this.onStreamDriveFolderCreated);
-			this.stream.on('drive_folder_updated', this.onStreamDriveFolderUpdated);
+			stream.on('drive_file_created', this.onStreamDriveFileCreated);
+			stream.on('drive_file_updated', this.onStreamDriveFileUpdated);
+			stream.on('drive_folder_created', this.onStreamDriveFolderCreated);
+			stream.on('drive_folder_updated', this.onStreamDriveFolderUpdated);
 
 			// Riotのバグでnullを渡しても""になる
 			// https://github.com/riot/riot/issues/2080
@@ -292,10 +295,10 @@
 		});
 
 		this.on('unmount', () => {
-			this.stream.off('drive_file_created', this.onStreamDriveFileCreated);
-			this.stream.off('drive_file_updated', this.onStreamDriveFileUpdated);
-			this.stream.off('drive_folder_created', this.onStreamDriveFolderCreated);
-			this.stream.off('drive_folder_updated', this.onStreamDriveFolderUpdated);
+			stream.off('drive_file_created', this.onStreamDriveFileCreated);
+			stream.off('drive_file_updated', this.onStreamDriveFileUpdated);
+			stream.off('drive_folder_created', this.onStreamDriveFolderCreated);
+			stream.off('drive_folder_updated', this.onStreamDriveFolderUpdated);
 		});
 
 		this.onStreamDriveFileCreated = file => {
@@ -445,7 +448,7 @@
 				}).catch(err => {
 					switch (err) {
 						case 'detected-circular-definition':
-							this.dialog('<i class="fa fa-exclamation-triangle"></i>操作を完了できません',
+							dialog('<i class="fa fa-exclamation-triangle"></i>操作を完了できません',
 								'移動先のフォルダーは、移動するフォルダーのサブフォルダーです。', [{
 								text: 'OK'
 							}]);
@@ -479,13 +482,13 @@
 		};
 
 		this.urlUpload = () => {
-			this.inputDialog('URLアップロード', 'アップロードしたいファイルのURL', null, url => {
+			inputDialog('URLアップロード', 'アップロードしたいファイルのURL', null, url => {
 				this.api('drive/files/upload_from_url', {
 					url: url,
 					folder_id: this.folder ? this.folder.id : undefined
 				});
 
-				this.dialog('<i class="fa fa-check"></i>アップロードをリクエストしました',
+				dialog('<i class="fa fa-check"></i>アップロードをリクエストしました',
 					'アップロードが完了するまで時間がかかる場合があります。', [{
 					text: 'OK'
 				}]);
@@ -493,7 +496,7 @@
 		};
 
 		this.createFolder = () => {
-			this.inputDialog('フォルダー作成', 'フォルダー名', null, name => {
+			inputDialog('フォルダー作成', 'フォルダー名', null, name => {
 				this.api('drive/folders/create', {
 					name: name,
 					folder_id: this.folder ? this.folder.id : undefined
diff --git a/src/web/app/desktop/tags/drive/file-contextmenu.tag b/src/web/app/desktop/tags/drive/file-contextmenu.tag
index 17376adb86..edcbfba63e 100644
--- a/src/web/app/desktop/tags/drive/file-contextmenu.tag
+++ b/src/web/app/desktop/tags/drive/file-contextmenu.tag
@@ -35,15 +35,14 @@
 		</ul>
 	</mk-contextmenu>
 	<script>
-		const copyToClipboard = require('../../../common/scripts/copy-to-clipboard');
+		import copyToClipboard from '../../../common/scripts/copy-to-clipboard';
+		import dialog from '../../scripts/dialog';
+		import inputDialog from '../../scripts/input-dialog';
+		import updateAvatar from '../../scripts/update-avatar';
+		import NotImplementedException from '../../scripts/not-implemented-exception';
 
-		this.mixin('api');
 		this.mixin('i');
-		this.mixin('update-avatar');
-		this.mixin('update-banner');
-		this.mixin('dialog');
-		this.mixin('input-dialog');
-		this.mixin('NotImplementedException');
+		this.mixin('api');
 
 		this.browser = this.opts.browser;
 		this.file = this.opts.file;
@@ -85,16 +84,16 @@
 
 		this.setAvatar = () => {
 			this.refs.ctx.close();
-			this.updateAvatar(this.I, null, this.file);
+			updateAvatar(this.I, null, this.file);
 		};
 
 		this.setBanner = () => {
 			this.refs.ctx.close();
-			this.updateBanner(this.I, null, this.file);
+			updateBanner(this.I, null, this.file);
 		};
 
 		this.addApp = () => {
-			this.NotImplementedException();
+			NotImplementedException();
 		};
 	</script>
 </mk-drive-browser-file-contextmenu>
diff --git a/src/web/app/desktop/tags/drive/file.tag b/src/web/app/desktop/tags/drive/file.tag
index 519d04f6dc..7da0f8b0d0 100644
--- a/src/web/app/desktop/tags/drive/file.tag
+++ b/src/web/app/desktop/tags/drive/file.tag
@@ -144,13 +144,13 @@
 
 	</style>
 	<script>
-		this.bytesToSize = require('../../../common/scripts/bytes-to-size');
+		import bytesToSize from '../../../common/scripts/bytes-to-size';
 
 		this.mixin('i');
 
 		this.file = this.opts.file;
 		this.browser = this.parent;
-		this.title = `${this.file.name}\n${this.file.type} ${this.bytesToSize(this.file.datasize)}`;
+		this.title = `${this.file.name}\n${this.file.type} ${bytesToSize(this.file.datasize)}`;
 		this.isContextmenuShowing = false;
 		this.isSelected = this.browser.selectedFiles.some(f => f.id == this.file.id);
 
diff --git a/src/web/app/desktop/tags/drive/folder-contextmenu.tag b/src/web/app/desktop/tags/drive/folder-contextmenu.tag
index 71982a7c74..a1f0947f39 100644
--- a/src/web/app/desktop/tags/drive/folder-contextmenu.tag
+++ b/src/web/app/desktop/tags/drive/folder-contextmenu.tag
@@ -18,8 +18,9 @@
 		</ul>
 	</mk-contextmenu>
 	<script>
+		import inputDialog from '../../scripts/input-dialog';
+
 		this.mixin('api');
-		this.mixin('input-dialog');
 
 		this.browser = this.opts.browser;
 		this.folder = this.opts.folder;
@@ -51,7 +52,7 @@
 		this.rename = () => {
 			this.refs.ctx.close();
 
-			this.inputDialog('フォルダ名の変更', '新しいフォルダ名を入力してください', this.folder.name, name => {
+			inputDialog('フォルダ名の変更', '新しいフォルダ名を入力してください', this.folder.name, name => {
 				this.api('drive/folders/update', {
 					folder_id: this.folder.id,
 					name: name
diff --git a/src/web/app/desktop/tags/drive/folder.tag b/src/web/app/desktop/tags/drive/folder.tag
index 1fd6fd17c7..c7ad43c7a0 100644
--- a/src/web/app/desktop/tags/drive/folder.tag
+++ b/src/web/app/desktop/tags/drive/folder.tag
@@ -50,8 +50,9 @@
 
 	</style>
 	<script>
+		import dialog from '../../scripts/dialog';
+
 		this.mixin('api');
-		this.mixin('dialog');
 
 		this.folder = this.opts.folder;
 		this.browser = this.parent;
@@ -144,7 +145,7 @@
 				}).catch(err => {
 					switch (err) {
 						case 'detected-circular-definition':
-							this.dialog('<i class="fa fa-exclamation-triangle"></i>操作を完了できません',
+							dialog('<i class="fa fa-exclamation-triangle"></i>操作を完了できません',
 								'移動先のフォルダーは、移動するフォルダーのサブフォルダーです。', [{
 								text: 'OK'
 							}]);
diff --git a/src/web/app/desktop/tags/follow-button.tag b/src/web/app/desktop/tags/follow-button.tag
index 1877e4a53f..d3e562c401 100644
--- a/src/web/app/desktop/tags/follow-button.tag
+++ b/src/web/app/desktop/tags/follow-button.tag
@@ -67,12 +67,16 @@
 
 	</style>
 	<script>
+		import isPromise from '../../common/scripts/is-promise';
+
+		this.mixin('i');
 		this.mixin('api');
-		this.mixin('is-promise');
 		this.mixin('stream');
 
+		const stream = this.stream.event;
+
 		this.user = null;
-		this.userPromise = this.isPromise(this.opts.user)
+		this.userPromise = isPromise(this.opts.user)
 			? this.opts.user
 			: Promise.resolve(this.opts.user);
 		this.init = true;
@@ -84,14 +88,14 @@
 					init: false,
 					user: user
 				});
-				this.stream.on('follow', this.onStreamFollow);
-				this.stream.on('unfollow', this.onStreamUnfollow);
+				stream.on('follow', this.onStreamFollow);
+				stream.on('unfollow', this.onStreamUnfollow);
 			});
 		});
 
 		this.on('unmount', () => {
-			this.stream.off('follow', this.onStreamFollow);
-			this.stream.off('unfollow', this.onStreamUnfollow);
+			stream.off('follow', this.onStreamFollow);
+			stream.off('unfollow', this.onStreamUnfollow);
 		});
 
 		this.onStreamFollow = user => {
diff --git a/src/web/app/desktop/tags/home-widgets/photo-stream.tag b/src/web/app/desktop/tags/home-widgets/photo-stream.tag
index 7cbb07b4de..d135b95af9 100644
--- a/src/web/app/desktop/tags/home-widgets/photo-stream.tag
+++ b/src/web/app/desktop/tags/home-widgets/photo-stream.tag
@@ -57,14 +57,17 @@
 
 	</style>
 	<script>
+		this.mixin('i');
 		this.mixin('api');
 		this.mixin('stream');
 
+		const stream = this.stream.event;
+
 		this.images = [];
 		this.initializing = true;
 
 		this.on('mount', () => {
-			this.stream.on('drive_file_created', this.onStreamDriveFileCreated);
+			stream.on('drive_file_created', this.onStreamDriveFileCreated);
 
 			this.api('drive/stream', {
 				type: 'image/*',
@@ -78,7 +81,7 @@
 		});
 
 		this.on('unmount', () => {
-			this.stream.off('drive_file_created', this.onStreamDriveFileCreated);
+			stream.off('drive_file_created', this.onStreamDriveFileCreated);
 		});
 
 		this.onStreamDriveFileCreated = file => {
diff --git a/src/web/app/desktop/tags/home-widgets/profile.tag b/src/web/app/desktop/tags/home-widgets/profile.tag
index 8ee2186149..d5bc1f4645 100644
--- a/src/web/app/desktop/tags/home-widgets/profile.tag
+++ b/src/web/app/desktop/tags/home-widgets/profile.tag
@@ -41,17 +41,19 @@
 
 	</style>
 	<script>
+		import inputDialog from '../../scripts/input-dialog';
+		import updateAvatar from '../../scripts/update-avatar';
+		import updateBanner from '../../scripts/update-banner';
+
 		this.mixin('i');
 		this.mixin('user-preview');
-		this.mixin('update-avatar');
-		this.mixin('update-banner');
 
 		this.setAvatar = () => {
-			this.updateAvatar(this.I);
+			updateAvatar(this.I);
 		};
 
 		this.setBanner = () => {
-			this.updateBanner(this.I);
+			updateBanner(this.I);
 		};
 	</script>
 </mk-profile-home-widget>
diff --git a/src/web/app/desktop/tags/home-widgets/rss-reader.tag b/src/web/app/desktop/tags/home-widgets/rss-reader.tag
index c8700a8b63..b8cfd8bddc 100644
--- a/src/web/app/desktop/tags/home-widgets/rss-reader.tag
+++ b/src/web/app/desktop/tags/home-widgets/rss-reader.tag
@@ -65,7 +65,6 @@
 	</style>
 	<script>
 		this.mixin('api');
-		this.mixin('NotImplementedException');
 
 		this.url = 'http://news.yahoo.co.jp/pickup/rss.xml';
 		this.items = [];
@@ -81,7 +80,7 @@
 		});
 
 		this.fetch = () => {
-			this.api(CONFIG.url + '/api:rss', {
+			this.api('/api:rss', {
 				url: this.url
 			}).then(feed => {
 				this.update({
@@ -92,7 +91,6 @@
 		};
 
 		this.settings = () => {
-			this.NotImplementedException();
 		};
 	</script>
 </mk-rss-reader-home-widget>
diff --git a/src/web/app/desktop/tags/home-widgets/timeline.tag b/src/web/app/desktop/tags/home-widgets/timeline.tag
index a0a8790eaa..f4c870b99c 100644
--- a/src/web/app/desktop/tags/home-widgets/timeline.tag
+++ b/src/web/app/desktop/tags/home-widgets/timeline.tag
@@ -36,15 +36,17 @@
 		this.mixin('api');
 		this.mixin('stream');
 
+		const stream = this.stream.event;
+
 		this.isLoading = true;
 		this.isEmpty = false;
 		this.moreLoading = false;
 		this.noFollowing = this.I.following_count == 0;
 
 		this.on('mount', () => {
-			this.stream.on('post', this.onStreamPost);
-			this.stream.on('follow', this.onStreamFollow);
-			this.stream.on('unfollow', this.onStreamUnfollow);
+			stream.on('post', this.onStreamPost);
+			stream.on('follow', this.onStreamFollow);
+			stream.on('unfollow', this.onStreamUnfollow);
 
 			document.addEventListener('keydown', this.onDocumentKeydown);
 			window.addEventListener('scroll', this.onScroll);
@@ -53,9 +55,9 @@
 		});
 
 		this.on('unmount', () => {
-			this.stream.off('post', this.onStreamPost);
-			this.stream.off('follow', this.onStreamFollow);
-			this.stream.off('unfollow', this.onStreamUnfollow);
+			stream.off('post', this.onStreamPost);
+			stream.off('follow', this.onStreamFollow);
+			stream.off('unfollow', this.onStreamUnfollow);
 
 			document.removeEventListener('keydown', this.onDocumentKeydown);
 			window.removeEventListener('scroll', this.onScroll);
diff --git a/src/web/app/desktop/tags/notifications.tag b/src/web/app/desktop/tags/notifications.tag
index 7ba74412ea..d459b7f31a 100644
--- a/src/web/app/desktop/tags/notifications.tag
+++ b/src/web/app/desktop/tags/notifications.tag
@@ -177,10 +177,15 @@
 
 	</style>
 	<script>
+		import getPostSummary from '../../common/scripts/get-post-summary';
+		this.getPostSummary = getPostSummary;
+
+		this.mixin('i');
 		this.mixin('api');
-		this.mixin('stream');
 		this.mixin('user-preview');
-		this.mixin('get-post-summary');
+		this.mixin('stream');
+
+		const stream = this.stream.event;
 
 		this.notifications = [];
 		this.loading = true;
@@ -193,11 +198,11 @@
 				});
 			});
 
-			this.stream.on('notification', this.onNotification);
+			stream.on('notification', this.onNotification);
 		});
 
 		this.on('unmount', () => {
-			this.stream.off('notification', this.onNotification);
+			stream.off('notification', this.onNotification);
 		});
 
 		this.onNotification = notification => {
diff --git a/src/web/app/desktop/tags/pages/home.tag b/src/web/app/desktop/tags/pages/home.tag
index e988cbc76e..1bffbc7b4a 100644
--- a/src/web/app/desktop/tags/pages/home.tag
+++ b/src/web/app/desktop/tags/pages/home.tag
@@ -7,11 +7,14 @@
 			display block
 	</style>
 	<script>
+		import Progress from '../../../common/scripts/loading';
+		import getPostSummary from '../../../common/scripts/get-post-summary';
+
 		this.mixin('i');
 		this.mixin('api');
-		this.mixin('ui-progress');
 		this.mixin('stream');
-		this.mixin('get-post-summary');
+
+		const stream = this.stream.event;
 
 		this.unreadCount = 0;
 
@@ -19,23 +22,23 @@
 
 		this.on('mount', () => {
 			this.refs.ui.refs.home.on('loaded', () => {
-				this.Progress.done();
+				Progress.done();
 			});
 			document.title = 'Misskey';
-			this.Progress.start();
-			this.stream.on('post', this.onStreamPost);
+			Progress.start();
+			stream.on('post', this.onStreamPost);
 			document.addEventListener('visibilitychange', this.windowOnVisibilitychange, false);
 		});
 
 		this.on('unmount', () => {
-			this.stream.off('post', this.onStreamPost);
+			stream.off('post', this.onStreamPost);
 			document.removeEventListener('visibilitychange', this.windowOnVisibilitychange);
 		});
 
 		this.onStreamPost = post => {
 			if (document.hidden && post.user_id != this.I.id) {
 				this.unreadCount++;
-				document.title = `(${this.unreadCount}) ${this.getPostSummary(post)}`;
+				document.title = `(${this.unreadCount}) ${getPostSummary(post)}`;
 			}
 		};
 
diff --git a/src/web/app/desktop/tags/pages/post.tag b/src/web/app/desktop/tags/pages/post.tag
index b04ba69dc5..42acdd6aa4 100644
--- a/src/web/app/desktop/tags/pages/post.tag
+++ b/src/web/app/desktop/tags/pages/post.tag
@@ -16,19 +16,19 @@
 
 	</style>
 	<script>
-		this.mixin('ui-progress');
+		import Progress from '../../../common/scripts/loading';
 
 		this.post = this.opts.post;
 
 		this.on('mount', () => {
-			this.Progress.start();
+			Progress.start();
 
 			this.refs.ui.refs.detail.on('post-fetched', () => {
-				this.Progress.set(0.5);
+				Progress.set(0.5);
 			});
 
 			this.refs.ui.refs.detail.on('loaded', () => {
-				this.Progress.done();
+				Progress.done();
 			});
 		});
 	</script>
diff --git a/src/web/app/desktop/tags/pages/search.tag b/src/web/app/desktop/tags/pages/search.tag
index ace9e27ab9..7a97e4781d 100644
--- a/src/web/app/desktop/tags/pages/search.tag
+++ b/src/web/app/desktop/tags/pages/search.tag
@@ -7,13 +7,13 @@
 			display block
 	</style>
 	<script>
-		this.mixin('ui-progress');
+		import Progress from '../../../common/scripts/loading';
 
 		this.on('mount', () => {
-			this.Progress.start();
+			Progress.start();
 
 			this.refs.ui.refs.search.on('loaded', () => {
-				this.Progress.done();
+				Progress.done();
 			});
 		});
 	</script>
diff --git a/src/web/app/desktop/tags/pages/user.tag b/src/web/app/desktop/tags/pages/user.tag
index 767af31e9a..9e35383088 100644
--- a/src/web/app/desktop/tags/pages/user.tag
+++ b/src/web/app/desktop/tags/pages/user.tag
@@ -7,20 +7,20 @@
 			display block
 	</style>
 	<script>
-		this.mixin('ui-progress');
+		import Progress from '../../../common/scripts/loading';
 
 		this.user = this.opts.user;
 
 		this.on('mount', () => {
-			this.Progress.start();
+			Progress.start();
 
 			this.refs.ui.refs.user.on('user-fetched', user => {
-				this.Progress.set(0.5);
+				Progress.set(0.5);
 				document.title = user.name + ' | Misskey'
 			});
 
 			this.refs.ui.refs.user.on('loaded', () => {
-				this.Progress.done();
+				Progress.done();
 			});
 		});
 	</script>
diff --git a/src/web/app/desktop/tags/post-detail-sub.tag b/src/web/app/desktop/tags/post-detail-sub.tag
index 2de123d1ac..a0473eb709 100644
--- a/src/web/app/desktop/tags/post-detail-sub.tag
+++ b/src/web/app/desktop/tags/post-detail-sub.tag
@@ -115,8 +115,10 @@
 	</style>
 	<script>
 		this.mixin('api');
-		this.mixin('text');
-		this.mixin('date-stringify');
+
+		import compile from '../../common/scripts/text-compiler';
+
+		this.dateStringify = require('../../common/scripts/date-stringify');
 		this.mixin('user-preview');
 
 		this.post = this.opts.post;
@@ -124,9 +126,9 @@
 
 		this.on('mount', () => {
 			if (this.post.text) {
-				const tokens = this.analyze(this.post.text);
+				const tokens = this.post.ast;
 
-				this.refs.text.innerHTML = this.compile(tokens);
+				this.refs.text.innerHTML = compile(tokens);
 
 				this.refs.text.children.forEach(e => {
 					if (e.tagName == 'MK-URL') riot.mount(e);
diff --git a/src/web/app/desktop/tags/post-detail.tag b/src/web/app/desktop/tags/post-detail.tag
index ca2d59f2e2..34d625204d 100644
--- a/src/web/app/desktop/tags/post-detail.tag
+++ b/src/web/app/desktop/tags/post-detail.tag
@@ -340,9 +340,11 @@
 	</style>
 	<script>
 		this.mixin('api');
-		this.mixin('text');
+
+		import compile from '../../common/scripts/text-compiler';
+
 		this.mixin('user-preview');
-		this.mixin('date-stringify');
+		this.dateStringify = require('../../common/scripts/date-stringify');
 		this.mixin('NotImplementedException');
 
 		this.fetching = true;
@@ -367,9 +369,9 @@
 				this.trigger('loaded');
 
 				if (this.p.text) {
-					const tokens = this.analyze(this.p.text);
+					const tokens = this.p.ast;
 
-					this.refs.text.innerHTML = this.compile(tokens);
+					this.refs.text.innerHTML = compile(tokens);
 
 					this.refs.text.children.forEach(e => {
 						if (e.tagName == 'MK-URL') riot.mount(e);
diff --git a/src/web/app/desktop/tags/post-form.tag b/src/web/app/desktop/tags/post-form.tag
index df5b6d9344..922cb74271 100644
--- a/src/web/app/desktop/tags/post-form.tag
+++ b/src/web/app/desktop/tags/post-form.tag
@@ -306,11 +306,11 @@
 
 	</style>
 	<script>
-		const getCat = require('../../common/scripts/get-cat');
+		import getCat from '../../common/scripts/get-cat';
+		import notify from '../scripts/notify';
+		import Autocomplete from '../scripts/autocomplete';
 
 		this.mixin('api');
-		this.mixin('notify');
-		this.mixin('autocomplete');
 
 		this.wait = false;
 		this.uploadings = [];
@@ -355,7 +355,7 @@
 				this.trigger('change-uploading-files', uploads);
 			});
 
-			this.autocomplete = new this.Autocomplete(this.refs.text);
+			this.autocomplete = new Autocomplete(this.refs.text);
 			this.autocomplete.attach();
 
 			// 書きかけの投稿を復元
@@ -488,13 +488,13 @@
 				this.clear();
 				this.removeDraft();
 				this.trigger('post');
-				this.notify(this.repost
+				notify(this.repost
 					? 'Repostしました!'
 					: this.inReplyToPost
 						? '返信しました!'
 						: '投稿しました!');
 			}).catch(err => {
-				this.notify(this.repost
+				notify(this.repost
 					? 'Repostできませんでした'
 					: this.inReplyToPost
 						? '返信できませんでした'
diff --git a/src/web/app/desktop/tags/post-preview.tag b/src/web/app/desktop/tags/post-preview.tag
index 458a6f83f2..be94012faa 100644
--- a/src/web/app/desktop/tags/post-preview.tag
+++ b/src/web/app/desktop/tags/post-preview.tag
@@ -83,11 +83,12 @@
 
 	</style>
 	<script>
-		this.mixin('date-stringify');
+		import dateStringify from '../../common/scripts/date-stringify';
+
 		this.mixin('user-preview');
 
 		this.post = this.opts.post;
 
-		this.title = this.dateStringify(this.post.created_at);
+		this.title = dateStringify(this.post.created_at);
 	</script>
 </mk-post-preview>
diff --git a/src/web/app/desktop/tags/repost-form.tag b/src/web/app/desktop/tags/repost-form.tag
index 2bbed8aca7..b7a25fc1dd 100644
--- a/src/web/app/desktop/tags/repost-form.tag
+++ b/src/web/app/desktop/tags/repost-form.tag
@@ -85,8 +85,9 @@
 
 	</style>
 	<script>
+		import notify from '../scripts/notify';
+
 		this.mixin('api');
-		this.mixin('notify');
 
 		this.wait = false;
 		this.quote = false;
@@ -101,9 +102,9 @@
 				repost_id: this.opts.post.id
 			}).then(data => {
 				this.trigger('posted');
-				this.notify('Repostしました!');
+				notify('Repostしました!');
 			}).catch(err => {
-				this.notify('Repostできませんでした');
+				notify('Repostできませんでした');
 			}).then(() => {
 				this.update({
 					wait: false
diff --git a/src/web/app/desktop/tags/set-avatar-suggestion.tag b/src/web/app/desktop/tags/set-avatar-suggestion.tag
index 335148ea5d..2fe7f963fe 100644
--- a/src/web/app/desktop/tags/set-avatar-suggestion.tag
+++ b/src/web/app/desktop/tags/set-avatar-suggestion.tag
@@ -31,11 +31,12 @@
 
 	</style>
 	<script>
+		import updateAvatar from '../scripts/update-avatar';
+
 		this.mixin('i');
-		this.mixin('update-avatar');
 
 		this.set = () => {
-			this.updateAvatar(this.I);
+			updateAvatar(this.I);
 		};
 
 		this.close = e => {
diff --git a/src/web/app/desktop/tags/set-banner-suggestion.tag b/src/web/app/desktop/tags/set-banner-suggestion.tag
index deddc478cc..d3c50362d9 100644
--- a/src/web/app/desktop/tags/set-banner-suggestion.tag
+++ b/src/web/app/desktop/tags/set-banner-suggestion.tag
@@ -31,11 +31,12 @@
 
 	</style>
 	<script>
+		import updateBanner from '../scripts/update-banner';
+
 		this.mixin('i');
-		this.mixin('update-banner');
 
 		this.set = () => {
-			this.updateBanner(this.I);
+			updateBanner(this.I);
 		};
 
 		this.close = e => {
diff --git a/src/web/app/desktop/tags/settings.tag b/src/web/app/desktop/tags/settings.tag
index 32d62758ec..6d692ed9aa 100644
--- a/src/web/app/desktop/tags/settings.tag
+++ b/src/web/app/desktop/tags/settings.tag
@@ -188,11 +188,11 @@
 
 	</style>
 	<script>
+		import updateAvatar from '../scripts/update-avatar';
+		import notify from '../scripts/notify';
+
 		this.mixin('i');
 		this.mixin('api');
-		this.mixin('notify');
-		this.mixin('dialog');
-		this.mixin('update-avatar');
 
 		this.page = 'account';
 
@@ -201,7 +201,7 @@
 		};
 
 		this.avatar = () => {
-			this.updateAvatar(this.I);
+			updateAvatar(this.I);
 		};
 
 		this.updateAccount = () => {
@@ -211,7 +211,7 @@
 				description: this.refs.accountDescription.value || undefined,
 				birthday: this.refs.accountBirthday.value || undefined
 			}).then(() => {
-				this.notify('プロフィールを更新しました');
+				notify('プロフィールを更新しました');
 			});
 		};
 
diff --git a/src/web/app/desktop/tags/sub-post-content.tag b/src/web/app/desktop/tags/sub-post-content.tag
index e0509ab7d4..fab2e65af1 100644
--- a/src/web/app/desktop/tags/sub-post-content.tag
+++ b/src/web/app/desktop/tags/sub-post-content.tag
@@ -34,15 +34,16 @@
 
 	</style>
 	<script>
-		this.mixin('text');
+		import compile from '../../common/scripts/text-compiler';
+
 		this.mixin('user-preview');
 
 		this.post = this.opts.post;
 
 		this.on('mount', () => {
 			if (this.post.text) {
-				const tokens = this.analyze(this.post.text);
-				this.refs.text.innerHTML = this.compile(tokens, false);
+				const tokens = this.post.ast;
+				this.refs.text.innerHTML = compile(tokens, false);
 
 				this.refs.text.children.forEach(e => {
 					if (e.tagName == 'MK-URL') riot.mount(e);
diff --git a/src/web/app/desktop/tags/timeline-post-sub.tag b/src/web/app/desktop/tags/timeline-post-sub.tag
index 3d8747e9f8..a858a04b7c 100644
--- a/src/web/app/desktop/tags/timeline-post-sub.tag
+++ b/src/web/app/desktop/tags/timeline-post-sub.tag
@@ -97,10 +97,11 @@
 
 	</style>
 	<script>
-		this.mixin('date-stringify');
+		import dateStringify from '../../common/scripts/date-stringify';
+
 		this.mixin('user-preview');
 
 		this.post = this.opts.post;
-		this.title = this.dateStringify(this.post.created_at);
+		this.title = dateStringify(this.post.created_at);
 	</script>
 </mk-timeline-post-sub>
diff --git a/src/web/app/desktop/tags/timeline-post.tag b/src/web/app/desktop/tags/timeline-post.tag
index 8929a8dd5a..0559aaf6a0 100644
--- a/src/web/app/desktop/tags/timeline-post.tag
+++ b/src/web/app/desktop/tags/timeline-post.tag
@@ -55,7 +55,7 @@
 				<button class={ liked: p.is_liked } onclick={ like } title="善哉"><i class="fa fa-thumbs-o-up"></i>
 					<p class="count" if={ p.likes_count > 0 }>{ p.likes_count }</p>
 				</button>
-				<button onclick={ NotImplementedException }>
+				<button>
 					<i class="fa fa-ellipsis-h"></i>
 				</button>
 				<button onclick={ toggleDetail } title="詳細">
@@ -327,26 +327,26 @@
 
 	</style>
 	<script>
+		import compile from '../../common/scripts/text-compiler';
+		import dateStringify from '../../common/scripts/date-stringify';
+
 		this.mixin('api');
-		this.mixin('text');
-		this.mixin('date-stringify');
 		this.mixin('user-preview');
-		this.mixin('NotImplementedException');
 
 		this.post = this.opts.post;
 		this.isRepost = this.post.repost && this.post.text == null && this.post.media_ids == null && this.post.poll == null;
 		this.p = this.isRepost ? this.post.repost : this.post;
 
-		this.title = this.dateStringify(this.p.created_at);
+		this.title = dateStringify(this.p.created_at);
 
 		this.url = `/${this.p.user.username}/${this.p.id}`;
 		this.isDetailOpened = false;
 
 		this.on('mount', () => {
 			if (this.p.text) {
-				const tokens = this.analyze(this.p.text);
+				const tokens = this.p.ast;
 
-				this.refs.text.innerHTML = this.refs.text.innerHTML.replace('<p class="dummy"></p>', this.compile(tokens));
+				this.refs.text.innerHTML = this.refs.text.innerHTML.replace('<p class="dummy"></p>', compile(tokens));
 
 				this.refs.text.children.forEach(e => {
 					if (e.tagName == 'MK-URL') riot.mount(e);
diff --git a/src/web/app/desktop/tags/ui-header-account.tag b/src/web/app/desktop/tags/ui-header-account.tag
index c035529f79..e2408e5178 100644
--- a/src/web/app/desktop/tags/ui-header-account.tag
+++ b/src/web/app/desktop/tags/ui-header-account.tag
@@ -159,10 +159,10 @@
 
 	</style>
 	<script>
-		const contains = require('../../common/scripts/contains');
+		import contains from '../../common/scripts/contains';
+		import signout from '../../common/scripts/signout';
 
 		this.mixin('i');
-		this.mixin('signout');
 
 		this.isOpen = false;
 
diff --git a/src/web/app/desktop/tags/ui-header-nav.tag b/src/web/app/desktop/tags/ui-header-nav.tag
index 5ad0c93596..67ba475440 100644
--- a/src/web/app/desktop/tags/ui-header-nav.tag
+++ b/src/web/app/desktop/tags/ui-header-nav.tag
@@ -99,11 +99,13 @@
 		this.mixin('api');
 		this.mixin('stream');
 
+		const stream = this.stream.event;
+
 		this.page = this.opts.page;
 
 		this.on('mount', () => {
-			this.stream.on('read_all_messaging_messages', this.onReadAllMessagingMessages);
-			this.stream.on('unread_messaging_message', this.onUnreadMessagingMessage);
+			stream.on('read_all_messaging_messages', this.onReadAllMessagingMessages);
+			stream.on('unread_messaging_message', this.onUnreadMessagingMessage);
 
 			// Fetch count of unread messaging messages
 			this.api('messaging/unread').then(res => {
@@ -116,8 +118,8 @@
 		});
 
 		this.on('unmount', () => {
-			this.stream.off('read_all_messaging_messages', this.onReadAllMessagingMessages);
-			this.stream.off('unread_messaging_message', this.onUnreadMessagingMessage);
+			stream.off('read_all_messaging_messages', this.onReadAllMessagingMessages);
+			stream.off('unread_messaging_message', this.onUnreadMessagingMessage);
 		});
 
 		this.onReadAllMessagingMessages = () => {
diff --git a/src/web/app/desktop/tags/ui-header-notifications.tag b/src/web/app/desktop/tags/ui-header-notifications.tag
index 05a9e6d772..8afa6c5346 100644
--- a/src/web/app/desktop/tags/ui-header-notifications.tag
+++ b/src/web/app/desktop/tags/ui-header-notifications.tag
@@ -75,7 +75,7 @@
 
 	</style>
 	<script>
-		const contains = require('../../common/scripts/contains');
+		import contains from '../../common/scripts/contains';
 
 		this.isOpen = false;
 
diff --git a/src/web/app/desktop/tags/user-header.tag b/src/web/app/desktop/tags/user-header.tag
index c7ebaff446..8ec5003b99 100644
--- a/src/web/app/desktop/tags/user-header.tag
+++ b/src/web/app/desktop/tags/user-header.tag
@@ -5,8 +5,10 @@
 		<p class="username">@{ user.username }</p>
 		<p class="location" if={ user.profile.location }><i class="fa fa-map-marker"></i>{ user.profile.location }</p>
 	</div>
-	<footer><a href={ '/' + user.username }>投稿</a><a href={ '/' + user.username + '/media' }>メディア</a><a href={ '/' + user.username + '/graphs' }>グラフ</a>
-		<button onclick={ NotImplementedException }><i class="fa fa-ellipsis-h"></i></button>
+	<footer>
+		<a href={ '/' + user.username }>投稿</a>
+		<a href={ '/' + user.username + '/media' }>メディア</a>
+		<a href={ '/' + user.username + '/graphs' }>グラフ</a>
 	</footer>
 	<style>
 		:scope
@@ -104,9 +106,9 @@
 
 	</style>
 	<script>
+		import updateBanner from '../scripts/update-banner';
+
 		this.mixin('i');
-		this.mixin('update-banner');
-		this.mixin('NotImplementedException');
 
 		this.user = this.opts.user;
 
@@ -136,7 +138,7 @@
 		this.onUpdateBanner = () => {
 			if (!this.SIGNIN || this.I.id != this.user.id) return;
 
-			this.updateBanner(this.I, i => {
+			updateBanner(this.I, i => {
 				this.user.banner_url = i.banner_url;
 				this.update();
 			});
diff --git a/src/web/app/desktop/tags/user-photos.tag b/src/web/app/desktop/tags/user-photos.tag
index 86f12aceb9..8ba0c05bee 100644
--- a/src/web/app/desktop/tags/user-photos.tag
+++ b/src/web/app/desktop/tags/user-photos.tag
@@ -57,13 +57,14 @@
 
 	</style>
 	<script>
+		import isPromise from '../../common/scripts/is-promise';
+
 		this.mixin('api');
-		this.mixin('is-promise');
 
 		this.images = [];
 		this.initializing = true;
 		this.user = null;
-		this.userPromise = this.isPromise(this.opts.user)
+		this.userPromise = isPromise(this.opts.user)
 			? this.opts.user
 			: Promise.resolve(this.opts.user);
 
diff --git a/src/web/app/desktop/tags/user-timeline.tag b/src/web/app/desktop/tags/user-timeline.tag
index b0bad65a4d..ac3f995b3c 100644
--- a/src/web/app/desktop/tags/user-timeline.tag
+++ b/src/web/app/desktop/tags/user-timeline.tag
@@ -48,12 +48,12 @@
 
 	</style>
 	<script>
+		import isPromise from '../../common/scripts/is-promise';
+
 		this.mixin('api');
-		this.mixin('is-promise');
-		this.mixin('get-post-summary');
 
 		this.user = null;
-		this.userPromise = this.isPromise(this.opts.user)
+		this.userPromise = isPromise(this.opts.user)
 			? this.opts.user
 			: Promise.resolve(this.opts.user);
 		this.isLoading = true;
diff --git a/src/web/app/desktop/tags/window.tag b/src/web/app/desktop/tags/window.tag
index 55bb666239..9e48958a11 100644
--- a/src/web/app/desktop/tags/window.tag
+++ b/src/web/app/desktop/tags/window.tag
@@ -173,7 +173,7 @@
 
 	</style>
 	<script>
-		const contains = require('../../common/scripts/contains');
+		import contains from '../../common/scripts/contains';
 
 		this.minHeight = 40;
 		this.minWidth = 200;
diff --git a/src/web/app/dev/router.js b/src/web/app/dev/router.js
index 71c098463b..27384cdf63 100644
--- a/src/web/app/dev/router.js
+++ b/src/web/app/dev/router.js
@@ -34,7 +34,7 @@ module.exports = me => {
 	route();
 };
 
-const riot = require('riot');
+import * as riot from 'riot';
 function mount(content) {
 	if (page) page.unmount();
 	const body = document.getElementById('app');
diff --git a/src/web/app/dev/script.js b/src/web/app/dev/script.js
index a20d960000..38e864af0b 100644
--- a/src/web/app/dev/script.js
+++ b/src/web/app/dev/script.js
@@ -6,7 +6,7 @@
 import './style.styl';
 
 require('./tags');
-const boot = require('../boot');
+import boot from '../boot';
 const route = require('./router');
 
 /**
diff --git a/src/web/app/mobile/mixins.js b/src/web/app/mobile/mixins.js
deleted file mode 100644
index 98601a1072..0000000000
--- a/src/web/app/mobile/mixins.js
+++ /dev/null
@@ -1,25 +0,0 @@
-const riot = require('riot');
-
-module.exports = me => {
-	if (me) {
-		require('./scripts/stream')(me);
-	}
-
-	require('./scripts/ui');
-
-	riot.mixin('open-post-form', {
-		openPostForm: opts => {
-			const app = document.getElementById('app');
-			app.style.display = 'none';
-
-			function recover() {
-				app.style.display = 'block';
-			}
-
-			const form = riot.mount(document.body.appendChild(document.createElement('mk-post-form')), opts)[0];
-			form
-				.on('cancel', recover)
-				.on('post', recover);
-		}
-	});
-};
diff --git a/src/web/app/mobile/router.js b/src/web/app/mobile/router.js
index df4871f292..d0b45d9614 100644
--- a/src/web/app/mobile/router.js
+++ b/src/web/app/mobile/router.js
@@ -2,11 +2,11 @@
  * Mobile App Router
  */
 
-const riot = require('riot');
+import * as riot from 'riot';
 const route = require('page');
 let page = null;
 
-module.exports = me => {
+export default me => {
 	route('/',                           index);
 	route('/i/notifications',            notifications);
 	route('/i/messaging',                messaging);
diff --git a/src/web/app/mobile/script.js b/src/web/app/mobile/script.js
index 53f4d9f524..22150f46ad 100644
--- a/src/web/app/mobile/script.js
+++ b/src/web/app/mobile/script.js
@@ -6,9 +6,8 @@
 import './style.styl';
 
 require('./tags');
-const boot = require('../boot');
-const mixins = require('./mixins');
-const route = require('./router');
+import boot from '../boot';
+import route from './router';
 
 /**
  * Boot
@@ -17,9 +16,6 @@ boot(me => {
 	// http://qiita.com/junya/items/3ff380878f26ca447f85
 	document.body.setAttribute('ontouchstart', '');
 
-	// Register mixins
-	mixins(me);
-
 	// Start routing
 	route(me);
 });
diff --git a/src/web/app/mobile/scripts/open-post-form.js b/src/web/app/mobile/scripts/open-post-form.js
new file mode 100644
index 0000000000..e0fae4d8ca
--- /dev/null
+++ b/src/web/app/mobile/scripts/open-post-form.js
@@ -0,0 +1,15 @@
+import * as riot from 'riot';
+
+export default opts => {
+	const app = document.getElementById('app');
+	app.style.display = 'none';
+
+	function recover() {
+		app.style.display = 'block';
+	}
+
+	const form = riot.mount(document.body.appendChild(document.createElement('mk-post-form')), opts)[0];
+	form
+		.on('cancel', recover)
+		.on('post', recover);
+};
diff --git a/src/web/app/mobile/scripts/stream.js b/src/web/app/mobile/scripts/stream.js
deleted file mode 100644
index e12788f60b..0000000000
--- a/src/web/app/mobile/scripts/stream.js
+++ /dev/null
@@ -1,11 +0,0 @@
-const stream = require('../../common/scripts/stream');
-const riot = require('riot');
-
-module.exports = me => {
-	const s = stream(me);
-	riot.mixin('stream', {
-		stream: s.event,
-		getStreamState: s.getState,
-		streamStateEv: s.stateEv
-	});
-};
diff --git a/src/web/app/mobile/scripts/ui-event.js b/src/web/app/mobile/scripts/ui-event.js
new file mode 100644
index 0000000000..2e406549a4
--- /dev/null
+++ b/src/web/app/mobile/scripts/ui-event.js
@@ -0,0 +1,5 @@
+import * as riot from 'riot';
+
+const ev = riot.observable();
+
+export default ev;
diff --git a/src/web/app/mobile/scripts/ui.js b/src/web/app/mobile/scripts/ui.js
deleted file mode 100644
index 51ab6acd2d..0000000000
--- a/src/web/app/mobile/scripts/ui.js
+++ /dev/null
@@ -1,7 +0,0 @@
-const riot = require('riot');
-
-const ui = riot.observable();
-
-riot.mixin('ui', {
-	ui: ui
-});
diff --git a/src/web/app/mobile/tags/drive.tag b/src/web/app/mobile/tags/drive.tag
index 9dc2fbe4ca..adf1bf01ac 100644
--- a/src/web/app/mobile/tags/drive.tag
+++ b/src/web/app/mobile/tags/drive.tag
@@ -133,9 +133,12 @@
 
 	</style>
 	<script>
+		this.mixin('i');
 		this.mixin('api');
 		this.mixin('stream');
 
+		const stream = this.stream.event;
+
 		this.files = [];
 		this.folders = [];
 		this.hierarchyFolders = [];
@@ -151,10 +154,10 @@
 		this.multiple =this.opts.multiple;
 
 		this.on('mount', () => {
-			this.stream.on('drive_file_created', this.onStreamDriveFileCreated);
-			this.stream.on('drive_file_updated', this.onStreamDriveFileUpdated);
-			this.stream.on('drive_folder_created', this.onStreamDriveFolderCreated);
-			this.stream.on('drive_folder_updated', this.onStreamDriveFolderUpdated);
+			stream.on('drive_file_created', this.onStreamDriveFileCreated);
+			stream.on('drive_file_updated', this.onStreamDriveFileUpdated);
+			stream.on('drive_folder_created', this.onStreamDriveFolderCreated);
+			stream.on('drive_folder_updated', this.onStreamDriveFolderUpdated);
 
 			// Riotのバグでnullを渡しても""になる
 			// https://github.com/riot/riot/issues/2080
@@ -170,10 +173,10 @@
 		});
 
 		this.on('unmount', () => {
-			this.stream.off('drive_file_created', this.onStreamDriveFileCreated);
-			this.stream.off('drive_file_updated', this.onStreamDriveFileUpdated);
-			this.stream.off('drive_folder_created', this.onStreamDriveFolderCreated);
-			this.stream.off('drive_folder_updated', this.onStreamDriveFolderUpdated);
+			stream.off('drive_file_created', this.onStreamDriveFileCreated);
+			stream.off('drive_file_updated', this.onStreamDriveFileUpdated);
+			stream.off('drive_folder_created', this.onStreamDriveFolderCreated);
+			stream.off('drive_folder_updated', this.onStreamDriveFolderUpdated);
 		});
 
 		this.onStreamDriveFileCreated = file => {
diff --git a/src/web/app/mobile/tags/drive/file-viewer.tag b/src/web/app/mobile/tags/drive/file-viewer.tag
index ac3a120b4d..2d5c722552 100644
--- a/src/web/app/mobile/tags/drive/file-viewer.tag
+++ b/src/web/app/mobile/tags/drive/file-viewer.tag
@@ -11,9 +11,9 @@
 			</span>
 			<span class="separator"></span>
 			<span class="aspect-ratio">
-				<span class="width">{ file.properties.width / getGcd(file.properties.width, file.properties.height) }</span>
+				<span class="width">{ file.properties.width / gcd(file.properties.width, file.properties.height) }</span>
 				<span class="colon">:</span>
-				<span class="height">{ file.properties.height / getGcd(file.properties.width, file.properties.height) }</span>
+				<span class="height">{ file.properties.height / gcd(file.properties.width, file.properties.height) }</span>
 			</span>
 		</footer>
 	</div>
@@ -184,8 +184,11 @@
 
 	</style>
 	<script>
-		this.bytesToSize = require('../../../common/scripts/bytes-to-size.js');
-		this.getGcd = require('../../../common/scripts/gcd.js');
+		import bytesToSize from '../../../common/scripts/bytes-to-size';
+		import gcd from '../../../common/scripts/gcd';
+
+		this.bytesToSize = bytesToSize;
+		this.gcd = gcd;
 
 		this.mixin('api');
 
diff --git a/src/web/app/mobile/tags/drive/file.tag b/src/web/app/mobile/tags/drive/file.tag
index a6b81f19c6..618ed911cf 100644
--- a/src/web/app/mobile/tags/drive/file.tag
+++ b/src/web/app/mobile/tags/drive/file.tag
@@ -122,7 +122,8 @@
 
 	</style>
 	<script>
-		this.bytesToSize = require('../../../common/scripts/bytes-to-size');
+		import bytesToSize from '../../../common/scripts/bytes-to-size';
+		this.bytesToSize = bytesToSize;
 
 		this.browser = this.parent;
 		this.file = this.opts.file;
diff --git a/src/web/app/mobile/tags/follow-button.tag b/src/web/app/mobile/tags/follow-button.tag
index ae6d19f593..cae466150d 100644
--- a/src/web/app/mobile/tags/follow-button.tag
+++ b/src/web/app/mobile/tags/follow-button.tag
@@ -48,12 +48,16 @@
 
 	</style>
 	<script>
+		this.mixin('i');
 		this.mixin('api');
-		this.mixin('is-promise');
 		this.mixin('stream');
 
+		const stream = this.stream.event;
+
+		const isPromise = require('../../common/scripts/is-promise');
+
 		this.user = null;
-		this.userPromise = this.isPromise(this.opts.user)
+		this.userPromise = isPromise(this.opts.user)
 			? this.opts.user
 			: Promise.resolve(this.opts.user);
 		this.init = true;
@@ -65,14 +69,14 @@
 					init: false,
 					user: user
 				});
-				this.stream.on('follow', this.onStreamFollow);
-				this.stream.on('unfollow', this.onStreamUnfollow);
+				stream.on('follow', this.onStreamFollow);
+				stream.on('unfollow', this.onStreamUnfollow);
 			});
 		});
 
 		this.on('unmount', () => {
-			this.stream.off('follow', this.onStreamFollow);
-			this.stream.off('unfollow', this.onStreamUnfollow);
+			stream.off('follow', this.onStreamFollow);
+			stream.off('unfollow', this.onStreamUnfollow);
 		});
 
 		this.onStreamFollow = user => {
diff --git a/src/web/app/mobile/tags/home-timeline.tag b/src/web/app/mobile/tags/home-timeline.tag
index 9a8ab9b743..7c929fc5ad 100644
--- a/src/web/app/mobile/tags/home-timeline.tag
+++ b/src/web/app/mobile/tags/home-timeline.tag
@@ -5,9 +5,12 @@
 			display block
 	</style>
 	<script>
+		this.mixin('i');
 		this.mixin('api');
 		this.mixin('stream');
 
+		const stream = this.stream.event;
+
 		this.init = new Promise((res, rej) => {
 			this.api('posts/timeline').then(posts => {
 				res(posts);
@@ -16,15 +19,15 @@
 		});
 
 		this.on('mount', () => {
-			this.stream.on('post', this.onStreamPost);
-			this.stream.on('follow', this.onStreamFollow);
-			this.stream.on('unfollow', this.onStreamUnfollow);
+			stream.on('post', this.onStreamPost);
+			stream.on('follow', this.onStreamFollow);
+			stream.on('unfollow', this.onStreamUnfollow);
 		});
 
 		this.on('unmount', () => {
-			this.stream.off('post', this.onStreamPost);
-			this.stream.off('follow', this.onStreamFollow);
-			this.stream.off('unfollow', this.onStreamUnfollow);
+			stream.off('post', this.onStreamPost);
+			stream.off('follow', this.onStreamFollow);
+			stream.off('unfollow', this.onStreamUnfollow);
 		});
 
 		this.more = () => {
diff --git a/src/web/app/mobile/tags/notification-preview.tag b/src/web/app/mobile/tags/notification-preview.tag
index b93b92d919..e20307ebc5 100644
--- a/src/web/app/mobile/tags/notification-preview.tag
+++ b/src/web/app/mobile/tags/notification-preview.tag
@@ -107,7 +107,8 @@
 
 	</style>
 	<script>
-		this.mixin('get-post-summary');
+		import getPostSummary from '../../common/scripts/get-post-summary';
+		this.getPostSummary = getPostSummary;
 		this.notification = this.opts.notification;
 	</script>
 </mk-notification-preview>
diff --git a/src/web/app/mobile/tags/notification.tag b/src/web/app/mobile/tags/notification.tag
index d32e6b40ae..591638858d 100644
--- a/src/web/app/mobile/tags/notification.tag
+++ b/src/web/app/mobile/tags/notification.tag
@@ -167,7 +167,8 @@
 
 	</style>
 	<script>
-		this.mixin('get-post-summary');
+		import getPostSummary from '../../common/scripts/get-post-summary';
+		this.getPostSummary = getPostSummary;
 		this.notification = this.opts.notification;
 	</script>
 </mk-notification>
diff --git a/src/web/app/mobile/tags/notifications.tag b/src/web/app/mobile/tags/notifications.tag
index 039a9a4b07..4f49edd5ac 100644
--- a/src/web/app/mobile/tags/notifications.tag
+++ b/src/web/app/mobile/tags/notifications.tag
@@ -57,9 +57,13 @@
 
 	</style>
 	<script>
+		import getPostSummary from '../../common/scripts/get-post-summary';
+		this.getPostSummary = getPostSummary;
+
 		this.mixin('api');
 		this.mixin('stream');
-		this.mixin('get-post-summary');
+
+		const stream = this.stream.event;
 
 		this.notifications = [];
 		this.loading = true;
@@ -74,11 +78,11 @@
 				this.trigger('fetched');
 			});
 
-			this.stream.on('notification', this.onNotification);
+			stream.on('notification', this.onNotification);
 		});
 
 		this.on('unmount', () => {
-			this.stream.off('notification', this.onNotification);
+			stream.off('notification', this.onNotification);
 		});
 
 		this.onNotification = notification => {
diff --git a/src/web/app/mobile/tags/page/drive.tag b/src/web/app/mobile/tags/page/drive.tag
index bacfa25826..1042c061da 100644
--- a/src/web/app/mobile/tags/page/drive.tag
+++ b/src/web/app/mobile/tags/page/drive.tag
@@ -7,27 +7,27 @@
 			display block
 	</style>
 	<script>
-		this.mixin('ui');
-		this.mixin('ui-progress');
+		import ui from '../../scripts/ui-event';
+		import Progress from '../../../common/scripts/loading';
 
 		this.on('mount', () => {
 			document.title = 'Misskey Drive';
-			this.ui.trigger('title', '<i class="fa fa-cloud"></i>ドライブ');
+			ui.trigger('title', '<i class="fa fa-cloud"></i>ドライブ');
 
-			this.ui.trigger('func', () => {
+			ui.trigger('func', () => {
 				this.refs.ui.refs.browser.openContextMenu();
 			}, 'ellipsis-h');
 
 			this.refs.ui.refs.browser.on('begin-fetch', () => {
-				this.Progress.start();
+				Progress.start();
 			});
 
 			this.refs.ui.refs.browser.on('fetched-mid', () => {
-				this.Progress.set(0.5);
+				Progress.set(0.5);
 			});
 
 			this.refs.ui.refs.browser.on('fetched', () => {
-				this.Progress.done();
+				Progress.done();
 			});
 
 			this.refs.ui.refs.browser.on('move-root', () => {
@@ -37,7 +37,7 @@
 				history.pushState(null, title, '/i/drive');
 
 				document.title = title;
-				this.ui.trigger('title', '<i class="fa fa-cloud"></i>ドライブ');
+				ui.trigger('title', '<i class="fa fa-cloud"></i>ドライブ');
 			});
 
 			this.refs.ui.refs.browser.on('open-folder', (folder, silent) => {
@@ -50,7 +50,7 @@
 
 				document.title = title;
 				// TODO: escape html characters in folder.name
-				this.ui.trigger('title', '<i class="fa fa-folder-open"></i>' + folder.name);
+				ui.trigger('title', '<i class="fa fa-folder-open"></i>' + folder.name);
 			});
 
 			this.refs.ui.refs.browser.on('open-file', (file, silent) => {
@@ -63,7 +63,7 @@
 
 				document.title = title;
 				// TODO: escape html characters in file.name
-				this.ui.trigger('title', '<mk-file-type-icon class="icon"></mk-file-type-icon>' + file.name);
+				ui.trigger('title', '<mk-file-type-icon class="icon"></mk-file-type-icon>' + file.name);
 				riot.mount('mk-file-type-icon', {
 					type: file.type
 				});
diff --git a/src/web/app/mobile/tags/page/home.tag b/src/web/app/mobile/tags/page/home.tag
index 68178aa991..561e0be5f7 100644
--- a/src/web/app/mobile/tags/page/home.tag
+++ b/src/web/app/mobile/tags/page/home.tag
@@ -7,42 +7,45 @@
 			display block
 	</style>
 	<script>
+		import ui from '../../scripts/ui-event';
+		import Progress from '../../../common/scripts/loading';
+		import getPostSummary from '../../../common/scripts/get-post-summary';
+		import openPostForm from '../../scripts/open-post-form';
+
 		this.mixin('i');
-		this.mixin('ui');
-		this.mixin('ui-progress');
 		this.mixin('stream');
-		this.mixin('get-post-summary');
-		this.mixin('open-post-form');
+
+		const stream = this.stream.event;
 
 		this.unreadCount = 0;
 
 		this.on('mount', () => {
 			document.title = 'Misskey'
-			this.ui.trigger('title', '<i class="fa fa-home"></i>ホーム');
+			ui.trigger('title', '<i class="fa fa-home"></i>ホーム');
 
-			this.ui.trigger('func', () => {
-				this.openPostForm();
+			ui.trigger('func', () => {
+				openPostForm();
 			}, 'pencil');
 
-			this.Progress.start();
+			Progress.start();
 
-			this.stream.on('post', this.onStreamPost);
+			stream.on('post', this.onStreamPost);
 			document.addEventListener('visibilitychange', this.onVisibilitychange, false);
 
 			this.refs.ui.refs.home.on('loaded', () => {
-				this.Progress.done();
+				Progress.done();
 			});
 		});
 
 		this.on('unmount', () => {
-			this.stream.off('post', this.onStreamPost);
+			stream.off('post', this.onStreamPost);
 			document.removeEventListener('visibilitychange', this.onVisibilitychange);
 		});
 
 		this.onStreamPost = post => {
 			if (document.hidden && post.user_id !== this.I.id) {
 				this.unreadCount++;
-				document.title = `(${this.unreadCount}) ${this.getPostSummary(post)}`;
+				document.title = `(${this.unreadCount}) ${getPostSummary(post)}`;
 			}
 		};
 
diff --git a/src/web/app/mobile/tags/page/messaging-room.tag b/src/web/app/mobile/tags/page/messaging-room.tag
index 8ba5bd2c2a..fbfa9c51d6 100644
--- a/src/web/app/mobile/tags/page/messaging-room.tag
+++ b/src/web/app/mobile/tags/page/messaging-room.tag
@@ -7,8 +7,9 @@
 			display block
 	</style>
 	<script>
+		import ui from '../../scripts/ui-event';
+
 		this.mixin('api');
-		this.mixin('ui');
 
 		this.fetching = true;
 
@@ -23,7 +24,7 @@
 
 				document.title = `メッセージ: ${user.name} | Misskey`;
 				// TODO: ユーザー名をエスケープ
-				this.ui.trigger('title', '<i class="fa fa-comments-o"></i>' + user.name);
+				ui.trigger('title', '<i class="fa fa-comments-o"></i>' + user.name);
 			});
 		});
 	</script>
diff --git a/src/web/app/mobile/tags/page/messaging.tag b/src/web/app/mobile/tags/page/messaging.tag
index fd94cbfa4a..30b2b39891 100644
--- a/src/web/app/mobile/tags/page/messaging.tag
+++ b/src/web/app/mobile/tags/page/messaging.tag
@@ -7,12 +7,13 @@
 			display block
 	</style>
 	<script>
-		this.mixin('ui');
+		import ui from '../../scripts/ui-event';
+
 		this.mixin('page');
 
 		this.on('mount', () => {
 			document.title = 'Misskey | メッセージ';
-			this.ui.trigger('title', '<i class="fa fa-comments-o"></i>メッセージ');
+			ui.trigger('title', '<i class="fa fa-comments-o"></i>メッセージ');
 
 			this.refs.ui.refs.index.on('navigate-user', user => {
 				this.page('/i/messaging/' + user.username);
diff --git a/src/web/app/mobile/tags/page/notifications.tag b/src/web/app/mobile/tags/page/notifications.tag
index ae19303e5f..90baf82e9b 100644
--- a/src/web/app/mobile/tags/page/notifications.tag
+++ b/src/web/app/mobile/tags/page/notifications.tag
@@ -5,20 +5,19 @@
 	<style>
 		:scope
 			display block
-
 	</style>
 	<script>
-		this.mixin('ui');
-		this.mixin('ui-progress');
+		import ui from '../../scripts/ui-event';
+		import Progress from '../../../common/scripts/loading';
 
 		this.on('mount', () => {
 			document.title = 'Misskey | 通知';
-			this.ui.trigger('title', '<i class="fa fa-bell-o"></i>通知');
+			ui.trigger('title', '<i class="fa fa-bell-o"></i>通知');
 
-			this.Progress.start();
+			Progress.start();
 
 			this.refs.ui.refs.notifications.on('fetched', () => {
-				this.Progress.done();
+				Progress.done();
 			});
 		});
 	</script>
diff --git a/src/web/app/mobile/tags/page/post.tag b/src/web/app/mobile/tags/page/post.tag
index 2c4eba37a0..dae9bf84be 100644
--- a/src/web/app/mobile/tags/page/post.tag
+++ b/src/web/app/mobile/tags/page/post.tag
@@ -18,23 +18,23 @@
 
 	</style>
 	<script>
-		this.mixin('ui');
-		this.mixin('ui-progress');
+		import ui from '../../scripts/ui-event';
+		import Progress from '../../../common/scripts/loading';
 
 		this.post = this.opts.post;
 
 		this.on('mount', () => {
 			document.title = 'Misskey';
-			this.ui.trigger('title', '<i class="fa fa-sticky-note-o"></i>投稿');
+			ui.trigger('title', '<i class="fa fa-sticky-note-o"></i>投稿');
 
-			this.Progress.start();
+			Progress.start();
 
 			this.refs.ui.refs.post.on('post-fetched', () => {
-				this.Progress.set(0.5);
+				Progress.set(0.5);
 			});
 
 			this.refs.ui.refs.post.on('loaded', () => {
-				this.Progress.done();
+				Progress.done();
 			});
 		});
 	</script>
diff --git a/src/web/app/mobile/tags/page/search.tag b/src/web/app/mobile/tags/page/search.tag
index fbf741f2e6..b5be30ae82 100644
--- a/src/web/app/mobile/tags/page/search.tag
+++ b/src/web/app/mobile/tags/page/search.tag
@@ -7,18 +7,18 @@
 			display block
 	</style>
 	<script>
-		this.mixin('ui');
-		this.mixin('ui-progress');
+		import ui from '../../scripts/ui-event';
+		import Progress from '../../../common/scripts/loading';
 
 		this.on('mount', () => {
 			document.title = `検索: ${this.opts.query} | Misskey`
 			// TODO: クエリをHTMLエスケープ
-			this.ui.trigger('title', '<i class="fa fa-search"></i>' + this.opts.query);
+			ui.trigger('title', '<i class="fa fa-search"></i>' + this.opts.query);
 
-			this.Progress.start();
+			Progress.start();
 
 			this.refs.ui.refs.search.on('loaded', () => {
-				this.Progress.done();
+				Progress.done();
 			});
 		});
 	</script>
diff --git a/src/web/app/mobile/tags/page/settings.tag b/src/web/app/mobile/tags/page/settings.tag
index 59974e8cb6..0185260ca1 100644
--- a/src/web/app/mobile/tags/page/settings.tag
+++ b/src/web/app/mobile/tags/page/settings.tag
@@ -13,11 +13,11 @@
 			display block
 	</style>
 	<script>
-		this.mixin('ui');
+		import ui from '../../scripts/ui-event';
 
 		this.on('mount', () => {
 			document.title = 'Misskey | 設定';
-			this.ui.trigger('title', '<i class="fa fa-cog"></i>設定');
+			ui.trigger('title', '<i class="fa fa-cog"></i>設定');
 		});
 	</script>
 </mk-settings-page>
diff --git a/src/web/app/mobile/tags/page/settings/api.tag b/src/web/app/mobile/tags/page/settings/api.tag
index e4f954f51a..5725bcb4c1 100644
--- a/src/web/app/mobile/tags/page/settings/api.tag
+++ b/src/web/app/mobile/tags/page/settings/api.tag
@@ -7,11 +7,11 @@
 			display block
 	</style>
 	<script>
-		this.mixin('ui');
+		const ui = require('../../../scripts/ui-event');
 
 		this.on('mount', () => {
 			document.title = 'Misskey | API';
-			this.ui.trigger('title', '<i class="fa fa-key"></i>API');
+			ui.trigger('title', '<i class="fa fa-key"></i>API');
 		});
 	</script>
 </mk-api-info-page>
diff --git a/src/web/app/mobile/tags/page/settings/authorized-apps.tag b/src/web/app/mobile/tags/page/settings/authorized-apps.tag
index 84904c91e5..8fc62214ad 100644
--- a/src/web/app/mobile/tags/page/settings/authorized-apps.tag
+++ b/src/web/app/mobile/tags/page/settings/authorized-apps.tag
@@ -7,11 +7,11 @@
 			display block
 	</style>
 	<script>
-		this.mixin('ui');
+		const ui = require('../../../scripts/ui-event');
 
 		this.on('mount', () => {
 			document.title = 'Misskey | アプリケーション';
-			this.ui.trigger('title', '<i class="fa fa-puzzle-piece"></i>アプリケーション');
+			ui.trigger('title', '<i class="fa fa-puzzle-piece"></i>アプリケーション');
 		});
 	</script>
 </mk-authorized-apps-page>
diff --git a/src/web/app/mobile/tags/page/settings/signin.tag b/src/web/app/mobile/tags/page/settings/signin.tag
index 874cdf4856..7ada9717c3 100644
--- a/src/web/app/mobile/tags/page/settings/signin.tag
+++ b/src/web/app/mobile/tags/page/settings/signin.tag
@@ -7,11 +7,11 @@
 			display block
 	</style>
 	<script>
-		this.mixin('ui');
+		const ui = require('../../../scripts/ui-event');
 
 		this.on('mount', () => {
 			document.title = 'Misskey | ログイン履歴';
-			this.ui.trigger('title', '<i class="fa fa-sign-in"></i>ログイン履歴');
+			ui.trigger('title', '<i class="fa fa-sign-in"></i>ログイン履歴');
 		});
 	</script>
 </mk-signin-history-page>
diff --git a/src/web/app/mobile/tags/page/settings/twitter.tag b/src/web/app/mobile/tags/page/settings/twitter.tag
index 2026ab7daf..5a3be10f5c 100644
--- a/src/web/app/mobile/tags/page/settings/twitter.tag
+++ b/src/web/app/mobile/tags/page/settings/twitter.tag
@@ -7,11 +7,11 @@
 			display block
 	</style>
 	<script>
-		this.mixin('ui');
+		const ui = require('../../../scripts/ui-event');
 
 		this.on('mount', () => {
 			document.title = 'Misskey | Twitter連携';
-			this.ui.trigger('title', '<i class="fa fa-twitter"></i>Twitter連携');
+			ui.trigger('title', '<i class="fa fa-twitter"></i>Twitter連携');
 		});
 	</script>
 </mk-twitter-setting-page>
diff --git a/src/web/app/mobile/tags/page/user-followers.tag b/src/web/app/mobile/tags/page/user-followers.tag
index 249897be2c..5ecf58fd61 100644
--- a/src/web/app/mobile/tags/page/user-followers.tag
+++ b/src/web/app/mobile/tags/page/user-followers.tag
@@ -7,15 +7,16 @@
 			display block
 	</style>
 	<script>
-		this.mixin('ui');
-		this.mixin('ui-progress');
+		import ui from '../../scripts/ui-event';
+		import Progress from '../../../common/scripts/loading';
+
 		this.mixin('api');
 
 		this.fetching = true;
 		this.user = null;
 
 		this.on('mount', () => {
-			this.Progress.start();
+			Progress.start();
 
 			this.api('users/show', {
 				username: this.opts.user
@@ -27,10 +28,10 @@
 
 				document.title = user.name + 'のフォロワー | Misskey';
 				// TODO: ユーザー名をエスケープ
-				this.ui.trigger('title', '<img src="' + user.avatar_url + '?thumbnail&size=64">' + user.name + 'のフォロワー');
+				ui.trigger('title', '<img src="' + user.avatar_url + '?thumbnail&size=64">' + user.name + 'のフォロワー');
 
 				this.refs.ui.refs.list.on('loaded', () => {
-					this.Progress.done();
+					Progress.done();
 				});
 			});
 		});
diff --git a/src/web/app/mobile/tags/page/user-following.tag b/src/web/app/mobile/tags/page/user-following.tag
index a682715a02..27d7961da2 100644
--- a/src/web/app/mobile/tags/page/user-following.tag
+++ b/src/web/app/mobile/tags/page/user-following.tag
@@ -7,15 +7,16 @@
 			display block
 	</style>
 	<script>
-		this.mixin('ui');
-		this.mixin('ui-progress');
+		import ui from '../../scripts/ui-event';
+		import Progress from '../../../common/scripts/loading';
+
 		this.mixin('api');
 
 		this.fetching = true;
 		this.user = null;
 
 		this.on('mount', () => {
-			this.Progress.start();
+			Progress.start();
 
 			this.api('users/show', {
 				username: this.opts.user
@@ -27,10 +28,10 @@
 
 				document.title = user.name + 'のフォロー | Misskey';
 				// TODO: ユーザー名をエスケープ
-				this.ui.trigger('title', '<img src="' + user.avatar_url + '?thumbnail&size=64">' + user.name + 'のフォロー');
+				ui.trigger('title', '<img src="' + user.avatar_url + '?thumbnail&size=64">' + user.name + 'のフォロー');
 
 				this.refs.ui.refs.list.on('loaded', () => {
-					this.Progress.done();
+					Progress.done();
 				});
 			});
 		});
diff --git a/src/web/app/mobile/tags/page/user.tag b/src/web/app/mobile/tags/page/user.tag
index 2a0e48426f..3826beecff 100644
--- a/src/web/app/mobile/tags/page/user.tag
+++ b/src/web/app/mobile/tags/page/user.tag
@@ -5,22 +5,21 @@
 	<style>
 		:scope
 			display block
-
 	</style>
 	<script>
-		this.mixin('ui');
-		this.mixin('ui-progress');
+		import ui from '../../scripts/ui-event';
+		import Progress from '../../../common/scripts/loading';
 
 		this.user = this.opts.user;
 
 		this.on('mount', () => {
-			this.Progress.start();
+			Progress.start();
 
 			this.refs.ui.refs.user.on('loaded', user => {
-				this.Progress.done();
+				Progress.done();
 				document.title = user.name + ' | Misskey';
 				// TODO: ユーザー名をエスケープ
-				this.ui.trigger('title', '<i class="fa fa-user"></i>' + user.name);
+				ui.trigger('title', '<i class="fa fa-user"></i>' + user.name);
 			});
 		});
 	</script>
diff --git a/src/web/app/mobile/tags/post-detail.tag b/src/web/app/mobile/tags/post-detail.tag
index f6f6bb62db..107fca5938 100644
--- a/src/web/app/mobile/tags/post-detail.tag
+++ b/src/web/app/mobile/tags/post-detail.tag
@@ -342,9 +342,11 @@
 	</style>
 	<script>
 		this.mixin('api');
-		this.mixin('text');
-		this.mixin('get-post-summary');
-		this.mixin('open-post-form');
+
+		import compile from '../../common/scripts/text-compiler';
+
+		this.getPostSummary = require('../../common/scripts/get-post-summary');
+		this.openPostForm = require('../scripts/open-post-form');
 
 		this.fetching = true;
 		this.loadingContext = false;
@@ -368,9 +370,9 @@
 				this.trigger('loaded');
 
 				if (this.p.text) {
-					const tokens = this.analyze(this.p.text);
+					const tokens = this.p.ast;
 
-					this.refs.text.innerHTML = this.compile(tokens);
+					this.refs.text.innerHTML = compile(tokens);
 
 					this.refs.text.children.forEach(e => {
 						if (e.tagName == 'MK-URL') riot.mount(e);
diff --git a/src/web/app/mobile/tags/post-form.tag b/src/web/app/mobile/tags/post-form.tag
index a3208efe37..ae3f444c15 100644
--- a/src/web/app/mobile/tags/post-form.tag
+++ b/src/web/app/mobile/tags/post-form.tag
@@ -182,7 +182,7 @@
 
 	</style>
 	<script>
-		const getCat = require('../../common/scripts/get-cat');
+		import getCat from '../../common/scripts/get-cat';
 
 		this.mixin('api');
 
diff --git a/src/web/app/mobile/tags/sub-post-content.tag b/src/web/app/mobile/tags/sub-post-content.tag
index 5ff01c5020..beb524061b 100644
--- a/src/web/app/mobile/tags/sub-post-content.tag
+++ b/src/web/app/mobile/tags/sub-post-content.tag
@@ -28,14 +28,14 @@
 
 	</style>
 	<script>
-		this.mixin('text');
+		import compile from '../../common/scripts/text-compiler';
 
 		this.post = this.opts.post;
 
 		this.on('mount', () => {
 			if (this.post.text) {
-				const tokens = this.analyze(this.post.text);
-				this.refs.text.innerHTML = this.compile(tokens, false);
+				const tokens = this.post.ast;
+				this.refs.text.innerHTML = compile(tokens, false);
 
 				this.refs.text.children.forEach(e => {
 					if (e.tagName == 'MK-URL') riot.mount(e);
diff --git a/src/web/app/mobile/tags/timeline-post.tag b/src/web/app/mobile/tags/timeline-post.tag
index f706dc7de6..c861130b66 100644
--- a/src/web/app/mobile/tags/timeline-post.tag
+++ b/src/web/app/mobile/tags/timeline-post.tag
@@ -306,21 +306,22 @@
 	</style>
 	<script>
 		this.mixin('api');
-		this.mixin('text');
-		this.mixin('get-post-summary');
-		this.mixin('open-post-form');
+
+		import compile from '../../common/scripts/text-compiler';
+		import getPostSummary from '../../common/scripts/get-post-summary';
+		import openPostForm from '../scripts/open-post-form';
 
 		this.post = this.opts.post;
 		this.isRepost = this.post.repost != null && this.post.text == null;
 		this.p = this.isRepost ? this.post.repost : this.post;
-		this.summary = this.getPostSummary(this.p);
+		this.summary = getPostSummary(this.p);
 		this.url = `/${this.p.user.username}/${this.p.id}`;
 
 		this.on('mount', () => {
 			if (this.p.text) {
-				const tokens = this.analyze(this.p.text);
+				const tokens = this.p.ast;
 
-				this.refs.text.innerHTML = this.refs.text.innerHTML.replace('<p class="dummy"></p>', this.compile(tokens));
+				this.refs.text.innerHTML = this.refs.text.innerHTML.replace('<p class="dummy"></p>', compile(tokens));
 
 				this.refs.text.children.forEach(e => {
 					if (e.tagName == 'MK-URL') riot.mount(e);
@@ -338,7 +339,7 @@
 		});
 
 		this.reply = () => {
-			this.openPostForm({
+			openPostForm({
 				reply: this.p
 			});
 		};
diff --git a/src/web/app/mobile/tags/ui-header.tag b/src/web/app/mobile/tags/ui-header.tag
index 265f12fd45..7d822cf6c8 100644
--- a/src/web/app/mobile/tags/ui-header.tag
+++ b/src/web/app/mobile/tags/ui-header.tag
@@ -88,14 +88,14 @@
 
 	</style>
 	<script>
-		this.mixin('ui');
+		import ui from '../scripts/ui-event';
 
 		this.func = null;
 		this.funcIcon = null;
 
 		this.on('unmount', () => {
-			this.ui.off('title', this.setTitle);
-			this.ui.off('func', this.setFunc);
+			ui.off('title', this.setTitle);
+			ui.off('func', this.setFunc);
 		});
 
 		this.setTitle = title => {
@@ -109,7 +109,7 @@
 			});
 		};
 
-		this.ui.on('title', this.setTitle);
-		this.ui.on('func', this.setFunc);
+		ui.on('title', this.setTitle);
+		ui.on('func', this.setFunc);
 	</script>
 </mk-ui-header>
diff --git a/src/web/app/mobile/tags/ui.tag b/src/web/app/mobile/tags/ui.tag
index 3e76733324..4c0e825a71 100644
--- a/src/web/app/mobile/tags/ui.tag
+++ b/src/web/app/mobile/tags/ui.tag
@@ -11,16 +11,20 @@
 			padding-top 48px
 	</style>
 	<script>
+		this.mixin('i');
+
 		this.mixin('stream');
 
+		const stream = this.stream.event;
+
 		this.isDrawerOpening = false;
 
 		this.on('mount', () => {
-			this.stream.on('notification', this.onStreamNotification);
+			stream.on('notification', this.onStreamNotification);
 		});
 
 		this.on('unmount', () => {
-			this.stream.off('notification', this.onStreamNotification);
+			stream.off('notification', this.onStreamNotification);
 		});
 
 		this.toggleDrawer = () => {
diff --git a/test/text.js b/test/text.js
index e4c72560f0..49e2f02b5d 100644
--- a/test/text.js
+++ b/test/text.js
@@ -4,8 +4,8 @@
 
 const assert = require('assert');
 
-const analyze = require('../src/common/text');
-const syntaxhighlighter = require('../src/common/text/core/syntax-highlighter');
+const analyze = require('../built/api/common/text').default;
+const syntaxhighlighter = require('../built/api/common/text/core/syntax-highlighter').default;
 
 describe('Text', () => {
 	it('is correctly analyzed', () => {
@@ -90,6 +90,14 @@ describe('Text', () => {
 	});
 
 	describe('syntax highlighting', () => {
+		it('comment', () => {
+			const html1 = syntaxhighlighter('// Strawberry pasta');
+			assert.equal(html1, '<span class="comment">// Strawberry pasta</span>');
+
+			const html2 = syntaxhighlighter('x // x\ny // y');
+			assert.equal(html2, 'x <span class="comment">// x\n</span>y <span class="comment">// y</span>');
+		});
+
 		it('regexp', () => {
 			const html = syntaxhighlighter('/.*/');
 			assert.equal(html, '<span class="regexp">/.*/</span>');
diff --git a/webpack.config.ts b/webpack.config.ts
index 65aeda5ea4..cfb985c989 100644
--- a/webpack.config.ts
+++ b/webpack.config.ts
@@ -63,9 +63,7 @@ module.exports = new Promise(async resolve => {
 		plugins: [
 			new webpack.DefinePlugin({
 				VERSION: JSON.stringify(version),
-				CONFIG: {
-					themeColor: JSON.stringify(constants.themeColor)
-				}
+				THEME_COLOR: JSON.stringify(constants.themeColor)
 			}),
 			new StringReplacePlugin()
 		],
-- 
GitLab