摘要:代碼如下首頁的模版博客網站的基本配置菜單生成,這里不講講中的遍歷,然后生成一個數組默認按發布時間排序置頂替換五集成在編譯博客的過程中,一些操作利用會簡單快捷許多。
文章較長,耐心讀下來我想你肯定會有所收獲 : )
作為一個技術人員,見到別人那光鮮亮麗的個人博客,心里總免不了想搭建自己博客的沖動。當然,搭建博客的方式有好多種,但是大體上分這兩種:
服務端數據庫
例如:你可以用 WordPress 搭建自己的博客,你可以利用 PHP 和 MySQL 數據庫在服務器上架設屬于自己的網站。
純靜態頁面
市面上有挺多的免費 靜態文件(HTML)托管機構,當然其中最簡單,最方便的可能就是 Github Pages 了。純靜態文件構建的網站有很多的優點,比如靜態網頁的訪問速度比較快、容易被搜索引擎檢索等。
當然,僅僅用作博客的話,純靜態頁面足夠使用了。評論系統的話可以用第三方的插件,比如 Disqus。
Github PagesGithub Pages 是Github提供的一個靜態文件托管系統,配合Github倉庫,使用起來特別方便。如果你不會使用的話,請看這里。
而且,Github Pages 集成了 Jekyll,可以自動幫你把 markdown 語法編譯成漂亮的 html 頁面。
市面上有很多的博客生成工具,可以跟 Github pages 很好的結合,像是 Hexo。其實本質上很簡單,Hexo就是幫你把 markdown 編譯成了 html,
并且幫你生成了完善的目錄和路由。
通過一篇文章很難把整個工具描述的一清二楚,所以先放源代碼在這里。源代碼通過我們寫的工具可以作出的博客效果是這樣的:http://isweety.me/
我們得知了博客生成的本質,那么動手做出一個博客生成工具也就沒有那么大的難度了。我們先來梳理一下博客生成工具需要有哪些最基本的功能:
markdown 編譯成 html
我們寫博客,如果自己去寫html的話,那怕會被累死。。 Markdown 語法幫我們解決了這個問題,如果你對markdown不了解的話,可以看這里。
生成目錄結構
我們想一下,確實,一個博客的話最基本的就兩個部分:目錄和博客內容。我們模仿Hexo的命令,設計如下:
我們把工具命名為 Bloger。
bloger init blog # 初始化一個名為blog的博客項目 bloger new hello-world # 創建一篇名為 hello-word 的博客 bloger build # 編譯博客網站 bloger dev # 監聽 markdown 文件,實時編譯博客網站 bloger serve # 本地起服務
按照以上的設計,我們開始寫工具:
一、目錄設計我們需要為我們 生成的博客項目 設計一個合理的文件目錄。如下:
blog ├── my.json (網站的基本配置) ├── index.html (首頁) ├── node_modules ├── package.json ├── _posts (博客 markdown 源文件) │ └── 2018 │ ├── test.md │ └── hello-world.md ├── blog (_posts 中 markdown 生成的 html 文件) │ └── 2018 │ ├── test │ │ └──index.html (這樣設計的話,我們就可以通過訪問 https://xxx.com/blog/2018/test/ 來訪問這篇博客了) │ └── hello-world │ └──index.html └── static (博客 markdown 源文件) ├── css (網站的css存放的文件) ├── iconfonts (網站的 iconfonts 存放的文件夾) ├── images (網站的圖片存放的文件夾) └── less (存放于這兒的 less 文件,會在 dev 的時候被編譯到 css 文件夾中,生成同名的 css 文件)
下面是我們寫的工具的源碼結構:
bloger ├── bin │ └── cli.js ├── lib │ ├── less (博客的樣式文件) │ ├── pages (博客的ejs模版) │ ├── tasks (編譯網站的腳本) │ └── gulpfile.js └── tpl (生成的博客模版,結構見上方)二、markdown編譯成html
markdown編譯成html,有許多成熟的庫,這里我們選用 mdpack。這個項目其實是在marked上的一層封裝。
mdpack 支持模版定制,支持多markdown拼接。
一篇文章有很多的信息需要我們配置,比如 標題、標簽、發布日期 等等,Hexo 和 Jekyll 通常有一個規范是這樣的,在markdown文件的頂部放置文章的配置,
front-matter 格式如下:
--- title: Hello world date: 2018-09-10 tag: JavaScript,NodeJs info: 這篇文章簡單介紹了寫一個博客生成工具. ---
我們需要寫個腳本將這些信息提取,并且轉換成一個json對象,比如上邊的信息,我們要轉換成這樣:
{ "title": "Hello world", "date": "2018-09-10", "tag": "JavaScript,NodeJs", "info": "這篇文章簡單介紹了寫一個博客生成工具." }
腳本如下:
// task/metadata.js const frontMatter = require("@egoist/front-matter"); // 截取頭部front-matter信息 const fs = require("fs"); const path = require("path"); const root = process.cwd(); const metadata = { post: [] }; // 把提取出來的front-matter字符串解析,生成對象 function getMetadata(content) { const head = frontMatter(content).head.split(" "); const ret = {}; head.forEach((h) => { const [key, value] = h.split(": "); ret[key.trim()] = value.trim(); }); if (!ret.type) { ret.type = "原創"; } return ret; } try { // 便利 _posts 文件夾,將所有的markdown內容的front-matter轉換成對象,存放到metadata數組中 // 將生成的metadata信息寫入一個文件中,我們命名為postMap.json,保存到所生成項目的根目錄,以備使用 fs.readdirSync(path.resolve(root, "_posts")) .filter(m => fs.statSync(path.resolve(root, "_posts", m)).isDirectory()) .forEach((year) => { fs.readdirSync(path.resolve(root, "_posts", year)) .forEach((post) => { const content = fs.readFileSync(path.resolve(root, "_posts", year, post), "utf8"); metadata.post.push({ year, filename: post.split(".md")[0], metadata: getMetadata(content) }); }); }); fs.writeFileSync(path.resolve(root, "postMap.json"), JSON.stringify(metadata), "utf8"); } catch (err) {} module.exports = metadata;四、博客目錄生成
通過讀取postMap.json中的metadata信息,我們可以構建一個博客目錄出來。代碼如下:
const fs = require("fs-extra"); const path = require("path"); const ejs = require("ejs"); // 首頁的ejs模版 const homeTpl = fs.readFileSync(path.resolve(__dirname, "../pages/home.ejs"), "utf8"); const root = process.cwd(); function buildHomeHtml() { const metadata = require("./metadata"); // 博客網站的基本配置 const myInfo = require(path.resolve(root, "my.json")); const htmlMenu = require("./menu")(); // 菜單生成,這里不講 // 講postMap.json中的metadata遍歷,然后生成一個blogList數組 const blogList = metadata.post.map((postInfo) => { const data = postInfo.metadata; return { title: data.title, date: data.date, url: `/blog/${postInfo.year}/${postInfo.filename}`, intro: data.intro, tags: data.tag.split(","), author: data.author, type: data.type, top: data.top === "true" ? true : false }; }); // 默認按發布時間排序 blogList.sort((a, b) => new Date(a.date) - new Date(b.date)); // 置頂 blogList.sort((a, b) => !a.top); // ejs替換 fs.outputFile( path.resolve(root, "index.html"), ejs.render(homeTpl, { name: myInfo.name, intro: myInfo.intro, homepage: myInfo.homepage, links: myInfo.links, blogList, htmlMenu }), (err) => { console.log(" Upadate home html success! "); } ); } module.exports = buildHomeHtml;五、集成gulp
在編譯博客的過程中,一些操作利用 gulp 會簡單快捷許多。比如 編譯less、打包iconfonts、監聽文件改動 等。
但是gulp是一個命令行工具,我們怎么樣能把gulp繼承到我們的工具中呢?方法很簡單,如下:
const gulp = require("gulp"); require("./gulpfile.js"); // 啟動gulpfile中的build任務 if(gulp.tasks.build) { gulp.start("build"); }
通過以上的方法,我們可以在我們的cli工具中集成 gulp,那么好多問題就變得特別簡單,貼上完整的 gulpfile:
const fs = require("fs"); const path = require("path"); const url = require("url"); const del = require("del"); const gulp = require("gulp"); const log = require("fancy-log"); const less = require("gulp-less"); const minifyCSS = require("gulp-csso"); const autoprefixer = require("gulp-autoprefixer"); const plumber = require("gulp-plumber"); const iconfont = require("gulp-iconfont"); const iconfontCss = require("gulp-iconfont-css"); const mdpack = require("mdpack"); const buildHome = require("./tasks/home"); const root = process.cwd(); // 編譯博客文章頁面 function build() { const metadata = require(path.resolve(root, "postMap.json")); const myInfo = require(path.resolve(root, "my.json")); const htmlMenu = require("./tasks/menu")(); // 跳過 // 刪除博客文件夾 del.sync(path.resolve(root, "blog")); // 遍歷_posts文件夾,編譯所有的markdown文件 // 生成的格式為 blog/${year}/${filename}/index.html fs.readdirSync(path.resolve(root, "_posts")) .filter(m => fs.statSync(path.resolve(root, "_posts", m)).isDirectory()) .forEach((year) => { fs.readdirSync(path.resolve(root, "_posts", year)) .forEach((post) => { const filename = post.split(".md")[0]; const _meta = metadata.post.find(_m => _m.filename === filename).metadata; const currentUrl = url.resolve(myInfo.homepage, `blog/${year}/${filename}`); const mdConfig = { entry: path.resolve(root, "_posts", year, post), output: { path: path.resolve(root, "blog", year, filename), name: "index" }, format: ["html"], plugins: [ // 去除markdown文件頭部的front-matter new mdpack.plugins.mdpackPluginRemoveHead() ], template: path.join(__dirname, "pages/blog.ejs"), resources: { markdownCss: "/static/css/markdown.css", highlightCss: "/static/css/highlight.css", title: _meta.title, author: _meta.author, type: _meta.type, intro: _meta.intro, tag: _meta.tag, keywords: _meta.keywords, homepage: myInfo.homepage, name: myInfo.name, disqusUrl: myInfo.disqus ? myInfo.disqus.src : false, currentUrl, htmlMenu } }; mdpack(mdConfig); }); }); } // 編譯css gulp.task("css", () => { log("Compile less."); // 我們編譯當前項目下的 lib/less/*.less 和 生成的博客項目下的 static/less/**/*.less return gulp.src([path.resolve(__dirname, "less/*.less"), path.resolve(root, "static/less/**/*.less")]) .pipe(plumber()) .pipe(less({ paths: [root] })) // css壓縮 .pipe(minifyCSS()) // 自動加前綴 .pipe(autoprefixer({ browsers: ["last 2 versions"], cascade: false })) // 將編譯生成的css放入生成的博客項目下的 static/css 文件夾中 .pipe(gulp.dest(path.resolve(root, "static/css"))); }); // 監聽css文件的改動,編譯css gulp.task("cssDev", () => { log("Starting watch less files..."); return gulp.watch([path.resolve(__dirname, "less/**/*.less"), path.resolve(root, "static/less/**/*.less")], ["css"]); }); // 監聽markdown文件的改動,編譯首頁和博客文章頁 gulp.task("mdDev", () => { log("Starting watch markdown files..."); return gulp.watch(path.resolve(root, "_posts/**/*.md"), ["home", "blog"]); }); // 編譯首頁 gulp.task("home", buildHome); // build博客 gulp.task("blog", build); gulp.task("default", ["build"]); // 監聽模式 gulp.task("dev", ["cssDev", "mdDev"]); // 執行build的時候會編譯css,編譯首頁,編譯文章頁 gulp.task("build", ["css", "home", "blog"]); // 生成iconfonts gulp.task("fonts", () => { console.log("Task: [Generate icon fonts and stylesheets and preview html]"); return gulp.src([path.resolve(root, "static/iconfonts/svgs/**/*.svg")]) .pipe(iconfontCss({ fontName: "icons", path: "css", targetPath: "icons.css", cacheBuster: Math.random() })) .pipe(iconfont({ fontName: "icons", prependUnicode: true, fontHeight: 1000, normalize: true })) .pipe(gulp.dest(path.resolve(root, "static/iconfonts/icons"))); });六、cli文件
我們已經把gulpfile寫完了,下面就要寫我們的命令行工具了,并且集成gulp。代碼如下:
// cli.js #!/usr/bin/env node const gulp = require("gulp"); const program = require("commander"); // 命令行參數解析 const fs = require("fs-extra"); const path = require("path"); const spawn = require("cross-spawn"); const chalk = require("chalk"); const dateTime = require("date-time"); require("../lib/gulpfile"); const { version } = require("../package.json"); const root = process.cwd(); // 判斷是否是所生成博客項目的根目錄(因為我們必須進入到所生成的博客項目中,才可以執行我們的build和dev等命令) const isRoot = fs.existsSync(path.resolve(root, "_posts")); // 如果不是根目錄的話,輸出的內容 const notRootError = chalk.red(" Error: You should in the root path of blog project! "); // 參數解析,正如我們上面所設計的命令用法,我們實現了以下幾個命令 // bloger init [blogName] // bloger new [blog] // bloger build // bloger dev // bloger iconfonts program .version(version) .option("init [blogName]", "init blog project") .option("new [blog]", "Create a new blog") .option("build", "Build blog") .option("dev", "Writing blog, watch mode.") .option("iconfonts", "Generate iconfonts.") .parse(process.argv); // 如果使用 bloger init 命令的話,執行以下操作 if (program.init) { const projectName = typeof program.init === "string" ? program.init : "blog"; const tplPath = path.resolve(__dirname, "../tpl"); const projectPath = path.resolve(root, projectName); // 將我們的項目模版復制到當前目錄下 fs.copy(tplPath, projectPath) .then((err) => { if (err) throw err; console.log(" Init project success!"); console.log(" Install npm packages... "); fs.ensureDirSync(projectPath); // 確保存在項目目錄 process.chdir(projectPath); // 進入到我們生成的博客項目,然后執行 npm install 操作 const commond = "npm"; const args = [ "install" ]; // npm install spawn(commond, args, { stdio: "inherit" }).on("close", code => { if (code !== 0) { process.exit(1); } // npm install 之后執行 npm run build,構建博客項目 spawn("npm", ["run", "build"], { stdio: "inherit" }).on("close", code => { if (code !== 0) { process.exit(1); } // 構建成功之后輸出成功信息 console.log(chalk.cyan(" Project created! ")); console.log(`${chalk.cyan("You can")} ${chalk.grey(`cd ${projectName} && npm start`)} ${chalk.cyan("to serve blog website.")} `); }); }); }); } // bloger build 執行的操作 if (program.build && gulp.tasks.build) { if (isRoot) { gulp.start("build"); } else { console.log(notRootError); } } // bloger dev執行的操作 if (program.dev && gulp.tasks.dev) { if (isRoot) { gulp.start("dev"); } else { console.log(notRootError); } } // bloger new 執行的操作 if (program.new && typeof program.new === "string") { if (isRoot) { const postRoot = path.resolve(root, "_posts"); const date = new Date(); const thisYear = date.getFullYear().toString(); // 在_posts文件夾中生成一個markdown文件,內容是下邊的字符串模版 const template = `--- title: ${program.new} date: ${dateTime()} author: 作者 tag: 標簽 intro: 簡短的介紹這篇文章. type: 原創 --- Blog Content`; fs.ensureDirSync(path.resolve(postRoot, thisYear)); const allList = fs.readdirSync(path.resolve(postRoot, thisYear)).map(name => name.split(".md")[0]); // name exist if (~allList.indexOf(program.new)) { console.log(chalk.red(` File ${program.new}.md already exist! `)); process.exit(2); } fs.outputFile(path.resolve(postRoot, thisYear, `${program.new}.md`), template, "utf8", (err) => { if (err) throw err; console.log(chalk.green(` Create new blog ${chalk.cyan(`${program.new}.md`)} done! `)); }); } else { console.log(notRootError); } } // bloger iconfonts執行的操作 if (program.iconfonts && gulp.tasks.fonts) { if (isRoot) { gulp.start("fonts"); } else { console.log(notRootError); } }
完整的項目源代碼:https://github.com/PengJiyuan...相關閱讀:手把手教你寫一個命令行工具
本章完
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/97673.html
摘要:在我轉前端以來,一直想要實現一個愿望自己搭建一個可以自動解析文檔的個人站今天終于實現啦,先貼上我的地址確認需求其實一個最簡單的個人站,就是許多的頁面,你只要可以用寫出來就可以,然后掛到上。 在我轉前端以來,一直想要實現一個愿望: 自己搭建一個可以自動解析Markdown文檔的個人站 今天終于實現啦,先貼上我的blog地址 確認需求 其實一個最簡單的個人站,就是許多的HTML頁面,你只要...
摘要:重構系統是一項非常具有挑戰性的事情。架構與說起來,我一直是一個黨。如下圖是采用的架構這與我們在項目上的系統架構目前相似。而這是大部分所不支持的。允許內容通過內容服務更新使用于是,有了一個名為的框架用于管理內容,并存儲為。 重構系統是一項非常具有挑戰性的事情。通常來說,在我們的系統是第二個系統的時候才需要重構,即這個系統本身已經很臃腫。我們花費了太量的時間在代碼間的邏輯,開發新的功能變得...
摘要:添加你修改的代碼找到你主題文件夾里的對應位置以我的路徑為例子里面有個文件夾和一個文件,主要用于打包代碼輸出成樣式的文件分析下其源代碼。注意本人不提倡去修改除了下的其他個文件里的源代碼,可能后面出問題不好還原。 showImg(https://segmentfault.com/img/remote/1460000008744124?w=1920&h=1280); 前言 之前答應一個評論朋...
摘要:添加你修改的代碼找到你主題文件夾里的對應位置以我的路徑為例子里面有個文件夾和一個文件,主要用于打包代碼輸出成樣式的文件分析下其源代碼。注意本人不提倡去修改除了下的其他個文件里的源代碼,可能后面出問題不好還原。 showImg(https://segmentfault.com/img/remote/1460000008744124?w=1920&h=1280); 前言 之前答應一個評論朋...
摘要:添加你修改的代碼找到你主題文件夾里的對應位置以我的路徑為例子里面有個文件夾和一個文件,主要用于打包代碼輸出成樣式的文件分析下其源代碼。注意本人不提倡去修改除了下的其他個文件里的源代碼,可能后面出問題不好還原。 showImg(https://segmentfault.com/img/remote/1460000008744124?w=1920&h=1280); 前言 之前答應一個評論朋...
閱讀 3560·2021-10-09 09:43
閱讀 6177·2021-09-07 10:15
閱讀 2757·2019-08-30 14:03
閱讀 3088·2019-08-29 11:01
閱讀 1724·2019-08-29 10:56
閱讀 1089·2019-08-28 17:52
閱讀 3508·2019-08-26 11:42
閱讀 2564·2019-08-26 10:33