《赖田捕手:番外篇》第 36 天:用 Netlify 布署前端网页 (一)

《赖田捕手:番外篇》第 36 天:用 Netlify 布署前端网页 (一)

那麽,你说你要一个完全免费而且反应迅速的 Line Bot。好,我告诉你一个,别再跟我要求更多。现在时间已经不早了,我们明天可还有一大段旅程得走。

~节录自《而 Netlify 的回音荡漾》

延伸自系列文《从LINE BOT到资料视觉化:赖田捕手》

  最近本土疫情严峻,为了能尽快将疫情平息下来,重拾往日无拘无束的生活,大家各自宅在家里避免不必要的外出,「耍废救台湾」这一句话变得琅琅上口起来。宅在家里归宅在家里,不过有些人即使宅在家也耍不了废,总是得找点事情做才能让心静下来。这时候就不得不推荐这款居家旅行的必备嗜好:「撰写 Line Bot」。
  《赖田捕手:番外篇》预计会有五天份的文章内容,预计分成五个星期完成。在这次的内容当中,我们将会介绍一个全新的,既强大、又快速、又免费的平台:Netlify➀,一步一步讲解如何在这个平台上布署 Line Bot,并尝试利用这个平台给出的所有资源,实作一个可以根据使用者需求,做出客制化名片的 Line Bot。跟先前 35 篇文章相比,最大的差异就是这次我们要以 JavaScript 来撰写 Line Bot。

Line Bot 运作方式

  对於我们这种业余爱好者来说,「撰写 Line Bot」最苦恼的,并不是撰写的过程,而是准备好我们的 Webhook 服务器,也就是 Line Bot 运作逻辑存放的位置。什麽意思呢?这要从 Line Bot 的运作方式讲起,如图一。整个讯息传递的方式可以用邮差送信的概念来想。

https://ithelp.ithome.com.tw/upload/images/20210605/201201785IDoPgEzDe.png
图一、Line Bot 运作方式

当使用者向 Line Bot 传送讯息时

  事实上,使用者的讯息是先被送到了 Line 的官方服务器上,接着,再按照每一个 Line Bot 在 Line 官方系统中注册的 Webhook URL,将使用者的讯息送到 Line Bot 手中。此外,在讯息到达 Line Bot 的时候,系统会先用 Line Bot 所必须持有的 Channel Access Token 跟 Channel Secret 做一个验明正身的动作。确认无误之後,使用者所传送的讯息才会真正被 Line Bot 接收到。就像我们在日常生活中寄信时 (而且是挂号信),每一封信是先被汇整到邮局。接着,邮局的工作人员按照信上面的收件地址,将信再送出去。当邮差找到收件人时,会先请收件人拿出代表个人身份的印章,确认为本人後,信才会真正到达收件人手中。

当 Line Bot 向使用者传送讯息时

  同样的,Line Bot 向使用者传送讯息时,讯息也是先经过 Line 的官方服务器,接着才到使用者手中。这边就不多赘述。

  由於使用者通常期待 Line Bot 是 24 小时随传随到的,也就是说,当使用者的讯息从 Line 服务器转传过来时,我们的 Line Bot 要随时都能够表明身分并且给出回应,具体来说,我们须要自己准备一个 24 小时运行不间断的服务器才行。发现这有多麻烦了吗?光是撰写程序码还不够,我们必须得懂得架设并维护服务器。

云端运算平台

  幸好有各种云端平台为我们提供了服务器的服务。包括之前文章中介绍过的 Heroku➁,Amazon Web Service (AWS)➂,还有 Google 与微软各自推出的云端运算服务平台,这些平台的用意,就是提供一个储存并且运行程序码的地方,从此程序设计师只需要专注在撰写程序码上,而运行程序码、维护服务器等就交由云端运算服务平台来处理。不同的云端运算平台有各自的架构与运行方式,因此运行效能表现以及收费标准也有所不同,如图二

https://ithelp.ithome.com.tw/upload/images/20210605/20120178EqS6jpz1mp.png
图二、云端运算平台 Heroku、AWS、Netlify 比较

  这边就以最低费率或免费的方案来做比较。Heroku 为每一个帐号提供了每月 550 小时的免费 Dyno 时数,经由信用卡认证後每月免费 Dyno 时数增加到 1000 小时。也就是说,一个 Heroku 帐号每月最多免费 Dyno 时数为 1000 小时。这够用吗?让我们来算一下。一个月 31 天,共 744 小时。看起来好像很够。但注意一下,免费的 1000 小时必须分给同一个帐号底下的所有 App。也就是说,一个 Heroku 帐号,最多就养一个 24 小时不睡觉的 Line Bot。至於什麽叫做【30 分钟入睡限制】可以参考系列文第 13 天
  接着看看 AWS。AWS 提供了一种函式即服务 (Function as a Service,FaaS) 的云端运算方式:AWS Lambda。先不论 AWS 在介面设定上有些时候相当繁琐,从价格上来说,对我们这种业余玩家而言,AWS Lambda 可以说是一个非常接近免费的服务。首先,AWS 赠送给新帐号一年的免费方案,需要注意的包括 AWS Lambda 每月 100 万个免费请求以及每月 400,000 GB-秒的运算时间、Amazon API Gateway 每月 100 万次 API 呼叫接收。这够用吗?让我们来算一下。假设我们的 Line Bot 记忆体配置为 1 GB,平均每次运算时间为 10 秒,所以每接受一个使用者传来的讯息,需要花 10 GB-秒的运算时间,那麽一个月就可以免费接收 40,000 个讯息。其实是非常多了。所以在享有免费方案的这一年当中,我们基本不需要付钱的。那麽重点来了,结束免费方案之後,我们要付多少钱呢?AWS Lambda 的免费方案属於永远免费的类型,所以即使一年过後,我们每个月还是会有 100 万个免费请求以及 400,000 GB-秒的免费运算时间。然而 Amazon API Gateway 的免费方案则会到期,之後我们要为每 100 万次 API 呼叫接收付出约 1 美元的代价。恩,基本还是很便宜。

  那 Netlify 呢?这边我们可以不夸张地套句经典电影《让子弹飞》的经典台词:「Netlify 推出云端运算服务只办三件事:免费,免费,还是他 X 的免费。」

  Netlify Functions 背後的架构,其实是帮我们整合了 AWS Lambda 与 Amazon API Gateway,免除了在 AWS 上调整各种设定,好让不同服务串接在一起的困扰。同时也把 AWS那套烦人的价格计算方式屏除在遥远的门外,让我们可以安全地待在免费的避风港。说实在,以我程序设计师业余爱好者的身分来看,还找不出任何理由逼得我去使用 Netlify 的付费方案 (当然还是有付费方案的,不过这就等我们更了解 Netlify 运作模式之後再作解释)。
  好的,说了这麽多,那 Netlify 究竟能为我们做什麽,又是如何运作呢?简单来说,Netlify 可以帮我们布署前端的静态网站,也可以用 Netlify Functions 帮我们执行属於後端的程序码 (Line Bot 的运作属於这类),而且它提供的的布署方式相当特别:特别简单。我们今天就先从布署前端网页开始,来熟悉 Netlify 所提供的特别简单的布署方式吧。

拖放布署 (Netlify Drop)

  要使用 Netlify 之前,我们得先注册一个 Netlify 帐号。当然可以直接用个人信箱注册一个全新帐号。不过方便起见,我们也可以用 GitHub 帐号、GitLab 帐号、或是 Bitbucket 帐号来注册 Netlify。注册并登入帐号之後,就可以看到个人的工作看板,如图三。里面显示了各项数据用量,包括当月份所使用的 Bandwidth、Build time 等等,还可以看到该帐号最近的几笔 Build 历史纪录,以及近期透过 Netlify 发布出去的网站。

https://ithelp.ithome.com.tw/upload/images/20210605/20120178yTCfeB0Lrg.png
图三、Netlify 个人工作看板

  从工作看板的 Team overview 分页,切换到 Sites 分页,可以看到该帐号所有透过 Netlify 发布出去的网站。同时也可以开始布署新的网站。首先,我们就从拖放布署来试试。拖放布署 (Netlify Drop),顾名思义,就是将我们要布署的网页,所有的相关档案,包括 HTML 档、CSS 档、和 JavaScript 档全都准备在一个资料夹里,接着将该资料夹「拖放」(Drag and Drop) 到 Netlify 工作面板的 Sites 分页上就行。有没有这麽简单?就是这麽简单!用讲的可能大家不相信,Netlify 官方做了一个不到 2 分钟的示范影片➃,展示给大家看什麽叫做拖放布署。
  没问题的话,我们自己也来拖放布署一个网页试看看吧。若是手边没有想要布署的网页,大家可以参考这一份:

  • index.html
<!doctype html>
<html lang="en">
  <head>
    <!-- Required meta tags -->
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">

    <!-- Bootstrap CSS -->
    <link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-+0n0xVW2eSR5OomGNYDnhzAbDsOXxcvSN1TPprVMTNDbiYZCxYbOOl7+AMvyTG2x" crossorigin="anonymous">

    <title>Hello Netlify</title>
  </head>
  <body>
    <div class="container">
      <h1> Hello Netlify </h1>
    </div>
    
    <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js" integrity="sha384-gtEjrD/SeCtmISkJkNUaaKMoLD0//ElJ19smozuHV6z3Iehds+3Ulb9Bn9Plx0x4" crossorigin="anonymous"></script>
  </body>
</html>

  也不是什麽多花俏的网页,其实就是 Bootstrap 提供的起手式样板➄。将这一份 HTML 档放进资料夹中,形成如这样子的档案架构即可:

netlify-drop-demo
└───index.html

  在上面的示范中,我将资料夹命名为netlify-drop-demo,而该资料夹内有档名为index.html的 HTML 档。准备好了之後,就将该资料夹用拖放的方式,拖放到 Netlify ,如图四图五。若没有问题,Netlify 就会直接帮我们布署网站,成功之後页面显示如图六,Netlify 开心的宣布「Your site is live」,并且提供了网址跟预览页面给我们。如此就算完成了拖放布署。

https://ithelp.ithome.com.tw/upload/images/20210605/20120178kumpVv8Akm.png
图四、从个人工作看板 Sites 分页执行拖放布署

https://ithelp.ithome.com.tw/upload/images/20210605/20120178Nm4qJiScT2.png
图五、从 Netlify Drop➃ 执行拖放布署

https://ithelp.ithome.com.tw/upload/images/20210605/20120178D8L3g5Z3up.png
图六、Your site is live ?

重新导向 (Netlify Redirects)

  除了拖放布署这样非常潮又有创意的设计之外,Netlify 还提供了一个有趣而且相当实用的功能:Redirects,为的就是要帮我们解决跨来源资源共用 (CORS) 的问题。相信对前端熟悉的工程师都知道,要在前端向另一个网站或 API 请求资料,必须要处理 CORS 的问题。当前端向另一个存在於不同网域、通讯协定、或是通讯埠的网站请求资源时,必须要遵守跨来源资源共用的权限。简单来说,除非我们的目标网站或 API 有设定允许任何人来请求资源(Access-Control-Allow-Origin: *),否则我们无法从前端向其他网站、API 拿到任何资料。
  要解决这个问题,可能的做法有有两种。第一种,用鼎鼎大名的 CORS Anywhere➆。可惜的是,CORS Anywhere 今年开始改成只提供示范性质的短暂服务,而非可供个人永久使用的服务。第二种,直接了当:既然我没办法从前端向另一个网站、API 发送请求取得资料,那我从後端发送请求总可以了吧?我们只要先架设好一个後端,利用该後端向目标网站、API 发送请求取得资料,再用我们的前端网站向这个後端发送请求就可以了。虽然过了一手,不过该拿的资料还是拿到了。比较麻烦的,是我们得先自己弄出一个後端来才行。

  而 Netlify 的 Redirects 则为我们提供了第三种选项。

  首先,让我们试看看如果不处理 CORS 的问题,直接从前端向其他网域的网站发送请求会发生什麽状况。那麽,重新准备一下我们的index.html,在<body>标签当中多加一行<script>,引入我们自己的 JavaScript 档案,方便後续的可读跟管理。如此,index.html<body>标签内容修改如下:

  • index.html
<body>
  <div class="container">
    <h1>Hello Netlify</h1>
    <h2>Data from time_series_covid19_confirmed_global</h2>
    <textarea class="form-control" id="textarea" rows="15" readonly></textarea>
  </div>

  <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js"
    integrity="sha384-gtEjrD/SeCtmISkJkNUaaKMoLD0//ElJ19smozuHV6z3Iehds+3Ulb9Bn9Plx0x4"
    crossorigin="anonymous"></script>
  <script src="index.js"></script>
</body>

  接着,来撰写我们自己的 JavaScript 档案index.js

  • index.js
const textarea = document.getElementById("textarea");

async function getData() {
  const url = 'https://github.com/CSSEGISandData/COVID-19/blob/master/csse_covid_19_data/csse_covid_19_time_series/time_series_covid19_confirmed_global.csv';
  try {
    const res = await fetch(url);
    const data = await res.text();
    return data;
  } catch (err) {
    return err;
  }
}

async function main() {
  const data = await getData();
  textarea.textContent = data;
}

main();

  在这个范例中,我们试着用 JavaScript ES6 所提供的语法fetch来向网站请求资料,转换成网页原始码,并显示在多行文字输入框 textarea 里面。而我们请求的网址,是约翰霍普金斯大学在 GitHub 上维护的 Covid-19 资料集➇。该资料集当中包含了全球各地每日的确诊人数、死亡人数、以及康复人数。相信对於前端网站小有研究的人,应该马上感觉到,上面这行程序码是完全没办法帮我们抓到资料的。哪边出错了呢?答案很简单:同源政策不允许读取。还不死心想要试看看吗?我们就用拖放布署来看看会发生什麽事吧。用於拖放布署的整个资料夹架构如下所示:

netlify-drop-demo
├───index.html
└───index.js

https://ithelp.ithome.com.tw/upload/images/20210605/20120178Iq7PLZyeHw.png
图七、同源政策不允许读取

  还真的抓不到耶。

  那麽,Netlify 可以帮我们做到什麽呢?
  由 Netlify 提供的 Redirects 功能,可以帮我们扮演 Proxy 的角色,将原本不同源的网址,变成同源网址,这麽一来 CORS 就不是问题了。实际上要怎麽做呢?首先,准备一个档名为_redirects的档案,撰写的规则为:

前端浏览器请求位址 服务器重新导向位址 回应状态码

  包括三个部分:前端浏览器请求位址、服务器重新导向位址、回应状态码,并以空白键间隔开来。

  以我们想要从前端向 Covid-19 资料集发出请求为例,档案内容就写成:

  • _redirects
/covid_19/api https://github.com/CSSEGISandData/COVID-19/blob/master/csse_covid_19_data/csse_covid_19_time_series/time_series_covid19_confirmed_global.csv 200

  第一部分是我们希望的相对路径,可以自己设计。第二部分是重新导向的位址,第三部分则是 HTTP 状态码。以我们刚才写下的_redirects内容为例,当我们从 Netlify 帮忙部署的网站上,由前端向/covid_19/api/这个相对路径发送请求时,Netlify 会自动帮我们导向 Covid 19 资料集存放的位置https://github.com/CSSEGISandData/COVID-19/blob/master/csse_covid_19_data/csse_covid_19_time_series/time_series_covid19_confirmed_global.csv,并在请求完成时,传回代表成功回应的200状态码。注意到了吗,使用_redirects时,我们并没有向位於不同网域的 Covid-19 资料集传送请求,而是向我们自订的具有相同网域的/covid_19/api发送请求。这个时候,当然就没有所谓的 CORS 问题了。让我们实际来试一试,将index.js改成如下:

  • index.js
const textarea = document.getElementById("textarea");

async function getData() {

  // 修改抓取资料的 url 位址
  const url = '/covid_19/api';
  
  try {
    const res = await fetch(url);
    const data = await res.text();
    return data;
  } catch (err) {
    return err;
  }
}

async function main() {
  const data = await getData();
  textarea.textContent = data;
}

main();
  • 第三行:const url = '/covid_19/api';
      唯一要修改的地方就是我们的url。这次不是向 Github 发出请求,而是要向 Netlify 提供的 Proxy /covid_19/api发出请求。

  用於拖放布署的整个资料夹架构如下所示:

netlify-drop-demo
├───index.html
├───index.js
└───_redirects

https://ithelp.ithome.com.tw/upload/images/20210605/20120178Tfs9NpzJ53.png
图八、成功从前端拿到不同网域的 GitHub 资料

  成功从前端拿到资料了!

重新导向:更多用法

  Netlify 提供的 Redirects 果然好用,对吗?如果我们想要重新导向的位址不只一个,而是两个、三个,或甚至十个,那该怎麽做呢?

/browser-request-1  /server-redirect-1  200
/browser-request-2  /server-redirect-2  200
/browser-request-3  /server-redirect-3  200
...

  很简单,有几个就写几个。重新导向的规则想怎麽订,想订几条,都随我们开心。不过,大多数的状况,我们需要用到 Redirects,都是想重新导向到另一个网域的 API。若重新导向的规则要一条一条写下来,不仅是有点麻烦,有时候甚至是不可能。

  你想到的问题,Netlify 也想到了。

  所以在 Redirects 的规则中,还有更具弹性的写法,包括占位符 (placeholder)、查询字串 (query string),都可以用在 Redirects 上➈。

/news/:month/:date/:year/:slug  /blog/:year/:month/:date/:slug  200

  这样写,当使用者从向/news/06/06/2021/ithome发送请求时,Netlify 就会帮忙重新导向到/blog/2021/06/06/ithome这个位址。

/store id=:id  /blog/:id  301

  而上面这种写法,则可以让使用者向/store?id=5566发出的请求重新导向到/blog/5566

  不过最简单易懂,又相当万用的写法,莫过於 splat 了:

/news/*  /blog/:splat  200

  用*:splat,可以让使用者向/news/any/path/you/want发送的请求,重新导向到/blog/any/path/you/want。我们就来试着用 splat 的写法,重新定义一下我们需要的_redirects吧:

  • _redirects
/covid_19/api/* https://github.com/CSSEGISandData/COVID-19/blob/master/csse_covid_19_data/csse_covid_19_time_series/:splat 200

  修改成这样的用意是什麽呢?在 Covid-19 资料集当中,除了我们一开始造访的全球确诊人数统计资料外 (time_series_covid19_confirmed_global),还有全球康复人数、全球死亡人数、以及美国各州的统计资料。因此我们将_redirects用 splat 的功能修改之後,可以透过造访/covid_19/api/time_series_covid19_confirmed_global.csv拿到确诊人数资料,还可以造访/covid_19/api/time_series_covid19_recovered_global.csv拿到康复人数资料,相当万用。

  既然如此,不如就利用 Netlify 提供的 Redirects 功能,画一个台湾近日 Covid-19 确诊/康复人数图表吧:

  • index.html
<!doctype html>
<html lang="en">

<head>
  <!-- Required meta tags -->
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">

  <!-- Bootstrap CSS -->
  <link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet"
    integrity="sha384-+0n0xVW2eSR5OomGNYDnhzAbDsOXxcvSN1TPprVMTNDbiYZCxYbOOl7+AMvyTG2x" crossorigin="anonymous">

  <title>Hello Netlify</title>
</head>

<body>
  <div class="container">
    <h1>Hello Netlify</h1>
    <h2>Data from csse_covid_19_data</h2>
    <canvas id="myChart"></canvas>
  </div>

  <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js"
    integrity="sha384-gtEjrD/SeCtmISkJkNUaaKMoLD0//ElJ19smozuHV6z3Iehds+3Ulb9Bn9Plx0x4"
    crossorigin="anonymous"></script>
  <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
  <script type="module" src="index.js"></script>
</body>

</html>
  • index.js
const ctx = document.getElementById("myChart").getContext("2d");

async function getData(endpoint) {
  const url = `/covid_19/api/${endpoint}.csv`;
  try {
    const res = await fetch(url);
    const data = await res.text();
    return data;
  } catch (err) {
    console.log("can't fetch data...");
  }
}

function getAccumulate(data, setLength) {
  const parser = new DOMParser();
  const doc = parser.parseFromString(data, "text/html");
  const rawCol = doc.querySelector(".blob-code.blob-code-inner.js-file-line").innerText.split(',');
  const rawTaiwan = [...doc.querySelectorAll(".blob-code.blob-code-inner.js-file-line")].filter(x => x.innerHTML.split(',')[1].includes("Taiwan"))[0].innerText.split(',');
  const totalLength = rawCol.length;
  const col = rawCol.splice(totalLength - setLength);
  const taiwan = rawTaiwan.splice(totalLength - setLength);
  return { col, taiwan };
}

function getDaily(accu) {
  const daily = accu.slice(1).map((x, i) => x - accu[i]);
  return daily;
}

function plotChart(col, labels, dailyData, colors) {
  const getBorderColor = (color) => `rgba(${color}, 1)`;
  const getBackgroundColor = (color) => `rgba(${color}, 0.2)`;
  const myChart = new Chart(ctx, {
    type: 'line',
    data: {
      labels: col,
      datasets: dailyData.map((x, i) => ({
        label: labels[i],
        data: x,
        borderColor: getBorderColor(colors[i]),
        backgroundColor: getBackgroundColor(colors[i]),
        tension: 0.4
      })),
    },
    options: {
      scales: {
        y: {
          beginAtZero: true
        }
      }
    }
  });
}

async function main() {
  const endpoints = ["time_series_covid19_confirmed_global", "time_series_covid19_recovered_global"];
  const labels = ["# of Comfirmed", "# of Recovered"];
  const colors = ["255, 99, 132", "54, 162, 235"];
  const data = await Promise.all(endpoints.map(async endpoint => await getData(endpoint)));
  const setLength = 21;
  const accuData = data.map(d => getAccumulate(d, setLength));
  const col = accuData[0].col;
  const dailyData = accuData.map(d => getDaily(d.taiwan));

  plotChart(col, labels, dailyData, colors);
}

main();
  • _redirects
/covid_19/api/* https://github.com/CSSEGISandData/COVID-19/blob/master/csse_covid_19_data/csse_covid_19_time_series/:splat 200

  这边就不特别说明index.js的内容了。大致上来说,是从 Covid-19 资料集上抓取网页原始资料,用 DOMParser 转换成 DOM 文件,从中找出台湾相关资料,并用 Chart.js➉ 画成图表。资料夹中的档案架构如下:

netlify-drop-demo
├───index.html
├───index.js
└───_redirects

  试试看将netlify-drop-demo拖放布署到 Netlify 上面吧!

https://ithelp.ithome.com.tw/upload/images/20210605/201201785ivEB9ELS5.png
图九、近日台湾 Covid-19 相关资料 by Netlify

  今天介绍了 Netlify Drop 跟 Redirects 功能,还顺道快速布署了一堆免费的网站,是不是觉得 Netlify Drop 真厉害呢?但事实上,当网站/应用程序架构增加,开始使用 Node.js Modules 时,选择使用 Netlify Drop 来布署似乎就显得不是很恰当,因为 Netlify Drop 要求的是一个已经完成编译/建构的网站/应用程序。

  你想到的问题,Netlify 也想到了。

  因此下一次的文章内容,将会介绍 Netlify 另外提供的两种布署方式:Netlify Continuous Deployment 跟 Netlify Dev。

参考资料

➀ Netlify 官方
➁ Line Bot on Heroku 快速介绍:《赖田捕手:追加篇》
➂ Line Bot on AWS 快速介绍:《LINE Bot by Python 全攻略》
➃ Netlify Drop 示范影片
➄ Bootstrap 官方
➅ Netlify Drop 示范网站
➆ CORS Anywhere GitHub
➇ 约翰霍普金斯大学 Covid-19 资料集
➈ Netlify Redirects 官方文件
➉ Chart.js 官方


<<:  伸缩自如的Flask [day 22] pythonanywhere 部署

>>:  【红黑树十讲・参】红黑树新增・四大规则介绍・完整图解步骤

Halloween Kills线上看2021

Halloween Kills线上看2021 《月光光心慌慌:杀戮》(英语:Halloween Ki...

Day 7:持续拆解主类别

上一篇漏掉了一个主类别的函数: void anotherInstanceStarted (const...

[Day8] 机器学习进行时间序列预测及注意事项(上)

(资料更新中,会尽快补上缺漏部分) 第八篇我们进到机器学习的范畴。 说到用机器学习模型做时间序列预测...

前端工程师也能开发全端网页:挑战 30 天用 React 加上 Firebase 打造社群网站|Day8 文章主题列表

连续 30 天不中断每天上传一支教学影片,教你如何用 React 加上 Firebase 打造社群...

Day 14:vim-plug

前面讲过了 zsh、tmux 的 plugin manager,vim 一样有 plguin man...