├── package.json
├── index.js
├── LICENSE
├── lib
├── process.js
├── helper.js
├── customfs.js
└── generator.js
├── .gitignore
├── readme-cn.md
└── readme.md
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "hexo-cdn-jsdelivr",
3 | "version": "0.0.9",
4 | "description": "A hexo plugin to help you automatically use jsdelivr CDN for Github to speed up the loading of static resources like images.",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1"
8 | },
9 | "repository": "renzibei/hexo-cdn-jsdelivr",
10 | "keywords": [
11 | "hexo",
12 | "plugin",
13 | "image",
14 | "cdn",
15 | "jsdelivr"
16 | ],
17 | "dependencies": {
18 | "url-join": "^4.0.1",
19 | "nunjucks": "^3.2.1",
20 | "moment": "^2.18.0",
21 | "hexo-fs": "^3.1.0"
22 | },
23 | "author": "renzibei",
24 | "license": "MIT"
25 | }
26 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | /* global hexo */
2 | 'use strict';
3 |
4 |
5 | hexo.extend.helper.register('cdn_css', require('./lib/helper').cdn_css);
6 | hexo.extend.helper.register('cdn_js', require('./lib/helper').cdn_js);
7 | hexo.extend.helper.register('cdn_asset', require('./lib/helper').cdn_asset);
8 | var cdnConfig = require('./lib/process').getCdnConfig(hexo);
9 |
10 | if (cdnConfig && 'use_cdn' in cdnConfig && cdnConfig.use_cdn) {
11 | hexo.extend.filter.register('before_post_render', require('./lib/process').processPost);
12 | hexo.extend.filter.register('before_exit', require('./lib/generator').assetsGenerator);
13 | hexo.extend.filter.register('before_exit', require('./lib/generator').assetsDeployer);
14 | }
15 |
16 |
17 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Ren Zibei
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/lib/process.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const pathFn = require('path');
4 | const urljoin = require('url-join');
5 |
6 |
7 | function getCdnConfig(ctx) {
8 | var cdnConfig = null;
9 | if (typeof (ctx.config.jsdelivr_cdn) !== 'undefined') {
10 | cdnConfig = ctx.config.jsdelivr_cdn;
11 | }
12 | return cdnConfig;
13 | }
14 |
15 | function removeLastSlash(s) {
16 | if (s.length === 0)
17 | return s;
18 | if (s[s.length - 1] === '/')
19 | return s.substring(0, s.length - 1);
20 | return s;
21 | }
22 |
23 | // replace all asset_img tag with html img tag
24 | function processPost(data) {
25 | let cdnConfig = getCdnConfig(this);
26 | const postAssetDirName = "post-assets";
27 | // var reg = /!\[(.*)\]\((.*)\)/g;: ", data.source, " full_source: ", data.full_source);
28 | if(typeof data.asset_dir !== "undefined" && typeof this.config !== "undefined") {
29 | if(!this.config.post_asset_folder) {
30 | throw new TypeError("post_asset_folder in _config.yml must be set true to use cdn-jsdelivr");
31 | }
32 | let cdnUrlPrefix = removeLastSlash(cdnConfig.cdn_url_prefix) + "@latest"
33 | let postUrlPrefix = urljoin(cdnUrlPrefix, postAssetDirName);
34 | let imgTagReg = /{%\s*asset_img\s+([^%\s]+)\s+([^%]*)%?}/g;
35 | data.content = data.content.replace(imgTagReg, "
");
36 | }
37 | return data;
38 | }
39 |
40 |
41 |
42 | module.exports.processPost = processPost;
43 |
44 | module.exports.getCdnConfig = getCdnConfig;
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Mac
2 | .DS_Store
3 |
4 | # vscode
5 | .vscode
6 |
7 | package-lock.json
8 |
9 | logs
10 | *.log
11 | npm-debug.log*
12 | yarn-debug.log*
13 | yarn-error.log*
14 | lerna-debug.log*
15 |
16 | # Diagnostic reports (https://nodejs.org/api/report.html)
17 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
18 |
19 | # Runtime data
20 | pids
21 | *.pid
22 | *.seed
23 | *.pid.lock
24 |
25 | # Directory for instrumented libs generated by jscoverage/JSCover
26 | lib-cov
27 |
28 | # Coverage directory used by tools like istanbul
29 | coverage
30 | *.lcov
31 |
32 | # nyc test coverage
33 | .nyc_output
34 |
35 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
36 | .grunt
37 |
38 | # Bower dependency directory (https://bower.io/)
39 | bower_components
40 |
41 | # node-waf configuration
42 | .lock-wscript
43 |
44 | # Compiled binary addons (https://nodejs.org/api/addons.html)
45 | build/Release
46 |
47 | # Dependency directories
48 | node_modules/
49 | jspm_packages/
50 |
51 | # Snowpack dependency directory (https://snowpack.dev/)
52 | web_modules/
53 |
54 | # TypeScript cache
55 | *.tsbuildinfo
56 |
57 | # Optional npm cache directory
58 | .npm
59 |
60 | # Optional eslint cache
61 | .eslintcache
62 |
63 | # Microbundle cache
64 | .rpt2_cache/
65 | .rts2_cache_cjs/
66 | .rts2_cache_es/
67 | .rts2_cache_umd/
68 |
69 | # Optional REPL history
70 | .node_repl_history
71 |
72 | # Output of 'npm pack'
73 | *.tgz
74 |
75 | # Yarn Integrity file
76 | .yarn-integrity
77 |
78 | # dotenv environment variables file
79 | .env
80 | .env.test
81 |
82 | # parcel-bundler cache (https://parceljs.org/)
83 | .cache
84 | .parcel-cache
85 |
86 | # Next.js build output
87 | .next
88 | out
89 |
90 | # Nuxt.js build / generate output
91 | .nuxt
92 | dist
93 |
94 | # Gatsby files
95 | .cache/
96 | # Comment in the public line in if your project uses Gatsby and not Next.js
97 | # https://nextjs.org/blog/next-9-1#public-directory-support
98 | # public
99 |
100 | # vuepress build output
101 | .vuepress/dist
102 |
103 | # Serverless directories
104 | .serverless/
105 |
106 | # FuseBox cache
107 | .fusebox/
108 |
109 | # DynamoDB Local files
110 | .dynamodb/
111 |
112 | # TernJS port file
113 | .tern-port
114 |
115 | # Stores VSCode versions used for testing VSCode extensions
116 | .vscode-test
117 |
118 | # yarn v2
119 | .yarn/cache
120 | .yarn/unplugged
121 | .yarn/build-state.yml
122 | .yarn/install-state.gz
123 | .pnp.*
124 |
125 |
--------------------------------------------------------------------------------
/lib/helper.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const pathFn = require('path');
4 | const urljoin = require('url-join');
5 |
6 | function removeLastSlash(s) {
7 | if (s.length === 0)
8 | return s;
9 | if (s[s.length - 1] === '/')
10 | return s.substring(0, s.length - 1);
11 | return s;
12 | }
13 |
14 | function cdn_js(path) {
15 |
16 | // const baseDirPath = this.base_dir;
17 | // const deployAssetsDir = ".deploy_static_assets";
18 | // const deployAssetsDirPath = path.join(baseDirPath, deployAssetsDir);
19 | // const jsAssetsDirPath = path.join(deployAssetsDirPath, "js");
20 |
21 | // const hexo_js = this.extend.helper.get('js').bind(this);
22 |
23 |
24 | let cdnConfig = require('./process').getCdnConfig(this);
25 |
26 | if (!path.endsWith('.js')) {
27 | path += '.js';
28 | }
29 | let jsUrl = path;
30 | if(typeof cdnConfig.use_cdn !== 'undefined' && cdnConfig.use_cdn) {
31 |
32 | let jsFileBaseName = pathFn.basename(path);
33 |
34 | let cdnUrlPrefix = removeLastSlash(cdnConfig.cdn_url_prefix) + "@latest"
35 | jsUrl = urljoin(cdnUrlPrefix, "js", jsFileBaseName);
36 | }
37 | return '';
38 | }
39 |
40 | function cdn_css(path) {
41 | // const hexo_css = this.extend.helper.get('css').bind(this);
42 | if (!path.endsWith('.css')) {
43 | path += '.css';
44 | }
45 | let cssUrl = path;
46 | let cdnConfig = require('./process').getCdnConfig(this);
47 | if(typeof cdnConfig.use_cdn !== 'undefined' && cdnConfig.use_cdn) {
48 | let cssFileBaseName = pathFn.basename(path);
49 | let cdnUrlPrefix = removeLastSlash(cdnConfig.cdn_url_prefix) + "@latest"
50 | cssUrl = urljoin(cdnUrlPrefix, "css", cssFileBaseName);
51 | }
52 | return '';
53 | }
54 |
55 | function cdn_asset(path) {
56 | let assetUrl = path;
57 | let cdnConfig = require('./process').getCdnConfig(this);
58 | if(typeof cdnConfig.use_cdn !== 'undefined' && cdnConfig.use_cdn) {
59 | let cdnUrlPrefix = removeLastSlash(cdnConfig.cdn_url_prefix) + "@latest"
60 | let fileBaseName = pathFn.basename(path);
61 | let baseDirName = pathFn.basename(pathFn.dirname(path));
62 | assetUrl = urljoin(cdnUrlPrefix, baseDirName, fileBaseName);
63 | }
64 | return this.url_for(assetUrl);
65 | }
66 |
67 | module.exports.cdn_js = cdn_js;
68 | module.exports.cdn_css = cdn_css;
69 | module.exports.cdn_asset = cdn_asset;
--------------------------------------------------------------------------------
/lib/customfs.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const Promise = require('bluebird');
4 | const fs = require('graceful-fs');
5 | const fsPromises = fs.promises;
6 | const {join} = require("path")
7 |
8 | function trueFn() {
9 | return true;
10 | }
11 |
12 | function ignoreHiddenFiles(ignore) {
13 | if (!ignore) return trueFn;
14 |
15 | return ({ name }) => !name.startsWith('.');
16 | }
17 |
18 | function ignoreFilesRegex(regex) {
19 | if (!regex) return trueFn;
20 |
21 | return ({ name }) => !regex.test(name);
22 | }
23 |
24 | async function _readAndFilterDir(path, options) {
25 | const { ignoreHidden = true, ignorePattern } = options;
26 | return (await fsPromises.readdir(path, { ...options, withFileTypes: true }))
27 | .filter(ignoreHiddenFiles(ignoreHidden))
28 | .filter(ignoreFilesRegex(ignorePattern));
29 |
30 | }
31 |
32 | function _readAndFilterDirSync(path, options) {
33 | const { ignoreHidden = true, ignorePattern } = options;
34 | return fs.readdirSync(path, { ...options, withFileTypes: true })
35 | .filter(ignoreHiddenFiles(ignoreHidden))
36 | .filter(ignoreFilesRegex(ignorePattern));
37 | }
38 |
39 | async function _listDirWalker(path, results, parent, options) {
40 | const promises = [];
41 |
42 | for (const item of await _readAndFilterDir(path, options)) {
43 | const currentPath = join(path, item.name);
44 |
45 | if (item.isDirectory()) {
46 | promises.push(_listDirWalker(join(path, item.name), results, currentPath, options));
47 | } else {
48 | results.push(currentPath);
49 | }
50 | }
51 |
52 | await Promise.all(promises);
53 | }
54 |
55 | // return all files (not directories) in the dir, including the files in the sub directories; the returned files are path-like
56 | function listDirPath(path, options = {}) {
57 | if (!path) throw new TypeError('path is required!');
58 |
59 | const results = [];
60 |
61 | return Promise.resolve(_listDirWalker(path, results, '', options)).return(results);
62 | }
63 |
64 | function _listDirSyncWalker(path, results, parent, options) {
65 | for (const item of _readAndFilterDirSync(path, options)) {
66 | const currentPath = join(path, item.name);
67 |
68 | if (item.isDirectory()) {
69 | _listDirSyncWalker(join(path, item.name), results, currentPath, options);
70 | } else {
71 | results.push(currentPath);
72 | }
73 | }
74 | }
75 |
76 | function listDirPathSync(path, options = {}) {
77 | if (!path) throw new TypeError('path is required!');
78 |
79 | const results = [];
80 |
81 | _listDirSyncWalker(path, results, '', options);
82 |
83 | return results;
84 | }
85 |
86 | module.exports.listDirPath = listDirPath;
87 | module.exports.listDirPathSync = listDirPathSync;
--------------------------------------------------------------------------------
/readme-cn.md:
--------------------------------------------------------------------------------
1 | # hexo-cdn-jsdelivr
2 |
3 | 简体中文 | [English](./readme.md)
4 |
5 | ## 介绍
6 |
7 | 一个自动地帮助你将图片等静态资源使用jsDelivr的Github CDN加速的hexo插件。
8 |
9 | 如果你想要使用CDN来加速你的Hexo网站中的静态资源,那么 [jsDelivr](https://www.jsdelivr.com/)提供了一个开源的CDN。
10 |
11 | 你可能在以下场景中想要使用该插件和jsDelivr CDN:
12 |
13 | - 加速Hexo网站中静态资源的加载
14 | - 避免网站在不同地区访问体验的不一致。例如,Github Pages在中国的访问速度较慢且体验因地区差异较大。
15 | - 节省浏览器的带宽
16 |
17 | ## 安装
18 |
19 | ```shell
20 | $ npm install hexo-cdn-jsdelivr --save
21 | ```
22 |
23 | 如果你使用node 8.x版本,那么可能会遇到一些问题。我们推荐你至少使用node 10.x版本。
24 |
25 | ## 配置选项
26 |
27 | 你可以在项目的`_config.yml`文件中配置该插件。
28 |
29 | ```yaml
30 | jsdelivr_cdn:
31 | # 如果 use_cdn 被设置为false, 则该插件不会工作
32 | use_cdn: true
33 |
34 | # 如果 deploy_when_generating 被设置为true, 那么静态资源目录将会在每次使用'hexo g'命令生成hexo项目的时候被推送到github。如果被设置为false,则只会在deploy时被推送。你也可以在该标志设置为false时使用'hexo g cdn'命令手动推送到github。
35 | deploy_when_generating: true
36 |
37 | # cdn_url_prefix是jsdelivr上你的github仓库(这是专门用来作CDN加速的静态资源仓库而非原hexo项目的部署仓库)的对应网址,应该形如 https://cdn.jsdelivr.net/gh///
38 | cdn_url_prefix: <你在github上的静态资源仓库对应的jsdelivr网址>
39 |
40 | # git_repo_url是你github上的静态资源仓库的url,应该形如git@github.com:/.git
41 | git_repo_url:
42 |
43 | # 你可以使用Github token来验证推送你的资源仓库。如果不想使用token验证,那么只要将token值设置为空或者注释掉这一行即可。我们不推荐将token直接写在_config.yml中。我们推荐使用环境变量储存token,只要将token值的第一个字符设置为'$',该插件就会从该环境变量中读取。例如你可以使用'$GITHUB_TOKEN'环境变量来储存token。当使用token验证时,必须通过http(s)连接的方式。更多关于Github token的信息可以去 https://help.github.com/articles/creating-a-personal-access-token-for-the-command-line 内查看。
44 | token:
45 |
46 | # asset_dirs是可选的需要上传至github静态资源仓库的目录列表,其中的每一个资源目录应该是相对于你的hexo项目目录的路径, 例如 assets 或者 source/assets 或者 themes//assets 。如果你只是想用CDN加速你的post中的图片,则可以不设置asset_dirs
47 | asset_dirs:
48 | - [存放静态资源的目录]
49 | - [另一个存放静态资源的目录]
50 | ```
51 |
52 | ## 如何使用
53 |
54 | 如果你想要使用jsdelivr CDN加速你的posts中的图片,那么你需要设置`_config.yml`中的`post_asset_folder`选项为`true`。并且你需要将上面的配置选项加入`_config.yml`中。
55 |
56 | 你需要在Github上新建一个仓库专门来存放静态资源。新建完仓库后,将仓库的链接填写在配置选项中,该插件就会帮助你将静态资源自动地推送到Github上的仓库中。然后jsDelivr的CDN中可以访问你的静态资源了。
57 |
58 | ### 文章内图片
59 |
60 | 正如[Hexo官方文档](https://hexo.io/zh-cn/docs/asset-folders.html)中推荐的那样,你可以在文章的markdown文件中使用 `{% asset_img <图片名> [图片注解] %}`tag 来在文章中插入图片。如果还不熟悉这种插入图片的方法,可以再浏览一下官方文档。
61 |
62 | 在没有安装该插件时,如果`_config.yml` 中的`post_asset_folder`选项被设置为`true`,那么每篇文章都会有一个同名的资源文件夹,你需要将文章的图片放在对应的资源文件夹中。在使用该插件后,你不需要移动任何文件夹,仍然使用该种方式存放图片和在文章中引用图片。该插件会自动化地将你文章资源文件夹中的所有图片推送到Github资源仓库中,并且会把生成的html页面内的图片链接全部替换为指向jsDelivr CDN中的链接。
63 |
64 | ### 其他的静态资源
65 |
66 | 除了文章内的图片,你还可以使用该插件来将通过CDN加速hexo项目内的其他的静态资源。例如,你的网站中可能有一些`.js`或`.css`文件是你想通过CDN加速的。
67 |
68 | 有一些你可以在模板文件如`.ejs`文件中使用的模板方法(在hexo中被称为`Helper`),你可以使用他们来将原本指向本站的资源链接转换为CDN上的链接。
69 |
70 | ```ejs
71 |
72 | <%- cdn_css(path) %>
73 |
74 |
75 | <%- cdn_js(path) %>
76 |
77 |
78 | <%- cdn_asset(path) %>
79 |
80 |
81 | <%- cdn_css('style.css') %>
82 |
83 |
84 | <%- cdn_js('test.js') %>
85 |
86 |
87 |
88 |
89 | ```
90 |
91 |
92 |
93 | ## 工作原理
94 |
95 | 感谢 [jsDelivr](https://www.jsdelivr.com)。jsDelivr为Github提供了一个开源CDN。因此你可以从jsDelivr的CDN上加载任何你上传到Github公开仓库中的文件。你可以进一步地了解jsDelivr CDN通过访问[他们的网站](https://www.jsdelivr.com/features)
96 |
97 | 简单而言,如果你的Github仓库中的一个文件的网址形如 `https://github.com///blob/master/`,那么也可以通过jsDelivr CDN的链接访问它 `https://cdn.jsdelivr.net/gh///`
98 |
99 | `hexo-cdn-jsdelivr`会将静态资源拷贝至`.deploy_static_assets`目录,然后将该目录推送到你Github上的资源仓库。然后你就可以通过jsDelivr的CDN来加载上传至Github仓库中的静态资源。
100 |
101 | 该插件会将文章中所有使用`asset_img`tag的图片链接替换为CDN中的图片链接。
102 |
103 | 除了`_post`目录下的文章资源目录与配置选项中`asset_dirs`中的目录,该插件还会扫描`public`目录下的文件,并将所有`.js`与`.css`文件推送至Github仓库。通过该方式,使用`stylus`等css模板产生的css文件也能够被CDN获取。
104 |
105 | ## TODO List
106 |
107 | - [x] 添加对通过Github token推送的支持
108 |
109 | - [ ] 考虑支持markdown风格的文件插入链接方法
110 |
111 | ## 重置
112 |
113 | ```
114 | $ rm -rf .deploy_static_assets
115 | ```
116 |
117 | ## 参考
118 |
119 | 部分代码参考了 [hexo-deployer-git](https://github.com/hexojs/hexo-deployer-git)的实现
120 |
121 | ## License
122 |
123 | MIT
--------------------------------------------------------------------------------
/readme.md:
--------------------------------------------------------------------------------
1 | # hexo-cdn-jsdelivr
2 |
3 | English | [简体中文](./readme-cn.md)
4 |
5 | ## Introduction
6 |
7 | A hexo plugin to help you automatically use jsDelivr CDN for Github to speed up the loading of static resources like images.
8 |
9 | If you want to use CDN for the static resources of your hexo website, [jsDelivr](https://www.jsdelivr.com/) provides an open-source CDN.
10 |
11 | You may want to use this plugin and jsDelivr CDN when:
12 |
13 | - Speed up the loading of static resources of your Hexo website
14 | - Avoid inconsistent website access experience in different regions. For example, Github Pages is very slow to visit in China
15 |
16 | - Save the bandwidth of your server
17 |
18 | ## Installation
19 |
20 | ```shell
21 | $ npm install hexo-cdn-jsdelivr --save
22 | ```
23 |
24 | You may come across some problems when using node 8.x version. We recommend that you use node >= 10.x version.
25 |
26 | ## Options
27 |
28 | You can configure this plugin in `_config.yml`.
29 |
30 | ```yaml
31 | jsdelivr_cdn:
32 | # If use_cdn is false, this plugin will not work
33 | use_cdn: true
34 |
35 | # If deploy_when_generating is true, the assets repository will be pushed to github every time you generate the hexo project using 'hexo g' command. If this flag is set false, the repository will be pushed when deploying. You can manually push the assets repository to github when generating by using 'hexo g cdn' command.
36 | deploy_when_generating: true
37 |
38 | # cdn_url_prefix is the jsdelivr cdn url of your github repository(the assets repository for static assets rather than the hexo project deployment repository), it should be like: https://cdn.jsdelivr.net/gh///
39 | cdn_url_prefix:
40 |
41 | # git_repo_url is the url of your new assets repository on github, it should be like git@github.com:/.git
42 | git_repo_url:
43 |
44 | # you can use github token to push your assets repository. If you don't want to use a token, you can use a empty string '' or comment out this line. We do not recommand that you directly write your token in the _config.yml. We suggest that you read the token from the environment variable by setting token with a prefix '$'. e.g. '$GITHUB_TOKEN'. When you want to use token, you must use http(s) link of your repo. More information about github token can be found in https://help.github.com/articles/creating-a-personal-access-token-for-the-command-line.
45 | token:
46 |
47 | # The path of an asset directory in asset_dirs should be the relative path to your hexo project directory, e.g. assets or source/assets or themes//assets. If you only want to use the cdn for the images in your posts, you can leave asset_dirs as empty
48 | asset_dirs:
49 | - [assets directory]
50 | - [another assets directory]
51 | ```
52 |
53 | ## Usage
54 |
55 | If you want to use this plugin, you should set the`post_asset_folder` to `true` in `_config.yml` of your project. And you have to add the Options mentioned above to your `_config.yml`
56 |
57 | You have to create a new repository in Github to store static assets. Set the repository link in options and this plugin will help you push static assets to the Github repository. Then the CDN link of your assets will be available in jsDelivr.
58 |
59 | ### Images in Posts
60 |
61 | As it is recommanded in [Hexo documents](https://hexo.io/docs/asset-folders), you can use `{% asset_img slug [title] %}` tag in your markdown post file to insert pictures in the posts.
62 |
63 | Originally, setting `post_asset_folder` as true, you place the pictures in each folder for the posts. Now with `hexo-cdn-jsdelivr`, you don't have to move your pictures assets. This plugin will automatically push all your pictures of posts to the repository on Github and using the jsdelivr CDN link in the generated html pages.
64 |
65 | ### Other Static Assets
66 |
67 | Except for the images in posts, you can use this plugin to upload other static assets to CDN too! For example, you may have some `.js` or `.css` files on your site that you want to upload to the CDN.
68 |
69 | There are several templates(which is `Helper` in hexo) you can use in to turn assets to CDN link.
70 |
71 | ```ejs
72 |
73 | <%- cdn_css(path) %>
74 |
75 |
76 | <%- cdn_js(path) %>
77 |
78 |
79 | <%- cdn_asset(path) %>
80 |
81 |
82 | <%- cdn_css('style.css') %>
83 |
84 |
85 | <%- cdn_js('test.js') %>
86 |
87 |
88 |
89 |
90 | ```
91 |
92 | ## How it works
93 |
94 | Thanks to [jsDelivr](https://www.jsdelivr.com). jsDelivr provides an open-source CDN for Github. So you can load all the asset files uploaded to Github from the CDN. You can know the jsDelivr CDN further by visiting [their website](https://www.jsdelivr.com/features).
95 |
96 | In short, if the url of a file in your Github repo is like this: `https://github.com///blob/master/`,you can also access the jsDelivr CDN url of it by `https://cdn.jsdelivr.net/gh///`
97 |
98 | `hexo-cdn-jsdelivr` works by copying the static assets files to `.deploy_static_assets` and then it will push this directory to your assets git repo on Github. Then you can load your assets through jsDelivr CDN.
99 |
100 | This plugin will replace all the image links which come from `asset_img` tags in posts with the CDN link of each image.
101 |
102 | Except for the assets in the `_post` directory and the `asset_dirs` set in the Options, the plugin will scan the `public` directory to find all the `.js` and `.css` files and upload them to github repo, in which way the css files generated by `stylus` can also be accessed by the CDN.
103 |
104 | ## TODO List
105 |
106 | - [x] Add support for Github token
107 | - [ ] Add support for markdown-style image inserting
108 |
109 | ## Reset
110 |
111 | ```
112 | $ rm -rf .deploy_static_assets
113 | ```
114 |
115 | ## Reference
116 |
117 | Some of the codes refer to [hexo-deployer-git](https://github.com/hexojs/hexo-deployer-git)
118 |
119 | ## License
120 |
121 | MIT
122 |
--------------------------------------------------------------------------------
/lib/generator.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 |
4 | const pathFn = require('path');
5 | const fs = require('hexo-fs');
6 | const spawn = require('hexo-util/lib/spawn');
7 | const nunjucks = require('nunjucks');
8 | const moment = require('moment');
9 | const customFs = require('./customfs');
10 |
11 |
12 |
13 |
14 | function getUrlFromConfig(config) {
15 |
16 | const rRepoURL = /^(?:(git|https?|git\+https|git\+ssh):\/\/)?(?:[^@]+@)?([^\/]+?)[\/:](.+?)\.git$/;
17 | const { URL } = require('url');
18 |
19 | let url = config.git_repo_url;
20 | if (typeof config.token !== 'undefined') {
21 | const configToken = config.token;
22 |
23 | if (rRepoURL.test(url)) {
24 | const match = url.match(rRepoURL);
25 | const scheme = match[1];
26 |
27 | if (configToken && (scheme === 'http' || scheme === 'https')) {
28 | let repoUrl, userToken;
29 | try {
30 | repoUrl = new URL(url);
31 | } catch (e) {
32 | throw new TypeError('Fail to parse your repo url, check your config!');
33 | }
34 |
35 | if (configToken.startsWith('$')) {
36 | userToken = process.env[configToken.substring(1)];
37 | if (!userToken) throw new TypeError('Fail to read environment varable: ' + configToken + ', check your config!');
38 | } else {
39 | userToken = configToken;
40 | }
41 | repoUrl.username = userToken;
42 | url = repoUrl.href;
43 | }
44 | }
45 | }
46 |
47 | return url;
48 | }
49 |
50 |
51 | const swigHelpers = {
52 | now: function(format) {
53 | return moment().format(format);
54 | }
55 | };
56 |
57 | function isJsFile(path) {
58 | return /\.js$/.test(path);
59 | }
60 |
61 | function isCssFile(path) {
62 | return /\.css$/.test(path);
63 | }
64 |
65 | function shouldDeployWhenGenerating(cdnConfig) {
66 | return !(typeof cdnConfig.deploy_when_generating === 'undefined' || !cdnConfig.deploy_when_generating);
67 | }
68 |
69 | function isGenerating() {
70 | return (process.argv.indexOf('generate') > -1 || process.argv.indexOf('g') > -1);
71 | }
72 |
73 | function isDeploying() {
74 | return (process.argv.indexOf('deploy') > -1 || process.argv.indexOf('d') > -1);
75 | }
76 |
77 |
78 | function assetsGenerator(args) {
79 |
80 | const cdnConfig = require('./process').getCdnConfig(this);
81 | const log = this.log;
82 |
83 | if (isGenerating())
84 | log.info("use jsdelivr cdn");
85 |
86 | if (!( (isGenerating() && (shouldDeployWhenGenerating(cdnConfig) || process.argv.indexOf('cdn') > -1))
87 | || (!shouldDeployWhenGenerating(cdnConfig) && isDeploying()) ) )
88 | return;
89 |
90 | log.info("begin assets generate");
91 | const baseDirPath = this.base_dir;
92 | const sourceDir = this.source_dir;
93 | const publicDirPath = this.public_dir;
94 | const postDirPath = pathFn.join(sourceDir, "_posts");
95 | const deployAssetsDir = ".deploy_static_assets";
96 | const deployAssetsDirPath = pathFn.join(baseDirPath, deployAssetsDir);
97 | const postAssetDirName = "post-assets";
98 | const postAssetsPath = pathFn.join(deployAssetsDirPath, postAssetDirName);
99 |
100 |
101 | function git(...args) {
102 | return spawn('git', args, {
103 | cwd: deployAssetsDirPath,
104 | stdio: 'inherit'
105 | }).then((ret) => {
106 | return ret;
107 | });
108 | };
109 |
110 | function initAssetsDir(assetsDirPath) {
111 | const userName = '';
112 | const userEmail = '';
113 |
114 | // Create a placeholder for the first commit
115 | return fs.writeFile(pathFn.join(assetsDirPath, 'placeholder'), '').then(() => {
116 | return git('init');
117 | }).then(() => {
118 | return userName && git('config', 'user.name', userName);
119 | }).then(() => {
120 | return userEmail && git('config', 'user.email', userEmail);
121 | }).then(() => {
122 | return git('add', '-A');
123 | }).then(() => {
124 | return git('commit', '-m', 'First commit');
125 | });
126 | };
127 |
128 | function makeAssetsDir(assetsDirPath) {
129 | return fs.exists(assetsDirPath).then(exist => {
130 | if(exist) return;
131 | return fs.mkdirs(assetsDirPath)
132 | .then(() => { return initAssetsDir(assetsDirPath)});
133 | });
134 | };
135 |
136 |
137 |
138 |
139 |
140 |
141 | function isTmpFile(path) {
142 | return path.endsWith('%') || path.endsWith('~');
143 | }
144 |
145 | function isHiddenFile(path) {
146 | return /(^|\/)\./.test(path);
147 | }
148 |
149 | function isExcludedFile(path, config) {
150 | if (isTmpFile(path)) return true;
151 | if (isHiddenFile(path) && !isMatch(path, config.include)) return true;
152 | return false;
153 | }
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 | return makeAssetsDir(deployAssetsDirPath)
162 | .then(()=>{
163 | log.info("clear assets...");
164 | return fs.emptyDir(deployAssetsDirPath);
165 | })
166 | .then( () => {
167 | log.info("copy post assets...");
168 | return fs.copyDir(postDirPath, postAssetsPath, { ignorePattern: /\.md$/ });
169 | })
170 | .then( () => {
171 | if(typeof cdnConfig.asset_dirs !== 'undefined') {
172 | log.info("copy other assets...");
173 | let assetsDirList = cdnConfig.asset_dirs
174 | let jsAssetsDirPath = pathFn.join(deployAssetsDirPath, "js");
175 | let cssAssetsDirPath = pathFn.join(deployAssetsDirPath, "css");
176 | if(Array.isArray(assetsDirList)) {
177 | let listLen = assetsDirList.length;
178 | for(let i = 0; i < listLen; ++i) {
179 | let staticAssetDir = assetsDirList[i];
180 | let staticAssetDirPath = pathFn.join(baseDirPath, staticAssetDir);
181 | let dstDirPath = pathFn.join(deployAssetsDirPath, pathFn.basename(staticAssetDir));
182 | fs.copyDir(staticAssetDirPath, dstDirPath, {ignorePattern: /\.(js|css)$/});
183 | customFs.listDirPathSync(staticAssetDirPath)
184 | .filter(item => isJsFile(item) || isCssFile(item))
185 | .map(item => {
186 | log.info("Copy from: ", item);
187 | let fileBaseName = pathFn.basename(item);
188 | if(isJsFile(item)) {
189 | return fs.copyFile(item, pathFn.join(jsAssetsDirPath, fileBaseName));
190 | }
191 | else if(isCssFile(item)) {
192 | return fs.copyFile(item, pathFn.join(cssAssetsDirPath, fileBaseName));
193 | }
194 | else {
195 | log.info("what happend!");
196 | }
197 | });
198 | }
199 | }
200 | }
201 |
202 | });
203 |
204 | }
205 |
206 | function assetsDeployer(args) {
207 |
208 |
209 |
210 | const cdnConfig = require('./process').getCdnConfig(this);
211 |
212 | if (!( (isGenerating() && (shouldDeployWhenGenerating(cdnConfig) || process.argv.indexOf('cdn') > -1))
213 | || (!shouldDeployWhenGenerating(cdnConfig) && isDeploying()) ) )
214 | return;
215 |
216 |
217 | const log = this.log;
218 |
219 | const baseDirPath = this.base_dir;
220 | const publicDirPath = this.public_dir;
221 | const deployAssetsDir = ".deploy_static_assets";
222 | const deployAssetsDirPath = pathFn.join(baseDirPath, deployAssetsDir);
223 | const message = commitMessage("");
224 | const jsAssetsDirPath = pathFn.join(deployAssetsDirPath, "js");
225 | const cssAssetsDirPath = pathFn.join(deployAssetsDirPath, "css");
226 |
227 | function git(...args) {
228 | return spawn('git', args, {
229 | cwd: deployAssetsDirPath,
230 | stdio: 'inherit'
231 | }).then((ret) => {
232 | return ret;
233 | });
234 | };
235 |
236 | function commitMessage(m) {
237 | const message = m || 'Site updated: {{ now(\'YYYY-MM-DD HH:mm:ss\') }}';
238 | return nunjucks.renderString(message, swigHelpers);
239 | };
240 |
241 | function push(repo_url) {
242 | return git('add', '-A').then(() => {
243 | return git('commit', '-m', message).catch(() => {
244 | // Do nothing. It's OK if nothing to commit.
245 | });
246 | }).then(() => {
247 | return git('push', '-u', repo_url, 'HEAD:' + "master", '--force');
248 | });
249 | };
250 |
251 |
252 | log.info("copy the js and css files in public");
253 |
254 | return customFs.listDirPath(publicDirPath)
255 | .catch(err => {
256 | if (err && err.code === 'ENOENT') return [];
257 | throw err;
258 | })
259 | .filter(item => isJsFile(item) || isCssFile(item))
260 | .map(item => {
261 | log.info("Copy from: ", item);
262 | let fileBaseName = pathFn.basename(item);
263 | if(isJsFile(item)) {
264 | return fs.copyFile(item, pathFn.join(jsAssetsDirPath, fileBaseName));
265 | }
266 | else if(isCssFile(item)) {
267 | return fs.copyFile(item, pathFn.join(cssAssetsDirPath, fileBaseName));
268 | }
269 | else {
270 | log.info("what happend!");
271 | }
272 | })
273 | .then(() => {
274 | log.info("begin push...");
275 | return push(getUrlFromConfig(cdnConfig));
276 | });
277 |
278 |
279 |
280 | }
281 |
282 | module.exports.assetsGenerator = assetsGenerator;
283 |
284 | module.exports.assetsDeployer = assetsDeployer;
--------------------------------------------------------------------------------