日,我的一位同事向我寻求建议,她打算为自己构建一个博客。于是,我对静态网站生成器和博客引擎进行了一番研究,发现 Hugo 是一个很不错的选择。但是,我的同事还有一些特殊要求,比如,她想要一个自定义的博客网址和 CSS 主题。尽管这些 Hugo 都可以实现,但我并不打算花时间来学习它。我想自己创建一个简单的静态网站生成器,以便我的同事在她已经准备好的 HTML 中编写博客文章。
这个静态网站生成器的代码大约 100 行,非常简洁。它提供了详细代码和示例博客 。众所周知,GitLab 提供静态页面的免费托管服务,还带有 CI/CD 功能,它允许你在部署之前编译页面。
以下教程将带你使用 Node.js 设置自己的静态网站生成器,Node.js 的版本需要 “>=8.11.x”。
npm init npm i --save-exact bluebird chokidar fs-extra mustache mkdir src mkdir public
首先,设置项目:
开始之前,我们需要弄清楚一个问题:为什么需要静态网站生成器?因为某些情况并不需要静态网站生成器。假如你的博客访问量很小,你只需简单地手工创建 HTML 页面并发布它们即可。实际上,在服务器编程兴起之前,在很长时间内这就是大多数 Web 的发布方式。但是,一旦页面和内容增加,对这些页面中的通用部分(例如页面底部)进行更改将会变得非常重复和乏味。因此,我们开始寻找一种更加理想的方法,尝试使用某种简单的模板引擎来分离常见内容,然后在特定的地方插入所需的内容。
开始研究模板引擎之前,先设置我们的网站。我们需要在项目根目录下创建 2 个文件夹 :
我们的目标是将 src 目录的内容复制到 public 目录中。在项目根目录下创建 index.js 文件,其内容如下:
const Promise=require("bluebird"); const fse=require("fs-extra"); Promise.resolve().then(async ()=> { await main(); }); const main=async()=> { await generateSite(); }; const generateSite=async()=> { await copyAssets(); }; const copyAssets=async()=> { await fse.emptyDir("public"); await fse.copy("src", "public"); };
执行命令 node index.js,即可启动该脚本。
祝贺你!此刻,你已荣升为一名后端开发人员。
接下来,我们将添加文件监视器,src 文件夹中的内容一旦发生更改就将重新生成网站。该博客总共包含 500-1000 个文件,我们可以在任何变化发生时重新生成整个网站:
const chokidar=require("chokidar"); const main=async()=> { await generateSite(); watchFiles(); }; const watchFiles=()=> { const watcher=chokidar.watch( [ "src" ], { ignored: /(^|[/\])../, // chokidar will watch folders recursively ignoreInitial: false, persistent: true } ); watcher.on("change", async path=> { console.log("changed " + path + ", recompiling"); await generateSite(); }); // catch ctrl+c event and exit normally process.on("SIGINT", function() { watcher.close(); }); };
上面的代码清楚地说明了为什么初始版本有一个名为 generateSite 的函数。现在执行命令 node index.js 启动我们的静态网站生成器,如果在 src 目录中编辑任何文件,public 都会发生变化。此时,我们还将添加一个环境变量来区分开发和生产模式。在开发模式中,我们将关注更改情况并重新生成网站,而在生产模式中,我们只需重新生成:
const env=process.env.NODE_ENV || "dev"; const main=async ()=> { console.log("Running app in " + env); await generateSite(); if (env==="dev") { watchFiles(); } };
我们可以执行命令 export NODE_ENV=prod || set NODE_ENV=prod && node index.js 来运行以上代码。请注意,观察源目录的更改和重新编译并不是每次都必须的,你可以跳过此步骤,只需在每次进行更改时运行脚本即可。
至此,差不多完成了!现在来说说模板。我们将使用 Mustache.js 模板,它非常简单易用,并且我们的需求并不复杂。创建一个文件夹 src/partials,用来存放网站的公共部分。然后稍微修改我们的网站结构,保证所有页面都存放在 src/pages 目录中。接下来加载页面并使用 Mustache 渲染:
const fs=require("fs"); const generateSite=async ()=> { await copyAssets(); await buildContent(); }; const buildContent=async ()=> { const pages=await compilePages(); await writePages(pages); }; const compilePages=async ()=> { const partials=await loadPartials(); const result={}; const pagesDir=path.join("src", "pages"); const fileNames=await fs.readdirAsync(pagesDir); for (const fileName of fileNames) { const name=path.parse(fileName).name; const fileContent=await fs.readFileAsync(path.join(pagesDir, fileName)); result[name]=Mustache.render(fileContent.toString(), {}, partials); } return result; }; const loadPartials=async ()=> { const result={}; const partialsDir=path.join("src", "partials"); const fileNames=await fs.readdirAsync(partialsDir); for (const fileName of fileNames) { const name=path.parse(fileName).name; const content=await fs.readFileAsync(path.join(partialsDir, fileName)); result[name]=content.toString(); } return result; }; const writePages=async pages=> { for (const page of Object.keys(pages)) { await fs.writeFileAsync(path.join("public", page + ".html"), pages[page]); } };
想要了解最终版本,请查看 Software Dawg 项目(https://gitlab.com/wheresvic/software-dawg)。它与本教程有一些细微差别:
此外,你还可以安装 browser-sync 软件包,然后通过命令 npm run live-reload 运行它,如此一来,只要有任何更改发生浏览器就会自动刷新。请注意,由于任何更改都将重新生成整个网站,因此并不适用于 Windows。
GitLab 提供静态网站免费托管,只需一个 .gitlab-ci.yml 配置文件即可。真正令人难以置信之处在于,你可以自定义构建过程,这意味着在该例中,我们可以在部署之前生成网站!有关此功能的详细信息,请参见https://about.gitlab.com/features/pages/。
本教程到此结束,我的同事对此非常满意,该方案非常灵活,它允许她根据自己的喜好进行自定义,也希望对你有所助益!
原文:https://smalldata.tech/blog/2018/08/16/building-a-simple-static-site-generator-using-node-js
作者简介:Victor Parmar,是一位全栈工程师,热爱旅行,热爱 DIY。
译者:安翔,责编:屠敏
信息加速发展的互联网时代,越来越多的科技公司为了专注核心竞争力业务以及降低软件项目成本,开始将项目中的部分业务模块分发给第三方外包公司来完成。而这样是否就意味着大幅度地降低成本了?
事实告诉我们,并没有。
本文作者作为一名外包商,以自身的经历告诉我们本可以在3天之内完成了的一个报价仅为 1500 美元的静态 HTML 页面,是如何被大型企业硬是拖成了一个为期 7 周且需要耗费 18000 美元项目的。
作者 | Ibrahim Diallo
译者 | 王艳妮,责编 | 屠敏
出品 | CSDN(ID:CSDNnews)
不久前,我作为承包商工作,经常从一个项目跳到另一个项目。有些是短期的,工作一周左右,可很快提交我的工作成果。也有的项目会持续几个月,这期间我会攒一些钱用以休息一段时间。我更喜欢短期工作,因为这样的工作使我可以在单位时间内收取更高的费用。这样不仅我感觉是在为自己打工,而且我觉得我不需要太努力工作就能过上还算体面的生活了。我的最高费率仍然在合理的范围之内,而且我总是提供高质量的服务。这就是我和一家大公司定下这个项目之前我的工作状态。
这家公司联系我的时候显得很着急,经理告诉我他们现在就需要一个人来搞定这件事。需要一个不怎么需要公司培训就能马上上手,而且能交付最大性能的人。不管怎么说,这刚好是我的座右铭。这个项目正是我喜欢的工作类型。它内容简短,很快就能做好,而且报酬很高。
在谈判确定好合适的费率后,我收到了一封包含说明的电子邮件。他们给了我更多关于这个项目的背景。他们的开发人员在没有事先告知的情况下就离开了,并且从未跟任何其他人汇报过项目的进展。
我们需要您毫不分心地完成此项目。在合同期限内,您将只与我们合作,并及时交付成果。我们会对给您造成的麻烦进行补偿。
任务说明很简单:阅读这些需求然后估计完成这个项目需要多长时间。这是我职业生涯中遇到的一个那类比较容易的项目之一。这是一个HTML页面,包含一些简单的动画和几个嵌入的视频。我花了一个晚上研究需求并在脑中模拟实施。这些年来,我已经学会了在能确定收到报酬之前不为客户写任何代码。
我确定了这个项目充其量也就是一天的活儿。但为了保持谨慎,我上报了20个小时,总计1500美元。毕竟这只是一个HTML页面而已,我也只能收取这么多费用。他们让我到25英里外的卫星办公室去。在为他们工作的那三天里我必须天天开车去那儿。
第二天,我到了卫星办公室。在一个购物中心,然后通过一扇秘密的门进入了一个秘密的世界,一些工作人员在他们的小隔间里安静地工作着。接待员给我看了一个我将用它来工作的全新MacBook Pro,我必须从零开始设置环境。我的确更偏向于使用公司的笔记本电脑,因为他们经常要求承包商安装一些可疑的软件。(我可不想装到自己电脑上。)
我花了一天时间下载我的工具包,设置电子邮件、ssh密钥和请求服务的授权。换句话说,我什么都没做。这就是为什么我上报了20个小时,还没开始写代码呢,光前期设置就耗费了8个小时。
第二天,我准备开始真正地干活了。有了MacBook Pro,我用它发了一封电子邮件给经理。我告诉他我已经准备好工作了,正在等待上述的资源。那天,我在我柔和灯光下的工位上待着,玩着手指,直到太阳落山。
我再次计算了一下。根据我的估计,我还只剩4个小时的时间来完成这项工作,这对单个HTML页面来说也不是不可能。但不用说,第二天,我把这剩下的4个小时花在了吃公司赞助的午餐上,伙食很不错,而且我与其他员工玩得很开心。
当预计的20小时到期时,我确保向经理发送了另一封电子邮件,让他知道我确实人一直在公司,但我没有收到我需要的资源。当然,那封电子邮件被无视了。
接下来的星期一,我犹豫地开过了这25英里。令我惊讶的是,经理已经来到卫星办公室,并热情地问候了我。他是个三十来岁,很随和很不错的人。我很不解,他并不像当初要雇我的那时候那么着急了。我们进行了友好的交谈,没有提到任何工作。后来,我们去吃午餐,他付了钱。这是美好的一天。完全没工作。
好吧你可以说我很容易形成习惯,但如果你供我吃喝并每天呵护我,我会习惯这一切。这变成了一个例程。我来上班,花一些时间在网上阅读以及看视频。我每天发一封电子邮件,所以他们知道我确实去了公司。然后,我会去吃午饭并和碰见的有趣的人一起玩耍。在一天结束时,我站起来,伸个懒腰,打一个当之无愧的哈欠,然后开车回家。
我习惯了。事实上,我在期待这些。当我终于收到一封带有指向我需要的资源的链接的电子邮件时,我反而有点失望。我重新开始脚踏实地,变回自己工作时的严肃脸。但是,在花了几分钟查看zip文件后,我才注意到它缺少了我需要的大部分内容。设计师给我发了一些Adobe Illustrator文件,我无法在MacBook上打开它。
我回复了电子邮件来解释我的疑虑,而且一并问了一些其他问题以节省时间。那时,我当初上报的20个小时时间早都已经过了。我现在真的想要完成这项工作了。点击发送后不久,我收到了一封电子邮件。只有一句:“转发给Alex”,然后Alex得到了这封电子邮件的抄送。Alex回答说他转发给了Steve。Steve回答说Michelle是设计师,她会了解得更多一些。 Michelle的自动回复称她正在度假,所有询问都应该直接告诉她的经理。她的经理回复说“谁是Ibrahim?(我的名字)”我的经理回复说他很抱歉还没有向大家介绍我。
作为承包商,在人们注意到我在那里工作之前,我通常就已经完成我的工作并离开那家公司了。但这次,我收到了大量欢迎的电子邮件。这样的邮件持续了一段时间,而我被迫回复那些友好地过了头的邮件。有些人很想跟我本人见面。当我说我在加利福尼亚州,离得远着呢,他们有点失望。以及羡慕,他们说他们羡慕加州美好的天气。
他们很有礼貌地无视我的电子邮件。他们用抄送来转移我的问题。他们把我问过的任何事情归为垃圾邮件。我花了很多时间,像一位考古学家在深深的电子邮件之沟内挖掘,希望找到我问题的答案。你可以想象每当我想起我唯一的任务是构建一个静态HTML页面时,我感觉到的冒名顶替综合症(心虚,怀疑自己的回报不是理所应得的)的程度之深。原本虚报了的20个小时的项目变成了为期7周的冒险,期间我享受免费午餐,每天开车50英里,并翻看电子邮件。
当我最终完成项目时,我在GitHub上将它发送给了团队。所有伟大的冒险都必须有个尽头。但不久之后,我收到了邀请,整个团队会用Google Hangout开视频会议对我的代码进行code review。我花了一个多月的时间来写一个静态HTML页面,而现在整个团队都要评价我的工作?那个什么,我要为自己说句话,这个页面也包含一些JavaScript交互,是响应式的,还包括CSS动画......好吧我真的觉得自己像个来冒名顶替的。
当然,视频会议的时间又重新安排了几次。当它终于发生时,我和我的工作已经不是会议的主题了。他们都坐在纽约某个地方的同一个房间里,像一个紧密团结的团体一样聊了一会儿。事实上,他们所说的关于我做的项目的所有内容只有:
那天晚上回家的时候,我意识到自己正面临另一个挑战。我在这家公司工作了7个星期,而我的原始报价为1,500美元。这相当于每年11,100美元或每周214美元。或者直接说,每小时5.35美元。
这几乎还不够我付油钱的。所以,我给他们发了一张发票,我按照原来的每小时费率给他们报了7个星期。总额达18,000美元。我当然感到羞耻,但我还能怎么办呢?
就像我预期的那样,我没有收到回复。如果所有大公司都有什么相同之处,那就是他们并不急于按时支付账单。这么简单的工作要价这么多,我觉得自己像一个骗子,但话又说回来了,我又不是来做慈善的。我每天开车50英里来做这项工作,如果工作没有完成,那不是因为我不想。这是因为他们回复太缓慢了。
接下来的一周我得到了回复。这是一封来自经理的冷邮件,他把我每天的工作日分成不同的时间段。然后他把我工作的那部分时间高亮了,每天标记一个小时的午休时间。最后他用我们商定的小时费率做了一些计算。
显然,我算错了。我错误估算了总数。调整后,他们欠我的总金额是21,000美元。
请确认重新调整后的小时数,以便财务可以给您写个支票。
我很快回复了确认。
原文:https://idiallo.com/blog/18000-dollars-static-web-page
作者简介:Ibrahim Diallo,具有多年开发经验的软件工程师。
本文为 CSDN 翻译,转载请注明来源出处。
之前我介绍了在spring boot中使用thymeleaf模板,这次我会给大家介绍在spring boot中使用freemarker模板技术,同时利用freemarker生成静态html页面。生成静态html页面就能实现网站的静态化进而提高网站的访问速度以及提高SEO能力。
首先在pom.xml中添加依赖
添加依赖
<dependency> <groupId>org.freemarker</groupId> <artifactId>freemarker</artifactId> <version>2.3.23</version> </dependency>
application配置
在application.properties中添加freemarker的配置参数
##freemarker spring.freemarker.cache=false spring.freemarker.charset=UTF-8 spring.freemarker.check-template-location=true spring.freemarker.content-type=text/html spring.freemarker.enabled=true spring.freemarker.suffix=.ftl spring.freemarker.template-loader-path=classpath:/templates
Controller和ftl模板
下一步我们就建一个基础Controller类和配套的ftl模板
Controller类
package com.hw.myp2c.common.controller; import freemarker.template.Configuration; import freemarker.template.Template; import freemarker.template.TemplateException; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import javax.annotation.Resource; import java.io.*; import java.net.URISyntaxException; import java.util.HashMap; import java.util.Map; @Controller @RequestMapping("") public class MainController { @GetMapping public String main(Model model){ String w="Welcome FreeMarker!"; Map root=new HashMap(); root.put("w",w); model.addAttribute("w","Welcome FreeMarker!"); return "test"; } }
可以看到很简单,跟之前的thymelefa和jsp的没有区别。
freemarker模板
<html> <head> <title>Welcome!</title> <link rel="stylesheet" href="/bootstrap.min.css"> <script src="/lib/jquery.min.js"></script> </head> <body> <h1>Hello ${w}!</h1> </body> </html>
这样之后我们就能完成了基础freemarker的使用,更多的使用参见freemarker官方网站,这里不做过多的描述。
这里我们已经完成了标准的freemarker集成,下面我们将介绍如何利用freemarker生成静态html页面,直接上代码,作为演示我们还是在Controller中完成,在实际应用中我们可以按照自己的实际需要进行封装。
package com.hw.myp2c.common.controller; import freemarker.template.Configuration; import freemarker.template.Template; import freemarker.template.TemplateException; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import javax.annotation.Resource; import java.io.*; import java.net.URISyntaxException; import java.util.HashMap; import java.util.Map; @Controller @RequestMapping("") public class MainController { @Resource Configuration cfg; @GetMapping public String main(Model model){ String w="Welcome FreeMarker!"; Map root=new HashMap(); root.put("w",w); freeMarkerContent(root); model.addAttribute("w","Welcome FreeMarker!"); return "test"; } private void freeMarkerContent(Map<String,Object> root){ try { Template temp=cfg.getTemplate("test.ftl"); //以classpath下面的static目录作为静态页面的存储目录,同时命名生成的静态html文件名称 String path=this.getClass().getResource("/").toURI().getPath()+"static/test.html"; Writer file=new FileWriter(new File(path.substring(path.indexOf("/")))); temp.process(root, file); file.flush(); file.close(); } catch (IOException e) { e.printStackTrace(); } catch (TemplateException e) { e.printStackTrace(); } catch (URISyntaxException e) { e.printStackTrace(); } } }
利用freemarker生成静态页面我理解的流程是这样的
1.利用Configuration读取想生成静态页面的模板,这里是test.ftl
2.解析模板文件,并将模板中的${}!包含的参数替换成真实的数据
3.最终将读取了真实数据的模板生成相应的html文件,并写入指定目录
这样我们就完成了spring boot中使用freemarker模板,并且利用freemarker生成静态html文件
*请认真填写需求信息,我们会在24小时内与您取得联系。