把博客迁移到了 Hexo

其实一直都想把博客迁移到全静态的引擎,正好最近有时间,就把原本的用的主题移植了一份暗色版的,然后全部转移到用 Hexo。

我的这份主题也公开在 Github 上了,不过手机端的 UI 我还要再修一下,而且还有很多需要精简掉的部分。目标是做到页面秒开,无附加外部 JS 和 CSS。

切过来之后就可以直接写 Markdown 了,虽然我之前也用了 Wordpress 的 Markdown 插件,不过用 Hexo 之后更直接一些。

不得不说,纯静态是真的快,还没开始优化,Google 的评分就已经 99 分了。

果然不引入外部 JS 的话首屏展示时间特别快。不过由于使用 Disqus 的评论,网页加载的大部分时间都耗费在 Disqus的 JS 上面了,这个后续还要看看怎么能优化一下。

附:

最近在听的 Myoya 的音乐,也给大家推荐一下:https://www.youtube.com/watch?v=0eD4SV9rtoY

理解 Webpack 的工作原理

Webpack 可以把模块打包到一个文件里面去的,但具体它是怎么实现的呢?

让我们一起来通过 Webpack 输出的代码和 Chrome DevTools 来理解 Webpack 实现的原理吧。

准备代码

关于 Webpack 的基础使用,请看我的另一篇博客文章

下面我们准备一个简单的程序。这里我们使用 CommonJS 的语法,ES6 的我们之后再看。

/src/index.js 代码

const hello = require("./component/hello");

hello()

/src/component/hello.js 代码

function hello() {
    console.log('hello');
}

module.exports = hello;

webpack.config.js 需要配置模式和 Source Map 格式,这样生成出来的代码最容易方便看懂。

module.exports = {
    mode: 'development',
    devtool: 'inline-source-map',
    /* 省略 */
};

运行 npx webpack 打包,然后查看 /dist/main.js 的内容。

分析 Webpack 生成的代码

/******/ (function(modules) { // webpackBootstrap
/******/     // The module cache
/******/     var installedModules = {};
/******/

/* 省略 */
/******/ ({

/***/ "./src/component/hello.js":
/*!********************************!*\
  !*** ./src/component/hello.js ***!
  \********************************/
/*! no static exports found */
/***/ (function(module, exports) {


function hello() {
    console.log('hello');
}

module.exports = hello;

/***/ }),
/* 省略 */

生成出来的代码,上面一部分是 Webpack 的代码,下面就是我们写的模块了。

这么一大串,其实它就是一个立即函数(IIFE)

立即函数相关的资料可以看上面的文档,它最主要的好处就是内部的变量不会污染到全局环境。

Webpack 生成的代码其实就可以简化为:

(function(modules) {
    /* 初始化 */
    /* 定义一个 __webpack_require__ 函数 */
    function __webpack_require__(moduleId) {
        /* 省略 */
    }

    return __webpack_require__("./src/index.js");
})(
    {
        "./src/index.js": {
            /* index.js */
        },
        "./src/component/hello.js": {
            /* hello.js */
        }
    }
)

它传入了一个模块的 Object,其中 Key 是文件名, Value 就是代码部分。

我们把它的代码从上面一步一步看。

/******/ (function(modules) { // webpackBootstrap
/******/     // The module cache
/******/     var installedModules = {};
/******/

这个 installedModules ,根据注释上说的,这个是模块的缓存?继续往下看。

/******/     // The require function
/******/     function __webpack_require__(moduleId) {
/******/
/******/         // Check if module is in cache
/******/         if(installedModules[moduleId]) {
/******/             return installedModules[moduleId].exports;
/******/         }
/******/         // Create a new module (and put it into the cache)
/******/         var module = installedModules[moduleId] = {
/******/             i: moduleId,
/******/             l: false,
/******/             exports: {}
/******/         };
/******/
/******/         // Execute the module function
/******/         modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/
/******/         // Flag the module as loaded
/******/         module.l = true;
/******/
/******/         // Return the exports of the module
/******/         return module.exports;
/******/     }

这里定义了 require 的函数。

/******/         // Check if module is in cache
/******/         if(installedModules[moduleId]) {
/******/             return installedModules[moduleId].exports;
/******/         }

它首先检查了 installedModules 里面有没有这个模块,有就直接返回

/******/         // Create a new module (and put it into the cache)
/******/         var module = installedModules[moduleId] = {
/******/             i: moduleId,
/******/             l: false,
/******/             exports: {}
/******/         };

这里创建一个新的模块,然后同时放到 installedModules 里面。

其中三个属性:

  • i 应该是 ID,是传进来的 moduleId。
  • l 是一个 flag,指示这个模块是否已经加载,现在设为了 false。
  • exports 目前是空的,继续往下看。
/******/         // Execute the module function
/******/         modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);

这里调用了 Function.prototype.call ,第一个参数是执行的环境(即 this ),后面的都是传入的参数。

继续往后看。

/******/         // Flag the module as loaded
/******/         module.l = true;
/******/
/******/         // Return the exports of the module
/******/         return module.exports;
/******/     }

之后 loaded flag 设置为了 true,然后返回了 module.exports 。为什么要返回 exports 呢?继续往后看。

后面一部分都是扩展 __webpack_require__ 方法,暂时省略。

/******/     // Load entry module and return exports
/******/     return __webpack_require__(__webpack_require__.s = "./src/index.js");
/******/ })

这里返回了入口模块的路径。也就是把 "./src/index.js" 当 ID 调用了一下 __webpack_require__ 方法。

看了一轮之后,好像也没能理解到底做了什么,实际运行一下看看吧。

分析 Webpack 的运行过程

把它嵌入一个网页上运行一下,可以使用 live-server

webpack-dev-server 会插入一些其他的东西在 main.js 里,会干扰我们看代码。

把脚本放在一个 html 里就可以了,下面是一个例子。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <script src="main.js"></script>
</body>
</html>

打开 Chrome DevTools,切换到 Sources 标签页,然后在第三行 installedModules 的行号左边空白处点一下,添加一个断点。

界面如图,Chrome 还会自动识别模块,所以它会创建一个新的标签页。

前面都是在配置 __webpack_require__ 方法,可以一路步过。

到了后面,点一下 Step Into 来看看它是怎么初始化入口的。

首先它以 "./src/index.js" 为 moduleId,检查了缓存,然后一路创建新模块。

此时 exports 都是空的,到了最关键的 call 这一步,我们 Step Into 看看。

Chrome 又为这个模块开了个新标签页。这样子我们看不到调用的函数,所以我们切回 main.js 的代码

先看这个函数传入的参数

function(module, exports, __webpack_require__)

和刚才的 call 函数相对应,原来我们代码里写的 require 会调用 __webpack_require__ 方法。

modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);

那前面两个参数有什么用呢?继续往下看。

我们再 Step Into 一次,看看 __webpack_require__("./src/component/hello.js"); 又帮我们做了什么。

这次以 "./src/component/hello.js" 为 ID 了,再进去 call 看看。

咦?这里是我们调用的 module.exports,我们回去看一下 Webpack 的代码。

var module = installedModules[moduleId] = {

    i: moduleId,

    l: false,

    exports: {}
};

modules[moduleId].call(module.exports, module /* 这里 */, module.exports, __webpack_require__);

之前给 moduleexports 属性设了个空的对象,然后我们用 module.exports 给它赋了值,也就是说现在 exports 是我们导出的函数了。

我们过下一步看看。

果然,现在 moduleexports 是我们的函数了。

然后整个函数返回了 module.export,我们再想想刚才这个函数是从哪里调用的。

原来就是我们的 require 函数,也就是这个时候返回的是模块里的 hello() 函数。

到这里,你应该对 Webpack 的 import / export 有点概念了。

有时间的话务必动手一步一步调试看看,比如 ES6 的模块, Webpack 会怎么处理呢?导出多个模块,default 导出,打包后都会变成什么样子呢?这就作为下次博客的题材了。

Webpack 的基础使用

Webpack 和 Babel 好像用得很多了,但就是每次配置项目的时候查一下文档,这里稍微总结一下

ES Module 和 Webpack

ES2015 开始,JavaScript 有了模块的概念。这里一个模块就是一个独立的 js 文件。

没有模块之前,在一个文件里面定义的变量或者函数之类的,都是全局的。在另一个 js 文件里面也能访问到,也就是没有作用域 (scope)。网页开发中就有可能会引入多个 JS 的情况下,出现方法名被覆盖这种问题。

有了 Module 之后,作用域限定在了一个文件内,冲突的可能性也少多了。

模块化之后也会带来一些问题,比如零散文件会变多,请求数会增加(但 HTTP/2 可以解决这个问题)。比较旧的浏览器不支持 import 等等。

但模块化开发十分方便,所以 Webpack 出现了,它能够把众多的 Modules 打包成一个文件。不仅如此,它还支持把 css、模板 html 等,还有静态资源也能打包到一起,方便部署。

Webpack 官方网站

前期准备

需要 Node.js 和 npm 环境,这个就不需要多说了。

ES Module

推荐阅读阮一峰的ES6教程

简单来说,不同模块之间需要调用的时候,需要有明确的导出和导入,即 exportimport

这是 hello.mjs 代码:


const message = "Hello world"

const hello = () => {
    return message;
}

export {hello}

这是 index.mjs 的代码:

import { hello } from "./hello.mjs";

console.log(hello());

然后我们执行 node --experimental-modules index.mjs 就可以看到输出了。

注意:目前 Nodejs 需要导入导出模块的话,需要使用 .mjs 后缀名,并且运行的时候要加上 --experimental-modules 参数。

使用 Webpack 的话就不用了。

Webpack 的作用

Webpack 的作用就是把 import 调用的所有文件,通过一些方式连接在一起,输出为单一的一个 js 文件,称为 Bundle。

使用 Webpack

Webpack 安装

首先在一个新的目录下,用 npm init 命令来创建一个新的项目吧。

接下来使用 npm 安装 Webpack

npm install webpack webpack-cli --save-dev

这里安装了 Webpack 的本体,以及它相关的命令行工具。

Webpack 官方不推荐全局安装 Webpack,所以每个项目都独立安装一份吧。

附上官方安装指南页面

准备我们的项目

创建一个 src 目录,准备我们的代码文件。

sample/
  └ src/
      ├ hello.js
      └ index.js

用的还是刚才的代码,不过这次不需要使用 mjs 后缀了,可以用回 js 后缀。

hello.js 代码:


const message = "Hello world"

const hello = () => {
    return message;
}

export {hello}

index.mjs 代码:

import { hello } from "./hello"; // 这里后缀名是可以省略掉的

console.log(hello());

使用 Webpack 进行打包

运行以下命令就可以进行打包了

npx webpack

npx 是 npm 提供的一个命令,如果不存在,要确认以下 npm 的版本是不是太低,或者 Path 环境变量配置有问题了。

实际上 npx 是帮你执行 \node_modules.bin 里面的 webpack。

Webpack 打包完成之后,可以看到一个 dist 文件夹。打开里面的 main.js ,可以看到十分复杂的东西。

!function(e){var t={};function r(n){if(t[n])return t[n].exports;var o=t[n]={i:n,l:!1,exports:{}};return e[n].call(o.exports,o,o.exports,r),o.l=!0,o.exports}
/* 中间省略 */
return Object.prototype.hasOwnProperty.call(e,t)},r.p="",r(r.s=0)}([function(e,t,r){"use strict";r.r(t);console.log("Hello world")}]);

有种 js uglify 压缩过的样子。运行一下试试吧。

node .\dist\main.js

webpack.config.js 的配置

在项目根目录下创建一个 webpack.config.js,可以进行 Webpack 的配置。当然这些配置大部分都可以通过命令行参数来实现。

最基础的配置文件如下。可以查看官方文档查看所有可配置项目。

module.exports = {
    entry: './src/index.js', 
    output: {
        path: __dirname + '/dist',
        filename: 'main.js'
    }
};
  • entry 是入口点的文件名。
  • output 是输出文件相关的配置。

默认 Webpack 使用的是 webpack.config.js,但你也可以在命令行参数上直接指定使用的配置文件名。比如一套 development 配置,一套 production 配置。

npx webpack --config <文件名>

模式配置

Webpack 打包的时候你应该会看到一个警告

The 'mode' option has not been set, webpack will fallback to 'production' for this value. Set 'mode' option to 'development' or 'production' to enable defaults for each environment.

因为 Webpack 要求指定一个 mode,其中有两个可选:

  • development 开发模式,方便 debug
  • production 生产模式,尽量减少文件体积 (这个是默认值)

虽然有警告提示,但 Webpack 默认使用的是 production

我们试试 development 模式是什么效果。

修改 webpack.config.js 如下

module.exports = {
    mode: 'development',
    entry: './src/index.js', 
    output: {
        path: __dirname + '/dist',
        filename: 'main.js'
    }
};

再次 npx webpack 之后,打开 dist/main.js 看看。

/******/ (function(modules) { // webpackBootstrap
/******/     // The module cache
/******/     var installedModules = {};
/******/
/******/     // The require function
/******/     function __webpack_require__(moduleId) {
/******/
/******/         // Check if module is in cache
/******/         if(installedModules[moduleId]) {
/******/             return installedModules[moduleId].exports;
/******/         }
/******/         // Create a new module (and put it into the cache)
/******/         var module = installedModules[moduleId] = {
/******/             i: moduleId,
/******/             l: false,
/******/             exports: {}
/******/         };
/******/
/******/         // Execute the module function
/******/         modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/
/******/         // Flag the module as loaded
/******/         module.l = true;
/******/
/******/         // Return the exports of the module
/******/         return module.exports;
/******/     }
/******/
/******/
/******/     // expose the modules object (__webpack_modules__)
/******/     __webpack_require__.m = modules;

可以发现它多了很多的注释,变量名也比较清楚。这样也方便让我们理解它是怎么实现的。

Source Map

使用 Source Map 可以方便 Debug 的时候,找到源代码对应的行数。

module.exports = {
    mode: 'development',
    devtool: 'inline-source-map',
    /* 省略 */
};

添加 devtool 配置即可。在 Chrome 里就可以看到源码文件了。

这里可以配置很多不同的值,具体每个不同的效果,可以参照官方文档

Watch 模式

开启 Watch 模式,可以让你修改过文件之后,立刻打包新的文件。而且第二次以后是差分更新,速度会快很多,方便开发。

在运行 webpack 命令的时候,加上 --watch 参数就可以了。

npx webpack --watch

如果不像每次都输入,直接配置在 webpack.config.js 里也是可以的。

module.exports = {
    watch: true,
    /* 省略 */
};

有些文件,比如 node_modules,一般不需要加入到监视文件列表里面,这时候增加一个忽略名单。

module.exports = {
    watch: true,
    watchOptions: {
        ignored: ['node_modules']
    },
    /* 省略 */
};

退出 Watch 模式直接按 Ctrl + C 即可。

添加到 NPM Script

可以把执行的命令用添加到 NPM 里面,方便运行。

打开 package.json,修改里面的 script 内容。

"scripts": {
    "build": "webpack"
},

就这么简单,运行的时候使用 npm run build,就可以执行这个脚本。

当然,只是这么简单的也没有必要,那么一般是怎么用的呢?

基于上面的 webpack.config.js,我们可以配出一份开发用的,一份部署用的配置。

  • webpack.dev.config
  • webpack.prod.config

然后我们可以创建两个 script

{
    "scripts": {
        "start": "webpack --watch --config webpack.dev.config",
        "build": "webpack --config webpack.pro.config"
    },
    /* 省略 */
}

这个 start 的 script,在运行的时候可以不用使用 npm run start,直接 npm start 即可。

Spring Boot 【2】 使用 Thymeleaf 模板引擎

现在网站大致就是两种方案,服务器端渲染HTML或者单页面应用+API的方式。

服务器端渲染以前有 JSP 这样的方案,但由于 JSP 中要夹杂很多的 Java 代码,和前端开发一起工作的时候会前端可能会看不懂 JSP 的页面,不知道哪些部分可以修改。
而使用 Thymeleaf 来进行开发的话,它的语法就和现在的前端框架很接近。而且 Thymeleaf 的 HTML 文件脱离了我们 Java 后端的数据,依然可以在浏览器中看到页面的效果,对前端开发比较友好。
当然,如果写习惯了 JSP,也没有使用过一些常见的前端框架的话,可能就对 Thymeleaf 的语法比较陌生了。  

开始使用

我们这个项目是 Maven 构建的,所以先在 pom.xml 中添加所需要的依赖。

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>

保存之后,IntelliJ IDEA 会提示我们是否导入我们添加的依赖,点击 Enable Auto-Import ,以后我们修改了 pom.xml 中的依赖它就会自动开始下载了。

打开 resources 下的 application.properties ,在里面添加一行 spring.thymeleaf.cache=false 这样子可以关闭掉 Thymeleaf 的缓存,target 中的文件改变之后不需要重启 Spring Boot 应用(Tomcat)就可以刷新看到变化。

打开之前创建的 HomeController ,然后我们把 index 方法改成这样

@GetMapping("/")
public String index(Model model) {
    model.addAttribute("message", "Hello Thymeleaf");
    return "index";
}

Read More

Spring Boot 【1】 开始你的第一个 Spring Boot 项目

序言

这大概会是写成一个很长的系列。将会使用 Spring Boot、Spring Data JPA、Spring Secutiry、 Thymeleaf、 Bootstrap 等制作一个包括支持数据库增删查改、用户注册登录等功能的完整网站。

这个系列开始于 2019年10月18日 ,尽可能使用当前新的版本的来讲解,所以将基于 Spring Boot 2.2.0。

使用 Spring Initializer 开始你的项目

使用最方便的 Spring Initializer 来初始化我们的项目,可以在这里使用:
https://start.spring.io

spring-initializer

上面就是 Spring Initializer 的界面。

选择好 Maven 项目、 Java 、Spring Boot 的版本号,此处选择 2.2.0 填写 Group 和 Artifact ,接下来就可以在下面添加依赖了。

这一篇只是最基础的部分,所以只需要添加 Spring Web 和 Spring Boot DevTools 两个依赖就可以了。输入名字即可搜索添加。

添加完成之后点击 Generate 就可以获得一个 zip 文件,里面就是项目文件了。

Read More

为 Imagick 添加 webp 支持 (Ubuntu 16.04)

Ubuntu 16.04 源里面安装的 Imagick 不支持 webp ,所以得自己编译一个。

编译 ImageMagick

首先下载源码 wget https://imagemagick.org/download/ImageMagick.tar.gz 解压并打开

tar xvzf ImageMagick.tar.gz
cd ImageMagick-7.0.8

打开webp支持 ./configure --with-webp=yes 编译

make
make install

更新一下lib的绑定 ldconfig /usr/local/lib

运行一下可以发现已经有 WebP 的支持了 convert -list format | grep WebP  

编译 PHP 模块

首先要有phpize,下面的命令可以安装 sudo apt-get install php7.0-dev

克隆下面这个git仓库,编译安装

git clone https://github.com/mkoppanen/imagick.git imagick
cd miagick
./configure
make
make install

文件编译在当前目录的 modules 下,会有提示 Libraries have been installed in: ***

修改/etc/php/7.0/fpm/php.ini,添加一行 extension=/root/imagick/modules/imagick.so

重启一下 php 和 nginx 的服务

systemctl restart php7.0-fpm
systemctl restart nginx

此时已经完成,Imgick 有 WebP 的支持了

中国版 节奏过山车 :音炫轨道 试玩

听闻两台场测的 GC 拉回了广州,周末立刻就去试玩了。

中国版也采用的是音炫轨道RYTHMVADERS 这个标题。

由于这两台机之前已经有展出大半个月了,所以上方按键已经开始不灵敏了,长按过程中轻轻推动助力器会导致按键接触不良,需要经常维护才行了。

上手之后,感觉这个中国版还是很有诚意的。

虽然现在测试阶段并未有广域网联网服务,但店内联网功能可以正常使用。

最重要的是,这个游戏现有的三个领航员都进行了完整的中文配音,而且不是崩掉的配音。

我录了一个一局游戏的流程视频,包括新手教程,里面也是有配音的。

http://www.bilibili.com/video/av45947546

Read More

在 OpenWrt 上使用 华为 E5573s

华为 E5573s 可能是国内卖得最火的 随身WiFi 了。

不过它本身不能刷 OpenWrt,所以只能把他插在一个 OpenWrt 的路由器上,这也是 OpenWrt 上装4G模块最方便的方法了。

虽然可以用 OpenWrt 去桥接 E5573s 的 WiFi,不过无线的连接怎么都没有有线来得稳定。

如果有试过把 E5573s 的 USB 连接到电脑上,会发现可以直接当作一个 USB 4G 网卡来用,那么插在 OpenWrt 上也是可以的。

首先在 OpenWrt 上查找到了 这篇wiki《Use RNDIS USB Dongle for WAN connection》

简单的安装了下面这两个软件包之后,发现并不能行。

kmod-usb-net-rndis usb-modeswitch

查看 dmesg,发现识别到了 usb 设备,但是出现的是存储设备,就是电脑上会显示的那个驱动盘。

Read More

DANCERUSH STARDOM 舞律炫步 在中国

记录用。为了方便查看,改为时间倒序。

2019年4月

这个月在各地机厅举办了多个小型比赛,还有北京展的比赛。 4月30日公布了新的会员制度,版本号更新为:REC:C:A:A:2019042500 实际上的更新是从5月1日开始的,主要的更新内容有: 实装新会员制度、歌曲解禁系统。

新会员制度修改为两个档次:

基本会员VIP会员
费用15 E-am点/月 60 E-am点/半年30 E-am点/月 120 E-am点/半年
曲库58首(隐藏20首)79首(隐藏26首)
解锁消费2 E-am点/次1 E-am点/次

其中解锁消费的部分和新实装的歌曲解禁系统有关。

游玩上锁的歌曲需要消费额外的点数,不同会员的价格不同。

当上锁歌曲游玩10次超过80分以上,即可解锁到日常曲库,无需再额外消费。

EXTRA STAGE曲目也是同样,但需要10次90分以上可以解锁到日常曲库。

其次是每局可游玩的STAGE数的修改

会员(包括基本会员和VIP会员)都为2+1首,游客为2首。

2+1的含义是,前两首保底曲目,当前两首总分相加达到180分以后,可以游玩第三首。

此时第三首会出现游玩普通曲目还是EXTRA STAGE专用曲目的选项,游玩EX曲需要消耗对应解锁点数。并且EX曲目的锁血机制已经取消,掉10个的话会STAGE FAIL。

目前,基本会员暂时没有补差价升级VIP会员的功能。 关于老会员的补偿方案还未公布。   以及目前我玩到的几个BUG

基础会员在普通难度下看不到待解禁列表,只显示在简单中。

部分歌曲无法显示解锁进度(目前服务端已经重新统计完的样子)

顺便吐槽一句,直到这个版本,DRS还是使用U盘升级的方法。

Read More